diff --git a/doc/dev.html b/doc/dev.html index 4b8e6bfe9..8cdf2d2ed 100644 --- a/doc/dev.html +++ b/doc/dev.html @@ -194,6 +194,9 @@ operation are as follows: auth:User:Server:Password (check if a username/password pair is correct)
  • isuser:User:Server (check if it’s a valid user)
  • setpass:User:Server:Password (set user’s password) +
  • tryregister:User:Server:Password (try to register an account) +
  • removeuser:User:Server (remove this account) +
  • removeuser3:User:Server:Password (remove this account if the password is correct)
  • write to stdout: AABB diff --git a/doc/dev.tex b/doc/dev.tex index f597d591e..1e46c1af2 100644 --- a/doc/dev.tex +++ b/doc/dev.tex @@ -176,6 +176,9 @@ That script is supposed to do theses actions, in an infinite loop: \item auth:User:Server:Password (check if a username/password pair is correct) \item isuser:User:Server (check if it's a valid user) \item setpass:User:Server:Password (set user's password) + \item tryregister:User:Server:Password (try to register an account) + \item removeuser:User:Server (remove this account) + \item removeuser3:User:Server:Password (remove this account if the password is correct) \end{itemize} \end{itemize} \item write to stdout: AABB diff --git a/doc/guide.html b/doc/guide.html index 0e1294080..635afe51e 100644 --- a/doc/guide.html +++ b/doc/guide.html @@ -1030,14 +1030,13 @@ for user authentication. The syntax is:

    {auth_method, [Method, ...]}.

    The following authentication methods are supported by ejabberd:

    Account creation is only supported by internal and odbc methods.

    +
  • Account creation is only supported by internal, external and odbc methods.

    Internal

    ejabberd uses its internal Mnesia database as the default authentication method. The value internal will enable the internal authentication method.

    Examples: @@ -1048,7 +1047,31 @@ authentication on example.net: {host_config, "example.net", [{auth_method, [ldap]}]}.

  • To use internal authentication on all virtual hosts:
    {auth_method, internal}.
    -
  • +

    +

    External Script

    +

    In this authentication method, when ejabberd starts, +it start a script, and calls it to perform authentication tasks.

    The server administrator can write the external authentication script +in any language. +The details on the interface between ejabberd and the script are described +in the ejabberd Developers Guide. +There are also several example authentication scripts.

    These are the specific options: +

    +{extauth_program, PathToScript}
    +Indicate in this option the full path to the external authentication script. +The script must be executable by ejabberd.
    {extauth_cache, false|CacheTimeInteger}
    +The value false disables the caching feature, this is the default. +The integer 0 (zero) enables caching for statistics, but doesn’t use that cached information to authenticate users. +If another integer value is set, caching is enabled both for statistics and for authentication: +the CacheTimeInteger indicates the number of seconds that ejabberd can reuse +the authentication information since the user last disconnected, +to verify again the user authentication without querying again the extauth script. +Note: caching should not be enabled in a host if internal auth is also enabled. +If caching is enabled, mod_last or mod_last_odbc must be enabled also in that vhost. +

    This example sets external authentication, the extauth script, and enables caching for 10 minutes: +

    {auth_method, [external]}.
    +{extauth_program, "/etc/ejabberd/JabberAuth.class.php"}.
    +{extauth_cache, 600}.
    +

    SASL Anonymous and Anonymous Login

    The value anonymous will enable the internal authentication method.

    The anonymous authentication method can be configured with the following options. Remember that you can use the host_config option to set virtual diff --git a/doc/guide.tex b/doc/guide.tex index 44e8a3af8..9463d57aa 100644 --- a/doc/guide.tex +++ b/doc/guide.tex @@ -1187,8 +1187,7 @@ for user authentication. The syntax is: The following authentication methods are supported by \ejabberd{}: \begin{itemize} \item internal (default) --- See section~\ref{internalauth}. -\item external --- There are \footahref{http://www.ejabberd.im/extauth}{some - example authentication scripts}. +\item external --- See section~\ref{extauth}. \item ldap --- See section~\ref{ldap}. \item odbc --- See section~\ref{mysql}, \ref{pgsql}, \ref{mssql} and \ref{odbc}. @@ -1196,7 +1195,7 @@ The following authentication methods are supported by \ejabberd{}: \item pam --- See section~\ref{pam}. \end{itemize} -Account creation is only supported by internal and odbc methods. +Account creation is only supported by internal, external and odbc methods. \makesubsubsection{internalauth}{Internal} \ind{internal authentication}\ind{Mnesia} @@ -1218,6 +1217,42 @@ Examples: \end{verbatim} \end{itemize} +\makesubsubsection{extauth}{External Script} +\ind{external authentication} + +In this authentication method, when \ejabberd{} starts, +it start a script, and calls it to perform authentication tasks. + +The server administrator can write the external authentication script +in any language. +The details on the interface between ejabberd and the script are described +in the \term{ejabberd Developers Guide}. +There are also \footahref{http://www.ejabberd.im/extauth}{several example authentication scripts}. + +These are the specific options: +\begin{description} + \titem{\{extauth\_program, PathToScript\}} + Indicate in this option the full path to the external authentication script. + The script must be executable by ejabberd. + + \titem{\{extauth\_cache, false|CacheTimeInteger\}} + The value \term{false} disables the caching feature, this is the default. + The integer \term{0} (zero) enables caching for statistics, but doesn't use that cached information to authenticate users. + If another integer value is set, caching is enabled both for statistics and for authentication: + the CacheTimeInteger indicates the number of seconds that ejabberd can reuse + the authentication information since the user last disconnected, + to verify again the user authentication without querying again the extauth script. + Note: caching should not be enabled in a host if internal auth is also enabled. + If caching is enabled, \term{mod\_last} or \term{mod\_last\_odbc} must be enabled also in that vhost. +\end{description} + +This example sets external authentication, the extauth script, and enables caching for 10 minutes: +\begin{verbatim} +{auth_method, [external]}. +{extauth_program, "/etc/ejabberd/JabberAuth.class.php"}. +{extauth_cache, 600}. +\end{verbatim} + \makesubsubsection{saslanonymous}{SASL Anonymous and Anonymous Login} \ind{sasl anonymous}\ind{anonymous login} diff --git a/examples/extauth/check_pass_null.pl b/examples/extauth/check_pass_null.pl old mode 100644 new mode 100755 diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl index 60cea18f3..dc9f95ebb 100644 --- a/src/ejabberd_auth_external.erl +++ b/src/ejabberd_auth_external.erl @@ -35,6 +35,9 @@ try_register/3, dirty_get_registered_users/0, get_vh_registered_users/1, + get_vh_registered_users/2, + get_vh_registered_users_number/1, + get_vh_registered_users_number/2, get_password/2, get_password_s/2, is_user_exists/2, @@ -43,45 +46,87 @@ plain_password_required/0 ]). +-include("ejabberd.hrl"). + %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start(Host) -> extauth:start( Host, ejabberd_config:get_local_option({extauth_program, Host})), - ok. + case check_cache_last_options(Host) of + cache -> + ok = ejabberd_auth_internal:start(Host); + no_cache -> + ok + end. + +check_cache_last_options(Server) -> + %% if extauth_cache is enabled, then a mod_last module must also be enabled + case get_cache_option(Server) of + false -> no_cache; + {true, _CacheTime} -> + case get_mod_last_enabled(Server) of + no_mod_last -> + ?ERROR_MSG("In host ~p extauth is used, extauth_cache is enabled but " + "mod_last is not enabled.", [Server]), + no_cache; + _ -> cache + end + end. plain_password_required() -> true. check_password(User, Server, Password) -> - extauth:check_password(User, Server, Password) andalso Password /= "". + case get_cache_option(Server) of + false -> check_password_extauth(User, Server, Password); + {true, CacheTime} -> check_password_cache(User, Server, Password, CacheTime) + end. check_password(User, Server, Password, _Digest, _DigestGen) -> check_password(User, Server, Password). set_password(User, Server, Password) -> case extauth:set_password(User, Server, Password) of - true -> ok; + true -> set_password_internal(User, Server, Password), + ok; _ -> {error, unknown_problem} end. -try_register(_User, _Server, _Password) -> - {error, not_allowed}. +try_register(User, Server, Password) -> + case get_cache_option(Server) of + false -> try_register_extauth(User, Server, Password); + {true, _CacheTime} -> try_register_external_cache(User, Server, Password) + end. -%% TODO -%% Return the list of all users handled by external dirty_get_registered_users() -> - []. + ejabberd_auth_internal:dirty_get_registered_users(). -get_vh_registered_users(_Server) -> - []. +get_vh_registered_users(Server) -> + ejabberd_auth_internal:get_vh_registered_users(Server). -get_password(_User, _Server) -> - false. +get_vh_registered_users(Server, Data) -> + ejabberd_auth_internal:get_vh_registered_users(Server, Data). -get_password_s(_User, _Server) -> - "". +get_vh_registered_users_number(Server) -> + ejabberd_auth_internal:get_vh_registered_users_number(Server). + +get_vh_registered_users_number(Server, Data) -> + ejabberd_auth_internal:get_vh_registered_users_number(Server, Data). + +%% The password can only be returned if cache is enabled, cached info exists and is fresh enough. +get_password(User, Server) -> + case get_cache_option(Server) of + false -> false; + {true, CacheTime} -> get_password_cache(User, Server, CacheTime) + end. + +get_password_s(User, Server) -> + case get_password(User, Server) of + false -> []; + Other -> Other + end. %% @spec (User, Server) -> true | false | {error, Error} is_user_exists(User, Server) -> @@ -91,9 +136,169 @@ is_user_exists(User, Server) -> _:Error -> {error, Error} end. -remove_user(_User, _Server) -> - {error, not_allowed}. +remove_user(User, Server) -> + case extauth:remove_user(User, Server) of + false -> false; + true -> + case get_cache_option(Server) of + false -> false; + {true, _CacheTime} -> + ejabberd_auth_internal:remove_user(User, Server) + end + end. -remove_user(_User, _Server, _Password) -> - not_allowed. +remove_user(User, Server, Password) -> + case extauth:remove_user(User, Server, Password) of + false -> false; + true -> + case get_cache_option(Server) of + false -> false; + {true, _CacheTime} -> + ejabberd_auth_internal:remove_user(User, Server, Password) + end + end. +%%% +%%% Extauth cache management +%%% + +%% @spec (Host::string()) -> false | {true, CacheTime::integer()} +get_cache_option(Host) -> + case ejabberd_config:get_local_option({extauth_cache, Host}) of + CacheTime when is_integer(CacheTime) -> {true, CacheTime}; + _ -> false + end. + +%% @spec (User, Server, Password) -> true | false +check_password_extauth(User, Server, Password) -> + extauth:check_password(User, Server, Password) andalso Password /= "". + +%% @spec (User, Server, Password) -> true | false +try_register_extauth(User, Server, Password) -> + extauth:try_register(User, Server, Password). + +check_password_cache(User, Server, Password, CacheTime) -> + case get_last_access(User, Server) of + online -> + check_password_internal(User, Server, Password); + never -> + check_password_external_cache(User, Server, Password); + mod_last_required -> + ?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []), + check_password_external_cache(User, Server, Password); + TimeStamp -> + %% If last access exists, compare last access with cache refresh time + case is_fresh_enough(TimeStamp, CacheTime) of + %% If no need to refresh, check password against Mnesia + true -> + case check_password_internal(User, Server, Password) of + %% If password valid in Mnesia, accept it + true -> + true; + %% Else (password nonvalid in Mnesia), check in extauth and cache result + false -> + check_password_external_cache(User, Server, Password) + end; + %% Else (need to refresh), check in extauth and cache result + false -> + check_password_external_cache(User, Server, Password) + end + end. + +get_password_internal(User, Server) -> + ejabberd_auth_internal:get_password(User, Server). + +%% @spec (User, Server, CacheTime) -> false | Password::string() +get_password_cache(User, Server, CacheTime) -> + case get_last_access(User, Server) of + online -> + get_password_internal(User, Server); + never -> + false; + mod_last_required -> + ?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []), + false; + TimeStamp -> + case is_fresh_enough(TimeStamp, CacheTime) of + true -> + get_password_internal(User, Server); + false -> + false + end + end. + + +%% Check the password using extauth; if success then cache it +check_password_external_cache(User, Server, Password) -> + case check_password_extauth(User, Server, Password) of + true -> + set_password_internal(User, Server, Password), true; + false -> + false + end. + +%% Try to register using extauth; if success then cache it +try_register_external_cache(User, Server, Password) -> + case try_register_extauth(User, Server, Password) of + {atomic, ok} = R -> + set_password_internal(User, Server, Password), + R; + _ -> {error, not_allowed} + end. + +%% @spec (User, Server, Password) -> true | false +check_password_internal(User, Server, Password) -> + ejabberd_auth_internal:check_password(User, Server, Password). + +%% @spec (User, Server, Password) -> ok | {error, invalid_jid} +set_password_internal(User, Server, Password) -> + ejabberd_auth_internal:set_password(User, Server, Password). + +%% @spec (TimeLast, CacheTime) -> true | false +%% TimeLast = online | never | integer() +%% CacheTime = integer() | false +is_fresh_enough(online, _CacheTime) -> + true; +is_fresh_enough(never, _CacheTime) -> + false; +is_fresh_enough(TimeStampLast, CacheTime) -> + {MegaSecs, Secs, _MicroSecs} = now(), + Now = MegaSecs * 1000000 + Secs, + (TimeStampLast + CacheTime > Now). + +%% @spec (User, Server) -> online | never | mod_last_required | TimeStamp::integer() +%% Code copied from mod_configure.erl +%% Code copied from web/ejabberd_web_admin.erl +%% TODO: Update time format to XEP-0202: Entity Time +get_last_access(User, Server) -> + case ejabberd_sm:get_user_resources(User, Server) of + [] -> + _US = {User, Server}, + case get_last_info(User, Server) of + mod_last_required -> + mod_last_required; + not_found -> + never; + {ok, Timestamp, _Status} -> + Timestamp + end; + _ -> + online + end. +%% @spec (User, Server) -> {ok, Timestamp, Status} | not_found | mod_last_required +get_last_info(User, Server) -> + case get_mod_last_enabled(Server) of + mod_last -> mod_last:get_last_info(User, Server); + mod_last_odbc -> mod_last_odbc:get_last_info(User, Server); + mod_mod_last -> mod_last_required + end. + +%% @spec (Server) -> mod_last | mod_last_odbc | no_mod_last +get_mod_last_enabled(Server) -> + ML = lists:member(mod_last, gen_mod:loaded_modules(Server)), + MLO = lists:member(mod_last_odbc, gen_mod:loaded_modules(Server)), + case {ML, MLO} of + {true, _} -> mod_last; + {false, true} -> mod_last_odbc; + {false, false} -> no_mod_last + end. diff --git a/src/extauth.erl b/src/extauth.erl index 80a8aa816..1cbd33126 100644 --- a/src/extauth.erl +++ b/src/extauth.erl @@ -27,8 +27,15 @@ -module(extauth). -author('leifj@it.su.se'). --export([start/2, stop/1, init/2, - check_password/3, set_password/3, is_user_exists/2]). +-export([start/2, + stop/1, + init/2, + check_password/3, + set_password/3, + try_register/3, + remove_user/2, + remove_user/3, + is_user_exists/2]). -include("ejabberd.hrl"). @@ -56,6 +63,18 @@ is_user_exists(User, Server) -> set_password(User, Server, Password) -> call_port(Server, ["setpass", User, Server, Password]). +try_register(User, Server, Password) -> + case call_port(Server, ["tryregister", User, Server, Password]) of + true -> {atomic, ok}; + false -> {error, not_allowed} + end. + +remove_user(User, Server) -> + call_port(Server, ["removeuser", User, Server]). + +remove_user(User, Server, Password) -> + call_port(Server, ["removeuser3", User, Server, Password]). + call_port(Server, Msg) -> LServer = jlib:nameprep(Server), gen_mod:get_module_proc(LServer, eauth) ! {call, self(), Msg},