Create branch for ejabberd 2.1.x release line.
SVN Revision: 2688
This commit is contained in:
commit
04545f2668
18
README
18
README
|
@ -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.
|
||||
- ImageMagick’s 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/
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]).
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
18
doc/Makefile
18
doc/Makefile
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
16
doc/dev.html
16
doc/dev.html
|
@ -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  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>  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>  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>  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>  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’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>  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>  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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 –
|
||||
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. —
|
||||
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. —
|
||||
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
4211
doc/guide.html
4211
doc/guide.html
File diff suppressed because it is too large
Load Diff
4255
doc/guide.tex
4255
doc/guide.tex
File diff suppressed because it is too large
Load Diff
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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/
|
|
@ -1,2 +1,2 @@
|
|||
% ejabberd version (automatically generated).
|
||||
\newcommand{\version}{2.1.0-alpha}
|
||||
\newcommand{\version}{2.1.0}
|
||||
|
|
225
src/Makefile.in
225
src/Makefile.in
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
16
src/acl.erl
16
src/acl.erl
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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))).
|
||||
|
|
|
@ -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
|
|
@ -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).
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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) ->
|
||||
|
|
|
@ -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).
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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} ->
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
|
@ -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) ->
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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].
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]}}.
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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().
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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) $<
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}) ->
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
_ ->
|
||||
|
|
|
@ -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
|
||||
|
|
125
src/expat_erl.c
125
src/expat_erl.c
|
@ -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);
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue