Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cec11aca59 |
@@ -1,6 +1,3 @@
|
||||
As a special exception, the authors give permission to link this program
|
||||
with the OpenSSL library and distribute the resulting binary.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
Win32 build: Make it possible to compile with +debug_info flag.
|
||||
admin tool
|
||||
mod_muc logging
|
||||
|
||||
admin interface
|
||||
users management
|
||||
statistics about each user
|
||||
statistics about each connection
|
||||
node management
|
||||
node restart/shutdown
|
||||
statistics about memory usage
|
||||
|
||||
S2S:
|
||||
rewrite S2S key validation
|
||||
check "id" attributes in db:verify packets
|
||||
|
||||
more correctly work with SRV DNS records (priority, weight, etc...)
|
||||
TLS
|
||||
make roster set to work in one transaction
|
||||
add traffic shapers to c2s connection before authentification
|
||||
more traffic shapers
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
extract_translations - auxiliary tool that extracts lines to be translated
|
||||
from ejabberd source tree.
|
||||
|
||||
Building:
|
||||
erlc extract_translations.erl
|
||||
|
||||
Invoking 1:
|
||||
erl -noinput -s extract_translations -extra dirname message_file
|
||||
|
||||
where dirname is the directory "src" in ejabberd's source tree root,
|
||||
message_file is a file with translated messages (src/msgs/*.msg).
|
||||
|
||||
Result is a list of messages from source files which aren't contained in
|
||||
message file.
|
||||
|
||||
Invoking 2:
|
||||
erl -noinput -s extract_translations -extra -unused dirname message_file
|
||||
|
||||
Result is a list of messages from message file which aren't in source
|
||||
files anymore.
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : extract_translations.erl
|
||||
%%% Author : Sergei Golovan <sgolovan@nes.ru>
|
||||
%%% Purpose : Auxiliary tool for interface/messages translators
|
||||
%%% Created : 23 Apr 2005 by Sergei Golovan <sgolovan@nes.ru>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(extract_translations).
|
||||
-author('sgolovan@nes.ru').
|
||||
|
||||
-export([start/0]).
|
||||
|
||||
-define(STATUS_SUCCESS, 0).
|
||||
-define(STATUS_ERROR, 1).
|
||||
-define(STATUS_USAGE, 2).
|
||||
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
|
||||
start() ->
|
||||
ets:new(translations, [named_table, public]),
|
||||
ets:new(files, [named_table, public]),
|
||||
ets:new(vars, [named_table, public]),
|
||||
case init:get_plain_arguments() of
|
||||
["-unused", Dir, File] ->
|
||||
Status = process(Dir, File, unused),
|
||||
halt(Status);
|
||||
[Dir, File] ->
|
||||
Status = process(Dir, File, used),
|
||||
halt(Status);
|
||||
_ ->
|
||||
print_usage(),
|
||||
halt(?STATUS_USAGE)
|
||||
end.
|
||||
|
||||
|
||||
process(Dir, File, Used) ->
|
||||
case load_file(File) of
|
||||
{error, Reason} ->
|
||||
io:format("~s: ~s~n", [File, file:format_error(Reason)]),
|
||||
?STATUS_ERROR;
|
||||
_ ->
|
||||
FileList = find_src_files(Dir),
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_file(Dir, F, Used)
|
||||
end, FileList),
|
||||
case Used of
|
||||
unused ->
|
||||
ets:foldl(fun({Key, _}, _) ->
|
||||
io:format("~p~n", [Key])
|
||||
end, ok, translations);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
?STATUS_SUCCESS
|
||||
end.
|
||||
|
||||
parse_file(Dir, File, Used) ->
|
||||
ets:delete_all_objects(vars),
|
||||
case epp:parse_file(File, [Dir, filename:dirname(File)], []) of
|
||||
{ok, Forms} ->
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_form(Dir, File, F, Used)
|
||||
end, Forms);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
parse_form(Dir, File, Form, Used) ->
|
||||
case Form of
|
||||
{call,
|
||||
_,
|
||||
{remote, _, {atom, _, translate}, {atom, _, translate}},
|
||||
[_, {string, _, Str}]
|
||||
} ->
|
||||
process_string(Dir, File, Str, Used);
|
||||
{call,
|
||||
_,
|
||||
{remote, _, {atom, _, translate}, {atom, _, translate}},
|
||||
[_, {var, _, Name}]
|
||||
} ->
|
||||
case ets:lookup(vars, Name) of
|
||||
[{_Name, Value}] ->
|
||||
process_string(Dir, File, Value, Used);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
{match,
|
||||
_,
|
||||
{var, _, Name},
|
||||
{string, _, Value}
|
||||
} ->
|
||||
ets:insert(vars, {Name, Value});
|
||||
L when is_list(L) ->
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_form(Dir, File, F, Used)
|
||||
end, L);
|
||||
T when is_tuple(T) ->
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_form(Dir, File, F, Used)
|
||||
end, tuple_to_list(T));
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
process_string(_Dir, File, Str, Used) ->
|
||||
case {ets:lookup(translations, Str), Used} of
|
||||
{[{_Key, _Trans}], unused} ->
|
||||
ets:delete(translations, Str);
|
||||
{[{_Key, _Trans}], used} ->
|
||||
ok;
|
||||
{_, used} ->
|
||||
case ets:lookup(files, File) of
|
||||
[{_}] ->
|
||||
ok;
|
||||
_ ->
|
||||
io:format("~n% ~s~n", [File]),
|
||||
ets:insert(files, {File})
|
||||
end,
|
||||
io:format("{~p, \"\"}.~n", [Str]),
|
||||
ets:insert(translations, {Str, ""});
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
load_file(File) ->
|
||||
case file:consult(File) of
|
||||
{ok, Terms} ->
|
||||
lists:foreach(
|
||||
fun({Orig, Trans}) ->
|
||||
case Trans of
|
||||
"" ->
|
||||
ok;
|
||||
_ ->
|
||||
ets:insert(translations, {Orig, Trans})
|
||||
end
|
||||
end, Terms);
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
find_src_files(Dir) ->
|
||||
case file:list_dir(Dir) of
|
||||
{ok, FileList} ->
|
||||
recurse_filelist(
|
||||
lists:map(
|
||||
fun(F) ->
|
||||
filename:join(Dir, F)
|
||||
end, FileList));
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
recurse_filelist(FileList) ->
|
||||
recurse_filelist(FileList, []).
|
||||
|
||||
recurse_filelist([], Acc) ->
|
||||
lists:reverse(Acc);
|
||||
|
||||
recurse_filelist([H | T], Acc) ->
|
||||
case file:read_file_info(H) of
|
||||
{ok, #file_info{type = directory}} ->
|
||||
recurse_filelist(T, lists:reverse(find_src_files(H)) ++ Acc);
|
||||
{ok, #file_info{type = regular}} ->
|
||||
case string:substr(H, string:len(H) - 3) of
|
||||
".erl" ->
|
||||
recurse_filelist(T, [H | Acc]);
|
||||
".hrl" ->
|
||||
recurse_filelist(T, [H | Acc]);
|
||||
_ ->
|
||||
recurse_filelist(T, Acc)
|
||||
end;
|
||||
_ ->
|
||||
recurse_filelist(T, Acc)
|
||||
end.
|
||||
|
||||
|
||||
print_usage() ->
|
||||
io:format(
|
||||
"Usage: extract_translations [-unused] dir file~n"
|
||||
"~n"
|
||||
"Example:~n"
|
||||
" extract_translations . ./msgs/ru.msg~n"
|
||||
).
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Frontend for ejabberd's extract_translations.erl
|
||||
# by Badlop
|
||||
# last updated: 8 December 2005
|
||||
|
||||
while [ "$1" != "" ]
|
||||
do
|
||||
case "$1" in
|
||||
-help)
|
||||
echo "Options:"
|
||||
echo " -lang LANGUAGE"
|
||||
echo " -src FULL_PATH_EJABBERD"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " ./prepare-translation.sh -lang es -src /home/admin/ejabberd"
|
||||
exit 0
|
||||
;;
|
||||
-lang)
|
||||
# This is the language to be extracted
|
||||
LANGU=$2
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-src)
|
||||
# This is the path to the ejabberd source dir
|
||||
EJA_DIR=$2
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "unknown option: '$1 $2'"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Where is Erlang binary
|
||||
ERL=`which erl`
|
||||
|
||||
EXTRACT_DIR=$EJA_DIR/contrib/extract_translations/
|
||||
EXTRACT_ERL=extract_translations.erl
|
||||
EXTRACT_BEAM=extract_translations.beam
|
||||
SRC_DIR=$EJA_DIR/src
|
||||
MSGS_DIR=$SRC_DIR/msgs
|
||||
MSGS_FILE=$LANGU.msg
|
||||
MSGS_FILE2=$LANGU.msg.translate
|
||||
MSGS_PATH=$MSGS_DIR/$MSGS_FILE
|
||||
MSGS_PATH2=$MSGS_DIR/$MSGS_FILE2
|
||||
|
||||
if !([[ -n $EJA_DIR ]])
|
||||
then
|
||||
echo "ejabberd dir does not exist: $EJA_DIR"
|
||||
fi
|
||||
|
||||
if !([[ -x $EXTRACT_BEAM ]])
|
||||
then
|
||||
echo -n "Compiling extract_translations.erl: "
|
||||
sh -c "cd $EXTRACT_DIR; $ERL -compile $EXTRACT_ERL"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -n "Extracting language strings for '$LANGU':"
|
||||
|
||||
echo -n " new..."
|
||||
$ERL -pa $EXTRACT_DIR -noinput -noshell -s extract_translations -s init stop -extra $SRC_DIR $MSGS_PATH >$MSGS_PATH.new
|
||||
|
||||
echo -n " old..."
|
||||
$ERL -pa $EXTRACT_DIR -noinput -noshell -s extract_translations -s init stop -extra -unused $SRC_DIR $MSGS_PATH >$MSGS_PATH.unused
|
||||
|
||||
cat $MSGS_PATH >$MSGS_PATH2
|
||||
|
||||
echo "" >>$MSGS_PATH2
|
||||
|
||||
cat $MSGS_PATH.new >>$MSGS_PATH2
|
||||
rm $MSGS_PATH.new
|
||||
echo "" >>$MSGS_PATH2
|
||||
|
||||
cat $MSGS_PATH.unused >>$MSGS_PATH2
|
||||
rm $MSGS_PATH.unused
|
||||
|
||||
echo " ok"
|
||||
|
||||
echo ""
|
||||
echo "Process completed."
|
||||
|
||||
echo ""
|
||||
echo " A new file has been created for you, with the current, the new and the deprecated strings:"
|
||||
echo " $MSGS_PATH2"
|
||||
echo ""
|
||||
echo " At the end of that file you will find the strings you must update:"
|
||||
echo " - Untranslated strings are like this: {"March", ""}."
|
||||
echo " To translate the string, add the text inside the commas. Example:"
|
||||
echo " {"March", "Marzo"}."
|
||||
echo " - Old strings that are not used: "Woowoa""
|
||||
echo " Search the entire file for those strings and remove them"
|
||||
echo ""
|
||||
echo " Once you have translated all the strings and removed all the old ones,"
|
||||
echo " rename the file to overwrite the previous one:"
|
||||
echo " $MSGS_PATH"
|
||||
@@ -1,46 +0,0 @@
|
||||
# $Id$
|
||||
|
||||
all: release pdf html
|
||||
|
||||
release:
|
||||
@echo "Notes for the releaser:"
|
||||
@echo "* Do not forget to add a link to the release notes in guide.tex"
|
||||
@echo "* Do not forget to change the version number in version.tex"
|
||||
@echo "* Do not forget to update the features in introduction.tex (including \new{} and \improved{} tags)."
|
||||
@read -p "press any key to continue"
|
||||
|
||||
html: guide.html dev.html features.html
|
||||
|
||||
pdf: guide.pdf features.pdf
|
||||
|
||||
clean:
|
||||
rm -f *.aux
|
||||
rm -f *.haux
|
||||
rm -f *.html
|
||||
rm -f *.htoc
|
||||
rm -f *.idx
|
||||
rm -f *.ilg
|
||||
rm -f *.ind
|
||||
rm -f *.log
|
||||
rm -f *.out
|
||||
rm -f *.pdf
|
||||
rm -f *.toc
|
||||
|
||||
guide.html: guide.tex
|
||||
hevea -fix -noiso -pedantic guide.tex
|
||||
|
||||
dev.html: dev.tex
|
||||
hevea -fix -noiso -pedantic dev.tex
|
||||
|
||||
features.html: features.tex
|
||||
hevea -fix -noiso -pedantic features.tex
|
||||
|
||||
guide.pdf: guide.tex
|
||||
pdflatex guide.tex
|
||||
pdflatex guide.tex
|
||||
pdflatex guide.tex
|
||||
makeindex guide.idx
|
||||
pdflatex guide.tex
|
||||
|
||||
features.pdf: features.tex
|
||||
pdflatex features.tex
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
@@ -1,184 +1,97 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD><TITLE>Ejabberd Developers Guide</TITLE>
|
||||
|
||||
<HEAD>
|
||||
|
||||
<TITLE>Ejabberd 1.1.2 Developers Guide</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<META name="GENERATOR" content="hevea 1.08">
|
||||
<STYLE type="text/css">
|
||||
.toc{list-style:none;}
|
||||
.title{margin:auto;text-align:center}
|
||||
.center{text-align:center;margin-left:auto;margin-right:auto;}
|
||||
.flushleft{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
.flushright{text-align:right;margin-left:auto;margin-right:0ex;}
|
||||
DIV TABLE{margin-left:inherit;margin-right:inherit;}
|
||||
PRE{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
BLOCKQUOTE{margin-left:4ex;margin-right:4ex;text-align:left;}
|
||||
.part{margin:auto;text-align:center}
|
||||
</STYLE>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=ISO8859-1">
|
||||
<META name="GENERATOR" content="hevea 1.06">
|
||||
</HEAD>
|
||||
|
||||
<BODY >
|
||||
<!--HEVEA command line is: hevea -fix -noiso -pedantic dev.tex -->
|
||||
<!--HEVEA command line is: /usr/bin/hevea -charset ISO8859-1 dev.tex -->
|
||||
<!--HTMLHEAD-->
|
||||
<!--ENDHTML-->
|
||||
<!--PREFIX <ARG ></ARG>-->
|
||||
<!--CUT DEF section 1 -->
|
||||
|
||||
<BR>
|
||||
<BR>
|
||||
<A NAME="sec:titlepage"></A>
|
||||
|
||||
<TABLE CLASS="title">
|
||||
<TR><TD>
|
||||
<H1 CLASS="titlemain">Ejabberd 1.1.2 Developers Guide</H1>
|
||||
<H3 CLASS="titlerest">Alexey Shchepin<BR>
|
||||
|
||||
<H1 ALIGN=center>Ejabberd Developers Guide</H1>
|
||||
|
||||
<H3 ALIGN=center>Alexey Shchepin<BR>
|
||||
<A HREF="mailto:alexey@sevcom.net"><TT>mailto:alexey@sevcom.net</TT></A><BR>
|
||||
<A HREF="xmpp:aleksey@jabber.ru"><TT>xmpp:aleksey@jabber.ru</TT></A></H3></TD>
|
||||
</TR></TABLE><BR>
|
||||
<BR>
|
||||
<DIV CLASS="center">
|
||||
|
||||
<IMG SRC="logo.png" ALT="logo.png">
|
||||
<A HREF="xmpp:aleksey@jabber.ru"><TT>xmpp:aleksey@jabber.ru</TT></A></H3>
|
||||
|
||||
<H3 ALIGN=center>September 10, 2003</H3><DIV ALIGN=center>
|
||||
|
||||
<IMG SRC="logo.png">
|
||||
|
||||
|
||||
<BR>
|
||||
</DIV><BR>
|
||||
<BR>
|
||||
|
||||
</DIV>
|
||||
<BLOCKQUOTE CLASS="quotation"><I>I can thoroughly recommend ejabberd for ease of setup –
|
||||
Kevin Smith, Current maintainer of the Psi project</I></BLOCKQUOTE>
|
||||
<!--TOC section Contents-->
|
||||
|
||||
<H2 CLASS="section">Contents</H2><!--SEC END -->
|
||||
<!--TOC section Table of Contents-->
|
||||
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<H2>Table of Contents</H2><!--SEC END -->
|
||||
|
||||
<UL><LI>
|
||||
<A HREF="#htoc1">1 Introduction</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc2">1.1 Key Features</A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc3">1.2 Additional Features</A>
|
||||
<UL><LI>
|
||||
<A HREF="#htoc2">1.1 How it works</A>
|
||||
<UL><LI>
|
||||
<A HREF="#htoc3">1.1.1 Router</A>
|
||||
<LI><A HREF="#htoc4">1.1.2 Local Router</A>
|
||||
<LI><A HREF="#htoc5">1.1.3 Session Manager</A>
|
||||
<LI><A HREF="#htoc6">1.1.4 S2S Manager</A>
|
||||
</UL>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc4">2 How it Works</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc5">2.1 Router</A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc6">2.2 Local Router</A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc7">2.3 Session Manager</A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc8">2.4 S2S Manager</A>
|
||||
</UL>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc9">3 XML Representation</A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc10">4 Module <TT>xml</TT></A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc11">5 Module <TT>xml_stream</TT></A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc12">6 Modules</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc13">6.1 Module gen_iq_handler</A>
|
||||
<LI CLASS="li-toc"><A HREF="#htoc14">6.2 Services</A>
|
||||
<LI><A HREF="#htoc7">2 XML representation</A>
|
||||
<LI><A HREF="#htoc8">3 Module <TT>xml</TT></A>
|
||||
<LI><A HREF="#htoc9">4 <TT>ejabberd</TT> modules</A>
|
||||
<UL><LI>
|
||||
<A HREF="#htoc10">4.1 <CODE>gen_mod</CODE> behaviour</A>
|
||||
<LI><A HREF="#htoc11">4.2 Module <CODE>gen_iq_handler</CODE></A>
|
||||
<LI><A HREF="#htoc12">4.3 Services</A>
|
||||
</UL>
|
||||
</UL>
|
||||
|
||||
<!--TOC section Introduction-->
|
||||
|
||||
<H2 CLASS="section"><A NAME="htoc1">1</A> <A NAME="intro">Introduction</A></H2><!--SEC END -->
|
||||
<H2><A NAME="htoc1">1</A> Introduction</H2><!--SEC END -->
|
||||
|
||||
<A NAME="sec:intro"></A>
|
||||
|
||||
<TT>ejabberd</TT> is a free and open source instant messaging server written in <A HREF="http://www.erlang.org/">Erlang</A>.<BR>
|
||||
<TT>ejabberd</TT> is a Free and Open Source fault-tolerant distributed Jabber
|
||||
server. It is writen mostly in Erlang.<BR>
|
||||
<BR>
|
||||
<TT>ejabberd</TT> is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication.<BR>
|
||||
<BR>
|
||||
<TT>ejabberd</TT> is designed to be a rock-solid and feature rich XMPP server.<BR>
|
||||
<BR>
|
||||
<TT>ejabberd</TT> is suitable for small deployments, whether they need to be scalable or not, as well as extremely big deployments.<BR>
|
||||
<BR>
|
||||
<!--TOC subsection Key Features-->
|
||||
|
||||
<H3 CLASS="subsection"><A NAME="htoc2">1.1</A> <A NAME="keyfeatures">Key Features</A></H3><!--SEC END -->
|
||||
|
||||
<A NAME="sec:keyfeatures"></A>
|
||||
|
||||
<TT>ejabberd</TT> is:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Cross-platform: <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.<BR>
|
||||
<BR>
|
||||
<LI CLASS="li-itemize">Distributed: You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.<BR>
|
||||
<BR>
|
||||
<LI CLASS="li-itemize">Fault-tolerant: You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced `on the fly'.<BR>
|
||||
<BR>
|
||||
<LI CLASS="li-itemize">Administrator Friendly: <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Comprehensive documentation.
|
||||
<LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows.
|
||||
<LI CLASS="li-itemize">Web interface for administration tasks.
|
||||
<LI CLASS="li-itemize">Shared Roster Groups.
|
||||
<LI CLASS="li-itemize">Command line administration tool.
|
||||
<LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
|
||||
<LI CLASS="li-itemize">Capability to send announce messages.
|
||||
</UL><BR>
|
||||
<BR>
|
||||
<LI CLASS="li-itemize">Internationalized: <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Translated in 12 languages.
|
||||
<LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
|
||||
</UL><BR>
|
||||
<BR>
|
||||
<LI CLASS="li-itemize">Open Standards: <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Fully XMPP compliant.
|
||||
<LI CLASS="li-itemize">XML-based protocol.
|
||||
<LI CLASS="li-itemize"><A HREF="http://ejabberd.jabber.ru/protocols">Many JEPs supported</A>.
|
||||
</UL></UL>
|
||||
<!--TOC subsection Additional Features-->
|
||||
|
||||
<H3 CLASS="subsection"><A NAME="htoc3">1.2</A> <A NAME="addfeatures">Additional Features</A></H3><!--SEC END -->
|
||||
|
||||
<A NAME="sec:addfeatures"></A>
|
||||
|
||||
Moreover, <TT>ejabberd</TT> comes with a wide range of other state-of-the-art features:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Modular
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Load only the modules you want.
|
||||
<LI CLASS="li-itemize">Extend <TT>ejabberd</TT> with your own custom modules.
|
||||
The main features of <TT>ejabberd</TT> is:
|
||||
<UL><LI>
|
||||
Works on most of popular platforms: *nix (tested on Linux and FreeBSD)
|
||||
and Win32
|
||||
<LI>Distributed: You can run <TT>ejabberd</TT> on a cluster of machines and all of
|
||||
them will serve one Jabber domain.
|
||||
<LI>Fault-tolerance: You can setup an <TT>ejabberd</TT> cluster so that all the
|
||||
information required for a properly working service will be stored
|
||||
permanently on more than one node. This means that if one of the nodes
|
||||
crashes, then the others will continue working without disruption.
|
||||
You can also add or replace more nodes ``on the fly''.
|
||||
<LI>Built-in <A HREF="http://www.jabber.org/jeps/jep-0045.html">Multi-User
|
||||
Chat</A> service
|
||||
<LI>Built-in IRC transport
|
||||
<LI>Built-in
|
||||
<A HREF="http://www.jabber.org/jeps/jep-0060.html">Publish-Subscribe</A>
|
||||
service
|
||||
<LI>Built-in Jabber Users Directory service based on users vCards
|
||||
<LI>Support for
|
||||
<A HREF="http://www.jabber.org/jeps/jep-0030.html">JEP-0030</A>
|
||||
(Service Discovery).
|
||||
<LI>Support for
|
||||
<A HREF="http://www.jabber.org/jeps/jep-0039.html">JEP-0039</A>
|
||||
(Statistics Gathering).
|
||||
<LI>Support for <TT>xml:lang</TT> attribute in many XML elements
|
||||
</UL>
|
||||
<LI CLASS="li-itemize">Security
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
SASL and STARTTLS for c2s and s2s connections.
|
||||
<LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
|
||||
<LI CLASS="li-itemize">Web interface accessible via HTTPS secure access.
|
||||
</UL>
|
||||
<LI CLASS="li-itemize">Databases
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Native MySQL support.
|
||||
<LI CLASS="li-itemize">Native PostgreSQL support.
|
||||
<LI CLASS="li-itemize">Mnesia.
|
||||
<LI CLASS="li-itemize">ODBC data storage support.
|
||||
<LI CLASS="li-itemize">Microsoft SQL Server support.
|
||||
</UL>
|
||||
<LI CLASS="li-itemize">Authentication
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
LDAP and ODBC.
|
||||
<LI CLASS="li-itemize">External Authentication script.
|
||||
<LI CLASS="li-itemize">Internal Authentication.
|
||||
</UL>
|
||||
<LI CLASS="li-itemize">Others
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Compressing XML streams with Stream Compression (<A HREF="http://www.jabber.org/jeps/jep-0138.html">JEP-0138</A>).
|
||||
<LI CLASS="li-itemize">Interface with networks such as AIM, ICQ and MSN.
|
||||
<LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.jabber.org/jeps/jep-0039.html">JEP-0039</A>).
|
||||
<LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
|
||||
<LI CLASS="li-itemize"><A HREF="http://www.jabber.org/jeps/jep-0045.html">Multi-User Chat</A> module with logging.
|
||||
<LI CLASS="li-itemize">Users Directory based on users vCards.
|
||||
<LI CLASS="li-itemize"><A HREF="http://www.jabber.org/jeps/jep-0060.html">Publish-Subscribe</A> component.
|
||||
<LI CLASS="li-itemize">Support for virtual hosting.
|
||||
<LI CLASS="li-itemize"><A HREF="http://www.jabber.org/jeps/jep-0025.html">HTTP Polling</A> service.
|
||||
<LI CLASS="li-itemize">IRC transport.
|
||||
</UL>
|
||||
</UL>
|
||||
<!--TOC section How it Works-->
|
||||
<!--TOC subsection How it works-->
|
||||
|
||||
<H2 CLASS="section"><A NAME="htoc4">2</A> How it Works</H2><!--SEC END -->
|
||||
<H3><A NAME="htoc2">1.1</A> How it works</H3><!--SEC END -->
|
||||
|
||||
<A NAME="sec:howitworks"></A>
|
||||
A Jabber domain is served by one or more <TT>ejabberd</TT> nodes. These nodes can
|
||||
@@ -190,15 +103,15 @@ needed because all nodes exchange information about connected users, S2S
|
||||
connections, registered services, etc...<BR>
|
||||
<BR>
|
||||
Each <TT>ejabberd</TT> node have following modules:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
<UL><LI>
|
||||
router;
|
||||
<LI CLASS="li-itemize">local router.
|
||||
<LI CLASS="li-itemize">session manager;
|
||||
<LI CLASS="li-itemize">S2S manager;
|
||||
<LI>local router.
|
||||
<LI>session manager;
|
||||
<LI>S2S manager;
|
||||
</UL>
|
||||
<!--TOC subsection Router-->
|
||||
<!--TOC subsubsection Router-->
|
||||
|
||||
<H3 CLASS="subsection"><A NAME="htoc5">2.1</A> Router</H3><!--SEC END -->
|
||||
<H4><A NAME="htoc3">1.1.1</A> Router</H4><!--SEC END -->
|
||||
|
||||
This module is the main router of Jabber packets on each node. It routes
|
||||
them based on their destinations domains. It has two tables: local and global
|
||||
@@ -208,26 +121,26 @@ searches in global table, and is routed to the appropriate <TT>ejabberd</TT> nod
|
||||
process. If it does not exists in either tables, then it sent to the S2S
|
||||
manager.<BR>
|
||||
<BR>
|
||||
<!--TOC subsection Local Router-->
|
||||
<!--TOC subsubsection Local Router-->
|
||||
|
||||
<H3 CLASS="subsection"><A NAME="htoc6">2.2</A> Local Router</H3><!--SEC END -->
|
||||
<H4><A NAME="htoc4">1.1.2</A> Local Router</H4><!--SEC END -->
|
||||
|
||||
This module routes packets which have a destination domain equal to this server
|
||||
name. If destination JID has a non-empty user part, then it routed to the
|
||||
session manager, else it is processed depending on it's content.<BR>
|
||||
<BR>
|
||||
<!--TOC subsection Session Manager-->
|
||||
<!--TOC subsubsection Session Manager-->
|
||||
|
||||
<H3 CLASS="subsection"><A NAME="htoc7">2.3</A> Session Manager</H3><!--SEC END -->
|
||||
<H4><A NAME="htoc5">1.1.3</A> Session Manager</H4><!--SEC END -->
|
||||
|
||||
This module routes packets to local users. It searches for what user resource
|
||||
packet must be sended via presence table. If this resource is connected to
|
||||
this node, it is routed to C2S process, if it connected via another node, then
|
||||
the packet is sent to session manager on that node.<BR>
|
||||
<BR>
|
||||
<!--TOC subsection S2S Manager-->
|
||||
<!--TOC subsubsection S2S Manager-->
|
||||
|
||||
<H3 CLASS="subsection"><A NAME="htoc8">2.4</A> S2S Manager</H3><!--SEC END -->
|
||||
<H4><A NAME="htoc6">1.1.4</A> S2S Manager</H4><!--SEC END -->
|
||||
|
||||
This module routes packets to other Jabber servers. First, it checks if an
|
||||
open S2S connection from the domain of the packet source to the domain of
|
||||
@@ -236,13 +149,13 @@ routes the packet to S2S manager on that node, if it is open on this node, then
|
||||
it is routed to the process that serves this connection, and if a connection
|
||||
does not exist, then it is opened and registered.<BR>
|
||||
<BR>
|
||||
<!--TOC section XML Representation-->
|
||||
<!--TOC section XML representation-->
|
||||
|
||||
<H2 CLASS="section"><A NAME="htoc9">3</A> XML Representation</H2><!--SEC END -->
|
||||
<H2><A NAME="htoc7">2</A> XML representation</H2><!--SEC END -->
|
||||
|
||||
<A NAME="sec:xmlrepr"></A>
|
||||
Each XML stanza is represented as the following tuple:
|
||||
<PRE CLASS="verbatim">
|
||||
Each XML stanza represented as following tuple:
|
||||
<PRE>
|
||||
XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
|
||||
Name = string()
|
||||
Attrs = [Attr]
|
||||
@@ -252,14 +165,14 @@ XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
|
||||
ElementOrCDATA = XMLElement | CDATA
|
||||
CDATA = {xmlcdata, string()}
|
||||
</PRE>E. g. this stanza:
|
||||
<PRE CLASS="verbatim">
|
||||
<message to='test@conference.example.org' type='groupchat'>
|
||||
<PRE>
|
||||
<message to='test@conference.e.localhost' type='groupchat'>
|
||||
<body>test</body>
|
||||
</message>
|
||||
</PRE>is represented as the following structure:
|
||||
<PRE CLASS="verbatim">
|
||||
</PRE>represented as following structure:
|
||||
<PRE>
|
||||
{xmlelement, "message",
|
||||
[{"to", "test@conference.example.org"},
|
||||
[{"to", "test@conference.e.localhost"},
|
||||
{"type", "groupchat"}],
|
||||
[{xmlelement, "body",
|
||||
[],
|
||||
@@ -267,28 +180,29 @@ XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
|
||||
</PRE>
|
||||
<!--TOC section Module <TT>xml</TT>-->
|
||||
|
||||
<H2 CLASS="section"><A NAME="htoc10">4</A> Module <TT>xml</TT></H2><!--SEC END -->
|
||||
<H2><A NAME="htoc8">3</A> Module <TT>xml</TT></H2><!--SEC END -->
|
||||
|
||||
<A NAME="sec:xmlmod"></A>
|
||||
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>element_to_string(El) -> string()</CODE>
|
||||
<PRE CLASS="verbatim">
|
||||
<DL COMPACT=compact><DT>
|
||||
<CODE><B>element_to_string(El) -> string()</B></CODE><DD>
|
||||
<PRE>
|
||||
El = XMLElement
|
||||
</PRE>Returns string representation of XML stanza <TT>El</TT>.<BR>
|
||||
<BR>
|
||||
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>crypt(S) -> string()</CODE>
|
||||
<PRE CLASS="verbatim">
|
||||
<DT><CODE><B>crypt(S) -> string()</B></CODE><DD>
|
||||
<PRE>
|
||||
S = string()
|
||||
</PRE>Returns string which correspond to <TT>S</TT> with encoded XML special
|
||||
characters.<BR>
|
||||
<BR>
|
||||
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>remove_cdata(ECList) -> EList</CODE>
|
||||
<PRE CLASS="verbatim">
|
||||
<DT><CODE><B>remove_cdata(ECList) -> EList</B></CODE><DD>
|
||||
<PRE>
|
||||
ECList = [ElementOrCDATA]
|
||||
EList = [XMLElement]
|
||||
</PRE><TT>EList</TT> is a list of all non-CDATA elements of ECList.<BR>
|
||||
<BR>
|
||||
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>get_path_s(El, Path) -> Res</CODE>
|
||||
<PRE CLASS="verbatim">
|
||||
<DT><CODE><B>get_path_s(El, Path) -> Res</B></CODE><DD>
|
||||
<PRE>
|
||||
El = XMLElement
|
||||
Path = [PathItem]
|
||||
PathItem = PathElem | PathAttr | PathCDATA
|
||||
@@ -299,78 +213,75 @@ Name = string()
|
||||
Res = string() | XMLElement
|
||||
</PRE>If <TT>Path</TT> is empty, then returns <TT>El</TT>. Else sequentially
|
||||
consider elements of <TT>Path</TT>. Each element is one of:
|
||||
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>{elem, Name}</CODE> <TT>Name</TT> is name of subelement of
|
||||
<DL COMPACT=compact><DT>
|
||||
<CODE><B>{elem, Name}</B></CODE><DD> <TT>Name</TT> is name of subelement of
|
||||
<TT>El</TT>, if such element exists, then this element considered in
|
||||
following steps, else returns empty string.
|
||||
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>{attr, Name}</CODE> If <TT>El</TT> have attribute <TT>Name</TT>, then
|
||||
<DT><CODE><B>{attr, Name}</B></CODE><DD> If <TT>El</TT> have attribute <TT>Name</TT>, then
|
||||
returns value of this attribute, else returns empty string.
|
||||
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>cdata</CODE> Returns CDATA of <TT>El</TT>.
|
||||
<DT><CODE><B>cdata</B></CODE><DD> Returns CDATA of <TT>El</TT>.
|
||||
</DL><BR>
|
||||
<BR>
|
||||
<DT CLASS="dt-description"><DD CLASS="dd-description">TODO:
|
||||
<PRE CLASS="verbatim">
|
||||
<DT><B>TODO:</B><DD>
|
||||
<PRE>
|
||||
get_cdata/1, get_tag_cdata/1
|
||||
get_attr/2, get_attr_s/2
|
||||
get_tag_attr/2, get_tag_attr_s/2
|
||||
get_subtag/2
|
||||
</PRE></DL>
|
||||
<!--TOC section Module <TT>xml_stream</TT>-->
|
||||
<!--TOC section <TT>ejabberd</TT> modules-->
|
||||
|
||||
<H2 CLASS="section"><A NAME="htoc11">5</A> Module <TT>xml_stream</TT></H2><!--SEC END -->
|
||||
|
||||
<A NAME="sec:xmlstreammod"></A>
|
||||
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>parse_element(Str) -> XMLElement | {error, Err}</CODE>
|
||||
<PRE CLASS="verbatim">
|
||||
Str = string()
|
||||
Err = term()
|
||||
</PRE>Parses <TT>Str</TT> using XML parser, returns either parsed element or error
|
||||
tuple.
|
||||
</DL>
|
||||
<!--TOC section Modules-->
|
||||
|
||||
<H2 CLASS="section"><A NAME="htoc12">6</A> Modules</H2><!--SEC END -->
|
||||
<H2><A NAME="htoc9">4</A> <TT>ejabberd</TT> modules</H2><!--SEC END -->
|
||||
|
||||
<A NAME="sec:emods"></A>
|
||||
<!--TOC subsection Module gen_iq_handler-->
|
||||
<!--TOC subsection <CODE>gen_mod</CODE> behaviour-->
|
||||
|
||||
<H3 CLASS="subsection"><A NAME="htoc13">6.1</A> Module gen_iq_handler</H3><!--SEC END -->
|
||||
<H3><A NAME="htoc10">4.1</A> <CODE>gen_mod</CODE> behaviour</H3><!--SEC END -->
|
||||
|
||||
<A NAME="sec:genmod"></A>
|
||||
TBD<BR>
|
||||
<BR>
|
||||
<!--TOC subsection Module <CODE>gen_iq_handler</CODE>-->
|
||||
|
||||
<H3><A NAME="htoc11">4.2</A> Module <CODE>gen_iq_handler</CODE></H3><!--SEC END -->
|
||||
|
||||
<A NAME="sec:geniqhandl"></A>
|
||||
The module <CODE>gen_iq_handler</CODE> allows to easily write handlers for IQ packets
|
||||
of particular XML namespaces that addressed to server or to users bare JIDs.<BR>
|
||||
<BR>
|
||||
In this module the following functions are defined:
|
||||
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>add_iq_handler(Component, Host, NS, Module, Function, Type)</CODE>
|
||||
<PRE CLASS="verbatim">
|
||||
<DL COMPACT=compact><DT>
|
||||
<CODE><B>add_iq_handler(Component, NS, Module, Function, Type)</B></CODE><DD>
|
||||
<PRE>
|
||||
Component = Module = Function = atom()
|
||||
Host = NS = string()
|
||||
NS = string()
|
||||
Type = no_queue | one_queue | parallel
|
||||
</PRE>Registers function <CODE>Module:Function</CODE> as handler for IQ packets on
|
||||
virtual host <CODE>Host</CODE> that contain child of namespace <CODE>NS</CODE> in
|
||||
<CODE>Component</CODE>. Queueing discipline is <CODE>Type</CODE>. There are at least
|
||||
two components defined:
|
||||
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>ejabberd_local</CODE> Handles packets that addressed to server JID;
|
||||
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>ejabberd_sm</CODE> Handles packets that addressed to users bare JIDs.
|
||||
</PRE>Registers function <CODE>Module:Function</CODE> as handler for IQ packets that
|
||||
contain child of namespace <CODE>NS</CODE> in <CODE>Component</CODE>. Queueing
|
||||
discipline is <CODE>Type</CODE>. There are at least two components defined:
|
||||
<DL COMPACT=compact><DT>
|
||||
<CODE><B>ejabberd_local</B></CODE><DD> Handles packets that addressed to server JID;
|
||||
<DT><CODE><B>ejabberd_sm</B></CODE><DD> Handles packets that addressed to users bare JIDs.
|
||||
</DL>
|
||||
<DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>remove_iq_handler(Component, Host, NS)</CODE>
|
||||
<PRE CLASS="verbatim">
|
||||
<DT><CODE><B>remove_iq_handler(Component, NS)</B></CODE><DD>
|
||||
<PRE>
|
||||
Component = atom()
|
||||
Host = NS = string()
|
||||
</PRE>Removes IQ handler on virtual host <CODE>Host</CODE> for namespace <CODE>NS</CODE> from
|
||||
<CODE>Component</CODE>.
|
||||
NS = string()
|
||||
</PRE>Removes IQ handler for namespace <CODE>NS</CODE> from <CODE>Component</CODE>.
|
||||
</DL>
|
||||
Handler function must have the following type:
|
||||
<DL CLASS="description" COMPACT=compact><DT CLASS="dt-description"><DD CLASS="dd-description"><CODE>Module:Function(From, To, IQ)</CODE>
|
||||
<PRE CLASS="verbatim">
|
||||
<DL COMPACT=compact><DT>
|
||||
<CODE><B>Module:Function(From, To, IQ)</B></CODE><DD>
|
||||
<PRE>
|
||||
From = To = jid()
|
||||
</PRE></DL>
|
||||
<PRE CLASS="verbatim">
|
||||
<PRE>
|
||||
-module(mod_cputime).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
process_local_iq/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -378,13 +289,13 @@ From = To = jid()
|
||||
|
||||
-define(NS_CPUTIME, "ejabberd:cputime").
|
||||
|
||||
start(Host, Opts) ->
|
||||
start(Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_CPUTIME,
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, ?NS_CPUTIME,
|
||||
?MODULE, process_local_iq, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_CPUTIME).
|
||||
stop() ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, ?NS_CPUTIME).
|
||||
|
||||
process_local_iq(From, To, {iq, ID, Type, XMLNS, SubEl}) ->
|
||||
case Type of
|
||||
@@ -402,23 +313,25 @@ process_local_iq(From, To, {iq, ID, Type, XMLNS, SubEl}) ->
|
||||
</PRE>
|
||||
<!--TOC subsection Services-->
|
||||
|
||||
<H3 CLASS="subsection"><A NAME="htoc14">6.2</A> Services</H3><!--SEC END -->
|
||||
<H3><A NAME="htoc12">4.3</A> Services</H3><!--SEC END -->
|
||||
|
||||
<A NAME="sec:services"></A>
|
||||
<PRE CLASS="verbatim">
|
||||
TBD<BR>
|
||||
<BR>
|
||||
TODO: use <CODE>proc_lib</CODE>
|
||||
<PRE>
|
||||
-module(mod_echo).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, init/1, stop/1]).
|
||||
-export([start/1, init/1, stop/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
MyHost = gen_mod:get_opt(host, Opts, "echo." ++ Host),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, init, [MyHost])).
|
||||
start(Opts) ->
|
||||
Host = gen_mod:get_opt(host, Opts, "echo." ++ ?MYNAME),
|
||||
register(ejabberd_mod_echo, spawn(?MODULE, init, [Host])).
|
||||
|
||||
init(Host) ->
|
||||
ejabberd_router:register_local_route(Host),
|
||||
@@ -430,20 +343,22 @@ loop(Host) ->
|
||||
ejabberd_router:route(To, From, Packet),
|
||||
loop(Host);
|
||||
stop ->
|
||||
ejabberd_router:unregister_route(Host),
|
||||
ejabberd_router:unregister_local_route(Host),
|
||||
ok;
|
||||
_ ->
|
||||
loop(Host)
|
||||
end.
|
||||
|
||||
stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
Proc ! stop,
|
||||
{wait, Proc}.
|
||||
stop() ->
|
||||
ejabberd_mod_echo ! stop,
|
||||
ok.
|
||||
</PRE>
|
||||
<!--HTMLFOOT-->
|
||||
<!--ENDHTML-->
|
||||
<!--FOOTER-->
|
||||
<HR SIZE=2><BLOCKQUOTE CLASS="quote"><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
|
||||
</EM><A HREF="http://pauillac.inria.fr/~maranget/hevea/index.html"><EM>H<FONT SIZE=2><sup>E</sup></FONT>V<FONT SIZE=2><sup>E</sup></FONT>A</EM></A><EM>.</EM></BLOCKQUOTE></BODY>
|
||||
<HR SIZE=2>
|
||||
<BLOCKQUOTE><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
|
||||
</EM><A HREF="http://pauillac.inria.fr/~maranget/hevea/index.html"><EM>H<FONT SIZE=2><sup>E</sup></FONT>V<FONT SIZE=2><sup>E</sup></FONT>A</EM></A><EM>.
|
||||
</EM></BLOCKQUOTE>
|
||||
</BODY>
|
||||
</HTML>
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
\documentclass[a4paper,10pt]{article}
|
||||
\documentclass[10pt]{article}
|
||||
|
||||
%% Packages
|
||||
\usepackage{graphics}
|
||||
\usepackage{hevea}
|
||||
\usepackage{makeidx}
|
||||
\usepackage{verbatim}
|
||||
|
||||
%% Index
|
||||
\makeindex
|
||||
% Remove the index anchors from the HTML version to save size and bandwith.
|
||||
\newcommand{\ind}[1]{\begin{latexonly}\index{#1}\end{latexonly}}
|
||||
|
||||
%% Images
|
||||
\newcommand{\logoscale}{0.7}
|
||||
\newcommand{\imgscale}{0.58}
|
||||
\newcommand{\insimg}[1]{\insscaleimg{\imgscale}{#1}}
|
||||
|
||||
\newcommand{\insscaleimg}[2]{
|
||||
\imgsrc{#2}{}
|
||||
\begin{latexonly}
|
||||
@@ -22,84 +16,86 @@
|
||||
\end{latexonly}
|
||||
}
|
||||
|
||||
%% Various
|
||||
\newcommand{\ns}[1]{\texttt{#1}}
|
||||
\newcommand{\ejabberd}{\texttt{ejabberd}}
|
||||
\newcommand{\Jabber}{Jabber}
|
||||
|
||||
%% Modules
|
||||
\newcommand{\module}[1]{\texttt{#1}}
|
||||
\newcommand{\modadhoc}{\module{mod\_adhoc}}
|
||||
\newcommand{\modannounce}{\module{mod\_announce}}
|
||||
\newcommand{\modconfigure}{\module{mod\_configure}}
|
||||
\newcommand{\moddisco}{\module{mod\_disco}}
|
||||
\newcommand{\modecho}{\module{mod\_echo}}
|
||||
\newcommand{\modirc}{\module{mod\_irc}}
|
||||
\newcommand{\modlast}{\module{mod\_last}}
|
||||
\newcommand{\modlastodbc}{\module{mod\_last\_odbc}}
|
||||
\newcommand{\modmuc}{\module{mod\_muc}}
|
||||
\newcommand{\modmuclog}{\module{mod\_muc\_log}}
|
||||
\newcommand{\modoffline}{\module{mod\_offline}}
|
||||
\newcommand{\modofflineodbc}{\module{mod\_offline\_odbc}}
|
||||
\newcommand{\modprivacy}{\module{mod\_privacy}}
|
||||
\newcommand{\modprivate}{\module{mod\_private}}
|
||||
\newcommand{\modpubsub}{\module{mod\_pubsub}}
|
||||
\newcommand{\modregister}{\module{mod\_register}}
|
||||
\newcommand{\modroster}{\module{mod\_roster}}
|
||||
\newcommand{\modrosterodbc}{\module{mod\_roster\_odbc}}
|
||||
\newcommand{\modservicelog}{\module{mod\_service\_log}}
|
||||
\newcommand{\modsharedroster}{\module{mod\_shared\_roster}}
|
||||
\newcommand{\modstats}{\module{mod\_stats}}
|
||||
\newcommand{\modtime}{\module{mod\_time}}
|
||||
\newcommand{\modvcard}{\module{mod\_vcard}}
|
||||
\newcommand{\modvcardldap}{\module{mod\_vcard\_ldap}}
|
||||
\newcommand{\modvcardodbc}{\module{mod\_vcard\_odbc}}
|
||||
\newcommand{\modversion}{\module{mod\_version}}
|
||||
\newcommand{\modregister}{\texttt{mod\_register}}
|
||||
\newcommand{\modroster}{\texttt{mod\_roster}}
|
||||
\newcommand{\modconfigure}{\texttt{mod\_configure}}
|
||||
\newcommand{\moddisco}{\texttt{mod\_disco}}
|
||||
\newcommand{\modstats}{\texttt{mod\_stats}}
|
||||
\newcommand{\modvcard}{\texttt{mod\_vcard}}
|
||||
\newcommand{\modoffline}{\texttt{mod\_offline}}
|
||||
\newcommand{\modecho}{\texttt{mod\_echo}}
|
||||
\newcommand{\modprivate}{\texttt{mod\_private}}
|
||||
\newcommand{\modtime}{\texttt{mod\_time}}
|
||||
\newcommand{\modversion}{\texttt{mod\_version}}
|
||||
c
|
||||
%\setcounter{tocdepth}{3}
|
||||
|
||||
%% Title page
|
||||
\include{version}
|
||||
\title{Ejabberd \version\ Developers Guide}
|
||||
|
||||
\title{Ejabberd Developers Guide}
|
||||
\author{Alexey Shchepin \\
|
||||
\ahrefurl{mailto:alexey@sevcom.net} \\
|
||||
\ahrefurl{xmpp:aleksey@jabber.ru}}
|
||||
|
||||
%% Options
|
||||
\newcommand{\marking}[1]{#1} % Marking disabled
|
||||
\newcommand{\quoting}[2][yozhik]{} % Quotes disabled
|
||||
\newcommand{\new}{\begin{latexonly}\marginpar{\textsc{new}}\end{latexonly}} % Highlight new features
|
||||
\newcommand{\improved}{\begin{latexonly}\marginpar{\textsc{improved}}\end{latexonly}} % Highlight improved features
|
||||
\newcommand{\moreinfo}[1]{} % Hide details
|
||||
|
||||
%% Footnotes
|
||||
\newcommand{\tjepref}[2]{\footahref{http://www.jabber.org/jeps/jep-#1.html}{#2}}
|
||||
\newcommand{\jepref}[1]{\tjepref{#1}{JEP-#1}}
|
||||
\date{September 10, 2003}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\label{sec:titlepage}
|
||||
\begin{titlepage}
|
||||
\maketitle{}
|
||||
|
||||
\begin{center}
|
||||
{\insscaleimg{\logoscale}{logo.png}
|
||||
|
||||
{\centering
|
||||
\insscaleimg{\logoscale}{logo.png}
|
||||
\par
|
||||
}
|
||||
\end{center}
|
||||
|
||||
\begin{quotation}\textit{I can thoroughly recommend ejabberd for ease of setup --
|
||||
Kevin Smith, Current maintainer of the Psi project}\end{quotation}
|
||||
|
||||
\end{titlepage}
|
||||
|
||||
%\newpage
|
||||
\tableofcontents{}
|
||||
|
||||
% Input introduction.tex
|
||||
\input{introduction}
|
||||
\newpage
|
||||
\section{Introduction}
|
||||
\label{sec:intro}
|
||||
|
||||
\section{How it Works}
|
||||
\ejabberd{} is a Free and Open Source fault-tolerant distributed \Jabber{}
|
||||
server. It is writen mostly in Erlang.
|
||||
|
||||
The main features of \ejabberd{} is:
|
||||
\begin{itemize}
|
||||
\item Works on most of popular platforms: *nix (tested on Linux and FreeBSD)
|
||||
and Win32
|
||||
\item Distributed: You can run \ejabberd{} on a cluster of machines and all of
|
||||
them will serve one Jabber domain.
|
||||
\item Fault-tolerance: You can setup an \ejabberd{} cluster so that all the
|
||||
information required for a properly working service will be stored
|
||||
permanently on more than one node. This means that if one of the nodes
|
||||
crashes, then the others will continue working without disruption.
|
||||
You can also add or replace more nodes ``on the fly''.
|
||||
\item Built-in \footahref{http://www.jabber.org/jeps/jep-0045.html}{Multi-User
|
||||
Chat} service
|
||||
\item Built-in IRC transport
|
||||
\item Built-in
|
||||
\footahref{http://www.jabber.org/jeps/jep-0060.html}{Publish-Subscribe}
|
||||
service
|
||||
\item Built-in Jabber Users Directory service based on users vCards
|
||||
\item Support for
|
||||
\footahref{http://www.jabber.org/jeps/jep-0030.html}{JEP-0030}
|
||||
(Service Discovery).
|
||||
\item Support for
|
||||
\footahref{http://www.jabber.org/jeps/jep-0039.html}{JEP-0039}
|
||||
(Statistics Gathering).
|
||||
\item Support for \ns{xml:lang} attribute in many XML elements
|
||||
\end{itemize}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
\subsection{How it works}
|
||||
\label{sec:howitworks}
|
||||
|
||||
|
||||
|
||||
A \Jabber{} domain is served by one or more \ejabberd{} nodes. These nodes can
|
||||
be run on different machines that are connected via a network. They all must
|
||||
have the ability to connect to port 4369 of all another nodes, and must have
|
||||
@@ -119,7 +115,7 @@ Each \ejabberd{} node have following modules:
|
||||
\end{itemize}
|
||||
|
||||
|
||||
\subsection{Router}
|
||||
\subsubsection{Router}
|
||||
|
||||
This module is the main router of \Jabber{} packets on each node. It routes
|
||||
them based on their destinations domains. It has two tables: local and global
|
||||
@@ -130,14 +126,14 @@ process. If it does not exists in either tables, then it sent to the S2S
|
||||
manager.
|
||||
|
||||
|
||||
\subsection{Local Router}
|
||||
\subsubsection{Local Router}
|
||||
|
||||
This module routes packets which have a destination domain equal to this server
|
||||
name. If destination JID has a non-empty user part, then it routed to the
|
||||
session manager, else it is processed depending on it's content.
|
||||
|
||||
|
||||
\subsection{Session Manager}
|
||||
\subsubsection{Session Manager}
|
||||
|
||||
This module routes packets to local users. It searches for what user resource
|
||||
packet must be sended via presence table. If this resource is connected to
|
||||
@@ -145,7 +141,7 @@ this node, it is routed to C2S process, if it connected via another node, then
|
||||
the packet is sent to session manager on that node.
|
||||
|
||||
|
||||
\subsection{S2S Manager}
|
||||
\subsubsection{S2S Manager}
|
||||
|
||||
This module routes packets to other \Jabber{} servers. First, it checks if an
|
||||
open S2S connection from the domain of the packet source to the domain of
|
||||
@@ -157,10 +153,10 @@ does not exist, then it is opened and registered.
|
||||
|
||||
|
||||
|
||||
\section{XML Representation}
|
||||
\section{XML representation}
|
||||
\label{sec:xmlrepr}
|
||||
|
||||
Each XML stanza is represented as the following tuple:
|
||||
Each XML stanza represented as following tuple:
|
||||
\begin{verbatim}
|
||||
XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
|
||||
Name = string()
|
||||
@@ -173,14 +169,14 @@ XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
|
||||
\end{verbatim}
|
||||
E.\,g. this stanza:
|
||||
\begin{verbatim}
|
||||
<message to='test@conference.example.org' type='groupchat'>
|
||||
<message to='test@conference.e.localhost' type='groupchat'>
|
||||
<body>test</body>
|
||||
</message>
|
||||
\end{verbatim}
|
||||
is represented as the following structure:
|
||||
represented as following structure:
|
||||
\begin{verbatim}
|
||||
{xmlelement, "message",
|
||||
[{"to", "test@conference.example.org"},
|
||||
[{"to", "test@conference.e.localhost"},
|
||||
{"type", "groupchat"}],
|
||||
[{xmlelement, "body",
|
||||
[],
|
||||
@@ -193,20 +189,20 @@ is represented as the following structure:
|
||||
\label{sec:xmlmod}
|
||||
|
||||
\begin{description}
|
||||
\item{\verb|element_to_string(El) -> string()|}
|
||||
\item[\verb|element_to_string(El) -> string()|]
|
||||
\begin{verbatim}
|
||||
El = XMLElement
|
||||
\end{verbatim}
|
||||
Returns string representation of XML stanza \texttt{El}.
|
||||
|
||||
\item{\verb|crypt(S) -> string()|}
|
||||
\item[\verb|crypt(S) -> string()|]
|
||||
\begin{verbatim}
|
||||
S = string()
|
||||
\end{verbatim}
|
||||
Returns string which correspond to \texttt{S} with encoded XML special
|
||||
characters.
|
||||
|
||||
\item{\verb|remove_cdata(ECList) -> EList|}
|
||||
\item[\verb|remove_cdata(ECList) -> EList|]
|
||||
\begin{verbatim}
|
||||
ECList = [ElementOrCDATA]
|
||||
EList = [XMLElement]
|
||||
@@ -215,7 +211,7 @@ EList = [XMLElement]
|
||||
|
||||
|
||||
|
||||
\item{\verb|get_path_s(El, Path) -> Res|}
|
||||
\item[\verb|get_path_s(El, Path) -> Res|]
|
||||
\begin{verbatim}
|
||||
El = XMLElement
|
||||
Path = [PathItem]
|
||||
@@ -229,15 +225,15 @@ Res = string() | XMLElement
|
||||
If \texttt{Path} is empty, then returns \texttt{El}. Else sequentially
|
||||
consider elements of \texttt{Path}. Each element is one of:
|
||||
\begin{description}
|
||||
\item{\verb|{elem, Name}|} \texttt{Name} is name of subelement of
|
||||
\item[\verb|{elem, Name}|] \texttt{Name} is name of subelement of
|
||||
\texttt{El}, if such element exists, then this element considered in
|
||||
following steps, else returns empty string.
|
||||
\item{\verb|{attr, Name}|} If \texttt{El} have attribute \texttt{Name}, then
|
||||
\item[\verb|{attr, Name}|] If \texttt{El} have attribute \texttt{Name}, then
|
||||
returns value of this attribute, else returns empty string.
|
||||
\item{\verb|cdata|} Returns CDATA of \texttt{El}.
|
||||
\item[\verb|cdata|] Returns CDATA of \texttt{El}.
|
||||
\end{description}
|
||||
|
||||
\item{TODO:}
|
||||
\item[TODO:]
|
||||
\begin{verbatim}
|
||||
get_cdata/1, get_tag_cdata/1
|
||||
get_attr/2, get_attr_s/2
|
||||
@@ -247,30 +243,18 @@ Res = string() | XMLElement
|
||||
\end{description}
|
||||
|
||||
|
||||
\section{Module \texttt{xml\_stream}}
|
||||
\label{sec:xmlstreammod}
|
||||
|
||||
\begin{description}
|
||||
\item{\verb!parse_element(Str) -> XMLElement | {error, Err}!}
|
||||
\begin{verbatim}
|
||||
Str = string()
|
||||
Err = term()
|
||||
\end{verbatim}
|
||||
Parses \texttt{Str} using XML parser, returns either parsed element or error
|
||||
tuple.
|
||||
\end{description}
|
||||
|
||||
|
||||
\section{Modules}
|
||||
\section{\ejabberd{} modules}
|
||||
\label{sec:emods}
|
||||
|
||||
|
||||
%\subsection{gen\_mod behaviour}
|
||||
%\label{sec:genmod}
|
||||
\subsection{\verb|gen_mod| behaviour}
|
||||
\label{sec:genmod}
|
||||
|
||||
%TBD
|
||||
TBD
|
||||
|
||||
\subsection{Module gen\_iq\_handler}
|
||||
\subsection{Module \verb|gen_iq_handler|}
|
||||
\label{sec:geniqhandl}
|
||||
|
||||
The module \verb|gen_iq_handler| allows to easily write handlers for IQ packets
|
||||
@@ -278,32 +262,30 @@ of particular XML namespaces that addressed to server or to users bare JIDs.
|
||||
|
||||
In this module the following functions are defined:
|
||||
\begin{description}
|
||||
\item{\verb|add_iq_handler(Component, Host, NS, Module, Function, Type)|}
|
||||
\item[\verb|add_iq_handler(Component, NS, Module, Function, Type)|]
|
||||
\begin{verbatim}
|
||||
Component = Module = Function = atom()
|
||||
Host = NS = string()
|
||||
NS = string()
|
||||
Type = no_queue | one_queue | parallel
|
||||
\end{verbatim}
|
||||
Registers function \verb|Module:Function| as handler for IQ packets on
|
||||
virtual host \verb|Host| that contain child of namespace \verb|NS| in
|
||||
\verb|Component|. Queueing discipline is \verb|Type|. There are at least
|
||||
two components defined:
|
||||
Registers function \verb|Module:Function| as handler for IQ packets that
|
||||
contain child of namespace \verb|NS| in \verb|Component|. Queueing
|
||||
discipline is \verb|Type|. There are at least two components defined:
|
||||
\begin{description}
|
||||
\item{\verb|ejabberd_local|} Handles packets that addressed to server JID;
|
||||
\item{\verb|ejabberd_sm|} Handles packets that addressed to users bare JIDs.
|
||||
\item[\verb|ejabberd_local|] Handles packets that addressed to server JID;
|
||||
\item[\verb|ejabberd_sm|] Handles packets that addressed to users bare JIDs.
|
||||
\end{description}
|
||||
\item{\verb|remove_iq_handler(Component, Host, NS)|}
|
||||
\item[\verb|remove_iq_handler(Component, NS)|]
|
||||
\begin{verbatim}
|
||||
Component = atom()
|
||||
Host = NS = string()
|
||||
NS = string()
|
||||
\end{verbatim}
|
||||
Removes IQ handler on virtual host \verb|Host| for namespace \verb|NS| from
|
||||
\verb|Component|.
|
||||
Removes IQ handler for namespace \verb|NS| from \verb|Component|.
|
||||
\end{description}
|
||||
|
||||
Handler function must have the following type:
|
||||
\begin{description}
|
||||
\item{\verb|Module:Function(From, To, IQ)|}
|
||||
\item[\verb|Module:Function(From, To, IQ)|]
|
||||
\begin{verbatim}
|
||||
From = To = jid()
|
||||
\end{verbatim}
|
||||
@@ -316,8 +298,8 @@ From = To = jid()
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2,
|
||||
stop/1,
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
process_local_iq/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -325,13 +307,13 @@ From = To = jid()
|
||||
|
||||
-define(NS_CPUTIME, "ejabberd:cputime").
|
||||
|
||||
start(Host, Opts) ->
|
||||
start(Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_CPUTIME,
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, ?NS_CPUTIME,
|
||||
?MODULE, process_local_iq, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_CPUTIME).
|
||||
stop() ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, ?NS_CPUTIME).
|
||||
|
||||
process_local_iq(From, To, {iq, ID, Type, XMLNS, SubEl}) ->
|
||||
case Type of
|
||||
@@ -352,24 +334,23 @@ process_local_iq(From, To, {iq, ID, Type, XMLNS, SubEl}) ->
|
||||
\subsection{Services}
|
||||
\label{sec:services}
|
||||
|
||||
%TBD
|
||||
TBD
|
||||
|
||||
|
||||
%TODO: use \verb|proc_lib|
|
||||
TODO: use \verb|proc_lib|
|
||||
\begin{verbatim}
|
||||
-module(mod_echo).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([start/2, init/1, stop/1]).
|
||||
-export([start/1, init/1, stop/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
MyHost = gen_mod:get_opt(host, Opts, "echo." ++ Host),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, init, [MyHost])).
|
||||
start(Opts) ->
|
||||
Host = gen_mod:get_opt(host, Opts, "echo." ++ ?MYNAME),
|
||||
register(ejabberd_mod_echo, spawn(?MODULE, init, [Host])).
|
||||
|
||||
init(Host) ->
|
||||
ejabberd_router:register_local_route(Host),
|
||||
@@ -381,16 +362,15 @@ loop(Host) ->
|
||||
ejabberd_router:route(To, From, Packet),
|
||||
loop(Host);
|
||||
stop ->
|
||||
ejabberd_router:unregister_route(Host),
|
||||
ejabberd_router:unregister_local_route(Host),
|
||||
ok;
|
||||
_ ->
|
||||
loop(Host)
|
||||
end.
|
||||
|
||||
stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
Proc ! stop,
|
||||
{wait, Proc}.
|
||||
stop() ->
|
||||
ejabberd_mod_echo ! stop,
|
||||
ok.
|
||||
\end{verbatim}
|
||||
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 7.8 KiB |
@@ -1,163 +0,0 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
|
||||
<HEAD>
|
||||
|
||||
<TITLE>Ejabberd 1.1.2 Feature Sheet</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<META name="GENERATOR" content="hevea 1.08">
|
||||
<STYLE type="text/css">
|
||||
.toc{list-style:none;}
|
||||
.title{margin:auto;text-align:center}
|
||||
.center{text-align:center;margin-left:auto;margin-right:auto;}
|
||||
.flushleft{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
.flushright{text-align:right;margin-left:auto;margin-right:0ex;}
|
||||
DIV TABLE{margin-left:inherit;margin-right:inherit;}
|
||||
PRE{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
BLOCKQUOTE{margin-left:4ex;margin-right:4ex;text-align:left;}
|
||||
.part{margin:auto;text-align:center}
|
||||
SPAN{width:20%; float:right; text-align:left; margin-left:auto;}
|
||||
</STYLE>
|
||||
</HEAD>
|
||||
|
||||
<BODY >
|
||||
<!--HEVEA command line is: hevea -fix -noiso -pedantic features.tex -->
|
||||
<!--HTMLHEAD-->
|
||||
<!--ENDHTML-->
|
||||
<!--PREFIX <ARG ></ARG>-->
|
||||
<!--CUT DEF section 1 -->
|
||||
|
||||
<BR>
|
||||
<BR>
|
||||
<A NAME="titlepage"></A>
|
||||
|
||||
<TABLE CLASS="title">
|
||||
<TR><TD>
|
||||
<H1 CLASS="titlemain">Ejabberd 1.1.2 Feature Sheet</H1>
|
||||
<H3 CLASS="titlerest">Sander Devrieze<BR>
|
||||
<A HREF="mailto:s.devrieze@pandora.be"><TT>mailto:s.devrieze@pandora.be</TT></A><BR>
|
||||
<A HREF="xmpp:sander@devrieze.dyndns.org"><TT>xmpp:sander@devrieze.dyndns.org</TT></A></H3></TD>
|
||||
</TR></TABLE><BR>
|
||||
<BR>
|
||||
<DIV CLASS="center">
|
||||
|
||||
<IMG SRC="logo.png" ALT="logo.png">
|
||||
|
||||
|
||||
<BR>
|
||||
<BR>
|
||||
|
||||
</DIV>
|
||||
<BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I can thoroughly recommend ejabberd for ease of setup –
|
||||
Kevin Smith, Current maintainer of the Psi project</I></FONT></BLOCKQUOTE>
|
||||
<!--TOC section Introduction-->
|
||||
|
||||
<H2 CLASS="section"><A NAME="intro">Introduction</A></H2><!--SEC END -->
|
||||
|
||||
<A NAME="sec:intro"></A>
|
||||
<BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I just tried out ejabberd and was impressed both by ejabberd itself and the language it is written in, Erlang. —
|
||||
Joeri</I></FONT></BLOCKQUOTE>
|
||||
<TT>ejabberd</TT> is a <B><FONT SIZE=4><FONT COLOR="#001376">free and open source</FONT></FONT></B> instant messaging server written in <A HREF="http://www.erlang.org/">Erlang</A>.<BR>
|
||||
<BR>
|
||||
<TT>ejabberd</TT> is <B><FONT SIZE=4><FONT COLOR="#001376">cross-platform</FONT></FONT></B>, distributed, fault-tolerant, and based on open standards to achieve real-time communication.<BR>
|
||||
<BR>
|
||||
<TT>ejabberd</TT> is designed to be a <B><FONT SIZE=4><FONT COLOR="#001376">rock-solid and feature rich</FONT></FONT></B> XMPP server.<BR>
|
||||
<BR>
|
||||
<TT>ejabberd</TT> is suitable for small deployments, whether they need to be <B><FONT SIZE=4><FONT COLOR="#001376">scalable</FONT></FONT></B> or not, as well as extremely big deployments.<BR>
|
||||
<BR>
|
||||
<!--TOC subsection Key Features-->
|
||||
|
||||
<H3 CLASS="subsection"><A NAME="keyfeatures">Key Features</A></H3><!--SEC END -->
|
||||
|
||||
<A NAME="sec:keyfeatures"></A>
|
||||
|
||||
<BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>Erlang seems to be tailor-made for writing stable, robust servers. —
|
||||
Peter Saint-André, Executive Director of the Jabber Software Foundation</I></FONT></BLOCKQUOTE>
|
||||
<TT>ejabberd</TT> is:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
<B><FONT SIZE=4><FONT COLOR="#001376">Cross-platform:</FONT></FONT></B> <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.<BR>
|
||||
<BR>
|
||||
<LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Distributed:</FONT></FONT></B> You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.<BR>
|
||||
<BR>
|
||||
<LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Fault-tolerant:</FONT></FONT></B> You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced `on the fly'.<BR>
|
||||
<BR>
|
||||
<LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Administrator Friendly:</FONT></FONT></B> <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Comprehensive documentation.
|
||||
<LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows.<FONT COLOR="#FFAA00"><SPAN STYLE="font-variant:small-caps">improved</SPAN></FONT>
|
||||
<LI CLASS="li-itemize">Web interface for administration tasks.
|
||||
<LI CLASS="li-itemize">Shared Roster Groups.
|
||||
<LI CLASS="li-itemize">Command line administration tool.<FONT COLOR="#FFAA00"><SPAN STYLE="font-variant:small-caps">improved</SPAN></FONT>
|
||||
<LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
|
||||
<LI CLASS="li-itemize">Capability to send announce messages.
|
||||
</UL><BR>
|
||||
<BR>
|
||||
<LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Internationalized:</FONT></FONT></B> <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Translated in 12 languages.<FONT COLOR="#FFAA00"><SPAN STYLE="font-variant:small-caps">improved</SPAN></FONT>
|
||||
<LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
|
||||
</UL><BR>
|
||||
<BR>
|
||||
<LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Open Standards:</FONT></FONT></B> <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Fully XMPP compliant.
|
||||
<LI CLASS="li-itemize">XML-based protocol.
|
||||
<LI CLASS="li-itemize"><A HREF="http://ejabberd.jabber.ru/protocols">Many JEPs supported</A>.
|
||||
</UL></UL>
|
||||
<!--TOC subsection Additional Features-->
|
||||
|
||||
<H3 CLASS="subsection"><A NAME="addfeatures">Additional Features</A></H3><!--SEC END -->
|
||||
|
||||
<A NAME="sec:addfeatures"></A>
|
||||
|
||||
<BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>ejabberd is making inroads to solving the "buggy incomplete server" problem —
|
||||
Justin Karneges, Founder of the Psi and the Delta projects</I></FONT></BLOCKQUOTE>
|
||||
Moreover, <TT>ejabberd</TT> comes with a wide range of other state-of-the-art features:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Modular
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Load only the modules you want.
|
||||
<LI CLASS="li-itemize">Extend <TT>ejabberd</TT> with your own custom modules.
|
||||
</UL>
|
||||
<LI CLASS="li-itemize">Security
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
SASL and STARTTLS for c2s and s2s connections.
|
||||
<LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
|
||||
<LI CLASS="li-itemize">Web interface accessible via HTTPS secure access.
|
||||
</UL>
|
||||
<LI CLASS="li-itemize">Databases
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Native MySQL support.
|
||||
<LI CLASS="li-itemize">Native PostgreSQL support.
|
||||
<LI CLASS="li-itemize">Mnesia.
|
||||
<LI CLASS="li-itemize">ODBC data storage support.
|
||||
<LI CLASS="li-itemize">Microsoft SQL Server support.<FONT COLOR="red"><SPAN STYLE="font-variant:small-caps">new</SPAN></FONT>
|
||||
</UL>
|
||||
<LI CLASS="li-itemize">Authentication
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
LDAP and ODBC.<FONT COLOR="#FFAA00"><SPAN STYLE="font-variant:small-caps">improved</SPAN></FONT>
|
||||
<LI CLASS="li-itemize">External Authentication script.
|
||||
<LI CLASS="li-itemize">Internal Authentication.
|
||||
</UL>
|
||||
<LI CLASS="li-itemize">Others
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Compressing XML streams with Stream Compression (<A HREF="http://www.jabber.org/jeps/jep-0138.html">JEP-0138</A>).
|
||||
<LI CLASS="li-itemize">Interface with networks such as AIM, ICQ and MSN.
|
||||
<LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.jabber.org/jeps/jep-0039.html">JEP-0039</A>).
|
||||
<LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
|
||||
<LI CLASS="li-itemize"><A HREF="http://www.jabber.org/jeps/jep-0045.html">Multi-User Chat</A> module with logging.<FONT COLOR="#FFAA00"><SPAN STYLE="font-variant:small-caps">improved</SPAN></FONT>
|
||||
<LI CLASS="li-itemize">Users Directory based on users vCards.
|
||||
<LI CLASS="li-itemize"><A HREF="http://www.jabber.org/jeps/jep-0060.html">Publish-Subscribe</A> component.
|
||||
<LI CLASS="li-itemize">Support for virtual hosting.
|
||||
<LI CLASS="li-itemize"><A HREF="http://www.jabber.org/jeps/jep-0025.html">HTTP Polling</A> service.
|
||||
<LI CLASS="li-itemize">IRC transport.
|
||||
</UL>
|
||||
</UL>
|
||||
<!--HTMLFOOT-->
|
||||
<!--ENDHTML-->
|
||||
<!--FOOTER-->
|
||||
<HR SIZE=2><BLOCKQUOTE CLASS="quote"><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
|
||||
</EM><A HREF="http://pauillac.inria.fr/~maranget/hevea/index.html"><EM>H<FONT SIZE=2><sup>E</sup></FONT>V<FONT SIZE=2><sup>E</sup></FONT>A</EM></A><EM>.</EM></BLOCKQUOTE></BODY>
|
||||
</HTML>
|
||||
@@ -1,136 +0,0 @@
|
||||
\documentclass[a4paper,10pt]{article}
|
||||
|
||||
%% Packages
|
||||
\usepackage{epsfig}
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{graphics}
|
||||
\usepackage{hevea}
|
||||
\usepackage[pdftex,colorlinks,unicode,urlcolor=blue,linkcolor=blue,pdftitle=Ejabberd\
|
||||
Feature\ Sheet,pdfauthor=Sander\
|
||||
Devrieze,pdfsubject=ejabberd,pdfkeywords=ejabberd]{hyperref}
|
||||
\usepackage{verbatim}
|
||||
\usepackage{color}
|
||||
|
||||
%% Index
|
||||
% Remove the index anchors from the HTML version to save size and bandwith.
|
||||
\newcommand{\ind}[1]{\begin{latexonly}\index{#1}\end{latexonly}}
|
||||
|
||||
%% Images
|
||||
\newcommand{\logoscale}{0.7}
|
||||
\newcommand{\imgscale}{0.58}
|
||||
\newcommand{\insimg}[1]{\insscaleimg{\imgscale}{#1}}
|
||||
\newcommand{\insscaleimg}[2]{
|
||||
\imgsrc{#2}{}
|
||||
\begin{latexonly}
|
||||
\scalebox{#1}{\includegraphics{#2}}
|
||||
\end{latexonly}
|
||||
}
|
||||
|
||||
%% Various
|
||||
\newcommand{\bracehack}{\def\{{\char"7B}\def\}{\char"7D}}
|
||||
\newcommand{\titem}[1]{\item[\bracehack\texttt{#1}]}
|
||||
\newcommand{\ns}[1]{\texttt{#1}}
|
||||
\newcommand{\jid}[1]{\texttt{#1}}
|
||||
\newcommand{\option}[1]{\texttt{#1}}
|
||||
\newcommand{\poption}[1]{{\bracehack\texttt{#1}}}
|
||||
\newcommand{\node}[1]{\texttt{#1}}
|
||||
\newcommand{\term}[1]{\texttt{#1}}
|
||||
\newcommand{\shell}[1]{\texttt{#1}}
|
||||
\newcommand{\ejabberd}{\texttt{ejabberd}}
|
||||
\newcommand{\Jabber}{Jabber}
|
||||
|
||||
%% Title page
|
||||
\include{version}
|
||||
\title{Ejabberd \version\ Feature Sheet}
|
||||
\author{Sander Devrieze \\
|
||||
\ahrefurl{mailto:s.devrieze@pandora.be} \\
|
||||
\ahrefurl{xmpp:sander@devrieze.dyndns.org}}
|
||||
|
||||
% Options
|
||||
\newcommand{\marking}[1]{\textbf{\begin{large}\textcolor{ejblue}{#1}\end{large}}} % Marking enabled
|
||||
\newcommand{\quoting}[2][yozhik]{\begin{quotation}\textcolor{#1}{\textit{#2}}\end{quotation}} % Quotes enabled
|
||||
\newcommand{\new}{\marginpar{\textcolor{red}{\textsc{new}}}} % Highlight new features
|
||||
\newcommand{\improved}{\marginpar{\textcolor{orange}{\textsc{improved}}}} % Highlight improved features
|
||||
\setcounter{secnumdepth}{-1} % Disable section numbering
|
||||
|
||||
%% To by-pass errors in the HTML version.
|
||||
\newstyle{SPAN}{width:20\%; float:right; text-align:left; margin-left:auto;}
|
||||
\definecolor{orange} {cmyk}{0.000,0.333,1.000,0.000}
|
||||
|
||||
%% Footnotes
|
||||
\begin{latexonly}
|
||||
\global\parskip=9pt plus 3pt minus 1pt
|
||||
\global\parindent=0pt
|
||||
\gdef\ahrefurl#1{\href{#1}{\texttt{#1}}}
|
||||
\gdef\footahref#1#2{#2\footnote{\href{#1}{\texttt{#1}}}}
|
||||
\end{latexonly}
|
||||
\newcommand{\tjepref}[2]{\footahref{http://www.jabber.org/jeps/jep-#1.html}{#2}}
|
||||
\newcommand{\jepref}[1]{\tjepref{#1}{JEP-#1}}
|
||||
|
||||
%% Fancy header
|
||||
\fancyhf{}
|
||||
\pagestyle{fancy}
|
||||
\rhead{\textcolor{ejblue}{The Expandable Jabber Daemon.}}
|
||||
\renewcommand{\headrule}{{\color{ejblue}%
|
||||
\hrule width\headwidth height\headrulewidth \vskip-\headrulewidth}}
|
||||
\lhead{\setlength{\unitlength}{-6mm}
|
||||
\begin{picture}(0,0)
|
||||
\put(5.8,3.25){\includegraphics[width=1.3\textwidth]{yozhikheader.png}}
|
||||
\end{picture}}
|
||||
|
||||
% Official ejabberd colours
|
||||
\definecolor{ejblue} {cmyk}{1.000,0.831,0.000,0.537} %RGB: 0,0,118 HTML: 000076
|
||||
\definecolor{ejgreenyellow}{cmyk}{0.079,0.000,0.275,0.102} %RGB: 209,229,159 HTML: d1e59f
|
||||
\definecolor{ejgreendark} {cmyk}{0.131,0.000,0.146,0.220} %RGB: 166,199,162 HTML: a6c7a2
|
||||
\definecolor{ejgreen} {cmyk}{0.077,0.000,0.081,0.078} %RGB: 216,236,215 HTML: d8ecd7
|
||||
\definecolor{ejgreenwhite} {cmyk}{0.044,0.000,0.048,0.020} %RGB: 239,250,238 HTML: effaee
|
||||
\definecolor{yozhik} {cmyk}{0.000,0.837,1.000,0.424} %RGB: 147,0,0 HTML: 930000
|
||||
|
||||
\begin{document}
|
||||
|
||||
\label{titlepage}
|
||||
\begin{titlepage}
|
||||
\maketitle{}
|
||||
|
||||
\thispagestyle{empty}
|
||||
|
||||
\begin{center}
|
||||
{\insscaleimg{\logoscale}{logo.png}
|
||||
\par
|
||||
}
|
||||
\end{center}
|
||||
\quoting{I can thoroughly recommend ejabberd for ease of setup --
|
||||
Kevin Smith, Current maintainer of the Psi project}
|
||||
|
||||
\end{titlepage}
|
||||
|
||||
\newpage
|
||||
% Set the page counter to 2 so that the titlepage and the second page do not
|
||||
% have the same page number. This fixes the PDFLaTeX warning "destination with
|
||||
% the same identifier".
|
||||
\begin{latexonly}
|
||||
\setcounter{page}{2}
|
||||
\pagecolor{ejgreenwhite}
|
||||
\end{latexonly}
|
||||
|
||||
% Input introduction.tex
|
||||
\input{introduction}
|
||||
|
||||
\end{document}
|
||||
|
||||
%% TODO
|
||||
% * illustrations (e.g. screenshot from web interface)
|
||||
% * commented parts
|
||||
% * slides, guide and html version
|
||||
% * cleaning and improving LaTeX code
|
||||
% * key features: something like this (shorter)? (more focussed on Erlang now): "To reach the goal of high
|
||||
% availability, performance and clustering, ejabberd is written in Erlang, a programming language perfectly
|
||||
% suited for this. Besides that, some parts are written in C to also incude the advantages of this language. In
|
||||
% short, ejabberd is a perfect mix of mainly Erlang code, peppered with some C code to get the final touch!"
|
||||
% <picture of a cocktail>
|
||||
% * key features: saying that ejabberd the only XMPP server is that can do real clustering:
|
||||
% http://www.jivesoftware.org/forums/thread.jspa?threadID=14602
|
||||
% "What I find interesting is that *no* XMPP servers truly provide clustering. This includes all the commercial
|
||||
% servers. The one partial exception appears to be ejabberd, which can cluster certain data such as sessions,
|
||||
% but not all services such as MUC."
|
||||
% * try it today: links to migration tutorials
|
||||
@@ -1,131 +0,0 @@
|
||||
\section{\aname{intro}{Introduction}}
|
||||
\label{sec:intro}
|
||||
|
||||
\quoting{I just tried out ejabberd and was impressed both by ejabberd itself and the language it is written in, Erlang. ---
|
||||
Joeri}
|
||||
|
||||
%ejabberd is a free and open source instant messaging server written in Erlang. ejabberd is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication (Jabber/XMPP).
|
||||
|
||||
\ejabberd{} is a \marking{free and open source} instant messaging server written in \footahref{http://www.erlang.org/}{Erlang}.
|
||||
|
||||
\ejabberd{} is \marking{cross-platform}, distributed, fault-tolerant, and based on open standards to achieve real-time communication.
|
||||
|
||||
\ejabberd{} is designed to be a \marking{rock-solid and feature rich} XMPP server.
|
||||
|
||||
\ejabberd{} is suitable for small deployments, whether they need to be \marking{scalable} or not, as well as extremely big deployments.
|
||||
|
||||
%\subsection{\aname{layout}{Layout with example deployment (title needs a better name)}}
|
||||
%\label{sec:layout}
|
||||
|
||||
%In this section there will be a graphical overview like these:\\
|
||||
%\verb|http://www.tipic.com/var/timp/timp_dep.gif| \\
|
||||
%\verb|http://www.jabber.com/images/jabber_Com_Platform.jpg| \\
|
||||
%\verb|http://www.antepo.com/files/OPN45systemdatasheet.pdf| \\
|
||||
|
||||
%A page full with names of Jabber client that are known to work with ejabberd. \begin{tiny}tiny font\end{tiny}
|
||||
|
||||
%\subsection{\aname{trytoday}{Try It Today}}
|
||||
%\label{sec:trytoday}
|
||||
|
||||
%(Not sure if I will include/finish this section for the next version.)
|
||||
|
||||
%\begin{itemize}
|
||||
%\item Erlang REPOS
|
||||
%\item Packages in distributions
|
||||
%\item Windows binary
|
||||
%\item source tar.gz
|
||||
%\item Migration from Jabberd14 (and so also Jabberd2 because you can migrate from version 2 back to 14) and Jabber Inc. XCP possible.
|
||||
%\end{itemize}
|
||||
|
||||
\newpage
|
||||
\subsection{\aname{keyfeatures}{Key Features}}
|
||||
\label{sec:keyfeatures}
|
||||
\ind{features!key features}
|
||||
|
||||
\quoting{Erlang seems to be tailor-made for writing stable, robust servers. ---
|
||||
Peter Saint-Andr\'e, Executive Director of the Jabber Software Foundation}
|
||||
|
||||
\ejabberd{} is:
|
||||
\begin{itemize}
|
||||
\item \marking{Cross-platform:} \ejabberd{} runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.
|
||||
|
||||
\item \marking{Distributed:} You can run \ejabberd{} on a cluster of machines and all of them will serve the same \Jabber{} domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.
|
||||
|
||||
\item \marking{Fault-tolerant:} You can deploy an \ejabberd{} cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced `on the fly'.
|
||||
|
||||
\item \marking{Administrator Friendly:} \ejabberd{} is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
|
||||
\begin{itemize}
|
||||
\item Comprehensive documentation.
|
||||
\item Straightforward installers for Linux, Mac OS X, and Windows.\improved{}
|
||||
\item Web interface for administration tasks.
|
||||
\item Shared Roster Groups.
|
||||
\item Command line administration tool.\improved{}
|
||||
\item Can integrate with existing authentication mechanisms.
|
||||
\item Capability to send announce messages.
|
||||
\end{itemize}
|
||||
|
||||
\item \marking{Internationalized:} \ejabberd{} leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
|
||||
\begin{itemize}
|
||||
\item Translated in 12 languages.\improved{}
|
||||
\item Support for \footahref{http://www.ietf.org/rfc/rfc3490.txt}{IDNA}.
|
||||
\end{itemize}
|
||||
|
||||
\item \marking{Open Standards:} \ejabberd{} is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
|
||||
\begin{itemize}
|
||||
\item Fully XMPP compliant.
|
||||
\item XML-based protocol.
|
||||
\item \footahref{http://ejabberd.jabber.ru/protocols}{Many JEPs supported}.
|
||||
\end{itemize}
|
||||
|
||||
\end{itemize}
|
||||
|
||||
\newpage
|
||||
|
||||
\subsection{\aname{addfeatures}{Additional Features}}
|
||||
\label{sec:addfeatures}
|
||||
\ind{features!additional features}
|
||||
|
||||
\quoting{ejabberd is making inroads to solving the "buggy incomplete server" problem ---
|
||||
Justin Karneges, Founder of the Psi and the Delta projects}
|
||||
|
||||
Moreover, \ejabberd{} comes with a wide range of other state-of-the-art features:
|
||||
\begin{itemize}
|
||||
\item Modular
|
||||
\begin{itemize}
|
||||
\item Load only the modules you want.
|
||||
\item Extend \ejabberd{} with your own custom modules.
|
||||
\end{itemize}
|
||||
\item Security
|
||||
\begin{itemize}
|
||||
\item SASL and STARTTLS for c2s and s2s connections.
|
||||
\item STARTTLS and Dialback s2s connections.
|
||||
\item Web interface accessible via HTTPS secure access.
|
||||
\end{itemize}
|
||||
\item Databases
|
||||
\begin{itemize}
|
||||
\item Native MySQL support.
|
||||
\item Native PostgreSQL support.
|
||||
\item Mnesia.
|
||||
\item ODBC data storage support.
|
||||
\item Microsoft SQL Server support.\new{}
|
||||
\end{itemize}
|
||||
\item Authentication
|
||||
\begin{itemize}
|
||||
\item LDAP and ODBC.\improved{}
|
||||
\item External Authentication script.
|
||||
\item Internal Authentication.
|
||||
\end{itemize}
|
||||
\item Others
|
||||
\begin{itemize}
|
||||
\item Compressing XML streams with Stream Compression (\jepref{0138}).
|
||||
\item Interface with networks such as AIM, ICQ and MSN.
|
||||
\item Statistics via Statistics Gathering (\jepref{0039}).
|
||||
\item IPv6 support both for c2s and s2s connections.
|
||||
\item \tjepref{0045}{Multi-User Chat} module with logging.\improved{}
|
||||
\item Users Directory based on users vCards.
|
||||
\item \tjepref{0060}{Publish-Subscribe} component.
|
||||
\item Support for virtual hosting.
|
||||
\item \tjepref{0025}{HTTP Polling} service.
|
||||
\item IRC transport.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 20 KiB |
@@ -1,62 +0,0 @@
|
||||
Release notes
|
||||
ejabberd 0.9.1
|
||||
|
||||
This document describes the main changes from [25]ejabberd 0.9.
|
||||
|
||||
The code can be downloaded from the [26]download page.
|
||||
|
||||
For more detailed information, please refer to ejabberd [27]User Guide.
|
||||
|
||||
|
||||
Groupchat (Multi-user chat and IRC) improvements
|
||||
|
||||
The multi-user chat code has been improved to comply with the latest version
|
||||
of Jabber Enhancement Proposal 0045.
|
||||
|
||||
The IRC (Internet Relay Chat) features now support WHOIS and USERINFO
|
||||
requests.
|
||||
|
||||
|
||||
Web interface
|
||||
|
||||
ejabberd modules management features have been added to the web interface.
|
||||
They now allow to start or stop extension module without restarting the
|
||||
ejabberd server.
|
||||
|
||||
|
||||
Publish and subscribe
|
||||
|
||||
It is now possible to a subscribe node with a JabberID that includes a
|
||||
resource.
|
||||
|
||||
|
||||
Translations
|
||||
|
||||
A new script has been included to help translate ejabberd into new languages
|
||||
and maintain existing translations.
|
||||
|
||||
As a result, ejabberd is now translating into 10 languages:
|
||||
* Dutch
|
||||
* English
|
||||
* French
|
||||
* German
|
||||
* Polish
|
||||
* Portuguese
|
||||
* Russian
|
||||
* Spanish
|
||||
* Swedish
|
||||
* Ukrainian
|
||||
|
||||
|
||||
Migration
|
||||
|
||||
No changes have been made to the database. No particular conversion steps
|
||||
are needed. However, you should backup your database before upgrading to a
|
||||
new ejabberd version.
|
||||
|
||||
|
||||
Bugfixes
|
||||
|
||||
This release contains several bugfixes and architectural changes. Please
|
||||
refer to the Changelog file supplied with this release for details of all
|
||||
improvements in the ejabberd code.
|
||||
@@ -1,99 +0,0 @@
|
||||
Release notes
|
||||
ejabberd 0.9.8
|
||||
2005-08-01
|
||||
|
||||
This document describes the main changes in ejabberd 0.9.8. This
|
||||
version prepares the way for the release of ejabberd 1.0, which
|
||||
is due later this year.
|
||||
|
||||
The code can be downloaded from the Process-one website:
|
||||
http://www.process-one.net/en/projects/ejabberd/
|
||||
|
||||
For more detailed information, please refer to ejabberd User Guide
|
||||
on the Process-one website:
|
||||
http://www.process-one.net/en/projects/ejabberd/docs.html
|
||||
|
||||
|
||||
Recent changes include....
|
||||
|
||||
|
||||
Enhanced virtual hosting
|
||||
|
||||
Virtual hosting applies to many more setting options and
|
||||
features and is transparent. Virtual hosting accepts different
|
||||
parameters for different virtual hosts regarding the following
|
||||
features: authentication method, access control lists and access
|
||||
rules, users management, statistics, and shared roster. The web
|
||||
interface gives access to each virtual host's parameters.
|
||||
|
||||
|
||||
Enhanced Publish-Subscribe module
|
||||
|
||||
ejabberd's Publish-Subscribe module integrates enhancements
|
||||
coming from J-EAI, an XMPP-based integration server built on
|
||||
ejabberd. ejabberd thus supports Publish-Subscribe node
|
||||
configuration. It is possible to define nodes that should be
|
||||
persistent, and the number of items to persist. Besides that, it
|
||||
is also possible to define various notification parameters, such
|
||||
as the delivery of the payload with the notifications, and the
|
||||
notification of subscribers when some changes occur on items.
|
||||
Other examples are: the maximum size of the items payload, the
|
||||
subscription approvers, the limitation of the notification to
|
||||
online users only, etc.
|
||||
|
||||
|
||||
Code reorganisation and update
|
||||
|
||||
- The mod_register module has been cleaned up.
|
||||
- ODBC support has been updated and several bugs have been fixed.
|
||||
|
||||
|
||||
Development API
|
||||
|
||||
To ease the work of Jabber/XMPP developers, a filter_packet hook
|
||||
has been added. As a result it is possible to develop plugins to
|
||||
filter or modify packets flowing through ejabberd.
|
||||
|
||||
|
||||
Translations
|
||||
|
||||
- Translations have been updated to support the new Publish-Subscribe features.
|
||||
- A new Brazilian Portuguese translation has been contributed.
|
||||
|
||||
|
||||
Web interface
|
||||
|
||||
- The CSS stylesheet from the web interface is W3C compliant.
|
||||
|
||||
|
||||
Installers
|
||||
|
||||
Installers are provided for Microsoft Windows and Linux/x86. The
|
||||
Linux installer includes Erlang ASN.1 modules for LDAP
|
||||
authentication support.
|
||||
|
||||
|
||||
Bugfixes
|
||||
|
||||
- This release contains several bugfixes and architectural
|
||||
changes. Among other bugfixes include improvements in LDAP
|
||||
authentication. Please refer to the ChangeLog file supplied
|
||||
with this release regarding all improvements in ejabberd.
|
||||
|
||||
|
||||
References
|
||||
|
||||
The ejabberd feature sheet helps comparing with other Jabber/XMPP
|
||||
servers:
|
||||
http://www.process-one.net/en/projects/ejabberd/docs/features.pdf
|
||||
|
||||
Contributed tutorials of interest are:
|
||||
- Migration from Jabberd1.4 to ejabberd:
|
||||
http://ejabberd.jabber.ru/jabberd1-to-ejabberd
|
||||
- Migration from Jabberd2 to ejabberd:
|
||||
http://ejabberd.jabber.ru/jabberd2-to-ejabberd
|
||||
- Transport configuration for connecting to other networks:
|
||||
http://ejabberd.jabber.ru/tutorials-transports
|
||||
|
||||
END
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
Release notes
|
||||
ejabberd 0.9
|
||||
|
||||
This document describes the major new features of and changes to
|
||||
ejabberd 0.9, compared to latest public release ejabber 0.7.5.
|
||||
|
||||
For more detailed information, please refer to ejabberd User
|
||||
Guide.
|
||||
|
||||
|
||||
Virtual Hosting
|
||||
|
||||
ejabberd now can host several domain on the same instance.
|
||||
This option is enabled by using:
|
||||
|
||||
{hosts, ["erlang-projects.org", "erlang-fr.org"]}.
|
||||
|
||||
instead of the previous host directive.
|
||||
|
||||
Note that you are now using a list of hosts. The main one should
|
||||
be the first listed. See migration section further in this release
|
||||
note for details.
|
||||
|
||||
|
||||
Shared Roster
|
||||
|
||||
Shared roster is a new feature that allow the ejabberd
|
||||
administrator to add jabber user that will be present in the
|
||||
roster of every users on the server.
|
||||
Shared roster are enabled by adding:
|
||||
|
||||
{mod_shared_roster, []}
|
||||
|
||||
at the end of your module list in your ejabberd.cfg file.
|
||||
|
||||
|
||||
PostgreSQL (ODBC) support
|
||||
|
||||
This feature is experimental and not yet properly documented. This
|
||||
feature is released for testing purpose.
|
||||
|
||||
You need to have Erlang/OTP R10 to compile with ODBC on various
|
||||
flavour of *nix. You should use Erlang/OTP R10B-4, as this task
|
||||
has became easier with this release. It comes already build in
|
||||
Erlang/OTP Microsoft Windows binary.
|
||||
|
||||
PostgreSQL support is enabled by using the following module in
|
||||
ejabberd.cfg instead of their standard counterpart:
|
||||
|
||||
mod_last_odbc.erl
|
||||
mod_offline_odbc.erl
|
||||
mod_roster_odbc.erl
|
||||
|
||||
The database schema is located in the src/odbc/pq.sql file.
|
||||
|
||||
Look at the src/ejabberd.cfg.example file for more information on
|
||||
how to configure ejabberd with odbc support. You can get support
|
||||
on how to configure ejabberd with a relational database.
|
||||
|
||||
|
||||
Migration from ejabberd 0.7.5
|
||||
|
||||
Migration is pretty straightforward as Mnesia database schema
|
||||
conversions is handled automatically. Remember however that you
|
||||
must backup your ejabberd database before migration.
|
||||
|
||||
Here are the following steps to proceed:
|
||||
|
||||
1. Stop your instance of ejabberd.
|
||||
|
||||
2. In ejabberd.cfg, define the host lists. Change the host
|
||||
directive to the hosts one:
|
||||
Before:
|
||||
{host, "erlang-projects.org"}.
|
||||
After:
|
||||
{hosts, ["erlang-projects.org", "erlang-fr.org"]}.
|
||||
Note that when you restart the server the existing users will be
|
||||
affected to the first virtual host, so the order is important. You
|
||||
should keep the previous hostname as the first virtual host.
|
||||
|
||||
3. Restart ejabberd.
|
||||
|
||||
|
||||
Bugfixes
|
||||
|
||||
This release contains several bugfixes and architectural changes.
|
||||
Please refer to the Changelog file supplied with this release for
|
||||
details of all improvements in the ejabberd code.
|
||||
@@ -1,120 +0,0 @@
|
||||
Release Notes
|
||||
ejabberd 1.0.0
|
||||
14 December 2005
|
||||
|
||||
This document describes the main changes in ejabberd 1.0.0. Unique in this
|
||||
version is the compliancy with the XMPP (eXtensible Messaging and Presence
|
||||
Protocol) standard. ejabberd is the first Open Source Jabber server claiming
|
||||
to fully comply to the XMPP standard.
|
||||
|
||||
ejabberd can be downloaded from the Process-one website:
|
||||
http://www.process-one.net/en/projects/ejabberd/
|
||||
|
||||
Detailed information can be found in the ejabberd Feature Sheet and User
|
||||
Guide which are available on the Process-one website:
|
||||
http://www.process-one.net/en/projects/ejabberd/docs.html
|
||||
|
||||
|
||||
Recent changes include:
|
||||
|
||||
|
||||
Server-to-server Encryption for Enhanced Security
|
||||
|
||||
- Support for STARTTLS and SASL EXTERNAL to secure server-to-server traffic
|
||||
has been added.
|
||||
- Also, STARTTLS and Dialback has been implemented for server-to-server (s2s)
|
||||
connections. Detailed information about these new features can be found on
|
||||
http://ejabberd.jabber.ru/s2s-encryption
|
||||
- commonName and dNSName fields matching were introduced to ease the process
|
||||
of retrieving certificates.
|
||||
- Different certificates can be defined for each virtual host.
|
||||
|
||||
ODBC Support
|
||||
|
||||
- ODBC support has been improved to allow production use of ejabberd with
|
||||
relational databases.
|
||||
- Support for vCard storage in ODBC has been added.
|
||||
- ejd2odbc.erl is a tool to convert an installation from Erlang's database
|
||||
Mnesia to an ODBC compatible relational database.
|
||||
|
||||
Native PostgreSQL Support
|
||||
|
||||
- Native PostgreSQL support gives you a better performance when you use
|
||||
PostgreSQL.
|
||||
|
||||
Shared Roster groups
|
||||
|
||||
- Shared Roster groups support has been enhanced. New is the ability to add
|
||||
all registered users to everyone's roster. Detailed information about this
|
||||
new feature can be found on http://ejabberd.jabber.ru/shared-roster-all
|
||||
|
||||
Web Interface
|
||||
|
||||
- The web interface internal code has been modified for better integration
|
||||
and compliancy with J-EAI, an ejabberd-based Enterprise Application
|
||||
Integration platform.
|
||||
- More XHTML 1.0 Transitional compliancy work was done.
|
||||
|
||||
Transports
|
||||
|
||||
- A transport workaround can be enabled during compilation. To do this, you
|
||||
can pass the "--enable-roster-gateway-workaround" option to the configure
|
||||
script. (./configure --enable-roster-gateway-workaround)
|
||||
This option allows transports to add items with subscription "to" in the
|
||||
roster by sending <presence type='subscribed'/> stanza to user. This option
|
||||
is only needed for JIT ICQ transport.
|
||||
Warning: by enabling this option, ejabberd will not be fully XMPP compliant
|
||||
anymore.
|
||||
|
||||
Documentation and Internationalization
|
||||
|
||||
- Documentation has been extended to cover more topics.
|
||||
- Translations have been updated.
|
||||
|
||||
Bugfixes
|
||||
|
||||
- This release contains several bugfixes.
|
||||
- Among other bugfixes include improvements to the client-to-server (c2s)
|
||||
connection management module.
|
||||
- Please refer to the ChangeLog file supplied
|
||||
with this release regarding all improvements in ejabberd.
|
||||
|
||||
|
||||
Installation Notes
|
||||
|
||||
|
||||
Supported Erlang Version
|
||||
|
||||
- You need at least Erlang/OTP R9C to run ejabberd 1.0.0.
|
||||
|
||||
Installation
|
||||
|
||||
Installers are provided for Microsoft Windows and Linux/x86.
|
||||
Installers can be retrieved from:
|
||||
http://www.process-one.net/en/projects/ejabberd/download.html
|
||||
|
||||
Migration Notes
|
||||
|
||||
- Before any migration, ejabberd system and database must be properly
|
||||
backed up.
|
||||
- When upgrading an ODBC-based installation, you will need to change the
|
||||
relational database schema. The following SQL commands must be run on the
|
||||
database:
|
||||
CREATE SEQUENCE spool_seq_seq;
|
||||
ALTER TABLE spool ADD COLUMN seq integer;
|
||||
ALTER TABLE spool ALTER COLUMN seq SET DEFAULT nextval('spool_seq_seq');
|
||||
UPDATE spool SET seq = DEFAULT;
|
||||
ALTER TABLE spool ALTER COLUMN seq SET NOT NULL;
|
||||
|
||||
References
|
||||
|
||||
Contributed tutorials of interest are:
|
||||
- Migration from Jabberd1.4 to ejabberd:
|
||||
http://ejabberd.jabber.ru/jabberd1-to-ejabberd
|
||||
- Migration from Jabberd2 to ejabberd:
|
||||
http://ejabberd.jabber.ru/jabberd2-to-ejabberd
|
||||
- Transport configuration for connecting to other networks:
|
||||
http://ejabberd.jabber.ru/tutorials-transports
|
||||
|
||||
END
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
Release Notes
|
||||
ejabberd 1.1.0
|
||||
24 April 2006
|
||||
|
||||
This document describes the main changes in ejabberd 1.1.0. This version
|
||||
introduce new features including support for new Jabber Enhancement
|
||||
Proposals and several performance improvements enabling deployments on an
|
||||
even larger scale than already possible.
|
||||
|
||||
ejabberd can be downloaded from the Process-one website:
|
||||
http://www.process-one.net/en/projects/ejabberd/
|
||||
|
||||
Detailed information can be found in the ejabberd Feature Sheet and User
|
||||
Guide which are available on the Process-one website:
|
||||
http://www.process-one.net/en/projects/ejabberd/docs.html
|
||||
|
||||
A complete list of changes is available from:
|
||||
http://support.process-one.net/secure/ReleaseNote.jspa?projectId=10011&styleName=Html&version=10025
|
||||
|
||||
|
||||
Recent changes include:
|
||||
|
||||
|
||||
New Jabber Enhancement Proposal support:
|
||||
|
||||
- JEP-0050: Ad-Hoc Commands.
|
||||
- JEP-0138: Stream Compression.
|
||||
- JEP-0175: SASL anonymous.
|
||||
|
||||
Anonymous login
|
||||
|
||||
- SASL anonymous.
|
||||
- Anonymous login for clients that do not yet support SASL Anonymous.
|
||||
|
||||
Relational database Support
|
||||
|
||||
- MySQL is now fully supported through ODBC and in native mode.
|
||||
- Various improvements to the native database interfaces.
|
||||
- The migration tool can use relational databases.
|
||||
|
||||
Multi-User Chat improvements
|
||||
|
||||
- Logging of room discussion to text file is now supported.
|
||||
- Better reconfiguration support.
|
||||
- Security oriented fixes.
|
||||
- Several improvements and updates to latest JEP-0045.
|
||||
|
||||
Performance scalability improvements for large clusters
|
||||
|
||||
- Improved session synchronisation management between cluster nodes.
|
||||
- Internal architecture has been reworked to use generalize Erlang/OTP
|
||||
framework usage.
|
||||
- Speed improvement on logger.
|
||||
- TCP/IP packet reception change for better network throttling and
|
||||
regulation.
|
||||
As a result, these improvements will reduce load on large scale deployments.
|
||||
|
||||
XMPP Protocol related improvements
|
||||
|
||||
- XML stanza size can be limited.
|
||||
- Messages are send to all resources with the same highest priority.
|
||||
|
||||
Documentation and Internationalization
|
||||
|
||||
- Documentation has been extended to cover more topics.
|
||||
- Translations have been updated.
|
||||
|
||||
Web interface
|
||||
|
||||
- XHTML 1.0 compliance.
|
||||
|
||||
Bugfixes
|
||||
|
||||
- This release contains many bugfixes on various areas such as Publish-Subscribe, build
|
||||
chain, installers, IRC gateway, ejabberdctl, amongst others.
|
||||
- Please refer to the ChangeLog file supplied with this release regarding
|
||||
all improvements in ejabberd.
|
||||
|
||||
|
||||
|
||||
Installation Notes
|
||||
|
||||
Supported Erlang Version
|
||||
|
||||
- You need at least Erlang/OTP R9C-2 to run ejabberd 1.1.0.
|
||||
|
||||
Installation
|
||||
|
||||
Installers are provided for Microsoft Windows, Linux/x86 and MacOSX/PPC.
|
||||
Installers can be retrieved from:
|
||||
http://www.process-one.net/en/projects/ejabberd/download.html
|
||||
|
||||
Migration Notes
|
||||
|
||||
- Before any migration, ejabberd system and database must be properly
|
||||
backed up.
|
||||
- The database schema has not been changed comparing to version 1.0.0 and
|
||||
consequently it does not require any migration.
|
||||
|
||||
|
||||
References
|
||||
|
||||
Contributed tutorials and documents of interest are:
|
||||
- Migration from Jabberd1.4, Jabberd2 and WPJabber to ejabberd:
|
||||
http://ejabberd.jabber.ru/migrate-to-ejabberd
|
||||
- Transport configuration for connecting to other networks:
|
||||
http://ejabberd.jabber.ru/tutorials-transports
|
||||
- Using ejabberd with MySQL native driver:
|
||||
http://support.process-one.net/doc/display/MESSENGER/Using+ejabberd+with+MySQL+native+driver
|
||||
- Anonymous User Support:
|
||||
http://support.process-one.net/doc/display/MESSENGER/Anonymous+users+support
|
||||
- Frequently Asked Questions:
|
||||
http://ejabberd.jabber.ru/faq
|
||||
|
||||
END
|
||||
@@ -1,119 +0,0 @@
|
||||
Release Notes
|
||||
ejabberd 1.1.1
|
||||
28 April 2006
|
||||
|
||||
This document describes the main changes in ejabberd 1.1.x. This version
|
||||
introduce new features including support for new Jabber Enhancement
|
||||
Proposals and several performance improvements enabling deployments on an
|
||||
even larger scale than already possible.
|
||||
|
||||
This release fix a security issue introduced in ejabberd 1.1.0. In SASL
|
||||
mode, anonymous login was enabled as a default. Upgrading ejabberd 1.1.0 to
|
||||
ejabberd 1.1.1 is highly recommended.
|
||||
|
||||
ejabberd can be downloaded from the Process-one website:
|
||||
http://www.process-one.net/en/projects/ejabberd/
|
||||
|
||||
Detailed information can be found in the ejabberd Feature Sheet and User
|
||||
Guide which are available on the Process-one website:
|
||||
http://www.process-one.net/en/projects/ejabberd/docs.html
|
||||
|
||||
A complete list of changes is available from:
|
||||
http://support.process-one.net/secure/ReleaseNote.jspa?projectId=10011&styleName=Html&version=10025
|
||||
|
||||
|
||||
Recent changes include:
|
||||
|
||||
|
||||
New Jabber Enhancement Proposal support:
|
||||
|
||||
- JEP-0050: Ad-Hoc Commands.
|
||||
- JEP-0138: Stream Compression.
|
||||
- JEP-0175: SASL anonymous.
|
||||
|
||||
Anonymous login
|
||||
|
||||
- SASL anonymous.
|
||||
- Anonymous login for clients that do not yet support SASL Anonymous.
|
||||
|
||||
Relational database Support
|
||||
|
||||
- MySQL is now fully supported through ODBC and in native mode.
|
||||
- Various improvements to the native database interfaces.
|
||||
- The migration tool can use relational databases.
|
||||
|
||||
Multi-User Chat improvements
|
||||
|
||||
- Logging of room discussion to text file is now supported.
|
||||
- Better reconfiguration support.
|
||||
- Security oriented fixes.
|
||||
- Several improvements and updates to latest JEP-0045.
|
||||
|
||||
Performance scalability improvements for large clusters
|
||||
|
||||
- Improved session synchronisation management between cluster nodes.
|
||||
- Internal architecture has been reworked to use generalize Erlang/OTP
|
||||
framework usage.
|
||||
- Speed improvement on logger.
|
||||
- TCP/IP packet reception change for better network throttling and
|
||||
regulation.
|
||||
As a result, these improvements will reduce load on large scale deployments.
|
||||
|
||||
XMPP Protocol related improvements
|
||||
|
||||
- XML stanza size can be limited.
|
||||
- Messages are send to all resources with the same highest priority.
|
||||
|
||||
Documentation and Internationalization
|
||||
|
||||
- Documentation has been extended to cover more topics.
|
||||
- Translations have been updated.
|
||||
|
||||
Web interface
|
||||
|
||||
- XHTML 1.0 compliance.
|
||||
|
||||
Bugfixes
|
||||
|
||||
- This release contains many bugfixes on various areas such as Publish-Subscribe, build
|
||||
chain, installers, IRC gateway, ejabberdctl, amongst others.
|
||||
- Please refer to the ChangeLog file supplied with this release regarding
|
||||
all improvements in ejabberd.
|
||||
|
||||
|
||||
|
||||
Installation Notes
|
||||
|
||||
Supported Erlang Version
|
||||
|
||||
- You need at least Erlang/OTP R9C-2 to run ejabberd 1.1.0.
|
||||
|
||||
Installation
|
||||
|
||||
Installers are provided for Microsoft Windows, Linux/x86 and MacOSX/PPC.
|
||||
Installers can be retrieved from:
|
||||
http://www.process-one.net/en/projects/ejabberd/download.html
|
||||
|
||||
Migration Notes
|
||||
|
||||
- Before any migration, ejabberd system and database must be properly
|
||||
backed up.
|
||||
- The database schema has not been changed comparing to version 1.0.0 and
|
||||
consequently it does not require any migration.
|
||||
|
||||
|
||||
References
|
||||
|
||||
Contributed tutorials and documents of interest are:
|
||||
- Migration from Jabberd1.4, Jabberd2 and WPJabber to ejabberd:
|
||||
http://ejabberd.jabber.ru/migrate-to-ejabberd
|
||||
- Transport configuration for connecting to other networks:
|
||||
http://ejabberd.jabber.ru/tutorials-transports
|
||||
- Using ejabberd with MySQL native driver:
|
||||
http://support.process-one.net/doc/display/MESSENGER/Using+ejabberd+with+MySQL+native+driver
|
||||
- Anonymous User Support:
|
||||
http://support.process-one.net/doc/display/MESSENGER/Anonymous+users+support
|
||||
- Frequently Asked Questions:
|
||||
http://ejabberd.jabber.ru/faq
|
||||
|
||||
END
|
||||
@@ -1,119 +0,0 @@
|
||||
Release Notes
|
||||
ejabberd 1.1.2
|
||||
27 September 2006
|
||||
|
||||
This document describes the main changes in ejabberd 1.1.2.
|
||||
|
||||
This version is a major improvement over ejabberd 1.1.1, improving the
|
||||
overall behaviour of the server in many areas. Users of ejabberd 1.1.1
|
||||
should upgrade to this new release for improved robustness and compliance.
|
||||
|
||||
ejabberd can be downloaded from the Process-one website:
|
||||
http://www.process-one.net/en/projects/ejabberd/
|
||||
|
||||
Detailed information can be found in the Feature Sheet and in the
|
||||
Installation and Operation Guide which are both available on the
|
||||
Process-one website:
|
||||
http://www.process-one.net/en/projects/ejabberd/docs.html
|
||||
|
||||
ejabberd includes 44 improvements. A complete list of changes can be
|
||||
retrieved from:
|
||||
http://redir.process-one.net/ejabberd-1.1.2
|
||||
|
||||
|
||||
Recent changes include:
|
||||
|
||||
LDAP Improvements
|
||||
|
||||
- Major improvements have been made on the LDAP module. It is now more
|
||||
flexible and more robust.
|
||||
|
||||
HTTP Polling Fixes
|
||||
|
||||
- The HTTP polling modules have been fixed and improved: the connections are
|
||||
closed properly and polled messages cannot be lost anymore.
|
||||
|
||||
Roster Management Improvement
|
||||
|
||||
- Roster management improvements increase reliability, especially in cases
|
||||
where users are on different servers.
|
||||
- Shared rosters are more reliable.
|
||||
|
||||
Improved Robustness
|
||||
|
||||
- It is now possible to limit the number of opened connections for a single
|
||||
user.
|
||||
|
||||
Relational databases
|
||||
|
||||
- Database support: Microsoft SQL Server is now officially supported in ODBC
|
||||
mode.
|
||||
|
||||
Publish-Subscribe Improvement
|
||||
|
||||
- Restricting node creation with a dedicated ACL rule is now possible.
|
||||
|
||||
Localization
|
||||
|
||||
- A Czech translation has been added.
|
||||
- Translations have been updated.
|
||||
|
||||
Binary Installer
|
||||
|
||||
- New binary installer for Windows including all requirements.
|
||||
- Improved installers for Linux and MacOSX (PowerPC)
|
||||
|
||||
XMPP Compliancy
|
||||
|
||||
- Some protocol compliance fix have been added, after the Portland XMPP
|
||||
Interop Meeting in July.
|
||||
|
||||
Miscelanous
|
||||
|
||||
- MUC have been improved (logging rendering).
|
||||
- The command line tool ejabberdctl has been improved.
|
||||
- The build chain has been improved, including MacOSX support.
|
||||
- The documentation has been improved and updated to describe the new
|
||||
features.
|
||||
|
||||
Bugfixes
|
||||
|
||||
- Anonymous login bugfixes.
|
||||
- Please refer to the ChangeLog file supplied with this release regarding
|
||||
all improvements in ejabberd.
|
||||
|
||||
|
||||
Installation Notes
|
||||
|
||||
Supported Erlang Version
|
||||
|
||||
- You need at least Erlang/OTP R9C-2 to run ejabberd 1.1.2.
|
||||
- The recommanded version is Erlang/OTP R10B-10.
|
||||
- Erlang/OTP R11B has not yet been fully certified for ejabberd.
|
||||
|
||||
Installation
|
||||
|
||||
Installers are provided for Microsoft Windows, Linux/x86 and MacOSX/PPC.
|
||||
They can be retrieved from:
|
||||
http://www.process-one.net/en/projects/ejabberd/download.html
|
||||
|
||||
Migration Notes
|
||||
|
||||
- Before any migration, ejabberd system and database must be properly
|
||||
backed up.
|
||||
- The relational database schema has changed between version 1.1.1 and
|
||||
1.1.2. An "askmessage" column needs to be added in the "rosterusers" table
|
||||
to perform the migration.
|
||||
|
||||
|
||||
References
|
||||
|
||||
Contributed tutorials and documents of interest are:
|
||||
- Migration from other XMPP servers to ejabberd:
|
||||
http://ejabberd.jabber.ru/migrate-to-ejabberd
|
||||
- Transport configuration for connecting to other networks:
|
||||
http://ejabberd.jabber.ru/tutorials-transports
|
||||
- Frequently Asked Questions:
|
||||
http://ejabberd.jabber.ru/faq
|
||||
|
||||
END
|
||||
@@ -1,50 +0,0 @@
|
||||
Release Notes
|
||||
ejabberd 1.1.3
|
||||
2 February 2007
|
||||
|
||||
This document describes the change in ejabberd 1.1.3.
|
||||
|
||||
This version is a security improvement over ejabberd 1.1.2, solving
|
||||
the mod_roster_odbc SQL injection issue.
|
||||
|
||||
Users of ejabberd 1.1.2 that are using mod_roster_odbc MUST upgrade to
|
||||
this new release.
|
||||
|
||||
ejabberd can be downloaded from the Process-one website:
|
||||
http://www.process-one.net/en/ejabberd/
|
||||
|
||||
Detailed information can be found in the Feature Sheet and in the
|
||||
Installation and Operation Guide which are both available on the
|
||||
Process-one website:
|
||||
http://www.process-one.net/en/ejabberd/docs.html
|
||||
|
||||
Installation Notes
|
||||
|
||||
Supported Erlang Version
|
||||
|
||||
- You need at least Erlang/OTP R9C-2 to run ejabberd 1.1.3.
|
||||
- The recommanded version is Erlang/OTP R11B-3.
|
||||
|
||||
Installation
|
||||
|
||||
Installers are provided for Microsoft Windows, Linux/x86 and MacOSX/PPC.
|
||||
They can be retrieved from:
|
||||
http://www.process-one.net/en/ejabberd/download.html
|
||||
|
||||
Migration Notes
|
||||
|
||||
- Before any migration, ejabberd system and database must be properly
|
||||
backed up.
|
||||
|
||||
|
||||
References
|
||||
|
||||
Contributed tutorials and documents of interest are:
|
||||
- Migration from other XMPP servers to ejabberd:
|
||||
http://ejabberd.jabber.ru/migrate-to-ejabberd
|
||||
- Transport configuration for connecting to other networks:
|
||||
http://ejabberd.jabber.ru/tutorials-transports
|
||||
- Frequently Asked Questions:
|
||||
http://ejabberd.jabber.ru/faq
|
||||
|
||||
END
|
||||
@@ -1,2 +0,0 @@
|
||||
% Define ejabberd version here.
|
||||
\newcommand{\version}{1.1.2}
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 157 KiB |
@@ -1,50 +0,0 @@
|
||||
#!/usr/local/bin/perl
|
||||
|
||||
use Unix::Syslog qw(:macros :subs);
|
||||
|
||||
my $domain = $ARGV[0] || "example.com";
|
||||
|
||||
while(1)
|
||||
{
|
||||
# my $rin = '',$rout;
|
||||
# vec($rin,fileno(STDIN),1) = 1;
|
||||
# $ein = $rin;
|
||||
# my $nfound = select($rout=$rin,undef,undef,undef);
|
||||
|
||||
my $buf = "";
|
||||
syslog LOG_INFO,"waiting for packet";
|
||||
my $nread = sysread STDIN,$buf,2;
|
||||
do { syslog LOG_INFO,"port closed"; exit; } unless $nread == 2;
|
||||
my $len = unpack "n",$buf;
|
||||
my $nread = sysread STDIN,$buf,$len;
|
||||
|
||||
my ($op,$user,$password) = split /:/,$buf;
|
||||
#$user =~ s/\./\//og;
|
||||
my $jid = "$user\@$domain";
|
||||
my $result;
|
||||
|
||||
syslog(LOG_INFO,"request (%s)", $op);
|
||||
|
||||
SWITCH:
|
||||
{
|
||||
$op eq 'auth' and do
|
||||
{
|
||||
$result = 1;
|
||||
},last SWITCH;
|
||||
|
||||
$op eq 'setpass' and do
|
||||
{
|
||||
$result = 1;
|
||||
},last SWITCH;
|
||||
|
||||
$op eq 'isuser' and do
|
||||
{
|
||||
# password is null. Return 1 if the user $user\@$domain exitst.
|
||||
$result = 1;
|
||||
},last SWITCH;
|
||||
};
|
||||
my $out = pack "nn",2,$result ? 1 : 0;
|
||||
syswrite STDOUT,$out;
|
||||
}
|
||||
|
||||
closelog;
|
||||
@@ -1,77 +0,0 @@
|
||||
<!-- aim-transport.xml -->
|
||||
|
||||
<jabber>
|
||||
|
||||
<!--
|
||||
You need to add elogger and rlogger entries when using ejabberd.
|
||||
In this case the transport will do the logging.
|
||||
-->
|
||||
|
||||
<log id='elogger'>
|
||||
<host/>
|
||||
<logtype/>
|
||||
<format>%d: [%t] (%h): %s</format>
|
||||
<file>/var/log/jabber/aim-transport-error.log</file>
|
||||
</log>
|
||||
|
||||
<log id='rlogger'>
|
||||
<host/>
|
||||
<logtype>record</logtype>
|
||||
<format>%d %h %s</format>
|
||||
<file>/var/log/jabber/aim-transport-record.log</file>
|
||||
</log>
|
||||
|
||||
<!--
|
||||
ejabberd do not provide XDB services.
|
||||
xdb_file.so is loaded in to handle all XDB requests.
|
||||
-->
|
||||
|
||||
<xdb id="xdb">
|
||||
<host/>
|
||||
<load>
|
||||
<xdb_file>/usr/local/lib/jabber/libjabberdxdbfile.so</xdb_file> <!-- This file is part of jabberd-1.4.x. -->
|
||||
</load>
|
||||
<xdb_file xmlns="jabber:config:xdb_file">
|
||||
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
|
||||
</xdb_file>
|
||||
</xdb>
|
||||
|
||||
<!--
|
||||
Make sure that all host names here are resolveable via DNS if you
|
||||
want the transport to be available to the public.
|
||||
-->
|
||||
|
||||
<service id='aim.SERVER.COM'>
|
||||
<!-- aim-transport configuration. -->
|
||||
<aimtrans xmlns='jabber:config:aimtrans'>
|
||||
<vCard>
|
||||
<FN>AIM/ICQ Transport</FN>
|
||||
<DESC>This is the AIM/ICQ Transport.</DESC>
|
||||
<MAIL>EMAIL@ADDRESS.COM</MAIL>
|
||||
<URL>http://aim-transport.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
<charset>cp1252</charset>
|
||||
</aimtrans>
|
||||
<!-- aim-transport module. -->
|
||||
<load>
|
||||
<aim_transport>/usr/local/lib/jabber/aim-transport.so</aim_transport>
|
||||
</load>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The settings below have to match the settings you made
|
||||
in your ejabberd.cfg configuration file.
|
||||
-->
|
||||
|
||||
<service id="icq-linker">
|
||||
<uplink/>
|
||||
<connect>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5233</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
</service>
|
||||
|
||||
<pidfile>/var/run/jabber/aim-transport.pid</pidfile>
|
||||
|
||||
</jabber>
|
||||
@@ -1,136 +0,0 @@
|
||||
<!-- ile.xml -->
|
||||
|
||||
<config>
|
||||
<jabber>
|
||||
<server>127.0.0.1</server>
|
||||
<port>5238</port>
|
||||
<secret>SECRET</secret>
|
||||
<service>ile.SERVER.COM</service>
|
||||
<connectsleep>7</connectsleep> <!-- seconds to wait if we get disconnected -->
|
||||
<language>en</language>
|
||||
<vCard>
|
||||
<FN>I Love Email</FN>
|
||||
<DESC>With this service you can receive email notifications.
|
||||
|
||||
Security warning: Be careful when using this. Your password will travel in clear from your client to your jabber server if you don't use SSL and it will probably travel in clear from the jabber server to your email server. Use with care. This shouldn't be an issue in your Intranet, but it is if you use an ILE installed in a foreign jabber server.</DESC>
|
||||
<MAIL>EMAIL@ADDRESS.COM</MAIL>
|
||||
<URL>http://ile.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
</jabber>
|
||||
|
||||
<debug>
|
||||
<file>/var/log/jabber/ile.log</file>
|
||||
<level>1</level> <!-- man Net::Jabber::Debug -->
|
||||
</debug>
|
||||
|
||||
<mail>
|
||||
<checkinterval>10</checkinterval> <!-- in minutes -->
|
||||
<timeout>20</timeout> <!-- timeout for IMAP/POP connection, in seconds -->
|
||||
</mail>
|
||||
|
||||
<files>
|
||||
<users>/var/spool/jabber/ile.SERVER.COM/users.db</users>
|
||||
<passwords>/var/spool/jabber/ile.SERVER.COM/passwords.db</passwords>
|
||||
<hosts>/var/spool/jabber/ile.SERVER.COM/hosts.db</hosts>
|
||||
<types>/var/spool/jabber/ile.SERVER.COM/types.db</types>
|
||||
<notifyxa>/var/spool/jabber/ile.SERVER.COM/notifyxa.db</notifyxa>
|
||||
<notifydnd>/var/spool/jabber/ile.SERVER.COM/notifydnd.db</notifydnd>
|
||||
<urls>/var/spool/jabber/ile.SERVER.COM/urls.db</urls>
|
||||
</files>
|
||||
|
||||
<form>
|
||||
<en>
|
||||
<instructions>Please fill in the fields,according to your email account settings and notification preferences</instructions>
|
||||
<title>ILE: Email notification service</title>
|
||||
<email_options>Email account settings</email_options>
|
||||
<user>Username</user>
|
||||
<pass>Password</pass>
|
||||
<host>Hostname</host>
|
||||
<type>Type</type>
|
||||
<newmail>You have received NUM email messages since last time I checked, which was CHECKINTERVAL minutes ago.</newmail>
|
||||
<errorcheck>There was an error while trying to check mail for ACCOUNT.</errorcheck>
|
||||
<notify_options>Notification Options</notify_options>
|
||||
<notifyxa>Notify even when Xtended Away (XA)</notifyxa>
|
||||
<notifydnd>Notify even when Do Not Disturb (DND)</notifydnd>
|
||||
<webmail_url>Webmail URL</webmail_url>
|
||||
<webmail_login>Login to ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: an email notifier component: http://ile.jabberstudio.org</iledesc>
|
||||
</en>
|
||||
|
||||
<es>
|
||||
<instructions>Por favor, rellene los campos del formulario.</instructions>
|
||||
<title>ILE: Servicio de notificación de correo</title>
|
||||
<email_options>Configuración de la cuenta de correo</email_options>
|
||||
<user>Usuario</user>
|
||||
<pass>Clave</pass>
|
||||
<host>Host</host>
|
||||
<type>Tipo</type>
|
||||
<newmail>Ha recibido NUM email(s) desde la última comprobación que fue hace CHECKINTERVAL minutos</newmail>
|
||||
<errorcheck>Ha habido un error en la comprobación del correo para la cuenta ACCOUNT.</errorcheck>
|
||||
<notify_options>Opciones de notificación</notify_options>
|
||||
<notifyxa>Notificar incluso si muy ausente (XA)</notifyxa>
|
||||
<notifydnd>Notificar incluso si no molestar (DND)</notifydnd>
|
||||
<webmail_url>Webmail URL</webmail_url>
|
||||
<webmail_login>Leer correo de ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: un notificador de nuevo email - http://ile.jabberstudio.org</iledesc>
|
||||
</es>
|
||||
|
||||
<ca>
|
||||
<instructions>Ompli els camps del formulari.</instructions>
|
||||
<title>ILE: Servei de notificació de nou email</title>
|
||||
<email_options>Dades del compte de mail</email_options>
|
||||
<user>Usuari</user>
|
||||
<pass>Clau</pass>
|
||||
<host>Host</host>
|
||||
<type>Tipus</type>
|
||||
<newmail>Ha rebut NUM email(s) des de la última comprobació que va ser fa CHECKINTERVAL minuts.</newmail>
|
||||
<errorcheck>S'ha produit un error en la comprobació del correu per al compte ACCOUNT.</errorcheck>
|
||||
<notify_options>Opcions de notificació</notify_options>
|
||||
<notifyxa>Notificar si molt absent (XA)</notifyxa>
|
||||
<notifydnd>Notificar si no molestar (DND)</notifydnd>
|
||||
<webmail_url>Webmail URL</webmail_url>
|
||||
<webmail_login>Llegir correu de ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: un notificador de nou email - http://ile.jabberstudio.org</iledesc>
|
||||
</ca>
|
||||
|
||||
<ro>
|
||||
<!-- Contributed by Adrian Rappa -->
|
||||
<instructions>Va rog completati urmatoarele campuri</instructions>
|
||||
<title>I Love Email: new email notification service</title>
|
||||
<email_options>Email account settings</email_options>
|
||||
<user>Nume utilizator</user>
|
||||
<pass>Parola</pass>
|
||||
<host>Nume gazda</host>
|
||||
<type>Tip</type>
|
||||
<newmail>Ati primit NUM mesaj(e) de la ultima verificare, care a fost acum CHECKINTERVAL minute.</newmail>
|
||||
<errorcheck>A fost eroare in timp ce incercam sa verific posta pentru ACCOUNT.</errorcheck>
|
||||
<notify_options>Notification Options</notify_options>
|
||||
<notifyxa>Notify even when Xtended Away (XA)</notifyxa>
|
||||
<notifydnd>Notify even when Do Not Disturb (DND)</notifydnd>
|
||||
<webmail_url>Webmail URL</webmail_url>
|
||||
<webmail_login>Login to ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: an email notifier component: http://ile.jabberstudio.org</iledesc>
|
||||
</ro>
|
||||
|
||||
<nl>
|
||||
<!-- Contributed by Sander Devrieze -->
|
||||
<instructions>Vul volgende velden in.</instructions>
|
||||
<title>ILE: Dienst voor e-mailnotificaties</title>
|
||||
<email_options>Instellingen van e-mailaccount</email_options>
|
||||
<user>Gebruikersnaam</user>
|
||||
<pass>Wachtwoord</pass>
|
||||
<host>Inkomende mailserver</host>
|
||||
<type>Type verbinding</type>
|
||||
<newmail>U hebt NUM berichten ontvangen sinds CHECKINTERVAL minuten geleden.</newmail>
|
||||
<errorcheck>Fout tijdens controle op nieuwe e-mails bij ACCOUNT. ILE zal deze account niet meer opnieuw controleren tot u uw registratiegegevens wijzigt of opnieuw aanmeldt.</errorcheck>
|
||||
<notify_options>Notificatie-instellingen</notify_options>
|
||||
<notifyxa>Notificeer ook in de status Niet Beschikbaar (XA)</notifyxa>
|
||||
<notifydnd>Notificeer ook in de status Niet Storen (DND)</notifydnd>
|
||||
<webmail_url>URL van webmail</webmail_url>
|
||||
<webmail_login>Aanmelden op ACCOUNT</webmail_login>
|
||||
<iledesc>ILE: een dienst om e-mailnotificaties te ontvangen: http://ile.jabberstudio.org</iledesc>
|
||||
</nl>
|
||||
|
||||
</form>
|
||||
|
||||
</config>
|
||||
@@ -1,149 +0,0 @@
|
||||
<jggtrans>
|
||||
|
||||
<service jid="gg.SERVER.COM"/>
|
||||
|
||||
<!-- This connects the jabber-gg-transport process to ejabberd. -->
|
||||
<connect id="gglinker">
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5237</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
|
||||
<register>
|
||||
<!-- This tag contains the message displayed to users at registration time.
|
||||
You can use <p/> and/or <br/> to break lines. Multiple spaces and newlines
|
||||
are converted to just one, so formatting of config file doesn't really matter. -->
|
||||
<instructions>
|
||||
Fill in your GG number (after "username")
|
||||
and password to register on the transport.
|
||||
<p/>To change your information in the GaduGadu directory you need to fill in the other fields.
|
||||
<p/>To remove registration you need to leave the form blank.
|
||||
</instructions>
|
||||
</register>
|
||||
|
||||
<search>
|
||||
<!-- This tag contains the message displayed to users at search time. -->
|
||||
<instructions>
|
||||
To search people:<br/>
|
||||
First fill in surname or family name, nickname, city, birthyear or range of birthyears (eg. 1950-1960)
|
||||
and gender (you may fill in more fields at once).<br/>
|
||||
or<br/>
|
||||
Fill in phone number<br/>
|
||||
or<br/>
|
||||
Fill in the GG number of the person you are searching.
|
||||
</instructions>
|
||||
</search>
|
||||
|
||||
<gateway>
|
||||
<!-- This is message, that may be displayed to user when adding gg contact. -->
|
||||
<desc>Please fill in the GaduGadu number of the person you want to add.</desc>
|
||||
<!-- And this is the prompt for GG number. -->
|
||||
<prompt>GG Nummer</prompt>
|
||||
</gateway>
|
||||
|
||||
<vCard>
|
||||
<FN>Gadu-Gadu Transport</FN>
|
||||
<DESC>This is the Gadu-Gadu Transport.</DESC>
|
||||
<EMAIL>EMAIL@ADDRESS.COM</EMAIL>
|
||||
<URL>http://www.jabberstudio.org/projects/jabber-gg-transport/</URL>
|
||||
</vCard>
|
||||
|
||||
<!-- Default user locale (language).
|
||||
Empty means system locale setting,
|
||||
no (or commented-out) <default_locale> tag means no translations. -->
|
||||
<!-- <default_locale>pl_PL</default_locale> -->
|
||||
|
||||
<!-- Logger configuration.
|
||||
You may configure one logger of type "syslog" and/or one of type "file".
|
||||
You may also not configure logging at all. -->
|
||||
<log type="syslog" facility="local0"/>
|
||||
<log type="file">/var/log/jabber/jabber-gg-transport.log</log>
|
||||
|
||||
<!-- Uncomment this, if you want proxy to be used for Gadu-Gadu connection. -->
|
||||
<!--
|
||||
<proxy>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>8080</port>
|
||||
</proxy>
|
||||
-->
|
||||
|
||||
<!-- You can change these values according to your needs. -->
|
||||
<conn_timeout>60</conn_timeout>
|
||||
<ping_interval>10</ping_interval>
|
||||
|
||||
<!-- Gadu-Gadu server doesn't seem to answer pings anymore :-(
|
||||
So let's give it 10 year :-) -->
|
||||
<pong_timeout>315360000</pong_timeout>
|
||||
|
||||
<!-- This time after disconnection from Gadu-Gadu server the transport
|
||||
will try to connect again. -->
|
||||
<reconnect>300</reconnect>
|
||||
|
||||
<!-- How long to wait before restart, after jabber server connection is broken
|
||||
negative value means, that jggtrans should terminate. -->
|
||||
<restart_timeout>60</restart_timeout>
|
||||
|
||||
<!-- Delay between the unavailable presence is received from user and loggin out
|
||||
from Gadu-Gadu - for nice <presence type="invisible"/> support. -->
|
||||
<disconnect_delay>5</disconnect_delay>
|
||||
|
||||
<!-- list of Gadu-Gadu servers to use.
|
||||
<hub/> means "use GG hub to find server"
|
||||
<server/> tag should contain server address and may contain "port"
|
||||
attribute with port number. When TLS is available (supported by libgadu)
|
||||
it will be used unless "tls" attribute is set to "no". Please notice,
|
||||
that not all servers will accept TLS connections.
|
||||
Servers (including hub) are tried in order as they appear in <servers/>
|
||||
element.
|
||||
A reasonable default server list is hardcoded in jggtrans.
|
||||
-->
|
||||
<!--
|
||||
<servers>
|
||||
<hub/>
|
||||
<server port="443">217.17.41.90</server>
|
||||
<server tls="no">217.17.41.85</server>
|
||||
<server tls="no">217.17.41.88</server>
|
||||
</servers>
|
||||
-->
|
||||
|
||||
<!-- Spool directory. This is the place, where user info will be stored. -->
|
||||
<!-- Be careful about permissions - users' Gadu-Gadu passwords are stored there. -->
|
||||
<spool>/var/spool/jabber/gg.SERVER.COM/</spool>
|
||||
|
||||
<!-- Where to store pid file. This tag is optional. -->
|
||||
<pidfile>/var/run/jabber/jabber-gg-transport.pid</pidfile>
|
||||
|
||||
<!-- jid allowed to do some administrative task (eg. discovering online users).
|
||||
May be used multiple times. -->
|
||||
<admin>GG_TRANSPORT_ADMIN@SERVER.COM</admin>
|
||||
|
||||
<!-- ACL gives detailed access control to the transport. -->
|
||||
<acl>
|
||||
<!-- Example entries: -->
|
||||
|
||||
<allow who="admin@SERVER.COM" what="iq/query?xmlns=http://jabber.org/protocol/stats"/>
|
||||
<!-- will allow statistics gathering to admin@SERVER.COM -->
|
||||
|
||||
<deny who="*" what="iq/query?xmlns=http://jabber.org/protocol/stats"/>
|
||||
<!-- will deny statistics gathering for anybody else -->
|
||||
|
||||
<!-- <allow who="*@SERVER.COM"/> -->
|
||||
<!-- will allow anything else to users from "SERVER.COM" -->
|
||||
|
||||
<!-- <deny what="iq/query?xmlns=jabber:x:register"/> -->
|
||||
<!-- will deny registration for all other users -->
|
||||
|
||||
<!-- <allow what="presence"/> -->
|
||||
<!-- allow presence from anybody -->
|
||||
|
||||
<!-- <allow what="iq"/> -->
|
||||
<!-- allow iq from anybody -->
|
||||
|
||||
<!-- <allow what="message"/> -->
|
||||
<!-- allow message from anybody -->
|
||||
|
||||
<!-- <deny/> -->
|
||||
<!-- will deny anything else -->
|
||||
</acl>
|
||||
|
||||
</jggtrans>
|
||||
@@ -1,128 +0,0 @@
|
||||
<!-- jit.xml -->
|
||||
|
||||
<jabber>
|
||||
|
||||
<!--
|
||||
You need to add elogger and rlogger entries here when using ejabberd.
|
||||
In this case the transport will do the logging.
|
||||
-->
|
||||
|
||||
<log id='elogger'>
|
||||
<host/>
|
||||
<logtype/>
|
||||
<file>/var/log/jabber/jit-error</file> <!-- WPJabber logs with date. -->
|
||||
</log>
|
||||
|
||||
<log id='rlogger'>
|
||||
<host/>
|
||||
<logtype>record</logtype>
|
||||
<file>/var/log/jabber/jit-record</file> <!-- WPJabber logs with date. -->
|
||||
</log>
|
||||
|
||||
<!--
|
||||
ejabberd do not provide XDB services.
|
||||
xdb_file-jit.so (the renamed xdb_file.so from WPJabber) is
|
||||
loaded in to handle all XDB requests.
|
||||
Read also the documentation in xdb_file/README from the JIT package.
|
||||
-->
|
||||
|
||||
<xdb id="xdb">
|
||||
<host/>
|
||||
<load>
|
||||
<xdb_file>/usr/local/lib/jabber/xdb_file.so</xdb_file> <!-- The xdb_file.so from WPJabber/JIT. -->
|
||||
</load>
|
||||
<xdb_file xmlns="jabber:config:xdb_file">
|
||||
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
|
||||
</xdb_file>
|
||||
</xdb>
|
||||
|
||||
<!--
|
||||
Make sure that all host names here are resolveable via DNS if you
|
||||
want the transport to be available to the public.
|
||||
-->
|
||||
|
||||
<service id="icq.SERVER.COM">
|
||||
<!--
|
||||
Replace SERVER.COM with the same as above to enable sms.
|
||||
-->
|
||||
<host>sms.icq.SERVER.COM</host>
|
||||
<!-- JIT configuration. -->
|
||||
<icqtrans xmlns="jabber:config:icqtrans">
|
||||
<sms>
|
||||
<host>sms.icq.SERVER.COM</host>
|
||||
<!-- Status of virtual "sms-contacts". -->
|
||||
<show>away</show>
|
||||
<status/>
|
||||
</sms>
|
||||
<instructions>Fill in your UIN and password.</instructions>
|
||||
<search>Search ICQ users.</search>
|
||||
<vCard>
|
||||
<FN>ICQ Transport (JIT)</FN>
|
||||
<DESC>This is the Jabber ICQ Transport.</DESC>
|
||||
<MAIL>EMAIL@ADDRESS.COM</MAIL>
|
||||
<URL>http://jit.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
<!-- Hashtable for users. -->
|
||||
<prime>3907</prime>
|
||||
<!-- Send messages from ICQ as chat to Jabber clients. -->
|
||||
<chat/>
|
||||
<!-- Enable this for ICQ web presence. -->
|
||||
<web/>
|
||||
<!--
|
||||
If you don't want jabber:x:data forms
|
||||
in reg and search uncomment this tag
|
||||
(Not recomended).
|
||||
-->
|
||||
<no_xdata/>
|
||||
<!--
|
||||
This tag is necessary when using ejabberd.
|
||||
In this way JIT will have its own contact list.
|
||||
-->
|
||||
<own_roster/>
|
||||
<!--
|
||||
When present, this tag will tell JIT not to try to
|
||||
get the user's roster (which will take a bit of time
|
||||
to fail in scenarios described above).
|
||||
-->
|
||||
<no_jabber_roster/>
|
||||
<!-- File with stats. -->
|
||||
<user_count_file>/var/spool/jabber/jit-count</user_count_file>
|
||||
<!--
|
||||
Interval beetween checking sessions: ping, messages, acks.
|
||||
-->
|
||||
<session_check>5</session_check>
|
||||
<!-- Reconnect retries. -->
|
||||
<reconnects>5</reconnects>
|
||||
<!--
|
||||
Time in sec when session can be inactive, 0=disabled.
|
||||
-->
|
||||
<session_timeout>18000</session_timeout>
|
||||
<charset>windows-1252</charset>
|
||||
<server>
|
||||
<host port="5190">login.icq.com</host>
|
||||
</server>
|
||||
</icqtrans>
|
||||
<!-- JIT module. -->
|
||||
<load>
|
||||
<icqtrans>/usr/local/lib/jabber/jit.so</icqtrans>
|
||||
</load>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The settings below have to match the settings you made
|
||||
in your ejabberd.cfg configuration file.
|
||||
-->
|
||||
|
||||
<service id="icq-linker">
|
||||
<host>SERVER.COM</host>
|
||||
<uplink/>
|
||||
<connect>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5234</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
</service>
|
||||
|
||||
<pidfile>/var/run/jabber/jit.pid</pidfile>
|
||||
|
||||
</jabber>
|
||||
@@ -1,118 +0,0 @@
|
||||
<!-- msn-transport.xml -->
|
||||
|
||||
<jabber>
|
||||
|
||||
<!--
|
||||
You need to add elogger and rlogger entries here when using ejabberd.
|
||||
In this case the transport will do the logging.
|
||||
-->
|
||||
|
||||
<log id='elogger'>
|
||||
<host/>
|
||||
<logtype/>
|
||||
<format>%d: [%t] (%h): %s</format>
|
||||
<file>/var/log/jabber/msn-transport-error.log</file>
|
||||
</log>
|
||||
|
||||
<log id='rlogger'>
|
||||
<host/>
|
||||
<logtype>record</logtype>
|
||||
<format>%d %h %s</format>
|
||||
<file>/var/log/jabber/msn-transport-record.log</file>
|
||||
</log>
|
||||
|
||||
<!--
|
||||
ejabberd do not provide XDB services.
|
||||
xdb_file.so is loaded in to handle all XDB requests.
|
||||
-->
|
||||
|
||||
<xdb id="xdb">
|
||||
<host/>
|
||||
<load>
|
||||
<xdb_file>/usr/local/lib/jabber/libjabberdxdbfile.so</xdb_file>
|
||||
</load>
|
||||
<xdb_file xmlns="jabber:config:xdb_file">
|
||||
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
|
||||
</xdb_file>
|
||||
</xdb>
|
||||
|
||||
<!--
|
||||
Make sure that all host names here are resolveable via DNS if you
|
||||
want the transport to be available to the public.
|
||||
-->
|
||||
|
||||
<service id="msn.SERVER.COM">
|
||||
<!-- msn-transport configuration. -->
|
||||
<msntrans xmlns="jabber:config:msntrans">
|
||||
<instructions>Fill in your MSN account and password (eg: user1@hotmail.com). A nickname is optional.</instructions>
|
||||
<vCard>
|
||||
<FN>MSN Transport</FN>
|
||||
<DESC>This is the MSN Transport.</DESC>
|
||||
<EMAIL>EMAIL@ADDRESS.COM</EMAIL>
|
||||
<URL>http://msn-transport.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
<!--
|
||||
Conference support allows you to create groupchat rooms on the
|
||||
msn-transport and invite MSN users to join.
|
||||
-->
|
||||
<conference id="conference.msn.SERVER.COM">
|
||||
<!--
|
||||
This will make MSN transport invite you to a special groupchat
|
||||
room when more then one user joins a normal one-on-one session.
|
||||
Joining this room will make MSN transport "switch" the session
|
||||
into groupchat mode. If you ignore it, MSN transport will
|
||||
continue to send the messages as one-on-one chats.
|
||||
-->
|
||||
<invite>More than one user entered this chat session. Enter this room to switch to groupchat modus.</invite>
|
||||
<notice>
|
||||
<join> is available</join>
|
||||
<leave> has leaved the room</leave>
|
||||
</notice>
|
||||
</conference>
|
||||
<!-- Enable Hotmail inbox notification. -->
|
||||
<headlines/>
|
||||
<!--
|
||||
Enable fancy friendly names
|
||||
If the user enters a nickname upon registration, and the user has
|
||||
a status message, their MSN friendly name will be "nickname - status message".
|
||||
|
||||
If the user does not enter a nickname on registration, but they do have
|
||||
a status message, their friendly name will just be their status message.
|
||||
|
||||
If the user did enter a nickname on registration, but they have a blank status message,
|
||||
then their friendly name will just be the registered nickname.
|
||||
|
||||
If the user did not enter a nickname on registration, and they have a blank status message,
|
||||
their nickname will just be the username portion of their JID.
|
||||
|
||||
If the above chosen friendly name is too long, then it will be truncated and "..." placed
|
||||
at the end. MSN only supports friendly names of 128 characters, so this is unavoidable.
|
||||
|
||||
If this is disabled, then the registered nick is always sent as the MSN friendly name,
|
||||
or if that is blank, the username portion of their JID is sent instead.
|
||||
-->
|
||||
<fancy_friendly/>
|
||||
</msntrans>
|
||||
<!-- msn-transport module. -->
|
||||
<load>
|
||||
<msntrans>/usr/local/lib/jabber/msn-transport.so</msntrans>
|
||||
</load>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The settings below have to match the settings you made
|
||||
in your ejabberd.cfg configuration file.
|
||||
-->
|
||||
|
||||
<service id="msn-linker">
|
||||
<uplink/>
|
||||
<connect>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5235</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
</service>
|
||||
|
||||
<pidfile>/var/run/jabber/msn-transport.pid</pidfile>
|
||||
|
||||
</jabber>
|
||||
@@ -1,86 +0,0 @@
|
||||
<!-- yahoo-transport-2.xml -->
|
||||
|
||||
<jabber>
|
||||
|
||||
<!--
|
||||
You need to add the elogger entry here when using ejabberd.
|
||||
In this case the transport will do the logging.
|
||||
-->
|
||||
|
||||
<log id='elogger'>
|
||||
<host/>
|
||||
<logtype/>
|
||||
<format>%d: [%t] (%h): %s</format>
|
||||
<file>/var/log/jabber/yahoo-transport-2-error.log</file>
|
||||
<stderr/>
|
||||
</log>
|
||||
|
||||
<!--
|
||||
ejabberd do not provide XDB services.
|
||||
xdb_file.so is loaded in to handle all XDB requests.
|
||||
-->
|
||||
|
||||
<xdb id="xdb">
|
||||
<host/>
|
||||
<load>
|
||||
<xdb_file>/usr/local/lib/jabber/libjabberdxdbfile.so</xdb_file>
|
||||
</load>
|
||||
<xdb_file xmlns="jabber:config:xdb_file">
|
||||
<spool><jabberd:cmdline flag='s'>/var/spool/jabber</jabberd:cmdline></spool>
|
||||
</xdb_file>
|
||||
</xdb>
|
||||
|
||||
<!--
|
||||
Make sure that all host names here are resolveable via DNS if you
|
||||
want the transport to be available to the public.
|
||||
-->
|
||||
|
||||
<service id="yahoo.SERVER.COM">
|
||||
<!-- yahoo-transport-2 configuration. -->
|
||||
<config xmlns="jabber:config:yahoo">
|
||||
<vCard>
|
||||
<NAME>Yahoo! Transport</NAME>
|
||||
<FN>vCard not implemented in current version</FN>
|
||||
<DESC>This is the Yahoo! transport.</DESC>
|
||||
<MAIL>EMAIL@ADDRESS.COM</MAIL>
|
||||
<URL>http://yahoo-transport-2.jabberstudio.org/</URL>
|
||||
</vCard>
|
||||
<instructions>Fill in your YAHOO! Messenger username and password to register on this transport.</instructions>
|
||||
<server>scs.msg.yahoo.com</server>
|
||||
<port>5050</port>
|
||||
<!--
|
||||
The character map. This provides character set translation from UTF-8
|
||||
to the indicated character map. See the man page for 'iconv' for available
|
||||
character maps on your platform. CP1252 is the standard Windows character
|
||||
set.
|
||||
-->
|
||||
<charmap>CP1252</charmap>
|
||||
<!--
|
||||
When this element exists, the transport will send new mail notifications as
|
||||
well as a count of unread messages when the user initially logs in.
|
||||
-->
|
||||
<newmail/>
|
||||
</config>
|
||||
<!-- yahoo-transport-2 module. -->
|
||||
<load>
|
||||
<yahoo_transport>/usr/local/lib/jabber/yahoo-transport-2.so</yahoo_transport>
|
||||
</load>
|
||||
</service>
|
||||
|
||||
<!--
|
||||
The settings below have to match the settings you made
|
||||
in your ejabberd.cfg configuration file.
|
||||
-->
|
||||
|
||||
<service id="yahoo-linker">
|
||||
<uplink/>
|
||||
<connect>
|
||||
<ip>127.0.0.1</ip>
|
||||
<port>5236</port>
|
||||
<secret>SECRET</secret>
|
||||
</connect>
|
||||
</service>
|
||||
|
||||
<pidfile>/var/run/jabber/yahoo-transport-2.pid</pidfile>
|
||||
|
||||
</jabber>
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# aim-transport -- script to start aim-transport.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/jabberd-aim-transport
|
||||
CONF=/etc/jabber/aim-transport.xml
|
||||
NAME=jabberd-aim-transport
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# ile -- script to start ILE.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/ile.pl
|
||||
NAME=ile.pl
|
||||
CONF=/etc/jabber/ile.xml
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
echo "Not implemented yet. Starting in normal mode"
|
||||
$0 start
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|status|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# jabber-gg-transport -- script to start jabber-gg-transport.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/jggtrans
|
||||
CONF=/etc/jabber/jabber-gg-transport.xml
|
||||
NAME=jggtrans
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
rm /var/run/jabber/jabber-gg-transport.pid
|
||||
;;
|
||||
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# jit -- script to start JIT.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/wpjabber-jit
|
||||
CONF=/etc/jabber/jit.xml
|
||||
NAME=wpjabber-jit
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/bin/sh
|
||||
#########################################################
|
||||
#
|
||||
# msn-transport -- script to start MSN Transport.
|
||||
#
|
||||
#########################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/jabberd-msn-transport
|
||||
CONF=/etc/jabber/msn-transport.xml
|
||||
NAME=jabberd-msn-transport
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#########################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
strace)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in strace mode."
|
||||
strace -o /opt/ejabberd/var/log/jabber/strace.log $DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/bin/sh
|
||||
##############################################################
|
||||
#
|
||||
# yahoo-transport-2 -- script to start Yahoo-transport-2.
|
||||
#
|
||||
#############################################################
|
||||
|
||||
DAEMON=/usr/local/sbin/jabberd-yahoo-transport-2
|
||||
CONF=/etc/jabber/yahoo-transport-2.xml
|
||||
NAME=jabberd-yahoo-transport-2
|
||||
HOME=/etc/jabber/
|
||||
USER=ejabberd
|
||||
|
||||
#############################################################
|
||||
|
||||
if [ "`/usr/bin/whoami`" != "$USER" ]; then
|
||||
|
||||
echo "You need to be" $USER "user to run this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
debug)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME in debugging mode."
|
||||
$DAEMON -D -H $HOME -c $CONF &
|
||||
;;
|
||||
start)
|
||||
test -f $DAEMON -a -f $CONF || exit 0
|
||||
echo "Starting $NAME."
|
||||
$DAEMON -H $HOME -c $CONF &
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME."
|
||||
killall $NAME &
|
||||
;;
|
||||
restart|reload)
|
||||
$0 stop
|
||||
sleep 3
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {debug|start|stop|restart}"
|
||||
exit 1
|
||||
esac
|
||||
@@ -6,57 +6,30 @@ CPPFLAGS = @CPPFLAGS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBS = @LIBS@
|
||||
|
||||
EXPAT_CFLAGS = @EXPAT_CFLAGS@
|
||||
ERLANG_CFLAGS= @ERLANG_CFLAGS@
|
||||
|
||||
EXPAT_LIBS = @EXPAT_LIBS@
|
||||
ERLANG_LIBS = @ERLANG_LIBS@
|
||||
|
||||
# make debug=true to compile Erlang module with debug informations.
|
||||
ifdef debug
|
||||
ERLC_FLAGS+=+debug_info
|
||||
endif
|
||||
|
||||
ifdef ejabberd_debug
|
||||
ERLC_FLAGS+=-Dejabberd_debug
|
||||
endif
|
||||
|
||||
ifeq (@roster_gateway_workaround@, true)
|
||||
ERLC_FLAGS+=-DROSTER_GATEWAY_WORKAROUND
|
||||
endif
|
||||
|
||||
prefix = @prefix@
|
||||
|
||||
SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @eldap@ @web@ stringprep @tls@ @odbc@ @ejabberd_zlib@
|
||||
INCLUDES = @ERLANG_CFLAGS@ @EXPAT_CFLAGS@
|
||||
|
||||
LIBDIRS = @ERLANG_LIBS@ @EXPAT_LIBS@
|
||||
|
||||
SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @eldap@ @web@ stringprep
|
||||
|
||||
ERLSHLIBS = expat_erl.so
|
||||
SOURCES = $(wildcard *.erl)
|
||||
BEAMS = $(SOURCES:.erl=.beam)
|
||||
|
||||
DESTDIR =
|
||||
DESTDIR =
|
||||
|
||||
EJABBERDDIR = $(DESTDIR)@prefix@/var/lib/ejabberd
|
||||
EJABBERDDIR = $(DESTDIR)/var/lib/ejabberd
|
||||
BEAMDIR = $(EJABBERDDIR)/ebin
|
||||
PRIVDIR = $(EJABBERDDIR)/priv
|
||||
SODIR = $(PRIVDIR)/lib
|
||||
MSGSDIR = $(PRIVDIR)/msgs
|
||||
LOGDIR = $(DESTDIR)@prefix@/var/log/ejabberd
|
||||
ETCDIR = $(DESTDIR)@prefix@/etc/ejabberd
|
||||
|
||||
ASN_FLAGS = -bber_bin +der +compact_bit_string +optimize +noobj
|
||||
|
||||
ifeq ($(shell uname),Darwin)
|
||||
DYNAMIC_LIB_CFLAGS = -fPIC -bundle -flat_namespace -undefined suppress
|
||||
else
|
||||
# Assume Linux-style dynamic library flags
|
||||
DYNAMIC_LIB_CFLAGS = -fpic -shared
|
||||
endif
|
||||
LOGDIR = $(DESTDIR)/var/log/ejabberd
|
||||
ETCDIR = $(DESTDIR)/etc/ejabberd
|
||||
|
||||
all: $(ERLSHLIBS) compile-beam all-recursive
|
||||
|
||||
compile-beam: XmppAddr.hrl $(BEAMS)
|
||||
|
||||
%.beam: %.erl
|
||||
@ERLC@ -W $(ERLC_FLAGS) $<
|
||||
compile-beam:
|
||||
@erl -s make all report -noinput -s erlang halt
|
||||
|
||||
|
||||
all-recursive install-recursive uninstall-recursive \
|
||||
@@ -69,15 +42,13 @@ mostlyclean-recursive maintainer-clean-recursive:
|
||||
done
|
||||
|
||||
|
||||
%.hrl: %.asn1
|
||||
@ERLC@ $(ASN_FLAGS) $<
|
||||
|
||||
$(ERLSHLIBS): %.so: %.c
|
||||
gcc -Wall $(CFLAGS) $(LDFLAGS) $(LIBS) \
|
||||
gcc -Wall $(INCLUDES) $(CFLAGS) $(LDFLAGS) $(LIBDIRS) \
|
||||
$(subst ../,,$(subst .so,.c,$@)) \
|
||||
$(EXPAT_LIBS) $(EXPAT_CFLAGS) \
|
||||
$(ERLANG_LIBS) $(ERLANG_CFLAGS) \
|
||||
-o $@ $(DYNAMIC_LIB_CFLAGS)
|
||||
-lexpat \
|
||||
-lerl_interface \
|
||||
-lei \
|
||||
-o $@ -fpic -shared \
|
||||
|
||||
install: all
|
||||
install -d $(BEAMDIR)
|
||||
@@ -89,14 +60,13 @@ install: all
|
||||
install -d $(MSGSDIR)
|
||||
install -m 644 msgs/*.msg $(MSGSDIR)
|
||||
install -d $(ETCDIR)
|
||||
[ -f $(ETCDIR)/ejabberd.cfg ] && install -b -m 644 ejabberd.cfg.example $(ETCDIR)/ejabberd.cfg-new || install -b -m 644 ejabberd.cfg.example $(ETCDIR)/ejabberd.cfg
|
||||
install -b -m 644 ejabberd.cfg.example $(ETCDIR)/ejabberd.cfg
|
||||
install -d $(LOGDIR)
|
||||
|
||||
clean: clean-recursive clean-local
|
||||
|
||||
clean-local:
|
||||
rm -f *.beam $(ERLSHLIBS)
|
||||
rm -f XmppAddr.asn1db XmppAddr.erl XmppAddr.hrl
|
||||
|
||||
distclean: distclean-recursive clean-local
|
||||
rm -f config.status
|
||||
|
||||
@@ -8,8 +8,8 @@ EREL=$(REL)\ejabberd-$(EJABBERD_VERSION)
|
||||
EBIN_DIR=$(EREL)\ebin
|
||||
SRC_DIR=$(EREL)\src
|
||||
PRIV_DIR=$(EREL)\priv
|
||||
SO_DIR=$(EREL)
|
||||
MSGS_DIR=$(EREL)\msgs
|
||||
SO_DIR=$(PRIV_DIR)\lib
|
||||
MSGS_DIR=$(PRIV_DIR)\msgs
|
||||
WIN32_DIR=$(EREL)\win32
|
||||
DOC_DIR=$(EREL)\doc
|
||||
|
||||
@@ -35,15 +35,18 @@ release : build release_clean
|
||||
copy *.beam $(EBIN_DIR)
|
||||
@erase $(EBIN_DIR)\configure.beam
|
||||
copy *.app $(EBIN_DIR)
|
||||
mkdir $(PRIV_DIR)
|
||||
mkdir $(SO_DIR)
|
||||
copy *.dll $(SO_DIR)
|
||||
mkdir $(MSGS_DIR)
|
||||
copy msgs\*.msg $(MSGS_DIR)
|
||||
mkdir $(WIN32_DIR)
|
||||
copy win32\ejabberd.cfg $(EREL)
|
||||
copy win32\inetrc $(EREL)
|
||||
copy $(SYSTEMROOT)\system32\libeay32.dll $(EREL)
|
||||
copy $(SYSTEMROOT)\system32\ssleay32.dll $(EREL)
|
||||
copy win32\ejabberd.ico $(WIN32_DIR)
|
||||
mkdir $(WIN32_DIR)\5.3
|
||||
copy win32\5.3\*.beam $(WIN32_DIR)\5.3
|
||||
mkdir $(SRC_DIR)
|
||||
copy *.app $(SRC_DIR)
|
||||
copy *.erl $(SRC_DIR)
|
||||
@@ -63,19 +66,12 @@ release : build release_clean
|
||||
copy stringprep\*.erl $(SRC_DIR)\stringprep
|
||||
copy stringprep\*.c $(SRC_DIR)\stringprep
|
||||
copy stringprep\*.tcl $(SRC_DIR)\stringprep
|
||||
mkdir $(SRC_DIR)\tls
|
||||
copy tls\*.erl $(SRC_DIR)\tls
|
||||
copy tls\*.c $(SRC_DIR)\tls
|
||||
mkdir $(SRC_DIR)\ejabberd_zlib
|
||||
copy ejabberd_zlib\*.erl $(SRC_DIR)\ejabberd_zlib
|
||||
copy ejabberd_zlib\*.c $(SRC_DIR)\ejabberd_zlib
|
||||
mkdir $(SRC_DIR)\web
|
||||
copy web\*.erl $(SRC_DIR)\web
|
||||
mkdir $(SRC_DIR)\odbc
|
||||
copy odbc\*.erl $(SRC_DIR)\odbc
|
||||
copy odbc\*.sql $(EREL)
|
||||
mkdir $(SRC_DIR)\win32
|
||||
mkdir $(SRC_DIR)\win32\5.3
|
||||
copy win32\5.3\*.erl $(SRC_DIR)\win32\5.3
|
||||
mkdir $(DOC_DIR)
|
||||
copy ..\doc\*.txt $(DOC_DIR)
|
||||
copy ..\doc\*.html $(DOC_DIR)
|
||||
copy ..\doc\*.png $(DOC_DIR)
|
||||
|
||||
@@ -96,22 +92,15 @@ all-recursive :
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\stringprep
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\tls
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\ejabberd_zlib
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\web
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..\odbc
|
||||
cd ..\win32\5.3
|
||||
nmake -nologo -f Makefile.win32
|
||||
cd ..
|
||||
cd ..\..
|
||||
|
||||
compile-beam : XmppAddr.hrl
|
||||
compile-beam :
|
||||
erl -s make all report -noinput -s erlang halt
|
||||
|
||||
XmppAddr.hrl : XmppAddr.asn1
|
||||
erlc -bber_bin +der +compact_bit_string +optimize +noobj XmppAddr.asn1
|
||||
|
||||
CLEAN : clean-recursive clean-local
|
||||
|
||||
clean-local :
|
||||
@@ -120,9 +109,6 @@ clean-local :
|
||||
-@erase expat_erl.exp
|
||||
-@erase expat_erl.lib
|
||||
-@erase *.beam
|
||||
-@erase XmppAddr.asn1db
|
||||
-@erase XmppAddr.erl
|
||||
-@erase XmppAddr.hrl
|
||||
|
||||
clean-recursive :
|
||||
cd eldap
|
||||
@@ -135,15 +121,11 @@ clean-recursive :
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\stringprep
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\tls
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\ejabberd_zlib
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\web
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..\odbc
|
||||
cd ..\win32\5.3
|
||||
nmake -nologo -f Makefile.win32 clean
|
||||
cd ..
|
||||
cd ..\..
|
||||
|
||||
distclean : release_clean clean
|
||||
-@erase $(NSIS_HEADER)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
XmppAddr { iso(1) identified-organization(3)
|
||||
dod(6) internet(1) security(5) mechanisms(5) pkix(7)
|
||||
id-on(8) id-on-xmppAddr(5) }
|
||||
|
||||
DEFINITIONS EXPLICIT TAGS ::=
|
||||
BEGIN
|
||||
|
||||
id-on-xmppAddr OBJECT IDENTIFIER ::= { iso(1) identified-organization(3)
|
||||
dod(6) internet(1) security(5) mechanisms(5) pkix(7)
|
||||
id-on(8) 5 }
|
||||
|
||||
XmppAddr ::= UTF8String
|
||||
|
||||
END
|
||||
@@ -11,12 +11,12 @@
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-export([start/0,
|
||||
to_record/3,
|
||||
add/3,
|
||||
add_list/3,
|
||||
match_rule/3,
|
||||
to_record/2,
|
||||
add/2,
|
||||
add_list/2,
|
||||
match_rule/2,
|
||||
% for debugging only
|
||||
match_acl/3]).
|
||||
match_acl/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
@@ -30,35 +30,30 @@ start() ->
|
||||
mnesia:add_table_copy(acl, node(), ram_copies),
|
||||
ok.
|
||||
|
||||
to_record(Host, ACLName, ACLSpec) ->
|
||||
#acl{aclname = {ACLName, Host}, aclspec = ACLSpec}.
|
||||
to_record(ACLName, ACLSpec) ->
|
||||
#acl{aclname = ACLName, aclspec = ACLSpec}.
|
||||
|
||||
add(Host, ACLName, ACLSpec) ->
|
||||
add(ACLName, ACLSpec) ->
|
||||
F = fun() ->
|
||||
mnesia:write(#acl{aclname = {ACLName, Host},
|
||||
aclspec = ACLSpec})
|
||||
mnesia:write(#acl{aclname = ACLName, aclspec = ACLSpec})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
add_list(Host, ACLs, Clear) ->
|
||||
add_list(ACLs, Clear) ->
|
||||
F = fun() ->
|
||||
if
|
||||
Clear ->
|
||||
Ks = mnesia:select(
|
||||
acl, [{{acl, {'$1', Host}, '$2'}, [], ['$1']}]),
|
||||
Ks = mnesia:all_keys(acl),
|
||||
lists:foreach(fun(K) ->
|
||||
mnesia:delete({acl, {K, Host}})
|
||||
mnesia:delete({acl, K})
|
||||
end, Ks);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
lists:foreach(fun(ACL) ->
|
||||
case ACL of
|
||||
#acl{aclname = ACLName,
|
||||
aclspec = ACLSpec} ->
|
||||
mnesia:write(
|
||||
#acl{aclname = {ACLName, Host},
|
||||
aclspec = ACLSpec})
|
||||
#acl{} ->
|
||||
mnesia:write(ACL)
|
||||
end
|
||||
end, ACLs)
|
||||
end,
|
||||
@@ -71,61 +66,30 @@ add_list(Host, ACLs, Clear) ->
|
||||
|
||||
|
||||
|
||||
match_rule(global, Rule, JID) ->
|
||||
match_rule(Rule, JID) ->
|
||||
case Rule of
|
||||
all -> allow;
|
||||
none -> deny;
|
||||
_ ->
|
||||
case ejabberd_config:get_global_option({access, Rule, global}) of
|
||||
case ejabberd_config:get_global_option({access, Rule}) of
|
||||
undefined ->
|
||||
deny;
|
||||
GACLs ->
|
||||
match_acls(GACLs, JID, global)
|
||||
end
|
||||
end;
|
||||
|
||||
match_rule(Host, Rule, JID) ->
|
||||
case Rule of
|
||||
all -> allow;
|
||||
none -> deny;
|
||||
_ ->
|
||||
case ejabberd_config:get_global_option({access, Rule, global}) of
|
||||
undefined ->
|
||||
case ejabberd_config:get_global_option({access, Rule, Host}) of
|
||||
undefined ->
|
||||
deny;
|
||||
ACLs ->
|
||||
match_acls(ACLs, JID, Host)
|
||||
end;
|
||||
GACLs ->
|
||||
case ejabberd_config:get_global_option({access, Rule, Host}) of
|
||||
undefined ->
|
||||
match_acls(GACLs, JID, Host);
|
||||
ACLs ->
|
||||
case lists:reverse(GACLs) of
|
||||
[{allow, all} | Rest] ->
|
||||
match_acls(
|
||||
lists:reverse(Rest) ++ ACLs ++
|
||||
[{allow, all}],
|
||||
JID, Host);
|
||||
_ ->
|
||||
match_acls(GACLs ++ ACLs, JID, Host)
|
||||
end
|
||||
end
|
||||
ACLs ->
|
||||
match_acls(ACLs, JID)
|
||||
end
|
||||
end.
|
||||
|
||||
match_acls([], _, Host) ->
|
||||
match_acls([], _) ->
|
||||
deny;
|
||||
match_acls([{Access, ACL} | ACLs], JID, Host) ->
|
||||
case match_acl(ACL, JID, Host) of
|
||||
match_acls([{Access, ACL} | ACLs], JID) ->
|
||||
case match_acl(ACL, JID) of
|
||||
true ->
|
||||
Access;
|
||||
_ ->
|
||||
match_acls(ACLs, JID, Host)
|
||||
match_acls(ACLs, JID)
|
||||
end.
|
||||
|
||||
match_acl(ACL, JID, Host) ->
|
||||
match_acl(ACL, JID) ->
|
||||
case ACL of
|
||||
all -> true;
|
||||
none -> false;
|
||||
@@ -136,20 +100,14 @@ match_acl(ACL, JID, Host) ->
|
||||
all ->
|
||||
true;
|
||||
{user, U} ->
|
||||
(U == User)
|
||||
andalso
|
||||
((Host == Server) orelse
|
||||
((Host == global) andalso
|
||||
lists:member(Server, ?MYHOSTS)));
|
||||
(U == User) andalso (?MYNAME == Server);
|
||||
{user, U, S} ->
|
||||
(U == User) andalso (S == Server);
|
||||
{server, S} ->
|
||||
S == Server;
|
||||
{user_regexp, UR} ->
|
||||
((Host == Server) orelse
|
||||
((Host == global) andalso
|
||||
lists:member(Server, ?MYHOSTS)))
|
||||
andalso is_regexp_match(User, UR);
|
||||
(?MYNAME == Server) andalso
|
||||
is_regexp_match(User, UR);
|
||||
{user_regexp, UR, S} ->
|
||||
(S == Server) andalso
|
||||
is_regexp_match(User, UR);
|
||||
@@ -159,10 +117,7 @@ match_acl(ACL, JID, Host) ->
|
||||
is_regexp_match(Server, SR) andalso
|
||||
is_regexp_match(User, UR);
|
||||
{user_glob, UR} ->
|
||||
((Host == Server) orelse
|
||||
((Host == global) andalso
|
||||
lists:member(Server, ?MYHOSTS)))
|
||||
andalso
|
||||
(?MYNAME == Server) andalso
|
||||
is_glob_match(User, UR);
|
||||
{user_glob, UR, S} ->
|
||||
(S == Server) andalso
|
||||
@@ -171,17 +126,9 @@ match_acl(ACL, JID, Host) ->
|
||||
is_glob_match(Server, SR);
|
||||
{node_glob, UR, SR} ->
|
||||
is_glob_match(Server, SR) andalso
|
||||
is_glob_match(User, UR);
|
||||
WrongSpec ->
|
||||
?ERROR_MSG(
|
||||
"Wrong ACL expression: ~p~n"
|
||||
"Check your config file and reload it with the override_acls option enabled",
|
||||
[WrongSpec]),
|
||||
false
|
||||
is_glob_match(User, UR)
|
||||
end
|
||||
end,
|
||||
ets:lookup(acl, {ACL, global}) ++
|
||||
ets:lookup(acl, {ACL, Host}))
|
||||
end, ets:lookup(acl, ACL))
|
||||
end.
|
||||
|
||||
is_regexp_match(String, RegExp) ->
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
AC_DEFUN(AM_WITH_EXPAT,
|
||||
[ AC_ARG_WITH(expat,
|
||||
[ --with-expat=PREFIX prefix where EXPAT is installed])
|
||||
[ --with-expat=PREFIX path to expat library],
|
||||
, with_expat=no)
|
||||
|
||||
EXPAT_CFLAGS=
|
||||
EXPAT_LIBS=
|
||||
if test x"$with_expat" != x; then
|
||||
if test $with_expat != no; then
|
||||
if test $with_expat != yes; then
|
||||
EXPAT_CFLAGS="-I$with_expat/include"
|
||||
EXPAT_LIBS="-L$with_expat/lib"
|
||||
fi
|
||||
|
||||
AC_CHECK_LIB(expat, XML_ParserCreate,
|
||||
[ EXPAT_LIBS="$EXPAT_LIBS -lexpat"
|
||||
expat_found=yes ],
|
||||
@@ -24,42 +25,12 @@ AC_DEFUN(AM_WITH_EXPAT,
|
||||
AC_MSG_ERROR([Could not find expat.h])
|
||||
fi
|
||||
CFLAGS="$expat_save_CFLAGS"
|
||||
fi
|
||||
|
||||
AC_SUBST(EXPAT_CFLAGS)
|
||||
AC_SUBST(EXPAT_LIBS)
|
||||
])
|
||||
|
||||
AC_DEFUN(AM_WITH_ZLIB,
|
||||
[ AC_ARG_WITH(zlib,
|
||||
[ --with-zlib=PREFIX prefix where zlib is installed])
|
||||
|
||||
ZLIB_CFLAGS=
|
||||
ZLIB_LIBS=
|
||||
if test x"$with_zlib" != x; then
|
||||
ZLIB_CFLAGS="-I$with_zlib/include"
|
||||
ZLIB_LIBS="-L$with_zlib/lib"
|
||||
fi
|
||||
|
||||
AC_CHECK_LIB(z, gzgets,
|
||||
[ ZLIB_LIBS="$ZLIB_LIBS -lz"
|
||||
zlib_found=yes ],
|
||||
[ zlib_found=no ],
|
||||
"$ZLIB_LIBS")
|
||||
if test $zlib_found = no; then
|
||||
AC_MSG_ERROR([Could not find the zlib library])
|
||||
fi
|
||||
zlib_save_CFLAGS="$CFLAGS"
|
||||
CFLAGS="$CFLAGS $ZLIB_CFLAGS"
|
||||
AC_CHECK_HEADERS(zlib.h, , zlib_found=no)
|
||||
if test $zlib_found = no; then
|
||||
AC_MSG_ERROR([Could not find zlib.h])
|
||||
fi
|
||||
CFLAGS="$zlib_save_CFLAGS"
|
||||
|
||||
AC_SUBST(ZLIB_CFLAGS)
|
||||
AC_SUBST(ZLIB_LIBS)
|
||||
])
|
||||
|
||||
AC_DEFUN(AM_WITH_ERLANG,
|
||||
[ AC_ARG_WITH(erlang,
|
||||
[ --with-erlang=PREFIX path to erlc and erl ])
|
||||
@@ -68,7 +39,7 @@ AC_DEFUN(AM_WITH_ERLANG,
|
||||
AC_PATH_TOOL(ERLC, erlc, , $PATH:$with_erlang:$with_erlang/bin)
|
||||
AC_PATH_TOOL(ERL, erl, , $PATH:$with_erlang:$with_erlang/bin)
|
||||
|
||||
if test "z$ERLC" = "z" || test "z$ERL" = "z"; then
|
||||
if test "z$ERLC" == "z" || test "z$ERL" == "z"; then
|
||||
AC_MSG_ERROR([erlang not found])
|
||||
fi
|
||||
|
||||
@@ -77,44 +48,14 @@ AC_DEFUN(AM_WITH_ERLANG,
|
||||
|
||||
-module(conftest).
|
||||
-author('alexey@sevcom.net').
|
||||
|
||||
|
||||
-export([[start/0]]).
|
||||
|
||||
|
||||
start() ->
|
||||
EIDirS = code:lib_dir("erl_interface") ++ "\n",
|
||||
EILibS = libpath("erl_interface") ++ "\n",
|
||||
RootDirS = code:root_dir() ++ "\n",
|
||||
file:write_file("conftest.out", list_to_binary(EIDirS ++ EILibS ++ RootDirS)),
|
||||
halt().
|
||||
|
||||
%% return physical architecture based on OS/Processor
|
||||
archname() ->
|
||||
ArchStr = erlang:system_info(system_architecture),
|
||||
case os:type() of
|
||||
{win32, _} -> "windows";
|
||||
{unix,UnixName} ->
|
||||
Specs = string:tokens(ArchStr,"-"),
|
||||
Cpu = case lists:nth(2,Specs) of
|
||||
"pc" -> "x86";
|
||||
_ -> hd(Specs)
|
||||
end,
|
||||
atom_to_list(UnixName) ++ "-" ++ Cpu;
|
||||
_ -> "generic"
|
||||
end.
|
||||
|
||||
%% Return arch-based library path or a default value if this directory
|
||||
%% does not exist
|
||||
libpath(App) ->
|
||||
PrivDir = code:priv_dir(App),
|
||||
ArchDir = archname(),
|
||||
LibArchDir = filename:join([[PrivDir,"lib",ArchDir]]),
|
||||
case file:list_dir(LibArchDir) of
|
||||
%% Arch lib dir exists: We use it
|
||||
{ok, _List} -> LibArchDir;
|
||||
%% Arch lib dir does not exist: Return the default value
|
||||
%% ({error, enoent}):
|
||||
_Error -> code:lib_dir("erl_interface") ++ "/lib"
|
||||
end.
|
||||
EIDirS = code:lib_dir("erl_interface") ++ "\n",
|
||||
RootDirS = code:root_dir() ++ "\n",
|
||||
file:write_file("conftest.out", list_to_binary(EIDirS ++ RootDirS)),
|
||||
halt().
|
||||
|
||||
_EOF
|
||||
|
||||
@@ -130,15 +71,11 @@ _EOF
|
||||
AC_MSG_ERROR([erlang program was not properly executed, (conftest.out was not produced)])
|
||||
fi
|
||||
|
||||
# First line
|
||||
ERLANG_EI_DIR=`cat conftest.out | head -n 1`
|
||||
# Second line
|
||||
ERLANG_EI_LIB=`cat conftest.out | head -n 2 | tail -n 1`
|
||||
# Third line
|
||||
ERLANG_DIR=`cat conftest.out | tail -n 1`
|
||||
|
||||
ERLANG_CFLAGS="-I$ERLANG_EI_DIR/include -I$ERLANG_DIR/usr/include"
|
||||
ERLANG_LIBS="-L$ERLANG_EI_LIB -lerl_interface -lei"
|
||||
ERLANG_LIBS="-L$ERLANG_EI_DIR/lib"
|
||||
|
||||
AC_SUBST(ERLANG_CFLAGS)
|
||||
AC_SUBST(ERLANG_LIBS)
|
||||
@@ -156,7 +93,7 @@ AC_ARG_ENABLE($1,
|
||||
[ --enable-$1 enable $1 (default: $2)],
|
||||
[mr_enable_$1="$enableval"],
|
||||
[mr_enable_$1=$2])
|
||||
if test "$mr_enable_$1" = "yes"; then
|
||||
if test "$mr_enable_$1" = $2; then
|
||||
$1=$1
|
||||
make_$1=$1/Makefile
|
||||
fi
|
||||
@@ -173,8 +110,9 @@ AC_DEFUN([AM_ICONV],
|
||||
[
|
||||
dnl Some systems have iconv in libc, some have it in libiconv (OSF/1 and
|
||||
dnl those with the standalone portable GNU libiconv installed).
|
||||
|
||||
AC_ARG_WITH([libiconv-prefix],
|
||||
[ --with-libiconv-prefix=PREFIX prefix where libiconv is installed], [
|
||||
[ --with-libiconv-prefix=DIR Search for libiconv in DIR/include and DIR/lib], [
|
||||
for dir in `echo "$withval" | tr : ' '`; do
|
||||
if test -d $dir/include; then CPPFLAGS="$CPPFLAGS -I$dir/include"; fi
|
||||
if test -d $dir/include; then CFLAGS="$CFLAGS -I$dir/include"; fi
|
||||
@@ -255,40 +193,3 @@ size_t iconv();
|
||||
fi
|
||||
AC_SUBST(LIBICONV)
|
||||
])
|
||||
|
||||
dnl <openssl>
|
||||
AC_DEFUN(AM_WITH_OPENSSL,
|
||||
[ AC_ARG_WITH(openssl,
|
||||
[ --with-openssl=PREFIX prefix where OPENSSL is installed ])
|
||||
unset SSL_LIBS;
|
||||
unset SSL_CFLAGS;
|
||||
have_openssl=no
|
||||
if test x"$tls" != x; then
|
||||
for ssl_prefix in $withval /usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr; do
|
||||
printf "looking for openssl in $ssl_prefix...\n"
|
||||
SSL_CFLAGS="-I$ssl_prefix/include/openssl"
|
||||
SSL_LIBS="-L$ssl_prefix/lib -lcrypto"
|
||||
AC_CHECK_LIB(ssl, SSL_new, [ have_openssl=yes ], [ have_openssl=no ], [ $SSL_LIBS $SSL_CFLAGS ])
|
||||
if test x"$have_openssl" = xyes; then
|
||||
save_CPPFLAGS=$CPPFLAGS
|
||||
CPPFLAGS="-I$ssl_prefix/lib $CPPFLAGS"
|
||||
AC_CHECK_HEADERS(openssl/ssl.h, have_openssl_h=yes)
|
||||
CPPFLAGS=$save_CPPFLAGS
|
||||
if test x"$have_openssl_h" = xyes; then
|
||||
have_openssl=yes
|
||||
printf "openssl found in $ssl_prefix\n";
|
||||
SSL_LIBS="-L$ssl_prefix/lib -lssl -lcrypto"
|
||||
CPPFLAGS="-I$ssl_prefix/lib $CPPFLAGS"
|
||||
SSL_CFLAGS="-DHAVE_SSL"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if test x${have_openssl} != xyes; then
|
||||
AC_MSG_ERROR([openssl library cannot be found. Install openssl or disable `tls' module (--disable-tls).])
|
||||
fi
|
||||
AC_SUBST(SSL_LIBS)
|
||||
AC_SUBST(SSL_CFLAGS)
|
||||
fi
|
||||
])
|
||||
dnl <openssl/>
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : adhoc.erl
|
||||
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%% Purpose : Provide helper functions for ad-hoc commands (JEP-0050)
|
||||
%%% Created : 31 Oct 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(adhoc).
|
||||
-author('henoch@dtek.chalmers.se').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-export([parse_request/1,
|
||||
produce_response/2,
|
||||
produce_response/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("adhoc.hrl").
|
||||
|
||||
%% Parse an ad-hoc request. Return either an adhoc_request record or
|
||||
%% an {error, ErrorType} tuple.
|
||||
parse_request(#iq{type = set, lang = Lang, sub_el = SubEl, xmlns = ?NS_COMMANDS}) ->
|
||||
?DEBUG("entering parse_request...", []),
|
||||
Node = xml:get_tag_attr_s("node", SubEl),
|
||||
SessionID = xml:get_tag_attr_s("sessionid", SubEl),
|
||||
Action = xml:get_tag_attr_s("action", SubEl),
|
||||
XData = find_xdata_el(SubEl),
|
||||
{xmlelement, _, _, AllEls} = SubEl,
|
||||
if XData ->
|
||||
Others = lists:delete(XData, AllEls);
|
||||
true ->
|
||||
Others = AllEls
|
||||
end,
|
||||
|
||||
#adhoc_request{lang = Lang,
|
||||
node = Node,
|
||||
sessionid = SessionID,
|
||||
action = Action,
|
||||
xdata = XData,
|
||||
others = Others};
|
||||
parse_request(_) ->
|
||||
{error, ?ERR_BAD_REQUEST}.
|
||||
|
||||
%% Borrowed from mod_vcard.erl
|
||||
find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
|
||||
find_xdata_el1(SubEls).
|
||||
|
||||
find_xdata_el1([]) ->
|
||||
false;
|
||||
find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_XDATA ->
|
||||
{xmlelement, Name, Attrs, SubEls};
|
||||
_ ->
|
||||
find_xdata_el1(Els)
|
||||
end;
|
||||
find_xdata_el1([_ | Els]) ->
|
||||
find_xdata_el1(Els).
|
||||
|
||||
%% Produce a <command/> node to use as response from an adhoc_response
|
||||
%% record, filling in values for language, node and session id from
|
||||
%% the request.
|
||||
produce_response(#adhoc_request{lang = Lang,
|
||||
node = Node,
|
||||
sessionid = SessionID},
|
||||
Response) ->
|
||||
produce_response(Response#adhoc_response{lang = Lang,
|
||||
node = Node,
|
||||
sessionid = SessionID}).
|
||||
|
||||
%% Produce a <command/> node to use as response from an adhoc_response
|
||||
%% record.
|
||||
produce_response(#adhoc_response{lang = _Lang,
|
||||
node = Node,
|
||||
sessionid = ProvidedSessionID,
|
||||
status = Status,
|
||||
defaultaction = DefaultAction,
|
||||
actions = Actions,
|
||||
notes = Notes,
|
||||
elements = Elements}) ->
|
||||
SessionID = if is_list(ProvidedSessionID), ProvidedSessionID /= "" ->
|
||||
ProvidedSessionID;
|
||||
true ->
|
||||
jlib:now_to_utc_string(now())
|
||||
end,
|
||||
case Actions of
|
||||
[] ->
|
||||
ActionsEls = [];
|
||||
_ ->
|
||||
case DefaultAction of
|
||||
"" ->
|
||||
ActionsElAttrs = [];
|
||||
_ ->
|
||||
ActionsElAttrs = [{"execute", DefaultAction}]
|
||||
end,
|
||||
ActionsEls = [{xmlelement, "actions",
|
||||
ActionsElAttrs,
|
||||
[{xmlelement, Action, [], []} || Action <- Actions]}]
|
||||
end,
|
||||
NotesEls = lists:map(fun({Type, Text}) ->
|
||||
{xmlelement, "note",
|
||||
[{"type", Type}],
|
||||
[{xmlcdata, Text}]}
|
||||
end, Notes),
|
||||
{xmlelement, "command",
|
||||
[{"xmlns", ?NS_COMMANDS},
|
||||
{"sessionid", SessionID},
|
||||
{"node", Node},
|
||||
{"status", atom_to_list(Status)}],
|
||||
ActionsEls ++ NotesEls ++ Elements}.
|
||||
@@ -1,15 +0,0 @@
|
||||
-record(adhoc_request, {lang,
|
||||
node,
|
||||
sessionid,
|
||||
action,
|
||||
xdata,
|
||||
others}).
|
||||
|
||||
-record(adhoc_response, {lang,
|
||||
node,
|
||||
sessionid,
|
||||
status,
|
||||
defaultaction = "",
|
||||
actions = [],
|
||||
notes = [],
|
||||
elements = []}).
|
||||
@@ -14,72 +14,30 @@ AM_WITH_ERLANG
|
||||
AM_ICONV
|
||||
#locating libexpat
|
||||
AM_WITH_EXPAT
|
||||
#locating zlib
|
||||
AM_WITH_ZLIB
|
||||
|
||||
|
||||
|
||||
|
||||
# Checks for typedefs, structures, and compiler characteristics.
|
||||
AC_C_CONST
|
||||
|
||||
# Change default prefix
|
||||
AC_PREFIX_DEFAULT /
|
||||
|
||||
# Checks for library functions.
|
||||
AC_FUNC_MALLOC
|
||||
AC_HEADER_STDC
|
||||
|
||||
|
||||
AC_MOD_ENABLE(mod_pubsub, yes)
|
||||
AC_MOD_ENABLE(mod_irc, yes)
|
||||
AC_MOD_ENABLE(mod_muc, yes)
|
||||
AC_MOD_ENABLE(eldap, yes)
|
||||
AC_MOD_ENABLE(web, yes)
|
||||
AC_MOD_ENABLE(tls, yes)
|
||||
AC_MOD_ENABLE(odbc, no)
|
||||
AC_MOD_ENABLE(ejabberd_zlib, yes)
|
||||
|
||||
AC_ARG_ENABLE(roster_gateway_workaround,
|
||||
[ --enable-roster-gateway-workaround Turn on workaround for processing gateway subscriptions (default: no)],
|
||||
[case "${enableval}" in
|
||||
yes) roster_gateway_workaround=true ;;
|
||||
no) roster_gateway_workaround=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-roster-gateway-workaround) ;;
|
||||
esac],[roster_gateway_workaround=false])
|
||||
AC_SUBST(roster_gateway_workaround)
|
||||
|
||||
AC_ARG_ENABLE(mssql,
|
||||
[ --enable-mssql Use Microsoft SQL Server database (default: no, requires --enable-odbc)],
|
||||
[case "${enableval}" in
|
||||
yes) db_type=mssql ;;
|
||||
no) db_type=generic ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-mssql) ;;
|
||||
esac],[db_type=generic])
|
||||
AC_SUBST(db_type)
|
||||
|
||||
|
||||
AC_CONFIG_FILES([Makefile
|
||||
$make_mod_irc
|
||||
$make_mod_muc
|
||||
$make_mod_pubsub
|
||||
$make_eldap
|
||||
$make_web
|
||||
stringprep/Makefile
|
||||
$make_tls
|
||||
$make_odbc
|
||||
$make_ejabberd_zlib])
|
||||
#openssl
|
||||
AM_WITH_OPENSSL
|
||||
# If ssl is kerberized it need krb5.h
|
||||
# On RedHat and OpenBSD, krb5.h is in an unsual place:
|
||||
KRB5_INCLUDE="`krb5-config --cflags 2>/dev/null`"
|
||||
if test -n "$KRB5_INCLUDE" ; then
|
||||
CPPFLAGS="$CPPFLAGS $KRB5_INCLUDE"
|
||||
else
|
||||
# For RedHat For BSD
|
||||
for D in /usr/kerberos/include /usr/include/kerberos /usr/include/kerberosV
|
||||
do
|
||||
if test -d $D ; then
|
||||
CPPFLAGS="$CPPFLAGS -I$D"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
AC_CHECK_HEADER(krb5.h,,)
|
||||
|
||||
stringprep/Makefile])
|
||||
AC_OUTPUT
|
||||
|
||||
@@ -28,45 +28,33 @@ start() ->
|
||||
ExpatLib = "EXPAT_LIB = $(EXPAT_DIR)\\StaticLibs\\libexpatMT.lib\n",
|
||||
ExpatFlag = "EXPAT_FLAG = -DXML_STATIC\n",
|
||||
IconvDir = "ICONV_DIR = c:\\progra~1\\libiconv-1.9.1-static\n",
|
||||
IconvLib = "ICONV_LIB = $(ICONV_DIR)\\lib\\iconv.lib\n",
|
||||
ZlibDir = "ZLIB_DIR = c:\\progra~1\\zlib-1.2.3\n",
|
||||
ZlibLib = "ZLIB_LIB = $(ZLIB_DIR)\\lib\\zlib.lib\n";
|
||||
IconvLib = "ICONV_LIB = $(ICONV_DIR)\\lib\\iconv.lib\n";
|
||||
false ->
|
||||
ExpatLib = "EXPAT_LIB = $(EXPAT_DIR)\\Libs\\libexpat.lib\n",
|
||||
ExpatFlag = "",
|
||||
IconvDir = "ICONV_DIR = c:\\progra~1\\libiconv-1.9.1\n",
|
||||
IconvLib = "ICONV_LIB = $(ICONV_DIR)\\lib\\iconv.lib\n",
|
||||
ZlibDir = "ZLIB_DIR = c:\\progra~1\\zlib-1.2.3\n",
|
||||
ZlibLib = "ZLIB_LIB = $(ZLIB_DIR)\\lib\\zlib1.lib\n"
|
||||
IconvLib = "ICONV_LIB = $(ICONV_DIR)\\lib\\iconv.lib\n"
|
||||
end,
|
||||
|
||||
EVersion = "ERLANG_VERSION = " ++ erlang:system_info(version) ++ "\n",
|
||||
EIDirS = "EI_DIR = " ++ code:lib_dir("erl_interface") ++ "\n",
|
||||
RootDirS = "ERLANG_DIR = " ++ code:root_dir() ++ "\n",
|
||||
Version = "EJABBERD_VERSION = " ++ ?VERSION ++ "\n",
|
||||
ExpatDir = "EXPAT_DIR = c:\\progra~1\\expat-1.95.7\n",
|
||||
OpenSSLDir = "OPENSSL_DIR = c:\\progra~1\\OpenSSL\n",
|
||||
DBType = "DBTYPE = generic\n", %% 'generic' or 'mssql'
|
||||
|
||||
SSLDir = "SSLDIR = " ++ code:lib_dir("ssl") ++ "\n",
|
||||
StdLibDir = "STDLIBDIR = " ++ code:lib_dir("stdlib") ++ "\n",
|
||||
|
||||
file:write_file("Makefile.inc",
|
||||
list_to_binary(EVersion ++
|
||||
EIDirS ++
|
||||
list_to_binary(EIDirS ++
|
||||
RootDirS ++
|
||||
Version ++
|
||||
SSLDir ++
|
||||
StdLibDir ++
|
||||
OpenSSLDir ++
|
||||
DBType ++
|
||||
ExpatDir ++
|
||||
ExpatLib ++
|
||||
ExpatFlag ++
|
||||
IconvDir ++
|
||||
IconvLib ++
|
||||
ZlibDir ++
|
||||
ZlibLib)),
|
||||
IconvLib)),
|
||||
halt().
|
||||
|
||||
|
||||
|
||||
@@ -11,21 +11,20 @@
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-export([start/0,
|
||||
register_mechanism/3,
|
||||
listmech/1,
|
||||
server_new/6,
|
||||
register_mechanism/2,
|
||||
listmech/0,
|
||||
server_new/4,
|
||||
server_start/3,
|
||||
server_step/2]).
|
||||
|
||||
-record(sasl_mechanism, {mechanism, module, require_plain_password}).
|
||||
-record(sasl_state, {service, myname, realm,
|
||||
get_password, check_password,
|
||||
mech_mod, mech_state}).
|
||||
-record(sasl_mechanism, {mechanism, module}).
|
||||
-record(sasl_state, {service, myname, realm, mech_mod, mech_state}).
|
||||
|
||||
-export([behaviour_info/1]).
|
||||
|
||||
behaviour_info(callbacks) ->
|
||||
[{mech_new, 3}, {mech_step, 2}];
|
||||
[{mech_new, 0},
|
||||
{mech_step, 2}];
|
||||
behaviour_info(Other) ->
|
||||
undefined.
|
||||
|
||||
@@ -35,14 +34,11 @@ start() ->
|
||||
{keypos, #sasl_mechanism.mechanism}]),
|
||||
cyrsasl_plain:start([]),
|
||||
cyrsasl_digest:start([]),
|
||||
cyrsasl_anonymous:start([]),
|
||||
ok.
|
||||
|
||||
register_mechanism(Mechanism, Module, RequirePlainPassword) ->
|
||||
ets:insert(sasl_mechanism,
|
||||
#sasl_mechanism{mechanism = Mechanism,
|
||||
module = Module,
|
||||
require_plain_password = RequirePlainPassword}).
|
||||
register_mechanism(Mechanism, Module) ->
|
||||
ets:insert(sasl_mechanism, #sasl_mechanism{mechanism = Mechanism,
|
||||
module = Module}).
|
||||
|
||||
% TODO: use callbacks
|
||||
-include("ejabberd.hrl").
|
||||
@@ -79,46 +75,24 @@ check_credentials(State, Props) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
listmech(Host) ->
|
||||
RequirePlainPassword = ejabberd_auth:plain_password_required(Host),
|
||||
|
||||
Mechs = ets:select(sasl_mechanism,
|
||||
[{#sasl_mechanism{mechanism = '$1',
|
||||
require_plain_password = '$2',
|
||||
_ = '_'},
|
||||
if
|
||||
RequirePlainPassword ->
|
||||
[{'==', '$2', false}];
|
||||
true ->
|
||||
[]
|
||||
end,
|
||||
['$1']}]),
|
||||
filter_anonymous(Host, Mechs).
|
||||
listmech() ->
|
||||
ets:select(sasl_mechanism,
|
||||
[{#sasl_mechanism{mechanism = '$1', _ = '_'}, [], ['$1']}]).
|
||||
|
||||
server_new(Service, ServerFQDN, UserRealm, SecFlags,
|
||||
GetPassword, CheckPassword) ->
|
||||
|
||||
server_new(Service, ServerFQDN, UserRealm, SecFlags) ->
|
||||
#sasl_state{service = Service,
|
||||
myname = ServerFQDN,
|
||||
realm = UserRealm,
|
||||
get_password = GetPassword,
|
||||
check_password = CheckPassword}.
|
||||
realm = UserRealm}.
|
||||
|
||||
server_start(State, Mech, ClientIn) ->
|
||||
case lists:member(Mech, listmech(State#sasl_state.myname)) of
|
||||
true ->
|
||||
case ets:lookup(sasl_mechanism, Mech) of
|
||||
[#sasl_mechanism{module = Module}] ->
|
||||
{ok, MechState} = Module:mech_new(
|
||||
State#sasl_state.myname,
|
||||
State#sasl_state.get_password,
|
||||
State#sasl_state.check_password),
|
||||
server_step(State#sasl_state{mech_mod = Module,
|
||||
mech_state = MechState},
|
||||
ClientIn);
|
||||
_ ->
|
||||
{error, "no-mechanism"}
|
||||
end;
|
||||
false ->
|
||||
case ets:lookup(sasl_mechanism, Mech) of
|
||||
[#sasl_mechanism{module = Module}] ->
|
||||
{ok, MechState} = Module:mech_new(),
|
||||
server_step(State#sasl_state{mech_mod = Module,
|
||||
mech_state = MechState},
|
||||
ClientIn);
|
||||
_ ->
|
||||
{error, "no-mechanism"}
|
||||
end.
|
||||
|
||||
@@ -140,10 +114,3 @@ server_step(State, ClientIn) ->
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
%% Remove the anonymous mechanism from the list if not enabled for the given
|
||||
%% host
|
||||
filter_anonymous(Host, Mechs) ->
|
||||
case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(Host) of
|
||||
true -> Mechs;
|
||||
false -> Mechs -- ["ANONYMOUS"]
|
||||
end.
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : cyrsasl_anonymous.erl
|
||||
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%% Purpose : ANONYMOUS SASL mechanism
|
||||
%%% Created : 23 Aug 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%% See http://www.ietf.org/internet-drafts/draft-ietf-sasl-anon-05.txt
|
||||
|
||||
-module(cyrsasl_anonymous).
|
||||
-vsn('$Revision$').
|
||||
|
||||
-export([start/1, stop/0, mech_new/3, mech_step/2]).
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
|
||||
-record(state, {server}).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("ANONYMOUS", ?MODULE, false),
|
||||
ok.
|
||||
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(Host, _GetPassword, _CheckPassword) ->
|
||||
{ok, #state{server = Host}}.
|
||||
|
||||
mech_step(State, _ClientIn) ->
|
||||
%% We generate a random username:
|
||||
User = lists:concat([randoms:get_string() | tuple_to_list(now())]),
|
||||
Server = State#state.server,
|
||||
|
||||
%% Checks that the username is available
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
true -> {error, "not-authorized"};
|
||||
false -> {ok, [{username, User}]}
|
||||
end.
|
||||
@@ -12,28 +12,35 @@
|
||||
|
||||
-export([start/1,
|
||||
stop/0,
|
||||
mech_new/3,
|
||||
mech_new/0,
|
||||
mech_step/2]).
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
%-behaviour(gen_mod).
|
||||
|
||||
-record(state, {step, nonce, username, authzid, get_password}).
|
||||
-record(state, {step, nonce, username, authzid}).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, true).
|
||||
start(Opts) ->
|
||||
case ejabberd_auth:plain_password_required() of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE),
|
||||
ok
|
||||
end.
|
||||
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(_Host, GetPassword, _CheckPassword) ->
|
||||
mech_new() ->
|
||||
{ok, #state{step = 1,
|
||||
nonce = randoms:get_string(),
|
||||
get_password = GetPassword}}.
|
||||
nonce = randoms:get_string()}}.
|
||||
|
||||
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
|
||||
{continue,
|
||||
"nonce=\"" ++ Nonce ++
|
||||
"\",qop=\"auth\",charset=utf-8,algorithm=md5-sess",
|
||||
%"\",qop=\"auth,auth-int\",charset=utf-8,algorithm=md5-sess",
|
||||
State#state{step = 3}};
|
||||
mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
@@ -42,9 +49,9 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
|
||||
KeyVals ->
|
||||
UserName = xml:get_attr_s("username", KeyVals),
|
||||
AuthzId = xml:get_attr_s("authzid", KeyVals),
|
||||
case (State#state.get_password)(UserName) of
|
||||
case ejabberd_auth:get_password(UserName) of
|
||||
false ->
|
||||
{error, "not-authorized"};
|
||||
{error, "no-user"};
|
||||
Passwd ->
|
||||
Response = response(KeyVals, UserName, Passwd,
|
||||
Nonce, AuthzId, "AUTHENTICATE"),
|
||||
@@ -59,13 +66,13 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
|
||||
username = UserName,
|
||||
authzid = AuthzId}};
|
||||
_ ->
|
||||
{error, "not-authorized"}
|
||||
{error, "bad-auth"}
|
||||
end
|
||||
end
|
||||
end;
|
||||
mech_step(#state{step = 5,
|
||||
username = UserName,
|
||||
authzid = AuthzId}, "") ->
|
||||
authzid = AuthzId} = State, "") ->
|
||||
{ok, [{username, UserName}, {authzid, AuthzId}]};
|
||||
mech_step(A, B) ->
|
||||
io:format("SASL DIGEST: A ~p B ~p", [A,B]),
|
||||
@@ -77,15 +84,11 @@ parse(S) ->
|
||||
|
||||
parse1([$= | Cs], S, Ts) ->
|
||||
parse2(Cs, lists:reverse(S), "", Ts);
|
||||
parse1([$, | Cs], [], Ts) ->
|
||||
parse1(Cs, [], Ts);
|
||||
parse1([$\s | Cs], [], Ts) ->
|
||||
parse1(Cs, [], Ts);
|
||||
parse1([C | Cs], S, Ts) ->
|
||||
parse1(Cs, [C | S], Ts);
|
||||
parse1([], [], T) ->
|
||||
lists:reverse(T);
|
||||
parse1([], _S, _T) ->
|
||||
parse1([], S, T) ->
|
||||
bad.
|
||||
|
||||
parse2([$" | Cs], Key, Val, Ts) ->
|
||||
@@ -104,8 +107,6 @@ parse3([], _, _, _) ->
|
||||
|
||||
parse4([$, | Cs], Key, Val, Ts) ->
|
||||
parse1(Cs, "", [{Key, lists:reverse(Val)} | Ts]);
|
||||
parse4([$\s | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, Val, Ts);
|
||||
parse4([C | Cs], Key, Val, Ts) ->
|
||||
parse4(Cs, Key, [C | Val], Ts);
|
||||
parse4([], Key, Val, Ts) ->
|
||||
@@ -147,13 +148,13 @@ response(KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) ->
|
||||
crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
|
||||
":" ++ Nonce ++ ":" ++ CNonce ++ ":" ++ AuthzId
|
||||
end,
|
||||
A2 = case QOP of
|
||||
"auth" ->
|
||||
A2Prefix ++ ":" ++ DigestURI;
|
||||
_ ->
|
||||
A2Prefix ++ ":" ++ DigestURI ++
|
||||
":00000000000000000000000000000000"
|
||||
end,
|
||||
case QOP of
|
||||
"auth" ->
|
||||
A2 = A2Prefix ++ ":" ++ DigestURI;
|
||||
_ ->
|
||||
A2 = A2Prefix ++ ":" ++ DigestURI ++
|
||||
":00000000000000000000000000000000"
|
||||
end,
|
||||
T = hex(binary_to_list(crypto:md5(A1))) ++ ":" ++ Nonce ++ ":" ++
|
||||
NC ++ ":" ++ CNonce ++ ":" ++ QOP ++ ":" ++
|
||||
hex(binary_to_list(crypto:md5(A2))),
|
||||
|
||||
@@ -10,26 +10,25 @@
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-export([start/1, stop/0, mech_new/3, mech_step/2, parse/1]).
|
||||
-export([start/1, stop/0, mech_new/0, mech_step/2, parse/1]).
|
||||
|
||||
-behaviour(cyrsasl).
|
||||
%-behaviour(gen_mod).
|
||||
|
||||
-record(state, {check_password}).
|
||||
|
||||
start(_Opts) ->
|
||||
cyrsasl:register_mechanism("PLAIN", ?MODULE, false),
|
||||
start(Opts) ->
|
||||
cyrsasl:register_mechanism("PLAIN", ?MODULE),
|
||||
ok.
|
||||
|
||||
stop() ->
|
||||
ok.
|
||||
|
||||
mech_new(_Host, _GetPassword, CheckPassword) ->
|
||||
{ok, #state{check_password = CheckPassword}}.
|
||||
mech_new() ->
|
||||
{ok, []}.
|
||||
|
||||
mech_step(State, ClientIn) ->
|
||||
case parse(ClientIn) of
|
||||
[AuthzId, User, Password] ->
|
||||
case (State#state.check_password)(User, Password) of
|
||||
case ejabberd_auth:check_password(User, Password) of
|
||||
true ->
|
||||
{ok, [{username, User}, {authzid, AuthzId}]};
|
||||
_ ->
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{application, ejabberd,
|
||||
[{description, "ejabberd"},
|
||||
{vsn, "1.1.2"},
|
||||
{vsn, "0.1-alpha"},
|
||||
{modules, [acl,
|
||||
configure,
|
||||
cyrsasl,
|
||||
|
||||
@@ -20,15 +20,11 @@
|
||||
%{acl, test, {user_regexp, "^test"}}.
|
||||
%{acl, test, {user_glob, "test*"}}.
|
||||
|
||||
% Everybody can create pubsub nodes
|
||||
{access, pubsub_createnode, [{allow, all}]}.
|
||||
|
||||
% Only admins can use configuration interface:
|
||||
{access, configure, [{allow, admin}]}.
|
||||
|
||||
% Every username can be registered via in-band registration:
|
||||
% You could replace {allow, all} with {deny, all} to prevent user from using
|
||||
% in-band registration
|
||||
{access, register, [{allow, all}]}.
|
||||
|
||||
% After successful registration user will get message with following subject
|
||||
@@ -75,73 +71,35 @@
|
||||
{access, local, [{allow, local}]}.
|
||||
|
||||
|
||||
% Authentication method. If you want to use internal user base, then use
|
||||
% Authentification method. If you want to use internal user base, then use
|
||||
% this line:
|
||||
{auth_method, internal}.
|
||||
|
||||
% For LDAP authentication use these lines instead of above one:
|
||||
% For LDAP uthentification use these lines instead of above one:
|
||||
%{auth_method, ldap}.
|
||||
%{ldap_servers, ["localhost"]}. % List of LDAP servers
|
||||
%{ldap_uidattr, "uid"}. % LDAP attribute that holds user ID
|
||||
%{ldap_base, "dc=example,dc=com"}. % Search base of LDAP directory
|
||||
%{ldap_rootdn, "dc=example,dc=com"}. % LDAP manager
|
||||
%{ldap_password, "******"}. % Password to LDAP manager
|
||||
|
||||
% For authentication via external script use the following:
|
||||
%{auth_method, external}.
|
||||
%{extauth_program, "/path/to/authentication/script"}.
|
||||
|
||||
% For authentication via ODBC use the following:
|
||||
%{auth_method, odbc}.
|
||||
%{odbc_server, "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"}.
|
||||
%{ldap_base, "dc=example,dc=com"}. % Base of LDAP directory
|
||||
|
||||
|
||||
% Host name:
|
||||
{hosts, ["localhost"]}.
|
||||
{host, "localhost"}.
|
||||
|
||||
%% Define the maximum number of time a single user is allowed to connect:
|
||||
{max_user_sessions, 10}.
|
||||
|
||||
%% Anonymous login support:
|
||||
%% auth_method: anonymous
|
||||
%% anonymous_protocol: sasl_anon|login_anon|both
|
||||
%% allow_multiple_connections: true|false
|
||||
%%{host_config, "public.example.org", [{auth_method, anonymous},
|
||||
%% {allow_multiple_connections, false},
|
||||
%% {anonymous_protocol, sasl_anon}]}.
|
||||
%% To use both anonymous and internal authentication:
|
||||
%%{host_config, "public.example.org", [{auth_method, [anonymous, internal]}]}.
|
||||
|
||||
% Default language for server messages
|
||||
{language, "en"}.
|
||||
|
||||
% Listened ports:
|
||||
{listen,
|
||||
[{5222, ejabberd_c2s, [{access, c2s}, {shaper, c2s_shaper},
|
||||
{max_stanza_size, 65536},
|
||||
starttls, {certfile, "./ssl.pem"}]},
|
||||
{5223, ejabberd_c2s, [{access, c2s},
|
||||
{max_stanza_size, 65536},
|
||||
tls, {certfile, "./ssl.pem"}]},
|
||||
% Use these two lines instead if TLS support is not compiled
|
||||
%{5222, ejabberd_c2s, [{access, c2s}, {shaper, c2s_shaper}]},
|
||||
%{5223, ejabberd_c2s, [{access, c2s}, ssl, {certfile, "./ssl.pem"}]},
|
||||
{5269, ejabberd_s2s_in, [{shaper, s2s_shaper},
|
||||
{max_stanza_size, 131072}
|
||||
]},
|
||||
[{5222, ejabberd_c2s, [{access, c2s}, {shaper, c2s_shaper}]},
|
||||
{5223, ejabberd_c2s, [{access, c2s}, ssl, {certfile, "./ssl.pem"}]},
|
||||
{5269, ejabberd_s2s_in, [{shaper, s2s_shaper}]},
|
||||
{5280, ejabberd_http, [http_poll, web_admin]},
|
||||
{8888, ejabberd_service, [{access, all},
|
||||
{hosts, ["icq.localhost", "sms.localhost"],
|
||||
[{password, "secret"}]}]}
|
||||
]}.
|
||||
|
||||
|
||||
% Use STARTTLS+Dialback for S2S connections
|
||||
{s2s_use_starttls, true}.
|
||||
{s2s_certfile, "./ssl.pem"}.
|
||||
%{domain_certfile, "example.org", "./example_org.pem"}.
|
||||
%{domain_certfile, "example.com", "./example_com.pem"}.
|
||||
|
||||
% If SRV lookup fails, then port 5269 is used to communicate with remote server
|
||||
{outgoing_s2s_port, 5269}.
|
||||
|
||||
@@ -152,14 +110,12 @@
|
||||
{mod_register, [{access, register}]},
|
||||
{mod_roster, []},
|
||||
{mod_privacy, []},
|
||||
{mod_adhoc, []},
|
||||
{mod_configure, []}, % Depends on mod_adhoc
|
||||
{mod_configure, []},
|
||||
{mod_configure2, []},
|
||||
{mod_disco, []},
|
||||
{mod_stats, []},
|
||||
{mod_vcard, []},
|
||||
{mod_offline, []},
|
||||
{mod_announce, [{access, announce}]}, % Depends on mod_adhoc
|
||||
{mod_echo, [{host, "echo.localhost"}]},
|
||||
{mod_private, []},
|
||||
{mod_irc, []},
|
||||
@@ -171,9 +127,7 @@
|
||||
{mod_muc, [{access, muc},
|
||||
{access_create, muc},
|
||||
{access_admin, muc_admin}]},
|
||||
% {mod_muc_log, []},
|
||||
% {mod_shared_roster, []},
|
||||
{mod_pubsub, [{access_createnode, pubsub_createnode}]},
|
||||
{mod_pubsub, []},
|
||||
{mod_time, []},
|
||||
{mod_last, []},
|
||||
{mod_version, []}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-define(VERSION, "1.1.3").
|
||||
-define(VERSION, "0.6-alpha").
|
||||
|
||||
%-define(ejabberd_debug, true).
|
||||
%-define(DBGFSM, true).
|
||||
@@ -27,9 +27,10 @@
|
||||
[self(),?MODULE,?LINE]++Args)).
|
||||
|
||||
|
||||
-define(MYHOSTS, ejabberd_config:get_global_option(hosts)).
|
||||
-define(MYNAME, hd(ejabberd_config:get_global_option(hosts))).
|
||||
%-define(MYNAME,"e.localhost").
|
||||
-define(MYNAME, ejabberd_config:get_global_option(host)).
|
||||
-define(S2STIMEOUT, 600000).
|
||||
%-define(S2STIMEOUT, 6000).
|
||||
-define(MYLANG, ejabberd_config:get_global_option(language)).
|
||||
|
||||
-define(MSGS_DIR, "msgs").
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : ejabberd_admin.erl
|
||||
%%% Author : Mickael Remond <mremond@process-one.net>
|
||||
%%% Description : This module gathers admin functions used by different
|
||||
%%% access method:
|
||||
%%% - ejabberdctl command-line tool
|
||||
%%% - web admin interface
|
||||
%%% - adhoc mode
|
||||
%%%
|
||||
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%% Id : $Id: $
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Copyright (c) 2006 Process One
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_admin).
|
||||
-author('mickael.remond@process-one.net').
|
||||
-vsn('$Revision: $ ').
|
||||
|
||||
-export([restore/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
%% Mnesia database restore
|
||||
%% This function is called from ejabberd_ctl, ejabberd_web_admin and
|
||||
%% mod_configure/adhoc
|
||||
restore(Path) ->
|
||||
mnesia:restore(Path, [{keep_tables,keep_tables()},
|
||||
{default_op, skip_tables}]).
|
||||
|
||||
%% This function return a list of tables that should be kept from a previous
|
||||
%% version backup.
|
||||
%% Obsolete tables or tables created by module who are no longer used are not
|
||||
%% restored and are ignored.
|
||||
keep_tables() ->
|
||||
lists:flatten([acl, passwd, config, local_config, disco_publish,
|
||||
keep_modules_tables()]).
|
||||
|
||||
%% Returns the list of modules tables in use, according to the list of actually
|
||||
%% loaded modules
|
||||
keep_modules_tables() ->
|
||||
lists:map(fun(Module) -> module_tables(Module) end,
|
||||
gen_mod:loaded_modules(?MYNAME)).
|
||||
|
||||
%% TODO: This mapping should probably be moved to a callback function in each
|
||||
%% module.
|
||||
%% Mapping between modules and their tables
|
||||
module_tables(mod_announce) -> [motd, motd_users];
|
||||
module_tables(mod_irc) -> [irc_custom];
|
||||
module_tables(mod_last) -> [last_activity];
|
||||
module_tables(mod_muc) -> [muc_room, muc_registered];
|
||||
module_tables(mod_offline) -> [offline_msg];
|
||||
module_tables(mod_privacy) -> [privacy];
|
||||
module_tables(mod_private) -> [private_storage];
|
||||
module_tables(mod_pubsub) -> [pubsub_node];
|
||||
module_tables(mod_roster) -> [roster];
|
||||
module_tables(mod_shared_roster) -> [sr_group, sr_user];
|
||||
module_tables(mod_vcard) -> [vcard, vcard_search];
|
||||
module_tables(_Other) -> [].
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
-export([start/2, stop/1, init/0]).
|
||||
|
||||
-export([dump_ports/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
start(normal, _Args) ->
|
||||
@@ -24,16 +26,15 @@ start(normal, _Args) ->
|
||||
catch ssl:start(),
|
||||
translate:start(),
|
||||
acl:start(),
|
||||
ejabberd_ctl:init(),
|
||||
gen_mod:start(),
|
||||
ejabberd_config:start(),
|
||||
Sup = ejabberd_sup:start_link(),
|
||||
ejabberd_auth:start(),
|
||||
cyrsasl:start(),
|
||||
% Profiling
|
||||
%eprof:start(),
|
||||
%eprof:profile([self()]),
|
||||
%fprof:trace(start, "/tmp/fprof"),
|
||||
Sup = ejabberd_sup:start_link(),
|
||||
start(),
|
||||
load_modules(),
|
||||
Sup;
|
||||
@@ -63,11 +64,8 @@ init() ->
|
||||
end
|
||||
end,
|
||||
error_logger:add_report_handler(ejabberd_logger_h, LogPath),
|
||||
erl_ddll:load_driver(ejabberd:get_so_path(), tls_drv),
|
||||
case erl_ddll:load_driver(ejabberd:get_so_path(), expat_erl) of
|
||||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
%timer:apply_interval(3600000, ?MODULE, dump_ports, []),
|
||||
ok = erl_ddll:load_driver(ejabberd:get_so_path(), expat_erl),
|
||||
Port = open_port({spawn, expat_erl}, [binary]),
|
||||
loop(Port).
|
||||
|
||||
@@ -89,16 +87,17 @@ db_init() ->
|
||||
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity).
|
||||
|
||||
load_modules() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
case ejabberd_config:get_local_option({modules, Host}) of
|
||||
undefined ->
|
||||
ok;
|
||||
Modules ->
|
||||
lists:foreach(
|
||||
fun({Module, Args}) ->
|
||||
gen_mod:start_module(Host, Module, Args)
|
||||
end, Modules)
|
||||
end
|
||||
end, ?MYHOSTS).
|
||||
case ejabberd_config:get_local_option(modules) of
|
||||
undefined ->
|
||||
ok;
|
||||
Modules ->
|
||||
lists:foreach(fun({Module, Args}) ->
|
||||
gen_mod:start_module(Module, Args)
|
||||
end, Modules)
|
||||
end.
|
||||
|
||||
|
||||
dump_ports() ->
|
||||
?INFO_MSG("ports:~n ~p",
|
||||
[lists:map(fun(P) -> erlang:port_info(P) end, erlang:ports())]).
|
||||
|
||||
|
||||
@@ -1,176 +1,359 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : Authentification
|
||||
%%% Purpose :
|
||||
%%% Created : 23 Nov 2002 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Updated : 23 Feb 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%% for anonymous login support
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%% TODO: Use the functions in ejabberd auth to add and remove users.
|
||||
|
||||
-module(ejabberd_auth).
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% External exports
|
||||
-export([start/0,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
try_register/3,
|
||||
-export([start/0, start_link/0,
|
||||
set_password/2,
|
||||
check_password/2,
|
||||
check_password/4,
|
||||
try_register/2,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
get_password/2,
|
||||
get_password_s/2,
|
||||
is_user_exists/2,
|
||||
is_user_exists_in_other_modules/3,
|
||||
get_password/1,
|
||||
get_password_s/1,
|
||||
is_user_exists/1,
|
||||
remove_user/1,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/1,
|
||||
ctl_process_get_registered/3
|
||||
plain_password_required/0,
|
||||
check_password_ldap/2, % TODO: remove
|
||||
is_user_exists_ldap/1 % TODO: remove
|
||||
]).
|
||||
|
||||
-export([auth_modules/1]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
code_change/3,
|
||||
handle_info/2,
|
||||
terminate/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_ctl.hrl").
|
||||
-include("eldap/eldap.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
-record(passwd, {user, password}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
lists:foreach(
|
||||
fun(M) ->
|
||||
M:start(Host)
|
||||
end, auth_modules(Host))
|
||||
end, ?MYHOSTS).
|
||||
gen_server:start({local, ejabberd_auth}, ejabberd_auth, [], []).
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ejabberd_auth}, ejabberd_auth, [], []).
|
||||
|
||||
plain_password_required(Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:plain_password_required()
|
||||
end, auth_modules(Server)).
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Callback functions from gen_server
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:check_password(User, Server, Password)
|
||||
end, auth_modules(Server)).
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: init/1
|
||||
%% Returns: {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%%----------------------------------------------------------------------
|
||||
init([]) ->
|
||||
mnesia:create_table(passwd,[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, passwd)}]),
|
||||
case auth_method() of
|
||||
internal ->
|
||||
ok;
|
||||
ldap ->
|
||||
LDAPServers = ejabberd_config:get_local_option(ldap_servers),
|
||||
eldap:start_link("ejabberd", LDAPServers, 389, "", "")
|
||||
end,
|
||||
{ok, #state{}}.
|
||||
|
||||
check_password(User, Server, Password, StreamID, Digest) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:check_password(User, Server, Password, StreamID, Digest)
|
||||
end, auth_modules(Server)).
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_call/3
|
||||
%% Returns: {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} | (terminate/2 is called)
|
||||
%% {stop, Reason, State} (terminate/2 is called)
|
||||
%%----------------------------------------------------------------------
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
lists:foldl(
|
||||
fun(M, {error, _}) ->
|
||||
M:set_password(User, Server, Password);
|
||||
(_M, Res) ->
|
||||
Res
|
||||
end, {error, not_allowed}, auth_modules(Server)).
|
||||
|
||||
try_register(User, Server, Password) ->
|
||||
case is_user_exists(User,Server) of
|
||||
true ->
|
||||
{atomic, exists};
|
||||
false ->
|
||||
case lists:member(jlib:nameprep(Server), ?MYHOSTS) of
|
||||
true ->
|
||||
lists:foldl(
|
||||
fun(_M, {atomic, ok} = Res) ->
|
||||
Res;
|
||||
(M, _) ->
|
||||
M:try_register(User, Server, Password)
|
||||
end, {error, not_allowed}, auth_modules(Server));
|
||||
false ->
|
||||
{error, not_allowed}
|
||||
end
|
||||
end.
|
||||
|
||||
%% Registered users list do not include anonymous users logged
|
||||
dirty_get_registered_users() ->
|
||||
lists:flatmap(
|
||||
fun(M) ->
|
||||
M:dirty_get_registered_users()
|
||||
end, auth_modules(?MYNAME)).
|
||||
|
||||
%% Registered users list do not include anonymous users logged
|
||||
get_vh_registered_users(Server) ->
|
||||
lists:flatmap(
|
||||
fun(M) ->
|
||||
M:get_vh_registered_users(Server)
|
||||
end, auth_modules(Server)).
|
||||
|
||||
get_password(User, Server) ->
|
||||
lists:foldl(
|
||||
fun(M, false) ->
|
||||
M:get_password(User, Server);
|
||||
(_M, Password) ->
|
||||
Password
|
||||
end, false, auth_modules(Server)).
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false ->
|
||||
"";
|
||||
Password ->
|
||||
Password
|
||||
end.
|
||||
|
||||
%% Returns true if the user exists in the DB or if an anonymous user is logged
|
||||
%% under the given name
|
||||
is_user_exists(User, Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:is_user_exists(User, Server)
|
||||
end, auth_modules(Server)).
|
||||
|
||||
%% Check if the user exists in all authentications module except the module
|
||||
%% passed as parameter
|
||||
is_user_exists_in_other_modules(Module, User, Server) ->
|
||||
lists:any(
|
||||
fun(M) ->
|
||||
M:is_user_exists(User, Server)
|
||||
end, auth_modules(Server)--[Module]).
|
||||
|
||||
remove_user(User, Server) ->
|
||||
lists:foreach(
|
||||
fun(M) ->
|
||||
M:remove_user(User, Server)
|
||||
end, auth_modules(Server)).
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
lists:foreach(
|
||||
fun(M) ->
|
||||
M:remove_user(User, Server, Password)
|
||||
end, auth_modules(Server)).
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_cast/2
|
||||
%% Returns: {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State} (terminate/2 is called)
|
||||
%%----------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
ctl_process_get_registered(_Val, Host, ["registered-users"]) ->
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
SUsers = lists:sort(Users),
|
||||
FUsers = lists:map(fun({U, _S}) -> [U, NewLine] end, SUsers),
|
||||
io:format("~s", [FUsers]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process_get_registered(Val, _Host, _Args) ->
|
||||
Val.
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_info/2
|
||||
%% Returns: {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State} (terminate/2 is called)
|
||||
%%----------------------------------------------------------------------
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: terminate/2
|
||||
%% Purpose: Shutdown the server
|
||||
%% Returns: any (ignored by gen_server)
|
||||
%%----------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
auth_modules(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
Method = ejabberd_config:get_local_option({auth_method, LServer}),
|
||||
Methods = if
|
||||
Method == undefined -> [];
|
||||
is_list(Method) -> Method;
|
||||
is_atom(Method) -> [Method]
|
||||
end,
|
||||
[list_to_atom("ejabberd_auth_" ++ atom_to_list(M)) || M <- Methods].
|
||||
|
||||
auth_method() ->
|
||||
case ejabberd_config:get_local_option(auth_method) of
|
||||
ldap ->
|
||||
ldap;
|
||||
_ ->
|
||||
internal
|
||||
end.
|
||||
|
||||
plain_password_required() ->
|
||||
case auth_method() of
|
||||
internal ->
|
||||
false;
|
||||
ldap ->
|
||||
true
|
||||
end.
|
||||
|
||||
|
||||
check_password(User, Password) ->
|
||||
case auth_method() of
|
||||
internal ->
|
||||
check_password_internal(User, Password);
|
||||
ldap ->
|
||||
check_password_ldap(User, Password)
|
||||
end.
|
||||
|
||||
check_password_internal(User, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
case catch mnesia:dirty_read({passwd, LUser}) of
|
||||
[#passwd{password = Password}] ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
check_password(User, Password, StreamID, Digest) ->
|
||||
case auth_method() of
|
||||
internal ->
|
||||
check_password_internal(User, Password, StreamID, Digest);
|
||||
ldap ->
|
||||
check_password_ldap(User, Password, StreamID, Digest)
|
||||
end.
|
||||
|
||||
check_password_internal(User, Password, StreamID, Digest) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
case catch mnesia:dirty_read({passwd, LUser}) of
|
||||
[#passwd{password = Passwd}] ->
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
Digest == sha:sha(StreamID ++ Passwd);
|
||||
true ->
|
||||
false
|
||||
end,
|
||||
if DigRes ->
|
||||
true;
|
||||
true ->
|
||||
(Passwd == Password) and (Password /= "")
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
|
||||
set_password(User, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error -> {error, invalid_jid};
|
||||
LUser ->
|
||||
F = fun() ->
|
||||
mnesia:write(#passwd{user = LUser,
|
||||
password = Password})
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
end.
|
||||
|
||||
|
||||
try_register(User, Password) ->
|
||||
case auth_method() of
|
||||
internal ->
|
||||
try_register_internal(User, Password);
|
||||
ldap ->
|
||||
{error, not_allowed}
|
||||
end.
|
||||
|
||||
try_register_internal(User, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error -> {error, invalid_jid};
|
||||
LUser ->
|
||||
F = fun() ->
|
||||
case mnesia:read({passwd, LUser}) of
|
||||
[] ->
|
||||
mnesia:write(#passwd{user = LUser,
|
||||
password = Password}),
|
||||
ok;
|
||||
[_E] ->
|
||||
exists
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
mnesia:dirty_all_keys(passwd).
|
||||
|
||||
get_password(User) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
case catch mnesia:dirty_read(passwd, LUser) of
|
||||
[#passwd{password = Password}] ->
|
||||
Password;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
get_password_s(User) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
case catch mnesia:dirty_read(passwd, LUser) of
|
||||
[#passwd{password = Password}] ->
|
||||
Password;
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
is_user_exists(User) ->
|
||||
case auth_method() of
|
||||
internal ->
|
||||
is_user_exists_internal(User);
|
||||
ldap ->
|
||||
is_user_exists_ldap(User)
|
||||
end.
|
||||
|
||||
is_user_exists_internal(User) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
case catch mnesia:dirty_read({passwd, LUser}) of
|
||||
[] ->
|
||||
false;
|
||||
[_] ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
remove_user(User) ->
|
||||
case auth_method() of
|
||||
internal ->
|
||||
remove_user_internal(User);
|
||||
ldap ->
|
||||
{error, not_allowed}
|
||||
end.
|
||||
|
||||
remove_user_internal(User) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
F = fun() ->
|
||||
mnesia:delete({passwd, LUser})
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
catch mod_roster:remove_user(User),
|
||||
catch mod_offline:remove_user(User),
|
||||
catch mod_last:remove_user(User),
|
||||
catch mod_vcard:remove_user(User),
|
||||
catch mod_private:remove_user(User).
|
||||
|
||||
remove_user(User, Password) ->
|
||||
case auth_method() of
|
||||
internal ->
|
||||
remove_user_internal(User, Password);
|
||||
ldap ->
|
||||
not_allowed
|
||||
end.
|
||||
|
||||
remove_user_internal(User, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
F = fun() ->
|
||||
case mnesia:read({passwd, LUser}) of
|
||||
[#passwd{password = Password}] ->
|
||||
mnesia:delete({passwd, LUser}),
|
||||
ok;
|
||||
[_] ->
|
||||
not_allowed;
|
||||
_ ->
|
||||
not_exists
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
catch mod_roster:remove_user(User),
|
||||
catch mod_offline:remove_user(User),
|
||||
catch mod_last:remove_user(User),
|
||||
catch mod_vcard:remove_user(User),
|
||||
catch mod_private:remove_user(User),
|
||||
ok;
|
||||
{atomic, Res} ->
|
||||
Res;
|
||||
_ ->
|
||||
bad_request
|
||||
end.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
check_password_ldap(User, Password, StreamID, Digest) ->
|
||||
check_password_ldap(User, Password).
|
||||
|
||||
check_password_ldap(User, Password) ->
|
||||
case find_user_dn(User) of
|
||||
false ->
|
||||
false;
|
||||
DN ->
|
||||
case eldap:bind("ejabberd", DN, Password) of
|
||||
ok ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
is_user_exists_ldap(User) ->
|
||||
case find_user_dn(User) of
|
||||
false ->
|
||||
false;
|
||||
_DN ->
|
||||
true
|
||||
end.
|
||||
|
||||
find_user_dn(User) ->
|
||||
Attr = ejabberd_config:get_local_option(ldap_uidattr),
|
||||
Filter = eldap:equalityMatch(Attr, User),
|
||||
Base = ejabberd_config:get_local_option(ldap_base),
|
||||
case eldap:search("ejabberd", [{base, Base},
|
||||
{filter, Filter},
|
||||
{attributes, []}]) of
|
||||
#eldap_search_result{entries = [E | _]} ->
|
||||
E#eldap_entry.object_name;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth_anonymous.erl
|
||||
%%% Author : Mickael Remond <mickael.remond@process-one.net>
|
||||
%%% Purpose : Anonymous feature support in ejabberd
|
||||
%%% Created : 17 Feb 2006 by Mickael Remond <mremond@process-one.net>
|
||||
%%%
|
||||
%%% Anonymous support is based on the work of Magnus Henoch
|
||||
%%% <henoch@dtek.chalmers.se> and heavily extended by Process-one.
|
||||
%%%
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_anonymous).
|
||||
-author('mickael.remond@process-one.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-export([start/1,
|
||||
allow_anonymous/1,
|
||||
is_sasl_anonymous_enabled/1,
|
||||
is_login_anonymous_enabled/1,
|
||||
anonymous_user_exist/2,
|
||||
allow_multiple_connections/1,
|
||||
register_connection/2,
|
||||
unregister_connection/2
|
||||
]).
|
||||
|
||||
|
||||
%% Function used by ejabberd_auth:
|
||||
-export([login/2,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
try_register/3,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
get_password/2,
|
||||
get_password/3,
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-record(anonymous, {us, sid}).
|
||||
|
||||
%% Create the anonymous table if at least one virtual host has anonymous features enabled
|
||||
%% Register to login / logout events
|
||||
start(Host) ->
|
||||
%% TODO: Check cluster mode
|
||||
mnesia:create_table(anonymous, [{ram_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, anonymous)}]),
|
||||
%% The hooks are needed to add / remove users from the anonymous tables
|
||||
ejabberd_hooks:add(sm_register_connection_hook, Host,
|
||||
?MODULE, register_connection, 100),
|
||||
ejabberd_hooks:add(sm_remove_connection_hook, Host,
|
||||
?MODULE, unregister_connection, 100),
|
||||
ok.
|
||||
|
||||
%% Return true if anonymous is allowed for host or false otherwise
|
||||
allow_anonymous(Host) ->
|
||||
lists:member(?MODULE, ejabberd_auth:auth_modules(Host)).
|
||||
|
||||
%% Return true if anonymous mode is enabled and if anonymous protocol is SASL
|
||||
%% anonymous protocol can be: sasl_anon|login_anon|both
|
||||
is_sasl_anonymous_enabled(Host) ->
|
||||
case allow_anonymous(Host) of
|
||||
false -> false;
|
||||
true ->
|
||||
case anonymous_protocol(Host) of
|
||||
sasl_anon -> true;
|
||||
both -> true;
|
||||
_Other -> false
|
||||
end
|
||||
end.
|
||||
|
||||
%% Return true if anonymous login is enabled on the server
|
||||
%% anonymous login can be use using standard authentication method (i.e. with
|
||||
%% clients that do not support anonymous login)
|
||||
is_login_anonymous_enabled(Host) ->
|
||||
case allow_anonymous(Host) of
|
||||
false -> false;
|
||||
true ->
|
||||
case anonymous_protocol(Host) of
|
||||
login_anon -> true;
|
||||
both -> true;
|
||||
_Other -> false
|
||||
end
|
||||
end.
|
||||
|
||||
%% Return the anonymous protocol to use: sasl_anon|login_anon|both
|
||||
%% defaults to login_anon
|
||||
anonymous_protocol(Host) ->
|
||||
case ejabberd_config:get_local_option({anonymous_protocol, Host}) of
|
||||
sasl_anon -> sasl_anon;
|
||||
login_anon -> login_anon;
|
||||
both -> both;
|
||||
_Other -> sasl_anon
|
||||
end.
|
||||
|
||||
%% Return true if multiple connections have been allowed in the config file
|
||||
%% defaults to false
|
||||
allow_multiple_connections(Host) ->
|
||||
case ejabberd_config:get_local_option({allow_multiple_connections, Host}) of
|
||||
true -> true;
|
||||
_Other -> false
|
||||
end.
|
||||
|
||||
%% Check if user exist in the anonymus database
|
||||
anonymous_user_exist(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({anonymous, US}) of
|
||||
[] ->
|
||||
false;
|
||||
[_H|_T] ->
|
||||
true
|
||||
end.
|
||||
|
||||
%% Remove connection from Mnesia tables
|
||||
remove_connection(SID, LUser, LServer) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:delete_object({anonymous, US, SID})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
%% Register connection
|
||||
register_connection(SID, #jid{luser = LUser, lserver = LServer}) ->
|
||||
US = {LUser, LServer},
|
||||
mnesia:sync_dirty(
|
||||
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
|
||||
end).
|
||||
|
||||
%% Remove an anonymous user from the anonymous users table
|
||||
unregister_connection(SID, #jid{luser = LUser, lserver = LServer}) ->
|
||||
remove_connection(SID, LUser, LServer).
|
||||
|
||||
%% ---------------------------------
|
||||
%% Specific anonymous auth functions
|
||||
%% ---------------------------------
|
||||
|
||||
%% When anonymous login is enabled, check the password for permenant users
|
||||
%% before allowing access
|
||||
check_password(User, Server, Password) ->
|
||||
check_password(User, Server, Password, undefined, undefined).
|
||||
check_password(User, Server, _Password, _StreamID, _Digest) ->
|
||||
%% We refuse login for registered accounts (They cannot logged but
|
||||
%% they however are "reserved")
|
||||
case ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
|
||||
User, Server) of
|
||||
true -> false;
|
||||
false -> login(User, Server)
|
||||
end.
|
||||
|
||||
login(User, Server) ->
|
||||
case is_login_anonymous_enabled(Server) of
|
||||
false -> false;
|
||||
true ->
|
||||
case anonymous_user_exist(User, Server) of
|
||||
%% Reject the login if an anonymous user with the same login
|
||||
%% is already logged and if multiple login has not been enable
|
||||
%% in the config file.
|
||||
true -> allow_multiple_connections(Server);
|
||||
%% Accept login and add user to the anonymous table
|
||||
false -> true
|
||||
end
|
||||
end.
|
||||
|
||||
%% When anonymous login is enabled, check that the user is permanent before
|
||||
%% changing its password
|
||||
set_password(User, Server, _Password) ->
|
||||
case anonymous_user_exist(User, Server) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
{error, not_allowed}
|
||||
end.
|
||||
|
||||
%% When anonymous login is enabled, check if permanent users are allowed on
|
||||
%% the server:
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
[].
|
||||
|
||||
get_vh_registered_users(_Server) ->
|
||||
[].
|
||||
|
||||
|
||||
%% Return password of permanent user or false for anonymous users
|
||||
get_password(User, Server) ->
|
||||
get_password(User, Server, "").
|
||||
|
||||
get_password(User, Server, DefaultValue) ->
|
||||
case anonymous_user_exist(User, Server) of
|
||||
%% We return the default value if the user is anonymous
|
||||
true ->
|
||||
DefaultValue;
|
||||
%% We return the permanent user password otherwise
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% Returns true if the user exists in the DB or if an anonymous user is logged
|
||||
%% under the given name
|
||||
is_user_exists(User, Server) ->
|
||||
anonymous_user_exist(User, Server).
|
||||
|
||||
remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
remove_user(_User, _Server, _Password) ->
|
||||
not_allowed.
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth_external.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : Authentification via LDAP external script
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_external).
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
%% External exports
|
||||
-export([start/1,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
try_register/3,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
get_password/2,
|
||||
get_password_s/2,
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
extauth:start(
|
||||
Host, ejabberd_config:get_local_option({extauth_program, Host})),
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
extauth:check_password(User, Server, Password).
|
||||
|
||||
check_password(User, Server, Password, _StreamID, _Digest) ->
|
||||
check_password(User, Server, Password).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
extauth:set_password(User, Server, Password).
|
||||
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
[].
|
||||
|
||||
get_vh_registered_users(_Server) ->
|
||||
[].
|
||||
|
||||
get_password(_User, _Server) ->
|
||||
false.
|
||||
|
||||
get_password_s(_User, _Server) ->
|
||||
"".
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
extauth:is_user_exists(User, Server).
|
||||
|
||||
remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
remove_user(_User, _Server, _Password) ->
|
||||
not_allowed.
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth_internal.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : Authentification via mnesia
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_internal).
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
%% External exports
|
||||
-export([start/1,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
try_register/3,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
get_password/2,
|
||||
get_password_s/2,
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(passwd, {us, password}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
mnesia:create_table(passwd, [{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, passwd)}]),
|
||||
update_table(),
|
||||
ejabberd_ctl:register_commands(
|
||||
Host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered),
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Password}] ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, StreamID, Digest) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Passwd}] ->
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
Digest == sha:sha(StreamID ++ Passwd);
|
||||
true ->
|
||||
false
|
||||
end,
|
||||
if DigRes ->
|
||||
true;
|
||||
true ->
|
||||
(Passwd == Password) and (Password /= "")
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
if
|
||||
(LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
true ->
|
||||
F = fun() ->
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password})
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
end.
|
||||
|
||||
|
||||
try_register(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
if
|
||||
(LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
true ->
|
||||
F = fun() ->
|
||||
case mnesia:read({passwd, US}) of
|
||||
[] ->
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password}),
|
||||
ok;
|
||||
[_E] ->
|
||||
exists
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
mnesia:dirty_all_keys(passwd).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
mnesia:dirty_select(
|
||||
passwd,
|
||||
[{#passwd{us = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, LServer}],
|
||||
['$1']}]).
|
||||
|
||||
get_password(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read(passwd, US) of
|
||||
[#passwd{password = Password}] ->
|
||||
Password;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read(passwd, US) of
|
||||
[#passwd{password = Password}] ->
|
||||
Password;
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[] ->
|
||||
false;
|
||||
[_] ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
mnesia:delete({passwd, US})
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
ejabberd_hooks:run(remove_user, LServer, [User, Server]).
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
F = fun() ->
|
||||
case mnesia:read({passwd, US}) of
|
||||
[#passwd{password = Password}] ->
|
||||
mnesia:delete({passwd, US}),
|
||||
ok;
|
||||
[_] ->
|
||||
not_allowed;
|
||||
_ ->
|
||||
not_exists
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
ejabberd_hooks:run(remove_user, LServer, [User, Server]),
|
||||
ok;
|
||||
{atomic, Res} ->
|
||||
Res;
|
||||
_ ->
|
||||
bad_request
|
||||
end.
|
||||
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, passwd),
|
||||
case mnesia:table_info(passwd, attributes) of
|
||||
Fields ->
|
||||
ok;
|
||||
[user, password] ->
|
||||
?INFO_MSG("Converting passwd table from "
|
||||
"{user, password} format", []),
|
||||
Host = ?MYNAME,
|
||||
{atomic, ok} = mnesia:create_table(
|
||||
ejabberd_auth_internal_tmp_table,
|
||||
[{disc_only_copies, [node()]},
|
||||
{type, bag},
|
||||
{local_content, true},
|
||||
{record_name, passwd},
|
||||
{attributes, record_info(fields, passwd)}]),
|
||||
mnesia:transform_table(passwd, ignore, Fields),
|
||||
F1 = fun() ->
|
||||
mnesia:write_lock_table(ejabberd_auth_internal_tmp_table),
|
||||
mnesia:foldl(
|
||||
fun(#passwd{us = U} = R, _) ->
|
||||
mnesia:dirty_write(
|
||||
ejabberd_auth_internal_tmp_table,
|
||||
R#passwd{us = {U, Host}})
|
||||
end, ok, passwd)
|
||||
end,
|
||||
mnesia:transaction(F1),
|
||||
mnesia:clear_table(passwd),
|
||||
F2 = fun() ->
|
||||
mnesia:write_lock_table(passwd),
|
||||
mnesia:foldl(
|
||||
fun(R, _) ->
|
||||
mnesia:dirty_write(R)
|
||||
end, ok, ejabberd_auth_internal_tmp_table)
|
||||
end,
|
||||
mnesia:transaction(F2),
|
||||
mnesia:delete_table(ejabberd_auth_internal_tmp_table);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating passwd table", []),
|
||||
mnesia:transform_table(passwd, ignore, Fields)
|
||||
end.
|
||||
|
||||
|
||||
|
||||
@@ -1,385 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth_ldap.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : Authentification via LDAP
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_ldap).
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1,
|
||||
handle_info/2,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
terminate/2,
|
||||
code_change/3
|
||||
]).
|
||||
|
||||
%% External exports
|
||||
-export([start/1,
|
||||
stop/1,
|
||||
start_link/1,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
try_register/3,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
get_password/2,
|
||||
get_password_s/2,
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("eldap/eldap.hrl").
|
||||
|
||||
-record(state, {host,
|
||||
eldap_id,
|
||||
bind_eldap_id,
|
||||
servers,
|
||||
port,
|
||||
dn,
|
||||
password,
|
||||
base,
|
||||
uidattr,
|
||||
uidattr_format,
|
||||
ufilter,
|
||||
sfilter,
|
||||
dn_filter,
|
||||
dn_filter_attrs
|
||||
}).
|
||||
|
||||
%% Unused callbacks.
|
||||
handle_cast(_Request, State) ->
|
||||
{noreply, State}.
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
%% -----
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
start(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
ChildSpec = {
|
||||
Proc, {?MODULE, start_link, [Host]},
|
||||
permanent, 1000, worker, [?MODULE]
|
||||
},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:call(Proc, stop),
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
start_link(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
ejabberd_ctl:unregister_commands(
|
||||
State#state.host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered).
|
||||
|
||||
init(Host) ->
|
||||
State = parse_options(Host),
|
||||
eldap:start_link(State#state.eldap_id,
|
||||
State#state.servers,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password),
|
||||
eldap:start_link(State#state.bind_eldap_id,
|
||||
State#state.servers,
|
||||
State#state.port,
|
||||
State#state.dn,
|
||||
State#state.password),
|
||||
ejabberd_ctl:register_commands(
|
||||
Host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered),
|
||||
{ok, State}.
|
||||
|
||||
-define(REPLY_TIMEOUT, 10000).
|
||||
|
||||
plain_password_required() ->
|
||||
true.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
Proc = gen_mod:get_module_proc(Server, ?MODULE),
|
||||
case catch gen_server:call(Proc,
|
||||
{check_pass, User, Password}, ?REPLY_TIMEOUT) of
|
||||
{'EXIT', _} ->
|
||||
false;
|
||||
Result ->
|
||||
Result
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, _StreamID, _Digest) ->
|
||||
check_password(User, Server, Password).
|
||||
|
||||
set_password(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
get_vh_registered_users(?MYNAME).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
Proc = gen_mod:get_module_proc(Server, ?MODULE),
|
||||
case catch gen_server:call(Proc,
|
||||
get_vh_registered_users, ?REPLY_TIMEOUT) of
|
||||
{'EXIT', _} ->
|
||||
[];
|
||||
Result ->
|
||||
Result
|
||||
end.
|
||||
|
||||
get_password(_User, _Server) ->
|
||||
false.
|
||||
|
||||
get_password_s(_User, _Server) ->
|
||||
"".
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
Proc = gen_mod:get_module_proc(Server, ?MODULE),
|
||||
case catch gen_server:call(Proc,
|
||||
{is_user_exists, User}, ?REPLY_TIMEOUT) of
|
||||
{'EXIT', _} ->
|
||||
false;
|
||||
Result ->
|
||||
Result
|
||||
end.
|
||||
|
||||
remove_user(_User, _Server) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
remove_user(_User, _Server, _Password) ->
|
||||
not_allowed.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
handle_call({check_pass, User, Password}, _From, State) ->
|
||||
Reply = case find_user_dn(User, State) of
|
||||
false ->
|
||||
false;
|
||||
DN ->
|
||||
case eldap:bind(State#state.bind_eldap_id, DN, Password) of
|
||||
ok -> true;
|
||||
_ -> false
|
||||
end
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
|
||||
handle_call(get_vh_registered_users, _From, State) ->
|
||||
UA = State#state.uidattr,
|
||||
UAF = State#state.uidattr_format,
|
||||
Eldap_ID = State#state.eldap_id,
|
||||
Server = State#state.host,
|
||||
SortedDNAttrs = usort_attrs(State#state.dn_filter_attrs),
|
||||
Reply = case eldap_filter:parse(State#state.sfilter) of
|
||||
{ok, EldapFilter} ->
|
||||
case eldap:search(Eldap_ID, [{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{attributes, SortedDNAttrs}]) of
|
||||
#eldap_search_result{entries = Entries} ->
|
||||
lists:flatmap(
|
||||
fun(#eldap_entry{attributes = Attrs,
|
||||
object_name = DN}) ->
|
||||
case is_valid_dn(DN, Attrs, State) of
|
||||
false -> [];
|
||||
_ ->
|
||||
case get_ldap_attr(UA, Attrs) of
|
||||
"" -> [];
|
||||
User ->
|
||||
case get_user_part(User, UAF) of
|
||||
{ok, U} ->
|
||||
case jlib:nodeprep(U) of
|
||||
error -> [];
|
||||
LU -> [{LU, jlib:nameprep(Server)}]
|
||||
end;
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
end
|
||||
end, Entries);
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
|
||||
handle_call({is_user_exists, User}, _From, State) ->
|
||||
Reply = case find_user_dn(User, State) of
|
||||
false -> false;
|
||||
_DN -> true
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, ok, State};
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{reply, bad_request, State}.
|
||||
|
||||
find_user_dn(User, State) ->
|
||||
DNAttrs = usort_attrs(State#state.dn_filter_attrs),
|
||||
case eldap_filter:parse(State#state.ufilter, [{"%u", User}]) of
|
||||
{ok, Filter} ->
|
||||
case eldap:search(State#state.eldap_id, [{base, State#state.base},
|
||||
{filter, Filter},
|
||||
{attributes, DNAttrs}]) of
|
||||
#eldap_search_result{entries = [#eldap_entry{attributes = Attrs,
|
||||
object_name = DN} | _]} ->
|
||||
is_valid_dn(DN, Attrs, State);
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
is_valid_dn(DN, _, #state{dn_filter = undefined}) ->
|
||||
DN;
|
||||
|
||||
is_valid_dn(DN, Attrs, State) ->
|
||||
DNAttrs = State#state.dn_filter_attrs,
|
||||
UA = State#state.uidattr,
|
||||
UAF = State#state.uidattr_format,
|
||||
Values = [{"%s", get_ldap_attr(Attr, Attrs), 1} || Attr <- DNAttrs],
|
||||
SubstValues = case get_ldap_attr(UA, Attrs) of
|
||||
"" -> Values;
|
||||
S ->
|
||||
case get_user_part(S, UAF) of
|
||||
{ok, U} -> [{"%u", U} | Values];
|
||||
_ -> Values
|
||||
end
|
||||
end ++ [{"%d", State#state.host}, {"%D", DN}],
|
||||
case eldap_filter:parse(State#state.dn_filter, SubstValues) of
|
||||
{ok, EldapFilter} ->
|
||||
case eldap:search(State#state.eldap_id, [
|
||||
{base, State#state.base},
|
||||
{filter, EldapFilter},
|
||||
{attributes, ["dn"]}]) of
|
||||
#eldap_search_result{entries = [_|_]} ->
|
||||
DN;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Auxiliary functions
|
||||
%%%----------------------------------------------------------------------
|
||||
get_user_part(String, Pattern) ->
|
||||
F = fun(S, P) ->
|
||||
First = string:str(P, "%u"),
|
||||
TailLength = length(P) - (First+1),
|
||||
string:sub_string(S, First, length(S) - TailLength)
|
||||
end,
|
||||
case catch F(String, Pattern) of
|
||||
{'EXIT', _} ->
|
||||
{error, badmatch};
|
||||
Result ->
|
||||
case regexp:sub(Pattern, "%u", Result) of
|
||||
{ok, String, _} -> {ok, Result};
|
||||
_ -> {error, badmatch}
|
||||
end
|
||||
end.
|
||||
|
||||
case_insensitive_match(X, Y) ->
|
||||
X1 = stringprep:tolower(X),
|
||||
Y1 = stringprep:tolower(Y),
|
||||
if
|
||||
X1 == Y1 -> true;
|
||||
true -> false
|
||||
end.
|
||||
|
||||
get_ldap_attr(LDAPAttr, Attributes) ->
|
||||
Res = lists:filter(
|
||||
fun({Name, _}) ->
|
||||
case_insensitive_match(Name, LDAPAttr)
|
||||
end, Attributes),
|
||||
case Res of
|
||||
[{_, [Value|_]}] -> Value;
|
||||
_ -> ""
|
||||
end.
|
||||
|
||||
usort_attrs(Attrs) when is_list(Attrs) ->
|
||||
lists:usort(Attrs);
|
||||
|
||||
usort_attrs(_) ->
|
||||
[].
|
||||
|
||||
parse_options(Host) ->
|
||||
Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?MODULE)),
|
||||
Bind_Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
|
||||
LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}),
|
||||
LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
|
||||
undefined -> 389;
|
||||
P -> P
|
||||
end,
|
||||
RootDN = case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
|
||||
undefined -> "";
|
||||
RDN -> RDN
|
||||
end,
|
||||
Password = case ejabberd_config:get_local_option({ldap_password, Host}) of
|
||||
undefined -> "";
|
||||
Pass -> Pass
|
||||
end,
|
||||
UIDAttr = case ejabberd_config:get_local_option({ldap_uidattr, Host}) of
|
||||
undefined -> "uid";
|
||||
UA -> UA
|
||||
end,
|
||||
UIDAttrFormat = case ejabberd_config:get_local_option({ldap_uidattr_format, Host}) of
|
||||
undefined -> "%u";
|
||||
UAF -> UAF
|
||||
end,
|
||||
SubFilter = "(" ++ UIDAttr ++ "=" ++ UIDAttrFormat ++ ")",
|
||||
UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of
|
||||
undefined -> SubFilter;
|
||||
"" -> SubFilter;
|
||||
F -> "(&" ++ SubFilter ++ F ++ ")"
|
||||
end,
|
||||
SearchFilter = eldap_filter:do_sub(UserFilter, [{"%u", "*"}]),
|
||||
LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}),
|
||||
{DNFilter, DNFilterAttrs} =
|
||||
case ejabberd_config:get_local_option({ldap_dn_filter, Host}) of
|
||||
undefined -> {undefined, undefined};
|
||||
{DNF, DNFA} -> {DNF, DNFA}
|
||||
end,
|
||||
#state{host = Host,
|
||||
eldap_id = Eldap_ID,
|
||||
bind_eldap_id = Bind_Eldap_ID,
|
||||
servers = LDAPServers,
|
||||
port = LDAPPort,
|
||||
dn = RootDN,
|
||||
password = Password,
|
||||
base = LDAPBase,
|
||||
uidattr = UIDAttr,
|
||||
uidattr_format = UIDAttrFormat,
|
||||
ufilter = UserFilter,
|
||||
sfilter = SearchFilter,
|
||||
dn_filter = DNFilter,
|
||||
dn_filter_attrs = DNFilterAttrs
|
||||
}.
|
||||
@@ -1,215 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth_odbc.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : Authentification via ODBC
|
||||
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_odbc).
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
%% External exports
|
||||
-export([start/1,
|
||||
set_password/3,
|
||||
check_password/3,
|
||||
check_password/5,
|
||||
try_register/3,
|
||||
dirty_get_registered_users/0,
|
||||
get_vh_registered_users/1,
|
||||
get_password/2,
|
||||
get_password_s/2,
|
||||
is_user_exists/2,
|
||||
remove_user/2,
|
||||
remove_user/3,
|
||||
plain_password_required/0
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(passwd, {user, password}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
ChildSpec =
|
||||
{gen_mod:get_module_proc(Host, ejabberd_odbc_sup),
|
||||
{ejabberd_odbc_sup, start_link, [Host]},
|
||||
temporary,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_odbc_sup]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec),
|
||||
ejabberd_ctl:register_commands(
|
||||
Host,
|
||||
[{"registered-users", "list all registered users"}],
|
||||
ejabberd_auth, ctl_process_get_registered),
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
false.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:get_password(LServer, Username) of
|
||||
{selected, ["password"], [{Password}]} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, Server, Password, StreamID, Digest) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:get_password(LServer, Username) of
|
||||
{selected, ["password"], [{Passwd}]} ->
|
||||
DigRes = if
|
||||
Digest /= "" ->
|
||||
Digest == sha:sha(StreamID ++ Passwd);
|
||||
true ->
|
||||
false
|
||||
end,
|
||||
if DigRes ->
|
||||
true;
|
||||
true ->
|
||||
(Passwd == Password) and (Password /= "")
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
{error, invalid_jid};
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
LServer = jlib:nameprep(Server),
|
||||
catch odbc_queries:set_password_t(LServer, Username, Pass)
|
||||
end.
|
||||
|
||||
|
||||
try_register(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
{error, invalid_jid};
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:add_user(LServer, Username, Pass) of
|
||||
{updated, 1} ->
|
||||
{atomic, ok};
|
||||
_ ->
|
||||
{atomic, exists}
|
||||
end
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
get_vh_registered_users(?MYNAME).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:list_users(LServer) of
|
||||
{selected, ["username"], Res} ->
|
||||
[{U, LServer} || {U} <- Res];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
get_password(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:get_password(LServer, Username) of
|
||||
{selected, ["password"], [{Password}]} ->
|
||||
Password;
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
"";
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:get_password(LServer, Username) of
|
||||
{selected, ["password"], [{Password}]} ->
|
||||
Password;
|
||||
_ ->
|
||||
""
|
||||
end
|
||||
end.
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
false;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case catch odbc_queries:get_password(LServer, Username) of
|
||||
{selected, ["password"], [{_Password}]} ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
error;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
LServer = jlib:nameprep(Server),
|
||||
catch odbc_queries:del_user(LServer, Username),
|
||||
ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
|
||||
[User, Server])
|
||||
end.
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
case jlib:nodeprep(User) of
|
||||
error ->
|
||||
error;
|
||||
LUser ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
LServer = jlib:nameprep(Server),
|
||||
F = fun() ->
|
||||
Result = odbc_queries:del_user_return_password(
|
||||
LServer, Username, Pass),
|
||||
case Result of
|
||||
{selected, ["password"], [{Password}]} ->
|
||||
ejabberd_hooks:run(remove_user, jlib:nameprep(Server),
|
||||
[User, Server]),
|
||||
ok;
|
||||
{selected, ["password"], []} ->
|
||||
not_exists;
|
||||
_ ->
|
||||
not_allowed
|
||||
end
|
||||
end,
|
||||
{atomic, Result} = odbc_queries:transaction(LServer, F),
|
||||
Result
|
||||
end.
|
||||
@@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_config.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : Load config file
|
||||
%%% Purpose :
|
||||
%%% Created : 14 Dec 2002 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -19,12 +19,12 @@
|
||||
-record(config, {key, value}).
|
||||
-record(local_config, {key, value}).
|
||||
-record(state, {opts = [],
|
||||
hosts = [],
|
||||
override_local = false,
|
||||
override_global = false,
|
||||
override_acls = false}).
|
||||
|
||||
start() ->
|
||||
%ets:new(ejabberd_config, [named_table, public]),
|
||||
mnesia:create_table(config,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, config)}]),
|
||||
@@ -36,13 +36,7 @@ start() ->
|
||||
mnesia:add_table_copy(local_config, node(), ram_copies),
|
||||
Config = case application:get_env(config) of
|
||||
{ok, Path} -> Path;
|
||||
undefined ->
|
||||
case os:getenv("EJABBERD_CONFIG_PATH") of
|
||||
false ->
|
||||
?CONFIG_PATH;
|
||||
Path ->
|
||||
Path
|
||||
end
|
||||
undefined -> ?CONFIG_PATH
|
||||
end,
|
||||
load_file(Config).
|
||||
|
||||
@@ -50,38 +44,13 @@ start() ->
|
||||
load_file(File) ->
|
||||
case file:consult(File) of
|
||||
{ok, Terms} ->
|
||||
State = lists:foldl(fun search_hosts/2, #state{}, Terms),
|
||||
Res = lists:foldl(fun process_term/2, State, Terms),
|
||||
Res = lists:foldl(fun process_term/2, #state{}, Terms),
|
||||
set_opts(Res);
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Can't load config file ~p: ~p", [File, Reason]),
|
||||
exit(file:format_error(Reason))
|
||||
end.
|
||||
|
||||
search_hosts(Term, State) ->
|
||||
case Term of
|
||||
{host, Host} ->
|
||||
if
|
||||
State#state.hosts == [] ->
|
||||
add_option(hosts, [Host], State#state{hosts = [Host]});
|
||||
true ->
|
||||
?ERROR_MSG("Can't load config file: "
|
||||
"too many hosts definitions", []),
|
||||
exit("too many hosts definitions")
|
||||
end;
|
||||
{hosts, Hosts} ->
|
||||
if
|
||||
State#state.hosts == [] ->
|
||||
add_option(hosts, Hosts, State#state{hosts = Hosts});
|
||||
true ->
|
||||
?ERROR_MSG("Can't load config file: "
|
||||
"too many hosts definitions", []),
|
||||
exit("too many hosts definitions")
|
||||
end;
|
||||
_ ->
|
||||
State
|
||||
end.
|
||||
|
||||
process_term(Term, State) ->
|
||||
case Term of
|
||||
override_global ->
|
||||
@@ -90,59 +59,24 @@ process_term(Term, State) ->
|
||||
State#state{override_local = true};
|
||||
override_acls ->
|
||||
State#state{override_acls = true};
|
||||
{acl, ACLName, ACLData} ->
|
||||
process_host_term(Term, global, State);
|
||||
{access, RuleName, Rules} ->
|
||||
process_host_term(Term, global, State);
|
||||
{shaper, Name, Data} ->
|
||||
lists:foldl(fun(Host, S) -> process_host_term(Term, Host, S) end,
|
||||
State, State#state.hosts);
|
||||
{host, Host} ->
|
||||
State;
|
||||
{hosts, Hosts} ->
|
||||
State;
|
||||
{host_config, Host, Terms} ->
|
||||
lists:foldl(fun(T, S) -> process_host_term(T, Host, S) end,
|
||||
State, Terms);
|
||||
{listen, Val} ->
|
||||
add_option(listen, Val, State);
|
||||
{outgoing_s2s_port, Port} ->
|
||||
add_option(outgoing_s2s_port, Port, State);
|
||||
{s2s_use_starttls, Port} ->
|
||||
add_option(s2s_use_starttls, Port, State);
|
||||
{s2s_certfile, CertFile} ->
|
||||
add_option(s2s_certfile, CertFile, State);
|
||||
{domain_certfile, Domain, CertFile} ->
|
||||
add_option({domain_certfile, Domain}, CertFile, State);
|
||||
{Opt, Val} ->
|
||||
lists:foldl(fun(Host, S) -> process_host_term(Term, Host, S) end,
|
||||
State, State#state.hosts)
|
||||
end.
|
||||
|
||||
process_host_term(Term, Host, State) ->
|
||||
case Term of
|
||||
{acl, ACLName, ACLData} ->
|
||||
State#state{opts =
|
||||
[acl:to_record(Host, ACLName, ACLData) | State#state.opts]};
|
||||
[acl:to_record(ACLName, ACLData) | State#state.opts]};
|
||||
{access, RuleName, Rules} ->
|
||||
State#state{opts = [#config{key = {access, RuleName, Host},
|
||||
State#state{opts = [#config{key = {access, RuleName},
|
||||
value = Rules} |
|
||||
State#state.opts]};
|
||||
{shaper, Name, Data} ->
|
||||
State#state{opts = [#config{key = {shaper, Name, Host},
|
||||
State#state{opts = [#config{key = {shaper, Name},
|
||||
value = Data} |
|
||||
State#state.opts]};
|
||||
{host, Host} ->
|
||||
State;
|
||||
{hosts, Hosts} ->
|
||||
State;
|
||||
{Opt, Val} ->
|
||||
add_option({Opt, Host}, Val, State)
|
||||
add_option(Opt, Val, State)
|
||||
end.
|
||||
|
||||
add_option(Opt, Val, State) ->
|
||||
Table = case Opt of
|
||||
hosts ->
|
||||
host ->
|
||||
config;
|
||||
language ->
|
||||
config;
|
||||
|
||||
@@ -8,344 +8,164 @@
|
||||
|
||||
-module(ejabberd_ctl).
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-export([start/0,
|
||||
init/0,
|
||||
process/1,
|
||||
register_commands/3,
|
||||
register_commands/4,
|
||||
unregister_commands/3,
|
||||
unregister_commands/4]).
|
||||
|
||||
-include("ejabberd_ctl.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-export([start/0]).
|
||||
|
||||
start() ->
|
||||
case init:get_plain_arguments() of
|
||||
[SNode | Args] ->
|
||||
Node = list_to_atom(SNode),
|
||||
Status = case rpc:call(Node, ?MODULE, process, [Args]) of
|
||||
{badrpc, Reason} ->
|
||||
io:format("RPC failed on the node ~p: ~p~n",
|
||||
[Node, Reason]),
|
||||
?STATUS_BADRPC;
|
||||
S ->
|
||||
S
|
||||
end,
|
||||
halt(Status);
|
||||
process(Node, Args);
|
||||
_ ->
|
||||
print_usage(),
|
||||
halt(?STATUS_USAGE)
|
||||
end.
|
||||
|
||||
init() ->
|
||||
ets:new(ejabberd_ctl_cmds, [named_table, set, public]),
|
||||
ets:new(ejabberd_ctl_host_cmds, [named_table, set, public]).
|
||||
print_usage()
|
||||
end,
|
||||
halt().
|
||||
|
||||
|
||||
process(["status"]) ->
|
||||
{InternalStatus, ProvidedStatus} = init:get_status(),
|
||||
io:format("Node ~p is ~p. Status: ~p~n",
|
||||
[node(), InternalStatus, ProvidedStatus]),
|
||||
case lists:keysearch(ejabberd, 1, application:which_applications()) of
|
||||
false ->
|
||||
io:format("ejabberd is not running~n", []),
|
||||
?STATUS_ERROR;
|
||||
{value,_Version} ->
|
||||
io:format("ejabberd is running~n", []),
|
||||
?STATUS_SUCCESS
|
||||
process(Node, ["stop"]) ->
|
||||
case rpc:call(Node, init, stop, []) of
|
||||
{badrpc, Reason} ->
|
||||
io:format("Can't stop node ~p: ~p~n",
|
||||
[Node, Reason]);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
|
||||
process(["stop"]) ->
|
||||
init:stop(),
|
||||
?STATUS_SUCCESS;
|
||||
process(Node, ["restart"]) ->
|
||||
case rpc:call(Node, init, restart, []) of
|
||||
{badrpc, Reason} ->
|
||||
io:format("Can't restart node ~p: ~p~n",
|
||||
[Node, Reason]);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
|
||||
process(["restart"]) ->
|
||||
init:restart(),
|
||||
?STATUS_SUCCESS;
|
||||
process(Node, ["reopen-log"]) ->
|
||||
{error_logger, Node} ! {emulator, noproc, reopen};
|
||||
|
||||
process(["reopen-log"]) ->
|
||||
ejabberd_logger_h:reopen_log(),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["register", User, Server, Password]) ->
|
||||
case ejabberd_auth:try_register(User, Server, Password) of
|
||||
process(Node, ["register", User, Password]) ->
|
||||
case rpc:call(Node, ejabberd_auth, try_register, [User, Password]) of
|
||||
{atomic, ok} ->
|
||||
?STATUS_SUCCESS;
|
||||
ok;
|
||||
{atomic, exists} ->
|
||||
io:format("User ~p already registered at node ~p~n",
|
||||
[User ++ "@" ++ Server, node()]),
|
||||
?STATUS_ERROR;
|
||||
io:format("User ~p already registered on node ~p~n",
|
||||
[User, Node]);
|
||||
{error, Reason} ->
|
||||
io:format("Can't register user ~p at node ~p: ~p~n",
|
||||
[User ++ "@" ++ Server, node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
io:format("Can't register user ~p on node ~p: ~p~n",
|
||||
[User, Node, Reason]);
|
||||
{badrpc, Reason} ->
|
||||
io:format("Can't register user ~p on node ~p: ~p~n",
|
||||
[User, Node, Reason])
|
||||
end;
|
||||
|
||||
process(["unregister", User, Server]) ->
|
||||
case ejabberd_auth:remove_user(User, Server) of
|
||||
process(Node, ["unregister", User]) ->
|
||||
case rpc:call(Node, ejabberd_auth, remove_user, [User]) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{error, Reason} ->
|
||||
io:format("Can't unregister user ~p at node ~p: ~p~n",
|
||||
[User ++ "@" ++ Server, node(), Reason]),
|
||||
?STATUS_ERROR;
|
||||
_ ->
|
||||
?STATUS_SUCCESS
|
||||
io:format("Can't unregister user ~p on node ~p: ~p~n",
|
||||
[User, Node, Reason]);
|
||||
{badrpc, Reason} ->
|
||||
io:format("Can't unregister user ~p on node ~p: ~p~n",
|
||||
[User, Node, Reason])
|
||||
end;
|
||||
|
||||
process(["backup", Path]) ->
|
||||
case mnesia:backup(Path) of
|
||||
process(Node, ["backup", Path]) ->
|
||||
case rpc:call(Node, mnesia, backup, [Path]) of
|
||||
ok ->
|
||||
?STATUS_SUCCESS;
|
||||
ok;
|
||||
{error, Reason} ->
|
||||
io:format("Can't store backup in ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
io:format("Can't store backup in ~p on node ~p: ~p~n",
|
||||
[Path, Node, Reason]);
|
||||
{badrpc, Reason} ->
|
||||
io:format("Can't store backup in ~p on node ~p: ~p~n",
|
||||
[Path, Node, Reason])
|
||||
end;
|
||||
|
||||
process(["dump", Path]) ->
|
||||
case dump_to_textfile(Path) of
|
||||
process(Node, ["dump", Path]) ->
|
||||
case rpc:call(Node, mnesia, dump_to_textfile, [Path]) of
|
||||
ok ->
|
||||
?STATUS_SUCCESS;
|
||||
ok;
|
||||
{error, Reason} ->
|
||||
io:format("Can't store dump in ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
io:format("Can't store dump in ~p on node ~p: ~p~n",
|
||||
[Path, Node, Reason]);
|
||||
{badrpc, Reason} ->
|
||||
io:format("Can't store dump in ~p on node ~p: ~p~n",
|
||||
[Path, Node, Reason])
|
||||
end;
|
||||
|
||||
process(["load", Path]) ->
|
||||
case mnesia:load_textfile(Path) of
|
||||
{atomic, ok} ->
|
||||
?STATUS_SUCCESS;
|
||||
{error, Reason} ->
|
||||
io:format("Can't load dump in ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
end;
|
||||
|
||||
process(["restore", Path]) ->
|
||||
case ejabberd_admin:restore(Path) of
|
||||
{atomic, _} ->
|
||||
?STATUS_SUCCESS;
|
||||
{error, Reason} ->
|
||||
io:format("Can't restore backup from ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR;
|
||||
{aborted,{no_exists,Table}} ->
|
||||
io:format("Can't restore backup from ~p at node ~p: Table ~p does not exist.~n",
|
||||
[filename:absname(Path), node(), Table]),
|
||||
?STATUS_ERROR;
|
||||
{aborted,enoent} ->
|
||||
io:format("Can't restore backup from ~p at node ~p: File not found.~n",
|
||||
[filename:absname(Path), node()]),
|
||||
?STATUS_ERROR
|
||||
end;
|
||||
|
||||
process(["install-fallback", Path]) ->
|
||||
case mnesia:install_fallback(Path) of
|
||||
ok ->
|
||||
?STATUS_SUCCESS;
|
||||
{error, Reason} ->
|
||||
io:format("Can't install fallback from ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
end;
|
||||
|
||||
process(["import-file", Path]) ->
|
||||
case jd2ejd:import_file(Path) of
|
||||
process(Node, ["load", Path]) ->
|
||||
case rpc:call(Node, mnesia, load_textfile, [Path]) of
|
||||
ok ->
|
||||
?STATUS_SUCCESS;
|
||||
ok;
|
||||
{error, Reason} ->
|
||||
io:format("Can't import jabberd 1.4 spool file ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
io:format("Can't load dump in ~p on node ~p: ~p~n",
|
||||
[Path, Node, Reason]);
|
||||
{badrpc, Reason} ->
|
||||
io:format("Can't load dump in ~p on node ~p: ~p~n",
|
||||
[Path, Node, Reason])
|
||||
end;
|
||||
|
||||
process(["import-dir", Path]) ->
|
||||
case jd2ejd:import_dir(Path) of
|
||||
ok ->
|
||||
?STATUS_SUCCESS;
|
||||
{error, Reason} ->
|
||||
io:format("Can't import jabberd 1.4 spool dir ~p at node ~p: ~p~n",
|
||||
[filename:absname(Path), node(), Reason]),
|
||||
?STATUS_ERROR
|
||||
process(Node, ["restore", Path]) ->
|
||||
case rpc:call(Node,
|
||||
mnesia, restore, [Path, [{default_op, keep_tables}]]) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{error, Reason} ->
|
||||
io:format("Can't restore backup from ~p on node ~p: ~p~n",
|
||||
[Path, Node, Reason]);
|
||||
{badrpc, Reason} ->
|
||||
io:format("Can't restore backup from ~p on node ~p: ~p~n",
|
||||
[Path, Node, Reason])
|
||||
end;
|
||||
|
||||
process(["delete-expired-messages"]) ->
|
||||
mod_offline:remove_expired_messages(),
|
||||
?STATUS_SUCCESS;
|
||||
|
||||
process(["delete-old-messages", Days]) ->
|
||||
case catch list_to_integer(Days) of
|
||||
{'EXIT',{Reason, _Stack}} ->
|
||||
io:format("Can't delete old messages (~p). Please pass an integer as parameter.~n",
|
||||
[Reason]),
|
||||
?STATUS_ERROR;
|
||||
Integer when Integer >= 0 ->
|
||||
{atomic, _} = mod_offline:remove_old_messages(Integer),
|
||||
io:format("Removed messages older than ~s days~n", [Days]),
|
||||
?STATUS_SUCCESS;
|
||||
Integer ->
|
||||
io:format("Can't delete old messages. Please pass a positive integer as parameter.~n", []),
|
||||
?STATUS_ERROR
|
||||
process(Node, ["install-fallback", Path]) ->
|
||||
case rpc:call(Node, mnesia, install_fallback, [Path]) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{error, Reason} ->
|
||||
io:format("Can't install fallback from ~p on node ~p: ~p~n",
|
||||
[Path, Node, Reason]);
|
||||
{badrpc, Reason} ->
|
||||
io:format("Can't install fallback from ~p on node ~p: ~p~n",
|
||||
[Path, Node, Reason])
|
||||
end;
|
||||
|
||||
process(["vhost", H | Args]) ->
|
||||
case jlib:nameprep(H) of
|
||||
false ->
|
||||
io:format("Bad hostname: ~p~n", [H]),
|
||||
?STATUS_ERROR;
|
||||
Host ->
|
||||
case ejabberd_hooks:run_fold(
|
||||
ejabberd_ctl_process, Host, false, [Host, Args]) of
|
||||
false ->
|
||||
print_vhost_usage(Host),
|
||||
?STATUS_USAGE;
|
||||
Status ->
|
||||
Status
|
||||
end
|
||||
process(Node, ["registered-users"]) ->
|
||||
case rpc:call(Node, ejabberd_auth, dirty_get_registered_users, []) of
|
||||
Users when is_list(Users) ->
|
||||
NewLine = io_lib:format("~n", []),
|
||||
SUsers = lists:sort(Users),
|
||||
FUsers = lists:map(fun(U) -> [U, NewLine] end, SUsers),
|
||||
io:format("~s", [FUsers]),
|
||||
ok;
|
||||
{ErrorTag, Reason} when (ErrorTag == error) or (ErrorTag == badrpc) ->
|
||||
io:format("Can't get list of registered users on node ~p: ~p~n",
|
||||
[Node, Reason])
|
||||
end;
|
||||
|
||||
process(Args) ->
|
||||
case ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of
|
||||
false ->
|
||||
print_usage(),
|
||||
?STATUS_USAGE;
|
||||
Status ->
|
||||
Status
|
||||
end.
|
||||
process(_Node, _Args) ->
|
||||
print_usage().
|
||||
|
||||
|
||||
|
||||
print_usage() ->
|
||||
CmdDescs =
|
||||
[{"status", "get ejabberd status"},
|
||||
{"stop", "stop ejabberd"},
|
||||
{"restart", "restart ejabberd"},
|
||||
{"reopen-log", "reopen log file"},
|
||||
{"register user server password", "register a user"},
|
||||
{"unregister user server", "unregister a user"},
|
||||
{"backup file", "store a database backup to file"},
|
||||
{"restore file", "restore a database backup from file"},
|
||||
{"install-fallback file", "install a database fallback from file"},
|
||||
{"dump file", "dump a database to a text file"},
|
||||
{"load file", "restore a database from a text file"},
|
||||
{"import-file file", "import user data from jabberd 1.4 spool file"},
|
||||
{"import-dir dir", "import user data from jabberd 1.4 spool directory"},
|
||||
{"delete-expired-messages", "delete expired offline messages from database"},
|
||||
{"delete-old-messages n", "delete offline messages older than n days from database"},
|
||||
{"vhost host ...", "execute host-specific commands"}] ++
|
||||
ets:tab2list(ejabberd_ctl_cmds),
|
||||
MaxCmdLen =
|
||||
lists:max(lists:map(
|
||||
fun({Cmd, _Desc}) ->
|
||||
length(Cmd)
|
||||
end, CmdDescs)),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
FmtCmdDescs =
|
||||
lists:map(
|
||||
fun({Cmd, Desc}) ->
|
||||
[" ", Cmd, string:chars($\s, MaxCmdLen - length(Cmd) + 2),
|
||||
Desc, NewLine]
|
||||
end, CmdDescs),
|
||||
io:format(
|
||||
"Usage: ejabberdctl node command~n"
|
||||
"~n"
|
||||
"Available commands:~n"
|
||||
++ FmtCmdDescs ++
|
||||
" stop\t\t\t\tstop ejabberd~n"
|
||||
" restart\t\t\trestart ejabberd~n"
|
||||
" reopen-log\t\t\treopen log file~n"
|
||||
" register user password\tregister a user~n"
|
||||
" unregister user\t\tunregister a user~n"
|
||||
" backup file\t\t\tstore a database backup in file~n"
|
||||
" restore file\t\t\trestore a database backup from file~n"
|
||||
" install-fallback file\t\tinstall a database fallback from file~n"
|
||||
" dump file\t\t\tdump a database in a text file~n"
|
||||
" load file\t\t\trestore a database from a text file~n"
|
||||
" registered-users\t\tlist all registered users~n"
|
||||
"~n"
|
||||
"Example:~n"
|
||||
" ejabberdctl ejabberd@host restart~n"
|
||||
).
|
||||
|
||||
print_vhost_usage(Host) ->
|
||||
CmdDescs =
|
||||
ets:select(ejabberd_ctl_host_cmds,
|
||||
[{{Host, '$1', '$2'}, [], [{{'$1', '$2'}}]}]),
|
||||
MaxCmdLen =
|
||||
if
|
||||
CmdDescs == [] ->
|
||||
0;
|
||||
true ->
|
||||
lists:max(lists:map(
|
||||
fun({Cmd, _Desc}) ->
|
||||
length(Cmd)
|
||||
end, CmdDescs))
|
||||
end,
|
||||
NewLine = io_lib:format("~n", []),
|
||||
FmtCmdDescs =
|
||||
lists:map(
|
||||
fun({Cmd, Desc}) ->
|
||||
[" ", Cmd, string:chars($\s, MaxCmdLen - length(Cmd) + 2),
|
||||
Desc, NewLine]
|
||||
end, CmdDescs),
|
||||
io:format(
|
||||
"Usage: ejabberdctl node vhost host command~n"
|
||||
"~n"
|
||||
"Available commands:~n"
|
||||
++ FmtCmdDescs ++
|
||||
"~n"
|
||||
).
|
||||
|
||||
register_commands(CmdDescs, Module, Function) ->
|
||||
ets:insert(ejabberd_ctl_cmds, CmdDescs),
|
||||
ejabberd_hooks:add(ejabberd_ctl_process,
|
||||
Module, Function, 50),
|
||||
ok.
|
||||
|
||||
register_commands(Host, CmdDescs, Module, Function) ->
|
||||
ets:insert(ejabberd_ctl_host_cmds,
|
||||
[{Host, Cmd, Desc} || {Cmd, Desc} <- CmdDescs]),
|
||||
ejabberd_hooks:add(ejabberd_ctl_process, Host,
|
||||
Module, Function, 50),
|
||||
ok.
|
||||
|
||||
unregister_commands(CmdDescs, Module, Function) ->
|
||||
lists:foreach(fun(CmdDesc) ->
|
||||
ets:delete_object(ejabberd_ctl_cmds, CmdDesc)
|
||||
end, CmdDescs),
|
||||
ejabberd_hooks:delete(ejabberd_ctl_process,
|
||||
Module, Function, 50),
|
||||
ok.
|
||||
|
||||
unregister_commands(Host, CmdDescs, Module, Function) ->
|
||||
lists:foreach(fun({Cmd, Desc}) ->
|
||||
ets:delete_object(ejabberd_ctl_host_cmds,
|
||||
{Host, Cmd, Desc})
|
||||
end, CmdDescs),
|
||||
ejabberd_hooks:delete(ejabberd_ctl_process,
|
||||
Module, Function, 50),
|
||||
ok.
|
||||
|
||||
dump_to_textfile(File) ->
|
||||
dump_to_textfile(mnesia:system_info(is_running), file:open(File, write)).
|
||||
dump_to_textfile(yes, {ok, F}) ->
|
||||
Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)),
|
||||
Tabs = lists:filter(
|
||||
fun(T) ->
|
||||
case mnesia:table_info(T, storage_type) of
|
||||
disc_copies -> true;
|
||||
disc_only_copies -> true;
|
||||
_ -> false
|
||||
end
|
||||
end, Tabs1),
|
||||
Defs = lists:map(
|
||||
fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)},
|
||||
{attributes, mnesia:table_info(T, attributes)}]}
|
||||
end,
|
||||
Tabs),
|
||||
io:format(F, "~p.~n", [{tables, Defs}]),
|
||||
lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs),
|
||||
file:close(F);
|
||||
dump_to_textfile(_, {ok, F}) ->
|
||||
file:close(F),
|
||||
{error, mnesia_not_running};
|
||||
dump_to_textfile(_, {error, Reason}) ->
|
||||
{error, Reason}.
|
||||
|
||||
|
||||
dump_tab(F, T) ->
|
||||
W = mnesia:table_info(T, wild_pattern),
|
||||
{atomic,All} = mnesia:transaction(
|
||||
fun() -> mnesia:match_object(T, W, read) end),
|
||||
lists:foreach(
|
||||
fun(Term) -> io:format(F,"~p.~n", [setelement(1, Term, T)]) end, All).
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_ctl.hrl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose :
|
||||
%%% Created : 13 Feb 2006 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-define(STATUS_SUCCESS, 0).
|
||||
-define(STATUS_ERROR, 1).
|
||||
-define(STATUS_USAGE, 2).
|
||||
-define(STATUS_BADRPC, 3).
|
||||
@@ -1,199 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_hooks.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : Manage hooks
|
||||
%%% Created : 8 Aug 2004 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_hooks).
|
||||
-author('alexey@sevcom.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% External exports
|
||||
-export([start_link/0,
|
||||
add/4,
|
||||
delete/4,
|
||||
run/2,
|
||||
run_fold/3,
|
||||
add/5,
|
||||
delete/5,
|
||||
run/3,
|
||||
run_fold/4]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
code_change/3,
|
||||
handle_info/2,
|
||||
terminate/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ejabberd_hooks}, ejabberd_hooks, [], []).
|
||||
|
||||
add(Hook, Module, Function, Seq) ->
|
||||
add(Hook, global, Module, Function, Seq).
|
||||
|
||||
add(Hook, Host, Module, Function, Seq) ->
|
||||
gen_server:call(ejabberd_hooks, {add, Hook, Host, Module, Function, Seq}).
|
||||
|
||||
delete(Hook, Module, Function, Seq) ->
|
||||
delete(Hook, global, Module, Function, Seq).
|
||||
|
||||
delete(Hook, Host, Module, Function, Seq) ->
|
||||
gen_server:call(ejabberd_hooks, {delete, Hook, Host, Module, Function, Seq}).
|
||||
|
||||
run(Hook, Args) ->
|
||||
run(Hook, global, Args).
|
||||
|
||||
run(Hook, Host, Args) ->
|
||||
case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
run1(Ls, Hook, Args);
|
||||
[] ->
|
||||
ok
|
||||
end.
|
||||
|
||||
run_fold(Hook, Val, Args) ->
|
||||
run_fold(Hook, global, Val, Args).
|
||||
|
||||
run_fold(Hook, Host, Val, Args) ->
|
||||
case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
run_fold1(Ls, Hook, Val, Args);
|
||||
[] ->
|
||||
Val
|
||||
end.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Callback functions from gen_server
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: init/1
|
||||
%% Returns: {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%%----------------------------------------------------------------------
|
||||
init([]) ->
|
||||
ets:new(hooks, [named_table]),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_call/3
|
||||
%% Returns: {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} | (terminate/2 is called)
|
||||
%% {stop, Reason, State} (terminate/2 is called)
|
||||
%%----------------------------------------------------------------------
|
||||
handle_call({add, Hook, Host, Module, Function, Seq}, From, State) ->
|
||||
Reply = case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
El = {Seq, Module, Function},
|
||||
case lists:member(El, Ls) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
NewLs = lists:merge(Ls, [El]),
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
ok
|
||||
end;
|
||||
[] ->
|
||||
NewLs = [{Seq, Module, Function}],
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
ok
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
handle_call({delete, Hook, Host, Module, Function, Seq}, From, State) ->
|
||||
Reply = case ets:lookup(hooks, {Hook, Host}) of
|
||||
[{_, Ls}] ->
|
||||
NewLs = lists:delete({Seq, Module, Function}, Ls),
|
||||
ets:insert(hooks, {{Hook, Host}, NewLs}),
|
||||
ok;
|
||||
[] ->
|
||||
ok
|
||||
end,
|
||||
{reply, Reply, State};
|
||||
handle_call(Request, From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_cast/2
|
||||
%% Returns: {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State} (terminate/2 is called)
|
||||
%%----------------------------------------------------------------------
|
||||
handle_cast(Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_info/2
|
||||
%% Returns: {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State} (terminate/2 is called)
|
||||
%%----------------------------------------------------------------------
|
||||
handle_info(Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: terminate/2
|
||||
%% Purpose: Shutdown the server
|
||||
%% Returns: any (ignored by gen_server)
|
||||
%%----------------------------------------------------------------------
|
||||
terminate(Reason, State) ->
|
||||
ok.
|
||||
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
run1([], Hook, Args) ->
|
||||
ok;
|
||||
run1([{_Seq, Module, Function} | Ls], Hook, Args) ->
|
||||
case catch apply(Module, Function, Args) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nrunning hook: ~p",
|
||||
[Reason, {Hook, Args}]),
|
||||
run1(Ls, Hook, Args);
|
||||
stop ->
|
||||
ok;
|
||||
_ ->
|
||||
run1(Ls, Hook, Args)
|
||||
end.
|
||||
|
||||
|
||||
run_fold1([], Hook, Val, Args) ->
|
||||
Val;
|
||||
run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) ->
|
||||
case catch apply(Module, Function, [Val | Args]) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nrunning hook: ~p",
|
||||
[Reason, {Hook, Args}]),
|
||||
run_fold1(Ls, Hook, Val, Args);
|
||||
stop ->
|
||||
stopped;
|
||||
{stop, NewVal} ->
|
||||
NewVal;
|
||||
NewVal ->
|
||||
run_fold1(Ls, Hook, NewVal, Args)
|
||||
end.
|
||||
|
||||
|
||||
|
||||
@@ -70,8 +70,7 @@ init(Port, Module, Opts) ->
|
||||
{packet, 0},
|
||||
{active, false},
|
||||
{reuseaddr, true},
|
||||
{nodelay, true},
|
||||
{keepalive, true} |
|
||||
{nodelay, true} |
|
||||
SockOpts]),
|
||||
case Res of
|
||||
{ok, ListenSocket} ->
|
||||
@@ -93,13 +92,7 @@ accept(ListenSocket, Module, Opts) ->
|
||||
ok
|
||||
end,
|
||||
{ok, Pid} = Module:start({gen_tcp, Socket}, Opts),
|
||||
case gen_tcp:controlling_process(Socket, Pid) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, _Reason} ->
|
||||
gen_tcp:close(Socket)
|
||||
end,
|
||||
Module:become_controller(Pid),
|
||||
gen_tcp:controlling_process(Socket, Pid),
|
||||
accept(ListenSocket, Module, Opts);
|
||||
{error, Reason} ->
|
||||
?INFO_MSG("(~w) Failed TCP accept: ~w",
|
||||
@@ -146,8 +139,7 @@ accept_ssl(ListenSocket, Module, Opts) ->
|
||||
ok
|
||||
end,
|
||||
{ok, Pid} = Module:start({ssl, Socket}, Opts),
|
||||
catch ssl:controlling_process(Socket, Pid),
|
||||
Module:become_controller(Pid),
|
||||
ssl:controlling_process(Socket, Pid),
|
||||
accept_ssl(ListenSocket, Module, Opts);
|
||||
{error, timeout} ->
|
||||
accept_ssl(ListenSocket, Module, Opts);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_local.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : Route local packets
|
||||
%%% Purpose :
|
||||
%%% Created : 30 Nov 2002 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -10,46 +10,118 @@
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-behaviour(gen_server).
|
||||
-export([start_link/0, init/0]).
|
||||
|
||||
%% API
|
||||
-export([start_link/0]).
|
||||
|
||||
-export([route/3,
|
||||
-export([register_iq_handler/3,
|
||||
register_iq_handler/4,
|
||||
register_iq_handler/5,
|
||||
unregister_iq_handler/2,
|
||||
refresh_iq_handlers/0,
|
||||
bounce_resource_packet/3
|
||||
unregister_iq_handler/1,
|
||||
refresh_iq_handlers/0
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-record(state, {}).
|
||||
-record(state, {mydomain, iqtable}).
|
||||
|
||||
-define(IQTABLE, local_iqtable).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
register(ejabberd_local,
|
||||
Pid = proc_lib:spawn_link(ejabberd_local, init, [])),
|
||||
{ok, Pid}.
|
||||
|
||||
process_iq(From, To, Packet) ->
|
||||
init() ->
|
||||
MyDomain = ?MYNAME,
|
||||
ejabberd_router:register_route(MyDomain),
|
||||
catch ets:new(local_iqtable, [named_table, public]),
|
||||
loop(#state{mydomain = MyDomain,
|
||||
iqtable = local_iqtable}).
|
||||
|
||||
loop(State) ->
|
||||
receive
|
||||
{route, From, To, Packet} ->
|
||||
case catch do_route(State, From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
loop(State);
|
||||
{register_iq_handler, XMLNS, Module, Function} ->
|
||||
ets:insert(State#state.iqtable, {XMLNS, Module, Function}),
|
||||
catch mod_disco:register_feature(XMLNS),
|
||||
loop(State);
|
||||
{register_iq_handler, XMLNS, Module, Function, Opts} ->
|
||||
ets:insert(State#state.iqtable, {XMLNS, Module, Function, Opts}),
|
||||
catch mod_disco:register_feature(XMLNS),
|
||||
loop(State);
|
||||
{unregister_iq_handler, XMLNS} ->
|
||||
case ets:lookup(State#state.iqtable, XMLNS) of
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:stop_iq_handler(Module, Function, Opts);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ets:delete(State#state.iqtable, XMLNS),
|
||||
catch mod_disco:unregister_feature(XMLNS),
|
||||
loop(State);
|
||||
refresh_iq_handlers ->
|
||||
lists:foreach(
|
||||
fun(T) ->
|
||||
case T of
|
||||
{XMLNS, _Module, _Function, _Opts} ->
|
||||
catch mod_disco:register_feature(XMLNS);
|
||||
{XMLNS, _Module, _Function} ->
|
||||
catch mod_disco:register_feature(XMLNS);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end, ets:tab2list(State#state.iqtable)),
|
||||
loop(State);
|
||||
_ ->
|
||||
loop(State)
|
||||
end.
|
||||
|
||||
|
||||
do_route(State, From, To, Packet) ->
|
||||
?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
|
||||
[From, To, Packet, 8]),
|
||||
case To of
|
||||
#jid{luser = "", lresource = ""} ->
|
||||
{xmlelement, Name, Attrs, _Els} = Packet,
|
||||
case Name of
|
||||
"iq" ->
|
||||
process_iq(State, From, To, Packet);
|
||||
"message" ->
|
||||
ok;
|
||||
"presence" ->
|
||||
ok;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
#jid{luser = "", lresource = Res} ->
|
||||
{xmlelement, Name, Attrs, _Els} = Packet,
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
"error" -> ok;
|
||||
"result" -> ok;
|
||||
_ ->
|
||||
case {Res, Name} of
|
||||
{"announce/online", "message"} ->
|
||||
announce_online(From, To, Packet);
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_ITEM_NOT_FOUND),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
ejabberd_sm ! {route, From, To, Packet}
|
||||
end.
|
||||
|
||||
process_iq(State, From, To, Packet) ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
case IQ of
|
||||
#iq{xmlns = XMLNS} ->
|
||||
Host = To#jid.lserver,
|
||||
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
|
||||
case ets:lookup(State#state.iqtable, XMLNS) of
|
||||
[{_, Module, Function}] ->
|
||||
ResIQ = Module:Function(From, To, IQ),
|
||||
if
|
||||
@@ -60,7 +132,7 @@ process_iq(From, To, Packet) ->
|
||||
ok
|
||||
end;
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
gen_iq_handler:handle(Module, Function, Opts,
|
||||
From, To, IQ);
|
||||
[] ->
|
||||
Err = jlib:make_error_reply(
|
||||
@@ -75,171 +147,29 @@ process_iq(From, To, Packet) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
register_iq_handler(XMLNS, Module, Fun) ->
|
||||
ejabberd_local ! {register_iq_handler, XMLNS, Module, Fun}.
|
||||
|
||||
register_iq_handler(Host, XMLNS, Module, Fun) ->
|
||||
ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun}.
|
||||
register_iq_handler(XMLNS, Module, Fun, Opts) ->
|
||||
ejabberd_local ! {register_iq_handler, XMLNS, Module, Fun, Opts}.
|
||||
|
||||
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
|
||||
ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
|
||||
|
||||
unregister_iq_handler(Host, XMLNS) ->
|
||||
ejabberd_local ! {unregister_iq_handler, Host, XMLNS}.
|
||||
unregister_iq_handler(XMLNS) ->
|
||||
ejabberd_local ! {unregister_iq_handler, XMLNS}.
|
||||
|
||||
refresh_iq_handlers() ->
|
||||
ejabberd_local ! refresh_iq_handlers.
|
||||
|
||||
bounce_resource_packet(From, To, Packet) ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_ITEM_NOT_FOUND),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
stop.
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ejabberd_router:register_route(Host, {apply, ?MODULE, route}),
|
||||
ejabberd_hooks:add(local_send_to_resource_hook, Host,
|
||||
?MODULE, bounce_resource_packet, 100)
|
||||
end, ?MYHOSTS),
|
||||
catch ets:new(?IQTABLE, [named_table, public]),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({route, From, To, Packet}, State) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
|
||||
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function}),
|
||||
catch mod_disco:register_feature(Host, XMLNS),
|
||||
{noreply, State};
|
||||
handle_info({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) ->
|
||||
ets:insert(?IQTABLE, {{XMLNS, Host}, Module, Function, Opts}),
|
||||
catch mod_disco:register_feature(Host, XMLNS),
|
||||
{noreply, State};
|
||||
handle_info({unregister_iq_handler, Host, XMLNS}, State) ->
|
||||
case ets:lookup(?IQTABLE, {XMLNS, Host}) of
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:stop_iq_handler(Module, Function, Opts);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ets:delete(?IQTABLE, {XMLNS, Host}),
|
||||
catch mod_disco:unregister_feature(Host, XMLNS),
|
||||
{noreply, State};
|
||||
handle_info(refresh_iq_handlers, State) ->
|
||||
lists:foreach(
|
||||
fun(T) ->
|
||||
case T of
|
||||
{{XMLNS, Host}, _Module, _Function, _Opts} ->
|
||||
catch mod_disco:register_feature(Host, XMLNS);
|
||||
{{XMLNS, Host}, _Module, _Function} ->
|
||||
catch mod_disco:register_feature(Host, XMLNS);
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end, ets:tab2list(?IQTABLE)),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
%% Description: This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("local route~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
|
||||
[From, To, Packet, 8]),
|
||||
if
|
||||
To#jid.luser /= "" ->
|
||||
ejabberd_sm:route(From, To, Packet);
|
||||
To#jid.lresource == "" ->
|
||||
{xmlelement, Name, _Attrs, _Els} = Packet,
|
||||
case Name of
|
||||
"iq" ->
|
||||
process_iq(From, To, Packet);
|
||||
"message" ->
|
||||
ok;
|
||||
"presence" ->
|
||||
ok;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
true ->
|
||||
{xmlelement, _Name, Attrs, _Els} = Packet,
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
"error" -> ok;
|
||||
"result" -> ok;
|
||||
_ ->
|
||||
ejabberd_hooks:run(local_send_to_resource_hook,
|
||||
To#jid.lserver,
|
||||
[From, To, Packet])
|
||||
end
|
||||
end.
|
||||
announce_online(From, To, Packet) ->
|
||||
case acl:match_rule(announce, From) of
|
||||
deny ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_NOT_ALLOWED),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
allow ->
|
||||
Local = jlib:make_jid("", ?MYNAME, ""),
|
||||
lists:foreach(
|
||||
fun({U, R}) ->
|
||||
Dest = jlib:make_jid(U, ?MYNAME, R),
|
||||
ejabberd_router:route(Local, Dest, Packet)
|
||||
end, ejabberd_sm:dirty_get_sessions_list())
|
||||
end.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
%% gen_event callbacks
|
||||
-export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2,
|
||||
code_change/3, reopen_log/0]).
|
||||
code_change/3]).
|
||||
|
||||
-record(state, {fd, file}).
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
%% Other
|
||||
%%----------------------------------------------------------------------
|
||||
init(File) ->
|
||||
case file:open(File, [append, raw]) of
|
||||
case file:open(File, [append]) of
|
||||
{ok, Fd} ->
|
||||
{ok, #state{fd = Fd, file = File}};
|
||||
Error ->
|
||||
@@ -66,8 +66,7 @@ handle_info({'EXIT', _Fd, _Reason}, _State) ->
|
||||
remove_handler;
|
||||
handle_info({emulator, _GL, reopen}, State) ->
|
||||
file:close(State#state.fd),
|
||||
rotate_log(State#state.file),
|
||||
case file:open(State#state.file, [append, raw]) of
|
||||
case file:open(State#state.file, [append]) of
|
||||
{ok, Fd} ->
|
||||
{ok, State#state{fd = Fd}};
|
||||
Error ->
|
||||
@@ -90,9 +89,6 @@ terminate(_Reason, _State) ->
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
reopen_log() ->
|
||||
error_logger ! {emulator, noproc, reopen}.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -102,38 +98,38 @@ write_event(Fd, {Time, {error, _GL, {Pid, Format, Args}}}) ->
|
||||
T = write_time(Time),
|
||||
case catch io_lib:format(add_node(Format,Pid), Args) of
|
||||
S when list(S) ->
|
||||
file:write(Fd, io_lib:format(T ++ S, []));
|
||||
io:format(Fd, T ++ S, []);
|
||||
_ ->
|
||||
F = add_node("ERROR: ~p - ~p~n", Pid),
|
||||
file:write(Fd, io_lib:format(T ++ F, [Format,Args]))
|
||||
io:format(Fd, T ++ F, [Format,Args])
|
||||
end;
|
||||
write_event(Fd, {Time, {emulator, _GL, Chars}}) ->
|
||||
T = write_time(Time),
|
||||
case catch io_lib:format(Chars, []) of
|
||||
S when list(S) ->
|
||||
file:write(Fd, io_lib:format(T ++ S, []));
|
||||
io:format(Fd, T ++ S, []);
|
||||
_ ->
|
||||
file:write(Fd, io_lib:format(T ++ "ERROR: ~p ~n", [Chars]))
|
||||
io:format(Fd, T ++ "ERROR: ~p ~n", [Chars])
|
||||
end;
|
||||
write_event(Fd, {Time, {info, _GL, {Pid, Info, _}}}) ->
|
||||
T = write_time(Time),
|
||||
file:write(Fd, io_lib:format(T ++ add_node("~p~n",Pid), [Info]));
|
||||
io:format(Fd, T ++ add_node("~p~n",Pid),[Info]);
|
||||
write_event(Fd, {Time, {error_report, _GL, {Pid, std_error, Rep}}}) ->
|
||||
T = write_time(Time),
|
||||
S = format_report(Rep),
|
||||
file:write(Fd, io_lib:format(T ++ S ++ add_node("", Pid), []));
|
||||
io:format(Fd, T ++ S ++ add_node("", Pid), []);
|
||||
write_event(Fd, {Time, {info_report, _GL, {Pid, std_info, Rep}}}) ->
|
||||
T = write_time(Time, "INFO REPORT"),
|
||||
S = format_report(Rep),
|
||||
file:write(Fd, io_lib:format(T ++ S ++ add_node("", Pid), []));
|
||||
io:format(Fd, T ++ S ++ add_node("", Pid), []);
|
||||
write_event(Fd, {Time, {info_msg, _GL, {Pid, Format, Args}}}) ->
|
||||
T = write_time(Time, "INFO REPORT"),
|
||||
case catch io_lib:format(add_node(Format,Pid), Args) of
|
||||
S when list(S) ->
|
||||
file:write(Fd, io_lib:format(T ++ S, []));
|
||||
io:format(Fd, T ++ S, []);
|
||||
_ ->
|
||||
F = add_node("ERROR: ~p - ~p~n", Pid),
|
||||
file:write(Fd, io_lib:format(T ++ F, [Format,Args]))
|
||||
io:format(Fd, T ++ F, [Format,Args])
|
||||
end;
|
||||
write_event(_, _) ->
|
||||
ok.
|
||||
@@ -190,17 +186,3 @@ write_time({{Y,Mo,D},{H,Mi,S}}, Type) ->
|
||||
io_lib:format("~n=~s==== ~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w ===~n",
|
||||
[Type, Y, Mo, D, H, Mi, S]).
|
||||
|
||||
%% Rename the log file if it the filename exists
|
||||
%% This is needed in systems when the file must be closed before rotation (Windows).
|
||||
%% On most Unix-like system, the file can be renamed from the command line and
|
||||
%%the log can directly be reopened.
|
||||
rotate_log(Filename) ->
|
||||
case file:read_file_info(Filename) of
|
||||
{ok, _FileInfo} ->
|
||||
RotationName = filename:rootname(Filename),
|
||||
file:rename(Filename, [RotationName, "-old.log"]),
|
||||
ok;
|
||||
{error, _Reason} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
|
||||
@@ -10,88 +10,20 @@
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/5,
|
||||
start/3,
|
||||
start/4,
|
||||
-export([start/3,
|
||||
receiver/4,
|
||||
change_shaper/2,
|
||||
reset_stream/1,
|
||||
starttls/2,
|
||||
compress/2,
|
||||
become_controller/1,
|
||||
close/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
reset_stream/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(state, {socket,
|
||||
sock_mod,
|
||||
shaper_state,
|
||||
c2s_pid,
|
||||
max_stanza_size,
|
||||
xml_stream_state,
|
||||
timeout}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link(Socket, SockMod, Shaper, MaxStanzaSize, C2SPid) ->
|
||||
gen_server:start_link(
|
||||
?MODULE, [Socket, SockMod, Shaper, MaxStanzaSize, C2SPid], []).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start(Socket, SockMod, Shaper) ->
|
||||
start(Socket, SockMod, Shaper, infinity).
|
||||
proc_lib:spawn(?MODULE, receiver, [Socket, SockMod, Shaper, self()]).
|
||||
|
||||
start(Socket, SockMod, Shaper, MaxStanzaSize) ->
|
||||
{ok, Pid} = supervisor:start_child(
|
||||
ejabberd_receiver_sup,
|
||||
[Socket, SockMod, Shaper, MaxStanzaSize, self()]),
|
||||
Pid.
|
||||
|
||||
change_shaper(Pid, Shaper) ->
|
||||
gen_server:cast(Pid, {change_shaper, Shaper}).
|
||||
|
||||
reset_stream(Pid) ->
|
||||
gen_server:call(Pid, reset_stream).
|
||||
|
||||
starttls(Pid, TLSSocket) ->
|
||||
gen_server:call(Pid, {starttls, TLSSocket}).
|
||||
|
||||
compress(Pid, ZlibSocket) ->
|
||||
gen_server:call(Pid, {compress, ZlibSocket}).
|
||||
|
||||
become_controller(Pid) ->
|
||||
gen_server:call(Pid, become_controller).
|
||||
|
||||
close(Pid) ->
|
||||
gen_server:cast(Pid, close).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([Socket, SockMod, Shaper, MaxStanzaSize, C2SPid]) ->
|
||||
XMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
||||
receiver(Socket, SockMod, Shaper, C2SPid) ->
|
||||
XMLStreamPid = xml_stream:start(C2SPid),
|
||||
ShaperState = shaper:new(Shaper),
|
||||
Timeout = case SockMod of
|
||||
ssl ->
|
||||
@@ -99,174 +31,48 @@ init([Socket, SockMod, Shaper, MaxStanzaSize, C2SPid]) ->
|
||||
_ ->
|
||||
infinity
|
||||
end,
|
||||
{ok, #state{socket = Socket,
|
||||
sock_mod = SockMod,
|
||||
shaper_state = ShaperState,
|
||||
c2s_pid = C2SPid,
|
||||
max_stanza_size = MaxStanzaSize,
|
||||
xml_stream_state = XMLStreamState,
|
||||
timeout = Timeout}}.
|
||||
receiver(Socket, SockMod, ShaperState, C2SPid, XMLStreamPid, Timeout).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call({starttls, TLSSocket}, _From,
|
||||
#state{xml_stream_state = XMLStreamState,
|
||||
c2s_pid = C2SPid,
|
||||
max_stanza_size = MaxStanzaSize} = State) ->
|
||||
xml_stream:close(XMLStreamState),
|
||||
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
||||
NewState = State#state{socket = TLSSocket,
|
||||
sock_mod = tls,
|
||||
xml_stream_state = NewXMLStreamState},
|
||||
case tls:recv_data(TLSSocket, "") of
|
||||
{ok, TLSData} ->
|
||||
{reply, ok, process_data(TLSData, NewState)};
|
||||
{error, _Reason} ->
|
||||
{stop, normal, ok, NewState}
|
||||
end;
|
||||
handle_call({compress, ZlibSocket}, _From,
|
||||
#state{xml_stream_state = XMLStreamState,
|
||||
c2s_pid = C2SPid,
|
||||
max_stanza_size = MaxStanzaSize} = State) ->
|
||||
xml_stream:close(XMLStreamState),
|
||||
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
||||
NewState = State#state{socket = ZlibSocket,
|
||||
sock_mod = ejabberd_zlib,
|
||||
xml_stream_state = NewXMLStreamState},
|
||||
case ejabberd_zlib:recv_data(ZlibSocket, "") of
|
||||
{ok, ZlibData} ->
|
||||
{reply, ok, process_data(ZlibData, NewState)};
|
||||
{error, _Reason} ->
|
||||
{stop, normal, ok, NewState}
|
||||
end;
|
||||
handle_call(reset_stream, _From,
|
||||
#state{xml_stream_state = XMLStreamState,
|
||||
c2s_pid = C2SPid,
|
||||
max_stanza_size = MaxStanzaSize} = State) ->
|
||||
xml_stream:close(XMLStreamState),
|
||||
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
|
||||
Reply = ok,
|
||||
{reply, Reply, State#state{xml_stream_state = NewXMLStreamState}};
|
||||
handle_call(become_controller, _From, State) ->
|
||||
activate_socket(State),
|
||||
Reply = ok,
|
||||
{reply, Reply, State};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast({change_shaper, Shaper}, State) ->
|
||||
NewShaperState = shaper:new(Shaper),
|
||||
{noreply, State#state{shaper_state = NewShaperState}};
|
||||
handle_cast(close, State) ->
|
||||
{stop, normal, State};
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({Tag, _TCPSocket, Data},
|
||||
#state{socket = Socket,
|
||||
sock_mod = SockMod} = State)
|
||||
when (Tag == tcp) or (Tag == ssl) ->
|
||||
case SockMod of
|
||||
tls ->
|
||||
case tls:recv_data(Socket, Data) of
|
||||
{ok, TLSData} ->
|
||||
{noreply, process_data(TLSData, State)};
|
||||
{error, _Reason} ->
|
||||
{stop, normal, State}
|
||||
end;
|
||||
ejabberd_zlib ->
|
||||
case ejabberd_zlib:recv_data(Socket, Data) of
|
||||
{ok, ZlibData} ->
|
||||
{noreply, process_data(ZlibData, State)};
|
||||
{error, _Reason} ->
|
||||
{stop, normal, State}
|
||||
end;
|
||||
_ ->
|
||||
{noreply, process_data(Data, State)}
|
||||
end;
|
||||
handle_info({Tag, _TCPSocket}, State)
|
||||
when (Tag == tcp_closed) or (Tag == ssl_closed) ->
|
||||
{stop, normal, State};
|
||||
handle_info({Tag, _TCPSocket, Reason}, State)
|
||||
when (Tag == tcp_error) or (Tag == ssl_error) ->
|
||||
case Reason of
|
||||
timeout ->
|
||||
{noreply, State};
|
||||
_ ->
|
||||
{stop, normal, State}
|
||||
end;
|
||||
handle_info({timeout, _Ref, activate}, State) ->
|
||||
activate_socket(State),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
%% Description: This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, #state{xml_stream_state = XMLStreamState,
|
||||
c2s_pid = C2SPid} = State) ->
|
||||
xml_stream:close(XMLStreamState),
|
||||
gen_fsm:send_event(C2SPid, closed),
|
||||
catch (State#state.sock_mod):close(State#state.socket),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
activate_socket(#state{socket = Socket,
|
||||
sock_mod = SockMod}) ->
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
inet:setopts(Socket, [{active, once}]);
|
||||
_ ->
|
||||
SockMod:setopts(Socket, [{active, once}])
|
||||
receiver(Socket, SockMod, ShaperState, C2SPid, XMLStreamPid, Timeout) ->
|
||||
case catch SockMod:recv(Socket, 0, Timeout) of
|
||||
{ok, Text} ->
|
||||
ShaperSt1 = receive
|
||||
{change_shaper, Shaper} ->
|
||||
shaper:new(Shaper)
|
||||
after 0 ->
|
||||
ShaperState
|
||||
end,
|
||||
NewShaperState = shaper:update(ShaperSt1, size(Text)),
|
||||
XMLStreamPid1 = receive
|
||||
reset_stream ->
|
||||
exit(XMLStreamPid, closed),
|
||||
xml_stream:start(C2SPid)
|
||||
after 0 ->
|
||||
XMLStreamPid
|
||||
end,
|
||||
xml_stream:send_text(XMLStreamPid1, Text),
|
||||
receiver(Socket, SockMod, NewShaperState, C2SPid, XMLStreamPid1,
|
||||
Timeout);
|
||||
{error, timeout} ->
|
||||
receiver(Socket, SockMod, ShaperState, C2SPid, XMLStreamPid,
|
||||
Timeout);
|
||||
{error, Reason} ->
|
||||
exit(XMLStreamPid, closed),
|
||||
gen_fsm:send_event(C2SPid, closed),
|
||||
ok;
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("(~w) abnormal ~w:recv termination:~n\t~p~n",
|
||||
[Socket, SockMod, Reason]),
|
||||
exit(XMLStreamPid, closed),
|
||||
gen_fsm:send_event(C2SPid, closed),
|
||||
ok
|
||||
end.
|
||||
|
||||
process_data(Data,
|
||||
#state{xml_stream_state = XMLStreamState,
|
||||
shaper_state = ShaperState} = State) ->
|
||||
?DEBUG("Received XML on stream = ~p", [binary_to_list(Data)]),
|
||||
XMLStreamState1 = xml_stream:parse(XMLStreamState, Data),
|
||||
{NewShaperState, Pause} = shaper:update(ShaperState, size(Data)),
|
||||
if
|
||||
Pause > 0 ->
|
||||
erlang:start_timer(Pause, self(), activate);
|
||||
true ->
|
||||
activate_socket(State)
|
||||
end,
|
||||
State#state{xml_stream_state = XMLStreamState1,
|
||||
shaper_state = NewShaperState}.
|
||||
|
||||
change_shaper(Pid, Shaper) ->
|
||||
Pid ! {change_shaper, Shaper}.
|
||||
|
||||
reset_stream(Pid) ->
|
||||
Pid ! reset_stream.
|
||||
|
||||
|
||||
|
||||
@@ -10,121 +10,27 @@
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([route/3,
|
||||
register_route/1,
|
||||
register_route/2,
|
||||
register_routes/1,
|
||||
unregister_route/1,
|
||||
unregister_routes/1,
|
||||
dirty_get_all_routes/0,
|
||||
dirty_get_all_domains/0
|
||||
]).
|
||||
|
||||
-export([start_link/0]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([start_link/0, init/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-record(route, {domain, pid, local_hint}).
|
||||
-record(state, {}).
|
||||
-record(route, {domain, pid}).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
Pid = proc_lib:spawn_link(ejabberd_router, init, []),
|
||||
register(ejabberd_router, Pid),
|
||||
{ok, Pid}.
|
||||
|
||||
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
register_route(Domain) ->
|
||||
case jlib:nameprep(Domain) of
|
||||
error ->
|
||||
[] = {invalid_domain, Domain};
|
||||
LDomain ->
|
||||
Pid = self(),
|
||||
F = fun() ->
|
||||
mnesia:write(#route{domain = LDomain,
|
||||
pid = Pid})
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
end.
|
||||
|
||||
register_route(Domain, LocalHint) ->
|
||||
case jlib:nameprep(Domain) of
|
||||
error ->
|
||||
[] = {invalid_domain, Domain};
|
||||
LDomain ->
|
||||
Pid = self(),
|
||||
F = fun() ->
|
||||
mnesia:write(#route{domain = LDomain,
|
||||
pid = Pid,
|
||||
local_hint = LocalHint})
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
end.
|
||||
|
||||
register_routes(Domains) ->
|
||||
lists:foreach(fun(Domain) ->
|
||||
register_route(Domain)
|
||||
end, Domains).
|
||||
|
||||
unregister_route(Domain) ->
|
||||
case jlib:nameprep(Domain) of
|
||||
error ->
|
||||
[] = {invalid_domain, Domain};
|
||||
LDomain ->
|
||||
Pid = self(),
|
||||
F = fun() ->
|
||||
mnesia:delete_object(#route{domain = LDomain,
|
||||
pid = Pid})
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
end.
|
||||
|
||||
unregister_routes(Domains) ->
|
||||
lists:foreach(fun(Domain) ->
|
||||
unregister_route(Domain)
|
||||
end, Domains).
|
||||
|
||||
|
||||
dirty_get_all_routes() ->
|
||||
lists:usort(mnesia:dirty_all_keys(route)) -- ?MYHOSTS.
|
||||
|
||||
dirty_get_all_domains() ->
|
||||
lists:usort(mnesia:dirty_all_keys(route)).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
init() ->
|
||||
update_tables(),
|
||||
mnesia:create_table(route,
|
||||
[{ram_copies, [node()]},
|
||||
@@ -133,136 +39,91 @@ init([]) ->
|
||||
record_info(fields, route)}]),
|
||||
mnesia:add_table_copy(route, node(), ram_copies),
|
||||
mnesia:subscribe({table, route, simple}),
|
||||
lists:foreach(
|
||||
fun(Pid) ->
|
||||
erlang:monitor(process, Pid)
|
||||
end,
|
||||
mnesia:dirty_select(route, [{{route, '_', '$1', '_'}, [], ['$1']}])),
|
||||
{ok, #state{}}.
|
||||
loop().
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({route, From, To, Packet}, State) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
loop() ->
|
||||
receive
|
||||
{route, From, To, Packet} ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
loop();
|
||||
{register_route, Domain, Pid} ->
|
||||
F = fun() ->
|
||||
mnesia:write(#route{domain = Domain,
|
||||
pid = Pid})
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
loop();
|
||||
{unregister_route, Domain, Pid} ->
|
||||
F = fun() ->
|
||||
mnesia:delete_object(#route{domain = Domain,
|
||||
pid = Pid})
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
loop();
|
||||
{mnesia_table_event, {write, #route{pid = Pid}, _ActivityId}} ->
|
||||
erlang:monitor(process, Pid),
|
||||
loop();
|
||||
{'DOWN', _Ref, _Type, Pid, _Info} ->
|
||||
F = fun() ->
|
||||
Es = mnesia:select(
|
||||
route,
|
||||
[{#route{pid = Pid, _ = '_'},
|
||||
[],
|
||||
['$_']}]),
|
||||
lists:foreach(fun(E) ->
|
||||
mnesia:delete_object(E)
|
||||
end, Es)
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
loop();
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info({mnesia_table_event, {write, #route{pid = Pid}, _ActivityId}},
|
||||
State) ->
|
||||
erlang:monitor(process, Pid),
|
||||
{noreply, State};
|
||||
handle_info({'DOWN', _Ref, _Type, Pid, _Info}, State) ->
|
||||
F = fun() ->
|
||||
Es = mnesia:select(
|
||||
route,
|
||||
[{#route{pid = Pid, _ = '_'},
|
||||
[],
|
||||
['$_']}]),
|
||||
lists:foreach(fun(E) ->
|
||||
mnesia:delete_object(E)
|
||||
end, Es)
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
%% Description: This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
do_route(OrigFrom, OrigTo, OrigPacket) ->
|
||||
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket ~p~n",
|
||||
[OrigFrom, OrigTo, OrigPacket]),
|
||||
LOrigDstDomain = OrigTo#jid.lserver,
|
||||
case ejabberd_hooks:run_fold(filter_packet,
|
||||
{OrigFrom, OrigTo, OrigPacket}, []) of
|
||||
{From, To, Packet} ->
|
||||
LDstDomain = To#jid.lserver,
|
||||
case mnesia:dirty_read(route, LDstDomain) of
|
||||
[] ->
|
||||
ejabberd_s2s:route(From, To, Packet);
|
||||
[R] ->
|
||||
Pid = R#route.pid,
|
||||
if
|
||||
node(Pid) == node() ->
|
||||
case R#route.local_hint of
|
||||
{apply, Module, Function} ->
|
||||
Module:Function(From, To, Packet);
|
||||
_ ->
|
||||
Pid ! {route, From, To, Packet}
|
||||
end;
|
||||
true ->
|
||||
Pid ! {route, From, To, Packet}
|
||||
end;
|
||||
Rs ->
|
||||
case [R || R <- Rs, node(R#route.pid) == node()] of
|
||||
[] ->
|
||||
R = lists:nth(erlang:phash(now(), length(Rs)), Rs),
|
||||
Pid = R#route.pid,
|
||||
Pid ! {route, From, To, Packet};
|
||||
LRs ->
|
||||
LRs,
|
||||
R = lists:nth(erlang:phash(now(), length(LRs)), LRs),
|
||||
Pid = R#route.pid,
|
||||
case R#route.local_hint of
|
||||
{apply, Module, Function} ->
|
||||
Module:Function(From, To, Packet);
|
||||
_ ->
|
||||
Pid ! {route, From, To, Packet}
|
||||
end
|
||||
end
|
||||
end;
|
||||
drop ->
|
||||
ok
|
||||
loop()
|
||||
end.
|
||||
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("route~n\tfrom ~p~n\tto ~p~n\tpacket ~p~n", [From, To, Packet]),
|
||||
LDstDomain = To#jid.lserver,
|
||||
case mnesia:dirty_read(route, LDstDomain) of
|
||||
[] ->
|
||||
ejabberd_s2s ! {route, From, To, Packet};
|
||||
[R] ->
|
||||
Pid = R#route.pid,
|
||||
?DEBUG("routed to process ~p~n", [Pid]),
|
||||
Pid ! {route, From, To, Packet};
|
||||
Rs ->
|
||||
Rs1 = case [R || R <- Rs, node(R#route.pid) == node()] of
|
||||
[] -> Rs;
|
||||
LRs -> LRs
|
||||
end,
|
||||
R = lists:nth(erlang:phash(now(), length(Rs1)), Rs1),
|
||||
Pid = R#route.pid,
|
||||
?DEBUG("routed to process ~p~n", [Pid]),
|
||||
Pid ! {route, From, To, Packet}
|
||||
end.
|
||||
|
||||
|
||||
route(From, To, Packet) ->
|
||||
ejabberd_router ! {route, From, To, Packet}.
|
||||
|
||||
register_route(Domain) ->
|
||||
ejabberd_router ! {register_route, Domain, self()}.
|
||||
|
||||
unregister_route(Domain) ->
|
||||
ejabberd_router ! {unregister_route, Domain, self()}.
|
||||
|
||||
|
||||
dirty_get_all_routes() ->
|
||||
lists:delete(?MYNAME, lists:usort(mnesia:dirty_all_keys(route))).
|
||||
|
||||
dirty_get_all_domains() ->
|
||||
lists:usort(mnesia:dirty_all_keys(route)).
|
||||
|
||||
|
||||
|
||||
update_tables() ->
|
||||
@@ -270,8 +131,6 @@ update_tables() ->
|
||||
[domain, node, pid] ->
|
||||
mnesia:delete_table(route);
|
||||
[domain, pid] ->
|
||||
mnesia:delete_table(route);
|
||||
[domain, pid, local_hint] ->
|
||||
ok;
|
||||
{'EXIT', _} ->
|
||||
ok
|
||||
|
||||
@@ -10,55 +10,75 @@
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/0,
|
||||
route/3,
|
||||
-export([start_link/0, init/0,
|
||||
have_connection/1,
|
||||
get_key/1,
|
||||
try_register/1,
|
||||
remove_connection/1,
|
||||
dirty_get_connections/0,
|
||||
ctl_process/2
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
dirty_get_connections/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_ctl.hrl").
|
||||
|
||||
-record(s2s, {fromto, pid, key}).
|
||||
-record(state, {}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
Pid = proc_lib:spawn_link(ejabberd_s2s, init, []),
|
||||
register(ejabberd_s2s, Pid),
|
||||
{ok, Pid}.
|
||||
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
init() ->
|
||||
update_tables(),
|
||||
mnesia:create_table(s2s,[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, s2s)}]),
|
||||
mnesia:add_table_copy(s2s, node(), ram_copies),
|
||||
mnesia:subscribe(system),
|
||||
loop().
|
||||
|
||||
loop() ->
|
||||
receive
|
||||
{closed_conection, FromTo} ->
|
||||
remove_connection(FromTo),
|
||||
loop();
|
||||
{mnesia_system_event, {mnesia_down, Node}} ->
|
||||
clean_table_from_bad_node(Node),
|
||||
loop();
|
||||
{route, From, To, Packet} ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
loop();
|
||||
_ ->
|
||||
ok
|
||||
loop()
|
||||
end.
|
||||
|
||||
|
||||
|
||||
remove_connection(FromTo) ->
|
||||
F = fun() ->
|
||||
mnesia:delete({s2s, FromTo})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
|
||||
|
||||
clean_table_from_bad_node(Node) ->
|
||||
F = fun() ->
|
||||
Es = mnesia:select(
|
||||
s2s,
|
||||
[{#s2s{pid = '$1', _ = '_'},
|
||||
[{'==', {node, '$1'}, Node}],
|
||||
['$_']}]),
|
||||
lists:foreach(fun(E) ->
|
||||
mnesia:delete_object(E)
|
||||
end, Es)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
have_connection(FromTo) ->
|
||||
case catch mnesia:dirty_read(s2s, FromTo) of
|
||||
[_] ->
|
||||
@@ -95,107 +115,9 @@ try_register(FromTo) ->
|
||||
false
|
||||
end.
|
||||
|
||||
dirty_get_connections() ->
|
||||
mnesia:dirty_all_keys(s2s).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
update_tables(),
|
||||
mnesia:create_table(s2s, [{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, s2s)}]),
|
||||
mnesia:add_table_copy(s2s, node(), ram_copies),
|
||||
mnesia:subscribe(system),
|
||||
ejabberd_ctl:register_commands(
|
||||
[{"incoming-s2s-number", "print number of incoming s2s connections on the node"},
|
||||
{"outgoing-s2s-number", "print number of outgoing s2s connections on the node"}],
|
||||
?MODULE, ctl_process),
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
|
||||
clean_table_from_bad_node(Node),
|
||||
{noreply, State};
|
||||
handle_info({route, From, To, Packet}, State) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
%% Description: This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
clean_table_from_bad_node(Node) ->
|
||||
F = fun() ->
|
||||
Es = mnesia:select(
|
||||
s2s,
|
||||
[{#s2s{pid = '$1', _ = '_'},
|
||||
[{'==', {node, '$1'}, Node}],
|
||||
['$_']}]),
|
||||
lists:foreach(fun(E) ->
|
||||
mnesia:delete_object(E)
|
||||
end, Es)
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
|
||||
@@ -237,26 +159,19 @@ find_connection(From, To) ->
|
||||
Pid
|
||||
end
|
||||
end,
|
||||
TRes = mnesia:transaction(F),
|
||||
ejabberd_s2s_out:start_connection(Pid),
|
||||
TRes;
|
||||
mnesia:transaction(F);
|
||||
[El] ->
|
||||
{atomic, El#s2s.pid}
|
||||
end.
|
||||
|
||||
|
||||
send_element(Pid, El) ->
|
||||
Pid ! {send_element, El}.
|
||||
|
||||
ctl_process(_Val, ["incoming-s2s-number"]) ->
|
||||
N = length(supervisor:which_children(ejabberd_s2s_in_sup)),
|
||||
io:format("~p~n", [N]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(_Val, ["outgoing-s2s-number"]) ->
|
||||
N = length(supervisor:which_children(ejabberd_s2s_out_sup)),
|
||||
io:format("~p~n", [N]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(Val, _Args) ->
|
||||
Val.
|
||||
|
||||
dirty_get_connections() ->
|
||||
mnesia:dirty_all_keys(s2s).
|
||||
|
||||
|
||||
update_tables() ->
|
||||
case catch mnesia:table_info(s2s, attributes) of
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_s2s_in.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : Serve incoming s2s connection
|
||||
%%% Purpose :
|
||||
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_s2s_in).
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
|
||||
%% External exports
|
||||
-export([start/2,
|
||||
start_link/2,
|
||||
become_controller/1,
|
||||
match_domain/2]).
|
||||
send_text/2,
|
||||
send_element/2]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
-export([init/1,
|
||||
wait_for_stream/2,
|
||||
wait_for_feature_request/2,
|
||||
stream_established/2,
|
||||
handle_event/3,
|
||||
handle_sync_event/4,
|
||||
@@ -30,22 +30,13 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include_lib("ssl/include/PKIX1Explicit88.hrl").
|
||||
-include_lib("ssl/include/PKIX1Implicit88.hrl").
|
||||
-include("XmppAddr.hrl").
|
||||
|
||||
-define(DICT, dict).
|
||||
|
||||
-record(state, {socket,
|
||||
sockmod,
|
||||
receiver,
|
||||
streamid,
|
||||
shaper,
|
||||
tls = false,
|
||||
tls_enabled = false,
|
||||
tls_options = [],
|
||||
authenticated = false,
|
||||
auth_domain,
|
||||
connections = ?DICT:new(),
|
||||
timer}).
|
||||
|
||||
@@ -58,13 +49,13 @@
|
||||
-define(FSMOPTS, []).
|
||||
-endif.
|
||||
|
||||
-define(STREAM_HEADER(Version),
|
||||
-define(STREAM_HEADER,
|
||||
("<?xml version='1.0'?>"
|
||||
"<stream:stream "
|
||||
"xmlns:stream='http://etherx.jabber.org/streams' "
|
||||
"xmlns='jabber:server' "
|
||||
"xmlns:db='jabber:server:dialback' "
|
||||
"id='" ++ StateData#state.streamid ++ "'" ++ Version ++ ">")
|
||||
"id='" ++ StateData#state.streamid ++ "'>")
|
||||
).
|
||||
|
||||
-define(STREAM_TRAILER, "</stream:stream>").
|
||||
@@ -87,9 +78,6 @@ start(SockData, Opts) ->
|
||||
start_link(SockData, Opts) ->
|
||||
gen_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], ?FSMOPTS).
|
||||
|
||||
become_controller(Pid) ->
|
||||
gen_fsm:send_all_state_event(Pid, become_controller).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Callback functions from gen_fsm
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -103,39 +91,17 @@ become_controller(Pid) ->
|
||||
%%----------------------------------------------------------------------
|
||||
init([{SockMod, Socket}, Opts]) ->
|
||||
?INFO_MSG("started: ~p", [{SockMod, Socket}]),
|
||||
ReceiverPid = ejabberd_receiver:start(Socket, SockMod, none),
|
||||
Shaper = case lists:keysearch(shaper, 1, Opts) of
|
||||
{value, {_, S}} -> S;
|
||||
_ -> none
|
||||
end,
|
||||
MaxStanzaSize =
|
||||
case lists:keysearch(max_stanza_size, 1, Opts) of
|
||||
{value, {_, Size}} -> Size;
|
||||
_ -> infinity
|
||||
end,
|
||||
ReceiverPid = ejabberd_receiver:start(
|
||||
Socket, SockMod, none, MaxStanzaSize),
|
||||
StartTLS = case ejabberd_config:get_local_option(s2s_use_starttls) of
|
||||
undefined ->
|
||||
false;
|
||||
UseStartTLS ->
|
||||
UseStartTLS
|
||||
end,
|
||||
TLSOpts = case ejabberd_config:get_local_option(s2s_certfile) of
|
||||
undefined ->
|
||||
[];
|
||||
CertFile ->
|
||||
[{certfile, CertFile}]
|
||||
end,
|
||||
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
|
||||
{ok, wait_for_stream,
|
||||
#state{socket = Socket,
|
||||
sockmod = SockMod,
|
||||
receiver = ReceiverPid,
|
||||
streamid = new_id(),
|
||||
shaper = Shaper,
|
||||
tls = StartTLS,
|
||||
tls_enabled = false,
|
||||
tls_options = TLSOpts,
|
||||
timer = Timer}}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
@@ -146,61 +112,19 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
%%----------------------------------------------------------------------
|
||||
|
||||
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
case {xml:get_attr_s("xmlns", Attrs),
|
||||
xml:get_attr_s("xmlns:db", Attrs),
|
||||
xml:get_attr_s("version", Attrs) == "1.0"} of
|
||||
{"jabber:server", _, true} when
|
||||
StateData#state.tls and (not StateData#state.authenticated) ->
|
||||
send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
|
||||
SASL =
|
||||
if
|
||||
StateData#state.tls_enabled ->
|
||||
case tls:get_peer_certificate(StateData#state.socket) of
|
||||
{ok, _Cert} ->
|
||||
case tls:get_verify_result(
|
||||
StateData#state.socket) of
|
||||
0 ->
|
||||
[{xmlelement, "mechanisms",
|
||||
[{"xmlns", ?NS_SASL}],
|
||||
[{xmlelement, "mechanism", [],
|
||||
[{xmlcdata, "EXTERNAL"}]}]}];
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
error ->
|
||||
[]
|
||||
end;
|
||||
true ->
|
||||
[]
|
||||
end,
|
||||
StartTLS = if
|
||||
StateData#state.tls_enabled ->
|
||||
[];
|
||||
true ->
|
||||
[{xmlelement, "starttls",
|
||||
[{"xmlns", ?NS_TLS}], []}]
|
||||
end,
|
||||
send_element(StateData,
|
||||
{xmlelement, "stream:features", [],
|
||||
SASL ++ StartTLS}),
|
||||
{next_state, wait_for_feature_request, StateData};
|
||||
{"jabber:server", _, true} when
|
||||
StateData#state.authenticated ->
|
||||
send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
|
||||
send_element(StateData,
|
||||
{xmlelement, "stream:features", [], []}),
|
||||
{next_state, stream_established, StateData};
|
||||
{"jabber:server", "jabber:server:dialback", _} ->
|
||||
send_text(StateData, ?STREAM_HEADER("")),
|
||||
{next_state, stream_established, StateData};
|
||||
% TODO
|
||||
case {xml:get_attr_s("xmlns", Attrs), xml:get_attr_s("xmlns:db", Attrs)} of
|
||||
{"jabber:server", "jabber:server:dialback"} ->
|
||||
send_text(StateData#state.socket, ?STREAM_HEADER),
|
||||
{next_state, stream_established, StateData#state{}};
|
||||
_ ->
|
||||
send_text(StateData, ?INVALID_NAMESPACE_ERR),
|
||||
send_text(StateData#state.socket, ?INVALID_NAMESPACE_ERR),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
|
||||
wait_for_stream({xmlstreamerror, _}, StateData) ->
|
||||
send_text(StateData,
|
||||
?STREAM_HEADER("") ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
send_text(StateData#state.socket,
|
||||
?STREAM_HEADER ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_stream(timeout, StateData) ->
|
||||
@@ -209,106 +133,6 @@ wait_for_stream(timeout, StateData) ->
|
||||
wait_for_stream(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
|
||||
|
||||
wait_for_feature_request({xmlstreamelement, El}, StateData) ->
|
||||
{xmlelement, Name, Attrs, Els} = El,
|
||||
TLS = StateData#state.tls,
|
||||
TLSEnabled = StateData#state.tls_enabled,
|
||||
SockMod = StateData#state.sockmod,
|
||||
case {xml:get_attr_s("xmlns", Attrs), Name} of
|
||||
{?NS_TLS, "starttls"} when TLS == true,
|
||||
TLSEnabled == false,
|
||||
SockMod == gen_tcp ->
|
||||
?INFO_MSG("starttls", []),
|
||||
Socket = StateData#state.socket,
|
||||
TLSOpts = StateData#state.tls_options,
|
||||
{ok, TLSSocket} = tls:tcp_to_tls(Socket, TLSOpts),
|
||||
ejabberd_receiver:starttls(StateData#state.receiver, TLSSocket),
|
||||
send_element(StateData,
|
||||
{xmlelement, "proceed", [{"xmlns", ?NS_TLS}], []}),
|
||||
{next_state, wait_for_stream,
|
||||
StateData#state{sockmod = tls,
|
||||
socket = TLSSocket,
|
||||
streamid = new_id(),
|
||||
tls_enabled = true
|
||||
}};
|
||||
{?NS_SASL, "auth"} when TLSEnabled ->
|
||||
Mech = xml:get_attr_s("mechanism", Attrs),
|
||||
case Mech of
|
||||
"EXTERNAL" ->
|
||||
Auth = jlib:decode_base64(xml:get_cdata(Els)),
|
||||
AuthDomain = jlib:nameprep(Auth),
|
||||
AuthRes =
|
||||
case tls:get_peer_certificate(StateData#state.socket) of
|
||||
{ok, Cert} ->
|
||||
case tls:get_verify_result(
|
||||
StateData#state.socket) of
|
||||
0 ->
|
||||
case AuthDomain of
|
||||
error ->
|
||||
false;
|
||||
_ ->
|
||||
case idna:domain_utf8_to_ascii(AuthDomain) of
|
||||
false ->
|
||||
false;
|
||||
PCAuthDomain ->
|
||||
lists:any(
|
||||
fun(D) ->
|
||||
match_domain(
|
||||
PCAuthDomain, D)
|
||||
end, get_cert_domains(Cert))
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
error ->
|
||||
false
|
||||
end,
|
||||
if
|
||||
AuthRes ->
|
||||
ejabberd_receiver:reset_stream(
|
||||
StateData#state.receiver),
|
||||
send_element(StateData,
|
||||
{xmlelement, "success",
|
||||
[{"xmlns", ?NS_SASL}], []}),
|
||||
?INFO_MSG("(~w) Accepted s2s authentication for ~s",
|
||||
[StateData#state.socket, AuthDomain]),
|
||||
{next_state, wait_for_stream,
|
||||
StateData#state{streamid = new_id(),
|
||||
authenticated = true,
|
||||
auth_domain = AuthDomain
|
||||
}};
|
||||
true ->
|
||||
send_element(StateData,
|
||||
{xmlelement, "failure",
|
||||
[{"xmlns", ?NS_SASL}], []}),
|
||||
send_text(StateData, ?STREAM_TRAILER),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
send_element(StateData,
|
||||
{xmlelement, "failure",
|
||||
[{"xmlns", ?NS_SASL}],
|
||||
[{xmlelement, "invalid-mechanism", [], []}]}),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
stream_established({xmlstreamelement, El}, StateData)
|
||||
end;
|
||||
|
||||
wait_for_feature_request({xmlstreamend, _Name}, StateData) ->
|
||||
send_text(StateData, ?STREAM_TRAILER),
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_feature_request({xmlstreamerror, _}, StateData) ->
|
||||
send_text(StateData, ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_feature_request(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
|
||||
|
||||
stream_established({xmlstreamelement, El}, StateData) ->
|
||||
cancel_timer(StateData#state.timer),
|
||||
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
|
||||
@@ -324,13 +148,13 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
||||
Key, StateData#state.streamid}),
|
||||
Conns = ?DICT:store({LFrom, LTo}, wait_for_verification,
|
||||
StateData#state.connections),
|
||||
change_shaper(StateData, LTo, jlib:make_jid("", LFrom, "")),
|
||||
change_shaper(StateData, jlib:make_jid("", LFrom, "")),
|
||||
{next_state,
|
||||
stream_established,
|
||||
StateData#state{connections = Conns,
|
||||
timer = Timer}};
|
||||
_ ->
|
||||
send_text(StateData, ?HOST_UNKNOWN_ERR),
|
||||
send_text(StateData#state.socket, ?HOST_UNKNOWN_ERR),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
{verify, To, From, Id, Key} ->
|
||||
@@ -341,7 +165,7 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
||||
Type = if Key == Key1 -> "valid";
|
||||
true -> "invalid"
|
||||
end,
|
||||
send_element(StateData,
|
||||
send_element(StateData#state.socket,
|
||||
{xmlelement,
|
||||
"db:verify",
|
||||
[{"from", To},
|
||||
@@ -351,8 +175,7 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
||||
[]}),
|
||||
{next_state, stream_established, StateData#state{timer = Timer}};
|
||||
_ ->
|
||||
NewEl = jlib:remove_attr("xmlns", El),
|
||||
{xmlelement, Name, Attrs, _Els} = NewEl,
|
||||
{xmlelement, Name, Attrs, _Els} = El,
|
||||
From_s = xml:get_attr_s("from", Attrs),
|
||||
From = jlib:string_to_jid(From_s),
|
||||
To_s = xml:get_attr_s("to", Attrs),
|
||||
@@ -361,40 +184,18 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
||||
(To /= error) and (From /= error) ->
|
||||
LFrom = From#jid.lserver,
|
||||
LTo = To#jid.lserver,
|
||||
if
|
||||
StateData#state.authenticated ->
|
||||
case (LFrom == StateData#state.auth_domain)
|
||||
andalso
|
||||
lists:member(
|
||||
LTo,
|
||||
ejabberd_router:dirty_get_all_domains()) of
|
||||
true ->
|
||||
if ((Name == "iq") or
|
||||
(Name == "message") or
|
||||
(Name == "presence")) ->
|
||||
ejabberd_router:route(
|
||||
From, To, NewEl);
|
||||
true ->
|
||||
error
|
||||
end;
|
||||
false ->
|
||||
case ?DICT:find({LFrom, LTo},
|
||||
StateData#state.connections) of
|
||||
{ok, established} ->
|
||||
if ((Name == "iq") or
|
||||
(Name == "message") or
|
||||
(Name == "presence")) ->
|
||||
ejabberd_router:route(From, To, El);
|
||||
true ->
|
||||
error
|
||||
end;
|
||||
true ->
|
||||
case ?DICT:find({LFrom, LTo},
|
||||
StateData#state.connections) of
|
||||
{ok, established} ->
|
||||
if ((Name == "iq") or
|
||||
(Name == "message") or
|
||||
(Name == "presence")) ->
|
||||
ejabberd_router:route(
|
||||
From, To, NewEl);
|
||||
true ->
|
||||
error
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
true ->
|
||||
error
|
||||
@@ -403,7 +204,7 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
||||
end;
|
||||
|
||||
stream_established({valid, From, To}, StateData) ->
|
||||
send_element(StateData,
|
||||
send_element(StateData#state.socket,
|
||||
{xmlelement,
|
||||
"db:result",
|
||||
[{"from", To},
|
||||
@@ -418,7 +219,7 @@ stream_established({valid, From, To}, StateData) ->
|
||||
{next_state, stream_established, NSD};
|
||||
|
||||
stream_established({invalid, From, To}, StateData) ->
|
||||
send_element(StateData,
|
||||
send_element(StateData#state.socket,
|
||||
{xmlelement,
|
||||
"db:result",
|
||||
[{"from", To},
|
||||
@@ -436,8 +237,8 @@ stream_established({xmlstreamend, _Name}, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
|
||||
stream_established({xmlstreamerror, _}, StateData) ->
|
||||
send_text(StateData,
|
||||
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
send_text(StateData#state.socket,
|
||||
?STREAM_HEADER ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
{stop, normal, StateData};
|
||||
|
||||
stream_established(timeout, StateData) ->
|
||||
@@ -467,12 +268,6 @@ stream_established(closed, StateData) ->
|
||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
handle_event(become_controller, StateName, StateData) ->
|
||||
ok = (StateData#state.sockmod):controlling_process(
|
||||
StateData#state.socket,
|
||||
StateData#state.receiver),
|
||||
ejabberd_receiver:become_controller(StateData#state.receiver),
|
||||
{next_state, StateName, StateData};
|
||||
handle_event(_Event, StateName, StateData) ->
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
@@ -499,10 +294,10 @@ code_change(_OldVsn, StateName, StateData, _Extra) ->
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
handle_info({send_text, Text}, StateName, StateData) ->
|
||||
send_text(StateData, Text),
|
||||
send_text(StateData#state.socket, Text),
|
||||
{next_state, StateName, StateData};
|
||||
|
||||
handle_info({timeout, Timer, _}, _StateName,
|
||||
handle_info({timeout, Timer, _}, StateName,
|
||||
#state{timer = Timer} = StateData) ->
|
||||
{stop, normal, StateData};
|
||||
|
||||
@@ -516,23 +311,31 @@ handle_info(_, StateName, StateData) ->
|
||||
%% Returns: any
|
||||
%%----------------------------------------------------------------------
|
||||
terminate(Reason, _StateName, StateData) ->
|
||||
% case StateData#state.user of
|
||||
% "" ->
|
||||
% ok;
|
||||
% _ ->
|
||||
% %ejabberd_sm:close_session(StateData#state.user,
|
||||
% % StateData#state.resource)
|
||||
% end,
|
||||
%ejabberd_s2s ! {closed_conection, StateData#state.server},
|
||||
?INFO_MSG("terminated: ~p", [Reason]),
|
||||
ejabberd_receiver:close(StateData#state.receiver),
|
||||
gen_tcp:close(StateData#state.socket),
|
||||
ok.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
send_text(StateData, Text) ->
|
||||
(StateData#state.sockmod):send(StateData#state.socket, Text).
|
||||
send_text(Socket, Text) ->
|
||||
gen_tcp:send(Socket,Text).
|
||||
|
||||
send_element(StateData, El) ->
|
||||
send_text(StateData, xml:element_to_string(El)).
|
||||
send_element(Socket, El) ->
|
||||
send_text(Socket, xml:element_to_string(El)).
|
||||
|
||||
|
||||
change_shaper(StateData, Host, JID) ->
|
||||
Shaper = acl:match_rule(Host, StateData#state.shaper, JID),
|
||||
change_shaper(StateData, JID) ->
|
||||
Shaper = acl:match_rule(StateData#state.shaper, JID),
|
||||
ejabberd_receiver:change_shaper(StateData#state.receiver, Shaper).
|
||||
|
||||
|
||||
@@ -565,122 +368,4 @@ is_key_packet(_) ->
|
||||
false.
|
||||
|
||||
|
||||
get_cert_domains(Cert) ->
|
||||
{rdnSequence, Subject} =
|
||||
(Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.subject,
|
||||
Extensions =
|
||||
(Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.extensions,
|
||||
lists:flatmap(
|
||||
fun(#'AttributeTypeAndValue'{type = ?'id-at-commonName',
|
||||
value = Val}) ->
|
||||
case 'PKIX1Explicit88':decode('X520CommonName', Val) of
|
||||
{ok, {_, D1}} ->
|
||||
D = if
|
||||
is_list(D1) -> D1;
|
||||
is_binary(D1) -> binary_to_list(D1);
|
||||
true -> error
|
||||
end,
|
||||
if
|
||||
D /= error ->
|
||||
case jlib:string_to_jid(D) of
|
||||
#jid{luser = "",
|
||||
lserver = LD,
|
||||
lresource = ""} ->
|
||||
[LD];
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
true ->
|
||||
[]
|
||||
end;
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
(_) ->
|
||||
[]
|
||||
end, lists:flatten(Subject)) ++
|
||||
lists:flatmap(
|
||||
fun(#'Extension'{extnID = ?'id-ce-subjectAltName',
|
||||
extnValue = Val}) ->
|
||||
BVal = if
|
||||
is_list(Val) -> list_to_binary(Val);
|
||||
is_binary(Val) -> Val;
|
||||
true -> Val
|
||||
end,
|
||||
case 'PKIX1Implicit88':decode('SubjectAltName', BVal) of
|
||||
{ok, SANs} ->
|
||||
lists:flatmap(
|
||||
fun({otherName,
|
||||
#'AnotherName'{'type-id' = ?'id-on-xmppAddr',
|
||||
value = XmppAddr
|
||||
}}) ->
|
||||
case 'XmppAddr':decode(
|
||||
'XmppAddr', XmppAddr) of
|
||||
{ok, D} when is_binary(D) ->
|
||||
case jlib:string_to_jid(
|
||||
binary_to_list(D)) of
|
||||
#jid{luser = "",
|
||||
lserver = LD,
|
||||
lresource = ""} ->
|
||||
case idna:domain_utf8_to_ascii(LD) of
|
||||
false ->
|
||||
[];
|
||||
PCLD ->
|
||||
[PCLD]
|
||||
end;
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
({dNSName, D}) when is_list(D) ->
|
||||
case jlib:string_to_jid(D) of
|
||||
#jid{luser = "",
|
||||
lserver = LD,
|
||||
lresource = ""} ->
|
||||
[LD];
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
(_) ->
|
||||
[]
|
||||
end, SANs);
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
(_) ->
|
||||
[]
|
||||
end, Extensions).
|
||||
|
||||
match_domain(Domain, Domain) ->
|
||||
true;
|
||||
match_domain(Domain, Pattern) ->
|
||||
DLabels = string:tokens(Domain, "."),
|
||||
PLabels = string:tokens(Pattern, "."),
|
||||
match_labels(DLabels, PLabels).
|
||||
|
||||
match_labels([], []) ->
|
||||
true;
|
||||
match_labels([], [_ | _]) ->
|
||||
false;
|
||||
match_labels([_ | _], []) ->
|
||||
false;
|
||||
match_labels([DL | DLabels], [PL | PLabels]) ->
|
||||
case lists:all(fun(C) -> (($a =< C) andalso (C =< $z))
|
||||
orelse (($0 =< C) andalso (C =< $9))
|
||||
orelse (C == $-) orelse (C == $*)
|
||||
end, PL) of
|
||||
true ->
|
||||
Regexp = regexp:sh_to_awk(PL),
|
||||
case regexp:match(DL, Regexp) of
|
||||
{match, _, _} ->
|
||||
match_labels(DLabels, PLabels);
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
|
||||
|
||||
@@ -8,46 +8,30 @@
|
||||
|
||||
-module(ejabberd_s2s_out).
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
|
||||
%% External exports
|
||||
-export([start/3,
|
||||
start_link/3,
|
||||
start_connection/1]).
|
||||
-export([start/3, start_link/3, send_text/2, send_element/2]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
-export([init/1,
|
||||
open_socket/2,
|
||||
wait_for_stream/2,
|
||||
wait_for_validation/2,
|
||||
wait_for_features/2,
|
||||
wait_for_auth_result/2,
|
||||
wait_for_starttls_proceed/2,
|
||||
reopen_socket/2,
|
||||
stream_established/2,
|
||||
handle_event/3,
|
||||
handle_sync_event/4,
|
||||
handle_info/3,
|
||||
terminate/3,
|
||||
code_change/4,
|
||||
test_get_addr_port/1]).
|
||||
code_change/4]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
-record(state, {socket, receiver,
|
||||
sockmod,
|
||||
streamid,
|
||||
use_v10,
|
||||
tls = false,
|
||||
tls_required = false,
|
||||
tls_enabled = false,
|
||||
tls_options = [],
|
||||
authenticated = false,
|
||||
db_enabled = true,
|
||||
try_auth = true,
|
||||
myname, server, queue,
|
||||
-record(state, {socket, receiver, streamid,
|
||||
myname, server, xmlpid, queue,
|
||||
new = false, verify = false,
|
||||
timer}).
|
||||
|
||||
@@ -65,7 +49,7 @@
|
||||
"xmlns:stream='http://etherx.jabber.org/streams' "
|
||||
"xmlns='jabber:server' "
|
||||
"xmlns:db='jabber:server:dialback' "
|
||||
"to='~s'~s>"
|
||||
"to='~s'>"
|
||||
).
|
||||
|
||||
-define(STREAM_TRAILER, "</stream:stream>").
|
||||
@@ -88,9 +72,6 @@ start(From, Host, Type) ->
|
||||
start_link(From, Host, Type) ->
|
||||
gen_fsm:start_link(ejabberd_s2s_out, [From, Host, Type], ?FSMOPTS).
|
||||
|
||||
start_connection(Pid) ->
|
||||
gen_fsm:send_event(Pid, init).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Callback functions from gen_fsm
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -104,31 +85,15 @@ start_connection(Pid) ->
|
||||
%%----------------------------------------------------------------------
|
||||
init([From, Server, Type]) ->
|
||||
?INFO_MSG("started: ~p", [{From, Server, Type}]),
|
||||
TLS = case ejabberd_config:get_local_option(s2s_use_starttls) of
|
||||
undefined ->
|
||||
false;
|
||||
UseStartTLS ->
|
||||
UseStartTLS
|
||||
end,
|
||||
UseV10 = TLS,
|
||||
TLSOpts = case ejabberd_config:get_local_option(s2s_certfile) of
|
||||
undefined ->
|
||||
[];
|
||||
CertFile ->
|
||||
[{certfile, CertFile}, connect]
|
||||
end,
|
||||
gen_fsm:send_event(self(), init),
|
||||
{New, Verify} = case Type of
|
||||
{new, Key} ->
|
||||
{Key, false};
|
||||
{verify, Pid, Key, SID} ->
|
||||
start_connection(self()),
|
||||
{false, {Pid, Key, SID}}
|
||||
end,
|
||||
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
|
||||
{ok, open_socket, #state{use_v10 = UseV10,
|
||||
tls = TLS,
|
||||
tls_options = TLSOpts,
|
||||
queue = queue:new(),
|
||||
{ok, open_socket, #state{queue = queue:new(),
|
||||
myname = From,
|
||||
server = Server,
|
||||
new = New,
|
||||
@@ -142,97 +107,92 @@ init([From, Server, Type]) ->
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
open_socket(init, StateData) ->
|
||||
AddrList = get_addr_port(StateData#state.server),
|
||||
case lists:foldl(fun({Addr, Port}, Acc) ->
|
||||
case Acc of
|
||||
{ok, Socket} ->
|
||||
{ok, Socket};
|
||||
_ ->
|
||||
open_socket1(Addr, Port)
|
||||
end
|
||||
end, {error, badarg}, AddrList) of
|
||||
{ok, Socket} ->
|
||||
ReceiverPid = ejabberd_receiver:start(Socket, gen_tcp, none),
|
||||
ok = gen_tcp:controlling_process(Socket, ReceiverPid),
|
||||
ejabberd_receiver:become_controller(ReceiverPid),
|
||||
Version = if
|
||||
StateData#state.use_v10 ->
|
||||
" version='1.0'";
|
||||
true ->
|
||||
""
|
||||
end,
|
||||
NewStateData = StateData#state{socket = Socket,
|
||||
sockmod = gen_tcp,
|
||||
tls_enabled = false,
|
||||
receiver = ReceiverPid,
|
||||
streamid = new_id()},
|
||||
send_text(NewStateData, io_lib:format(?STREAM_HEADER,
|
||||
[StateData#state.server,
|
||||
Version])),
|
||||
{next_state, wait_for_stream, NewStateData};
|
||||
{error, _Reason} ->
|
||||
Error = ?ERR_REMOTE_SERVER_NOT_FOUND,
|
||||
bounce_messages(Error),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
open_socket(_, StateData) ->
|
||||
{next_state, open_socket, StateData}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
open_socket1(Addr, Port) ->
|
||||
{Addr, Port} = get_addr_port(StateData#state.server),
|
||||
Res = case idna:domain_utf8_to_ascii(Addr) of
|
||||
false -> {error, badarg};
|
||||
ASCIIAddr ->
|
||||
?DEBUG("s2s_out: connecting to ~s:~p~n", [ASCIIAddr, Port]),
|
||||
case catch gen_tcp:connect(ASCIIAddr, Port,
|
||||
[binary, {packet, 0},
|
||||
{active, false}]) of
|
||||
case gen_tcp:connect(ASCIIAddr, Port,
|
||||
[binary, {packet, 0}]) of
|
||||
{ok, _Socket} = R -> R;
|
||||
{error, Reason1} ->
|
||||
?DEBUG("s2s_out: connect return ~p~n", [Reason1]),
|
||||
catch gen_tcp:connect(Addr, Port,
|
||||
[binary, {packet, 0},
|
||||
{active, false}, inet6]);
|
||||
{'EXIT', Reason1} ->
|
||||
?DEBUG("s2s_out: connect crashed ~p~n", [Reason1]),
|
||||
catch gen_tcp:connect(Addr, Port,
|
||||
[binary, {packet, 0},
|
||||
{active, false}, inet6])
|
||||
[binary, {packet, 0}, inet6])
|
||||
end
|
||||
end,
|
||||
case Res of
|
||||
{ok, Socket} ->
|
||||
{ok, Socket};
|
||||
XMLStreamPid = xml_stream:start(self()),
|
||||
send_text(Socket, io_lib:format(?STREAM_HEADER,
|
||||
[StateData#state.server])),
|
||||
{next_state, wait_for_stream,
|
||||
StateData#state{socket = Socket,
|
||||
xmlpid = XMLStreamPid,
|
||||
streamid = new_id()}};
|
||||
{error, Reason} ->
|
||||
?DEBUG("s2s_out: inet6 connect return ~p~n", [Reason]),
|
||||
{error, Reason};
|
||||
Error = ?ERR_REMOTE_SERVER_NOT_FOUND,
|
||||
bounce_messages(Error),
|
||||
{stop, normal, StateData};
|
||||
{'EXIT', Reason} ->
|
||||
?DEBUG("s2s_out: inet6 connect crashed ~p~n", [Reason]),
|
||||
{error, Reason}
|
||||
Error = ?ERR_REMOTE_SERVER_NOT_FOUND,
|
||||
bounce_messages(Error),
|
||||
{stop, normal, StateData}
|
||||
end.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
|
||||
|
||||
wait_for_stream({xmlstreamstart, Name, Attrs}, StateData) ->
|
||||
case {xml:get_attr_s("xmlns", Attrs),
|
||||
xml:get_attr_s("xmlns:db", Attrs),
|
||||
xml:get_attr_s("version", Attrs) == "1.0"} of
|
||||
{"jabber:server", "jabber:server:dialback", false} ->
|
||||
send_db_request(StateData);
|
||||
{"jabber:server", "jabber:server:dialback", true} when
|
||||
StateData#state.use_v10 ->
|
||||
{next_state, wait_for_features, StateData};
|
||||
{"jabber:server", "", true} when StateData#state.use_v10 ->
|
||||
{next_state, wait_for_features, StateData#state{db_enabled = false}};
|
||||
% TODO
|
||||
case {xml:get_attr_s("xmlns", Attrs), xml:get_attr_s("xmlns:db", Attrs)} of
|
||||
{"jabber:server", "jabber:server:dialback"} ->
|
||||
Server = StateData#state.server,
|
||||
New = case StateData#state.new of
|
||||
false ->
|
||||
case
|
||||
ejabberd_s2s:try_register(
|
||||
{StateData#state.myname, Server}) of
|
||||
{key, Key} ->
|
||||
Key;
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
Key ->
|
||||
Key
|
||||
end,
|
||||
case New of
|
||||
false ->
|
||||
ok;
|
||||
Key1 ->
|
||||
send_element(StateData#state.socket,
|
||||
{xmlelement,
|
||||
"db:result",
|
||||
[{"from", StateData#state.myname},
|
||||
{"to", Server}],
|
||||
[{xmlcdata, Key1}]})
|
||||
end,
|
||||
case StateData#state.verify of
|
||||
false ->
|
||||
ok;
|
||||
{Pid, Key2, SID} ->
|
||||
send_element(StateData#state.socket,
|
||||
{xmlelement,
|
||||
"db:verify",
|
||||
[{"from", StateData#state.myname},
|
||||
{"to", StateData#state.server},
|
||||
{"id", SID}],
|
||||
[{xmlcdata, Key2}]})
|
||||
end,
|
||||
{next_state, wait_for_validation, StateData#state{new = New}};
|
||||
_ ->
|
||||
send_text(StateData, ?INVALID_NAMESPACE_ERR),
|
||||
send_text(StateData#state.socket, ?INVALID_NAMESPACE_ERR),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
|
||||
wait_for_stream({xmlstreamerror, _}, StateData) ->
|
||||
send_text(StateData,
|
||||
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
send_text(StateData#state.socket,
|
||||
?STREAM_HEADER ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_stream(timeout, StateData) ->
|
||||
@@ -249,7 +209,7 @@ wait_for_validation({xmlstreamelement, El}, StateData) ->
|
||||
?INFO_MSG("recv result: ~p", [{From, To, Id, Type}]),
|
||||
case Type of
|
||||
"valid" ->
|
||||
send_queue(StateData, StateData#state.queue),
|
||||
send_queue(StateData#state.socket, StateData#state.queue),
|
||||
{next_state, stream_established,
|
||||
StateData#state{queue = queue:new()}};
|
||||
_ ->
|
||||
@@ -274,12 +234,7 @@ wait_for_validation({xmlstreamelement, El}, StateData) ->
|
||||
StateData#state.server,
|
||||
StateData#state.myname})
|
||||
end,
|
||||
if
|
||||
StateData#state.verify == false ->
|
||||
{stop, normal, StateData};
|
||||
true ->
|
||||
{next_state, wait_for_validation, StateData}
|
||||
end
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
{next_state, wait_for_validation, StateData}
|
||||
@@ -289,8 +244,8 @@ wait_for_validation({xmlstreamend, Name}, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_validation({xmlstreamerror, _}, StateData) ->
|
||||
send_text(StateData,
|
||||
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
send_text(StateData#state.socket,
|
||||
?STREAM_HEADER ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_validation(timeout, StateData) ->
|
||||
@@ -300,234 +255,6 @@ wait_for_validation(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
|
||||
|
||||
wait_for_features({xmlstreamelement, El}, StateData) ->
|
||||
case El of
|
||||
{xmlelement, "stream:features", _Attrs, Els} ->
|
||||
{SASLEXT, StartTLS, StartTLSRequired} =
|
||||
lists:foldl(
|
||||
fun({xmlelement, "mechanisms", Attrs1, Els1} = El1,
|
||||
{SEXT, STLS, STLSReq} = Acc) ->
|
||||
case xml:get_attr_s("xmlns", Attrs1) of
|
||||
?NS_SASL ->
|
||||
NewSEXT =
|
||||
lists:any(
|
||||
fun({xmlelement, "mechanism", _, Els2}) ->
|
||||
case xml:get_cdata(Els2) of
|
||||
"EXTERNAL" -> true;
|
||||
_ -> false
|
||||
end;
|
||||
(_) -> false
|
||||
end, Els1),
|
||||
{NewSEXT, STLS, STLSReq};
|
||||
_ ->
|
||||
Acc
|
||||
end;
|
||||
({xmlelement, "starttls", Attrs1, Els1} = El1,
|
||||
{SEXT, STLS, STLSReq} = Acc) ->
|
||||
case xml:get_attr_s("xmlns", Attrs1) of
|
||||
?NS_TLS ->
|
||||
Req = case xml:get_subtag(El1, "required") of
|
||||
{xmlelement, _, _, _} -> true;
|
||||
false -> false
|
||||
end,
|
||||
{SEXT, true, Req};
|
||||
_ ->
|
||||
Acc
|
||||
end;
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, {false, false, false}, Els),
|
||||
if
|
||||
(not SASLEXT) and (not StartTLS) and
|
||||
StateData#state.authenticated ->
|
||||
send_queue(StateData, StateData#state.queue),
|
||||
{next_state, stream_established,
|
||||
StateData#state{queue = queue:new()}};
|
||||
SASLEXT and StateData#state.try_auth and
|
||||
(StateData#state.new /= false) ->
|
||||
send_element(StateData,
|
||||
{xmlelement, "auth",
|
||||
[{"xmlns", ?NS_SASL},
|
||||
{"mechanism", "EXTERNAL"}],
|
||||
[{xmlcdata,
|
||||
jlib:encode_base64(
|
||||
StateData#state.myname)}]}),
|
||||
{next_state, wait_for_auth_result,
|
||||
StateData#state{try_auth = false}};
|
||||
StartTLS and StateData#state.tls and
|
||||
(not StateData#state.tls_enabled) ->
|
||||
send_element(StateData,
|
||||
{xmlelement, "starttls",
|
||||
[{"xmlns", ?NS_TLS}], []}),
|
||||
{next_state, wait_for_starttls_proceed, StateData};
|
||||
StartTLSRequired and (not StateData#state.tls) ->
|
||||
?INFO_MSG("restarted: ~p", [{StateData#state.myname,
|
||||
StateData#state.server}]),
|
||||
ejabberd_receiver:close(StateData#state.receiver),
|
||||
{next_state, reopen_socket,
|
||||
StateData#state{socket = undefined,
|
||||
use_v10 = false}};
|
||||
StateData#state.db_enabled ->
|
||||
send_db_request(StateData);
|
||||
true ->
|
||||
?INFO_MSG("restarted: ~p", [{StateData#state.myname,
|
||||
StateData#state.server}]),
|
||||
% TODO: clear message queue
|
||||
ejabberd_receiver:close(StateData#state.receiver),
|
||||
{next_state, reopen_socket, StateData#state{socket = undefined,
|
||||
use_v10 = false}}
|
||||
end;
|
||||
_ ->
|
||||
send_text(StateData,
|
||||
xml:element_to_string(?SERR_BAD_FORMAT) ++
|
||||
?STREAM_TRAILER),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
|
||||
wait_for_features({xmlstreamend, Name}, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_features({xmlstreamerror, _}, StateData) ->
|
||||
send_text(StateData,
|
||||
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_features(timeout, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_features(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
|
||||
|
||||
wait_for_auth_result({xmlstreamelement, El}, StateData) ->
|
||||
case El of
|
||||
{xmlelement, "success", Attrs, _Els} ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_SASL ->
|
||||
?INFO_MSG("auth: ~p", [{StateData#state.myname,
|
||||
StateData#state.server}]),
|
||||
ejabberd_receiver:reset_stream(
|
||||
StateData#state.receiver),
|
||||
send_text(StateData,
|
||||
io_lib:format(?STREAM_HEADER,
|
||||
[StateData#state.server,
|
||||
" version='1.0'"])),
|
||||
{next_state, wait_for_stream,
|
||||
StateData#state{streamid = new_id(),
|
||||
authenticated = true
|
||||
}};
|
||||
_ ->
|
||||
send_text(StateData,
|
||||
xml:element_to_string(?SERR_BAD_FORMAT) ++
|
||||
?STREAM_TRAILER),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
{xmlelement, "failure", Attrs, _Els} ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_SASL ->
|
||||
?INFO_MSG("restarted: ~p", [{StateData#state.myname,
|
||||
StateData#state.server}]),
|
||||
ejabberd_receiver:close(StateData#state.receiver),
|
||||
{next_state, reopen_socket,
|
||||
StateData#state{socket = undefined}};
|
||||
_ ->
|
||||
send_text(StateData,
|
||||
xml:element_to_string(?SERR_BAD_FORMAT) ++
|
||||
?STREAM_TRAILER),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
send_text(StateData,
|
||||
xml:element_to_string(?SERR_BAD_FORMAT) ++
|
||||
?STREAM_TRAILER),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
|
||||
wait_for_auth_result({xmlstreamend, Name}, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_auth_result({xmlstreamerror, _}, StateData) ->
|
||||
send_text(StateData,
|
||||
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_auth_result(timeout, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_auth_result(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
|
||||
|
||||
wait_for_starttls_proceed({xmlstreamelement, El}, StateData) ->
|
||||
case El of
|
||||
{xmlelement, "proceed", Attrs, _Els} ->
|
||||
case xml:get_attr_s("xmlns", Attrs) of
|
||||
?NS_TLS ->
|
||||
?INFO_MSG("starttls: ~p", [{StateData#state.myname,
|
||||
StateData#state.server}]),
|
||||
Socket = StateData#state.socket,
|
||||
TLSOpts = case ejabberd_config:get_local_option(
|
||||
{domain_certfile,
|
||||
StateData#state.server}) of
|
||||
undefined ->
|
||||
StateData#state.tls_options;
|
||||
CertFile ->
|
||||
[{certfile, CertFile} |
|
||||
lists:keydelete(
|
||||
certfile, 1,
|
||||
StateData#state.tls_options)]
|
||||
end,
|
||||
{ok, TLSSocket} = tls:tcp_to_tls(Socket, TLSOpts),
|
||||
ejabberd_receiver:starttls(
|
||||
StateData#state.receiver, TLSSocket),
|
||||
NewStateData = StateData#state{sockmod = tls,
|
||||
socket = TLSSocket,
|
||||
streamid = new_id(),
|
||||
tls_enabled = true
|
||||
},
|
||||
send_text(NewStateData,
|
||||
io_lib:format(?STREAM_HEADER,
|
||||
[StateData#state.server,
|
||||
" version='1.0'"])),
|
||||
{next_state, wait_for_stream, NewStateData};
|
||||
_ ->
|
||||
send_text(StateData,
|
||||
xml:element_to_string(?SERR_BAD_FORMAT) ++
|
||||
?STREAM_TRAILER),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
|
||||
wait_for_starttls_proceed({xmlstreamend, Name}, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_starttls_proceed({xmlstreamerror, _}, StateData) ->
|
||||
send_text(StateData,
|
||||
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_starttls_proceed(timeout, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
|
||||
wait_for_starttls_proceed(closed, StateData) ->
|
||||
{stop, normal, StateData}.
|
||||
|
||||
|
||||
reopen_socket({xmlstreamelement, El}, StateData) ->
|
||||
{next_state, reopen_socket, StateData};
|
||||
reopen_socket({xmlstreamend, Name}, StateData) ->
|
||||
{next_state, reopen_socket, StateData};
|
||||
reopen_socket({xmlstreamerror, _}, StateData) ->
|
||||
{next_state, reopen_socket, StateData};
|
||||
reopen_socket(timeout, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
reopen_socket(closed, StateData) ->
|
||||
gen_fsm:send_event(self(), init),
|
||||
{next_state, open_socket, StateData}.
|
||||
|
||||
|
||||
stream_established({xmlstreamelement, El}, StateData) ->
|
||||
?INFO_MSG("stream established", []),
|
||||
case is_verify_res(El) of
|
||||
@@ -559,8 +286,8 @@ stream_established({xmlstreamend, Name}, StateData) ->
|
||||
{stop, normal, StateData};
|
||||
|
||||
stream_established({xmlstreamerror, _}, StateData) ->
|
||||
send_text(StateData,
|
||||
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
send_text(StateData#state.socket,
|
||||
?STREAM_HEADER ++ ?INVALID_XML_ERR ++ ?STREAM_TRAILER),
|
||||
{stop, normal, StateData};
|
||||
|
||||
stream_established(timeout, StateData) ->
|
||||
@@ -616,7 +343,7 @@ code_change(OldVsn, StateName, StateData, Extra) ->
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
handle_info({send_text, Text}, StateName, StateData) ->
|
||||
send_text(StateData, Text),
|
||||
send_text(StateData#state.socket, Text),
|
||||
cancel_timer(StateData#state.timer),
|
||||
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
|
||||
{next_state, StateName, StateData#state{timer = Timer}};
|
||||
@@ -626,7 +353,7 @@ handle_info({send_element, El}, StateName, StateData) ->
|
||||
Timer = erlang:start_timer(?S2STIMEOUT, self(), []),
|
||||
case StateName of
|
||||
stream_established ->
|
||||
send_element(StateData, El),
|
||||
send_element(StateData#state.socket, El),
|
||||
{next_state, StateName, StateData#state{timer = Timer}};
|
||||
_ ->
|
||||
Q = queue:in(El, StateData#state.queue),
|
||||
@@ -634,17 +361,17 @@ handle_info({send_element, El}, StateName, StateData) ->
|
||||
timer = Timer}}
|
||||
end;
|
||||
|
||||
%handle_info({tcp, Socket, Data}, StateName, StateData) ->
|
||||
% xml_stream:send_text(StateData#state.xmlpid, Data),
|
||||
% {next_state, StateName, StateData};
|
||||
%
|
||||
%handle_info({tcp_closed, Socket}, StateName, StateData) ->
|
||||
% gen_fsm:send_event(self(), closed),
|
||||
% {next_state, StateName, StateData};
|
||||
%
|
||||
%handle_info({tcp_error, Socket, Reason}, StateName, StateData) ->
|
||||
% gen_fsm:send_event(self(), closed),
|
||||
% {next_state, StateName, StateData};
|
||||
handle_info({tcp, Socket, Data}, StateName, StateData) ->
|
||||
xml_stream:send_text(StateData#state.xmlpid, Data),
|
||||
{next_state, StateName, StateData};
|
||||
|
||||
handle_info({tcp_closed, Socket}, StateName, StateData) ->
|
||||
gen_fsm:send_event(self(), closed),
|
||||
{next_state, StateName, StateData};
|
||||
|
||||
handle_info({tcp_error, Socket, Reason}, StateName, StateData) ->
|
||||
gen_fsm:send_event(self(), closed),
|
||||
{next_state, StateName, StateData};
|
||||
|
||||
handle_info({timeout, Timer, _}, StateName,
|
||||
#state{timer = Timer} = StateData) ->
|
||||
@@ -660,19 +387,21 @@ handle_info(_, StateName, StateData) ->
|
||||
%%----------------------------------------------------------------------
|
||||
terminate(Reason, StateName, StateData) ->
|
||||
?INFO_MSG("terminated: ~p", [Reason]),
|
||||
bounce_queue(StateData#state.queue, ?ERR_REMOTE_SERVER_NOT_FOUND),
|
||||
Error = ?ERR_REMOTE_SERVER_NOT_FOUND,
|
||||
bounce_queue(StateData#state.queue, Error),
|
||||
case StateData#state.new of
|
||||
false ->
|
||||
ok;
|
||||
Key ->
|
||||
ejabberd_s2s:remove_connection({StateData#state.myname,
|
||||
StateData#state.server})
|
||||
ejabberd_s2s ! {closed_conection, {StateData#state.myname,
|
||||
StateData#state.server}}
|
||||
end,
|
||||
case StateData#state.socket of
|
||||
undefined ->
|
||||
ok;
|
||||
_Socket ->
|
||||
ejabberd_receiver:close(StateData#state.receiver)
|
||||
Socket ->
|
||||
gen_tcp:close(Socket),
|
||||
exit(StateData#state.xmlpid, closed)
|
||||
end,
|
||||
ok.
|
||||
|
||||
@@ -680,17 +409,17 @@ terminate(Reason, StateName, StateData) ->
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
send_text(StateData, Text) ->
|
||||
(StateData#state.sockmod):send(StateData#state.socket, Text).
|
||||
send_text(Socket, Text) ->
|
||||
gen_tcp:send(Socket,Text).
|
||||
|
||||
send_element(StateData, El) ->
|
||||
send_text(StateData, xml:element_to_string(El)).
|
||||
send_element(Socket, El) ->
|
||||
send_text(Socket, xml:element_to_string(El)).
|
||||
|
||||
send_queue(StateData, Q) ->
|
||||
send_queue(Socket, Q) ->
|
||||
case queue:out(Q) of
|
||||
{{value, El}, Q1} ->
|
||||
send_element(StateData, El),
|
||||
send_queue(StateData, Q1);
|
||||
send_element(Socket, El),
|
||||
send_queue(Socket, Q1);
|
||||
{empty, Q1} ->
|
||||
ok
|
||||
end.
|
||||
@@ -737,46 +466,20 @@ bounce_messages(Error) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
|
||||
send_db_request(StateData) ->
|
||||
Server = StateData#state.server,
|
||||
New = case StateData#state.new of
|
||||
false ->
|
||||
case ejabberd_s2s:try_register(
|
||||
{StateData#state.myname, Server}) of
|
||||
{key, Key} ->
|
||||
Key;
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
Key ->
|
||||
Key
|
||||
end,
|
||||
case New of
|
||||
false ->
|
||||
ok;
|
||||
Key1 ->
|
||||
send_element(StateData,
|
||||
{xmlelement,
|
||||
"db:result",
|
||||
[{"from", StateData#state.myname},
|
||||
{"to", Server}],
|
||||
[{xmlcdata, Key1}]})
|
||||
end,
|
||||
case StateData#state.verify of
|
||||
false ->
|
||||
ok;
|
||||
{Pid, Key2, SID} ->
|
||||
send_element(StateData,
|
||||
{xmlelement,
|
||||
"db:verify",
|
||||
[{"from", StateData#state.myname},
|
||||
{"to", StateData#state.server},
|
||||
{"id", SID}],
|
||||
[{xmlcdata, Key2}]})
|
||||
end,
|
||||
{next_state, wait_for_validation, StateData#state{new = New}}.
|
||||
|
||||
%is_key_packet({xmlelement, Name, Attrs, Els}) when Name == "db:result" ->
|
||||
% {key,
|
||||
% xml:get_attr_s("to", Attrs),
|
||||
% xml:get_attr_s("from", Attrs),
|
||||
% xml:get_attr_s("id", Attrs),
|
||||
% xml:get_cdata(Els)};
|
||||
%is_key_packet({xmlelement, Name, Attrs, Els}) when Name == "db:verify" ->
|
||||
% {verify,
|
||||
% xml:get_attr_s("to", Attrs),
|
||||
% xml:get_attr_s("from", Attrs),
|
||||
% xml:get_attr_s("id", Attrs),
|
||||
% xml:get_cdata(Els)};
|
||||
%is_key_packet(_) ->
|
||||
% false.
|
||||
|
||||
is_verify_res({xmlelement, Name, Attrs, Els}) when Name == "db:result" ->
|
||||
{result,
|
||||
@@ -808,50 +511,18 @@ get_addr_port(Server) ->
|
||||
case Res of
|
||||
{error, Reason} ->
|
||||
?DEBUG("srv lookup of '~s' failed: ~p~n", [Server, Reason]),
|
||||
[{Server, ejabberd_config:get_local_option(outgoing_s2s_port)}];
|
||||
{Server, ejabberd_config:get_local_option(outgoing_s2s_port)};
|
||||
{ok, HEnt} ->
|
||||
?DEBUG("srv lookup of '~s': ~p~n",
|
||||
[Server, HEnt#hostent.h_addr_list]),
|
||||
case HEnt#hostent.h_addr_list of
|
||||
[] ->
|
||||
[{Server,
|
||||
ejabberd_config:get_local_option(outgoing_s2s_port)}];
|
||||
AddrList ->
|
||||
% Probabilities are not exactly proportional to weights
|
||||
% for simplicity (higher weigths are overvalued)
|
||||
{A1, A2, A3} = now(),
|
||||
random:seed(A1, A2, A3),
|
||||
case (catch lists:map(
|
||||
fun({Priority, Weight, Port, Host}) ->
|
||||
N = case Weight of
|
||||
0 -> 0;
|
||||
_ -> (Weight + 1) * random:uniform()
|
||||
end,
|
||||
{Priority * 65536 - N, Host, Port}
|
||||
end, AddrList)) of
|
||||
{'EXIT', _Reasn} ->
|
||||
[{Server,
|
||||
ejabberd_config:get_local_option(outgoing_s2s_port)}];
|
||||
SortedList ->
|
||||
List = lists:map(
|
||||
fun({_, Host, Port}) ->
|
||||
{Host, Port}
|
||||
end, lists:keysort(1, SortedList)),
|
||||
?DEBUG("srv lookup of '~s': ~p~n", [Server, List]),
|
||||
List
|
||||
end
|
||||
{Server,
|
||||
ejabberd_config:get_local_option(outgoing_s2s_port)};
|
||||
[{_, _, Port, Host} | _] ->
|
||||
{Host, Port}
|
||||
end
|
||||
end.
|
||||
|
||||
test_get_addr_port(Server) ->
|
||||
lists:foldl(
|
||||
fun(_, Acc) ->
|
||||
[HostPort | _] = get_addr_port(Server),
|
||||
case lists:keysearch(HostPort, 1, Acc) of
|
||||
false ->
|
||||
[{HostPort, 1} | Acc];
|
||||
{value, {_, Num}} ->
|
||||
lists:keyreplace(HostPort, 1, Acc, {HostPort, Num + 1})
|
||||
end
|
||||
end, [], lists:seq(1, 100000)).
|
||||
|
||||
|
||||
|
||||
@@ -13,11 +13,7 @@
|
||||
-behaviour(gen_fsm).
|
||||
|
||||
%% External exports
|
||||
-export([start/2,
|
||||
start_link/2,
|
||||
send_text/2,
|
||||
send_element/2,
|
||||
become_controller/1]).
|
||||
-export([start/2, start_link/2, receiver/3, send_text/2, send_element/2]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
-export([init/1,
|
||||
@@ -79,9 +75,6 @@ start(SockData, Opts) ->
|
||||
start_link(SockData, Opts) ->
|
||||
gen_fsm:start_link(ejabberd_service, [SockData, Opts], ?FSMOPTS).
|
||||
|
||||
become_controller(Pid) ->
|
||||
gen_fsm:send_all_state_event(Pid, become_controller).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Callback functions from gen_fsm
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -123,7 +116,7 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
ReceiverPid = ejabberd_receiver:start(Socket, SockMod, none),
|
||||
ReceiverPid = spawn(?MODULE, receiver, [Socket, SockMod, self()]),
|
||||
{ok, wait_for_stream, #state{socket = Socket,
|
||||
receiver = ReceiverPid,
|
||||
streamid = new_id(),
|
||||
@@ -178,11 +171,11 @@ wait_for_handshake({xmlstreamelement, El}, StateData) ->
|
||||
end, StateData#state.hosts),
|
||||
{next_state, stream_established, StateData};
|
||||
_ ->
|
||||
send_text(StateData, ?INVALID_HANDSHAKE_ERR),
|
||||
send_text(StateData, ?INVALID_HEADER_ERR),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
_ ->
|
||||
{next_state, wait_for_handshake, StateData}
|
||||
{next_state, wait_for_key, StateData}
|
||||
end;
|
||||
|
||||
wait_for_handshake({xmlstreamend, _Name}, StateData) ->
|
||||
@@ -197,8 +190,7 @@ wait_for_handshake(closed, StateData) ->
|
||||
|
||||
|
||||
stream_established({xmlstreamelement, El}, StateData) ->
|
||||
NewEl = jlib:remove_attr("xmlns", El),
|
||||
{xmlelement, Name, Attrs, _Els} = NewEl,
|
||||
{xmlelement, Name, Attrs, _Els} = El,
|
||||
From = xml:get_attr_s("from", Attrs),
|
||||
FromJID1 = jlib:string_to_jid(From),
|
||||
FromJID = case FromJID1 of
|
||||
@@ -218,9 +210,9 @@ stream_established({xmlstreamelement, El}, StateData) ->
|
||||
(Name == "message") or
|
||||
(Name == "presence")) and
|
||||
(ToJID /= error) and (FromJID /= error) ->
|
||||
ejabberd_router:route(FromJID, ToJID, NewEl);
|
||||
ejabberd_router:route(FromJID, ToJID, El);
|
||||
true ->
|
||||
Err = jlib:make_error_reply(NewEl, ?ERR_BAD_REQUEST),
|
||||
Err = jlib:make_error_reply(El, ?ERR_BAD_REQUEST),
|
||||
send_element(StateData, Err),
|
||||
error
|
||||
end,
|
||||
@@ -259,12 +251,6 @@ stream_established(closed, StateData) ->
|
||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||
%% {stop, Reason, NewStateData}
|
||||
%%----------------------------------------------------------------------
|
||||
handle_event(become_controller, StateName, StateData) ->
|
||||
ok = (StateData#state.sockmod):controlling_process(
|
||||
StateData#state.socket,
|
||||
StateData#state.receiver),
|
||||
ejabberd_receiver:become_controller(StateData#state.receiver),
|
||||
{next_state, StateName, StateData};
|
||||
handle_event(_Event, StateName, StateData) ->
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
@@ -297,7 +283,7 @@ handle_info({send_element, El}, StateName, StateData) ->
|
||||
send_element(StateData, El),
|
||||
{next_state, StateName, StateData};
|
||||
handle_info({route, From, To, Packet}, StateName, StateData) ->
|
||||
case acl:match_rule(global, StateData#state.access, From) of
|
||||
case acl:match_rule(StateData#state.access, From) of
|
||||
allow ->
|
||||
{xmlelement, Name, Attrs, Els} = Packet,
|
||||
Attrs2 = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
|
||||
@@ -317,8 +303,7 @@ handle_info({route, From, To, Packet}, StateName, StateData) ->
|
||||
%% Purpose: Shutdown the fsm
|
||||
%% Returns: any
|
||||
%%----------------------------------------------------------------------
|
||||
terminate(Reason, StateName, StateData) ->
|
||||
?INFO_MSG("terminated: ~p", [Reason]),
|
||||
terminate(_Reason, StateName, StateData) ->
|
||||
case StateName of
|
||||
stream_established ->
|
||||
lists:foreach(
|
||||
@@ -328,13 +313,28 @@ terminate(Reason, StateName, StateData) ->
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_receiver:close(StateData#state.receiver),
|
||||
(StateData#state.sockmod):close(StateData#state.socket),
|
||||
ok.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
receiver(Socket, SockMod, C2SPid) ->
|
||||
XMLStreamPid = xml_stream:start(C2SPid),
|
||||
receiver(Socket, SockMod, C2SPid, XMLStreamPid).
|
||||
|
||||
receiver(Socket, SockMod, C2SPid, XMLStreamPid) ->
|
||||
case SockMod:recv(Socket, 0) of
|
||||
{ok, Text} ->
|
||||
xml_stream:send_text(XMLStreamPid, Text),
|
||||
receiver(Socket, SockMod, C2SPid, XMLStreamPid);
|
||||
{error, _Reason} ->
|
||||
exit(XMLStreamPid, closed),
|
||||
gen_fsm:send_event(C2SPid, closed),
|
||||
ok
|
||||
end.
|
||||
|
||||
send_text(StateData, Text) ->
|
||||
(StateData#state.sockmod):send(StateData#state.socket,Text).
|
||||
|
||||
|
||||
@@ -8,288 +8,133 @@
|
||||
|
||||
-module(ejabberd_sm).
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/0,
|
||||
route/3,
|
||||
open_session/4, close_session/4,
|
||||
bounce_offline_message/3,
|
||||
disconnect_removed_user/2,
|
||||
get_user_resources/2,
|
||||
set_presence/6,
|
||||
unset_presence/5,
|
||||
close_session_unset_presence/5,
|
||||
-export([start_link/0, init/0, open_session/2, close_session/2,
|
||||
get_user_resources/1,
|
||||
set_presence/3,
|
||||
unset_presence/3,
|
||||
dirty_get_sessions_list/0,
|
||||
dirty_get_my_sessions_list/0,
|
||||
get_vh_session_list/1,
|
||||
register_iq_handler/3,
|
||||
register_iq_handler/4,
|
||||
register_iq_handler/5,
|
||||
unregister_iq_handler/2,
|
||||
ctl_process/2
|
||||
unregister_iq_handler/1
|
||||
]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("ejabberd_ctl.hrl").
|
||||
|
||||
-record(session, {sid, usr, us, priority}).
|
||||
-record(state, {}).
|
||||
-record(session, {ur, user, pid}).
|
||||
-record(presence, {ur, user, priority}).
|
||||
|
||||
%% default value for the maximum number of user connections
|
||||
-define(MAX_USER_SESSIONS, infinity).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
Pid = proc_lib:spawn_link(ejabberd_sm, init, []),
|
||||
register(ejabberd_sm, Pid),
|
||||
{ok, Pid}.
|
||||
|
||||
route(From, To, Packet) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
open_session(SID, User, Server, Resource) ->
|
||||
set_session(SID, User, Server, Resource, undefined),
|
||||
check_for_sessions_to_replace(User, Server, Resource),
|
||||
JID = jlib:make_jid(User, Server, Resource),
|
||||
ejabberd_hooks:run(sm_register_connection_hook, JID#jid.lserver,
|
||||
[SID, JID]).
|
||||
|
||||
close_session(SID, User, Server, Resource) ->
|
||||
F = fun() ->
|
||||
mnesia:delete({session, SID})
|
||||
end,
|
||||
mnesia:sync_dirty(F),
|
||||
JID = jlib:make_jid(User, Server, Resource),
|
||||
ejabberd_hooks:run(sm_remove_connection_hook, JID#jid.lserver,
|
||||
[SID, JID]).
|
||||
|
||||
bounce_offline_message(From, To, Packet) ->
|
||||
Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err),
|
||||
stop.
|
||||
|
||||
disconnect_removed_user(User, Server) ->
|
||||
ejabberd_sm:route(jlib:make_jid("", "", ""),
|
||||
jlib:make_jid(User, Server, ""),
|
||||
{xmlelement, "broadcast", [],
|
||||
[{exit, "User removed"}]}).
|
||||
|
||||
get_user_resources(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_index_read(session, US, #session.us) of
|
||||
{'EXIT', _Reason} ->
|
||||
[];
|
||||
Ss ->
|
||||
[element(3, S#session.usr) || S <- clean_session_list(Ss)]
|
||||
end.
|
||||
|
||||
set_presence(SID, User, Server, Resource, Priority, Presence) ->
|
||||
set_session(SID, User, Server, Resource, Priority),
|
||||
ejabberd_hooks:run(set_presence_hook, jlib:nameprep(Server),
|
||||
[User, Server, Resource, Presence]).
|
||||
|
||||
unset_presence(SID, User, Server, Resource, Status) ->
|
||||
set_session(SID, User, Server, Resource, undefined),
|
||||
ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
|
||||
[User, Server, Resource, Status]).
|
||||
|
||||
close_session_unset_presence(SID, User, Server, Resource, Status) ->
|
||||
close_session(SID, User, Server, Resource),
|
||||
ejabberd_hooks:run(unset_presence_hook, jlib:nameprep(Server),
|
||||
[User, Server, Resource, Status]).
|
||||
|
||||
|
||||
dirty_get_sessions_list() ->
|
||||
mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{usr = '$1', _ = '_'},
|
||||
[],
|
||||
['$1']}]).
|
||||
|
||||
dirty_get_my_sessions_list() ->
|
||||
mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{sid = {'_', '$1'}, _ = '_'},
|
||||
[{'==', {node, '$1'}, node()}],
|
||||
['$_']}]).
|
||||
|
||||
get_vh_session_list(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{usr = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, LServer}],
|
||||
['$1']}]).
|
||||
|
||||
register_iq_handler(Host, XMLNS, Module, Fun) ->
|
||||
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}.
|
||||
|
||||
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
|
||||
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
|
||||
|
||||
unregister_iq_handler(Host, XMLNS) ->
|
||||
ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}.
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([]) ->
|
||||
init() ->
|
||||
update_tables(),
|
||||
mnesia:create_table(session,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, session)}]),
|
||||
mnesia:add_table_index(session, usr),
|
||||
mnesia:add_table_index(session, us),
|
||||
mnesia:create_table(session, [{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, session)}]),
|
||||
mnesia:add_table_index(session, user),
|
||||
mnesia:add_table_copy(session, node(), ram_copies),
|
||||
mnesia:create_table(presence,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, presence)}]),
|
||||
mnesia:add_table_index(presence, user),
|
||||
mnesia:subscribe(system),
|
||||
ets:new(sm_iqtable, [named_table]),
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ejabberd_hooks:add(offline_message_hook, Host,
|
||||
ejabberd_sm, bounce_offline_message, 100),
|
||||
ejabberd_hooks:add(remove_user, Host,
|
||||
ejabberd_sm, disconnect_removed_user, 100)
|
||||
end, ?MYHOSTS),
|
||||
ejabberd_ctl:register_commands(
|
||||
[{"connected-users", "list all established sessions"},
|
||||
{"connected-users-number", "print a number of established sessions"},
|
||||
{"user-resources user server", "print user's connected resources"}],
|
||||
?MODULE, ctl_process),
|
||||
loop().
|
||||
|
||||
{ok, #state{}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({route, From, To, Packet}, State) ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
loop() ->
|
||||
receive
|
||||
{route, From, To, Packet} ->
|
||||
case catch do_route(From, To, Packet) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p~nwhen processing: ~p",
|
||||
[Reason, {From, To, Packet}]);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
loop();
|
||||
{open_session, User, Resource, From} ->
|
||||
register_connection(User, Resource, From),
|
||||
loop();
|
||||
{close_session, User, Resource} ->
|
||||
remove_connection(User, Resource),
|
||||
loop();
|
||||
{mnesia_system_event, {mnesia_down, Node}} ->
|
||||
clean_table_from_bad_node(Node),
|
||||
loop();
|
||||
{register_iq_handler, XMLNS, Module, Function} ->
|
||||
ets:insert(sm_iqtable, {XMLNS, Module, Function}),
|
||||
loop();
|
||||
{register_iq_handler, XMLNS, Module, Function, Opts} ->
|
||||
ets:insert(sm_iqtable, {XMLNS, Module, Function, Opts}),
|
||||
loop();
|
||||
{unregister_iq_handler, XMLNS} ->
|
||||
case ets:lookup(sm_iqtable, XMLNS) of
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:stop_iq_handler(Module, Function, Opts);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ets:delete(sm_iqtable, XMLNS),
|
||||
loop();
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
|
||||
clean_table_from_bad_node(Node),
|
||||
{noreply, State};
|
||||
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
|
||||
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}),
|
||||
{noreply, State};
|
||||
handle_info({register_iq_handler, Host, XMLNS, Module, Function, Opts}, State) ->
|
||||
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function, Opts}),
|
||||
{noreply, State};
|
||||
handle_info({unregister_iq_handler, Host, XMLNS}, State) ->
|
||||
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:stop_iq_handler(Module, Function, Opts);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ets:delete(sm_iqtable, {XMLNS, Host}),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
loop()
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
%% Description: This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
open_session(User, Resource) ->
|
||||
ejabberd_sm ! {open_session, User, Resource, self()}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
close_session(User, Resource) ->
|
||||
ejabberd_sm ! {close_session, User, Resource}.
|
||||
|
||||
set_session(SID, User, Server, Resource, Priority) ->
|
||||
|
||||
register_connection(User, Resource, Pid) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
LResource = jlib:resourceprep(Resource),
|
||||
US = {LUser, LServer},
|
||||
USR = {LUser, LServer, LResource},
|
||||
UR = {LUser, LResource},
|
||||
F = fun() ->
|
||||
mnesia:write(#session{sid = SID,
|
||||
usr = USR,
|
||||
us = US,
|
||||
priority = Priority})
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
Ss = mnesia:wread({session, UR}),
|
||||
mnesia:write(#session{ur = UR, user = LUser, pid = Pid}),
|
||||
Ss
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Ss} ->
|
||||
lists:foreach(
|
||||
fun(R) ->
|
||||
R#session.pid ! replaced
|
||||
end, Ss);
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
|
||||
remove_connection(User, Resource) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LResource = jlib:resourceprep(Resource),
|
||||
UR = {LUser, LResource},
|
||||
F = fun() ->
|
||||
mnesia:delete({session, UR})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
|
||||
clean_table_from_bad_node(Node) ->
|
||||
F = fun() ->
|
||||
Es = mnesia:select(
|
||||
session,
|
||||
[{#session{sid = {'_', '$1'}, _ = '_'},
|
||||
[{#session{pid = '$1', _ = '_'},
|
||||
[{'==', {node, '$1'}, Node}],
|
||||
['$_']}]),
|
||||
lists:foreach(fun(E) ->
|
||||
mnesia:delete({session, E#session.sid})
|
||||
mnesia:delete_object(E)
|
||||
end, Es)
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
mnesia:transaction(F).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
@@ -297,7 +142,7 @@ clean_table_from_bad_node(Node) ->
|
||||
do_route(From, To, Packet) ->
|
||||
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
|
||||
[From, To, Packet, 8]),
|
||||
#jid{user = User, server = Server,
|
||||
#jid{user = User,
|
||||
luser = LUser, lserver = LServer, lresource = LResource} = To,
|
||||
{xmlelement, Name, Attrs, _Els} = Packet,
|
||||
case LResource of
|
||||
@@ -307,55 +152,62 @@ do_route(From, To, Packet) ->
|
||||
{Pass, Subsc} =
|
||||
case xml:get_attr_s("type", Attrs) of
|
||||
"subscribe" ->
|
||||
Reason = xml:get_path_s(
|
||||
Packet,
|
||||
[{elem, "status"}, cdata]),
|
||||
{ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server, From, subscribe, Reason]),
|
||||
{mod_roster:in_subscription(User,
|
||||
From,
|
||||
subscribe),
|
||||
true};
|
||||
"subscribed" ->
|
||||
{ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server, From, subscribed, ""]),
|
||||
{mod_roster:in_subscription(User,
|
||||
From,
|
||||
subscribed),
|
||||
true};
|
||||
"unsubscribe" ->
|
||||
{ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server, From, unsubscribe, ""]),
|
||||
{mod_roster:in_subscription(User,
|
||||
From,
|
||||
unsubscribe),
|
||||
true};
|
||||
"unsubscribed" ->
|
||||
{ejabberd_hooks:run_fold(
|
||||
roster_in_subscription,
|
||||
LServer,
|
||||
false,
|
||||
[User, Server, From, unsubscribed, ""]),
|
||||
{mod_roster:in_subscription(User,
|
||||
From,
|
||||
unsubscribed),
|
||||
true};
|
||||
_ ->
|
||||
{true, false}
|
||||
end,
|
||||
if Pass ->
|
||||
LFrom = jlib:jid_tolower(From),
|
||||
PResources = get_user_present_resources(
|
||||
LUser, LServer),
|
||||
lists:foreach(
|
||||
fun({_, R}) ->
|
||||
if LFrom /=
|
||||
{LUser, LServer, R} ->
|
||||
do_route(
|
||||
From,
|
||||
jlib:jid_replace_resource(To, R),
|
||||
Packet);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end, PResources);
|
||||
PResources = get_user_present_resources(User),
|
||||
if
|
||||
PResources /= [] ->
|
||||
lists:foreach(
|
||||
fun({_, R}) ->
|
||||
if LFrom /=
|
||||
{LUser, LServer, R} ->
|
||||
ejabberd_sm !
|
||||
{route,
|
||||
From,
|
||||
jlib:jid_replace_resource(To, R),
|
||||
Packet};
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end, PResources);
|
||||
true ->
|
||||
if
|
||||
Subsc ->
|
||||
case ejabberd_auth:is_user_exists(LUser) of
|
||||
true ->
|
||||
catch mod_offline:store_packet(
|
||||
From, To, Packet);
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end;
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end;
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
@@ -366,16 +218,17 @@ do_route(From, To, Packet) ->
|
||||
"broadcast" ->
|
||||
lists:foreach(
|
||||
fun(R) ->
|
||||
do_route(From,
|
||||
jlib:jid_replace_resource(To, R),
|
||||
Packet)
|
||||
end, get_user_resources(User, Server));
|
||||
ejabberd_sm ! {route,
|
||||
From,
|
||||
jlib:jid_replace_resource(To, R),
|
||||
Packet}
|
||||
end, get_user_resources(User));
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
USR = {LUser, LServer, LResource},
|
||||
case mnesia:dirty_index_read(session, USR, #session.usr) of
|
||||
LUR = {LUser, LResource},
|
||||
case mnesia:dirty_read({session, LUR}) of
|
||||
[] ->
|
||||
case Name of
|
||||
"message" ->
|
||||
@@ -393,9 +246,8 @@ do_route(From, To, Packet) ->
|
||||
_ ->
|
||||
?DEBUG("packet droped~n", [])
|
||||
end;
|
||||
Ss ->
|
||||
Session = lists:max(Ss),
|
||||
Pid = element(2, Session#session.sid),
|
||||
[Sess] ->
|
||||
Pid = Sess#session.pid,
|
||||
?DEBUG("sending to process ~p~n", [Pid]),
|
||||
Pid ! {route, From, To, Packet}
|
||||
end
|
||||
@@ -403,136 +255,96 @@ do_route(From, To, Packet) ->
|
||||
|
||||
route_message(From, To, Packet) ->
|
||||
LUser = To#jid.luser,
|
||||
LServer = To#jid.lserver,
|
||||
PrioRes = get_user_present_resources(LUser, LServer),
|
||||
case catch lists:max(PrioRes) of
|
||||
{Priority, _R} when is_integer(Priority), Priority >= 0 ->
|
||||
lists:foreach(
|
||||
%% Route messages to all priority that equals the max, if
|
||||
%% positive
|
||||
fun({P, R}) when P == Priority ->
|
||||
LResource = jlib:resourceprep(R),
|
||||
USR = {LUser, LServer, LResource},
|
||||
case mnesia:dirty_index_read(session, USR, #session.usr) of
|
||||
[] ->
|
||||
ok; % Race condition
|
||||
Ss ->
|
||||
Session = lists:max(Ss),
|
||||
Pid = element(2, Session#session.sid),
|
||||
?DEBUG("sending to process ~p~n", [Pid]),
|
||||
Pid ! {route, From, To, Packet}
|
||||
end;
|
||||
%% Ignore other priority:
|
||||
({_Prio, _Res}) ->
|
||||
ok
|
||||
end,
|
||||
PrioRes);
|
||||
_ ->
|
||||
case catch lists:max(get_user_present_resources(LUser)) of
|
||||
{'EXIT', _} ->
|
||||
case xml:get_tag_attr_s("type", Packet) of
|
||||
"error" ->
|
||||
ok;
|
||||
_ ->
|
||||
case ejabberd_auth:is_user_exists(LUser, LServer) of
|
||||
case ejabberd_auth:is_user_exists(LUser) of
|
||||
true ->
|
||||
ejabberd_hooks:run(offline_message_hook,
|
||||
LServer,
|
||||
[From, To, Packet]);
|
||||
case catch mod_offline:store_packet(
|
||||
From, To, Packet) of
|
||||
{'EXIT', _} ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
Packet, ?ERR_ITEM_NOT_FOUND),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
end
|
||||
end;
|
||||
{_, R} ->
|
||||
LResource = jlib:resourceprep(R),
|
||||
LUR = {LUser, LResource},
|
||||
case mnesia:dirty_read({session, LUR}) of
|
||||
[] ->
|
||||
ok; % Race condition
|
||||
[Sess] ->
|
||||
Pid = Sess#session.pid,
|
||||
?DEBUG("sending to process ~p~n", [Pid]),
|
||||
Pid ! {route, From, To, Packet}
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
clean_session_list(Ss) ->
|
||||
clean_session_list(lists:keysort(#session.usr, Ss), []).
|
||||
|
||||
clean_session_list([], Res) ->
|
||||
Res;
|
||||
clean_session_list([S], Res) ->
|
||||
[S | Res];
|
||||
clean_session_list([S1, S2 | Rest], Res) ->
|
||||
if
|
||||
S1#session.usr == S2#session.usr ->
|
||||
if
|
||||
S1#session.sid > S2#session.sid ->
|
||||
clean_session_list([S1 | Rest], Res);
|
||||
true ->
|
||||
clean_session_list([S2 | Rest], Res)
|
||||
end;
|
||||
true ->
|
||||
clean_session_list([S2 | Rest], [S1 | Res])
|
||||
end.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
get_user_present_resources(LUser, LServer) ->
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_index_read(session, US, #session.us) of
|
||||
get_user_resources(User) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
case catch mnesia:dirty_index_read(session, LUser, #session.user) of
|
||||
{'EXIT', _Reason} ->
|
||||
[];
|
||||
Ss ->
|
||||
[{S#session.priority, element(3, S#session.usr)} ||
|
||||
S <- clean_session_list(Ss), is_integer(S#session.priority)]
|
||||
Rs ->
|
||||
lists:map(fun(R) ->
|
||||
element(2, R#session.ur)
|
||||
end, Rs)
|
||||
end.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
%% On new session, check if some existing connections need to be replace
|
||||
check_for_sessions_to_replace(User, Server, Resource) ->
|
||||
set_presence(User, Resource, Priority) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
LResource = jlib:resourceprep(Resource),
|
||||
F = fun() ->
|
||||
UR = {User, Resource},
|
||||
mnesia:write(#presence{ur = UR, user = LUser,
|
||||
priority = Priority})
|
||||
end,
|
||||
mnesia:transaction(F).
|
||||
|
||||
%% TODO: Depending on how this is executed, there could be an unneeded
|
||||
%% replacement for max_sessions. We need to check this at some point.
|
||||
check_existing_resources(LUser, LServer, LResource),
|
||||
check_max_sessions(LUser, LServer).
|
||||
unset_presence(User, Resource, Status) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
F = fun() ->
|
||||
UR = {User, Resource},
|
||||
mnesia:delete({presence, UR})
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
catch mod_last:on_presence_update(LUser, Status).
|
||||
|
||||
check_existing_resources(LUser, LServer, LResource) ->
|
||||
USR = {LUser, LServer, LResource},
|
||||
%% A connection exist with the same resource. We replace it:
|
||||
SIDs = mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{sid = '$1', usr = USR, _ = '_'}, [], ['$1']}]),
|
||||
if
|
||||
SIDs == [] -> ok;
|
||||
true ->
|
||||
MaxSID = lists:max(SIDs),
|
||||
lists:foreach(
|
||||
fun({_, Pid} = S) when S /= MaxSID ->
|
||||
Pid ! replaced;
|
||||
(_) -> ok
|
||||
end, SIDs)
|
||||
get_user_present_resources(LUser) ->
|
||||
case catch mnesia:dirty_index_read(presence, LUser, #presence.user) of
|
||||
{'EXIT', _Reason} ->
|
||||
[];
|
||||
Rs ->
|
||||
lists:map(fun(R) ->
|
||||
{R#presence.priority, element(2, R#presence.ur)}
|
||||
end, Rs)
|
||||
end.
|
||||
|
||||
check_max_sessions(LUser, LServer) ->
|
||||
%% If the max number of sessions for a given is reached, we replace the
|
||||
%% first one
|
||||
SIDs = mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{sid = '$1', usr = {LUser, LServer, '_'}, _ = '_'}, [], ['$1']}]),
|
||||
MaxSessions = get_max_user_sessions(LServer),
|
||||
if length(SIDs) =< MaxSessions -> ok;
|
||||
true -> {_, Pid} = lists:min(SIDs),
|
||||
Pid ! replaced
|
||||
end.
|
||||
dirty_get_sessions_list() ->
|
||||
mnesia:dirty_all_keys(session).
|
||||
|
||||
|
||||
%% Get the user_max_session setting
|
||||
%% This option defines the max number of time a given users are allowed to
|
||||
%% log in
|
||||
%% Defaults to infinity
|
||||
get_max_user_sessions(Host) ->
|
||||
case ejabberd_config:get_local_option({max_user_sessions, Host}) of
|
||||
undefined -> ?MAX_USER_SESSIONS;
|
||||
Max -> Max
|
||||
end.
|
||||
dirty_get_my_sessions_list() ->
|
||||
mnesia:dirty_select(
|
||||
session,
|
||||
[{#session{pid = '$1', _ = '_'},
|
||||
[{'==', {node, '$1'}, node()}],
|
||||
['$_']}]).
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
@@ -541,24 +353,25 @@ process_iq(From, To, Packet) ->
|
||||
IQ = jlib:iq_query_info(Packet),
|
||||
case IQ of
|
||||
#iq{xmlns = XMLNS} ->
|
||||
Host = To#jid.lserver,
|
||||
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
|
||||
case ets:lookup(sm_iqtable, XMLNS) of
|
||||
[{_, Module, Function}] ->
|
||||
ResIQ = Module:Function(From, To, IQ),
|
||||
if
|
||||
ResIQ /= ignore ->
|
||||
ejabberd_router:route(To, From,
|
||||
jlib:iq_to_xml(ResIQ));
|
||||
ejabberd_router ! {route,
|
||||
To,
|
||||
From,
|
||||
jlib:iq_to_xml(ResIQ)};
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
[{_, Module, Function, Opts}] ->
|
||||
gen_iq_handler:handle(Host, Module, Function, Opts,
|
||||
gen_iq_handler:handle(Module, Function, Opts,
|
||||
From, To, IQ);
|
||||
[] ->
|
||||
Err = jlib:make_error_reply(
|
||||
Packet, ?ERR_SERVICE_UNAVAILABLE),
|
||||
ejabberd_router:route(To, From, Err)
|
||||
Packet, ?ERR_FEATURE_NOT_IMPLEMENTED),
|
||||
ejabberd_router ! {route, To, From, Err}
|
||||
end;
|
||||
reply ->
|
||||
ok;
|
||||
@@ -568,27 +381,15 @@ process_iq(From, To, Packet) ->
|
||||
ok
|
||||
end.
|
||||
|
||||
register_iq_handler(XMLNS, Module, Fun) ->
|
||||
ejabberd_sm ! {register_iq_handler, XMLNS, Module, Fun}.
|
||||
|
||||
register_iq_handler(XMLNS, Module, Fun, Opts) ->
|
||||
ejabberd_sm ! {register_iq_handler, XMLNS, Module, Fun, Opts}.
|
||||
|
||||
unregister_iq_handler(XMLNS) ->
|
||||
ejabberd_sm ! {unregister_iq_handler, XMLNS}.
|
||||
|
||||
ctl_process(_Val, ["connected-users"]) ->
|
||||
USRs = dirty_get_sessions_list(),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
SUSRs = lists:sort(USRs),
|
||||
FUSRs = lists:map(fun({U, S, R}) -> [U, $@, S, $/, R, NewLine] end, SUSRs),
|
||||
io:format("~s", [FUSRs]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(_Val, ["connected-users-number"]) ->
|
||||
N = length(dirty_get_sessions_list()),
|
||||
io:format("~p~n", [N]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(_Val, ["user-resources", User, Server]) ->
|
||||
Resources = get_user_resources(User, Server),
|
||||
NewLine = io_lib:format("~n", []),
|
||||
SResources = lists:sort(Resources),
|
||||
FResources = lists:map(fun(R) -> [R, NewLine] end, SResources),
|
||||
io:format("~s", [FResources]),
|
||||
{stop, ?STATUS_SUCCESS};
|
||||
ctl_process(Val, _Args) ->
|
||||
Val.
|
||||
|
||||
|
||||
update_tables() ->
|
||||
@@ -596,20 +397,10 @@ update_tables() ->
|
||||
[ur, user, node] ->
|
||||
mnesia:delete_table(session);
|
||||
[ur, user, pid] ->
|
||||
mnesia:delete_table(session);
|
||||
[usr, us, pid] ->
|
||||
mnesia:delete_table(session);
|
||||
[sid, usr, us, priority] ->
|
||||
ok;
|
||||
{'EXIT', _} ->
|
||||
ok
|
||||
end,
|
||||
case lists:member(presence, mnesia:system_info(tables)) of
|
||||
true ->
|
||||
mnesia:delete_table(presence);
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
case lists:member(local_session, mnesia:system_info(tables)) of
|
||||
true ->
|
||||
mnesia:delete_table(local_session);
|
||||
|
||||
@@ -19,13 +19,36 @@ start_link() ->
|
||||
|
||||
|
||||
init([]) ->
|
||||
Hooks =
|
||||
{ejabberd_hooks,
|
||||
{ejabberd_hooks, start_link, []},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_hooks]},
|
||||
Router = {ejabberd_router,
|
||||
{ejabberd_router, start_link, []},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_router]},
|
||||
SM = {ejabberd_sm,
|
||||
{ejabberd_sm, start_link, []},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_sm]},
|
||||
S2S = {ejabberd_s2s,
|
||||
{ejabberd_s2s, start_link, []},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_s2s]},
|
||||
Local = {ejabberd_local,
|
||||
{ejabberd_local, start_link, []},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_local]},
|
||||
Listener = {ejabberd_listener,
|
||||
{ejabberd_listener, start_link, []},
|
||||
permanent,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_listener]},
|
||||
StringPrep =
|
||||
{stringprep,
|
||||
{stringprep, start_link, []},
|
||||
@@ -33,49 +56,6 @@ init([]) ->
|
||||
brutal_kill,
|
||||
worker,
|
||||
[stringprep]},
|
||||
Router =
|
||||
{ejabberd_router,
|
||||
{ejabberd_router, start_link, []},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_router]},
|
||||
SM =
|
||||
{ejabberd_sm,
|
||||
{ejabberd_sm, start_link, []},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_sm]},
|
||||
S2S =
|
||||
{ejabberd_s2s,
|
||||
{ejabberd_s2s, start_link, []},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_s2s]},
|
||||
Local =
|
||||
{ejabberd_local,
|
||||
{ejabberd_local, start_link, []},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_local]},
|
||||
Listener =
|
||||
{ejabberd_listener,
|
||||
{ejabberd_listener, start_link, []},
|
||||
permanent,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_listener]},
|
||||
ReceiverSupervisor =
|
||||
{ejabberd_receiver_sup,
|
||||
{ejabberd_tmp_sup, start_link,
|
||||
[ejabberd_receiver_sup, ejabberd_receiver]},
|
||||
permanent,
|
||||
infinity,
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
C2SSupervisor =
|
||||
{ejabberd_c2s_sup,
|
||||
{ejabberd_tmp_sup, start_link, [ejabberd_c2s_sup, ejabberd_c2s]},
|
||||
@@ -132,13 +112,8 @@ init([]) ->
|
||||
supervisor,
|
||||
[ejabberd_tmp_sup]},
|
||||
{ok, {{one_for_one, 10, 1},
|
||||
[Hooks,
|
||||
[Router, SM, S2S, Local,
|
||||
StringPrep,
|
||||
Router,
|
||||
SM,
|
||||
S2S,
|
||||
Local,
|
||||
ReceiverSupervisor,
|
||||
C2SSupervisor,
|
||||
S2SInSupervisor,
|
||||
S2SOutSupervisor,
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : ejabberd_update.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : Update ejabberd
|
||||
%%% Created : 27 Jan 2006 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id: ejabberd_c2s.erl 492 2006-01-25 00:35:12Z alexey $
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_update).
|
||||
-author('alexey@sevcom.net').
|
||||
|
||||
%% API
|
||||
-export([update/0, update_info/0]).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
update() ->
|
||||
case update_info() of
|
||||
{ok, Dir, _UpdatedBeams, _Script, LowLevelScript, _Check} ->
|
||||
Eval =
|
||||
release_handler_1:eval_script(
|
||||
LowLevelScript, [],
|
||||
[{ejabberd, "", filename:join(Dir, "..")}]),
|
||||
io:format("eval: ~p~n", [Eval]),
|
||||
Eval;
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
update_info() ->
|
||||
Dir = filename:dirname(code:which(ejabberd)),
|
||||
case file:list_dir(Dir) of
|
||||
{ok, Files} ->
|
||||
Beams = [list_to_atom(filename:rootname(FN)) ||
|
||||
FN <- Files, lists:suffix(".beam", FN)],
|
||||
UpdatedBeams =
|
||||
lists:filter(
|
||||
fun(Module) ->
|
||||
{ok, {Module, [NewVsn]}} =
|
||||
beam_lib:version(code:which(Module)),
|
||||
case code:is_loaded(Module) of
|
||||
{file, _} ->
|
||||
Attrs = Module:module_info(attributes),
|
||||
{value, {vsn, [CurVsn]}} =
|
||||
lists:keysearch(vsn, 1, Attrs),
|
||||
NewVsn /= CurVsn;
|
||||
false ->
|
||||
false
|
||||
end
|
||||
end, Beams),
|
||||
io:format("beam files: ~p~n", [UpdatedBeams]),
|
||||
Script = make_script(UpdatedBeams),
|
||||
io:format("script: ~p~n", [Script]),
|
||||
LowLevelScript = make_low_level_script(UpdatedBeams, Script),
|
||||
io:format("low level script: ~p~n", [LowLevelScript]),
|
||||
Check =
|
||||
release_handler_1:check_script(
|
||||
LowLevelScript,
|
||||
[{ejabberd, "", filename:join(Dir, "..")}]),
|
||||
io:format("check: ~p~n", [Check]),
|
||||
{ok, Dir, UpdatedBeams, Script, LowLevelScript, Check};
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
%% From systools.hrl
|
||||
-record(application,
|
||||
{name, %% Name of the application, atom().
|
||||
type = permanent, %% Application start type, atom().
|
||||
vsn = "", %% Version of the application, string().
|
||||
id = "", %% Id of the application, string().
|
||||
description = "", %% Description of application, string().
|
||||
modules = [], %% [Module | {Module,Vsn}] of modules
|
||||
%% incorporated in the application,
|
||||
%% Module = atom(), Vsn = string().
|
||||
uses = [], %% [Application] list of applications required
|
||||
%% by the application, Application = atom().
|
||||
includes = [], %% [Application] list of applications included
|
||||
%% by the application, Application = atom().
|
||||
regs = [], %% [RegNames] a list of registered process
|
||||
%% names used by the application, RegNames =
|
||||
%% atom().
|
||||
env = [], %% [{Key,Value}] environment variable of
|
||||
%% application, Key = Value = term().
|
||||
maxT = infinity, %% Max time an application may exist,
|
||||
%% integer() | infinity.
|
||||
maxP = infinity, %% Max number of processes in an application,
|
||||
%% integer() | infinity.
|
||||
mod = [], %% [] | {Mod, StartArgs}, Mod= atom(),
|
||||
%% StartArgs = list().
|
||||
start_phases = [], %% [] | {Phase, PhaseArgs}, Phase = atom(),
|
||||
%% PhaseArgs = list().
|
||||
dir = "" %% The directory where the .app file was
|
||||
%% found (internal use).
|
||||
}).
|
||||
|
||||
|
||||
make_script(UpdatedBeams) ->
|
||||
lists:map(
|
||||
fun(Module) ->
|
||||
{ok, {Module, [{attributes, NewAttrs}]}} =
|
||||
beam_lib:chunks(code:which(Module), [attributes]),
|
||||
CurAttrs = Module:module_info(attributes),
|
||||
case lists:keysearch(update_info, 1, NewAttrs) of
|
||||
{value, {_, [{update, _}]}} ->
|
||||
case lists:keysearch(update_info, 1, CurAttrs) of
|
||||
{value, {_, [{update, Extra}]}} ->
|
||||
{update, Module, {advanced, Extra}};
|
||||
false ->
|
||||
{update, Module, {advanced, 0}}
|
||||
end;
|
||||
false ->
|
||||
{load_module, Module}
|
||||
end
|
||||
end, UpdatedBeams).
|
||||
|
||||
make_low_level_script(UpdatedBeams, Script) ->
|
||||
EJDApp = #application{name = ejabberd,
|
||||
modules = UpdatedBeams},
|
||||
{ok, LowLevelScript} =
|
||||
systools_rc:translate_scripts([Script], [EJDApp], [EJDApp]),
|
||||
LowLevelScript.
|
||||
@@ -1,45 +0,0 @@
|
||||
# $Id$
|
||||
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@ @ZLIB_CFLAGS@ @ERLANG_CFLAGS@
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBS = @LIBS@ @ZLIB_LIBS@ @ERLANG_LIBS@
|
||||
|
||||
ifeq ($(shell uname),Darwin)
|
||||
DYNAMIC_LIB_CFLAGS = -fPIC -bundle -flat_namespace -undefined suppress
|
||||
else
|
||||
# Assume Linux-style dynamic library flags
|
||||
DYNAMIC_LIB_CFLAGS = -fpic -shared
|
||||
endif
|
||||
|
||||
SUBDIRS =
|
||||
|
||||
ERLSHLIBS = ../ejabberd_zlib_drv.so
|
||||
|
||||
OUTDIR = ..
|
||||
EFLAGS = -I .. -pz ..
|
||||
OBJS = \
|
||||
$(OUTDIR)/ejabberd_zlib.beam
|
||||
|
||||
all: $(OBJS) $(ERLSHLIBS)
|
||||
|
||||
$(OUTDIR)/%.beam: %.erl
|
||||
@ERLC@ -W $(EFLAGS) -o $(OUTDIR) $<
|
||||
|
||||
#all: $(ERLSHLIBS)
|
||||
# erl -s make all report "{outdir, \"..\"}" -noinput -s erlang halt
|
||||
|
||||
$(ERLSHLIBS): ../%.so: %.c
|
||||
$(CC) -Wall $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) \
|
||||
$(subst ../,,$(subst .so,.c,$@)) $(LIBS) \
|
||||
-o $@ $(DYNAMIC_LIB_CFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(ERLSHLIBS)
|
||||
|
||||
distclean: clean
|
||||
rm -f Makefile
|
||||
|
||||
TAGS:
|
||||
etags *.erl
|
||||
@@ -1,37 +0,0 @@
|
||||
|
||||
include ..\Makefile.inc
|
||||
|
||||
OUTDIR = ..
|
||||
EFLAGS = -I .. -pz ..
|
||||
|
||||
OBJS = \
|
||||
$(OUTDIR)\ejabberd_zlib.beam
|
||||
|
||||
SOURCE = ejabberd_zlib_drv.c
|
||||
OBJECT = ejabberd_zlib_drv.o
|
||||
DLL = $(OUTDIR)\ejabberd_zlib_drv.dll
|
||||
|
||||
ALL : $(DLL) $(OBJS)
|
||||
|
||||
CLEAN :
|
||||
-@erase $(DLL)
|
||||
-@erase $(OUTDIR)\ejabberd_zlib_drv.exp
|
||||
-@erase $(OUTDIR)\ejabberd_zlib_drv.lib
|
||||
-@erase $(OBJECT)
|
||||
-@erase $(OBJS)
|
||||
|
||||
$(OUTDIR)\ejabberd_zlib.beam : ejabberd_zlib.erl
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) ejabberd_zlib.erl
|
||||
|
||||
CC=cl.exe
|
||||
CC_FLAGS=-nologo -D__WIN32__ -DWIN32 -DWINDOWS -D_WIN32 -DNT -MD -Ox -I"$(ERLANG_DIR)\usr\include" -I"$(EI_DIR)\include" -I"$(ZLIB_DIR)\include"
|
||||
|
||||
LD=link.exe
|
||||
LD_FLAGS=-release -nologo -incremental:no -dll "$(EI_DIR)\lib\ei_md.lib" "$(EI_DIR)\lib\erl_interface_md.lib" "$(ZLIB_LIB)" MSVCRT.LIB kernel32.lib advapi32.lib gdi32.lib user32.lib comctl32.lib comdlg32.lib shell32.lib
|
||||
|
||||
$(DLL) : $(OBJECT)
|
||||
$(LD) $(LD_FLAGS) -out:$(DLL) $(OBJECT)
|
||||
|
||||
$(OBJECT) : $(SOURCE)
|
||||
$(CC) $(CC_FLAGS) -c -Fo$(OBJECT) $(SOURCE)
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_zlib.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : Interface to zlib
|
||||
%%% Created : 19 Jan 2006 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_zlib).
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([start/0, start_link/0,
|
||||
enable_zlib/2, disable_zlib/1,
|
||||
send/2,
|
||||
recv/2, recv/3, recv_data/2,
|
||||
setopts/2,
|
||||
controlling_process/2,
|
||||
close/1]).
|
||||
|
||||
%% Internal exports, call-back functions.
|
||||
-export([init/1,
|
||||
handle_call/3,
|
||||
handle_cast/2,
|
||||
handle_info/2,
|
||||
code_change/3,
|
||||
terminate/2]).
|
||||
|
||||
-define(DEFLATE, 1).
|
||||
-define(INFLATE, 2).
|
||||
|
||||
-record(zlibsock, {sockmod, socket, zlibport}).
|
||||
|
||||
start() ->
|
||||
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
init([]) ->
|
||||
case erl_ddll:load_driver(ejabberd:get_so_path(), ejabberd_zlib_drv) of
|
||||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, ejabberd_zlib_drv}, [binary]),
|
||||
{ok, Port}.
|
||||
|
||||
|
||||
%%% --------------------------------------------------------
|
||||
%%% The call-back functions.
|
||||
%%% --------------------------------------------------------
|
||||
|
||||
handle_call(_, _, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_cast(_, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({'EXIT', Port, Reason}, Port) ->
|
||||
{stop, {port_died, Reason}, Port};
|
||||
|
||||
handle_info({'EXIT', _Pid, _Reason}, Port) ->
|
||||
{noreply, Port};
|
||||
|
||||
handle_info(_, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
terminate(_Reason, Port) ->
|
||||
Port ! {self, close},
|
||||
ok.
|
||||
|
||||
|
||||
enable_zlib(SockMod, Socket) ->
|
||||
case erl_ddll:load_driver(ejabberd:get_so_path(), ejabberd_zlib_drv) of
|
||||
ok -> ok;
|
||||
{error, already_loaded} -> ok
|
||||
end,
|
||||
Port = open_port({spawn, ejabberd_zlib_drv}, [binary]),
|
||||
{ok, #zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}}.
|
||||
|
||||
disable_zlib(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) ->
|
||||
port_close(Port),
|
||||
{SockMod, Socket}.
|
||||
|
||||
recv(Socket, Length) ->
|
||||
recv(Socket, Length, infinity).
|
||||
recv(#zlibsock{sockmod = SockMod, socket = Socket} = ZlibSock,
|
||||
Length, Timeout) ->
|
||||
case SockMod:recv(Socket, Length, Timeout) of
|
||||
{ok, Packet} ->
|
||||
recv_data(ZlibSock, Packet);
|
||||
{error, _Reason} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
recv_data(#zlibsock{zlibport = Port} = _ZlibSock, Packet) ->
|
||||
case port_control(Port, ?INFLATE, Packet) of
|
||||
<<0, In/binary>> ->
|
||||
{ok, In};
|
||||
<<1, Error/binary>> ->
|
||||
{error, binary_to_list(Error)}
|
||||
end.
|
||||
|
||||
send(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port},
|
||||
Packet) ->
|
||||
case port_control(Port, ?DEFLATE, Packet) of
|
||||
<<0, Out/binary>> ->
|
||||
SockMod:send(Socket, Out);
|
||||
<<1, Error/binary>> ->
|
||||
{error, binary_to_list(Error)}
|
||||
end.
|
||||
|
||||
|
||||
setopts(#zlibsock{sockmod = SockMod, socket = Socket}, Opts) ->
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
inet:setopts(Socket, Opts);
|
||||
_ ->
|
||||
SockMod:setopts(Socket, Opts)
|
||||
end.
|
||||
|
||||
controlling_process(#zlibsock{sockmod = SockMod, socket = Socket}, Pid) ->
|
||||
SockMod:controlling_process(Socket, Pid).
|
||||
|
||||
close(#zlibsock{sockmod = SockMod, socket = Socket, zlibport = Port}) ->
|
||||
SockMod:close(Socket),
|
||||
port_close(Port).
|
||||
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
/* $Id$ */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <erl_driver.h>
|
||||
#include <zlib.h>
|
||||
|
||||
|
||||
#define BUF_SIZE 1024
|
||||
|
||||
typedef struct {
|
||||
ErlDrvPort port;
|
||||
z_stream *d_stream;
|
||||
z_stream *i_stream;
|
||||
} ejabberd_zlib_data;
|
||||
|
||||
|
||||
static ErlDrvData ejabberd_zlib_drv_start(ErlDrvPort port, char *buff)
|
||||
{
|
||||
ejabberd_zlib_data *d =
|
||||
(ejabberd_zlib_data *)driver_alloc(sizeof(ejabberd_zlib_data));
|
||||
d->port = port;
|
||||
|
||||
d->d_stream = (z_stream *)malloc(sizeof(z_stream));
|
||||
|
||||
d->d_stream->zalloc = (alloc_func)0;
|
||||
d->d_stream->zfree = (free_func)0;
|
||||
d->d_stream->opaque = (voidpf)0;
|
||||
|
||||
deflateInit(d->d_stream, Z_DEFAULT_COMPRESSION);
|
||||
|
||||
d->i_stream = (z_stream *)malloc(sizeof(z_stream));
|
||||
|
||||
d->i_stream->zalloc = (alloc_func)0;
|
||||
d->i_stream->zfree = (free_func)0;
|
||||
d->i_stream->opaque = (voidpf)0;
|
||||
|
||||
inflateInit(d->i_stream);
|
||||
|
||||
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
|
||||
|
||||
return (ErlDrvData)d;
|
||||
}
|
||||
|
||||
static void ejabberd_zlib_drv_stop(ErlDrvData handle)
|
||||
{
|
||||
ejabberd_zlib_data *d = (ejabberd_zlib_data *)handle;
|
||||
|
||||
deflateEnd(d->d_stream);
|
||||
free(d->d_stream);
|
||||
|
||||
inflateEnd(d->i_stream);
|
||||
free(d->i_stream);
|
||||
|
||||
driver_free((char *)handle);
|
||||
}
|
||||
|
||||
|
||||
#define DEFLATE 1
|
||||
#define INFLATE 2
|
||||
|
||||
#define die_unless(cond, errstr) \
|
||||
if (!(cond)) \
|
||||
{ \
|
||||
rlen = strlen(errstr) + 1; \
|
||||
b = driver_realloc_binary(b, rlen); \
|
||||
b->orig_bytes[0] = 1; \
|
||||
strncpy(b->orig_bytes + 1, errstr, rlen - 1); \
|
||||
*rbuf = (char *)b; \
|
||||
return rlen; \
|
||||
}
|
||||
|
||||
|
||||
static int ejabberd_zlib_drv_control(ErlDrvData handle,
|
||||
unsigned int command,
|
||||
char *buf, int len,
|
||||
char **rbuf, int rlen)
|
||||
{
|
||||
ejabberd_zlib_data *d = (ejabberd_zlib_data *)handle;
|
||||
int err;
|
||||
int size;
|
||||
ErlDrvBinary *b;
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case DEFLATE:
|
||||
size = BUF_SIZE + 1;
|
||||
rlen = 1;
|
||||
b = driver_alloc_binary(size);
|
||||
b->orig_bytes[0] = 0;
|
||||
|
||||
d->d_stream->next_in = buf;
|
||||
d->d_stream->avail_in = len;
|
||||
d->d_stream->avail_out = 0;
|
||||
err = Z_OK;
|
||||
|
||||
while (err == Z_OK && d->d_stream->avail_out == 0)
|
||||
{
|
||||
d->d_stream->next_out = b->orig_bytes + rlen;
|
||||
d->d_stream->avail_out = BUF_SIZE;
|
||||
|
||||
err = deflate(d->d_stream, Z_SYNC_FLUSH);
|
||||
die_unless((err == Z_OK) || (err == Z_STREAM_END),
|
||||
"Deflate error");
|
||||
|
||||
rlen += (BUF_SIZE - d->d_stream->avail_out);
|
||||
size += (BUF_SIZE - d->d_stream->avail_out);
|
||||
b = driver_realloc_binary(b, size);
|
||||
}
|
||||
b = driver_realloc_binary(b, rlen);
|
||||
*rbuf = (char *)b;
|
||||
return rlen;
|
||||
case INFLATE:
|
||||
size = BUF_SIZE + 1;
|
||||
rlen = 1;
|
||||
b = driver_alloc_binary(size);
|
||||
b->orig_bytes[0] = 0;
|
||||
|
||||
if (len > 0) {
|
||||
d->i_stream->next_in = buf;
|
||||
d->i_stream->avail_in = len;
|
||||
d->i_stream->avail_out = 0;
|
||||
err = Z_OK;
|
||||
|
||||
while (err == Z_OK && d->i_stream->avail_out == 0)
|
||||
{
|
||||
d->i_stream->next_out = b->orig_bytes + rlen;
|
||||
d->i_stream->avail_out = BUF_SIZE;
|
||||
|
||||
err = inflate(d->i_stream, Z_SYNC_FLUSH);
|
||||
die_unless((err == Z_OK) || (err == Z_STREAM_END),
|
||||
"Inflate error");
|
||||
|
||||
rlen += (BUF_SIZE - d->i_stream->avail_out);
|
||||
size += (BUF_SIZE - d->i_stream->avail_out);
|
||||
b = driver_realloc_binary(b, size);
|
||||
}
|
||||
}
|
||||
b = driver_realloc_binary(b, rlen);
|
||||
*rbuf = (char *)b;
|
||||
return rlen;
|
||||
}
|
||||
|
||||
b = driver_alloc_binary(1);
|
||||
b->orig_bytes[0] = 0;
|
||||
*rbuf = (char *)b;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
ErlDrvEntry ejabberd_zlib_driver_entry = {
|
||||
NULL, /* F_PTR init, N/A */
|
||||
ejabberd_zlib_drv_start, /* L_PTR start, called when port is opened */
|
||||
ejabberd_zlib_drv_stop, /* F_PTR stop, called when port is closed */
|
||||
NULL, /* F_PTR output, called when erlang has sent */
|
||||
NULL, /* F_PTR ready_input, called when input descriptor ready */
|
||||
NULL, /* F_PTR ready_output, called when output descriptor ready */
|
||||
"ejabberd_zlib_drv", /* char *driver_name, the argument to open_port */
|
||||
NULL, /* F_PTR finish, called when unloaded */
|
||||
NULL, /* handle */
|
||||
ejabberd_zlib_drv_control, /* F_PTR control, port_command callback */
|
||||
NULL, /* F_PTR timeout, reserved */
|
||||
NULL /* F_PTR outputv, reserved */
|
||||
};
|
||||
|
||||
DRIVER_INIT(ejabberd_zlib_drv) /* must match name in driver_entry */
|
||||
{
|
||||
return &ejabberd_zlib_driver_entry;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,310 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejd2odbc.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : Export some mnesia tables to SQL DB
|
||||
%%% Created : 22 Aug 2005 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejd2odbc).
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
%% External exports
|
||||
-export([export_passwd/2,
|
||||
export_roster/2,
|
||||
export_offline/2,
|
||||
export_last/2,
|
||||
export_vcard/2,
|
||||
export_vcard_search/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("jlib.hrl").
|
||||
-include("mod_roster.hrl").
|
||||
|
||||
-record(offline_msg, {us, timestamp, expire, from, to, packet}).
|
||||
-record(last_activity, {us, timestamp, status}).
|
||||
-record(vcard, {us, vcard}).
|
||||
-record(vcard_search, {us,
|
||||
user, luser,
|
||||
fn, lfn,
|
||||
family, lfamily,
|
||||
given, lgiven,
|
||||
middle, lmiddle,
|
||||
nickname, lnickname,
|
||||
bday, lbday,
|
||||
ctry, lctry,
|
||||
locality, llocality,
|
||||
email, lemail,
|
||||
orgname, lorgname,
|
||||
orgunit, lorgunit
|
||||
}).
|
||||
|
||||
-define(MAX_RECORDS_PER_TRANSACTION, 1000).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
export_passwd(Server, Output) ->
|
||||
export_common(
|
||||
Server, passwd, Output,
|
||||
fun(Host, {passwd, {LUser, LServer}, Password} = R)
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
["delete from users where username='", Username ,"';"
|
||||
"insert into users(username, password) "
|
||||
"values ('", Username, "', '", Pass, "');"];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end).
|
||||
|
||||
export_roster(Server, Output) ->
|
||||
export_common(
|
||||
Server, roster, Output,
|
||||
fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||
ItemVals = record_to_string(R),
|
||||
ItemGroups = groups_to_string(R),
|
||||
["delete from rosterusers "
|
||||
" where username='", Username, "' "
|
||||
" and jid='", SJID, "';"
|
||||
"insert into rosterusers("
|
||||
" username, jid, nick, "
|
||||
" subscription, ask, askmessage, "
|
||||
" server, subscribe, type) "
|
||||
" values ", ItemVals, ";"
|
||||
"delete from rostergroups "
|
||||
" where username='", Username, "' "
|
||||
" and jid='", SJID, "';",
|
||||
[["insert into rostergroups("
|
||||
" username, jid, grp) "
|
||||
" values ", ItemGroup, ";"] ||
|
||||
ItemGroup <- ItemGroups]];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end).
|
||||
|
||||
export_offline(Server, Output) ->
|
||||
export_common(
|
||||
Server, offline_msg, Output,
|
||||
fun(Host, #offline_msg{us = {LUser, LServer},
|
||||
timestamp = TimeStamp,
|
||||
from = From,
|
||||
to = To,
|
||||
packet = Packet})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
{xmlelement, Name, Attrs, Els} = Packet,
|
||||
Attrs2 = jlib:replace_from_to_attrs(
|
||||
jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To),
|
||||
Attrs),
|
||||
NewPacket = {xmlelement, Name, Attrs2,
|
||||
Els ++
|
||||
[jlib:timestamp_to_xml(
|
||||
calendar:now_to_universal_time(TimeStamp))]},
|
||||
XML =
|
||||
ejabberd_odbc:escape(
|
||||
lists:flatten(
|
||||
xml:element_to_string(NewPacket))),
|
||||
["insert into spool(username, xml) "
|
||||
"values ('", Username, "', '",
|
||||
XML,
|
||||
"');"];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end).
|
||||
|
||||
export_last(Server, Output) ->
|
||||
export_common(
|
||||
Server, last_activity, Output,
|
||||
fun(Host, #last_activity{us = {LUser, LServer},
|
||||
timestamp = TimeStamp,
|
||||
status = Status})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Seconds = ejabberd_odbc:escape(integer_to_list(TimeStamp)),
|
||||
State = ejabberd_odbc:escape(Status),
|
||||
["delete from last where username='", Username, "';"
|
||||
"insert into last(username, seconds, state) "
|
||||
"values ('", Username, "', '", Seconds, "', '", State, "');"];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end).
|
||||
|
||||
export_vcard(Server, Output) ->
|
||||
export_common(
|
||||
Server, vcard, Output,
|
||||
fun(Host, #vcard{us = {LUser, LServer},
|
||||
vcard = VCARD})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SVCARD = ejabberd_odbc:escape(
|
||||
lists:flatten(xml:element_to_string(VCARD))),
|
||||
["delete from vcard where username='", Username, "';"
|
||||
"insert into vcard(username, vcard) "
|
||||
"values ('", Username, "', '", SVCARD, "');"];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end).
|
||||
|
||||
export_vcard_search(Server, Output) ->
|
||||
export_common(
|
||||
Server, vcard_search, Output,
|
||||
fun(Host, #vcard_search{user = {User, LServer},
|
||||
luser = LUser,
|
||||
fn = FN, lfn = LFN,
|
||||
family = Family, lfamily = LFamily,
|
||||
given = Given, lgiven = LGiven,
|
||||
middle = Middle, lmiddle = LMiddle,
|
||||
nickname = Nickname, lnickname = LNickname,
|
||||
bday = BDay, lbday = LBDay,
|
||||
ctry = CTRY, lctry = LCTRY,
|
||||
locality = Locality, llocality = LLocality,
|
||||
email = EMail, lemail = LEMail,
|
||||
orgname = OrgName, lorgname = LOrgName,
|
||||
orgunit = OrgUnit, lorgunit = LOrgUnit
|
||||
})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_odbc:escape(User),
|
||||
LUsername = ejabberd_odbc:escape(LUser),
|
||||
|
||||
SFN = ejabberd_odbc:escape(FN),
|
||||
SLFN = ejabberd_odbc:escape(LFN),
|
||||
SFamily = ejabberd_odbc:escape(Family),
|
||||
SLFamily = ejabberd_odbc:escape(LFamily),
|
||||
SGiven = ejabberd_odbc:escape(Given),
|
||||
SLGiven = ejabberd_odbc:escape(LGiven),
|
||||
SMiddle = ejabberd_odbc:escape(Middle),
|
||||
SLMiddle = ejabberd_odbc:escape(LMiddle),
|
||||
SNickname = ejabberd_odbc:escape(Nickname),
|
||||
SLNickname = ejabberd_odbc:escape(LNickname),
|
||||
SBDay = ejabberd_odbc:escape(BDay),
|
||||
SLBDay = ejabberd_odbc:escape(LBDay),
|
||||
SCTRY = ejabberd_odbc:escape(CTRY),
|
||||
SLCTRY = ejabberd_odbc:escape(LCTRY),
|
||||
SLocality = ejabberd_odbc:escape(Locality),
|
||||
SLLocality = ejabberd_odbc:escape(LLocality),
|
||||
SEMail = ejabberd_odbc:escape(EMail),
|
||||
SLEMail = ejabberd_odbc:escape(LEMail),
|
||||
SOrgName = ejabberd_odbc:escape(OrgName),
|
||||
SLOrgName = ejabberd_odbc:escape(LOrgName),
|
||||
SOrgUnit = ejabberd_odbc:escape(OrgUnit),
|
||||
SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
|
||||
|
||||
["delete from vcard_search where lusername='", LUsername, "';"
|
||||
"insert into vcard_search("
|
||||
" username, lusername, fn, lfn, family, lfamily,"
|
||||
" given, lgiven, middle, lmiddle, nickname, lnickname,"
|
||||
" bday, lbday, ctry, lctry, locality, llocality,"
|
||||
" email, lemail, orgname, lorgname, orgunit, lorgunit)"
|
||||
"values (",
|
||||
" '", Username, "', '", LUsername, "',"
|
||||
" '", SFN, "', '", SLFN, "',"
|
||||
" '", SFamily, "', '", SLFamily, "',"
|
||||
" '", SGiven, "', '", SLGiven, "',"
|
||||
" '", SMiddle, "', '", SLMiddle, "',"
|
||||
" '", SNickname, "', '", SLNickname, "',"
|
||||
" '", SBDay, "', '", SLBDay, "',"
|
||||
" '", SCTRY, "', '", SLCTRY, "',"
|
||||
" '", SLocality, "', '", SLLocality, "',"
|
||||
" '", SEMail, "', '", SLEMail, "',"
|
||||
" '", SOrgName, "', '", SLOrgName, "',"
|
||||
" '", SOrgUnit, "', '", SLOrgUnit, "');"];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
export_common(Server, Table, Output, ConvertFun) ->
|
||||
IO = case Output of
|
||||
odbc ->
|
||||
odbc;
|
||||
_ ->
|
||||
{ok, IODevice} = file:open(Output, [write, raw]),
|
||||
IODevice
|
||||
end,
|
||||
mnesia:transaction(
|
||||
fun() ->
|
||||
mnesia:read_lock_table(Table),
|
||||
LServer = jlib:nameprep(Server),
|
||||
{_N, SQLs} =
|
||||
mnesia:foldl(
|
||||
fun(R, {N, SQLs} = Acc) ->
|
||||
case ConvertFun(LServer, R) of
|
||||
[] ->
|
||||
Acc;
|
||||
SQL ->
|
||||
if
|
||||
N < ?MAX_RECORDS_PER_TRANSACTION - 1 ->
|
||||
{N + 1, [SQL | SQLs]};
|
||||
true ->
|
||||
output(LServer, IO,
|
||||
["begin;",
|
||||
lists:reverse([SQL | SQLs]),
|
||||
"commit"]),
|
||||
{0, []}
|
||||
end
|
||||
end
|
||||
end, {0, []}, Table),
|
||||
output(LServer, IO,
|
||||
["begin;",
|
||||
lists:reverse(SQLs),
|
||||
"commit"])
|
||||
end).
|
||||
|
||||
output(LServer, IO, SQL) ->
|
||||
case IO of
|
||||
odbc ->
|
||||
catch ejabberd_odbc:sql_query(LServer, SQL);
|
||||
_ ->
|
||||
file:write(IO, [SQL, $;, $\n])
|
||||
end.
|
||||
|
||||
record_to_string(#roster{usj = {User, Server, JID},
|
||||
name = Name,
|
||||
subscription = Subscription,
|
||||
ask = Ask,
|
||||
askmessage = AskMessage}) ->
|
||||
Username = ejabberd_odbc:escape(User),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(JID)),
|
||||
Nick = ejabberd_odbc:escape(Name),
|
||||
SSubscription = case Subscription of
|
||||
both -> "B";
|
||||
to -> "T";
|
||||
from -> "F";
|
||||
none -> "N"
|
||||
end,
|
||||
SAsk = case Ask of
|
||||
subscribe -> "S";
|
||||
unsubscribe -> "U";
|
||||
both -> "B";
|
||||
out -> "O";
|
||||
in -> "I";
|
||||
none -> "N"
|
||||
end,
|
||||
["("
|
||||
"'", Username, "',"
|
||||
"'", SJID, "',"
|
||||
"'", Nick, "',"
|
||||
"'", SSubscription, "',"
|
||||
"'", SAsk, "',"
|
||||
"'", AskMessage, "',"
|
||||
"'N', '', 'item')"].
|
||||
|
||||
groups_to_string(#roster{usj = {User, Server, JID},
|
||||
groups = Groups}) ->
|
||||
Username = ejabberd_odbc:escape(User),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(JID)),
|
||||
[["("
|
||||
"'", Username, "',"
|
||||
"'", SJID, "',"
|
||||
"'", ejabberd_odbc:escape(Group), "')"] || Group <- Groups].
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
# $Id$
|
||||
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@ @ERLANG_CFLAGS@
|
||||
CFLAGS = @CFLAGS@
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBS = @LIBS@ @ERLANG_LIBS@
|
||||
LIBS = @LIBS@
|
||||
|
||||
INCLUDES = @ERLANG_CFLAGS@
|
||||
|
||||
LIBDIRS = @ERLANG_LIBS@
|
||||
|
||||
SUBDIRS =
|
||||
|
||||
|
||||
OUTDIR = ..
|
||||
EFLAGS = -I .. -pz ..
|
||||
OBJS = \
|
||||
$(OUTDIR)/eldap.beam \
|
||||
$(OUTDIR)/ELDAPv3.beam \
|
||||
$(OUTDIR)/eldap_filter.beam
|
||||
$(OUTDIR)/ELDAPv3.beam
|
||||
|
||||
all: $(OBJS)
|
||||
|
||||
ELDAPv3.erl: ELDAPv3.asn
|
||||
@ERLC@ -bber_bin -W $(EFLAGS) $<
|
||||
erlc -bber_bin -W $(EFLAGS) $<
|
||||
|
||||
$(OUTDIR)/%.beam: %.erl ELDAPv3.erl
|
||||
@ERLC@ -W $(EFLAGS) -o $(OUTDIR) $<
|
||||
erlc -W $(EFLAGS) -o $(OUTDIR) $<
|
||||
|
||||
|
||||
clean:
|
||||
|
||||
@@ -6,8 +6,7 @@ EFLAGS = -I .. -pz ..
|
||||
|
||||
OBJS = \
|
||||
$(OUTDIR)\eldap.beam \
|
||||
$(OUTDIR)\ELDAPv3.beam \
|
||||
$(OUTDIR)\eldap_filter.beam
|
||||
$(OUTDIR)\ELDAPv3.beam
|
||||
|
||||
ALL : $(OBJS)
|
||||
|
||||
|
||||
@@ -765,7 +765,7 @@ retry_connect() ->
|
||||
cmd_timeout(Timer, Id, S) ->
|
||||
Dict = S#eldap.dict,
|
||||
case dict:find(Id, Dict) of
|
||||
{ok, [{Timer, From, Name}|Res]} ->
|
||||
{ok, [{Id, Timer, From, Name}|Res]} ->
|
||||
case Name of
|
||||
searchRequest ->
|
||||
{Res1, Ref1} = polish(Res),
|
||||
@@ -808,7 +808,7 @@ polish([], Res, Ref) ->
|
||||
%%-----------------------------------------------------------------------
|
||||
connect_bind(S) ->
|
||||
Host = next_host(S#eldap.host, S#eldap.hosts),
|
||||
TcpOpts = [{packet, asn1}, {active, true}, {keepalive, true}, binary],
|
||||
TcpOpts = [{packet, asn1}, {active, true}, binary],
|
||||
case gen_tcp:connect(Host, S#eldap.port, TcpOpts) of
|
||||
{ok, Socket} ->
|
||||
case bind_request(Socket, S) of
|
||||
|
||||
@@ -1,285 +0,0 @@
|
||||
%%%====================================================
|
||||
%%% File: eldap_filter.erl
|
||||
%%% Purpose: Converts String Representation of
|
||||
%%% LDAP Search Filter (RFC 2254)
|
||||
%%% to eldap's representation of filter
|
||||
%%% Author: Evgeniy Khramtsov <xramtsov@gmail.com>
|
||||
%%% License: GPL
|
||||
%%%====================================================
|
||||
|
||||
-module(eldap_filter).
|
||||
-author('xram@jabber.ru').
|
||||
|
||||
%%%======================
|
||||
%%% Export functions
|
||||
%%%======================
|
||||
|
||||
-export([parse/1,
|
||||
parse/2,
|
||||
do_sub/2
|
||||
]).
|
||||
|
||||
%%%-------------------------------------------------------------------------
|
||||
%%% Arity: parse/1
|
||||
%%% Function: parse(RFC2254_Filter) -> {ok, EldapFilter} |
|
||||
%%% {error, bad_filter}
|
||||
%%%
|
||||
%%% RFC2254_Filter = string().
|
||||
%%%
|
||||
%%% Description: Converts String Representation of LDAP Search Filter (RFC 2254)
|
||||
%%% to eldap's representation of filter.
|
||||
%%%
|
||||
%%% Example:
|
||||
%%% > eldap_filter:parse("(&(!(uid<=100))(mail=*))").
|
||||
%%%
|
||||
%%% {ok,{'and',[{'not',{lessOrEqual,{'AttributeValueAssertion',"uid","100"}}},
|
||||
%%% {present,"mail"}]}}
|
||||
%%%-------------------------------------------------------------------------
|
||||
parse(RFC2254_Filter) ->
|
||||
parse(RFC2254_Filter, []).
|
||||
|
||||
%%%-------------------------------------------------------------------------
|
||||
%%% Arity: parse/2
|
||||
%%% Function: parse(RFC2254_Filter, [SubstValue |...]) ->
|
||||
%%% {ok, EldapFilter} |
|
||||
%%% {error, bad_filter} |
|
||||
%%% {error, bad_regexp} |
|
||||
%%% {error, max_substitute_recursion}
|
||||
%%%
|
||||
%%% SubstValue = {RegExp, Value} | {RegExp, Value, N},
|
||||
%%% RFC2254_Filter = RegExp = Value = string(),
|
||||
%%% N = integer().
|
||||
%%%
|
||||
%%% Description: The same as parse/1, but substitutes N or all occurences
|
||||
%%% of RegExp with Value *after* parsing.
|
||||
%%%
|
||||
%%% Example:
|
||||
%%% > eldap_filter:parse(
|
||||
%%% "(|(mail=%u@%d)(jid=%u@%d))",
|
||||
%%% [{"%u", "xramtsov"},{"%d","gmail.com"}]).
|
||||
%%%
|
||||
%%% {ok,{'or',[{equalityMatch,{'AttributeValueAssertion',
|
||||
%%% "mail",
|
||||
%%% "xramtsov@gmail.com"}},
|
||||
%%% {equalityMatch,{'AttributeValueAssertion',
|
||||
%%% "jid",
|
||||
%%% "xramtsov@gmail.com"}}]}}
|
||||
%%%--------------------------------------------------------------------------
|
||||
parse(RFC2254_Filter, ListOfSubValues) ->
|
||||
case catch convert_filter(parse_filter(RFC2254_Filter), ListOfSubValues) of
|
||||
[EldapFilter] when is_tuple(EldapFilter) ->
|
||||
{ok, EldapFilter};
|
||||
{regexp, Error} ->
|
||||
{error, Error};
|
||||
_ ->
|
||||
{error, bad_filter}
|
||||
end.
|
||||
|
||||
%%%==========================
|
||||
%%% Internal functions
|
||||
%%%==========================
|
||||
|
||||
%%%----------------------
|
||||
%%% split/1,4
|
||||
%%%----------------------
|
||||
split(Filter) ->
|
||||
split(Filter, 0, [], []).
|
||||
|
||||
split([], _, _, Result) ->
|
||||
Result;
|
||||
|
||||
split([H|T], Num, Rest, Result) ->
|
||||
NewNum = case H of
|
||||
$( -> Num + 1;
|
||||
$) -> Num - 1;
|
||||
_ -> Num
|
||||
end,
|
||||
if
|
||||
NewNum == 0 ->
|
||||
X = Rest++[H],
|
||||
LenX = length(X),
|
||||
if
|
||||
LenX > 2 ->
|
||||
split(T, 0, [], Result ++ [lists:sublist(X, 2, LenX-2)]);
|
||||
true ->
|
||||
split(T, 0, Rest, Result)
|
||||
end;
|
||||
true ->
|
||||
split(T, NewNum, Rest++[H], Result)
|
||||
end.
|
||||
|
||||
%%%-----------------------
|
||||
%%% parse_filter/1
|
||||
%%%-----------------------
|
||||
parse_filter(Filter) ->
|
||||
case Filter of
|
||||
[$! | T] ->
|
||||
{'not', parse_filter(T)};
|
||||
[$| | T] ->
|
||||
{'or', parse_filter(T)};
|
||||
[$& | T] ->
|
||||
{'and', parse_filter(T)};
|
||||
[$( | _] ->
|
||||
parse_filter(split(Filter));
|
||||
[List | _] when is_list(List) ->
|
||||
[parse_filter(X) || X <- Filter];
|
||||
_ ->
|
||||
Filter
|
||||
end.
|
||||
|
||||
%%%--------------------
|
||||
%%% convert_filter/2
|
||||
%%%--------------------
|
||||
convert_filter({'not', [Val | _]}, Replace) ->
|
||||
eldap:'not'(convert_filter(Val, Replace));
|
||||
|
||||
convert_filter({'or', Vals}, Replace) ->
|
||||
eldap:'or'([convert_filter(X, Replace) || X <- Vals]);
|
||||
|
||||
convert_filter({'and', Vals}, Replace) ->
|
||||
eldap:'and'([convert_filter(X, Replace) || X <- Vals]);
|
||||
|
||||
convert_filter([H|_] = Filter, Replace) when is_integer(H) ->
|
||||
parse_attr(Filter, Replace);
|
||||
|
||||
convert_filter(Filter, Replace) when is_list(Filter) ->
|
||||
[convert_filter(X, Replace) || X <- Filter].
|
||||
|
||||
%%%-----------------
|
||||
%%% parse_attr/2,3
|
||||
%%%-----------------
|
||||
parse_attr(Attr, ListOfSubValues) ->
|
||||
{Action, [_|_] = Name, [_|_] = Value} = split_attribute(Attr),
|
||||
parse_attr(Action, {Name, Value}, ListOfSubValues).
|
||||
|
||||
parse_attr(approx, {Name, Value}, ListOfSubValues) ->
|
||||
NewValue = do_sub(Value, ListOfSubValues),
|
||||
eldap:approxMatch(Name, NewValue);
|
||||
|
||||
parse_attr(greater, {Name, Value}, ListOfSubValues) ->
|
||||
NewValue = do_sub(Value, ListOfSubValues),
|
||||
eldap:greaterOrEqual(Name, NewValue);
|
||||
|
||||
parse_attr(less, {Name, Value}, ListOfSubValues) ->
|
||||
NewValue = do_sub(Value, ListOfSubValues),
|
||||
eldap:lessOrEqual(Name, NewValue);
|
||||
|
||||
parse_attr(equal, {Name, Value}, ListOfSubValues) ->
|
||||
{ok, RegSList} = regexp:split(remove_extra_asterisks(Value), "[*]"),
|
||||
Pattern = case [do_sub(X, ListOfSubValues) || X <- RegSList] of
|
||||
[Head | Tail] when Tail /= [] ->
|
||||
{Head, lists:sublist(Tail, length(Tail)-1), lists:last(Tail)};
|
||||
R ->
|
||||
R
|
||||
end,
|
||||
case Pattern of
|
||||
[V] ->
|
||||
eldap:equalityMatch(Name, V);
|
||||
{[], [], []} ->
|
||||
eldap:present(Name);
|
||||
{"", Any, ""} ->
|
||||
eldap:substrings(Name, [{any, X} || X<-Any]);
|
||||
{H, Any, ""} ->
|
||||
eldap:substrings(Name, [{initial, H}]++[{any, X} || X<-Any]);
|
||||
{"", Any, T} ->
|
||||
eldap:substrings(Name, [{any, X} || X<-Any]++[{final, T}]);
|
||||
{H, Any, T} ->
|
||||
eldap:substrings(Name, [{initial, H}]++[{any, X} || X<-Any]++[{final, T}])
|
||||
end;
|
||||
|
||||
parse_attr(_, _, _) ->
|
||||
false.
|
||||
|
||||
%%%--------------------
|
||||
%%% do_sub/2,3
|
||||
%%%--------------------
|
||||
|
||||
-define(MAX_RECURSION, 100).
|
||||
|
||||
do_sub(S, []) ->
|
||||
S;
|
||||
|
||||
do_sub([], _) ->
|
||||
[];
|
||||
|
||||
do_sub(S, [{RegExp, New} | T]) ->
|
||||
Result = do_sub(S, {RegExp, replace_amps(New)}, 1),
|
||||
do_sub(Result, T);
|
||||
|
||||
do_sub(S, [{RegExp, New, Times} | T]) ->
|
||||
Result = do_sub(S, {RegExp, replace_amps(New), Times}, 1),
|
||||
do_sub(Result, T).
|
||||
|
||||
do_sub(S, {RegExp, New}, Iter) ->
|
||||
case regexp:sub(S, RegExp, New) of
|
||||
{ok, NewS, 0} ->
|
||||
NewS;
|
||||
{ok, NewS, _} when Iter =< ?MAX_RECURSION ->
|
||||
do_sub(NewS, {RegExp, New}, Iter+1);
|
||||
{ok, _, _} when Iter > ?MAX_RECURSION ->
|
||||
throw({regexp, max_substitute_recursion});
|
||||
_ ->
|
||||
throw({regexp, bad_regexp})
|
||||
end;
|
||||
|
||||
do_sub(S, {_, _, N}, _) when N<1 ->
|
||||
S;
|
||||
|
||||
do_sub(S, {RegExp, New, Times}, Iter) ->
|
||||
case regexp:sub(S, RegExp, New) of
|
||||
{ok, NewS, 0} ->
|
||||
NewS;
|
||||
{ok, NewS, _} when Iter < Times ->
|
||||
do_sub(NewS, {RegExp, New, Times}, Iter+1);
|
||||
{ok, NewS, _} ->
|
||||
NewS;
|
||||
_ ->
|
||||
throw({regexp, bad_regexp})
|
||||
end.
|
||||
|
||||
remove_extra_asterisks(String) ->
|
||||
{Res, _} = lists:foldl(
|
||||
fun(X, {Acc, Last}) ->
|
||||
case X of
|
||||
$* when Last==$* ->
|
||||
{Acc, X};
|
||||
_ ->
|
||||
{Acc ++ [X], X}
|
||||
end
|
||||
end,
|
||||
{"", ""}, String),
|
||||
Res.
|
||||
|
||||
replace_amps(String) ->
|
||||
lists:foldl(
|
||||
fun(X, Acc) ->
|
||||
if
|
||||
X == $& ->
|
||||
Acc ++ "\\&";
|
||||
true ->
|
||||
Acc ++ [X]
|
||||
end
|
||||
end,
|
||||
"", String).
|
||||
|
||||
split_attribute(String) ->
|
||||
split_attribute(String, "", $0).
|
||||
|
||||
split_attribute([], _, _) ->
|
||||
{error, "", ""};
|
||||
|
||||
split_attribute([H|Tail], Acc, Last) ->
|
||||
case H of
|
||||
$= when Last==$> ->
|
||||
{greater, lists:sublist(Acc, 1, length(Acc)-1), Tail};
|
||||
$= when Last==$< ->
|
||||
{less, lists:sublist(Acc, 1, length(Acc)-1), Tail};
|
||||
$= when Last==$~ ->
|
||||
{approx, lists:sublist(Acc, 1, length(Acc)-1), Tail};
|
||||
$= when Last==$: ->
|
||||
{equal, lists:sublist(Acc, 1, length(Acc)-1), Tail};
|
||||
$= ->
|
||||
{equal, Acc, Tail};
|
||||
_ ->
|
||||
split_attribute(Tail, Acc++[H], H)
|
||||
end.
|
||||
@@ -106,10 +106,6 @@ int ei_x_encode_string_fixed(ei_x_buff* x, const char* s)
|
||||
#define XML_CDATA 2
|
||||
#define XML_ERROR 3
|
||||
|
||||
#define PARSE_COMMAND 0
|
||||
#define PARSE_FINAL_COMMAND 1
|
||||
|
||||
ei_x_buff event_buf;
|
||||
|
||||
typedef struct {
|
||||
ErlDrvPort port;
|
||||
@@ -121,39 +117,44 @@ void *erlXML_StartElementHandler(expat_data *d,
|
||||
const XML_Char **atts)
|
||||
{
|
||||
int i;
|
||||
|
||||
ei_x_encode_list_header(&event_buf, 1);
|
||||
ei_x_encode_tuple_header(&event_buf, 2);
|
||||
ei_x_encode_long(&event_buf, XML_START);
|
||||
ei_x_encode_tuple_header(&event_buf, 2);
|
||||
ei_x_encode_string_fixed(&event_buf, name);
|
||||
|
||||
ei_x_buff buf;
|
||||
|
||||
ei_x_new_with_version(&buf);
|
||||
ei_x_encode_tuple_header(&buf, 2);
|
||||
ei_x_encode_long(&buf, XML_START);
|
||||
ei_x_encode_tuple_header(&buf, 2);
|
||||
ei_x_encode_string_fixed(&buf, name);
|
||||
|
||||
for (i = 0; atts[i]; i += 2) {}
|
||||
|
||||
if (i > 0)
|
||||
ei_x_encode_list_header(&buf, i/2);
|
||||
|
||||
for (i = 0; atts[i]; i += 2)
|
||||
{
|
||||
ei_x_encode_list_header(&event_buf, i/2);
|
||||
|
||||
for (i = 0; atts[i]; i += 2)
|
||||
{
|
||||
ei_x_encode_tuple_header(&event_buf, 2);
|
||||
ei_x_encode_string_fixed(&event_buf, atts[i]);
|
||||
ei_x_encode_string_fixed(&event_buf, atts[i+1]);
|
||||
}
|
||||
ei_x_encode_tuple_header(&buf, 2);
|
||||
ei_x_encode_string_fixed(&buf, atts[i]);
|
||||
ei_x_encode_string_fixed(&buf, atts[i+1]);
|
||||
}
|
||||
|
||||
ei_x_encode_empty_list(&event_buf);
|
||||
|
||||
|
||||
ei_x_encode_empty_list(&buf);
|
||||
|
||||
driver_output(d->port, buf.buff, buf.index);
|
||||
ei_x_free(&buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *erlXML_EndElementHandler(expat_data *d,
|
||||
const XML_Char *name)
|
||||
{
|
||||
ei_x_encode_list_header(&event_buf, 1);
|
||||
ei_x_encode_tuple_header(&event_buf, 2);
|
||||
ei_x_encode_long(&event_buf, XML_END);
|
||||
ei_x_encode_string_fixed(&event_buf, name);
|
||||
ei_x_buff buf;
|
||||
|
||||
ei_x_new_with_version(&buf);
|
||||
ei_x_encode_tuple_header(&buf, 2);
|
||||
ei_x_encode_long(&buf, XML_END);
|
||||
ei_x_encode_string_fixed(&buf, name);
|
||||
|
||||
driver_output(d->port, buf.buff, buf.index);
|
||||
ei_x_free(&buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -161,10 +162,15 @@ void *erlXML_CharacterDataHandler(expat_data *d,
|
||||
const XML_Char *s,
|
||||
int len)
|
||||
{
|
||||
ei_x_encode_list_header(&event_buf, 1);
|
||||
ei_x_encode_tuple_header(&event_buf, 2);
|
||||
ei_x_encode_long(&event_buf, XML_CDATA);
|
||||
ei_x_encode_binary(&event_buf, s, len);
|
||||
ei_x_buff buf;
|
||||
|
||||
ei_x_new_with_version(&buf);
|
||||
ei_x_encode_tuple_header(&buf, 2);
|
||||
ei_x_encode_long(&buf, XML_CDATA);
|
||||
ei_x_encode_string_len_fixed(&buf, s, len);
|
||||
|
||||
driver_output(d->port, buf.buff, buf.index);
|
||||
ei_x_free(&buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -176,8 +182,6 @@ static ErlDrvData expat_erl_start(ErlDrvPort port, char *buff)
|
||||
d->parser = XML_ParserCreate("UTF-8");
|
||||
XML_SetUserData(d->parser, d);
|
||||
|
||||
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
|
||||
|
||||
XML_SetStartElementHandler(
|
||||
d->parser, (XML_StartElementHandler)erlXML_StartElementHandler);
|
||||
XML_SetEndElementHandler(
|
||||
@@ -195,66 +199,48 @@ static void expat_erl_stop(ErlDrvData handle)
|
||||
driver_free((char*)handle);
|
||||
}
|
||||
|
||||
static int expat_erl_control(ErlDrvData drv_data,
|
||||
unsigned int command,
|
||||
char *buf, int len,
|
||||
char **rbuf, int rlen)
|
||||
static void expat_erl_output(ErlDrvData handle, char *buff, int bufflen)
|
||||
{
|
||||
expat_data* d = (expat_data*)drv_data;
|
||||
expat_data* d = (expat_data*)handle;
|
||||
int res, errcode;
|
||||
char *errstring;
|
||||
ErlDrvBinary *b;
|
||||
size_t size;
|
||||
ei_x_buff buf;
|
||||
|
||||
switch (command)
|
||||
res = XML_Parse(d->parser, buff, bufflen, 0);
|
||||
|
||||
if(!res)
|
||||
{
|
||||
case PARSE_COMMAND:
|
||||
case PARSE_FINAL_COMMAND:
|
||||
ei_x_new_with_version(&event_buf);
|
||||
res = XML_Parse(d->parser, buf, len, command == PARSE_FINAL_COMMAND);
|
||||
errcode = XML_GetErrorCode(d->parser);
|
||||
errstring = (char *)XML_ErrorString(errcode);
|
||||
|
||||
if(!res)
|
||||
{
|
||||
errcode = XML_GetErrorCode(d->parser);
|
||||
errstring = (char *)XML_ErrorString(errcode);
|
||||
ei_x_new_with_version(&buf);
|
||||
ei_x_encode_tuple_header(&buf, 2);
|
||||
ei_x_encode_long(&buf, XML_ERROR);
|
||||
ei_x_encode_tuple_header(&buf, 2);
|
||||
ei_x_encode_long(&buf, errcode);
|
||||
ei_x_encode_string_fixed(&buf, errstring);
|
||||
|
||||
ei_x_encode_list_header(&event_buf, 1);
|
||||
ei_x_encode_tuple_header(&event_buf, 2);
|
||||
ei_x_encode_long(&event_buf, XML_ERROR);
|
||||
ei_x_encode_tuple_header(&event_buf, 2);
|
||||
ei_x_encode_long(&event_buf, errcode);
|
||||
ei_x_encode_string_fixed(&event_buf, errstring);
|
||||
}
|
||||
|
||||
ei_x_encode_empty_list(&event_buf);
|
||||
|
||||
size = event_buf.index;
|
||||
|
||||
b = driver_alloc_binary(size);
|
||||
memcpy(b->orig_bytes, event_buf.buff, size);
|
||||
|
||||
ei_x_free(&event_buf);
|
||||
|
||||
*rbuf = (char *)b;
|
||||
return size;
|
||||
default:
|
||||
return 0;
|
||||
driver_output(d->port, buf.buff, buf.index);
|
||||
ei_x_free(&buf);
|
||||
}
|
||||
|
||||
//driver_output(d->port, &res, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
ErlDrvEntry expat_driver_entry = {
|
||||
NULL, /* F_PTR init, N/A */
|
||||
expat_erl_start, /* L_PTR start, called when port is opened */
|
||||
expat_erl_stop, /* F_PTR stop, called when port is closed */
|
||||
NULL, /* F_PTR output, called when erlang has sent */
|
||||
NULL, /* F_PTR ready_input, called when input descriptor ready */
|
||||
NULL, /* F_PTR ready_output, called when output descriptor ready */
|
||||
"expat_erl", /* char *driver_name, the argument to open_port */
|
||||
NULL, /* F_PTR finish, called when unloaded */
|
||||
NULL, /* handle */
|
||||
expat_erl_control, /* F_PTR control, port_command callback */
|
||||
NULL, /* F_PTR timeout, reserved */
|
||||
NULL /* F_PTR outputv, reserved */
|
||||
NULL, /* F_PTR init, N/A */
|
||||
expat_erl_start, /* L_PTR start, called when port is opened */
|
||||
expat_erl_stop, /* F_PTR stop, called when port is closed */
|
||||
expat_erl_output, /* F_PTR output, called when erlang has sent */
|
||||
NULL, /* F_PTR ready_input, called when input descriptor ready */
|
||||
NULL, /* F_PTR ready_output, called when output descriptor ready */
|
||||
"expat_erl", /* char *driver_name, the argument to open_port */
|
||||
NULL, /* F_PTR finish, called when unloaded */
|
||||
NULL, /* F_PTR control, port_command callback */
|
||||
NULL, /* F_PTR timeout, reserved */
|
||||
NULL /* F_PTR outputv, reserved */
|
||||
};
|
||||
|
||||
DRIVER_INIT(expat_erl) /* must match name in driver_entry */
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : extauth.erl
|
||||
%%% Author : Leif Johansson <leifj@it.su.se>
|
||||
%%% Purpose : External authentication using a simple port-driver
|
||||
%%% Created : 30 Jul 2004 by Leif Johansson <leifj@it.su.se>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(extauth).
|
||||
-author('leifj@it.su.se').
|
||||
|
||||
-export([start/2, stop/1, init/2,
|
||||
check_password/3, set_password/3, is_user_exists/2]).
|
||||
|
||||
|
||||
start(Host, ExtPrg) ->
|
||||
spawn(?MODULE, init, [Host, ExtPrg]).
|
||||
|
||||
init(Host, ExtPrg) ->
|
||||
register(gen_mod:get_module_proc(Host, eauth), self()),
|
||||
process_flag(trap_exit,true),
|
||||
Port = open_port({spawn, ExtPrg}, [{packet,2}]),
|
||||
loop(Port).
|
||||
|
||||
stop(Host) ->
|
||||
gen_mod:get_module_proc(Host, eauth) ! stop.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
call_port(Server, ["auth", User, Server, Password]).
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
call_port(Server, ["isuser", User, Server]).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
call_port(Server, ["setpass", User, Server, Password]).
|
||||
|
||||
call_port(Server, Msg) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
gen_mod:get_module_proc(LServer, eauth) ! {call, self(), Msg},
|
||||
receive
|
||||
{eauth,Result} ->
|
||||
Result
|
||||
end.
|
||||
|
||||
loop(Port) ->
|
||||
receive
|
||||
{call, Caller, Msg} ->
|
||||
Port ! {self(), {command, encode(Msg)}},
|
||||
receive
|
||||
{Port, {data, Data}} ->
|
||||
Caller ! {eauth, decode(Data)}
|
||||
end,
|
||||
loop(Port);
|
||||
stop ->
|
||||
Port ! {self(), close},
|
||||
receive
|
||||
{Port, closed} ->
|
||||
exit(normal)
|
||||
end;
|
||||
{'EXIT', Port, Reason} ->
|
||||
io:format("~p ~n", [Reason]),
|
||||
exit(port_terminated)
|
||||
end.
|
||||
|
||||
join(List, Sep) ->
|
||||
lists:foldl(fun(A, "") -> A;
|
||||
(A, Acc) -> Acc ++ Sep ++ A
|
||||
end, "", List).
|
||||
|
||||
encode(L) ->
|
||||
join(L,":").
|
||||
|
||||
decode([0,0]) ->
|
||||
false;
|
||||
decode([0,1]) ->
|
||||
true.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : gen_iq_handler.erl
|
||||
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Purpose : IQ handler support
|
||||
%%% Purpose :
|
||||
%%% Created : 22 Jan 2003 by Alexey Shchepin <alexey@sevcom.net>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -10,92 +10,58 @@
|
||||
-author('alexey@sevcom.net').
|
||||
-vsn('$Revision$ ').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/3,
|
||||
add_iq_handler/6,
|
||||
remove_iq_handler/3,
|
||||
-export([start/0,
|
||||
start_link/2,
|
||||
add_iq_handler/5,
|
||||
remove_iq_handler/2,
|
||||
stop_iq_handler/3,
|
||||
handle/7,
|
||||
process_iq/6]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
handle/6,
|
||||
process_iq/5,
|
||||
queue_init/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(state, {host,
|
||||
module,
|
||||
function}).
|
||||
start() ->
|
||||
ok.
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link(Host, Module, Function) ->
|
||||
gen_server:start_link(?MODULE, [Host, Module, Function], []).
|
||||
|
||||
add_iq_handler(Component, Host, NS, Module, Function, Type) ->
|
||||
add_iq_handler(Component, NS, Module, Function, Type) ->
|
||||
case Type of
|
||||
no_queue ->
|
||||
Component:register_iq_handler(Host, NS, Module, Function, no_queue);
|
||||
Component:register_iq_handler(NS, Module, Function, no_queue);
|
||||
one_queue ->
|
||||
{ok, Pid} = supervisor:start_child(ejabberd_iq_sup,
|
||||
[Host, Module, Function]),
|
||||
Component:register_iq_handler(Host, NS, Module, Function,
|
||||
[Module, Function]),
|
||||
Component:register_iq_handler(NS, Module, Function,
|
||||
{one_queue, Pid});
|
||||
{queues, N} ->
|
||||
Pids =
|
||||
lists:map(
|
||||
fun(_) ->
|
||||
{ok, Pid} = supervisor:start_child(
|
||||
ejabberd_iq_sup,
|
||||
[Host, Module, Function]),
|
||||
Pid
|
||||
end, lists:seq(1, N)),
|
||||
Component:register_iq_handler(Host, NS, Module, Function,
|
||||
{queues, Pids});
|
||||
parallel ->
|
||||
Component:register_iq_handler(Host, NS, Module, Function, parallel)
|
||||
Component:register_iq_handler(NS, Module, Function, parallel)
|
||||
end.
|
||||
|
||||
remove_iq_handler(Component, Host, NS) ->
|
||||
Component:unregister_iq_handler(Host, NS).
|
||||
remove_iq_handler(Component, NS) ->
|
||||
Component:unregister_iq_handler(NS).
|
||||
|
||||
stop_iq_handler(_Module, _Function, Opts) ->
|
||||
stop_iq_handler(Module, Function, Opts) ->
|
||||
case Opts of
|
||||
{one_queue, Pid} ->
|
||||
gen_server:call(Pid, stop);
|
||||
{queues, Pids} ->
|
||||
lists:foreach(fun(Pid) ->
|
||||
catch gen_server:call(Pid, stop)
|
||||
end, Pids);
|
||||
exit(Pid, kill);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
handle(Host, Module, Function, Opts, From, To, IQ) ->
|
||||
handle(Module, Function, Opts, From, To, IQ) ->
|
||||
case Opts of
|
||||
no_queue ->
|
||||
process_iq(Host, Module, Function, From, To, IQ);
|
||||
process_iq(Module, Function, From, To, IQ);
|
||||
{one_queue, Pid} ->
|
||||
Pid ! {process_iq, From, To, IQ};
|
||||
{queues, Pids} ->
|
||||
Pid = lists:nth(erlang:phash(now(), length(Pids)), Pids),
|
||||
Pid ! {process_iq, From, To, IQ};
|
||||
parallel ->
|
||||
spawn(?MODULE, process_iq, [Host, Module, Function, From, To, IQ]);
|
||||
spawn(?MODULE, process_iq, [Module, Function, From, To, IQ]);
|
||||
_ ->
|
||||
todo
|
||||
end.
|
||||
|
||||
|
||||
process_iq(_Host, Module, Function, From, To, IQ) ->
|
||||
process_iq(Module, Function, From, To, IQ) ->
|
||||
case catch Module:Function(From, To, IQ) of
|
||||
{'EXIT', Reason} ->
|
||||
?ERROR_MSG("~p", [Reason]);
|
||||
@@ -109,76 +75,18 @@ process_iq(_Host, Module, Function, From, To, IQ) ->
|
||||
end
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
start_link(Module, Function) ->
|
||||
{ok, proc_lib:spawn_link(?MODULE, queue_init, [Module, Function])}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: init(Args) -> {ok, State} |
|
||||
%% {ok, State, Timeout} |
|
||||
%% ignore |
|
||||
%% {stop, Reason}
|
||||
%% Description: Initiates the server
|
||||
%%--------------------------------------------------------------------
|
||||
init([Host, Module, Function]) ->
|
||||
{ok, #state{host = Host,
|
||||
module = Module,
|
||||
function = Function}}.
|
||||
queue_init(Module, Function) ->
|
||||
queue_loop(Module, Function).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
%% {reply, Reply, State, Timeout} |
|
||||
%% {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, Reply, State} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling call messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_call(stop, _From, State) ->
|
||||
Reply = ok,
|
||||
{stop, normal, Reply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
||||
%% {noreply, State, Timeout} |
|
||||
%% {stop, Reason, State}
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({process_iq, From, To, IQ},
|
||||
#state{host = Host,
|
||||
module = Module,
|
||||
function = Function} = State) ->
|
||||
process_iq(Host, Module, Function, From, To, IQ),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: terminate(Reason, State) -> void()
|
||||
%% Description: This function is called by a gen_server when it is about to
|
||||
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
%% Description: Convert process state when code is changed
|
||||
%%--------------------------------------------------------------------
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
% TODO: use gen_event
|
||||
queue_loop(Module, Function) ->
|
||||
receive
|
||||
{process_iq, From, To, IQ} ->
|
||||
process_iq(Module, Function, From, To, IQ),
|
||||
queue_loop(Module, Function);
|
||||
_ ->
|
||||
queue_loop(Module, Function)
|
||||
end.
|
||||
|
||||