25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-12-26 17:38:45 +01:00

* src/odbc/pg.sql: DB creation script for postgres

* src/odbc/ejabberd_odbc.erl: Experimental support for ODBC
* src/mod_last_odbc.erl: Likewise
* src/mod_offline_odbc.erl: Likewise
* src/ejabberd_auth_odbc.erl: Likewise
* src/ejabberd_auth.erl: Likewise

SVN Revision: 292
This commit is contained in:
Alexey Shchepin 2004-12-13 23:00:12 +00:00
parent 0e9f506a91
commit 8c8e3469bc
7 changed files with 811 additions and 0 deletions

View File

@ -1,3 +1,13 @@
2004-12-13 Alexey Shchepin <alexey@sevcom.net>
* src/odbc/pg.sql: DB creation script for postgres
* src/odbc/ejabberd_odbc.erl: Experimental support for ODBC
* src/mod_last_odbc.erl: Likewise
* src/mod_offline_odbc.erl: Likewise
* src/ejabberd_auth_odbc.erl: Likewise
* src/ejabberd_auth.erl: Likewise
2004-12-12 Alexey Shchepin <alexey@sevcom.net>
* src/mod_stats.erl: Minor optimizations

View File

@ -74,6 +74,8 @@ auth_module() ->
ejabberd_auth_external;
ldap ->
ejabberd_auth_ldap;
odbc ->
ejabberd_auth_odbc;
_ ->
ejabberd_auth_internal
end.

210
src/ejabberd_auth_odbc.erl Normal file
View File

@ -0,0 +1,210 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_auth_odbc.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : Authentification via ODBC
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(ejabberd_auth_odbc).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
%% External exports
-export([start/0,
set_password/2,
check_password/2,
check_password/4,
try_register/2,
dirty_get_registered_users/0,
get_password/1,
get_password_s/1,
is_user_exists/1,
remove_user/1,
remove_user/2,
plain_password_required/0
]).
-record(passwd, {user, password}).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start() ->
ok.
plain_password_required() ->
false.
check_password(User, Password) ->
case jlib:nodeprep(User) of
error ->
false;
LUser ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
["select password from users "
"where username='", Username, "'"]) of
{selected, ["password"], [{Password}]} ->
true;
_ ->
false
end
end.
check_password(User, Password, StreamID, Digest) ->
case jlib:nodeprep(User) of
error ->
false;
LUser ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
["select password from users "
"where username='", Username, "'"]) of
{selected, ["password"], [{Passwd}]} ->
DigRes = if
Digest /= "" ->
Digest == sha:sha(StreamID ++ Passwd);
true ->
false
end,
if DigRes ->
true;
true ->
(Passwd == Password) and (Password /= "")
end;
_ ->
false
end
end.
set_password(User, Password) ->
case jlib:nodeprep(User) of
error ->
{error, invalid_jid};
LUser ->
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
catch ejabberd_odbc:sql_query(
["begin;"
"delete from users where username='", Username ,"';"
"insert into users(username, password) "
"values ('", Username, "', '", Pass, "'); commit"])
end.
try_register(User, Password) ->
case jlib:nodeprep(User) of
error ->
{error, invalid_jid};
LUser ->
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
case catch ejabberd_odbc:sql_query(
["insert into users(username, password) "
"values ('", Username, "', '", Pass, "')"]) of
{updated, _} ->
{atomic, ok};
_ ->
{atomic, exists}
end
end.
dirty_get_registered_users() ->
case catch ejabberd_odbc:sql_query("select username from users") of
{selected, ["username"], Res} ->
[U || {U} <- Res];
_ ->
[]
end.
get_password(User) ->
case jlib:nodeprep(User) of
error ->
false;
LUser ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
["select password from users "
"where username='", Username, "'"]) of
{selected, ["password"], [{Password}]} ->
Password;
_ ->
false
end
end.
get_password_s(User) ->
case jlib:nodeprep(User) of
error ->
"";
LUser ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
["select password from users "
"where username='", Username, "'"]) of
{selected, ["password"], [{Password}]} ->
Password;
_ ->
""
end
end.
is_user_exists(User) ->
case jlib:nodeprep(User) of
error ->
false;
LUser ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
["select password from users "
"where username='", Username, "'"]) of
{selected, ["password"], [{_Password}]} ->
true;
_ ->
false
end
end.
remove_user(User) ->
case jlib:nodeprep(User) of
error ->
error;
LUser ->
Username = ejabberd_odbc:escape(LUser),
catch ejabberd_odbc:sql_query(
["delete from users where username='", Username ,"'"]),
catch mod_roster:remove_user(User),
catch mod_offline:remove_user(User),
catch mod_last:remove_user(User),
catch mod_vcard:remove_user(User),
catch mod_private:remove_user(User)
end.
remove_user(User, Password) ->
case jlib:nodeprep(User) of
error ->
error;
LUser ->
Username = ejabberd_odbc:escape(LUser),
Pass = ejabberd_odbc:escape(Password),
case catch
ejabberd_odbc:sql_query(
["begin;"
"select password from users where username='", Username, "';"
"delete from users "
"where username='", Username, "' and password='", Pass, "';"
"commit"]) of
{selected, ["password"], [{Password}]} ->
catch mod_roster:remove_user(User),
catch mod_offline:remove_user(User),
catch mod_last:remove_user(User),
catch mod_vcard:remove_user(User),
catch mod_private:remove_user(User),
ok;
{selected, ["password"], []} ->
not_exists;
_ ->
not_allowed
end
end.

135
src/mod_last_odbc.erl Normal file
View File

@ -0,0 +1,135 @@
%%%----------------------------------------------------------------------
%%% File : mod_last_odbc.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : jabber:iq:last support (JEP-0012)
%%% Created : 24 Oct 2003 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(mod_last_odbc).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_mod).
-export([start/1,
stop/0,
process_local_iq/3,
process_sm_iq/3,
on_presence_update/3,
remove_user/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
start(Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_local, ?NS_LAST,
?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, ?NS_LAST,
?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(unset_presence_hook,
?MODULE, on_presence_update, 50).
stop() ->
gen_iq_handler:remove_iq_handler(ejabberd_local, ?NS_LAST),
gen_iq_handler:remove_iq_handler(ejabberd_sm, ?NS_LAST).
process_local_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 ->
Sec = trunc(element(1, erlang:statistics(wall_clock))/1000),
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_LAST},
{"seconds", integer_to_list(Sec)}],
[]}]}
end.
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 ->
User = To#jid.luser,
{Subscription, _Groups} =
mod_roster:get_jid_info(User, From),
if
(Subscription == both) or (Subscription == from) ->
case catch mod_privacy:get_user_list(User) of
{'EXIT', _Reason} ->
get_last(IQ, SubEl, User);
List ->
case catch mod_privacy:check_packet(
User, List,
{From, To,
{xmlelement, "presence", [], []}},
out) of
{'EXIT', _Reason} ->
get_last(IQ, SubEl, User);
allow ->
get_last(IQ, SubEl, User);
deny ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end
end;
true ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
end
end.
get_last(IQ, SubEl, LUser) ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
["select seconds, state from last "
"where username='", Username, "'"]) of
{'EXIT', _Reason} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
{selected, ["seconds","state"], []} ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
{selected, ["seconds","state"], [{STimeStamp, Status}]} ->
case catch list_to_integer(STimeStamp) of
TimeStamp when is_integer(TimeStamp) ->
{MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp2 = MegaSecs * 1000000 + Secs,
Sec = TimeStamp2 - TimeStamp,
IQ#iq{type = result,
sub_el = [{xmlelement, "query",
[{"xmlns", ?NS_LAST},
{"seconds", integer_to_list(Sec)}],
[{xmlcdata, Status}]}]};
_ ->
IQ#iq{type = error,
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end
end.
on_presence_update(User, _Resource, Status) ->
LUser = jlib:nodeprep(User),
{MegaSecs, Secs, _MicroSecs} = now(),
TimeStamp = MegaSecs * 1000000 + Secs,
Username = ejabberd_odbc:escape(LUser),
Seconds = ejabberd_odbc:escape(integer_to_list(TimeStamp)),
State = ejabberd_odbc:escape(Status),
ejabberd_odbc:sql_query(
["begin;"
"delete from last where username='", Username, "';"
"insert into last(username, seconds, state) "
"values ('", Username, "', '", Seconds, "', '", State, "');",
"commit"]).
remove_user(User) ->
LUser = jlib:nodeprep(User),
Username = ejabberd_odbc:escape(LUser),
ejabberd_odbc:sql_query(
["delete from last where username='", Username, "'"]).

242
src/mod_offline_odbc.erl Normal file
View File

@ -0,0 +1,242 @@
%%%----------------------------------------------------------------------
%%% File : mod_offline_odbc.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose :
%%% Created : 5 Jan 2003 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(mod_offline_odbc).
-author('alexey@sevcom.net').
-behaviour(gen_mod).
-export([start/1,
init/0,
stop/0,
store_packet/3,
pop_offline_messages/2,
remove_user/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-record(offline_msg, {user, timestamp, expire, from, to, packet}).
-define(PROCNAME, ejabberd_offline).
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
start(_) ->
% TODO: remove
ejabberd_odbc:start(),
ejabberd_hooks:add(offline_message_hook,
?MODULE, store_packet, 50),
ejabberd_hooks:add(offline_subscription_hook,
?MODULE, store_packet, 50),
ejabberd_hooks:add(resend_offline_messages_hook,
?MODULE, pop_offline_messages, 50),
register(?PROCNAME, spawn(?MODULE, init, [])).
init() ->
loop().
loop() ->
receive
#offline_msg{} = Msg ->
Msgs = receive_all([Msg]),
% TODO
Query = lists:map(
fun(M) ->
Username =
ejabberd_odbc:escape(
(M#offline_msg.to)#jid.luser),
From = M#offline_msg.from,
To = M#offline_msg.to,
{xmlelement, Name, Attrs, Els} =
M#offline_msg.packet,
Attrs2 = jlib:replace_from_to_attrs(
jlib:jid_to_string(From),
jlib:jid_to_string(To),
Attrs),
Packet = {xmlelement, Name, Attrs2,
Els ++
[jlib:timestamp_to_xml(
calendar:now_to_universal_time(
M#offline_msg.timestamp))]},
XML =
ejabberd_odbc:escape(
lists:flatten(
xml:element_to_string(Packet))),
["insert into spool(username, xml) "
"values ('", Username, "', '",
XML,
"');"]
end, Msgs),
case catch ejabberd_odbc:sql_query(
["begin; ", Query, " commit"]) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~n", [Reason]);
_ ->
ok
end,
loop();
_ ->
loop()
end.
receive_all(Msgs) ->
receive
#offline_msg{} = Msg ->
receive_all([Msg | Msgs])
after 0 ->
Msgs
end.
stop() ->
ejabberd_hooks:delete(offline_message_hook,
?MODULE, store_packet, 50),
ejabberd_hooks:delete(offline_subscription_hook,
?MODULE, store_packet, 50),
ejabberd_hooks:delete(resend_offline_messages_hook,
?MODULE, pop_offline_messages, 50),
exit(whereis(?PROCNAME), stop),
ok.
store_packet(From, To, Packet) ->
Type = xml:get_tag_attr_s("type", Packet),
if
(Type /= "error") and (Type /= "groupchat") ->
case check_event(From, To, Packet) of
true ->
#jid{luser = LUser} = To,
TimeStamp = now(),
{xmlelement, _Name, _Attrs, Els} = Packet,
Expire = find_x_expire(TimeStamp, Els),
?PROCNAME ! #offline_msg{user = LUser,
timestamp = TimeStamp,
expire = Expire,
from = From,
to = To,
packet = Packet},
stop;
_ ->
ok
end;
true ->
ok
end.
check_event(From, To, Packet) ->
{xmlelement, Name, Attrs, Els} = Packet,
case find_x_event(Els) of
false ->
true;
El ->
case xml:get_subtag(El, "id") of
false ->
case xml:get_subtag(El, "offline") of
false ->
true;
_ ->
ID = case xml:get_tag_attr_s("id", Packet) of
"" ->
{xmlelement, "id", [], []};
S ->
{xmlelement, "id", [],
[{xmlcdata, S}]}
end,
ejabberd_router:route(
To, From, {xmlelement, Name, Attrs,
[{xmlelement, "x",
[{"xmlns", ?NS_EVENT}],
[ID,
{xmlelement, "offline", [], []}]}]
}),
true
end;
_ ->
false
end
end.
find_x_event([]) ->
false;
find_x_event([{xmlcdata, _} | Els]) ->
find_x_event(Els);
find_x_event([El | Els]) ->
case xml:get_tag_attr_s("xmlns", El) of
?NS_EVENT ->
El;
_ ->
find_x_event(Els)
end.
find_x_expire(_, []) ->
never;
find_x_expire(TimeStamp, [{xmlcdata, _} | Els]) ->
find_x_expire(TimeStamp, Els);
find_x_expire(TimeStamp, [El | Els]) ->
case xml:get_tag_attr_s("xmlns", El) of
?NS_EXPIRE ->
case xml:get_tag_attr_s("seconds", El) of
Val ->
case catch list_to_integer(Val) of
{'EXIT', _} ->
never;
Int when Int > 0 ->
{MegaSecs, Secs, MicroSecs} = TimeStamp,
S = MegaSecs * 1000000 + Secs + Int,
MegaSecs1 = S div 1000000,
Secs1 = S rem 1000000,
{MegaSecs1, Secs1, MicroSecs};
_ ->
never
end;
_ ->
never
end;
_ ->
find_x_expire(TimeStamp, Els)
end.
pop_offline_messages(Ls, User) ->
LUser = jlib:nodeprep(User),
EUser = ejabberd_odbc:escape(LUser),
case ejabberd_odbc:sql_query(
["begin;"
"select * from spool where username='", EUser, "';"
"delete from spool where username='", EUser, "';"
"commit"]) of
{selected, ["username","xml"], Rs} ->
Ls ++ lists:flatmap(
fun({_, XML}) ->
case xml_stream:parse_element(XML) of
{error, _Reason} ->
[];
El ->
To = jlib:string_to_jid(
xml:get_tag_attr_s("to", El)),
From = jlib:string_to_jid(
xml:get_tag_attr_s("from", El)),
if
(To /= error) and
(From /= error) ->
[{route, From, To, El}];
true ->
[]
end
end
end, Rs);
_ ->
Ls
end.
remove_user(User) ->
LUser = jlib:nodeprep(User),
Username = ejabberd_odbc:escape(LUser),
ejabberd_odbc:sql_query(
["delete from spool where username='", Username, "'"]).

123
src/odbc/ejabberd_odbc.erl Normal file
View File

@ -0,0 +1,123 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_odbc.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : Serve ODBC connection
%%% Created : 8 Dec 2004 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(ejabberd_odbc).
-author('alexey@sevcom.net').
-vsn('$Revision$ ').
-behaviour(gen_server).
%% External exports
-export([start/0, start_link/0,
sql_query/1,
escape/1]).
%% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
code_change/3,
handle_info/2,
terminate/2]).
-record(state, {odbc_ref}).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start() ->
gen_server:start({local, ejabberd_odbc}, ejabberd_odbc, [], []).
start_link() ->
gen_server:start_link({local, ejabberd_odbc}, ejabberd_odbc, [], []).
sql_query(Query) ->
gen_server:call(ejabberd_odbc, {sql_query, Query}, 60000).
escape(S) ->
[case C of
$\0 -> "\\0";
$\n -> "\\n";
$\t -> "\\t";
$\b -> "\\b";
$\r -> "\\r";
$' -> "\\'";
$" -> "\\\"";
$% -> "\\%";
$_ -> "\\_";
$\\ -> "\\\\";
_ -> C
end || C <- S].
%%%----------------------------------------------------------------------
%%% Callback functions from gen_server
%%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%%----------------------------------------------------------------------
init([]) ->
{ok, Ref} = odbc:connect("DSN=ejabberd;UID=ejabberd;PWD=ejabberd",
[{scrollable_cursors, off}]),
{ok, #state{odbc_ref = Ref}}.
%%----------------------------------------------------------------------
%% Func: handle_call/3
%% Returns: {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} | (terminate/2 is called)
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_call({sql_query, Query}, _From, State) ->
Reply = odbc:sql_query(State#state.odbc_ref, Query),
{reply, Reply, State};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
%%----------------------------------------------------------------------
%% Func: handle_cast/2
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%----------------------------------------------------------------------
%% Func: handle_info/2
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_info(_Info, State) ->
{noreply, State}.
%%----------------------------------------------------------------------
%% Func: terminate/2
%% Purpose: Shutdown the server
%% Returns: any (ignored by gen_server)
%%----------------------------------------------------------------------
terminate(_Reason, _State) ->
ok.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------

89
src/odbc/pg.sql Normal file
View File

@ -0,0 +1,89 @@
CREATE TABLE users (
username text NOT NULL,
"password" text NOT NULL
);
CREATE TABLE last (
username text NOT NULL,
seconds text NOT NULL,
state text
);
CREATE TABLE rosterusers (
username text NOT NULL,
jid text NOT NULL,
nick text,
subscription character(1) NOT NULL,
ask character(1) NOT NULL,
server character(1) NOT NULL,
subscribe text,
"type" text
);
CREATE TABLE rostergroups (
username text NOT NULL,
jid text NOT NULL,
grp text NOT NULL
);
CREATE TABLE spool (
username text NOT NULL,
xml text
);
CREATE TABLE vcard (
username text NOT NULL,
full_name text,
first_name text,
last_name text,
nick_name text,
url text,
address1 text,
address2 text,
locality text,
region text,
pcode text,
country text,
telephone text,
email text,
orgname text,
orgunit text,
title text,
role text,
b_day date,
descr text
);
CREATE INDEX i_users_login ON users USING btree (username, "password");
CREATE INDEX i_rosteru_user_jid ON rosterusers USING btree (username, jid);
CREATE INDEX i_rosteru_username ON rosterusers USING btree (username);
CREATE INDEX pk_rosterg_user_jid ON rostergroups USING btree (username, jid);
CREATE INDEX i_despool ON spool USING btree (username);
CREATE INDEX i_rosteru_jid ON rosterusers USING btree (jid);
ALTER TABLE ONLY users
ADD CONSTRAINT users_pkey PRIMARY KEY (username);
ALTER TABLE ONLY last
ADD CONSTRAINT last_pkey PRIMARY KEY (username);
ALTER TABLE ONLY vcard
ADD CONSTRAINT vcard_pkey PRIMARY KEY (username);