rpms/ejabberd/devel ejabberd.init, NONE, 1.1 ejabberd.logrotate, NONE, 1.1 ejabberd.spec, NONE, 1.1 ejabberd_auth_ad.erl, NONE, 1.1 inetrc, NONE, 1.1 mod_ctlextra.erl, NONE, 1.1 mod_shared_roster_ad.erl, NONE, 1.1 mod_vcard_ad.erl, NONE, 1.1 .cvsignore, 1.1, 1.2 sources, 1.1, 1.2

Jeffrey C. Ollie (jcollie) fedora-extras-commits at redhat.com
Fri Jun 23 12:40:03 UTC 2006


Author: jcollie

Update of /cvs/extras/rpms/ejabberd/devel
In directory cvs-int.fedora.redhat.com:/tmp/cvs-serv24618/devel

Modified Files:
	.cvsignore sources 
Added Files:
	ejabberd.init ejabberd.logrotate ejabberd.spec 
	ejabberd_auth_ad.erl inetrc mod_ctlextra.erl 
	mod_shared_roster_ad.erl mod_vcard_ad.erl 
Log Message:
auto-import ejabberd-1.1.1-7.fc5 on branch devel from ejabberd-1.1.1-7.fc5.src.rpm


--- NEW FILE ejabberd.init ---
#!/bin/bash
#
# ejabberd    Starts, Stops and Reloads ejabberd.
#
# chkconfig: - 40 60
# description: ejabberd
# processname: ejabberd
# pidfile: /var/run/ejabberd.pid

. /etc/rc.d/init.d/functions

start() {
        echo -n $"Starting ejabberd: "
        daemon --user=ejabberd --check=ejabberd \
	    "erl" "-pa @libdir@/ejabberd- at version@/ebin \
                   -sname ejabberd \
                   -s ejabberd \
                   -ejabberd config \\\"/etc/ejabberd/ejabberd.cfg\\\" \
                             log_path \\\"/var/log/ejabberd/ejabberd.log\\\" \
                   -sasl sasl_error_logger \\{file,\\\"/var/log/ejabberd/sasl.log\\\"\\} \
                   -mnesia dir \\\"/var/lib/ejabberd/spool\\\" \
                   -kernel inetrc \\\"/etc/ejabberd/inetrc\\\" \
                   -detached"
        RETVAL=$?
        [ $RETVAL -eq 0 ] && touch /var/lock/subsys/ejabberd
        echo
        return $RETVAL
}

stop() {
        # Stop daemons.
        echo -n "Shutting down ejabberd: "
        runuser -s /bin/bash - ejabberd -c "erl -pa @libdir@/ejabberd- at version@/ebin -noinput -sname ejabberdctl -s ejabberd_ctl -extra ejabberd@`hostname -s` stop" && success || failure
        RETVAL=$?
        [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/ejabberd
        echo
        return $RETVAL
}

restart() {
        stop
        start
}

# See how we were called.
case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  restart)
        restart
        ;;
  condrestart)
        [ -f /var/lock/subsys/ejabberd ] && restart || :
        ;;
  status)
        runuser -s /bin/bash - ejabberd -c "erl -pa @libdir@/ejabberd- at version@/ebin -noinput -sname ejabberdctl -s ejabberd_ctl -extra ejabberd@`hostname -s` status"
        ;;
  *)
        echo "Usage: ejabberd {start|stop|restart|reload|condrestart|status}"
        exit 1
esac

exit $?




--- NEW FILE ejabberd.logrotate ---
/var/log/ejabberd/ejabberd.log /var/log/ejabberd/sasl.log {
    missingok
    notifempty
    create 0640 ejabberd ejabberd
    sharedscripts
    postrotate
        runuser -s /bin/bash - ejabberd -c "erl -pa @libdir@/ejabberd- at version@/ebin -noinput -sname ejabberdctl -s ejabberd_ctl -extra ejabberd@`hostname -s` reopen-log >/dev/null 2>/dev/null || true
    endscript
}


--- NEW FILE ejabberd.spec ---
Name:           ejabberd
Version:        1.1.1
Release:        7%{?dist}
Summary:        A distributed, fault-tolerant Jabber/XMPP server

Group:          Applications/Internet
License:        GPL
URL:            http://ejabberd.jabber.ru/
Source0:        http://www.process-one.net/en/projects/ejabberd/download/%{version}/ejabberd-%{version}.tar.gz
Source1:	ejabberd.init
Source2:	ejabberd.logrotate
Source3:	inetrc

# http://ejabberd.jabber.ru/ejabberdctl-extra
Source4:        http://ejabberd.jabber.ru/files/efiles/mod_ctlextra.erl

# The following were extracted from a patch found on http://realloc.spb.ru/share/ejabberdad.html
Source5:        ejabberd_auth_ad.erl
Source6:        mod_shared_roster_ad.erl
Source7:        mod_vcard_ad.erl

BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)

BuildRequires:  expat-devel
BuildRequires:	openssl-devel
BuildRequires:  erlang
BuildRequires:  hevea

Requires:       erlang

Requires(pre): fedora-usermgmt
Requires(post): /sbin/chkconfig
Requires(preun): /sbin/chkconfig
Requires(preun): /sbin/service
Requires(postun): /sbin/service

%description
ejabberd is a Free and Open Source distributed fault-tolerant
Jabber/XMPP server. It is mostly written in Erlang, and runs on many
platforms (tested on Linux, FreeBSD, NetBSD, Solaris, Mac OS X and
Windows NT/2000/XP).

%package doc
Summary: Documentation for ejabberd
Group: Documentation

%description doc
Documentation for ejabberd.

%pre
/usr/sbin/fedora-groupadd 27 -r ejabberd &>/dev/null || :
/usr/sbin/fedora-useradd  27 -r -s /sbin/nologin -d /var/lib/ejabberd -M \
                             -c 'ejabberd' -g ejabberd ejabberd &>/dev/null || :

%post
/sbin/chkconfig --add ejabberd

%preun
if [ $1 = 0 ]; then
        /sbin/service ejabberd stop >/dev/null 2>&1
        /sbin/chkconfig --del ejabberd
fi

%postun
if [ "$1" -ge "1" ]; then
        /sbin/service ejabberd condrestart >/dev/null 2>&1
fi

%prep
%setup -q

%{__perl} -pi -e "s!/var/lib/ejabberd!%{_libdir}/ejabberd-%{version}!g" src/Makefile.in
%{__perl} -pi -e "s!/etc!%{_sysconfdir}!g" src/Makefile.in
%{__perl} -pi -e "s!\@prefix\@!!g" src/Makefile.in

cp %{S:4} src
cp %{S:5} src
cp %{S:6} src
cp %{S:7} src

%build
pushd src
%configure --enable-odbc
make %{?_smp_mflags}
popd
pushd doc
make html pdf
popd

%install
rm -rf %{buildroot}

pushd src
make install DESTDIR=%{buildroot}
popd

chmod a+x %{buildroot}%{_libdir}/ejabberd-%{version}/priv/lib/*.so

%{__perl} -pi -e 's!./ssl.pem!/etc/ejabberd/ejabberd.pem!g' %{buildroot}/etc/ejabberd/ejabberd.cfg

mkdir -p %{buildroot}/var/log/ejabberd
mkdir -p %{buildroot}/var/lib/ejabberd/spool

mkdir -p %{buildroot}%{_initrddir}
cp %{S:1} %{buildroot}%{_initrddir}/ejabberd
chmod a+x %{buildroot}%{_initrddir}/ejabberd

mkdir -p %{buildroot}%{_sysconfdir}/logrotate.d
cp %{S:2} %{buildroot}%{_sysconfdir}/logrotate.d/ejabberd

%{__perl} -pi -e 's!\@libdir\@!%{_libdir}!g' %{buildroot}%{_initrddir}/ejabberd %{buildroot}%{_sysconfdir}/logrotate.d/ejabberd
%{__perl} -pi -e 's!\@version\@!%{version}!g' %{buildroot}%{_initrddir}/ejabberd %{buildroot}%{_sysconfdir}/logrotate.d/ejabberd

cp %{S:3} %{buildroot}%{_sysconfdir}/ejabberd/inetrc

%clean
rm -rf %{buildroot}

%files
%defattr(-,root,root,-)
%doc COPYING

%attr(750,ejabberd,ejabberd) %dir %{_sysconfdir}/ejabberd
%attr(640,ejabberd,ejabberd) %config(noreplace) %{_sysconfdir}/ejabberd/ejabberd.cfg
%attr(640,ejabberd,ejabberd) %config(noreplace) %{_sysconfdir}/ejabberd/inetrc

%{_initrddir}/ejabberd
%config(noreplace) %{_sysconfdir}/logrotate.d/ejabberd

%dir %{_libdir}/ejabberd-%{version}
%dir %{_libdir}/ejabberd-%{version}/ebin
%{_libdir}/ejabberd-%{version}/ebin/*.app
%{_libdir}/ejabberd-%{version}/ebin/*.beam

%dir %{_libdir}/ejabberd-%{version}/priv

%dir %{_libdir}/ejabberd-%{version}/priv/lib
%{_libdir}/ejabberd-%{version}/priv/lib/*.so

%dir %{_libdir}/ejabberd-%{version}/priv/msgs
%{_libdir}/ejabberd-%{version}/priv/msgs/*.msg

%attr(750,ejabberd,ejabberd) %dir /var/lib/ejabberd
%attr(750,ejabberd,ejabberd) %dir /var/lib/ejabberd/spool

%attr(750,ejabberd,ejabberd) %dir /var/log/ejabberd

%files doc
%defattr(-,root,root,-)
%doc ChangeLog COPYING TODO doc/*.pdf doc/*.html doc/*.png doc/release_notes_*

%changelog
* Thu Jun 22 2006 Jeffrey C. Ollie <jeff at ocjtech.us> - 1.1.1-7
- Oops drop bad patch.

* Thu Jun 22 2006 Jeffrey C. Ollie <jeff at ocjtech.us> - 1.1.1-6
- Split documentation off to a subpackage.
- Own %{_libdir}/ejabberd-%{version}
- Mark %{_sysconfdir}/logrotate.d/ejabberd as %config

* Thu Jun  8 2006 Jeffrey C. Ollie <jeff at ocjtech.us> - 1.1.1-5
- Patch the makefile so that it adds a soname to shared libs.

* Fri May 26 2006 Jeffrey C. Ollie <jeff at ocjtech.us> - 1.1.1-4
- Modify AD modules not to check for group membership.

* Thu May 25 2006 Jeffrey C. Ollie <jeff at ocjtech.us> - 1.1.1-3
- Add some extra modules

* Wed May 24 2006 Jeffrey C. Ollie <jeff at ocjtech.us> - 1.1.1-2
- Munge Makefile.in a bit more...
- Change ownership/permissions - not *everything* needs to be owned by ejabberd

* Wed May 24 2006 Jeffrey C. Ollie <jeff at ocjtech.us> - 1.1.1-1
- First version for Fedora Extras


--- NEW FILE ejabberd_auth_ad.erl ---
%%%----------------------------------------------------------------------
%%% File    : ejabberd_auth_ad.erl
%%% Author  : Alexey Shchepin <alexey at sevcom.net>
%%% Author  : Alex Gorbachenko <agent_007 at immo.ru>
%%% Author  : Stanislav Bogatyrev <realloc at realloc.spb.ru>
%%% Purpose : Authentification via Active Directory
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey at sevcom.net>
%%% Id      : $Id: ejabberd_auth_ad.erl 386 2005-12-20 10:06:37Z agent_007 $
%%%----------------------------------------------------------------------

-module(ejabberd_auth_ad).
-author('alexey at sevcom.net').
-author('agent_007 at immo.ru').
-author('realloc at realloc.spb.ru').
-vsn('$Revision: 386 $ ').

%% External exports
-export([start/1,
	 set_password/3,
	 check_password/3,
	 check_password/5,
	 try_register/3,
	 dirty_get_registered_users/0,
	 get_vh_registered_users/1,
	 get_password/2,
	 get_password_s/2,
	 is_user_exists/2,
	 remove_user/2,
	 remove_user/3,
	 plain_password_required/0
	]).

-include("ejabberd.hrl").
-include("eldap/eldap.hrl").

%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
    LDAPServers = ejabberd_config:get_local_option({ad_servers, Host}),
    RootDN = ejabberd_config:get_local_option({ad_rootdn, Host}),
    Password = ejabberd_config:get_local_option({ad_password, Host}),
    eldap:start_link(get_eldap_id(Host, ejabberd),
		     LDAPServers, 389, RootDN, Password),
    eldap:start_link(get_eldap_id(Host, ejabberd_bind),
		     LDAPServers, 389, RootDN, Password),
    ok.

plain_password_required() ->
    true.

check_password(User, Server, Password) ->
    case find_user_dn(User, Server) of
	false ->
	    false;
	DN ->
	    LServer = jlib:nameprep(Server),
	    case eldap:bind(get_eldap_id(LServer, ejabberd_bind),
			    DN, Password) of
		ok ->
		    true;
		_ ->
		    false
	    end
    end.

check_password(User, Server, Password, _StreamID, _Digest) ->
    check_password(User, Server, Password).

set_password(_User, _Server, _Password) ->
    {error, not_allowed}.

try_register(_User, _Server, _Password) ->
    {error, not_allowed}.

dirty_get_registered_users() ->
    get_vh_registered_users(?MYNAME).

get_vh_registered_users(Server) ->
    LServer = jlib:nameprep(Server),
    Attr = ejabberd_config:get_local_option({ad_uidattr, LServer}),
%    AdGroup = ejabberd_config:get_local_option({ad_group, LServer}),
    FilterPerson = eldap:equalityMatch("objectCategory", "person"),
    FilterComp = eldap:equalityMatch("objectClass", "computer"),
    FilterHidden = eldap:equalityMatch("description", "hidden"),
%    FilterGroup = eldap:equalityMatch("memberOf", AdGroup),
    FilterLive = eldap:equalityMatch("userAccountControl", "66050"),
    FilterDef = eldap:present(Attr),
    Filter = eldap:'and'([
			  FilterDef,
			  FilterPerson,
%			  FilterGroup,
			  eldap:'not'(FilterComp),
			  eldap:'not'(FilterHidden),
			  eldap:'not'(FilterLive)]),
    Base = ejabberd_config:get_local_option({ad_base, LServer}),
    case eldap:search(get_eldap_id(LServer, ejabberd),
		      [{base, Base},
		       {filter, Filter},
		       {attributes, [Attr]}]) of
	#eldap_search_result{entries = Es} ->
	    lists:flatmap(
	      fun(E) ->
		      case lists:keysearch(Attr, 1, E#eldap_entry.attributes) of
			  {value, {_, [U]}} ->
			      case jlib:nodeprep(U) of
				  error ->
				      [];
				  LU ->
				      [{LU, LServer}]
			      end;
			  _ ->
			      []
		      end
	      end, Es);
	_ ->
	    []
    end.

get_password(_User, _Server) ->
    false.

get_password_s(_User, _Server) ->
    "".

is_user_exists(User, Server) ->
    case find_user_dn(User, Server) of
	false ->
	    false;
	_DN ->
	    true
    end.

remove_user(_User, _Server) ->
    {error, not_allowed}.

remove_user(_User, _Server, _Password) ->
    not_allowed.


%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------

find_user_dn(User, Server) ->
    LServer = jlib:nameprep(Server),
    AdGroup = ejabberd_config:get_local_option({ad_group, LServer}),
    Attr = ejabberd_config:get_local_option({ad_uidattr, LServer}),
    FilterAttr = eldap:equalityMatch(Attr, User),
    FilterGroup = eldap:equalityMatch("memberOf", AdGroup),
    Filter = eldap:'and'([
                          FilterAttr,
                          FilterGroup
                          ]),
    Base = ejabberd_config:get_local_option({ad_base, LServer}),
    case eldap:search(get_eldap_id(LServer, ejabberd),
		      [{base, Base},
		       {filter, Filter},
		       {attributes, []}]) of
	#eldap_search_result{entries = [E | _]} ->
	    E#eldap_entry.object_name;
	_ ->
	    false
    end.

get_eldap_id(Host, Name) ->
    atom_to_list(gen_mod:get_module_proc(Host, Name)).


--- NEW FILE inetrc ---
{file, resolv, "/etc/resolv.conf"}.


--- NEW FILE mod_ctlextra.erl ---
%%%----------------------------------------------------------------------
%%% File    : mod_ctlextra.erl
%%% Author  :
%%% Purpose : Adds more options for ejabberd_ctl
%%% Created :
%%% Id      :
%%%----------------------------------------------------------------------

%%% mod_ctlextra v0.2.2 for ejabberd 1.1.1 (18/May/2006) 
%%%
%%% INSTALL:
%%%  1. Copy mod_ctlextra.erl to your ejabberd/src
%%%  2. Add to your ejabberd.cfg, on the modules section:
%%%         {mod_ctlextra, []},
%%%  3. Recompile and restart ejabberd 
%%%
%%% USAGE:
%%%  - Now you have several new options for ejabberd-ctl
%%%  - Example for vcard: ejabberdctl eja at host vcard-get joe myjab.net email
%%%  - The file used by 'pushroster' and 'pushroster-all' must be placed on 
%%%      the same directory where the .beam files are. Example content:
%%%         [{"bob", "myserver", "workers", "Bob"},
%%%          {"mart", "myserver", "workers", "Mart"},
%%%          {"Rich", "myserver", "bosses", "Rich"}].

-module(mod_ctlextra).
-author('').
-vsn('Version 0.2.1').

-behaviour(gen_mod).

-export([
	start/2,
	stop/1,
	ctl_process/2,
	ctl_process/3
	]).

-include("ejabberd_ctl.hrl").
-include("jlib.hrl").

start(Host, _Opts) ->
	ejabberd_ctl:register_commands([
		{"compile file", "recompile and reload file"},
		{"load-config file", "load config from file"},
		{"remove-node nodename", "remove an ejabberd node from the database"},

		% ejabberd_auth
		{"delete-older-users days", "delete users that have not logged in the last 'days'"},
		{"set-password user server password", "set password to user at server"},

		% ejd2odbc
		{"export2odbc server output", "export all possible tables on server to output"},

		% mod_offline
		{"delete-older-messages days", "delete offline messages older than 'days'"},

		% mod_shared_roster
		{"srg-create group host name description display", "create the group with options"},
		{"srg-delete group host", "delete the group"},
		{"srg-user-add user server group host", "add user at server to group on host"},
		{"srg-user-del user server group host", "delete user at server from group on host"},
			
		% mod_vcard
		{"vcard-get user host data [data2]", "get data from the vCard of the user"},
		{"vcard-set user host data [data2] content", "set data to content on the vCard"},

		% mod_announce
		% announce_send_online host message
		% announce_send_all host, message

		% mod_muc
		% muc-add room opts
		% muc-del room
		% muc-del-older 90 : delete rooms older than X days (with no activity (chat, presence, logins) in 6 months)

		% mod_roster
		{"add-rosteritem user1 server1 user2 server2 nick group subs", "Add user2 at server2 to user1 at server1"},
		%{"", "subs= none, from, to or both"},
		%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
		%{"", "will add mike at server.com to peter at localhost roster"},
		{"pushroster file user server", "push template roster in file to user at server"},
		{"pushroster-all file", "push template roster in file to all those users"},
		{"push-alltoall server group", "adds all the users to all the users in Group"},

		{"stats registeredusers", "number of registered users"},
		{"stats onlineusers", "number of logged users"},

		% misc
		{"killsession user server resource", "kill a user session"}
	], ?MODULE, ctl_process),
    ejabberd_ctl:register_commands(Host, [
		% mod_last
		{"num-active-users days", "number of users active in the last 'days'"},
		{"stats registeredusers", "number of registered users"},
		{"stats onlineusers", "number of logged users"}
	], ?MODULE, ctl_process),
	ok.

stop(_Host) ->
	ok.


ctl_process(_Val, ["blo"]) ->
	FResources = "eeeaaa aaa",
	io:format("~s", [FResources]),
	?STATUS_SUCCESS;

ctl_process(_Val, ["delete-older-messages", Days]) ->
    mod_offline:remove_old_messages(list_to_integer(Days)),
    ?STATUS_SUCCESS;

ctl_process(_Val, ["delete-older-users", Days]) ->
	{removed, N, UR} = delete_older_users(list_to_integer(Days)),
	io:format("Deleted ~p users: ~p~n", [N, UR]),
	?STATUS_SUCCESS;

ctl_process(_Val, ["export2odbc", Server, Output1]) ->
	Output = list_to_atom(Output1),
	ejd2odbc:export_passwd(Server, Output),
	ejd2odbc:export_roster(Server, Output),
	ejd2odbc:export_offline(Server, Output),
	ejd2odbc:export_last(Server, Output),
	ejd2odbc:export_vcard(Server, Output),
	ejd2odbc:export_vcard_search(Server, Output),
	?STATUS_SUCCESS;

ctl_process(_Val, ["set-password", User, Server, Password]) ->
    ejabberd_auth:set_password(User, Server, Password),
	?STATUS_SUCCESS;

ctl_process(_Val, ["vcard-get", User, Server, Data]) ->
	{ok, Res} = vcard_get(User, Server, {Data}),
    io:format("~s~n", [Res]),
    ?STATUS_SUCCESS;

ctl_process(_Val, ["vcard-get", User, Server, Data1, Data2]) ->
	{ok, Res} = vcard_get(User, Server, {Data1, Data2}),
    io:format("~s~n", [Res]),
    ?STATUS_SUCCESS;

ctl_process(_Val, ["vcard-set", User, Server, Data, Content]) ->
	{ok, Res} = vcard_set(User, Server, Data, Content),
    io:format("~s~n", [Res]),
    ?STATUS_SUCCESS;

ctl_process(_Val, ["vcard-set", User, Server, Data1, Data2, Content]) ->
	{ok, Res} = vcard_set(User, Server, Data1, Data2, Content),
    io:format("~s~n", [Res]),
    ?STATUS_SUCCESS;

ctl_process(_Val, ["compile", Module]) ->
    compile:file(Module),
	?STATUS_SUCCESS;

ctl_process(_Val, ["remove-node", Node]) ->
	mnesia:del_table_copy(schema, list_to_atom(Node)),
	?STATUS_SUCCESS;

ctl_process(_Val, ["srg-create", Group, Host, Name, Description, Display]) ->
	Opts = [{name, Name}, {displayed_groups, [Display]}, {description, Description}],
	{atomic, ok} = mod_shared_roster:create_group(Host, Group, Opts),
    ?STATUS_SUCCESS;

ctl_process(_Val, ["srg-delete", Group, Host]) ->
	{atomic, ok} = mod_shared_roster:delete_group(Host, Group),
    ?STATUS_SUCCESS;

ctl_process(_Val, ["srg-user-add", User, Server, Group, Host]) ->
	{atomic, ok} = mod_shared_roster:add_user_to_group(Host, {User, Server}, Group),
    ?STATUS_SUCCESS;

ctl_process(_Val, ["srg-user-del", User, Server, Group, Host]) ->
	{atomic, ok} = mod_shared_roster:remove_user_from_group(Host, {User, Server}, Group),
    ?STATUS_SUCCESS;

ctl_process(_Val, ["add-rosteritem", LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, Subs]) ->
    case add_rosteritem(LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, list_to_atom(Subs), []) of
	{atomic, ok} ->
	    ?STATUS_SUCCESS;
	{error, Reason} ->
	    io:format("Can't add ~p@~p to ~p@~p: ~p~n",
		      [RemoteUser, RemoteServer, LocalUser, LocalServer, Reason]),
	    ?STATUS_ERROR;
	{badrpc, Reason} ->
	    io:format("Can't add roster item to user ~p: ~p~n",
		      [LocalUser, Reason]),
	    ?STATUS_BADRPC
    end;

ctl_process(_Val, ["pushroster", File, User, Server]) ->
    case pushroster(File, User, Server) of
	ok ->
	    ?STATUS_SUCCESS;
	{error, Reason} ->
	    io:format("Can't push roster ~p to ~p@~p: ~p~n",
		      [File, User, Server, Reason]),
	    ?STATUS_ERROR;
	{badrpc, Reason} ->
	    io:format("Can't push roster ~p: ~p~n",
		      [File, Reason]),
	    ?STATUS_BADRPC
    end;

ctl_process(_Val, ["pushroster-all", File]) ->
    case pushroster_all([File]) of
	ok ->
	    ?STATUS_SUCCESS;
	{error, Reason} ->
	    io:format("Can't push roster ~p: ~p~n",
		      [File, Reason]),
	    ?STATUS_ERROR;
	{badrpc, Reason} ->
	    io:format("Can't push roster ~p: ~p~n",
		      [File, Reason]),
	    ?STATUS_BADRPC
    end;

ctl_process(_Val, ["push-alltoall", Server, Group]) ->
    case push_alltoall(Server, Group) of
	ok ->
	    ?STATUS_SUCCESS;
	{error, Reason} ->
	    io:format("Can't push all to all: ~p~n",
		      [Reason]),
	    ?STATUS_ERROR;
	{badrpc, Reason} ->
	    io:format("Can't push all to all: ~p~n",
		      [Reason]),
	    ?STATUS_BADRPC
    end;

ctl_process(_Val, ["load-config", Path]) ->
    case ejabberd_config:load_file(Path) of
        {atomic, ok} ->
            ?STATUS_SUCCESS;
        {error, Reason} ->
            io:format("Can't load config file ~p: ~p~n",
                      [filename:absname(Path), Reason]),
	    ?STATUS_ERROR;
        {badrpc, Reason} ->
            io:format("Can't load config file ~p: ~p~n",
                      [filename:absname(Path), Reason]),
	    ?STATUS_BADRPC
    end;

ctl_process(_Val, ["stats", Stat]) ->
	Res = case Stat of
		"registeredusers" -> mnesia:table_info(passwd, size);
		"onlineusers" -> mnesia:table_info(session, size)
	end,
    io:format("~p~n", [Res]),
    ?STATUS_SUCCESS;

ctl_process(_Val, ["killsession", User, Server, Resource]) ->
    ejabberd_router:route(
        jlib:make_jid("", "", ""), 
        jlib:make_jid(User, Server, Resource),
        {xmlelement, "broadcast", [], [{exit, "killed"}]}),
    ?STATUS_SUCCESS;

ctl_process(Val, _Args) ->
	Val.


ctl_process(_Val, Host, ["num-active-users", Days]) ->
	Number = num_active_users(Host, list_to_integer(Days)),
    io:format("~p~n", [Number]),
    ?STATUS_SUCCESS;

ctl_process(_Val, Host, ["stats", Stat]) ->
	Res = case Stat of
		"registeredusers" -> length(ejabberd_auth:get_vh_registered_users(Host));
		"onlineusers" -> length(ejabberd_sm:get_vh_session_list(Host))
	end,
    io:format("~p~n", [Res]),
    ?STATUS_SUCCESS;

ctl_process(Val, _Host, _Args) ->
	Val.


%%-------------
%% UTILS
%%-------------

add_rosteritem(LU, LS, RU, RS, Nick, Group, Subscription, Xattrs) ->
	subscribe(LU, LS, RU, RS, Nick, Group, Subscription, Xattrs),
	% TODO: if the server is not local and Subs=to or both: send subscription request
	% TODO: check if the 'remote server' is a virtual host here, else do nothing
	%add_rosteritem2(RU, RS, LU, LS, LU, "", invert_subs(Subscription), Xattrs, Host).
	subscribe(RU, RS, LU, LS, LU, "", invert_subs(Subscription), Xattrs).

invert_subs(none) -> none;
invert_subs(to) -> none;
invert_subs(from) -> to;
invert_subs(both) -> both.

subscribe(LocalUser, LocalServer, RemoteUser, RemoteServer, Nick, Group, Subscription, Xattrs) ->
	mnesia:transaction(
		fun() ->
			mnesia:write({
				roster,
				{LocalUser,LocalServer,{RemoteUser,RemoteServer,[]}}, % usj
				{LocalUser,LocalServer},                 % us
				{RemoteUser,RemoteServer,[]},      % jid
				Nick,                  % name: "Mom", []
				Subscription,  % subscription: none, to=you see him, from=he sees you, both
				none,          % ask: out=send request, in=somebody requests you, none
				[Group],       % groups: ["Family"]
				Xattrs,        % xattrs: [{"category","conference"}]
				[]             % xs: []
		})
	end).

pushroster(File, User, Server) ->
	{ok, [Roster]} = file:consult(File),
	subscribe_roster({User, Server, "", User}, Roster).

pushroster_all(File) ->
	{ok, [Roster]} = file:consult(File),
	subscribe_all(Roster).

subscribe_all(Roster) ->
	subscribe_all(Roster, Roster).
subscribe_all([], _) ->
	ok;
subscribe_all([User1 | Users], Roster) ->
	subscribe_roster(User1, Roster),
	subscribe_all(Users, Roster).

subscribe_roster(_, []) ->
	ok;
% Do not subscribe a user to itself
subscribe_roster({Name, Server, Group, Nick}, [{Name, Server, _, _} | Roster]) ->
	subscribe_roster({Name, Server, Group, Nick}, Roster);
% Subscribe Name2 to Name1
subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick2} | Roster]) ->
	subscribe(Name1, Server1, Name2, Server2, Nick2, Group2, both, []),
	subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).

push_alltoall(S, G) ->
	Users = ejabberd_auth:get_vh_registered_users(S),
	Users2 = build_list_users(G, Users, []),
	subscribe_all(Users2).

build_list_users(_Group, [], Res) ->
	Res;
build_list_users(Group, [{User, Server}|Users], Res) ->
	build_list_users(Group, Users, [{User, Server, Group, User}|Res]).

vcard_get(User, Server, DataX) ->
    [{_, _, A1}] = mnesia:dirty_read(vcard, {User, Server}),
    Elem = vcard_get(DataX, A1),
    {ok, xml:get_tag_cdata(Elem)}.

vcard_get({Data1, Data2}, A1) ->
    A2 = xml:get_subtag(A1, Data1),
    A3 = xml:get_subtag(A2, Data2),
    case A3 of
		"" -> A2;
		_ -> A3
	end;

vcard_get({Data}, A1) ->
    xml:get_subtag(A1, Data).

vcard_set(User, Server, Data1, Data2, Content) ->
	Content2 = {xmlelement, Data2, [], [{xmlcdata,Content}]},
	R = {xmlelement, Data1, [], [Content2]},
	vcard_set2(User, Server, R, Data1).

vcard_set(User, Server, Data, Content) ->
	R = {xmlelement, Data, [], [{xmlcdata,Content}]},
	vcard_set2(User, Server, R, Data).

vcard_set2(User, Server, R, Data) ->
	% Get old vcard
    [{_, _, A1}] = mnesia:dirty_read(vcard, {User, Server}),
	{_, _, _, A2} = A1,

    A3 = lists:keydelete(Data, 2, A2),
    A4 = [R | A3],

	% Build new vcard
	SubEl = {xmlelement, "vCard", [{"xmlns","vcard-temp"}], A4},
	IQ = #iq{type=set, sub_el = SubEl},
	JID = jlib:make_jid(User, Server, ""),

	mod_vcard:process_sm_iq(JID, JID, IQ),
	{ok, "done"}.


-record(last_activity, {us, timestamp, status}).

delete_older_users(Days) ->
	% Convert older time
	SecOlder = Days*24*60*60,

	% Get current time
	{MegaSecs, Secs, _MicroSecs} = now(),
	TimeStamp_now = MegaSecs * 1000000 + Secs,

	% Get the list of registered users
	Users = ejabberd_auth:dirty_get_registered_users(),

	% For a user, remove if required and answer true
	F = fun({LUser, LServer}) ->
		% Check if the user is logged
		case ejabberd_sm:get_user_resources(LUser, LServer) of
			% If it isn't
			[] ->
				% Look for his last_activity
				case mnesia:dirty_read(last_activity, {LUser, LServer}) of
				% If it is
					% existent:
					[#last_activity{timestamp = TimeStamp}] ->
						% get his age
						Sec = TimeStamp_now - TimeStamp,
						% If he is
						if 
							% younger than SecOlder: 
							Sec < SecOlder ->
								% do nothing
								false;
							% older: 
							true ->
								% remove the user
								ejabberd_auth:remove_user(LUser, LServer),
								true
						end;
					% nonexistent:
					[] ->
						% remove the user
						ejabberd_auth:remove_user(LUser, LServer),
						true
				end;
			% Else
			_ ->
				% do nothing
				false
		end
	end,
	% Apply the function to every user in the list
	Users_removed = lists:filter(F, Users),
	{removed, length(Users_removed), Users_removed}.

num_active_users(Host, Days) ->
	list_last_activity(Host, true, Days).

% Code based on ejabberd/src/web/ejabberd_web_admin.erl
list_last_activity(Host, Integral, Days) ->
    {MegaSecs, Secs, _MicroSecs} = now(),
    TimeStamp = MegaSecs * 1000000 + Secs,
    TS = TimeStamp - Days * 86400,
    case catch mnesia:dirty_select(
		 last_activity, [{{last_activity, {'_', Host}, '$1', '_'},
				  [{'>', '$1', TS}],
				  [{'trunc', {'/',
					      {'-', TimeStamp, '$1'},
					      86400}}]}]) of
	{'EXIT', _Reason} ->
	    [];
	Vals ->
	    Hist = histogram(Vals, Integral),
	    if
		Hist == [] ->
		    0;
		true ->
		    Left = if
			       Days == infinity ->
				   0;
			       true ->
				   Days - length(Hist)
			   end,
		    Tail = if
			       Integral ->
				   lists:duplicate(Left, lists:last(Hist));
			       true ->
				   lists:duplicate(Left, 0)
			   end,
		    lists:nth(Days, Hist ++ Tail)
	    end
    end.
histogram(Values, Integral) ->
    histogram(lists:sort(Values), Integral, 0, 0, []).
histogram([H | T], Integral, Current, Count, Hist) when Current == H ->
    histogram(T, Integral, Current, Count + 1, Hist);
histogram([H | _] = Values, Integral, Current, Count, Hist) when Current < H ->
    if
	Integral ->
	    histogram(Values, Integral, Current + 1, Count, [Count | Hist]);
	true ->
	    histogram(Values, Integral, Current + 1, 0, [Count | Hist])
    end;
histogram([], _Integral, _Current, Count, Hist) ->
    if
	Count > 0 ->
	    lists:reverse([Count | Hist]);
	true ->
	    lists:reverse(Hist)
    end.


--- NEW FILE mod_shared_roster_ad.erl ---
%%%----------------------------------------------------------------------
%%% File    : mod_shared_roster.erl
%%% Author  : Alexey Shchepin <alexey at sevcom.net>
%%% Author  : Stanislav Bogatyrev <realloc at realloc.spb.ru>
%%% Purpose : Shared roster management
%%% Created :  5 Mar 2005 by Alexey Shchepin <alexey at sevcom.net>
%%% Id      : $Id: mod_shared_roster.erl 24 2005-04-14 01:15:31Z alexey $
%%%----------------------------------------------------------------------

-module(mod_shared_roster_ad).
-author('alexey at sevcom.net').
-author('realloc at realloc.spb.ru').
-vsn('$Revision: 24 $ ').

-behaviour(gen_mod).

-export([start/2, stop/1,
	 get_user_roster/2,
	 get_subscription_lists/3,
	 get_jid_info/4,
	 in_subscription/5,
	 out_subscription/4,
	 list_groups/1,
	 create_group/2,
	 create_group/3,
	 delete_group/2,
	 get_group_opts/2,
	 set_group_opts/3,
	 get_group_users/2,
	 get_group_explicit_users/2,
	 add_user_to_group/3,
	 remove_user_from_group/3]).

-include("ejabberd.hrl").
-include("jlib.hrl").
-include("mod_roster.hrl").
-include("eldap/eldap.hrl").

-record(sr_group, {group_host, opts}).
-record(sr_user, {us, group_host}).

start(Host, _Opts) ->
    mnesia:create_table(sr_group,
			[{disc_copies, [node()]},
			 {attributes, record_info(fields, sr_group)}]),
    mnesia:create_table(sr_user,
			[{disc_copies, [node()]},
			 {type, bag},
			 {attributes, record_info(fields, sr_user)}]),
    mnesia:add_table_index(sr_user, group_host),
    ejabberd_hooks:add(roster_get, Host,
		       ?MODULE, get_user_roster, 70),
    ejabberd_hooks:add(roster_in_subscription, Host,
        	       ?MODULE, in_subscription, 30),
    ejabberd_hooks:add(roster_out_subscription, Host,
        	       ?MODULE, out_subscription, 30),
    ejabberd_hooks:add(roster_get_subscription_lists, Host,
		       ?MODULE, get_subscription_lists, 70),
    ejabberd_hooks:add(roster_get_jid_info, Host,
        	       ?MODULE, get_jid_info, 70),
    
    %ejabberd_hooks:add(remove_user, Host,
    %    	       ?MODULE, remove_user, 50),
    LDAPServers = ejabberd_config:get_local_option({ad_servers, Host}),
    RootDN = ejabberd_config:get_local_option({ad_rootdn, Host}),
    Password = ejabberd_config:get_local_option({ad_password, Host}),
    eldap:start_link("mod_shared_roster_ad", LDAPServers, 389, RootDN, Password).
   

  
stop(Host) ->
    ejabberd_hooks:delete(roster_get, Host,
			  ?MODULE, get_user_roster, 70),
    ejabberd_hooks:delete(roster_in_subscription, Host,
        		  ?MODULE, in_subscription, 30),
    ejabberd_hooks:delete(roster_out_subscription, Host,
        		  ?MODULE, out_subscription, 30),
    ejabberd_hooks:delete(roster_get_subscription_lists, Host,
        		  ?MODULE, get_subscription_lists, 70),
    ejabberd_hooks:delete(roster_get_jid_info, Host,
        		  ?MODULE, get_jid_info, 70).
    %ejabberd_hooks:delete(remove_user, Host,
    %    		  ?MODULE, remove_user, 50),


get_user_roster(Items, US) ->
    {U, S} = US,
    DisplayedGroups = get_user_displayed_groups_ad(US),
    SRUsers = 
	lists:foldl(
	  fun(Group, Acc1) ->
		  lists:foldl(
		    fun(User, Acc2) ->
			    dict:append(User, Group, Acc2)
		    end, Acc1, get_group_users_ad(S, Group))
	  end, dict:new(), DisplayedGroups),
    {NewItems1, SRUsersRest} =
	lists:mapfoldl(
	  fun(Item, SRUsers1) ->
		  {_, _, {U1, S1, _}} = Item#roster.usj,
		  US1 = {U1, S1},
		  case dict:find(US1, SRUsers1) of
		      {ok, _GroupNames} ->
			  {Item#roster{subscription = both, ask = none},
			   dict:erase(US1, SRUsers1)};
		      error ->
			  {Item, SRUsers1}
		  end
	  end, SRUsers, Items),
    SRItems = [#roster{usj = {U, S, {U1, S1, ""}},
		       us = US,
		       jid = {U1, S1, ""},
		       name = get_user_fn(U1,S1),
		       subscription = both,
		       ask = none,
		       groups = GroupNames} ||
		  {{U1, S1}, GroupNames} <- dict:to_list(SRUsersRest)],
    SRItems ++ NewItems1.

get_subscription_lists({F, T}, User, Server) ->
    LUser = jlib:nodeprep(User),
    LServer = jlib:nameprep(Server),
    US = {LUser, LServer},
    DisplayedGroups = get_user_displayed_groups_ad(US),
    SRUsers =
	lists:usort(
	  lists:flatmap(
	    fun(Group) ->
		    get_group_users_ad(LServer, Group)
	    end, DisplayedGroups)),
    SRJIDs = [{U1, S1, ""} || {U1, S1} <- SRUsers],
    {lists:usort(SRJIDs ++ F), lists:usort(SRJIDs ++ T)}.

get_jid_info({Subscription, Groups}, User, Server, JID) ->
    LUser = jlib:nodeprep(User),
    LServer = jlib:nameprep(Server),
    US = {LUser, LServer},
    {U1, S1, _} = jlib:jid_tolower(JID),
    US1 = {U1, S1},
    DisplayedGroups = get_user_displayed_groups_ad(US),
    SRUsers = 
	lists:foldl(
	  fun(Group, Acc1) ->
		  lists:foldl(
		    fun(User1, Acc2) ->
			    dict:append(
			      User1, Group, Acc2)
		    end, Acc1, get_group_users_ad(LServer, Group))
	  end, dict:new(), DisplayedGroups),
    case dict:find(US1, SRUsers) of
	{ok, GroupNames} ->
	    NewGroups = if
			    Groups == [] -> GroupNames;
			    true -> Groups
			end,
	    {both, NewGroups};
	error ->
	    {Subscription, Groups}
    end.

in_subscription(Acc, User, Server, JID, Type) ->
    process_subscription(in, User, Server, JID, Type, Acc).

out_subscription(User, Server, JID, Type) ->
    process_subscription(out, User, Server, JID, Type, false).

process_subscription(Direction, User, Server, JID, _Type, Acc) ->
    LUser = jlib:nodeprep(User),
    LServer = jlib:nameprep(Server),
    US = {LUser, LServer},
    {U1, S1, _} = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
    US1 = {U1, S1},
    DisplayedGroups = get_user_displayed_groups_ad(US),
    SRUsers =
	lists:usort(
	  lists:flatmap(
	    fun(Group) ->
		    get_group_users_ad(LServer, Group)
	    end, DisplayedGroups)),
    case lists:member(US1, SRUsers) of
	true ->
	    case Direction of
		in ->
		    {stop, false};
		out ->
		    stop
	    end;
	false ->
	    Acc
    end.

list_groups(Host) ->
    get_user_displayed_groups_ad({"",Host}).


create_group(Host, Group) ->
    create_group(Host, Group, []).

create_group(Host, Group, Opts) ->
    R = #sr_group{group_host = {Group, Host}, opts = Opts},
    F = fun() ->
		mnesia:write(R)
	end,
    mnesia:transaction(F).

delete_group(Host, Group) ->
    F = fun() ->
		mnesia:delete({sr_group, {Group, Host}})
	end,
    mnesia:transaction(F).

get_group_opts(Host, Group) ->
    case catch mnesia:dirty_read(sr_group, {Group, Host}) of
	[#sr_group{opts = Opts}] ->
	    Opts;
	_ ->
	    error
    end.

set_group_opts(Host, Group, Opts) ->
    R = #sr_group{group_host = {Group, Host}, opts = Opts},
    F = fun() ->
		mnesia:write(R)
	end,
    mnesia:transaction(F).



get_user_groups(US) ->
    Host = element(2, US),
    case catch mnesia:dirty_read(sr_user, US) of
	Rs when is_list(Rs) ->
	    [Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
	_ ->
	    []
    end ++ get_all_users_groups(Host).

is_group_enabled(Host, Group) ->
    case catch mnesia:dirty_read(sr_group, {Group, Host}) of
	[#sr_group{opts = Opts}] ->
	    not lists:member(disabled, Opts);
	_ ->
	    false
    end.

get_group_opt(Host, Group, Opt, Default) ->
    case catch mnesia:dirty_read(sr_group, {Group, Host}) of
	[#sr_group{opts = Opts}] ->
	    case lists:keysearch(Opt, 1, Opts) of
		{value, {_, Val}} ->
		    Val;
		false ->
		    Default
	    end;
	_ ->
	    false
    end.

get_group_users(Host, Group) ->
    case get_group_opt(Host, Group, all_users, false) of
	true ->
	    ejabberd_auth:get_vh_registered_users(Host);
	false ->
	    []
    end ++ get_group_explicit_users(Host, Group).

get_group_explicit_users(Host, Group) ->
    case catch mnesia:dirty_index_read(
		 sr_user, {Group, Host}, #sr_user.group_host) of
	Rs when is_list(Rs) ->
	    [R#sr_user.us || R <- Rs];
	_ ->
	    []
    end.

get_group_name(Host, Group) ->
    get_group_opt(Host, Group, name, Group).

get_all_users_groups(Host) ->
    lists:filter(
      fun(Group) -> get_group_opt(Host, Group, all_users, false) end,
      list_groups(Host)).

get_user_displayed_groups(US) ->
    Host = element(2, US),
    DisplayedGroups1 =
	lists:usort(
	  lists:flatmap(
	    fun(Group) ->
		    case is_group_enabled(Host, Group) of
			true ->
			    get_group_opt(Host, Group, displayed_groups, []);
			false ->
			    []
		    end
	    end, get_user_groups(US))),
    [Group || Group <- DisplayedGroups1, is_group_enabled(Host, Group)].




add_user_to_group(Host, US, Group) ->
    R = #sr_user{us = US, group_host = {Group, Host}},
    F = fun() ->
		mnesia:write(R)
	end,
    mnesia:transaction(F).

remove_user_from_group(Host, US, Group) ->
    R = #sr_user{us = US, group_host = {Group, Host}},
    F = fun() ->
		mnesia:delete_object(R)
	end,
    mnesia:transaction(F).



find_user_attr(User, Host) ->
    Attr = ejabberd_config:get_local_option({ad_uidattr, Host}),
    Filter = eldap:equalityMatch(Attr, User),
    Base = ejabberd_config:get_local_option({ad_base, Host}),
    
    case eldap:search("mod_shared_roster_ad",
                      [{base, Base},
                       {filter, Filter},
                       {attributes, []}]) of
        #eldap_search_result{entries = [E | _]} ->
            E;
        _ ->
            false
    end.

get_user_displayed_groups_ad(US) ->
    {_, Host} = US,
    AdGroup = ejabberd_config:get_local_option({ad_group, Host}),
    FilterGroup = eldap:equalityMatch("memberOf", AdGroup),
    Base = ejabberd_config:get_local_option({ad_base, Host}),
    
    case eldap:search("mod_shared_roster_ad",
                      [{base, Base},
                       {filter, FilterGroup},
                       {attributes, []}]) of
        #eldap_search_result{entries = E} ->
	    lists:usort(lists:map(
	      fun(X) ->
		      case X of
			  #eldap_entry{attributes = Attributes} ->
			      ldap_get_value(Attributes,"department");
			  false ->
			      ""	    
		      end
		      end, E
	     ));
	    
        _ ->
            []
    end.

get_eldap_id(Host, Name) ->
    atom_to_list(gen_mod:get_module_proc(Host, Name)).


get_group_users_ad(Host, Group) ->
    Attr = ejabberd_config:get_local_option({ad_uidattr, Host}),
%    AdGroup = ejabberd_config:get_local_option({ad_group, Host}),
    FilterPerson = eldap:equalityMatch("objectCategory", "person"),
    FilterComp = eldap:equalityMatch("objectClass", "computer"),
    FilterHidden = eldap:equalityMatch("description", "hidden"),
%    FilterGroup = eldap:equalityMatch("memberOf", AdGroup),
    FilterDep = eldap:equalityMatch("department", Group),
    FilterLive = eldap:equalityMatch("userAccountControl", "66050"),
    FilterDef = eldap:present(Attr),
    Filter = eldap:'and'([
			  FilterDef,
			  FilterPerson,
%			  FilterGroup,
			  FilterDep,
			  eldap:'not'(FilterComp),
			  eldap:'not'(FilterHidden),
			  eldap:'not'(FilterLive)]),
    Base = ejabberd_config:get_local_option({ad_base, Host}),
    case eldap:search(get_eldap_id(Host, ejabberd),
		      [{base, Base},
		       {filter, Filter},
		       {attributes, [Attr]}]) of
	#eldap_search_result{entries = Es} ->
	    lists:flatmap(
	      fun(E) ->
		      case lists:keysearch(Attr, 1, E#eldap_entry.attributes) of
			  {value, {_, [U]}} ->
			      case jlib:nodeprep(U) of
				  error ->
				      [];
				  LU ->
				      [{LU, Host}]
			      end;
			  _ ->
			      []
		      end
	      end, Es);
	_ ->
	    []
    end.





ldap_get_value(E,Attribute) ->
    case lists:filter(fun({A,_}) ->
			      string:equal(A,Attribute)
		      end,E) of
	[{_,[Value|_]}] ->
	    Value;
	_ -> 
	    none
    end.

get_user_fn(User, Host) ->
    case find_user_attr(User,Host) of
	#eldap_entry{attributes = Attributes} ->
	    ldap_get_value(Attributes,"cn");
	
	false ->
	    ""	    
    end.




--- NEW FILE mod_vcard_ad.erl ---

%%%----------------------------------------------------------------------
%%% File    : mod_vcard_ad.erl
%%% Author  : Stanislav Bogatyrev <realloc at realloc.spb.ru>
%%% Author  : Alexey Shchepin <alexey at sevcom.net>
%%% Author  : Alex <agent_007> Gorbachenko (agent_007 at immo.ru)
%%% Purpose : 
%%% Created :  2 Jan 2003 by Alexey Shchepin <alexey at sevcom.net>
%%% Id      : $Id: mod_vcard_ad.erl 437 2005-11-19 01:20:05Z agent_007 $
%%%----------------------------------------------------------------------

-module(mod_vcard_ad).
-author('realloc at realloc.spb.ru').
-author('alexey at sevcom.net').
-author('agent_007 at immo.ru').
-vsn('$Revision: 437 $ ').

-behaviour(gen_mod).

-export([start/2, init/3, stop/1,
 	 get_sm_features/5,
	 process_local_iq/3,
	 process_sm_iq/3,
	 remove_user/1]).

-include("ejabberd.hrl").
-include("eldap/eldap.hrl").
-include("jlib.hrl").

-define(PROCNAME, ejabberd_mod_vcard_ad).

start(Host, Opts) ->
    IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
				  ?MODULE, process_local_iq, IQDisc),
    gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_VCARD,
				  ?MODULE, process_sm_iq, IQDisc),
    ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
    LDAPServers = ejabberd_config:get_local_option({ad_servers, Host}),
    RootDN = ejabberd_config:get_local_option({ad_rootdn, Host}),
    Password = ejabberd_config:get_local_option({ad_password, Host}),
    eldap:start_link("mod_vcard_ad", LDAPServers, 389, RootDN, Password),
    MyHost = gen_mod:get_opt(host, Opts, "vjud." ++ Host),
    Search = gen_mod:get_opt(search, Opts, true),
    register(gen_mod:get_module_proc(Host, ?PROCNAME),
	     spawn(?MODULE, init, [MyHost, Host, Search])).

init(Host, ServerHost, Search) ->
    case Search of
	false ->
	    loop(Host, ServerHost);
	_ ->
	    ejabberd_router:register_route(Host),
	    loop(Host, ServerHost)
    end.

loop(Host, ServerHost) ->
    receive
	{route, From, To, Packet} ->
	    case catch do_route(ServerHost, From, To, Packet) of
		{'EXIT', Reason} ->
		    ?ERROR_MSG("~p", [Reason]);
		_ ->
		    ok
	    end,
	    loop(Host, ServerHost);
	stop ->
	    ejabberd_router:unregister_route(Host),
	    ok;
	_ ->
	    loop(Host, ServerHost)
    end.

stop(Host) ->
    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
    gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_VCARD),
    ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
    Proc ! stop,
    {wait, Proc}.

get_sm_features({error, _Error} = Acc, _From, _To, _Node, _Lang) ->
    Acc;
get_sm_features(Acc, _From, _To, Node, _Lang) ->
    case Node of
 	[] ->
 	    case Acc of
 		{result, Features} ->
 		    {result, [?NS_VCARD | Features]};
 		empty ->
 		    {result, [?NS_VCARD]}
 	    end;
  	_ ->
 	    Acc
    end.

process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ) ->
    case Type of
	set ->
	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
	get ->
	    IQ#iq{type = result,
		  sub_el = [{xmlelement, "vCard",
			     [{"xmlns", ?NS_VCARD}],
			     [{xmlelement, "FN", [],
			       [{xmlcdata, "ejabberd"}]},
			      {xmlelement, "URL", [],
			       [{xmlcdata,
				 "http://ejabberd.jabberstudio.org/"}]},
			      {xmlelement, "DESC", [],
			       [{xmlcdata,
				 translate:translate(
				   Lang,
				   "Erlang Jabber Server\n"
				   "Copyright (c) 2002-2005 Alexey Shchepin")}]},
			      {xmlelement, "BDAY", [],
			       [{xmlcdata, "2002-11-16"}]}
			     ]}]}
    end.

find_ldap_user(Host, User) ->
    Attr = ejabberd_config:get_local_option({ad_uidattr, Host}),
    Filter = eldap:equalityMatch(Attr, User),
    Base = ejabberd_config:get_local_option({ad_base, Host}),
    case eldap:search("mod_vcard_ad", [{base, Base},
					 {filter, Filter},
					 {attributes, []}]) of
	#eldap_search_result{entries = [E | _]} ->
	    E;
	_ ->
	    false
    end.

is_attribute_read_allowed(Name,From,To) ->
    true.

ldap_attribute_to_vcard(Prefix,{Name,Values},From,To) ->
    case is_attribute_read_allowed(Name,From,To) of 
	true ->
	    ldap_lca_to_vcard(Prefix,stringprep:tolower(Name),Values);
	_ ->
	    none
    end.

ldap_lca_to_vcard(vCard,"displayname",[Value|_]) ->
    {xmlelement,"FN",[],[{xmlcdata,Value}]};

ldap_lca_to_vcard(vCard,"cn",[Value|_]) ->
    {xmlelement,"NICKNAME",[],[{xmlcdata,Value}]};

ldap_lca_to_vcard(vCard,"title",[Value|_]) ->
    {xmlelement,"TITLE",[],[{xmlcdata,Value}]};

ldap_lca_to_vcard(vCard,"wwwhomepage",[Value|_]) ->
    {xmlelement,"URL",[],[{xmlcdata,Value}]};

ldap_lca_to_vcard(vCard,"description",[Value|_]) ->
    {xmlelement,"DESC",[],[{xmlcdata,Value}]};

ldap_lca_to_vcard(vCard,"telephonenumber",[Value|_]) ->
    {xmlelement,"TEL",[],[{xmlelement,"VOICE",[],[]},
			  {xmlelement,"WORK",[],[]},
			  {xmlelement,"NUMBER",[],[{xmlcdata,Value}]}]};

ldap_lca_to_vcard(vCard,"mail",[Value|_]) ->
    {xmlelement,"EMAIL",[],[{xmlelement,"INTERNET",[],[]},
			    {xmlelement,"PREF",[],[]},
			    {xmlelement,"USERID",[],[{xmlcdata,Value}]}]};

ldap_lca_to_vcard(vCardN,"sn",[Value|_]) ->
    {xmlelement,"FAMILY",[],[{xmlcdata,Value}]};

ldap_lca_to_vcard(vCardN,"givenname",[Value|_]) ->
    {xmlelement,"GIVEN",[],[{xmlcdata,Value}]};

ldap_lca_to_vcard(vCardN,"initials",[Value|_]) ->
    {xmlelement,"MIDDLE",[],[{xmlcdata,Value}]};

ldap_lca_to_vcard(vCardAdr,"streetaddress",[Value|_]) ->
    {xmlelement,"STREET",[],[{xmlcdata,Value}]};
ldap_lca_to_vcard(vCardAdr,"co",[Value|_]) ->
    {xmlelement,"CTRY",[],[{xmlcdata,Value}]};
ldap_lca_to_vcard(vCardAdr,"l",[Value|_]) ->
    {xmlelement,"LOCALITY",[],[{xmlcdata,Value}]};
ldap_lca_to_vcard(vCardAdr,"st",[Value|_]) ->
    {xmlelement,"REGION",[],[{xmlcdata,Value}]};
ldap_lca_to_vcard(vCardAdr,"postalcode",[Value|_]) ->
    {xmlelement,"PCODE",[],[{xmlcdata,Value}]};
ldap_lca_to_vcard(vCardO,"company",[Value|_]) ->
    {xmlelement,"ORGNAME",[],[{xmlcdata,Value}]};

ldap_lca_to_vcard(vCardO,"department",[Value|_]) ->
    {xmlelement,"ORGUNIT",[],[{xmlcdata,Value}]};

ldap_lca_to_vcard(_,_,_) -> none.

ldap_attributes_to_vcard(Attributes,From,To) ->
    Elts = lists:map(fun(Attr) ->
			     ldap_attribute_to_vcard(vCard,Attr,From,To)
		     end,Attributes),
    FElts = [ X || X <- Elts, X /= none ],
    NElts = lists:map(fun(Attr) ->
			      ldap_attribute_to_vcard(vCardN,Attr,From,To)
		      end,Attributes),
    FNElts = [ X || X <- NElts, X /= none ],
   
    ADRElts = lists:map(fun(Attr) ->
			      ldap_attribute_to_vcard(vCardAdr,Attr,From,To)
		      end,Attributes),
    FADRElts = [ X || X <- ADRElts, X /= none ],
    
    OElts = lists:map(fun(Attr) ->
			      ldap_attribute_to_vcard(vCardO,Attr,From,To)
		      end,Attributes),
    FOElts = [ X || X <- OElts, X /= none ],
    [{xmlelement, "vCard", [{"xmlns", ?NS_VCARD}],
      lists:append(FElts,
		   [{xmlelement,"N",[],FNElts},
		    {xmlelement,"ADR",[],FADRElts},
		    {xmlelement,"ORG",[],FOElts}])
     }].

process_sm_iq(From, To, #iq{type = Type, sub_el = SubEl} = IQ) ->
    case Type of
	set ->
	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
	get ->
	    #jid{luser = LUser, lserver = LServer} = To,
	    case find_ldap_user(LServer, LUser) of
		#eldap_entry{attributes = Attributes} ->
		    Vcard = ldap_attributes_to_vcard(Attributes,From,To),
		    IQ#iq{type = result, sub_el = Vcard};
		_ ->
		    IQ#iq{type = result, sub_el = []}
	    end
	end.

-define(TLFIELD(Type, Label, Var),
	{xmlelement, "field", [{"type", Type},
			       {"label", translate:translate(Lang, Label)},
			       {"var", Var}], []}).


-define(FORM(JID),
	[{xmlelement, "instructions", [],
	  [{xmlcdata, translate:translate(Lang, "You need an x:data capable client to search")}]},
	 {xmlelement, "x", [{"xmlns", ?NS_XDATA}, {"type", "form"}],
	  [{xmlelement, "title", [],
	    [{xmlcdata, translate:translate(Lang, "Search users in ") ++
	      jlib:jid_to_string(JID)}]},
	   {xmlelement, "instructions", [],
	    [{xmlcdata, translate:translate(Lang, "Fill in fields to search "
					    "for any matching Jabber User")}]},
	   ?TLFIELD("text-single", "User", "user"),
	   ?TLFIELD("text-single", "Full Name", "fn"),
	   ?TLFIELD("text-single", "Given Name", "given"),
	   ?TLFIELD("text-single", "Middle Name", "middle"),
	   ?TLFIELD("text-single", "Family Name", "family"),
	   ?TLFIELD("text-single", "Nickname", "nickname"),
	   ?TLFIELD("text-single", "Birthday", "bday"),
	   ?TLFIELD("text-single", "Country", "ctry"),
	   ?TLFIELD("text-single", "City", "locality"),
	   ?TLFIELD("text-single", "email", "email"),
	   ?TLFIELD("text-single", "Organization Name", "orgname"),
	   ?TLFIELD("text-single", "Organization Unit", "orgunit")
	  ]}]).




do_route(ServerHost, From, To, Packet) ->
    #jid{user = User, resource = Resource} = To,
    if
	(User /= "") or (Resource /= "") ->
	    Err = jlib:make_error_reply(Packet, ?ERR_SERVICE_UNAVAILABLE),
	    ejabberd_router ! {route, To, From, Err};
	true ->
	    IQ = jlib:iq_query_info(Packet),
	    case IQ of
		#iq{type = Type, xmlns = ?NS_SEARCH, lang = Lang, sub_el = SubEl} ->
		    case Type of
			set ->
			    XDataEl = find_xdata_el(SubEl),
			    case XDataEl of
				false ->
				    Err = jlib:make_error_reply(
					    Packet, ?ERR_BAD_REQUEST),
				    ejabberd_router:route(To, From, Err);
				_ ->
				    XData = jlib:parse_xdata_submit(XDataEl),
				    case XData of
					invalid ->
					    Err = jlib:make_error_reply(
						    Packet,
						    ?ERR_BAD_REQUEST),
					    ejabberd_router:route(To, From,
								  Err);
					_ ->
					    ResIQ =
						IQ#iq{
						  type = result,
						  sub_el =
						  [{xmlelement,
						    "query",
						    [{"xmlns", ?NS_SEARCH}],
						    [{xmlelement, "x",
						      [{"xmlns", ?NS_XDATA},
						       {"type", "result"}],
						      search_result(Lang, To, ServerHost, XData)
						     }]}]},
					    ejabberd_router:route(
					      To, From, jlib:iq_to_xml(ResIQ))
				    end
			    end;
			get ->
			    ResIQ = IQ#iq{type = result,
					  sub_el = [{xmlelement,
						     "query",
						     [{"xmlns", ?NS_SEARCH}],
						     ?FORM(To)
						    }]},
			    ejabberd_router:route(To,
						  From,
						  jlib:iq_to_xml(ResIQ))
		    end;
		#iq{type = Type, xmlns = ?NS_DISCO_INFO, sub_el = SubEl} ->
		    case Type of
			set ->
			    Err = jlib:make_error_reply(
				    Packet, ?ERR_NOT_ALLOWED),
			    ejabberd_router:route(To, From, Err);
			get ->
			    ResIQ =
				IQ#iq{type = result,
				      sub_el = [{xmlelement,
						 "query",
						 [{"xmlns", ?NS_DISCO_INFO}],
						 [{xmlelement, "identity",
						   [{"category", "directory"},
						    {"type", "user"},
						    {"name",
						     "vCard User Search"}],
						   []},
						  {xmlelement, "feature",
						   [{"var", ?NS_SEARCH}], []},
						  {xmlelement, "feature",
						   [{"var", ?NS_VCARD}], []}
						 ]
						}]},
			    ejabberd_router:route(To,
						  From,
						  jlib:iq_to_xml(ResIQ))
		    end;
		#iq{type = Type, xmlns = ?NS_DISCO_ITEMS, sub_el = SubEl} ->
		    case Type of
			set ->
			    Err = jlib:make_error_reply(
				    Packet, ?ERR_NOT_ALLOWED),
			    ejabberd_router:route(To, From, Err);
			get ->
			    ResIQ = 
				IQ#iq{type = result,
				      sub_el = [{xmlelement,
						 "query",
						 [{"xmlns", ?NS_DISCO_ITEMS}],
						 []}]},
			    ejabberd_router:route(To,
						  From,
						  jlib:iq_to_xml(ResIQ))
		    end;
		#iq{type = get, xmlns = ?NS_VCARD, lang = Lang} ->
		    ResIQ = 
			IQ#iq{type = result,
			      sub_el = [{xmlelement,
					 "vCard",
					 [{"xmlns", ?NS_VCARD}],
					 iq_get_vcard(Lang)}]},
		    ejabberd_router:route(To,
					  From,
					  jlib:iq_to_xml(ResIQ));
		_ ->
		    Err = jlib:make_error_reply(Packet,
						?ERR_SERVICE_UNAVAILABLE),
		    ejabberd_router:route(To, From, Err)
	    end
    end.

iq_get_vcard(Lang) ->
    [{xmlelement, "FN", [],
      [{xmlcdata, "ejabberd/mod_vcard"}]},
     {xmlelement, "URL", [],
      [{xmlcdata,
        "http://ejabberd.jabberstudio.org/"}]},
     {xmlelement, "DESC", [],
      [{xmlcdata, translate:translate(
		    Lang,
		    "ejabberd vCard module\n"
		    "Copyright (c) 2003-2005 Alexey Shchepin")}]}].

find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
    find_xdata_el1(SubEls).

find_xdata_el1([]) ->
    false;
find_xdata_el1([{xmlelement, Name, Attrs, SubEls} | Els]) ->
    case xml:get_attr_s("xmlns", Attrs) of
	?NS_XDATA ->
	    {xmlelement, Name, Attrs, SubEls};
	_ ->
	    find_xdata_el1(Els)
    end;
find_xdata_el1([_ | Els]) ->
    find_xdata_el1(Els).

-define(LFIELD(Label, Var),
	{xmlelement, "field", [{"label", translate:translate(Lang, Label)},
			       {"var", Var}], []}).

search_result(Lang, JID, ServerHost, Data) ->
    [{xmlelement, "title", [],
      [{xmlcdata, translate:translate(Lang, "Results of search in ") ++
	jlib:jid_to_string(JID)}]},
     {xmlelement, "reported", [],
      [?LFIELD("JID", "jid"),
       ?LFIELD("Full Name", "fn"),
       ?LFIELD("Given Name", "given"),
       ?LFIELD("Middle Name", "middle"),
       ?LFIELD("Family Name", "family"),
       ?LFIELD("Nickname", "nickname"),
       ?LFIELD("Birthday", "bday"),
       ?LFIELD("Country", "ctry"),
       ?LFIELD("City", "locality"),
       ?LFIELD("email", "email"),
       ?LFIELD("Organization Name", "orgname"),
       ?LFIELD("Organization Unit", "orgunit")
      ]}] ++ lists:map(fun(E) -> 
			       record_to_item(E#eldap_entry.attributes)
		       end, search(ServerHost, Data)).

-define(FIELD(Var, Val),
	{xmlelement, "field", [{"var", Var}],
	 [{xmlelement, "value", [],
	   [{xmlcdata, Val}]}]}).

case_exact_compare(none,_) ->
    false;
case_exact_compare(_,none) ->
    false;
case_exact_compare(X,Y) ->
    X > Y.

ldap_sort_entries(L) ->
    lists:sort(fun(E1,E2) ->
		       case_exact_compare(ldap_get_value(E1,"cn"),ldap_get_value(E2,"cn"))
	       end,L).

ldap_get_value(E,Attribute) ->
    #eldap_entry{attributes = Attributes} = E,
    case lists:filter(fun({A,_}) ->
			      string:equal(A,Attribute)
		      end,Attributes) of
	[{Attr,[Value|_]}] ->
	    Value;
	_ -> 
	    none
    end.

ldap_attribute_to_item("samaccountname",Value) ->
    [
     ?FIELD("jid",Value ++ "@" ++ ?MYNAME),
     ?FIELD("uid",Value)
    ];

ldap_attribute_to_item("cn",Value) ->
    [
     ?FIELD("nickname",Value)
    ];

ldap_attribute_to_item("displayname",Value) ->
    [
     ?FIELD("fn",Value)
    ];

ldap_attribute_to_item("sn",Value) ->
    [
     ?FIELD("family",Value)
    ];
ldap_attribute_to_item("co",Value) ->
    [
     ?FIELD("ctry",Value)
    ];
ldap_attribute_to_item("l",Value) ->
    [
     ?FIELD("locality",Value)
    ];

ldap_attribute_to_item("givenname",Value) ->
    [
     ?FIELD("given",Value)
    ];

ldap_attribute_to_item("initials",Value) ->
    [
     ?FIELD("middle",Value)
    ];

ldap_attribute_to_item("mail",Value) ->
    [
     ?FIELD("email",Value)
    ];

ldap_attribute_to_item("company",Value) ->
    [
     ?FIELD("orgname",Value)
    ];

ldap_attribute_to_item("department",Value) ->
    [
     ?FIELD("orgunit",Value)
    ];

ldap_attribute_to_item(_,_) ->
    [none].

record_to_item(Attributes) ->
    List = lists:append(lists:map(fun({Attr,[Value|_]}) -> 
					  ldap_attribute_to_item(stringprep:tolower(Attr),Value)
				  end,Attributes)),
    FList = [X || X <- List, X /= none],
    {xmlelement, "item", [],FList}.

search(LServer, Data) ->
%    AdGroup = ejabberd_config:get_local_option({ad_group, LServer}),
    FilterDef = make_filter(Data),
    FilterPerson =  eldap:equalityMatch("objectCategory", "person"),
    FilterComp = eldap:equalityMatch("objectClass", "computer"),
    FilterHidden = eldap:equalityMatch("description", "hidden"),
%    FilterGroup = eldap:equalityMatch("memberOf", AdGroup),
    FilterLive = eldap:equalityMatch("userAccountControl", "66050"),
    Filter = eldap:'and'([
			  FilterDef,
			  FilterPerson,
%			  FilterGroup,
			  eldap:'not'(FilterComp),
			  eldap:'not'(FilterHidden),
			  eldap:'not'(FilterLive)]),
    Base = ejabberd_config:get_local_option({ad_base, LServer}),
    UIDAttr = ejabberd_config:get_local_option({ad_uidattr, LServer}),
    case eldap:search("mod_vcard_ad",[{base, Base},
					{filter, Filter},
					{attributes, []}]) of
	#eldap_search_result{entries = E} ->
	    [X || X <- E,
		  ejabberd_auth:is_user_exists(
		    ldap_get_value(X, UIDAttr), LServer)];
	Err ->
	    ?ERROR_MSG("Bad search: ~p", [[LServer, {base, Base},
					{filter, Filter},
					{attributes, []}]])
    end.


make_filter(Data) ->
    Filter = [X || X <- lists:map(fun(R) -> 
					  make_assertion(R)
				  end, Data),
		   X /= none ],
    case Filter of
	[F] -> 
	    F;
	_ ->
	    eldap:'and'(Filter)
    end.


make_assertion("givenName",Value) ->
    eldap:substrings("givenName",[{any,Value}]);

make_assertion("cn",Value) ->
    eldap:substrings("cn",[{any,Value}]);

make_assertion("sn",Value) ->
    eldap:substrings("sn",[{any,Value}]);

make_assertion(Attr, Value) ->
    eldap:equalityMatch(Attr,Value).

make_assertion({SVar, [Val]}) ->
    LAttr = ldap_attribute(SVar),
    case LAttr of
	none ->
	    none;
	_ ->
	    if 
		is_list(Val) and (Val /= "") ->
		    make_assertion(LAttr,Val);
		true ->
		    none
	    end
    end.

ldap_attribute("user") ->
    "samaccountname";

ldap_attribute("fn") ->
    "cn";

ldap_attribute("family") ->
    "sn";

ldap_attribute("given") ->
    "givenName";

ldap_attribute("middle") ->
    "initials";

ldap_attribute("email") ->
    "mail";

ldap_attribute("orgname") ->
    "company";

ldap_attribute("orgunit") ->
    "department";

ldap_attribute(_) ->
    none.

remove_user(User) ->
    true.



Index: .cvsignore
===================================================================
RCS file: /cvs/extras/rpms/ejabberd/devel/.cvsignore,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- .cvsignore	23 Jun 2006 12:38:32 -0000	1.1
+++ .cvsignore	23 Jun 2006 12:40:03 -0000	1.2
@@ -0,0 +1 @@
+ejabberd-1.1.1.tar.gz


Index: sources
===================================================================
RCS file: /cvs/extras/rpms/ejabberd/devel/sources,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- sources	23 Jun 2006 12:38:32 -0000	1.1
+++ sources	23 Jun 2006 12:40:03 -0000	1.2
@@ -0,0 +1 @@
+ef6fae4a3f9c7f807f21e9cd3dae195b  ejabberd-1.1.1.tar.gz




More information about the fedora-extras-commits mailing list