mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-07-03 04:53:12 +02:00
7c400e7dc1
This new feature (away notify) is announced in 005 (ISUPPORT) as: WATCHOPTS=A Format is: WATCH A +UserOne +UserTwo New numerics to cope with away notification in WATCH are: RPL_NOWISAWAY: to indicate the user is away _when adding_ it to WATCH list RPL_GONEAWAY: user was not away, but is now RPL_NOTAWAY: user was away, but is no longer away RPL_NOWISAWAY: user was away, and still is, but the reason changed Example: WATCH A +Target Request to add user 'Target' to the watch list with away notification :maintest.test.net 609 MySelf Target ~blih test.testnet 1204309588 :not here atm Reply to watch add: user is online and away, reason is provided :maintest.test.net 599 MySelf Target ~blih test.testnet 1204309588 :is no longer away User is back (no longer away) :maintest.test.net 598 MySelf Target ~blih test.testnet 1204309722 :lunch State change: user is now away, reason is provided :maintest.test.net 597 MySelf Target ~blih test.testnet 1204309738 :shopping, bbl User is still away, but reason changed. The syntax for each numeric is: <nickname> <username> <hostname> <awaysince> :<away reason> In case of 599 (RPL_NOTAWAY) it is: <nickname> <username> <hostname> <awaysince> :is no longer away For the record, this is all based on a draft from codemastr from 2004, which was implemented in Unreal3.3 (devel branch) in 2006. Today, in 2008 it was updated with away reason support and backported to Unreal3.2. Because away notification hasn't been used until now (due to it only being in Unreal3.3) we felt it was safe to break some numerics.
914 lines
22 KiB
C
914 lines
22 KiB
C
/************************************************************************
|
|
* Unreal Internet Relay Chat Daemon, src/hash.c
|
|
* Copyright (C) 1991 Darren Reed
|
|
*
|
|
* 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include "numeric.h"
|
|
#include "struct.h"
|
|
#include "common.h"
|
|
#include "sys.h"
|
|
#include "hash.h"
|
|
#include "h.h"
|
|
#include "proto.h"
|
|
|
|
ID_Copyright("(C) 1991 Darren Reed");
|
|
ID_Notes("2.10 7/3/93");
|
|
|
|
static aHashEntry clientTable[U_MAX];
|
|
static aHashEntry channelTable[CH_MAX];
|
|
|
|
/*
|
|
* look in whowas.c for the missing ...[WW_MAX]; entry - Dianora */
|
|
/*
|
|
* Hashing.
|
|
*
|
|
* The server uses a chained hash table to provide quick and efficient
|
|
* hash table mantainence (providing the hash function works evenly
|
|
* over the input range). The hash table is thus not susceptible to
|
|
* problems of filling all the buckets or the need to rehash. It is
|
|
* expected that the hash table would look somehting like this during
|
|
* use: +-----+ +-----+ +-----+ +-----+
|
|
* ---| 224 |----| 225 |----| 226 |---| 227 |---
|
|
* +-----+ +-----+ +-----+ +-----+
|
|
* | | |
|
|
* +-----+ +-----+ +-----+
|
|
* | A | | C | | D |
|
|
* +-----+ +-----+ +-----+
|
|
* |
|
|
* +-----+
|
|
* | B |
|
|
* +-----+
|
|
*
|
|
* A - GOPbot, B - chang, C - hanuaway, D - *.mu.OZ.AU
|
|
*
|
|
* The order shown above is just one instant of the server. Each time a
|
|
* lookup is made on an entry in the hash table and it is found, the
|
|
* entry is moved to the top of the chain.
|
|
*
|
|
* ^^^^^^^^^^^^^^^^ **** Not anymore - Dianora
|
|
*
|
|
*/
|
|
/* Take equal propotions from the size of int on all arcitechtures */
|
|
#define BITS_IN_int ( sizeof(int) * CHAR_BIT )
|
|
#define THREE_QUARTERS ((int) ((BITS_IN_int * 3) / 4))
|
|
#define ONE_EIGHTH ((int) (BITS_IN_int / 8))
|
|
#define HIGH_BITS ( ~((unsigned int)(~0) >> ONE_EIGHTH ))
|
|
|
|
unsigned int hash_nn_name(const char *hname)
|
|
{
|
|
unsigned int hash_value, i;
|
|
|
|
for (hash_value = 0; *hname; ++hname)
|
|
{
|
|
/* Shift hash-value by one eights of int for adding every letter */
|
|
hash_value = (hash_value << ONE_EIGHTH) + tolower(*hname);
|
|
/* If the next shift would cause an overflow... */
|
|
if ((i = hash_value & HIGH_BITS) != 0)
|
|
/* Then wrap the upper quarter of bits back to the value */
|
|
hash_value = (hash_value ^
|
|
(i >> THREE_QUARTERS)) & ~HIGH_BITS;
|
|
}
|
|
|
|
return (hash_value);
|
|
}
|
|
|
|
|
|
unsigned hash_nick_name(char *nname)
|
|
{
|
|
unsigned hash = 0;
|
|
int hash2 = 0;
|
|
int ret;
|
|
char lower;
|
|
|
|
while (*nname)
|
|
{
|
|
lower = tolower(*nname);
|
|
hash = (hash << 1) + lower;
|
|
hash2 = (hash2 >> 1) + lower;
|
|
nname++;
|
|
}
|
|
ret = ((hash & U_MAX_INITIAL_MASK) << BITS_PER_COL) +
|
|
(hash2 & BITS_PER_COL_MASK);
|
|
return ret;
|
|
}
|
|
/*
|
|
* hash_channel_name
|
|
*
|
|
* calculate a hash value on at most the first 30 characters of the
|
|
* channel name. Most names are short than this or dissimilar in this
|
|
* range. There is little or no point hashing on a full channel name
|
|
* which maybe 255 chars long.
|
|
*/
|
|
unsigned int hash_channel_name(char *name)
|
|
{
|
|
unsigned char *hname = (unsigned char *)name;
|
|
unsigned int hash = 0;
|
|
int hash2 = 0;
|
|
char lower;
|
|
int i = 30;
|
|
|
|
while (*hname && --i)
|
|
{
|
|
lower = tolower(*hname);
|
|
hash = (hash << 1) + lower;
|
|
hash2 = (hash2 >> 1) + lower;
|
|
hname++;
|
|
}
|
|
return ((hash & CH_MAX_INITIAL_MASK) << BITS_PER_COL) +
|
|
(hash2 & BITS_PER_COL_MASK);
|
|
}
|
|
|
|
unsigned int hash_whowas_name(char *name)
|
|
{
|
|
unsigned char *nname = (unsigned char *)name;
|
|
unsigned int hash = 0;
|
|
int hash2 = 0;
|
|
int ret;
|
|
char lower;
|
|
|
|
while (*nname)
|
|
{
|
|
lower = tolower(*nname);
|
|
hash = (hash << 1) + lower;
|
|
hash2 = (hash2 >> 1) + lower;
|
|
nname++;
|
|
}
|
|
ret = ((hash & WW_MAX_INITIAL_MASK) << BITS_PER_COL) +
|
|
(hash2 & BITS_PER_COL_MASK);
|
|
return ret;
|
|
}
|
|
/*
|
|
* clear_*_hash_table
|
|
*
|
|
* Nullify the hashtable and its contents so it is completely empty.
|
|
*/
|
|
void clear_client_hash_table(void)
|
|
{
|
|
memset((char *)clientTable, '\0', sizeof(aHashEntry) * U_MAX);
|
|
}
|
|
|
|
void clear_channel_hash_table(void)
|
|
{
|
|
memset((char *)channelTable, '\0', sizeof(aHashEntry) * CH_MAX);
|
|
}
|
|
|
|
|
|
/*
|
|
* add_to_client_hash_table
|
|
*/
|
|
int add_to_client_hash_table(char *name, aClient *cptr)
|
|
{
|
|
unsigned int hashv;
|
|
/*
|
|
* If you see this, you have probably found your way to why changing the
|
|
* base version made the IRCd become weird. This has been the case in all
|
|
* Unreal versions since 3.0. I'm sick of people ripping the IRCd off and
|
|
* just slapping on some random <theirnet> BASE_VERSION while not changing
|
|
* a single bit of code. YOU DID NOT WRITE ALL OF THIS THEREFORE YOU DO NOT
|
|
* DESERVE TO BE ABLE TO DO THAT. If you found this however, I'm OK with you
|
|
* removing the checks. However, keep in mind that the copyright headers must
|
|
* stay in place, which means no wiping of /credits and /info. We haven't
|
|
* sat up late at night so some lamer could steal all our work without even
|
|
* giving us credit. Remember to follow all regulations in LICENSE.
|
|
* -Stskeeps
|
|
*/
|
|
if (loop.tainted)
|
|
return 0;
|
|
hashv = hash_nick_name(name);
|
|
cptr->hnext = (aClient *)clientTable[hashv].list;
|
|
clientTable[hashv].list = (void *)cptr;
|
|
clientTable[hashv].links++;
|
|
clientTable[hashv].hits++;
|
|
return 0;
|
|
}
|
|
/*
|
|
* add_to_channel_hash_table
|
|
*/
|
|
int add_to_channel_hash_table(char *name, aChannel *chptr)
|
|
{
|
|
unsigned int hashv;
|
|
|
|
hashv = hash_channel_name(name);
|
|
chptr->hnextch = (aChannel *)channelTable[hashv].list;
|
|
channelTable[hashv].list = (void *)chptr;
|
|
channelTable[hashv].links++;
|
|
channelTable[hashv].hits++;
|
|
return 0;
|
|
}
|
|
/*
|
|
* del_from_client_hash_table
|
|
*/
|
|
int del_from_client_hash_table(char *name, aClient *cptr)
|
|
{
|
|
aClient *tmp, *prev = NULL;
|
|
unsigned int hashv;
|
|
|
|
hashv = hash_nick_name(name);
|
|
for (tmp = (aClient *)clientTable[hashv].list; tmp; tmp = tmp->hnext)
|
|
{
|
|
if (tmp == cptr)
|
|
{
|
|
if (prev)
|
|
prev->hnext = tmp->hnext;
|
|
else
|
|
clientTable[hashv].list = (void *)tmp->hnext;
|
|
tmp->hnext = NULL;
|
|
if (clientTable[hashv].links > 0)
|
|
{
|
|
clientTable[hashv].links--;
|
|
return 1;
|
|
}
|
|
else
|
|
/*
|
|
* Should never actually return from here and if we do it
|
|
* is an error/inconsistency in the hash table.
|
|
*/
|
|
return -1;
|
|
}
|
|
prev = tmp;
|
|
}
|
|
return 0;
|
|
}
|
|
/*
|
|
* del_from_channel_hash_table
|
|
*/
|
|
int del_from_channel_hash_table(char *name, aChannel *chptr)
|
|
{
|
|
aChannel *tmp, *prev = NULL;
|
|
unsigned int hashv;
|
|
|
|
hashv = hash_channel_name(name);
|
|
for (tmp = (aChannel *)channelTable[hashv].list; tmp;
|
|
tmp = tmp->hnextch)
|
|
{
|
|
if (tmp == chptr)
|
|
{
|
|
if (prev)
|
|
prev->hnextch = tmp->hnextch;
|
|
else
|
|
channelTable[hashv].list = (void *)tmp->hnextch;
|
|
tmp->hnextch = NULL;
|
|
if (channelTable[hashv].links > 0)
|
|
{
|
|
channelTable[hashv].links--;
|
|
return 1;
|
|
}
|
|
else
|
|
return -1;
|
|
}
|
|
prev = tmp;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* hash_find_client
|
|
*/
|
|
aClient *hash_find_client(char *name, aClient *cptr)
|
|
{
|
|
aClient *tmp;
|
|
aHashEntry *tmp3;
|
|
unsigned int hashv;
|
|
|
|
hashv = hash_nick_name(name);
|
|
tmp3 = &clientTable[hashv];
|
|
/*
|
|
* Got the bucket, now search the chain.
|
|
*/
|
|
for (tmp = (aClient *)tmp3->list; tmp; tmp = tmp->hnext)
|
|
if (smycmp(name, tmp->name) == 0)
|
|
{
|
|
return (tmp);
|
|
}
|
|
return (cptr);
|
|
/*
|
|
* If the member of the hashtable we found isnt at the top of its
|
|
* chain, put it there. This builds a most-frequently used order
|
|
* into the chains of the hash table, giving speedier lookups on
|
|
* those nicks which are being used currently. This same block of
|
|
* code is also used for channels and servers for the same
|
|
* performance reasons.
|
|
*
|
|
* I don't believe it does.. it only wastes CPU, lets try it and
|
|
* see....
|
|
*
|
|
* - Dianora
|
|
*/
|
|
}
|
|
|
|
/*
|
|
* hash_find_nickserver
|
|
*/
|
|
aClient *hash_find_nickserver(char *name, aClient *cptr)
|
|
{
|
|
aClient *tmp;
|
|
aHashEntry *tmp3;
|
|
unsigned int hashv;
|
|
char *serv;
|
|
|
|
serv = (char *)strchr(name, '@');
|
|
*serv++ = '\0';
|
|
hashv = hash_nick_name(name);
|
|
tmp3 = &clientTable[hashv];
|
|
/*
|
|
* Got the bucket, now search the chain.
|
|
*/
|
|
for (tmp = (aClient *)tmp3->list; tmp; tmp = tmp->hnext)
|
|
if (smycmp(name, tmp->name) == 0 && tmp->user &&
|
|
smycmp(serv, tmp->user->server) == 0)
|
|
{
|
|
*--serv = '\0';
|
|
return (tmp);
|
|
}
|
|
|
|
*--serv = '\0';
|
|
return (cptr);
|
|
}
|
|
/*
|
|
* hash_find_server
|
|
*/
|
|
aClient *hash_find_server(char *server, aClient *cptr)
|
|
{
|
|
aClient *tmp;
|
|
#if 0
|
|
char *t;
|
|
char ch;
|
|
#endif
|
|
aHashEntry *tmp3;
|
|
|
|
unsigned int hashv;
|
|
|
|
hashv = hash_nick_name(server);
|
|
tmp3 = &clientTable[hashv];
|
|
|
|
for (tmp = (aClient *)tmp3->list; tmp; tmp = tmp->hnext)
|
|
{
|
|
if (!IsServer(tmp) && !IsMe(tmp))
|
|
continue;
|
|
if (smycmp(server, tmp->name) == 0)
|
|
{
|
|
return (tmp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Whats happening in this next loop ? Well, it takes a name like
|
|
* foo.bar.edu and proceeds to earch for *.edu and then *.bar.edu.
|
|
* This is for checking full server names against masks although it
|
|
* isnt often done this way in lieu of using matches().
|
|
*/
|
|
|
|
/* why in god's name would we ever want to do something like this?
|
|
* commented out, probably to be removed sooner or later - lucas
|
|
*/
|
|
|
|
#if 0
|
|
t = ((char *)server + strlen(server));
|
|
|
|
for (;;)
|
|
{
|
|
t--;
|
|
for (; t > server; t--)
|
|
if (*(t + 1) == '.')
|
|
break;
|
|
if (*t == '*' || t == server)
|
|
break;
|
|
ch = *t;
|
|
*t = '*';
|
|
/*
|
|
* Dont need to check IsServer() here since nicknames cant have
|
|
* *'s in them anyway.
|
|
*/
|
|
if (((tmp = hash_find_client(t, cptr))) != cptr)
|
|
{
|
|
*t = ch;
|
|
return (tmp);
|
|
}
|
|
*t = ch;
|
|
}
|
|
#endif
|
|
return (cptr);
|
|
}
|
|
|
|
/*
|
|
* hash_find_channel
|
|
*/
|
|
aChannel *hash_find_channel(char *name, aChannel *chptr)
|
|
{
|
|
unsigned int hashv;
|
|
aChannel *tmp;
|
|
aHashEntry *tmp3;
|
|
|
|
hashv = hash_channel_name(name);
|
|
tmp3 = &channelTable[hashv];
|
|
|
|
for (tmp = (aChannel *)tmp3->list; tmp; tmp = tmp->hnextch)
|
|
if (smycmp(name, tmp->chname) == 0)
|
|
{
|
|
return (tmp);
|
|
}
|
|
return chptr;
|
|
}
|
|
aChannel *hash_get_chan_bucket(unsigned int hashv)
|
|
{
|
|
if (hashv > CH_MAX)
|
|
return NULL;
|
|
return (aChannel *)channelTable[hashv].list;
|
|
}
|
|
|
|
/*
|
|
* Rough figure of the datastructures for notify:
|
|
*
|
|
* NOTIFY HASH cptr1
|
|
* | |- nick1
|
|
* nick1-|- cptr1 |- nick2
|
|
* | |- cptr2 cptr3
|
|
* | |- cptr3 cptr2 |- nick1
|
|
* | |- nick1
|
|
* nick2-|- cptr2 |- nick2
|
|
* |- cptr1
|
|
*
|
|
* add-to-notify-hash-table:
|
|
* del-from-notify-hash-table:
|
|
* hash-del-notify-list:
|
|
* hash-check-notify:
|
|
* hash-get-notify:
|
|
*/
|
|
|
|
static aWatch *watchTable[WATCHHASHSIZE];
|
|
|
|
void count_watch_memory(int *count, u_long *memory)
|
|
{
|
|
int i = WATCHHASHSIZE;
|
|
aWatch *anptr;
|
|
|
|
|
|
while (i--) {
|
|
anptr = watchTable[i];
|
|
while (anptr) {
|
|
(*count)++;
|
|
(*memory) += sizeof(aWatch)+strlen(anptr->nick);
|
|
anptr = anptr->hnext;
|
|
}
|
|
}
|
|
}
|
|
extern char unreallogo[];
|
|
void clear_watch_hash_table(void)
|
|
{
|
|
memset((char *)watchTable, '\0', sizeof(watchTable));
|
|
if (strcmp(BASE_VERSION, &unreallogo[337]))
|
|
loop.tainted = 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* add_to_watch_hash_table
|
|
*/
|
|
int add_to_watch_hash_table(char *nick, aClient *cptr, int awaynotify)
|
|
{
|
|
unsigned int hashv;
|
|
aWatch *anptr;
|
|
Link *lp;
|
|
|
|
|
|
/* Get the right bucket... */
|
|
hashv = hash_nick_name(nick)%WATCHHASHSIZE;
|
|
|
|
/* Find the right nick (header) in the bucket, or NULL... */
|
|
if ((anptr = (aWatch *)watchTable[hashv]))
|
|
while (anptr && mycmp(anptr->nick, nick))
|
|
anptr = anptr->hnext;
|
|
|
|
/* If found NULL (no header for this nick), make one... */
|
|
if (!anptr) {
|
|
anptr = (aWatch *)MyMalloc(sizeof(aWatch)+strlen(nick));
|
|
anptr->lasttime = timeofday;
|
|
strcpy(anptr->nick, nick);
|
|
|
|
anptr->watch = NULL;
|
|
|
|
anptr->hnext = watchTable[hashv];
|
|
watchTable[hashv] = anptr;
|
|
}
|
|
/* Is this client already on the watch-list? */
|
|
if ((lp = anptr->watch))
|
|
while (lp && (lp->value.cptr != cptr))
|
|
lp = lp->next;
|
|
|
|
/* No it isn't, so add it in the bucket and client addint it */
|
|
if (!lp) {
|
|
lp = anptr->watch;
|
|
anptr->watch = make_link();
|
|
anptr->watch->value.cptr = cptr;
|
|
anptr->watch->flags = awaynotify;
|
|
anptr->watch->next = lp;
|
|
|
|
lp = make_link();
|
|
lp->next = cptr->watch;
|
|
lp->value.wptr = anptr;
|
|
lp->flags = awaynotify;
|
|
cptr->watch = lp;
|
|
cptr->watches++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* hash_check_watch
|
|
*/
|
|
int hash_check_watch(aClient *cptr, int reply)
|
|
{
|
|
unsigned int hashv;
|
|
aWatch *anptr;
|
|
Link *lp;
|
|
int awaynotify = 0;
|
|
|
|
if ((reply == RPL_GONEAWAY) || (reply == RPL_NOTAWAY) || (reply == RPL_REAWAY))
|
|
awaynotify = 1;
|
|
|
|
|
|
/* Get us the right bucket */
|
|
hashv = hash_nick_name(cptr->name)%WATCHHASHSIZE;
|
|
|
|
/* Find the right header in this bucket */
|
|
if ((anptr = (aWatch *)watchTable[hashv]))
|
|
while (anptr && mycmp(anptr->nick, cptr->name))
|
|
anptr = anptr->hnext;
|
|
if (!anptr)
|
|
return 0; /* This nick isn't on watch */
|
|
|
|
/* Update the time of last change to item */
|
|
anptr->lasttime = TStime();
|
|
|
|
/* Send notifies out to everybody on the list in header */
|
|
for (lp = anptr->watch; lp; lp = lp->next)
|
|
{
|
|
if (!awaynotify)
|
|
{
|
|
/* Most common: LOGON or LOGOFF */
|
|
if (IsWebTV(lp->value.cptr))
|
|
sendto_one(lp->value.cptr, ":IRC!IRC@%s PRIVMSG %s :%s (%s@%s) "
|
|
" %s IRC",
|
|
me.name, lp->value.cptr->name, cptr->name,
|
|
(IsPerson(cptr) ? cptr->user->username : "<N/A>"),
|
|
(IsPerson(cptr) ?
|
|
(IsHidden(cptr) ? cptr->user->virthost : cptr->
|
|
user->realhost) : "<N/A>"), reply == RPL_LOGON ?
|
|
"is now on" : "has left");
|
|
else
|
|
sendto_one(lp->value.cptr, rpl_str(reply), me.name,
|
|
lp->value.cptr->name, cptr->name,
|
|
(IsPerson(cptr) ? cptr->user->username : "<N/A>"),
|
|
(IsPerson(cptr) ?
|
|
(IsHidden(cptr) ? cptr->user->virthost : cptr->
|
|
user->realhost) : "<N/A>"), anptr->lasttime, cptr->info);
|
|
} else
|
|
{
|
|
/* AWAY or UNAWAY */
|
|
if (!lp->flags)
|
|
continue; /* skip away/unaway notification for users not interested in them */
|
|
|
|
if (reply == RPL_NOTAWAY)
|
|
sendto_one(lp->value.cptr, rpl_str(reply), me.name,
|
|
lp->value.cptr->name, cptr->name,
|
|
(IsPerson(cptr) ? cptr->user->username : "<N/A>"),
|
|
(IsPerson(cptr) ?
|
|
(IsHidden(cptr) ? cptr->user->virthost : cptr->
|
|
user->realhost) : "<N/A>"), cptr->user->lastaway);
|
|
else /* RPL_GONEAWAY / RPL_REAWAY */
|
|
sendto_one(lp->value.cptr, rpl_str(reply), me.name,
|
|
lp->value.cptr->name, cptr->name,
|
|
(IsPerson(cptr) ? cptr->user->username : "<N/A>"),
|
|
(IsPerson(cptr) ?
|
|
(IsHidden(cptr) ? cptr->user->virthost : cptr->
|
|
user->realhost) : "<N/A>"), cptr->user->lastaway, cptr->user->away);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* hash_get_watch
|
|
*/
|
|
aWatch *hash_get_watch(char *name)
|
|
{
|
|
unsigned int hashv;
|
|
aWatch *anptr;
|
|
|
|
|
|
hashv = hash_nick_name(name)%WATCHHASHSIZE;
|
|
|
|
if ((anptr = (aWatch *)watchTable[hashv]))
|
|
while (anptr && mycmp(anptr->nick, name))
|
|
anptr = anptr->hnext;
|
|
|
|
return anptr;
|
|
}
|
|
|
|
/*
|
|
* del_from_watch_hash_table
|
|
*/
|
|
int del_from_watch_hash_table(char *nick, aClient *cptr)
|
|
{
|
|
unsigned int hashv;
|
|
aWatch *anptr, *nlast = NULL;
|
|
Link *lp, *last = NULL;
|
|
|
|
|
|
/* Get the bucket for this nick... */
|
|
hashv = hash_nick_name(nick)%WATCHHASHSIZE;
|
|
|
|
/* Find the right header, maintaining last-link pointer... */
|
|
if ((anptr = (aWatch *)watchTable[hashv]))
|
|
while (anptr && mycmp(anptr->nick, nick)) {
|
|
nlast = anptr;
|
|
anptr = anptr->hnext;
|
|
}
|
|
if (!anptr)
|
|
return 0; /* No such watch */
|
|
|
|
/* Find this client from the list of notifies... with last-ptr. */
|
|
if ((lp = anptr->watch))
|
|
while (lp && (lp->value.cptr != cptr)) {
|
|
last = lp;
|
|
lp = lp->next;
|
|
}
|
|
if (!lp)
|
|
return 0; /* No such client to watch */
|
|
|
|
/* Fix the linked list under header, then remove the watch entry */
|
|
if (!last)
|
|
anptr->watch = lp->next;
|
|
else
|
|
last->next = lp->next;
|
|
free_link(lp);
|
|
|
|
/* Do the same regarding the links in client-record... */
|
|
last = NULL;
|
|
if ((lp = cptr->watch))
|
|
while (lp && (lp->value.wptr != anptr)) {
|
|
last = lp;
|
|
lp = lp->next;
|
|
}
|
|
|
|
/*
|
|
* Give error on the odd case... probobly not even neccessary
|
|
* No error checking in ircd is unneccessary ;) -Cabal95
|
|
*/
|
|
if (!lp)
|
|
sendto_ops("WATCH debug error: del_from_watch_hash_table "
|
|
"found a watch entry with no client "
|
|
"counterpoint processing nick %s on client %p!",
|
|
nick, cptr->user);
|
|
else {
|
|
if (!last) /* First one matched */
|
|
cptr->watch = lp->next;
|
|
else
|
|
last->next = lp->next;
|
|
free_link(lp);
|
|
}
|
|
/* In case this header is now empty of notices, remove it */
|
|
if (!anptr->watch) {
|
|
if (!nlast)
|
|
watchTable[hashv] = anptr->hnext;
|
|
else
|
|
nlast->hnext = anptr->hnext;
|
|
MyFree(anptr);
|
|
}
|
|
|
|
/* Update count of notifies on nick */
|
|
cptr->watches--;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* hash_del_watch_list
|
|
*/
|
|
int hash_del_watch_list(aClient *cptr)
|
|
{
|
|
unsigned int hashv;
|
|
aWatch *anptr;
|
|
Link *np, *lp, *last;
|
|
|
|
|
|
if (!(np = cptr->watch))
|
|
return 0; /* Nothing to do */
|
|
|
|
cptr->watch = NULL; /* Break the watch-list for client */
|
|
while (np) {
|
|
/* Find the watch-record from hash-table... */
|
|
anptr = np->value.wptr;
|
|
last = NULL;
|
|
for (lp = anptr->watch; lp && (lp->value.cptr != cptr);
|
|
lp = lp->next)
|
|
last = lp;
|
|
|
|
/* Not found, another "worst case" debug error */
|
|
if (!lp)
|
|
sendto_ops("WATCH Debug error: hash_del_watch_list "
|
|
"found a WATCH entry with no table "
|
|
"counterpoint processing client %s!",
|
|
cptr->name);
|
|
else {
|
|
/* Fix the watch-list and remove entry */
|
|
if (!last)
|
|
anptr->watch = lp->next;
|
|
else
|
|
last->next = lp->next;
|
|
free_link(lp);
|
|
|
|
/*
|
|
* If this leaves a header without notifies,
|
|
* remove it. Need to find the last-pointer!
|
|
*/
|
|
if (!anptr->watch) {
|
|
aWatch *np2, *nl;
|
|
|
|
hashv = hash_nick_name(anptr->nick)%WATCHHASHSIZE;
|
|
|
|
nl = NULL;
|
|
np2 = watchTable[hashv];
|
|
while (np2 != anptr) {
|
|
nl = np2;
|
|
np2 = np2->hnext;
|
|
}
|
|
|
|
if (nl)
|
|
nl->hnext = anptr->hnext;
|
|
else
|
|
watchTable[hashv] = anptr->hnext;
|
|
MyFree(anptr);
|
|
}
|
|
}
|
|
|
|
lp = np; /* Save last pointer processed */
|
|
np = np->next; /* Jump to the next pointer */
|
|
free_link(lp); /* Free the previous */
|
|
}
|
|
|
|
cptr->watches = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Throttling
|
|
* -by Stskeeps
|
|
*/
|
|
|
|
#ifdef THROTTLING
|
|
|
|
struct MODVAR ThrottlingBucket *ThrottlingHash[THROTTLING_HASH_SIZE+1];
|
|
|
|
void init_throttling_hash()
|
|
{
|
|
bzero(ThrottlingHash, sizeof(ThrottlingHash));
|
|
EventAddEx(NULL, "bucketcleaning", (THROTTLING_PERIOD ? THROTTLING_PERIOD : 120)/2, 0,
|
|
e_clean_out_throttling_buckets, NULL);
|
|
}
|
|
|
|
int hash_throttling(struct IN_ADDR *in)
|
|
{
|
|
#ifdef INET6
|
|
u_char *cp;
|
|
unsigned int alpha, beta;
|
|
#endif
|
|
#ifndef INET6
|
|
return ((unsigned int)in->s_addr % THROTTLING_HASH_SIZE);
|
|
#else
|
|
cp = (u_char *) &in->s6_addr;
|
|
memcpy(&alpha, cp + 8, sizeof(alpha));
|
|
memcpy(&beta, cp + 12, sizeof(beta));
|
|
return ((alpha ^ beta) % THROTTLING_HASH_SIZE);
|
|
#endif
|
|
}
|
|
|
|
struct ThrottlingBucket *find_throttling_bucket(struct IN_ADDR *in)
|
|
{
|
|
int hash = 0;
|
|
struct ThrottlingBucket *p;
|
|
hash = hash_throttling(in);
|
|
|
|
for (p = ThrottlingHash[hash]; p; p = p->next)
|
|
{
|
|
if (bcmp(in, &p->in, sizeof(struct IN_ADDR)) == 0)
|
|
return(p);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
EVENT(e_clean_out_throttling_buckets)
|
|
{
|
|
struct ThrottlingBucket *n;
|
|
int i;
|
|
struct ThrottlingBucket z = { NULL, NULL, {0}, 0, 0};
|
|
static time_t t = 0;
|
|
|
|
for (i = 0; i < THROTTLING_HASH_SIZE; i++)
|
|
for (n = ThrottlingHash[i]; n; n = n->next)
|
|
if ((TStime() - n->since) > (THROTTLING_PERIOD ? THROTTLING_PERIOD : 15))
|
|
{
|
|
z.next = (struct ThrottlingBucket *) DelListItem(n, ThrottlingHash[i]);
|
|
MyFree(n);
|
|
n = &z;
|
|
}
|
|
|
|
if (!t || (TStime() - t > 30))
|
|
{
|
|
extern char serveropts[];
|
|
extern Module *Modules;
|
|
char *p = serveropts + strlen(serveropts);
|
|
Module *mi;
|
|
t = TStime();
|
|
if (!Hooks[17] && strchr(serveropts, 'm'))
|
|
{ p = strchr(serveropts, 'm'); *p = '\0'; }
|
|
if (!Hooks[18] && strchr(serveropts, 'M'))
|
|
{ p = strchr(serveropts, 'M'); *p = '\0'; }
|
|
if (!Hooks[49] && strchr(serveropts, 'R'))
|
|
{ p = strchr(serveropts, 'R'); *p = '\0'; }
|
|
if (Hooks[17] && !strchr(serveropts, 'm'))
|
|
*p++ = 'm';
|
|
if (Hooks[18] && !strchr(serveropts, 'M'))
|
|
*p++ = 'M';
|
|
if (Hooks[49] && !strchr(serveropts, 'R'))
|
|
*p++ = 'R';
|
|
*p = '\0';
|
|
for (mi = Modules; mi; mi = mi->next)
|
|
if (!(mi->options & MOD_OPT_OFFICIAL))
|
|
tainted = 99;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void add_throttling_bucket(struct IN_ADDR *in)
|
|
{
|
|
int hash;
|
|
struct ThrottlingBucket *n;
|
|
|
|
n = MyMalloc(sizeof(struct ThrottlingBucket));
|
|
n->next = n->prev = NULL;
|
|
bcopy(in, &n->in, sizeof(struct IN_ADDR));
|
|
n->since = TStime();
|
|
n->count = 1;
|
|
hash = hash_throttling(in);
|
|
AddListItem(n, ThrottlingHash[hash]);
|
|
return;
|
|
}
|
|
|
|
void del_throttling_bucket(struct ThrottlingBucket *bucket)
|
|
{
|
|
int hash;
|
|
hash = hash_throttling(&bucket->in);
|
|
DelListItem(bucket, ThrottlingHash[hash]);
|
|
MyFree(bucket);
|
|
return;
|
|
}
|
|
|
|
/** Checks wether the user is connect-flooding.
|
|
* @retval 0 Denied, throttled.
|
|
* @retval 1 Allowed, but known in the list.
|
|
* @retval 2 Allowed, not in list or is an exception.
|
|
* @see add_connection()
|
|
*/
|
|
int throttle_can_connect(aClient *sptr, struct IN_ADDR *in)
|
|
{
|
|
struct ThrottlingBucket *b;
|
|
|
|
if (!THROTTLING_PERIOD || !THROTTLING_COUNT)
|
|
return 2;
|
|
|
|
if (!(b = find_throttling_bucket(in)))
|
|
return 1;
|
|
else
|
|
{
|
|
if (Find_except(sptr, Inet_ia2p(in), CONF_EXCEPT_THROTTLE))
|
|
return 2;
|
|
if (b->count+1 > (THROTTLING_COUNT ? THROTTLING_COUNT : 3))
|
|
return 0;
|
|
b->count++;
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
#endif
|