mirror of
https://github.com/anope/anope.git
synced 2026-06-25 15:26:39 +02:00
4e409e31cb
git-svn-id: svn://svn.anope.org/anope/trunk@1210 31f1291d-b8d6-0310-a050-a5561fc1590b git-svn-id: http://anope.svn.sourceforge.net/svnroot/anope/trunk@929 5417fbe8-f217-4b02-8779-1006273d7864
1124 lines
30 KiB
C
1124 lines
30 KiB
C
/* BotServ functions
|
|
*
|
|
* (C) 2003-2005 Anope Team
|
|
* Contact us at info@anope.org
|
|
*
|
|
* Please read COPYING and README for further details.
|
|
*
|
|
* Based on the original code of Epona by Lara.
|
|
* Based on the original code of Services by Andy Church.
|
|
*
|
|
* $Id$
|
|
*
|
|
*/
|
|
|
|
/*************************************************************************/
|
|
|
|
#include "services.h"
|
|
#include "pseudo.h"
|
|
|
|
|
|
/*************************************************************************/
|
|
|
|
BotInfo *botlists[256]; /* Hash list of bots */
|
|
int nbots = 0;
|
|
|
|
/*************************************************************************/
|
|
|
|
static UserData *get_user_data(Channel * c, User * u);
|
|
|
|
static void check_ban(ChannelInfo * ci, User * u, int ttbtype);
|
|
static void bot_kick(ChannelInfo * ci, User * u, int message, ...);
|
|
|
|
E void moduleAddBotServCmds(void);
|
|
|
|
/*************************************************************************/
|
|
/* *INDENT-OFF* */
|
|
void moduleAddBotServCmds(void) {
|
|
modules_core_init(BotServCoreNumber, BotServCoreModules);
|
|
}
|
|
/* *INDENT-ON* */
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
|
|
/* Return information on memory use. Assumes pointers are valid. */
|
|
|
|
void get_botserv_stats(long *nrec, long *memuse)
|
|
{
|
|
long count = 0, mem = 0;
|
|
int i;
|
|
BotInfo *bi;
|
|
|
|
for (i = 0; i < 256; i++) {
|
|
for (bi = botlists[i]; bi; bi = bi->next) {
|
|
count++;
|
|
mem += sizeof(*bi);
|
|
mem += strlen(bi->nick) + 1;
|
|
mem += strlen(bi->user) + 1;
|
|
mem += strlen(bi->host) + 1;
|
|
mem += strlen(bi->real) + 1;
|
|
}
|
|
}
|
|
|
|
*nrec = count;
|
|
*memuse = mem;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
|
|
/* BotServ initialization. */
|
|
|
|
void bs_init(void)
|
|
{
|
|
Command *cmd;
|
|
if (s_BotServ) {
|
|
moduleAddBotServCmds();
|
|
cmd = findCommand(BOTSERV, "SET SYMBIOSIS");
|
|
if (cmd)
|
|
cmd->help_param1 = s_ChanServ;
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Main BotServ routine. */
|
|
|
|
void botserv(User * u, char *buf)
|
|
{
|
|
char *cmd, *s;
|
|
|
|
cmd = strtok(buf, " ");
|
|
|
|
if (!cmd) {
|
|
return;
|
|
} else if (stricmp(cmd, "\1PING") == 0) {
|
|
if (!(s = strtok(NULL, ""))) {
|
|
s = "";
|
|
}
|
|
anope_cmd_ctcp(s_BotServ, u->nick, "PING %s", s);
|
|
} else if (skeleton) {
|
|
notice_lang(s_BotServ, u, SERVICE_OFFLINE, s_BotServ);
|
|
} else {
|
|
mod_run_cmd(s_BotServ, u, BOTSERV, cmd);
|
|
}
|
|
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Handles all messages sent to bots. (Currently only answers to pings ;) */
|
|
|
|
void botmsgs(User * u, BotInfo * bi, char *buf)
|
|
{
|
|
char *cmd = strtok(buf, " ");
|
|
char *s;
|
|
|
|
if (!cmd || !u)
|
|
return;
|
|
|
|
if (!stricmp(cmd, "\1PING")) {
|
|
if (!(s = strtok(NULL, ""))) {
|
|
s = "";
|
|
}
|
|
anope_cmd_ctcp(bi->nick, u->nick, "PING %s", s);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Handles all messages that are sent to registered channels where a
|
|
* bot is on.
|
|
*
|
|
*/
|
|
|
|
void botchanmsgs(User * u, ChannelInfo * ci, char *buf)
|
|
{
|
|
int c;
|
|
int16 cstatus = 0;
|
|
char *cmd;
|
|
UserData *ud;
|
|
|
|
if (!u || !buf || !ci) {
|
|
return;
|
|
}
|
|
|
|
/* Answer to ping if needed, without breaking the buffer. */
|
|
if (!strnicmp(buf, "\1PING", 5)) {
|
|
anope_cmd_ctcp(ci->bi->nick, u->nick, "PING %s", buf);
|
|
}
|
|
|
|
/* If it's a /me, cut the CTCP part at the beginning (not
|
|
* at the end, because one character just doesn't matter,
|
|
* but the ACTION may create strange behaviours with the
|
|
* caps or badwords kickers */
|
|
if (!strnicmp(buf, "\1ACTION ", 8))
|
|
buf += 8;
|
|
|
|
/* Now we can make kicker stuff. We try to order the checks
|
|
* from the fastest one to the slowest one, since there's
|
|
* no need to process other kickers if an user is kicked before
|
|
* the last kicker check.
|
|
*
|
|
* But FIRST we check whether the user is protected in any
|
|
* way.
|
|
*/
|
|
|
|
/* We first retrieve the user status on the channel if needed */
|
|
if (ci->botflags & (BS_DONTKICKOPS | BS_DONTKICKVOICES))
|
|
cstatus = chan_get_user_status(ci->c, u);
|
|
|
|
if (buf && !check_access(u, ci, CA_NOKICK) &&
|
|
(!(ci->botflags & BS_DONTKICKOPS)
|
|
|| !(cstatus & (CUS_HALFOP | CUS_OP | CUS_OWNER | CUS_PROTECT)))
|
|
|
|
&& (!(ci->botflags & BS_DONTKICKVOICES) || !(cstatus & CUS_VOICE))) {
|
|
/* Bolds kicker */
|
|
if ((ci->botflags & BS_KICK_BOLDS) && strchr(buf, 2)) {
|
|
check_ban(ci, u, TTB_BOLDS);
|
|
bot_kick(ci, u, BOT_REASON_BOLD);
|
|
return;
|
|
}
|
|
|
|
/* Color kicker */
|
|
if ((ci->botflags & BS_KICK_COLORS) && strchr(buf, 3)) {
|
|
check_ban(ci, u, TTB_COLORS);
|
|
bot_kick(ci, u, BOT_REASON_COLOR);
|
|
return;
|
|
}
|
|
|
|
/* Reverses kicker */
|
|
if ((ci->botflags & BS_KICK_REVERSES) && strchr(buf, 22)) {
|
|
check_ban(ci, u, TTB_REVERSES);
|
|
bot_kick(ci, u, BOT_REASON_REVERSE);
|
|
return;
|
|
}
|
|
|
|
/* Underlines kicker */
|
|
if ((ci->botflags & BS_KICK_UNDERLINES) && strchr(buf, 31)) {
|
|
check_ban(ci, u, TTB_UNDERLINES);
|
|
bot_kick(ci, u, BOT_REASON_UNDERLINE);
|
|
return;
|
|
}
|
|
|
|
/* Caps kicker */
|
|
if ((ci->botflags & BS_KICK_CAPS)
|
|
&& ((c = strlen(buf)) >= ci->capsmin)) {
|
|
int i = 0;
|
|
int l = 0;
|
|
char *s = buf;
|
|
|
|
do {
|
|
if (isupper(*s))
|
|
i++;
|
|
else if (islower(*s))
|
|
l++;
|
|
} while (*s++);
|
|
|
|
/* i counts uppercase chars, l counts lowercase chars. Only
|
|
* alphabetic chars (so islower || isupper) qualify for the
|
|
* percentage of caps to kick for; the rest is ignored. -GD
|
|
*/
|
|
|
|
if (i >= ci->capsmin && i * 100 / (i + l) >= ci->capspercent) {
|
|
check_ban(ci, u, TTB_CAPS);
|
|
bot_kick(ci, u, BOT_REASON_CAPS);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Bad words kicker */
|
|
if (ci->botflags & BS_KICK_BADWORDS) {
|
|
int i;
|
|
int mustkick = 0;
|
|
char *nbuf;
|
|
BadWord *bw;
|
|
|
|
/* Normalize the buffer */
|
|
nbuf = normalizeBuffer(buf);
|
|
|
|
for (i = 0, bw = ci->badwords; i < ci->bwcount; i++, bw++) {
|
|
if (!bw->in_use)
|
|
continue;
|
|
|
|
if (bw->type == BW_ANY
|
|
&& ((BSCaseSensitive && strstr(nbuf, bw->word))
|
|
|| (!BSCaseSensitive && stristr(nbuf, bw->word)))) {
|
|
mustkick = 1;
|
|
} else if (bw->type == BW_SINGLE) {
|
|
int len = strlen(bw->word);
|
|
|
|
if ((BSCaseSensitive && !strcmp(nbuf, bw->word))
|
|
|| (!BSCaseSensitive
|
|
&& (!stricmp(nbuf, bw->word)))) {
|
|
mustkick = 1;
|
|
/* two next if are quite odd isn't it? =) */
|
|
} else if ((strchr(nbuf, ' ') == nbuf + len)
|
|
&&
|
|
((BSCaseSensitive
|
|
&& !strcmp(nbuf, bw->word))
|
|
|| (!BSCaseSensitive
|
|
&& (stristr(nbuf, bw->word) ==
|
|
nbuf)))) {
|
|
mustkick = 1;
|
|
} else {
|
|
if ((strrchr(nbuf, ' ') ==
|
|
nbuf + strlen(nbuf) - len - 1)
|
|
&&
|
|
((BSCaseSensitive
|
|
&& (strstr(nbuf, bw->word) ==
|
|
nbuf + strlen(nbuf) - len))
|
|
|| (!BSCaseSensitive
|
|
&& (stristr(nbuf, bw->word) ==
|
|
nbuf + strlen(nbuf) - len)))) {
|
|
mustkick = 1;
|
|
} else {
|
|
char *wordbuf = scalloc(len + 3, 1);
|
|
|
|
wordbuf[0] = ' ';
|
|
wordbuf[len + 1] = ' ';
|
|
wordbuf[len + 2] = '\0';
|
|
memcpy(wordbuf + 1, bw->word, len);
|
|
|
|
if ((BSCaseSensitive
|
|
&& (strstr(nbuf, wordbuf)))
|
|
|| (!BSCaseSensitive
|
|
&& (stristr(nbuf, wordbuf)))) {
|
|
mustkick = 1;
|
|
}
|
|
}
|
|
}
|
|
} else if (bw->type == BW_START) {
|
|
int len = strlen(bw->word);
|
|
|
|
if ((BSCaseSensitive
|
|
&& (!strncmp(nbuf, bw->word, len)))
|
|
|| (!BSCaseSensitive
|
|
&& (!strnicmp(nbuf, bw->word, len)))) {
|
|
mustkick = 1;
|
|
} else {
|
|
char *wordbuf = scalloc(len + 2, 1);
|
|
|
|
memcpy(wordbuf + 1, bw->word, len);
|
|
wordbuf[0] = ' ';
|
|
wordbuf[len + 1] = '\0';
|
|
|
|
if ((BSCaseSensitive && (strstr(nbuf, wordbuf)))
|
|
|| (!BSCaseSensitive
|
|
&& (stristr(nbuf, wordbuf))))
|
|
mustkick = 1;
|
|
|
|
free(wordbuf);
|
|
}
|
|
} else if (bw->type == BW_END) {
|
|
int len = strlen(bw->word);
|
|
|
|
if ((BSCaseSensitive
|
|
&&
|
|
(!strncmp
|
|
(nbuf + strlen(nbuf) - len, bw->word, len)))
|
|
|| (!BSCaseSensitive
|
|
&&
|
|
(!strnicmp
|
|
(nbuf + strlen(nbuf) - len, bw->word,
|
|
len)))) {
|
|
mustkick = 1;
|
|
} else {
|
|
char *wordbuf = scalloc(len + 2, 1);
|
|
|
|
memcpy(wordbuf, bw->word, len);
|
|
wordbuf[len] = ' ';
|
|
wordbuf[len + 1] = '\0';
|
|
|
|
if ((BSCaseSensitive && (strstr(nbuf, wordbuf)))
|
|
|| (!BSCaseSensitive
|
|
&& (stristr(nbuf, wordbuf))))
|
|
mustkick = 1;
|
|
|
|
free(wordbuf);
|
|
}
|
|
}
|
|
|
|
if (mustkick) {
|
|
check_ban(ci, u, TTB_BADWORDS);
|
|
if (BSGentleBWReason)
|
|
bot_kick(ci, u, BOT_REASON_BADWORD_GENTLE);
|
|
else
|
|
bot_kick(ci, u, BOT_REASON_BADWORD, bw->word);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Free the normalized buffer */
|
|
if (nbuf)
|
|
free(nbuf);
|
|
}
|
|
|
|
/* Flood kicker */
|
|
if (ci->botflags & BS_KICK_FLOOD) {
|
|
time_t now = time(NULL);
|
|
|
|
ud = get_user_data(ci->c, u);
|
|
if (!ud) {
|
|
return;
|
|
}
|
|
|
|
if (now - ud->last_start > ci->floodsecs) {
|
|
ud->last_start = time(NULL);
|
|
ud->lines = 0;
|
|
}
|
|
|
|
ud->lines++;
|
|
if (ud->lines >= ci->floodlines) {
|
|
check_ban(ci, u, TTB_FLOOD);
|
|
bot_kick(ci, u, BOT_REASON_FLOOD);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Repeat kicker */
|
|
if (ci->botflags & BS_KICK_REPEAT) {
|
|
ud = get_user_data(ci->c, u);
|
|
if (!ud) {
|
|
return;
|
|
}
|
|
if (ud->lastline && stricmp(ud->lastline, buf)) {
|
|
free(ud->lastline);
|
|
ud->lastline = sstrdup(buf);
|
|
ud->times = 0;
|
|
} else {
|
|
if (!ud->lastline)
|
|
ud->lastline = sstrdup(buf);
|
|
ud->times++;
|
|
}
|
|
|
|
if (ud->times >= ci->repeattimes) {
|
|
check_ban(ci, u, TTB_REPEAT);
|
|
bot_kick(ci, u, BOT_REASON_REPEAT);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* return if the user is on the ignore list */
|
|
if (get_ignore(u->nick) != NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Fantaisist commands */
|
|
|
|
if (buf && (ci->botflags & BS_FANTASY) && *buf == *BSFantasyCharacter) {
|
|
cmd = strtok(buf, " ");
|
|
|
|
if (cmd && (cmd[0] == *BSFantasyCharacter)) {
|
|
char *params = strtok(NULL, "");
|
|
char *event_name = EVENT_BOT_FANTASY_NO_ACCESS;
|
|
|
|
/* Strip off the fantasy character */
|
|
cmd++;
|
|
|
|
if (check_access(u, ci, CA_FANTASIA))
|
|
event_name = EVENT_BOT_FANTASY;
|
|
|
|
if (params)
|
|
send_event(event_name, 4, cmd, u->nick, ci->name, params);
|
|
else
|
|
send_event(event_name, 3, cmd, u->nick, ci->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Load/save data files. */
|
|
|
|
|
|
#define SAFE(x) do { \
|
|
if ((x) < 0) { \
|
|
if (!forceload) \
|
|
fatal("Read error on %s", BotDBName); \
|
|
failed = 1; \
|
|
break; \
|
|
} \
|
|
} while (0)
|
|
|
|
void load_bs_dbase(void)
|
|
{
|
|
dbFILE *f;
|
|
int c, ver;
|
|
uint16 tmp16;
|
|
uint32 tmp32;
|
|
BotInfo *bi;
|
|
int failed = 0;
|
|
|
|
if (!(f = open_db(s_BotServ, BotDBName, "r", BOT_VERSION)))
|
|
return;
|
|
|
|
ver = get_file_version(f);
|
|
|
|
while (!failed && (c = getc_db(f)) != 0) {
|
|
char *s;
|
|
|
|
if (c != 1)
|
|
fatal("Invalid format in %s %d", BotDBName, c);
|
|
|
|
SAFE(read_string(&s, f));
|
|
bi = makebot(s);
|
|
free(s);
|
|
SAFE(read_string(&bi->user, f));
|
|
SAFE(read_string(&bi->host, f));
|
|
SAFE(read_string(&bi->real, f));
|
|
if (ver >= 10) {
|
|
SAFE(read_int16(&tmp16, f));
|
|
bi->flags = tmp16;
|
|
}
|
|
SAFE(read_int32(&tmp32, f));
|
|
bi->created = tmp32;
|
|
SAFE(read_int16(&tmp16, f));
|
|
bi->chancount = tmp16;
|
|
}
|
|
|
|
close_db(f);
|
|
}
|
|
|
|
#undef SAFE
|
|
|
|
/*************************************************************************/
|
|
|
|
#define SAFE(x) do { \
|
|
if ((x) < 0) { \
|
|
restore_db(f); \
|
|
log_perror("Write error on %s", BotDBName); \
|
|
if (time(NULL) - lastwarn > WarningTimeout) { \
|
|
anope_cmd_global(NULL, "Write error on %s: %s", BotDBName, \
|
|
strerror(errno)); \
|
|
lastwarn = time(NULL); \
|
|
} \
|
|
return; \
|
|
} \
|
|
} while (0)
|
|
|
|
void save_bs_dbase(void)
|
|
{
|
|
dbFILE *f;
|
|
BotInfo *bi;
|
|
static time_t lastwarn = 0;
|
|
int i;
|
|
|
|
if (!(f = open_db(s_BotServ, BotDBName, "w", BOT_VERSION)))
|
|
return;
|
|
|
|
for (i = 0; i < 256; i++) {
|
|
for (bi = botlists[i]; bi; bi = bi->next) {
|
|
SAFE(write_int8(1, f));
|
|
SAFE(write_string(bi->nick, f));
|
|
SAFE(write_string(bi->user, f));
|
|
SAFE(write_string(bi->host, f));
|
|
SAFE(write_string(bi->real, f));
|
|
SAFE(write_int16(bi->flags, f));
|
|
SAFE(write_int32(bi->created, f));
|
|
SAFE(write_int16(bi->chancount, f));
|
|
}
|
|
}
|
|
SAFE(write_int8(0, f));
|
|
|
|
close_db(f);
|
|
|
|
}
|
|
|
|
#undef SAFE
|
|
|
|
/*************************************************************************/
|
|
|
|
void save_bs_rdb_dbase(void)
|
|
{
|
|
#ifdef USE_RDB
|
|
int i;
|
|
BotInfo *bi;
|
|
|
|
if (!rdb_open())
|
|
return;
|
|
|
|
if (rdb_tag_table("anope_bs_core") == 0) {
|
|
alog("Unable to tag table 'anope_bs_core' - BotServ RDB save failed.");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < 256; i++) {
|
|
for (bi = botlists[i]; bi; bi = bi->next) {
|
|
if (rdb_save_bs_core(bi) == 0) {
|
|
alog("Unable to save BotInfo for %s - BotServ RDB save failed.", bi->nick);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rdb_clean_table("anope_bs_core") == 0) {
|
|
alog("Unable to clean table 'anope_bs_core' - BotServ RDB save failed.");
|
|
return;
|
|
}
|
|
|
|
rdb_close();
|
|
#endif
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Inserts a bot in the bot list. I can't be much explicit mh? */
|
|
|
|
void insert_bot(BotInfo * bi)
|
|
{
|
|
BotInfo *ptr, *prev;
|
|
|
|
for (prev = NULL, ptr = botlists[tolower(*bi->nick)];
|
|
ptr != NULL && stricmp(ptr->nick, bi->nick) < 0;
|
|
prev = ptr, ptr = ptr->next);
|
|
bi->prev = prev;
|
|
bi->next = ptr;
|
|
if (!prev)
|
|
botlists[tolower(*bi->nick)] = bi;
|
|
else
|
|
prev->next = bi;
|
|
if (ptr)
|
|
ptr->prev = bi;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
BotInfo *makebot(char *nick)
|
|
{
|
|
BotInfo *bi;
|
|
|
|
if (!nick) {
|
|
if (debug) {
|
|
alog("debug: makebot called with NULL values");
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bi = scalloc(sizeof(BotInfo), 1);
|
|
bi->nick = sstrdup(nick);
|
|
bi->lastmsg = time(NULL);
|
|
insert_bot(bi);
|
|
nbots++;
|
|
return bi;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
|
|
/*************************************************************************/
|
|
|
|
|
|
/*************************************************************************/
|
|
|
|
BotInfo *findbot(char *nick)
|
|
{
|
|
BotInfo *bi;
|
|
Uid *ud;
|
|
|
|
/* to keep make strict happy */
|
|
ud = NULL;
|
|
|
|
if (!nick || !*nick)
|
|
return NULL;
|
|
|
|
for (bi = botlists[tolower(*nick)]; bi; bi = bi->next) {
|
|
if (UseTS6 && ircd->ts6) {
|
|
ud = find_nickuid(nick);
|
|
}
|
|
if (!stricmp(nick, bi->nick)) {
|
|
return bi;
|
|
}
|
|
if (ud && UseTS6 && ircd->ts6) {
|
|
if (!stricmp(ud->nick, bi->nick)) {
|
|
return bi;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Unassign a bot from a channel. Assumes u, ci and ci->bi are not NULL */
|
|
|
|
void unassign(User * u, ChannelInfo * ci)
|
|
{
|
|
send_event(EVENT_BOT_UNASSIGN, 2, ci->name, ci->bi->nick);
|
|
|
|
if (ci->c && ci->c->usercount >= BSMinUsers) {
|
|
anope_cmd_part(ci->bi->nick, ci->name, "UNASSIGN from %s",
|
|
u->nick);
|
|
}
|
|
ci->bi->chancount--;
|
|
ci->bi = NULL;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Returns ban data associated with an user if it exists, allocates it
|
|
otherwise. */
|
|
|
|
static BanData *get_ban_data(Channel * c, User * u)
|
|
{
|
|
char mask[BUFSIZE];
|
|
BanData *bd, *next;
|
|
time_t now = time(NULL);
|
|
|
|
if (!c || !u)
|
|
return NULL;
|
|
|
|
snprintf(mask, sizeof(mask), "%s@%s", u->username,
|
|
common_get_vhost(u));
|
|
|
|
for (bd = c->bd; bd; bd = next) {
|
|
if (now - bd->last_use > BSKeepData) {
|
|
if (bd->next)
|
|
bd->next->prev = bd->prev;
|
|
if (bd->prev)
|
|
bd->prev->next = bd->next;
|
|
else
|
|
c->bd = bd->next;
|
|
if (bd->mask)
|
|
free(bd->mask);
|
|
next = bd->next;
|
|
free(bd);
|
|
continue;
|
|
}
|
|
if (!stricmp(bd->mask, mask)) {
|
|
bd->last_use = now;
|
|
return bd;
|
|
}
|
|
next = bd->next;
|
|
}
|
|
|
|
/* If we fall here it is that we haven't found the record */
|
|
bd = scalloc(sizeof(BanData), 1);
|
|
bd->mask = sstrdup(mask);
|
|
bd->last_use = now;
|
|
|
|
bd->prev = NULL;
|
|
bd->next = c->bd;
|
|
if (bd->next)
|
|
bd->next->prev = bd;
|
|
c->bd = bd;
|
|
|
|
return bd;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Returns BotServ data associated with an user on a given channel.
|
|
* Allocates it if necessary.
|
|
*/
|
|
|
|
static UserData *get_user_data(Channel * c, User * u)
|
|
{
|
|
struct c_userlist *user;
|
|
|
|
if (!c || !u)
|
|
return NULL;
|
|
|
|
for (user = c->users; user; user = user->next) {
|
|
if (user->user == u) {
|
|
if (user->ud) {
|
|
time_t now = time(NULL);
|
|
|
|
/* Checks whether data is obsolete */
|
|
if (now - user->ud->last_use > BSKeepData) {
|
|
if (user->ud->lastline)
|
|
free(user->ud->lastline);
|
|
/* We should not free and realloc, but reset to 0
|
|
instead. */
|
|
memset(user->ud, 0, sizeof(UserData));
|
|
user->ud->last_use = now;
|
|
}
|
|
|
|
return user->ud;
|
|
} else {
|
|
user->ud = scalloc(sizeof(UserData), 1);
|
|
user->ud->last_use = time(NULL);
|
|
return user->ud;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Makes the bot join a channel and op himself. */
|
|
|
|
void bot_join(ChannelInfo * ci)
|
|
{
|
|
int i;
|
|
|
|
if (!ci || !ci->c || !ci->bi)
|
|
return;
|
|
|
|
if (BSSmartJoin) {
|
|
/* We check for bans */
|
|
int count = ci->c->bancount;
|
|
if (count) {
|
|
char botmask[BUFSIZE];
|
|
char **bans = scalloc(sizeof(char *) * count, 1);
|
|
char *av[3];
|
|
|
|
memcpy(bans, ci->c->bans, sizeof(char *) * count);
|
|
snprintf(botmask, sizeof(botmask), "%s!%s@%s", ci->bi->nick,
|
|
ci->bi->user, ci->bi->host);
|
|
|
|
av[0] = ci->c->name;
|
|
av[1] = sstrdup("-b");
|
|
for (i = 0; i < count; i++) {
|
|
if (match_wild_nocase(ci->c->bans[i], botmask)) {
|
|
anope_cmd_mode(ci->bi->nick, ci->name, "%s", bans[i]);
|
|
av[2] = sstrdup(bans[i]);
|
|
do_cmode(ci->bi->nick, 3, av);
|
|
free(av[2]);
|
|
}
|
|
}
|
|
free(av[1]);
|
|
free(bans);
|
|
}
|
|
|
|
/* Should we be invited? */
|
|
if ((ci->c->mode & anope_get_invite_mode())
|
|
|| (ci->c->limit && ci->c->usercount >= ci->c->limit))
|
|
anope_cmd_notice_ops(NULL, ci->c->name,
|
|
"%s invited %s into the channel.",
|
|
ci->bi->nick, ci->bi->nick);
|
|
}
|
|
anope_cmd_join(ci->bi->nick, ci->c->name, ci->c->creation_time);
|
|
anope_cmd_bot_chan_mode(ci->bi->nick, ci->c->name);
|
|
send_event(EVENT_BOT_JOIN, 2, ci->name, ci->bi->nick);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* This makes the bot rejoin all channel he is on when he gets killed
|
|
* or changed.
|
|
*/
|
|
|
|
void bot_rejoin_all(BotInfo * bi)
|
|
{
|
|
int i;
|
|
ChannelInfo *ci;
|
|
|
|
for (i = 0; i < 256; i++)
|
|
for (ci = chanlists[i]; ci; ci = ci->next)
|
|
if (ci->bi == bi && ci->c && (ci->c->usercount >= BSMinUsers))
|
|
bot_join(ci);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* This makes a ban if the user has to have one. In every cases it increments
|
|
the kick count for the user. */
|
|
|
|
static void check_ban(ChannelInfo * ci, User * u, int ttbtype)
|
|
{
|
|
BanData *bd = get_ban_data(ci->c, u);
|
|
|
|
if (!bd)
|
|
return;
|
|
|
|
bd->ttb[ttbtype]++;
|
|
if (bd->ttb[ttbtype] == ci->ttb[ttbtype]) {
|
|
char *av[3];
|
|
char mask[BUFSIZE];
|
|
|
|
bd->ttb[ttbtype] = 0;
|
|
|
|
av[0] = ci->name;
|
|
av[1] = sstrdup("+b");
|
|
get_idealban(ci, u, mask, sizeof(mask));
|
|
av[2] = mask;
|
|
anope_cmd_mode(ci->bi->nick, av[0], "+b %s", av[2]);
|
|
do_cmode(ci->bi->nick, 3, av);
|
|
send_event(EVENT_BOT_BAN, 3, u->nick, ci->name, mask);
|
|
free(av[1]);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* This makes a bot kick an user. Works somewhat like notice_lang in fact ;) */
|
|
|
|
static void bot_kick(ChannelInfo * ci, User * u, int message, ...)
|
|
{
|
|
va_list args;
|
|
char buf[1024];
|
|
const char *fmt;
|
|
char *av[3];
|
|
|
|
if (!ci || !ci->bi || !ci->c || !u)
|
|
return;
|
|
|
|
va_start(args, message);
|
|
fmt = getstring(u->na, message);
|
|
if (!fmt)
|
|
return;
|
|
vsnprintf(buf, sizeof(buf), fmt, args);
|
|
va_end(args);
|
|
|
|
av[0] = ci->name;
|
|
av[1] = u->nick;
|
|
av[2] = buf;
|
|
anope_cmd_kick(ci->bi->nick, av[0], av[1], "%s", av[2]);
|
|
do_kick(ci->bi->nick, 3, av);
|
|
send_event(EVENT_BOT_KICK, 3, u->nick, ci->name, buf);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Makes a simple ban and kicks the target */
|
|
|
|
void bot_raw_ban(User * requester, ChannelInfo * ci, char *nick,
|
|
char *reason)
|
|
{
|
|
int ac;
|
|
char *av[4];
|
|
char mask[BUFSIZE];
|
|
char buf[BUFSIZE];
|
|
User *u = finduser(nick);
|
|
|
|
if (!u)
|
|
return;
|
|
|
|
if (ircd->protectedumode) {
|
|
if (is_protected(u) && (requester != u)) {
|
|
anope_cmd_privmsg(ci->bi->nick, ci->name, "%s",
|
|
getstring2(NULL, PERMISSION_DENIED));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((ci->flags & CI_PEACE) && stricmp(requester->nick, nick)
|
|
&& (get_access(u, ci) >= get_access(requester, ci)))
|
|
return;
|
|
|
|
if (ircd->except) {
|
|
if (is_excepted(ci, u) == 1) {
|
|
anope_cmd_privmsg(ci->bi->nick, ci->name, "%s",
|
|
getstring2(NULL, BOT_EXCEPT));
|
|
return;
|
|
}
|
|
}
|
|
|
|
get_idealban(ci, u, mask, sizeof(mask));
|
|
|
|
if (ircdcap->tsmode) {
|
|
snprintf(buf, BUFSIZE - 1, "%ld", (long int) time(NULL));
|
|
av[0] = ci->name;
|
|
av[1] = buf;
|
|
av[2] = sstrdup("+b");
|
|
av[3] = mask;
|
|
ac = 4;
|
|
} else {
|
|
av[0] = ci->name;
|
|
av[1] = sstrdup("+b");
|
|
av[2] = mask;
|
|
ac = 3;
|
|
}
|
|
|
|
anope_cmd_mode(ci->bi->nick, ci->name, "+b %s", mask);
|
|
do_cmode(ci->bi->nick, ac, av);
|
|
|
|
/* We need to free our sstrdup'd "+b" -GD */
|
|
if (ircdcap->tsmode)
|
|
free(av[2]);
|
|
else
|
|
free(av[1]);
|
|
|
|
av[0] = ci->name;
|
|
av[1] = nick;
|
|
|
|
if (!reason) {
|
|
av[2] = ci->bi->nick;
|
|
} else {
|
|
if (strlen(reason) > 200)
|
|
reason[200] = '\0';
|
|
av[2] = reason;
|
|
}
|
|
|
|
anope_cmd_kick(ci->bi->nick, av[0], av[1], "%s", av[2]);
|
|
do_kick(ci->bi->nick, 3, av);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Makes a kick with a "dynamic" reason ;) */
|
|
|
|
void bot_raw_kick(User * requester, ChannelInfo * ci, char *nick,
|
|
char *reason)
|
|
{
|
|
char *av[3];
|
|
User *u = finduser(nick);
|
|
|
|
if (!u || !is_on_chan(ci->c, u))
|
|
return;
|
|
|
|
if (ircd->protectedumode) {
|
|
if (is_protected(u) && (requester != u)) {
|
|
anope_cmd_privmsg(ci->bi->nick, ci->name, "%s",
|
|
getstring2(NULL, PERMISSION_DENIED));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((ci->flags & CI_PEACE) && stricmp(requester->nick, nick)
|
|
&& (get_access(u, ci) >= get_access(requester, ci)))
|
|
return;
|
|
|
|
av[0] = ci->name;
|
|
av[1] = nick;
|
|
|
|
if (!reason) {
|
|
av[2] = ci->bi->nick;
|
|
} else {
|
|
if (strlen(reason) > 200)
|
|
reason[200] = '\0';
|
|
av[2] = reason;
|
|
}
|
|
|
|
anope_cmd_kick(ci->bi->nick, av[0], av[1], "%s", av[2]);
|
|
do_kick(ci->bi->nick, 3, av);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Makes a mode operation on a channel for a nick */
|
|
|
|
void bot_raw_mode(User * requester, ChannelInfo * ci, char *mode,
|
|
char *nick)
|
|
{
|
|
char *av[4];
|
|
int ac;
|
|
char buf[BUFSIZE];
|
|
User *u;
|
|
|
|
*buf = '\0';
|
|
u = finduser(nick);
|
|
|
|
if (!u || !is_on_chan(ci->c, u))
|
|
return;
|
|
|
|
snprintf(buf, BUFSIZE - 1, "%ld", (long int) time(NULL));
|
|
|
|
if (ircd->protectedumode) {
|
|
if (is_protected(u) && *mode == '-' && (requester != u)) {
|
|
anope_cmd_privmsg(ci->bi->nick, ci->name, "%s",
|
|
getstring2(NULL, PERMISSION_DENIED));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (*mode == '-' && (ci->flags & CI_PEACE)
|
|
&& stricmp(requester->nick, nick)
|
|
&& (get_access(u, ci) >= get_access(requester, ci)))
|
|
return;
|
|
|
|
if (ircdcap->tsmode) {
|
|
av[0] = ci->name;
|
|
av[1] = buf;
|
|
av[2] = mode;
|
|
av[3] = nick;
|
|
ac = 4;
|
|
anope_cmd_mode(ci->bi->nick, av[0], "%s %s", av[2], av[3]);
|
|
} else {
|
|
av[0] = ci->name;
|
|
av[1] = mode;
|
|
av[2] = nick;
|
|
ac = 3;
|
|
anope_cmd_mode(ci->bi->nick, av[0], "%s %s", av[1], av[2]);
|
|
}
|
|
|
|
do_cmode(ci->bi->nick, ac, av);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/**
|
|
* Normalize buffer stripping control characters and colors
|
|
* @param A string to be parsed for control and color codes
|
|
* @return A string stripped of control and color codes
|
|
*/
|
|
char *normalizeBuffer(char *buf)
|
|
{
|
|
char *newbuf;
|
|
int i, len, j = 0;
|
|
|
|
len = strlen(buf);
|
|
newbuf = (char *) smalloc(sizeof(char) * len + 1);
|
|
|
|
for (i = 0; i < len; i++) {
|
|
switch (buf[i]) {
|
|
/* ctrl char */
|
|
case 1:
|
|
break;
|
|
/* Bold ctrl char */
|
|
case 2:
|
|
break;
|
|
/* Color ctrl char */
|
|
case 3:
|
|
/* If the next character is a digit, its also removed */
|
|
if (isdigit(buf[i + 1])) {
|
|
i++;
|
|
|
|
/* not the best way to remove colors
|
|
* which are two digit but no worse then
|
|
* how the Unreal does with +S - TSL
|
|
*/
|
|
if (isdigit(buf[i + 1])) {
|
|
i++;
|
|
}
|
|
|
|
/* Check for background color code
|
|
* and remove it as well
|
|
*/
|
|
if (buf[i + 1] == ',') {
|
|
i++;
|
|
|
|
if (isdigit(buf[i + 1])) {
|
|
i++;
|
|
}
|
|
/* not the best way to remove colors
|
|
* which are two digit but no worse then
|
|
* how the Unreal does with +S - TSL
|
|
*/
|
|
if (isdigit(buf[i + 1])) {
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
/* line feed char */
|
|
case 10:
|
|
break;
|
|
/* carriage returns char */
|
|
case 13:
|
|
break;
|
|
/* Reverse ctrl char */
|
|
case 22:
|
|
break;
|
|
/* Underline ctrl char */
|
|
case 31:
|
|
break;
|
|
/* A valid char gets copied into the new buffer */
|
|
default:
|
|
newbuf[j] = buf[i];
|
|
j++;
|
|
}
|
|
}
|
|
|
|
/* Terminate the string */
|
|
newbuf[j] = 0;
|
|
|
|
return (newbuf);
|
|
}
|