1 %%%----------------------------------------------------------------------
2 %%% File    : mod_register.erl
3 %%% Author  : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : Inband registration support
5 %%% Created :  8 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2012   ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23 %%% 02111-1307 USA
24 %%%
25 %%%----------------------------------------------------------------------
26 
27 
28 -module(mod_register).
29 
30 -author('alexey@process-one.net').
31 
32 -behaviour(gen_mod).
33 
34 -export([start/2, stop/1, stream_feature_register/2, unauthenticated_iq_register/4,
35 	 try_register/5, process_iq/3]).
36 
37 -include_lib("exmpp/include/exmpp.hrl").
38 
39 -include("ejabberd.hrl").
40 
41 start(Host, Opts) when is_list(Host) -> start(list_to_binary(Host), Opts);
42 start(HostB, Opts) ->
43     IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
44     gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_INBAND_REGISTER, ?MODULE,
45 				  process_iq, IQDisc),
46     gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_INBAND_REGISTER, ?MODULE,
47 				  process_iq, IQDisc),
48     ejabberd_hooks:add(c2s_stream_features, HostB, ?MODULE, stream_feature_register, 50),
49     ejabberd_hooks:add(c2s_unauthenticated_iq, HostB, ?MODULE,
50 		       unauthenticated_iq_register, 50),
51     mnesia:create_table(mod_register_ip,
52 			[{ram_copies, [node()]}, {local_content, true},
53 			 {attributes, [key, value]}]),
54     mnesia:add_table_copy(mod_register_ip, node(), ram_copies),
55     ok.
56 
57 stop(Host) ->
58     HostB = list_to_binary(Host),
59     ejabberd_hooks:delete(c2s_stream_features, HostB, ?MODULE, stream_feature_register,
60 			  50),
61     ejabberd_hooks:delete(c2s_unauthenticated_iq, HostB, ?MODULE,
62 			  unauthenticated_iq_register, 50),
63     gen_iq_handler:remove_iq_handler(ejabberd_local, HostB, ?NS_INBAND_REGISTER),
64     gen_iq_handler:remove_iq_handler(ejabberd_sm, HostB, ?NS_INBAND_REGISTER).
65 
66 stream_feature_register(Acc, _Host) ->
67     [#xmlel{ns = ?NS_INBAND_REGISTER_FEAT, name = register} | Acc].
68 
69 unauthenticated_iq_register(_Acc, Server, #iq{ns = ?NS_INBAND_REGISTER} = IQ_Rec, IP) ->
70     Address = case IP of
71 		{A, _Port} -> A;
72 		_ -> undefined
73 	      end,
74     BareJID = exmpp_jid:make(Server),
75     ResIQ = process_iq(exmpp_jid:make(), BareJID, IQ_Rec, Address),
76     exmpp_iq:iq_to_xmlel(ResIQ, BareJID, exmpp_jid:make());
77 unauthenticated_iq_register(Acc, _Server, _IQ, _IP) -> Acc.
78 
79 process_iq(From, To, IQ) ->
80     process_iq(From, To, IQ,
81 	       {exmpp_jid:prep_node_as_list(From), exmpp_jid:prep_domain_as_list(From),
82 		exmpp_jid:prep_resource_as_list(From)}).
83 
84 process_iq(From, To, #iq{type = Type, lang = Lang, payload = SubEl, id = ID} = IQ_Rec,
85 	   Source) ->
86     IsCaptchaEnabled = case gen_mod:get_module_opt(exmpp_jid:domain_as_list(To), ?MODULE,
87 						   captcha_protected, false)
88 			   of
89 			 true -> true;
90 			 _ -> false
91 		       end,
92     case Type of
93       set ->
94 	  UTag = exmpp_xml:get_element(SubEl, username),
95 	  PTag = exmpp_xml:get_element(SubEl, password),
96 	  RTag = exmpp_xml:get_element(SubEl, remove),
97 	  Server = exmpp_jid:prep_domain_as_list(To),
98 	  Access = gen_mod:get_module_opt(Server, ?MODULE, access, all),
99 	  AllowRemove = allow == acl:match_rule(Server, Access, From),
100 	  if (UTag /= undefined) and (RTag /= undefined) and AllowRemove ->
101 		 User = exmpp_xml:get_cdata_as_list(UTag),
102 		 case {exmpp_jid:node_as_list(From), exmpp_jid:prep_domain_as_list(From)}
103 		     of
104 		   {User, Server} ->
105 		       ejabberd_auth:remove_user(User, Server),
106 		       exmpp_iq:result(IQ_Rec, SubEl);
107 		   _ ->
108 		       if PTag /= undefined ->
109 			      Password = exmpp_xml:get_cdata_as_list(PTag),
110 			      case ejabberd_auth:remove_user(User, Server, Password) of
111 				ok -> exmpp_iq:result(IQ_Rec, SubEl);
112 				                                        %% TODO FIXME: This piece of
113 									%% code does not work since
114 									%% the code have been changed
115 									%% to allow several auth
116 									%% modules.  lists:foreach can
117 									%% only return ok:
118 				not_allowed -> exmpp_iq:error(IQ_Rec, 'not-allowed');
119 				not_exists -> exmpp_iq:error(IQ_Rec, 'item-not-found');
120 				_ -> exmpp_iq:error(IQ_Rec, 'internal-server-error')
121 			      end;
122 			  true -> exmpp_iq:error(IQ_Rec, 'bad-request')
123 		       end
124 		 end;
125 	     (UTag == undefined) and (RTag /= undefined) and AllowRemove ->
126 		 case {exmpp_jid:node_as_list(From), exmpp_jid:prep_domain_as_list(From),
127 		       exmpp_jid:resource_as_list(From)}
128 		     of
129 		   {User, Server, Resource} ->
130 		       ResIQ = exmpp_iq:result(IQ_Rec, SubEl),
131 		       ejabberd_router:route(exmpp_jid:make(User, Server, Resource),
132 					     exmpp_jid:make(User, Server, Resource),
133 					     exmpp_iq:iq_to_xmlel(ResIQ)),
134 		       ejabberd_auth:remove_user(User, Server),
135 		       ignore;
136 		   _ -> exmpp_iq:error(IQ_Rec, 'not-allowed')
137 		 end;
138 	     (UTag /= undefined) and (PTag /= undefined) ->
139 		 User = exmpp_xml:get_cdata_as_list(UTag),
140 		 Password = exmpp_xml:get_cdata_as_list(PTag),
141 		 try_register_or_set_password(User, Server, Password, From, IQ_Rec, SubEl,
142 					      Source, Lang, not IsCaptchaEnabled);
143 	     IsCaptchaEnabled ->
144 		 case ejabberd_captcha:process_reply(SubEl) of
145 		   ok ->
146 		       case process_xdata_submit(SubEl) of
147 			 {ok, User, Password} ->
148 			     try_register_or_set_password(User, Server, Password, From,
149 							  IQ_Rec, SubEl, Source, Lang,
150 							  true);
151 			 _ -> exmpp_iq:error(IQ_Rec, 'bad-request')
152 		       end;
153 		   {error, malformed} -> exmpp_iq:error(IQ_Rec, 'bad-request');
154 		   _ ->
155 		       ErrText = translate:translate(Lang, "Captcha test failed"),
156 		       exmpp_iq:error(IQ_Rec, 'not-allowed', ErrText)
157 		 end;
158 	     true -> exmpp_iq:error(IQ_Rec, 'bad-request')
159 	  end;
160       get ->
161 	  {IsRegistered, UsernameSubels, QuerySubels} = case
162 							  {exmpp_jid:node_as_list(From),
163 							   exmpp_jid:prep_domain_as_list(From)}
164 							    of
165 							  {User, Server}
166 							      when is_list(User) and
167 								     is_list(Server) ->
168 							      case
169 								ejabberd_auth:is_user_exists(User,
170 											     Server)
171 								  of
172 								true ->
173 								    {true,
174 								     [#xmlcdata{cdata =
175 										    list_to_binary(User)}],
176 								     [#xmlel{ns =
177 										 ?NS_INBAND_REGISTER,
178 									     name =
179 										 registered}]};
180 								false ->
181 								    {false,
182 								     [#xmlcdata{cdata =
183 										    list_to_binary(User)}],
184 								     []}
185 							      end;
186 							  _ -> {false, [], []}
187 							end,
188 	  if IsCaptchaEnabled and not IsRegistered ->
189 		 TopInstrEl = #xmlel{ns = ?NS_INBAND_REGISTER, name = instructions,
190 				     children =
191 					 [#xmlcdata{cdata =
192 							list_to_binary(translate:translate(Lang,
193 											   "You need an x:data capable client with CAPTCHA support to register"))}]},
194 		 InstrEl = #xmlel{ns = ?NS_INBAND_REGISTER, name = instructions,
195 				  children =
196 				      [#xmlcdata{cdata =
197 						     list_to_binary(translate:translate(Lang,
198 											"Choose a username and password to register with this server"))}]},
199 		 UField = #xmlel{ns = ?NS_DATA_FORMS, name = field,
200 				 attrs =
201 				     [?XMLATTR(<<"var">>,
202 					       <<"username">>),
203 				      ?XMLATTR(<<"type">>,
204 					       <<"text-single">>),
205 				      ?XMLATTR(<<"label">>,
206 					       (translate:translate(Lang, "User")))],
207 				 children =
208 				     [#xmlel{ns = ?NS_DATA_FORMS, name = required}]},
209 		 PField = #xmlel{ns = ?NS_DATA_FORMS, name = field,
210 				 attrs =
211 				     [?XMLATTR(<<"var">>,
212 					       <<"password">>),
213 				      ?XMLATTR(<<"type">>,
214 					       <<"text-private">>),
215 				      ?XMLATTR(<<"label">>,
216 					       (translate:translate(Lang, "Password")))],
217 				 children =
218 				     [#xmlel{ns = ?NS_DATA_FORMS, name = required}]},
219 		 case ejabberd_captcha:create_captcha_x(ID, To, Lang,
220 							[InstrEl, UField, PField])
221 		     of
222 		   {ok, CaptchaEls} ->
223 		       Result = #xmlel{ns = ?NS_INBAND_REGISTER, name = 'query',
224 				       children = [TopInstrEl | CaptchaEls]},
225 		       exmpp_iq:result(IQ_Rec, Result);
226 		   error ->
227 		       ErrText = translate:translate(Lang,
228 						     "Unable to generate a captcha"),
229 		       exmpp_iq:error(IQ_Rec, 'internal-server-error', ErrText)
230 		 end;
231 	     true ->
232 		 Result = #xmlel{ns = ?NS_INBAND_REGISTER, name = 'query',
233 				 children =
234 				     [#xmlel{ns = ?NS_INBAND_REGISTER,
235 					     name = instructions,
236 					     children =
237 						 [#xmlcdata{cdata =
238 								list_to_binary(translate:translate(Lang,
239 												   "Choose a username and password to register with this server"))}]},
240 				      #xmlel{ns = ?NS_INBAND_REGISTER, name = username,
241 					     children = UsernameSubels},
242 				      #xmlel{ns = ?NS_INBAND_REGISTER, name = password}
243 				      | QuerySubels]},
244 		 exmpp_iq:result(IQ_Rec, Result)
245 	  end
246     end.
247 
248 try_register_or_set_password(User, Server, Password, From, IQ_Rec, SubEl, Source, Lang,
249 			     CaptchaSucceed) ->
250     case {exmpp_jid:node_as_list(From), exmpp_jid:prep_domain_as_list(From)} of
251       {User, Server} -> try_set_password(User, Server, Password, IQ_Rec, SubEl, Lang);
252       _ when CaptchaSucceed ->
253 	  case check_from(From, Server) of
254 	    allow ->
255 		case try_register(User, Server, Password, Source, Lang) of
256 		  ok -> exmpp_iq:result(IQ_Rec, SubEl);
257 		  {error, Error} -> exmpp_iq:error(IQ_Rec, Error)
258 		end;
259 	    deny -> exmpp_iq:error(IQ_Rec, forbidden)
260 	  end;
261       _ -> exmpp_iq:error(IQ_Rec, 'not-allowed')
262     end.
263 
264 %% @doc Try to change password and return IQ response
265 try_set_password(User, Server, Password, IQ_Rec, SubEl, Lang) ->
266     case is_strong_password(Server, Password) of
267       true -> try_set_password_strong(User, Server, Password, IQ_Rec, SubEl, Lang);
268       false ->
269 	  ErrText = translate:translate(Lang, "The password is too weak"),
270 	  exmpp_iq:error(IQ_Rec, 'not-acceptable', ErrText)
271     end.
272 
273 try_set_password_strong(User, Server, Password, IQ_Rec, SubEl, _Lang) ->
274     case ejabberd_auth:set_password(User, Server, Password) of
275       ok -> exmpp_iq:result(IQ_Rec, SubEl);
276       {error, empty_password} -> exmpp_iq:error(IQ_Rec, 'bad-request');
277       {error, not_allowed} -> exmpp_iq:error(IQ_Rec, 'not-allowed');
278       {error, invalid_jid} -> exmpp_iq:error(IQ_Rec, 'item-not-found');
279       _ -> exmpp_iq:error(IQ_Rec, 'internal-server-error')
280     end.
281 
282 try_register_strong(User, Server, Password, Source, _Lang, JID) ->
283     case ejabberd_auth:try_register(User, Server, Password) of
284       {atomic, ok} ->
285 	  send_welcome_message(JID), send_registration_notifications(JID, Source), ok;
286       Error ->
287 	  remove_timeout(Source),
288 	  case Error of
289 	    {atomic, exists} -> {error, conflict};
290 	    {error, invalid_jid} -> {error, 'jid-malformed'};
291 	    {error, not_allowed} -> {error, 'not-allowed'};
292 	    {error, _Reason} -> {error, 'internal-server-error'}
293 	  end
294     end.
295 
296 try_register(User, Server, Password, SourceRaw, Lang) ->
297     case exmpp_stringprep:is_node(User) of
298       false -> {error, 'bad-request'};
299       _ ->
300 	  JID = exmpp_jid:make(User, Server),
301 	  Access = gen_mod:get_module_opt(Server, ?MODULE, access, all),
302 	  IPAccess = get_ip_access(Server),
303 	  case {acl:match_rule(Server, Access, JID), check_ip_access(SourceRaw, IPAccess)}
304 	      of
305 	    {deny, _} -> {error, forbidden};
306 	    {_, deny} -> {error, forbidden};
307 	    {allow, allow} ->
308 		Source = may_remove_resource(SourceRaw),
309 		case check_timeout(Source) of
310 		  true ->
311 		      case is_strong_password(Server, Password) of
312 			true ->
313 			    try_register_strong(User, Server, Password, Source, Lang,
314 						JID);
315 			false ->
316 			    ErrText = "The password is too weak",
317 			    {error,
318 			     exmpp_stanza:error(?NS_JABBER_CLIENT, 'not-acceptable',
319 						{Lang, ErrText})}
320 		      end;
321 		  false ->
322 		      ErrText = "Users are not allowed to register accounts so quickly",
323 		      {error,
324 		       exmpp_stanza:error(?NS_JABBER_CLIENT, 'resource-constraint',
325 					  {Lang, ErrText})}
326 		end
327 	  end
328     end.
329 
330 send_welcome_message(JID) ->
331     Host = exmpp_jid:prep_domain_as_list(JID),
332     case gen_mod:get_module_opt(Host, ?MODULE, welcome_message, {"", ""}) of
333       {"", ""} -> ok;
334       {Subj, Body} ->
335 	  ejabberd_router:route(exmpp_jid:make(Host), JID,
336 				exmpp_message:normal(Subj, Body));
337       _ -> ok
338     end.
339 
340 send_registration_notifications(UJID, Source) ->
341     Host = exmpp_jid:prep_domain_as_list(UJID),
342     case gen_mod:get_module_opt(Host, ?MODULE, registration_watchers, []) of
343       [] -> ok;
344       JIDs when is_list(JIDs) ->
345 	  Body =
346 	      lists:flatten(io_lib:format("[~s] The account ~s was registered from IP address ~s on node ~w using ~p.",
347 					  [get_time_string(), exmpp_jid:to_list(UJID),
348 					   ip_to_string(Source), node(), ?MODULE])),
349 	  lists:foreach(fun (S) ->
350 				try JID = exmpp_jid:parse(S),
351 				    ejabberd_router:route(exmpp_jid:make(Host), JID,
352 							  exmpp_message:chat(Body))
353 				catch
354 				  _ -> ok
355 				end
356 			end,
357 			JIDs);
358       _ -> ok
359     end.
360 
361 check_from(JID, Server) ->
362     case {exmpp_jid:node(JID), exmpp_jid:prep_domain(JID)} of
363       {undefined, undefined} -> allow;
364       _ ->
365 	  Access = gen_mod:get_module_opt(Server, ?MODULE, access_from, none),
366 	  acl:match_rule(Server, Access, JID)
367     end.
368 
369 check_timeout(undefined) -> true;
370 check_timeout(Source) ->
371     Timeout = case ejabberd_config:get_local_option(registration_timeout) of
372 		undefined -> 600;
373 		TO -> TO
374 	      end,
375     if is_integer(Timeout) ->
376 	   {MSec, Sec, _USec} = now(),
377 	   Priority = -(MSec * 1000000 + Sec),
378 	   CleanPriority = Priority + Timeout,
379 	   F = fun () ->
380 		       Treap = case mnesia:read(mod_register_ip, treap, write) of
381 				 [] -> treap:empty();
382 				 [{mod_register_ip, treap, T}] -> T
383 			       end,
384 		       Treap1 = clean_treap(Treap, CleanPriority),
385 		       case treap:lookup(Source, Treap1) of
386 			 error ->
387 			     Treap2 = treap:insert(Source, Priority, [], Treap1),
388 			     mnesia:write({mod_register_ip, treap, Treap2}),
389 			     true;
390 			 {ok, _, _} ->
391 			     mnesia:write({mod_register_ip, treap, Treap1}), false
392 		       end
393 	       end,
394 	   case mnesia:transaction(F) of
395 	     {atomic, Res} -> Res;
396 	     {aborted, Reason} ->
397 		 ?ERROR_MSG("mod_register: timeout check error: ~p~n", [Reason]), true
398 	   end;
399        true -> true
400     end.
401 
402 clean_treap(Treap, CleanPriority) ->
403     case treap:is_empty(Treap) of
404       true -> Treap;
405       false ->
406 	  {_Key, Priority, _Value} = treap:get_root(Treap),
407 	  if Priority > CleanPriority ->
408 		 clean_treap(treap:delete_root(Treap), CleanPriority);
409 	     true -> Treap
410 	  end
411     end.
412 
413 remove_timeout(undefined) -> true;
414 remove_timeout(Source) ->
415     Timeout = case ejabberd_config:get_local_option(registration_timeout) of
416 		undefined -> 600;
417 		TO -> TO
418 	      end,
419     if is_integer(Timeout) ->
420 	   F = fun () ->
421 		       Treap = case mnesia:read(mod_register_ip, treap, write) of
422 				 [] -> treap:empty();
423 				 [{mod_register_ip, treap, T}] -> T
424 			       end,
425 		       Treap1 = treap:delete(Source, Treap),
426 		       mnesia:write({mod_register_ip, treap, Treap1}),
427 		       ok
428 	       end,
429 	   case mnesia:transaction(F) of
430 	     {atomic, ok} -> ok;
431 	     {aborted, Reason} ->
432 		 ?ERROR_MSG("mod_register: timeout remove error: ~p~n", [Reason]), ok
433 	   end;
434        true -> ok
435     end.
436 
437 ip_to_string(Source) when is_tuple(Source) -> inet_parse:ntoa(Source);
438 ip_to_string(undefined) -> "undefined";
439 ip_to_string(_) -> "unknown".
440 
441 get_time_string() -> write_time(erlang:localtime()).
442 
443 %% Function copied from ejabberd_logger_h.erl and customized
444 write_time({{Y, Mo, D}, {H, Mi, S}}) ->
445     io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", [Y, Mo, D, H, Mi, S]).
446 
447 process_xdata_submit(El) ->
448     case exmpp_xml:get_element(El, x) of
449       #xmlel{} = Xdata ->
450 	  Fields = jlib:parse_xdata_submit(Xdata),
451 	  case catch {proplists:get_value("username", Fields),
452 		      proplists:get_value("password", Fields)}
453 	      of
454 	    {[User | _], [Pass | _]} -> {ok, User, Pass};
455 	    _ -> error
456 	  end;
457       _ -> error
458     end.
459 
460 is_strong_password(Server, Password) ->
461     LServer = exmpp_stringprep:nameprep(Server),
462     case gen_mod:get_module_opt(LServer, ?MODULE, password_strength, 0) of
463       Entropy when is_number(Entropy), Entropy >= 0 ->
464 	  if Entropy == 0 -> true;
465 	     true -> ejabberd_auth:entropy(Password) >= Entropy
466 	  end;
467       Wrong -> ?WARNING_MSG("Wrong value for password_strength option: ~p", [Wrong]), true
468     end.
469 
470 %%%
471 %%% ip_access management
472 %%%
473 
474 
475 may_remove_resource({U, S, _}) -> {U, S, ""};
476 may_remove_resource(From) -> From.
477 
478 get_ip_access(Host) ->
479     IPAccess = gen_mod:get_module_opt(Host, ?MODULE, ip_access, []),
480     lists:flatmap(fun ({Access, S}) ->
481 			  case parse_ip_netmask(S) of
482 			    {ok, IP, Mask} -> [{Access, IP, Mask}];
483 			    error ->
484 				?ERROR_MSG("mod_register: invalid network specification: ~p",
485 					   [S]),
486 				[]
487 			  end
488 		  end,
489 		  IPAccess).
490 
491 parse_ip_netmask(S) ->
492     case string:tokens(S, "/") of
493       [IPStr] ->
494 	  case inet_parse:address(IPStr) of
495 	    {ok, {_, _, _, _} = IP} -> {ok, IP, 32};
496 	    {ok, {_, _, _, _, _, _, _, _} = IP} -> {ok, IP, 128};
497 	    _ -> error
498 	  end;
499       [IPStr, MaskStr] ->
500 	  case catch list_to_integer(MaskStr) of
501 	    Mask when is_integer(Mask), Mask >= 0 ->
502 		case inet_parse:address(IPStr) of
503 		  {ok, {_, _, _, _} = IP} when Mask =< 32 -> {ok, IP, Mask};
504 		  {ok, {_, _, _, _, _, _, _, _} = IP} when Mask =< 128 -> {ok, IP, Mask};
505 		  _ -> error
506 		end;
507 	    _ -> error
508 	  end;
509       _ -> error
510     end.
511 
512 check_ip_access(_Source, []) -> allow;
513 check_ip_access({User, Server, Resource}, IPAccess) ->
514     JID = exmpp_jid:make(User, Server, Resource),
515     case ejabberd_sm:get_user_ip(JID) of
516       {IPAddress, _PortNumber} -> check_ip_access(IPAddress, IPAccess);
517       _ -> true
518     end;
519 check_ip_access({_, _, _, _} = IP, [{Access, {_, _, _, _} = Net, Mask} | IPAccess]) ->
520     IPInt = ip_to_integer(IP),
521     NetInt = ip_to_integer(Net),
522     M = bnot (1 bsl (32 - Mask) - 1),
523     if IPInt band M =:= NetInt band M -> Access;
524        true -> check_ip_access(IP, IPAccess)
525     end;
526 check_ip_access({_, _, _, _, _, _, _, _} = IP,
527 		[{Access, {_, _, _, _, _, _, _, _} = Net, Mask} | IPAccess]) ->
528     IPInt = ip_to_integer(IP),
529     NetInt = ip_to_integer(Net),
530     M = bnot (1 bsl (128 - Mask) - 1),
531     if IPInt band M =:= NetInt band M -> Access;
532        true -> check_ip_access(IP, IPAccess)
533     end;
534 check_ip_access(IP, [_ | IPAccess]) -> check_ip_access(IP, IPAccess).
535 
536 ip_to_integer({IP1, IP2, IP3, IP4}) -> IP1 bsl 8 bor IP2 bsl 8 bor IP3 bsl 8 bor IP4;
537 ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8}) ->
538     IP1 bsl 16 bor IP2 bsl 16 bor IP3 bsl 16 bor IP4 bsl 16 bor IP5 bsl 16 bor IP6 bsl 16
539       bor IP7
540       bsl 16
541       bor IP8.