1
0
mirror of https://github.com/irssi/irssi.git synced 2024-06-30 02:45:25 -04:00
irssi/src/irc/core/servers-redirect.c
Timo Sirainen 06a33691f4 make sure redirect_continue is set to NULL if it contains the redirection
that is being destroyed.


git-svn-id: http://svn.irssi.org/repos/irssi/trunk@2385 dbcabf3a-b0e7-0310-adc4-f8d773084564
2002-02-03 22:28:09 +00:00

733 lines
20 KiB
C

/*
server-redirect.c : irssi
Copyright (C) 1999-2000 Timo Sirainen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "module.h"
#include "signals.h"
#include "misc.h"
#include "rawlog.h"
#include "irc-servers.h"
#include "servers-redirect.h"
#define DEFAULT_REDIRECT_TIMEOUT 60
/* Allow one non-expected redirections to come before the expected one
before aborting it. Some IRC bouncers/proxies reply to eg. PINGs
immediately. */
#define MAX_FAILURE_COUNT 1
typedef struct {
char *name;
int refcount;
int remote;
int timeout;
GSList *start, *stop, *opt; /* char *event, int argpos, ... */
} REDIRECT_CMD_REC;
struct _REDIRECT_REC {
REDIRECT_CMD_REC *cmd;
time_t created;
int failures;
unsigned int destroyed:1;
unsigned int aborted:1;
unsigned int remote:1;
unsigned int first_signal_sent:1;
char *arg;
int count;
char *failure_signal, *default_signal, *first_signal, *last_signal;
GSList *signals; /* event, signal, ... */
};
static GHashTable *command_redirects; /* "command xxx" : REDIRECT_CMD_REC* */
/* Find redirection command record for specified command line. */
static REDIRECT_CMD_REC *redirect_cmd_find(const char *command)
{
REDIRECT_CMD_REC *rec;
const char *p;
char *cmd;
p = strchr(command, ' ');
if (p == NULL)
rec = g_hash_table_lookup(command_redirects, command);
else {
cmd = g_strndup(command, (int) (p-command));
rec = g_hash_table_lookup(command_redirects, cmd);
g_free(cmd);
}
return rec;
}
static void redirect_cmd_destroy(REDIRECT_CMD_REC *rec)
{
GSList *tmp;
for (tmp = rec->start; tmp != NULL; tmp = tmp->next->next)
g_free(tmp->data);
for (tmp = rec->stop; tmp != NULL; tmp = tmp->next->next)
g_free(tmp->data);
for (tmp = rec->opt; tmp != NULL; tmp = tmp->next->next)
g_free(tmp->data);
g_slist_free(rec->start);
g_slist_free(rec->stop);
g_slist_free(rec->opt);
g_free(rec->name);
g_free(rec);
}
static void redirect_cmd_ref(REDIRECT_CMD_REC *rec)
{
rec->refcount++;
}
static void redirect_cmd_unref(REDIRECT_CMD_REC *rec)
{
if (--rec->refcount <= 0)
redirect_cmd_destroy(rec);
}
void server_redirect_destroy(REDIRECT_REC *rec)
{
redirect_cmd_unref(rec->cmd);
g_free_not_null(rec->arg);
g_free_not_null(rec->failure_signal);
g_free_not_null(rec->default_signal);
g_free_not_null(rec->first_signal);
g_free_not_null(rec->last_signal);
g_slist_foreach(rec->signals, (GFunc) g_free, NULL);
g_slist_free(rec->signals);
g_free(rec);
}
void server_redirect_register(const char *command,
int remote, int timeout, ...)
{
va_list va;
GSList *start, *stop, *opt, **list;
const char *event;
int argpos;
va_start(va, timeout);
start = stop = opt = NULL; list = &start;
for (;;) {
event = va_arg(va, const char *);
if (event == NULL) {
if (list == &start)
list = &stop;
else if (list == &stop)
list = &opt;
else
break;
continue;
}
argpos = va_arg(va, int);
*list = g_slist_append(*list, g_strdup(event));
*list = g_slist_append(*list, GINT_TO_POINTER(argpos));
}
va_end(va);
server_redirect_register_list(command, remote, timeout,
start, stop, opt);
}
void server_redirect_register_list(const char *command,
int remote, int timeout,
GSList *start, GSList *stop, GSList *opt)
{
REDIRECT_CMD_REC *rec;
gpointer key, value;
g_return_if_fail(command != NULL);
g_return_if_fail(stop != NULL);
if (g_hash_table_lookup_extended(command_redirects, command,
&key, &value)) {
/* Already registered - might have changed so destroy
the old one */
g_hash_table_remove(command_redirects, command);
redirect_cmd_unref(value);
}
rec = g_new0(REDIRECT_CMD_REC, 1);
redirect_cmd_ref(rec);
rec->name = g_strdup(command);
rec->remote = remote;
rec->timeout = timeout > 0 ? timeout : DEFAULT_REDIRECT_TIMEOUT;
rec->start = start;
rec->stop = stop;
rec->opt = opt;
g_hash_table_insert(command_redirects, rec->name, rec);
}
void server_redirect_event(IRC_SERVER_REC *server, const char *command,
int count, const char *arg, int remote,
const char *failure_signal, ...)
{
GSList *signals;
const char *event, *signal;
va_list va;
va_start(va, failure_signal);
signals = NULL;
while ((event = va_arg(va, const char *)) != NULL) {
signal = va_arg(va, const char *);
if (signal == NULL) {
g_warning("server_redirect_event(%s): "
"signal not specified for event", command);
break;
}
signals = g_slist_append(signals, g_strdup(event));
signals = g_slist_append(signals, g_strdup(signal));
}
va_end(va);
server_redirect_event_list(server, command, count, arg, remote,
failure_signal, signals);
}
/* Find specified event from signals list. If it's found, remove it from the
list and return it's target signal. */
static char *signal_list_move(GSList **signals, const char *event)
{
GSList *link;
char *linkevent, *linksignal;
link = gslist_find_string(*signals, event);
if (link == NULL)
return NULL;
linkevent = link->data;
linksignal = link->next->data;
*signals = g_slist_remove(*signals, linkevent);
*signals = g_slist_remove(*signals, linksignal);
g_free(linkevent);
return linksignal;
}
void server_redirect_event_list(IRC_SERVER_REC *server, const char *command,
int count, const char *arg, int remote,
const char *failure_signal, GSList *signals)
{
REDIRECT_CMD_REC *cmdrec;
REDIRECT_REC *rec;
g_return_if_fail(IS_IRC_SERVER(server));
g_return_if_fail(command != NULL);
g_return_if_fail((g_slist_length(signals) & 1) == 0);
cmdrec = g_hash_table_lookup(command_redirects, command);
if (cmdrec == NULL) {
g_warning("Unknown redirection command: %s", command);
return;
}
redirect_cmd_ref(cmdrec);
rec = g_new0(REDIRECT_REC, 1);
rec->created = time(NULL);
rec->cmd = cmdrec;
rec->arg = g_strdup(arg);
rec->count = count;
rec->remote = remote != -1 ? remote : cmdrec->remote;
rec->failure_signal = g_strdup(failure_signal);
rec->default_signal = signal_list_move(&signals, "");
rec->first_signal = signal_list_move(&signals, "redirect first");
rec->last_signal = signal_list_move(&signals, "redirect last");
rec->signals = signals;
if (server->redirect_next != NULL)
server_redirect_destroy(server->redirect_next);
server->redirect_next = rec;
}
void server_redirect_command(IRC_SERVER_REC *server, const char *command,
REDIRECT_REC *redirect)
{
REDIRECT_CMD_REC *cmdrec;
g_return_if_fail(IS_IRC_SERVER(server));
g_return_if_fail(command != NULL);
if (redirect == NULL) {
/* no redirection wanted, but still register the command
so future redirections wont get messed up. */
cmdrec = redirect_cmd_find(command);
if (cmdrec == NULL)
return;
redirect_cmd_ref(cmdrec);
redirect = g_new0(REDIRECT_REC, 1);
redirect->created = time(NULL);
redirect->cmd = cmdrec;
redirect->remote = cmdrec->remote;
}
server->redirects = g_slist_append(server->redirects, redirect);
}
static int redirect_args_match(const char *event_args,
const char *arg, int pos)
{
const char *start;
if (pos == -1)
return TRUE;
/* skip to the start of the wanted argument */
while (pos > 0 && *event_args != '\0') {
while (*event_args != ' ' && *event_args != '\0') event_args++;
while (*event_args == ' ') event_args++;
pos--;
}
/* now compare the arguments */
start = event_args;
while (*arg != '\0') {
while (*arg != '\0' && *arg != ' ' && *event_args != '\0') {
if (i_toupper(*arg) != i_toupper(*event_args))
break;
arg++; event_args++;
}
if ((*arg == '\0' || *arg == ' ') &&
(*event_args == '\0' || *event_args == ' '))
return TRUE;
/* compare the next argument */
while (*arg != ' ' && *arg != '\0') arg++;
while (*arg == ' ') arg++;
event_args = start;
}
return FALSE;
}
static GSList *redirect_cmd_list_find(GSList *list, const char *event)
{
while (list != NULL) {
const char *str = list->data;
if (strcmp(str, event) == 0)
break;
list = list->next->next;
}
return list;
}
#define MATCH_NONE 0
#define MATCH_START 1
#define MATCH_STOP 2
static const char *redirect_match(REDIRECT_REC *redirect, const char *event,
const char *args, int *match)
{
GSList *tmp, *cmdpos;
const char *signal;
int match_list;
if (redirect->aborted)
return NULL;
/* get the signal for redirection event - if it's not found we'll
use the default signal */
signal = NULL;
for (tmp = redirect->signals; tmp != NULL; tmp = tmp->next->next) {
if (strcmp(tmp->data, event) == 0) {
signal = tmp->next->data;
break;
}
}
/* find the argument position */
if (redirect->destroyed) {
/* stop event is already found for this redirection, but
we'll still want to look for optional events */
cmdpos = redirect_cmd_list_find(redirect->cmd->opt, event);
if (cmdpos == NULL)
return NULL;
match_list = MATCH_STOP;
} else {
/* look from start/stop lists */
cmdpos = redirect_cmd_list_find(redirect->cmd->start, event);
if (cmdpos != NULL)
match_list = MATCH_START;
else {
cmdpos = redirect_cmd_list_find(redirect->cmd->stop,
event);
match_list = cmdpos != NULL ? MATCH_STOP : MATCH_NONE;
}
}
if (signal == NULL && cmdpos == NULL) {
/* event not found from specified redirection events nor
registered command events */
return NULL;
}
/* check that arguments match */
if (args != NULL && redirect->arg != NULL && cmdpos != NULL &&
!redirect_args_match(args, redirect->arg,
GPOINTER_TO_INT(cmdpos->next->data)))
return NULL;
*match = match_list;
return signal != NULL ? signal : redirect->default_signal;
}
static void redirect_abort(IRC_SERVER_REC *server, REDIRECT_REC *rec)
{
char *str;
server->redirects =
g_slist_remove(server->redirects, rec);
if (rec->aborted || !rec->destroyed) {
/* emit the failure signal */
str = g_strdup_printf(rec->failure_signal != NULL ?
"FAILED %s: %s" : "FAILED %s",
rec->cmd->name, rec->failure_signal);
rawlog_redirect(server->rawlog, str);
g_free(str);
if (rec->failure_signal != NULL)
signal_emit(rec->failure_signal, 1, server);
} else if (rec->last_signal != NULL) {
/* emit the last signal */
signal_emit(rec->last_signal, 1, server);
}
if (server->redirect_continue == rec)
server->redirect_continue = NULL;
server_redirect_destroy(rec);
}
#define REDIRECT_IS_TIMEOUTED(rec) \
((now-(rec)->created) > (rec)->cmd->timeout)
static REDIRECT_REC *redirect_find(IRC_SERVER_REC *server, const char *event,
const char *args, const char **signal,
int *match)
{
REDIRECT_REC *redirect;
GSList *tmp, *next;
time_t now;
const char *match_signal;
/* find the redirection */
*signal = NULL; redirect = NULL;
for (tmp = server->redirects; tmp != NULL; tmp = tmp->next) {
REDIRECT_REC *rec = tmp->data;
match_signal = redirect_match(rec, event, args, match);
if (match_signal != NULL && *match != MATCH_NONE) {
redirect = rec;
*signal = match_signal;
break;
}
}
/* remove the destroyed, non-remote and timeouted remote
redirections that should have happened before this redirection */
now = time(NULL);
for (tmp = server->redirects; tmp != NULL; tmp = next) {
REDIRECT_REC *rec = tmp->data;
if (rec == redirect)
break;
next = tmp->next;
if (rec->destroyed) {
/* redirection is finished, destroy it */
redirect_abort(server, rec);
} else if (redirect != NULL) {
/* check if redirection failed */
if (rec->aborted ||
rec->failures++ >= MAX_FAILURE_COUNT) {
/* enough failures, abort it now */
if (!rec->remote || REDIRECT_IS_TIMEOUTED(rec))
redirect_abort(server, rec);
}
}
}
return redirect;
}
static const char *
server_redirect_get(IRC_SERVER_REC *server, const char *event,
const char *args, REDIRECT_REC **redirect, int *match)
{
const char *signal;
*redirect = NULL;
*match = MATCH_NONE;
if (server->redirects == NULL)
return NULL;
if (server->redirect_continue == NULL) {
/* find the redirection */
*redirect = redirect_find(server, event, args, &signal, match);
} else {
/* redirection is already started, now we'll just need to
keep redirecting until stop-event is found. */
*redirect = server->redirect_continue;
signal = redirect_match(*redirect, event, NULL, match);
if (signal == NULL) {
/* unknown event - redirect to the default signal. */
if (strncmp(event, "event ", 6) == 0 &&
i_isdigit(event[6])) {
signal = (*redirect)->default_signal;
if (*match == MATCH_NONE)
*match = MATCH_START;
} else {
/* not a numeric, so we've lost the
stop event.. */
(*redirect)->aborted = TRUE;
redirect_abort(server, *redirect);
*redirect = NULL;
signal = NULL;
}
}
}
if (*redirect != NULL && (*redirect)->first_signal != NULL &&
!(*redirect)->first_signal_sent) {
/* emit the first_signal */
(*redirect)->first_signal_sent = TRUE;
signal_emit((*redirect)->first_signal, 1, server);
}
return signal;
}
const char *server_redirect_get_signal(IRC_SERVER_REC *server,
const char *event,
const char *args)
{
REDIRECT_REC *redirect;
const char *signal;
int match;
signal = server_redirect_get(server, event, args, &redirect, &match);
if (match != MATCH_STOP || redirect == NULL)
server->redirect_continue = redirect;
else {
/* stop event - remove this redirection next time this
function is called (can't destroy now or our return
value would be corrupted) */
if (--redirect->count <= 0)
redirect->destroyed = TRUE;
server->redirect_continue = NULL;
}
return signal;
}
const char *server_redirect_peek_signal(IRC_SERVER_REC *server,
const char *event,
const char *args,
int *redirected)
{
REDIRECT_REC *redirect;
const char *signal;
int match;
signal = server_redirect_get(server, event, args, &redirect, &match);
*redirected = match != MATCH_NONE;
return signal;
}
static void sig_disconnected(IRC_SERVER_REC *server)
{
if (!IS_IRC_SERVER(server))
return;
g_slist_foreach(server->redirects,
(GFunc) server_redirect_destroy, NULL);
g_slist_free(server->redirects);
server->redirects = NULL;
if (server->redirect_next != NULL) {
server_redirect_destroy(server->redirect_next);
server->redirect_next = NULL;
}
}
static void cmd_redirect_destroy(char *key, REDIRECT_CMD_REC *cmd)
{
redirect_cmd_unref(cmd);
}
void servers_redirect_init(void)
{
command_redirects = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal);
/* WHOIS - register as remote command by default
with a default timeout */
server_redirect_register("whois", TRUE, 0,
"event 311", 1, /* Begins the WHOIS */
NULL,
"event 401", 1, /* No such nick */
"event 318", 1, /* End of WHOIS */
"event 402", 1, /* No such server */
NULL,
"event 318", 1, /* After 401, we should get 318, but in OPN we don't.. */
NULL);
/* WHOWAS */
server_redirect_register("whowas", FALSE, 0,
"event 314", 1, /* Begins the WHOWAS */
"event 406", 1, /* There was no such nick */
NULL,
"event 369", 1, /* End of WHOWAS */
NULL,
NULL);
/* WHO */
server_redirect_register("who", FALSE, 0,
"event 352", 1, /* Begins the WHO */
"event 401", 1, /* No such nick/channel */
NULL,
"event 315", 1, /* End of WHO */
"event 403", 1, /* no such channel */
NULL,
NULL);
/* LIST */
server_redirect_register("list", FALSE, 0,
"event 321", 1, /* Begins the LIST */
NULL,
"event 323", 1, /* End of LIST */
NULL,
NULL);
/* ISON */
server_redirect_register("ison", FALSE, 0,
NULL,
"event 303", -1, /* ISON */
NULL,
NULL);
/* USERHOST */
server_redirect_register("userhost", FALSE, 0,
"event 401", 1, /* no such nick */
NULL,
"event 302", -1, /* Userhost */
"event 461", -1, /* Not enough parameters */
NULL,
NULL);
/* MODE user */
server_redirect_register("mode user", FALSE, 0,
NULL,
"event mode", 0, /* MODE-reply */
"event 501", -1, /* Uknown MODE flag */
"event 502", -1, /* Can't change mode for other users */
"event 403", 1, /* That channel doesn't exist (tried to change mode to others) */
NULL,
NULL);
/* MODE #channel */
server_redirect_register("mode channel", FALSE, 0,
NULL,
"event 324", 1, /* MODE-reply */
"event 403", 1, /* no such channel */
"event 442", 1, /* "you're not on that channel" */
"event 479", 1, /* "Cannot join channel (illegal name)" IMHO this is not a logical reply from server. */
NULL,
"event 329", 1, /* Channel create time */
NULL);
/* MODE #channel b */
server_redirect_register("mode b", FALSE, 0,
"event 367", 1,
NULL,
"event 368", 1, /* End of Channel ban List */
"event 403", 1, /* no such channel */
"event 442", 1, /* "you're not on that channel" */
"event 479", 1, /* "Cannot join channel (illegal name)" IMHO this is not a logical reply from server. */
NULL,
NULL);
/* MODE #channel e */
server_redirect_register("mode e", FALSE, 0,
"event 348", 1,
NULL,
"event 349", 1, /* End of ban exceptions */
"event 482", 1, /* not channel operator - OPN's ircd doesn't want non-ops to see ban exceptions */
"event 403", 1, /* no such channel */
"event 442", 1, /* "you're not on that channel" */
"event 479", 1, /* "Cannot join channel (illegal name)" IMHO this is not a logical reply from server. */
"event 472", -1, /* unknown mode (you should check e-mode's existance from 004 event instead of relying on this) */
NULL,
NULL);
/* MODE #channel I */
server_redirect_register("mode I", FALSE, 0,
"event 346", 1,
NULL,
"event 347", 1, /* End of invite list */
"event 482", 1, /* not channel operator - OPN's ircd doesn't want non-ops to see ban exceptions */
"event 403", 1, /* no such channel */
"event 442", 1, /* "you're not on that channel" */
"event 479", 1, /* "Cannot join channel (illegal name)" IMHO this is not a logical reply from server. */
"event 472", -1, /* unknown mode (you should check I-mode's existance from 004 event instead of relying on this) */
NULL,
NULL);
/* PING - use default timeout */
server_redirect_register("ping", TRUE, 0,
NULL,
"event 402", -1, /* no such server */
"event pong", -1, /* PONG */
NULL,
NULL);
signal_add("server disconnected", (SIGNAL_FUNC) sig_disconnected);
}
void servers_redirect_deinit(void)
{
g_hash_table_foreach(command_redirects,
(GHFunc) cmd_redirect_destroy, NULL);
g_hash_table_destroy(command_redirects);
signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected);
}