Skip to content
Browse files

Commit ejabberd_ircd.

  • Loading branch information...
0 parents commit 226b3c937341924ba19e2aa04dabc9a3d61fac2f @legoscia committed Jan 14, 2007
Showing with 1,226 additions and 0 deletions.
  1. +33 −0 README.txt
  2. +1 −0 build.bat
  3. +2 −0 build.sh
  4. +193 −0 conf/ejabberd.cfg.example
  5. +838 −0 src/ejabberd_ircd.erl
  6. +159 −0 src/ejabberd_sup.erl
33 README.txt
@@ -0,0 +1,33 @@
+This is an IRC server frontend to ejabberd. It supports a subset of
+the IRC protocol, allowing IRC users to use a subset of Jabber MUC
+functions. Users log in with their username and password, just as if
+they were Jabber users. Therefore, configuring the IRC interface to
+use an anonymous authentication backend is probably what users expect.
+Channel names are translated to MUC rooms on a particular MUC service.
+
+The most obvious missing functions in this module are operator actions
+and a command to list channels.
+
+Note that this module changes ejabberd_sup.erl, which may collide with
+other extensions. Merging the changes by hand should not be
+difficult.
+
+Something like this should be inserted in the "listen" section of the
+configuration file:
+
+ {6667, ejabberd_ircd, [{access, c2s},
+ {host, "localhost"},
+ {muc_host, "conference.localhost"},
+ {encoding, "utf-8"},
+ {mappings,
+ [{"#esperanto", "esperanto@conference.jabber.org"}]}]},
+
+access is the ACL matching users allowed to use the IRC backend.
+host is the hostname part of the JIDs of IRC users.
+muc_host is the MUC service hosting IRC "channels".
+encoding is the encoding that IRC users are expected to use.
+mappings is a list of mappings from channel names to MUC rooms on
+other MUC services.
+
+Author: Magnus Henoch, xmpp:legoscia@jabber.cd.chalmers.se,
+mailto:henoch@dtek.chalmers.se
1 build.bat
@@ -0,0 +1 @@
+erl -pa ../../ejabberd-dev/trunk/ebin -pa ebin -make
2 build.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+erl -pa ../../ejabberd-dev/trunk/ebin -pz ebin -make
193 conf/ejabberd.cfg.example
@@ -0,0 +1,193 @@
+% $Id: ejabberd.cfg.example 577 2006-06-07 08:38:37Z mremond $
+
+%override_acls.
+
+
+% Users that have admin access. Add line like one of the following after you
+% will be successfully registered on server to get admin access:
+%{acl, admin, {user, "aleksey"}}.
+%{acl, admin, {user, "ermine"}}.
+
+% Blocked users:
+%{acl, blocked, {user, "test"}}.
+
+% Local users:
+{acl, local, {user_regexp, ""}}.
+
+% Another examples of ACLs:
+%{acl, jabberorg, {server, "jabber.org"}}.
+%{acl, aleksey, {user, "aleksey", "jabber.ru"}}.
+%{acl, test, {user_regexp, "^test"}}.
+%{acl, test, {user_glob, "test*"}}.
+
+% Everybody can create pubsub nodes
+{access, pubsub_createnode, [{allow, all}]}.
+
+% Only admins can use configuration interface:
+{access, configure, [{allow, admin}]}.
+
+% Every username can be registered via in-band registration:
+% You could replace {allow, all} with {deny, all} to prevent user from using
+% in-band registration
+{access, register, [{allow, all}]}.
+
+% After successful registration user will get message with following subject
+% and body:
+{welcome_message,
+ {"Welcome!",
+ "Welcome to Jabber Service. "
+ "For information about Jabber visit http://jabber.org"}}.
+% Replace them with 'none' if you don't want to send such message:
+%{welcome_message, none}.
+
+% List of people who will get notifications about registered users
+%{registration_watchers, ["admin1@localhost",
+% "admin2@localhost"]}.
+
+% Only admins can send announcement messages:
+{access, announce, [{allow, admin}]}.
+
+
+% Only non-blocked users can use c2s connections:
+{access, c2s, [{deny, blocked},
+ {allow, all}]}.
+
+% Set shaper with name "normal" to limit traffic speed to 1000B/s
+{shaper, normal, {maxrate, 1000}}.
+
+% Set shaper with name "fast" to limit traffic speed to 50000B/s
+{shaper, fast, {maxrate, 50000}}.
+
+% For all users except admins used "normal" shaper
+{access, c2s_shaper, [{none, admin},
+ {normal, all}]}.
+
+% For all S2S connections used "fast" shaper
+{access, s2s_shaper, [{fast, all}]}.
+
+% Admins of this server are also admins of MUC service:
+{access, muc_admin, [{allow, admin}]}.
+
+% All users are allowed to use MUC service:
+{access, muc, [{allow, all}]}.
+
+% This rule allows access only for local users:
+{access, local, [{allow, local}]}.
+
+
+% Authentication method. If you want to use internal user base, then use
+% this line:
+{auth_method, internal}.
+
+% For LDAP authentication use these lines instead of above one:
+%{auth_method, ldap}.
+%{ldap_servers, ["localhost"]}. % List of LDAP servers
+%{ldap_uidattr, "uid"}. % LDAP attribute that holds user ID
+%{ldap_base, "dc=example,dc=com"}. % Search base of LDAP directory
+%{ldap_rootdn, "dc=example,dc=com"}. % LDAP manager
+%{ldap_password, "******"}. % Password to LDAP manager
+
+% For authentication via external script use the following:
+%{auth_method, external}.
+%{extauth_program, "/path/to/authentication/script"}.
+
+% For authentication via ODBC use the following:
+%{auth_method, odbc}.
+%{odbc_server, "DSN=ejabberd;UID=ejabberd;PWD=ejabberd"}.
+
+
+% Host name:
+{hosts, ["localhost"]}.
+
+%% Define the maximum number of time a single user is allowed to connect:
+{max_user_sessions, 10}.
+
+%% Anonymous login support:
+%% auth_method: anonymous
+%% anonymous_protocol: sasl_anon|login_anon|both
+%% allow_multiple_connections: true|false
+%%{host_config, "public.example.org", [{auth_method, anonymous},
+%% {allow_multiple_connections, false},
+%% {anonymous_protocol, sasl_anon}]}.
+%% To use both anonymous and internal authentication:
+%%{host_config, "public.example.org", [{auth_method, [anonymous, internal]}]}.
+
+% Default language for server messages
+{language, "en"}.
+
+% Listened ports:
+{listen,
+ [{5222, ejabberd_c2s, [{access, c2s}, {shaper, c2s_shaper},
+ {max_stanza_size, 65536},
+ starttls, {certfile, "./ssl.pem"}]},
+ {5223, ejabberd_c2s, [{access, c2s},
+ {max_stanza_size, 65536},
+ tls, {certfile, "./ssl.pem"}]},
+ {6667, ejabberd_ircd, [{access, c2s},
+ {host, "localhost"},
+ {muc_host, "conference.localhost"},
+ {encoding, "utf-8"},
+ {mappings,
+ [{"#esperanto", "esperanto@conference.jabber.org"}]}]},
+ % Use these two lines instead if TLS support is not compiled
+ %{5222, ejabberd_c2s, [{access, c2s}, {shaper, c2s_shaper}]},
+ %{5223, ejabberd_c2s, [{access, c2s}, ssl, {certfile, "./ssl.pem"}]},
+ {5269, ejabberd_s2s_in, [{shaper, s2s_shaper},
+ {max_stanza_size, 131072}
+ ]},
+ {5280, ejabberd_http, [http_poll, web_admin]},
+ {8888, ejabberd_service, [{access, all},
+ {hosts, ["icq.localhost", "sms.localhost"],
+ [{password, "secret"}]}]}
+ ]}.
+
+
+% Use STARTTLS+Dialback for S2S connections
+{s2s_use_starttls, true}.
+{s2s_certfile, "./ssl.pem"}.
+%{domain_certfile, "example.org", "./example_org.pem"}.
+%{domain_certfile, "example.com", "./example_com.pem"}.
+
+% If SRV lookup fails, then port 5269 is used to communicate with remote server
+{outgoing_s2s_port, 5269}.
+
+
+% Used modules:
+{modules,
+ [
+ {mod_register, [{access, register}]},
+ {mod_roster, []},
+ {mod_privacy, []},
+ {mod_adhoc, []},
+ {mod_configure, []}, % Depends on mod_adhoc
+ {mod_configure2, []},
+ {mod_disco, []},
+ {mod_stats, []},
+ {mod_vcard, []},
+ {mod_offline, []},
+ {mod_announce, [{access, announce}]}, % Depends on mod_adhoc
+ {mod_echo, [{host, "echo.localhost"}]},
+ {mod_private, []},
+ {mod_irc, []},
+% Default options for mod_muc:
+% host: "conference." ++ ?MYNAME
+% access: all
+% access_create: all
+% access_admin: none (only room creator has owner privileges)
+ {mod_muc, [{access, muc},
+ {access_create, muc},
+ {access_admin, muc_admin}]},
+% {mod_muc_log, []},
+% {mod_shared_roster, []},
+ {mod_pubsub, [{access_createnode, pubsub_createnode}]},
+ {mod_time, []},
+ {mod_last, []},
+ {mod_version, []}
+ ]}.
+
+
+
+
+% Local Variables:
+% mode: erlang
+% End:
838 src/ejabberd_ircd.erl
@@ -0,0 +1,838 @@
+-module(ejabberd_ircd).
+-author('henoch@dtek.chalmers.se').
+-update_info({update, 0}).
+
+-behaviour(gen_fsm).
+
+%% External exports
+-export([start/2,
+ start_link/2,
+ socket_type/0]).
+
+%% gen_fsm callbacks
+-export([init/1,
+ wait_for_nick/2,
+ wait_for_cmd/2,
+ handle_event/3,
+ handle_sync_event/4,
+ code_change/4,
+ handle_info/3,
+ terminate/3
+ ]).
+
+%-define(ejabberd_debug, true).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+
+-define(DICT, dict).
+
+-record(state, {socket,
+ sockmod,
+ access,
+ encoding,
+ shaper,
+ host,
+ muc_host,
+ sid = none,
+ pass = "",
+ nick = none,
+ user = none,
+ %% joining is a mapping from room JIDs to nicknames
+ %% received but not yet forwarded
+ joining = ?DICT:new(),
+ joined = ?DICT:new(),
+ %% mapping certain channels to certain rooms
+ channels_to_jids = ?DICT:new(),
+ jids_to_channels = ?DICT:new()
+ }).
+-record(channel, {participants = [],
+ topic = ""}).
+
+-record(line, {prefix, command, params}).
+
+%-define(DBGFSM, true).
+
+-ifdef(DBGFSM).
+-define(FSMOPTS, [{debug, [trace]}]).
+-else.
+-define(FSMOPTS, []).
+-endif.
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+start(SockData, Opts) ->
+ supervisor:start_child(ejabberd_ircd_sup, [SockData, Opts]).
+
+start_link(SockData, Opts) ->
+ gen_fsm:start_link(ejabberd_ircd, [SockData, Opts], ?FSMOPTS).
+
+socket_type() ->
+ raw.
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from gen_fsm
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% Func: init/1
+%% Returns: {ok, StateName, StateData} |
+%% {ok, StateName, StateData, Timeout} |
+%% ignore |
+%% {stop, StopReason}
+%%----------------------------------------------------------------------
+init([{SockMod, Socket}, Opts]) ->
+ iconv:start(),
+ Access = case lists:keysearch(access, 1, Opts) of
+ {value, {_, A}} -> A;
+ _ -> all
+ end,
+ Shaper = case lists:keysearch(shaper, 1, Opts) of
+ {value, {_, S}} -> S;
+ _ -> none
+ end,
+ Host = case lists:keysearch(host, 1, Opts) of
+ {value, {_, H}} -> H;
+ _ -> ?MYNAME
+ end,
+ MucHost = case lists:keysearch(muc_host, 1, Opts) of
+ {value, {_, M}} -> M;
+ _ -> "conference." ++ ?MYNAME
+ end,
+ Encoding = case lists:keysearch(encoding, 1, Opts) of
+ {value, {_, E}} -> E;
+ _ -> "utf-8"
+ end,
+ ChannelMappings = case lists:keysearch(mappings, 1, Opts) of
+ {value, {_, C}} -> C;
+ _ -> []
+ end,
+ {ChannelToJid, JidToChannel} =
+ lists:foldl(fun({Channel, Room}, {CToJ, JToC}) ->
+ RoomJID = jlib:string_to_jid(Room),
+ BareChannel = case Channel of
+ [$#|R] -> R;
+ _ -> Channel
+ end,
+ {?DICT:store(BareChannel, RoomJID, CToJ),
+ ?DICT:store(RoomJID, BareChannel, JToC)}
+ end, {?DICT:new(), ?DICT:new()},
+ ChannelMappings),
+ inet:setopts(Socket, [list, {packet, line}, {active, true}]),
+ %%_ReceiverPid = start_ircd_receiver(Socket, SockMod),
+ {ok, wait_for_nick, #state{socket = Socket,
+ sockmod = SockMod,
+ access = Access,
+ encoding = Encoding,
+ shaper = Shaper,
+ host = Host,
+ muc_host = MucHost,
+ channels_to_jids = ChannelToJid,
+ jids_to_channels = JidToChannel
+ }}.
+
+handle_info({tcp, _Socket, Line}, StateName, StateData) ->
+ DecodedLine = iconv:convert(StateData#state.encoding, "utf-8", Line),
+ Parsed = parse_line(DecodedLine),
+ ?MODULE:StateName({line, Parsed}, StateData);
+handle_info({tcp_closed, _}, _StateName, StateData) ->
+ {stop, normal, StateData};
+handle_info({route, _, _, _} = Event, StateName, StateData) ->
+ ?MODULE:StateName(Event, StateData);
+handle_info(Info, StateName, StateData) ->
+ ?ERROR_MSG("Unexpected info: ~p", [Info]),
+ {next_state, StateName, StateData}.
+
+handle_sync_event(Event, _From, StateName, StateData) ->
+ ?ERROR_MSG("Unexpected sync event: ~p", [Event]),
+ Reply = ok,
+ {reply, Reply, StateName, StateData}.
+
+handle_event(_Event, StateName, StateData) ->
+ {next_state, StateName, StateData}.
+
+code_change(_OldVsn, StateName, StateData, _Extra) ->
+ {ok, StateName, StateData}.
+terminate(_Reason, _StateName, #state{socket = Socket, sockmod = SockMod,
+ sid = SID, host = Host, nick = Nick,
+ joined = JoinedDict} = State) ->
+ ?INFO_MSG("closing IRC connection for ~p", [Nick]),
+ case SID of
+ none ->
+ ok;
+ _ ->
+ Packet = {xmlelement, "presence",
+ [{"type", "unavailable"}], []},
+ FromJID = user_jid(State),
+ ?DICT:map(fun(ChannelJID, _ChannelData) ->
+ ejabberd_router:route(FromJID, ChannelJID, Packet)
+ end, JoinedDict),
+ ejabberd_sm:close_session_unset_presence(SID, Nick, Host, "irc", "Logged out")
+ end,
+ gen_tcp = SockMod,
+ ok = gen_tcp:close(Socket),
+ ok.
+
+
+wait_for_nick({line, #line{command = "PASS", params = Params}}, State) ->
+ ?DEBUG("in wait_for_nick", []),
+ Pass = hd(Params),
+ ?DEBUG("got password", []),
+ {next_state, wait_for_nick, State#state{pass = Pass}};
+wait_for_nick({line, #line{command = "NICK", params = Params}}, State) ->
+ ?DEBUG("in wait_for_nick", []),
+ Nick = hd(Params),
+ Pass = State#state.pass,
+ Server = State#state.host,
+
+ JID = jlib:make_jid(Nick, Server, "irc"),
+ case JID of
+ error ->
+ ?DEBUG("invalid nick '~p'", [Nick]),
+ send_reply('ERR_ERRONEUSNICKNAME', [Nick, "Erroneous nickname"], State),
+ {next_state, wait_for_nick, State};
+ _ ->
+ case acl:match_rule(Server, State#state.access, JID) of
+ deny ->
+ ?DEBUG("access denied for '~p'", [Nick]),
+ send_reply('ERR_NICKCOLLISION', [Nick, "Nickname collision"], State),
+ {next_state, wait_for_nick, State};
+ allow ->
+ case ejabberd_auth:check_password(Nick, Server, Pass) of
+ false ->
+ ?DEBUG("auth failed for '~p'", [Nick]),
+ send_reply('ERR_NICKCOLLISION', [Nick, "Authentication failed"], State),
+ {next_state, wait_for_nick, State};
+ true ->
+ ?DEBUG("good nickname '~p'", [Nick]),
+ SID = {now(), self()},
+ ejabberd_sm:open_session(
+ SID, Nick, Server, "irc"),
+ send_command("", "001", [Nick, "IRC interface of ejabberd server "++Server], State),
+ send_reply('RPL_MOTDSTART', [Nick, "- "++Server++" Message of the day - "], State),
+ send_reply('RPL_MOTD', [Nick, "- This is the IRC interface of the ejabberd server "++Server++"."], State),
+ send_reply('RPL_MOTD', [Nick, "- Your full JID is "++Nick++"@"++Server++"/irc."], State),
+ send_reply('RPL_MOTD', [Nick, "- Channel #whatever corresponds to MUC room whatever@"++State#state.muc_host++"."], State),
+ send_reply('RPL_MOTD', [Nick, "- This IRC interface is quite immature. You will probably find bugs."], State),
+ send_reply('RPL_MOTD', [Nick, "- Have a good time!"], State),
+ send_reply('RPL_ENDOFMOTD', [Nick, "End of /MOTD command"], State),
+ {next_state, wait_for_cmd, State#state{nick = Nick, sid = SID, pass = ""}}
+ end
+ end
+ end;
+wait_for_nick(Event, State) ->
+ ?DEBUG("in wait_for_nick", []),
+ ?INFO_MSG("unexpected event ~p", [Event]),
+ {next_state, wait_for_nick, State}.
+
+wait_for_cmd({line, #line{command = "USER", params = [_Username, _Hostname, _Servername, _Realname]}}, State) ->
+ %% Yeah, like we care.
+ {next_state, wait_for_cmd, State};
+wait_for_cmd({line, #line{command = "JOIN", params = Params}}, State) ->
+ {ChannelsString, KeysString} =
+ case Params of
+ [C, K] ->
+ {C, K};
+ [C] ->
+ {C, []}
+ end,
+ Channels = string:tokens(ChannelsString, ","),
+ Keys = string:tokens(KeysString, ","),
+ NewState = join_channels(Channels, Keys, State),
+ {next_state, wait_for_cmd, NewState};
+
+wait_for_cmd({line, #line{command = "PART", params = [ChannelsString | MaybeMessage]}}, State) ->
+ Message = case MaybeMessage of
+ [] -> nothing;
+ [M] -> M
+ end,
+ Channels = string:tokens(ChannelsString, ","),
+ NewState = part_channels(Channels, State, Message),
+ {next_state, wait_for_cmd, NewState};
+
+wait_for_cmd({line, #line{command = "PRIVMSG", params = [To, Text]}}, State) ->
+ Recipients = string:tokens(To, ","),
+ FromJID = user_jid(State),
+ lists:foreach(
+ fun(Rcpt) ->
+ case Rcpt of
+ [$# | Roomname] ->
+ Packet = {xmlelement, "message",
+ [{"type", "groupchat"}],
+ [{xmlelement, "body", [],
+ filter_cdata(translate_action(Text))}]},
+ ToJID = channel_to_jid(Roomname, State),
+ ejabberd_router:route(FromJID, ToJID, Packet);
+ _ ->
+ case string:tokens(Rcpt, "#") of
+ [Nick, Channel] ->
+ Packet = {xmlelement, "message",
+ [{"type", "chat"}],
+ [{xmlelement, "body", [],
+ filter_cdata(translate_action(Text))}]},
+ ToJID = channel_nick_to_jid(Nick, Channel, State),
+ ejabberd_router:route(FromJID, ToJID, Packet);
+ _ ->
+ send_command(Rcpt, "NOTICE", [State#state.nick,
+ "Your message to "++
+ Rcpt++
+ " was dropped. "
+ "Try sending it to "++Rcpt++
+ "#somechannel."], State)
+ end
+ end
+ end, Recipients),
+ {next_state, wait_for_cmd, State};
+
+wait_for_cmd({line, #line{command = "PING", params = Params}}, State) ->
+ {Token, Whom} =
+ case Params of
+ [A] ->
+ {A, ""};
+ [A, B] ->
+ {A, B}
+ end,
+ if Whom == ""; Whom == State#state.host ->
+ %% Ping to us
+ send_command("", "PONG", [State#state.host, Token], State);
+ true ->
+ %% Ping to someone else
+ ?DEBUG("ignoring ping to ~s", [Whom]),
+ ok
+ end,
+ {next_state, wait_for_cmd, State};
+
+wait_for_cmd({line, #line{command = "TOPIC", params = Params}}, State) ->
+ case Params of
+ [Channel] ->
+ %% user asks for topic
+ case ?DICT:find(channel_to_jid(Channel, State),
+ State#state.joined) of
+ {ok, #channel{topic = Topic}} ->
+ case Topic of
+ "" ->
+ send_reply('RPL_NOTOPIC', ["No topic is set"], State);
+ _ ->
+ send_reply('RPL_TOPIC', [Topic], State)
+ end;
+ _ ->
+ send_reply('ERR_NOTONCHANNEL', ["You're not on that channel"], State)
+ end;
+ [Channel, NewTopic] ->
+ Packet =
+ {xmlelement, "message",
+ [{"type", "groupchat"}],
+ [{xmlelement, "subject", [], filter_cdata(NewTopic)}]},
+ FromJID = user_jid(State),
+ ToJID = channel_to_jid(Channel, State),
+ ejabberd_router:route(FromJID, ToJID, Packet)
+ end,
+ {next_state, wait_for_cmd, State};
+
+wait_for_cmd({line, #line{command = "MODE", params = [ModeOf | Params]}}, State) ->
+ case ModeOf of
+ [$# | Channel] ->
+ ChannelJid = channel_to_jid(Channel, State),
+ Joined = ?DICT:find(ChannelJid, State#state.joined),
+ case Joined of
+ {ok, _ChannelData} ->
+ case Params of
+ [] ->
+ %% This is where we could mirror some advanced MUC
+ %% properties.
+ %%send_reply('RPL_CHANNELMODEIS', [Channel, Modes], State);
+ send_reply('ERR_NOCHANMODES', [Channel], State);
+ ["b"] ->
+ send_reply('RPL_ENDOFBANLIST', [Channel, "Ban list not available"], State);
+ _ ->
+ send_reply('ERR_UNKNOWNCOMMAND', ["MODE", io_lib:format("MODE ~p not understood", [Params])], State)
+ end;
+ _ ->
+ send_reply('ERR_NOTONCHANNEL', [Channel, "You're not on that channel"], State)
+ end;
+ Nick ->
+ if Nick == State#state.nick ->
+ case Params of
+ [] ->
+ send_reply('RPL_UMODEIS', [], State);
+ [Flags|_] ->
+ send_reply('ERR_UMODEUNKNOWNFLAG', [Flags, "No MODE flags supported"], State)
+ end;
+ true ->
+ send_reply('ERR_USERSDONTMATCH', ["Can't change mode for other users"], State)
+ end
+ end,
+ {next_state, wait_for_cmd, State};
+
+wait_for_cmd({line, #line{command = "QUIT"}}, State) ->
+ %% quit message is ignored for now
+ {stop, normal, State};
+
+wait_for_cmd({line, #line{command = Unknown, params = Params} = Line}, State) ->
+ ?INFO_MSG("Unknown command: ~p", [Line]),
+ send_reply('ERR_UNKNOWNCOMMAND', [Unknown, "Unknown command or arity: " ++
+ Unknown ++ "/" ++ integer_to_list(length(Params))], State),
+ {next_state, wait_for_cmd, State};
+
+wait_for_cmd({route, From, _To, {xmlelement, "presence", Attrs, Els} = El}, State) ->
+ Type = xml:get_attr_s("type", Attrs),
+ FromRoom = jlib:jid_remove_resource(From),
+ FromNick = From#jid.resource,
+
+ Channel = jid_to_channel(From, State),
+ MyNick = State#state.nick,
+ IRCSender = make_irc_sender(FromNick, FromRoom, State),
+
+ Joining = ?DICT:find(FromRoom, State#state.joining),
+ Joined = ?DICT:find(FromRoom, State#state.joined),
+ case {Joining, Joined, Type} of
+ {{ok, BufferedNicks}, _, ""} ->
+ case BufferedNicks of
+ [] ->
+ %% If this is the first presence, tell the
+ %% client that it's joining.
+ send_command(make_irc_sender(MyNick, FromRoom, State),
+ "JOIN", [Channel], State);
+ _ ->
+ ok
+ end,
+
+ NewRole = case find_el("x", ?NS_MUC_USER, Els) of
+ nothing ->
+ "";
+ XMucEl ->
+ xml:get_path_s(XMucEl, [{elem, "item"}, {attr, "role"}])
+ end,
+ NewBufferedNicks = [{FromNick, NewRole} | BufferedNicks],
+ ?DEBUG("~s is present in ~s. we now have ~p.",
+ [FromNick, Channel, NewBufferedNicks]),
+ %% We receive our own presence last. XXX: there
+ %% are some status codes here. See XEP-0045,
+ %% section 7.1.3.
+ NewState =
+ case FromNick of
+ MyNick ->
+ send_reply('RPL_NAMREPLY',
+ [MyNick, "=",
+ Channel,
+ lists:append(
+ lists:map(
+ fun({Nick, Role}) ->
+ case Role of
+ "moderator" ->
+ "@";
+ "participant" ->
+ "+";
+ _ ->
+ ""
+ end ++ Nick ++ " "
+ end, NewBufferedNicks))],
+ State),
+ send_reply('RPL_ENDOFNAMES',
+ [Channel,
+ "End of /NAMES list"],
+ State),
+ NewJoiningDict = ?DICT:erase(FromRoom, State#state.joining),
+ ChannelData = #channel{participants = NewBufferedNicks},
+ NewJoinedDict = ?DICT:store(FromRoom, ChannelData, State#state.joined),
+ State#state{joining = NewJoiningDict,
+ joined = NewJoinedDict};
+ _ ->
+ NewJoining = ?DICT:store(FromRoom, NewBufferedNicks, State#state.joining),
+ State#state{joining = NewJoining}
+ end,
+ {next_state, wait_for_cmd, NewState};
+ {{ok, _BufferedNicks}, _, "error"} ->
+ NewState =
+ case FromNick of
+ MyNick ->
+ %% we couldn't join the room
+ {ReplyCode, ErrorDescription} =
+ case xml:get_subtag(El, "error") of
+ {xmlelement, _, _, _} = ErrorEl ->
+ {ErrorName, ErrorText} = parse_error(ErrorEl),
+ {case ErrorName of
+ "forbidden" -> 'ERR_INVITEONLYCHAN';
+ _ -> 'ERR_NOSUCHCHANNEL'
+ end,
+ if is_list(ErrorText) ->
+ ErrorName ++ ": " ++ ErrorText;
+ true ->
+ ErrorName
+ end};
+ _ ->
+ {'ERR_NOSUCHCHANNEL', "Unknown error"}
+ end,
+ send_reply(ReplyCode, [Channel, ErrorDescription], State),
+
+ NewJoiningDict = ?DICT:erase(FromRoom, State#state.joining),
+ State#state{joining = NewJoiningDict};
+ _ ->
+ ?ERROR_MSG("ignoring presence of type ~s from ~s while joining room",
+ [Type, jlib:jid_to_string(From)]),
+ State
+ end,
+ {next_state, wait_for_cmd, NewState};
+ %% Presence in a channel we have already joined
+ {_, {ok, _}, ""} ->
+ %% Someone enters
+ send_command(IRCSender, "JOIN", [Channel], State),
+ {next_state, wait_for_cmd, State};
+ {_, {ok, _}, _} ->
+ %% Someone leaves
+ send_command(IRCSender, "PART", [Channel], State),
+ {next_state, wait_for_cmd, State};
+ _ ->
+ ?INFO_MSG("unexpected presence from ~s", [jlib:jid_to_string(From)]),
+ {next_state, wait_for_cmd, State}
+ end;
+
+wait_for_cmd({route, From, _To, {xmlelement, "message", Attrs, Els} = El}, State) ->
+ Type = xml:get_attr_s("type", Attrs),
+ case Type of
+ "groupchat" ->
+ ChannelJID = jlib:jid_remove_resource(From),
+ case ?DICT:find(ChannelJID, State#state.joined) of
+ {ok, #channel{} = ChannelData} ->
+ FromChannel = jid_to_channel(From, State),
+ FromNick = From#jid.resource,
+ Subject = xml:get_path_s(El, [{elem, "subject"}, cdata]),
+ Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
+ XDelay = lists:any(fun({xmlelement, "x", XAttrs, _}) ->
+ xml:get_attr_s("xmlns", XAttrs) == ?NS_DELAY;
+ (_) ->
+ false
+ end, Els),
+ if
+ Subject /= "" ->
+ CleanSubject = lists:map(fun($\n) ->
+ $\ ;
+ (C) -> C
+ end, Subject),
+ send_command(make_irc_sender(From, State),
+ "TOPIC", [FromChannel, CleanSubject], State),
+ NewChannelData = ChannelData#channel{topic = CleanSubject},
+ NewState = State#state{joined = ?DICT:store(jlib:jid_remove_resource(From), NewChannelData, State#state.joined)},
+ {next_state, wait_for_cmd, NewState};
+ not XDelay, FromNick == State#state.nick ->
+ %% there is no message echo in IRC.
+ %% we let the backlog through, though.
+ {next_state, wait_for_cmd, State};
+ true ->
+ BodyLines = string:tokens(Body, "\n"),
+ lists:foreach(
+ fun(Line) ->
+ Line1 =
+ case Line of
+ [$/, $m, $e, $ | Action] ->
+ [1]++"ACTION "++Action++[1];
+ _ ->
+ Line
+ end,
+ send_command(make_irc_sender(From, State),
+ "PRIVMSG", [FromChannel, Line1], State)
+ end, BodyLines),
+ {next_state, wait_for_cmd, State}
+ end;
+ error ->
+ ?ERROR_MSG("got message from ~s without having joined it",
+ [jlib:jid_to_string(ChannelJID)]),
+ #channel{}
+ end;
+ "error" ->
+ MucHost = State#state.muc_host,
+ ErrorFrom =
+ case From of
+ #jid{lserver = MucHost,
+ luser = Room,
+ lresource = ""} ->
+ [$#|Room];
+ #jid{lserver = MucHost,
+ luser = Room,
+ lresource = Nick} ->
+ Nick++"#"++Room;
+ #jid{} ->
+ %% ???
+ jlib:jid_to_string(From)
+ end,
+ %% I think this should cover all possible combinations of
+ %% XMPP and non-XMPP error messages...
+ ErrorText =
+ error_to_string(xml:get_subtag(El, "error")),
+ send_command("", "NOTICE", [State#state.nick,
+ "Message to "++ErrorFrom++" bounced: "++
+ ErrorText], State),
+ {next_state, wait_for_cmd, State};
+ _ ->
+ ChannelJID = jlib:jid_remove_resource(From),
+ case ?DICT:find(ChannelJID, State#state.joined) of
+ {ok, #channel{}} ->
+ FromNick = From#jid.lresource++jid_to_channel(From, State),
+ Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
+ BodyLines = string:tokens(Body, "\n"),
+ lists:foreach(
+ fun(Line) ->
+ Line1 =
+ case Line of
+ [$/, $m, $e, $ | Action] ->
+ [1]++"ACTION "++Action++[1];
+ _ ->
+ Line
+ end,
+ send_command(FromNick, "PRIVMSG", [State#state.nick, Line1], State)
+ end, BodyLines),
+ {next_state, wait_for_cmd, State};
+ _ ->
+ ?INFO_MSG("unexpected message from ~s", [jlib:jid_to_string(From)]),
+ {next_state, wait_for_cmd, State}
+ end
+ end;
+
+wait_for_cmd(Event, State) ->
+ ?INFO_MSG("unexpected event ~p", [Event]),
+ {next_state, wait_for_cmd, State}.
+
+join_channels([], _, State) ->
+ State;
+join_channels(Channels, [], State) ->
+ join_channels(Channels, [none], State);
+join_channels([Channel | Channels], [Key | Keys],
+ #state{nick = Nick} = State) ->
+ Packet =
+ {xmlelement, "presence", [],
+ [{xmlelement, "x", [{"xmlns", ?NS_MUC}],
+ case Key of
+ none ->
+ [];
+ _ ->
+ [{xmlelement, "password", [], filter_cdata(Key)}]
+ end}]},
+ From = user_jid(State),
+ To = channel_nick_to_jid(Nick, Channel, State),
+ Room = jlib:jid_remove_resource(To),
+ ejabberd_router:route(From, To, Packet),
+ NewState = State#state{joining = ?DICT:store(Room, [], State#state.joining)},
+ join_channels(Channels, Keys, NewState).
+
+part_channels([], State, _Message) ->
+ State;
+part_channels([Channel | Channels], State, Message) ->
+ Packet =
+ {xmlelement, "presence",
+ [{"type", "unavailable"}],
+ case Message of
+ nothing -> [];
+ _ -> [{xmlelement, "status", [],
+ [{xmlcdata, Message}]}]
+ end},
+ From = user_jid(State),
+ To = channel_nick_to_jid(State#state.nick, Channel, State),
+ ejabberd_router:route(From, To, Packet),
+ RoomJID = channel_to_jid(Channel, State),
+ NewState = State#state{joined = ?DICT:erase(RoomJID, State#state.joined)},
+ part_channels(Channels, NewState, Message).
+
+parse_line(Line) ->
+ {Line1, LastParam} =
+ case string:str(Line, " :") of
+ 0 ->
+ {Line, []};
+ Index ->
+ {string:substr(Line, 1, Index - 1),
+ [string:substr(Line, Index + 2) -- "\r\n"]}
+ end,
+ Tokens = string:tokens(Line1, " \r\n"),
+ {Prefix, Tokens1} =
+ case Line1 of
+ [$: | _] ->
+ {hd(Tokens), tl(Tokens)};
+ _ ->
+ {none, Tokens}
+ end,
+ [Command | Params] = Tokens1,
+ UCCommand = upcase(Command),
+ #line{prefix = Prefix, command = UCCommand, params = Params ++ LastParam}.
+
+upcase([]) ->
+ [];
+upcase([C|String]) ->
+ [if $a =< C, C =< $z ->
+ C - ($a - $A);
+ true ->
+ C
+ end | upcase(String)].
+
+%% sender
+
+send_line(Line, #state{sockmod = SockMod, socket = Socket, encoding = Encoding}) ->
+ ?DEBUG("sending ~s", [Line]),
+ gen_tcp = SockMod,
+ EncodedLine = iconv:convert("utf-8", Encoding, Line),
+ ok = gen_tcp:send(Socket, [EncodedLine, 13, 10]).
+
+send_command(Sender, Command, Params, State) ->
+ Prefix = case Sender of
+ "" ->
+ [$: | State#state.host];
+ _ ->
+ [$: | Sender]
+ end,
+ ParamString = make_param_string(Params),
+ send_line(Prefix ++ " " ++ Command ++ ParamString, State).
+
+send_reply(Reply, Params, State) ->
+ Number = case Reply of
+ 'ERR_UNKNOWNCOMMAND' ->
+ "421";
+ 'ERR_ERRONEUSNICKNAME' ->
+ "432";
+ 'ERR_NICKCOLLISION' ->
+ "436";
+ 'ERR_NOTONCHANNEL' ->
+ "442";
+ 'ERR_NOCHANMODES' ->
+ "477";
+ 'ERR_UMODEUNKNOWNFLAG' ->
+ "501";
+ 'ERR_USERSDONTMATCH' ->
+ "502";
+ 'RPL_UMODEIS' ->
+ "221";
+ 'RPL_CHANNELMODEIS' ->
+ "324";
+ 'RPL_NAMREPLY' ->
+ "353";
+ 'RPL_ENDOFNAMES' ->
+ "366";
+ 'RPL_BANLIST' ->
+ "367";
+ 'RPL_ENDOFBANLIST' ->
+ "368";
+ 'RPL_NOTOPIC' ->
+ "331";
+ 'RPL_TOPIC' ->
+ "332";
+ 'RPL_MOTD' ->
+ "372";
+ 'RPL_MOTDSTART' ->
+ "375";
+ 'RPL_ENDOFMOTD' ->
+ "376"
+ end,
+ send_command("", Number, Params, State).
+
+make_param_string([]) -> "";
+make_param_string([LastParam]) ->
+ case {LastParam, lists:member($\ , LastParam)} of
+ {_, true} ->
+ " :" ++ LastParam;
+ {[$:|_], _} ->
+ " :" ++ LastParam;
+ {_, _} ->
+ " " ++ LastParam
+ end;
+make_param_string([Param | Params]) ->
+ case lists:member($\ , Param) of
+ false ->
+ " " ++ Param ++ make_param_string(Params)
+ end.
+
+find_el(Name, NS, [{xmlelement, N, Attrs, _} = El|Els]) ->
+ XMLNS = xml:get_attr_s("xmlns", Attrs),
+ case {Name, NS} of
+ {N, XMLNS} ->
+ El;
+ _ ->
+ find_el(Name, NS, Els)
+ end;
+find_el(_, _, []) ->
+ nothing.
+
+channel_to_jid([$#|Channel], State) ->
+ channel_to_jid(Channel, State);
+channel_to_jid(Channel, #state{muc_host = MucHost,
+ channels_to_jids = ChannelsToJids}) ->
+ case ?DICT:find(Channel, ChannelsToJids) of
+ {ok, RoomJID} -> RoomJID;
+ _ -> jlib:make_jid(Channel, MucHost, "")
+ end.
+
+channel_nick_to_jid(Nick, [$#|Channel], State) ->
+ channel_nick_to_jid(Nick, Channel, State);
+channel_nick_to_jid(Nick, Channel, #state{muc_host = MucHost,
+ channels_to_jids = ChannelsToJids}) ->
+ case ?DICT:find(Channel, ChannelsToJids) of
+ {ok, RoomJID} -> jlib:jid_replace_resource(RoomJID, Nick);
+ _ -> jlib:make_jid(Channel, MucHost, Nick)
+ end.
+
+jid_to_channel(#jid{user = Room} = RoomJID,
+ #state{jids_to_channels = JidsToChannels}) ->
+ case ?DICT:find(jlib:jid_remove_resource(RoomJID), JidsToChannels) of
+ {ok, Channel} -> [$#|Channel];
+ _ -> [$#|Room]
+ end.
+
+make_irc_sender(Nick, #jid{luser = Room} = RoomJID,
+ #state{jids_to_channels = JidsToChannels}) ->
+ case ?DICT:find(jlib:jid_remove_resource(RoomJID), JidsToChannels) of
+ {ok, Channel} -> Nick++"!"++Nick++"@"++Channel;
+ _ -> Nick++"!"++Nick++"@"++Room
+ end.
+make_irc_sender(#jid{lresource = Nick} = JID, State) ->
+ make_irc_sender(Nick, JID, State).
+
+user_jid(#state{nick = Nick, host = Host}) ->
+ jlib:make_jid(Nick, Host, "irc").
+
+filter_cdata(Msg) ->
+ [{xmlcdata, filter_message(Msg)}].
+
+filter_message(Msg) ->
+ lists:filter(
+ fun(C) ->
+ if (C < 32) and
+ (C /= 9) and
+ (C /= 10) and
+ (C /= 13) ->
+ false;
+ true -> true
+ end
+ end, Msg).
+
+translate_action(Msg) ->
+ case Msg of
+ [1, $A, $C, $T, $I, $O, $N, $ | Action] ->
+ "/me "++Action;
+ _ ->
+ Msg
+ end.
+
+parse_error({xmlelement, "error", _ErrorAttrs, ErrorEls} = ErrorEl) ->
+ ErrorTextEl = xml:get_subtag(ErrorEl, "text"),
+ ErrorName =
+ case ErrorEls -- [ErrorTextEl] of
+ [{xmlelement, ErrorReason, _, _}] ->
+ ErrorReason;
+ _ ->
+ "unknown error"
+ end,
+ ErrorText =
+ case ErrorTextEl of
+ {xmlelement, _, _, _} ->
+ xml:get_tag_cdata(ErrorTextEl);
+ _ ->
+ nothing
+ end,
+ {ErrorName, ErrorText}.
+
+error_to_string({xmlelement, "error", _ErrorAttrs, _ErrorEls} = ErrorEl) ->
+ case parse_error(ErrorEl) of
+ {ErrorName, ErrorText} when is_list(ErrorText) ->
+ ErrorName ++ ": " ++ ErrorText;
+ {ErrorName, _} ->
+ ErrorName
+ end;
+error_to_string(_) ->
+ "unknown error".
159 src/ejabberd_sup.erl
@@ -0,0 +1,159 @@
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_sup.erl
+%%% Author : Alexey Shchepin <alexey@sevcom.net>
+%%% Purpose :
+%%% Created : 31 Jan 2003 by Alexey Shchepin <alexey@sevcom.net>
+%%% Id : $Id: ejabberd_sup.erl 495 2006-01-29 04:38:31Z alexey $
+%%%----------------------------------------------------------------------
+
+-module(ejabberd_sup).
+-author('alexey@sevcom.net').
+-vsn('$Revision$ ').
+
+-behaviour(supervisor).
+
+-export([start_link/0, init/1]).
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+
+init([]) ->
+ Hooks =
+ {ejabberd_hooks,
+ {ejabberd_hooks, start_link, []},
+ permanent,
+ brutal_kill,
+ worker,
+ [ejabberd_hooks]},
+ StringPrep =
+ {stringprep,
+ {stringprep, start_link, []},
+ permanent,
+ brutal_kill,
+ worker,
+ [stringprep]},
+ Router =
+ {ejabberd_router,
+ {ejabberd_router, start_link, []},
+ permanent,
+ brutal_kill,
+ worker,
+ [ejabberd_router]},
+ SM =
+ {ejabberd_sm,
+ {ejabberd_sm, start_link, []},
+ permanent,
+ brutal_kill,
+ worker,
+ [ejabberd_sm]},
+ S2S =
+ {ejabberd_s2s,
+ {ejabberd_s2s, start_link, []},
+ permanent,
+ brutal_kill,
+ worker,
+ [ejabberd_s2s]},
+ Local =
+ {ejabberd_local,
+ {ejabberd_local, start_link, []},
+ permanent,
+ brutal_kill,
+ worker,
+ [ejabberd_local]},
+ Listener =
+ {ejabberd_listener,
+ {ejabberd_listener, start_link, []},
+ permanent,
+ infinity,
+ supervisor,
+ [ejabberd_listener]},
+ ReceiverSupervisor =
+ {ejabberd_receiver_sup,
+ {ejabberd_tmp_sup, start_link,
+ [ejabberd_receiver_sup, ejabberd_receiver]},
+ permanent,
+ infinity,
+ supervisor,
+ [ejabberd_tmp_sup]},
+ C2SSupervisor =
+ {ejabberd_c2s_sup,
+ {ejabberd_tmp_sup, start_link, [ejabberd_c2s_sup, ejabberd_c2s]},
+ permanent,
+ infinity,
+ supervisor,
+ [ejabberd_tmp_sup]},
+ IRCDSupervisor =
+ {ejabberd_ircd_sup,
+ {ejabberd_tmp_sup, start_link, [ejabberd_ircd_sup, ejabberd_ircd]},
+ permanent,
+ infinity,
+ supervisor,
+ [ejabberd_tmp_sup]},
+ S2SInSupervisor =
+ {ejabberd_s2s_in_sup,
+ {ejabberd_tmp_sup, start_link,
+ [ejabberd_s2s_in_sup, ejabberd_s2s_in]},
+ permanent,
+ infinity,
+ supervisor,
+ [ejabberd_tmp_sup]},
+ S2SOutSupervisor =
+ {ejabberd_s2s_out_sup,
+ {ejabberd_tmp_sup, start_link,
+ [ejabberd_s2s_out_sup, ejabberd_s2s_out]},
+ permanent,
+ infinity,
+ supervisor,
+ [ejabberd_tmp_sup]},
+ ServiceSupervisor =
+ {ejabberd_service_sup,
+ {ejabberd_tmp_sup, start_link,
+ [ejabberd_service_sup, ejabberd_service]},
+ permanent,
+ infinity,
+ supervisor,
+ [ejabberd_tmp_sup]},
+ HTTPSupervisor =
+ {ejabberd_http_sup,
+ {ejabberd_tmp_sup, start_link,
+ [ejabberd_http_sup, ejabberd_http]},
+ permanent,
+ infinity,
+ supervisor,
+ [ejabberd_tmp_sup]},
+ HTTPPollSupervisor =
+ {ejabberd_http_poll_sup,
+ {ejabberd_tmp_sup, start_link,
+ [ejabberd_http_poll_sup, ejabberd_http_poll]},
+ permanent,
+ infinity,
+ supervisor,
+ [ejabberd_tmp_sup]},
+ IQSupervisor =
+ {ejabberd_iq_sup,
+ {ejabberd_tmp_sup, start_link,
+ [ejabberd_iq_sup, gen_iq_handler]},
+ permanent,
+ infinity,
+ supervisor,
+ [ejabberd_tmp_sup]},
+ {ok, {{one_for_one, 10, 1},
+ [Hooks,
+ StringPrep,
+ Router,
+ SM,
+ S2S,
+ Local,
+ ReceiverSupervisor,
+ C2SSupervisor,
+ IRCDSupervisor,
+ S2SInSupervisor,
+ S2SOutSupervisor,
+ ServiceSupervisor,
+ HTTPSupervisor,
+ HTTPPollSupervisor,
+ IQSupervisor,
+ Listener]}}.
+
+

0 comments on commit 226b3c9

Please sign in to comment.
Something went wrong with that request. Please try again.