This repository has been archived on 2025-02-12. You can view files and clone it, but cannot push or open issues or pull requests.
NeoStats-seenserv/seen.c

624 lines
17 KiB
C

/* SeenServ - Nickname Seen Service - NeoStats Addon Module
** Copyright (c) 2003-2006 Justin Hammond, Mark Hetherington, Jeff Lang
**
** 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
**
** SeenServ CVS Identification
** $Id$
*/
#include "neostats.h" /* Required for bot support */
#include "seenserv.h"
#define MAX_NICK_HISTORY 5
#define SEEN_ENTRY_NICK_SIZE ( ( MAXNICK + 3 ) * MAX_NICK_HISTORY )
static list_t *seenlist;
static char combinedtimetext[SS_GENCHARLEN];
static char matchstr[USERHOSTLEN];
static char currentlyconnectedtext[SS_GENCHARLEN];
static char seenentrynick[SEEN_ENTRY_NICK_SIZE];
static char matchednickstr[SS_MESSAGESIZE];
static char nicklower[MAXNICK];
/** @brief findnick
*
* list sorting helper
*
* @param key1
* @param key2
*
* @return results of strcmp
*/
int findnick( const void *key1, const void *key2 )
{
SeenData *sd = ( SeenData * ) key1;
return ircstrcasecmp( sd->nick, key2 );
}
/*
* Removes SeenData for nickname if exists
*
* returns NS_SUCCESS if nick removed or NS_FAILURE if nick not in list
*/
int removepreviousnick(char *nick)
{
lnode_t *ln;
SeenData *sd;
ln = list_last( seenlist );
while ( ln )
{
sd = lnode_get( ln );
if (!ircstrcasecmp(nick, sd->nick))
{
ns_free( sd );
list_delete( seenlist, ln );
lnode_destroy( ln );
return NS_SUCCESS;
}
ln = list_prev( seenlist, ln );
}
return NS_FAILURE;
}
/*
* add new seen entry to list
*/
void addseenentry(char *nick, char *host, char *vhost, char *message, int type)
{
SeenData *sd;
int nickremoved;
nickremoved = removepreviousnick(nick);
sd = ns_calloc(sizeof(SeenData));
strlcpy(sd->nick, nick, MAXNICK);
strlcpy(sd->userhost, host, USERHOSTLEN);
strlcpy(sd->uservhost, vhost, USERHOSTLEN);
strlcpy(sd->message, message ? message : "", SS_MESSAGESIZE);
sd->seentype = type;
sd->seentime = me.now;
sd->recordsaved = 0;
lnode_create_append( seenlist, sd );
/* only check list limit if the nick wasn't already in the list */
if( nickremoved == NS_FAILURE )
checkseenlistlimit(SS_LISTLIMIT_COUNT);
return;
}
/*
* Save Data to DB on timer
*
* remove records from list if set to work from DB only
*/
int dbsavetimer(void *userptr)
{
lnode_t *ln, *ln2;
SeenData *sd;
SET_SEGV_LOCATION();
ln = list_last( seenlist );
while ( ln )
{
sd = lnode_get( ln );
ln2 = list_prev( seenlist, ln );
if (sd->recordsaved == 0)
{
sd->recordsaved = 1;
strlcpy( nicklower, sd->nick, MAXNICK );
DBAStore( "seendata", ns_strlwr(nicklower),( void * )sd, sizeof( SeenData ) );
if( !SeenServ.memorylist )
{
ns_free( sd );
list_delete( seenlist, ln );
lnode_destroy( ln );
}
} else {
if( !SeenServ.memorylist )
{
ns_free( sd );
list_delete( seenlist, ln );
lnode_destroy( ln );
} else {
return NS_SUCCESS;
}
}
ln = ln2;
}
return NS_SUCCESS;
}
/*
* Removes SeenData if records past max entries setting
*/
void checkseenlistlimit(int checktype)
{
int currentlistcount;
int maxageallowed;
lnode_t *ln, *ln2;
SeenData *sd;
currentlistcount = list_count(seenlist);
maxageallowed = me.now - ( SeenServ.expiretime * TS_ONE_DAY );
ln = list_first( seenlist );
sd = lnode_get( ln );
while( ( checktype == SS_LISTLIMIT_COUNT && currentlistcount > SeenServ.maxentries ) || ( checktype == SS_LISTLIMIT_AGE && SeenServ.expiretime > 0 && maxageallowed > sd->seentime ) )
{
ln2 = list_next( seenlist, ln );
strlcpy( nicklower, sd->nick, MAXNICK );
DBADelete( "seendata", ns_strlwr( nicklower ) );
ns_free( sd );
list_delete( seenlist, ln );
lnode_destroy( ln );
ln = ln2;
sd = lnode_get( ln );
currentlistcount --;
}
return;
}
/*
* Load Saved Seen Records
*/
int loadseenrecords(void *data, int size)
{
SeenData *sd;
sd = ns_calloc( sizeof( SeenData ) );
os_memcpy( sd, data, sizeof( SeenData ) );
sd->recordsaved = 1;
lnode_create_append( seenlist, sd );
return NS_FALSE;
}
void createseenlist(void)
{
seenlist = list_create( LISTCOUNT_T_MAX );
}
void loadseendata(void)
{
DBAFetchRows( "seendata", loadseenrecords );
list_sort( seenlist, sortlistbytime );
return;
}
int sortlistbytime( const void *key1, const void *key2 )
{
const SeenData *sd1 = key1;
const SeenData *sd2 = key2;
return (sd1->seentime - sd2->seentime);
}
/*
* Destroy Seen List
*/
void destroyseenlist(void)
{
lnode_t *ln, *ln2;
SeenData *sd;
ln = list_first(seenlist);
while( ln )
{
sd = lnode_get(ln);
ln2 = list_next( seenlist, ln );
ns_free(sd);
list_delete(seenlist, ln);
lnode_destroy(ln);
ln = ln2;
}
list_destroy_auto(seenlist);
}
#if 0
/** seen_report
*
* handles channel/user message selection
*/
/* we do this via a macro now, to avoid possible exploits,
* see seenserv.h. If the macro isn't portable, then we can use this function now
* without having the exploit present, but its spins more CPU cycles!
*/
static char seen_report_buf[BUFSIZE];
void seen_report( const CmdParams *cmdparams, const char *fmt, ... )
{
va_list ap;
va_start( ap, fmt );
ircvsnprintf( seen_report_buf, BUFSIZE, fmt, ap );
va_end( ap );
if( cmdparams->channel == NULL )
irc_prefmsg (sns_bot, cmdparams->source, "%s", seen_report_buf );
else
irc_chanprivmsg (sns_bot, cmdparams->channel->name, "%s" seen_report_buf );
}
#endif
/*
* Check whether we can run the seen command
*/
static int SeenAvailable( const CmdParams *cmdparams )
{
#if 0
if( strstr(cmdparams->av[0], "%") != NULL )
return NS_FALSE;
#endif
if( cmdparams->source->user->ulevel < NS_ULEVEL_LOCOPER )
{
if( !SeenServ.enable && cmdparams->channel == NULL )
return NS_FALSE;
if( !SeenServ.enableseenchan && cmdparams->channel != NULL )
return NS_FALSE;
}
return NS_TRUE;
}
/*
* Seen for wildcarded Host
*/
int sns_cmd_seenhost(const CmdParams *cmdparams)
{
Client *u;
SET_SEGV_LOCATION();
/* do lookup on nick only if working from DB and not memory list */
if( !SeenServ.memorylist )
return sns_cmd_seennick(cmdparams);
if( SeenAvailable( cmdparams ) == NS_FALSE )
return NS_SUCCESS;
/* ensure DB is saved before doing lookup */
dbsavetimer( NULL );
if( ValidateNick( cmdparams->av[0] ) == NS_SUCCESS )
{
u = FindUser( cmdparams->av[0] );
if (u)
{
seen_report( cmdparams, "%s (%s@%s) is connected right now", u->name, u->user->username, u->user->vhost);
return NS_SUCCESS;
}
}
if( CheckSeenData( cmdparams, SS_CHECK_WILDCARD ) == NS_FAILURE )
seen_report( cmdparams, "Sorry %s, I can't remember seeing anyone matching that mask (%s)", cmdparams->source->name, matchstr );
return NS_SUCCESS;
}
/*
* Seen for valid nickname
*/
int sns_cmd_seennick(const CmdParams *cmdparams)
{
Client *u;
SET_SEGV_LOCATION();
if( SeenAvailable( cmdparams ) == NS_FALSE )
return NS_SUCCESS;
/* ensure DB is saved before doing lookup */
dbsavetimer( NULL );
if( ValidateNick( cmdparams->av[0] ) == NS_FAILURE )
{
seen_report( cmdparams, "%s is not a valid nickname", cmdparams->av[0] );
return NS_SUCCESS;
}
u = FindUser( cmdparams->av[0] );
if (u)
{
seen_report( cmdparams, "%s (%s@%s) is connected right now", u->name, u->user->username, u->user->vhost);
return NS_SUCCESS;
}
if( CheckSeenData( cmdparams, SS_CHECK_NICK ) == NS_FAILURE )
seen_report( cmdparams, "Sorry %s, I can't remember seeing anyone called %s", cmdparams->source->name, cmdparams->av[0] );
return NS_SUCCESS;
}
/*
* Build time string
*/
void BuildTimeString( int ts )
{
static char temptimetext[12];
int d, h, m, s;
combinedtimetext[0] = '\0';
if (ts > 0)
{
s = ( ts % 60 );
ts -= s;
ts = ( ts / 60 );
m = ( ts % 60 );
ts -= m;
ts = ( ts / 60 );
h = ( ts % 24 );
ts -= h;
d = ( ts / 24 );
if( d )
{
ircsnprintf( temptimetext, 12, "%d Days ", d );
strlcat( combinedtimetext, temptimetext, SS_GENCHARLEN );
}
if( h )
{
ircsnprintf( temptimetext, 12, "%d Hours ", h );
strlcat( combinedtimetext, temptimetext, SS_GENCHARLEN );
}
if( m )
{
ircsnprintf( temptimetext, 12, "%d Minutes ", m );
strlcat( combinedtimetext, temptimetext, SS_GENCHARLEN );
}
if( s )
{
ircsnprintf( temptimetext, 12, "%d Seconds", s );
strlcat( combinedtimetext, temptimetext, SS_GENCHARLEN );
}
} else {
ircsnprintf( combinedtimetext, SS_GENCHARLEN, "0 Seconds" );
}
}
/*
* Check For Seen Records
*/
int CheckSeenData(const CmdParams *cmdparams, SEEN_CHECK checktype)
{
lnode_t *ln, *oln[MAX_NICK_HISTORY];
SeenData *sd, *sdo = NULL;
Client *u;
Channel *c;
int matchfound = 0, seenentriesfound = 0, maxageallowed = 0;
int isopersource = 0, i;
if( cmdparams->source->user->ulevel >= NS_ULEVEL_LOCOPER )
isopersource = 1;
/* used for expiring records on age if shown in request */
for( i = 0 ; i < MAX_NICK_HISTORY ; i++ )
oln[i] = NULL;
seenentrynick[0] = '\0';
currentlyconnectedtext[0] = '\0';
if (checktype == SS_CHECK_WILDCARD)
{
if ( strchr( cmdparams->av[0], '*' ) )
ircsnprintf(matchstr, USERHOSTLEN, "%s", cmdparams->av[0]);
else
ircsnprintf(matchstr, USERHOSTLEN, "*%s*", cmdparams->av[0]);
}
if( !SeenServ.memorylist )
{
sdo = ns_calloc( sizeof( SeenData ) );
strlcpy( nicklower, cmdparams->av[0], MAXNICK );
if( DBAFetch( "seendata", ns_strlwr(nicklower), ( void * )sdo, sizeof( SeenData ) ) != NS_FAILURE )
seenentriesfound = 1;
} else {
ln = list_last(seenlist);
while( ln != NULL && seenentriesfound < MAX_NICK_HISTORY )
{
sd = lnode_get(ln);
if (checktype == SS_CHECK_NICK)
{
if( !ircstrcasecmp( cmdparams->av[0], sd->nick ) )
matchfound = 1;
}
else if (checktype == SS_CHECK_WILDCARD)
{
if( isopersource )
{
if ( match( matchstr, sd->userhost ) )
matchfound = 1;
}
if( match( matchstr, sd->uservhost ) )
matchfound = 1;
}
if (matchfound)
{
oln[seenentriesfound] = ln;
seenentriesfound++;
if( seenentriesfound == 1 )
{
sdo = sd;
if (checktype == SS_CHECK_NICK)
break;
else
strlcpy( seenentrynick, sd->nick, SEEN_ENTRY_NICK_SIZE );
} else {
strlcat( seenentrynick, ", ", SEEN_ENTRY_NICK_SIZE );
strlcat( seenentrynick, sd->nick, SEEN_ENTRY_NICK_SIZE );
}
matchfound = 0;
}
ln = list_prev(seenlist, ln);
}
}
if( seenentriesfound == 0 )
return NS_FAILURE;
BuildTimeString( ( int )( me.now - sdo->seentime ) );
matchednickstr[0] = '\0';
if( seenentriesfound > 1 )
ircsnprintf(matchednickstr, SS_MESSAGESIZE, "The %d most recent matches are - %s : ", seenentriesfound, seenentrynick);
switch( sdo->seentype )
{
case SS_CONNECTED:
u = FindUser(sdo->nick);
if (u)
{
if (!ircstrcasecmp(sdo->userhost, u->user->userhostmask))
ircsnprintf(currentlyconnectedtext, SS_GENCHARLEN, ", %s is currently connected", u->name);
}
if( isopersource && cmdparams->channel == NULL )
irc_prefmsg( sns_bot, cmdparams->source, "%s%s was last seen connecting %s ago%s", matchednickstr, sdo->userhost, combinedtimetext, currentlyconnectedtext );
else
seen_report( cmdparams, "%s%s was last seen connecting %s ago%s", matchednickstr, sdo->nick, combinedtimetext, currentlyconnectedtext );
break;
case SS_QUIT:
if( isopersource && cmdparams->channel == NULL )
irc_prefmsg( sns_bot, cmdparams->source, "%s%s was last seen quiting %s ago, stating %s", matchednickstr, sdo->userhost, combinedtimetext, sdo->message );
else
seen_report( cmdparams, "%s%s was last seen quiting %s ago, stating %s", matchednickstr, sdo->uservhost, combinedtimetext, sdo->message);
break;
case SS_KILLED:
if( isopersource && cmdparams->channel == NULL )
irc_prefmsg( sns_bot, cmdparams->source, "%s%s was last seen being killed %s ago %s", matchednickstr, sdo->userhost, combinedtimetext, sdo->message );
else
seen_report( cmdparams, "%s%s was last seen being killed %s ago %s", matchednickstr, sdo->uservhost, combinedtimetext, sdo->message );
break;
case SS_NICKCHANGE:
u = FindUser(sdo->message);
if (u)
{
if (!ircstrcasecmp(sdo->userhost, u->user->userhostmask))
ircsnprintf(currentlyconnectedtext, SS_GENCHARLEN, ", %s is currently connected", u->name);
}
if( isopersource && cmdparams->channel == NULL )
irc_prefmsg( sns_bot, cmdparams->source, "%s%s was last seen changing Nickname %s ago to %s%s", matchednickstr, sdo->userhost, combinedtimetext, sdo->message, currentlyconnectedtext );
else
seen_report( cmdparams, "%s%s was last seen changing Nickname %s ago to %s%s", matchednickstr, sdo->uservhost, combinedtimetext, sdo->message, currentlyconnectedtext );
break;
case SS_JOIN:
u = FindUser(sdo->nick);
if (u)
{
if (!ircstrcasecmp(sdo->userhost, u->user->userhostmask))
{
c = FindChannel(sdo->message);
if (c)
{
if (IsChannelMember(c, u) && !is_hidden_chan(c))
ircsnprintf(currentlyconnectedtext, SS_GENCHARLEN, ", %s is currently in %s", u->name, c->name);
}
}
}
if( isopersource && cmdparams->channel == NULL )
irc_prefmsg (sns_bot, cmdparams->source, "%s%s was last seen Joining %s %s ago%s", matchednickstr, sdo->userhost, sdo->message, combinedtimetext, currentlyconnectedtext);
else
seen_report( cmdparams, "%s%s was last seen Joining %s %s ago%s", matchednickstr, sdo->uservhost, sdo->message, combinedtimetext, currentlyconnectedtext );
break;
case SS_PART:
if( isopersource && cmdparams->channel == NULL )
irc_prefmsg( sns_bot, cmdparams->source, "%s%s was last seen Parting %s %s ago", matchednickstr, sdo->userhost, sdo->message, combinedtimetext );
else
seen_report( cmdparams, "%s%s was last seen Parting %s %s ago", matchednickstr, sdo->uservhost, sdo->message, combinedtimetext );
break;
case SS_KICKED:
if( isopersource && cmdparams->channel == NULL )
irc_prefmsg( sns_bot, cmdparams->source, "%s%s was last seen being Kicked From %s %s ago", matchednickstr, sdo->userhost, sdo->message, combinedtimetext );
else
seen_report( cmdparams, "%s%s was last seen Kicked From %s %s", matchednickstr, sdo->uservhost, sdo->message, combinedtimetext);
break;
default:
break;
}
if( !SeenServ.memorylist )
{
/* delete record if DB Only and past expiration date */
if( SeenServ.expiretime > 0 && ( ( me.now - ( SeenServ.expiretime * TS_ONE_DAY ) ) > sdo->seentime ) )
{
strlcpy( nicklower, sdo->nick, MAXNICK );
DBADelete( "seendata", ns_strlwr(nicklower) );
}
ns_free( sdo );
} else {
/* expire displayed records on age if required */
if( SeenServ.expiretime > 0 )
{
maxageallowed = me.now - ( SeenServ.expiretime * TS_ONE_DAY );
i = 0;
while( i < MAX_NICK_HISTORY && oln[i] != NULL )
{
ln = oln[i];
sd = lnode_get( ln );
if( maxageallowed > sd->seentime )
{
strlcpy( nicklower, sd->nick, MAXNICK );
DBADelete( "seendata", ns_strlwr(nicklower) );
ns_free( sd );
list_delete( seenlist, ln );
lnode_destroy( ln );
}
i++;
}
}
}
return NS_SUCCESS;
}
/*
* Delete all matching entries
*/
int sns_cmd_del(const CmdParams *cmdparams)
{
lnode_t *ln, *ln2;
SeenData *sd;
int i;
SET_SEGV_LOCATION();
i = 0;
ln = list_first(seenlist);
while (ln != NULL)
{
sd = lnode_get(ln);
if (match(cmdparams->av[0], sd->userhost) || match(cmdparams->av[0], sd->uservhost))
{
strlcpy( nicklower, sd->nick, MAXNICK );
DBADelete( "seendata", ns_strlwr( nicklower ) );
i++;
ns_free(sd);
ln2 = list_next(seenlist, ln);
list_delete(seenlist, ln);
lnode_destroy(ln);
ln = ln2;
} else {
ln = list_next(seenlist, ln);
}
}
seen_report( cmdparams, "%d matching entries deleted", i );
return NS_SUCCESS;
}
/*
* Display Seen Statistics
*/
int sns_cmd_status(const CmdParams *cmdparams)
{
int seenstats[SEEN_TYPE_MAX];
lnode_t *ln;
SeenData *sd;
SET_SEGV_LOCATION();
if( !SeenServ.memorylist )
{
seen_report( cmdparams, "Seen Statistics Unavailable when using DB only" );
return NS_SUCCESS;
}
os_memset( seenstats, 0, sizeof( seenstats ) );
ln = list_first(seenlist);
while (ln)
{
sd = lnode_get(ln);
seenstats[ sd->seentype ]++;
ln = list_next(seenlist, ln);
}
seen_report( cmdparams, "Seen Statistics (Current Records Per Type)" );
seen_report( cmdparams, "%d Connections", seenstats[SS_CONNECTED] );
seen_report( cmdparams, "%d Quits", seenstats[SS_QUIT] );
seen_report( cmdparams, "%d Kills", seenstats[SS_KILLED] );
seen_report( cmdparams, "%d Nick Changes", seenstats[SS_NICKCHANGE] );
seen_report( cmdparams, "%d Channel Joins", seenstats[SS_JOIN] );
seen_report( cmdparams, "%d Channel Parts", seenstats[SS_PART] );
seen_report( cmdparams, "%d Channel Kicks", seenstats[SS_KICKED] );
seen_report( cmdparams, "End Of Statistics");
return NS_SUCCESS;
}