Advertisement
Guest User

mod_wall

a guest
Jul 6th, 2011
363
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Erlang 11.21 KB | None | 0 0
  1. %%%   Copyright 2011 EnerNOC, Inc
  2. %%%
  3. %%%   Licensed under the Apache License, Version 2.0 (the "License");
  4. %%%   you may not use this file except in compliance with the License.
  5. %%%   You may obtain a copy of the License at
  6. %%%
  7. %%%       http://www.apache.org/licenses/LICENSE-2.0
  8. %%%
  9. %%%   Unless required by applicable law or agreed to in writing, software
  10. %%%   distributed under the License is distributed on an "AS IS" BASIS,
  11. %%%   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. %%%   See the License for the specific language governing permissions and
  13. %%%   limitations under the License.
  14.  
  15. %%%----------------------------------------------------------------------
  16. %%% File    : mod_wall.erl
  17. %%% Author  : Ed Mackowiak <emackowiak@enernoc.com>
  18. %%% Purpose : flexible IQ filtering by IQ namespace, sender and receiver
  19. %%% Created : June 1st, 2011
  20. %%% Id      : $Id$
  21. %%%----------------------------------------------------------------------
  22.  
  23. %%%----------------------------------------------------------------------
  24. %%% # Example module config (all options may be ommited to disable extra functionality)
  25. %%%  [{debug, true}, {audit_collector, "test1@emackowiak-mbp.local/Psi+"} ]
  26. %%%    - If debug=true, info messages will be logged to ejabberd.log
  27. %%%    - If audit_collector is specified, a message will be send to the collector whenever a packet is dropped
  28. %%%
  29. %%% # Example ACL/Access Rule Configuration: (mod_wall uses the acl/access rule style of mod_filter)
  30. %%%
  31. %%% -Block all disco IQ packets
  32. %%% {access, 'http://jabber.org/protocol/disco#info', [{deny, all}]}
  33. %%%           ^ xmlns (atom) is used as rule name ^
  34. %%%
  35. %%% -Block some disco IQ packets
  36. %%% {access, 'http://jabber.org/protocol/disco#info', [{deny, all},
  37. %%%                                                    {allow, some-acl},
  38. %%%                                                    {allow, another-acl},  ]}
  39. %%%
  40. %%% -Block some disco IQ packets, depending on their intended recipient
  41. %%% {access, 'http://jabber.org/protocol/disco#info', [{deny, all},
  42. %%%                                                    {allow, some-acl},
  43. %%%                                                    {restrict-receivers, all},  ]}
  44. %%%                                                     ^^ if a rule is found instead of allow/deny,
  45. %%%                                                     filtering is performed against the packet receiver
  46. %%%                                                    
  47. %%% {access, restrict-receivers, [{deny, denied-receiver-acl}, {allow,all}]}
  48. %%%                                                             ^^ guard (important)
  49.  
  50.  
  51. -module(mod_wall).
  52. -behavior(gen_mod).
  53. -include("ejabberd.hrl").
  54. -include("jlib.hrl").
  55.  
  56. -export([start/2, stop/1, on_filter_packet/1, logger/3, apply_rule/1]).
  57.  
  58.    
  59. start(_Host, _Opts) ->
  60.     ejabberd_hooks:add(filter_packet, global, ?MODULE, on_filter_packet, 50),
  61.     ok.
  62.    
  63. stop(_Host) ->
  64.     ejabberd_hooks:delete(filter_packet, global, ?MODULE, on_filter_packet, 50),
  65.     ok.
  66.    
  67. on_filter_packet(drop) ->
  68.     drop;
  69.    
  70. on_filter_packet({From, To, Packet} = Input) ->
  71.     %%?INFO_MSG("Running filter.  Debug: ~p ", [gen_mod:get_module_opt(global, ?MODULE, debug, false)]),
  72.    
  73.     %returns true if packet type is a set or a get, else false
  74.     ApplyRule = apply_rule( Packet ),
  75.    
  76.     case ApplyRule of
  77.         false -> Input;
  78.         _  ->
  79.    
  80.             Prefix = get_prefix( Packet ),
  81.             if
  82.                 Prefix == "" -> FilteredNs = get_iq_namespace( Packet );
  83.                 true         -> FilteredNs = get_iq_namespace( Packet, Prefix )
  84.             end,
  85.            
  86.             info("Namespace: ~p ", [FilteredNs]),
  87.             % if the packet isnt an IQ, let it through
  88.             case FilteredNs of
  89.                 "" ->
  90.                     info("Blank Ns pass through ", []),
  91.                     Input;
  92.                 _ ->
  93.                     FromAccess = match_rule_default_allow(global, list_to_atom(FilteredNs), From),
  94.                     case FromAccess of
  95.                     deny ->    
  96.                         info("From Filter: Dropping packet: ~p ", [Input]),
  97.                         % log the error
  98.                         logger( From, To, Packet ),
  99.                         %ErrorText = "<error code='403' type='auth'><forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>",
  100.                         XmlBody = make_error(Packet, To, From),
  101.                         %%   Trickiness right here:  switched To and From to send back to sender
  102.                         ejabberd_router:route(To, From, XmlBody),
  103.                         drop;
  104.                     allow ->
  105.                         info("From Filter: Pass through: ~p ", [Input]),
  106.                         Input;
  107.                     ToAccessRule ->
  108.                         ToAccess = acl:match_rule(global, ToAccessRule, To),
  109.                        
  110.                         case ToAccess of
  111.                         allow ->
  112.                             info("To Filter: Pass through: ~p ", [Input]),
  113.                             Input;
  114.                         deny ->
  115.                             info("To Filter: Dropping packet: ~p ", [Input]),
  116.                             % log the error
  117.                             logger( From, To, Packet ),
  118.                             XmlBody = make_error(Packet, To, From),
  119.                             %%   Trickiness right here:  switched To and From to send back to sender
  120.                             ejabberd_router:route(To, From, XmlBody),
  121.                             drop
  122.                         end
  123.                     end
  124.             end    
  125.     end.
  126.    
  127. %%----------------------------------------------------------------------
  128. %% Function: logger/3
  129. %% Purpose: Handle logging of denied packets.
  130. %% Args:    From, To, Packet
  131. %%----------------------------------------------------------------------
  132.  
  133. logger( From, To, Packet ) ->
  134.  
  135.     Collector = gen_mod:get_module_opt(global, ?MODULE, audit_collector, none),
  136.    
  137.     case Collector of
  138.     none ->
  139.         none;
  140.     _    ->
  141.         %route the error to the collector
  142.         CollectorJID = jlib:string_to_jid(Collector),
  143.         XmlBody =  {xmlelement, "message",
  144.            [ {"type", "audit-error"},
  145.              {"from", "audit-service"},
  146.              {"to", Collector}                     
  147.            ],
  148.            [
  149.              Packet
  150.            ]
  151.         },
  152.         ejabberd_router:route(From, CollectorJID, XmlBody)
  153.     end.
  154.  
  155.  
  156. %%----------------------------------------------------------------------
  157. %% ## UTILITY FUNCTIONS ##
  158. %% The following functions are used to handle xml, log messages, etc
  159. %%----------------------------------------------------------------------
  160.  
  161.  
  162. %%----------------------------------------------------------------------
  163. %% Function: make_error/2
  164. %% Purpose: Generate an error (403) IQ.
  165. %% Args:    Packet = xmlelement; the packet to be dropped, required to get ID for response
  166. %%          To, From = jid;
  167. %% Returns: An xmlelement ready for routing
  168. %%----------------------------------------------------------------------
  169. make_error(Packet, To, From) ->
  170.     Id = get_iq_id(Packet),
  171.         XmlBody =  {xmlelement, "iq",
  172.            [ {"type", "error"},
  173.              {"from", jlib:jid_to_string(To)},
  174.              {"to", jlib:jid_to_string(From)},
  175.              {"id", Id }                       
  176.            ],
  177.            [ { xmlelement, "error", [{"code", "403"}, {"type", "auth"}],
  178.                 [ { xmlelement, "forbidden",
  179.                     [ { "xmlns", "urn:ietf:params:xml:ns:xmpp-stanzas" } ], []
  180.                   }
  181.                 ]
  182.              }
  183.            ]
  184.         },
  185.         XmlBody.
  186.  
  187.  
  188. %%----------------------------------------------------------------------
  189. %% Function: get_prefix/1
  190. %% Purpose: Get namespace prefixing of a packet, if present
  191. %% Args:    Packet = xmlelement;
  192. %% Returns: A string containing the namespace prefix, or a blank string if not present
  193. %%----------------------------------------------------------------------
  194. %% example:  <ns0:control />  would return "ns0"
  195. %% example:  <control /> would return ""
  196. get_prefix( Packet ) ->
  197.    
  198.     Name = get_iq_payload_name( Packet ),
  199.     %erlang:display( Name ),
  200.     get_prefix( Name, "" ).
  201.    
  202. get_prefix( [], _ ) ->
  203.     "";
  204.  
  205. get_prefix( [ Head | Tail ], Prefix ) ->
  206.     case [Head] of
  207.     ":" -> % XML is prefixed...
  208.         Prefix;
  209.     _   -> % no prefix found
  210.         get_prefix( Tail, Prefix ++ [Head] )
  211.     end.
  212.        
  213.        
  214. %%----------------------------------------------------------------------
  215. %% Function: get_iq_Id/1
  216. %% Purpose:  Returns the ID of an IQ packet
  217. %% Args:     xmlelement;
  218. %% Returns:  A string containing the ID if the argument is an IQ, else an empty string
  219. %%----------------------------------------------------------------------
  220. get_iq_id({xmlelement, Name, Attrs, _Els}) when Name == "iq" ->
  221.     xml:get_attr_s("id", Attrs);
  222.    
  223. get_iq_id(_) ->
  224.     "".
  225.        
  226. %%----------------------------------------------------------------------
  227. %% Function: apply_rule/1
  228. %% Purpose:  Decide if ACLs and Access Rules should apply to a packet
  229. %% Args:     xmlelement;
  230. %% Returns:  Returns the true if the IQ is of type set or get, else false
  231. %%----------------------------------------------------------------------
  232. apply_rule({xmlelement, Name, Attrs, _Els}) when Name == "iq" ->
  233.    
  234.     Type = xml:get_attr_s("type", Attrs),
  235.    
  236.     case Type of
  237.         "set"    -> true;
  238.         "get"    -> true;
  239.         "error"  -> false;
  240.         "cancel" -> false;
  241.         _        -> false
  242.     end;
  243.    
  244. apply_rule(_) ->
  245.     false.
  246.        
  247. %%----------------------------------------------------------------------
  248. %% Function: get_iq_namespace/1
  249. %% Purpose:  Gets the namespace of a non-prefixed IQ payload
  250. %% Args:     xmlelement; an IQ
  251. %% Returns:  A string containing the xmlns if present and the packet is an IQ, else an empty string
  252. %%----------------------------------------------------------------------       
  253.  
  254. get_iq_namespace({xmlelement, Name, _Attrs, Els}) when Name == "iq" ->
  255.     case xml:remove_cdata(Els) of
  256.     [{xmlelement, _Name2, Attrs2, _Els2}] ->
  257.         xml:get_attr_s("xmlns", Attrs2);
  258.     _ ->
  259.         ""
  260.     end;
  261. get_iq_namespace(_) ->
  262.     "".
  263.  
  264.  
  265. %%----------------------------------------------------------------------
  266. %% Function: get_iq_namespace/2
  267. %% Purpose:  Gets the namespace of a prefixed IQ payload
  268. %% Args:     xmlelement; an IQ
  269. %%           string; the namespace prefix
  270. %% Returns:  A string containing the non-prefixed xmlns if present and the packet is an IQ, else an empty string
  271. %%----------------------------------------------------------------------
  272. get_iq_namespace({xmlelement, Name, _Attrs, Els}, Prefix) when Name == "iq" ->
  273.     case xml:remove_cdata(Els) of
  274.     [{xmlelement, _Name2, Attrs2, _Els2}] ->
  275.         xml:get_attr_s("xmlns"++":"++ Prefix, Attrs2);
  276.     _ ->
  277.         ""
  278.     end;
  279. get_iq_namespace(_, _Prefix) ->
  280.     "".
  281.  
  282. %%----------------------------------------------------------------------
  283. %% Function: get_iq_payload_name/1
  284. %% Purpose:  Get the tag name of an IQ payload
  285. %% Args:     xmlelement; an IQ
  286. %% Returns:  A string containing the tag name of the payload of the IQ if it exists, else an empty string
  287. %%----------------------------------------------------------------------
  288. get_iq_payload_name({xmlelement, Name, _Attrs, Els}) when Name == "iq" ->
  289.     case xml:remove_cdata(Els) of
  290.     [{xmlelement, Name2, _Attrs2, _Els2}] ->
  291.         Name2;
  292.     _ ->
  293.         ""
  294.     end;
  295.  
  296. get_iq_payload_name(_) ->
  297.     "".
  298.  
  299. %%----------------------------------------------------------------------
  300. %% Function: match_rule_default_allow/3
  301. %% Purpose:  Look for a matching access rule, but default to 'allow', rather than 'deny'
  302. %% Args:     global;
  303. %%           Rule; an atomized IQ namespace
  304. %%           JID;
  305. %% Returns:  allow | deny
  306. %%----------------------------------------------------------------------
  307. match_rule_default_allow(global, Rule, JID) ->
  308.     case ejabberd_config:get_global_option({access, Rule, global}) of
  309.     % let undefined namespaces through...
  310.     undefined ->
  311.         allow;
  312.     _ ->
  313.         acl:match_rule(global, Rule, JID)
  314.     end.
  315.    
  316. %%----------------------------------------------------------------------
  317. %% Function: info/2
  318. %% Purpose:  Log info messages (if the module 'debug' option is set to true
  319. %% Args:     Str; The format string to be logged
  320. %%           Arg; A list of elements to be logged
  321. %% Returns:  nothing
  322. %%----------------------------------------------------------------------
  323. info(Str, Arg) ->
  324.  
  325.     Debug = gen_mod:get_module_opt(global, ?MODULE, debug, false),
  326.  
  327.     if
  328.     Debug == true  -> ?INFO_MSG( Str, Arg );
  329.     Debug == false -> false
  330.     end.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement