]> granicus.if.org Git - ejabberd/commitdiff
* src/odbc/pg.sql: DB creation script for postgres
authorAlexey Shchepin <alexey@process-one.net>
Mon, 13 Dec 2004 23:00:12 +0000 (23:00 +0000)
committerAlexey Shchepin <alexey@process-one.net>
Mon, 13 Dec 2004 23:00:12 +0000 (23:00 +0000)
* 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

ChangeLog
src/ejabberd_auth.erl
src/ejabberd_auth_odbc.erl [new file with mode: 0644]
src/mod_last_odbc.erl [new file with mode: 0644]
src/mod_offline_odbc.erl [new file with mode: 0644]
src/odbc/ejabberd_odbc.erl [new file with mode: 0644]
src/odbc/pg.sql [new file with mode: 0644]

index 997126e9590bb3ff34e1c098eaa65c2419c44667..c388cd053d5c4ff061f187404dfa89ad80ef0152 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
index 2c548a33c73f0c90c111bf88131a0b45b50bb8a0..8abd2ac6b33fa672f0e3e609685dfd091ebcda95 100644 (file)
@@ -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 (file)
index 0000000..8003338
--- /dev/null
@@ -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 (file)
index 0000000..4937778
--- /dev/null
@@ -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 (file)
index 0000000..abc06b4
--- /dev/null
@@ -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 (file)
index 0000000..c496bf2
--- /dev/null
@@ -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 (file)
index 0000000..9e0c154
--- /dev/null
@@ -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);
+