Create branch for ejabberd 2.1.x release line.

SVN Revision: 2688
This commit is contained in:
Badlop 2009-10-20 15:28:48 +00:00
commit 04545f2668
291 changed files with 91907 additions and 28177 deletions

5812
ChangeLog

File diff suppressed because it is too large Load Diff

18
README
View File

@ -8,16 +8,20 @@ Quickstart guide
To compile ejabberd you need:
- GNU Make
- GCC
- libexpat 1.95 or higher
- Erlang/OTP R10B-9 up to R11B-5. Erlang R12 releases are not yet
officially supported, and are not recommended for production
servers
- Libexpat 1.95 or higher
- Erlang/OTP R10B-9 or higher. The recommended version is R12B-5.
Support for R13 is experimental.
- OpenSSL 0.9.6 or higher, for STARTTLS, SASL and SSL
encryption. Optional, highly recommended.
- Zlib 1.2.3 or higher, for Stream Compression support
(XEP-0138). Optional.
- Erlang mysql library. Optional. MySQL authentication/storage.
- Erlang pgsql library. Optional. PostgreSQL authentication/storage.
- PAM library. Optional. For Pluggable Authentication Modules (PAM).
- GNU Iconv 1.8 or higher, for the IRC Transport
(mod_irc). Optional. Not needed on systems with GNU Libc.
- ImageMagicks Convert program. Optional. For CAPTCHA challenges.
- exmpp 0.9.1 or higher. Optional. For import/export XEP-0227 files.
1. Compile and install on *nix systems
@ -32,9 +36,11 @@ To install ejabberd, run this command with system administrator rights
sudo make install
These commands will:
- Install a startup script: /sbin/ejabberdctl
- Install ejabberd in /var/lib/ejabberd/
- Install the configuration files in /etc/ejabberd/
- Install ejabberd binary, header and runtime files in /lib/ejabberd/
- Install the administration script: /sbin/ejabberdctl
- Install ejabberd documentation in /share/doc/ejabberd/
- Create a spool directory: /var/lib/ejabberd/
- Create a directory for log files: /var/log/ejabberd/

View File

@ -1,5 +1,5 @@
% List of ejabberd-modules to add for ejabberd packaging (source archive and installer)
%
% HTTP-binding:
https://svn.process-one.net/ejabberd-modules/http_bind/tags/ejabberd-2.0.0-beta1
https://svn.process-one.net/ejabberd-modules/mod_http_fileserver/tags/ejabberd-2.0.0-beta1
%https://svn.process-one.net/ejabberd-modules/http_bind/trunk
%https://svn.process-one.net/ejabberd-modules/mod_http_fileserver/trunk

View File

@ -20,9 +20,14 @@
start() ->
ets:new(translations, [named_table, public]),
ets:new(translations_obsolete, [named_table, public]),
ets:new(files, [named_table, public]),
ets:new(vars, [named_table, public]),
case init:get_plain_arguments() of
["-srcmsg2po", Dir, File] ->
print_po_header(File),
Status = process(Dir, File, srcmsg2po),
halt(Status);
["-unused", Dir, File] ->
Status = process(Dir, File, unused),
halt(Status);
@ -50,7 +55,11 @@ process(Dir, File, Used) ->
unused ->
ets:foldl(fun({Key, _}, _) ->
io:format("~p~n", [Key])
end, ok, translations);
end, ok, translations);
srcmsg2po ->
ets:foldl(fun({Key, Trans}, _) ->
print_translation_obsolete(Key, Trans)
end, ok, translations_obsolete);
_ ->
ok
end,
@ -59,7 +68,7 @@ process(Dir, File, Used) ->
parse_file(Dir, File, Used) ->
ets:delete_all_objects(vars),
case epp:parse_file(File, [Dir, filename:dirname(File)], []) of
case epp:parse_file(File, [Dir, filename:dirname(File) | code:get_path()], []) of
{ok, Forms} ->
lists:foreach(
fun(F) ->
@ -71,49 +80,57 @@ parse_file(Dir, File, Used) ->
parse_form(Dir, File, Form, Used) ->
case Form of
%%{undefined, Something} ->
%% io:format("Undefined: ~p~n", [Something]);
{call,
_,
{remote, _, {atom, _, translate}, {atom, _, translate}},
[_, {string, _, Str}]
[_, {string, Line, Str}]
} ->
process_string(Dir, File, Str, Used);
process_string(Dir, File, Line, Str, Used);
{call,
_,
{remote, _, {atom, _, translate}, {atom, _, translate}},
[_, {var, _, Name}]
} ->
case ets:lookup(vars, Name) of
[{_Name, Value}] ->
process_string(Dir, File, Value, Used);
[{_Name, Value, Line}] ->
process_string(Dir, File, Line, Value, Used);
_ ->
ok
end;
{match,
_,
{var, _, Name},
{string, _, Value}
{string, Line, Value}
} ->
ets:insert(vars, {Name, Value});
ets:insert(vars, {Name, Value, Line});
L when is_list(L) ->
lists:foreach(
fun(F) ->
parse_form(Dir, File, F, Used)
parse_form(Dir, File, F, Used)
end, L);
T when is_tuple(T) ->
lists:foreach(
fun(F) ->
parse_form(Dir, File, F, Used)
parse_form(Dir, File, F, Used)
end, tuple_to_list(T));
_ ->
ok
end.
process_string(_Dir, File, Str, Used) ->
process_string(_Dir, _File, _Line, "", _Used) ->
ok;
process_string(_Dir, File, Line, Str, Used) ->
case {ets:lookup(translations, Str), Used} of
{[{_Key, _Trans}], unused} ->
ets:delete(translations, Str);
{[{_Key, _Trans}], used} ->
ok;
{[{_Key, Trans}], srcmsg2po} ->
ets:delete(translations_obsolete, Str),
print_translation(File, Line, Str, Trans);
{_, used} ->
case ets:lookup(files, File) of
[{_}] ->
@ -127,6 +144,15 @@ process_string(_Dir, File, Str, Used) ->
_ -> io:format("{~p, \"\"}.~n", [Str])
end,
ets:insert(translations, {Str, ""});
{_, srcmsg2po} ->
case ets:lookup(files, File) of
[{_}] ->
ok;
_ ->
ets:insert(files, {File})
end,
ets:insert(translations, {Str, ""}),
print_translation(File, Line, Str, "");
_ ->
ok
end.
@ -140,7 +166,8 @@ load_file(File) ->
"" ->
ok;
_ ->
ets:insert(translations, {Orig, Trans})
ets:insert(translations, {Orig, Trans}),
ets:insert(translations_obsolete, {Orig, Trans})
end
end, Terms);
Err ->
@ -191,3 +218,77 @@ print_usage() ->
" extract_translations . ./msgs/ru.msg~n"
).
%%%
%%% Gettext
%%%
print_po_header(File) ->
MsgProps = get_msg_header_props(File),
{Language, [LastT | AddT]} = prepare_props(MsgProps),
application:load(ejabberd),
{ok, Version} = application:get_key(ejabberd, vsn),
print_po_header(Version, Language, LastT, AddT).
get_msg_header_props(File) ->
{ok, F} = file:open(File, [read]),
Lines = get_msg_header_props(F, []),
file:close(F),
Lines.
get_msg_header_props(F, Lines) ->
String = io:get_line(F, ""),
case io_lib:fread("% ", String) of
{ok, [], RemString} ->
case io_lib:fread("~s", RemString) of
{ok, [Key], Value} when Value /= "\n" ->
%% The first character in Value is a blankspace:
%% And the last characters are 'slash n'
ValueClean = string:substr(Value, 2, string:len(Value)-2),
get_msg_header_props(F, Lines ++ [{Key, ValueClean}]);
_ ->
get_msg_header_props(F, Lines)
end;
_ ->
Lines
end.
prepare_props(MsgProps) ->
Language = proplists:get_value("Language:", MsgProps),
Authors = proplists:get_all_values("Author:", MsgProps),
{Language, Authors}.
print_po_header(Version, Language, LastTranslator, AdditionalTranslatorsList) ->
AdditionalTranslatorsString = build_additional_translators(AdditionalTranslatorsList),
HeaderString =
"msgid \"\"\n"
"msgstr \"\"\n"
"\"Project-Id-Version: " ++ Version ++ "\\n\"\n"
++ "\"X-Language: " ++ Language ++ "\\n\"\n"
"\"Last-Translator: " ++ LastTranslator ++ "\\n\"\n"
++ AdditionalTranslatorsString ++
"\"MIME-Version: 1.0\\n\"\n"
"\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
"\"Content-Transfer-Encoding: 8bit\\n\"\n",
io:format("~s~n", [HeaderString]).
build_additional_translators(List) ->
lists:foldl(
fun(T, Str) ->
Str ++ "\"X-Additional-Translator: " ++ T ++ "\\n\"\n"
end,
"",
List).
print_translation(File, Line, Str, StrT) ->
{ok, StrQ, _} = regexp:gsub(Str, "\"", "\\\""),
{ok, StrTQ, _} = regexp:gsub(StrT, "\"", "\\\""),
io:format("#: ~s:~p~nmsgid \"~s\"~nmsgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).
print_translation_obsolete(Str, StrT) ->
File = "unknown.erl",
Line = 1,
{ok, StrQ, _} = regexp:gsub(Str, "\"", "\\\""),
{ok, StrTQ, _} = regexp:gsub(StrT, "\"", "\\\""),
io:format("#: ~s:~p~n#~~ msgid \"~s\"~n#~~ msgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).

View File

@ -3,16 +3,24 @@
# Frontend for ejabberd's extract_translations.erl
# by Badlop
# How to create template files for a new language:
# NEWLANG=zh
# cp msgs/ejabberd.pot msgs/$NEWLANG.po
# echo \{\"\",\"\"\}. > msgs/$NEWLANG.msg
# ../../extract_translations/prepare-translation.sh -updateall
prepare_dirs ()
{
# Where is Erlang binary
ERL=`which erl`
EJA_DIR=`pwd`/../..
EJA_SRC_DIR=$EJA_DIR/src/
EJA_MSGS_DIR=$EJA_SRC_DIR/msgs/
EXTRACT_DIR=$EJA_DIR/contrib/extract_translations/
EXTRACT_ERL=extract_translations.erl
EXTRACT_BEAM=extract_translations.beam
SRC_DIR=$EJA_DIR/src
EXTRACT_ERL=$EXTRACT_DIR/extract_translations.erl
EXTRACT_BEAM=$EXTRACT_DIR/extract_translations.beam
SRC_DIR=$RUN_DIR/src
MSGS_DIR=$SRC_DIR/msgs
if !([[ -n $EJA_DIR ]])
@ -22,9 +30,7 @@ prepare_dirs ()
if !([[ -x $EXTRACT_BEAM ]])
then
echo -n "Compiling extract_translations.erl: "
sh -c "cd $EXTRACT_DIR; $ERL -compile $EXTRACT_ERL"
echo "ok"
fi
}
@ -76,14 +82,14 @@ extract_lang ()
extract_lang_all ()
{
cd $MSGS_DIR
for i in *.msg; do
for i in $( ls *.msg ) ; do
extract_lang $i;
done
echo -e "File\tMissing\tLanguage\t\tLast translator"
echo -e "----\t-------\t--------\t\t---------------"
cd $MSGS_DIR
for i in *.msg; do
for i in $( ls *.msg ) ; do
MISSING=`cat $i.translate | grep "\", \"\"}." | wc -l`
LANGUAGE=`grep "Language:" $i.translate | sed 's/% Language: //g'`
LASTAUTH=`grep "Author:" $i.translate | head -n 1 | sed 's/% Author: //g'`
@ -140,6 +146,130 @@ find_unused_full ()
cd ..
}
extract_lang_srcmsg2po ()
{
LANG=$1
LANG_CODE=$LANG.$PROJECT
MSGS_PATH=$MSGS_DIR/$LANG_CODE.msg
PO_PATH=$MSGS_DIR/$LANG_CODE.po
echo $MSGS_PATH
$ERL -pa $EXTRACT_DIR -pa $SRC_DIR -pa $EJA_SRC_DIR -pa /lib/ejabberd/include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$PO_PATH.1
sed -e 's/ \[\]$/ \"\"/g;' $PO_PATH.1 > $PO_PATH.2
msguniq --sort-by-file $PO_PATH.2 --output-file=$PO_PATH
rm $PO_PATH.*
}
extract_lang_src2pot ()
{
LANG_CODE=$PROJECT
MSGS_PATH=$MSGS_DIR/$LANG_CODE.msg
POT_PATH=$MSGS_DIR/$LANG_CODE.pot
echo -n "" >$MSGS_PATH
echo "% Language: Language Name" >>$MSGS_PATH
echo "% Author: Translator name and contact method" >>$MSGS_PATH
echo "" >>$MSGS_PATH
cd $SRC_DIR
$ERL -pa $EXTRACT_DIR -pa $SRC_DIR -pa $EJA_SRC_DIR -pa /lib/ejabberd/include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$POT_PATH.1
sed -e 's/ \[\]$/ \"\"/g;' $POT_PATH.1 > $POT_PATH.2
#msguniq --sort-by-file $POT_PATH.2 $EJA_MSGS_DIR --output-file=$POT_PATH
msguniq --sort-by-file $POT_PATH.2 --output-file=$POT_PATH
rm $POT_PATH.*
rm $MSGS_PATH
# If the project is a specific module, not the main ejabberd
if [[ $PROJECT != ejabberd ]] ; then
# Remove from project.pot the strings that are already present in the general ejabberd
EJABBERD_MSG_FILE=$EJA_MSGS_DIR/es.po # This is just some file with translated strings
POT_PATH_TEMP=$POT_PATH.temp
msgattrib --set-obsolete --only-file=$EJABBERD_MSG_FILE -o $POT_PATH_TEMP $POT_PATH
mv $POT_PATH_TEMP $POT_PATH
fi
}
extract_lang_popot2po ()
{
LANG_CODE=$1
PO_PATH=$MSGS_DIR/$LANG_CODE.po
POT_PATH=$MSGS_DIR/$PROJECT.pot
msgmerge $PO_PATH $POT_PATH >$PO_PATH.translate 2>/dev/null
mv $PO_PATH.translate $PO_PATH
}
extract_lang_po2msg ()
{
LANG_CODE=$1
PO_PATH=$LANG_CODE.po
MS_PATH=$PO_PATH.ms
MSGID_PATH=$PO_PATH.msgid
MSGSTR_PATH=$PO_PATH.msgstr
MSGS_PATH=$LANG_CODE.msg
cd $MSGS_DIR
# Check PO has correct ~
# Let's convert to C format so we can use msgfmt
PO_TEMP=$LANG_CODE.po.temp
cat $PO_PATH | sed 's/%/perc/g' | sed 's/~/%/g' | sed 's/#:.*/#, c-format/g' >$PO_TEMP
msgfmt $PO_TEMP --check-format
result=$?
rm $PO_TEMP
if [ $result -ne 0 ] ; then
exit 1
fi
msgattrib $PO_PATH --translated --no-fuzzy --no-obsolete --no-location --no-wrap | grep "^msg" | tail --lines=+3 >$MS_PATH
grep "^msgid" $PO_PATH.ms | sed 's/^msgid //g' >$MSGID_PATH
grep "^msgstr" $PO_PATH.ms | sed 's/^msgstr //g' >$MSGSTR_PATH
paste $MSGID_PATH $MSGSTR_PATH --delimiter=, | awk '{print "{" $0 "}."}' | sort -g >$MSGS_PATH
rm $MS_PATH
rm $MSGID_PATH
rm $MSGSTR_PATH
}
extract_lang_updateall ()
{
echo "Generating POT"
extract_lang_src2pot
cd $MSGS_DIR
echo ""
echo -e "File Missing Language Last translator"
echo -e "---- ------- -------- ---------------"
for i in $( ls *.msg ) ; do
LANG_CODE=${i%.msg}
echo -n $LANG_CODE | awk '{printf "%-6s", $1 }'
# Convert old MSG file to PO
PO=$LANG_CODE.po
[ -f $PO ] || extract_lang_srcmsg2po $LANG_CODE
extract_lang_popot2po $LANG_CODE
extract_lang_po2msg $LANG_CODE
MISSING=`msgfmt --statistics $PO 2>&1 | awk '{printf "%5s", $4 }'`
echo -n " $MISSING"
LANGUAGE=`grep "Language:" $PO | sed 's/\"X-Language: //g' | sed 's/\\\\n\"//g' | awk '{printf "%-12s", $1}'`
echo -n " $LANGUAGE"
LASTAUTH=`grep "Last-Translator" $PO | sed 's/\"Last-Translator: //g' | sed 's/\\\\n\"//g'`
echo " $LASTAUTH"
done
echo ""
rm messages.mo
cd ..
}
translation_instructions ()
{
echo ""
@ -158,34 +288,76 @@ translation_instructions ()
echo " $MSGS_PATH"
}
case "$1" in
-help)
echo "Options:"
echo " -langall"
echo " -lang LANGUAGE_FILE"
echo ""
echo "Example:"
echo " ./prepare-translation.sh -lang es.msg"
exit 0
EJA_DIR=`pwd`/..
RUN_DIR=`pwd`/..
PROJECT=ejabberd
while [ $# -ne 0 ] ; do
PARAM=$1
shift
case $PARAM in
--) break ;;
-project)
PROJECT=$1
shift
;;
-ejadir)
EJA_DIR=$1
shift
;;
-rundir)
RUN_DIR=$1
shift
;;
-lang)
LANGU=$2
LANGU=$1
prepare_dirs
extract_lang $LANGU
shift
shift
;;
-langall)
prepare_dirs
extract_lang_all
;;
-srcmsg2po)
LANG_CODE=$1
prepare_dirs
extract_lang_srcmsg2po $LANG_CODE
shift
;;
-popot2po)
LANG_CODE=$1
prepare_dirs
extract_lang_popot2po $LANG_CODE
shift
;;
-src2pot)
prepare_dirs
extract_lang_src2pot
;;
-po2msg)
LANG_CODE=$1
prepare_dirs
extract_lang_po2msg $LANG_CODE
shift
;;
-updateall)
prepare_dirs
extract_lang_updateall
;;
*)
echo "unknown option: '$1 $2'"
shift
shift
echo "Options:"
echo " -langall"
echo " -lang LANGUAGE_FILE"
echo " -srcmsg2po LANGUAGE Construct .msg file using source code to PO file"
echo " -src2pot Generate template POT file from source code"
echo " -popot2po LANGUAGE Update PO file with template POT file"
echo " -po2msg LANGUAGE Export PO file to MSG file"
echo " -updateall Generate POT and update all PO"
echo ""
echo "Example:"
echo " ./prepare-translation.sh -lang es.msg"
exit 0
;;
esac
echo ""
echo "End."
esac
done

View File

@ -3,12 +3,9 @@
SHELL = /bin/bash
CONTRIBUTED_MODULES = ""
ifeq ($(shell ls mod_http_bind.tex),mod_http_bind.tex)
CONTRIBUTED_MODULES += "\\n\\setboolean{modhttpbind}{true}"
endif
ifeq ($(shell ls mod_http_fileserver.tex),mod_http_fileserver.tex)
CONTRIBUTED_MODULES += "\\n\\setboolean{modhttpfileserver}{true}"
endif
#ifeq ($(shell ls mod_http_bind.tex),mod_http_bind.tex)
# CONTRIBUTED_MODULES += "\\n\\setboolean{modhttpbind}{true}"
#endif
all: release pdf html
@ -16,12 +13,12 @@ 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 update the version number in src/ejabberd.hrl!"
@echo "* Do not forget to update the version number in src/ejabberd.app!"
@echo "* Do not forget to update the features in introduction.tex (including \new{} and \improved{} tags)."
@echo "Press any key to continue"
@read foo
@echo "% ejabberd version (automatically generated)." > version.tex
@echo "\newcommand{\version}{"`sed '/VERSION/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../src/ejabberd.hrl`"}" >> version.tex
@echo "\newcommand{\version}{"`sed '/vsn/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../src/ejabberd.app`"}" >> version.tex
@echo -n "% Contributed modules (automatically generated)." > contributed_modules.tex
@echo -e "$(CONTRIBUTED_MODULES)" >> contributed_modules.tex
@ -32,7 +29,6 @@ pdf: guide.pdf features.pdf
clean:
rm -f *.aux
rm -f *.haux
rm -f *.html
rm -f *.htoc
rm -f *.idx
rm -f *.ilg
@ -41,6 +37,10 @@ clean:
rm -f *.out
rm -f *.pdf
rm -f *.toc
[ ! -f contributed_modules.tex ] || rm contributed_modules.tex
distclean: clean
rm -f *.html
guide.html: guide.tex
hevea -fix -pedantic guide.tex

View File

@ -1,5 +1,5 @@
APPNAME = ejabberd
VSN = $(shell sed '/VERSION/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../../src/ejabberd.hrl)
VSN = $(shell sed '/vsn/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../../src/ejabberd.app)
DOCDIR=.
SRCDIR=../../src

View File

@ -1,6 +1,6 @@
@author Mickael Remond <mickael.remond@process-one.net>
[http://www.process-one.net/]
@copyright 2007 Process-one
@copyright 2007 ProcessOne
@version {@vsn}, {@date} {@time}
@title ejabberd Development API Documentation

View File

@ -71,10 +71,22 @@ pre, tt, code {
}
pre {
margin-left: 1em;
margin:1ex 2ex;
border:1px dashed lightgrey;
background-color:#f9f9f9;
padding:0.5ex;
}
pre em {
font-style: normal;
font-weight: bold;
}
dt {
margin:0ex 2ex;
font-weight:bold;
}
dd {
margin:0ex 0ex 1ex 4ex;
}

View File

@ -2,7 +2,7 @@
"http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD>
<TITLE>Ejabberd 2.1.0-alpha Developers Guide
<TITLE>Ejabberd 2.1.0 Developers Guide
</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
@ -49,7 +49,7 @@ TD P{margin:0px;}
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic dev.tex -->
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.0-alpha Developers Guide</H1><H3 CLASS="titlerest">Alexey Shchepin<BR>
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.0 Developers Guide</H1><H3 CLASS="titlerest">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><DIV CLASS="center">
@ -84,7 +84,7 @@ Kevin Smith, Current maintainer of the Psi project</I></BLOCKQUOTE><!--TOC secti
</LI><LI CLASS="li-toc"><A HREF="#htoc15">8.2&#XA0;&#XA0;Services</A>
</LI></UL>
</LI></UL><P>Introduction
<A NAME="intro"></A></P><P><TT>ejabberd</TT> is a free and open source instant messaging server written in <A HREF="http://www.erlang.org/">Erlang</A>.</P><P><TT>ejabberd</TT> is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a rock-solid and feature rich XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be scalable or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
<A NAME="intro"></A></P><P><TT>ejabberd</TT> is a free and open source instant messaging server written in <A HREF="http://www.erlang.org/">Erlang/OTP</A>.</P><P><TT>ejabberd</TT> is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a rock-solid and feature rich XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be scalable or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc1">1</A>&#XA0;&#XA0;Key Features</H2><!--SEC END --><P>
<A NAME="keyfeatures"></A>
</P><P><TT>ejabberd</TT> is:
@ -98,7 +98,7 @@ Comprehensive documentation.
</LI><LI CLASS="li-itemize">Capability to send announce messages.
</LI></UL></LI><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 to 24 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
Translated to 25 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
</LI></UL></LI><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.
@ -139,14 +139,14 @@ Support for virtual hosting.
</LI><LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</A>).
</LI><LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-00163.html">Personal Eventing via Pubsub</A>.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
</LI><LI CLASS="li-itemize">IRC transport.
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
</LI></UL>
</LI></UL><!--TOC section How it Works-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc3">3</A>&#XA0;&#XA0;How it Works</H2><!--SEC END --><P>
<A NAME="howitworks"></A></P><P>A Jabber domain is served by one or more <TT>ejabberd</TT> nodes. These nodes can
<A NAME="howitworks"></A></P><P>A XMPP domain is served by one or more <TT>ejabberd</TT> 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
the same magic cookie (see Erlang/OTP documentation, in other words the file
@ -159,7 +159,7 @@ router;
</LI><LI CLASS="li-itemize">session manager;
</LI><LI CLASS="li-itemize">S2S manager;
</LI></UL><!--TOC subsection Router-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc4">3.1</A>&#XA0;&#XA0;Router</H3><!--SEC END --><P>This module is the main router of Jabber packets on each node. It routes
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc4">3.1</A>&#XA0;&#XA0;Router</H3><!--SEC END --><P>This module is the main router of XMPP packets on each node. It routes
them based on their destinations domains. It has two tables: local and global
routes. First, domain of packet destination searched in local table, and if it
found, then the packet is routed to appropriate process. If no, then it
@ -173,7 +173,7 @@ session manager, else it is processed depending on it&#X2019;s content.</P><!--T
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.</P><!--TOC subsection S2S Manager-->
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc7">3.4</A>&#XA0;&#XA0;S2S Manager</H3><!--SEC END --><P>This module routes packets to other Jabber servers. First, it checks if an
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc7">3.4</A>&#XA0;&#XA0;S2S Manager</H3><!--SEC END --><P>This module routes packets to other XMPP servers. First, it checks if an
open S2S connection from the domain of the packet source to the domain of
packet destination already exists. If it is open on another node, then it
routes the packet to S2S manager on that node, if it is open on this node, then

View File

@ -26,6 +26,7 @@
\newcommand{\ns}[1]{\texttt{#1}}
\newcommand{\ejabberd}{\texttt{ejabberd}}
\newcommand{\Jabber}{Jabber}
\newcommand{\XMPP}{XMPP}
%% Modules
\newcommand{\module}[1]{\texttt{#1}}
@ -100,7 +101,7 @@
\label{howitworks}
A \Jabber{} domain is served by one or more \ejabberd{} nodes. These nodes can
A \XMPP{} 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
the same magic cookie (see Erlang/OTP documentation, in other words the file
@ -121,7 +122,7 @@ Each \ejabberd{} node have following modules:
\subsection{Router}
This module is the main router of \Jabber{} packets on each node. It routes
This module is the main router of \XMPP{} packets on each node. It routes
them based on their destinations domains. It has two tables: local and global
routes. First, domain of packet destination searched in local table, and if it
found, then the packet is routed to appropriate process. If no, then it
@ -147,7 +148,7 @@ the packet is sent to session manager on that node.
\subsection{S2S Manager}
This module routes packets to other \Jabber{} servers. First, it checks if an
This module routes packets to other \XMPP{} servers. First, it checks if an
open S2S connection from the domain of the packet source to the domain of
packet destination already exists. If it is open on another node, then it
routes the packet to S2S manager on that node, if it is open on this node, then

View File

@ -2,7 +2,7 @@
"http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD>
<TITLE>Ejabberd 2.1.0-alpha Feature Sheet
<TITLE>Ejabberd 2.1.0 Feature Sheet
</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
@ -50,7 +50,7 @@ SPAN{width:20%; float:right; text-align:left; margin-left:auto;}
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic features.tex -->
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.0-alpha Feature Sheet</H1><H3 CLASS="titlerest">Sander Devrieze<BR>
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.0 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><DIV CLASS="center">
@ -61,7 +61,7 @@ SPAN{width:20%; float:right; text-align:left; margin-left:auto;}
</DIV><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I can thoroughly recommend ejabberd for ease of setup &#X2013;
Kevin Smith, Current maintainer of the Psi project</I></FONT></BLOCKQUOTE><P>Introduction
<A NAME="intro"></A></P><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. &#X2014;
Joeri</I></FONT></BLOCKQUOTE><P><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>.</P><P><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.</P><P><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.</P><P><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.</P><!--TOC section Key Features-->
Joeri</I></FONT></BLOCKQUOTE><P><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/OTP</A>.</P><P><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.</P><P><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.</P><P><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.</P><!--TOC section Key Features-->
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc1"></A>Key Features</H2><!--SEC END --><P>
<A NAME="keyfeatures"></A>
</P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>Erlang seems to be tailor-made for writing stable, robust servers. &#X2014;
@ -76,7 +76,7 @@ Comprehensive documentation.
</LI><LI CLASS="li-itemize">Capability to send announce messages.
</LI></UL></LI><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 to 24 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
Translated to 25 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
</LI></UL></LI><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.
@ -118,7 +118,7 @@ Support for virtual hosting.
</LI><LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</A>).
</LI><LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-00163.html">Personal Eventing via Pubsub</A>.
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
</LI><LI CLASS="li-itemize">IRC transport.
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.

View File

@ -70,7 +70,7 @@
%% Fancy header
\fancyhf{}
\pagestyle{fancy}
\rhead{\textcolor{ejblue}{The Expandable Jabber Daemon.}}
\rhead{\textcolor{ejblue}{The Expandable Jabber/XMPP Daemon.}}
\renewcommand{\headrule}{{\color{ejblue}%
\hrule width\headwidth height\headrulewidth \vskip-\headrulewidth}}
\lhead{\setlength{\unitlength}{-6mm}
@ -133,4 +133,4 @@
% "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
% * try it today: links to migration tutorials

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ 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 a \marking{free and open source} instant messaging server written in \footahref{http://www.erlang.org/}{Erlang/OTP}.
\ejabberd{} is \marking{cross-platform}, distributed, fault-tolerant, and based on open standards to achieve real-time communication.
@ -68,7 +68,7 @@ Peter Saint-Andr\'e, Executive Director of the Jabber Software Foundation}
\item \marking{Internationalized:} \ejabberd{} leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
\begin{itemize}
\item Translated to 24 languages. %%\improved{}
\item Translated to 25 languages. %%\improved{}
\item Support for \footahref{http://www.ietf.org/rfc/rfc3490.txt}{IDNA}.
\end{itemize}
@ -125,7 +125,7 @@ Moreover, \ejabberd{} comes with a wide range of other state-of-the-art features
\item IPv6 support both for c2s and s2s connections.
\item \txepref{0045}{Multi-User Chat} module with support for clustering and HTML logging. %%\improved{}
\item Users Directory based on users vCards.
\item \txepref{0060}{Publish-Subscribe} component with support for \txepref{00163}{Personal Eventing via Pubsub}.
\item \txepref{0060}{Publish-Subscribe} component with support for \txepref{0163}{Personal Eventing via Pubsub}.
\item Support for web clients: \txepref{0025}{HTTP Polling} and \txepref{0206}{HTTP Binding (BOSH)} services.
\item IRC transport.
\item Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.

View File

@ -0,0 +1,34 @@
Release Notes
ejabberd 2.0.2
28 August 2008
ejabberd 2.0.2 is the second bug fix release for ejabberd 2.0.x branch.
ejabberd 2.0.2 includes many bugfixes and a few improvements.
A complete list of changes can be retrieved from:
http://redir.process-one.net/ejabberd-2.0.2
The new code can be downloaded from ejabberd download page:
http://www.process-one.net/en/ejabberd/
Recent changes include:
- Anti-abuse feature: client blacklist support by IP.
- Guide: new section Securing ejabberd; improved usability.
- LDAP filter optimisation: ability to filter user in ejabberd and not LDAP.
- MUC improvements: room options to restrict visitors; broadcast reasons.
- Privacy rules: fix MySQL storage.
- Pub/Sub and PEP: many improvements in implementation and protocol compliance.
- Proxy65: send valid SOCKS5 reply (removed support for Psi < 0.10).
- Web server embedded: better support for HTTPS.
- Binary installers: SMP on Windows; don't remove config when uninstalling.
Bug reports
You can officially report bugs on ProcessOne support site:
http://support.process-one.net/
END

View File

@ -0,0 +1,35 @@
Release Notes
ejabberd 2.0.3
14 January 2009
ejabberd 2.0.3 is the third bugfix release for ejabberd 2.0.x branch.
ejabberd 2.0.3 includes several bugfixes and a few improvements.
A complete list of changes can be retrieved from:
http://redir.process-one.net/ejabberd-2.0.3
The new code can be downloaded from ejabberd download page:
http://www.process-one.net/en/ejabberd/
Recent changes include:
- Do not ask certificate for client (c2s)
- Check digest-uri in SASL digest authentication
- Use send timeout to avoid locking on gen_tcp:send
- Fix ejabberd reconnection to database
- HTTP-Bind: handle wrong order of packets
- MUC: Improve traffic regulation management
- PubSub: Several bugfixes and improvements for best coverage of XEP-0060 v1.12
- Shared Roster Groups: push immediately membership changes
- Rotate also sasl.log on "reopen-log" command
- Binary Windows installer: better detect "Error running Post Install Script"
Bug reports
You can officially report bugs on ProcessOne support site:
http://support.process-one.net/
END

View File

@ -0,0 +1,44 @@
Release Notes
ejabberd 2.0.4
ejabberd 2.0.4 is the fourth bugfix release for ejabberd 2.0.x branch.
ejabberd 2.0.4 includes several bugfixes.
A detailed list of changes can be retrieved from:
http://redir.process-one.net/ejabberd-2.0.4
The new code can be downloaded from ejabberd download page:
http://www.process-one.net/en/ejabberd/
The changes are:
- Ensure ID attribute in roster push is unique
- Authentication: Fix Anonymous auth when enabled with broken ODBC
- Authentication: Unquote correctly backslash in DIGEST-MD5 SASL responses
- Authentication: Cancel presence subscriptions on account deletion
- LDAP: Close a connection on tcp_error
- LDAP: Implemented queue for pending queries
- LDAP: On failure of LDAP connection, waiting is done on pending queue
- MUC: Owner of a password protected room must also provide the password
- MUC: Prevent XSS in MUC logs by linkifying only a few known protocols
- Privacy rules: Items are now processed in the specified order
- Privacy rules: Fix to correctly block subscription requests
- Proxy65: If ip option is not defined, take an IP address of a local hostname
- PubSub: Add roster subscription handling; send PEP events to all resources
- PubSub: Allow node creation without configure item
- PubSub: Requesting items on a node which exists, but empty returns an error
- PEP: Fix sending notifications to other domains and s2s
- S2S: Fix problem with encrypted connection to Gtalk and recent Openfire
- S2S: Workaround to get DNS SRV lookup to work on Windows machine
- Shared Roster Groups: Fix to not resend authorization request
- WebAdmin: Fix encryption problem for ejabberd_http after timeout
Bug reports
You can officially report bugs on ProcessOne support site:
http://support.process-one.net/
END

View File

@ -0,0 +1,33 @@
Release Notes
ejabberd 2.0.5
ejabberd 2.0.5 is the fifth bugfix release in ejabberd 2.0.x branch.
ejabberd 2.0.5 includes three bugfixes.
More details of those fixes can be retrieved from:
http://redir.process-one.net/ejabberd-2.0.5
The new code can be downloaded from ejabberd download page:
http://www.process-one.net/en/ejabberd/
The changes are:
- Fix two problems introduced in ejabberd 2.0.4: subscription request
produced many authorization requests with some clients and
transports; and subscription requests were not stored for later
delivery when receiver was offline.
- Fix warning in expat_erl.c about implicit declaration of x_fix_buff
- HTTP-Bind (BOSH): Fix a missing stream:error in the returned
remote-stream-error stanza
Bug reports
You can officially report bugs on ProcessOne support site:
http://support.process-one.net/
END

269
doc/release_notes_2.1.0.txt Normal file
View File

@ -0,0 +1,269 @@
Release Notes
ejabberd 2.1.0
ejabberd 2.1.0 is a major new version for ejabberd adding many
new features, performance and scalability improvements.
ejabberd 2.1.0 includes many new features, improvements and bug fixes.
A complete list of changes can be retrieved from:
http://redir.process-one.net/ejabberd-2.1.0
The new code can be downloaded from ejabberd download page:
http://www.process-one.net/en/ejabberd/
New features and improvements:
* Anti-abuse
- Captcha support (XEP-0158). The example script uses ImageMagick.
- New option: registration_timeout to limit registrations by time
- Use send timeout to avoid locking on gen_tcp:send
- mod_ip_blacklist: client blacklist support by IP
* API
- ejabberd_http provides Host, Port, Headers and Protocol in HTTP requests
- Export function to create MUC room
- New events: s2s_send_packet and s2s_receive_packet
- New event: webadmin_user_parse_query when POST in web admin user page
- Support distributed hooks over the cluster
* Authentification
- Extauth responses: log strange responses and add timeout
* Binary Installer
- Includes exmpp library to support import/export XML files
* Caps
- Remove useless caps tables entries
- mod_caps must handle correctly external contacts with several resources
- Complain if mod_caps disabled and mod_pubsub has PEP plugin enabled
* Clustering and Architecture
* Configuration
- Added option access_max_user_messages for mod_offline
- Added option backlog for ejabberd_listener to increase TCP backlog
- Added option define_macro and use_macro
- Added option include_config_file to include additional configuration files
- Added option max_fsm_queue
- Added option outgoing_s2s_options to define IP address families and timeout
- Added option registration_timeout to ejabberd.cfg.example
- Added option s2s_dns_options to define DNS timeout and retries
- Added option ERL_OPTIONS to ejabberdctl.cfg
- Added option FIREWALL_WINDOW to ejabberdctl.cfg
- Added option EJABBERD_PID_PATH to ejabberdctl.cfg
- Deleted option user_max_messages of mod_offline
- Check certfiles are readable on server start and listener start
- Config file management mix file reading and sanity check
- Include example PAM configuration file: ejabberd.pam
- New ejabberd listener: ejabberd_stun
- Support to bind the same port to multiple interfaces
- New syntax to specify the IP address and IPv6 in listeners
configuration. The old options {ip,{1,2,3,4}} and inet6 are
supported even if they aren't documented.
- New syntax to specify the network protocol: tcp or udp
- Report error at startup if a listener module isn't available
- Only listen in a port when actually ready to serve requests
- In default config, only local accounts can create rooms and PubSub nodes
* Core architecture
- More verbose error reporting for xml:element_to_string
- Deliver messages when first presence is Invisible
- Better log message when config file is not found
- Include original timestamp on delayed presences
* Crypto
- Do not ask certificate for client (c2s)
- SSL code remove from ejabberd in favor of TLS
- Support Zlib compression after STARTTLS encryption
- tls v1 client hello
* Documentation
- Document possible default MUC room options
- Document service_check_from in the Guide
- Document s2s_default_policy and s2s_host in the Guide
- new command and guide instructions to change node name in a Mnesia database
* ejabberd commands
- ejabberd commands: separate command definition and calling interface
- access_commands restricts who can execute what commands and arguments
- ejabberdctl script now displays help and categorization of commands
* HTTP Binding and HTTP Polling
- HTTP-Bind: module optimization and clean-up
- HTTP-Bind: allow configuration of max_inactivity timeout
- HTTP-Poll: turn session timeout into a config file parameter
* Jingle
- STUN server that facilitates the client-to-client negotiation process
* LDAP
- Faster reconnection to LDAP servers
- LDAP filter optimisation: Add ability to filter user in ejabberd and not LDAP
- LDAP differentiates failed auth and unavailable auth service
- Improve LDAP logging
- LDAPS support using TLS.
* Localization
- Use Gettext PO for translators, export to ejabberd MSG
- Support translation files for additional projects
- Most translations are updated to latest code
- New translation to Greek language
* Multi-User Chat (MUC)
- Allow admins to send messages to rooms
- Allow to store room description
- Captcha support in MUC: the admin of a room can configure it to
require participants to fill a captcha to join the room.
- Limit number of characters in Room ID, Name and Description
- Prevent unvoiced occupants from changing nick
- Support Result Set Management (XEP-0059) for listing rooms
- Support for decline of invitation to MUC room
- mod_muc_log options: plaintext format; filename with only room name
* Performance
- Run roster_get_jid_info only if privacy list has subscription or group item
- Significant PubSub performance improvements
* Publish-Subscribe
- Add nodetree filtering/authorization
- Add subscription option support for collection nodes
- Allow Multiple Subscriptions
- Check option of the nodetree instead of checking configuration
- Implement whitelist authorize and roster access model
- Implicit item deletion is not notified when deleting node
- Make PubSub x-data configuration form handles list value
- Make default node name convention XEP-compatible, document usage of hierarchy
- Send authorization update event (XEP-0060, 8.6)
- Support of collection node subscription options
- Support ODBC storage. Experimental, needs more testing.
* Relational databases:
- Added MSSQL 2000 and 2005
- Privacy rules storage in MySQL
- Implement reliable ODBC transaction nesting
* Source Package
- Default installation directories changed. Please see the upgrade notes below.
- Allow more environment variable overrides in ejabberdctl
- ChangeLog is not edited manually anymore; it's generated automatically.
- Install the ejabberd Guide
- Install the ejabberd include files
- New option for the 'configure' script: --enable-user which installs
ejabberd granting permission to manage it to a regular system user;
no need to use root account to.
- Only try to install epam if pam was enabled in configure script
- Spool, config and log dirs: owner writes, group reads, others do nothing.
- Provides an example ejabberd.init file
* S2S
- Option to define s2s outgoing behaviour: IPv4, IPv6 and timeout
- DNS timeout and retries, configurable with s2s_dns_options.
* Shared rosters
- When a member is added/removed to group, send roster upgrade to group members
* Users management
- When account is deleted, cancel presence subscription for all roster items
* XEP Support
- Added XEP-0059 Result Set Management (for listing rooms)
- Added XEP-0082 Date Time
- Added XEP-0085 Chat State Notifications
- Added XEP-0157 Contact Addresses for XMPP Services
- Added XEP-0158 CAPTCHA Forms (in MUC rooms)
- Added STUN server, for XEP-0176: Jingle ICE-UDP Transport Method
- Added XEP-0199 XMPP Ping
- Added XEP-0202 Entity Time
- Added XEP-0203 Delayed Delivery
- Added XEP-0227 Portable Import/Export Format for XMPP-IM Servers
- Added XEP-0237 Roster Versioning
* Web Admin
- Display the connection method of user sessions
- Cross link of ejabberd users in the list of users and rosters
- Improved the browsing menu: don't disappear when browsing a host or node
- Include Last-Modified HTTP header in responses to allow caching
- Make some Input areas multiline: options of listening ports and modules
- Support PUT and DELETE methods in ejabberd_http
- WebAdmin serves Guide and links to related sections
* Web plugins
- mod_http_fileserver: new option directory_indices, and improve logging
Important Notes:
- ejabberd 2.1.0 requires Erlang R10B-9 or higher.
R12B-5 is the recommended version. Support for R13B is experimental.
Upgrading From ejabberd 1.x.x:
- Check the Release Notes of the intermediate versions for additional
information about database or configuration changes.
Upgrading From ejabberd 2.0.x:
- The database schemas didn't change since ejabberd 1.1.4.
Anyway, it is recommended to backup the Mnesia spool directory and
your SQL database (if used) before upgrading ejabberd.
- The plugin of mod_pubsub "default" is renamed to "flat". You need
to edit the ejabberd configuration file and replace those names.
- The listener options 'ip' and inet6' are not documented anymore
but they are supported and you can continue using them.
There is a new syntax to define IP address and IP version.
As usual, check the ejabberd Guide for more information.
- The log file sasl.log is now called erlang.log
- ejabberdctl commands now have _ characters instead of -.
For backwards compatibility, it is still supported -.
- mod_offline has a new option: access_max_user_messages.
The old option user_max_messages is no longer supported.
- If you upgrade from ejabberd trunk SVN, you must execute this:
$ ejabberdctl rename_default_nodeplugin
- Default installation directories changed a bit:
* The Mnesia spool files that were previously stored in
/var/lib/ejabberd/db/NODENAME/*
are now stored in
/var/lib/ejabberd/*
* The directories
/var/lib/ejabberd/ebin
/var/lib/ejabberd/priv
and their content is now installed as
/lib/ejabberd/ebin
/lib/ejabberd/priv
* There is a new directory with Erlang header files:
/lib/ejabberd/include
* There is a new directory for ejabberd documentation,
which includes the Admin Guide and the release notes::
/share/doc/ejabberd
- How to upgrade from previous version to ejabberd 2.1.0:
1. Stop the old instance of ejabberd.
2. Run 'make install' of new ejabberd 2.1.0 to create the new directories.
3. Copy the content of your old directory:
/var/lib/ejabberd/db/NODENAME/
to the new location:
/var/lib/ejabberd/
so you will have the files like this:
/var/lib/ejabberd/acl.DCD ...
4. You can backup the content of those directories and delete them:
/var/lib/ejabberd/ebin
/var/lib/ejabberd/priv
/var/lib/ejabberd/db
5. Now try to start your new ejabberd 2.1.0.
Bug reports
You can officially report bugs on ProcessOne support site:
http://support.process-one.net/

View File

@ -1,2 +1,2 @@
% ejabberd version (automatically generated).
\newcommand{\version}{2.1.0-alpha}
\newcommand{\version}{2.1.0}

View File

@ -12,43 +12,60 @@ ERLANG_CFLAGS= @ERLANG_CFLAGS@
EXPAT_LIBS = @EXPAT_LIBS@
ERLANG_LIBS = @ERLANG_LIBS@
ERLC_FLAGS += @ERLANG_SSL39@
ASN_FLAGS = -bber_bin +der +compact_bit_string +optimize +noobj
INSTALLUSER=@INSTALLUSER@
# if no user was enabled, don't set privileges or ownership
ifeq ($(INSTALLUSER),)
O_USER=
G_USER=
CHOWN_COMMAND=echo
CHOWN_OUTPUT=/dev/null
INIT_USER=root
else
O_USER=-o $(INSTALLUSER)
G_USER=-g $(INSTALLUSER)
CHOWN_COMMAND=chown
CHOWN_OUTPUT=&1
INIT_USER=$(INSTALLUSER)
endif
EFLAGS += @ERLANG_SSL39@ -pa .
# make debug=true to compile Erlang module with debug informations.
ifdef debug
ERLC_FLAGS+=+debug_info
EFLAGS+=+debug_info
endif
ifdef ejabberd_debug
ERLC_FLAGS+=-Dejabberd_debug
EFLAGS+=-Dejabberd_debug
endif
ifeq (@hipe@, true)
ERLC_FLAGS+=+native
EFLAGS+=+native
endif
ifeq (@roster_gateway_workaround@, true)
ERLC_FLAGS+=-DROSTER_GATEWAY_WORKAROUND
EFLAGS+=-DROSTER_GATEWAY_WORKAROUND
endif
ifeq (@full_xml@, true)
ERLC_FLAGS+=-DFULL_XML_SUPPORT
EFLAGS+=-DFULL_XML_SUPPORT
endif
ifeq (@transient_supervisors@, false)
ERLC_FLAGS+=-DNO_TRANSIENT_SUPERVISORS
EFLAGS+=-DNO_TRANSIENT_SUPERVISORS
endif
INSTALL_EPAM=
ifeq (@pam@, pam)
INSTALL_EPAM=install -m 750 epam $(PBINDIR)
INSTALL_EPAM=install -m 750 $(O_USER) epam $(PBINDIR)
endif
prefix = @prefix@
exec_prefix = @exec_prefix@
SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ stringprep @tls@ @odbc@ @ejabberd_zlib@
SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @mod_proxy65@ @eldap@ @pam@ @web@ stringprep stun @tls@ @odbc@ @ejabberd_zlib@
ERLSHLIBS = expat_erl.so
ERLBEHAVS = cyrsasl.erl gen_mod.erl p1_fsm.erl
SOURCES_ALL = $(wildcard *.erl)
@ -58,21 +75,54 @@ BEAMS = $(SOURCES:.erl=.beam)
DESTDIR =
EJABBERDDIR = $(DESTDIR)@localstatedir@/lib/ejabberd
BEAMDIR = $(EJABBERDDIR)/ebin
PRIVDIR = $(EJABBERDDIR)/priv
SODIR = $(PRIVDIR)/lib
PBINDIR = $(PRIVDIR)/bin
MSGSDIR = $(PRIVDIR)/msgs
LOGDIR = $(DESTDIR)@localstatedir@/log/ejabberd
# /etc/ejabberd/
ETCDIR = $(DESTDIR)@sysconfdir@/ejabberd
# /sbin/
SBINDIR = $(DESTDIR)@sbindir@
ifeq ($(shell uname),Darwin)
DYNAMIC_LIB_CFLAGS = -fPIC -bundle -flat_namespace -undefined suppress
else
# /lib/ejabberd/
EJABBERDDIR = $(DESTDIR)@libdir@/ejabberd
# /share/doc/ejabberd
PACKAGE_TARNAME = @PACKAGE_TARNAME@
datarootdir = @datarootdir@
DOCDIR = $(DESTDIR)@docdir@
# /usr/lib/ejabberd/ebin/
BEAMDIR = $(EJABBERDDIR)/ebin
# /usr/lib/ejabberd/include/
INCLUDEDIR = $(EJABBERDDIR)/include
# /usr/lib/ejabberd/priv/
PRIVDIR = $(EJABBERDDIR)/priv
# /usr/lib/ejabberd/priv/bin
PBINDIR = $(PRIVDIR)/bin
# /usr/lib/ejabberd/priv/lib
SODIR = $(PRIVDIR)/lib
# /usr/lib/ejabberd/priv/msgs
MSGSDIR = $(PRIVDIR)/msgs
# /var/lib/ejabberd/
SPOOLDIR = $(DESTDIR)@localstatedir@/lib/ejabberd
# /var/lib/ejabberd/.erlang.cookie
COOKIEFILE = $(SPOOLDIR)/.erlang.cookie
# /var/log/ejabberd/
LOGDIR = $(DESTDIR)@localstatedir@/log/ejabberd
# Assume Linux-style dynamic library flags
DYNAMIC_LIB_CFLAGS = -fpic -shared
ifeq ($(shell uname),Darwin)
DYNAMIC_LIB_CFLAGS = -fPIC -bundle -flat_namespace -undefined suppress
endif
ifeq ($(shell uname),SunOs)
DYNAMIC_LIB_CFLAGS = -KPIC -G -z text
endif
all: $(ERLSHLIBS) compile-beam all-recursive
@ -84,7 +134,7 @@ $(BEAMS): $(ERLBEHAVBEAMS)
all-recursive: $(ERLBEHAVBEAMS)
%.beam: %.erl
@ERLC@ -W $(ERLC_FLAGS) $<
@ERLC@ -W $(EFLAGS) $<
all-recursive install-recursive uninstall-recursive \
@ -99,59 +149,135 @@ mostlyclean-recursive maintainer-clean-recursive:
%.hrl: %.asn1
@ERLC@ $(ASN_FLAGS) $<
@ERLC@ -W $(ERLC_FLAGS) $*.erl
@ERLC@ -W $(EFLAGS) $*.erl
$(ERLSHLIBS): %.so: %.c
$(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) \
$(subst ../,,$(subst .so,.c,$@)) \
$(EXPAT_LIBS) $(EXPAT_CFLAGS) \
$(ERLANG_LIBS) $(ERLANG_CFLAGS) \
-o $@ $(DYNAMIC_LIB_CFLAGS)
$(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) \
$(subst ../,,$(subst .so,.c,$@)) \
$(EXPAT_LIBS) \
$(EXPAT_CFLAGS) \
$(ERLANG_LIBS) \
$(ERLANG_CFLAGS) \
-o $@ \
$(DYNAMIC_LIB_CFLAGS)
translations:
../contrib/extract_translations/prepare-translation.sh -updateall
install: all
#
# Configuration files
install -d -m 750 $(G_USER) $(ETCDIR)
[ -f $(ETCDIR)/ejabberd.cfg ] \
&& install -b -m 640 $(G_USER) ejabberd.cfg.example $(ETCDIR)/ejabberd.cfg-new \
|| install -b -m 640 $(G_USER) ejabberd.cfg.example $(ETCDIR)/ejabberd.cfg
sed -e "s*@rootdir@*@prefix@*" \
-e "s*@installuser@*@INSTALLUSER@*" \
-e "s*@LIBDIR@*@libdir@*" \
-e "s*@SYSCONFDIR@*@sysconfdir@*" \
-e "s*@LOCALSTATEDIR@*@localstatedir@*" \
-e "s*@DOCDIR@*@docdir@*" \
-e "s*@erl@*@ERL@*" ejabberdctl.template \
> ejabberdctl.example
[ -f $(ETCDIR)/ejabberdctl.cfg ] \
&& install -b -m 640 $(G_USER) ejabberdctl.cfg.example $(ETCDIR)/ejabberdctl.cfg-new \
|| install -b -m 640 $(G_USER) ejabberdctl.cfg.example $(ETCDIR)/ejabberdctl.cfg
install -b -m 644 $(G_USER) inetrc $(ETCDIR)/inetrc
#
# Administration script
[ -d $(SBINDIR) ] || install -d -m 755 $(SBINDIR)
install -m 550 $(G_USER) ejabberdctl.example $(SBINDIR)/ejabberdctl
#
# Init script
sed -e "s*@ctlscriptpath@*$(SBINDIR)*" \
-e "s*@installuser@*$(INIT_USER)*" ejabberd.init.template \
> ejabberd.init
#
# Binary Erlang files
install -d $(BEAMDIR)
install -m 644 *.app $(BEAMDIR)
install -m 644 *.beam $(BEAMDIR)
rm -f $(BEAMDIR)/configure.beam
install -m 644 *.app $(BEAMDIR)
install -d $(SODIR)
#
# ejabberd header files
install -d $(INCLUDEDIR)
install -m 644 *.hrl $(INCLUDEDIR)
install -d $(INCLUDEDIR)/eldap/
install -m 644 eldap/*.hrl $(INCLUDEDIR)/eldap/
install -d $(INCLUDEDIR)/mod_muc/
install -m 644 mod_muc/*.hrl $(INCLUDEDIR)/mod_muc/
install -d $(INCLUDEDIR)/mod_proxy65/
install -m 644 mod_proxy65/*.hrl $(INCLUDEDIR)/mod_proxy65/
install -d $(INCLUDEDIR)/mod_pubsub/
install -m 644 mod_pubsub/*.hrl $(INCLUDEDIR)/mod_pubsub/
install -d $(INCLUDEDIR)/web/
install -m 644 web/*.hrl $(INCLUDEDIR)/web/
#
# Binary C programs
install -d $(PBINDIR)
install -m 644 *.so $(SODIR)
install -m 750 $(O_USER) ../tools/captcha.sh $(PBINDIR)
$(INSTALL_EPAM)
#
# Binary system libraries
install -d $(SODIR)
install -m 644 *.so $(SODIR)
#
# Translated strings
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
sed -e "s*@rootdir@*@prefix@*" ejabberdctl.template > ejabberdctl.example
[ -f $(ETCDIR)/ejabberdctl.cfg ] && install -b -m 644 ejabberdctl.cfg.example $(ETCDIR)/ejabberdctl.cfg-new || install -b -m 644 ejabberdctl.cfg.example $(ETCDIR)/ejabberdctl.cfg
install -b -m 644 inetrc $(ETCDIR)/inetrc
install -d $(SBINDIR)
install -m 755 ejabberdctl.example $(SBINDIR)/ejabberdctl
install -d $(LOGDIR)
#
# Spool directory
install -d -m 750 $(O_USER) $(SPOOLDIR)
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(SPOOLDIR) >$(CHOWN_OUTPUT)
chmod -R 750 $(SPOOLDIR)
[ ! -f $(COOKIEFILE) ] || { $(CHOWN_COMMAND) @INSTALLUSER@ $(COOKIEFILE) >$(CHOWN_OUTPUT) ; chmod 400 $(COOKIEFILE) ; }
#
# Log directory
install -d -m 750 $(O_USER) $(LOGDIR)
$(CHOWN_COMMAND) -R @INSTALLUSER@ $(LOGDIR) >$(CHOWN_OUTPUT)
chmod -R 750 $(LOGDIR)
#
# Documentation
install -d $(DOCDIR)
install ../doc/guide.html $(DOCDIR)
install ../doc/*.png $(DOCDIR)
install ../doc/*.txt $(DOCDIR)
uninstall: uninstall-binary
uninstall-binary:
rm -rf $(BEAMDIR)
rm -rf $(SODIR)
rm -rf $(MSGSDIR)
rm -rf $(PRIVDIR)
rm -rf $(SBINDIR)/ejabberdctl
rm -f $(SBINDIR)/ejabberdctl
rm -fr $(DOCDIR)
rm -f $(BEAMDIR)/*.beam
rm -f $(BEAMDIR)/*.app
rm -fr $(BEAMDIR)
rm -f $(INCLUDEDIR)/*.hrl
rm -fr $(INCLUDEDIR)
rm -fr $(PBINDIR)
rm -f $(SODIR)/*.so
rm -fr $(SODIR)
rm -f $(MSGSDIR)/*.msgs
rm -fr $(MSGSDIR)
rm -fr $(PRIVDIR)
rm -fr $(EJABBERDDIR)
uninstall-all: uninstall-binary
rm -rf $(ETCDIR)
rm -rf $(LOGDIR)
rm -rf $(EJABBERDDIR)
rm -rf $(SPOOLDIR)
rm -rf $(LOGDIR)
clean: clean-recursive clean-local
clean-local:
rm -f *.beam $(ERLSHLIBS) epam
rm -f *.beam $(ERLSHLIBS) epam ejabberdctl.example
rm -f XmppAddr.asn1db XmppAddr.erl XmppAddr.hrl
distclean: distclean-recursive clean-local
rm -f config.status
rm -f config.log
rm -f Makefile
[ ! -f ../ChangeLog ] || rm -f ../ChangeLog
TAGS:
etags *.erl
@ -160,3 +286,12 @@ Makefile: Makefile.in
dialyzer: $(BEAMS)
@dialyzer -c .
LASTSVNREVCHANGELOG = 2075
changelog:
svn up -r $(LASTSVNREVCHANGELOG) ../ChangeLog
mv ../ChangeLog ../ChangeLog.old
svn2cl -r BASE:$(LASTSVNREVCHANGELOG) -o ../ChangeLog --group-by-day \
--separate-daylogs --break-before-msg --reparagraph ..
cat ../ChangeLog.old >> ../ChangeLog
rm ../ChangeLog.old

View File

@ -66,6 +66,9 @@ 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)\stun
copy stun\*.erl $(SRC_DIR)\stun
copy stun\*.hrl $(SRC_DIR)\stun
mkdir $(SRC_DIR)\tls
copy tls\*.erl $(SRC_DIR)\tls
copy tls\*.c $(SRC_DIR)\tls
@ -101,6 +104,8 @@ all-recursive :
nmake -nologo -f Makefile.win32
cd ..\stringprep
nmake -nologo -f Makefile.win32
cd ..\stun
nmake -nologo -f Makefile.win32
cd ..\tls
nmake -nologo -f Makefile.win32
cd ..\ejabberd_zlib
@ -142,6 +147,8 @@ clean-recursive :
nmake -nologo -f Makefile.win32 clean
cd ..\stringprep
nmake -nologo -f Makefile.win32 clean
cd ..\stun
nmake -nologo -f Makefile.win32 clean
cd ..\tls
nmake -nologo -f Makefile.win32 clean
cd ..\ejabberd_zlib
@ -166,5 +173,4 @@ $(DLL) : $(OBJECT)
$(LD) $(LD_FLAGS) -out:$(DLL) $(OBJECT)
$(OBJECT) : $(SOURCE)
$(CC) $(CC_FLAGS) -c -Fo$(OBJECT) $(SOURCE)
$(CC) $(CC_FLAGS) -c -Fo$(OBJECT) $(SOURCE)

View File

@ -5,7 +5,7 @@
%%% Created : 18 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -158,7 +158,7 @@ match_acl(ACL, JID, Host) ->
all -> true;
none -> false;
_ ->
{User, Server, _Resource} = jlib:jid_tolower(JID),
{User, Server, Resource} = jlib:jid_tolower(JID),
lists:any(fun(#acl{aclspec = Spec}) ->
case Spec of
all ->
@ -173,16 +173,24 @@ match_acl(ACL, JID, Host) ->
(U == User) andalso (S == Server);
{server, S} ->
S == Server;
{resource, R} ->
R == Resource;
{user_regexp, UR} ->
((Host == Server) orelse
((Host == global) andalso
lists:member(Server, ?MYHOSTS)))
andalso is_regexp_match(User, UR);
{shared_group, G} ->
mod_shared_roster:is_user_in_group({User, Server}, G, Host);
{shared_group, G, H} ->
mod_shared_roster:is_user_in_group({User, Server}, G, H);
{user_regexp, UR, S} ->
(S == Server) andalso
is_regexp_match(User, UR);
{server_regexp, SR} ->
is_regexp_match(Server, SR);
{resource_regexp, RR} ->
is_regexp_match(Resource, RR);
{node_regexp, UR, SR} ->
is_regexp_match(Server, SR) andalso
is_regexp_match(User, UR);
@ -197,6 +205,8 @@ match_acl(ACL, JID, Host) ->
is_glob_match(User, UR);
{server_glob, SR} ->
is_glob_match(Server, SR);
{resource_glob, RR} ->
is_glob_match(Resource, RR);
{node_glob, UR, SR} ->
is_glob_match(Server, SR) andalso
is_glob_match(User, UR);

32
src/aclocal.m4 vendored
View File

@ -1,6 +1,6 @@
AC_DEFUN(AM_WITH_EXPAT,
[ AC_ARG_WITH(expat,
[ --with-expat=PREFIX prefix where EXPAT is installed])
[AC_HELP_STRING([--with-expat=PREFIX], [prefix where EXPAT is installed])])
EXPAT_CFLAGS=
EXPAT_LIBS=
@ -15,7 +15,7 @@ AC_DEFUN(AM_WITH_EXPAT,
[ expat_found=no ],
"$EXPAT_LIBS")
if test $expat_found = no; then
AC_MSG_ERROR([Could not find the Expat library])
AC_MSG_ERROR([Could not find development files of Expat library])
fi
expat_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $EXPAT_CFLAGS"
@ -34,8 +34,9 @@ AC_DEFUN(AM_WITH_EXPAT,
AC_DEFUN(AM_WITH_ZLIB,
[ AC_ARG_WITH(zlib,
[ --with-zlib=PREFIX prefix where zlib is installed])
[AC_HELP_STRING([--with-zlib=PREFIX], [prefix where zlib is installed])])
if test x"$ejabberd_zlib" != x; then
ZLIB_CFLAGS=
ZLIB_LIBS=
if test x"$with_zlib" != x; then
@ -49,7 +50,7 @@ AC_DEFUN(AM_WITH_ZLIB,
[ zlib_found=no ],
"$ZLIB_LIBS")
if test $zlib_found = no; then
AC_MSG_ERROR([Could not find the zlib library])
AC_MSG_ERROR([Could not find development files of zlib library. Install them or disable `ejabberd_zlib' with: --disable-ejabberd_zlib])
fi
zlib_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $ZLIB_CFLAGS"
@ -57,19 +58,20 @@ AC_DEFUN(AM_WITH_ZLIB,
CPPFLAGS="$CPPFLAGS $ZLIB_CFLAGS"
AC_CHECK_HEADERS(zlib.h, , zlib_found=no)
if test $zlib_found = no; then
AC_MSG_ERROR([Could not find zlib.h])
AC_MSG_ERROR([Could not find zlib.h. Install it or disable `ejabberd_zlib' with: --disable-ejabberd_zlib])
fi
CFLAGS="$zlib_save_CFLAGS"
CPPFLAGS="$zlib_save_CPPFLAGS"
AC_SUBST(ZLIB_CFLAGS)
AC_SUBST(ZLIB_LIBS)
fi
])
AC_DEFUN(AM_WITH_PAM,
[ AC_ARG_WITH(pam,
[ --with-pam=PREFIX prefix where PAM is installed])
[AC_HELP_STRING([--with-pam=PREFIX], [prefix where PAM is installed])])
if test x"$pam" != x; then
PAM_CFLAGS=
PAM_LIBS=
if test x"$with_pam" != x; then
@ -83,7 +85,7 @@ AC_DEFUN(AM_WITH_PAM,
[ pam_found=no ],
"$PAM_LIBS")
if test $pam_found = no; then
AC_MSG_WARN([Could not find the PAM library])
AC_MSG_ERROR([Could not find development files of PAM library. Install them or disable `pam' with: --disable-pam])
fi
pam_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $PAM_CFLAGS"
@ -91,18 +93,19 @@ AC_DEFUN(AM_WITH_PAM,
CPPFLAGS="$CPPFLAGS $PAM_CFLAGS"
AC_CHECK_HEADERS(security/pam_appl.h, , pam_found=no)
if test $pam_found = no; then
AC_MSG_WARN([Could not find security/pam_appl.h])
AC_MSG_ERROR([Could not find security/pam_appl.h. Install it or disable `pam' with: --disable-pam])
fi
CFLAGS="$pam_save_CFLAGS"
CPPFLAGS="$pam_save_CPPFLAGS"
AC_SUBST(PAM_CFLAGS)
AC_SUBST(PAM_LIBS)
fi
])
AC_DEFUN(AM_WITH_ERLANG,
[ AC_ARG_WITH(erlang,
[ --with-erlang=PREFIX path to erlc and erl ])
[AC_HELP_STRING([--with-erlang=PREFIX], [path to erlc and erl])])
AC_PATH_TOOL(ERLC, erlc, , $with_erlang:$with_erlang/bin:$PATH)
AC_PATH_TOOL(ERL, erl, , $with_erlang:$with_erlang/bin:$PATH)
@ -195,14 +198,13 @@ _EOF
AC_SUBST(ERL)
])
AC_DEFUN(AC_MOD_ENABLE,
[
$1=
make_$1=
AC_MSG_CHECKING([whether build $1])
AC_ARG_ENABLE($1,
[ --enable-$1 enable $1 (default: $2)],
[AC_HELP_STRING([--enable-$1], [enable $1 (default: $2)])],
[mr_enable_$1="$enableval"],
[mr_enable_$1=$2])
if test "$mr_enable_$1" = "yes"; then
@ -223,7 +225,7 @@ 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], [
[AC_HELP_STRING([--with-libiconv-prefix=PREFIX], [prefix where libiconv is installed])], [
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
@ -308,7 +310,7 @@ size_t iconv();
dnl <openssl>
AC_DEFUN(AM_WITH_OPENSSL,
[ AC_ARG_WITH(openssl,
[ --with-openssl=PREFIX prefix where OPENSSL is installed ])
[AC_HELP_STRING([--with-openssl=PREFIX], [prefix where OPENSSL is installed])])
unset SSL_LIBS;
unset SSL_CFLAGS;
have_openssl=no
@ -338,7 +340,7 @@ if test x"$tls" != x; then
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).])
AC_MSG_ERROR([Could not find development files of OpenSSL library. Install them or disable `tls' with: --disable-tls])
fi
AC_SUBST(SSL_LIBS)
AC_SUBST(SSL_CFLAGS)

View File

@ -5,7 +5,7 @@
%%% Created : 31 Oct 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA

View File

@ -1,6 +1,6 @@
%%%----------------------------------------------------------------------
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -11,7 +11,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA

1533
src/config.guess vendored Normal file

File diff suppressed because it is too large Load Diff

1693
src/config.sub vendored Normal file

File diff suppressed because it is too large Load Diff

6036
src/configure vendored

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.53)
AC_INIT(ejabberd.erl,, ejabberd@process-one.net)
AC_INIT(ejabberd.erl, version, [ejabberd@process-one.net], [ejabberd])
# Checks for programs.
AC_PROG_CC
@ -18,14 +18,13 @@ AM_WITH_ERLANG
AM_ICONV
#locating libexpat
AM_WITH_EXPAT
#locating zlib
AM_WITH_ZLIB
#locating PAM
AM_WITH_PAM
# Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST
# Check Erlang headers are installed
#AC_CHECK_HEADER(erl_driver.h,,[AC_MSG_ERROR([cannot find Erlang header files])])
# Change default prefix
AC_PREFIX_DEFAULT(/)
@ -33,19 +32,25 @@ AC_PREFIX_DEFAULT(/)
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(mod_proxy65, yes)
AC_MOD_ENABLE(mod_pubsub, yes)
AC_MOD_ENABLE(eldap, yes)
AC_MOD_ENABLE(pam, no)
AC_MOD_ENABLE(web, yes)
AC_MOD_ENABLE(tls, yes)
AC_MOD_ENABLE(odbc, no)
AC_MOD_ENABLE(tls, yes)
AC_MOD_ENABLE(web, yes)
AC_MOD_ENABLE(ejabberd_zlib, yes)
#locating zlib
AM_WITH_ZLIB
AC_MOD_ENABLE(pam, no)
#locating PAM
AM_WITH_PAM
AC_ARG_ENABLE(hipe,
[ --enable-hipe Compile natively with HiPE, not recommended (default: no)],
[AC_HELP_STRING([--enable-hipe], [compile natively with HiPE, not recommended (default: no)])],
[case "${enableval}" in
yes) hipe=true ;;
no) hipe=false ;;
@ -54,7 +59,7 @@ esac],[hipe=false])
AC_SUBST(hipe)
AC_ARG_ENABLE(roster_gateway_workaround,
[ --enable-roster-gateway-workaround Turn on workaround for processing gateway subscriptions (default: no)],
[AC_HELP_STRING([--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 ;;
@ -63,7 +68,7 @@ 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)],
[AC_HELP_STRING([--enable-mssql], [use Microsoft SQL Server database (default: no, requires --enable-odbc)])],
[case "${enableval}" in
yes) db_type=mssql ;;
no) db_type=generic ;;
@ -72,7 +77,7 @@ esac],[db_type=generic])
AC_SUBST(db_type)
AC_ARG_ENABLE(transient_supervisors,
[ --enable-transient_supervisors Use Erlang supervision for transient process (default: yes)],
[AC_HELP_STRING([--enable-transient_supervisors], [use Erlang supervision for transient process (default: yes)])],
[case "${enableval}" in
yes) transient_supervisors=true ;;
no) transient_supervisors=false ;;
@ -81,7 +86,7 @@ esac],[transient_supervisors=true])
AC_SUBST(transient_supervisors)
AC_ARG_ENABLE(full_xml,
[ --enable-full-xml Use XML features in XMPP stream (ex: CDATA) (default: no, requires XML compliant clients)],
[AC_HELP_STRING([--enable-full-xml], [use XML features in XMPP stream (ex: CDATA) (default: no, requires XML compliant clients)])],
[case "${enableval}" in
yes) full_xml=true ;;
no) full_xml=false ;;
@ -98,6 +103,7 @@ AC_CONFIG_FILES([Makefile
$make_pam
$make_web
stringprep/Makefile
stun/Makefile
$make_tls
$make_odbc
$make_ejabberd_zlib])
@ -119,13 +125,45 @@ else
fi
AC_CHECK_HEADER(krb5.h,,)
AC_OUTPUT
if test -n "$ERLANG_SSL39" ; then
echo
echo "**************** WARNING ! ********************"
echo "ejabberd will be compiled with Erlang R12."
echo "This version of Erlang is not supported"
echo "and not recommended for production servers"
echo "***********************************************"
ENABLEUSER=""
AC_ARG_ENABLE(user,
[AS_HELP_STRING([--enable-user[[[[=USER]]]]], [allow this system user to start ejabberd (default: no)])],
[case "${enableval}" in
yes) ENABLEUSER=`whoami` ;;
no) ENABLEUSER="" ;;
*) ENABLEUSER=$enableval
esac],
[])
if test "$ENABLEUSER" != ""; then
echo "allow this system user to start ejabberd: $ENABLEUSER"
AC_SUBST([INSTALLUSER], [$ENABLEUSER])
fi
AC_CANONICAL_SYSTEM
#AC_DEFINE_UNQUOTED(CPU_VENDOR_OS, "$target")
#AC_SUBST(target_os)
case "$target_os" in
*darwin10*)
echo "Target OS is 'Darwin10'"
AC_LANG(Erlang)
AC_RUN_IFELSE(
[AC_LANG_PROGRAM([],[dnl
halt(case erlang:system_info(wordsize) of
8 -> 0; 4 -> 1 end)])],
[AC_MSG_NOTICE(found 64-bit Erlang)
CBIT=-m64],
[AC_MSG_NOTICE(found 32-bit Erlang)
CBIT=-m32])
;;
*)
echo "Target OS is '$target_os'"
CBIT=""
;;
esac
CFLAGS="$CFLAGS $CBIT"
LD_SHARED="$LD_SHARED $CBIT"
echo "CBIT is set to '$CBIT'"
AC_OUTPUT

View File

@ -5,7 +5,7 @@
%%% Created : 27 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -60,6 +60,8 @@ start() ->
EVersion = "ERLANG_VERSION = " ++ erlang:system_info(version) ++ "\n",
EIDirS = "EI_DIR = " ++ code:lib_dir("erl_interface") ++ "\n",
RootDirS = "ERLANG_DIR = " ++ code:root_dir() ++ "\n",
%% Load the ejabberd application description so that ?VERSION can read the vsn key
application:load(ejabberd),
Version = "EJABBERD_VERSION = " ++ ?VERSION ++ "\n",
ExpatDir = "EXPAT_DIR = c:\\sdk\\Expat-2.0.0\n",
OpenSSLDir = "OPENSSL_DIR = c:\\sdk\\OpenSSL\n",

View File

@ -5,7 +5,7 @@
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -30,19 +30,19 @@
-export([start/0,
register_mechanism/3,
listmech/1,
server_new/6,
server_new/7,
server_start/3,
server_step/2]).
-record(sasl_mechanism, {mechanism, module, require_plain_password}).
-record(sasl_state, {service, myname, realm,
get_password, check_password,
get_password, check_password, check_password_digest,
mech_mod, mech_state}).
-export([behaviour_info/1]).
behaviour_info(callbacks) ->
[{mech_new, 3}, {mech_step, 2}];
[{mech_new, 4}, {mech_step, 2}];
behaviour_info(_Other) ->
undefined.
@ -113,12 +113,13 @@ listmech(Host) ->
filter_anonymous(Host, Mechs).
server_new(Service, ServerFQDN, UserRealm, _SecFlags,
GetPassword, CheckPassword) ->
GetPassword, CheckPassword, CheckPasswordDigest) ->
#sasl_state{service = Service,
myname = ServerFQDN,
realm = UserRealm,
get_password = GetPassword,
check_password = CheckPassword}.
check_password = CheckPassword,
check_password_digest= CheckPasswordDigest}.
server_start(State, Mech, ClientIn) ->
case lists:member(Mech, listmech(State#sasl_state.myname)) of
@ -128,7 +129,8 @@ server_start(State, Mech, ClientIn) ->
{ok, MechState} = Module:mech_new(
State#sasl_state.myname,
State#sasl_state.get_password,
State#sasl_state.check_password),
State#sasl_state.check_password,
State#sasl_state.check_password_digest),
server_step(State#sasl_state{mech_mod = Module,
mech_state = MechState},
ClientIn);

View File

@ -6,7 +6,7 @@
%%% Created : 23 Aug 2005 by Magnus Henoch <henoch@dtek.chalmers.se>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -17,7 +17,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -27,7 +27,7 @@
-module(cyrsasl_anonymous).
-export([start/1, stop/0, mech_new/3, mech_step/2]).
-export([start/1, stop/0, mech_new/4, mech_step/2]).
-behaviour(cyrsasl).
@ -40,7 +40,7 @@ start(_Opts) ->
stop() ->
ok.
mech_new(Host, _GetPassword, _CheckPassword) ->
mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
{ok, #state{server = Host}}.
mech_step(State, _ClientIn) ->
@ -51,5 +51,6 @@ mech_step(State, _ClientIn) ->
%% Checks that the username is available
case ejabberd_auth:is_user_exists(User, Server) of
true -> {error, "not-authorized"};
false -> {ok, [{username, User}]}
false -> {ok, [{username, User},
{auth_module, ejabberd_auth_anonymous}]}
end.

View File

@ -3,7 +3,25 @@
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : DIGEST-MD5 SASL mechanism
%%% Created : 11 Mar 2003 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-module(cyrsasl_digest).
@ -11,14 +29,15 @@
-export([start/1,
stop/0,
mech_new/3,
mech_new/4,
mech_step/2]).
-include("ejabberd.hrl").
-behaviour(cyrsasl).
-record(state, {step, nonce, username, authzid, get_password}).
-record(state, {step, nonce, username, authzid, get_password, check_password, auth_module,
host}).
start(_Opts) ->
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, true).
@ -26,10 +45,12 @@ start(_Opts) ->
stop() ->
ok.
mech_new(_Host, GetPassword, _CheckPassword) ->
mech_new(Host, GetPassword, _CheckPassword, CheckPasswordDigest) ->
{ok, #state{step = 1,
nonce = randoms:get_string(),
get_password = GetPassword}}.
host = Host,
get_password = GetPassword,
check_password = CheckPasswordDigest}}.
mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
{continue,
@ -41,38 +62,51 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
bad ->
{error, "bad-protocol"};
KeyVals ->
DigestURI = xml:get_attr_s("digest-uri", KeyVals),
UserName = xml:get_attr_s("username", KeyVals),
AuthzId = xml:get_attr_s("authzid", KeyVals),
case (State#state.get_password)(UserName) of
case is_digesturi_valid(DigestURI, State#state.host) of
false ->
?DEBUG("User login not authorized because digest-uri "
"seems invalid: ~p", [DigestURI]),
{error, "not-authorized", UserName};
Passwd ->
Response = response(KeyVals, UserName, Passwd,
Nonce, AuthzId, "AUTHENTICATE"),
case xml:get_attr_s("response", KeyVals) of
Response ->
RspAuth = response(KeyVals,
UserName, Passwd,
Nonce, AuthzId, ""),
{continue,
"rspauth=" ++ RspAuth,
State#state{step = 5,
username = UserName,
authzid = AuthzId}};
_ ->
{error, "not-authorized", UserName}
true ->
AuthzId = xml:get_attr_s("authzid", KeyVals),
case (State#state.get_password)(UserName) of
{false, _} ->
{error, "not-authorized", UserName};
{Passwd, AuthModule} ->
case (State#state.check_password)(UserName, "",
xml:get_attr_s("response", KeyVals),
fun(PW) -> response(KeyVals, UserName, PW, Nonce, AuthzId,
"AUTHENTICATE") end) of
{true, _} ->
RspAuth = response(KeyVals,
UserName, Passwd,
Nonce, AuthzId, ""),
{continue,
"rspauth=" ++ RspAuth,
State#state{step = 5,
auth_module = AuthModule,
username = UserName,
authzid = AuthzId}};
false ->
{error, "not-authorized", UserName};
{false, _} ->
{error, "not-authorized", UserName}
end
end
end
end;
mech_step(#state{step = 5,
auth_module = AuthModule,
username = UserName,
authzid = AuthzId}, "") ->
{ok, [{username, UserName}, {authzid, AuthzId}]};
{ok, [{username, UserName}, {authzid, AuthzId},
{auth_module, AuthModule}]};
mech_step(A, B) ->
?DEBUG("SASL DIGEST: A ~p B ~p", [A,B]),
{error, "bad-protocol"}.
parse(S) ->
parse1(S, "", []).
@ -115,6 +149,23 @@ parse4([], Key, Val, Ts) ->
parse1([], "", [{Key, lists:reverse(Val)} | Ts]).
%% @doc Check if the digest-uri is valid.
%% RFC-2831 allows to provide the IP address in Host,
%% however ejabberd doesn't allow that.
%% If the service (for example jabber.example.org)
%% is provided by several hosts (being one of them server3.example.org),
%% then digest-uri can be like xmpp/server3.example.org/jabber.example.org
%% In that case, ejabberd only checks the service name, not the host.
is_digesturi_valid(DigestURICase, JabberHost) ->
DigestURI = stringprep:tolower(DigestURICase),
case catch string:tokens(DigestURI, "/") of
["xmpp", Host] when Host == JabberHost ->
true;
["xmpp", _Host, ServName] when ServName == JabberHost ->
true;
_ ->
false
end.

View File

@ -5,7 +5,7 @@
%%% Created : 8 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -27,7 +27,7 @@
-module(cyrsasl_plain).
-author('alexey@process-one.net').
-export([start/1, stop/0, mech_new/3, mech_step/2, parse/1]).
-export([start/1, stop/0, mech_new/4, mech_step/2, parse/1]).
-behaviour(cyrsasl).
@ -40,15 +40,16 @@ start(_Opts) ->
stop() ->
ok.
mech_new(_Host, _GetPassword, CheckPassword) ->
mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) ->
{ok, #state{check_password = CheckPassword}}.
mech_step(State, ClientIn) ->
case parse(ClientIn) of
[AuthzId, User, Password] ->
case (State#state.check_password)(User, Password) of
true ->
{ok, [{username, User}, {authzid, AuthzId}]};
{true, AuthModule} ->
{ok, [{username, User}, {authzid, AuthzId},
{auth_module, AuthModule}]};
_ ->
{error, "not-authorized", User}
end;

268
src/dynamic_compile.erl Normal file
View File

@ -0,0 +1,268 @@
%% Copyright (c) 2007
%% Mats Cronqvist <mats.cronqvist@ericsson.com>
%% Chris Newcombe <chris.newcombe@gmail.com>
%% Jacob Vorreuter <jacob.vorreuter@gmail.com>
%%
%% Permission is hereby granted, free of charge, to any person
%% obtaining a copy of this software and associated documentation
%% files (the "Software"), to deal in the Software without
%% restriction, including without limitation the rights to use,
%% copy, modify, merge, publish, distribute, sublicense, and/or sell
%% copies of the Software, and to permit persons to whom the
%% Software is furnished to do so, subject to the following
%% conditions:
%%
%% The above copyright notice and this permission notice shall be
%% included in all copies or substantial portions of the Software.
%%
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
%% OTHER DEALINGS IN THE SOFTWARE.
%%%-------------------------------------------------------------------
%%% File : dynamic_compile.erl
%%% Description :
%%% Authors : Mats Cronqvist <mats.cronqvist@ericsson.com>
%%% Chris Newcombe <chris.newcombe@gmail.com>
%%% Jacob Vorreuter <jacob.vorreuter@gmail.com>
%%% TODO :
%%% - add support for limit include-file depth (and prevent circular references)
%%% prevent circular macro expansion set FILE correctly when -module() is found
%%% -include_lib support $ENVVAR in include filenames
%%% substitute-stringize (??MACRO)
%%% -undef/-ifdef/-ifndef/-else/-endif
%%% -file(File, Line)
%%%-------------------------------------------------------------------
-module(dynamic_compile).
%% API
-export([from_string/1, from_string/2]).
-import(lists, [reverse/1, keyreplace/4]).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function:
%% Description:
%% Returns a binary that can be used with
%% code:load_binary(Module, ModuleFilenameForInternalRecords, Binary).
%%--------------------------------------------------------------------
from_string(CodeStr) ->
from_string(CodeStr, []).
% takes Options as for compile:forms/2
from_string(CodeStr, CompileFormsOptions) ->
%% Initialise the macro dictionary with the default predefined macros,
%% (adapted from epp.erl:predef_macros/1
Filename = "compiled_from_string",
%%Machine = list_to_atom(erlang:system_info(machine)),
Ms0 = dict:new(),
% Ms1 = dict:store('FILE', {[], "compiled_from_string"}, Ms0),
% Ms2 = dict:store('LINE', {[], 1}, Ms1), % actually we might add special code for this
% Ms3 = dict:store('MODULE', {[], undefined}, Ms2),
% Ms4 = dict:store('MODULE_STRING', {[], undefined}, Ms3),
% Ms5 = dict:store('MACHINE', {[], Machine}, Ms4),
% InitMD = dict:store(Machine, {[], true}, Ms5),
InitMD = Ms0,
%% From the docs for compile:forms:
%% When encountering an -include or -include_dir directive, the compiler searches for header files in the following directories:
%% 1. ".", the current working directory of the file server;
%% 2. the base name of the compiled file;
%% 3. the directories specified using the i option. The directory specified last is searched first.
%% In this case, #2 is meaningless.
IncludeSearchPath = ["." | reverse([Dir || {i, Dir} <- CompileFormsOptions])],
{RevForms, _OutMacroDict} = scan_and_parse(CodeStr, Filename, 1, [], InitMD, IncludeSearchPath),
Forms = reverse(RevForms),
%% note: 'binary' is forced as an implicit option, whether it is provided or not.
case compile:forms(Forms, CompileFormsOptions) of
{ok, ModuleName, CompiledCodeBinary} when is_binary(CompiledCodeBinary) ->
{ModuleName, CompiledCodeBinary};
{ok, ModuleName, CompiledCodeBinary, []} when is_binary(CompiledCodeBinary) -> % empty warnings list
{ModuleName, CompiledCodeBinary};
{ok, _ModuleName, _CompiledCodeBinary, Warnings} ->
throw({?MODULE, warnings, Warnings});
Other ->
throw({?MODULE, compile_forms, Other})
end.
%%====================================================================
%% Internal functions
%%====================================================================
%%% Code from Mats Cronqvist
%%% See http://www.erlang.org/pipermail/erlang-questions/2007-March/025507.html
%%%## 'scan_and_parse'
%%%
%%% basically we call the OTP scanner and parser (erl_scan and
%%% erl_parse) line-by-line, but check each scanned line for (or
%%% definitions of) macros before parsing.
%% returns {ReverseForms, FinalMacroDict}
scan_and_parse([], _CurrFilename, _CurrLine, RevForms, MacroDict, _IncludeSearchPath) ->
{RevForms, MacroDict};
scan_and_parse(RemainingText, CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath) ->
case scanner(RemainingText, CurrLine, MacroDict) of
{tokens, NLine, NRemainingText, Toks} ->
{ok, Form} = erl_parse:parse_form(Toks),
scan_and_parse(NRemainingText, CurrFilename, NLine, [Form | RevForms], MacroDict, IncludeSearchPath);
{macro, NLine, NRemainingText, NMacroDict} ->
scan_and_parse(NRemainingText, CurrFilename, NLine, RevForms,NMacroDict, IncludeSearchPath);
{include, NLine, NRemainingText, IncludeFilename} ->
IncludeFileRemainingTextents = read_include_file(IncludeFilename, IncludeSearchPath),
%%io:format("include file ~p contents: ~n~p~nRemainingText = ~p~n", [IncludeFilename,IncludeFileRemainingTextents, RemainingText]),
%% Modify the FILE macro to reflect the filename
%%IncludeMacroDict = dict:store('FILE', {[],IncludeFilename}, MacroDict),
IncludeMacroDict = MacroDict,
%% Process the header file (inc. any nested header files)
{RevIncludeForms, IncludedMacroDict} = scan_and_parse(IncludeFileRemainingTextents, IncludeFilename, 1, [], IncludeMacroDict, IncludeSearchPath),
%io:format("include file results = ~p~n", [R]),
%% Restore the FILE macro in the NEW MacroDict (so we keep any macros defined in the header file)
%%NMacroDict = dict:store('FILE', {[],CurrFilename}, IncludedMacroDict),
NMacroDict = IncludedMacroDict,
%% Continue with the original file
scan_and_parse(NRemainingText, CurrFilename, NLine, RevIncludeForms ++ RevForms, NMacroDict, IncludeSearchPath);
done ->
scan_and_parse([], CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath)
end.
scanner(Text, Line, MacroDict) ->
case erl_scan:tokens([],Text,Line) of
{done, {ok,Toks,NLine}, LeftOverChars} ->
case pre_proc(Toks, MacroDict) of
{tokens, NToks} -> {tokens, NLine, LeftOverChars, NToks};
{macro, NMacroDict} -> {macro, NLine, LeftOverChars, NMacroDict};
{include, Filename} -> {include, NLine, LeftOverChars, Filename}
end;
{more, _Continuation} ->
%% This is supposed to mean "term is not yet complete" (i.e. a '.' has
%% not been reached yet).
%% However, for some bizarre reason we also get this if there is a comment after the final '.' in a file.
%% So we check to see if Text only consists of comments.
case is_only_comments(Text) of
true ->
done;
false ->
throw({incomplete_term, Text, Line})
end
end.
is_only_comments(Text) -> is_only_comments(Text, not_in_comment).
is_only_comments([], _) -> true;
is_only_comments([$ |T], not_in_comment) -> is_only_comments(T, not_in_comment); % skipping whitspace outside of comment
is_only_comments([$\t |T], not_in_comment) -> is_only_comments(T, not_in_comment); % skipping whitspace outside of comment
is_only_comments([$\n |T], not_in_comment) -> is_only_comments(T, not_in_comment); % skipping whitspace outside of comment
is_only_comments([$% |T], not_in_comment) -> is_only_comments(T, in_comment); % found start of a comment
is_only_comments(_, not_in_comment) -> false;
% found any significant char NOT in a comment
is_only_comments([$\n |T], in_comment) -> is_only_comments(T, not_in_comment); % found end of a comment
is_only_comments([_ |T], in_comment) -> is_only_comments(T, in_comment). % skipping over in-comment chars
%%%## 'pre-proc'
%%%
%%% have to implement a subset of the pre-processor, since epp insists
%%% on running on a file.
%%% only handles 2 cases;
%% -define(MACRO, something).
%% -define(MACRO(VAR1,VARN),{stuff,VAR1,more,stuff,VARN,extra,stuff}).
pre_proc([{'-',_},{atom,_,define},{'(',_},{_,_,Name}|DefToks],MacroDict) ->
false = dict:is_key(Name, MacroDict),
case DefToks of
[{',',_} | Macro] ->
{macro, dict:store(Name, {[], macro_body_def(Macro, [])}, MacroDict)};
[{'(',_} | Macro] ->
{macro, dict:store(Name, macro_params_body_def(Macro, []), MacroDict)}
end;
pre_proc([{'-',_}, {atom,_,include}, {'(',_}, {string,_,Filename}, {')',_}, {dot,_}], _MacroDict) ->
{include, Filename};
pre_proc(Toks,MacroDict) ->
{tokens, subst_macros(Toks, MacroDict)}.
macro_params_body_def([{')',_},{',',_} | Toks], RevParams) ->
{reverse(RevParams), macro_body_def(Toks, [])};
macro_params_body_def([{var,_,Param} | Toks], RevParams) ->
macro_params_body_def(Toks, [Param | RevParams]);
macro_params_body_def([{',',_}, {var,_,Param} | Toks], RevParams) ->
macro_params_body_def(Toks, [Param | RevParams]).
macro_body_def([{')',_}, {dot,_}], RevMacroBodyToks) ->
reverse(RevMacroBodyToks);
macro_body_def([Tok|Toks], RevMacroBodyToks) ->
macro_body_def(Toks, [Tok | RevMacroBodyToks]).
subst_macros(Toks, MacroDict) ->
reverse(subst_macros_rev(Toks, MacroDict, [])).
%% returns a reversed list of tokes
subst_macros_rev([{'?',_}, {_,LineNum,'LINE'} | Toks], MacroDict, RevOutToks) ->
%% special-case for ?LINE, to avoid creating a new MacroDict for every line in the source file
subst_macros_rev(Toks, MacroDict, [{integer,LineNum,LineNum}] ++ RevOutToks);
subst_macros_rev([{'?',_}, {_,_,Name}, {'(',_} = Paren | Toks], MacroDict, RevOutToks) ->
case dict:fetch(Name, MacroDict) of
{[], MacroValue} ->
%% This macro does not have any vars, so ignore the fact that the invocation is followed by "(...stuff"
%% Recursively expand any macro calls inside this macro's value
%% TODO: avoid infinite expansion due to circular references (even indirect ones)
RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []),
subst_macros_rev([Paren|Toks], MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks);
ParamsAndBody ->
%% This macro does have vars.
%% Collect all of the passe arguments, in an ordered list
{NToks, Arguments} = subst_macros_get_args(Toks, []),
%% Expand the varibles
ExpandedParamsToks = subst_macros_subst_args_for_vars(ParamsAndBody, Arguments),
%% Recursively expand any macro calls inside this macro's value
%% TODO: avoid infinite expansion due to circular references (even indirect ones)
RevExpandedOtherMacrosToks = subst_macros_rev(ExpandedParamsToks, MacroDict, []),
subst_macros_rev(NToks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks)
end;
subst_macros_rev([{'?',_}, {_,_,Name} | Toks], MacroDict, RevOutToks) ->
%% This macro invocation does not have arguments.
%% Therefore the definition should not have parameters
{[], MacroValue} = dict:fetch(Name, MacroDict),
%% Recursively expand any macro calls inside this macro's value
%% TODO: avoid infinite expansion due to circular references (even indirect ones)
RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []),
subst_macros_rev(Toks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks);
subst_macros_rev([Tok|Toks], MacroDict, RevOutToks) ->
subst_macros_rev(Toks, MacroDict, [Tok|RevOutToks]);
subst_macros_rev([], _MacroDict, RevOutToks) -> RevOutToks.
subst_macros_get_args([{')',_} | Toks], RevArgs) ->
{Toks, reverse(RevArgs)};
subst_macros_get_args([{',',_}, {var,_,ArgName} | Toks], RevArgs) ->
subst_macros_get_args(Toks, [ArgName| RevArgs]);
subst_macros_get_args([{var,_,ArgName} | Toks], RevArgs) ->
subst_macros_get_args(Toks, [ArgName | RevArgs]).
subst_macros_subst_args_for_vars({[], BodyToks}, []) ->
BodyToks;
subst_macros_subst_args_for_vars({[Param | Params], BodyToks}, [Arg|Args]) ->
NBodyToks = keyreplace(Param, 3, BodyToks, {var,1,Arg}),
subst_macros_subst_args_for_vars({Params, NBodyToks}, Args).
read_include_file(Filename, IncludeSearchPath) ->
case file:path_open(IncludeSearchPath, Filename, [read, raw, binary]) of
{ok, IoDevice, FullName} ->
{ok, Data} = file:read(IoDevice, filelib:file_size(FullName)),
file:close(IoDevice),
binary_to_list(Data);
{error, Reason} ->
throw({failed_to_read_include_file, Reason, Filename, IncludeSearchPath})
end.

View File

@ -2,7 +2,7 @@
{application, ejabberd,
[{description, "ejabberd"},
{vsn, "2.0.0"},
{vsn, "2.1.0"},
{modules, [acl,
adhoc,
configure,
@ -115,7 +115,6 @@
nodetree_virtual,
p1_fsm,
p1_mnesia,
ram_file_io_server,
randoms,
sha,
shaper,

View File

@ -1,10 +1,11 @@
%%%
%%% ejabberd configuration file
%%%
%%%'
%%% The parameters used in this configuration file are explained in more detail
%%% in the ejabberd Installation and Operation Guide.
%%% Please consult the Guide in case of doubts, it is included in
%%% Please consult the Guide in case of doubts, it is included in
%%% your copy of ejabberd, and is also available online at
%%% http://www.process-one.net/en/ejabberd/docs/
@ -16,26 +17,26 @@
%%% - Each term ends in a dot, for example:
%%% override_global.
%%%
%%% - A tuple has a fixed definition, its elements are
%%% - A tuple has a fixed definition, its elements are
%%% enclosed in {}, and separated with commas:
%%% {loglevel, 4}.
%%%
%%% - A list can have as many elements as you want,
%%% - A list can have as many elements as you want,
%%% and is enclosed in [], for example:
%%% [http_poll, web_admin, tls]
%%%
%%% - A keyword of ejabberd is a word in lowercase.
%%% - A keyword of ejabberd is a word in lowercase.
%%% The strings are enclosed in "" and can have spaces, dots...
%%% {language, "en"}.
%%% {ldap_rootdn, "dc=example,dc=com"}.
%%% {ldap_rootdn, "dc=example,dc=com"}.
%%%
%%% - This term includes a tuple, a keyword, a list and two strings:
%%% {hosts, ["jabber.example.net", "im.example.com"]}.
%%%
%%% =======================
%%% OVERRIDE STORED OPTIONS
%%%. =======================
%%%' OVERRIDE STORED OPTIONS
%%
%% Override the old values stored in the database.
@ -57,8 +58,8 @@
%%override_acls.
%%% =========
%%% DEBUGGING
%%%. =========
%%%' DEBUGGING
%%
%% loglevel: Verbosity of log files generated by ejabberd.
@ -72,14 +73,15 @@
{loglevel, 4}.
%%
%% watchdog_admins: If an ejabberd process consumes too much memory,
%% send live notifications to those Jabber accounts.
%% watchdog_admins: Only useful for developers: if an ejabberd process
%% consumes a lot of memory, send live notifications to these XMPP
%% accounts.
%%
%%{watchdog_admins, ["bob@example.com"]}.
%%% ================
%%% SERVED HOSTNAMES
%%%. ================
%%%' SERVED HOSTNAMES
%%
%% hosts: Domains served by ejabberd.
@ -89,15 +91,15 @@
{hosts, ["localhost"]}.
%%
%% route_subdomains: Delegate subdomains to other Jabber server.
%% route_subdomains: Delegate subdomains to other XMPP server.
%% For example, if this ejabberd serves example.org and you want
%% to allow communication with a Jabber server called im.example.org.
%% to allow communication with a XMPP server called im.example.org.
%%
%%{route_subdomains, s2s}.
%%% ===============
%%% LISTENING PORTS
%%%. ===============
%%%' LISTENING PORTS
%%
%% listen: Which ports will ejabberd listen, which service handles it
@ -110,7 +112,7 @@
%%
%% If TLS is compiled and you installed a SSL
%% certificate, put the correct path to the
%% certificate, put the correct path to the
%% file and uncomment this line:
%%
%%{certfile, "/path/to/ssl.pem"}, starttls,
@ -139,7 +141,7 @@
%% ejabberd_service: Interact with external components (transports...)
%%
%%{8888, ejabberd_service, [
%% {access, all},
%% {access, all},
%% {shaper_rule, fast},
%% {ip, {127, 0, 0, 1}},
%% {hosts, ["icq.example.org", "sms.example.org"],
@ -147,8 +149,19 @@
%% }
%% ]},
%%
%% ejabberd_stun: Handles STUN Binding requests
%%
%%{{3478, udp}, ejabberd_stun, []},
{5280, ejabberd_http, [
http_poll,
%%{request_handlers,
%% [
%% {["pub", "archive"], mod_http_fileserver}
%% ]},
captcha,
http_bind,
http_poll,
web_admin
]}
@ -185,14 +198,22 @@
%%{{s2s_host, "goodhost.org"}, allow}.
%%{{s2s_host, "badhost.org"}, deny}.
%%
%% Outgoing S2S options
%%
%% Preferred address families (which to try first) and connect timeout
%% in milliseconds.
%%
%%{outgoing_s2s_options, [ipv4, ipv6], 10000}.
%%% ==============
%%% AUTHENTICATION
%%%. ==============
%%%' AUTHENTICATION
%%
%% auth_method: Method used to authenticate the users.
%% The default method is the internal.
%% If you want to use a different method,
%% If you want to use a different method,
%% comment this line and enable the correct ones.
%%
{auth_method, internal}.
@ -222,19 +243,30 @@
%%{auth_method, ldap}.
%%
%% List of LDAP servers:
%%{ldap_servers, ["localhost"]}.
%%{ldap_servers, ["localhost"]}.
%%
%% LDAP attribute that holds user ID:
%%{ldap_uids, [{"mail", "%u@mail.example.org"}]}.
%% Encryption of connection to LDAP servers:
%%{ldap_encrypt, none}.
%%{ldap_encrypt, tls}.
%%
%% Search base of LDAP directory:
%%{ldap_base, "dc=example,dc=com"}.
%% Port connect to LDAP servers:
%%{ldap_port, 389}.
%%{ldap_port, 636}.
%%
%% LDAP manager:
%%{ldap_rootdn, "dc=example,dc=com"}.
%%{ldap_rootdn, "dc=example,dc=com"}.
%%
%% Password to LDAP manager:
%%{ldap_password, "******"}.
%%{ldap_password, "******"}.
%%
%% Search base of LDAP directory:
%%{ldap_base, "dc=example,dc=com"}.
%%
%% LDAP attribute that holds user ID:
%%{ldap_uids, [{"mail", "%u@mail.example.org"}]}.
%%
%% LDAP filter:
%%{ldap_filter, "(objectClass=shadowAccount)"}.
%%
%% Anonymous login support:
@ -251,8 +283,8 @@
%%{host_config, "public.example.org", [{auth_method, [internal, anonymous]}]}.
%%% ==============
%%% DATABASE SETUP
%%%. ==============
%%%' DATABASE SETUP
%% ejabberd uses by default the internal Mnesia database,
%% so you can avoid this section.
@ -298,8 +330,8 @@
%%{odbc_keepalive_interval, undefined}.
%%% ===============
%%% TRAFFIC SHAPERS
%%%. ===============
%%%' TRAFFIC SHAPERS
%%
%% The "normal" shaper limits traffic speed to 1.000 B/s
@ -312,11 +344,11 @@
{shaper, fast, {maxrate, 50000}}.
%%% ====================
%%% ACCESS CONTROL LISTS
%%%. ====================
%%%' ACCESS CONTROL LISTS
%%
%% The 'admin' ACL grants administrative privileges to Jabber accounts.
%% The 'admin' ACL grants administrative privileges to XMPP accounts.
%% You can put as many accounts as you want.
%%
%%{acl, admin, {user, "aleksey", "localhost"}}.
@ -351,12 +383,15 @@
%%}.
%%% ============
%%% ACCESS RULES
%%%. ============
%%%' ACCESS RULES
%% Define the maximum number of time a single user is allowed to connect:
%% Maximum number of simultaneous sessions allowed for a single user:
{access, max_user_sessions, [{10, all}]}.
%% Maximum number of offline messages that users can have:
{access, max_user_offline_messages, [{5000, admin}, {100, all}]}.
%% This rule allows access only for local users:
{access, local, [{allow, local}]}.
@ -364,11 +399,11 @@
{access, c2s, [{deny, blocked},
{allow, all}]}.
%% For all users except admins used "normal" shaper
%% For C2S connections, all users except admins use "normal" shaper
{access, c2s_shaper, [{none, admin},
{normal, all}]}.
%% For all S2S connections used "fast" shaper
%% All S2S connections use "fast" shaper
{access, s2s_shaper, [{fast, all}]}.
%% Only admins can send announcement messages:
@ -380,15 +415,22 @@
%% Admins of this server are also admins of MUC service:
{access, muc_admin, [{allow, admin}]}.
%% Only accounts of the local ejabberd server can create rooms:
{access, muc_create, [{allow, local}]}.
%% All users are allowed to use MUC service:
{access, muc, [{allow, all}]}.
%% Every username can be registered via in-band registration:
%% Only accounts in the local ejabberd server can create Pubsub nodes:
{access, pubsub_createnode, [{allow, local}]}.
%% In-band registration allows registration of any possible username.
%% To disable in-band registration, replace 'allow' with 'deny'.
{access, register, [{allow, all}]}.
%% Everybody can create pubsub nodes
{access, pubsub_createnode, [{allow, all}]}.
%% By default frequency of account registrations from the same IP
%% is limited to 1 account every 10 minutes. To disable put: infinity
%%{registration_timeout, 600}.
%%
%% Define specific Access rules in a virtual host.
@ -401,8 +443,8 @@
%%}.
%%% ================
%%% DEFAULT LANGUAGE
%%%. ================
%%%' DEFAULT LANGUAGE
%%
%% language: Default language used for server messages.
@ -410,15 +452,29 @@
{language, "en"}.
%%
%% Set a different language in a virtual host.
%% Set a different default language in a virtual host.
%%
%%{host_config, "localhost",
%% [{language, "ru"}]
%%}.
%%% =======
%%% MODULES
%%%. =======
%%%' CAPTCHA
%%
%% Full path to a script that generates the image.
%%
%%{captcha_cmd, "/lib/ejabberd/priv/bin/captcha.sh"}.
%%
%% Host part of the URL sent to the user.
%%
%%{captcha_host, "example.org:5280"}.
%%%. =======
%%%' MODULES
%%
%% Modules enabled in all ejabberd virtual hosts.
@ -427,39 +483,47 @@
[
{mod_adhoc, []},
{mod_announce, [{access, announce}]}, % recommends mod_adhoc
{mod_caps, []},
{mod_caps, []},
{mod_configure,[]}, % requires mod_adhoc
{mod_disco, []},
%%{mod_echo, [{host, "echo.localhost"}]},
{mod_irc, []},
{mod_http_bind, []},
%%{mod_http_fileserver, [
%% {docroot, "/var/www"},
%% {accesslog, "/var/log/ejabberd/access.log"}
%% ]},
{mod_last, []},
{mod_muc, [
%%{host, "conference.@HOST@"},
{access, muc},
{access_create, muc},
{access_persistent, muc},
{access_create, muc_create},
{access_persistent, muc_create},
{access_admin, muc_admin}
]},
%%{mod_muc_log,[]},
{mod_offline, []},
{mod_offline, [{access_max_user_messages, max_user_offline_messages}]},
{mod_ping, []},
{mod_privacy, []},
{mod_private, []},
%%{mod_proxy65,[]},
{mod_pubsub, [ % requires mod_caps
{mod_pubsub, [
{access_createnode, pubsub_createnode},
{plugins, ["default", "pep"]}
{ignore_pep_from_offline, true},
{last_item_cache, false},
{plugins, ["flat", "hometree", "pep"]} % pep requires mod_caps
]},
{mod_register, [
%%
%% After successful registration, the user receives
%% After successful registration, the user receives
%% a message with this subject and body.
%%
{welcome_message, {"Welcome!",
"Welcome to this Jabber server."}},
{welcome_message, {"Welcome!",
"Hi.\nWelcome to this XMPP server."}},
%%
%% When a user registers, send a notification to
%% these Jabber accounts.
%% When a user registers, send a notification to
%% these XMPP accounts.
%%
%%{registration_watchers, ["admin1@example.org"]},
@ -486,9 +550,12 @@
%% ]}.
%%%.
%%%'
%%% $Id$
%%% Local Variables:
%%% mode: erlang
%%% End:
%%% vim: set filetype=erlang tabstop=8:
%%% vim: set filetype=erlang tabstop=8 foldmarker=%%%',%%%. foldmethod=marker:

View File

@ -5,7 +5,7 @@
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -28,14 +28,16 @@
-author('alexey@process-one.net').
-export([start/0, stop/0,
get_pid_file/0,
get_so_path/0, get_bin_path/0]).
start() ->
%%ejabberd_cover:start(),
application:start(ejabberd).
stop() ->
application:stop(ejabberd).
%%ejabberd_cover:stop().
get_so_path() ->
case os:getenv("EJABBERD_SO_PATH") of
@ -62,3 +64,14 @@ get_bin_path() ->
Path ->
Path
end.
%% @spec () -> false | string()
get_pid_file() ->
case os:getenv("EJABBERD_PID_PATH") of
false ->
false;
"" ->
false;
Path ->
Path
end.

View File

@ -1,6 +1,6 @@
%%%----------------------------------------------------------------------
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -11,7 +11,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -19,7 +19,9 @@
%%%
%%%----------------------------------------------------------------------
-define(VERSION, "2.1.0-alpha").
%% This macro returns a string of the ejabberd version running, e.g. "2.3.4"
%% If the ejabberd application description isn't loaded, returns atom: undefined
-define(VERSION, element(2, application:get_key(ejabberd,vsn))).
-define(MYHOSTS, ejabberd_config:get_global_option(hosts)).
-define(MYNAME, hd(ejabberd_config:get_global_option(hosts))).

View File

@ -0,0 +1,46 @@
#! /bin/sh
set -o errexit
set -o nounset
DIR=@ctlscriptpath@
CTL="$DIR"/ejabberdctl
USER=@installuser@
test -x "$CTL" || {
echo "ERROR: ejabberd not found: $DIR"
exit 1
}
grep ^"$USER": /etc/passwd >/dev/null || {
echo "ERROR: System user not found: $USER"
exit 2
}
export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
case "$1" in
start)
test -x "$CTL" || exit 0
echo "Starting ejabberd..."
su - $USER -c "$CTL start"
su - $USER -c "$CTL started"
echo "done."
;;
stop)
test -x "$CTL" || exit 0
echo "Stopping ejabberd..."
su - $USER -c "$CTL stop"
su - $USER -c "$CTL stopped"
echo "done."
;;
force-reload|restart)
"$0" stop
"$0" start
;;
*)
echo "Usage: $0 {start|stop|restart|force-reload}"
exit 1
esac
exit 0

View File

@ -1,15 +1,11 @@
%%%-------------------------------------------------------------------
%%% 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
%%% Purpose : Administrative functions and commands
%%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -20,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -31,9 +27,291 @@
-module(ejabberd_admin).
-author('mickael.remond@process-one.net').
-export([restore/1]).
-export([start/0, stop/0,
%% Server
status/0, reopen_log/0,
%% Accounts
register/3, unregister/2,
registered_users/1,
%% Migration jabberd1.4
import_file/1, import_dir/1,
%% Purge DB
delete_expired_messages/0, delete_old_messages/1,
%% Mnesia
backup_mnesia/1, restore_mnesia/1,
dump_mnesia/1, dump_table/2, load_mnesia/1,
install_fallback_mnesia/1,
dump_to_textfile/1, dump_to_textfile/2,
mnesia_change_nodename/4,
restore/1 % Still used by some modules
]).
-include("ejabberd.hrl").
-include("ejabberd_commands.hrl").
start() ->
ejabberd_commands:register_commands(commands()).
stop() ->
ejabberd_commands:unregister_commands(commands()).
%%%
%%% ejabberd commands
%%%
commands() ->
[
%% The commands status, stop and restart are implemented also in ejabberd_ctl
%% They are defined here so that other interfaces can use them too
#ejabberd_commands{name = status, tags = [server],
desc = "Get status of the ejabberd server",
module = ?MODULE, function = status,
args = [], result = {res, restuple}},
#ejabberd_commands{name = stop, tags = [server],
desc = "Stop ejabberd gracefully",
module = init, function = stop,
args = [], result = {res, rescode}},
#ejabberd_commands{name = restart, tags = [server],
desc = "Restart ejabberd gracefully",
module = init, function = restart,
args = [], result = {res, rescode}},
#ejabberd_commands{name = reopen_log, tags = [logs, server],
desc = "Reopen the log files",
module = ?MODULE, function = reopen_log,
args = [], result = {res, rescode}},
#ejabberd_commands{name = get_loglevel, tags = [logs, server],
desc = "Get the current loglevel",
module = ejabberd_loglevel, function = get,
args = [],
result = {leveltuple, {tuple, [{levelnumber, integer},
{levelatom, atom},
{leveldesc, string}
]}}},
#ejabberd_commands{name = register, tags = [accounts],
desc = "Register a user",
module = ?MODULE, function = register,
args = [{user, string}, {host, string}, {password, string}],
result = {res, restuple}},
#ejabberd_commands{name = unregister, tags = [accounts],
desc = "Unregister a user",
module = ?MODULE, function = unregister,
args = [{user, string}, {host, string}],
result = {res, restuple}},
#ejabberd_commands{name = registered_users, tags = [accounts],
desc = "List all registered users in HOST",
module = ?MODULE, function = registered_users,
args = [{host, string}],
result = {users, {list, {username, string}}}},
#ejabberd_commands{name = import_file, tags = [mnesia],
desc = "Import user data from jabberd14 spool file",
module = ?MODULE, function = import_file,
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = import_dir, tags = [mnesia],
desc = "Import users data from jabberd14 spool dir",
module = ?MODULE, function = import_dir,
args = [{file, string}],
result = {res, restuple}},
#ejabberd_commands{name = import_piefxis, tags = [mnesia],
desc = "Import users data from a PIEFXIS file (XEP-0227)",
module = ejabberd_piefxis, function = import_file,
args = [{file, string}], result = {res, rescode}},
#ejabberd_commands{name = export_piefxis, tags = [mnesia],
desc = "Export data of all users in the server to PIEFXIS files (XEP-0227)",
module = ejabberd_piefxis, function = export_server,
args = [{dir, string}], result = {res, rescode}},
#ejabberd_commands{name = export_piefxis_host, tags = [mnesia],
desc = "Export data of users in a host to PIEFXIS files (XEP-0227)",
module = ejabberd_piefxis, function = export_host,
args = [{dir, string}, {host, string}], result = {res, rescode}},
#ejabberd_commands{name = delete_expired_messages, tags = [purge],
desc = "Delete expired offline messages from database",
module = ?MODULE, function = delete_expired_messages,
args = [], result = {res, rescode}},
#ejabberd_commands{name = delete_old_messages, tags = [purge],
desc = "Delete offline messages older than DAYS",
module = ?MODULE, function = delete_old_messages,
args = [{days, integer}], result = {res, rescode}},
#ejabberd_commands{name = rename_default_nodeplugin, tags = [mnesia],
desc = "Update PubSub table from ejabberd trunk SVN to 2.1.0",
module = mod_pubsub, function = rename_default_nodeplugin,
args = [], result = {res, rescode}},
#ejabberd_commands{name = mnesia_change_nodename, tags = [mnesia],
desc = "Change the erlang node name in a backup file",
module = ?MODULE, function = mnesia_change_nodename,
args = [{oldnodename, string}, {newnodename, string},
{oldbackup, string}, {newbackup, string}],
result = {res, restuple}},
#ejabberd_commands{name = backup, tags = [mnesia],
desc = "Store the database to backup file",
module = ?MODULE, function = backup_mnesia,
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = restore, tags = [mnesia],
desc = "Restore the database from backup file",
module = ?MODULE, function = restore_mnesia,
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = dump, tags = [mnesia],
desc = "Dump the database to text file",
module = ?MODULE, function = dump_mnesia,
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = dump_table, tags = [mnesia],
desc = "Dump a table to text file",
module = ?MODULE, function = dump_table,
args = [{file, string}, {table, string}], result = {res, restuple}},
#ejabberd_commands{name = load, tags = [mnesia],
desc = "Restore the database from text file",
module = ?MODULE, function = load_mnesia,
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = install_fallback, tags = [mnesia],
desc = "Install the database from a fallback file",
module = ?MODULE, function = install_fallback_mnesia,
args = [{file, string}], result = {res, restuple}}
].
%%%
%%% Server management
%%%
status() ->
{InternalStatus, ProvidedStatus} = init:get_status(),
String1 = io_lib:format("The node ~p is ~p. Status: ~p",
[node(), InternalStatus, ProvidedStatus]),
{Is_running, String2} =
case lists:keysearch(ejabberd, 1, application:which_applications()) of
false ->
{ejabberd_not_running, "ejabberd is not running in that node."};
{value, {_, _, Version}} ->
{ok, io_lib:format("ejabberd ~s is running in that node", [Version])}
end,
{Is_running, String1 ++ String2}.
reopen_log() ->
ejabberd_hooks:run(reopen_log_hook, []),
%% TODO: Use the Reopen log API for logger_h ?
ejabberd_logger_h:reopen_log(),
case application:get_env(sasl,sasl_error_logger) of
{ok, {file, SASLfile}} ->
error_logger:delete_report_handler(sasl_report_file_h),
ejabberd_logger_h:rotate_log(SASLfile),
error_logger:add_report_handler(sasl_report_file_h,
{SASLfile, get_sasl_error_logger_type()});
_ -> false
end,
ok.
%% Function copied from Erlang/OTP lib/sasl/src/sasl.erl which doesn't export it
get_sasl_error_logger_type () ->
case application:get_env (sasl, errlog_type) of
{ok, error} -> error;
{ok, progress} -> progress;
{ok, all} -> all;
{ok, Bad} -> exit ({bad_config, {sasl, {errlog_type, Bad}}});
_ -> all
end.
%%%
%%% Account management
%%%
register(User, Host, Password) ->
case ejabberd_auth:try_register(User, Host, Password) of
{atomic, ok} ->
{ok, io_lib:format("User ~s@~s succesfully registered", [User, Host])};
{atomic, exists} ->
String = io_lib:format("User ~s@~s already registered at node ~p",
[User, Host, node()]),
{exists, String};
{error, Reason} ->
String = io_lib:format("Can't register user ~s@~s at node ~p: ~p",
[User, Host, node(), Reason]),
{cannot_register, String}
end.
unregister(User, Host) ->
ejabberd_auth:remove_user(User, Host),
{ok, ""}.
registered_users(Host) ->
Users = ejabberd_auth:get_vh_registered_users(Host),
SUsers = lists:sort(Users),
lists:map(fun({U, _S}) -> U end, SUsers).
%%%
%%% Migration management
%%%
import_file(Path) ->
case jd2ejd:import_file(Path) of
ok ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't import jabberd14 spool file ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_import_file, String}
end.
import_dir(Path) ->
case jd2ejd:import_dir(Path) of
ok ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't import jabberd14 spool dir ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_import_dir, String}
end.
%%%
%%% Purge DB
%%%
delete_expired_messages() ->
{atomic, ok} = mod_offline:remove_expired_messages(),
ok.
delete_old_messages(Days) ->
{atomic, _} = mod_offline:remove_old_messages(Days),
ok.
%%%
%%% Mnesia management
%%%
backup_mnesia(Path) ->
case mnesia:backup(Path) of
ok ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't store backup in ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_backup, String}
end.
restore_mnesia(Path) ->
case ejabberd_admin:restore(Path) of
{atomic, _} ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't restore backup from ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_restore, String};
{aborted,{no_exists,Table}} ->
String = io_lib:format("Can't restore backup from ~p at node ~p: Table ~p does not exist.",
[filename:absname(Path), node(), Table]),
{table_not_exists, String};
{aborted,enoent} ->
String = io_lib:format("Can't restore backup from ~p at node ~p: File not found.",
[filename:absname(Path), node()]),
{file_not_found, String}
end.
%% Mnesia database restore
%% This function is called from ejabberd_ctl, ejabberd_web_admin and
@ -71,3 +349,127 @@ module_tables(mod_roster) -> [roster];
module_tables(mod_shared_roster) -> [sr_group, sr_user];
module_tables(mod_vcard) -> [vcard, vcard_search];
module_tables(_Other) -> [].
get_local_tables() ->
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),
Tabs.
dump_mnesia(Path) ->
Tabs = get_local_tables(),
dump_tables(Path, Tabs).
dump_table(Path, STable) ->
Table = list_to_atom(STable),
dump_tables(Path, [Table]).
dump_tables(Path, Tables) ->
case dump_to_textfile(Path, Tables) of
ok ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't store dump in ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_dump, String}
end.
dump_to_textfile(File) ->
Tabs = get_local_tables(),
dump_to_textfile(File, Tabs).
dump_to_textfile(File, Tabs) ->
dump_to_textfile(mnesia:system_info(is_running), Tabs, file:open(File, [write])).
dump_to_textfile(yes, Tabs, {ok, F}) ->
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).
load_mnesia(Path) ->
case mnesia:load_textfile(Path) of
{atomic, ok} ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't load dump in ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_load, String}
end.
install_fallback_mnesia(Path) ->
case mnesia:install_fallback(Path) of
ok ->
{ok, ""};
{error, Reason} ->
String = io_lib:format("Can't install fallback from ~p at node ~p: ~p",
[filename:absname(Path), node(), Reason]),
{cannot_fallback, String}
end.
mnesia_change_nodename(FromString, ToString, Source, Target) ->
From = list_to_atom(FromString),
To = list_to_atom(ToString),
Switch =
fun
(Node) when Node == From ->
io:format(" - Replacing nodename: '~p' with: '~p'~n", [From, To]),
To;
(Node) when Node == To ->
%% throw({error, already_exists});
io:format(" - Node: '~p' will not be modified (it is already '~p')~n", [Node, To]),
Node;
(Node) ->
io:format(" - Node: '~p' will not be modified (it is not '~p')~n", [Node, From]),
Node
end,
Convert =
fun
({schema, db_nodes, Nodes}, Acc) ->
io:format(" +++ db_nodes ~p~n", [Nodes]),
{[{schema, db_nodes, lists:map(Switch,Nodes)}], Acc};
({schema, version, Version}, Acc) ->
io:format(" +++ version: ~p~n", [Version]),
{[{schema, version, Version}], Acc};
({schema, cookie, Cookie}, Acc) ->
io:format(" +++ cookie: ~p~n", [Cookie]),
{[{schema, cookie, Cookie}], Acc};
({schema, Tab, CreateList}, Acc) ->
io:format("~n * Checking table: '~p'~n", [Tab]),
Keys = [ram_copies, disc_copies, disc_only_copies],
OptSwitch =
fun({Key, Val}) ->
case lists:member(Key, Keys) of
true ->
io:format(" + Checking key: '~p'~n", [Key]),
{Key, lists:map(Switch, Val)};
false-> {Key, Val}
end
end,
Res = {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc},
Res;
(Other, Acc) ->
{[Other], Acc}
end,
mnesia:traverse_backup(Source, Target, Convert, switched).

View File

@ -5,7 +5,7 @@
%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -29,7 +29,7 @@
-behaviour(application).
-export([start/2, prep_stop/1, stop/1, init/0]).
-export([start_modules/0,start/2, get_log_path/0, prep_stop/1, stop/1, init/0]).
-include("ejabberd.hrl").
@ -40,41 +40,50 @@
start(normal, _Args) ->
ejabberd_loglevel:set(4),
write_pid_file(),
application:start(sasl),
randoms:start(),
db_init(),
sha:start(),
stringprep_sup:start_link(),
start(),
translate:start(),
acl:start(),
ejabberd_ctl:init(),
ejabberd_commands:init(),
ejabberd_admin:start(),
gen_mod:start(),
ejabberd_config:start(),
ejabberd_check:config(),
start(),
connect_nodes(),
Sup = ejabberd_sup:start_link(),
ejabberd_rdbms:start(),
ejabberd_auth:start(),
cyrsasl:start(),
% Profiling
%eprof:start(),
%eprof:profile([self()]),
%fprof:trace(start, "/tmp/fprof"),
%ejabberd_debug:eprof_start(),
%ejabberd_debug:fprof_start(),
maybe_add_nameservers(),
start_modules(),
ejabberd_listener:start_listeners(),
?INFO_MSG("ejabberd ~s is started in the node ~p", [?VERSION, node()]),
Sup;
start(_, _) ->
{error, badarg}.
%% Prepare the application for termination.
%% This function is called when an application is about to be stopped,
%% This function is called when an application is about to be stopped,
%% before shutting down the processes of the application.
prep_stop(State) ->
stop_modules(),
ejabberd_admin:stop(),
State.
%% All the processes were killed when this function is called
stop(_State) ->
?INFO_MSG("ejabberd ~s is stopped in the node ~p", [?VERSION, node()]),
delete_pid_file(),
ejabberd_debug:stop(),
ok.
@ -89,18 +98,7 @@ init() ->
register(ejabberd, self()),
%erlang:system_flag(fullsweep_after, 0),
%error_logger:logfile({open, ?LOG_PATH}),
LogPath =
case application:get_env(log_path) of
{ok, Path} ->
Path;
undefined ->
case os:getenv("EJABBERD_LOG_PATH") of
false ->
?LOG_PATH;
Path ->
Path
end
end,
LogPath = get_log_path(),
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
@ -124,7 +122,7 @@ db_init() ->
_ ->
ok
end,
mnesia:start(),
application:start(mnesia, permanent),
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity).
%% Start all the modules in all the hosts
@ -152,7 +150,7 @@ stop_modules() ->
Modules ->
lists:foreach(
fun({Module, _Args}) ->
gen_mod:stop_module(Host, Module)
gen_mod:stop_module_keep_config(Host, Module)
end, Modules)
end
end, ?MYHOSTS).
@ -167,3 +165,65 @@ connect_nodes() ->
end, Nodes)
end.
%% @spec () -> string()
%% @doc Returns the full path to the ejabberd log file.
%% It first checks for application configuration parameter 'log_path'.
%% If not defined it checks the environment variable EJABBERD_LOG_PATH.
%% And if that one is neither defined, returns the default value:
%% "ejabberd.log" in current directory.
get_log_path() ->
case application:get_env(log_path) of
{ok, Path} ->
Path;
undefined ->
case os:getenv("EJABBERD_LOG_PATH") of
false ->
?LOG_PATH;
Path ->
Path
end
end.
%% If ejabberd is running on some Windows machine, get nameservers and add to Erlang
maybe_add_nameservers() ->
case os:type() of
{win32, _} -> add_windows_nameservers();
_ -> ok
end.
add_windows_nameservers() ->
IPTs = win32_dns:get_nameservers(),
?INFO_MSG("Adding machine's DNS IPs to Erlang system:~n~p", [IPTs]),
lists:foreach(fun(IPT) -> inet_db:add_ns(IPT) end, IPTs).
%%%
%%% PID file
%%%
write_pid_file() ->
case ejabberd:get_pid_file() of
false ->
ok;
PidFilename ->
write_pid_file(os:getpid(), PidFilename)
end.
write_pid_file(Pid, PidFilename) ->
case file:open(PidFilename, [write]) of
{ok, Fd} ->
io:format(Fd, "~s~n", [Pid]),
file:close(Fd);
{error, Reason} ->
?ERROR_MSG("Cannot write PID file ~s~nReason: ~p", [PidFilename, Reason]),
throw({cannot_write_pid_file, PidFilename, Reason})
end.
delete_pid_file() ->
case ejabberd:get_pid_file() of
false ->
ok;
PidFilename ->
file:delete(PidFilename)
end.

View File

@ -5,7 +5,7 @@
%%% Created : 23 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -34,6 +34,8 @@
set_password/3,
check_password/3,
check_password/5,
check_password_with_authmodule/3,
check_password_with_authmodule/5,
try_register/3,
dirty_get_registered_users/0,
get_vh_registered_users/1,
@ -42,18 +44,17 @@
get_vh_registered_users_number/2,
get_password/2,
get_password_s/2,
get_password_with_authmodule/2,
is_user_exists/2,
is_user_exists_in_other_modules/3,
remove_user/2,
remove_user/3,
plain_password_required/1,
ctl_process_get_registered/3
plain_password_required/1
]).
-export([auth_modules/1]).
-include("ejabberd.hrl").
-include("ejabberd_ctl.hrl").
%%%----------------------------------------------------------------------
%%% API
@ -73,21 +74,60 @@ plain_password_required(Server) ->
M:plain_password_required()
end, auth_modules(Server)).
%% @doc Check if the user and password can login in server.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% true | false
check_password(User, Server, Password) ->
lists:any(
fun(M) ->
M:check_password(User, Server, Password)
end, auth_modules(Server)).
case check_password_with_authmodule(User, Server, Password) of
{true, _AuthModule} -> true;
false -> false
end.
check_password(User, Server, Password, StreamID, Digest) ->
lists:any(
fun(M) ->
M:check_password(User, Server, Password, StreamID, Digest)
end, auth_modules(Server)).
%% @doc Check if the user and password can login in server.
%% @spec (User::string(), Server::string(), Password::string(),
%% Digest::string(), DigestGen::function()) ->
%% true | false
check_password(User, Server, Password, Digest, DigestGen) ->
case check_password_with_authmodule(User, Server, Password,
Digest, DigestGen) of
{true, _AuthModule} -> true;
false -> false
end.
%% We do not allow empty password:
%% @doc Check if the user and password can login in server.
%% The user can login if at least an authentication method accepts the user
%% and the password.
%% The first authentication method that accepts the credentials is returned.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% {true, AuthModule} | false
%% where
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
%% | ejabberd_auth_internal | ejabberd_auth_ldap
%% | ejabberd_auth_odbc | ejabberd_auth_pam
check_password_with_authmodule(User, Server, Password) ->
check_password_loop(auth_modules(Server), [User, Server, Password]).
check_password_with_authmodule(User, Server, Password, Digest, DigestGen) ->
check_password_loop(auth_modules(Server), [User, Server, Password,
Digest, DigestGen]).
check_password_loop([], _Args) ->
false;
check_password_loop([AuthModule | AuthModules], Args) ->
case apply(AuthModule, check_password, Args) of
true ->
{true, AuthModule};
false ->
check_password_loop(AuthModules, Args)
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, ErrorType}
%% where ErrorType = empty_password | not_allowed | invalid_jid
set_password(_User, _Server, "") ->
{error, not_allowed};
%% We do not allow empty password
{error, empty_password};
set_password(User, Server, Password) ->
lists:foldl(
fun(M, {error, _}) ->
@ -96,8 +136,9 @@ set_password(User, Server, Password) ->
Res
end, {error, not_allowed}, auth_modules(Server)).
%% We do not allow empty password:
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, not_allowed}
try_register(_User, _Server, "") ->
%% We do not allow empty password
{error, not_allowed};
try_register(User, Server, Password) ->
case is_user_exists(User,Server) of
@ -106,12 +147,19 @@ try_register(User, Server, Password) ->
false ->
case lists:member(jlib:nameprep(Server), ?MYHOSTS) of
true ->
lists:foldl(
Res = lists:foldl(
fun(_M, {atomic, ok} = Res) ->
Res;
(M, _) ->
M:try_register(User, Server, Password)
end, {error, not_allowed}, auth_modules(Server));
end, {error, not_allowed}, auth_modules(Server)),
case Res of
{atomic, ok} ->
ejabberd_hooks:run(register_user, Server,
[User, Server]),
{atomic, ok};
_ -> Res
end;
false ->
{error, not_allowed}
end
@ -134,7 +182,13 @@ get_vh_registered_users(Server) ->
get_vh_registered_users(Server, Opts) ->
lists:flatmap(
fun(M) ->
M:get_vh_registered_users(Server, Opts)
case erlang:function_exported(
M, get_vh_registered_users, 2) of
true ->
M:get_vh_registered_users(Server, Opts);
false ->
M:get_vh_registered_users(Server)
end
end, auth_modules(Server)).
get_vh_registered_users_number(Server) ->
@ -163,6 +217,8 @@ get_vh_registered_users_number(Server, Opts) ->
end
end, auth_modules(Server))).
%% @doc Get the password of the user.
%% @spec (User::string(), Server::string()) -> Password::string()
get_password(User, Server) ->
lists:foldl(
fun(M, false) ->
@ -179,6 +235,17 @@ get_password_s(User, Server) ->
Password
end.
%% @doc Get the password of the user and the auth module.
%% @spec (User::string(), Server::string()) ->
%% {Password::string(), AuthModule::atom()} | {false, none}
get_password_with_authmodule(User, Server) ->
lists:foldl(
fun(M, {false, _}) ->
{M:get_password(User, Server), M};
(_M, {Password, AuthModule}) ->
{Password, AuthModule}
end, {false, none}, auth_modules(Server)).
%% 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) ->
@ -189,33 +256,59 @@ is_user_exists(User, Server) ->
%% Check if the user exists in all authentications module except the module
%% passed as parameter
%% @spec (Module::atom(), User, Server) -> true | false | maybe
is_user_exists_in_other_modules(Module, User, Server) ->
lists:any(
fun(M) ->
M:is_user_exists(User, Server)
end, auth_modules(Server)--[Module]).
is_user_exists_in_other_modules_loop(
auth_modules(Server)--[Module],
User, Server).
is_user_exists_in_other_modules_loop([], _User, _Server) ->
false;
is_user_exists_in_other_modules_loop([AuthModule|AuthModules], User, Server) ->
case AuthModule:is_user_exists(User, Server) of
true ->
true;
false ->
is_user_exists_in_other_modules_loop(AuthModules, User, Server);
{error, Error} ->
?DEBUG("The authentication module ~p returned an error~nwhen "
"checking user ~p in server ~p~nError message: ~p",
[AuthModule, User, Server, Error]),
maybe
end.
%% @spec (User, Server) -> ok | error | {error, not_allowed}
%% @doc Remove user.
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) ->
lists:foreach(
R = lists:foreach(
fun(M) ->
M:remove_user(User, Server)
end, auth_modules(Server)).
end, auth_modules(Server)),
case R of
ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]);
_ -> none
end,
R.
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request | error
%% @doc Try to remove user if the provided password is correct.
%% The removal is attempted in each auth method provided:
%% when one returns 'ok' the loop stops;
%% if no method returns 'ok' then it returns the error message indicated by the last method attempted.
remove_user(User, Server, Password) ->
lists:foreach(
fun(M) ->
R = lists:foldl(
fun(_M, ok = Res) ->
Res;
(M, _) ->
M:remove_user(User, Server, Password)
end, auth_modules(Server)).
end, error, auth_modules(Server)),
case R of
ok -> ejabberd_hooks:run(remove_user, jlib:nameprep(Server), [User, Server]);
_ -> none
end,
R.
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),
?PRINT("~s", [FUsers]),
{stop, ?STATUS_SUCCESS};
ctl_process_get_registered(Val, _Host, _Args) ->
Val.
%%%----------------------------------------------------------------------
%%% Internal functions

View File

@ -5,7 +5,7 @@
%%% Created : 17 Feb 2006 by Mickael Remond <mremond@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -141,11 +141,17 @@ remove_connection(SID, LUser, LServer) ->
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).
register_connection(SID, #jid{luser = LUser, lserver = LServer}, Info) ->
AuthModule = xml:get_attr_s(auth_module, Info),
case AuthModule == ?MODULE of
true ->
US = {LUser, LServer},
mnesia:sync_dirty(
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
end);
false ->
ok
end.
%% Remove an anonymous user from the anonymous users table
unregister_connection(SID, #jid{luser = LUser, lserver = LServer}, _) ->
@ -167,12 +173,15 @@ purge_hook(true, LUser, LServer) ->
%% before allowing access
check_password(User, Server, Password) ->
check_password(User, Server, Password, undefined, undefined).
check_password(User, Server, _Password, _StreamID, _Digest) ->
check_password(User, Server, _Password, _Digest, _DigestGen) ->
%% 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
%% If user exists in other module, reject anonnymous authentication
true -> false;
%% If we are not sure whether the user exists in other module, reject anon auth
maybe -> false;
false -> login(User, Server)
end.
@ -217,7 +226,7 @@ get_password(User, Server) ->
get_password(User, Server, "").
get_password(User, Server, DefaultValue) ->
case anonymous_user_exist(User, Server) of
case anonymous_user_exist(User, Server) or login(User, Server) of
%% We return the default value if the user is anonymous
true ->
DefaultValue;

View File

@ -5,7 +5,7 @@
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -57,11 +57,14 @@ plain_password_required() ->
check_password(User, Server, Password) ->
extauth:check_password(User, Server, Password) andalso Password /= "".
check_password(User, Server, Password, _StreamID, _Digest) ->
check_password(User, Server, Password, _Digest, _DigestGen) ->
check_password(User, Server, Password).
set_password(User, Server, Password) ->
extauth:set_password(User, Server, Password).
case extauth:set_password(User, Server, Password) of
true -> ok;
_ -> {error, unknown_problem}
end.
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
@ -80,8 +83,13 @@ get_password(_User, _Server) ->
get_password_s(_User, _Server) ->
"".
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
extauth:is_user_exists(User, Server).
try extauth:is_user_exists(User, Server) of
Res -> Res
catch
_:Error -> {error, Error}
end.
remove_user(_User, _Server) ->
{error, not_allowed}.

View File

@ -5,7 +5,7 @@
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -49,6 +49,7 @@
-include("ejabberd.hrl").
-record(passwd, {us, password}).
-record(reg_users_counter, {vhost, count}).
%%%----------------------------------------------------------------------
%%% API
@ -56,13 +57,19 @@
start(Host) ->
mnesia:create_table(passwd, [{disc_copies, [node()]},
{attributes, record_info(fields, passwd)}]),
mnesia:create_table(reg_users_counter,
[{ram_copies, [node()]},
{attributes, record_info(fields, reg_users_counter)}]),
update_table(),
ejabberd_ctl:register_commands(
Host,
[{"registered-users", "list all registered users"}],
ejabberd_auth, ctl_process_get_registered),
update_reg_users_counter_table(Host),
ok.
update_reg_users_counter_table(Server) ->
Set = get_vh_registered_users(Server),
Size = length(Set),
LServer = jlib:nameprep(Server),
set_vh_registered_users_counter(LServer, Size).
plain_password_required() ->
false.
@ -77,7 +84,7 @@ check_password(User, Server, Password) ->
false
end.
check_password(User, Server, Password, StreamID, Digest) ->
check_password(User, Server, Password, Digest, DigestGen) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
@ -85,7 +92,7 @@ check_password(User, Server, Password, StreamID, Digest) ->
[#passwd{password = Passwd}] ->
DigRes = if
Digest /= "" ->
Digest == sha:sha(StreamID ++ Passwd);
Digest == DigestGen(Passwd);
true ->
false
end,
@ -98,6 +105,8 @@ check_password(User, Server, Password, StreamID, Digest) ->
false
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
set_password(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
@ -110,9 +119,11 @@ set_password(User, Server, Password) ->
mnesia:write(#passwd{us = US,
password = Password})
end,
mnesia:transaction(F)
{atomic, ok} = mnesia:transaction(F),
ok
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {aborted, Reason}
try_register(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
@ -126,6 +137,7 @@ try_register(User, Server, Password) ->
[] ->
mnesia:write(#passwd{us = US,
password = Password}),
inc_vh_registered_users_counter(LServer),
ok;
[_E] ->
exists
@ -193,8 +205,17 @@ get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
Set = get_vh_registered_users(Server),
length(Set).
LServer = jlib:nameprep(Server),
Query = mnesia:dirty_select(
reg_users_counter,
[{#reg_users_counter{vhost = LServer, count = '$1'},
[],
['$1']}]),
case Query of
[Count] ->
Count;
_ -> 0
end.
get_vh_registered_users_number(Server, [{prefix, Prefix}]) when is_list(Prefix) ->
Set = [{U, S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
@ -203,6 +224,40 @@ get_vh_registered_users_number(Server, [{prefix, Prefix}]) when is_list(Prefix)
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
inc_vh_registered_users_counter(LServer) ->
F = fun() ->
case mnesia:wread({reg_users_counter, LServer}) of
[C] ->
Count = C#reg_users_counter.count + 1,
C2 = C#reg_users_counter{count = Count},
mnesia:write(C2);
_ ->
mnesia:write(#reg_users_counter{vhost = LServer,
count = 1})
end
end,
mnesia:sync_dirty(F).
dec_vh_registered_users_counter(LServer) ->
F = fun() ->
case mnesia:wread({reg_users_counter, LServer}) of
[C] ->
Count = C#reg_users_counter.count - 1,
C2 = C#reg_users_counter{count = Count},
mnesia:write(C2);
_ ->
error
end
end,
mnesia:sync_dirty(F).
set_vh_registered_users_counter(LServer, Count) ->
F = fun() ->
mnesia:write(#reg_users_counter{vhost = LServer,
count = Count})
end,
mnesia:sync_dirty(F).
get_password(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
@ -225,6 +280,7 @@ get_password_s(User, Server) ->
[]
end.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
@ -234,20 +290,26 @@ is_user_exists(User, Server) ->
false;
[_] ->
true;
_ ->
false
Other ->
{error, Other}
end.
%% @spec (User, Server) -> ok
%% @doc Remove user.
%% Note: it returns ok even if there was some problem removing the user.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
mnesia:delete({passwd, US})
mnesia:delete({passwd, US}),
dec_vh_registered_users_counter(LServer)
end,
mnesia:transaction(F),
ejabberd_hooks:run(remove_user, LServer, [User, Server]).
ok.
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
@ -256,6 +318,7 @@ remove_user(User, Server, Password) ->
case mnesia:read({passwd, US}) of
[#passwd{password = Password}] ->
mnesia:delete({passwd, US}),
dec_vh_registered_users_counter(LServer),
ok;
[_] ->
not_allowed;
@ -265,7 +328,6 @@ remove_user(User, Server, Password) ->
end,
case mnesia:transaction(F) of
{atomic, ok} ->
ejabberd_hooks:run(remove_user, LServer, [User, Server]),
ok;
{atomic, Res} ->
Res;

View File

@ -5,7 +5,7 @@
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -48,6 +48,7 @@
try_register/3,
dirty_get_registered_users/0,
get_vh_registered_users/1,
get_vh_registered_users_number/1,
get_password/2,
get_password_s/2,
is_user_exists/2,
@ -65,6 +66,7 @@
servers,
backups,
port,
encrypt,
dn,
password,
base,
@ -85,6 +87,10 @@ handle_info(_Info, State) ->
{noreply, State}.
%% -----
-define(LDAP_SEARCH_TIMEOUT, 5). % Timeout for LDAP search queries in seconds
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
@ -107,11 +113,8 @@ 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).
terminate(_Reason, _State) ->
ok.
init(Host) ->
State = parse_options(Host),
@ -120,17 +123,15 @@ init(Host) ->
State#state.backups,
State#state.port,
State#state.dn,
State#state.password),
State#state.password,
State#state.encrypt),
eldap_pool:start_link(State#state.bind_eldap_id,
State#state.servers,
State#state.backups,
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),
State#state.password,
State#state.encrypt),
{ok, State}.
plain_password_required() ->
@ -149,12 +150,13 @@ check_password(User, Server, Password) ->
end
end.
check_password(User, Server, Password, _StreamID, _Digest) ->
check_password(User, Server, Password, _Digest, _DigestGen) ->
check_password(User, Server, Password).
set_password(_User, _Server, _Password) ->
{error, not_allowed}.
%% @spec (User, Server, Password) -> {error, not_allowed}
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
@ -171,16 +173,20 @@ get_vh_registered_users(Server) ->
Result -> Result
end.
get_vh_registered_users_number(Server) ->
length(get_vh_registered_users(Server)).
get_password(_User, _Server) ->
false.
get_password_s(_User, _Server) ->
"".
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
case catch is_user_exists_ldap(User, Server) of
{'EXIT', _} ->
false;
{'EXIT', Error} ->
{error, Error};
Result ->
Result
end.
@ -216,6 +222,7 @@ get_vh_registered_users_ldap(Server) ->
{ok, EldapFilter} ->
case eldap_pool:search(Eldap_ID, [{base, State#state.base},
{filter, EldapFilter},
{timeout, ?LDAP_SEARCH_TIMEOUT},
{attributes, SortedDNAttrs}]) of
#eldap_search_result{entries = Entries} ->
lists:flatmap(
@ -350,8 +357,13 @@ parse_options(Host) ->
undefined -> [];
Backups -> Backups
end,
LDAPEncrypt = ejabberd_config:get_local_option({ldap_encrypt, Host}),
LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
undefined -> 389;
undefined -> case LDAPEncrypt of
tls -> ?LDAPS_PORT;
starttls -> ?LDAP_PORT;
_ -> ?LDAP_PORT
end;
P -> P
end,
RootDN = case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
@ -386,6 +398,7 @@ parse_options(Host) ->
servers = LDAPServers,
backups = LDAPBackups,
port = LDAPPort,
encrypt = LDAPEncrypt,
dn = RootDN,
password = Password,
base = LDAPBase,

View File

@ -5,7 +5,7 @@
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -51,16 +51,13 @@
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
ejabberd_ctl:register_commands(
Host,
[{"registered-users", "list all registered users"}],
ejabberd_auth, ctl_process_get_registered),
start(_Host) ->
ok.
plain_password_required() ->
false.
%% @spec (User, Server, Password) -> true | false | {error, Error}
check_password(User, Server, Password) ->
case jlib:nodeprep(User) of
error ->
@ -68,26 +65,35 @@ check_password(User, Server, Password) ->
LUser ->
Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server),
case catch odbc_queries:get_password(LServer, Username) of
try odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{Password}]} ->
Password /= "";
_ ->
false
Password /= ""; %% Password is correct, and not empty
{selected, ["password"], [{_Password2}]} ->
false; %% Password is not correct
{selected, ["password"], []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
catch
_:_ ->
false %% Typical error is database not accessible
end
end.
check_password(User, Server, Password, StreamID, Digest) ->
%% @spec (User, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
check_password(User, Server, Password, Digest, DigestGen) ->
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
try odbc_queries:get_password(LServer, Username) of
%% Account exists, check if password is valid
{selected, ["password"], [{Passwd}]} ->
DigRes = if
Digest /= "" ->
Digest == sha:sha(StreamID ++ Passwd);
Digest == DigestGen(Passwd);
true ->
false
end,
@ -96,11 +102,18 @@ check_password(User, Server, Password, StreamID, Digest) ->
true ->
(Passwd == Password) and (Password /= "")
end;
_ ->
false
{selected, ["password"], []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
catch
_:_ ->
false %% Typical error is database not accessible
end
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
set_password(User, Server, Password) ->
case jlib:nodeprep(User) of
error ->
@ -109,10 +122,14 @@ set_password(User, Server, Password) ->
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
LServer = jlib:nameprep(Server),
catch odbc_queries:set_password_t(LServer, Username, Pass)
case catch odbc_queries:set_password_t(LServer, Username, Pass) of
{atomic, ok} -> ok;
Other -> {error, Other}
end
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
try_register(User, Server, Password) ->
case jlib:nodeprep(User) of
error ->
@ -202,6 +219,7 @@ get_password_s(User, Server) ->
end
end.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
case jlib:nodeprep(User) of
error ->
@ -209,14 +227,22 @@ is_user_exists(User, Server) ->
LUser ->
Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server),
case catch odbc_queries:get_password(LServer, Username) of
try odbc_queries:get_password(LServer, Username) of
{selected, ["password"], [{_Password}]} ->
true;
_ ->
false
true; %% Account exists
{selected, ["password"], []} ->
false; %% Account does not exist
{error, Error} ->
{error, Error} %% Typical error is that table doesn't exist
catch
_:B ->
{error, B} %% Typical error is database not accessible
end
end.
%% @spec (User, Server) -> ok | error
%% @doc Remove user.
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) ->
case jlib:nodeprep(User) of
error ->
@ -225,10 +251,11 @@ remove_user(User, Server) ->
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])
ok
end.
%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
case jlib:nodeprep(User) of
error ->
@ -242,8 +269,6 @@ remove_user(User, Server, 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;

View File

@ -5,7 +5,7 @@
%%% Created : 5 Jul 2007 by Evgeniy Khramtsov <xram@jabber.ru>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -55,7 +55,7 @@ start(_Host) ->
set_password(_User, _Server, _Password) ->
{error, not_allowed}.
check_password(User, Server, Password, _StreamID, _Digest) ->
check_password(User, Server, Password, _Digest, _DigestGen) ->
check_password(User, Server, Password).
check_password(User, Host, Password) ->
@ -80,6 +80,8 @@ get_password(_User, _Server) ->
get_password_s(_User, _Server) ->
"".
%% @spec (User, Server) -> true | false | {error, Error}
%% TODO: Improve this function to return an error instead of 'false' when connection to PAM failed
is_user_exists(User, Host) ->
Service = get_pam_service(Host),
case catch epam:acct_mgmt(Service, User) of
@ -91,7 +93,7 @@ remove_user(_User, _Server) ->
{error, not_allowed}.
remove_user(_User, _Server, _Password) ->
{error, not_allowed}.
not_allowed.
plain_password_required() ->
true.

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
%%% Created : 2 Nov 2007 by Mickael Remond <mremond@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -17,7 +17,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA

413
src/ejabberd_captcha.erl Normal file
View File

@ -0,0 +1,413 @@
%%%-------------------------------------------------------------------
%%% File : ejabberd_captcha.erl
%%% Author : Evgeniy Khramtsov <xramtsov@gmail.com>
%%% Purpose : CAPTCHA processing.
%%% Created : 26 Apr 2008 by Evgeniy Khramtsov <xramtsov@gmail.com>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%-------------------------------------------------------------------
-module(ejabberd_captcha).
-behaviour(gen_server).
%% API
-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([create_captcha/6, build_captcha_html/2, check_captcha/2,
process_reply/1, process/2, is_feature_available/0]).
-include("jlib.hrl").
-include("ejabberd.hrl").
-include("web/ejabberd_http.hrl").
-define(VFIELD(Type, Var, Value),
{xmlelement, "field", [{"type", Type}, {"var", Var}],
[{xmlelement, "value", [], [Value]}]}).
-define(CAPTCHA_TEXT(Lang), translate:translate(Lang, "Enter the text you see")).
-define(CAPTCHA_LIFETIME, 120000). % two minutes
-record(state, {}).
-record(captcha, {id, pid, key, tref, args}).
-define(T(S),
case catch mnesia:transaction(fun() -> S end) of
{atomic, Res} ->
Res;
{_, Reason} ->
?ERROR_MSG("mnesia transaction failed: ~p", [Reason]),
{error, Reason}
end).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
create_captcha(Id, SID, From, To, Lang, Args)
when is_list(Id), is_list(Lang), is_list(SID),
is_record(From, jid), is_record(To, jid) ->
case create_image() of
{ok, Type, Key, Image} ->
B64Image = jlib:encode_base64(binary_to_list(Image)),
JID = jlib:jid_to_string(From),
CID = "sha1+" ++ sha:sha(Image) ++ "@bob.xmpp.org",
Data = {xmlelement, "data",
[{"xmlns", ?NS_BOB}, {"cid", CID},
{"max-age", "0"}, {"type", Type}],
[{xmlcdata, B64Image}]},
Captcha =
{xmlelement, "captcha", [{"xmlns", ?NS_CAPTCHA}],
[{xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
[?VFIELD("hidden", "FORM_TYPE", {xmlcdata, ?NS_CAPTCHA}),
?VFIELD("hidden", "from", {xmlcdata, jlib:jid_to_string(To)}),
?VFIELD("hidden", "challenge", {xmlcdata, Id}),
?VFIELD("hidden", "sid", {xmlcdata, SID}),
{xmlelement, "field", [{"var", "ocr"}, {"label", ?CAPTCHA_TEXT(Lang)}],
[{xmlelement, "media", [{"xmlns", ?NS_MEDIA}],
[{xmlelement, "uri", [{"type", Type}],
[{xmlcdata, "cid:" ++ CID}]}]}]}]}]},
BodyString1 = translate:translate(Lang, "Your messages to ~s are being blocked. To unblock them, visit ~s"),
BodyString = io_lib:format(BodyString1, [JID, get_url(Id)]),
Body = {xmlelement, "body", [],
[{xmlcdata, BodyString}]},
OOB = {xmlelement, "x", [{"xmlns", ?NS_OOB}],
[{xmlelement, "url", [], [{xmlcdata, get_url(Id)}]}]},
Tref = erlang:send_after(?CAPTCHA_LIFETIME, ?MODULE, {remove_id, Id}),
case ?T(mnesia:write(#captcha{id=Id, pid=self(), key=Key,
tref=Tref, args=Args})) of
ok ->
{ok, [Body, OOB, Captcha, Data]};
_Err ->
error
end;
_Err ->
error
end.
%% @spec (Id::string(), Lang::string()) -> {FormEl, {ImgEl, TextEl, IdEl, KeyEl}} | captcha_not_found
%% where FormEl = xmlelement()
%% ImgEl = xmlelement()
%% TextEl = xmlelement()
%% IdEl = xmlelement()
%% KeyEl = xmlelement()
build_captcha_html(Id, Lang) ->
case mnesia:dirty_read(captcha, Id) of
[#captcha{}] ->
ImgEl = {xmlelement, "img", [{"src", get_url(Id ++ "/image")}], []},
TextEl = {xmlcdata, ?CAPTCHA_TEXT(Lang)},
IdEl = {xmlelement, "input", [{"type", "hidden"},
{"name", "id"},
{"value", Id}], []},
KeyEl = {xmlelement, "input", [{"type", "text"},
{"name", "key"},
{"size", "10"}], []},
FormEl = {xmlelement, "form", [{"action", get_url(Id)},
{"name", "captcha"},
{"method", "POST"}],
[ImgEl,
{xmlelement, "br", [], []},
TextEl,
{xmlelement, "br", [], []},
IdEl,
KeyEl,
{xmlelement, "br", [], []},
{xmlelement, "input", [{"type", "submit"},
{"name", "enter"},
{"value", "OK"}], []}
]},
{FormEl, {ImgEl, TextEl, IdEl, KeyEl}};
_ ->
captcha_not_found
end.
%% @spec (Id::string(), ProvidedKey::string()) -> captcha_valid | captcha_non_valid | captcha_not_found
check_captcha(Id, ProvidedKey) ->
?T(case mnesia:read(captcha, Id, write) of
[#captcha{pid=Pid, args=Args, key=StoredKey, tref=Tref}] ->
mnesia:delete({captcha, Id}),
erlang:cancel_timer(Tref),
if StoredKey == ProvidedKey ->
Pid ! {captcha_succeed, Args},
captcha_valid;
true ->
Pid ! {captcha_failed, Args},
captcha_non_valid
end;
_ ->
captcha_not_found
end).
process_reply({xmlelement, "captcha", _, _} = El) ->
case xml:get_subtag(El, "x") of
false ->
{error, malformed};
Xdata ->
Fields = jlib:parse_xdata_submit(Xdata),
case {proplists:get_value("challenge", Fields),
proplists:get_value("ocr", Fields)} of
{[Id|_], [OCR|_]} ->
?T(case mnesia:read(captcha, Id, write) of
[#captcha{pid=Pid, args=Args, key=Key, tref=Tref}] ->
mnesia:delete({captcha, Id}),
erlang:cancel_timer(Tref),
if OCR == Key ->
Pid ! {captcha_succeed, Args},
ok;
true ->
Pid ! {captcha_failed, Args},
{error, bad_match}
end;
_ ->
{error, not_found}
end);
_ ->
{error, malformed}
end
end;
process_reply(_) ->
{error, malformed}.
process(_Handlers, #request{method='GET', lang=Lang, path=[_, Id]}) ->
case build_captcha_html(Id, Lang) of
{FormEl, _} when is_tuple(FormEl) ->
Form =
{xmlelement, "div", [{"align", "center"}],
[FormEl]},
ejabberd_web:make_xhtml([Form]);
captcha_not_found ->
ejabberd_web:error(not_found)
end;
process(_Handlers, #request{method='GET', path=[_, Id, "image"]}) ->
case mnesia:dirty_read(captcha, Id) of
[#captcha{key=Key}] ->
case create_image(Key) of
{ok, Type, _, Img} ->
{200,
[{"Content-Type", Type},
{"Cache-Control", "no-cache"},
{"Last-Modified", httpd_util:rfc1123_date()}],
Img};
_ ->
ejabberd_web:error(not_found)
end;
_ ->
ejabberd_web:error(not_found)
end;
process(_Handlers, #request{method='POST', q=Q, lang=Lang, path=[_, Id]}) ->
ProvidedKey = proplists:get_value("key", Q, none),
case check_captcha(Id, ProvidedKey) of
captcha_valid ->
Form =
{xmlelement, "p", [],
[{xmlcdata,
translate:translate(Lang, "The captcha is valid.")
}]},
ejabberd_web:make_xhtml([Form]);
captcha_non_valid ->
ejabberd_web:error(not_allowed);
captcha_not_found ->
ejabberd_web:error(not_found)
end;
process(_Handlers, _Request) ->
ejabberd_web:error(not_found).
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([]) ->
mnesia:create_table(captcha,
[{ram_copies, [node()]},
{attributes, record_info(fields, captcha)}]),
mnesia:add_table_copy(captcha, node(), ram_copies),
check_captcha_setup(),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
{reply, bad_request, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({remove_id, Id}, State) ->
?DEBUG("captcha ~p timed out", [Id]),
_ = ?T(case mnesia:read(captcha, Id, write) of
[#captcha{args=Args, pid=Pid}] ->
Pid ! {captcha_failed, Args},
mnesia:delete({captcha, Id});
_ ->
ok
end),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
%% Function: create_image() -> {ok, Type, Key, Image} | {error, Reason}
%% Type = "image/png" | "image/jpeg" | "image/gif"
%% Key = string()
%% Image = binary()
%% Reason = atom()
%%--------------------------------------------------------------------
create_image() ->
%% Six numbers from 1 to 9.
Key = string:substr(randoms:get_string(), 1, 6),
create_image(Key).
create_image(Key) ->
FileName = get_prog_name(),
Cmd = lists:flatten(io_lib:format("~s ~s", [FileName, Key])),
case cmd(Cmd) of
{ok, <<16#89, $P, $N, $G, $\r, $\n, 16#1a, $\n, _/binary>> = Img} ->
{ok, "image/png", Key, Img};
{ok, <<16#ff, 16#d8, _/binary>> = Img} ->
{ok, "image/jpeg", Key, Img};
{ok, <<$G, $I, $F, $8, X, $a, _/binary>> = Img} when X==$7; X==$9 ->
{ok, "image/gif", Key, Img};
{error, enodata = Reason} ->
?ERROR_MSG("Failed to process output from \"~s\". "
"Maybe ImageMagick's Convert program is not installed.",
[Cmd]),
{error, Reason};
{error, Reason} ->
?ERROR_MSG("Failed to process an output from \"~s\": ~p",
[Cmd, Reason]),
{error, Reason};
_ ->
Reason = malformed_image,
?ERROR_MSG("Failed to process an output from \"~s\": ~p",
[Cmd, Reason]),
{error, Reason}
end.
get_prog_name() ->
case ejabberd_config:get_local_option(captcha_cmd) of
FileName when is_list(FileName) ->
FileName;
_ ->
""
end.
get_url(Str) ->
case ejabberd_config:get_local_option(captcha_host) of
Host when is_list(Host) ->
"http://" ++ Host ++ "/captcha/" ++ Str;
_ ->
"http://" ++ ?MYNAME ++ "/captcha/" ++ Str
end.
%%--------------------------------------------------------------------
%% Function: cmd(Cmd) -> Data | {error, Reason}
%% Cmd = string()
%% Data = binary()
%% Description: os:cmd/1 replacement
%%--------------------------------------------------------------------
-define(CMD_TIMEOUT, 5000).
-define(MAX_FILE_SIZE, 64*1024).
cmd(Cmd) ->
Port = open_port({spawn, Cmd}, [stream, eof, binary]),
TRef = erlang:start_timer(?CMD_TIMEOUT, self(), timeout),
recv_data(Port, TRef, <<>>).
recv_data(Port, TRef, Buf) ->
receive
{Port, {data, Bytes}} ->
NewBuf = <<Buf/binary, Bytes/binary>>,
if size(NewBuf) > ?MAX_FILE_SIZE ->
return(Port, TRef, {error, efbig});
true ->
recv_data(Port, TRef, NewBuf)
end;
{Port, {data, _}} ->
return(Port, TRef, {error, efbig});
{Port, eof} when Buf /= <<>> ->
return(Port, TRef, {ok, Buf});
{Port, eof} ->
return(Port, TRef, {error, enodata});
{timeout, TRef, _} ->
return(Port, TRef, {error, timeout})
end.
return(Port, TRef, Result) ->
case erlang:cancel_timer(TRef) of
false ->
receive
{timeout, TRef, _} ->
ok
after 0 ->
ok
end;
_ ->
ok
end,
catch port_close(Port),
Result.
is_feature_enabled() ->
case get_prog_name() of
"" -> false;
Prog when is_list(Prog) -> true
end.
is_feature_available() ->
case is_feature_enabled() of
false -> false;
true ->
case create_image() of
{ok, _, _, _} -> true;
_Error -> false
end
end.
check_captcha_setup() ->
case is_feature_enabled() andalso not is_feature_available() of
true ->
?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
"but it can't generate images.", []);
false ->
ok
end.

View File

@ -5,7 +5,7 @@
%%% Created : 27 Feb 2008 by Mickael Remond <mremond@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -39,19 +39,21 @@
libs() ->
ok.
%% Consistency check on ejabberd configuration
%% @doc Consistency check on ejabberd configuration
config() ->
check_database_modules().
check_database_modules() ->
[check_database_module(M)||M<-get_db_used()].
check_database_module(odbc) ->
check_modules(odbc, [odbc, odbc_app, odbc_sup, ejabberd_odbc, ejabberd_odbc_sup, odbc_queries]);
check_database_module(mysql) ->
check_modules(mysql, [mysql, mysql_auth, mysql_conn, mysql_recv]);
check_database_module(pgsql) ->
check_modules(pgsql, [pgsql, pgsql_proto, pgsql_tcp, pgsql_util]).
%% Issue a critical error and throw an exit if needing module is
%% @doc Issue a critical error and throw an exit if needing module is
%% missing.
check_modules(DB, Modules) ->
case get_missing_modules(Modules) of
@ -63,7 +65,7 @@ check_modules(DB, Modules) ->
end.
%% Return the list of undefined modules
%% @doc Return the list of undefined modules
get_missing_modules(Modules) ->
lists:filter(fun(Module) ->
case catch Module:module_info() of
@ -73,7 +75,7 @@ get_missing_modules(Modules) ->
end
end, Modules).
%% Return the list of databases used
%% @doc Return the list of databases used
get_db_used() ->
%% Retrieve domains with a database configured:
Domains =
@ -86,14 +88,22 @@ get_db_used() ->
case check_odbc_option(
ejabberd_config:get_local_option(
{auth_method, Domain})) of
true -> [element(1, DB)|Acc];
true -> [get_db_type(DB)|Acc];
_ -> Acc
end
end,
[], Domains),
lists:usort(DBs).
%% Return true if odbc option is used
%% @doc Depending in the DB definition, return which type of DB this is.
%% Note that MSSQL is detected as ODBC.
%% @spec (DB) -> mysql | pgsql | odbc
get_db_type(DB) when is_tuple(DB) ->
element(1, DB);
get_db_type(DB) when is_list(DB) ->
odbc.
%% @doc Return true if odbc option is used
check_odbc_option(odbc) ->
true;
check_odbc_option(AuthMethods) when is_list(AuthMethods) ->

427
src/ejabberd_commands.erl Normal file
View File

@ -0,0 +1,427 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_commands.erl
%%% Author : Badlop <badlop@process-one.net>
%%% Purpose : Management of ejabberd commands
%%% Created : 20 May 2008 by Badlop <badlop@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
%%% @headerfile "ejabberd_commands.hrl"
%%% @doc Management of ejabberd commands.
%%%
%%% An ejabberd command is an abstract function identified by a name,
%%% with a defined number and type of calling arguments and type of
%%% result, that can be defined in any Erlang module and executed
%%% using any valid frontend.
%%%
%%%
%%% == Define a new ejabberd command ==
%%%
%%% ejabberd commands can be defined and registered in
%%% any Erlang module.
%%%
%%% Some commands are procedures; and their purpose is to perform an
%%% action in the server, so the command result is only some result
%%% code or result tuple. Other commands are inspectors, and their
%%% purpose is to gather some information about the server and return
%%% a detailed response: it can be integer, string, atom, tuple, list
%%% or a mix of those ones.
%%%
%%% The arguments and result of an ejabberd command are strictly
%%% defined. The number and format of the arguments provided when
%%% calling an ejabberd command must match the definition of that
%%% command. The format of the result provided by an ejabberd command
%%% must be exactly its definition. For example, if a command is said
%%% to return an integer, it must always return an integer (except in
%%% case of a crash).
%%%
%%% If you are developing an Erlang module that will run inside
%%% ejabberd and you want to provide a new ejabberd command to
%%% administer some task related to your module, you only need to:
%%% implement a function, define the command, and register it.
%%%
%%%
%%% === Define a new ejabberd command ===
%%%
%%% An ejabberd command is defined using the Erlang record
%%% 'ejabberd_commands'. This record has several elements that you
%%% must define. Note that 'tags', 'desc' and 'longdesc' are optional.
%%%
%%% For example let's define an ejabberd command 'pow' that gets the
%%% integers 'base' and 'exponent'. Its result will be an integer
%%% 'power':
%%%
%%% <pre>#ejabberd_commands{name = pow, tags = [test],
%%% desc = "Return the power of base for exponent",
%%% longdesc = "This is an example command. The formula is:\n"
%%% " power = base ^ exponent",
%%% module = ?MODULE, function = pow,
%%% args = [{base, integer}, {exponent, integer}],
%%% result = {power, integer}}</pre>
%%%
%%%
%%% === Implement the function associated to the command ===
%%%
%%% Now implement a function in your module that matches the arguments
%%% and result of the ejabberd command.
%%%
%%% For example the function calc_power gets two integers Base and
%%% Exponent. It calculates the power and rounds to an integer:
%%%
%%% <pre>calc_power(Base, Exponent) ->
%%% PowFloat = math:pow(Base, Exponent),
%%% round(PowFloat).</pre>
%%%
%%% Since this function will be called by ejabberd_commands, it must be exported.
%%% Add to your module:
%%% <pre>-export([calc_power/2]).</pre>
%%%
%%% Only some types of result formats are allowed.
%%% If the format is defined as 'rescode', then your function must return:
%%% ok | true | atom()
%%% where the atoms ok and true as considered positive answers,
%%% and any other response atom is considered negative.
%%%
%%% If the format is defined as 'restuple', then the command must return:
%%% {rescode(), string()}
%%%
%%% If the format is defined as '{list, something()}', then the command
%%% must return a list of something().
%%%
%%%
%%% === Register the command ===
%%%
%%% Define this function and put inside the #ejabberd_command you
%%% defined in the beginning:
%%%
%%% <pre>commands() ->
%%% [
%%%
%%% ].</pre>
%%%
%%% You need to include this header file in order to use the record:
%%%
%%% <pre>-include("ejabberd_commands.hrl").</pre>
%%%
%%% When your module is initialized or started, register your commands:
%%%
%%% <pre>ejabberd_commands:register_commands(commands()),</pre>
%%%
%%% And when your module is stopped, unregister your commands:
%%%
%%% <pre>ejabberd_commands:unregister_commands(commands()),</pre>
%%%
%%% That's all! Now when your module is started, the command will be
%%% registered and any frontend can access it. For example:
%%%
%%% <pre>$ ejabberdctl help pow
%%%
%%% Command Name: pow
%%%
%%% Arguments: base::integer
%%% exponent::integer
%%%
%%% Returns: power::integer
%%%
%%% Tags: test
%%%
%%% Description: Return the power of base for exponent
%%%
%%% This is an example command. The formula is:
%%% power = base ^ exponent
%%%
%%% $ ejabberdctl pow 3 4
%%% 81
%%% </pre>
%%%
%%%
%%% == Execute an ejabberd command ==
%%%
%%% ejabberd commands are mean to be executed using any valid
%%% frontend. An ejabberd commands is implemented in a regular Erlang
%%% function, so it is also possible to execute this function in any
%%% Erlang module, without dealing with the associated ejabberd
%%% commands.
%%%
%%%
%%% == Frontend to ejabberd commands ==
%%%
%%% Currently there are two frontends to ejabberd commands: the shell
%%% script {@link ejabberd_ctl. ejabberdctl}, and the XML-RPC server
%%% ejabberd_xmlrpc.
%%%
%%%
%%% === ejabberdctl as a frontend to ejabberd commands ===
%%%
%%% It is possible to use ejabberdctl to get documentation of any
%%% command. But ejabberdctl does not support all the argument types
%%% allowed in ejabberd commands, so there are some ejabberd commands
%%% that cannot be executed using ejabberdctl.
%%%
%%% Also note that the ejabberdctl shell administration script also
%%% manages ejabberdctl commands, which are unrelated to ejabberd
%%% commands and can only be executed using ejabberdctl.
%%%
%%%
%%% === ejabberd_xmlrpc as a frontend to ejabberd commands ===
%%%
%%% ejabberd_xmlrpc provides an XML-RPC server to execute ejabberd commands.
%%% ejabberd_xmlrpc is a contributed module published in ejabberd-modules SVN.
%%%
%%% Since ejabberd_xmlrpc does not provide any method to get documentation
%%% of the ejabberd commands, please use ejabberdctl to know which
%%% commands are available, and their usage.
%%%
%%% The number and format of the arguments provided when calling an
%%% ejabberd command must match the definition of that command. Please
%%% make sure the XML-RPC call provides the required arguments, with
%%% the specified format. The order of the arguments in an XML-RPC
%%% call is not important, because all the data is tagged and will be
%%% correctly prepared by ejabberd_xmlrpc before executing the ejabberd
%%% command.
%%% TODO: consider this feature:
%%% All commands are catched. If an error happens, return the restuple:
%%% {error, flattened error string}
%%% This means that ecomm call APIs (ejabberd_ctl, ejabberd_xmlrpc) need to allows this.
%%% And ejabberd_xmlrpc must be prepared to handle such an unexpected response.
-module(ejabberd_commands).
-author('badlop@process-one.net').
-export([init/0,
list_commands/0,
get_command_format/1,
get_command_definition/1,
get_tags_commands/0,
register_commands/1,
unregister_commands/1,
execute_command/2,
execute_command/4
]).
-include("ejabberd_commands.hrl").
-include("ejabberd.hrl").
init() ->
ets:new(ejabberd_commands, [named_table, set, public,
{keypos, #ejabberd_commands.name}]).
%% @spec ([ejabberd_commands()]) -> ok
%% @doc Register ejabberd commands.
%% If a command is already registered, a warning is printed and the old command is preserved.
register_commands(Commands) ->
lists:foreach(
fun(Command) ->
case ets:insert_new(ejabberd_commands, Command) of
true ->
ok;
false ->
?DEBUG("This command is already defined:~n~p", [Command])
end
end,
Commands).
%% @spec ([ejabberd_commands()]) -> ok
%% @doc Unregister ejabberd commands.
unregister_commands(Commands) ->
lists:foreach(
fun(Command) ->
ets:delete_object(ejabberd_commands, Command)
end,
Commands).
%% @spec () -> [{Name::atom(), Args::[aterm()], Desc::string()}]
%% @doc Get a list of all the available commands, arguments and description.
list_commands() ->
Commands = ets:match(ejabberd_commands,
#ejabberd_commands{name = '$1',
args = '$2',
desc = '$3',
_ = '_'}),
[{A, B, C} || [A, B, C] <- Commands].
%% @spec (Name::atom()) -> {Args::[aterm()], Result::rterm()} | {error, command_unknown}
%% @doc Get the format of arguments and result of a command.
get_command_format(Name) ->
Matched = ets:match(ejabberd_commands,
#ejabberd_commands{name = Name,
args = '$1',
result = '$2',
_ = '_'}),
case Matched of
[] ->
{error, command_unknown};
[[Args, Result]] ->
{Args, Result}
end.
%% @spec (Name::atom()) -> ejabberd_commands() | command_not_found
%% @doc Get the definition record of a command.
get_command_definition(Name) ->
case ets:lookup(ejabberd_commands, Name) of
[E] -> E;
[] -> command_not_found
end.
%% @spec (Name::atom(), Arguments) -> ResultTerm | {error, command_unknown}
%% @doc Execute a command.
execute_command(Name, Arguments) ->
execute_command([], noauth, Name, Arguments).
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) -> ResultTerm | {error, Error}
%% where
%% AccessCommands = [{Access, CommandNames, Arguments}]
%% Auth = {User::string(), Server::string(), Password::string()} | noauth
%% Method = atom()
%% Arguments = [any()]
%% Error = command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
execute_command(AccessCommands, Auth, Name, Arguments) ->
case ets:lookup(ejabberd_commands, Name) of
[Command] ->
try check_access_commands(AccessCommands, Auth, Name, Command, Arguments) of
ok -> execute_command2(Command, Arguments)
catch
{error, Error} -> {error, Error}
end;
[] -> {error, command_unknown}
end.
execute_command2(Command, Arguments) ->
Module = Command#ejabberd_commands.module,
Function = Command#ejabberd_commands.function,
?DEBUG("Executing command ~p:~p with Args=~p", [Module, Function, Arguments]),
apply(Module, Function, Arguments).
%% @spec () -> [{Tag::string(), [CommandName::string()]}]
%% @doc Get all the tags and associated commands.
get_tags_commands() ->
CommandTags = ets:match(ejabberd_commands,
#ejabberd_commands{
name = '$1',
tags = '$2',
_ = '_'}),
Dict = lists:foldl(
fun([CommandNameAtom, CTags], D) ->
CommandName = atom_to_list(CommandNameAtom),
case CTags of
[] ->
orddict:append("untagged", CommandName, D);
_ ->
lists:foldl(
fun(TagAtom, DD) ->
Tag = atom_to_list(TagAtom),
orddict:append(Tag, CommandName, DD)
end,
D,
CTags)
end
end,
orddict:new(),
CommandTags),
orddict:to_list(Dict).
%% -----------------------------
%% Access verification
%% -----------------------------
%% @spec (AccessCommands, Auth, Method, Command, Arguments) -> ok
%% where
%% AccessCommands = [ {Access, CommandNames, Arguments} ]
%% Auth = {User::string(), Server::string(), Password::string()} | noauth
%% Method = atom()
%% Arguments = [any()]
%% @doc Check access is allowed to that command.
%% At least one AccessCommand must be satisfied.
%% It may throw {error, Error} where:
%% Error = account_unprivileged | invalid_account_data | no_auth_provided
check_access_commands([], _Auth, _Method, _Command, _Arguments) ->
ok;
check_access_commands(AccessCommands, Auth, Method, Command, Arguments) ->
{ok, User, Server} = check_auth(Auth),
AccessCommandsAllowed =
lists:filter(
fun({Access, Commands, ArgumentRestrictions}) ->
case check_access(Access, User, Server) of
true ->
check_access_command(Commands, Command, ArgumentRestrictions,
Method, Arguments);
false ->
false
end
end,
AccessCommands),
case AccessCommandsAllowed of
[] -> throw({error, account_unprivileged});
L when is_list(L) -> ok
end.
check_auth(noauth) ->
throw({error, no_auth_provided});
check_auth({User, Server, Password}) ->
%% Check the account exists and password is valid
AccountPass = ejabberd_auth:get_password_s(User, Server),
AccountPassMD5 = get_md5(AccountPass),
case Password of
AccountPass -> {ok, User, Server};
AccountPassMD5 -> {ok, User, Server};
_ -> throw({error, invalid_account_data})
end.
get_md5(AccountPass) ->
lists:flatten([io_lib:format("~.16B", [X])
|| X <- binary_to_list(crypto:md5(AccountPass))]).
check_access(Access, User, Server) ->
%% Check this user has access permission
case acl:match_rule(Server, Access, jlib:make_jid(User, Server, "")) of
allow -> true;
deny -> false
end.
check_access_command(Commands, Command, ArgumentRestrictions, Method, Arguments) ->
case Commands==all orelse lists:member(Method, Commands) of
true -> check_access_arguments(Command, ArgumentRestrictions, Arguments);
false -> false
end.
check_access_arguments(Command, ArgumentRestrictions, Arguments) ->
ArgumentsTagged = tag_arguments(Command#ejabberd_commands.args, Arguments),
lists:all(
fun({ArgName, ArgAllowedValue}) ->
%% If the call uses the argument, check the value is acceptable
case lists:keysearch(ArgName, 1, ArgumentsTagged) of
{value, {ArgName, ArgValue}} -> ArgValue == ArgAllowedValue;
false -> true
end
end, ArgumentRestrictions).
tag_arguments(ArgsDefs, Args) ->
lists:zipwith(
fun({ArgName, _ArgType}, ArgValue) ->
{ArgName, ArgValue}
end,
ArgsDefs,
Args).

52
src/ejabberd_commands.hrl Normal file
View File

@ -0,0 +1,52 @@
%%%----------------------------------------------------------------------
%%%
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-record(ejabberd_commands, {name, tags = [],
desc = "", longdesc = "",
module, function,
args = [], result = rescode}).
%% @type ejabberd_commands() = #ejabberd_commands{
%% name = atom(),
%% tags = [atom()],
%% desc = string(),
%% longdesc = string(),
%% module = atom(),
%% function = atom(),
%% args = [aterm()],
%% result = rterm()
%% }.
%% desc: Description of the command
%% args: Describe the accepted arguments.
%% This way the function that calls the command can format the
%% arguments before calling.
%% @type atype() = integer | string | {tuple, [aterm()]} | {list, aterm()}.
%% Allowed types for arguments are integer, string, tuple and list.
%% @type rtype() = integer | string | atom | {tuple, [rterm()]} | {list, rterm()} | rescode | restuple.
%% A rtype is either an atom or a tuple with two elements.
%% @type aterm() = {Name::atom(), Type::atype()}.
%% An argument term is a tuple with the term name and the term type.
%% @type rterm() = {Name::atom(), Type::rtype()}.
%% A result term is a tuple with the term name and the term type.

View File

@ -5,7 +5,7 @@
%%% Created : 14 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -31,9 +31,11 @@
add_global_option/2, add_local_option/2,
get_global_option/1, get_local_option/1]).
-export([get_vh_by_auth_method/1]).
-export([is_file_readable/1]).
-include("ejabberd.hrl").
-include("ejabberd_config.hrl").
-include_lib("kernel/include/file.hrl").
%% @type macro() = {macro_key(), macro_value()}
@ -55,7 +57,10 @@ start() ->
{attributes, record_info(fields, local_config)}]),
mnesia:add_table_copy(local_config, node(), ram_copies),
Config = get_ejabberd_config_path(),
load_file(Config).
load_file(Config),
%% This start time is used by mod_last:
add_local_option(node_start, now()),
ok.
%% @doc Get the filename of the ejabberd configuration file.
%% The filename can be specified with: erl -config "/path/to/ejabberd.cfg".
@ -76,7 +81,8 @@ get_ejabberd_config_path() ->
%% @doc Load the ejabberd configuration file.
%% It also includes additional configuration files and replaces macros.
%% @spec (File::string()) -> [term()]
%% This function will crash if finds some error in the configuration file.
%% @spec (File::string()) -> ok
load_file(File) ->
Terms = get_plain_terms_file(File),
State = lists:foldl(fun search_hosts/2, #state{}, Terms),
@ -95,9 +101,15 @@ get_plain_terms_file(File1) ->
case file:consult(File) of
{ok, Terms} ->
include_config_files(Terms);
{error, {_LineNumber, erl_parse, _ParseMessage} = Reason} ->
ExitText = lists:flatten(File ++ " approximately in the line "
++ file:format_error(Reason)),
?ERROR_MSG("Problem loading ejabberd config file ~n~s", [ExitText]),
exit(ExitText);
{error, Reason} ->
?ERROR_MSG("Can't load config file ~p: ~p", [File, Reason]),
exit(File ++ ": " ++ file:format_error(Reason))
ExitText = lists:flatten(File ++ ": " ++ file:format_error(Reason)),
?ERROR_MSG("Problem loading ejabberd config file ~n~s", [ExitText]),
exit(ExitText)
end.
%% @doc Convert configuration filename to absolute path.
@ -239,18 +251,18 @@ split_terms_macros(Terms) ->
lists:foldl(
fun(Term, {TOs, Ms}) ->
case Term of
{define_macro, Key, Value} ->
case is_atom(Key) and is_all_uppercase(Key) of
true ->
{TOs, Ms++[{Key, Value}]};
false ->
exit({macro_not_properly_defined, Term})
end;
Term ->
{TOs ++ [Term], Ms}
{define_macro, Key, Value} ->
case is_atom(Key) and is_all_uppercase(Key) of
true ->
{TOs, Ms++[{Key, Value}]};
false ->
exit({macro_not_properly_defined, Term})
end;
Term ->
{TOs ++ [Term], Ms}
end
end,
{[], []},
{[], []},
Terms).
%% @doc Recursively replace in Terms macro usages with the defined value.
@ -263,15 +275,15 @@ replace([Term|Terms], Macros) ->
[replace_term(Term, Macros) | replace(Terms, Macros)].
replace_term(Key, Macros) when is_atom(Key) ->
case is_all_uppercase(Key) of
true ->
case proplists:get_value(Key, Macros) of
undefined -> exit({undefined_macro, Key});
Value -> Value
end;
false ->
Key
end;
case is_all_uppercase(Key) of
true ->
case proplists:get_value(Key, Macros) of
undefined -> exit({undefined_macro, Key});
Value -> Value
end;
false ->
Key
end;
replace_term({use_macro, Key, Value}, Macros) ->
proplists:get_value(Key, Macros, Value);
replace_term(Term, Macros) when is_list(Term) ->
@ -284,9 +296,10 @@ replace_term(Term, _) ->
Term.
is_all_uppercase(Atom) ->
String = erlang:atom_to_list(Atom),
(String == string:to_upper(String)).
String = erlang:atom_to_list(Atom),
lists:all(fun(C) when C >= $a, C =< $z -> false;
(_) -> true
end, String).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Process terms
@ -314,18 +327,42 @@ process_term(Term, 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);
{listen, Listeners} ->
Listeners2 =
lists:map(
fun({PortIP, Module, Opts}) ->
{Port, IPT, _, _, Proto, OptsClean} =
ejabberd_listener:parse_listener_portip(PortIP, Opts),
{{Port, IPT, Proto}, Module, OptsClean}
end,
Listeners),
add_option(listen, Listeners2, State);
{language, Val} ->
add_option(language, Val, State);
{outgoing_s2s_port, Port} ->
add_option(outgoing_s2s_port, Port, State);
{outgoing_s2s_options, Methods, Timeout} ->
add_option(outgoing_s2s_options, {Methods, Timeout}, State);
{s2s_dns_options, PropList} ->
add_option(s2s_dns_options, PropList, State);
{s2s_use_starttls, Port} ->
add_option(s2s_use_starttls, Port, State);
{s2s_certfile, CertFile} ->
add_option(s2s_certfile, CertFile, State);
case ejabberd_config:is_file_readable(CertFile) of
true -> add_option(s2s_certfile, CertFile, State);
false ->
ErrorText = "There is a problem in the configuration: "
"the specified file is not readable: ",
throw({error, ErrorText ++ CertFile})
end;
{domain_certfile, Domain, CertFile} ->
add_option({domain_certfile, Domain}, CertFile, State);
case ejabberd_config:is_file_readable(CertFile) of
true -> add_option({domain_certfile, Domain}, CertFile, State);
false ->
ErrorText = "There is a problem in the configuration: "
"the specified file is not readable: ",
throw({error, ErrorText ++ CertFile})
end;
{node_type, NodeType} ->
add_option(node_type, NodeType, State);
{cluster_nodes, Nodes} ->
@ -336,9 +373,21 @@ process_term(Term, State) ->
add_option({domain_balancing_component_number, Domain}, N, State);
{watchdog_admins, Admins} ->
add_option(watchdog_admins, Admins, State);
{watchdog_large_heap, LH} ->
add_option(watchdog_large_heap, LH, State);
{registration_timeout, Timeout} ->
add_option(registration_timeout, Timeout, State);
{captcha_cmd, Cmd} ->
add_option(captcha_cmd, Cmd, State);
{captcha_host, Host} ->
add_option(captcha_host, Host, State);
{ejabberdctl_access_commands, ACs} ->
add_option(ejabberdctl_access_commands, ACs, State);
{loglevel, Loglevel} ->
ejabberd_loglevel:set(Loglevel),
State;
{max_fsm_queue, N} ->
add_option(max_fsm_queue, N, State);
{_Opt, _Val} ->
lists:foldl(fun(Host, S) -> process_host_term(Term, Host, S) end,
State, State#state.hosts)
@ -493,3 +542,16 @@ get_vh_by_auth_method(AuthMethod) ->
mnesia:dirty_select(local_config,
[{#local_config{key = {auth_method, '$1'},
value=AuthMethod},[],['$1']}]).
%% @spec (Path::string()) -> true | false
is_file_readable(Path) ->
case file:read_file_info(Path) of
{ok, FileInfo} ->
case {FileInfo#file_info.type, FileInfo#file_info.access} of
{regular, read} -> true;
{regular, read_write} -> true;
_ -> false
end;
{error, _Reason} ->
false
end.

View File

@ -1,6 +1,6 @@
%%%----------------------------------------------------------------------
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -11,7 +11,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
%%%----------------------------------------------------------------------
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -11,7 +11,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA

97
src/ejabberd_debug.erl Normal file
View File

@ -0,0 +1,97 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_debug.erl
%%% Author : Mickael Remond
%%% Purpose : ejabberd's application callback module
%%% Created : 6 may 2009 by Mickael Remond <mremond@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_debug).
-export([eprof_start/0, fprof_start/0, stop/0]).
-export([pids/0]).
eprof_start() ->
eprof:start(),
eprof:profile(pids()).
fprof_start() ->
fprof:trace([start, {file, "/tmp/fprof"}, {procs, pids()}]).
%% Stop all profilers
stop() ->
catch eprof:stop(),
catch fprof:stop(),
ok.
pids() ->
lists:zf(
fun(Pid) ->
case process_info(Pid) of
ProcessInfo when is_list(ProcessInfo) ->
CurrentFunction = current_function(ProcessInfo),
InitialCall = initial_call(ProcessInfo),
RegisteredName = registered_name(ProcessInfo),
Ancestor = ancestor(ProcessInfo),
filter_pid(Pid, CurrentFunction, InitialCall, RegisteredName, Ancestor);
_ ->
false
end
end,
processes()).
current_function(ProcessInfo) ->
{value, {_, {CurrentFunction, _,_}}} =
lists:keysearch(current_function, 1, ProcessInfo),
atom_to_list(CurrentFunction).
initial_call(ProcessInfo) ->
{value, {_, {InitialCall, _,_}}} =
lists:keysearch(initial_call, 1, ProcessInfo),
atom_to_list(InitialCall).
registered_name(ProcessInfo) ->
case lists:keysearch(registered_name, 1, ProcessInfo) of
{value, {_, Name}} when is_atom(Name) -> atom_to_list(Name);
_ -> ""
end.
ancestor(ProcessInfo) ->
{value, {_, Dictionary}} = lists:keysearch(dictionary, 1, ProcessInfo),
case lists:keysearch('$ancestors', 1, Dictionary) of
{value, {_, [Ancestor|_T]}} when is_atom(Ancestor) ->
atom_to_list(Ancestor);
_ ->
""
end.
filter_pid(Pid, "ejabberd" ++ _, _InitialCall, _RegisteredName, _Ancestor) ->
{true, Pid};
filter_pid(Pid, _CurrentFunction, "ejabberd" ++ _, _RegisteredName, _Ancestor) ->
{true, Pid};
filter_pid(Pid, _CurrentFunction, _InitialCall, "ejabberd"++_, _Ancestor) ->
{true, Pid};
filter_pid(Pid, _CurrentFunction, _InitialCall, "stringprep"++_, _Ancestor) ->
{true, Pid};
filter_pid(Pid, _CurrentFunction, _InitialCall, _RegisteredName, "ejabberd"++_) ->
{true, Pid};
filter_pid(_Pid, _CurrentFunction, _InitialCall, _RegisteredName, _Ancestor) ->
false.

View File

@ -5,7 +5,7 @@
%%% Created : 23 Aug 2006 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -53,6 +53,8 @@
-record(state, {sockmod, socket, receiver}).
-define(HIBERNATE_TIMEOUT, 90000).
%%====================================================================
%% API
%%====================================================================
@ -92,8 +94,9 @@ start(Module, SockMod, Socket, Opts) ->
todo
end.
starttls(FsmRef, TLSOpts) ->
gen_server:call(FsmRef, {starttls, TLSOpts}),
starttls(FsmRef, _TLSOpts) ->
%% TODO: Frontend improvements planned by Aleksey
%%gen_server:call(FsmRef, {starttls, TLSOpts}),
FsmRef.
starttls(FsmRef, TLSOpts, Data) ->
@ -135,8 +138,10 @@ close(FsmRef) ->
sockname(FsmRef) ->
gen_server:call(FsmRef, sockname).
peername(FsmRef) ->
gen_server:call(FsmRef, peername).
peername(_FsmRef) ->
%% TODO: Frontend improvements planned by Aleksey
%%gen_server:call(FsmRef, peername).
{ok, {{0, 0, 0, 0}, 0}}.
%%====================================================================
@ -153,11 +158,12 @@ peername(FsmRef) ->
init([Module, SockMod, Socket, Opts, Receiver]) ->
%% TODO: monitor the receiver
Node = ejabberd_node_groups:get_closest_node(backend),
{SockMod2, Socket2} = check_starttls(SockMod, Socket, Receiver, Opts),
{ok, Pid} =
rpc:call(Node, Module, start, [{?MODULE, self()}, Opts]),
ejabberd_receiver:become_controller(Receiver, Pid),
{ok, #state{sockmod = SockMod,
socket = Socket,
{ok, #state{sockmod = SockMod2,
socket = Socket2,
receiver = Receiver}}.
%%--------------------------------------------------------------------
@ -173,7 +179,8 @@ handle_call({starttls, TLSOpts}, _From, State) ->
{ok, TLSSocket} = tls:tcp_to_tls(State#state.socket, TLSOpts),
ejabberd_receiver:starttls(State#state.receiver, TLSSocket),
Reply = ok,
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls}};
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls},
?HIBERNATE_TIMEOUT};
handle_call({starttls, TLSOpts, Data}, _From, State) ->
{ok, TLSSocket} = tls:tcp_to_tls(State#state.socket, TLSOpts),
@ -181,7 +188,8 @@ handle_call({starttls, TLSOpts, Data}, _From, State) ->
catch (State#state.sockmod):send(
State#state.socket, Data),
Reply = ok,
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls}};
{reply, Reply, State#state{socket = TLSSocket, sockmod = tls},
?HIBERNATE_TIMEOUT};
handle_call(compress, _From, State) ->
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
@ -189,7 +197,8 @@ handle_call(compress, _From, State) ->
State#state.socket),
ejabberd_receiver:compress(State#state.receiver, ZlibSocket),
Reply = ok,
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib}};
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
?HIBERNATE_TIMEOUT};
handle_call({compress, Data}, _From, State) ->
{ok, ZlibSocket} = ejabberd_zlib:enable_zlib(
@ -199,35 +208,36 @@ handle_call({compress, Data}, _From, State) ->
catch (State#state.sockmod):send(
State#state.socket, Data),
Reply = ok,
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib}};
{reply, Reply, State#state{socket = ZlibSocket, sockmod = ejabberd_zlib},
?HIBERNATE_TIMEOUT};
handle_call(reset_stream, _From, State) ->
ejabberd_receiver:reset_stream(State#state.receiver),
Reply = ok,
{reply, Reply, State};
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call({send, Data}, _From, State) ->
catch (State#state.sockmod):send(
State#state.socket, Data),
Reply = ok,
{reply, Reply, State};
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call({change_shaper, Shaper}, _From, State) ->
ejabberd_receiver:change_shaper(State#state.receiver, Shaper),
Reply = ok,
{reply, Reply, State};
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_sockmod, _From, State) ->
Reply = State#state.sockmod,
{reply, Reply, State};
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_peer_certificate, _From, State) ->
Reply = tls:get_peer_certificate(State#state.socket),
{reply, Reply, State};
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(get_verify_result, _From, State) ->
Reply = tls:get_verify_result(State#state.socket),
{reply, Reply, State};
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(close, _From, State) ->
ejabberd_receiver:close(State#state.receiver),
@ -243,7 +253,7 @@ handle_call(sockname, _From, State) ->
_ ->
SockMod:sockname(Socket)
end,
{reply, Reply, State};
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(peername, _From, State) ->
#state{sockmod = SockMod, socket = Socket} = State,
@ -254,11 +264,11 @@ handle_call(peername, _From, State) ->
_ ->
SockMod:peername(Socket)
end,
{reply, Reply, State};
{reply, Reply, State, ?HIBERNATE_TIMEOUT};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
@ -267,7 +277,7 @@ handle_call(_Request, _From, State) ->
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
{noreply, State, ?HIBERNATE_TIMEOUT}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
@ -275,8 +285,11 @@ handle_cast(_Msg, State) ->
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(timeout, State) ->
proc_lib:hibernate(gen_server, enter_loop, [?MODULE, [], State]),
{noreply, State, ?HIBERNATE_TIMEOUT};
handle_info(_Info, State) ->
{noreply, State}.
{noreply, State, ?HIBERNATE_TIMEOUT}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
@ -298,3 +311,17 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
check_starttls(SockMod, Socket, Receiver, Opts) ->
TLSEnabled = lists:member(tls, Opts),
TLSOpts = lists:filter(fun({certfile, _}) -> true;
(_) -> false
end, Opts),
if
TLSEnabled ->
{ok, TLSSocket} = tls:tcp_to_tls(Socket, TLSOpts),
ejabberd_receiver:starttls(Receiver, TLSSocket),
{tls, TLSSocket};
true ->
{SockMod, Socket}
end.

View File

@ -5,7 +5,7 @@
%%% Created : 8 Aug 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -31,12 +31,18 @@
%% External exports
-export([start_link/0,
add/3,
add/4,
add_dist/5,
delete/3,
delete/4,
delete_dist/5,
run/2,
run_fold/3,
add/5,
add_dist/6,
delete/5,
delete_dist/6,
run/3,
run_fold/4]).
@ -50,6 +56,9 @@
-include("ejabberd.hrl").
%% Timeout of 5 seconds in calls to distributed hooks
-define(TIMEOUT_DISTRIBUTED_HOOK, 5000).
-record(state, {}).
%%%----------------------------------------------------------------------
@ -58,18 +67,55 @@
start_link() ->
gen_server:start_link({local, ejabberd_hooks}, ejabberd_hooks, [], []).
%% @spec (Hook::atom(), Function::function(), Seq::integer()) -> ok
%% @doc See add/4.
add(Hook, Function, Seq) when is_function(Function) ->
add(Hook, global, undefined, Function, Seq).
add(Hook, Host, Function, Seq) when is_function(Function) ->
add(Hook, Host, undefined, Function, Seq);
%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok
%% @doc Add a module and function to this hook.
%% The integer sequence is used to sort the calls: low number is called before high number.
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}).
add_dist(Hook, Node, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {add, Hook, global, Node, Module, Function, Seq}).
add_dist(Hook, Host, Node, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {add, Hook, Host, Node, Module, Function, Seq}).
%% @spec (Hook::atom(), Function::function(), Seq::integer()) -> ok
%% @doc See del/4.
delete(Hook, Function, Seq) when is_function(Function) ->
delete(Hook, global, undefined, Function, Seq).
delete(Hook, Host, Function, Seq) when is_function(Function) ->
delete(Hook, Host, undefined, Function, Seq);
%% @spec (Hook::atom(), Module::atom(), Function::atom(), Seq::integer()) -> ok
%% @doc Delete a module and function from this hook.
%% It is important to indicate exactly the same information than when the call was added.
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}).
delete_dist(Hook, Node, Module, Function, Seq) ->
delete_dist(Hook, global, Node, Module, Function, Seq).
delete_dist(Hook, Host, Node, Module, Function, Seq) ->
gen_server:call(ejabberd_hooks, {delete, Hook, Host, Node, Module, Function, Seq}).
%% @spec (Hook::atom(), Args) -> ok
%% @doc Run the calls of this hook in order, don't care about function results.
%% If a call returns stop, no more calls are performed.
run(Hook, Args) ->
run(Hook, global, Args).
@ -81,6 +127,12 @@ run(Hook, Host, Args) ->
ok
end.
%% @spec (Hook::atom(), Val, Args) -> Val | stopped | NewVal
%% @doc Run the calls of this hook in order.
%% The arguments passed to the function are: [Val | Args].
%% The result of a call is used as Val for the next call.
%% If a call returns 'stop', no more calls are performed and 'stopped' is returned.
%% If a call returns {stopped, NewVal}, no more calls are performed and NewVal is returned.
run_fold(Hook, Val, Args) ->
run_fold(Hook, global, Val, Args).
@ -134,6 +186,24 @@ handle_call({add, Hook, Host, Module, Function, Seq}, _From, State) ->
ok
end,
{reply, Reply, State};
handle_call({add, Hook, Host, Node, Module, Function, Seq}, _From, State) ->
Reply = case ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] ->
El = {Seq, Node, 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, Node, 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}] ->
@ -144,6 +214,16 @@ handle_call({delete, Hook, Host, Module, Function, Seq}, _From, State) ->
ok
end,
{reply, Reply, State};
handle_call({delete, Hook, Host, Node, Module, Function, Seq}, _From, State) ->
Reply = case ets:lookup(hooks, {Hook, Host}) of
[{_, Ls}] ->
NewLs = lists:delete({Seq, Node, 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}.
@ -184,8 +264,32 @@ code_change(_OldVsn, State, _Extra) ->
run1([], _Hook, _Args) ->
ok;
run1([{_Seq, Node, Module, Function} | Ls], Hook, Args) ->
case rpc:call(Node, Module, Function, Args, ?TIMEOUT_DISTRIBUTED_HOOK) of
timeout ->
?ERROR_MSG("Timeout on RPC to ~p~nrunning hook: ~p",
[Node, {Hook, Args}]),
run1(Ls, Hook, Args);
{badrpc, Reason} ->
?ERROR_MSG("Bad RPC error to ~p: ~p~nrunning hook: ~p",
[Node, Reason, {Hook, Args}]),
run1(Ls, Hook, Args);
stop ->
?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n"
"Stop.", [self(), node(), Node]), % debug code
ok;
Res ->
?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n"
"The response is:~n~s", [self(), node(), Node, Res]), % debug code
run1(Ls, Hook, Args)
end;
run1([{_Seq, Module, Function} | Ls], Hook, Args) ->
case catch apply(Module, Function, Args) of
Res = if is_function(Function) ->
catch apply(Function, Args);
true ->
catch apply(Module, Function, Args)
end,
case Res of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nrunning hook: ~p",
[Reason, {Hook, Args}]),
@ -199,8 +303,34 @@ run1([{_Seq, Module, Function} | Ls], Hook, Args) ->
run_fold1([], _Hook, Val, _Args) ->
Val;
run_fold1([{_Seq, Node, Module, Function} | Ls], Hook, Val, Args) ->
case rpc:call(Node, Module, Function, [Val | Args], ?TIMEOUT_DISTRIBUTED_HOOK) of
{badrpc, Reason} ->
?ERROR_MSG("Bad RPC error to ~p: ~p~nrunning hook: ~p",
[Node, Reason, {Hook, Args}]),
run_fold1(Ls, Hook, Val, Args);
timeout ->
?ERROR_MSG("Timeout on RPC to ~p~nrunning hook: ~p",
[Node, {Hook, Args}]),
run_fold1(Ls, Hook, Val, Args);
stop ->
stopped;
{stop, NewVal} ->
?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n"
"Stop, and the NewVal is:~n~p", [self(), node(), Node, NewVal]), % debug code
NewVal;
NewVal ->
?INFO_MSG("~nThe process ~p in node ~p ran a hook in node ~p.~n"
"The NewVal is:~n~p", [self(), node(), Node, NewVal]), % debug code
run_fold1(Ls, Hook, NewVal, Args)
end;
run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) ->
case catch apply(Module, Function, [Val | Args]) of
Res = if is_function(Function) ->
catch apply(Function, [Val | Args]);
true ->
catch apply(Module, Function, [Val | Args])
end,
case Res of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nrunning hook: ~p",
[Reason, {Hook, Args}]),
@ -212,6 +342,3 @@ run_fold1([{_Seq, Module, Function} | Ls], Hook, Val, Args) ->
NewVal ->
run_fold1(Ls, Hook, NewVal, Args)
end.

View File

@ -5,7 +5,7 @@
%%% Created : 16 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -29,83 +29,198 @@
-export([start_link/0, init/1, start/3,
init/3,
start_listeners/0,
start_listener/3,
stop_listener/1,
stop_listener/2,
parse_listener_portip/2,
add_listener/3,
delete_listener/1
delete_listener/2
]).
-include("ejabberd.hrl").
%% We do not block on send anymore.
-define(TCP_SEND_TIMEOUT, 15000).
start_link() ->
supervisor:start_link({local, ejabberd_listeners}, ?MODULE, []).
init(_) ->
{ok, {{one_for_one, 10, 1}, []}}.
start_listeners() ->
case ejabberd_config:get_local_option(listen) of
undefined ->
ignore;
Ls ->
{ok, {{one_for_one, 10, 1},
lists:map(
fun({Port, Module, Opts}) ->
{Port,
{?MODULE, start, [Port, Module, Opts]},
transient,
brutal_kill,
worker,
[?MODULE]}
end, Ls)}}
Ls2 = lists:map(
fun({Port, Module, Opts}) ->
case start_listener(Port, Module, Opts) of
{ok, _Pid} = R -> R;
{error, Error} ->
throw(Error)
end
end, Ls),
report_duplicated_portips(Ls),
{ok, {{one_for_one, 10, 1}, Ls2}}
end.
report_duplicated_portips(L) ->
LKeys = [Port || {Port, _, _} <- L],
LNoDupsKeys = proplists:get_keys(L),
case LKeys -- LNoDupsKeys of
[] -> ok;
Dups ->
?CRITICAL_MSG("In the ejabberd configuration there are duplicated "
"Port number + IP address:~n ~p",
[Dups])
end.
start(Port, Module, Opts) ->
case includes_deprecated_ssl_option(Opts) of
false ->
{ok, proc_lib:spawn_link(?MODULE, init,
[Port, Module, Opts])};
true ->
SSLErr="There is a problem with your ejabberd configuration file: "
"the option 'ssl' for listening sockets is no longer available."
"To get SSL encryption use the option 'tls'.",
?ERROR_MSG(SSLErr, []),
{error, SSLErr}
%% Check if the module is an ejabberd listener or an independent listener
ModuleRaw = strip_frontend(Module),
case ModuleRaw:socket_type() of
independent -> ModuleRaw:start_listener(Port, Opts);
_ -> start_dependent(Port, Module, Opts)
end.
%% Parse the options of the socket,
%% and return if the deprecated option 'ssl' is included
%% @spec(Opts::[opt()]) -> true | false
includes_deprecated_ssl_option(Opts) ->
case lists:keysearch(ssl, 1, Opts) of
{value, {ssl, _SSLOpts}} ->
true;
_ ->
lists:member(ssl, Opts)
%% @spec(Port, Module, Opts) -> {ok, Pid} | {error, ErrorMessage}
start_dependent(Port, Module, Opts) ->
try check_listener_options(Opts) of
ok ->
proc_lib:start_link(?MODULE, init, [Port, Module, Opts])
catch
throw:{error, Error} ->
?ERROR_MSG(Error, []),
{error, Error}
end.
init(Port, Module, Opts) ->
init(PortIP, Module, RawOpts) ->
{Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts),
%% The first inet|inet6 and the last {ip, _} work,
%% so overriding those in Opts
Opts = [IPV | OptsClean] ++ [{ip, IPT}],
SockOpts = lists:filter(fun({ip, _}) -> true;
(inet6) -> true;
(inet) -> true;
({backlog, _}) -> true;
(_) -> false
end, Opts),
if Proto == udp ->
init_udp(PortIP, Module, Opts, SockOpts, Port, IPS);
true ->
init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS)
end.
init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
case gen_udp:open(Port, [binary,
{active, false},
{reuseaddr, true} |
SockOpts]) of
{ok, Socket} ->
%% Inform my parent that this port was opened succesfully
proc_lib:init_ack({ok, self()}),
udp_recv(Socket, Module, Opts);
{error, Reason} ->
socket_error(Reason, PortIP, Module, SockOpts, Port, IPS)
end.
init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
SockOpts2 = case erlang:system_info(otp_release) >= "R13B" of
true -> [{send_timeout_close, true} | SockOpts];
false -> []
end,
Res = gen_tcp:listen(Port, [binary,
{packet, 0},
{packet, 0},
{active, false},
{reuseaddr, true},
{nodelay, true},
{send_timeout, ?TCP_SEND_TIMEOUT},
{keepalive, true} |
SockOpts]),
SockOpts2]),
case Res of
{ok, ListenSocket} ->
%% Inform my parent that this port was opened succesfully
proc_lib:init_ack({ok, self()}),
%% And now start accepting connection attempts
accept(ListenSocket, Module, Opts);
{error, Reason} ->
?ERROR_MSG("Failed to open socket for ~p: ~p",
[{Port, Module, Opts}, Reason]),
error
socket_error(Reason, PortIP, Module, SockOpts, Port, IPS)
end.
%% @spec (PortIP, Opts) -> {Port, IPT, IPS, IPV, OptsClean}
%% where
%% PortIP = Port | {Port, IPT | IPS}
%% Port = integer()
%% IPT = tuple()
%% IPS = string()
%% IPV = inet | inet6
%% Opts = [IPV | {ip, IPT} | atom() | tuple()]
%% OptsClean = [atom() | tuple()]
%% @doc Parse any kind of ejabberd listener specification.
%% The parsed options are returned in several formats.
%% OptsClean does not include inet/inet6 or ip options.
%% Opts can include the options inet6 and {ip, Tuple},
%% but they are only used when no IP address was specified in the PortIP.
%% The IP version (either IPv4 or IPv6) is inferred from the IP address type,
%% so the option inet/inet6 is only used when no IP is specified at all.
parse_listener_portip(PortIP, Opts) ->
{IPOpt, Opts2} = strip_ip_option(Opts),
{IPVOpt, OptsClean} = case lists:member(inet6, Opts2) of
true -> {inet6, Opts2 -- [inet6]};
false -> {inet, Opts2}
end,
{Port, IPT, IPS, Proto} =
case add_proto(PortIP, Opts) of
{P, Prot} ->
T = get_ip_tuple(IPOpt, IPVOpt),
S = inet_parse:ntoa(T),
{P, T, S, Prot};
{P, T, Prot} when is_integer(P) and is_tuple(T) ->
S = inet_parse:ntoa(T),
{P, T, S, Prot};
{P, S, Prot} when is_integer(P) and is_list(S) ->
[S | _] = string:tokens(S, "/"),
{ok, T} = inet_parse:address(S),
{P, T, S, Prot}
end,
IPV = case size(IPT) of
4 -> inet;
8 -> inet6
end,
{Port, IPT, IPS, IPV, Proto, OptsClean}.
add_proto(Port, Opts) when is_integer(Port) ->
{Port, get_proto(Opts)};
add_proto({Port, Proto}, _Opts) when is_atom(Proto) ->
{Port, normalize_proto(Proto)};
add_proto({Port, Addr}, Opts) ->
{Port, Addr, get_proto(Opts)};
add_proto({Port, Addr, Proto}, _Opts) ->
{Port, Addr, normalize_proto(Proto)}.
strip_ip_option(Opts) ->
{IPL, OptsNoIP} = lists:partition(
fun({ip, _}) -> true;
(_) -> false
end,
Opts),
case IPL of
%% Only the first ip option is considered
[{ip, T1} | _] when is_tuple(T1) ->
{T1, OptsNoIP};
[] ->
{no_ip_option, OptsNoIP}
end.
get_ip_tuple(no_ip_option, inet) ->
{0, 0, 0, 0};
get_ip_tuple(no_ip_option, inet6) ->
{0, 0, 0, 0, 0, 0, 0, 0};
get_ip_tuple(IPOpt, _IPVOpt) ->
IPOpt.
accept(ListenSocket, Module, Opts) ->
case gen_tcp:accept(ListenSocket) of
{ok, Socket} ->
@ -116,12 +231,11 @@ accept(ListenSocket, Module, Opts) ->
_ ->
ok
end,
case Module of
{frontend, Mod} ->
ejabberd_frontend_socket:start(Mod, gen_tcp, Socket, Opts);
_ ->
ejabberd_socket:start(Module, gen_tcp, Socket, Opts)
end,
CallMod = case is_frontend(Module) of
true -> ejabberd_frontend_socket;
false -> ejabberd_socket
end,
CallMod:start(strip_frontend(Module), gen_tcp, Socket, Opts),
accept(ListenSocket, Module, Opts);
{error, Reason} ->
?INFO_MSG("(~w) Failed TCP accept: ~w",
@ -129,20 +243,57 @@ accept(ListenSocket, Module, Opts) ->
accept(ListenSocket, Module, Opts)
end.
udp_recv(Socket, Module, Opts) ->
case gen_udp:recv(Socket, 0) of
{ok, {Addr, Port, Packet}} ->
case catch Module:udp_recv(Socket, Addr, Port, Packet, Opts) of
{'EXIT', Reason} ->
?ERROR_MSG("failed to process UDP packet:~n"
"** Source: {~p, ~p}~n"
"** Reason: ~p~n** Packet: ~p",
[Addr, Port, Reason, Packet]);
_ ->
ok
end,
udp_recv(Socket, Module, Opts);
{error, Reason} ->
?ERROR_MSG("unexpected UDP error: ~s", [format_error(Reason)]),
throw({error, Reason})
end.
%% @spec (Port, Module, Opts) -> {ok, Pid} | {error, Error}
start_listener(Port, Module, Opts) ->
start_module_sup(Module),
case start_listener2(Port, Module, Opts) of
{ok, _Pid} = R -> R;
{error, {{'EXIT', {undef, [{M, _F, _A}|_]}}, _} = Error} ->
?ERROR_MSG("Error starting the ejabberd listener: ~p.~n"
"It could not be loaded or is not an ejabberd listener.~n"
"Error: ~p~n", [Module, Error]),
{error, {module_not_available, M}};
{error, {already_started, Pid}} ->
{ok, Pid};
{error, Error} ->
{error, Error}
end.
%% @spec (Port, Module, Opts) -> {ok, Pid} | {error, Error}
start_listener2(Port, Module, Opts) ->
%% It is only required to start the supervisor in some cases.
%% But it doesn't hurt to attempt to start it for any listener.
%% So, it's normal (and harmless) that in most cases this call returns: {error, {already_started, pid()}}
start_module_sup(Port, Module),
start_listener_sup(Port, Module, Opts).
start_module_sup(Module) ->
Proc = gen_mod:get_module_proc("sup", Module),
ChildSpec =
{Proc,
{ejabberd_tmp_sup, start_link, [Proc, Module]},
start_module_sup(_Port, Module) ->
Proc1 = gen_mod:get_module_proc("sup", Module),
ChildSpec1 =
{Proc1,
{ejabberd_tmp_sup, start_link, [Proc1, strip_frontend(Module)]},
permanent,
infinity,
supervisor,
[ejabberd_tmp_sup]},
supervisor:start_child(ejabberd_sup, ChildSpec).
supervisor:start_child(ejabberd_sup, ChildSpec1).
start_listener_sup(Port, Module, Opts) ->
ChildSpec = {Port,
@ -153,30 +304,159 @@ start_listener_sup(Port, Module, Opts) ->
[?MODULE]},
supervisor:start_child(ejabberd_listeners, ChildSpec).
stop_listener(Port) ->
supervisor:terminate_child(ejabberd_listeners, Port),
supervisor:delete_child(ejabberd_listeners, Port).
%% @spec (PortIP, Module) -> ok
%% where
%% PortIP = {Port, IPT | IPS}
%% Port = integer()
%% IPT = tuple()
%% IPS = string()
%% Module = atom()
stop_listener(PortIP, _Module) ->
supervisor:terminate_child(ejabberd_listeners, PortIP),
supervisor:delete_child(ejabberd_listeners, PortIP).
add_listener(Port, Module, Opts) ->
%% @spec (PortIP, Module, Opts) -> {ok, Pid} | {error, Error}
%% where
%% PortIP = {Port, IPT | IPS}
%% Port = integer()
%% IPT = tuple()
%% IPS = string()
%% IPV = inet | inet6
%% Module = atom()
%% Opts = [IPV | {ip, IPT} | atom() | tuple()]
%% @doc Add a listener and store in config if success
add_listener(PortIP, Module, Opts) ->
{Port, IPT, _, _, Proto, _} = parse_listener_portip(PortIP, Opts),
PortIP1 = {Port, IPT, Proto},
case start_listener(PortIP1, Module, Opts) of
{ok, _Pid} ->
Ports = case ejabberd_config:get_local_option(listen) of
undefined ->
[];
Ls ->
Ls
end,
Ports1 = lists:keydelete(PortIP1, 1, Ports),
Ports2 = [{PortIP1, Module, Opts} | Ports1],
ejabberd_config:add_local_option(listen, Ports2),
ok;
{error, {already_started, _Pid}} ->
{error, {already_started, PortIP}};
{error, Error} ->
{error, Error}
end.
delete_listener(PortIP, Module) ->
delete_listener(PortIP, Module, []).
%% @spec (PortIP, Module, Opts) -> ok
%% where
%% PortIP = {Port, IPT | IPS}
%% Port = integer()
%% IPT = tuple()
%% IPS = string()
%% Module = atom()
%% Opts = [term()]
delete_listener(PortIP, Module, Opts) ->
{Port, IPT, _, _, Proto, _} = parse_listener_portip(PortIP, Opts),
PortIP1 = {Port, IPT, Proto},
Ports = case ejabberd_config:get_local_option(listen) of
undefined ->
[];
Ls ->
Ls
end,
Ports1 = lists:keydelete(Port, 1, Ports),
Ports2 = [{Port, Module, Opts} | Ports1],
ejabberd_config:add_local_option(listen, Ports2),
start_listener(Port, Module, Opts).
delete_listener(Port) ->
Ports = case ejabberd_config:get_local_option(listen) of
undefined ->
[];
Ls ->
Ls
end,
Ports1 = lists:keydelete(Port, 1, Ports),
Ports1 = lists:keydelete(PortIP1, 1, Ports),
ejabberd_config:add_local_option(listen, Ports1),
stop_listener(Port).
stop_listener(PortIP1, Module).
is_frontend({frontend, _Module}) -> true;
is_frontend(_) -> false.
%% @doc(FrontMod) -> atom()
%% where FrontMod = atom() | {frontend, atom()}
strip_frontend({frontend, Module}) -> Module;
strip_frontend(Module) when is_atom(Module) -> Module.
%%%
%%% Check options
%%%
check_listener_options(Opts) ->
case includes_deprecated_ssl_option(Opts) of
false -> ok;
true ->
Error = "There is a problem with your ejabberd configuration file: "
"the option 'ssl' for listening sockets is no longer available."
" To get SSL encryption use the option 'tls'.",
throw({error, Error})
end,
case certfile_readable(Opts) of
true -> ok;
{false, Path} ->
ErrorText = "There is a problem in the configuration: "
"the specified file is not readable: ",
throw({error, ErrorText ++ Path})
end,
ok.
%% Parse the options of the socket,
%% and return if the deprecated option 'ssl' is included
%% @spec (Opts) -> true | false
includes_deprecated_ssl_option(Opts) ->
case lists:keysearch(ssl, 1, Opts) of
{value, {ssl, _SSLOpts}} ->
true;
_ ->
lists:member(ssl, Opts)
end.
%% @spec (Opts) -> true | {false, Path::string()}
certfile_readable(Opts) ->
case proplists:lookup(certfile, Opts) of
none -> true;
{certfile, Path} ->
case ejabberd_config:is_file_readable(Path) of
true -> true;
false -> {false, Path}
end
end.
get_proto(Opts) ->
case proplists:get_value(proto, Opts) of
undefined ->
tcp;
Proto ->
normalize_proto(Proto)
end.
normalize_proto(tcp) -> tcp;
normalize_proto(udp) -> udp;
normalize_proto(UnknownProto) ->
?WARNING_MSG("There is a problem in the configuration: "
"~p is an unknown IP protocol. Using tcp as fallback",
[UnknownProto]),
tcp.
socket_error(Reason, PortIP, Module, SockOpts, Port, IPS) ->
ReasonT = case Reason of
eaddrnotavail ->
"IP address not available: " ++ IPS;
eaddrinuse ->
"IP address and port number already used: "
++IPS++" "++integer_to_list(Port);
_ ->
format_error(Reason)
end,
?ERROR_MSG("Failed to open socket:~n ~p~nReason: ~s",
[{Port, Module, SockOpts}, ReasonT]),
throw({Reason, PortIP}).
format_error(Reason) ->
case inet:format_error(Reason) of
"unknown POSIX error" ->
atom_to_list(Reason);
ReasonStr ->
ReasonStr
end.

View File

@ -5,7 +5,7 @@
%%% Created : 30 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -33,10 +33,13 @@
-export([start_link/0]).
-export([route/3,
route_iq/4,
process_iq_reply/3,
register_iq_handler/4,
register_iq_handler/5,
register_iq_response_handler/4,
unregister_iq_handler/2,
unregister_iq_response_handler/2,
refresh_iq_handlers/0,
bounce_resource_packet/3
]).
@ -50,10 +53,13 @@
-record(state, {}).
-record(iq_response, {id, module, function}).
-record(iq_response, {id, module, function, timer}).
-define(IQTABLE, local_iqtable).
%% This value is used in SIP and Megaco for a transaction lifetime.
-define(IQ_TIMEOUT, 32000).
%%====================================================================
%% API
%%====================================================================
@ -88,36 +94,24 @@ process_iq(From, To, Packet) ->
ejabberd_router:route(To, From, Err)
end;
reply ->
process_iq_reply(From, To, Packet);
IQReply = jlib:iq_query_or_response_info(Packet),
process_iq_reply(From, To, IQReply);
_ ->
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
ejabberd_router:route(To, From, Err),
ok
end.
process_iq_reply(From, To, Packet) ->
IQ = jlib:iq_query_or_response_info(Packet),
#iq{id = ID} = IQ,
case catch mnesia:dirty_read(iq_response, ID) of
[] ->
process_iq_reply(From, To, #iq{id = ID} = IQ) ->
case get_iq_callback(ID) of
{ok, undefined, Function} ->
Function(IQ),
ok;
{ok, Module, Function} ->
Module:Function(From, To, IQ),
ok;
_ ->
F = fun() ->
case mnesia:read({iq_response, ID}) of
[] ->
nothing;
[#iq_response{module = Module,
function = Function}] ->
mnesia:delete({iq_response, ID}),
{Module, Function}
end
end,
case mnesia:transaction(F) of
{atomic, {Module, Function}} ->
Module:Function(From, To, IQ);
_ ->
ok
end
nothing
end.
route(From, To, Packet) ->
@ -129,8 +123,23 @@ route(From, To, Packet) ->
ok
end.
register_iq_response_handler(Host, ID, Module, Fun) ->
ejabberd_local ! {register_iq_response_handler, Host, ID, Module, Fun}.
route_iq(From, To, #iq{type = Type} = IQ, F) when is_function(F) ->
Packet = if Type == set; Type == get ->
ID = randoms:get_string(),
Host = From#jid.lserver,
register_iq_response_handler(Host, ID, undefined, F),
jlib:iq_to_xml(IQ#iq{id = ID});
true ->
jlib:iq_to_xml(IQ)
end,
ejabberd_router:route(From, To, Packet).
register_iq_response_handler(_Host, ID, Module, Function) ->
TRef = erlang:start_timer(?IQ_TIMEOUT, ejabberd_local, ID),
mnesia:dirty_write(#iq_response{id = ID,
module = Module,
function = Function,
timer = TRef}).
register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun}.
@ -138,6 +147,10 @@ register_iq_handler(Host, XMLNS, Module, Fun) ->
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
ejabberd_local ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
unregister_iq_response_handler(_Host, ID) ->
catch get_iq_callback(ID),
ok.
unregister_iq_handler(Host, XMLNS) ->
ejabberd_local ! {unregister_iq_handler, Host, XMLNS}.
@ -168,6 +181,7 @@ init([]) ->
?MODULE, bounce_resource_packet, 100)
end, ?MYHOSTS),
catch ets:new(?IQTABLE, [named_table, public]),
update_table(),
mnesia:create_table(iq_response,
[{ram_copies, [node()]},
{attributes, record_info(fields, iq_response)}]),
@ -211,9 +225,6 @@ handle_info({route, From, To, Packet}, State) ->
ok
end,
{noreply, State};
handle_info({register_iq_response_handler, _Host, ID, Module, Function}, State) ->
mnesia:dirty_write(#iq_response{id = ID, module = Module, function = Function}),
{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),
@ -245,6 +256,9 @@ handle_info(refresh_iq_handlers, State) ->
end
end, ets:tab2list(?IQTABLE)),
{noreply, State};
handle_info({timeout, _TRef, ID}, State) ->
process_iq_timeout(ID),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
@ -298,3 +312,52 @@ do_route(From, To, Packet) ->
end
end.
update_table() ->
case catch mnesia:table_info(iq_response, attributes) of
[id, module, function] ->
mnesia:delete_table(iq_response);
[id, module, function, timer] ->
ok;
{'EXIT', _} ->
ok
end.
get_iq_callback(ID) ->
case mnesia:dirty_read(iq_response, ID) of
[#iq_response{module = Module, timer = TRef,
function = Function}] ->
cancel_timer(TRef),
mnesia:dirty_delete(iq_response, ID),
{ok, Module, Function};
_ ->
error
end.
process_iq_timeout(ID) ->
spawn(fun process_iq_timeout/0) ! ID.
process_iq_timeout() ->
receive
ID ->
case get_iq_callback(ID) of
{ok, undefined, Function} ->
Function(timeout);
_ ->
ok
end
after 5000 ->
ok
end.
cancel_timer(TRef) ->
case erlang:cancel_timer(TRef) of
false ->
receive
{timeout, TRef, _} ->
ok
after 0 ->
ok
end;
_ ->
ok
end.

View File

@ -5,7 +5,7 @@
%%% Created : 23 Oct 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -31,7 +31,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, reopen_log/0, rotate_log/1]).
-record(state, {fd, file}).
@ -117,7 +117,7 @@ reopen_log() ->
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) ->
S when is_list(S) ->
file:write(Fd, io_lib:format(T ++ S, []));
_ ->
F = add_node("ERROR: ~p - ~p~n", Pid),
@ -126,7 +126,7 @@ write_event(Fd, {Time, {error, _GL, {Pid, Format, Args}}}) ->
write_event(Fd, {Time, {emulator, _GL, Chars}}) ->
T = write_time(Time),
case catch io_lib:format(Chars, []) of
S when list(S) ->
S when is_list(S) ->
file:write(Fd, io_lib:format(T ++ S, []));
_ ->
file:write(Fd, io_lib:format(T ++ "ERROR: ~p ~n", [Chars]))
@ -145,7 +145,7 @@ write_event(Fd, {Time, {info_report, _GL, {Pid, std_info, Rep}}}) ->
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) ->
S when is_list(S) ->
file:write(Fd, io_lib:format(T ++ S, []));
_ ->
F = add_node("ERROR: ~p - ~p~n", Pid),
@ -154,7 +154,7 @@ write_event(Fd, {Time, {info_msg, _GL, {Pid, Format, Args}}}) ->
write_event(_, _) ->
ok.
format_report(Rep) when list(Rep) ->
format_report(Rep) when is_list(Rep) ->
case string_p(Rep) of
true ->
io_lib:format("~s~n",[Rep]);
@ -171,7 +171,7 @@ format_rep([Other|Rep]) ->
format_rep(_) ->
[].
add_node(X, Pid) when atom(X) ->
add_node(X, Pid) when is_atom(X) ->
add_node(atom_to_list(X), Pid);
add_node(X, Pid) when node(Pid) /= node() ->
lists:concat([X,"** at node ",node(Pid)," **~n"]);
@ -183,7 +183,7 @@ string_p([]) ->
string_p(Term) ->
string_p1(Term).
string_p1([H|T]) when integer(H), H >= $\s, H < 255 ->
string_p1([H|T]) when is_integer(H), H >= $\s, H < 255 ->
string_p1(T);
string_p1([$\n|T]) -> string_p1(T);
string_p1([$\r|T]) -> string_p1(T);
@ -192,7 +192,7 @@ string_p1([$\v|T]) -> string_p1(T);
string_p1([$\b|T]) -> string_p1(T);
string_p1([$\f|T]) -> string_p1(T);
string_p1([$\e|T]) -> string_p1(T);
string_p1([H|T]) when list(H) ->
string_p1([H|T]) when is_list(H) ->
case string_p1(H) of
true -> string_p1(T);
_ -> false
@ -206,10 +206,11 @@ 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
%% @doc Rename the log file if exists, to "*-old.log".
%% 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.
%% the log can directly be reopened.
%% @spec (Filename::string()) -> ok
rotate_log(Filename) ->
case file:read_file_info(Filename) of
{ok, _FileInfo} ->

View File

@ -9,7 +9,7 @@
%%% Created : 29 Nov 2006 by Mickael Remond <mremond@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -20,7 +20,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -31,64 +31,51 @@
-module(ejabberd_loglevel).
-author('mickael.remond@process-one.net').
-export([set/1]).
-export([set/1, get/0]).
-include("ejabberd.hrl").
-define(LOGMODULE, "error_logger").
%% Error levels:
%% 0 -> No log
%% 1 -> Critical
%% 2 -> Error
%% 3 -> Warning
%% 4 -> Info
%% 5 -> Debug
-define(LOG_LEVELS,[ {0, no_log, "No log"}
,{1, critical, "Critical"}
,{2, error, "Error"}
,{3, warning, "Warning"}
,{4, info, "Info"}
,{5, debug, "Debug"}
]).
get() ->
Level = ejabberd_logger:get(),
case lists:keysearch(Level, 1, ?LOG_LEVELS) of
{value, Result} -> Result;
_ -> erlang:error({no_such_loglevel, Level})
end.
set(LogLevel) when is_atom(LogLevel) ->
set(level_to_integer(LogLevel));
set(Loglevel) when is_integer(Loglevel) ->
Forms = compile_string(?LOGMODULE, ejabberd_logger_src(Loglevel)),
load_logger(Forms, ?LOGMODULE, Loglevel);
try
{Mod,Code} = dynamic_compile:from_string(ejabberd_logger_src(Loglevel)),
code:load_binary(Mod, ?LOGMODULE ++ ".erl", Code)
catch
Type:Error -> ?CRITICAL_MSG("Error compiling logger (~p): ~p~n", [Type, Error])
end;
set(_) ->
exit("Loglevel must be an integer").
%% --------------------------------------------------------------
%% Compile a string into a module and returns the binary
compile_string(Mod, Str) ->
Fname = Mod ++ ".erl",
{ok, Fd} = open_ram_file(Fname),
file:write(Fd, Str),
file:position(Fd, 0),
case epp_dodger:parse(Fd) of
{ok, Tree} ->
Forms = revert_tree(Tree),
close_ram_file(Fd),
Forms;
Error ->
close_ram_file(Fd),
Error
end.
open_ram_file(Fname) ->
ram_file_io_server:start(self(), Fname, [read,write]).
close_ram_file(Fd) ->
file:close(Fd).
revert_tree(Tree) ->
[erl_syntax:revert(T) || T <- Tree].
load_logger(Forms, Mod, Loglevel) ->
Fname = Mod ++ ".erl",
case compile:forms(Forms, [binary, {d,'LOGLEVEL',Loglevel}]) of
{ok, M, Bin} ->
code:load_binary(M, Fname, Bin);
Error ->
?CRITICAL_MSG("Error ~p~n", [Error])
level_to_integer(Level) ->
case lists:keysearch(Level, 2, ?LOG_LEVELS) of
{value, {Int, Level, _Desc}} -> Int;
_ -> erlang:error({no_such_loglevel, Level})
end.
%% --------------------------------------------------------------
%% Code of the ejabberd logger, dynamically compiled and loaded
%% This allows to dynamically change log level while keeping a
%% very efficient code.
%% very efficient code.
ejabberd_logger_src(Loglevel) ->
L = integer_to_list(Loglevel),
"-module(ejabberd_logger).
@ -98,7 +85,10 @@ ejabberd_logger_src(Loglevel) ->
info_msg/4,
warning_msg/4,
error_msg/4,
critical_msg/4]).
critical_msg/4,
get/0]).
get() -> "++ L ++".
%% Helper functions
debug_msg(Module, Line, Format, Args) when " ++ L ++ " >= 5 ->

View File

@ -5,7 +5,7 @@
%%% Created : 1 Nov 2006 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA

683
src/ejabberd_piefxis.erl Normal file
View File

@ -0,0 +1,683 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_piefxis.erl
%%% Author : Pablo Polvorin, Vidal Santiago Martinez
%%% Purpose : XEP-0227: Portable Import/Export Format for XMPP-IM Servers
%%% Created : 17 Jul 2008 by Pablo Polvorin <pablo.polvorin@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
%%% Not implemented:
%%% - write mod_piefxis with ejabberdctl commands
%%% - Export from mod_offline_odbc.erl
%%% - Export from mod_private_odbc.erl
%%% - XEP-227: 6. Security Considerations
%%% - Other schemas of XInclude are not tested, and may not be imported correctly.
%%% - If a host has many users, split that host in XML files with 50 users each.
%%%% Headers
-module(ejabberd_piefxis).
-export([import_file/1, export_server/1, export_host/2]).
-record(parsing_state, {parser, host, dir}).
-include("ejabberd.hrl").
%%-include_lib("exmpp/include/exmpp.hrl").
%%-include_lib("exmpp/include/exmpp_client.hrl").
%% Copied from exmpp header files:
-define(NS_ROSTER, "jabber:iq:roster").
-define(NS_VCARD, "vcard-temp").
-record(xmlcdata, {
cdata = <<>>
}).
-record(xmlattr, {
ns = undefined,
name,
value
}).
-record(xmlel, {
ns = undefined,
declared_ns = [],
name,
attrs = [],
children = []
}).
-record(iq, {
kind,
type,
id,
ns,
payload,
error,
lang,
iq_ns
}).
-record(xmlendtag, {
ns = undefined,
name
}).
%% Copied from mod_private.erl
-record(private_storage, {usns, xml}).
%%-define(ERROR_MSG(M,Args),io:format(M,Args)).
%%-define(INFO_MSG(M,Args),ok).
-define(CHUNK_SIZE,1024*20). %20k
-define(BTL, binary_to_list).
-define(LTB, list_to_binary).
-define(NS_XINCLUDE, 'http://www.w3.org/2001/XInclude').
%%%==================================
%%%% Import file
import_file(FileName) ->
_ = #xmlattr{}, %% this stupid line is only to prevent compilation warning about "recod xmlattr is unused"
import_file(FileName, 2).
import_file(FileName, RootDepth) ->
try_start_exmpp(),
Dir = filename:dirname(FileName),
{ok, IO} = try_open_file(FileName),
Parser = exmpp_xml:start_parser([{max_size,infinity},
{root_depth, RootDepth},
{emit_endtag,true}]),
read_chunks(IO, #parsing_state{parser=Parser, dir=Dir}),
file:close(IO),
exmpp_xml:stop_parser(Parser).
try_start_exmpp() ->
try exmpp:start()
catch
error:{already_started, exmpp} -> ok;
error:undef -> throw({error, exmpp_not_installed})
end.
try_open_file(FileName) ->
case file:open(FileName,[read,binary]) of
{ok, IO} -> {ok, IO};
{error, enoent} -> throw({error, {file_not_found, FileName}})
end.
%%File could be large.. we read it in chunks
read_chunks(IO,State) ->
case file:read(IO,?CHUNK_SIZE) of
{ok,Chunk} ->
NewState = process_chunk(Chunk,State),
read_chunks(IO,NewState);
eof ->
ok
end.
process_chunk(Chunk,S =#parsing_state{parser=Parser}) ->
case exmpp_xml:parse(Parser,Chunk) of
continue ->
S;
XMLElements ->
process_elements(XMLElements,S)
end.
%%%==================================
%%%% Process Elements
process_elements(Elements,State) ->
lists:foldl(fun process_element/2,State,Elements).
%%%==================================
%%%% Process Element
process_element(El=#xmlel{name=user, ns=_XMLNS},
State=#parsing_state{host=Host}) ->
case add_user(El,Host) of
{error, _Other} -> error;
_ -> ok
end,
State;
process_element(H=#xmlel{name=host},State) ->
State#parsing_state{host=?BTL(exmpp_xml:get_attribute(H,"jid",none))};
process_element(#xmlel{name='server-data'},State) ->
State;
process_element(El=#xmlel{name=include, ns=?NS_XINCLUDE}, State=#parsing_state{dir=Dir}) ->
case exmpp_xml:get_attribute(El, href, none) of
none ->
ok;
HrefB ->
Href = binary_to_list(HrefB),
%%?INFO_MSG("Parse also this file: ~n~p", [Href]),
FileName = filename:join([Dir, Href]),
import_file(FileName, 1),
Href
end,
State;
process_element(#xmlcdata{cdata = _CData},State) ->
State;
process_element(#xmlendtag{ns = _NS, name='server-data'},State) ->
State;
process_element(#xmlendtag{ns = _NS, name=_Name},State) ->
State;
process_element(El,State) ->
io:format("Warning!: unknown element found: ~p ~n",[El]),
State.
%%%==================================
%%%% Add user
add_user(El, Domain) ->
User = exmpp_xml:get_attribute(El,name,none),
Password = exmpp_xml:get_attribute(El,password,none),
add_user(El, Domain, ?BTL(User), ?BTL(Password)).
%% @spec (El::xmlel(), Domain::string(), User::string(), Password::string())
%% -> ok | {atomic, exists} | {error, not_allowed}
%% @doc Add a new user to the database.
%% If user already exists, it will be only updated.
add_user(El, Domain, User, Password) ->
case create_user(User,Password,Domain) of
ok ->
ok = exmpp_xml:foreach(
fun(_,Child) ->
populate_user(User,Domain,Child)
end,
El),
ok;
{atomic, exists} ->
?INFO_MSG("User ~p@~p already exists, using stored profile...~n",
[User, Domain]),
io:format(""),
ok = exmpp_xml:foreach(
fun(_,Child) ->
populate_user(User,Domain,Child)
end,
El);
{error, Other} ->
?ERROR_MSG("Error adding user ~s@~s: ~p~n", [User, Domain, Other])
end.
%% @spec (User::string(), Password::string(), Domain::string())
%% -> ok | {atomic, exists} | {error, not_allowed}
%% @doc Create a new user
create_user(User,Password,Domain) ->
case ejabberd_auth:try_register(User,Domain,Password) of
{atomic,ok} -> ok;
{atomic, exists} -> {atomic, exists};
{error, not_allowed} -> {error, not_allowed};
Other -> {error, Other}
end.
%%%==================================
%%%% Populate user
%% @spec (User::string(), Domain::string(), El::xml())
%% -> ok | {error, not_found}
%%
%% @doc Add a new user from a XML file with a roster list.
%%
%% Example of a file:
%% ```
%% <?xml version='1.0' encoding='UTF-8'?>
%% <server-data xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'>
%% <host jid='localhost'>
%% <user name='juliet' password='s3crEt'>
%% <query xmlns='jabber:iq:roster'>
%% <item jid='romeo@montague.net'
%% name='Romeo'
%% subscription='both'>
%% <group>Friends</group>
%% </item>
%% </query>
%% </user>
%% </host>
%% </server-data>
%% '''
populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) ->
io:format("Trying to add/update roster list...",[]),
case loaded_module(Domain,[mod_roster_odbc,mod_roster]) of
{ok, M} ->
case M:set_items(User, Domain, exmpp_xml:xmlel_to_xmlelement(El)) of
{atomic, ok} ->
io:format(" DONE.~n",[]),
ok;
_ ->
io:format(" ERROR.~n",[]),
?ERROR_MSG("Error trying to add a new user: ~s ~n",
[exmpp_xml:document_to_list(El)]),
{error, not_found}
end;
E -> io:format(" ERROR: ~p~n",[E]),
?ERROR_MSG("No modules loaded [mod_roster, mod_roster_odbc] ~s ~n",
[exmpp_xml:document_to_list(El)]),
{error, not_found}
end;
%% @spec User = String with the user name
%% Domain = String with a domain name
%% El = Sub XML element with vCard tags values
%% @ret ok | {error, not_found}
%% @doc Read vcards from the XML and send it to the server
%%
%% Example:
%% ```
%% <?xml version='1.0' encoding='UTF-8'?>
%% <server-data xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'>
%% <host jid='localhost'>
%% <user name='admin' password='s3crEt'>
%% <vCard xmlns='vcard-temp'>
%% <FN>Admin</FN>
%% </vCard>
%% </user>
%% </host>
%% </server-data>
%% '''
populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) ->
io:format("Trying to add/update vCards...",[]),
case loaded_module(Domain,[mod_vcard,mod_vcard_odbc]) of
{ok, M} -> FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)),
IQ = iq_to_old_iq(#iq{type = set, payload = El}),
case M:process_sm_iq(FullUser, FullUser , IQ) of
{error,_Err} ->
io:format(" ERROR.~n",[]),
?ERROR_MSG("Error processing vcard ~s : ~p ~n",
[exmpp_xml:document_to_list(El), _Err]);
_ ->
io:format(" DONE.~n",[]), ok
end;
_ ->
io:format(" ERROR.~n",[]),
?ERROR_MSG("No modules loaded [mod_vcard, mod_vcard_odbc] ~s ~n",
[exmpp_xml:document_to_list(El)]),
{error, not_found}
end;
%% @spec User = String with the user name
%% Domain = String with a domain name
%% El = Sub XML element with offline messages values
%% @ret ok | {error, not_found}
%% @doc Read off-line message from the XML and send it to the server
populate_user(User,Domain,El=#xmlel{name='offline-messages'}) ->
io:format("Trying to add/update offline-messages...",[]),
case loaded_module(Domain, [mod_offline, mod_offline_odbc]) of
{ok, M} ->
ok = exmpp_xml:foreach(
fun (_Element, {xmlcdata, _}) ->
ok;
(_Element, Child) ->
From = exmpp_xml:get_attribute(Child,from,none),
FullFrom = jid_to_old_jid(exmpp_jid:parse(From)),
FullUser = jid_to_old_jid(exmpp_jid:make(User,
Domain)),
OldChild = exmpp_xml:xmlel_to_xmlelement(Child),
_R = M:store_packet(FullFrom, FullUser, OldChild)
end, El), io:format(" DONE.~n",[]);
_ ->
io:format(" ERROR.~n",[]),
?ERROR_MSG("No modules loaded [mod_offline, mod_offline_odbc] ~s ~n",
[exmpp_xml:document_to_list(El)]),
{error, not_found}
end;
%% @spec User = String with the user name
%% Domain = String with a domain name
%% El = Sub XML element with private storage values
%% @ret ok | {error, not_found}
%% @doc Private storage parsing
populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:private'}) ->
io:format("Trying to add/update private storage...",[]),
case loaded_module(Domain,[mod_private_odbc,mod_private]) of
{ok, M} ->
FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)),
IQ = iq_to_old_iq(#iq{type = set,
ns = 'jabber:iq:private',
kind = request,
iq_ns = 'jabberd:client',
payload = El}),
case M:process_sm_iq(FullUser, FullUser, IQ ) of
{error, _Err} ->
io:format(" ERROR.~n",[]),
?ERROR_MSG("Error processing private storage ~s : ~p ~n",
[exmpp_xml:document_to_list(El), _Err]);
_ -> io:format(" DONE.~n",[]), ok
end;
_ ->
io:format(" ERROR.~n",[]),
?ERROR_MSG("No modules loaded [mod_private, mod_private_odbc] ~s ~n",
[exmpp_xml:document_to_list(El)]),
{error, not_found}
end;
populate_user(_User, _Domain, #xmlcdata{cdata = _CData}) ->
ok;
populate_user(_User, _Domain, _El) ->
ok.
%%%==================================
%%%% Utilities
loaded_module(Domain,Options) ->
LoadedModules = gen_mod:loaded_modules(Domain),
case lists:filter(fun(Module) ->
lists:member(Module, LoadedModules)
end, Options) of
[M|_] -> {ok, M};
[] -> {error,not_found}
end.
jid_to_old_jid(Jid) ->
{jid, to_list(exmpp_jid:node_as_list(Jid)),
to_list(exmpp_jid:domain_as_list(Jid)),
to_list(exmpp_jid:resource_as_list(Jid)),
to_list(exmpp_jid:prep_node_as_list(Jid)),
to_list(exmpp_jid:prep_domain_as_list(Jid)),
to_list(exmpp_jid:prep_resource_as_list(Jid))}.
iq_to_old_iq(#iq{id = ID, type = Type, lang = Lang, ns= NS, payload = El }) ->
{iq, to_list(ID), Type, to_list(NS), to_list(Lang),
exmpp_xml:xmlel_to_xmlelement(El)}.
to_list(L) when is_list(L) -> L;
to_list(B) when is_binary(B) -> binary_to_list(B);
to_list(undefined) -> "";
to_list(B) when is_atom(B) -> atom_to_list(B).
%%%==================================
%%%% Export server
%% @spec (Dir::string()) -> ok
export_server(Dir) ->
try_start_exmpp(),
FnT = make_filename_template(),
DFn = make_main_basefilename(Dir, FnT),
{ok, Fd} = file_open(DFn),
print(Fd, make_piefxis_xml_head()),
print(Fd, make_piefxis_server_head()),
Hosts = ?MYHOSTS,
FilesAndHosts = [{make_host_filename(FnT, Host), Host} || Host <- Hosts],
[print(Fd, make_xinclude(FnH)) || {FnH, _Host} <- FilesAndHosts],
print(Fd, make_piefxis_server_tail()),
print(Fd, make_piefxis_xml_tail()),
file_close(Fd),
[export_host(Dir, FnH, Host) || {FnH, Host} <- FilesAndHosts],
ok.
%%%==================================
%%%% Export host
%% @spec (Dir::string(), Host::string()) -> ok
export_host(Dir, Host) ->
try_start_exmpp(),
FnT = make_filename_template(),
FnH = make_host_filename(FnT, Host),
export_host(Dir, FnH, Host).
%% @spec (Dir::string(), Fn::string(), Host::string()) -> ok
export_host(Dir, FnH, Host) ->
DFn = make_host_basefilename(Dir, FnH),
{ok, Fd} = file_open(DFn),
print(Fd, make_piefxis_xml_head()),
print(Fd, make_piefxis_host_head(Host)),
Users = ejabberd_auth:get_vh_registered_users(Host),
[export_user(Fd, Username, Host) || {Username, _Host} <- Users],
print(Fd, make_piefxis_host_tail()),
print(Fd, make_piefxis_xml_tail()),
file_close(Fd).
%%%==================================
%%%% PIEFXIS formatting
%% @spec () -> string()
make_piefxis_xml_head() ->
"<?xml version='1.0' encoding='UTF-8'?>".
%% @spec () -> string()
make_piefxis_xml_tail() ->
"".
%% @spec () -> string()
make_piefxis_server_head() ->
"<server-data"
" xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'"
" xmlns:xi='http://www.w3.org/2001/XInclude'>".
%% @spec () -> string()
make_piefxis_server_tail() ->
"</server-data>".
%% @spec (Host::string()) -> string()
make_piefxis_host_head(Host) ->
NSString =
" xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'"
" xmlns:xi='http://www.w3.org/2001/XInclude'",
io_lib:format("<host~s jid='~s'>", [NSString, Host]).
%% @spec () -> string()
make_piefxis_host_tail() ->
"</host>".
%% @spec (Fn::string()) -> string()
make_xinclude(Fn) ->
Base = filename:basename(Fn),
io_lib:format("<xi:include href='~s'/>", [Base]).
%%%==================================
%%%% Export user
%% @spec (Fd, Username::string(), Host::string()) -> ok
%% @doc Extract user information and print it.
export_user(Fd, Username, Host) ->
UserString = extract_user(Username, Host),
print(Fd, UserString).
%% @spec (Username::string(), Host::string()) -> string()
extract_user(Username, Host) ->
Password = ejabberd_auth:get_password_s(Username, Host),
UserInfo = [extract_user_info(InfoName, Username, Host) || InfoName <- [roster, offline, private, vcard]],
UserInfoString = lists:flatten(UserInfo),
io_lib:format("<user name='~s' password='~s'>~s</user>", [Username, Password, UserInfoString]).
%% @spec (InfoName::atom(), Username::string(), Host::string()) -> string()
extract_user_info(roster, Username, Host) ->
case loaded_module(Host,[mod_roster_odbc,mod_roster]) of
{ok, M} ->
From = To = jlib:make_jid(Username, Host, ""),
SubelGet = {xmlelement, "query", [{"xmlns",?NS_ROSTER}], []},
%%IQGet = #iq{type=get, xmlns=?NS_ROSTER, payload=SubelGet}, % this is for 3.0.0 version
IQGet = {iq, "", get, ?NS_ROSTER, "" , SubelGet},
Res = M:process_local_iq(From, To, IQGet),
%%[El] = Res#iq.payload, % this is for 3.0.0 version
{iq, _, result, _, _, Els} = Res,
case Els of
[El] -> exmpp_xml:document_to_list(El);
[] -> ""
end;
_E ->
""
end;
extract_user_info(offline, Username, Host) ->
case loaded_module(Host,[mod_offline,mod_offline_odbc]) of
{ok, mod_offline} ->
Els = mnesia_pop_offline_messages([], Username, Host),
case Els of
[] -> "";
Els ->
OfEl = {xmlelement, "offline-messages", [], Els},
exmpp_xml:document_to_list(OfEl)
end;
{ok, mod_offline_odbc} ->
"";
_E ->
""
end;
extract_user_info(private, Username, Host) ->
case loaded_module(Host,[mod_private,mod_private_odbc]) of
{ok, mod_private} ->
get_user_private_mnesia(Username, Host);
{ok, mod_private_odbc} ->
"";
_E ->
""
end;
extract_user_info(vcard, Username, Host) ->
case loaded_module(Host,[mod_vcard, mod_vcard_odbc, mod_vcard_odbc]) of
{ok, M} ->
From = To = jlib:make_jid(Username, Host, ""),
SubelGet = {xmlelement, "vCard", [{"xmlns",?NS_VCARD}], []},
%%IQGet = #iq{type=get, xmlns=?NS_VCARD, payload=SubelGet}, % this is for 3.0.0 version
IQGet = {iq, "", get, ?NS_VCARD, "" , SubelGet},
Res = M:process_sm_iq(From, To, IQGet),
%%[El] = Res#iq.payload, % this is for 3.0.0 version
{iq, _, result, _, _, Els} = Res,
case Els of
[El] -> exmpp_xml:document_to_list(El);
[] -> ""
end;
_E ->
""
end.
%%%==================================
%%%% Interface with ejabberd offline storage
%% Copied from mod_offline.erl and customized
-record(offline_msg, {us, timestamp, expire, from, to, packet}).
mnesia_pop_offline_messages(Ls, User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
Rs = mnesia:wread({offline_msg, US}),
%%mnesia:delete({offline_msg, US}),
Rs
end,
case mnesia:transaction(F) of
{atomic, Rs} ->
TS = now(),
Ls ++ lists:map(
fun(R) ->
{xmlelement, Name, Attrs, Els} = R#offline_msg.packet,
FromString = jlib:jid_to_string(R#offline_msg.from),
Attrs2 = lists:keystore("from", 1, Attrs, {"from", FromString}),
Attrs3 = lists:keystore("xmlns", 1, Attrs2, {"xmlns", "jabber:client"}),
{xmlelement, Name, Attrs3,
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp))]}
end,
lists:filter(
fun(R) ->
case R#offline_msg.expire of
never ->
true;
TimeStamp ->
TS < TimeStamp
end
end,
lists:keysort(#offline_msg.timestamp, Rs)));
_ ->
Ls
end.
%%%==================================
%%%% Interface with ejabberd private storage
get_user_private_mnesia(Username, Host) ->
ListNsEl = mnesia:dirty_select(private_storage,
[{#private_storage{usns={Username, Host, '$1'}, xml = '$2'},
[], ['$$']}]),
Els = [exmpp_xml:document_to_list(El) || [_Ns, El] <- ListNsEl],
case lists:flatten(Els) of
"" -> "";
ElsString ->
io_lib:format("<query xmlns='jabber:iq:private'>~s</query>", [ElsString])
end.
%%%==================================
%%%% Disk file access
%% @spec () -> string()
make_filename_template() ->
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(),
lists:flatten(
io_lib:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w",
[Year, Month, Day, Hour, Minute, Second])).
%% @spec (Dir::string(), FnT::string()) -> string()
make_main_basefilename(Dir, FnT) ->
Filename2 = filename:flatten([FnT, ".xml"]),
filename:join([Dir, Filename2]).
%% @spec (FnT::string(), Host::string()) -> FnH::string()
%% @doc Make the filename for the host.
%% Example: ``("20080804-231550", "jabber.example.org") -> "20080804-231550_jabber_example_org.xml"''
make_host_filename(FnT, Host) ->
Host2 = string:join(string:tokens(Host, "."), "_"),
filename:flatten([FnT, "_", Host2, ".xml"]).
make_host_basefilename(Dir, FnT) ->
filename:join([Dir, FnT]).
%% @spec (Fn::string()) -> {ok, Fd}
file_open(Fn) ->
file:open(Fn, [write]).
%% @spec (Fd) -> ok
file_close(Fd) ->
file:close(Fd).
%% @spec (Fd, String::string()) -> ok
print(Fd, String) ->
io:format(Fd, String, []).
%%%==================================
%%% vim: set filetype=erlang tabstop=8 foldmarker=%%%%,%%%= foldmethod=marker:

View File

@ -5,7 +5,7 @@
%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -52,14 +52,21 @@ start_hosts() ->
%% Start the ODBC module on the given host
start_odbc(Host) ->
Supervisor_name = gen_mod:get_module_proc(Host, ejabberd_odbc_sup),
ChildSpec =
{gen_mod:get_module_proc(Host, ejabberd_odbc_sup),
{Supervisor_name,
{ejabberd_odbc_sup, start_link, [Host]},
temporary,
transient,
infinity,
supervisor,
[ejabberd_odbc_sup]},
supervisor:start_child(ejabberd_sup, ChildSpec).
case supervisor:start_child(ejabberd_sup, ChildSpec) of
{ok, _PID} ->
ok;
_Error ->
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying...~n", [Supervisor_name, _Error]),
start_odbc(Host)
end.
%% Returns true if we have configured odbc_server for the given host
needs_odbc(Host) ->

View File

@ -5,7 +5,7 @@
%%% Created : 10 Nov 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -54,6 +54,8 @@
xml_stream_state,
timeout}).
-define(HIBERNATE_TIMEOUT, 90000).
%%====================================================================
%% API
%%====================================================================
@ -141,7 +143,7 @@ handle_call({starttls, TLSSocket}, _From,
xml_stream_state = NewXMLStreamState},
case tls:recv_data(TLSSocket, "") of
{ok, TLSData} ->
{reply, ok, process_data(TLSData, NewState)};
{reply, ok, process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
{error, _Reason} ->
{stop, normal, ok, NewState}
end;
@ -156,7 +158,7 @@ handle_call({compress, ZlibSocket}, _From,
xml_stream_state = NewXMLStreamState},
case ejabberd_zlib:recv_data(ZlibSocket, "") of
{ok, ZlibData} ->
{reply, ok, process_data(ZlibData, NewState)};
{reply, ok, process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
{error, _Reason} ->
{stop, normal, ok, NewState}
end;
@ -167,17 +169,18 @@ handle_call(reset_stream, _From,
close_stream(XMLStreamState),
NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize),
Reply = ok,
{reply, Reply, State#state{xml_stream_state = NewXMLStreamState}};
{reply, Reply, State#state{xml_stream_state = NewXMLStreamState},
?HIBERNATE_TIMEOUT};
handle_call({become_controller, C2SPid}, _From, State) ->
XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size),
NewState = State#state{c2s_pid = C2SPid,
xml_stream_state = XMLStreamState},
activate_socket(NewState),
Reply = ok,
{reply, Reply, NewState};
{reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
{reply, Reply, State, ?HIBERNATE_TIMEOUT}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
@ -187,11 +190,11 @@ handle_call(_Request, _From, State) ->
%%--------------------------------------------------------------------
handle_cast({change_shaper, Shaper}, State) ->
NewShaperState = shaper:new(Shaper),
{noreply, State#state{shaper_state = NewShaperState}};
{noreply, State#state{shaper_state = NewShaperState}, ?HIBERNATE_TIMEOUT};
handle_cast(close, State) ->
{stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State}.
{noreply, State, ?HIBERNATE_TIMEOUT}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
@ -202,24 +205,26 @@ handle_cast(_Msg, State) ->
handle_info({Tag, _TCPSocket, Data},
#state{socket = Socket,
sock_mod = SockMod} = State)
when (Tag == tcp) or (Tag == ssl) ->
when (Tag == tcp) or (Tag == ssl) or (Tag == ejabberd_xml) ->
case SockMod of
tls ->
case tls:recv_data(Socket, Data) of
{ok, TLSData} ->
{noreply, process_data(TLSData, State)};
{noreply, process_data(TLSData, State),
?HIBERNATE_TIMEOUT};
{error, _Reason} ->
{stop, normal, State}
end;
ejabberd_zlib ->
case ejabberd_zlib:recv_data(Socket, Data) of
{ok, ZlibData} ->
{noreply, process_data(ZlibData, State)};
{noreply, process_data(ZlibData, State),
?HIBERNATE_TIMEOUT};
{error, _Reason} ->
{stop, normal, State}
end;
_ ->
{noreply, process_data(Data, State)}
{noreply, process_data(Data, State), ?HIBERNATE_TIMEOUT}
end;
handle_info({Tag, _TCPSocket}, State)
when (Tag == tcp_closed) or (Tag == ssl_closed) ->
@ -228,15 +233,18 @@ handle_info({Tag, _TCPSocket, Reason}, State)
when (Tag == tcp_error) or (Tag == ssl_error) ->
case Reason of
timeout ->
{noreply, State};
{noreply, State, ?HIBERNATE_TIMEOUT};
_ ->
{stop, normal, State}
end;
handle_info({timeout, _Ref, activate}, State) ->
activate_socket(State),
{noreply, State};
{noreply, State, ?HIBERNATE_TIMEOUT};
handle_info(timeout, State) ->
proc_lib:hibernate(gen_server, enter_loop, [?MODULE, [], State]),
{noreply, State, ?HIBERNATE_TIMEOUT};
handle_info(_Info, State) ->
{noreply, State}.
{noreply, State, ?HIBERNATE_TIMEOUT}.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
@ -286,13 +294,35 @@ activate_socket(#state{socket = Socket,
ok
end.
%% Data processing for connectors directly generating xmlelement in
%% Erlang data structure.
%% WARNING: Shaper does not work with Erlang data structure.
process_data([], State) ->
activate_socket(State),
State;
process_data([Element|Els], #state{c2s_pid = C2SPid} = State)
when element(1, Element) == xmlelement;
element(1, Element) == xmlstreamstart;
element(1, Element) == xmlstreamelement;
element(1, Element) == xmlstreamend ->
if
C2SPid == undefined ->
State;
true ->
catch gen_fsm:send_event(C2SPid, element_wrapper(Element)),
process_data(Els, State)
end;
%% Data processing for connectors receivind data as string.
process_data(Data,
#state{xml_stream_state = XMLStreamState,
shaper_state = ShaperState} = State) ->
shaper_state = ShaperState,
c2s_pid = C2SPid} = 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
C2SPid == undefined ->
ok;
Pause > 0 ->
erlang:start_timer(Pause, self(), activate);
true ->
@ -301,6 +331,16 @@ process_data(Data,
State#state{xml_stream_state = XMLStreamState1,
shaper_state = NewShaperState}.
%% Element coming from XML parser are wrapped inside xmlstreamelement
%% When we receive directly xmlelement tuple (from a socket module
%% speaking directly Erlang XML), we wrap it inside the same
%% xmlstreamelement coming from the XML parser.
element_wrapper(XMLElement)
when element(1, XMLElement) == xmlelement ->
{xmlstreamelement, XMLElement};
element_wrapper(Element) ->
Element.
close_stream(undefined) ->
ok;
close_stream(XMLStreamState) ->

View File

@ -5,7 +5,7 @@
%%% Created : 27 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -150,9 +150,9 @@ unregister_route(Domain) ->
mnesia:transaction(F);
_ ->
F = fun() ->
case mnesia:match(#route{domain = LDomain,
pid = Pid,
_ = '_'}) of
case mnesia:match_object(#route{domain=LDomain,
pid = Pid,
_ = '_'}) of
[R] ->
I = R#route.local_hint,
mnesia:write(

View File

@ -5,7 +5,7 @@
%%% Created : 7 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -34,20 +34,24 @@
route/3,
have_connection/1,
has_key/2,
get_connections_pids/1,
try_register/1,
remove_connection/3,
dirty_get_connections/0,
allow_host/2,
ctl_process/2
incoming_s2s_number/0,
outgoing_s2s_number/0
]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
%% ejabberd API
-export([get_info_s2s_connections/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_ctl.hrl").
-include("ejabberd_commands.hrl").
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER, 1).
-define(DEFAULT_MAX_S2S_CONNECTIONS_NUMBER_PER_NODE, 1).
@ -108,6 +112,14 @@ has_key(FromTo, Key) ->
true
end.
get_connections_pids(FromTo) ->
case catch mnesia:dirty_read(s2s, FromTo) of
L when is_list(L) ->
[Connection#s2s.pid || Connection <- L];
_ ->
[]
end.
try_register(FromTo) ->
Key = randoms:get_string(),
MaxS2SConnectionsNumber = max_s2s_connections_number(FromTo),
@ -155,10 +167,7 @@ init([]) ->
{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),
ejabberd_commands:register_commands(commands()),
{ok, #state{}}.
%%--------------------------------------------------------------------
@ -212,6 +221,7 @@ handle_info(_Info, State) ->
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ejabberd_commands:unregister_commands(commands()),
ok.
%%--------------------------------------------------------------------
@ -241,12 +251,17 @@ do_route(From, To, Packet) ->
?DEBUG("s2s manager~n\tfrom ~p~n\tto ~p~n\tpacket ~P~n",
[From, To, Packet, 8]),
case find_connection(From, To) of
{atomic, Pid} when pid(Pid) ->
{atomic, Pid} when is_pid(Pid) ->
?DEBUG("sending to process ~p~n", [Pid]),
{xmlelement, Name, Attrs, Els} = Packet,
NewAttrs = jlib:replace_from_to_attrs(jlib:jid_to_string(From),
jlib:jid_to_string(To),
Attrs),
#jid{lserver = MyServer} = From,
ejabberd_hooks:run(
s2s_send_packet,
MyServer,
[From, To, Packet]),
send_element(Pid, {xmlelement, Name, NewAttrs, Els}),
ok;
{aborted, _Reason} ->
@ -413,16 +428,35 @@ is_subdomain(Domain1, Domain2) ->
send_element(Pid, El) ->
Pid ! {send_element, El}.
ctl_process(_Val, ["incoming-s2s-number"]) ->
N = length(supervisor:which_children(ejabberd_s2s_in_sup)),
?PRINT("~p~n", [N]),
{stop, ?STATUS_SUCCESS};
ctl_process(_Val, ["outgoing-s2s-number"]) ->
N = length(supervisor:which_children(ejabberd_s2s_out_sup)),
?PRINT("~p~n", [N]),
{stop, ?STATUS_SUCCESS};
ctl_process(Val, _Args) ->
Val.
%%%----------------------------------------------------------------------
%%% ejabberd commands
commands() ->
[
#ejabberd_commands{name = incoming_s2s_number,
tags = [stats, s2s],
desc = "Number of incoming s2s connections on the node",
module = ?MODULE, function = incoming_s2s_number,
args = [],
result = {s2s_incoming, integer}},
#ejabberd_commands{name = outgoing_s2s_number,
tags = [stats, s2s],
desc = "Number of outgoing s2s connections on the node",
module = ?MODULE, function = outgoing_s2s_number,
args = [],
result = {s2s_outgoing, integer}}
].
incoming_s2s_number() ->
length(supervisor:which_children(ejabberd_s2s_in_sup)).
outgoing_s2s_number() ->
length(supervisor:which_children(ejabberd_s2s_out_sup)).
%%%----------------------------------------------------------------------
%%% Update Mnesia tables
update_tables() ->
case catch mnesia:table_info(s2s, type) of
@ -462,3 +496,32 @@ allow_host(MyServer, S2SHost) ->
_ -> true %% The default s2s policy is allow
end
end.
%% Get information about S2S connections of the specified type.
%% @spec (Type) -> [Info]
%% where Type = in | out
%% Info = [{InfoName::atom(), InfoValue::any()}]
get_info_s2s_connections(Type) ->
ChildType = case Type of
in -> ejabberd_s2s_in_sup;
out -> ejabberd_s2s_out_sup
end,
Connections = supervisor:which_children(ChildType),
get_s2s_info(Connections,Type).
get_s2s_info(Connections,Type)->
complete_s2s_info(Connections,Type,[]).
complete_s2s_info([],_,Result)->
Result;
complete_s2s_info([Connection|T],Type,Result)->
{_,PID,_,_}=Connection,
State = get_s2s_state(PID),
complete_s2s_info(T,Type,[State|Result]).
get_s2s_state(S2sPid)->
Infos = case gen_fsm:sync_send_all_state_event(S2sPid,get_state_infos) of
{state_infos, Is} -> [{status, open} | Is];
{noproc,_} -> [{status, closed}]; %% Connection closed
{badrpc,_} -> [{status, error}]
end,
[{s2s_pid, S2sPid} | Infos].

View File

@ -5,7 +5,7 @@
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -352,6 +352,7 @@ stream_established({xmlstreamelement, El}, StateData) ->
case {ejabberd_s2s:allow_host(To, From),
lists:member(LTo, ejabberd_router:dirty_get_all_domains())} of
{true, true} ->
ejabberd_s2s_out:terminate_if_waiting_delay(To, From),
ejabberd_s2s_out:start(To, From,
{verify, self(),
Key, StateData#state.streamid}),
@ -411,6 +412,10 @@ stream_established({xmlstreamelement, El}, StateData) ->
if ((Name == "iq") or
(Name == "message") or
(Name == "presence")) ->
ejabberd_hooks:run(
s2s_receive_packet,
LTo,
[From, To, NewEl]),
ejabberd_router:route(
From, To, NewEl);
true ->
@ -426,6 +431,10 @@ stream_established({xmlstreamelement, El}, StateData) ->
if ((Name == "iq") or
(Name == "message") or
(Name == "presence")) ->
ejabberd_hooks:run(
s2s_receive_packet,
LTo,
[From, To, NewEl]),
ejabberd_router:route(
From, To, NewEl);
true ->
@ -509,6 +518,44 @@ stream_established(closed, StateData) ->
%%----------------------------------------------------------------------
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: The associated StateData for this connection
%% {reply, Reply, NextStateName, NextStateData}
%% Reply = {state_infos, [{InfoName::atom(), InfoValue::any()]
%%----------------------------------------------------------------------
handle_sync_event(get_state_infos, _From, StateName, StateData) ->
SockMod = StateData#state.sockmod,
{Addr,Port} = try SockMod:peername(StateData#state.socket) of
{ok, {A,P}} -> {A,P};
{error, _} -> {unknown,unknown}
catch
_:_ -> {unknown,unknown}
end,
Domains = case StateData#state.authenticated of
true ->
[StateData#state.auth_domain];
false ->
Connections = StateData#state.connections,
[D || {{D, _}, established} <-
dict:to_list(Connections)]
end,
Infos = [
{direction, in},
{statename, StateName},
{addr, Addr},
{port, Port},
{streamid, StateData#state.streamid},
{tls, StateData#state.tls},
{tls_enabled, StateData#state.tls_enabled},
{tls_options, StateData#state.tls_options},
{authenticated, StateData#state.authenticated},
{shaper, StateData#state.shaper},
{sockmod, SockMod},
{domains, Domains}
],
Reply = {state_infos, Infos},
{reply,Reply,StateName,StateData};
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4

View File

@ -5,7 +5,7 @@
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -33,6 +33,7 @@
-export([start/3,
start_link/3,
start_connection/1,
terminate_if_waiting_delay/2,
stop_connection/1]).
%% p1_fsm callbacks (same as gen_fsm)
@ -51,7 +52,8 @@
handle_info/3,
terminate/3,
code_change/4,
test_get_addr_port/1]).
test_get_addr_port/1,
get_addr_port/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
@ -67,6 +69,7 @@
db_enabled = true,
try_auth = true,
myname, server, queue,
delay_to_retry = undefined_delay,
new = false, verify = false,
timer}).
@ -81,16 +84,20 @@
%% Module start with or without supervisor:
-ifdef(NO_TRANSIENT_SUPERVISORS).
-define(SUPERVISOR_START, p1_fsm:start(ejabberd_s2s_out, [From, Host, Type],
?FSMLIMITS ++ ?FSMOPTS)).
fsm_limit_opts() ++ ?FSMOPTS)).
-else.
-define(SUPERVISOR_START, supervisor:start_child(ejabberd_s2s_out_sup,
[From, Host, Type])).
-endif.
%% Only change this value if you now what your are doing:
-define(FSMLIMITS,[]).
%% -define(FSMLIMITS, [{max_queue, 2000}]).
-define(FSMTIMEOUT, 5000).
-define(FSMTIMEOUT, 30000).
%% We do not block on send anymore.
-define(TCP_SEND_TIMEOUT, 15000).
%% Maximum delay to wait before retrying to connect after a failed attempt.
%% Specified in miliseconds. Default value is 5 minutes.
-define(MAX_RETRY_DELAY, 300000).
-define(STREAM_HEADER,
"<?xml version='1.0'?>"
@ -112,6 +119,8 @@
-define(INVALID_XML_ERR,
xml:element_to_string(?SERR_XML_NOT_WELL_FORMED)).
-define(SOCKET_DEFAULT_RESULT, {error, badarg}).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
@ -120,7 +129,7 @@ start(From, Host, Type) ->
start_link(From, Host, Type) ->
p1_fsm:start_link(ejabberd_s2s_out, [From, Host, Type],
?FSMLIMITS ++ ?FSMOPTS).
fsm_limit_opts() ++ ?FSMOPTS).
start_connection(Pid) ->
p1_fsm:send_event(Pid, init).
@ -199,7 +208,7 @@ open_socket(init, StateData) ->
_ ->
open_socket1(Addr, Port)
end
end, {error, badarg}, AddrList) of
end, ?SOCKET_DEFAULT_RESULT, AddrList) of
{ok, Socket} ->
Version = if
StateData#state.use_v10 ->
@ -217,7 +226,7 @@ open_socket(init, StateData) ->
{error, _Reason} ->
?INFO_MSG("s2s connection: ~s -> ~s (remote server not found)",
[StateData#state.myname, StateData#state.server]),
wait_before_reconnect(StateData, 300000)
wait_before_reconnect(StateData)
%%{stop, normal, StateData}
end;
open_socket(stop, StateData) ->
@ -232,34 +241,46 @@ open_socket(_, StateData) ->
{next_state, open_socket, StateData}.
%%----------------------------------------------------------------------
open_socket1(Addr, Port) ->
?DEBUG("s2s_out: connecting to ~s:~p~n", [Addr, Port]),
Res = case catch ejabberd_socket:connect(
Addr, Port,
[binary, {packet, 0},
{active, false}]) of
{ok, _Socket} = R -> R;
{error, Reason1} ->
?DEBUG("s2s_out: connect return ~p~n", [Reason1]),
catch ejabberd_socket:connect(
Addr, Port,
[binary, {packet, 0},
{active, false}, inet6]);
{'EXIT', Reason1} ->
?DEBUG("s2s_out: connect crashed ~p~n", [Reason1]),
catch ejabberd_socket:connect(
Addr, Port,
[binary, {packet, 0},
{active, false}, inet6])
end,
case Res of
{ok, Socket} ->
{ok, Socket};
{error, Reason} ->
?DEBUG("s2s_out: inet6 connect return ~p~n", [Reason]),
{error, Reason};
%% IPv4
open_socket1({_,_,_,_} = Addr, Port) ->
open_socket2(inet, Addr, Port);
%% IPv6
open_socket1({_,_,_,_,_,_,_,_} = Addr, Port) ->
open_socket2(inet6, Addr, Port);
%% Hostname
open_socket1(Host, Port) ->
lists:foldl(fun(_Family, {ok, _Socket} = R) ->
R;
(Family, _) ->
Addrs = get_addrs(Host, Family),
lists:foldl(fun(_Addr, {ok, _Socket} = R) ->
R;
(Addr, _) ->
open_socket1(Addr, Port)
end, ?SOCKET_DEFAULT_RESULT, Addrs)
end, ?SOCKET_DEFAULT_RESULT, outgoing_s2s_families()).
open_socket2(Type, Addr, Port) ->
?DEBUG("s2s_out: connecting to ~p:~p~n", [Addr, Port]),
Timeout = outgoing_s2s_timeout(),
SockOpts = case erlang:system_info(otp_release) >= "R13B" of
true -> [{send_timeout_close, true}];
false -> []
end,
case (catch ejabberd_socket:connect(Addr, Port,
[binary, {packet, 0},
{send_timeout, ?TCP_SEND_TIMEOUT},
{send_timeout_close, true},
{active, false}, Type | SockOpts],
Timeout)) of
{ok, _Socket} = R -> R;
{error, Reason} = R ->
?DEBUG("s2s_out: connect return ~p~n", [Reason]),
R;
{'EXIT', Reason} ->
?DEBUG("s2s_out: inet6 connect crashed ~p~n", [Reason]),
?DEBUG("s2s_out: connect crashed ~p~n", [Reason]),
{error, Reason}
end.
@ -277,10 +298,11 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
{next_state, wait_for_features, StateData, ?FSMTIMEOUT};
{"jabber:server", "", true} when StateData#state.use_v10 ->
{next_state, wait_for_features, StateData#state{db_enabled = false}, ?FSMTIMEOUT};
_ ->
{NSProvided, _, _} ->
send_text(StateData, ?INVALID_NAMESPACE_ERR),
?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid namespace)",
[StateData#state.myname, StateData#state.server]),
?INFO_MSG("Closing s2s connection: ~s -> ~s (invalid namespace).~n"
"Namespace provided: ~p~nNamespace expected: \"jabber:server\"",
[StateData#state.myname, StateData#state.server, NSProvided]),
{stop, normal, StateData}
end;
@ -291,6 +313,11 @@ wait_for_stream({xmlstreamerror, _}, StateData) ->
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
wait_for_stream({xmlstreamend,_Name}, StateData) ->
?INFO_MSG("Closing s2s connection: ~s -> ~s (xmlstreamend)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
wait_for_stream(timeout, StateData) ->
?INFO_MSG("Closing s2s connection: ~s -> ~s (timeout in wait_for_stream)",
[StateData#state.myname, StateData#state.server]),
@ -369,6 +396,14 @@ wait_for_validation({xmlstreamerror, _}, StateData) ->
?INVALID_XML_ERR ++ ?STREAM_TRAILER),
{stop, normal, StateData};
wait_for_validation(timeout, #state{verify = {VPid, VKey, SID}} = StateData)
when is_pid(VPid) and is_list(VKey) and is_list(SID) ->
%% This is an auxiliary s2s connection for dialback.
%% This timeout is normal and doesn't represent a problem.
?DEBUG("wait_for_validation: ~s -> ~s (timeout in verify connection)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
wait_for_validation(timeout, StateData) ->
?INFO_MSG("wait_for_validation: ~s -> ~s (connect timeout)",
[StateData#state.myname, StateData#state.server]),
@ -666,7 +701,7 @@ stream_established({xmlstreamelement, El}, StateData) ->
{next_state, stream_established, StateData};
stream_established({xmlstreamend, _Name}, StateData) ->
?INFO_MSG("stream established: ~s -> ~s (xmlstreamend)",
?INFO_MSG("Connection closed in stream established: ~s -> ~s (xmlstreamend)",
[StateData#state.myname, StateData#state.server]),
{stop, normal, StateData};
@ -711,6 +746,42 @@ stream_established(closed, StateData) ->
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData, get_timeout_interval(StateName)}.
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: The associated StateData for this connection
%% {reply, Reply, NextStateName, NextStateData}
%% Reply = {state_infos, [{InfoName::atom(), InfoValue::any()]
%%----------------------------------------------------------------------
handle_sync_event(get_state_infos, _From, StateName, StateData) ->
{Addr,Port} = try ejabberd_socket:peername(StateData#state.socket) of
{ok, {A,P}} -> {A,P};
{error, _} -> {unknown,unknown}
catch
_:_ ->
{unknown,unknown}
end,
Infos = [
{direction, out},
{statename, StateName},
{addr, Addr},
{port, Port},
{streamid, StateData#state.streamid},
{use_v10, StateData#state.use_v10},
{tls, StateData#state.tls},
{tls_required, StateData#state.tls_required},
{tls_enabled, StateData#state.tls_enabled},
{tls_options, StateData#state.tls_options},
{authenticated, StateData#state.authenticated},
{db_enabled, StateData#state.db_enabled},
{try_auth, StateData#state.try_auth},
{myname, StateData#state.myname},
{server, StateData#state.server},
{delay_to_retry, StateData#state.delay_to_retry},
{verify, StateData#state.verify}
],
Reply = {state_infos, Infos},
{reply,Reply,StateName,StateData};
%%----------------------------------------------------------------------
%% Func: handle_sync_event/4
%% Returns: {next_state, NextStateName, NextStateData} |
@ -768,6 +839,12 @@ handle_info({timeout, Timer, _}, _StateName,
?INFO_MSG("Closing connection with ~s: timeout", [StateData#state.server]),
{stop, normal, StateData};
handle_info(terminate_if_waiting_before_retry, wait_before_retry, StateData) ->
{stop, normal, StateData};
handle_info(terminate_if_waiting_before_retry, StateName, StateData) ->
{next_state, StateName, StateData, get_timeout_interval(StateName)};
handle_info(_, StateName, StateData) ->
{next_state, StateName, StateData, get_timeout_interval(StateName)}.
@ -921,11 +998,7 @@ is_verify_res(_) ->
-include_lib("kernel/include/inet.hrl").
get_addr_port(Server) ->
Res = case inet_res:getbyname("_xmpp-server._tcp." ++ Server, srv) of
{error, _Reason} ->
inet_res:getbyname("_jabber._tcp." ++ Server, srv);
{ok, _HEnt} = R -> R
end,
Res = srv_lookup(Server),
case Res of
{error, Reason} ->
?DEBUG("srv lookup of '~s' failed: ~p~n", [Server, Reason]),
@ -949,7 +1022,7 @@ get_addr_port(Server) ->
end,
{Priority * 65536 - N, Host, Port}
end, AddrList)) of
{'EXIT', _Reasn} ->
{'EXIT', _Reason} ->
[{Server, outgoing_s2s_port()}];
SortedList ->
List = lists:map(
@ -962,6 +1035,36 @@ get_addr_port(Server) ->
end
end.
srv_lookup(Server) ->
Options = case ejabberd_config:get_local_option(s2s_dns_options) of
L when is_list(L) -> L;
_ -> []
end,
TimeoutMs = timer:seconds(proplists:get_value(timeout, Options, 10)),
Retries = proplists:get_value(retries, Options, 2),
srv_lookup(Server, TimeoutMs, Retries).
%% XXX - this behaviour is suboptimal in the case that the domain
%% has a "_xmpp-server._tcp." but not a "_jabber._tcp." record and
%% we don't get a DNS reply for the "_xmpp-server._tcp." lookup. In this
%% case we'll give up when we get the "_jabber._tcp." nxdomain reply.
srv_lookup(_Server, _Timeout, Retries) when Retries < 1 ->
{error, timeout};
srv_lookup(Server, Timeout, Retries) ->
case inet_res:getbyname("_xmpp-server._tcp." ++ Server, srv, Timeout) of
{error, _Reason} ->
case inet_res:getbyname("_jabber._tcp." ++ Server, srv, Timeout) of
{error, timeout} ->
?ERROR_MSG("The DNS servers~n ~p~ntimed out on request"
" for ~p IN SRV."
" You should check your DNS configuration.",
[inet_db:res_option(nameserver), Server]),
srv_lookup(Server, Timeout, Retries - 1);
R -> R
end;
{ok, _HEnt} = R -> R
end.
test_get_addr_port(Server) ->
lists:foldl(
fun(_, Acc) ->
@ -974,6 +1077,23 @@ test_get_addr_port(Server) ->
end
end, [], lists:seq(1, 100000)).
get_addrs(Host, Family) ->
Type = case Family of
inet4 -> inet;
ipv4 -> inet;
inet6 -> inet6;
ipv6 -> inet6
end,
case inet:gethostbyname(Host, Type) of
{ok, #hostent{h_addr_list = Addrs}} ->
?DEBUG("~s of ~s resolved to: ~p~n", [Type, Host, Addrs]),
Addrs;
{error, Reason} ->
?DEBUG("~s lookup of '~s' failed: ~p~n", [Type, Host, Reason]),
[]
end.
outgoing_s2s_port() ->
case ejabberd_config:get_local_option(outgoing_s2s_port) of
Port when is_integer(Port) ->
@ -982,6 +1102,36 @@ outgoing_s2s_port() ->
5269
end.
outgoing_s2s_families() ->
case ejabberd_config:get_local_option(outgoing_s2s_options) of
{Families, _} when is_list(Families) ->
Families;
undefined ->
%% DISCUSSION: Why prefer IPv4 first?
%%
%% IPv4 connectivity will be available for everyone for
%% many years to come. So, there's absolutely no benefit
%% in preferring IPv6 connections which are flaky at best
%% nowadays.
%%
%% On the other hand content providers hesitate putting up
%% AAAA records for their sites due to the mentioned
%% quality of current IPv6 connectivity. Making IPv6 the a
%% `fallback' may avoid these problems elegantly.
[ipv4, ipv6]
end.
outgoing_s2s_timeout() ->
case ejabberd_config:get_local_option(outgoing_s2s_options) of
{_, Timeout} when is_integer(Timeout) ->
Timeout;
{_, infinity} ->
infinity;
undefined ->
%% 10 seconds
10000
end.
%% Human readable S2S logging: Log only new outgoing connections as INFO
%% Do not log dialback
log_s2s_out(false, _, _) -> ok;
@ -989,7 +1139,7 @@ log_s2s_out(false, _, _) -> ok;
log_s2s_out(_, Myname, Server) ->
?INFO_MSG("Trying to open s2s connection: ~s -> ~s",[Myname, Server]).
%% Calcultate timeout depending on which state we are in:
%% Calculate timeout depending on which state we are in:
%% Can return integer > 0 | infinity
get_timeout_interval(StateName) ->
case StateName of
@ -1005,11 +1155,53 @@ get_timeout_interval(StateName) ->
%% This function is intended to be called at the end of a state
%% function that want to wait for a reconnect delay before stopping.
wait_before_reconnect(StateData, Delay) ->
wait_before_reconnect(StateData) ->
%% bounce queue manage by process and Erlang message queue
bounce_queue(StateData#state.queue, ?ERR_REMOTE_SERVER_NOT_FOUND),
bounce_messages(?ERR_REMOTE_SERVER_NOT_FOUND),
cancel_timer(StateData#state.timer),
Delay = case StateData#state.delay_to_retry of
undefined_delay ->
%% The initial delay is random between 1 and 15 seconds
%% Return a random integer between 1000 and 15000
{_, _, MicroSecs} = now(),
(MicroSecs rem 14000) + 1000;
D1 ->
%% Duplicate the delay with each successive failed
%% reconnection attempt, but don't exceed the max
lists:min([D1 * 2, get_max_retry_delay()])
end,
Timer = erlang:start_timer(Delay, self(), []),
{next_state, wait_before_retry, StateData#state{timer=Timer,
delay_to_retry = Delay,
queue = queue:new()}}.
%% @doc Get the maximum allowed delay for retry to reconnect (in miliseconds).
%% The default value is 5 minutes.
%% The option {s2s_max_retry_delay, Seconds} can be used (in seconds).
%% @spec () -> integer()
get_max_retry_delay() ->
case ejabberd_config:get_local_option(s2s_max_retry_delay) of
Seconds when is_integer(Seconds) ->
Seconds*1000;
_ ->
?MAX_RETRY_DELAY
end.
%% Terminate s2s_out connections that are in state wait_before_retry
terminate_if_waiting_delay(From, To) ->
FromTo = {From, To},
Pids = ejabberd_s2s:get_connections_pids(FromTo),
lists:foreach(
fun(Pid) ->
Pid ! terminate_if_waiting_before_retry
end,
Pids).
fsm_limit_opts() ->
case ejabberd_config:get_local_option(max_fsm_queue) of
N when is_integer(N) ->
[{max_queue, N}];
_ ->
[]
end.

View File

@ -1,11 +1,11 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_service.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : External component management
%%% Purpose : External component management (XEP-0114)
%%% Created : 6 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -27,7 +27,9 @@
-module(ejabberd_service).
-author('alexey@process-one.net').
-behaviour(gen_fsm).
-define(GEN_FSM, p1_fsm).
-behaviour(?GEN_FSM).
%% External exports
-export([start/2,
@ -73,13 +75,18 @@
-define(STREAM_TRAILER, "</stream:stream>").
-define(INVALID_HEADER_ERR,
"<stream:stream>"
"<stream:stream "
"xmlns:stream='http://etherx.jabber.org/streams'>"
"<stream:error>Invalid Stream Header</stream:error>"
"</stream:stream>"
).
-define(INVALID_HANDSHAKE_ERR,
"<stream:error>Invalid Handshake</stream:error>"
"<stream:error>"
"<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>"
"<text xmlns='urn:ietf:params:xml:ns:xmpp-streams' xml:lang='en'>"
"Invalid Handshake</text>"
"</stream:error>"
"</stream:stream>"
).
@ -95,7 +102,8 @@ start(SockData, Opts) ->
supervisor:start_child(ejabberd_service_sup, [SockData, Opts]).
start_link(SockData, Opts) ->
gen_fsm:start_link(ejabberd_service, [SockData, Opts], ?FSMOPTS).
?GEN_FSM:start_link(ejabberd_service, [SockData, Opts],
fsm_limit_opts(Opts) ++ ?FSMOPTS).
socket_type() ->
xml_stream.
@ -168,11 +176,15 @@ init([{SockMod, Socket}, Opts]) ->
%%----------------------------------------------------------------------
wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
% TODO
case xml:get_attr_s("xmlns", Attrs) of
"jabber:component:accept" ->
%% Note: XEP-0114 requires to check that destination is a Jabber
%% component served by this Jabber server.
%% However several transports don't respect that,
%% so ejabberd doesn't check 'to' attribute (EJAB-717)
To = xml:get_attr_s("to", Attrs),
Header = io_lib:format(?STREAM_HEADER,
[StateData#state.streamid, ?MYNAME]),
[StateData#state.streamid, xml:crypt(To)]),
send_text(StateData, Header),
{next_state, wait_for_handshake, StateData};
_ ->
@ -374,3 +386,16 @@ send_element(StateData, El) ->
new_id() ->
randoms:get_string().
fsm_limit_opts(Opts) ->
case lists:keysearch(max_fsm_queue, 1, Opts) of
{value, {_, N}} when is_integer(N) ->
[{max_queue, N}];
_ ->
case ejabberd_config:get_local_option(max_fsm_queue) of
N when is_integer(N) ->
[{max_queue, N}];
_ ->
[]
end
end.

View File

@ -5,7 +5,7 @@
%%% Created : 24 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -43,10 +43,13 @@
dirty_get_sessions_list/0,
dirty_get_my_sessions_list/0,
get_vh_session_list/1,
get_vh_session_number/1,
register_iq_handler/4,
register_iq_handler/5,
unregister_iq_handler/2,
ctl_process/2,
connected_users/0,
connected_users_number/0,
user_resources/2,
get_session_pid/3,
get_user_info/3,
get_user_ip/3
@ -58,9 +61,11 @@
-include("ejabberd.hrl").
-include("jlib.hrl").
-include("ejabberd_ctl.hrl").
-include("ejabberd_commands.hrl").
-include("mod_privacy.hrl").
-record(session, {sid, usr, us, priority, info}).
-record(session_counter, {vhost, count}).
-record(state, {}).
%% default value for the maximum number of user connections
@ -87,6 +92,7 @@ route(From, To, Packet) ->
open_session(SID, User, Server, Resource, Info) ->
set_session(SID, User, Server, Resource, undefined, Info),
inc_session_counter(jlib:nameprep(Server)),
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,
@ -98,7 +104,8 @@ close_session(SID, User, Server, Resource) ->
[#session{info=I}] -> I
end,
F = fun() ->
mnesia:delete({session, SID})
mnesia:delete({session, SID}),
dec_session_counter(jlib:nameprep(Server))
end,
mnesia:sync_dirty(F),
JID = jlib:make_jid(User, Server, Resource),
@ -211,6 +218,19 @@ get_vh_session_list(Server) ->
[{'==', {element, 2, '$1'}, LServer}],
['$1']}]).
get_vh_session_number(Server) ->
LServer = jlib:nameprep(Server),
Query = mnesia:dirty_select(
session_counter,
[{#session_counter{vhost = LServer, count = '$1'},
[],
['$1']}]),
case Query of
[Count] ->
Count;
_ -> 0
end.
register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}.
@ -237,6 +257,9 @@ init([]) ->
mnesia:create_table(session,
[{ram_copies, [node()]},
{attributes, record_info(fields, session)}]),
mnesia:create_table(session_counter,
[{ram_copies, [node()]},
{attributes, record_info(fields, session_counter)}]),
mnesia:add_table_index(session, usr),
mnesia:add_table_index(session, us),
mnesia:add_table_copy(session, node(), ram_copies),
@ -251,11 +274,7 @@ init([]) ->
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),
ejabberd_commands:register_commands(commands()),
{ok, #state{}}.
@ -325,6 +344,7 @@ handle_info(_Info, State) ->
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
ejabberd_commands:unregister_commands(commands()),
ok.
%%--------------------------------------------------------------------
@ -361,6 +381,8 @@ clean_table_from_bad_node(Node) ->
[{'==', {node, '$1'}, Node}],
['$_']}]),
lists:foreach(fun(E) ->
{_, LServer} = E#session.us,
dec_session_counter(LServer),
mnesia:delete({session, E#session.sid})
end, Es)
end,
@ -384,28 +406,32 @@ do_route(From, To, Packet) ->
Reason = xml:get_path_s(
Packet,
[{elem, "status"}, cdata]),
{ejabberd_hooks:run_fold(
{is_privacy_allow(From, To, Packet) andalso
ejabberd_hooks:run_fold(
roster_in_subscription,
LServer,
false,
[User, Server, From, subscribe, Reason]),
true};
"subscribed" ->
{ejabberd_hooks:run_fold(
{is_privacy_allow(From, To, Packet) andalso
ejabberd_hooks:run_fold(
roster_in_subscription,
LServer,
false,
[User, Server, From, subscribed, ""]),
true};
"unsubscribe" ->
{ejabberd_hooks:run_fold(
{is_privacy_allow(From, To, Packet) andalso
ejabberd_hooks:run_fold(
roster_in_subscription,
LServer,
false,
[User, Server, From, unsubscribe, ""]),
true};
"unsubscribed" ->
{ejabberd_hooks:run_fold(
{is_privacy_allow(From, To, Packet) andalso
ejabberd_hooks:run_fold(
roster_in_subscription,
LServer,
false,
@ -469,6 +495,31 @@ do_route(From, To, Packet) ->
end
end.
%% The default list applies to the user as a whole,
%% and is processed if there is no active list set
%% for the target session/resource to which a stanza is addressed,
%% or if there are no current sessions for the user.
is_privacy_allow(From, To, Packet) ->
User = To#jid.user,
Server = To#jid.server,
PrivacyList = ejabberd_hooks:run_fold(privacy_get_user_list, Server,
#userlist{}, [User, Server]),
is_privacy_allow(From, To, Packet, PrivacyList).
%% Check if privacy rules allow this delivery
%% Function copied from ejabberd_c2s.erl
is_privacy_allow(From, To, Packet, PrivacyList) ->
User = To#jid.user,
Server = To#jid.server,
allow == ejabberd_hooks:run_fold(
privacy_check_packet, Server,
allow,
[User,
Server,
PrivacyList,
{From, To, Packet},
in]).
route_message(From, To, Packet) ->
LUser = To#jid.luser,
LServer = To#jid.lserver,
@ -612,6 +663,33 @@ get_max_user_sessions(LUser, Host) ->
_ -> ?MAX_USER_SESSIONS
end.
inc_session_counter(LServer) ->
F = fun() ->
case mnesia:wread({session_counter, LServer}) of
[C] ->
Count = C#session_counter.count + 1,
C2 = C#session_counter{count = Count},
mnesia:write(C2);
_ ->
mnesia:write(#session_counter{vhost = LServer,
count = 1})
end
end,
mnesia:sync_dirty(F).
dec_session_counter(LServer) ->
F = fun() ->
case mnesia:wread({session_counter, LServer}) of
[C] ->
Count = C#session_counter.count - 1,
C2 = C#session_counter{count = Count},
mnesia:write(C2);
_ ->
error
end
end,
mnesia:sync_dirty(F).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@ -647,27 +725,46 @@ process_iq(From, To, Packet) ->
end.
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),
?PRINT("~s", [FUSRs]),
{stop, ?STATUS_SUCCESS};
ctl_process(_Val, ["connected-users-number"]) ->
N = length(dirty_get_sessions_list()),
?PRINT("~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),
?PRINT("~s", [FResources]),
{stop, ?STATUS_SUCCESS};
ctl_process(Val, _Args) ->
Val.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% ejabberd commands
commands() ->
[
#ejabberd_commands{name = connected_users,
tags = [session],
desc = "List all established sessions",
module = ?MODULE, function = connected_users,
args = [],
result = {connected_users, {list, {sessions, string}}}},
#ejabberd_commands{name = connected_users_number,
tags = [session, stats],
desc = "Get the number of established sessions",
module = ?MODULE, function = connected_users_number,
args = [],
result = {num_sessions, integer}},
#ejabberd_commands{name = user_resources,
tags = [session],
desc = "List user's connected resources",
module = ?MODULE, function = user_resources,
args = [{user, string}, {host, string}],
result = {resources, {list, {resource, string}}}}
].
connected_users() ->
USRs = dirty_get_sessions_list(),
SUSRs = lists:sort(USRs),
lists:map(fun({U, S, R}) -> [U, $@, S, $/, R] end, SUSRs).
connected_users_number() ->
length(dirty_get_sessions_list()).
user_resources(User, Server) ->
Resources = get_user_resources(User, Server),
lists:sort(Resources).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Update Mnesia tables
update_tables() ->
case catch mnesia:table_info(session, attributes) of

View File

@ -5,7 +5,7 @@
%%% Created : 23 Aug 2006 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -30,12 +30,14 @@
%% API
-export([start/4,
connect/3,
connect/4,
starttls/2,
starttls/3,
compress/1,
compress/2,
reset_stream/1,
send/2,
send_xml/2,
change_shaper/2,
monitor/1,
get_sockmod/1,
@ -44,13 +46,15 @@
close/1,
sockname/1, peername/1]).
-include("ejabberd.hrl").
-record(socket_state, {sockmod, socket, receiver}).
%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function:
%% Function:
%% Description:
%%--------------------------------------------------------------------
start(Module, SockMod, Socket, Opts) ->
@ -61,30 +65,51 @@ start(Module, SockMod, Socket, Opts) ->
{value, {_, Size}} -> Size;
_ -> infinity
end,
Receiver = ejabberd_receiver:start(Socket, SockMod, none, MaxStanzaSize),
{ReceiverMod, Receiver, RecRef} =
case catch SockMod:custom_receiver(Socket) of
{receiver, RecMod, RecPid} ->
{RecMod, RecPid, RecMod};
_ ->
RecPid = ejabberd_receiver:start(
Socket, SockMod, none, MaxStanzaSize),
{ejabberd_receiver, RecPid, RecPid}
end,
SocketData = #socket_state{sockmod = SockMod,
socket = Socket,
receiver = Receiver},
{ok, Pid} = Module:start({?MODULE, SocketData}, Opts),
case SockMod:controlling_process(Socket, Receiver) of
ok ->
ok;
receiver = RecRef},
case Module:start({?MODULE, SocketData}, Opts) of
{ok, Pid} ->
case SockMod:controlling_process(Socket, Receiver) of
ok ->
ok;
{error, _Reason} ->
SockMod:close(Socket)
end,
ReceiverMod:become_controller(Receiver, Pid);
{error, _Reason} ->
SockMod:close(Socket)
end,
ejabberd_receiver:become_controller(Receiver, Pid);
end;
independent ->
ok;
raw ->
{ok, Pid} = Module:start({SockMod, Socket}, Opts),
case SockMod:controlling_process(Socket, Pid) of
ok ->
ok;
case Module:start({SockMod, Socket}, Opts) of
{ok, Pid} ->
case SockMod:controlling_process(Socket, Pid) of
ok ->
ok;
{error, _Reason} ->
SockMod:close(Socket)
end;
{error, _Reason} ->
SockMod:close(Socket)
end
end.
connect(Addr, Port, Opts) ->
case gen_tcp:connect(Addr, Port, Opts) of
connect(Addr, Port, Opts, infinity).
connect(Addr, Port, Opts, Timeout) ->
case gen_tcp:connect(Addr, Port, Opts, Timeout) of
{ok, Socket} ->
Receiver = ejabberd_receiver:start(Socket, gen_tcp, none),
SocketData = #socket_state{sockmod = gen_tcp,
@ -129,18 +154,45 @@ compress(SocketData, Data) ->
send(SocketData, Data),
SocketData#socket_state{socket = ZlibSocket, sockmod = ejabberd_zlib}.
reset_stream(SocketData) ->
ejabberd_receiver:reset_stream(SocketData#socket_state.receiver).
reset_stream(SocketData) when is_pid(SocketData#socket_state.receiver) ->
ejabberd_receiver:reset_stream(SocketData#socket_state.receiver);
reset_stream(SocketData) when is_atom(SocketData#socket_state.receiver) ->
(SocketData#socket_state.receiver):reset_stream(
SocketData#socket_state.socket).
%% sockmod=gen_tcp|tls|ejabberd_zlib
send(SocketData, Data) ->
catch (SocketData#socket_state.sockmod):send(
case catch (SocketData#socket_state.sockmod):send(
SocketData#socket_state.socket, Data) of
ok -> ok;
{error, timeout} ->
?INFO_MSG("Timeout on ~p:send",[SocketData#socket_state.sockmod]),
exit(normal);
Error ->
?DEBUG("Error in ~p:send: ~p",[SocketData#socket_state.sockmod, Error]),
exit(normal)
end.
%% Can only be called when in c2s StateData#state.xml_socket is true
%% This function is used for HTTP bind
%% sockmod=ejabberd_http_poll|ejabberd_http_bind or any custom module
send_xml(SocketData, Data) ->
catch (SocketData#socket_state.sockmod):send_xml(
SocketData#socket_state.socket, Data).
change_shaper(SocketData, Shaper) ->
ejabberd_receiver:change_shaper(SocketData#socket_state.receiver, Shaper).
change_shaper(SocketData, Shaper)
when is_pid(SocketData#socket_state.receiver) ->
ejabberd_receiver:change_shaper(SocketData#socket_state.receiver, Shaper);
change_shaper(SocketData, Shaper)
when is_atom(SocketData#socket_state.receiver) ->
(SocketData#socket_state.receiver):change_shaper(
SocketData#socket_state.socket, Shaper).
monitor(SocketData) ->
erlang:monitor(process, SocketData#socket_state.receiver).
monitor(SocketData) when is_pid(SocketData#socket_state.receiver) ->
erlang:monitor(process, SocketData#socket_state.receiver);
monitor(SocketData) when is_atom(SocketData#socket_state.receiver) ->
(SocketData#socket_state.receiver):monitor(
SocketData#socket_state.socket).
get_sockmod(SocketData) ->
SocketData#socket_state.sockmod.

View File

@ -5,7 +5,7 @@
%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -84,6 +84,13 @@ init([]) ->
brutal_kill,
worker,
[ejabberd_local]},
Captcha =
{ejabberd_captcha,
{ejabberd_captcha, start_link, []},
permanent,
brutal_kill,
worker,
[ejabberd_captcha]},
Listener =
{ejabberd_listener,
{ejabberd_listener, start_link, []},
@ -162,6 +169,14 @@ init([]) ->
infinity,
supervisor,
[ejabberd_tmp_sup]},
STUNSupervisor =
{ejabberd_stun_sup,
{ejabberd_tmp_sup, start_link,
[ejabberd_stun_sup, ejabberd_stun]},
permanent,
infinity,
supervisor,
[ejabberd_tmp_sup]},
{ok, {{one_for_one, 10, 1},
[Hooks,
NodeGroups,
@ -170,6 +185,7 @@ init([]) ->
SM,
S2S,
Local,
Captcha,
ReceiverSupervisor,
C2SSupervisor,
S2SInSupervisor,
@ -178,6 +194,7 @@ init([]) ->
HTTPSupervisor,
HTTPPollSupervisor,
IQSupervisor,
STUNSupervisor,
FrontendSocketSupervisor,
Listener]}}.

View File

@ -5,7 +5,7 @@
%%% Created : 21 Mar 2007 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -51,7 +51,12 @@
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
LH = case ejabberd_config:get_local_option(watchdog_large_heap) of
I when is_integer(I) -> I;
_ -> 1000000
end,
Opts = [{large_heap, LH}],
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []).
process_command(From, To, Packet) ->
case To of
@ -90,9 +95,10 @@ process_command(From, To, Packet) ->
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
init(Opts) ->
LH = proplists:get_value(large_heap, Opts),
process_flag(priority, high),
erlang:system_monitor(self(), [{large_heap, 1000000}]),
erlang:system_monitor(self(), [{large_heap, LH}]),
lists:foreach(
fun(Host) ->
ejabberd_hooks:add(local_send_to_resource_hook, Host,
@ -109,10 +115,24 @@ init([]) ->
%% {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({get, large_heap}, _From, State) ->
{reply, get_large_heap(), State};
handle_call({set, large_heap, NewValue}, _From, State) ->
MonSettings = erlang:system_monitor(self(), [{large_heap, NewValue}]),
OldLH = get_large_heap(MonSettings),
NewLH = get_large_heap(),
{reply, {lh_changed, OldLH, NewLH}, State};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
get_large_heap() ->
MonSettings = erlang:system_monitor(),
get_large_heap(MonSettings).
get_large_heap(MonSettings) ->
{_MonitorPid, Options} = MonSettings,
proplists:get_value(large_heap, Options).
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%% {noreply, State, Timeout} |
@ -165,7 +185,7 @@ process_large_heap(Pid, Info) ->
JIDs /= [] ->
DetailedInfo = detailed_info(Pid),
Body = io_lib:format(
"(~w) The process ~w is consuming too much memory: ~w.~n"
"(~w) The process ~w is consuming too much memory:~n~p~n"
"~s",
[node(), Pid, Info, DetailedInfo]),
From = jlib:make_jid("", Host, "watchdog"),
@ -303,14 +323,25 @@ process_command1(From, To, Body) ->
process_command2(["kill", SNode, SPid], From, To) ->
Node = list_to_atom(SNode),
remote_command(Node, [kill, SPid], From, To);
process_command2(["showlh", SNode], From, To) ->
Node = list_to_atom(SNode),
remote_command(Node, [showlh], From, To);
process_command2(["setlh", SNode, NewValueString], From, To) ->
Node = list_to_atom(SNode),
NewValue = list_to_integer(NewValueString),
remote_command(Node, [setlh, NewValue], From, To);
process_command2(["help"], From, To) ->
send_message(To, From, help());
process_command2(_, From, To) ->
send_message(To, From, help()).
help() ->
"Commands:\n"
" kill <node> <pid>".
" kill <node> <pid>\n"
" showlh <node>\n"
" setlh <node> <integer>".
remote_command(Node, Args, From, To) ->
Message =
@ -325,6 +356,12 @@ remote_command(Node, Args, From, To) ->
process_remote_command([kill, SPid]) ->
exit(list_to_pid(SPid), kill),
"ok";
process_remote_command([showlh]) ->
Res = gen_server:call(ejabberd_system_monitor, {get, large_heap}),
io_lib:format("Current large heap: ~p", [Res]);
process_remote_command([setlh, NewValue]) ->
{lh_changed, OldLH, NewLH} = gen_server:call(ejabberd_system_monitor, {set, large_heap, NewValue}),
io_lib:format("Result of set large heap: ~p --> ~p", [OldLH, NewLH]);
process_remote_command(_) ->
throw(unknown_command).

View File

@ -5,7 +5,7 @@
%%% Created : 18 Jul 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA

View File

@ -5,7 +5,7 @@
%%% Created : 27 Jan 2006 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -28,13 +28,15 @@
-author('alexey@process-one.net').
%% API
-export([update/0, update_info/0]).
-export([update/0, update/1, update_info/0]).
-include("ejabberd.hrl").
%%====================================================================
%% API
%%====================================================================
%% Update all the modified modules
update() ->
case update_info() of
{ok, Dir, _UpdatedBeams, _Script, LowLevelScript, _Check} ->
@ -48,48 +50,91 @@ update() ->
{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),
?INFO_MSG("beam files: ~p~n", [UpdatedBeams]),
Script = make_script(UpdatedBeams),
?INFO_MSG("script: ~p~n", [Script]),
LowLevelScript = make_low_level_script(UpdatedBeams, Script),
?INFO_MSG("low level script: ~p~n", [LowLevelScript]),
Check =
release_handler_1:check_script(
LowLevelScript,
%% Update only the specified modules
update(ModulesToUpdate) ->
case update_info() of
{ok, Dir, UpdatedBeamsAll, _Script, _LowLevelScript, _Check} ->
UpdatedBeamsNow =
[A || A <- UpdatedBeamsAll, B <- ModulesToUpdate, A == B],
{_, LowLevelScript, _} = build_script(Dir, UpdatedBeamsNow),
Eval =
release_handler_1:eval_script(
LowLevelScript, [],
[{ejabberd, "", filename:join(Dir, "..")}]),
?INFO_MSG("check: ~p~n", [Check]),
{ok, Dir, UpdatedBeams, Script, LowLevelScript, Check};
?INFO_MSG("eval: ~p~n", [Eval]),
Eval;
{error, Reason} ->
{error, Reason}
end.
%% Get information about the modified modules
update_info() ->
Dir = filename:dirname(code:which(ejabberd)),
case file:list_dir(Dir) of
{ok, Files} ->
update_info(Dir, Files);
{error, Reason} ->
{error, Reason}
end.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
%% From systools.hrl
update_info(Dir, Files) ->
Beams = lists:sort(get_beams(Files)),
UpdatedBeams = get_updated_beams(Beams),
?INFO_MSG("beam files: ~p~n", [UpdatedBeams]),
{Script, LowLevelScript, Check} = build_script(Dir, UpdatedBeams),
{ok, Dir, UpdatedBeams, Script, LowLevelScript, Check}.
get_beams(Files) ->
[list_to_atom(filename:rootname(FN))
|| FN <- Files, lists:suffix(".beam", FN)].
%% Return only the beams that have different version
get_updated_beams(Beams) ->
lists:filter(
fun(Module) ->
NewVsn = get_new_version(Module),
case code:is_loaded(Module) of
{file, _} ->
CurVsn = get_current_version(Module),
(NewVsn /= CurVsn
andalso NewVsn /= unknown_version);
false ->
false
end
end, Beams).
get_new_version(Module) ->
Path = code:which(Module),
VersionRes = beam_lib:version(Path),
case VersionRes of
{ok, {Module, NewVsn}} -> NewVsn;
%% If a m1.erl has -module("m2"):
_ -> unknown_version
end.
get_current_version(Module) ->
Attrs = Module:module_info(attributes),
{value, {vsn, CurVsn}} = lists:keysearch(vsn, 1, Attrs),
CurVsn.
%% @spec(Dir::string(), UpdatedBeams::[atom()]) -> {Script,LowLevelScript,Check}
build_script(Dir, UpdatedBeams) ->
Script = make_script(UpdatedBeams),
?INFO_MSG("script: ~p~n", [Script]),
LowLevelScript = make_low_level_script(UpdatedBeams, Script),
?INFO_MSG("low level script: ~p~n", [LowLevelScript]),
Check =
release_handler_1:check_script(
LowLevelScript,
[{ejabberd, "", filename:join(Dir, "..")}]),
?INFO_MSG("check: ~p~n", [Check]),
{Script, LowLevelScript, Check}.
%% Copied from Erlang/OTP file: lib/sasl/src/systools.hrl
-record(application,
{name, %% Name of the application, atom().
type = permanent, %% Application start type, atom().

View File

@ -12,14 +12,18 @@ ZLIB_LIBS = @ZLIB_LIBS@
ERLANG_CFLAGS = @ERLANG_CFLAGS@
ERLANG_LIBS = @ERLANG_LIBS@
# Assume Linux-style dynamic library flags
DYNAMIC_LIB_CFLAGS = -fpic -shared
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
DYNAMIC_LIB_CFLAGS = -fPIC -bundle -flat_namespace -undefined suppress
endif
ifeq ($(shell uname),SunOs)
DYNAMIC_LIB_CFLAGS = -KPIC -G -z text
endif
EFLAGS = -I .. -pz ..
EFLAGS += -I ..
EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
EFLAGS+=+debug_info
@ -39,11 +43,15 @@ $(OUTDIR)/%.beam: %.erl
# erl -s make all report "{outdir, \"..\"}" -noinput -s erlang halt
$(ERLSHLIBS): ../%.so: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) \
$(subst ../,,$(subst .so,.c,$@)) $(LIBS) \
$(ZLIB_LIBS) $(ZLIB_CFLAGS) \
$(ERLANG_LIBS) $(ERLANG_CFLAGS) \
-o $@ $(DYNAMIC_LIB_CFLAGS)
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) \
$(subst ../,,$(subst .so,.c,$@)) \
$(LIBS) \
$(ZLIB_LIBS) \
$(ZLIB_CFLAGS) \
$(ERLANG_LIBS) \
$(ERLANG_CFLAGS) \
-o $@ \
$(DYNAMIC_LIB_CFLAGS)
clean:
rm -f $(BEAMS) $(ERLSHLIBS)

View File

@ -4,8 +4,7 @@ include ..\Makefile.inc
EFLAGS = -I .. -pz ..
OUTDIR = ..
SOURCES = $(wildcard *.erl)
BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam))
BEAMS = ..\ejabberd_zlib.beam
SOURCE = ejabberd_zlib_drv.c
OBJECT = ejabberd_zlib_drv.o

View File

@ -5,7 +5,7 @@
%%% Created : 19 Jan 2006 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -35,6 +35,7 @@
recv/2, recv/3, recv_data/2,
setopts/2,
sockname/1, peername/1,
get_sockmod/1,
controlling_process/2,
close/1]).
@ -116,7 +117,20 @@ recv(#zlibsock{sockmod = SockMod, socket = Socket} = ZlibSock,
Error
end.
recv_data(ZlibSock, Packet) ->
recv_data(#zlibsock{sockmod = SockMod, socket = Socket} = ZlibSock, Packet) ->
case SockMod of
gen_tcp ->
recv_data2(ZlibSock, Packet);
_ ->
case SockMod:recv_data(Socket, Packet) of
{ok, Packet2} ->
recv_data2(ZlibSock, Packet2);
Error ->
Error
end
end.
recv_data2(ZlibSock, Packet) ->
case catch recv_data1(ZlibSock, Packet) of
{'EXIT', Reason} ->
{error, Reason};
@ -158,6 +172,9 @@ sockname(#zlibsock{sockmod = SockMod, socket = Socket}) ->
SockMod:sockname(Socket)
end.
get_sockmod(#zlibsock{sockmod = SockMod}) ->
SockMod.
peername(#zlibsock{sockmod = SockMod, socket = Socket}) ->
case SockMod of
gen_tcp ->

View File

@ -1,5 +1,5 @@
/*
* ejabberd, Copyright (C) 2002-2008 Process-one
* ejabberd, Copyright (C) 2002-2009 ProcessOne
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@ -10,7 +10,7 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -107,14 +107,14 @@ static int ejabberd_zlib_drv_control(ErlDrvData handle,
b = driver_alloc_binary(size);
b->orig_bytes[0] = 0;
d->d_stream->next_in = buf;
d->d_stream->next_in = (unsigned char *)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->next_out = (unsigned char *)b->orig_bytes + rlen;
d->d_stream->avail_out = BUF_SIZE;
err = deflate(d->d_stream, Z_SYNC_FLUSH);
@ -135,14 +135,14 @@ static int ejabberd_zlib_drv_control(ErlDrvData handle,
b->orig_bytes[0] = 0;
if (len > 0) {
d->i_stream->next_in = buf;
d->i_stream->next_in = (unsigned char *)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->next_out = (unsigned char *)b->orig_bytes + rlen;
d->i_stream->avail_out = BUF_SIZE;
err = inflate(d->i_stream, Z_SYNC_FLUSH);

View File

@ -1,21 +1,22 @@
#
# In this file you can configure options that are passed by ejabberdctl
# In this file you can configure options that are passed by ejabberdctl
# to the erlang runtime system when starting ejabberd
#
# POLL: Kernel polling ([true|false])
#' POLL: Kernel polling ([true|false])
#
# The kernel polling option requires support in the kernel.
# Additionaly, you need to enable this feature while compiling Erlang.
# Additionally, you need to enable this feature while compiling Erlang.
#
# Default: true
#
#POLL=true
# SMP: SMP support ([enable|auto|disable])
#.
#' SMP: SMP support ([enable|auto|disable])
#
# Explanation in Erlang/OTP documentation:
# enable: starts the Erlang runtime system with SMP support enabled.
# enable: starts the Erlang runtime system with SMP support enabled.
# This may fail if no runtime system with SMP support is available.
# auto: starts the Erlang runtime system with SMP support enabled if it
# is available and more than one logical processor are detected.
@ -25,9 +26,10 @@
#
#SMP=auto
# ERL_MAX_PORTS: Maximum number of simultaneously open Erlang ports
#.
#' ERL_MAX_PORTS: Maximum number of simultaneously open Erlang ports
#
# ejabberd consumes two or three ports for every connection, either
# ejabberd consumes two or three ports for every connection, either
# from a client or from another Jabber server. So take this into
# account when setting this limit.
#
@ -36,21 +38,23 @@
#
#ERL_MAX_PORTS=32000
# FIREWALL_WINDOW: Range of allowed ports to pass through a firewall
#.
#' FIREWALL_WINDOW: Range of allowed ports to pass through a firewall
#
# If Ejabberd is configured to run in cluster, and a firewall is blocking ports,
# it's possible to make Erlang use a defined range of port (instead of dynamic ports)
# for node communication.
# it's possible to make Erlang use a defined range of port (instead of dynamic
# ports) for node communication.
#
# Default: not defined
# Example: 4200-4210
#
#FIREWALL_WINDOW=
# PROCESSES: Maximum number of Erlang processes
#.
#' PROCESSES: Maximum number of Erlang processes
#
# Erlang consumes a lot of lightweight processes. If there is a lot of activity
# on ejabberd so that the maximum number of proccesses is reached, people will
# on ejabberd so that the maximum number of processes is reached, people will
# experiment greater latency times. As these processes are implemented in
# Erlang, and therefore not related to the operating system processes, you do
# not have to worry about allowing a huge number of them.
@ -60,7 +64,8 @@
#
#PROCESSES=250000
# ERL_MAX_ETS_TABLES: Maximum number of ETS and Mnesia tables
#.
#' ERL_MAX_ETS_TABLES: Maximum number of ETS and Mnesia tables
#
# The number of concurrent ETS and Mnesia tables is limited. When the limit is
# reached, errors will appear in the logs:
@ -72,6 +77,24 @@
#
#ERL_MAX_ETS_TABLES=1400
#.
#' ERL_OPTIONS: Additional Erlang options
#
# The next variable allows to specify additional options passed to erlang while
# starting ejabberd. Some useful options are -noshell, -detached, -heart. When
# ejabberd is started from an init.d script options -noshell and -detached are
# added implicitly. See erl(1) for more info.
#
# It might be useful to add "-pa /usr/local/lib/ejabberd/ebin" if you
# want to add local modules in this path.
#
# Default: ""
#
#ERL_OPTIONS=""
#.
#' ERLANG_NODE: Erlang node name
#
# The next variable allows to explicitly specify erlang node for ejabberd
# It can be given in different formats:
# ERLANG_NODE=ejabberd
@ -80,9 +103,25 @@
# Erlang uses node name as is (so make sure that hostname is a real
# machine hostname or you'll not be able to control ejabberd)
# ERLANG_NODE=ejabberd@hostname.domainname
# The same as previous, but erlang will use long hostname
# The same as previous, but erlang will use long hostname
# (see erl (1) manual for details)
#
# Default: ejabberd
#
#ERLANG_NODE=ejabberd
#.
#' EJABBERD_PID_PATH: ejabberd PID file
#
# Indicate the full path to the ejabberd Process identifier (PID) file.
# If this variable is defined, ejabberd writes the PID file when starts,
# and deletes it when stops.
# Remember to create the directory and grant write permission to ejabberd.
#
# Default: don't write PID file
#
#EJABBERD_PID_PATH=/var/run/ejabberd/ejabberd.pid
#.
#'
# vim: foldmarker=#',#. foldmethod=marker:

View File

@ -11,14 +11,8 @@ ERL_MAX_ETS_TABLES=1400
NODE=ejabberd
HOST=localhost
ERLANG_NODE=$NODE@$HOST
ROOTDIR=@rootdir@
EJABBERD_CONFIG_PATH=$ROOTDIR/etc/ejabberd/ejabberd.cfg
LOGS_DIR=$ROOTDIR/var/log/ejabberd/
EJABBERD_DB=$ROOTDIR/var/lib/ejabberd/db/$NODE
# read custom configuration
CONFIG=$ROOTDIR/etc/ejabberd/ejabberdctl.cfg
[ -f "$CONFIG" ] && . "$CONFIG"
ERL=@erl@
INSTALLUSER=@installuser@
# parse command line parameters
ARGS=
@ -27,15 +21,55 @@ while [ $# -ne 0 ] ; do
shift
case $PARAM in
--) break ;;
--node) ERLANG_NODE=$1; shift ;;
--node) ERLANG_NODE_ARG=$1; shift ;;
--config-dir) ETCDIR=$1 ; shift ;;
--config) EJABBERD_CONFIG_PATH=$1 ; shift ;;
--ctl-config) CONFIG=$1 ; shift ;;
--ctl-config) EJABBERDCTL_CONFIG_PATH=$1 ; shift ;;
--logs) LOGS_DIR=$1 ; shift ;;
--spool) EJABBERD_DB=$1 ; shift ;;
--spool) SPOOLDIR=$1 ; shift ;;
*) ARGS="$ARGS $PARAM" ;;
esac
done
# Define ejabberd variable if they have not been defined from the command line
if [ "$ETCDIR" = "" ] ; then
ETCDIR=@SYSCONFDIR@/ejabberd
fi
if [ "$EJABBERD_CONFIG_PATH" = "" ] ; then
EJABBERD_CONFIG_PATH=$ETCDIR/ejabberd.cfg
fi
if [ "$EJABBERDCTL_CONFIG_PATH" = "" ] ; then
EJABBERDCTL_CONFIG_PATH=$ETCDIR/ejabberdctl.cfg
fi
[ -f "$EJABBERDCTL_CONFIG_PATH" ] && . "$EJABBERDCTL_CONFIG_PATH"
if [ "$LOGS_DIR" = "" ] ; then
LOGS_DIR=@LOCALSTATEDIR@/log/ejabberd
fi
if [ "$SPOOLDIR" = "" ] ; then
SPOOLDIR=@LOCALSTATEDIR@/lib/ejabberd
fi
if [ "$EJABBERD_DOC_PATH" = "" ] ; then
EJABBERD_DOC_PATH=@DOCDIR@
fi
if [ "$ERLANG_NODE_ARG" != "" ] ; then
ERLANG_NODE=$ERLANG_NODE_ARG
fi
# check the proper system user is used
ID=`id -g`
EJID=`id -g $INSTALLUSER`
EXEC_CMD="false"
if [ $ID -eq 0 ] ; then
EXEC_CMD="su ${INSTALLUSER} -c"
fi
if [ "$ID" -eq "$EJID" ] ; then
EXEC_CMD="sh -c"
fi
if [ "$EXEC_CMD" = "false" ] ; then
echo "This command can only be run by root or the user $INSTALLUSER" >&2
exit 1
fi
NAME=-name
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && NAME=-sname
@ -45,19 +79,37 @@ else
KERNEL_OPTS="-kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
fi
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $KERNEL_OPTS"
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS $KERNEL_OPTS"
# define additional environment variables
EJABBERD_EBIN=$ROOTDIR/var/lib/ejabberd/ebin
EJABBERD_MSGS_PATH=$ROOTDIR/var/lib/ejabberd/priv/msgs
EJABBERD_SO_PATH=$ROOTDIR/var/lib/ejabberd/priv/lib
EJABBERD_BIN_PATH=$ROOTDIR/var/lib/ejabberd/priv/bin
if [ "$EJABBERDDIR" = "" ]; then
EJABBERDDIR=@LIBDIR@/ejabberd
fi
if [ "$EJABBERD_EBIN_PATH" = "" ]; then
EJABBERD_EBIN_PATH=$EJABBERDDIR/ebin
fi
if [ "$EJABBERD_PRIV_PATH" = "" ]; then
EJABBERD_PRIV_PATH=$EJABBERDDIR/priv
fi
if [ "$EJABBRD_BIN_PATH" = "" ]; then
EJABBERD_BIN_PATH=$EJABBERD_PRIV_PATH/bin
fi
if [ "$EJABBERD_SO_PATH" = "" ]; then
EJABBERD_SO_PATH=$EJABBERD_PRIV_PATH/lib
fi
if [ "$EJABBERD_MSGS_PATH" = "" ]; then
EJABBERD_MSGS_PATH=$EJABBERD_PRIV_PATH/msgs
fi
EJABBERD_LOG_PATH=$LOGS_DIR/ejabberd.log
SASL_LOG_PATH=$LOGS_DIR/sasl.log
SASL_LOG_PATH=$LOGS_DIR/erlang.log
DATETIME=`date "+%Y%m%d-%H%M%S"`
ERL_CRASH_DUMP=$LOGS_DIR/erl_crash_$DATETIME.dump
ERL_INETRC=$ROOTDIR/etc/ejabberd/inetrc
HOME=$ROOTDIR/var/lib/ejabberd
ERL_INETRC=$ETCDIR/inetrc
HOME=$SPOOLDIR
# create the home dir with the proper user if doesn't exist, because it stores cookie file
[ -d $HOME ] || $EXEC_CMD "mkdir -p $HOME"
# export global variables
export EJABBERD_CONFIG_PATH
@ -65,14 +117,15 @@ export EJABBERD_MSGS_PATH
export EJABBERD_LOG_PATH
export EJABBERD_SO_PATH
export EJABBERD_BIN_PATH
export EJABBERD_DOC_PATH
export EJABBERD_PID_PATH
export ERL_CRASH_DUMP
export ERL_INETRC
export ERL_MAX_PORTS
export ERL_MAX_ETS_TABLES
export HOME
export EXEC_CMD
[ -d $EJABBERD_DB ] || mkdir -p $EJABBERD_DB
[ -d $LOGS_DIR ] || mkdir -p $LOGS_DIR
# Compatibility in ZSH
#setopt shwordsplit 2>/dev/null
@ -80,14 +133,14 @@ export HOME
# start server
start ()
{
erl \
$EXEC_CMD "$ERL \
$NAME $ERLANG_NODE \
-noinput -detached \
-pa $EJABBERD_EBIN \
-mnesia dir "\"$EJABBERD_DB\"" \
-pa $EJABBERD_EBIN_PATH \
-mnesia dir \"\\\"$SPOOLDIR\\\"\" \
-s ejabberd \
-sasl sasl_error_logger \{file,\"$SASL_LOG_PATH\"\} \
$ERLANG_OPTS $ARGS "$@"
-sasl sasl_error_logger \\{file,\\\"$SASL_LOG_PATH\\\"\\} \
$ERLANG_OPTS $ARGS \"$@\""
}
# attach to server
@ -109,10 +162,11 @@ debug ()
echo "Press any key to continue"
read foo
echo ""
erl \
$NAME ${NODE}debug \
$EXEC_CMD "$ERL \
$NAME debug-${ERLANG_NODE} \
-remsh $ERLANG_NODE \
$ERLANG_OPTS $ARGS "$@"
-hidden \
$ERLANG_OPTS $ARGS \"$@\""
}
# start interactive server
@ -133,39 +187,48 @@ live ()
echo "Press any key to continue"
read foo
echo ""
erl \
$EXEC_CMD "$ERL \
$NAME $ERLANG_NODE \
$ERLANG_OPTS \
-pa $EJABBERD_EBIN \
-mnesia dir "\"$EJABBERD_DB\"" \
-pa $EJABBERD_EBIN_PATH \
-mnesia dir \"\\\"$SPOOLDIR\\\"\" \
-s ejabberd \
$ERLANG_OPTS $ARGS "$@"
$ERLANG_OPTS $ARGS \"$@\""
}
help ()
{
echo ""
echo "Commands to start an ejabberd node:"
echo " start Start an ejabberd node in server mode"
echo " debug Attach an interactive Erlang shell to a running ejabberd node"
echo " live Start an ejabberd node in live (interactive) mode"
echo ""
echo "Optional parameters when starting an ejabberd node:"
echo " --config-dir dir Config ejabberd: $ETCDIR"
echo " --config file Config ejabberd: $EJABBERD_CONFIG_PATH"
echo " --ctl-config file Config ejabberdctl: $EJABBERDCTL_CONFIG_PATH"
echo " --logs dir Directory for logs: $LOGS_DIR"
echo " --spool dir Database spool dir: $SPOOLDIR"
echo " --node nodename ejabberd node name: $ERLANG_NODE"
echo ""
}
# common control function
ctl ()
{
erl \
$NAME ejabberdctl \
COMMAND=$@
$EXEC_CMD "$ERL \
$NAME ctl-${ERLANG_NODE} \
-noinput \
-pa $EJABBERD_EBIN \
-s ejabberd_ctl -extra $ERLANG_NODE $@
-hidden \
-pa $EJABBERD_EBIN_PATH \
-s ejabberd_ctl -extra $ERLANG_NODE $COMMAND"
result=$?
case $result in
0) :;;
*)
echo ""
echo "Commands to start an ejabberd node:"
echo " start Start an ejabberd node in server mode"
echo " debug Attach an interactive Erlang shell to a running ejabberd node"
echo " live Start an ejabberd node in live (interactive) mode"
echo ""
echo "Optional parameters when starting an ejabberd node:"
echo " --config file Config file of ejabberd: $EJABBERD_CONFIG_PATH"
echo " --ctl-config file Config file of ejabberdctl: $CONFIG"
echo " --logs dir Directory for logs: $LOGS_DIR"
echo " --spool dir Database spool dir: $EJABBERD_DB"
echo "";;
1) :;;
2) help;;
3) help;;
esac
return $result
}
@ -177,9 +240,42 @@ usage ()
exit
}
# stop epmd if there is no other running node
stop_epmd()
{
epmd -names | grep -q name || epmd -kill
}
# allow sync calls
wait_for_status()
{
# args: status try delay
# return: 0 OK, 1 KO
timeout=$2
status=4
while [ $status -ne $1 ]; do
sleep $3
let timeout=timeout-1
[ $timeout -eq 0 ] && {
status=$1
} || {
ctl status > /dev/null
status=$?
}
done
[ $timeout -eq 0 ] && {
status=1
} || {
status=0
}
return $status
}
case $ARGS in
' start') start;;
' debug') debug;;
' live') live;;
' started') wait_for_status 0 30 2;; # wait 30x2s before timeout
' stopped') wait_for_status 3 15 2; stop_epmd;; # wait 15x2s before timeout
*) ctl $ARGS;;
esac

View File

@ -5,7 +5,7 @@
%%% Created : 22 Aug 2005 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -131,7 +131,14 @@ export_offline(Server, Output) ->
NewPacket = {xmlelement, Name, Attrs2,
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(TimeStamp))]},
calendar:now_to_universal_time(TimeStamp),
utc,
jlib:make_jid("", Server, ""),
"Offline Storage"),
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
TimeStamp))]},
XML =
ejabberd_odbc:escape(
lists:flatten(

View File

@ -6,10 +6,14 @@ CPPFLAGS = @CPPFLAGS@
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@
ASN_FLAGS = -bber_bin +optimize +driver
ERLANG_CFLAGS = @ERLANG_CFLAGS@
ERLANG_LIBS = @ERLANG_LIBS@
EFLAGS = -I .. -pz ..
EFLAGS += -I ..
EFLAGS += -pz ..
# make debug=true to compile Erlang module with debug informations.
ifdef debug
EFLAGS+=+debug_info
@ -25,7 +29,7 @@ all: $(BEAMS) ELDAPv3.beam
ELDAPv3.beam: ELDAPv3.erl
ELDAPv3.erl: ELDAPv3.asn
@ERLC@ -bber_bin -W $(EFLAGS) $<
@ERLC@ $(ASN_FLAGS) -W $(EFLAGS) $<
$(OUTDIR)/%.beam: %.erl ELDAPv3.erl
@ERLC@ -W $(EFLAGS) -o $(OUTDIR) $<

View File

@ -4,8 +4,9 @@ include ..\Makefile.inc
EFLAGS = -I .. -pz ..
OUTDIR = ..
SOURCES = $(wildcard *.erl)
BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam))
BEAMS = ..\eldap.beam ..\eldap_filter.beam ..\eldap_pool.beam ..\eldap_utils.beam
ASN_FLAGS = -bber_bin +optimize +driver
ALL : $(BEAMS)
@ -17,7 +18,7 @@ Clean :
-@erase $(BEAMS)
ELDAPv3.erl : ELDAPv3.asn
erlc -bber_bin -W $(EFLAGS) ELDAPv3.asn
erlc $(ASN_FLAGS) -W $(EFLAGS) ELDAPv3.asn
$(OUTDIR)\eldap.beam : eldap.erl ELDAPv3.erl
erlc -W $(EFLAGS) -o $(OUTDIR) eldap.erl

View File

@ -42,6 +42,12 @@
%%% Modified by Mickael Remond <mremond@process-one.net>
%%% Now use ejabberd log mechanism
%%% Modified by:
%%% Thomas Baden <roo@ham9.net> 2008 April 6th
%%% Andy Harb <Ahmad.N.Abou-Harb@jpl.nasa.gov> 2008 April 28th
%%% Anton Podavalov <a.podavalov@gmail.com> 2009 February 22th
%%% Added LDAPS support, modeled off jungerl eldap.erl version.
%%% NOTICE: STARTTLS is not supported.
%%% --------------------------------------------------------------------
-vc('$Id$ ').
@ -61,7 +67,7 @@
-include("ejabberd.hrl").
%% External exports
-export([start_link/1, start_link/5]).
-export([start_link/1, start_link/6]).
-export([baseObject/0,singleLevel/0,wholeSubtree/0,close/1,
equalityMatch/2,greaterOrEqual/2,lessOrEqual/2,
@ -85,20 +91,33 @@
-define(RETRY_TIMEOUT, 500).
-define(BIND_TIMEOUT, 10000).
-define(CMD_TIMEOUT, 100000).
%% Used in gen_fsm sync calls.
-define(CALL_TIMEOUT, ?CMD_TIMEOUT + ?BIND_TIMEOUT + ?RETRY_TIMEOUT).
%% Used as a timeout for gen_tcp:send/2
-define(SEND_TIMEOUT, 30000).
-define(MAX_TRANSACTION_ID, 65535).
-define(MIN_TRANSACTION_ID, 0).
%% Grace period after "soft" LDAP bind errors:
-define(GRACEFUL_RETRY_TIMEOUT, 5000).
-define(SUPPORTEDEXTENSION, "1.3.6.1.4.1.1466.101.120.7").
-define(SUPPORTEDEXTENSIONSYNTAX, "1.3.6.1.4.1.1466.115.121.1.38").
-define(STARTTLS, "1.3.6.1.4.1.1466.20037").
-record(eldap, {version = ?LDAP_VERSION,
hosts, % Possible hosts running LDAP servers
host = null, % Connected Host LDAP server
port = 389, % The LDAP server port
sockmod, % SockMod (gen_tcp|tls)
tls = none, % LDAP/LDAPS (none|starttls|tls)
tls_options = [],
fd = null, % Socket filedescriptor.
rootdn = "", % Name of the entry to bind as
passwd, % Password for (above) entry
id = 0, % LDAP Request ID
bind_timer, % Ref to bind timeout
dict, % dict holding operation params and results
bind_q % Queue for bind() requests
req_q % Queue for requests
}).
%%%----------------------------------------------------------------------
@ -108,9 +127,9 @@ start_link(Name) ->
Reg_name = list_to_atom("eldap_" ++ Name),
gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
start_link(Name, Hosts, Port, Rootdn, Passwd) ->
start_link(Name, Hosts, Port, Rootdn, Passwd, Encrypt) ->
Reg_name = list_to_atom("eldap_" ++ Name),
gen_fsm:start_link({local, Reg_name}, ?MODULE, {Hosts, Port, Rootdn, Passwd}, []).
gen_fsm:start_link({local, Reg_name}, ?MODULE, {Hosts, Port, Rootdn, Passwd, Encrypt}, []).
%%% --------------------------------------------------------------------
%%% Get status of connection.
@ -139,13 +158,14 @@ close(Handle) ->
%%% {"telephoneNumber", ["545 555 00"]}]
%%% )
%%% --------------------------------------------------------------------
add(Handle, Entry, Attributes) when list(Entry),list(Attributes) ->
add(Handle, Entry, Attributes) when is_list(Entry), is_list(Attributes) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {add, Entry, add_attrs(Attributes)}).
gen_fsm:sync_send_event(Handle1, {add, Entry, add_attrs(Attributes)},
?CALL_TIMEOUT).
%%% Do sanity check !
add_attrs(Attrs) ->
F = fun({Type,Vals}) when list(Type),list(Vals) ->
F = fun({Type,Vals}) when is_list(Type), is_list(Vals) ->
%% Confused ? Me too... :-/
{'AddRequest_attributes',Type, Vals}
end,
@ -164,9 +184,9 @@ add_attrs(Attrs) ->
%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com"
%%% )
%%% --------------------------------------------------------------------
delete(Handle, Entry) when list(Entry) ->
delete(Handle, Entry) when is_list(Entry) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {delete, Entry}).
gen_fsm:sync_send_event(Handle1, {delete, Entry}, ?CALL_TIMEOUT).
%%% --------------------------------------------------------------------
%%% Modify an entry. Given an entry a number of modification
@ -179,18 +199,18 @@ delete(Handle, Entry) when list(Entry) ->
%%% add("description", ["LDAP hacker"])]
%%% )
%%% --------------------------------------------------------------------
modify(Handle, Object, Mods) when list(Object), list(Mods) ->
modify(Handle, Object, Mods) when is_list(Object), is_list(Mods) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {modify, Object, Mods}).
gen_fsm:sync_send_event(Handle1, {modify, Object, Mods}, ?CALL_TIMEOUT).
%%%
%%% Modification operations.
%%% Example:
%%% replace("telephoneNumber", ["555 555 00"])
%%%
mod_add(Type, Values) when list(Type), list(Values) -> m(add, Type, Values).
mod_delete(Type, Values) when list(Type), list(Values) -> m(delete, Type, Values).
mod_replace(Type, Values) when list(Type), list(Values) -> m(replace, Type, Values).
mod_add(Type, Values) when is_list(Type), is_list(Values) -> m(add, Type, Values).
mod_delete(Type, Values) when is_list(Type), is_list(Values) -> m(delete, Type, Values).
mod_replace(Type, Values) when is_list(Type), is_list(Values) -> m(replace, Type, Values).
m(Operation, Type, Values) ->
#'ModifyRequest_modification_SEQOF'{
@ -212,9 +232,12 @@ m(Operation, Type, Values) ->
%%% )
%%% --------------------------------------------------------------------
modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup)
when list(Entry),list(NewRDN),atom(DelOldRDN),list(NewSup) ->
when is_list(Entry), is_list(NewRDN), is_atom(DelOldRDN), is_list(NewSup) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {modify_dn, Entry, NewRDN, bool_p(DelOldRDN), optional(NewSup)}).
gen_fsm:sync_send_event(
Handle1,
{modify_dn, Entry, NewRDN, bool_p(DelOldRDN), optional(NewSup)},
?CALL_TIMEOUT).
%%% --------------------------------------------------------------------
@ -226,9 +249,9 @@ modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup)
%%% "secret")
%%% --------------------------------------------------------------------
bind(Handle, RootDN, Passwd)
when list(RootDN),list(Passwd) ->
when is_list(RootDN), is_list(Passwd) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}, infinity).
gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}, ?CALL_TIMEOUT).
%%% Sanity checks !
@ -262,18 +285,18 @@ optional(Value) -> Value.
%%% []}}
%%%
%%% --------------------------------------------------------------------
search(Handle, A) when record(A, eldap_search) ->
search(Handle, A) when is_record(A, eldap_search) ->
call_search(Handle, A);
search(Handle, L) when list(L) ->
search(Handle, L) when is_list(L) ->
case catch parse_search_args(L) of
{error, Emsg} -> {error, Emsg};
{'EXIT', Emsg} -> {error, Emsg};
A when record(A, eldap_search) -> call_search(Handle, A)
A when is_record(A, eldap_search) -> call_search(Handle, A)
end.
call_search(Handle, A) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {search, A}, infinity).
gen_fsm:sync_send_event(Handle1, {search, A}, ?CALL_TIMEOUT).
parse_search_args(Args) ->
parse_search_args(Args, #eldap_search{scope = wholeSubtree}).
@ -288,7 +311,7 @@ parse_search_args([{attributes, Attrs}|T],A) ->
parse_search_args(T,A#eldap_search{attributes = Attrs});
parse_search_args([{types_only, TypesOnly}|T],A) ->
parse_search_args(T,A#eldap_search{types_only = TypesOnly});
parse_search_args([{timeout, Timeout}|T],A) when integer(Timeout) ->
parse_search_args([{timeout, Timeout}|T],A) when is_integer(Timeout) ->
parse_search_args(T,A#eldap_search{timeout = Timeout});
parse_search_args([{limit, Limit}|T],A) when is_integer(Limit) ->
parse_search_args(T,A#eldap_search{limit = Limit});
@ -307,9 +330,9 @@ wholeSubtree() -> wholeSubtree.
%%%
%%% Boolean filter operations
%%%
'and'(ListOfFilters) when list(ListOfFilters) -> {'and',ListOfFilters}.
'or'(ListOfFilters) when list(ListOfFilters) -> {'or', ListOfFilters}.
'not'(Filter) when tuple(Filter) -> {'not',Filter}.
'and'(ListOfFilters) when is_list(ListOfFilters) -> {'and',ListOfFilters}.
'or'(ListOfFilters) when is_list(ListOfFilters) -> {'or', ListOfFilters}.
'not'(Filter) when is_tuple(Filter) -> {'not',Filter}.
%%%
%%% The following Filter parameters consist of an attribute
@ -327,7 +350,7 @@ av_assert(Desc, Value) ->
%%%
%%% Filter to check for the presence of an attribute
%%%
present(Attribute) when list(Attribute) ->
present(Attribute) when is_list(Attribute) ->
{present, Attribute}.
@ -346,15 +369,15 @@ present(Attribute) when list(Attribute) ->
%%% Example: substrings("sn",[{initial,"To"},{any,"kv"},{final,"st"}])
%%% will match entries containing: 'sn: Tornkvist'
%%%
substrings(Type, SubStr) when list(Type), list(SubStr) ->
substrings(Type, SubStr) when is_list(Type), is_list(SubStr) ->
Ss = {'SubstringFilter_substrings',v_substr(SubStr)},
{substrings,#'SubstringFilter'{type = Type,
substrings = Ss}}.
get_handle(Pid) when pid(Pid) -> Pid;
get_handle(Atom) when atom(Atom) -> Atom;
get_handle(Name) when list(Name) -> list_to_atom("eldap_" ++ Name).
get_handle(Pid) when is_pid(Pid) -> Pid;
get_handle(Atom) when is_atom(Atom) -> Atom;
get_handle(Name) when is_list(Name) -> list_to_atom("eldap_" ++ Name).
%%%----------------------------------------------------------------------
%%% Callback functions from gen_fsm
%%%----------------------------------------------------------------------
@ -370,19 +393,37 @@ get_handle(Name) when list(Name) -> list_to_atom("eldap_" ++ Name).
%%----------------------------------------------------------------------
init([]) ->
case get_config() of
{ok, Hosts, Rootdn, Passwd} ->
init({Hosts, Rootdn, Passwd});
{ok, Hosts, Rootdn, Passwd, Encrypt} ->
init({Hosts, Rootdn, Passwd, Encrypt});
{error, Reason} ->
{stop, Reason}
end;
init({Hosts, Port, Rootdn, Passwd}) ->
init({Hosts, Port, Rootdn, Passwd, Encrypt}) ->
catch ssl:start(),
{X1,X2,X3} = erlang:now(),
ssl:seed(integer_to_list(X1) ++ integer_to_list(X2) ++ integer_to_list(X3)),
PortTemp = case Port of
undefined ->
case Encrypt of
tls ->
?LDAPS_PORT;
starttls ->
?LDAP_PORT;
_ ->
?LDAP_PORT
end;
PT -> PT
end,
TLSOpts = [verify_none],
{ok, connecting, #eldap{hosts = Hosts,
port = Port,
port = PortTemp,
rootdn = Rootdn,
passwd = Passwd,
tls = Encrypt,
tls_options = TLSOpts,
id = 0,
dict = dict:new(),
bind_q = queue:new()}, 0}.
req_q = queue:new()}, 0}.
%%----------------------------------------------------------------------
%% Func: StateName/2
@ -405,38 +446,20 @@ connecting(timeout, S) ->
%% {stop, Reason, NewStateData} |
%% {stop, Reason, Reply, NewStateData}
%%----------------------------------------------------------------------
connecting(_Event, _From, S) ->
Reply = {error, connecting},
{reply, Reply, connecting, S}.
connecting(Event, From, S) ->
Q = queue:in({Event, From}, S#eldap.req_q),
{next_state, connecting, S#eldap{req_q=Q}}.
wait_bind_response(_Event, _From, S) ->
Reply = {error, wait_bind_response},
{reply, Reply, wait_bind_response, S}.
wait_bind_response(Event, From, S) ->
Q = queue:in({Event, From}, S#eldap.req_q),
{next_state, wait_bind_response, S#eldap{req_q=Q}}.
active_bind(Event, From, S) ->
Q = queue:in({Event, From}, S#eldap.req_q),
{next_state, active_bind, S#eldap{req_q=Q}}.
active(Event, From, S) ->
case catch send_command(Event, From, S) of
{ok, NewS} ->
case Event of
{bind, _, _} ->
{next_state, active_bind, NewS};
_ ->
{next_state, active, NewS}
end;
{error, Reason} ->
{reply, {error, Reason}, active, S};
{'EXIT', Reason} ->
{reply, {error, Reason}, active, S}
end.
active_bind({bind, RootDN, Passwd}, From, #eldap{bind_q=Q} = S) ->
NewQ = queue:in({{bind, RootDN, Passwd}, From}, Q),
{next_state, active_bind, S#eldap{bind_q=NewQ}};
active_bind(Event, From, S) ->
case catch send_command(Event, From, S) of
{ok, NewS} -> {next_state, active_bind, NewS};
{error, Reason} -> {reply, {error, Reason}, active_bind, S};
{'EXIT', Reason} -> {reply, {error, Reason}, active_bind, S}
end.
process_command(S, Event, From).
%%----------------------------------------------------------------------
%% Func: handle_event/3
@ -446,21 +469,8 @@ active_bind(Event, From, S) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(close, _StateName, S) ->
gen_tcp:close(S#eldap.fd),
{stop, closed, S};
handle_event(process_bind_q, active_bind, #eldap{bind_q=Q} = S) ->
case queue:out(Q) of
{{value, {BindEvent, To}}, NewQ} ->
NewStateData = case catch send_command(BindEvent, To, S) of
{ok, NewS} -> NewS;
{error, Reason} -> gen_fsm:reply(To, {error, Reason}), S;
{'EXIT', Reason} -> gen_fsm:reply(To, {error, Reason}), S
end,
{next_state, active_bind, NewStateData#eldap{bind_q=NewQ}};
{empty, Q} ->
{next_state, active, S}
end;
catch (S#eldap.sockmod):close(S#eldap.fd),
{stop, normal, S};
handle_event(_Event, StateName, S) ->
{next_state, StateName, S}.
@ -488,60 +498,70 @@ handle_sync_event(_Event, _From, StateName, S) ->
%%
%% Packets arriving in various states
%%
handle_info({tcp, _Socket, Data}, connecting, S) ->
?DEBUG("eldap. tcp packet received when disconnected!~n~p", [Data]),
handle_info({Tag, _Socket, Data}, connecting, S)
when Tag == tcp; Tag == ssl ->
?DEBUG("tcp packet received when disconnected!~n~p", [Data]),
{next_state, connecting, S};
handle_info({tcp, _Socket, Data}, wait_bind_response, S) ->
handle_info({Tag, _Socket, Data}, wait_bind_response, S)
when Tag == tcp; Tag == ssl ->
cancel_timer(S#eldap.bind_timer),
case catch recvd_wait_bind_response(Data, S) of
bound -> {next_state, active, S};
{fail_bind, _Reason} -> close_and_retry(S),
{next_state, connecting, S#eldap{fd = null}};
{'EXIT', _Reason} -> close_and_retry(S),
{next_state, connecting, S#eldap{fd = null}};
{error, _Reason} -> close_and_retry(S),
{next_state, connecting, S#eldap{fd = null}}
bound ->
dequeue_commands(S);
{fail_bind, Reason} ->
report_bind_failure(S#eldap.host, S#eldap.port, Reason),
{next_state, connecting, close_and_retry(S, ?GRACEFUL_RETRY_TIMEOUT)};
{'EXIT', Reason} ->
report_bind_failure(S#eldap.host, S#eldap.port, Reason),
{next_state, connecting, close_and_retry(S)};
{error, Reason} ->
report_bind_failure(S#eldap.host, S#eldap.port, Reason),
{next_state, connecting, close_and_retry(S)}
end;
handle_info({tcp, _Socket, Data}, StateName, S)
when StateName==active; StateName==active_bind ->
handle_info({Tag, _Socket, Data}, StateName, S)
when (StateName == active orelse StateName == active_bind) andalso
(Tag == tcp orelse Tag == ssl) ->
case catch recvd_packet(Data, S) of
{reply, Reply, To, NewS} -> gen_fsm:reply(To, Reply),
{next_state, StateName, NewS};
{ok, NewS} -> {next_state, StateName, NewS};
{'EXIT', _Reason} -> {next_state, StateName, S};
{error, _Reason} -> {next_state, StateName, S}
{response, Response, RequestType} ->
NewS = case Response of
{reply, Reply, To, S1} ->
gen_fsm:reply(To, Reply),
S1;
{ok, S1} ->
S1
end,
if (StateName == active_bind andalso
RequestType == bindRequest) orelse
(StateName == active) ->
dequeue_commands(NewS);
true ->
{next_state, StateName, NewS}
end;
_ ->
{next_state, StateName, S}
end;
handle_info({tcp_closed, _Socket}, Fsm_state, S) ->
handle_info({Tag, _Socket}, Fsm_state, S)
when Tag == tcp_closed; Tag == ssl_closed ->
?WARNING_MSG("LDAP server closed the connection: ~s:~p~nIn State: ~p",
[S#eldap.host, S#eldap.port ,Fsm_state]),
F = fun(_Id, [{Timer, From, _Name}|_]) ->
gen_fsm:reply(From, {error, tcp_closed}),
cancel_timer(Timer)
end,
dict:map(F, S#eldap.dict),
{ok, NextState, NewS} = connect_bind(S#eldap{fd = null,
dict = dict:new(),
bind_q=queue:new()}),
{next_state, NextState, NewS};
{next_state, connecting, close_and_retry(S)};
handle_info({tcp_error, _Socket, Reason}, Fsm_state, S) ->
handle_info({Tag, _Socket, Reason}, Fsm_state, S)
when Tag == tcp_error; Tag == ssl_error ->
?DEBUG("eldap received tcp_error: ~p~nIn State: ~p", [Reason, Fsm_state]),
%% XXX wouldn't it be safer to try reconnect ?
%% if we were waiting a result, we may mait forever
%% cause request is probably lost....
{next_state, Fsm_state, S};
{next_state, connecting, close_and_retry(S)};
%%
%% Timers
%%
handle_info({timeout, Timer, {cmd_timeout, Id}}, active, S) ->
handle_info({timeout, Timer, {cmd_timeout, Id}}, StateName, S) ->
case cmd_timeout(Timer, Id, S) of
{reply, To, Reason, NewS} -> gen_fsm:reply(To, Reason),
{next_state, active, NewS};
{error, _Reason} -> {next_state, active, S}
{next_state, StateName, NewS};
{error, _Reason} -> {next_state, StateName, S}
end;
handle_info({timeout, retry_connect}, connecting, S) ->
@ -549,8 +569,7 @@ handle_info({timeout, retry_connect}, connecting, S) ->
{next_state, NextState, NewS};
handle_info({timeout, _Timer, bind_timeout}, wait_bind_response, S) ->
close_and_retry(S),
{next_state, connecting, S#eldap{fd = null}};
{next_state, connecting, close_and_retry(S)};
%%
%% Make sure we don't fill the message queue with rubbish
@ -579,6 +598,34 @@ code_change(_OldVsn, StateName, S, _Extra) ->
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
dequeue_commands(S) ->
case queue:out(S#eldap.req_q) of
{{value, {Event, From}}, Q} ->
case process_command(S#eldap{req_q=Q}, Event, From) of
{_, active, NewS} ->
dequeue_commands(NewS);
Res ->
Res
end;
{empty, _} ->
{next_state, active, S}
end.
process_command(S, Event, From) ->
case send_command(Event, From, S) of
{ok, NewS} ->
case Event of
{bind, _, _} ->
{next_state, active_bind, NewS};
_ ->
{next_state, active, NewS}
end;
{error, _Reason} ->
Q = queue:in_r({Event, From}, S#eldap.req_q),
NewS = close_and_retry(S#eldap{req_q=Q}),
{next_state, connecting, NewS}
end.
send_command(Command, From, S) ->
Id = bump_id(S),
{Name, Request} = gen_req(Command),
@ -586,10 +633,10 @@ send_command(Command, From, S) ->
protocolOp = {Name, Request}},
?DEBUG("~p~n",[{Name, Request}]),
{ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message),
case gen_tcp:send(S#eldap.fd, Bytes) of
case (S#eldap.sockmod):send(S#eldap.fd, Bytes) of
ok ->
Timer = erlang:start_timer(?CMD_TIMEOUT, self(), {cmd_timeout, Id}),
New_dict = dict:store(Id, [{Timer, From, Name}], S#eldap.dict),
New_dict = dict:store(Id, [{Timer, Command, From, Name}], S#eldap.dict),
{ok, S#eldap{id = Id, dict = New_dict}};
Error ->
Error
@ -649,9 +696,10 @@ recvd_packet(Pkt, S) ->
Dict = S#eldap.dict,
Id = Msg#'LDAPMessage'.messageID,
{Timer, From, Name, Result_so_far} = get_op_rec(Id, Dict),
Answer =
case {Name, Op} of
{searchRequest, {searchResEntry, R}} when
record(R,'SearchResultEntry') ->
is_record(R,'SearchResultEntry') ->
New_dict = dict:append(Id, R, Dict),
{ok, S#eldap{dict = New_dict}};
{searchRequest, {searchResDone, Result}} ->
@ -696,14 +744,14 @@ recvd_packet(Pkt, S) ->
New_dict = dict:erase(Id, Dict),
cancel_timer(Timer),
Reply = check_bind_reply(Result, From),
gen_fsm:send_all_state_event(self(), process_bind_q),
{reply, Reply, From, S#eldap{dict = New_dict}};
{OtherName, OtherResult} ->
New_dict = dict:erase(Id, Dict),
cancel_timer(Timer),
{reply, {error, {invalid_result, OtherName, OtherResult}},
From, S#eldap{dict = New_dict}}
end;
end,
{response, Answer, Name};
Error -> Error
end.
@ -723,7 +771,7 @@ check_bind_reply(Other, _From) ->
get_op_rec(Id, Dict) ->
case dict:find(Id, Dict) of
{ok, [{Timer, From, Name}|Res]} ->
{ok, [{Timer, _Command, From, Name}|Res]} ->
{Timer, From, Name, Res};
error ->
throw({error, unkown_id})
@ -783,14 +831,24 @@ check_tag(Data) ->
_ -> throw({error,decoded_tag})
end.
close_and_retry(S, Timeout) ->
catch (S#eldap.sockmod):close(S#eldap.fd),
Queue = dict:fold(
fun(_Id, [{Timer, Command, From, _Name}|_], Q) ->
cancel_timer(Timer),
queue:in_r({Command, From}, Q);
(_, _, Q) ->
Q
end, S#eldap.req_q, S#eldap.dict),
erlang:send_after(Timeout, self(), {timeout, retry_connect}),
S#eldap{fd=null, req_q=Queue, dict=dict:new()}.
close_and_retry(S) ->
gen_tcp:close(S#eldap.fd),
retry_connect().
retry_connect() ->
erlang:send_after(?RETRY_TIMEOUT, self(),
{timeout, retry_connect}).
close_and_retry(S, ?RETRY_TIMEOUT).
report_bind_failure(Host, Port, Reason) ->
?WARNING_MSG("LDAP bind failed on ~s:~p~nReason: ~p",
[Host, Port, Reason]).
%%-----------------------------------------------------------------------
%% Sort out timed out commands
@ -798,7 +856,7 @@ retry_connect() ->
cmd_timeout(Timer, Id, S) ->
Dict = S#eldap.dict,
case dict:find(Id, Dict) of
{ok, [{Timer, From, Name}|Res]} ->
{ok, [{Timer, _Command, From, Name}|Res]} ->
case Name of
searchRequest ->
{Res1, Ref1} = polish(Res),
@ -825,7 +883,7 @@ cmd_timeout(Timer, Id, S) ->
polish(Entries) ->
polish(Entries, [], []).
polish([H|T], Res, Ref) when record(H, 'SearchResultEntry') ->
polish([H|T], Res, Ref) when is_record(H, 'SearchResultEntry') ->
ObjectName = H#'SearchResultEntry'.objectName,
F = fun({_,A,V}) -> {A,V} end,
Attrs = lists:map(F, H#'SearchResultEntry'.attributes),
@ -841,27 +899,40 @@ polish([], Res, Ref) ->
%%-----------------------------------------------------------------------
connect_bind(S) ->
Host = next_host(S#eldap.host, S#eldap.hosts),
TcpOpts = [{packet, asn1}, {active, true}, {keepalive, true}, binary],
?INFO_MSG("LDAP connection on ~s:~p", [Host, S#eldap.port]),
case gen_tcp:connect(Host, S#eldap.port, TcpOpts) of
SocketData = case S#eldap.tls of
tls ->
SockMod = ssl,
SslOpts = [{packet, asn1}, {active, true}, {keepalive, true},
binary],
ssl:connect(Host, S#eldap.port, SslOpts);
%% starttls -> %% TODO: Implement STARTTLS;
_ ->
SockMod = gen_tcp,
TcpOpts = [{packet, asn1}, {active, true}, {keepalive, true},
{send_timeout, ?SEND_TIMEOUT}, binary],
gen_tcp:connect(Host, S#eldap.port, TcpOpts)
end,
case SocketData of
{ok, Socket} ->
case bind_request(Socket, S) of
case bind_request(Socket, S#eldap{sockmod = SockMod}) of
{ok, NewS} ->
Timer = erlang:start_timer(?BIND_TIMEOUT, self(),
{timeout, bind_timeout}),
{ok, wait_bind_response, NewS#eldap{fd = Socket,
sockmod = SockMod,
host = Host,
bind_timer = Timer}};
{error, Reason} ->
?ERROR_MSG("LDAP bind failed on ~s:~p~nReason: ~p", [Host, S#eldap.port, Reason]),
gen_tcp:close(Socket),
retry_connect(),
{ok, connecting, S#eldap{host = Host}}
report_bind_failure(Host, S#eldap.port, Reason),
NewS = close_and_retry(S),
{ok, connecting, NewS#eldap{host = Host}}
end;
{error, Reason} ->
?ERROR_MSG("LDAP connection failed on ~s:~p~nReason: ~p", [Host, S#eldap.port, Reason]),
retry_connect(),
{ok, connecting, S#eldap{host = Host}}
?ERROR_MSG("LDAP connection failed on ~s:~p~nReason: ~p",
[Host, S#eldap.port, Reason]),
NewS = close_and_retry(S),
{ok, connecting, NewS#eldap{host = Host}}
end.
bind_request(Socket, S) ->
@ -873,7 +944,7 @@ bind_request(Socket, S) ->
protocolOp = {bindRequest, Req}},
?DEBUG("Bind Request Message:~p~n",[Message]),
{ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message),
case gen_tcp:send(Socket, Bytes) of
case (S#eldap.sockmod):send(Socket, Bytes) of
ok -> {ok, S#eldap{id = Id}};
Error -> Error
end.
@ -901,7 +972,7 @@ v_filter({greaterOrEqual,AV}) -> {greaterOrEqual,AV};
v_filter({lessOrEqual,AV}) -> {lessOrEqual,AV};
v_filter({approxMatch,AV}) -> {approxMatch,AV};
v_filter({present,A}) -> {present,A};
v_filter({substrings,S}) when record(S,'SubstringFilter') -> {substrings,S};
v_filter({substrings,S}) when is_record(S,'SubstringFilter') -> {substrings,S};
v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}).
v_modifications(Mods) ->
@ -913,7 +984,7 @@ v_modifications(Mods) ->
end,
lists:foreach(F, Mods).
v_substr([{Key,Str}|T]) when list(Str),Key==initial;Key==any;Key==final ->
v_substr([{Key,Str}|T]) when is_list(Str),Key==initial;Key==any;Key==final ->
[{Key,Str}|v_substr(T)];
v_substr([H|_]) ->
throw({error,{substring_arg,H}});
@ -928,11 +999,11 @@ v_bool(true) -> true;
v_bool(false) -> false;
v_bool(_Bool) -> throw({error,concat(["not Boolean: ",_Bool])}).
v_timeout(I) when integer(I), I>=0 -> I;
v_timeout(I) when is_integer(I), I>=0 -> I;
v_timeout(_I) -> throw({error,concat(["timeout not positive integer: ",_I])}).
v_attributes(Attrs) ->
F = fun(A) when list(A) -> A;
F = fun(A) when is_list(A) -> A;
(A) -> throw({error,concat(["attribute not String: ",A])})
end,
lists:map(F,Attrs).
@ -947,8 +1018,8 @@ get_config() ->
case file:consult(File) of
{ok, Entries} ->
case catch parse(Entries) of
{ok, Hosts, Port, Rootdn, Passwd} ->
{ok, Hosts, Port, Rootdn, Passwd};
{ok, Hosts, Port, Rootdn, Passwd, Encrypt} ->
{ok, Hosts, Port, Rootdn, Passwd, Encrypt};
{error, Reason} ->
{error, Reason};
{'EXIT', Reason} ->
@ -963,11 +1034,12 @@ parse(Entries) ->
get_hosts(host, Entries),
get_integer(port, Entries),
get_list(rootdn, Entries),
get_list(passwd, Entries)}.
get_list(passwd, Entries),
get_atom(encrypt, Entries)}.
get_integer(Key, List) ->
case lists:keysearch(Key, 1, List) of
{value, {Key, Value}} when integer(Value) ->
{value, {Key, Value}} when is_integer(Value) ->
Value;
{value, {Key, _Value}} ->
throw({error, "Bad Value in Config for " ++ atom_to_list(Key)});
@ -977,7 +1049,17 @@ get_integer(Key, List) ->
get_list(Key, List) ->
case lists:keysearch(Key, 1, List) of
{value, {Key, Value}} when list(Value) ->
{value, {Key, Value}} when is_list(Value) ->
Value;
{value, {Key, _Value}} ->
throw({error, "Bad Value in Config for " ++ atom_to_list(Key)});
false ->
throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
end.
get_atom(Key, List) ->
case lists:keysearch(Key, 1, List) of
{value, {Key, Value}} when is_atom(Value) ->
Value;
{value, {Key, _Value}} ->
throw({error, "Bad Value in Config for " ++ atom_to_list(Key)});
@ -986,13 +1068,13 @@ get_list(Key, List) ->
end.
get_hosts(Key, List) ->
lists:map(fun({Key1, {A,B,C,D}}) when integer(A),
integer(B),
integer(C),
integer(D),
lists:map(fun({Key1, {A,B,C,D}}) when is_integer(A),
is_integer(B),
is_integer(C),
is_integer(D),
Key == Key1->
{A,B,C,D};
({Key1, Value}) when list(Value),
({Key1, Value}) when is_list(Value),
Key == Key1->
Value;
({_Else, _Value}) ->

View File

@ -1,6 +1,6 @@
%%%----------------------------------------------------------------------
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -11,7 +11,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -19,6 +19,9 @@
%%%
%%%----------------------------------------------------------------------
-define(LDAP_PORT, 389).
-define(LDAPS_PORT, 636).
-record(eldap_search, {scope = wholeSubtree,
base = [],
filter,

View File

@ -6,7 +6,7 @@
%%% Author: Evgeniy Khramtsov <xramtsov@gmail.com>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -17,7 +17,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA

View File

@ -5,7 +5,7 @@
%%% Created : 12 Nov 2006 by Evgeniy Khramtsov <xram@jabber.ru>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -29,7 +29,7 @@
%% API
-export([
start_link/6,
start_link/7,
bind/3,
search/2
]).
@ -45,12 +45,12 @@ bind(PoolName, DN, Passwd) ->
search(PoolName, Opts) ->
do_request(PoolName, {search, [Opts]}).
start_link(Name, Hosts, Backups, Port, Rootdn, Passwd) ->
start_link(Name, Hosts, Backups, Port, Rootdn, Passwd, Encrypt) ->
PoolName = make_id(Name),
pg2:create(PoolName),
lists:foreach(fun(Host) ->
ID = erlang:ref_to_list(make_ref()),
case catch eldap:start_link(ID, [Host|Backups], Port, Rootdn, Passwd) of
case catch eldap:start_link(ID, [Host|Backups], Port, Rootdn, Passwd, Encrypt) of
{ok, Pid} ->
pg2:join(PoolName, Pid);
_ ->

View File

@ -5,7 +5,7 @@
%%% Created : 12 Oct 2006 by Mickael Remond <mremond@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA

View File

@ -1,4 +1,23 @@
/* $Id$ */
/*
* ejabberd, Copyright (C) 2002-2009 ProcessOne
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*
*/
#include <stdio.h>
#include <string.h>
@ -6,100 +25,6 @@
#include <ei.h>
#include <expat.h>
#define EI_ENCODE_STRING_BUG
#ifdef EI_ENCODE_STRING_BUG
/*
* Workaround for EI encode_string bug
*/
#define put8(s,n) do { \
(s)[0] = (char)((n) & 0xff); \
(s) += 1; \
} while (0)
#define put16be(s,n) do { \
(s)[0] = ((n) >> 8) & 0xff; \
(s)[1] = (n) & 0xff; \
(s) += 2; \
} while (0)
#define put32be(s,n) do { \
(s)[0] = ((n) >> 24) & 0xff; \
(s)[1] = ((n) >> 16) & 0xff; \
(s)[2] = ((n) >> 8) & 0xff; \
(s)[3] = (n) & 0xff; \
(s) += 4; \
} while (0)
int ei_encode_string_len_fixed(char *buf, int *index, const char *p, int len)
{
char *s = buf + *index;
char *s0 = s;
int i;
if (len <= 0xffff) {
if (!buf) s += 3;
else {
put8(s,ERL_STRING_EXT);
put16be(s,len);
memmove(s,p,len); /* unterminated string */
}
s += len;
}
else {
if (!buf) s += 6 + (2*len);
else {
/* strings longer than 65535 are encoded as lists */
put8(s,ERL_LIST_EXT);
put32be(s,len);
for (i=0; i<len; i++) {
put8(s,ERL_SMALL_INTEGER_EXT);
put8(s,p[i]);
}
put8(s,ERL_NIL_EXT);
}
}
*index += s-s0;
return 0;
}
int ei_encode_string_fixed(char *buf, int *index, const char *p)
{
return ei_encode_string_len_fixed(buf, index, p, strlen(p));
}
int ei_x_encode_string_len_fixed(ei_x_buff* x, const char* s, int len)
{
int i = x->index;
ei_encode_string_len_fixed(NULL, &i, s, len);
if (!x_fix_buff(x, i))
return -1;
return ei_encode_string_len_fixed(x->buff, &x->index, s, len);
}
int ei_x_encode_string_fixed(ei_x_buff* x, const char* s)
{
return ei_x_encode_string_len_fixed(x, s, strlen(s));
}
#else
#define ei_encode_string_len_fixed(buf, index, p, len) \
ei_encode_string_len(buf, index, p, len)
#define ei_encode_string_fixed(buf, index, p) \
ei_encode_string(buf, index, p)
#define ei_x_encode_string_len_fixed(x, s, len) \
ei_x_encode_string_len(x, s, len)
#define ei_x_encode_string_fixed(x, s) \
ei_x_encode_string(x, s)
#endif
#define XML_START 0
#define XML_END 1
@ -126,7 +51,7 @@ void *erlXML_StartElementHandler(expat_data *d,
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_encode_string(&event_buf, name);
for (i = 0; atts[i]; i += 2) {}
@ -137,8 +62,8 @@ void *erlXML_StartElementHandler(expat_data *d,
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_string(&event_buf, atts[i]);
ei_x_encode_string(&event_buf, atts[i+1]);
}
}
@ -153,7 +78,7 @@ void *erlXML_EndElementHandler(expat_data *d,
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_encode_string(&event_buf, name);
return NULL;
}
@ -223,7 +148,7 @@ static int expat_erl_control(ErlDrvData drv_data,
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_string(&event_buf, errstring);
}
ei_x_encode_empty_list(&event_buf);

View File

@ -5,7 +5,7 @@
%%% Created : 30 Jul 2004 by Leif Johansson <leifj@it.su.se>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 Process-one
%%% ejabberd, Copyright (C) 2002-2009 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -16,7 +16,7 @@
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
@ -32,6 +32,9 @@
-include("ejabberd.hrl").
-define(INIT_TIMEOUT, 60000). % Timeout is in milliseconds: 60 seconds == 60000
-define(CALL_TIMEOUT, 10000). % Timeout is in milliseconds: 10 seconds == 10000
start(Host, ExtPrg) ->
spawn(?MODULE, init, [Host, ExtPrg]).
@ -39,7 +42,7 @@ 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).
loop(Port, ?INIT_TIMEOUT).
stop(Host) ->
gen_mod:get_module_proc(Host, eauth) ! stop.
@ -61,15 +64,23 @@ call_port(Server, Msg) ->
Result
end.
loop(Port) ->
loop(Port, Timeout) ->
receive
{call, Caller, Msg} ->
Port ! {self(), {command, encode(Msg)}},
receive
{Port, {data, Data}} ->
Caller ! {eauth, decode(Data)}
?DEBUG("extauth call '~p' received data response:~n~p", [Msg, Data]),
Caller ! {eauth, decode(Data)};
{Port, Other} ->
?ERROR_MSG("extauth call '~p' received strange response:~n~p", [Msg, Other]),
Caller ! {eauth, false}
after
Timeout ->
?ERROR_MSG("extauth call '~p' didn't receive response", [Msg]),
Caller ! {eauth, false}
end,
loop(Port);
loop(Port, ?CALL_TIMEOUT);
stop ->
Port ! {self(), close},
receive

Some files were not shown because too many files have changed in this diff Show More