bitchx/source/hook.c

1859 lines
42 KiB
C

/*
* hook.c: Does those naughty hook functions.
*
* Written By Michael Sandrof
* Rewritten by Jeremy Nelson and then rewritten to use
* hash'd lists by Colten Edwards
* Copyright(c) 1997
*/
#define __hook_c
#include "irc.h"
static char cvsrevision[] = "$Id$";
CVS_REVISION(hook_c)
#include "struct.h"
#include "hook.h"
#include "vars.h"
#include "ircaux.h"
#include "if.h"
#include "alias.h"
#include "list.h"
#include "window.h"
#include "server.h"
#include "output.h"
#include "commands.h"
#include "parse.h"
#include "misc.h"
#include "stack.h"
#include "tcl_bx.h"
#define MAIN_SOURCE
#include "modval.h"
#include <stdarg.h>
/*
* The various ON levels: SILENT means the DISPLAY will be OFF and it will
* suppress the default action of the event, QUIET means the display will be
* OFF but the default action will still take place, NORMAL means you will be
* notified when an action takes place and the default action still occurs,
* NOISY means you are notified when an action occur plus you see the action
* in the display and the default actions still occurs
*/
static char *noise_level[] = { "UNKNOWN", "SILENT", "QUIET", "NORMAL", "NOISY" };
#define HF_NORECURSE 0x0001
#define HF_DEBUG 0x0010
/*
* NumericList: a special list type to dynamically handle numeric hook
* requests
*/
typedef struct numericlist_stru
{
struct numericlist_stru *next;
int numeric;
char name[4];
Hook *list;
char *filename;
} NumericList;
NumericList *numeric_list = NULL;
#ifdef WANT_DLL
NumericList *dll_numeric_list = NULL;
#endif
/* hook_functions: the list of all hook functions available */
#define HOOK_COUNT (sizeof hook_functions / sizeof *hook_functions)
HookFunc hook_functions[] =
{
{ "ACTION", NULL, 3, 0, 0 },
{ "BANS", NULL, 4, 0, 0 },
{ "BANS_FOOTER", NULL, 2, 0, 0 },
{ "BANS_HEADER", NULL, 4, 0, 0 },
{ "CDCC_NOTE", NULL, 3, 0, 0 },
{ "CDCC_PACK", NULL, 7, 0, 0 },
{ "CDCC_POSTPACK", NULL, 8, 0, 0 },
{ "CDCC_PREPACK", NULL, 14, 0, 0 },
{ "CDCC_QUEUE", NULL, 5, 0, 0 },
{ "CDCC_QUEUEH", NULL, 3, 0, 0 },
{ "CDCC_SEND_NICK", NULL, 3, 0, 0 },
{ "CDCC_STATS", NULL, 1, 0, 0 },
{ "CHANOP", NULL, 1, 0, 0 },
{ "CHANNEL_NICK", NULL, 3, 0, 0 },
{ "CHANNEL_SIGNOFF", NULL, 3, 0, 0 },
{ "CHANNEL_STATS", NULL, 32, 0, 0 },
{ "CHANNEL_SWITCH", NULL, 1, 0, 0 },
{ "CHANNEL_SYNCH", NULL, 2, 0, 0 },
{ "CLONE", NULL, 2, 0, 0 },
{ "CONNECT", NULL, 1, 0, 0 },
{ "CTCP", NULL, 4, 0, 0 },
{ "CTCP_REPLY", NULL, 3, 0, 0 },
{ "DCC_CHAT", NULL, 2, 0, 0 },
{ "DCC_CONNECT", NULL, 2, 0, 0 },
{ "DCC_ERROR", NULL, 6, 0, 0 },
{ "DCC_HEADER", NULL, 7, 0, 0 },
{ "DCC_LOST", NULL, 2, 0, 0 },
{ "DCC_OFFER", NULL, 1, 0, 0 },
{ "DCC_POST", NULL, 7, 0, 0 },
{ "DCC_RAW", NULL, 3, 0, 0 },
{ "DCC_REQUEST", NULL, 4, 0, 0 },
{ "DCC_STAT", NULL, 7, 0, 0 },
{ "DCC_STATF", NULL, 7, 0, 0 },
{ "DCC_STATF1", NULL, 5, 0, 0 },
{ "DCC_TRANSFER_STAT", NULL, 13, 0, 0 },
{ "DCC_UPDATE", NULL, 1, 0, 0 },
{ "DEBUG", NULL, 1, 0, 0 },
{ "DESYNC_MESSAGE", NULL, 2, 0, 0 },
{ "DISCONNECT", NULL, 1, 0, 0 },
{ "EBANS", NULL, 4, 0, 0 },
{ "EBANS_FOOTER", NULL, 2, 0, 0 },
{ "EBANS_HEADER", NULL, 4, 0, 0 },
{ "ENCRYPTED_NOTICE", NULL, 3, 0, 0 },
{ "ENCRYPTED_PRIVMSG", NULL, 3, 0, 0 },
{ "EXEC", NULL, 2, 0, 0 },
{ "EXEC_ERRORS", NULL, 2, 0, 0 },
{ "EXEC_EXIT", NULL, 2, 0, 0 },
{ "EXEC_PROMPT", NULL, 2, 0, 0 },
{ "EXIT", NULL, 1, 0, 0 },
{ "FLOOD", NULL, 4, 0, 0 },
{ "FTP", NULL, 1, 0, 0 },
{ "HELP", NULL, 2, 0, 0 },
{ "HELPSUBJECT", NULL, 2, 0, 0 },
{ "HELPTOPIC", NULL, 1, 0, 0 },
{ "HOOK", NULL, 1, 0, 0 },
{ "IDLE", NULL, 1, 0, 0 },
{ "INPUT", NULL, 1, 0, 0 },
{ "INVITE", NULL, 2, 0, 0 },
{ "JOIN", NULL, 3, 0, 0 },
{ "JOIN_ME", NULL, 1, 0, 0 },
{ "KICK", NULL, 3, 0, 0 },
{ "LEAVE", NULL, 2, 0, 0 },
{ "LEAVE_ME", NULL, 1, 0, 0 },
{ "LIST", NULL, 3, 0, 0 },
{ "LLOOK_ADDED", NULL, 2, 0, 0 },
{ "LLOOK_JOIN", NULL, 2, 0, 0 },
{ "LLOOK_SPLIT", NULL, 2, 0, 0 },
{ "MAIL", NULL, 2, 0, 0 },
{ "MODE", NULL, 3, 0, 0 },
{ "MODE_STRIPPED", NULL, 3, 0, 0 },
{ "MODULE", NULL, 1, 0, 0 },
{ "MSG", NULL, 2, 0, 0 },
{ "MSG_GROUP", NULL, 3, 0, 0 },
{ "MSGLOG", NULL, 4, 0, 0 },
{ "NAMES", NULL, 2, 0, 0 },
{ "NETSPLIT", NULL, 1, 0, 0 },
{ "NICK_COMP", NULL, 1, 0, 0 },
{ "NICKNAME", NULL, 2, 0, 0 },
{ "NOTE", NULL, 3, 0, 0 },
{ "NOTICE", NULL, 2, 0, 0 },
{ "NOTICE_GROUP", NULL, 3, 0, 0 },
{ "NOTIFY", NULL, 2, 0, 0 },
{ "NOTIFY_HEADER", NULL, 2, 0, 0 },
{ "NOTIFY_SIGNOFF", NULL, 1, 0, 0 },
{ "NOTIFY_SIGNON", NULL, 1, 0, 0 },
{ "NSLOOKUP", NULL, 3, 0, 0 },
{ "ODD_SERVER_STUFF", NULL, 3, 0, 0 },
{ "PASTE", NULL, 2, 0, 0 },
{ "PUBLIC", NULL, 3, 0, 0 },
{ "PUBLIC_AR", NULL, 3, 0, 0 },
{ "PUBLIC_MSG", NULL, 3, 0, 0 },
{ "PUBLIC_NOTICE", NULL, 3, 0, 0 },
{ "PUBLIC_OTHER", NULL, 3, 0, 0 },
{ "PUBLIC_OTHER_AR", NULL, 3, 0, 0 },
{ "RAW_IRC", NULL, 1, 0, 0 },
{ "REDIRECT", NULL, 2, 0, 0 },
{ "REPLY_AR", NULL, 1, 0, 0 },
{ "SAVEFILE", NULL, 2, 0, 0 },
{ "SAVEFILEPOST", NULL, 2, 0, 0 },
{ "SAVEFILEPRE", NULL, 2, 0, 0 },
{ "SEND_ACTION", NULL, 2, 0, HF_NORECURSE },
{ "SEND_CTCP", NULL, 3, 0, HF_NORECURSE },
{ "SEND_DCC_CHAT", NULL, 2, 0, HF_NORECURSE },
{ "SEND_MSG", NULL, 2, 0, HF_NORECURSE },
{ "SEND_NOTICE", NULL, 2, 0, HF_NORECURSE },
{ "SEND_PUBLIC", NULL, 2, 0, HF_NORECURSE },
{ "SEND_TO_SERVER", NULL, 3, 0, 0 },
{ "SERVER_NOTICE_FAKES",NULL, 3, 0, 0 },
{ "SERVER_NOTICE_FAKES_MYCHANNEL",NULL,3,0, 0 },
{ "SERVER_NOTICE_FOREIGN_KILL", NULL, 4, 0, 0 },
{ "SERVER_NOTICE_KILL", NULL, 4, 0, 0 },
{ "SERVER_NOTICE", NULL, 1, 0, 0 },
{ "SERVER_NOTICE_LOCAL_KILL",NULL,4, 0, 0 },
{ "SERVER_NOTICE_SERVER_KILL",NULL, 4, 0, 0 },
{ "SET", NULL, 2, 0, 0 },
{ "SHITLIST", NULL, 6, 0, 0 },
{ "SHITLIST_FOOTER", NULL, 1, 0, 0 },
{ "SHITLIST_HEADER", NULL, 6, 0, 0 },
{ "SHOWIDLE_FOOTER", NULL, 1, 0, 0 },
{ "SHOWIDLE_HEADER", NULL, 2, 0, 0 },
{ "SHOWIDLE", NULL, 4, 0, 0 },
{ "SIGNOFF", NULL, 1, 0, 0 },
{ "SILENCE", NULL, 2, 0, 0 },
{ "SOCKET", NULL, 3, 0, 0 },
{ "SOCKET_NOTIFY", NULL, 3, 0, 0 },
{ "STAT", NULL, 5, 0, 0 },
{ "STAT_FOOTER", NULL, 1, 0, 0 },
{ "STAT_HEADER", NULL, 5, 0, 0 },
{ "STATUS_UPDATE", NULL, 2, 0, 0 },
{ "SWITCH_CHANNELS", NULL, 3, 0, 0 },
{ "TIMER", NULL, 1, 0, 0 },
{ "TIMER_HOUR", NULL, 1, 0, 0 },
{ "TOPIC", NULL, 2, 0, 0 },
{ "URLGRAB", NULL, 1, 0, 0 },
{ "USAGE", NULL, 2, 0, 0 },
{ "USERLIST", NULL, 4, 0, 0 },
{ "USERLIST_FOOTER", NULL, 1, 0, 0 },
{ "USERLIST_HEADER", NULL, 5, 0, 0 },
{ "USERS", NULL, 5, 0, 0 },
{ "USERS_FOOTER", NULL, 1, 0, 0 },
{ "USERS_HEADER", NULL, 5, 0, 0 },
{ "USERS_IP", NULL, 3, 0, 0 },
{ "USERS_SERVER", NULL, 2, 0, 0 },
{ "USERS_SERVER_HEADER",NULL, 2, 0, 0 },
{ "WALL", NULL, 2, 0, 0 },
{ "WALLOP", NULL, 3, 0, 0 },
{ "WATCH", NULL, 3, 0, 0 },
{ "WHO", NULL, 6, 0, 0 },
{ "WHOLEFT", NULL, 6, 0, 0 },
{ "WHOLEFT_FOOTER", NULL, 1, 0, 0 },
{ "WHOLEFT_HEADER", NULL, 6, 0, 0 },
{ "WIDELIST", NULL, 1, 0, 0 },
{ "WINDOW", NULL, 2, 0, HF_NORECURSE },
{ "WINDOW_CREATE", NULL, 1, 0, 0 },
{ "WINDOW_FOCUS", NULL, 4, 0, 0 },
{ "WINDOW_KILL", NULL, 1, 0, 0 },
{ "WINDOW_SWAP", NULL, 1, 0, 0 },
{ "YELL", NULL, 1, 0, 0 }
};
void hook_add_to_list (Hook **list, Hook *item);
static Hook * hook_remove_from_list (Hook **list, char *item, int sernum);
static void add_numeric_list (NumericList *item);
static NumericList *find_numeric_list (int numeric);
static NumericList *remove_numeric_list (int numeric);
char *current_package(void) { return empty_string; }
/*
* name of the current hook that's running. a hook within a hook, might
* screw this up though.
*/
char hook_name[BIG_BUFFER_SIZE+1] = {0};
extern int last_function_call_level;
/*
* This converts a user-specified string of unknown composition and
* returns a string that contains at minimum "params" number of words
* in it. For any words that are forcibly added on, the last word will
* be a * (so it can match any number of words), and any previous words
* will be a % (so it forces the correct number of words to be caught.)
*/
static char * fill_it_out (char *str, int params)
{
char buffer[BIG_BUFFER_SIZE];
char *arg,
*ptr;
int i = 0;
ptr = LOCAL_COPY(str);
*buffer = 0;
while ((arg = next_arg(ptr, &ptr)) != NULL)
{
if (*buffer)
strlcat(buffer, space, sizeof buffer);
strlcat(buffer, arg, sizeof buffer);
if (++i == params)
break;
}
for (; i < params; i++)
strlcat(buffer, (i < params-1) ? " %" : " *", sizeof buffer);
if (*ptr)
{
strlcat(buffer, space, sizeof buffer);
strlcat(buffer, ptr, sizeof buffer);
}
return m_strdup(buffer);
}
#define INVALID_HOOKNUM -1001
/*
* find_hook: returns the numerical value for a specified hook name
*/
static int find_hook (char *name, int *first)
{
int which = INVALID_HOOKNUM, i, len, cnt;
if (first)
*first = -1;
if (!name || !(len = strlen(name)))
{
say("You must specify an event type!");
return INVALID_HOOKNUM;
}
upper(name);
for (cnt = 0, i = 0; i < NUMBER_OF_LISTS; i++)
{
if (!strncmp(name, hook_functions[i].name, len))
{
if (first && *first == -1)
*first = i;
if (strlen(hook_functions[i].name) == len)
{
cnt = 1;
which = i;
break;
}
else
{
cnt++;
which = i;
}
}
else if (cnt)
break;
}
if (cnt == 0)
{
if (is_number(name))
{
which = atol(name);
if ((which < 0) || (which > 999))
{
say("Numerics must be between 001 and 999");
return INVALID_HOOKNUM;
}
which = -which;
}
else
{
say("No such ON function: %s", name);
return INVALID_HOOKNUM;
}
}
else if (cnt > 1)
{
say("Ambiguous ON function: %s", name);
return INVALID_HOOKNUM;
}
return which;
}
/* * * * * ADDING A HOOK * * * * * */
/*
* This adds a numeric hook to the numeric hook list. The composition of that
* list is a linked list of "numeric holders" which simply indicate the
* numeric of that list, and a pointer to the actual list itself. The actual
* list itself is just a linked list of events sorted by serial number, then
* by "nick".
*/
static void add_numeric_hook (int numeric, char *nick, char *stuff, Noise noisy, int not, int sernum, int flexible)
{
NumericList *entry;
Hook *new;
if (!(entry = find_numeric_list(numeric)))
{
entry = (NumericList *) new_malloc(sizeof(NumericList));
entry->numeric = numeric;
sprintf(entry->name, "%3.3d", numeric);
entry->next = NULL;
entry->list = NULL;
add_numeric_list(entry);
}
if (!(new = hook_remove_from_list(&entry->list, nick, sernum)))
{
new = (Hook *)new_malloc(sizeof(Hook));
new->nick = NULL;
new->stuff = NULL;
}
malloc_strcpy(&new->nick, nick);
malloc_strcpy(&new->stuff, stuff);
new->noisy = noisy;
new->not = not;
new->sernum = sernum;
new->flexible = flexible;
new->global = loading_global;
malloc_strcpy(&new->filename, current_package());
new->next = NULL;
upper(new->nick);
hook_add_to_list(&entry->list, new);
}
#ifdef WANT_DLL
static void add_numeric_dll_hook (int numeric, Noise noise, int serial, char *nick, char *package, int (*func)(int, char *, char **))
{
NumericList *entry;
Hook *new;
if (!(entry = find_numeric_list(numeric)))
{
entry = (NumericList *) new_malloc(sizeof(NumericList));
entry->numeric = numeric;
sprintf(entry->name, "%3.3d", numeric);
entry->next = NULL;
entry->list = NULL;
add_numeric_list(entry);
}
new = (Hook *)new_malloc(sizeof(Hook));
malloc_strcpy(&new->nick, nick);
malloc_strcpy(&new->filename, package);
new->num_func = func;
new->noisy = noise;
new->sernum = serial;
upper(new->nick);
hook_add_to_list(&entry->list, new);
}
void add_dll_hook(int which, Noise noise, char *nick, char *package, int (*func1)(int, char *, char **), int (*func2)(char *, char *, char **))
{
Hook *new;
static int serial = 0;
serial++;
nick = fill_it_out(nick, which < 0 ? 1 : hook_functions[which].params);
if (which < 0)
{
add_numeric_dll_hook(which, noise, serial, nick, package, func1);
return;
}
new = (Hook *)new_malloc(sizeof(Hook));
malloc_strcpy(&new->nick, nick);
malloc_strcpy(&new->filename, package);
new->num_func = func1;
new->hook_func = func2;
new->noisy = noise;
new->sernum = serial;
upper(new->nick);
hook_add_to_list(&hook_functions[which].list, new);
}
#endif
/*
* add_hook: Given an index into the hook_functions array, this adds a new
* entry to the list as specified by the rest of the parameters. The new
* entry is added in alphabetical order (by nick).
*/
static void add_hook (int which, char *nick, char *stuff, Noise noisy, int not, int sernum, int flexible)
{
Hook *new;
if (which < 0)
{
add_numeric_hook(-which, nick, stuff, noisy,
not, sernum, flexible);
return;
}
if (!(new = hook_remove_from_list(&hook_functions[which].list, nick, sernum)))
{
new = (Hook *)new_malloc(sizeof(Hook));
new->nick = NULL;
new->stuff = NULL;
}
malloc_strcpy(&new->nick, nick);
malloc_strcpy(&new->stuff, stuff);
new->noisy = noisy;
new->not = not;
new->sernum = sernum;
new->flexible = flexible;
new->global = loading_global;
malloc_strcpy(&new->filename, current_package());
new->next = NULL;
upper(new->nick);
new->debug = (hook_functions[which].flags & HF_DEBUG) ? HF_DEBUG : 0;
hook_add_to_list(&hook_functions[which].list, new);
}
/* * * * * * REMOVING A HOOK * * * * * * * */
static void remove_numeric_hook (int numeric, char *nick, int sernum, int quiet)
{
NumericList *hook;
Hook *tmp,
*next;
if ((hook = find_numeric_list(numeric)))
{
if (nick)
{
if ((tmp = hook_remove_from_list(&hook->list, nick, sernum)))
{
if (!quiet)
{
say("%c%s%c removed from %d list",
(tmp->flexible?'\'':'"'), nick,
(tmp->flexible?'\'':'"'), numeric);
}
new_free(&(tmp->nick));
new_free(&tmp->filename);
new_free(&(tmp->stuff));
new_free((char **)&tmp);
if (!hook->list)
{
if ((hook = remove_numeric_list(numeric)))
new_free((char **)&hook);
}
return;
}
}
else
{
remove_numeric_list(numeric);
for (tmp = hook->list; tmp; tmp = next)
{
next = tmp->next;
tmp->not = 1;
new_free(&(tmp->nick));
new_free(&(tmp->stuff));
new_free(&tmp->filename);
new_free((char **)&tmp);
}
hook->list = NULL;
new_free((char **)&hook);
if (!quiet)
say("The %d list is empty", numeric);
return;
}
}
if (quiet)
return;
if (nick)
say("\"%s\" is not on the %d list", nick, numeric);
else
say("The %d list is empty", numeric);
}
static void remove_hook (int which, char *nick, int sernum, int quiet)
{
Hook *tmp,
*next;
if (which < 0)
{
remove_numeric_hook(-which, nick, sernum, quiet);
return;
}
if (nick)
{
if ((tmp = hook_remove_from_list(&hook_functions[which].list, nick, sernum)))
{
if (!quiet)
say("%c%s%c removed from %s list",
(tmp->flexible?'\'':'"'), nick,
(tmp->flexible?'\'':'"'),
hook_functions[which].name);
new_free(&(tmp->nick));
new_free(&(tmp->stuff));
new_free(&tmp->filename);
new_free((char **)&tmp); /* XXX why? */
}
else if (!quiet)
say("\"%s\" is not on the %s list", nick,
hook_functions[which].name);
}
else
{
Hook *prev = NULL;
Hook *top = NULL;
for (tmp = hook_functions[which].list; tmp; prev=tmp, tmp=next)
{
next = tmp->next;
/*
* If given a non-zero sernum, then we clean out
* only those hooks that are at that level.
*/
if (sernum && tmp->sernum != sernum)
{
if (!top)
top = tmp;
continue;
}
if (prev)
prev->next = tmp->next;
tmp->not = 1;
new_free(&(tmp->nick));
new_free(&(tmp->stuff));
new_free(&tmp->filename);
new_free((char **)&tmp);
}
hook_functions[which].list = top;
if (!quiet)
{
if (sernum)
say("The %s <%d> list is empty", hook_functions[which].name, sernum);
else
say("The %s list is empty", hook_functions[which].name);
}
}
}
/* Used to bulk-erase all of the currently scheduled ONs */
void flush_on_hooks (void)
{
int x;
int old_display = window_display;
window_display = 0;
for (x = 1; x < 999; x++)
remove_numeric_hook(x, NULL, x, 1);
for (x = 0; x < NUMBER_OF_LISTS; x++)
remove_hook(x, NULL, 0, 1);
window_display = old_display;
}
void unload_on_hooks (char *filename)
{
int x;
NumericList *tmp;
Hook *list, *next;
int old_display = window_display;
window_display = 0;
for (x = 1; x < 999; x++)
{
if ((tmp = find_numeric_list(x)))
{
for (list = tmp->list; list; list = next)
{
next = list->next;
if (!strcmp(list->filename, filename))
remove_numeric_hook(x, list->nick, list->sernum, 1);
}
}
}
for (x = 0; x < NUMBER_OF_LISTS; x++)
{
for (list = hook_functions[x].list; list; list = next)
{
next = list->next;
if (!strcmp(list->filename, filename))
remove_hook(x, list->nick, list->sernum, 1);
}
}
window_display = old_display;
}
void debug_hook(char *name, int x)
{
int cnt = 0, i;
for (cnt = 0, i = 0; i < NUMBER_OF_LISTS; i++)
{
if (!strcmp(name, hook_functions[i].name))
{
Hook *tmp;
if (x)
hook_functions[i].flags |= HF_DEBUG;
else
hook_functions[i].flags &= ~(HF_DEBUG);
for (tmp = hook_functions[i].list; tmp; tmp = tmp->next)
{
if (x)
tmp->debug |= HF_DEBUG;
else
tmp->debug &= ~(HF_DEBUG);
cnt++;
}
break;
}
}
if (cnt == 0)
{
int which;
if (is_number(name))
{
NumericList *tmp;
which = atol(name);
if ((which < 0) || (which > 999))
return;
if ((tmp = find_numeric_list(which)))
{
Hook *list;
for (list = tmp->list; list; list = list->next, cnt++)
{
if (x)
list->debug |= HF_DEBUG;
else
list->debug &= ~(HF_DEBUG);
cnt++;
}
}
}
}
}
/* * * * * * SHOWING A HOOK * * * * * * */
/* show_hook shows a single hook */
static void show_hook (Hook *list, char *name)
{
char *hooks = fget_string_var(FORMAT_HOOK_FSET);
if (hooks)
put_it("%s",
convert_output_format(hooks,
"%s %s %c %s %c %s %s %d",
list->filename[0] ? list->filename : "*",
name,
(list->flexible?'\'':'"'), list->nick,
(list->flexible?'\'':'"'),
(list->not ? "nothing" : list->stuff ? list->stuff:"DLL"),
noise_level[list->noisy],
list->sernum));
else
say("[%s] On %s from %c%s%c do %s [%s] <%d>",
list->filename[0] ? list->filename : "*",
name,
(list->flexible?'\'':'"'), list->nick,
(list->flexible?'\'':'"'),
(list->not ? "nothing" : list->stuff ? list->stuff:"DLL"),
noise_level[list->noisy],
list->sernum);
}
/*
* show_numeric_list: If numeric is 0, then all numeric lists are displayed.
* If numeric is non-zero, then that particular list is displayed. The total
* number of entries displayed is returned
*/
static int show_numeric_list (int numeric)
{
NumericList *tmp;
Hook *list;
char buf[4];
int cnt = 0;
if (numeric)
{
sprintf(buf, "%3.3d", numeric);
if ((tmp = find_numeric_list(numeric)))
{
for (list = tmp->list; list; list = list->next, cnt++)
show_hook(list, tmp->name);
}
}
else
{
for (tmp = numeric_list; tmp; tmp = tmp->next)
{
for (list = tmp->list; list; list = list->next, cnt++)
show_hook(list, tmp->name);
}
}
return (cnt);
}
/*
* show_list: Displays the contents of the list specified by the index into
* the hook_functions array. This function returns the number of entries in
* the list displayed
*/
static int show_list (int which)
{
Hook *list;
int cnt = 0;
/* Less garbage when issuing /on without args. (lynx) */
for (list = hook_functions[which].list; list; list = list->next, cnt++)
show_hook(list, hook_functions[which].name);
return (cnt);
}
/* * * * * * * * EXECUTING A HOOK * * * * * * */
#define NO_ACTION_TAKEN -1
#define SUPPRESS_DEFAULT 0
#define DONT_SUPPRESS_DEFAULT 1
#define RESULT_PENDING 2
#define RESULT_NEXT 3
/*
* do_hook: This is what gets called whenever a MSG, INVITES, WALL, (you get
* the idea) occurs. The nick is looked up in the appropriate list. If a
* match is found, the stuff field from that entry in the list is treated as
* if it were a command. First it gets expanded as though it were an alias
* (with the args parameter used as the arguments to the alias). After it
* gets expanded, it gets parsed as a command. This will return as its value
* the value of the noisy field of the found entry, or -1 if not found.
*/
int BX_do_hook (int which, char *format, ...)
{
Hook *tmp = NULL;
Hook *next = NULL;
Hook **list;
char buffer[BIG_BUFFER_SIZE * 10 + 1];
char *name = NULL;
int retval = DONT_SUPPRESS_DEFAULT;
unsigned display = window_display;
int i;
Hook *hook_array[2048] = { 0 };
int hook_num = 0;
char *result = NULL;
int old_debug_count = debug_count;
/*
* Figure out where the hooks are for the event type were asserting
*/
/* Numeric list */
if (which < 0)
{
NumericList *hook;
if ((hook = find_numeric_list(-which)))
{
name = hook->name;
list = &hook->list;
}
else
list = NULL;
}
/* Named list */
else if (which < HOOK_COUNT)
{
/*
* If we're already executing the type, and we're
* specifically not supposed to allow recursion, then
* don't allow recursion. ;-)
*/
if (hook_functions[which].mark &&
(hook_functions[which].flags & HF_NORECURSE))
list = NULL;
else
{
list = &(hook_functions[which].list);
name = hook_functions[which].name;
strncpy(hook_name, hook_functions[which].name, BIG_BUFFER_SIZE);
}
}
else /* invalid hook */
{
list = NULL;
}
/*
* Press the buffer using the specified format string and args
* We do this here so that we don't waste time doing the vsnprintf
* if we're not going to do any matching. So for types where the
* user has no hooks, its a cheapie call.
*/
if (format)
{
va_list args;
va_start (args, format);
vsnprintf(buffer, BIG_BUFFER_SIZE * 5, format, args);
va_end(args);
}
else
ircpanic("do_hook: format is NULL");
#ifdef WANT_TCL
if (tcl_interp)
check_on_hook(which, format?buffer:NULL);
#endif
/*
* No hooks to look at? No problem. Drop out.
*/
if (!list)
{
*hook_name = 0;
return NO_ACTION_TAKEN;
}
/*
* Mark the event as being executed. This is used to suppress
* unwanted recursion in some /on's.
*/
if (which >= 0)
hook_functions[which].mark++;
/* not attached, so don't "fix" it */
{
int currser = 0,
oldser = INT_MIN,
currmatch = 0,
oldmatch = 0;
Hook * bestmatch = NULL;
/*
* Walk the list of hooks for this event
*/
for (tmp = *list; tmp; tmp = tmp->next)
{
char * tmpnick = NULL;
int sa;
/*
* save the current serial number
*/
currser = tmp->sernum;
/*
* Is this a different serial number than the
* last hook? If it is, then we save the previous
* serial number's best hook to the hook_array.
*/
if (currser != oldser)
{
oldser = currser;
oldmatch = 0;
if (bestmatch)
hook_array[hook_num++] = bestmatch;
bestmatch = NULL;
}
/*
* If this is a flexible hook, expand the nick stuff
*/
if (tmp->flexible)
tmpnick = expand_alias(tmp->nick, empty_string,
&sa, NULL);
else
tmpnick = tmp->nick;
/*
* Check to see if the pattern matches the text
*/
currmatch = wild_match(tmpnick, buffer);
/*
* If it is the "best match" so far, then we mark
* its "value" and save a pointer to it.
*/
if (currmatch > oldmatch)
{
oldmatch = currmatch;
bestmatch = tmp;
}
/*
* Clean up after flexible hooks
*/
if (tmp->flexible)
new_free(&tmpnick);
}
/*
* OK. We've walked the list. If the last hook had a best
* match, use that one too. =)
*/
if (bestmatch)
hook_array[hook_num++] = bestmatch;
}
/*
* Now we walk the list of collected hook events that are to be run
*/
for (i = 0; i < hook_num; i++)
{
const char * saved_who_from;
unsigned long saved_who_level;
char * name_copy;
char * stuff_copy;
int old_alias_debug = alias_debug;
char *result1 = NULL;
/*
* This should never happen.
*/
if (!(tmp = hook_array[i]))
ircpanic("hook_array[%d] is null", i);
/*
* Check to see if this hook is supposed to suppress the
* default action for the event.
*/
hook_next:
if (tmp->noisy == SILENT && tmp->sernum == 0)
retval = SUPPRESS_DEFAULT;
else if (tmp->noisy == UNKNOWN && tmp->sernum == 0)
retval = RESULT_PENDING;
/*
* If this is a negated event, or there isn't anything to be
* executed, then we don't bother. Just go on to the next one
*/
if (tmp->not || !tmp->stuff || !*tmp->stuff)
{
if (!tmp->stuff)
{
if (tmp->hook_func)
(tmp->hook_func)(name, buffer, NULL);
else if (tmp->num_func)
(tmp->num_func)(which, buffer, NULL);
}
continue;
}
/*
* If this is a NORMAL or NOISY hook, then we tell the user
* that we're going to execute the hook.
*/
if (tmp->noisy > QUIET)
say("%s activated by %c%s%c",
name, tmp->flexible ? '\'' : '"',
buffer, tmp->flexible ? '\'' : '"');
if ((tmp->debug & HF_DEBUG) && (internal_debug & DEBUG_HOOK) && !in_debug_yell)
{
debugyell("ON %s activated [%s]", name, buffer);
alias_debug++;
}
/*
* Save some information that may be reset in the
* execution, turn off the display if the user specified.
*/
save_display_target(&saved_who_from, &saved_who_level);
if (tmp->noisy < NOISY)
window_display = 0;
name_copy = LOCAL_COPY(name);
stuff_copy = LOCAL_COPY(tmp->stuff);
if (tmp->noisy == UNKNOWN)
result = parse_line_with_return(name_copy, stuff_copy, buffer, 0, 0);
else
{
/*
* OK. Go and run the code. It is imperative to note
* that "tmp" may be deleted by the code executed here,
* so it is absolutely forbidden to reference "tmp" after
* this point.
*/
next = tmp->next;
will_catch_return_exceptions++;
result1 = parse_line_with_return(name_copy, stuff_copy, buffer, 0, 0);
will_catch_return_exceptions--;
return_exception = 0;
}
if (retval == RESULT_PENDING)
{
if (result && atol(result))
retval = SUPPRESS_DEFAULT;
else
retval = DONT_SUPPRESS_DEFAULT;
}
new_free(&result);
/*
* Clean up the stuff that may have been mangled by the
* execution.
*/
restore_display_target(saved_who_from, saved_who_level);
if (result1 && *result1 && !my_stricmp(result1, "next"))
{
new_free(&result1);
{
if ((tmp = next))
goto hook_next;
else
retval = RESULT_NEXT;
}
}
new_free(&result1);
alias_debug = old_alias_debug;
window_display = display;
}
/*
* Mark the event as not currently being done here.
*/
if (which >= 0)
hook_functions[which].mark--;
if (oper_command && *buffer)
memset(buffer, 0, strlen(buffer)-1);
/*
* And return the user-specified suppression level
*/
if (old_debug_count == 1)
debug_count = 1;
*hook_name = 0;
return retval;
}
/*
* shook: the SHOOK command -- this probably doesn't belong here,
* and shook is probably a stupid name. It simply asserts a fake
* hook event for a given type. Fraught with peril!
*/
BUILT_IN_COMMAND(shookcmd)
{
int which;
char *arg = next_arg(args, &args);
if ((which = find_hook(arg, NULL)) == INVALID_HOOKNUM)
return;
else
do_hook(which, "%s", args);
}
/* * * * * * SCHEDULING AN EVENT * * * * * * * */
/*
* The ON command:
* Format: /ON [#][+-^]TYPE ['] [SERNUM] NICK ['] [{] STUFF [}]
*
* The "ON" command mainly takes three arguments. The first argument
* is the "type" of callback that you want to schedule. This is either
* a three digit number, of it is one of the strings so enumerated at the
* top of this file in hook_list. The second argument is the "nick" or
* "pattern" that is to be used to match against future events. If the
* "nick" matches the text that is later passed to do_hook() with the given
* "type", then the commands in "stuff" will be executed.
*
* If "nick" is enclosed in single quotes ('), then it is a "flexible"
* pattern, and will be expanded before it is matched against the text
* in do_hook. Otherwise, the string so specified is "static", and is
* used as-is in do_hook().
*
* Within each type, there are at least 65,535 different "serial numbers",
* (there actually are MAX_INT of them, but by convention, only 16 bit
* serial numbers are used, from -32,768 to 32,767) which may be used to
* schedule any number of events at the given serial number.
*
* Each time an assertion occurs for a given "type", at most one of the
* scheduled events is executed for each of the distinct serial numbers that
* are in use for that event. The event to be executed is the one at a
* given serial number that "best" matches the text passed to do_hook().
* While in theory, up to MAX_INT events could be executed for a given single
* assertion, in practice, a hard limit of 2048 events per assertion is
* enforced.
*
* The runtime behavior of the event being scheduled can be modified by
* specifying a character at the beginning of the "type" argument. If you
* want to schedule an event at a serial number, then the first character
* must be a hash (#). The argument immediately FOLLOWING the "type"
* argument, and immediately PRECEDING the "nick" argument must be an
* integer number, and is used for the serial number for this event.
*
* The "verbosity" of the event may also be modified by specifying at most
* one of the following characters:
* A caret (^) is the SILENT level, and indicates that the event is to
* be executed with no output (window_display is turned off),
* and the "default action" (whatever that is) for the event is
* to be suppressed. The default action is actually only
* suppressed if the SILENT level is specified for serial number
* zero. This is the most common level used for overriding the
* output of most /on's.
* A minus (-) is the QUIET level, and is the same as the SILENT level,
* except that the default action (whatever that is) is not to
* be suppressed.
* No character is the "normal" case, and is the same as the "minus"
* level, with the addition that the client will inform you that
* the event was executed. This is useful for debugging.
* A plus (+) is the same as the "normal" (no character specified),
* except that the output is not suppressed (window_display is
* not changed.)
*/
BUILT_IN_COMMAND(oncmd)
{
char *func,
*nick,
*serial;
Noise noisy = NORMAL;
int not = 0,
sernum = 0,
remove = 0,
which = INVALID_HOOKNUM;
int flex = 0;
char type;
int first;
/*
* Get the type of event to be scheduled
*/
if ((func = next_arg(args, &args)) != NULL)
{
/*
* Check to see if this has a serial number.
*/
if (*func == '#')
{
if (!(serial = next_arg(args, &args)))
{
say("No serial number specified");
return;
}
sernum = atol(serial);
func++;
}
/*
* Get the verbosity level, if any.
*/
switch (*func)
{
case '?':
noisy = UNKNOWN;
func++;
break;
case '-':
noisy = QUIET;
func++;
break;
case '^':
noisy = SILENT;
func++;
break;
case '+':
noisy = NOISY;
func++;
break;
default:
noisy = NORMAL;
break;
}
/*
* Check to see if the event type is valid
*/
if ((which = find_hook(func, &first)) == INVALID_HOOKNUM)
{
/*
* OK. So either the user specified an invalid type
* or they specified an ambiguous type. Either way,
* we're not going to be going anywhere. So we have
* free reign to mangle 'args' at this point.
*/
int len;
/*
* If first is -1, then it was an unknown type.
* An error has already been output, just return here
*/
if (first == -1)
return;
/*
* Otherwise, its an ambiguous type. If they were
* trying to register the hook, then theyve already
* gotten the error message, just return;
*/
if (new_new_next_arg(args, &args, &type))
return;
/*
* OK. So they probably want a listing.
*/
say("ON listings:");
len = strlen(func);
while (!my_strnicmp(func, hook_functions[first].name, len))
{
show_list(first);
first++;
if (!hook_functions[first].name)
break;
}
return;
}
/*
* Check to see if this is a removal event or if this
* is a negated event.
*/
switch (*args)
{
case '-':
remove = 1;
args++;
break;
case '^':
not = 1;
args++;
break;
}
/*
* Grab the "nick"
*/
if ((nick = new_new_next_arg(args, &args, &type)))
{
char *exp;
/*
* Pad it to the appropriate number of args
*/
if (which < 0)
nick = fill_it_out(nick, 1);
else
nick = fill_it_out(nick, hook_functions[which].params);
/*
* If nick is empty, something is very wrong.
*/
if (!*nick)
{
say("No expression specified");
new_free(&nick);
return;
}
/*
* If we're doing a removal, do the deed.
*/
if (remove)
{
remove_hook(which, nick, sernum, 0);
new_free(&nick);
return;
}
/*
* Take a note if its flexible or not.
*/
if (type == '\'')
flex = 1;
else
flex = 0;
/*
* If this is a negative event, then we don't want
* to take any action for it.
*/
if (not)
args = empty_string;
/*
* Slurp up any whitespace after the nick
*/
while (my_isspace(*args))
args++;
/*
* Then slurp up the body ("text")
*/
if (*args == '{') /* } */
{
if (!(exp = next_expr(&args, '{'))) /* } */
{
say("Unmatched brace in ON");
new_free(&nick);
return;
}
}
else
exp = args;
/*
* Schedule the event
*/
add_hook(which, nick, exp, noisy, not, sernum, flex);
/*
* Tell the user that we're done.
*/
if (which < 0)
say("On %3.3u from %c%s%c do %s [%s] <%d>",
-which, type, nick, type,
(not ? "nothing" : exp),
noise_level[noisy], sernum);
else
say("On %s from %c%s%c do %s [%s] <%d>",
hook_functions[which].name,
type, nick, type,
(not ? "nothing" : exp),
noise_level[noisy], sernum);
/*
* Clean up after the nick
*/
new_free(&nick);
}
/*
* No "nick" argument was specified. That means the user
* either is deleting all of the events of a type, or it
* wants to list all the events of a type.
*/
else
{
/*
* if its a removal, do the deed
*/
if (remove)
{
remove_hook(which, NULL, sernum, 0);
return;
}
/*
* The help files say that an "/on 0" shows all
* of the numeric ONs. Since the ACTION hook is
* number 0, we have to check to see if the first
* character of "func" is a zero or not. If it is,
* we output all of the numeric functions.
*/
if (*func == '0')
{
if (!show_numeric_list(0))
say("All numeric ON lists are empty.");
}
else if (which < 0)
{
if (!show_numeric_list(-which))
say("The %3.3u list is empty.", -which);
}
else if (!show_list(which))
say("The %s list is empty.",
hook_functions[which].name);
}
}
/*
* No "Type" argument was specified. That means the user wants to
* list all of the ONs currently scheduled.
*/
else
{
int total = 0;
say("ON listings:");
/*
* Show the named events
*/
for (which = 0; which < NUMBER_OF_LISTS; which++)
total += show_list(which);
/*
* Show the numeric events
*/
total += show_numeric_list(0);
if (!total)
say("All ON lists are empty.");
}
}
/* * * * * * * * * * SAVING A HOOK * * * * * * * * * * */
static void write_hook (FILE *fp, Hook *hook, char *name)
{
char *stuff = NULL;
char flexi = '"';
if (hook->flexible)
flexi = '\'';
switch (hook->noisy)
{
case SILENT:
stuff = "^";
break;
case QUIET:
stuff = "-";
break;
case NORMAL:
stuff = empty_string;
break;
case NOISY:
stuff = "+";
break;
case UNKNOWN:
stuff = "?";
break;
}
if (hook->sernum)
fprintf(fp, "ON #%s%s %d", stuff, name, hook->sernum);
else
fprintf(fp, "ON %s%s", stuff, name);
fprintf(fp, " %c%s%c {%s}\n", flexi, hook->nick, flexi, hook->stuff);
}
/*
* save_hooks: for use by the SAVE command to write the hooks to a file so it
* can be interpreted by the LOAD command
*/
void save_hooks (FILE *fp, int do_all)
{
Hook *list;
NumericList *numeric;
int which;
for (which = 0; which < NUMBER_OF_LISTS; which++)
{
for (list = hook_functions[which].list; list; list = list->next)
if (!list->global || do_all)
write_hook(fp,list, hook_functions[which].name);
}
for (numeric = numeric_list; numeric; numeric = numeric->next)
{
for (list = numeric->list; list; list = list->next)
if (!list->global)
write_hook(fp, list, numeric->name);
}
}
/* * * * * * * * * * STACKING A HOOK * * * * * * * * */
typedef struct onstacklist
{
int which;
Hook *list;
struct onstacklist *next;
} OnStack;
static OnStack * on_stack = NULL;
void do_stack_on (int type, char *args)
{
int which;
Hook *list;
NumericList *nhook = NULL, *nptr;
if (!on_stack && (type == STACK_POP || type == STACK_LIST))
{
say("ON stack is empty!");
return;
}
if (!args || !*args)
{
say("Missing event type for STACK ON");
return;
}
if ((which = find_hook(args, NULL)) == INVALID_HOOKNUM)
return; /* Error message already outputted */
if (which < 0)
{
if ((nhook = find_numeric_list(-which)))
list = nhook->list;
else
list = NULL;
}
else
list = hook_functions[which].list;
if (type == STACK_PUSH)
{
OnStack *new;
new = (OnStack *) new_malloc(sizeof(OnStack));
new->which = which;
new->list = list;
new->next = on_stack;
on_stack = new;
if (which < 0)
{
if (nhook && numeric_list)
remove_numeric_list(-which);
}
else
hook_functions[which].list = NULL;
return;
}
else if (type == STACK_POP)
{
OnStack *p, *tmp = (OnStack *) 0;
for (p = on_stack; p; tmp = p, p = p->next)
{
if (p->which == which)
{
if (p == on_stack)
on_stack = p->next;
else
tmp->next = p->next;
break;
}
}
if (!p)
{
say("No %s on the stack", args);
return;
}
if ((which < 0 && nhook) ||
(which >= 0 && hook_functions[which].list))
remove_hook(which, NULL, 0, 1); /* free hooks */
if (which < 0)
{
/* look -- do we have any hooks already for this numeric? */
if (!(nptr = find_numeric_list(-which)))
{
if (p->list) /* not just a placeholder? */
{
/* No. make a new list and put the stack on it */
nptr = (NumericList *) new_malloc(sizeof(NumericList));
nptr->list = p->list;
nptr->next = NULL;
nptr->numeric = -which;
sprintf(nptr->name, "%3.3d", -which);
add_numeric_list(nptr);
}
}
else
{
remove_hook(which, NULL, 0, 1);
nptr->list = p->list;
}
}
else
hook_functions[which].list = p->list;
new_free((char **)&p);
return;
}
else if (type == STACK_LIST)
{
int slevel = 0;
OnStack *osptr;
for (osptr = on_stack; osptr; osptr = osptr->next)
{
if (osptr->which == which)
{
Hook *hptr;
slevel++;
say("Level %d stack", slevel);
for (hptr = osptr->list; hptr; hptr = hptr->next)
show_hook(hptr, args);
}
}
if (!slevel)
say("The STACK ON %s list is empty", args);
return;
}
say("Unknown STACK ON type ??");
}
/* List manips especially for on's. */
void hook_add_to_list (Hook **list, Hook *item)
{
Hook *tmp, *last = NULL;
for (tmp = *list; tmp; last = tmp, tmp = tmp->next)
{
if (tmp->sernum < item->sernum)
continue;
else if ((tmp->sernum == item->sernum) && my_stricmp(tmp->nick, item->nick) < 0)
continue;
else
break;
}
if (last)
{
item->next = last->next;
last->next = item;
}
else
{
item->next = *list;
*list = item;
}
}
static Hook *hook_remove_from_list (Hook **list, char *item, int sernum)
{
Hook *tmp, *last = NULL;
for (tmp = *list; tmp; last = tmp, tmp = tmp->next)
{
if ((tmp->sernum == sernum) && !my_stricmp(tmp->nick, item))
{
if (last)
last->next = tmp->next;
else
*list = tmp->next;
return tmp;
}
}
return NULL;
}
static void add_numeric_list (NumericList *item)
{
NumericList *tmp, *last = NULL;
for (tmp = numeric_list; tmp; last = tmp, tmp = tmp->next)
{
if (tmp->numeric > item->numeric)
break;
}
if (last)
{
last->next = item;
item->next = tmp;
}
else
{
item->next = numeric_list;
numeric_list = item;
}
}
static NumericList *find_numeric_list (int numeric)
{
NumericList *tmp;
for (tmp = numeric_list; tmp; tmp = tmp->next)
{
if (tmp->numeric == numeric)
return tmp;
}
return NULL;
}
static NumericList *remove_numeric_list (int numeric)
{
NumericList *tmp, *last = NULL;
for (tmp = numeric_list; tmp; last = tmp, tmp = tmp->next)
{
if (tmp->numeric == numeric)
break;
}
if (tmp)
{
if (last)
last->next = tmp->next;
else
numeric_list = numeric_list->next;
}
return tmp;
}
#ifdef WANT_DLL
#if 0
static NumericList *remove_numeric_dll_list(int numeric)
{
NumericList *tmp, *last = NULL;
for (tmp = numeric_list; tmp; last = tmp, tmp = tmp->next)
{
if (tmp->numeric == numeric)
break;
}
if (last)
last->next = tmp->next;
else
numeric_list = numeric_list->next;
return tmp;
}
static Hook *remove_dll_hook_list(Hook **entry)
{
Hook *tmp, *last = NULL;
for (tmp = *list; tmp; last = tmp, tmp = tmp->next)
{
if (last)
last->next = tmp->next;
else
*list = tmp->next;
return tmp;
}
return NULL;
}
#endif
int remove_dll_hook(char *package)
{
NumericList *tmp;
Hook *list, *next = NULL;
int x, ret = 0;
for (x = 1; x < 999; x++)
{
if ((tmp = find_numeric_list(x)))
{
for (list = tmp->list; list; list = next)
{
next = list->next;
if (!my_stricmp(list->filename, package))
{
remove_numeric_hook(x, list->nick, list->sernum, 1);
ret++;
}
}
}
}
for (x = 0; x < NUMBER_OF_LISTS; x++)
{
for (list = hook_functions[x].list; list; list = next)
{
next = list->next;
if (!my_stricmp(list->filename, package))
{
remove_hook(x, list->nick, list->sernum, 1);
ret++;
}
}
}
return ret;
}
#endif