1%% 2%% Copyright (c) 2013 Grasshopper 3%% 4%% Permission is hereby granted, free of charge, to any person obtaining a copy 5%% of this software and associated documentation files (the "Software"), to deal 6%% in the Software without restriction, including without limitation the rights 7%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8%% copies of the Software, and to permit persons to whom the Software is 9%% furnished to do so, subject to the following conditions: 10%% 11%% The above copyright notice and this permission notice shall be included in 12%% all copies or substantial portions of the Software. 13%% 14%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20%% THE SOFTWARE. 21%% 22%% Contributors: 23%% Chris Rienzo <chris.rienzo@grasshopper.com> 24%% 25%% Maintainer: Chris Rienzo <chris.rienzo@grasshopper.com> 26%% 27%% mod_rayo_gateway.erl -- ejabberd Rayo gateway module 28%% 29-module(mod_rayo_gateway). 30 31-include("ejabberd.hrl"). 32-include("jlib.hrl"). 33 34-behavior(gen_server). 35-behavior(gen_mod). 36 37%% JID mappings 38%% 39%% Entity Internal JID Mapped JID 40%% ====== =============== =============== 41%% Client user@domain/resource gateway@internal_domain/gw-resource 42%% Node node_domain external_domain 43%% Call uuid@node_domain node_domain|uuid@external_domain 44%% Call Resource uuid@node_domain/resource node_domain|uuid@external_domain/resource 45%% Mixer name@node_domain node_domain|name@external_domain 46%% Mixer Resource name@node_domain/resource node_domain|name@external_domain/resource 47 48%% TODO don't allow nodes to act as clients 49%% TODO don't allow clients to act as nodes 50 51-export([ 52 start_link/2, 53 start/2, 54 stop/1, 55 init/1, 56 handle_call/3, 57 handle_cast/2, 58 handle_info/2, 59 terminate/2, 60 code_change/3, 61 route_internal/3, 62 route_external/3 63]). 64 65-define(PROCNAME, ejabberd_mod_rayo_gateway). 66-define(NS_RAYO, "urn:xmpp:rayo:1"). 67-define(NS_PING, "urn:xmpp:ping"). 68 69-record(rayo_config, {name, value}). 70-record(rayo_clients, {jid, status}). 71-record(rayo_nodes, {jid, status}). 72-record(rayo_entities, {external_jid, internal_jid, dcp_jid, type}). 73 74start_link(Host, Opts) -> 75 Proc = gen_mod:get_module_proc(Host, ?PROCNAME), 76 gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). 77 78% Start the module process 79start(Host, Opts) -> 80 Proc = gen_mod:get_module_proc(Host, ?PROCNAME), 81 ChildSpec = {Proc, 82 {?MODULE, start_link, [Host, Opts]}, 83 temporary, 84 1000, 85 worker, 86 [?MODULE]}, 87 supervisor:start_child(ejabberd_sup, ChildSpec). 88 89% Shutdown the module 90stop(Host) -> 91 Proc = gen_mod:get_module_proc(Host, ?PROCNAME), 92 gen_server:call(Proc, stop), 93 supervisor:terminate_child(ejabberd_sup, Proc), 94 supervisor:delete_child(ejabberd_sup, Proc). 95 96% Initialize the module 97init([Host, Opts]) -> 98 ?DEBUG("MOD_RAYO_GATEWAY: Starting", []), 99 100 mnesia:delete_table(rayo_clients), 101 mnesia:create_table(rayo_clients, [{attributes, record_info(fields, rayo_clients)}]), 102 mnesia:delete_table(rayo_nodes), 103 mnesia:create_table(rayo_nodes, [{attributes, record_info(fields, rayo_nodes)}]), 104 mnesia:delete_table(rayo_entities), 105 mnesia:create_table(rayo_entities, [{attributes, record_info(fields, rayo_entities)}, {index, [internal_jid]}]), 106 mnesia:delete_table(rayo_config), 107 mnesia:create_table(rayo_config, [{attributes, record_info(fields, rayo_config)}]), 108 109 {A1,A2,A3} = now(), 110 random:seed(A1, A2, A3), 111 112 % create virtual domains 113 InternalDomain = gen_mod:get_opt_host(Host, Opts, "rayo-int.@HOST@"), 114 ExternalDomain = gen_mod:get_opt_host(Host, Opts, "rayo.@HOST@"), 115 {ok, Hostname} = inet:gethostname(), 116 InternalClient = "gateway@" ++ InternalDomain ++ "/" ++ Hostname ++ "-" ++ integer_to_list(random:uniform(65535)), 117 ?DEBUG("MOD_RAYO_GATEWAY: InternalDomain = ~p, ExternalDomain = ~p, InternalClient = ~p", [InternalDomain, ExternalDomain, InternalClient]), 118 mnesia:transaction( 119 fun() -> 120 mnesia:write(#rayo_config{name = "internal_domain", value = InternalDomain}), 121 mnesia:write(#rayo_config{name = "internal_client", value = InternalClient}), 122 mnesia:write(#rayo_config{name = "external_domain", value = ExternalDomain}) 123 end 124 ), 125 126 % set up routes to virtual domains 127 ejabberd_router:register_route(InternalDomain, {apply, ?MODULE, route_internal}), 128 ejabberd_router:register_route(ExternalDomain, {apply, ?MODULE, route_external}), 129 {ok, Host}. 130 131handle_call(stop, _From, Host) -> 132 {stop, normal, ok, Host}. 133 134handle_cast(_Msg, Host) -> 135 {noreply, Host}. 136 137handle_info(_Msg, Host) -> 138 {noreply, Host}. 139 140terminate(_Reason, Host) -> 141 ejabberd_router:unregister_route(Host), 142 ok. 143 144code_change(_OldVsn, Host, _Extra) -> 145 {ok, Host}. 146 147register_rayo_node(Jid) -> 148 Write = fun() -> 149 mnesia:write(#rayo_nodes{jid = Jid, status = "online" }) 150 end, 151 Result = mnesia:transaction(Write), 152 ?DEBUG("MOD_RAYO_GATEWAY: register node: ~p, result = ~p, ~p nodes total", [jlib:jid_to_string(Jid), Result, num_rayo_nodes()]), 153 case num_clients() >= 1 of 154 true -> 155 ejabberd_router:route(internal_client(), Jid, online_presence()); 156 _ -> 157 ok 158 end, 159 ok. 160 161% TODO call this when s2s connection is dropped 162unregister_rayo_node(Jid) -> 163 Delete = fun() -> 164 mnesia:delete({rayo_nodes, Jid}) 165 end, 166 Result = mnesia:transaction(Delete), 167 Size = mnesia:table_info(rayo_nodes, size), 168 ?DEBUG("MOD_RAYO_GATEWAY: unregister node: ~p, result = ~p, ~p nodes total", [jlib:jid_to_string(Jid), Result, Size]), 169 ok. 170 171% Add client 172register_rayo_client(Jid) -> 173 Write = fun() -> 174 mnesia:write(#rayo_clients{jid = Jid, status = "online" }) 175 end, 176 Result = mnesia:transaction(Write), 177 Size = num_clients(), 178 ?DEBUG("MOD_RAYO_GATEWAY: register client: ~p, result = ~p, ~p clients total", [jlib:jid_to_string(Jid), Result, Size]), 179 case Size of 180 1 -> 181 route_to_list(internal_client(), all_rayo_nodes(), online_presence()); 182 _ -> 183 ok 184 end, 185 ok. 186 187% Remove client 188% TODO call this when c2s connection is dropped 189unregister_rayo_client(Jid) -> 190 Delete = fun() -> 191 mnesia:delete({rayo_clients, Jid}) 192 end, 193 Result = mnesia:transaction(Delete), 194 Size = num_clients(), 195 ?DEBUG("MOD_RAYO_GATEWAY: unregister client: ~p, result = ~p, ~p clients total", [jlib:jid_to_string(Jid), Result, Size]), 196 case Size of 197 0 -> 198 route_to_list(internal_client(), all_rayo_nodes(), offline_presence()); 199 _ -> 200 ok 201 end, 202 ok. 203 204% Add node entity 205register_rayo_node_entity(ExtJid, IntJid, DcpJid, Type) -> 206 Write = fun() -> 207 mnesia:write(#rayo_entities{external_jid = ExtJid, internal_jid = IntJid, dcp_jid = DcpJid, type = Type}) 208 end, 209 Result = mnesia:transaction(Write), 210 Size = mnesia:table_info(rayo_entities, size), 211 ?DEBUG("MOD_RAYO_GATEWAY: register entity: ~p, result = ~p, ~p entities total", [jlib:jid_to_string(ExtJid), Result, Size]), 212 ok. 213 214% Remove node entity 215unregister_rayo_node_entity(ExtJid) -> 216 Delete = fun() -> 217 mnesia:delete({rayo_entities, ExtJid}) 218 end, 219 Result = mnesia:transaction(Delete), 220 Size = mnesia:table_info(rayo_entities, size), 221 ?DEBUG("MOD_RAYO_GATEWAY: unregister entity: ~p, result = ~p, ~p entities total", [jlib:jid_to_string(ExtJid), Result, Size]), 222 ok. 223 224% find node entity given enitity's (or its component's) internal JID 225find_rayo_node_entity_by_int_jid(IntJid) -> 226 % remove resource from JID to find component's parent call/mixer 227 case mnesia:dirty_index_read(rayo_entities, jlib:jid_remove_resource(IntJid), #rayo_entities.internal_jid) of 228 [Entity | _] -> 229 Entity; 230 _ -> 231 none 232 end. 233 234% find node entity given enitity's (or its component's) external JID 235find_rayo_node_entity_by_ext_jid(ExtJid) -> 236 % remove resource from JID to find component's parent call/mixer 237 case mnesia:dirty_read(rayo_entities, jlib:jid_remove_resource(ExtJid)) of 238 [Entity | _] -> 239 Entity; 240 _ -> 241 none 242 end. 243 244% find entity Definitive Controlling Party JID given entity external JID 245find_rayo_node_entity_dcp_by_ext_jid(ExtJid) -> 246 case find_rayo_node_entity_by_ext_jid(ExtJid) of 247 {rayo_entities, _, _, DcpJid, _} -> 248 DcpJid; 249 _ -> 250 none 251 end. 252 253% find entity Definitive Controlling Party JID given entity internal JID 254find_rayo_node_entity_dcp_by_int_jid(IntJid) -> 255 case find_rayo_node_entity_by_int_jid(IntJid) of 256 {rayo_entities, _, _, DcpJid, _} -> 257 DcpJid; 258 _ -> 259 none 260 end. 261 262% create External JID from Internal JID 263% intnode@intdomain/resource -> intdomain-intnode@extdomain/resource 264create_external_jid({jid, Node, Domain, Resource, _, _, _}) -> 265 jlib:make_jid(Domain ++ "|" ++ Node, jlib:jid_to_string(external_domain()), Resource). 266 267% create Internal JID from External JID 268% intdomain-intnode@extdomain/resource -> intnode@intdomain/resource 269create_internal_jid({jid, Node, _Domain, Resource, _, _, _}) -> 270 % TODO use rayo_entities to lookup node... it's safer 271 Idx = string:str(Node, "|"), 272 case Idx > 0 of 273 true -> 274 jlib:make_jid(string:substr(Node, Idx + 1), string:substr(Node, 1, Idx - 1), Resource); 275 false -> 276 none 277 end. 278 279% Take control of entity 280% Return {true, internal entity JID} if successful 281set_entity_dcp(PcpJid, EntityJid) -> 282 SetDcp = fun() -> 283 case mnesia:wread(rayo_entities, EntityJid) of 284 [{rayo_entities, EntityJid, InternalJid, none, Type}] -> 285 % take control 286 case mnesia:write(#rayo_entities{external_jid = EntityJid, internal_jid = InternalJid, dcp_jid = PcpJid, type = Type}) of 287 ok -> 288 {true, InternalJid}; 289 Else -> 290 {error, Else} 291 end; 292 _ -> 293 {false, []} 294 end 295 end, 296 {_, Result} = mnesia:transaction(SetDcp), 297 Result. 298 299% Check if PCP has control of entity 300% Return {true, internal entity JID} if true 301is_entity_dcp(PcpJid, EntityJid) -> 302 % quick check first 303 case mnesia:dirty_read(rayo_entities, EntityJid) of 304 [{rayo_entities, EntityJid, _, none, _}] -> 305 % take control 306 set_entity_dcp(PcpJid, EntityJid); 307 [{rayo_entities, EntityJid, InternalJid, PcpJid, _}] -> 308 {true, InternalJid}; 309 [{rayo_entities, EntityJid, InternalJid, _, _}] -> 310 {false, InternalJid}; 311 [] -> 312 ?DEBUG("MOD_RAYO_GATEWAY: no match for EntityJid ~p", [EntityJid]), 313 {false, none} 314 end. 315 316% Handle presence to external domain 317route_external(From, {jid, [], _Domain, [], [], _LDomain, []} = To, {xmlelement, "presence", _Attrs, _Els} = Presence) -> 318 ?DEBUG("MOD_RAYO_GATEWAY: got client presence ~n~p", [Presence]), 319 route_client_presence(From, To, Presence), 320 ok; 321 322% Handle presence to external domain resource 323route_external(From, To, {xmlelement, "presence", _Attrs, _Els} = Presence) -> 324 ?DEBUG("MOD_RAYO_GATEWAY: got client presence to mixer ~n~p", [Presence]), 325 % TODO check if actually being sent to mixer... 326 route_client_presence_to_mixer(From, To, Presence), 327 ok; 328 329% Handle <message> to external domain 330route_external(_From, _To, {xmlelement, "message", _Attrs, _Els} = Message) -> 331 % ignore 332 ?DEBUG("MOD_RAYO_GATEWAY: got client message ~n~p", [Message]), 333 ok; 334 335% Handle <iq> to external domain 336route_external(From, {jid, [], _Domain, [], [], _LDomain, []} = To, {xmlelement, "iq", _Attrs, _Els} = IQ) -> 337 ?DEBUG("MOD_RAYO_GATEWAY: got client iq to gateway ~n~p", [IQ]), 338 case get_attribute_as_list(IQ, "type", "") of 339 "get" -> 340 case get_element(IQ, ?NS_PING, "ping") of 341 undefined -> 342 route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST); 343 _ -> 344 route_result_reply(To, From, IQ) 345 end; 346 "set" -> 347 case get_element(IQ, ?NS_RAYO, "dial") of 348 undefined-> 349 route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST); 350 _ -> 351 route_dial_call(To, From, IQ) 352 end; 353 "" -> 354 route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST) 355 end, 356 ok; 357 358% Handle <iq> to external domain resource 359route_external(From, To, {xmlelement, "iq", _Attrs, _Els} = IQ) -> 360 ?DEBUG("MOD_RAYO_GATEWAY: got client iq ~n~p", [IQ]), 361 case is_entity_dcp(From, To) of 362 {true, _} -> 363 IntFrom = internal_client(), 364 IntTo = create_internal_jid(To), 365 route_iq_request(IntFrom, IntTo, IQ, fun(IQReply) -> route_iq_response(From, To, IQ, IQReply) end); 366 {false, _} -> 367 route_error_reply(To, From, IQ, ?ERR_CONFLICT); 368 _ -> 369 route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST) 370 end, 371 ok. 372 373% Handle <presence> to internal domain 374route_internal(From, {jid, [], _Domain, [], [], _LDomain, []} = To, {xmlelement, "presence", _Attrs, _Els} = Presence) -> 375 ?DEBUG("MOD_RAYO_GATEWAY: got node presence to internal domain ~n~p", [Presence]), 376 route_server_presence(From, To, Presence), 377 ok; 378 379% Handle <presence> to internal domain resource 380route_internal(From, To, {xmlelement, "presence", _Attrs, _Els} = Presence) -> 381 ?DEBUG("MOD_RAYO_GATEWAY: got node presence to internal domain ~n~p", [Presence]), 382 case To =:= internal_client() of 383 true -> 384 route_server_presence(From, To, Presence); 385 false -> 386 % TODO implement 387 ok 388 end, 389 ok; 390 391% Handle <message> to internal domain 392route_internal(_From, _To, {xmlelement, "message", _Attrs, _Els} = Message) -> 393 ?DEBUG("MOD_RAYO_GATEWAY: got node message ~n~p", [Message]), 394 % ignore 395 ok; 396 397% Handle <iq> to internal domain. 398route_internal(From, {jid, [], _Domain, [], [], _LDomain, []} = To, {xmlelement, "iq", _Attrs, _Els} = IQ) -> 399 ?DEBUG("MOD_RAYO_GATEWAY: got node iq ~n~p", [IQ]), 400 case get_attribute_as_list(IQ, "type", "") of 401 "get" -> 402 case get_element(IQ, ?NS_PING, "ping") of 403 undefined -> 404 route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST); 405 _ -> 406 route_result_reply(To, From, IQ) 407 end; 408 "result" -> 409 ejabberd_local:process_iq_reply(From, To, jlib:iq_query_or_response_info(IQ)); 410 "error" -> 411 ejabberd_local:process_iq_reply(From, To, jlib:iq_query_or_response_info(IQ)); 412 "" -> 413 % don't allow get/set from nodes 414 route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST) 415 end, 416 ok; 417 418% Handle <iq> to internal domain resource. 419route_internal(From, To, {xmlelement, "iq", _Attrs, _Els} = IQ) -> 420 ?DEBUG("MOD_RAYO_GATEWAY: got node iq ~n~p", [IQ]), 421 case get_attribute_as_list(IQ, "type", "") of 422 "result" -> 423 ejabberd_local:process_iq_reply(From, To, jlib:iq_query_or_response_info(IQ)); 424 "error" -> 425 ejabberd_local:process_iq_reply(From, To, jlib:iq_query_or_response_info(IQ)); 426 _ -> 427 % Don't allow get/set from nodes 428 route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST) 429 end, 430 ok. 431 432% Process presence message from rayo node 433route_rayo_node_presence(From, _To, Presence) -> 434 case get_attribute_as_list(Presence, "type", "") of 435 "" -> 436 case get_element(Presence, "show") of 437 undefined -> 438 ?DEBUG("MOD_RAYO_GATEWAY: ignoring empty presence", []); 439 Show -> 440 case get_cdata_as_list(Show) of 441 "chat" -> 442 register_rayo_node(From); 443 "dnd" -> 444 unregister_rayo_node(From); 445 "xa" -> 446 unregister_rayo_node(From); 447 "" -> 448 unregister_rayo_node(From) 449 end 450 end; 451 "unavailable" -> 452 %TODO broadcast end instead? 453 unregister_rayo_node(From) 454 end, 455 ok. 456 457% Process presence from call 458route_call_presence(From, _To, Presence) -> 459 %TODO join/unjoin mixer events 460 case get_attribute_as_list(Presence, "type", "") of 461 "" -> 462 case get_element(Presence, ?NS_RAYO, "offer") of 463 undefined -> 464 route_rayo_entity_stanza(From, Presence); 465 _ -> 466 route_offer_call(From, Presence) 467 end; 468 "unavailable" -> 469 case get_element(Presence, ?NS_RAYO, "end") of 470 undefined -> 471 route_rayo_entity_stanza(From, Presence); 472 _ -> 473 route_rayo_entity_stanza(From, Presence), 474 unregister_rayo_node_entity(create_external_jid(From)) 475 end 476 end, 477 ok. 478 479% presence from node 480route_server_presence({jid, [], _Domain, [], [], _LDomain, []} = From, To, Presence) -> 481 route_rayo_node_presence(From, To, Presence), 482 ok; 483 484% presence from call/mixer 485route_server_presence(From, To, Presence) -> 486 % TODO mixer 487 route_call_presence(From, To, Presence), 488 ok. 489 490% presence from Rayo Client 491route_client_presence(From, _To, Presence) -> 492 case get_attribute_as_list(Presence, "type", "") of 493 "" -> 494 case get_element(Presence, "show") of 495 undefined -> 496 ?DEBUG("MOD_RAYO_GATEWAY: ignoring empty presence", []); 497 Show -> 498 case get_cdata_as_list(Show) of 499 "chat" -> 500 register_rayo_client(From); 501 "dnd" -> 502 unregister_rayo_client(From); 503 _ -> 504 unregister_rayo_client(From) 505 end 506 end; 507 "unavailable" -> 508 unregister_rayo_client(From); 509 _ -> 510 ok 511 end, 512 ok. 513 514% route client directed presence to mixer 515route_client_presence_to_mixer(_From, _To, _Presence) -> 516 % TODO 517 ok. 518 519% Handle offer to client 520route_offer_call(From, Offer) -> 521 % Any clients available? 522 case pick_client() of 523 none -> 524 % TODO reject? 525 ok; 526 ClientDcp -> 527 % Remember call 528 ExtFrom = create_external_jid(From), 529 register_rayo_node_entity(ExtFrom, From, ClientDcp, call), 530 ejabberd_router:route(ExtFrom, ClientDcp, Offer) 531 end, 532 ok. 533 534% convert URI to a JID 535uri_to_jid(Uri) -> 536 JidString = case string:str(Uri, "xmpp:") of 537 1 -> 538 string:substr(Uri, 6); 539 _ -> 540 Uri 541 end, 542 jlib:string_to_jid(JidString). 543 544% convert internal IQ reply to an external reply 545create_external_iq_reply(OrigIQ, {xmlelement, _, _, Els} = IQReply) -> 546 IQId = get_attribute_as_list(OrigIQ, "id", ""), 547 IQType = get_attribute_as_list(IQReply, "type", ""), 548 {xmlelement, "iq", [{"id", IQId}, {"type", IQType}], Els}. 549 550% Process dial response 551route_dial_call_response(OrigFrom, OrigTo, OrigIQ, timeout) -> 552 % TODO retry on different node? 553 route_iq_response(OrigFrom, OrigTo, OrigIQ, timeout); 554 555route_dial_call_response(OrigFrom, OrigTo, OrigIQ, IQReply) -> 556 ?DEBUG("MOD_RAYO_GATEWAY: IQ response for ~p", [OrigIQ]), 557 IQReplyPacket = jlib:iq_to_xml(IQReply), 558 case get_element(IQReplyPacket, "error") of 559 undefined -> 560 case get_element(IQReplyPacket, "ref") of 561 undefined -> 562 ok; 563 Ref -> 564 IntJid = uri_to_jid(get_attribute_as_list(Ref, "uri", "")), 565 register_rayo_node_entity(create_external_jid(IntJid), IntJid, OrigFrom, call) 566 end; 567 _ -> 568 ok 569 end, 570 ejabberd_router:route(OrigTo, OrigFrom, create_external_iq_reply(OrigIQ, IQReplyPacket)), 571 ok. 572 573% Forward dial to node 574route_dial_call(From, To, Dial) -> 575 % any nodes available? 576 case num_rayo_nodes() > 0 of 577 true -> 578 IntFrom = internal_client(), 579 case pick_rayo_node() of 580 none -> 581 route_error_reply(To, From, Dial, ?ERR_SERVICE_UNAVAILABLE); 582 NodeJid -> 583 route_iq_request(IntFrom, NodeJid, Dial, fun(IQReply) -> route_dial_call_response(From, To, Dial, IQReply) end) 584 end; 585 _ -> 586 route_error_reply(To, From, Dial, ?ERR_RESOURCE_CONSTRAINT) 587 end. 588 589% return configuration value given name 590config_value(Name) -> 591 case catch mnesia:dirty_read(rayo_config, Name) of 592 [{rayo_config, Name, Value}] -> 593 Value; 594 _ -> 595 "" 596 end. 597 598% return internal client name 599internal_client() -> 600 jlib:string_to_jid(config_value("internal_client")). 601 602% return internal domain name 603internal_domain() -> 604 jlib:string_to_jid(config_value("internal_domain")). 605 606% return external domain name 607external_domain() -> 608 jlib:string_to_jid(config_value("external_domain")). 609 610% return number of registered clients 611num_clients() -> 612 mnesia:table_info(rayo_clients, size). 613 614% return all registered client JIDs 615all_clients() -> 616 case mnesia:transaction(fun() -> mnesia:all_keys(rayo_clients) end) of 617 {atomic, Keys} -> 618 Keys; 619 _ -> 620 [] 621 end. 622 623% pick a registered client 624pick_client() -> 625 % pick at random for now... 626 case all_clients() of 627 [] -> 628 none; 629 AllClients -> 630 lists:nth(random:uniform(length(AllClients)), AllClients) 631 end. 632 633% pick a registered node 634pick_rayo_node() -> 635 % pick at random for now... 636 case all_rayo_nodes() of 637 [] -> 638 none; 639 AllNodes -> 640 lists:nth(random:uniform(length(AllNodes)), AllNodes) 641 end. 642 643% return number of registered rayo nodes 644num_rayo_nodes() -> 645 mnesia:table_info(rayo_nodes, size). 646 647% return all rayo node JIDs 648all_rayo_nodes() -> 649 case mnesia:transaction(fun() -> mnesia:all_keys(rayo_nodes) end) of 650 {atomic, Keys} -> 651 Keys; 652 _ -> 653 [] 654 end. 655 656presence(Status) -> 657 {xmlelement, "presence", [], [ 658 {xmlelement, "show", [], [ 659 {xmlcdata, Status} 660 ]} 661 ]}. 662 663online_presence() -> 664 presence(<<"chat">>). 665 666offline_presence() -> 667 presence(<<"dnd">>). 668 669route_to_list(From, ToList, Stanza) -> 670 lists:map(fun(To) -> ejabberd_router:route(From, To, Stanza) end, ToList), 671 ok. 672 673% route stanza from entity 674route_rayo_entity_stanza(From, Stanza) -> 675 case find_rayo_node_entity_dcp_by_int_jid(From) of 676 none -> 677 ?DEBUG("MOD_RAYO_GATEWAY: Failed to find DCP for ~p", [From]), 678 ok; 679 DcpJid -> 680 ejabberd_router:route(create_external_jid(From), DcpJid, Stanza) 681 end, 682 ok. 683 684% route IQ response from node to client 685route_iq_response(OrigFrom, OrigTo, OrigIQ, timeout) -> 686 route_error_reply(OrigTo, OrigFrom, OrigIQ, ?ERR_REMOTE_SERVER_TIMEOUT), 687 ok; 688 689route_iq_response(OrigFrom, OrigTo, OrigIQ, IQReply) -> 690 ?DEBUG("MOD_RAYO_GATEWAY: IQ response for ~p", [OrigIQ]), 691 ejabberd_router:route(OrigTo, OrigFrom, create_external_iq_reply(OrigIQ, jlib:iq_to_xml(IQReply))), 692 ok. 693 694% route IQ from client to node 695route_iq_request(From, To, {xmlelement, "iq", _Atts, Els}, ResponseCallback) -> 696 ejabberd_local:route_iq(From, To, #iq{type = set, sub_el = Els}, ResponseCallback), 697 ok. 698 699% route IQ error given request 700route_error_reply(From, To, IQ, Reason) -> 701 ejabberd_router:route(From, To, jlib:make_error_reply(IQ, Reason)), 702 ok. 703 704% route IQ result given request 705route_result_reply(From, To, IQ) -> 706 ejabberd_router:route(From, To, jlib:make_result_iq_reply(IQ)), 707 ok. 708 709% XML parsing helpers 710 711get_element(Element, Name) -> 712 case xml:get_subtag(Element, Name) of 713 false -> 714 undefined; 715 Subtag -> 716 Subtag 717 end. 718 719get_element(Element, NS, Name) -> 720 case get_element(Element, Name) of 721 undefined -> 722 undefined; 723 Subtag -> 724 case get_attribute_as_list(Subtag, "xmlns", "") of 725 "" -> 726 undefined; 727 NS -> 728 Subtag 729 end 730 end. 731 732get_cdata_as_list(undefined) -> 733 ""; 734 735get_cdata_as_list(Element) -> 736 xml:get_tag_cdata(Element). 737 738get_element_cdata_as_list(Element, Name) -> 739 get_cdata_as_list(get_element(Element, Name)). 740 741get_element_cdata_as_list(Element, NS, Name) -> 742 get_cdata_as_list(get_element(Element, NS, Name)). 743 744get_element_attribute_as_list(Element, Name, AttrName, Default) -> 745 get_attribute_as_list(get_element(Element, Name), AttrName, Default). 746 747get_element_attribute_as_list(Element, NS, Name, AttrName, Default) -> 748 get_attribute_as_list(get_element(Element, NS, Name), AttrName, Default). 749 750get_attribute_as_list(undefined, _Name, _Default) -> 751 undefined; 752 753get_attribute_as_list(Element, Name, Default) -> 754 case xml:get_tag_attr_s(Name, Element) of 755 "" -> 756 Default; 757 Attr -> 758 Attr 759 end. 760