#ifdef RCS
static char rcsid[]="$Id: user.c,v 1.1.1.1 2000/11/13 02:42:50 holsta Exp $";
#endif
/******************************************************************************
* Internetting Cooperating Programmers
* ----------------------------------------------------------------------------
*
* ____ PROJECT
* | _ \ __ _ _ __ ___ ___ _ __
* | | | |/ _` | '_ \ / __/ _ \ '__|
* | |_| | (_| | | | | (_| __/ |
* |____/ \__,_|_| |_|\___\___|_| the IRC bot
*
* All files in this archive are subject to the GNU General Public License.
*
* $Source: /cvsroot/dancer/dancer/src/user.c,v $
* $Revision: 1.1.1.1 $
* $Date: 2000/11/13 02:42:50 $
* $Author: holsta $
* $State: Exp $
* $Locker: $
*
* ---------------------------------------------------------------------------
*****************************************************************************/
#include "dancer.h"
#include "trio.h"
#include "strio.h"
#include "function.h"
#include "user.h"
#include "tell.h"
#include "seen.h"
#include "transfer.h"
#include "flood.h"
#include "ourcrypt.h"
#include "link.h"
#include "bans.h"
#ifdef HAVE_LIBFPL
# include "fplrun.h"
#endif
#include <sys/socket.h> /* AF_INET */
/* --- Global ----------------------------------------------------- */
extern time_t now;
extern char nickname[];
extern char defaultpasswd[];
extern bool botop;
extern bool welcome;
extern bool warnmode;
extern bool mute;
extern bool nickflood;
extern bool masterflood;
extern int oplevel;
extern long levels[];
extern time_t mastertime;
char myaddr[MIDBUFFER];
char botmatch[MIDBUFFER];
int numUsers = 0;
int numGuests = 0;
int numSplitGuests = 0;
int numClients = 0;
int totalnumberofsplits = 0;
time_t lastsplittime = 0;
itemuser *userHead = NULL;
itemguest *guestHead = NULL;
itemsplit *splitHead = NULL;
itemclient *clientHead = NULL;
itemident *identHead = NULL;
itemlink *linkHead = NULL;
/* --- GetUserFlags ----------------------------------------------- */
static void GetUserFlags(uFlags *flags, char *pointer)
{
if (pointer && ('-' == *pointer)) {
for (pointer++; *pointer; pointer++) {
switch (*pointer) {
case FLAG_AUTOOP:
flags->autoop = TRUE;
break;
case FLAG_BANPROTECT:
flags->banprotect = TRUE;
break;
case FLAG_LINKBOT:
flags->linkbot = TRUE;
break;
case FLAG_REGULARBOT:
flags->regularbot = TRUE;
break;
case FLAG_DELETE:
flags->delete = TRUE;
break;
case FLAG_STEALTH:
flags->stealth = TRUE;
break;
}
}
}
}
/* --- AddUser ---------------------------------------------------- */
itemuser *AddUser(char *line)
{
char nick[NICKLEN+1], flags[MINIBUFFER], realname[MIDBUFFER];
char compare[NICKLEN+1];
char *pointer;
itemuser *u;
snapshot;
if (3 == StrScan(line, "%"NICKLENTXT"s %"MINIBUFFERTXT"s %"MIDBUFFERTXT"[^\n]",
nick, flags, realname)) {
/*
* Only add nicknames that use legal characters, as other names are
* likely to be a proof of a broken userfile or similar
*/
if ((1 == StrScan(nick, "%[0-9A-~-]", compare)) &&
StrEqualCase(nick, compare) &&
('A' <= nick[0]) && ('~' >= nick[0])) {
u = NewEntry(itemuser);
if (u) {
InsertLast(userHead, u);
u->domainhead = NewList(itemlist);
u->nick = StrDuplicate(nick);
u->level = StrToLong(flags, &pointer, 10);
GetUserFlags(&u->flags, pointer);
u->realname = StrDuplicate(realname);
numUsers++;
return u;
}
}
}
return NULL;
}
void AddNewData(itemuser *u, char *line)
{
char password[MINIBUFFER], lang[MINIBUFFER] = "";
time_t passwdtime = 0;
time_t modified = 0;
time_t created = 0;
long checksum = 0;
long totaljoins = 0;
long newsid = 0;
snapshot;
if (1 <= StrScan(line, "%"MINIBUFFERTXT"s %d %d %d %d %d %"MINIBUFFERTXT"s %d",
password, &passwdtime, &modified, &created, &checksum,
&totaljoins, lang, &newsid)) {
if (u->passwd)
StrFree(u->passwd);
u->passwd = StrDuplicate(password);
u->passwdtime = passwdtime;
u->created = created;
u->modified = modified;
u->checksum = checksum;
/*
* New in 4.1.5. (Added to once and for all stop the complaints on
* the join-count system that currently follows the hostpattern rather
* than the user.) Do note that this data will get changed on each join
* without that should count as a 'changed user' and thus the checksum
* routine MUST NOT include this when calculating.
* - Bagder
*/
u->totaljoins = totaljoins;
u->language = lang[0] ? LanguageNum(lang) : defaultlang;
u->newsid = newsid;
}
}
void AddComment(itemuser *u, char *line)
{
char comment[MIDBUFFER];
snapshot;
if (1 == StrScan(line, "%"MIDBUFFERTXT"[^\n]", comment)) {
if (u->comment)
StrFree(u->comment);
u->comment = StrDuplicate(comment);
}
}
void AddMoreInfo(itemuser *u, char *line)
{
char buffer[MIDBUFFER];
time_t when;
int flags;
snapshot;
if (3 == StrScan(line, "%"MIDBUFFERTXT"s %d %d", buffer, &when, &flags)) {
if (u->stat_bywhom)
StrFree(u->stat_bywhom);
u->stat_bywhom = StrDuplicate(buffer);
u->stat_lastchange = when;
u->stat_lastdone = flags;
}
}
void AddDomain(itemuser *u, char *line)
{
char domain[MIDBUFFER];
itemlist *l;
snapshot;
if (1 == StrScan(line, "%"MIDBUFFERTXT"[^ \n]", domain)) {
l = NewEntry(itemlist);
if (l) {
l->pointer = StrDuplicate(domain);
if (l->pointer) {
InsertLast(u->domainhead, l);
}
else {
free(l);
}
}
}
}
void AddLabel(itemuser *u, char *line)
{
#ifdef HAVE_LIBFPL
char labelname[BIGBUFFER], labelcontents[BIGBUFFER];
snapshot;
if (2 == StrScan(line, "%"BIGBUFFERTXT"s :%"BIGBUFFERTXT"[^\n]",
labelname, labelcontents)) {
UserlabelSet(labelname, labelcontents, LAB_STRING,
(itemuserlabel **)&u->label);
}
#endif
}
static long chksumstr(char *str)
{
long checksum = 0;
if (str) {
while (*str) {
checksum <<= 2;
checksum += *str;
str++;
}
return checksum;
}
return 0xC0CAC01A;
}
static long chksumint(int val)
{
return (val + 0xDEAD);
}
long ChecksumUser(itemuser *u)
{
long checksum = 0;
itemlist *l;
checksum = chksumstr(u->nick);
checksum += chksumint(u->level);
checksum += chksumstr(VerboseFlags(&u->flags));
checksum += chksumstr(u->realname);
checksum += chksumstr(u->passwd);
checksum += chksumstr(u->comment);
checksum += chksumint(u->passwdtime);
checksum += chksumint(u->modified);
checksum += chksumint(u->created);
if (u->stat_bywhom) {
checksum += chksumstr(u->stat_bywhom);
checksum += chksumint(u->stat_lastchange);
checksum += chksumint(u->stat_lastdone);
}
for (l = First(u->domainhead); l; l = Next(l)) {
checksum += chksumstr((char *)l->pointer);
}
return checksum;
}
/* --- UserLoad --------------------------------------------------- */
bool UserLoad(char *filename)
{
char line[MAXLINE];
bool changed = FALSE;
long checksum;
itemuser *u = NULL;
FILE *f;
snapshot;
if ((NULL == userHead) || (NULL == filename) || (NIL == filename[0]))
return FALSE;
f = fopen(filename, "r");
if (f) {
while (fgets(line, sizeof(line), f)) {
switch (line[0]) {
case '#':
case '\n':
break;
case '-': /* password */
if (u)
AddNewData(u, &line[1]);
break;
case '?': /* comment */
if (u)
AddComment(u, &line[1]);
break;
case '*': /* change info */
if (u)
AddMoreInfo(u, &line[1]);
break;
case '!': /* account pattern */
if (u)
AddDomain(u, &line[1]);
break;
case '$': /* fpl-added labels in the format <label> :<value> */
if (u)
AddLabel(u, &line[1]);
break;
default: /* new user */
u = AddUser(line);
break;
}
}
fclose(f);
/*
* Fill in default password if none is supplied and
* check checksums to track manually modified users
*/
for (u = First(userHead); u; u = Next(u)) {
checksum = ChecksumUser(u);
if (u->checksum && (u->checksum != checksum))
changed = TRUE;
else
changed = FALSE;
if (NULL == u->passwd) {
u->passwd = MakePassword(defaultpasswd);
u->passwdtime = now;
changed = TRUE;
}
if (changed)
u->modified = now;
}
return TRUE;
}
return FALSE;
}
/* --- UserReload ------------------------------------------------- */
void FreeUser(void *v)
{
itemuser *u;
u = (itemuser *)v;
if (u) {
if (u->nick)
StrFree(u->nick);
if (u->realname)
StrFree(u->realname);
if (u->passwd)
StrFree(u->passwd);
if (u->comment)
StrFree(u->comment);
if (u->stat_bywhom)
StrFree(u->stat_bywhom);
DeleteList(u->domainhead, FreeList);
#ifdef HAVE_LIBFPL
DeleteList(u->label, FreeUserlabel);
#endif
numUsers--;
}
}
void UserReload(char *filename)
{
itemident *p;
snapshot;
FlushList(userHead, FreeUser);
UserLoad(filename);
for (p = First(identHead); p; p = Next(p)) {
p->user = FindUser(p->nick, p->host);
if (p->user) {
p->level = p->user->level;
}
else {
p->level = 0;
p->passed = FALSE;
}
}
}
char *VerboseFlags(uFlags *flags)
{
static char buffer[MINIBUFFER];
snapshot;
if (flags->banprotect ||
flags->autoop ||
flags->linkbot ||
flags->regularbot ||
flags->delete ||
flags->stealth) {
StrFormatMax(buffer, sizeof(buffer), "Flags:%s%s%s%s%s%s",
flags->autoop ? " Autoop" : "",
flags->banprotect ? " Banprotected" : "",
flags->linkbot ? " Linkbot" : "",
flags->regularbot ? " Bot" : "",
flags->stealth ? " Stealth" : "",
flags->delete ? " DELETED" : "");
return buffer;
}
return NULL;
}
char *SetUserFlags(uFlags *flags)
{
static char buf[FLAG_MAXFLAGS+1];
char *bufp = buf;
if (flags->autoop)
*bufp++ = FLAG_AUTOOP;
if (flags->banprotect)
*bufp++ = FLAG_BANPROTECT;
if (flags->linkbot)
*bufp++ = FLAG_LINKBOT;
if (flags->regularbot)
*bufp++ = FLAG_REGULARBOT;
if (flags->delete)
*bufp++ = FLAG_DELETE;
if (flags->stealth)
*bufp++ = FLAG_STEALTH;
*bufp = (char)0;
return buf;
}
char *WriteUserHead(char *buffer, size_t buffer_size, itemuser *u)
{
int length, rc;
snapshot;
u->checksum = ChecksumUser(u);
rc = StrFormatMax(buffer, buffer_size, " %s %d-%s %s\n" "-%s %d %d %d %d %d %s %d\n",
u->nick, u->level, SetUserFlags(&u->flags), u->realname,
u->passwd, u->passwdtime, u->modified, u->created,
u->checksum, u->totaljoins, LanguageShort(u->language),
u->newsid);
if (rc > 0) {
length = rc;
if (u->comment) {
rc = StrFormatMax(&buffer[length], buffer_size - length, "?%s\n",
u->comment);
if (rc > 0)
length += rc;
}
if (u->stat_bywhom) {
rc = StrFormatMax(&buffer[length], buffer_size - length, "*%s %d %d\n",
u->stat_bywhom, u->stat_lastchange, u->stat_lastdone);
if (rc > 0)
length += rc;
}
}
return buffer;
}
/* --- UserSave --------------------------------------------------- */
/* Returns TRUE on failure */
bool UserSave(void)
{
extern char userfile[];
char tempfile[MIDBUFFER], buffer[3*BIGBUFFER];
bool ok = TRUE;
itemuser *u;
itemlist *l;
#ifdef HAVE_LIBFPL
itemuserlabel *ul;
#endif
FILE *f;
snapshot;
if (EmptyList(userHead))
return TRUE;
StrFormatMax(tempfile, sizeof(tempfile), "%s~", userfile);
f = fopen(tempfile, "w");
if (f) {
if (0 > fprintf(f, "# Dancer userlist version: " VERSIONMSG "\n")) {
ok = FALSE;
}
for (u = First(userHead); u && ok; u = Next(u)) {
if (u->flags.delete && ((u->modified + USER_DELETE_TIMEOUT) < now))
/*
* This user has been marked for deletion and it has not been
* changed for the USER_DELETE_TIMEOUT time. That means we should
* no longer save it. It is now deleted for REAL. At least in the
* next restart. - Bagder (added to 4.1)
*/
continue;
if (0 > fprintf(f, "%s", WriteUserHead(buffer, sizeof(buffer), u))) {
ok = FALSE;
break;
}
for (l = First(u->domainhead); l && ok; l = Next(l)) {
if (0 > fprintf(f, "!%s\n", (char *)l->pointer)) {
ok = FALSE;
break;
}
}
#ifdef HAVE_LIBFPL
for (ul = First((itemuserlabel *)u->label); ul && ok; ul = Next(ul)) {
if (ul->label && (ul->label->flags & LAB_SAVE) && (ul->label->flags & LAB_DEFINED)) {
if (ul->label->flags & LAB_STRING) {
if (0 > fprintf(f, "$%s :%s\n", ul->label->name, ul->cont.str)) {
ok = FALSE;
break;
}
}
else if (0 > fprintf(f, "$%s :%d\n", ul->label->name, ul->cont.val)) {
ok = FALSE;
break;
}
}
}
#endif
}
fclose(f);
if (ok)
rename(tempfile, userfile);
}
return !ok;
}
/* --- LatestUserUpdate ------------------------------------------- */
time_t LatestUserUpdate(void)
{
time_t time = 0;
itemuser *u;
for (u = First(userHead); u; u = Next(u)) {
if (u->modified > time)
time = u->modified;
}
return time;
}
/* --- FindUserByNick --------------------------------------------- */
itemuser *FindUserByNick(char *nick)
{
itemuser *u;
for (u = First(userHead); u; u = Next(u)) {
if (IRCEqual(u->nick, nick))
return u;
}
return NULL;
}
/* --- FindUser --------------------------------------------------- */
itemuser *FindUser(char *nick, char *userhost)
{
char nickpattern[NICKLEN+1];
char *hostpattern, *pointer;
itemuser *u;
itemlist *l;
snapshot;
if (userhost) {
for (u = First(userHead); u; u = Next(u)) {
for (l = First(u->domainhead); l; l = Next(l)) {
hostpattern = (char *)l->pointer;
if (nick) {
pointer = StrIndex(hostpattern, '!');
if (pointer) {
nickpattern[0] = (char)0;
StrScan(hostpattern, "%"NICKLENTXT"[^!]", nickpattern);
/* If nick doesn't match, the whole pattern doesn't match */
if (!Match(nick, nickpattern))
continue;
/* Make hostpattern point to userhost part of the pattern */
hostpattern = &pointer[1];
}
}
if (Match(userhost, hostpattern))
return u;
if (IsPrefix(userhost)) {
if (Match(&userhost[1], hostpattern))
return u;
if (IsPrefix(hostpattern))
if (Match(&userhost[1], &hostpattern[1]))
return u;
}
else {
if (IsPrefix(hostpattern))
if (Match(userhost, &hostpattern[1]))
return u;
}
}
}
}
return NULL;
}
/* --- SplitHost -------------------------------------------------- */
bool SplitHost(char *userhost, /* user@machine.host.domain */
char *parsedhost, /* should become 'machine.host.domain' */
char *parseddomain, /* should become '*.host.domain' OR
'machine.host.domain' if it is hostisp */
char *parseduser) /* should become 'user' */
{
char *host, *xs, *ptr;
int i;
snapshot;
host = StrIndexLast(userhost, '@');
if (host) {
StrCopyMax(parsedhost, MIDBUFFER, &host[1]);
xs = Userdomain(userhost);
if (xs) {
ptr = StrIndex(xs, '@');
StrCopyMax(parseddomain, MIDBUFFER, ptr ? &ptr[1] : xs);
StrFree(xs);
}
else
StrCopyMax(parseddomain, MIDBUFFER, &host[1]);
for (i = 0; (i < USERLEN) && (&userhost[i] < host); i++)
parseduser[i] = userhost[i];
parseduser[i] = (char)0;
}
else
return TRUE;
return FALSE;
}
/* --- AddIdent --------------------------------------------------- */
itemident *AddIdent(char *nick,
char *userhost,
itemuser *u,
itemguest *g,
itemclient *k)
{
char parsedhost[MIDBUFFER];
char parseddomain[MIDBUFFER];
char parseduser[USERLEN+1];
itemident *p;
snapshot;
if (SplitHost(userhost, parsedhost, parseddomain, parseduser))
return NULL;
p = NewEntry(itemident);
if (p) {
InsertLast(identHead, p);
p->level = u ? u->level : 0;
p->host = StrDuplicate(userhost);
p->userdomain = Userdomain(userhost);
p->signature = HashSignatureU(userhost);
p->user = u;
p->guest = g;
p->client = k;
p->passed = FALSE;
p->phost = StrDuplicate(parsedhost);
p->puserdomain = StrDuplicate(parseddomain);
p->pname = StrDuplicate(parseduser);
#ifdef HAVE_LIBFPL
p->label = u ? u->label : NULL;
#endif
StrCopyMax(p->nick, NICKLEN + 1, nick);
p->illegalname = StrIndex(parsedhost, '*') ? TRUE : FALSE;
}
return p;
}
void FreeIdent(void *v)
{
itemident *p;
p = (itemident *)v;
if (p) {
if (p->host)
StrFree(p->host);
if (p->userdomain)
StrFree(p->userdomain);
if (p->phost)
StrFree(p->phost);
if (p->pname)
StrFree(p->pname);
if (p->puserdomain)
StrFree(p->puserdomain);
}
}
/* --- AutoOp ----------------------------------------------------- */
bool IsAutoOp(itemguest *g)
{
return (g->ident->user && g->ident->user->flags.autoop);
}
void CheckForAutoOp(void)
{
extern long autoopswaiting;
snapshot;
if (botop && autoopswaiting) {
bool thereisone = FALSE;
register itemguest *g;
for (g = First(guestHead); g; g = Next(g)) {
if (!g->flags.chanop && g->op_this_person) {
if (g->op_this_person < now) {
Mode("+o %s", g->ident->nick);
g->op_this_person = 0; /* Don't do it again */
return;
}
thereisone = TRUE; /* Not right now! */
}
}
if (!thereisone)
autoopswaiting = 0; /* There are no users [left] to make chanops */
}
}
/* --- NetSplits -------------------------------------------------- */
/*
* These functions keep a list with all users that quit as the result
* of a split. If one of them joins within SPLITTIMEOUT seconds, we
* consider the split to be healed, otherwise we remove them from this
* list and consider them just quit.
*/
void FreeGuest(void *);
void FreeSplit(void *v)
{
itemsplit *p;
p = (itemsplit *)v;
if (p) {
if (p->servers)
StrFree(p->servers);
DeleteList(p->wholeft, FreeGuest);
}
}
void AddSplitter(itemguest *g, char *servers)
{
itemsplit *p;
snapshot;
g->flags.split = TRUE;
totalnumberofsplits++; /* Never decrease */
numSplitGuests++; /* One more [amount in the split lists] */
numGuests--; /* One less [in the channel right now] */
for (p = First(splitHead); p; p = Next(p)) {
if (StrEqual(p->servers, servers))
break;
}
if (NULL == p) { /* Make new if non-existant */
p = NewEntry(itemsplit);
if (p) {
InsertFirst(splitHead, p);
p->splittime = lastsplittime = now;
p->servers = StrDuplicate(servers);
p->wholeft = NewList(itemguest);
Log(LOGSPLIT, servers);
}
else
return; /* Error, no mem */
}
MoveLast(guestHead, g, p->wholeft);
}
itemguest *SplitterReturns(char *nick, char *host)
{
itemguest *g;
itemsplit *p;
snapshot;
for (p = First(splitHead); p; p = Next(p)) {
for (g = First(p->wholeft); g; g = Next(g)) {
if (Match(g->ident->nick, nick)) {
if (StrEqualCase(g->ident->host, host)) {
if (!p->heal) { /* Not previously healed */
p->heal = TRUE; /* This is now considered healed */
Log(LOGNHEAL, p->servers);
}
MoveLast(p->wholeft, g, guestHead);
if (g->flags.chanop)
g->flags.splitop = TRUE;
else
g->flags.splitop = FALSE;
g->flags.split = g->flags.chanop = g->flags.voice = FALSE;
return g; /* The prodigal son */
}
else if (!mute) { /* Warn about a potential collision */
SendNickf(nick, GetDefaultText(msg_warn_nick_collide),
g->ident->nick, TimeAgo(p->splittime), g->ident->host, p->servers);
}
}
}
}
return NULL; /* No, not a netjoin */
}
/* --- ScanSplitServers ------------------------------------------- */
void ScanSplitServers(bool cleanall)
{
itemsplit *p, *next;
snapshot;
for (p = First(splitHead); p; p = next) {
next = Next(p);
if (cleanall || EmptyList(p->wholeft) ||
((now - p->splittime) > SPLITTIMEOUT)) {
if (!cleanall && !p->heal)
Logf(LOGNHEAL, "(timeout) %s", p->servers);
DeleteEntry(splitHead, p, FreeSplit);
}
}
}
/* --- DoCountJoins ----------------------------------------------- */
int DoCountJoins(itemguest *g)
{
extern itemseen **hashsite;
char *host, *usite;
long joins = 1;
ulong x;
itemaux *p;
itemseen *ps;
snapshot;
host = g->ident->host;
if (IsPrefix(host))
host++;
x = Hash(g->ident->userdomain);
for (ps = hashsite[x]; ps; ps = ps->next) {
usite = ps->usite;
if (IsPrefix(usite))
usite++;
if (Match(host, usite)) {
for (p = ps->first; p; p = p->link)
joins += p->count; /* Accumulate all joins for user */
break;
}
}
g->joins = joins;
return joins;
}
char *GetPrefix(char prefix)
{
switch (prefix) {
case PREFIX_HAT:
return "^";
case PREFIX_EQUAL:
return "=";
case PREFIX_PLUS:
return "+";
case PREFIX_TILDE:
return "~";
case PREFIX_DASH:
return "-";
default:
return "";
}
}
char IsPrefix(char *name)
{
switch (name[0]) {
case PREFIX_HAT:
case PREFIX_TILDE:
case PREFIX_PLUS:
case PREFIX_EQUAL:
case PREFIX_DASH:
return name[0];
default:
return PREFIX_NONE;
}
}
/* --- BadUsername ------------------------------------------------ */
/* Returns TRUE if the 'username' consists of any of the characters we
* consider "illegal" and therefore enforces site bans/warns patterns.
*/
int BadUsername(char *name)
{
while (*name) {
switch (*name) {
case '*':
case '?':
#if 0
/* caused major confusion, better leave them out for now */
case '[':
case ']':
case '!':
#endif
case '@':
return TRUE;
}
name++;
}
return FALSE;
}
/* --- NewGuest --------------------------------------------------- */
void NewGuest_Who(char *line)
{
char username[MIDBUFFER], host[MIDBUFFER], nick[NICKLEN+1],
status[16];
char buf[MIDBUFFER];
bool chop, voice, ircop;
itemguest *g;
snapshot;
if (4 <= StrScan(line, "%*s %*s %"MIDBUFFERTXT"s %"MIDBUFFERTXT"s %*s %"NICKLENTXT"s %15s",
username, host, nick, status)) {
chop = (StrIndex(status, '@') ? TRUE : FALSE);
voice = (StrIndex(status, '+') ? TRUE : FALSE);
ircop = (StrIndex(status, '*') ? TRUE : FALSE);
/*
* Further:
* '*' means the person is an ircop
* 'H' means the person is not away
* 'G' means the person _is_ away
*
* But since we cannot get this info from joined users without making
* additional /WHO calls on each joined person, we won't get this kind
* of info on people not already joined when we join. - Bagder
*/
StrFormatMax(buf, sizeof(buf), "%s@%s", username, host);
AddGuest(nick, buf, chop, voice, ircop, &g);
if (StrEqualCase(nick, nickname)) {
/* this is us */
botop = chop;
if (g)
g->flags.bot = TRUE; /* after all, we are a bot */
StrFormatMax(myaddr, sizeof(myaddr), "%s@%s", username, host);
StrFormatMax(botmatch, sizeof(botmatch), "%s!%s", nick, myaddr);
}
}
else
Debug("Strange format: %s", line);
}
/* --- RemoveGuest ------------------------------------------------ */
void FreeGuest(void *v)
{
itemguest *g;
g = (itemguest *)v;
if (g) {
if (g->ident->client)
g->ident->guest = NULL;
else
DeleteEntry(identHead, g->ident, FreeIdent);
if (g->kickmsg)
StrFree(g->kickmsg);
if (g->flags.split)
numSplitGuests--;
else
numGuests--;
}
}
void RemoveGuest(itemguest *g)
{
snapshot;
if (welcome && !mute) { /* We are set to welcome / wave ! */
if (!g->posts)
Sayf("How rude, %s just left without saying *anything* to us!",
g->ident->nick);
else if ((g->jointime + SECINMIN) > now)
Actionf("sighs, a %d seconds visit! Is that all we deserve? :-(",
now - g->jointime);
}
#ifdef HAVE_LIBFPL
if (g->ident->user)
g->ident->user->label = g->ident->label;
#endif
DeleteEntry(guestHead, g, FreeGuest);
}
/* --- DeleteGuests ----------------------------------------------- */
void DeleteGuests(void)
{
itemguest *g;
snapshot;
if (guestHead) {
#ifdef HAVE_LIBFPL
for (g = First(guestHead); g; g = Next(g)) {
if (g->ident->user)
g->ident->user->label = g->ident->label;
}
#endif
/* Remove all splitted servers and the guests linked to them */
ScanSplitServers(TRUE);
FlushList(guestHead, FreeGuest);
}
}
/* --- NetHeal ----------------------------------------------------- */
/* May be called several times each netheal! */
void NetHeal(void)
{
extern bool possiblyfresh; /* this might be a restarted server!! */
extern int possiblyfreshtime; /* timeout for refreshed server netheals */
masterflood = TRUE;
mastertime = now;
if (possiblyfresh) {
possiblyfreshtime = now;
possiblyfresh = FALSE;
}
}
/* --- AddGuest --------------------------------------------------- */
bool AddGuest(char *nick,
char *host,
bool chop,
bool voice,
bool ircop,
itemguest **guest)
{
bool realjoin = TRUE; /* standard join */
itemguest *g;
itemclient *k;
snapshot;
*guest = NULL;
g = SplitterReturns(nick, host);
if (g) {
Logf(LOGNJOIN, "%s [%d] (%s)", nick, g->ident->level, host);
NetHeal();
numSplitGuests--; /* one less */
numGuests++; /* one more */
realjoin = FALSE; /* no "real" join */
}
else {
/*
* At certain occasions, when the bot makes a /who call, and the list isn't
* done yet, and a nick joins, we can get two entries with the same nick.
* We prevent this by checking that the nick we're about to add isn't
* already added.
*/
g = FindNick(nick);
if (NULL == g) {
g = NewEntry(itemguest);
if (g) {
InsertLast(guestHead, g);
/* Inherit privileges from client. If a user has made a client,
* changed nick and joined, we cannot know if it's really him...
* tough luck. If the user is spoofed, that's even tougher luck.
*/
k = FindClientByNick(nick);
if (k && StrEqualCase(k->ident->host, host)) {
g->ident = k->ident; /* This is our ident struct */
k->ident->guest = g; /* Make the ident point back to us */
}
else {
/* Each itemguest _must_ have an ident structure attached! */
g->ident = AddIdent(nick, host, FindUser(nick, host), g, NULL);
if (NULL == g->ident) {
DeleteEntry(guestHead, g, FreeGuest);
Debug("Out of memory. Cannot add guest %s (%s)",
nick, host);
return FALSE;
}
}
/*
* Check if the guest matched as a user that is some kind of
* bot, and if so, set the guest-flag for BOT.
*/
if (g->ident->user &&
(g->ident->user->flags.linkbot ||
g->ident->user->flags.regularbot))
g->flags.bot = TRUE;
else
g->flags.bot = FALSE;
g->flags.chanop = chop;
g->flags.voice = voice;
g->flags.ircop = ircop;
g->flags.splitop = FALSE;
}
else {
Debug("Out of memory");
return FALSE;
}
numGuests++;
Logf(LOGJOIN, "%s%s [%d] (%s)", chop ? "@" : (voice ? "+" : ""),
g->ident->nick, g->ident->level, g->ident->host);
g->jointime = now;
/*
* I think this might need some explanation. The joincount system has
* always been based on the seen data. When the user joins, we check
* the hostpattern and then sums the number of joins all the different
* nick names have done with that hostpattern.
* Now, since I intend to improve this system while there are already
* lots of systems running and many many joins counted, I need a way
* to get the old joins somehow read into the new system. I will
* therefore make the ->totalcount of the user struct count the total
* number of joins (for regged users) but I will continue doing the old
* count. If the old count shows a higher count than the new, the new
* will be raised to the old one. That way, the new should seen get at
* least the value of the largest old one for the users hostpatterns.
* - Bagder
*/
DoCountJoins(g);
if (g->ident->user) {
g->ident->user->totaljoins++; /* This user did join right now */
if (g->joins > g->ident->user->totaljoins)
/* Old system contains more joins, copy them to the new system */
g->ident->user->totaljoins = g->joins;
else
/* This is the total amount we know of */
g->joins = g->ident->user->totaljoins;
}
}
}
/* Don't send messages for the bot owner to the bot */
if (!StrEqualCase(g->ident->nick, nickname))
TellNotify(g);
*guest = g;
return realjoin;
}
/* --- FindNick --------------------------------------------------- */
/* Used very often! Make hashed nicks instead? */
itemguest *FindNick(char *nick)
{
register itemguest *g;
for (g = First(guestHead); g; g = Next(g)) {
if (IRCEqual(g->ident->nick, nick))
return g;
}
return NULL;
}
itemguest *FindSplitNick(char *nick)
{
itemsplit *p;
itemguest *g;
for (p = First(splitHead); p; p = Next(p)) {
for (g = First(p->wholeft); g; g = Next(g)) {
if (IRCEqual(g->ident->nick, nick))
return g;
}
}
return NULL;
}
/* --- FindHost --------------------------------------------------- */
itemguest *FindHost(char *pattern)
{
itemguest *g;
snapshot;
if (StrIndex(pattern, '!')) {
/* Full *!*@* pattern expected */
char nickpattern[NICKLEN+1];
char hostpattern[MIDBUFFER];
if (2 == StrScan(pattern, "%"NICKLENTXT"[^!]!%"MIDBUFFERTXT"s",
nickpattern, hostpattern)) {
for (g = First(guestHead); g; g = Next(g)) {
if (Match(g->ident->nick, nickpattern) &&
Match(g->ident->host, hostpattern))
return g;
}
}
}
else {
for (g = First(guestHead); g; g = Next(g)) {
if (Match(g->ident->host, pattern))
return g;
}
}
return NULL;
}
/* --- ChangeGuest ------------------------------------------------ */
void ChangeGuest(char *oldnick, char *newnick)
{
itemguest *g, *w;
itemclient *k;
itemsplit *p;
snapshot;
g = FindNick(oldnick);
if (g) {
StrCopy(g->ident->nick, newnick);
g->flags.kicked = FALSE;
if (g->flags.kick) {
Kick(newnick, g->kickmsg ? g->kickmsg : "Gotcha!");
AddKick(g->ident, nickname, "Nick flooder!", KICK_BOT);
}
else if (nickflood) {
if ((g->nicktime + 10) >= now) {
if (++g->nickchanges > 2)
Warning(g, "nick-flooders", "Nick flooder!");
}
else
g->nickchanges = 0;
}
g->nicktime = now;
if (warnmode && !g->flags.kick)
WarnCheck(g->ident->nick, g->ident->host);
/* Try to unify structures */
if (!g->ident->client &&
(k = FindClientByNick(g->ident->nick)) &&
(g->ident->user == k->ident->user) &&
StrEqualCase(g->ident->host, k->ident->host)) {
g->ident->passed |= k->ident->passed;
k->ident->client = NULL;
DeleteEntry(identHead, k->ident, FreeIdent);
k->ident = g->ident;
/*
* Hm, we should let the ident know it is used to identify a client
* too.
* - Bagder
*/
g->ident->client = k;
}
if (!mute) {
for (p = First(splitHead); p; p = Next(p)) {
for (w = First(p->wholeft); w; w = Next(w)) {
if (Match(w->ident->nick, newnick) &&
!StrEqualCase(g->ident->userdomain, w->ident->userdomain))
SendNickf(newnick, GetDefaultText(msg_warn_nick_collide),
w->ident->nick, TimeAgo(p->splittime), w->ident->host,
p->servers);
}
}
}
}
else
Debug("Internal confusion. Unknown user \"%s\" changed nick.", oldnick);
}
/* --- FindClientByNick ------------------------------------------- */
itemclient *FindClientByNick(char *nick)
{
itemclient *k;
for (k = First(clientHead); k; k = Next(k)) {
if (k->ident && IRCEqual(k->ident->nick, nick))
return k;
}
return NULL;
}
/* --- RemoveClient ----------------------------------------------- */
void FreeClient(void *v)
{
itemclient *k;
k = (itemclient *)v;
if (k) {
if (k->ident->guest) {
k->ident->client = NULL;
}
else {
DeleteEntry(identHead, k->ident, FreeIdent);
}
if (k->filebuffer)
free(k->filebuffer);
}
}
void RemoveClient(itemclient *k)
{
char buffer[MIDBUFFER];
snapshot;
StrFormatMax(buffer, sizeof(buffer), "%s removed at socket %d",
k->ident->nick, k->socket);
CloseConnection(k->socket);
DeleteEntry(clientHead, k, FreeClient);
Log(LOGCLIENT, buffer);
Multicast(SPYCAST, buffer);
numClients--;
}
/* --- AddClient -------------------------------------------------- */
itemclient *AddClient(char *nick, char *uhost, itemuser *u, int sock)
{
itemguest *g;
itemclient *k;
snapshot;
k = NewEntry(itemclient);
if (k) {
InsertLast(clientHead, k);
/* Find nick on channel, and inherit it's privileges */
g = FindNick(nick);
if (g) {
if (g->ident->client) {
DeleteEntry(clientHead, k, FreeClient); /* Remove again */
return NULL; /* Client already exist */
}
k->ident = g->ident;
g->ident->client = k;
}
else {
k->ident = AddIdent(nick, uhost, u, NULL, k);
if (NULL == k->ident) {
DeleteEntry(clientHead, k, FreeClient);
Debug("Out of memory. Cannot add client %s (%s)",
nick, uhost);
return NULL;
}
}
k->socket = sock;
k->status = CL_NONE;
k->buildtime = k->lasttime = now;
k->flags = WALLCAST;
k->chatecho = TRUE; /* we start in echomode by default */
if (u->level >= LEVELPUB)
k->flags |= REPORTCAST;
if (u->level >= LEVELOWNER)
k->flags |= DEBUGCAST;
numClients++;
Logf(LOGCLIENT, "%s (%s) added", k->ident->nick, u->nick);
Multicastf(SPYCAST, "client %s has connected", k->ident->nick);
}
return k;
}
/* --- DeleteClients ---------------------------------------------- */
void DeleteClients(void)
{
FlushList(clientHead, FreeClient);
Log(LOGCLIENT, "All clients removed");
}
/* --- Links ------------------------------------------------------ */
/* addr and port must be in network order */
itemlink *FindLink(ulong addr, ushort port)
{
itemlink *r;
for (r = First(linkHead); r; r = Next(r)) {
if ((r->addr.sin_addr.s_addr == addr) && (r->addr.sin_port == port))
return r;
}
return NULL;
}
itemlink *FindLinkByNick(char *nick)
{
itemlink *r;
for (r = First(linkHead); r; r = Next(r)) {
if (IRCEqual(r->name, nick))
return r;
}
return NULL;
}
itemlink *AddLink(char *name, itemuser *u, ulong num, ushort port)
{
itemlink *r;
snapshot;
r = FindLink(htonl(num), htons(port));
if (r)
return NULL;
r = NewEntry(itemlink);
if (r) {
InsertLast(linkHead, r);
r->user = u;
r->name = StrDuplicate(name);
r->addr.sin_family = AF_INET;
r->addr.sin_port = htons(port);
r->addr.sin_addr.s_addr = htonl(num);
r->passed = FALSE;
r->buildtime = r->lasttime = now;
r->mymsgid = 0;
r->yourmsgid = -1;
rtt_init(r);
}
return r;
}
void FreeLink(void *v)
{
itemlink *r;
r = (itemlink *)v;
if (r) {
FlushLinkQueue(r);
if (r->name)
StrFree(r->name);
}
}
itemlink *RemoveLink(itemlink *r)
{
itemlink *next;
next = Next(r);
DeleteEntry(linkHead, r, FreeLink);
return next;
}
/* --- UserInit --------------------------------------------------- */
void UserInit(void)
{
userHead = NewList(itemuser);
identHead = NewList(itemident);
linkHead = NewList(itemlink);
clientHead = NewList(itemclient);
guestHead = NewList(itemguest);
splitHead = NewList(itemsplit);
}
/* --- UserCleanup ------------------------------------------------ */
void UserCleanup(void)
{
UserSave();
DeleteList(splitHead, FreeSplit);
DeleteList(guestHead, FreeGuest);
DeleteList(clientHead, FreeClient);
DeleteList(linkHead, FreeLink);
DeleteList(identHead, FreeIdent);
DeleteList(userHead, FreeUser);
}
/* --- MakePassword ----------------------------------------------- */
#define PASSLEN 32
#define PASSLENTXT "31"
char *MakePassword(char *plain)
{
char passwd[PASSLEN] = "";
unsigned char salt[3];
int rnd;
StrScan(plain, "%"PASSLENTXT"s", passwd);
rnd = (int)(Rnd() * 4096);
to64(salt, rnd, 2);
salt[2] = (char)0;
return StrDuplicate(mycrypt(passwd, salt));
}
/* --- CheckPassword ---------------------------------------------- */
#define SALT "k9"
bool CheckPassword(char *plain, char *crypted)
{
char passwd[PASSLEN] = "";
char salt[3];
StrScan(plain, "%"PASSLENTXT"s", passwd);
if (StrEqualCaseMax(crypted, 3, "$1$")) {
salt[0] = crypted[3];
salt[1] = crypted[4];
salt[2] = (char)0;
return StrEqualCase(mycrypt(passwd, salt), crypted); /*NEW*/
}
else {
#if defined(HAVE__CRYPT)
return StrEqualCase((char *)_crypt(passwd, SALT), crypted);
#elif (!defined(AMIGA) || defined(__GNUC__)) && !defined(__CYGWIN32__) /* amiga version has no crypt() of its own at all */
return StrEqualCase((char *)crypt(passwd, SALT), crypted);
#endif
}
}
int User2ID(char *nick, char *flags)
{
itemguest *g;
itemuser *u;
itemclient *c;
g = FindNick(nick);
if (NULL == g)
g = FindSplitNick(nick);
u = FindUserByNick(nick);
c = FindClientByNick(nick);
if (StrContains(flags, "join"))
return (int)g;
if (StrContains(flags, "reg"))
return (int)u;
if (g)
return (int)g;
if (u)
return (int)u;
if (c)
return (int)c;
return 0;
}
#define ID_NONE 0
#define ID_GUEST 1
#define ID_SPLIT 2
#define ID_CLIENT 4
#define ID_USER 8
int ID2User(int id, itemuser **user, itemguest **guest, itemclient **client)
{
itemguest *g;
itemuser *u;
itemclient *c;
itemsplit *p;
for (g = First(guestHead); g; g = Next(g)) {
if (g == (itemguest *)id) {
*guest = g;
return ID_GUEST;
}
}
for (p = First(splitHead); p; p = Next(p)) {
for (g = First(p->wholeft); g; g = Next(g)) {
if (g == (itemguest *)id) {
*guest = g;
return ID_SPLIT;
}
}
}
for (u = First(userHead); u; u = Next(u)) {
if (u == (itemuser *)id) {
*user = u;
return ID_USER;
}
}
for (c = First(clientHead); c; c = Next(c)) {
if (c == (itemclient *)id) {
*client = c;
return ID_CLIENT;
}
}
return ID_NONE;
}
itemident *ID2Ident(int id)
{
itemguest *g;
itemuser *u;
itemclient *c;
switch (ID2User(id, &u, &g, &c)) {
case ID_NONE:
return NULL; /* nada */
case ID_GUEST:
case ID_SPLIT:
return g->ident;
case ID_USER:
return NULL;
case ID_CLIENT:
return c->ident;
}
return NULL;
}
#ifdef HAVE_LIBFPL
itemuserlabel **ID2Label(int id)
{
itemguest *g;
itemuser *u;
itemclient *c;
switch (ID2User(id, &u, &g, &c)) {
case ID_NONE:
return NULL; /* nada */
case ID_GUEST:
case ID_SPLIT:
return (itemuserlabel **)&g->ident->label;
case ID_USER:
return (itemuserlabel **)&u->label;
case ID_CLIENT:
return (itemuserlabel **)&c->ident->label;
}
return NULL;
}
#endif
/* =========================================================================
NickReport() experiments, please have patience!
========================================================================= */
/*****************
GENERAL-SEEN-NICK (no options)
A SEEN as usual. Say the usual. If joined, abort.
B Get the host pattern and search the ppl currently joined,
any match? If yes, say:
= "A person matching that pattern is currently joined as 'nick'"
- Check for the registered nick. If none of the reg'ed nick's hosts
match the already found pattern, check the channel for anyone
matching. Anyone joined?
1- If yes, say and abort.
= "The nick is registered to the user now joined as 'nick'."
2- If no, say and abort
= "The nick is registered to another user."
C Perform a seen -MOST- on the nick. If that gives us another
host, check the channel for it. Anyone joined?
1- If yes, say and abort
= "Another hostpattern 'host' has used the nick more often and is
currently joined as 'nick'."
2- If no. Say and abort
= "Another hostpattern 'host' has used the nick more often."
D Get the host pattern and search for a more frequently used nick.
Any match? If yes, say and abort.
= "The nick 'nick' has been used more often with that hostpattern."
*****************/
void NickReport(char *from, char *nick)
{
extern itemaux **hashnick; /* from seen */
long max = 0;
long hits = 0;
time_t nicktime = 0;
time_t roottime = 0;
itemuser *u;
itemguest *g, *w;
itemaux *p;
itemaux *maxnick = NULL;
itemaux *recentnick = NULL;
itemaux *recentroot = NULL;
snapshot;
g = FindNick(nick);
u = FindUserByNick(nick);
if (g) {
Sendf(from, "%s (%s) %s.", g->ident->nick, g->ident->userdomain,
GetText(msg_is_joined_now));
if (u) {
if (u == g->ident->user)
Send(from, GetText(msg_regged_user_with_nick));
else {
itemlist *l;
Send(from, GetText(msg_not_his_nick));
for (l = First(u->domainhead); l; l = Next(l)) {
w = FindHost((char *)l->pointer);
if (w) {
Sendf(from, GetText(msg_who_is_now), w->ident->nick);
break;
}
}
}
}
}
/*
* We won't use SeenMostNick(), SeenRecentNick() or SeenRecentRootNick()
* here cause this way we can get more information, and faster.
*/
for (p = hashnick[HashU(nick)]; p; p = p->next) {
if (IRCEqual(p->nick, nick)) {
hits++;
if (p->count > max) {
max = p->count;
maxnick = p;
}
if (p->departure > nicktime) {
nicktime = p->departure;
recentnick = p;
}
if (p->root->departure > roottime) {
roottime = p->root->departure;
recentroot = p;
}
}
}
if (recentnick) {
Sendf(from, GetText(msg_most_recently_used_by), recentnick->root->host);
w = FindHost(recentnick->root->host);
if (w)
Sendf(from, GetText(msg_from_which_host), w->ident->nick);
if (maxnick) {
if (recentnick == maxnick)
Send(from, GetText(msg_and_also_most_often_used));
else {
Sendf(from, GetText(msg_most_often_used_by), maxnick->root->host);
w = FindHost(maxnick->root->host);
if (w)
Sendf(from, GetText(msg_from_which_host), w->ident->nick);
}
}
if (recentroot) {
if (recentroot->root->departure > recentnick->departure) {
for (p = recentroot->root->first; p; p = p->link) {
if (p->departure > recentroot->departure)
recentroot = p;
}
Sendf(from, "%s (%s) who has also used this nick in the past, has been seen most recently",
recentroot->nick, recentroot->root->host);
w = FindHost(recentroot->root->host);
if (w)
Sendf(from, GetText(msg_from_which_host), w->ident->nick);
}
}
}
if ((NULL == g) && (NULL == recentnick))
Sendf(from, GetText(msg_never_seen), nick);
}
/*
* NonHostISP()
*
* Returns TRUE if the input parameter is listed as a non-host-ISP in
* the .config file.
*/
bool NonHostISP(char *checkthis)
{
extern itemlist *nonhostispHead;
itemlist *l;
for (l = First(nonhostispHead); l; l = Next(l)) {
if (Match(checkthis, l->pointer))
return TRUE;
}
return FALSE;
}
/*
* HostISP()
*
* Returns TRUE if the input parameter is listed as a host-ban-only ISP in
* the .config file.
*/
bool HostISP(char *checkthis)
{
extern itemlist *hostispHead;
itemlist *l;
for (l = First(hostispHead); l; l = Next(l)) {
if (Match(checkthis, l->pointer)) {
if (NonHostISP(checkthis))
return FALSE; /* This is not considered to be a hostisp */
else
return TRUE; /* Straight and clean hostisp */
}
}
return FALSE;
}
/*
* DontSiteBan()
*
* Returns TRUE if the input parameter is listed as a dontsiteban ISP in the
* .config file.
*/
bool DontSiteBan(char *checkthis)
{
extern itemlist *dontsitebanHead;
itemlist *l;
for (l = First(dontsitebanHead); l; l = Next(l)) {
if (Match(checkthis, l->pointer))
return TRUE; /* Prevent sitebans on this domain */
}
return FALSE;
}
syntax highlighted by Code2HTML, v. 0.9.1