summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ejabberd_auth.erl2
-rw-r--r--src/ejabberd_auth_odbc.erl210
-rw-r--r--src/mod_last_odbc.erl135
-rw-r--r--src/mod_offline_odbc.erl242
-rw-r--r--src/odbc/ejabberd_odbc.erl123
-rw-r--r--src/odbc/pg.sql89
6 files changed, 801 insertions, 0 deletions
diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl
index 2c548a33..8abd2ac6 100644
--- a/src/ejabberd_auth.erl
+++ b/src/ejabberd_auth.erl
@@ -74,6 +74,8 @@ auth_module() ->
ejabberd_auth_external;
ldap ->
ejabberd_auth_ldap;
+ odbc ->
+ ejabberd_auth_odbc;
_ ->
ejabberd_auth_internal
end.
diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl
new file mode 100644
index 00000000..8003338f
--- /dev/null
+++ b/src/ejabberd_auth_odbc.erl
@@ -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.
diff --git a/src/mod_last_odbc.erl b/src/mod_last_odbc.erl
new file mode 100644
index 00000000..4937778d
--- /dev/null
+++ b/src/mod_last_odbc.erl
@@ -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, "'"]).
+
diff --git a/src/mod_offline_odbc.erl b/src/mod_offline_odbc.erl
new file mode 100644
index 00000000..abc06b47
--- /dev/null
+++ b/src/mod_offline_odbc.erl
@@ -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, "'"]).
+
diff --git a/src/odbc/ejabberd_odbc.erl b/src/odbc/ejabberd_odbc.erl
new file mode 100644
index 00000000..c496bf2b
--- /dev/null
+++ b/src/odbc/ejabberd_odbc.erl
@@ -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
+%%%----------------------------------------------------------------------
+
diff --git a/src/odbc/pg.sql b/src/odbc/pg.sql
new file mode 100644
index 00000000..9e0c154d
--- /dev/null
+++ b/src/odbc/pg.sql
@@ -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);
+