diff options
author | Badlop <badlop@process-one.net> | 2013-03-14 10:33:02 +0100 |
---|---|---|
committer | Badlop <badlop@process-one.net> | 2013-03-14 10:33:02 +0100 |
commit | 9deb294328bb3f9eb6bd2c0e7cd500732e9b5830 (patch) | |
tree | 7e1066c130250627ee0abab44a135f583a28d07f /src/ejabberd_piefxis.erl | |
parent | list_to_integer/2 only works in OTP R14 and newer (diff) |
Accumulated patch to binarize and indent code
Diffstat (limited to '')
-rw-r--r-- | src/ejabberd_piefxis.erl | 984 |
1 files changed, 467 insertions, 517 deletions
diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl index b193dc67c..668f1ba62 100644 --- a/src/ejabberd_piefxis.erl +++ b/src/ejabberd_piefxis.erl @@ -3,6 +3,10 @@ %%% Author : Pablo Polvorin, Vidal Santiago Martinez %%% Purpose : XEP-0227: Portable Import/Export Format for XMPP-IM Servers %%% Created : 17 Jul 2008 by Pablo Polvorin <pablo.polvorin@process-one.net> +%%%------------------------------------------------------------------- +%%% @author Evgeniy Khramtsov <ekhramtsov@process-one.net> +%%% @copyright (C) 2012, Evgeniy Khramtsov +%%% @doc %%% %%% %%% ejabberd, Copyright (C) 2002-2013 ProcessOne @@ -31,239 +35,85 @@ %%% - XEP-227: 6. Security Considerations %%% - Other schemas of XInclude are not tested, and may not be imported correctly. %%% - If a host has many users, split that host in XML files with 50 users each. - %%%% Headers -module(ejabberd_piefxis). +%% API -export([import_file/1, export_server/1, export_host/2]). --record(parsing_state, {parser, host, dir}). +-define(CHUNK_SIZE, 1024*20). %20k -include("ejabberd.hrl"). +-include("jlib.hrl"). +-include("mod_privacy.hrl"). +-include("mod_roster.hrl"). %%-include_lib("exmpp/include/exmpp.hrl"). %%-include_lib("exmpp/include/exmpp_client.hrl"). %% Copied from exmpp header files: --define(NS_ROSTER, "jabber:iq:roster"). --define(NS_VCARD, "vcard-temp"). --record(xmlcdata, { - cdata = <<>> - }). --record(xmlattr, { - ns = undefined, - name, - value - }). --record(xmlel, { - ns = undefined, - declared_ns = [], - name, - attrs = [], - children = [] - }). --record(iq, { - kind, - type, - id, - ns, - payload, - error, - lang, - iq_ns - }). --record(xmlendtag, { - ns = undefined, - name - }). - - %% Copied from mod_private.erl --record(private_storage, {usns, xml}). - %%-define(ERROR_MSG(M,Args),io:format(M,Args)). %%-define(INFO_MSG(M,Args),ok). - --define(CHUNK_SIZE,1024*20). %20k - --define(BTL, binary_to_list). --define(LTB, list_to_binary). - --define(NS_XINCLUDE, 'http://www.w3.org/2001/XInclude'). - %%%================================== - %%%% Import file +-define(NS_PIE, <<"urn:xmpp:pie:0">>). +-define(NS_PIEFXIS, <<"http://www.xmpp.org/extensions/xep-0227.html#ns">>). +-define(NS_XI, <<"http://www.w3.org/2001/XInclude">>). -import_file(FileName) -> - _ = #xmlattr{}, %% this stupid line is only to prevent compilation warning about "recod xmlattr is unused" - import_file(FileName, 2). - -import_file(FileName, RootDepth) -> - try_start_exmpp(), - Dir = filename:dirname(FileName), - {ok, IO} = try_open_file(FileName), - Parser = exmpp_xml:start_parser([{max_size,infinity}, - {root_depth, RootDepth}, - {emit_endtag,true}]), - read_chunks(IO, #parsing_state{parser=Parser, dir=Dir}), - file:close(IO), - exmpp_xml:stop_parser(Parser). - -try_start_exmpp() -> - try exmpp:start() - catch - error:{already_started, exmpp} -> ok; - error:undef -> throw({error, exmpp_not_installed}) - end. +-record(state, {xml_stream_state :: xml_stream:xml_stream_state(), + user = <<"">> :: binary(), + server = <<"">> :: binary(), + fd :: file:io_device(), + dir = <<"">> :: binary()}). -try_open_file(FileName) -> - case file:open(FileName,[read,binary]) of - {ok, IO} -> {ok, IO}; - {error, enoent} -> throw({error, {file_not_found, FileName}}) - end. +-type state() :: #state{}. %%File could be large.. we read it in chunks -read_chunks(IO,State) -> - case file:read(IO,?CHUNK_SIZE) of - {ok,Chunk} -> - NewState = process_chunk(Chunk,State), - read_chunks(IO,NewState); - eof -> - ok - end. - -process_chunk(Chunk,S =#parsing_state{parser=Parser}) -> - case exmpp_xml:parse(Parser,Chunk) of - continue -> - S; - XMLElements -> - process_elements(XMLElements,S) +%%%=================================================================== +%%% API +%%%=================================================================== +import_file(FileName) -> + import_file(FileName, #state{}). + +-spec import_file(binary(), state()) -> ok | {error, atom()}. + +import_file(FileName, State) -> + case file:open(FileName, [read, binary]) of + {ok, Fd} -> + Dir = filename:dirname(FileName), + XMLStreamState = xml_stream:new(self(), infinity), + Res = process(State#state{xml_stream_state = XMLStreamState, + fd = Fd, + dir = Dir}), + file:close(Fd), + Res; + {error, Reason} -> + ErrTxt = file:format_error(Reason), + ?ERROR_MSG("Failed to open file '~s': ~s", [FileName, ErrTxt]), + {error, Reason} end. %%%================================== %%%% Process Elements - -process_elements(Elements,State) -> - lists:foldl(fun process_element/2,State,Elements). - %%%================================== %%%% Process Element - -process_element(El=#xmlel{name=user, ns=_XMLNS}, - State=#parsing_state{host=Host}) -> - case add_user(El,Host) of - ok -> ok; - {error, _Other} -> error - end, - State; - -process_element(H=#xmlel{name=host},State) -> - State#parsing_state{host=?BTL(exmpp_xml:get_attribute(H, <<"jid">>, none))}; - -process_element(#xmlel{name='server-data'},State) -> - State; - -process_element(El=#xmlel{name=include, ns=?NS_XINCLUDE}, State=#parsing_state{dir=Dir}) -> - case exmpp_xml:get_attribute(El, <<"href">>, none) of - none -> - ok; - HrefB -> - Href = binary_to_list(HrefB), - %%?INFO_MSG("Parse also this file: ~n~p", [Href]), - FileName = filename:join([Dir, Href]), - import_file(FileName, 1), - Href - end, - State; - -process_element(#xmlcdata{cdata = _CData},State) -> - State; - -process_element(#xmlendtag{ns = _NS, name='server-data'},State) -> - State; - -process_element(#xmlendtag{ns = _NS, name=_Name},State) -> - State; - -process_element(El,State) -> - io:format("Warning!: unknown element found: ~p ~n",[El]), - State. - %%%================================== %%%% Add user - -add_user(El, Domain) -> - User = exmpp_xml:get_attribute(El, <<"name">>, none), - PasswordFormat = exmpp_xml:get_attribute(El, <<"password-format">>, <<"plaintext">>), - Password = exmpp_xml:get_attribute(El, <<"password">>, none), - add_user(El, Domain, User, PasswordFormat, Password). - %% @spec (El::xmlel(), Domain::string(), User::binary(), Password::binary() | none) %% -> ok | {error, ErrorText::string()} %% @doc Add a new user to the database. %% If user already exists, it will be only updated. -add_user(El, Domain, UserBinary, <<"plaintext">>, none) -> - User = ?BTL(UserBinary), - io:format("Account ~s@~s will not be created, updating it...~n", - [User, Domain]), - io:format(""), - populate_user_with_elements(El, Domain, User), - ok; -add_user(El, Domain, UserBinary, PasswordFormat, PasswordBinary) -> - User = ?BTL(UserBinary), - Password2 = prepare_password(PasswordFormat, PasswordBinary, El), - case create_user(User,Password2,Domain) of - ok -> - populate_user_with_elements(El, Domain, User), - ok; - {atomic, exists} -> - io:format("Account ~s@~s already exists, updating it...~n", - [User, Domain]), - io:format(""), - populate_user_with_elements(El, Domain, User), - ok; - {error, Other} -> - ?ERROR_MSG("Error adding user ~s@~s: ~p~n", [User, Domain, Other]), - {error, Other} - end. - -prepare_password(<<"plaintext">>, PasswordBinary, _El) -> - ?BTL(PasswordBinary); -prepare_password(<<"scram">>, none, El) -> - ScramEl = exmpp_xml:get_element(El, 'scram-hash'), - #scram{storedkey = base64:decode(exmpp_xml:get_attribute( - ScramEl, <<"stored-key">>, none)), - serverkey = base64:decode(exmpp_xml:get_attribute( - ScramEl, <<"server-key">>, none)), - salt = base64:decode(exmpp_xml:get_attribute( - ScramEl, <<"salt">>, none)), - iterationcount = list_to_integer(exmpp_xml:get_attribute_as_list( - ScramEl, <<"iteration-count">>, - ?SCRAM_DEFAULT_ITERATION_COUNT)) - }. - -populate_user_with_elements(El, Domain, User) -> - exmpp_xml:foreach( - fun (_,Child) -> - populate_user(User,Domain,Child) - end, - El). +-spec export_server(binary()) -> any(). %% @spec (User::string(), Password::string(), Domain::string()) %% -> ok | {atomic, exists} | {error, not_allowed} %% @doc Create a new user -create_user(User,Password,Domain) -> - case ejabberd_auth:try_register(User,Domain,Password) of - {atomic,ok} -> ok; - {atomic, exists} -> {atomic, exists}; - {error, not_allowed} -> {error, not_allowed}; - Other -> {error, Other} - end. +export_server(Dir) -> + export_hosts(?MYHOSTS, Dir). %%%================================== %%%% Populate user - %% @spec (User::string(), Domain::string(), El::xml()) %% -> ok | {error, not_found} %% @@ -286,28 +136,10 @@ create_user(User,Password,Domain) -> %% </host> %% </server-data> %% ''' +-spec export_host(binary(), binary()) -> any(). -populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) -> - io:format("Trying to add/update roster list...",[]), - case loaded_module(Domain, mod_roster) of - {ok, _DBType} -> - case mod_roster:set_items(User, Domain, - exmpp_xml:xmlel_to_xmlelement(El)) of - {atomic, ok} -> - io:format(" DONE.~n",[]), - ok; - _ -> - io:format(" ERROR.~n",[]), - ?ERROR_MSG("Error trying to add a new user: ~s ~n", - [exmpp_xml:document_to_list(El)]), - {error, not_found} - end; - E -> io:format(" ERROR: ~p~n",[E]), - ?ERROR_MSG("No modules loaded [mod_roster] ~s ~n", - [exmpp_xml:document_to_list(El)]), - {error, not_found} - end; - +export_host(Dir, Host) -> + export_hosts([Host], Dir). %% @spec User = String with the user name %% Domain = String with a domain name @@ -328,180 +160,471 @@ populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:roster'}) -> %% </host> %% </server-data> %% ''' - -populate_user(User,Domain,El=#xmlel{name='vCard', ns='vcard-temp'}) -> - io:format("Trying to add/update vCards...",[]), - case loaded_module(Domain, mod_vcard) of - {ok, _} -> FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)), - IQ = iq_to_old_iq(#iq{type = set, payload = El}), - case mod_vcard:process_sm_iq(FullUser, FullUser , IQ) of - {error,_Err} -> - io:format(" ERROR.~n",[]), - ?ERROR_MSG("Error processing vcard ~s : ~p ~n", - [exmpp_xml:document_to_list(El), _Err]); - _ -> - io:format(" DONE.~n",[]), ok - end; - _ -> - io:format(" ERROR.~n",[]), - ?ERROR_MSG("No modules loaded [mod_vcard] ~s ~n", - [exmpp_xml:document_to_list(El)]), - {error, not_found} - end; +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +export_hosts(Hosts, Dir) -> + FnT = make_filename_template(), + DFn = make_main_basefilename(Dir, FnT), + case file:open(DFn, [raw, write]) of + {ok, Fd} -> + print(Fd, make_piefxis_xml_head()), + print(Fd, make_piefxis_server_head()), + FilesAndHosts = [{make_host_filename(FnT, Host), Host} + || Host <- Hosts], + lists:foreach( + fun({FnH, _}) -> + print(Fd, make_xinclude(FnH)) + end, FilesAndHosts), + print(Fd, make_piefxis_server_tail()), + print(Fd, make_piefxis_xml_tail()), + file:close(Fd), + lists:foldl( + fun({FnH, Host}, ok) -> + export_host(Dir, FnH, Host); + (_, Err) -> + Err + end, ok, FilesAndHosts); + {error, Reason} -> + ErrTxt = file:format_error(Reason), + ?ERROR_MSG("Failed to open file '~s': ~s", [DFn, ErrTxt]), + {error, Reason} + end. %% @spec User = String with the user name %% Domain = String with a domain name %% El = Sub XML element with offline messages values %% @ret ok | {error, not_found} %% @doc Read off-line message from the XML and send it to the server - -populate_user(User,Domain,El=#xmlel{name='offline-messages'}) -> - io:format("Trying to add/update offline-messages...",[]), - case loaded_module(Domain, mod_offline) of - {ok, _DBType} -> - ok = exmpp_xml:foreach( - fun (_Element, {xmlcdata, _}) -> - ok; - (_Element, Child) -> - From = exmpp_xml:get_attribute(Child, <<"from">>,none), - FullFrom = jid_to_old_jid(exmpp_jid:parse(From)), - FullUser = jid_to_old_jid(exmpp_jid:make(User, - Domain)), - OldChild = exmpp_xml:xmlel_to_xmlelement(Child), - _R = mod_offline:store_packet(FullFrom, FullUser, OldChild) - end, El), io:format(" DONE.~n",[]); - _ -> - io:format(" ERROR.~n",[]), - ?ERROR_MSG("No modules loaded [mod_offline] ~s ~n", - [exmpp_xml:document_to_list(El)]), - {error, not_found} - end; +export_host(Dir, FnH, Host) -> + DFn = make_host_basefilename(Dir, FnH), + case file:open(DFn, [raw, write]) of + {ok, Fd} -> + print(Fd, make_piefxis_xml_head()), + print(Fd, make_piefxis_host_head(Host)), + Users = ejabberd_auth:get_vh_registered_users(Host), + case export_users(Users, Host, Fd) of + ok -> + print(Fd, make_piefxis_host_tail()), + print(Fd, make_piefxis_xml_tail()), + file:close(Fd), + ok; + Err -> + file:close(Fd), + file:delete(DFn), + Err + end; + {error, Reason} -> + ErrTxt = file:format_error(Reason), + ?ERROR_MSG("Failed to open file '~s': ~s", [DFn, ErrTxt]), + {error, Reason} + end. %% @spec User = String with the user name %% Domain = String with a domain name %% El = Sub XML element with private storage values %% @ret ok | {error, not_found} %% @doc Private storage parsing - -populate_user(User,Domain,El=#xmlel{name='query', ns='jabber:iq:private'}) -> - io:format("Trying to add/update private storage...",[]), - case loaded_module(Domain, mod_private) of - {ok, _DBType} -> - FullUser = jid_to_old_jid(exmpp_jid:make(User, Domain)), - IQ = iq_to_old_iq(#iq{type = set, - ns = 'jabber:iq:private', - kind = request, - iq_ns = 'jabberd:client', - payload = El}), - case mod_private:process_sm_iq(FullUser, FullUser, IQ ) of - {error, _Err} -> - io:format(" ERROR.~n",[]), - ?ERROR_MSG("Error processing private storage ~s : ~p ~n", - [exmpp_xml:document_to_list(El), _Err]); - _ -> io:format(" DONE.~n",[]), ok - end; - _ -> - io:format(" ERROR.~n",[]), - ?ERROR_MSG("No modules loaded [mod_private] ~s ~n", - [exmpp_xml:document_to_list(El)]), - {error, not_found} +export_users([{User, _S}|Users], Server, Fd) -> + case export_user(User, Server, Fd) of + ok -> + export_users(Users, Server, Fd); + Err -> + Err end; - -populate_user(_User, _Domain, #xmlcdata{cdata = _CData}) -> - ok; - -populate_user(_User, _Domain, _El) -> +export_users([], _Server, _Fd) -> ok. %%%================================== %%%% Utilities - -loaded_module(Domain, Module) -> - case gen_mod:is_loaded(Domain, Module) of - true -> - {ok, gen_mod:db_type(Domain, Module)}; - false -> - {error, not_found} +export_user(User, Server, Fd) -> + Pass = ejabberd_auth:get_password_s(User, Server), + Els = get_offline(User, Server) ++ + get_vcard(User, Server) ++ + get_privacy(User, Server) ++ + get_roster(User, Server) ++ + get_private(User, Server), + print(Fd, xml:element_to_binary( + #xmlel{name = <<"user">>, + attrs = [{<<"name">>, User}, + {<<"password">>, Pass}], + children = Els})). + +get_vcard(User, Server) -> + JID = jlib:make_jid(User, Server, <<>>), + case mod_vcard:process_sm_iq(JID, JID, #iq{type = get}) of + #iq{type = result, sub_el = [_|_] = VCardEls} -> + VCardEls; + _ -> + [] end. -jid_to_old_jid(Jid) -> - {jid, to_list(exmpp_jid:node_as_list(Jid)), - to_list(exmpp_jid:domain_as_list(Jid)), - to_list(exmpp_jid:resource_as_list(Jid)), - to_list(exmpp_jid:prep_node_as_list(Jid)), - to_list(exmpp_jid:prep_domain_as_list(Jid)), - to_list(exmpp_jid:prep_resource_as_list(Jid))}. - -iq_to_old_iq(#iq{id = ID, type = Type, lang = Lang, ns= NS, payload = El }) -> - {iq, to_list(ID), Type, to_list(NS), to_list(Lang), - exmpp_xml:xmlel_to_xmlelement(El)}. - -to_list(L) when is_list(L) -> L; -to_list(B) when is_binary(B) -> binary_to_list(B); -to_list(undefined) -> ""; -to_list(B) when is_atom(B) -> atom_to_list(B). - %%%================================== +get_offline(User, Server) -> + case mod_offline:get_offline_els(User, Server) of + [] -> + []; + Els -> + NewEls = lists:map( + fun(#xmlel{attrs = Attrs} = El) -> + NewAttrs = lists:keystore(<<"xmlns">>, 1, + Attrs, + {<<"xmlns">>, + <<"jabber:client">>}), + El#xmlel{attrs = NewAttrs} + end, Els), + [#xmlel{name = <<"offline-messages">>, children = NewEls}] + end. %%%% Export hosts +get_privacy(User, Server) -> + case mod_privacy:get_user_lists(User, Server) of + {ok, #privacy{default = Default, + lists = [_|_] = Lists}} -> + XLists = lists:map( + fun({Name, Items}) -> + XItems = lists:map( + fun mod_privacy:item_to_xml/1, Items), + #xmlel{name = <<"list">>, + attrs = [{<<"name">>, Name}], + children = XItems} + end, Lists), + DefaultEl = case Default of + none -> + []; + _ -> + [#xmlel{name = <<"default">>, + attrs = [{<<"name">>, Default}]}] + end, + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_PRIVACY}], + children = DefaultEl ++ XLists}]; + _ -> + [] + end. %% @spec (Dir::string(), Hosts::[string()]) -> ok -export_hosts(Dir, Hosts) -> - try_start_exmpp(), +get_roster(User, Server) -> + JID = jlib:make_jid(User, Server, <<>>), + case mod_roster:get_roster(User, Server) of + [_|_] = Items -> + Subs = + lists:flatmap( + fun(#roster{ask = Ask, + askmessage = Msg} = R) + when Ask == in; Ask == both -> + Status = if is_binary(Msg) -> (Msg); + true -> <<"">> + end, + [#xmlel{name = <<"presence">>, + attrs = + [{<<"from">>, + jlib:jid_to_string(R#roster.jid)}, + {<<"to">>, jlib:jid_to_string(JID)}, + {<<"xmlns">>, <<"jabber:client">>}, + {<<"type">>, <<"subscribe">>}], + children = + [#xmlel{name = <<"status">>, + attrs = [], + children = + [{xmlcdata, Status}]}]}]; + (_) -> + [] + end, Items), + Rs = lists:flatmap( + fun(#roster{ask = in, subscription = none}) -> + []; + (R) -> + [mod_roster:item_to_xml(R)] + end, Items), + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_ROSTER}], + children = Rs} | Subs]; + _ -> + [] + end. - FnT = make_filename_template(), - DFn = make_main_basefilename(Dir, FnT), +get_private(User, Server) -> + case mod_private:get_data(User, Server) of + [_|_] = Els -> + [#xmlel{name = <<"query">>, + attrs = [{<<"xmlns">>, ?NS_PRIVATE}], + children = Els}]; + _ -> + [] + end. - {ok, Fd} = file_open(DFn), - print(Fd, make_piefxis_xml_head()), - print(Fd, make_piefxis_server_head()), +process(#state{xml_stream_state = XMLStreamState, fd = Fd} = State) -> + case file:read(Fd, ?CHUNK_SIZE) of + {ok, Data} -> + NewXMLStreamState = xml_stream:parse(XMLStreamState, Data), + case process_els(State#state{xml_stream_state = + NewXMLStreamState}) of + {ok, NewState} -> + process(NewState); + Err -> + xml_stream:close(NewXMLStreamState), + Err + end; + eof -> + xml_stream:close(XMLStreamState), + ok + end. - FilesAndHosts = [{make_host_filename(FnT, Host), Host} || Host <- Hosts], - [print(Fd, make_xinclude(FnH)) || {FnH, _Host} <- FilesAndHosts], +process_els(State) -> + receive + {'$gen_event', El} -> + case process_el(El, State) of + {ok, NewState} -> + process_els(NewState); + Err -> + Err + end + after 0 -> + {ok, State} + end. - print(Fd, make_piefxis_server_tail()), - print(Fd, make_piefxis_xml_tail()), - file_close(Fd), +process_el({xmlstreamstart, <<"server-data">>, Attrs}, State) -> + case xml:get_attr_s(<<"xmlns">>, Attrs) of + ?NS_PIEFXIS -> + {ok, State}; + ?NS_PIE -> + {ok, State}; + NS -> + stop("Unknown 'server-data' namespace = ~s", [NS]) + end; +process_el({xmlstreamend, _}, State) -> + {ok, State}; +process_el({xmlstreamcdata, _}, State) -> + {ok, State}; +process_el({xmlstreamelement, #xmlel{name = <<"xi:include">>, + attrs = Attrs}}, + #state{dir = Dir, user = <<"">>} = State) -> + FileName = xml:get_attr_s(<<"href">>, Attrs), + case import_file(filename:join([Dir, FileName]), State) of + ok -> + {ok, State}; + Err -> + Err + end; +process_el({xmlstreamstart, <<"host">>, Attrs}, State) -> + process_el({xmlstreamelement, #xmlel{name = <<"host">>, + attrs = Attrs}}, State); +process_el({xmlstreamelement, #xmlel{name = <<"host">>, + attrs = Attrs, + children = Els}}, State) -> + JIDS = xml:get_attr_s(<<"jid">>, Attrs), + case jlib:string_to_jid(JIDS) of + #jid{lserver = S} -> + case lists:member(S, ?MYHOSTS) of + true -> + process_users(Els, State#state{server = S}); + false -> + stop("Unknown host: ~s", [S]) + end; + error -> + stop("Invalid 'jid': ~s", [JIDS]) + end; +process_el({xmlstreamstart, <<"user">>, Attrs}, State = #state{server = S}) + when S /= <<"">> -> + process_el({xmlstreamelement, #xmlel{name = <<"user">>, attrs = Attrs}}, + State); +process_el({xmlstreamelement, #xmlel{name = <<"user">>} = El}, + State = #state{server = S}) when S /= <<"">> -> + process_user(El, State); +process_el({xmlstreamelement, El}, State = #state{server = S, user = U}) + when S /= <<"">>, U /= <<"">> -> + process_user_el(El, State); +process_el({xmlstreamelement, El}, _State) -> + stop("Unexpected tag: ~p", [El]); +process_el({xmlstreamstart, El, Attrs}, _State) -> + stop("Unexpected payload: ~p", [{El, Attrs}]); +process_el({xmlstreamerror, Err}, _State) -> + stop("Failed to process element = ~p", [Err]). + +process_users([#xmlel{} = El|Els], State) -> + case process_user(El, State) of + {ok, NewState} -> + process_users(Els, NewState); + Err -> + Err + end; +process_users([_|Els], State) -> + process_users(Els, State); +process_users([], State) -> + {ok, State}. + +process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els}, + #state{server = LServer} = State) -> + Name = xml:get_attr_s(<<"name">>, Attrs), + Pass = xml:get_attr_s(<<"password">>, Attrs), + case jlib:nodeprep(Name) of + error -> + stop("Invalid 'user': ~s", [Name]); + LUser -> + case ejabberd_auth:try_register(LUser, LServer, Pass) of + {atomic, _} -> + process_user_els(Els, State#state{user = LUser}); + Err -> + stop("Failed to create user '~s': ~p", [Name, Err]) + end + end. - [export_host(Dir, FnH, Host) || {FnH, Host} <- FilesAndHosts], +process_user_els([#xmlel{} = El|Els], State) -> + case process_user_el(El, State) of + {ok, NewState} -> + process_user_els(Els, NewState); + Err -> + Err + end; +process_user_els([_|Els], State) -> + process_user_els(Els, State); +process_user_els([], State) -> + {ok, State}. + +process_user_el(#xmlel{name = Name, attrs = Attrs, children = Els} = El, + State) -> + case {Name, xml:get_attr_s(<<"xmlns">>, Attrs)} of + {<<"query">>, ?NS_ROSTER} -> + process_roster(El, State); + {<<"query">>, ?NS_PRIVACY} -> + %% Make sure <list/> elements go before <active/> and <default/> + NewEls = lists:reverse(lists:keysort(#xmlel.name, Els)), + process_privacy_el(El#xmlel{children = NewEls}, State); + {<<"query">>, ?NS_PRIVATE} -> + process_private(El, State); + {<<"vCard">>, ?NS_VCARD} -> + process_vcard(El, State); + {<<"offline-messages">>, _} -> + process_offline_msgs(Els, State); + {<<"presence">>, <<"jabber:client">>} -> + process_presence(El, State); + _ -> + {ok, State} + end. - ok. +process_privacy_el(#xmlel{children = [#xmlel{} = SubEl|SubEls]} = El, State) -> + case process_privacy(#xmlel{children = [SubEl]}, State) of + {ok, NewState} -> + process_privacy_el(El#xmlel{children = SubEls}, NewState); + Err -> + Err + end; +process_privacy_el(#xmlel{children = [_|SubEls]} = El, State) -> + process_privacy_el(El#xmlel{children = SubEls}, State); +process_privacy_el(#xmlel{children = []}, State) -> + {ok, State}. + +process_offline_msgs([#xmlel{} = El|Els], State) -> + case process_offline_msg(El, State) of + {ok, NewState} -> + process_offline_msgs(Els, NewState); + Err -> + Err + end; +process_offline_msgs([_|Els], State) -> + process_offline_msgs(Els, State); +process_offline_msgs([], State) -> + {ok, State}. + +process_roster(El, State = #state{user = U, server = S}) -> + case mod_roster:set_items(U, S, El) of + {atomic, _} -> + {ok, State}; + Err -> + stop("Failed to write roster: ~p", [Err]) + end. %%%================================== %%%% Export server +process_privacy(El, State = #state{user = U, server = S}) -> + JID = jlib:make_jid(U, S, <<"">>), + case mod_privacy:process_iq_set( + [], JID, JID, #iq{type = set, sub_el = El}) of + {error, _} = Err -> + stop("Failed to write privacy: ~p", [Err]); + _ -> + {ok, State} + end. %% @spec (Dir::string()) -> ok -export_server(Dir) -> - Hosts = ?MYHOSTS, - export_hosts(Dir, Hosts). +process_private(El, State = #state{user = U, server = S}) -> + JID = jlib:make_jid(U, S, <<"">>), + case mod_private:process_sm_iq( + JID, JID, #iq{type = set, sub_el = El}) of + #iq{type = result} -> + {ok, State}; + Err -> + stop("Failed to write private: ~p", [Err]) + end. %%%================================== %%%% Export host +process_vcard(El, State = #state{user = U, server = S}) -> + JID = jlib:make_jid(U, S, <<"">>), + case mod_vcard:process_sm_iq( + JID, JID, #iq{type = set, sub_el = El}) of + #iq{type = result} -> + {ok, State}; + Err -> + stop("Failed to write vcard: ~p", [Err]) + end. %% @spec (Dir::string(), Host::string()) -> ok -export_host(Dir, Host) -> - Hosts = [Host], - export_hosts(Dir, Hosts). +process_offline_msg(El, State = #state{user = U, server = S}) -> + FromS = xml:get_attr_s(<<"from">>, El#xmlel.attrs), + case jlib:string_to_jid(FromS) of + #jid{} = From -> + To = jlib:make_jid(U, S, <<>>), + NewEl = jlib:replace_from_to(From, To, El), + case catch mod_offline:store_packet(From, To, NewEl) of + {'EXIT', _} = Err -> + stop("Failed to store offline message: ~p", [Err]); + _ -> + {ok, State} + end; + _ -> + stop("Invalid 'from' = ~s", [FromS]) + end. %% @spec (Dir::string(), Fn::string(), Host::string()) -> ok -export_host(Dir, FnH, Host) -> +process_presence(El, #state{user = U, server = S} = State) -> + FromS = xml:get_attr_s(<<"from">>, El#xmlel.attrs), + case jlib:string_to_jid(FromS) of + #jid{} = From -> + To = jlib:make_jid(U, S, <<>>), + NewEl = jlib:replace_from_to(From, To, El), + ejabberd_router:route(From, To, NewEl), + {ok, State}; + _ -> + stop("Invalid 'from' = ~s", [FromS]) + end. - DFn = make_host_basefilename(Dir, FnH), +stop(Fmt, Args) -> + ?ERROR_MSG(Fmt, Args), + {error, import_failed}. - {ok, Fd} = file_open(DFn), - print(Fd, make_piefxis_xml_head()), - print(Fd, make_piefxis_host_head(Host)), +make_filename_template() -> + {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(), + list_to_binary( + io_lib:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w", + [Year, Month, Day, Hour, Minute, Second])). - Users = ejabberd_auth:get_vh_registered_users(Host), - [export_user(Fd, Username, Host) || {Username, _Host} <- Users], - timer:sleep(500), % Delay to ensure ERROR_MSG are displayed in the shell +make_main_basefilename(Dir, FnT) -> + Filename2 = <<FnT/binary, ".xml">>, + filename:join([Dir, Filename2]). - print(Fd, make_piefxis_host_tail()), - print(Fd, make_piefxis_xml_tail()), - file_close(Fd). +%% @doc Make the filename for the host. +%% Example: ``(<<"20080804-231550">>, <<"jabber.example.org">>) -> +%% <<"20080804-231550_jabber_example_org.xml">>'' +make_host_filename(FnT, Host) -> + Host2 = str:join(str:tokens(Host, <<".">>), <<"_">>), + <<FnT/binary, "_", Host2/binary, ".xml">>. %%%================================== %%%% PIEFXIS formatting +make_host_basefilename(Dir, FnT) -> + filename:join([Dir, FnT]). %% @spec () -> string() make_piefxis_xml_head() -> @@ -513,9 +636,8 @@ make_piefxis_xml_tail() -> %% @spec () -> string() make_piefxis_server_head() -> - "<server-data" - " xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'" - " xmlns:xi='http://www.w3.org/2001/XInclude'>". + io_lib:format("<server-data xmlns='~s' xmlns:xi='~s'>", + [?NS_PIE, ?NS_XI]). %% @spec () -> string() make_piefxis_server_tail() -> @@ -523,10 +645,8 @@ make_piefxis_server_tail() -> %% @spec (Host::string()) -> string() make_piefxis_host_head(Host) -> - NSString = - " xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'" - " xmlns:xi='http://www.w3.org/2001/XInclude'", - io_lib:format("<host~s jid='~s'>", [NSString, Host]). + io_lib:format("<host xmlns='~s' xmlns:xi='~s' jid='~s'>", + [?NS_PIE, ?NS_XI, Host]). %% @spec () -> string() make_piefxis_host_tail() -> @@ -539,196 +659,26 @@ make_xinclude(Fn) -> %%%================================== %%%% Export user - %% @spec (Fd, Username::string(), Host::string()) -> ok %% @doc Extract user information and print it. -export_user(Fd, Username, Host) -> - try extract_user(Username, Host) of - UserString -> - print(Fd, UserString) - catch - E1:E2 -> - ?ERROR_MSG("The account ~s@~s is not exported because a problem " - "was found in it:~n~p: ~p", [Username, Host, E1, E2]) - end. - %% @spec (Username::string(), Host::string()) -> string() -extract_user(Username, Host) -> - Password = ejabberd_auth:get_password(Username, Host), - PasswordStr = build_password_string(Password), - UserInfo = [extract_user_info(InfoName, Username, Host) || InfoName <- [roster, offline, private, vcard]], - UserInfoString = lists:flatten(UserInfo), - io_lib:format("<user name='~s' ~s ~s</user>", - [Username, PasswordStr, UserInfoString]). - -build_password_string({StoredKey, ServerKey, Salt, IterationCount}) -> - io_lib:format("password-format='scram'>" - "<scram-hash stored-key='~s' server-key='~s' " - "salt='~s' iteration-count='~w'/> ", - [base64:encode_to_string(StoredKey), - base64:encode_to_string(ServerKey), - base64:encode_to_string(Salt), - IterationCount]); -build_password_string(Password) when is_list(Password) -> - io_lib:format("password-format='plaintext' password='~s'>", [Password]). - %% @spec (InfoName::atom(), Username::string(), Host::string()) -> string() -extract_user_info(roster, Username, Host) -> - case loaded_module(Host, mod_roster) of - {ok, _DBType} -> - From = To = jlib:make_jid(Username, Host, ""), - SubelGet = {xmlelement, "query", [{"xmlns",?NS_ROSTER}], []}, - %%IQGet = #iq{type=get, xmlns=?NS_ROSTER, payload=SubelGet}, % this is for 3.0.0 version - IQGet = {iq, "", get, ?NS_ROSTER, "" , SubelGet}, - Res = mod_roster:process_local_iq(From, To, IQGet), - %%[El] = Res#iq.payload, % this is for 3.0.0 version - {iq, _, result, _, _, Els} = Res, - case Els of - [El] -> exmpp_xml:document_to_list(El); - [] -> "" - end; - _E -> - "" - end; - -extract_user_info(offline, Username, Host) -> - case loaded_module(Host, mod_offline) of - {ok, mnesia} -> - Els = mnesia_pop_offline_messages([], Username, Host), - case Els of - [] -> ""; - Els -> - OfEl = {xmlelement, "offline-messages", [], Els}, - exmpp_xml:document_to_list(OfEl) - end; - {ok, odbc} -> - ""; - _E -> - "" - end; - -extract_user_info(private, Username, Host) -> - case loaded_module(Host, mod_private) of - {ok, mnesia} -> - get_user_private_mnesia(Username, Host); - {ok, odbc} -> - ""; - _E -> - "" - end; - -extract_user_info(vcard, Username, Host) -> - case loaded_module(Host, mod_vcard) of - {ok, _DBType} -> - From = To = jlib:make_jid(Username, Host, ""), - SubelGet = {xmlelement, "vCard", [{"xmlns",?NS_VCARD}], []}, - %%IQGet = #iq{type=get, xmlns=?NS_VCARD, payload=SubelGet}, % this is for 3.0.0 version - IQGet = {iq, "", get, ?NS_VCARD, "" , SubelGet}, - Res = mod_vcard:process_sm_iq(From, To, IQGet), - %%[El] = Res#iq.payload, % this is for 3.0.0 version - {iq, _, result, _, _, Els} = Res, - case Els of - [El] -> exmpp_xml:document_to_list(El); - [] -> "" - end; - _E -> - "" - end. - %%%================================== %%%% Interface with ejabberd offline storage - %% Copied from mod_offline.erl and customized --record(offline_msg, {us, timestamp, expire, from, to, packet}). -mnesia_pop_offline_messages(Ls, User, Server) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - US = {LUser, LServer}, - F = fun() -> - Rs = mnesia:wread({offline_msg, US}), - %%mnesia:delete({offline_msg, US}), - Rs - end, - case mnesia:transaction(F) of - {atomic, Rs} -> - TS = now(), - Ls ++ lists:map( - fun(R) -> - {xmlelement, Name, Attrs, Els} = R#offline_msg.packet, - FromString = jlib:jid_to_string(R#offline_msg.from), - Attrs2 = lists:keystore("from", 1, Attrs, {"from", FromString}), - Attrs3 = lists:keystore("xmlns", 1, Attrs2, {"xmlns", "jabber:client"}), - {xmlelement, Name, Attrs3, - Els ++ - [jlib:timestamp_to_xml( - calendar:now_to_universal_time( - R#offline_msg.timestamp))]} - end, - lists:filter( - fun(R) -> - case R#offline_msg.expire of - never -> - true; - TimeStamp -> - TS < TimeStamp - end - end, - lists:keysort(#offline_msg.timestamp, Rs))); - _ -> - Ls - end. - %%%================================== %%%% Interface with ejabberd private storage - -get_user_private_mnesia(Username, Host) -> - ListNsEl = mnesia:dirty_select(private_storage, - [{#private_storage{usns={Username, Host, '$1'}, xml = '$2'}, - [], ['$$']}]), - Els = [exmpp_xml:document_to_list(El) || [_Ns, El] <- ListNsEl], - case lists:flatten(Els) of - "" -> ""; - ElsString -> - io_lib:format("<query xmlns='jabber:iq:private'>~s</query>", [ElsString]) - end. - %%%================================== %%%% Disk file access - %% @spec () -> string() -make_filename_template() -> - {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(), - lists:flatten( - io_lib:format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w", - [Year, Month, Day, Hour, Minute, Second])). - %% @spec (Dir::string(), FnT::string()) -> string() -make_main_basefilename(Dir, FnT) -> - Filename2 = filename:flatten([FnT, ".xml"]), - filename:join([Dir, Filename2]). - %% @spec (FnT::string(), Host::string()) -> FnH::string() %% @doc Make the filename for the host. %% Example: ``("20080804-231550", "jabber.example.org") -> "20080804-231550_jabber_example_org.xml"'' -make_host_filename(FnT, Host) -> - Host2 = string:join(string:tokens(Host, "."), "_"), - filename:flatten([FnT, "_", Host2, ".xml"]). - -make_host_basefilename(Dir, FnT) -> - filename:join([Dir, FnT]). - %% @spec (Fn::string()) -> {ok, Fd} -file_open(Fn) -> - file:open(Fn, [write]). - %% @spec (Fd) -> ok -file_close(Fd) -> - file:close(Fd). - %% @spec (Fd, String::string()) -> ok print(Fd, String) -> - io:format(Fd, String, []). - %%%================================== - %%% vim: set filetype=erlang tabstop=8 foldmarker=%%%%,%%%= foldmethod=marker: + file:write(Fd, String). |