mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-24 16:23:40 +01:00
* src/mod_irc/: New IRC transport (not completed yet)
SVN Revision: 74
This commit is contained in:
parent
d19c1b3a3f
commit
a28f723243
@ -1,3 +1,7 @@
|
|||||||
|
2003-02-16 Alexey Shchepin <alexey@sevcom.net>
|
||||||
|
|
||||||
|
* src/mod_irc/: New IRC transport (not completed yet)
|
||||||
|
|
||||||
2003-02-14 Alexey Shchepin <alexey@sevcom.net>
|
2003-02-14 Alexey Shchepin <alexey@sevcom.net>
|
||||||
|
|
||||||
* src/ejabberd_service.erl: Answer "Bad Request" on unknown tags
|
* src/ejabberd_service.erl: Answer "Bad Request" on unknown tags
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
{mod_offline, []},
|
{mod_offline, []},
|
||||||
{mod_echo, [{host, "echo.e.localhost"}]},
|
{mod_echo, [{host, "echo.e.localhost"}]},
|
||||||
{mod_private, []},
|
{mod_private, []},
|
||||||
|
{mod_irc, []},
|
||||||
{mod_time, [{iqdisc, no_queue}]},
|
{mod_time, [{iqdisc, no_queue}]},
|
||||||
{mod_version, []}
|
{mod_version, []}
|
||||||
]}.
|
]}.
|
||||||
|
@ -112,7 +112,7 @@ do_route(From, To, Packet) ->
|
|||||||
Node = R#route.node,
|
Node = R#route.node,
|
||||||
case node() of
|
case node() of
|
||||||
Node ->
|
Node ->
|
||||||
Pid = R#local_route.pid,
|
Pid = R#route.pid,
|
||||||
?DEBUG("routed to process ~p~n", [Pid]),
|
?DEBUG("routed to process ~p~n", [Pid]),
|
||||||
Pid ! {route, From, To, Packet};
|
Pid ! {route, From, To, Packet};
|
||||||
_ ->
|
_ ->
|
||||||
|
44
src/mod_irc/Makefile
Normal file
44
src/mod_irc/Makefile
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# $Id$
|
||||||
|
|
||||||
|
include ../Makefile.inc
|
||||||
|
|
||||||
|
INCLUDES = -I/usr/lib/erlang/usr/include \
|
||||||
|
-I$(EI_DIR)/include \
|
||||||
|
-I/usr/local/include
|
||||||
|
|
||||||
|
LIBDIRS = -L$(EI_DIR)/lib -L/usr/local/lib
|
||||||
|
|
||||||
|
ERLSHLIBS = ../iconv_erl.so
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
OUTDIR = ..
|
||||||
|
EFLAGS = -I .. -pz ..
|
||||||
|
OBJS = \
|
||||||
|
$(OUTDIR)/mod_irc.beam \
|
||||||
|
$(OUTDIR)/mod_irc_connection.beam \
|
||||||
|
$(OUTDIR)/iconv.beam
|
||||||
|
|
||||||
|
all: $(OBJS) $(ERLSHLIBS)
|
||||||
|
|
||||||
|
$(OUTDIR)/%.beam: %.erl
|
||||||
|
erlc -W $(EFLAGS) -o $(OUTDIR) $<
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#all: $(ERLSHLIBS)
|
||||||
|
# erl -s make all report "{outdir, \"..\"}" -noinput -s erlang halt
|
||||||
|
|
||||||
|
$(ERLSHLIBS): ../%.so: %.c
|
||||||
|
gcc -Wall $(INCLUDES) $(LIBDIRS) \
|
||||||
|
$(subst ../,,$(subst .so,.c,$@)) \
|
||||||
|
-lerl_interface \
|
||||||
|
-lei \
|
||||||
|
-o $@ -fpic -shared \
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *.beam
|
||||||
|
|
||||||
|
TAGS:
|
||||||
|
etags *.erl
|
||||||
|
|
75
src/mod_irc/iconv.erl
Normal file
75
src/mod_irc/iconv.erl
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% File : iconv.erl
|
||||||
|
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||||
|
%%% Purpose : Interface to libiconv
|
||||||
|
%%% Created : 16 Feb 2003 by Alexey Shchepin <alexey@sevcom.net>
|
||||||
|
%%% Id : $Id$
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(iconv).
|
||||||
|
-author('alexey@sevcom.net').
|
||||||
|
-vsn('$Revision$ ').
|
||||||
|
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
-export([start/0, start_link/0, convert/3]).
|
||||||
|
|
||||||
|
%% Internal exports, call-back functions.
|
||||||
|
-export([init/1,
|
||||||
|
handle_call/3,
|
||||||
|
handle_cast/2,
|
||||||
|
handle_info/2,
|
||||||
|
code_change/3,
|
||||||
|
terminate/2]).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
start() ->
|
||||||
|
gen_server:start({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
init([]) ->
|
||||||
|
ok = erl_ddll:load_driver(".", iconv_erl),
|
||||||
|
Port = open_port({spawn, iconv_erl}, []),
|
||||||
|
ets:new(iconv_table, [set, public, named_table]),
|
||||||
|
ets:insert(iconv_table, {port, Port}),
|
||||||
|
{ok, Port}.
|
||||||
|
|
||||||
|
|
||||||
|
%%% --------------------------------------------------------
|
||||||
|
%%% The call-back functions.
|
||||||
|
%%% --------------------------------------------------------
|
||||||
|
|
||||||
|
handle_call(_, _, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
handle_cast(_, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
handle_info({'EXIT', Pid, Reason}, Port) ->
|
||||||
|
{noreply, Port};
|
||||||
|
|
||||||
|
handle_info({'EXIT', Port, Reason}, Port) ->
|
||||||
|
{stop, {port_died, Reason}, Port};
|
||||||
|
handle_info(_, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
code_change(OldVsn, State, Extra) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
terminate(_Reason, Port) ->
|
||||||
|
Port ! {self, close},
|
||||||
|
ok.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
convert(From, To, String) ->
|
||||||
|
[{port, Port} | _] = ets:lookup(iconv_table, port),
|
||||||
|
Bin = term_to_binary({From, To, String}),
|
||||||
|
BRes = port_control(Port, 1, Bin),
|
||||||
|
binary_to_list(BRes).
|
||||||
|
|
||||||
|
|
||||||
|
|
115
src/mod_irc/iconv_erl.c
Normal file
115
src/mod_irc/iconv_erl.c
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/* $Id$ */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <erl_driver.h>
|
||||||
|
#include <ei.h>
|
||||||
|
#include <iconv.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ErlDrvPort port;
|
||||||
|
iconv_t cd;
|
||||||
|
} iconv_data;
|
||||||
|
|
||||||
|
|
||||||
|
static ErlDrvData iconv_erl_start(ErlDrvPort port, char *buff)
|
||||||
|
{
|
||||||
|
iconv_data* d = (iconv_data*)driver_alloc(sizeof(iconv_data));
|
||||||
|
d->port = port;
|
||||||
|
d->cd = NULL;
|
||||||
|
|
||||||
|
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
|
||||||
|
|
||||||
|
return (ErlDrvData)d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iconv_erl_stop(ErlDrvData handle)
|
||||||
|
{
|
||||||
|
driver_free((char*)handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int iconv_erl_control(ErlDrvData drv_data,
|
||||||
|
unsigned int command,
|
||||||
|
char *buf, int len,
|
||||||
|
char **rbuf, int rlen)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int size;
|
||||||
|
int index = 0;
|
||||||
|
int avail;
|
||||||
|
int inleft, outleft;
|
||||||
|
ErlDrvBinary *b;
|
||||||
|
char *from, *to, *string, *stmp, *rstring, *rtmp;
|
||||||
|
iconv_t cd;
|
||||||
|
|
||||||
|
ei_decode_version(buf, &index, &i);
|
||||||
|
ei_decode_tuple_header(buf, &index, &i);
|
||||||
|
ei_get_type(buf, &index, &i, &size);
|
||||||
|
from = malloc(size + 1);
|
||||||
|
ei_decode_string(buf, &index, from);
|
||||||
|
|
||||||
|
ei_get_type(buf, &index, &i, &size);
|
||||||
|
to = malloc(size + 1);
|
||||||
|
ei_decode_string(buf, &index, to);
|
||||||
|
|
||||||
|
ei_get_type(buf, &index, &i, &size);
|
||||||
|
stmp = string = malloc(size + 1);
|
||||||
|
ei_decode_string(buf, &index, string);
|
||||||
|
|
||||||
|
cd = iconv_open(to, from);
|
||||||
|
// TODO: check result
|
||||||
|
/*
|
||||||
|
if(cd == (iconv_t) -1)
|
||||||
|
{
|
||||||
|
perror ("iconv_open");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("iconv_open from=%s, to=%s OK\r\n", from, to);
|
||||||
|
printf("string=%s size=%d\r\n", string, size);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
outleft = avail = 4*size;
|
||||||
|
inleft = size;
|
||||||
|
rtmp = rstring = malloc(avail);
|
||||||
|
iconv(cd, &stmp, &inleft, &rtmp, &outleft);
|
||||||
|
|
||||||
|
size = rtmp - rstring;
|
||||||
|
|
||||||
|
//printf("size=%d, res=%s\r\n", size, rstring);
|
||||||
|
|
||||||
|
*rbuf = (char*)(b = driver_alloc_binary(size));
|
||||||
|
memcpy(b->orig_bytes, rstring, size);
|
||||||
|
|
||||||
|
free(from);
|
||||||
|
free(to);
|
||||||
|
free(string);
|
||||||
|
free(rstring);
|
||||||
|
iconv_close(cd);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ErlDrvEntry iconv_driver_entry = {
|
||||||
|
NULL, /* F_PTR init, N/A */
|
||||||
|
iconv_erl_start, /* L_PTR start, called when port is opened */
|
||||||
|
iconv_erl_stop, /* F_PTR stop, called when port is closed */
|
||||||
|
NULL, /* F_PTR output, called when erlang has sent */
|
||||||
|
NULL, /* F_PTR ready_input, called when input descriptor ready */
|
||||||
|
NULL, /* F_PTR ready_output, called when output descriptor ready */
|
||||||
|
"iconv_erl", /* char *driver_name, the argument to open_port */
|
||||||
|
NULL, /* F_PTR finish, called when unloaded */
|
||||||
|
NULL, /* handle */
|
||||||
|
iconv_erl_control, /* F_PTR control, port_command callback */
|
||||||
|
NULL, /* F_PTR timeout, reserved */
|
||||||
|
NULL /* F_PTR outputv, reserved */
|
||||||
|
};
|
||||||
|
|
||||||
|
DRIVER_INIT(iconv_erl) /* must match name in driver_entry */
|
||||||
|
{
|
||||||
|
return &iconv_driver_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
93
src/mod_irc/mod_irc.erl
Normal file
93
src/mod_irc/mod_irc.erl
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% File : mod_irc.erl
|
||||||
|
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||||
|
%%% Purpose : IRC transport
|
||||||
|
%%% Created : 15 Feb 2003 by Alexey Shchepin <alexey@sevcom.net>
|
||||||
|
%%% Id : $Id$
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(mod_irc).
|
||||||
|
-author('alexey@sevcom.net').
|
||||||
|
-vsn('$Revision$ ').
|
||||||
|
|
||||||
|
-behaviour(gen_mod).
|
||||||
|
|
||||||
|
-export([start/1, init/1, stop/0]).
|
||||||
|
|
||||||
|
-include("ejabberd.hrl").
|
||||||
|
|
||||||
|
-record(irc_connection, {userserver, pid}).
|
||||||
|
|
||||||
|
start(Opts) ->
|
||||||
|
iconv:start(),
|
||||||
|
Host = gen_mod:get_opt(host, Opts, "irc." ++ ?MYNAME),
|
||||||
|
register(ejabberd_mod_irc, spawn(?MODULE, init, [Host])).
|
||||||
|
|
||||||
|
init(Host) ->
|
||||||
|
catch ets:new(irc_connection, [named_table,
|
||||||
|
public,
|
||||||
|
{keypos, #irc_connection.userserver}]),
|
||||||
|
ejabberd_router:register_route(Host),
|
||||||
|
loop(Host).
|
||||||
|
|
||||||
|
loop(Host) ->
|
||||||
|
receive
|
||||||
|
{route, From, To, Packet} ->
|
||||||
|
case catch do_route(Host, From, To, Packet) of
|
||||||
|
{'EXIT', Reason} ->
|
||||||
|
?ERROR_MSG("~p", [Reason]);
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
loop(Host);
|
||||||
|
stop ->
|
||||||
|
ejabberd_router:unregister_global_route(Host),
|
||||||
|
ok;
|
||||||
|
_ ->
|
||||||
|
loop(Host)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
do_route(Host, From, To, Packet) ->
|
||||||
|
{ChanServ, _, Resource} = To,
|
||||||
|
case ChanServ of
|
||||||
|
"" ->
|
||||||
|
% TODO
|
||||||
|
Err = jlib:make_error_reply(Packet, "406", "Not Acceptable"),
|
||||||
|
ejabberd_router:route(To, From, Err);
|
||||||
|
_ ->
|
||||||
|
case string:tokens(ChanServ, "%") of
|
||||||
|
[[_ | _] = Channel, [_ | _] = Server] ->
|
||||||
|
case ets:lookup(irc_connection, {From, Server}) of
|
||||||
|
[] ->
|
||||||
|
io:format("open new connection~n"),
|
||||||
|
{ok, Pid} = mod_irc_connection:start(
|
||||||
|
From, Host, Server),
|
||||||
|
ets:insert(
|
||||||
|
irc_connection,
|
||||||
|
#irc_connection{userserver = {From, Server},
|
||||||
|
pid = Pid}),
|
||||||
|
mod_irc_connection:route(
|
||||||
|
Pid, Channel, Resource, Packet),
|
||||||
|
ok;
|
||||||
|
[R] ->
|
||||||
|
Pid = R#irc_connection.pid,
|
||||||
|
io:format("send to process ~p~n",
|
||||||
|
[Pid]),
|
||||||
|
mod_irc_connection:route(
|
||||||
|
Pid, Channel, Resource, Packet),
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
Err = jlib:make_error_reply(
|
||||||
|
Packet, "406", "Not Acceptable"),
|
||||||
|
ejabberd_router:route(To, From, Err)
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
stop() ->
|
||||||
|
ejabberd_mod_irc ! stop,
|
||||||
|
ok.
|
443
src/mod_irc/mod_irc_connection.erl
Normal file
443
src/mod_irc/mod_irc_connection.erl
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% File : mod_irc_connection.erl
|
||||||
|
%%% Author : Alexey Shchepin <alexey@sevcom.net>
|
||||||
|
%%% Purpose :
|
||||||
|
%%% Created : 15 Feb 2003 by Alexey Shchepin <alexey@sevcom.net>
|
||||||
|
%%% Id : $Id$
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(mod_irc_connection).
|
||||||
|
-author('alexey@sevcom.net').
|
||||||
|
-vsn('$Revision$ ').
|
||||||
|
|
||||||
|
-behaviour(gen_fsm).
|
||||||
|
|
||||||
|
%% External exports
|
||||||
|
-export([start/3, receiver/2, route/4]).
|
||||||
|
|
||||||
|
%% gen_fsm callbacks
|
||||||
|
-export([init/1,
|
||||||
|
open_socket/2,
|
||||||
|
%wait_for_registration/2,
|
||||||
|
stream_established/2,
|
||||||
|
handle_event/3,
|
||||||
|
handle_sync_event/4,
|
||||||
|
handle_info/3,
|
||||||
|
terminate/3,
|
||||||
|
code_change/4]).
|
||||||
|
|
||||||
|
-include("ejabberd.hrl").
|
||||||
|
-include("namespaces.hrl").
|
||||||
|
|
||||||
|
-record(state, {socket, receiver, queue,
|
||||||
|
user, myname, server, nick,
|
||||||
|
inbuf = "", outbuf = ""}).
|
||||||
|
|
||||||
|
-define(IRC_ENCODING, "koi8-r").
|
||||||
|
|
||||||
|
-define(DBGFSM, true).
|
||||||
|
|
||||||
|
-ifdef(DBGFSM).
|
||||||
|
-define(FSMOPTS, [{debug, [trace]}]).
|
||||||
|
-else.
|
||||||
|
-define(FSMOPTS, []).
|
||||||
|
-endif.
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% API
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
start(From, Host, Server) ->
|
||||||
|
gen_fsm:start(?MODULE, [From, Host, Server], ?FSMOPTS).
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% Callback functions from gen_fsm
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
%% Func: init/1
|
||||||
|
%% Returns: {ok, StateName, StateData} |
|
||||||
|
%% {ok, StateName, StateData, Timeout} |
|
||||||
|
%% ignore |
|
||||||
|
%% {stop, StopReason}
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
init([From, Host, Server]) ->
|
||||||
|
gen_fsm:send_event(self(), init),
|
||||||
|
{Nick, _, _} = From,
|
||||||
|
{ok, open_socket, #state{queue = queue:new(),
|
||||||
|
user = From,
|
||||||
|
nick = Nick,
|
||||||
|
myname = Host,
|
||||||
|
server = Server}}.
|
||||||
|
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
%% Func: StateName/2
|
||||||
|
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||||
|
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||||
|
%% {stop, Reason, NewStateData}
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
open_socket(init, StateData) ->
|
||||||
|
Addr = StateData#state.server,
|
||||||
|
Port = 6667,
|
||||||
|
?DEBUG("connecting to ~s:~p~n", [Addr, Port]),
|
||||||
|
case gen_tcp:connect(Addr, Port, [binary, {packet, 0}]) of
|
||||||
|
{ok, Socket} ->
|
||||||
|
% TODO: send nick, etc...
|
||||||
|
%send_text(Socket, io_lib:format(?STREAM_HEADER,
|
||||||
|
% [StateData#state.server])),
|
||||||
|
%send_queue(StateData#state.socket, StateData#state.queue),
|
||||||
|
send_text(Socket, io_lib:format("NICK ~s\r\n",
|
||||||
|
[StateData#state.nick])),
|
||||||
|
send_text(Socket,
|
||||||
|
io_lib:format(
|
||||||
|
"USER ~s ~s ~s :~s\r\n",
|
||||||
|
[StateData#state.nick,
|
||||||
|
StateData#state.nick,
|
||||||
|
StateData#state.myname,
|
||||||
|
StateData#state.nick])),
|
||||||
|
{next_state, wait_for_registration,
|
||||||
|
StateData#state{socket = Socket}};
|
||||||
|
{error, Reason} ->
|
||||||
|
?DEBUG("connect return ~p~n", [Reason]),
|
||||||
|
Text = case Reason of
|
||||||
|
timeout -> "Server Connect Timeout";
|
||||||
|
_ -> "Server Connect Failed"
|
||||||
|
end,
|
||||||
|
bounce_messages(Text),
|
||||||
|
{stop, normal, StateData}
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
stream_established({xmlstreamend, Name}, StateData) ->
|
||||||
|
{stop, normal, StateData};
|
||||||
|
|
||||||
|
stream_established(timeout, StateData) ->
|
||||||
|
{stop, normal, StateData};
|
||||||
|
|
||||||
|
stream_established(closed, StateData) ->
|
||||||
|
{stop, normal, StateData}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
%% Func: StateName/3
|
||||||
|
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||||
|
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||||
|
%% {reply, Reply, NextStateName, NextStateData} |
|
||||||
|
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
|
||||||
|
%% {stop, Reason, NewStateData} |
|
||||||
|
%% {stop, Reason, Reply, NewStateData}
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
%state_name(Event, From, StateData) ->
|
||||||
|
% Reply = ok,
|
||||||
|
% {reply, Reply, state_name, StateData}.
|
||||||
|
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
%% Func: handle_event/3
|
||||||
|
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||||
|
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||||
|
%% {stop, Reason, NewStateData}
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
handle_event(Event, StateName, StateData) ->
|
||||||
|
{next_state, StateName, StateData}.
|
||||||
|
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
%% Func: handle_sync_event/4
|
||||||
|
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||||
|
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||||
|
%% {reply, Reply, NextStateName, NextStateData} |
|
||||||
|
%% {reply, Reply, NextStateName, NextStateData, Timeout} |
|
||||||
|
%% {stop, Reason, NewStateData} |
|
||||||
|
%% {stop, Reason, Reply, NewStateData}
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
handle_sync_event(Event, From, StateName, StateData) ->
|
||||||
|
Reply = ok,
|
||||||
|
{reply, Reply, StateName, StateData}.
|
||||||
|
|
||||||
|
code_change(OldVsn, StateName, StateData, Extra) ->
|
||||||
|
{ok, StateName, StateData}.
|
||||||
|
|
||||||
|
-define(SEND(S),
|
||||||
|
if
|
||||||
|
StateName == stream_established ->
|
||||||
|
send_text(StateData#state.socket, S),
|
||||||
|
StateData;
|
||||||
|
true ->
|
||||||
|
StateData#state{outbuf = StateData#state.outbuf ++ S}
|
||||||
|
end).
|
||||||
|
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
%% Func: handle_info/3
|
||||||
|
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||||
|
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||||
|
%% {stop, Reason, NewStateData}
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
handle_info({route, Channel, Resource, {xmlelement, "presence", Attrs, Els}},
|
||||||
|
StateName, StateData) ->
|
||||||
|
NewStateData =
|
||||||
|
case xml:get_attr_s("type", Attrs) of
|
||||||
|
"unavailable" ->
|
||||||
|
?SEND(io_lib:format("PART #~s\r\n", [Channel]));
|
||||||
|
"subscribe" -> StateData;
|
||||||
|
"subscribed" -> StateData;
|
||||||
|
"unsubscribe" -> StateData;
|
||||||
|
"unsubscribed" -> StateData;
|
||||||
|
_ ->
|
||||||
|
?SEND(io_lib:format("JOIN #~s\r\n", [Channel]))
|
||||||
|
end,
|
||||||
|
{next_state, StateName, NewStateData};
|
||||||
|
|
||||||
|
handle_info({route, Channel, Resource,
|
||||||
|
{xmlelement, "message", Attrs, Els} = El},
|
||||||
|
StateName, StateData) ->
|
||||||
|
NewStateData =
|
||||||
|
case xml:get_attr_s("type", Attrs) of
|
||||||
|
"groupchat" ->
|
||||||
|
ejabberd_router:route(
|
||||||
|
{lists:concat([Channel, "%", StateData#state.server]),
|
||||||
|
StateData#state.myname, StateData#state.nick},
|
||||||
|
StateData#state.user, El),
|
||||||
|
% TODO: remove newlines from body
|
||||||
|
Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
|
||||||
|
?SEND(io_lib:format("PRIVMSG #~s :~s\r\n", [Channel, Body]));
|
||||||
|
_ -> StateData
|
||||||
|
end,
|
||||||
|
{next_state, StateName, NewStateData};
|
||||||
|
|
||||||
|
handle_info({route, Channel, Resource, Packet}, StateName, StateData) ->
|
||||||
|
{next_state, StateName, StateData};
|
||||||
|
|
||||||
|
|
||||||
|
handle_info({ircstring, [$P, $I, $N, $G, $ | ID]}, StateName, StateData) ->
|
||||||
|
send_text(StateData#state.socket, "PONG " ++ ID ++ "\r\n"),
|
||||||
|
{next_state, StateName, StateData};
|
||||||
|
|
||||||
|
handle_info({ircstring, [$: | String]}, StateName, StateData) ->
|
||||||
|
Words = string:tokens(String, " "),
|
||||||
|
case Words of
|
||||||
|
[_, "353" | Items] ->
|
||||||
|
process_channel_list(StateData, Items);
|
||||||
|
[From, "PRIVMSG", [$# | Chan] | _] ->
|
||||||
|
process_privmsg(StateData, Chan, From, String);
|
||||||
|
[From, "PART", [$# | Chan] | _] ->
|
||||||
|
process_part(StateData, Chan, From, String);
|
||||||
|
[From, "JOIN", Chan | _] ->
|
||||||
|
process_join(StateData, Chan, From, String);
|
||||||
|
[From, "MODE", [$# | Chan], "+o", Nick | _] ->
|
||||||
|
process_mode_o(StateData, Chan, From, Nick,
|
||||||
|
"admin", "moderator");
|
||||||
|
[From, "MODE", [$# | Chan], "-o", Nick | _] ->
|
||||||
|
process_mode_o(StateData, Chan, From, Nick,
|
||||||
|
"member", "participant");
|
||||||
|
_ ->
|
||||||
|
io:format("unknown irc command '~s'~n", [String])
|
||||||
|
end,
|
||||||
|
NewStateData =
|
||||||
|
case StateData#state.outbuf of
|
||||||
|
"" ->
|
||||||
|
StateData;
|
||||||
|
Data ->
|
||||||
|
send_text(StateData#state.socket, Data),
|
||||||
|
StateData#state{outbuf = ""}
|
||||||
|
end,
|
||||||
|
{next_state, stream_established, NewStateData};
|
||||||
|
|
||||||
|
|
||||||
|
handle_info({ircstring, String}, StateName, StateData) ->
|
||||||
|
io:format("unknown irc command '~s'~n", [String]),
|
||||||
|
{next_state, StateName, StateData};
|
||||||
|
|
||||||
|
|
||||||
|
handle_info({send_text, Text}, StateName, StateData) ->
|
||||||
|
send_text(StateData#state.socket, Text),
|
||||||
|
{next_state, StateName, StateData};
|
||||||
|
handle_info({send_element, El}, StateName, StateData) ->
|
||||||
|
case StateName of
|
||||||
|
stream_established ->
|
||||||
|
send_element(StateData#state.socket, El),
|
||||||
|
{next_state, StateName, StateData};
|
||||||
|
_ ->
|
||||||
|
Q = queue:in(El, StateData#state.queue),
|
||||||
|
{next_state, StateName, StateData#state{queue = Q}}
|
||||||
|
end;
|
||||||
|
handle_info({tcp, Socket, Data}, StateName, StateData) ->
|
||||||
|
Buf = StateData#state.inbuf ++ binary_to_list(Data),
|
||||||
|
{ok, Strings} = regexp:split(Buf, "\r\n"),
|
||||||
|
io:format("strings=~p~n", [Strings]),
|
||||||
|
NewBuf = process_lines(Strings),
|
||||||
|
{next_state, StateName, StateData#state{inbuf = NewBuf}};
|
||||||
|
handle_info({tcp_closed, Socket}, StateName, StateData) ->
|
||||||
|
gen_fsm:send_event(self(), closed),
|
||||||
|
{next_state, StateName, StateData};
|
||||||
|
handle_info({tcp_error, Socket, Reason}, StateName, StateData) ->
|
||||||
|
gen_fsm:send_event(self(), closed),
|
||||||
|
{next_state, StateName, StateData}.
|
||||||
|
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
%% Func: terminate/3
|
||||||
|
%% Purpose: Shutdown the fsm
|
||||||
|
%% Returns: any
|
||||||
|
%%----------------------------------------------------------------------
|
||||||
|
terminate(Reason, StateName, StateData) ->
|
||||||
|
ejabberd_mod_irc ! {closed_conection, {StateData#state.user,
|
||||||
|
StateData#state.server}},
|
||||||
|
case StateData#state.socket of
|
||||||
|
undefined ->
|
||||||
|
ok;
|
||||||
|
Socket ->
|
||||||
|
gen_tcp:close(Socket)
|
||||||
|
end,
|
||||||
|
ok.
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% Internal functions
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
|
receiver(Socket, C2SPid) ->
|
||||||
|
XMLStreamPid = xml_stream:start(C2SPid),
|
||||||
|
receiver(Socket, C2SPid, XMLStreamPid).
|
||||||
|
|
||||||
|
receiver(Socket, C2SPid, XMLStreamPid) ->
|
||||||
|
case gen_tcp:recv(Socket, 0) of
|
||||||
|
{ok, Text} ->
|
||||||
|
xml_stream:send_text(XMLStreamPid, Text),
|
||||||
|
receiver(Socket, C2SPid, XMLStreamPid);
|
||||||
|
{error, Reason} ->
|
||||||
|
exit(XMLStreamPid, closed),
|
||||||
|
gen_fsm:send_event(C2SPid, closed),
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
send_text(Socket, Text) ->
|
||||||
|
CText = iconv:convert("utf-8", ?IRC_ENCODING, lists:flatten(Text)),
|
||||||
|
%io:format("IRC OUTu: ~s~nIRC OUTk: ~s~n", [Text, CText]),
|
||||||
|
gen_tcp:send(Socket, CText).
|
||||||
|
|
||||||
|
send_element(Socket, El) ->
|
||||||
|
send_text(Socket, xml:element_to_string(El)).
|
||||||
|
|
||||||
|
send_queue(Socket, Q) ->
|
||||||
|
case queue:out(Q) of
|
||||||
|
{{value, El}, Q1} ->
|
||||||
|
send_element(Socket, El),
|
||||||
|
send_queue(Socket, Q1);
|
||||||
|
{empty, Q1} ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
bounce_messages(Reason) ->
|
||||||
|
receive
|
||||||
|
{send_element, El} ->
|
||||||
|
{xmlelement, Name, Attrs, SubTags} = El,
|
||||||
|
case xml:get_attr_s("type", Attrs) of
|
||||||
|
"error" ->
|
||||||
|
ok;
|
||||||
|
_ ->
|
||||||
|
Err = jlib:make_error_reply(El,
|
||||||
|
"502", Reason),
|
||||||
|
From = jlib:string_to_jid(xml:get_attr_s("from", Attrs)),
|
||||||
|
To = jlib:string_to_jid(xml:get_attr_s("to", Attrs)),
|
||||||
|
ejabberd_router ! {route, To, From, Err}
|
||||||
|
end,
|
||||||
|
bounce_messages(Reason)
|
||||||
|
after 0 ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
route(Pid, Channel, Resource, Packet) ->
|
||||||
|
Pid ! {route, Channel, Resource, Packet}.
|
||||||
|
|
||||||
|
|
||||||
|
process_lines([S]) ->
|
||||||
|
S;
|
||||||
|
process_lines([S | Ss]) ->
|
||||||
|
self() ! {ircstring, iconv:convert(?IRC_ENCODING, "utf-8", S)},
|
||||||
|
process_lines(Ss).
|
||||||
|
|
||||||
|
process_channel_list(StateData, Items) ->
|
||||||
|
process_channel_list_find_chan(StateData, Items).
|
||||||
|
|
||||||
|
process_channel_list_find_chan(StateData, []) ->
|
||||||
|
ok;
|
||||||
|
process_channel_list_find_chan(StateData, [[$# | Chan] | Items]) ->
|
||||||
|
process_channel_list_users(StateData, Chan, Items);
|
||||||
|
process_channel_list_find_chan(StateData, [_ | Items]) ->
|
||||||
|
process_channel_list_find_chan(StateData, Items).
|
||||||
|
|
||||||
|
process_channel_list_users(StateData, Chan, []) ->
|
||||||
|
ok;
|
||||||
|
process_channel_list_users(StateData, Chan, [User | Items]) ->
|
||||||
|
process_channel_list_user(StateData, Chan, User),
|
||||||
|
process_channel_list_users(StateData, Chan, Items).
|
||||||
|
|
||||||
|
process_channel_list_user(StateData, Chan, User) ->
|
||||||
|
User1 = case User of
|
||||||
|
[$: | U1] -> U1;
|
||||||
|
_ -> User
|
||||||
|
end,
|
||||||
|
{User2, Affiliation, Role} =
|
||||||
|
case User1 of
|
||||||
|
[$@ | U2] -> {U2, "admin", "moderator"};
|
||||||
|
_ -> {User1, "member", "participant"}
|
||||||
|
end,
|
||||||
|
ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]),
|
||||||
|
StateData#state.myname, User2},
|
||||||
|
StateData#state.user,
|
||||||
|
{xmlelement, "presence", [],
|
||||||
|
[{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
|
||||||
|
[{xmlelement, "item",
|
||||||
|
[{"affiliation", Affiliation},
|
||||||
|
{"role", Role}],
|
||||||
|
[]}]}]}).
|
||||||
|
|
||||||
|
|
||||||
|
process_privmsg(StateData, Chan, From, String) ->
|
||||||
|
[FromUser | _] = string:tokens(From, "!"),
|
||||||
|
Msg = lists:last(string:tokens(String, ":")),
|
||||||
|
ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]),
|
||||||
|
StateData#state.myname, FromUser},
|
||||||
|
StateData#state.user,
|
||||||
|
{xmlelement, "message", [{"type", "groupchat"}],
|
||||||
|
[{xmlelement, "body", [], [{xmlcdata, Msg}]}]}).
|
||||||
|
|
||||||
|
process_part(StateData, Chan, From, String) ->
|
||||||
|
[FromUser | _] = string:tokens(From, "!"),
|
||||||
|
%Msg = lists:last(string:tokens(String, ":")),
|
||||||
|
ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]),
|
||||||
|
StateData#state.myname, FromUser},
|
||||||
|
StateData#state.user,
|
||||||
|
{xmlelement, "presence", [{"type", "unavailable"}],
|
||||||
|
[{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
|
||||||
|
[{xmlelement, "item",
|
||||||
|
[{"affiliation", "member"},
|
||||||
|
{"role", "none"}],
|
||||||
|
[]}]}]}).
|
||||||
|
|
||||||
|
|
||||||
|
process_join(StateData, Channel, From, String) ->
|
||||||
|
[FromUser | _] = string:tokens(From, "!"),
|
||||||
|
Chan = lists:subtract(Channel, ":#"),
|
||||||
|
ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]),
|
||||||
|
StateData#state.myname, FromUser},
|
||||||
|
StateData#state.user,
|
||||||
|
{xmlelement, "presence", [],
|
||||||
|
[{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
|
||||||
|
[{xmlelement, "item",
|
||||||
|
[{"affiliation", "member"},
|
||||||
|
{"role", "participant"}],
|
||||||
|
[]}]}]}).
|
||||||
|
|
||||||
|
|
||||||
|
process_mode_o(StateData, Chan, From, Nick, Affiliation, Role) ->
|
||||||
|
%Msg = lists:last(string:tokens(String, ":")),
|
||||||
|
ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]),
|
||||||
|
StateData#state.myname, Nick},
|
||||||
|
StateData#state.user,
|
||||||
|
{xmlelement, "presence", [],
|
||||||
|
[{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}],
|
||||||
|
[{xmlelement, "item",
|
||||||
|
[{"affiliation", Affiliation},
|
||||||
|
{"role", Role}],
|
||||||
|
[]}]}]}).
|
||||||
|
|
||||||
|
|
@ -19,5 +19,7 @@
|
|||||||
-define(NS_DELAY, "jabber:x:delay").
|
-define(NS_DELAY, "jabber:x:delay").
|
||||||
-define(NS_EVENT, "jabber:x:event").
|
-define(NS_EVENT, "jabber:x:event").
|
||||||
-define(NS_STATS, "http://jabber.org/protocol/stats").
|
-define(NS_STATS, "http://jabber.org/protocol/stats").
|
||||||
|
-define(NS_MUC, "http://jabber.org/protocol/muc").
|
||||||
|
-define(NS_MUC_USER, "http://jabber.org/protocol/muc#user").
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user