mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-07-01 20:06:38 +02:00
dddc8f07e4
- Server protocol: added PROTOCTL EATH=servername, which allows us to
authenticate the server very early in the handshake process. That way,
certain commands and PROTOCTL tokens can 'trust' the server.
See doc/technical/protoctl.txt for details.
- Server protocol: between new Unreal servers we now do the handshake a
little bit different, so it waits with sending the SERVER command until
the first PROTOCTL is received. Needed for next.
- Server protocol: added PROTOCTL SERVERS=1,2,3,4,etc by which a server can
inform the other server which servers (server numeric, actually) it has
linked. See doc/technical/protoctl.txt and next for details.
- When our server was trying to link to some server, and at the same time
another server was also trying to link with us, this would lead to a
server collision: the server would link (twice) ok at first, but then a
second later or so both would quit with 'Server Exists' with quite some
mess as a result. This isn't unique to Unreal, btw.
This happened more often when you had a low connfreq in your link blocks
(aka: quick reconnects), or had multiple hubs on autoconnect (with same
connfreq), or when you (re)started all servers at the same time.
This should now be solved by a new server handshake design, which detects
this race condition and solves it by closing one of the two (or more)
connections to avoid the issue.
This also means that it should now be safe to have multiple hubs with low
connfreq's (eg: 10s) without risking that your network falls apart.
This new server handshake (protocol updates, etc) was actually quite some
work, especially for something that only happened sporadically. I felt it
was needed though, because (re)linking stability is extremely important.
This new feature/design/fix requires extensive testing.
This feature can be disabled by: set { new-linking-protocol 0; };
1175 lines
29 KiB
C
1175 lines
29 KiB
C
|
|
/*
|
|
* Unreal Internet Relay Chat Daemon, src/s_misc.c
|
|
* Copyright (C) 1990 Jarkko Oikarinen and
|
|
* University of Oulu, Computing Center
|
|
*
|
|
* See file AUTHORS in IRC package for additional names of
|
|
* the programmers.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#ifndef CLEAN_COMPILE
|
|
static char sccsid[] =
|
|
"@(#)s_misc.c 2.42 3/1/94 (C) 1988 University of Oulu, \
|
|
Computing Center and Jarkko Oikarinen";
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
#include <sys/time.h>
|
|
#endif
|
|
#include "struct.h"
|
|
#include "common.h"
|
|
#include "sys.h"
|
|
#include "numeric.h"
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#if !defined(ULTRIX) && !defined(SGI) && \
|
|
!defined(__convex__) && !defined(_WIN32)
|
|
# include <sys/param.h>
|
|
#endif
|
|
#if defined(PCS) || defined(AIX) || defined(SVR3)
|
|
# include <time.h>
|
|
#endif
|
|
#ifdef HPUX
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef _WIN32
|
|
# include <io.h>
|
|
#endif
|
|
#include "h.h"
|
|
#include "proto.h"
|
|
#include "channel.h"
|
|
#include <string.h>
|
|
|
|
#ifndef NO_FDLIST
|
|
extern fdlist serv_fdlist;
|
|
extern fdlist oper_fdlist;
|
|
extern float currentrate;
|
|
extern float currentrate2;
|
|
#endif
|
|
extern ircstats IRCstats;
|
|
extern char *me_hash;
|
|
|
|
static void exit_one_client(aClient *, aClient *, aClient *, char *, int);
|
|
extern void exit_one_client_in_split(aClient *, aClient *, aClient *,
|
|
char *);
|
|
|
|
static char *months[] = {
|
|
"January", "February", "March", "April",
|
|
"May", "June", "July", "August",
|
|
"September", "October", "November", "December"
|
|
};
|
|
|
|
static char *weekdays[] = {
|
|
"Sunday", "Monday", "Tuesday", "Wednesday",
|
|
"Thursday", "Friday", "Saturday"
|
|
};
|
|
|
|
typedef struct {
|
|
int value; /** Unique integer value of item */
|
|
char character; /** Unique character assigned to item */
|
|
char *name; /** Name of item */
|
|
} BanActTable;
|
|
|
|
static BanActTable banacttable[] = {
|
|
{ BAN_ACT_KILL, 'K', "kill" },
|
|
{ BAN_ACT_TEMPSHUN, 'S', "tempshun" },
|
|
{ BAN_ACT_SHUN, 's', "shun" },
|
|
{ BAN_ACT_KLINE, 'k', "kline" },
|
|
{ BAN_ACT_ZLINE, 'z', "zline" },
|
|
{ BAN_ACT_GLINE, 'g', "gline" },
|
|
{ BAN_ACT_GZLINE, 'Z', "gzline" },
|
|
{ BAN_ACT_BLOCK, 'b', "block" },
|
|
{ BAN_ACT_DCCBLOCK, 'd', "dccblock" },
|
|
{ BAN_ACT_VIRUSCHAN,'v', "viruschan" },
|
|
{ BAN_ACT_WARN, 'w', "warn" },
|
|
{ 0, 0, 0 }
|
|
};
|
|
|
|
typedef struct {
|
|
int value; /** Unique integer value of item */
|
|
char character; /** Unique character assigned to item */
|
|
char *name; /** Name of item */
|
|
char *irccommand; /** Raw IRC command of item (not unique!) */
|
|
} SpamfilterTargetTable;
|
|
|
|
SpamfilterTargetTable spamfiltertargettable[] = {
|
|
{ SPAMF_CHANMSG, 'c', "channel", "PRIVMSG" },
|
|
{ SPAMF_USERMSG, 'p', "private", "PRIVMSG" },
|
|
{ SPAMF_USERNOTICE, 'n', "private-notice", "NOTICE" },
|
|
{ SPAMF_CHANNOTICE, 'N', "channel-notice", "NOTICE" },
|
|
{ SPAMF_PART, 'P', "part", "PART" },
|
|
{ SPAMF_QUIT, 'q', "quit", "QUIT" },
|
|
{ SPAMF_DCC, 'd', "dcc", "PRIVMSG" },
|
|
{ SPAMF_USER, 'u', "user", "NICK" },
|
|
{ SPAMF_AWAY, 'a', "away", "AWAY" },
|
|
{ SPAMF_TOPIC, 't', "topic", "TOPIC" },
|
|
{ 0, 0, 0 }
|
|
};
|
|
|
|
|
|
/*
|
|
* stats stuff
|
|
*/
|
|
struct stats ircst, *ircstp = &ircst;
|
|
|
|
char *date(time_t clock)
|
|
{
|
|
static char buf[80], plus;
|
|
struct tm *lt, *gm;
|
|
struct tm gmbuf;
|
|
int minswest;
|
|
|
|
if (!clock)
|
|
time(&clock);
|
|
gm = gmtime(&clock);
|
|
bcopy((char *)gm, (char *)&gmbuf, sizeof(gmbuf));
|
|
gm = &gmbuf;
|
|
lt = localtime(&clock);
|
|
#ifndef _WIN32
|
|
if (lt->tm_yday == gm->tm_yday)
|
|
minswest = (gm->tm_hour - lt->tm_hour) * 60 +
|
|
(gm->tm_min - lt->tm_min);
|
|
else if (lt->tm_yday > gm->tm_yday)
|
|
minswest = (gm->tm_hour - (lt->tm_hour + 24)) * 60;
|
|
else
|
|
minswest = ((gm->tm_hour + 24) - lt->tm_hour) * 60;
|
|
#else
|
|
minswest = (_timezone / 60);
|
|
#endif
|
|
plus = (minswest > 0) ? '-' : '+';
|
|
if (minswest < 0)
|
|
minswest = -minswest;
|
|
(void)ircsprintf(buf, "%s %s %d %d -- %02d:%02d %c%02d:%02d",
|
|
weekdays[lt->tm_wday], months[lt->tm_mon], lt->tm_mday,
|
|
1900 + lt->tm_year,
|
|
lt->tm_hour, lt->tm_min, plus, minswest / 60, minswest % 60);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
char *convert_time (time_t ltime)
|
|
{
|
|
unsigned long days = 0,hours = 0,minutes = 0,seconds = 0;
|
|
static char buffer[40];
|
|
|
|
|
|
*buffer = '\0';
|
|
seconds = ltime % 60;
|
|
ltime = (ltime - seconds) / 60;
|
|
minutes = ltime%60;
|
|
ltime = (ltime - minutes) / 60;
|
|
hours = ltime % 24;
|
|
days = (ltime - hours) / 24;
|
|
ircsprintf(buffer, "%ludays %luhours %luminutes %lusecs",
|
|
days, hours, minutes, seconds);
|
|
return(*buffer ? buffer : "");
|
|
}
|
|
|
|
|
|
/*
|
|
* Fixes a string so that the first white space found becomes an end of
|
|
* string marker (`\-`). returns the 'fixed' string or "*" if the string
|
|
* was NULL length or a NULL pointer.
|
|
*/
|
|
char *check_string(char *s)
|
|
{
|
|
static char star[2] = "*";
|
|
char *str = s;
|
|
|
|
if (BadPtr(s))
|
|
return star;
|
|
|
|
for (; *s; s++)
|
|
if (isspace(*s))
|
|
{
|
|
*s = '\0';
|
|
break;
|
|
}
|
|
|
|
return (BadPtr(str)) ? star : str;
|
|
}
|
|
|
|
char *make_user_host(char *name, char *host)
|
|
{
|
|
static char namebuf[USERLEN + HOSTLEN + 6];
|
|
char *s = namebuf;
|
|
|
|
bzero(namebuf, sizeof(namebuf));
|
|
name = check_string(name);
|
|
strncpyzt(s, name, USERLEN + 1);
|
|
s += strlen(s);
|
|
*s++ = '@';
|
|
host = check_string(host);
|
|
strncpyzt(s, host, HOSTLEN + 1);
|
|
s += strlen(s);
|
|
*s = '\0';
|
|
return (namebuf);
|
|
}
|
|
|
|
|
|
/*
|
|
* create a string of form "foo!bar@fubar" given foo, bar and fubar
|
|
* as the parameters. If NULL, they become "*".
|
|
*/
|
|
inline char *make_nick_user_host_r(char *namebuf, char *nick, char *name, char *host)
|
|
{
|
|
char *s = namebuf;
|
|
|
|
bzero(namebuf, sizeof(namebuf));
|
|
nick = check_string(nick);
|
|
strncpyzt(namebuf, nick, NICKLEN + 1);
|
|
s += strlen(s);
|
|
*s++ = '!';
|
|
name = check_string(name);
|
|
strncpyzt(s, name, USERLEN + 1);
|
|
s += strlen(s);
|
|
*s++ = '@';
|
|
host = check_string(host);
|
|
strncpyzt(s, host, HOSTLEN + 1);
|
|
s += strlen(s);
|
|
*s = '\0';
|
|
return (namebuf);
|
|
}
|
|
|
|
/*
|
|
* create a string of form "foo!bar@fubar" given foo, bar and fubar
|
|
* as the parameters. If NULL, they become "*".
|
|
*/
|
|
char *make_nick_user_host(char *nick, char *name, char *host)
|
|
{
|
|
static char namebuf[NICKLEN + USERLEN + HOSTLEN + 24];
|
|
|
|
return make_nick_user_host_r(namebuf, nick, name, host);
|
|
}
|
|
|
|
|
|
/**
|
|
** myctime()
|
|
** This is like standard ctime()-function, but it zaps away
|
|
** the newline from the end of that string. Also, it takes
|
|
** the time value as parameter, instead of pointer to it.
|
|
** Note that it is necessary to copy the string to alternate
|
|
** buffer (who knows how ctime() implements it, maybe it statically
|
|
** has newline there and never 'refreshes' it -- zapping that
|
|
** might break things in other places...)
|
|
**
|
|
**/
|
|
|
|
char *myctime(time_t value)
|
|
{
|
|
static char buf[28];
|
|
char *p;
|
|
|
|
(void)strlcpy(buf, ctime(&value), sizeof buf);
|
|
if ((p = (char *)index(buf, '\n')) != NULL)
|
|
*p = '\0';
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
** check_registered_user is used to cancel message, if the
|
|
** originator is a server or not registered yet. In other
|
|
** words, passing this test, *MUST* guarantee that the
|
|
** sptr->user exists (not checked after this--let there
|
|
** be coredumps to catch bugs... this is intentional --msa ;)
|
|
**
|
|
** There is this nagging feeling... should this NOT_REGISTERED
|
|
** error really be sent to remote users? This happening means
|
|
** that remote servers have this user registered, althout this
|
|
** one has it not... Not really users fault... Perhaps this
|
|
** error message should be restricted to local clients and some
|
|
** other thing generated for remotes...
|
|
*/
|
|
int check_registered_user(aClient *sptr)
|
|
{
|
|
if (!IsRegisteredUser(sptr))
|
|
{
|
|
sendto_one(sptr, err_str(ERR_NOTREGISTERED), me.name, "*");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
** check_registered user cancels message, if 'x' is not
|
|
** registered (e.g. we don't know yet whether a server
|
|
** or user)
|
|
*/
|
|
int check_registered(aClient *sptr)
|
|
{
|
|
if (!IsRegistered(sptr))
|
|
{
|
|
sendto_one(sptr, err_str(ERR_NOTREGISTERED), me.name, "*");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
** get_client_name
|
|
** Return the name of the client for various tracking and
|
|
** admin purposes. The main purpose of this function is to
|
|
** return the "socket host" name of the client, if that
|
|
** differs from the advertised name (other than case).
|
|
** But, this can be used to any client structure.
|
|
**
|
|
** Returns:
|
|
** "name[user@ip#.port]" if 'showip' is true;
|
|
** "name[sockethost]", if name and sockhost are different and
|
|
** showip is false; else
|
|
** "name".
|
|
**
|
|
** NOTE 1:
|
|
** Watch out the allocation of "nbuf", if either sptr->name
|
|
** or sptr->sockhost gets changed into pointers instead of
|
|
** directly allocated within the structure...
|
|
**
|
|
** NOTE 2:
|
|
** Function return either a pointer to the structure (sptr) or
|
|
** to internal buffer (nbuf). *NEVER* use the returned pointer
|
|
** to modify what it points!!!
|
|
*/
|
|
char *get_client_name(aClient *sptr, int showip)
|
|
{
|
|
static char nbuf[HOSTLEN * 2 + USERLEN + 5];
|
|
|
|
if (MyConnect(sptr))
|
|
{
|
|
if (showip)
|
|
(void)ircsprintf(nbuf, "%s[%s@%s.%u]",
|
|
sptr->name,
|
|
(!(sptr->flags & FLAGS_GOTID)) ? "" :
|
|
sptr->username,
|
|
#ifdef INET6
|
|
inetntop(AF_INET6,
|
|
(char *)&sptr->ip, mydummy, MYDUMMY_SIZE),
|
|
#else
|
|
inetntoa((char *)&sptr->ip),
|
|
#endif
|
|
(unsigned int)sptr->port);
|
|
else
|
|
{
|
|
if (mycmp(sptr->name, sptr->sockhost))
|
|
(void)ircsprintf(nbuf, "%s[%s]",
|
|
sptr->name, sptr->sockhost);
|
|
else
|
|
return sptr->name;
|
|
}
|
|
return nbuf;
|
|
}
|
|
return sptr->name;
|
|
}
|
|
|
|
char *get_client_host(aClient *cptr)
|
|
{
|
|
static char nbuf[HOSTLEN * 2 + USERLEN + 5];
|
|
|
|
if (!MyConnect(cptr))
|
|
return cptr->name;
|
|
if (!cptr->hostp)
|
|
return get_client_name(cptr, FALSE);
|
|
(void)ircsprintf(nbuf, "%s[%-.*s@%-.*s]",
|
|
cptr->name, USERLEN,
|
|
(!(cptr->flags & FLAGS_GOTID)) ? "" : cptr->username,
|
|
HOSTLEN, cptr->hostp->h_name);
|
|
return nbuf;
|
|
}
|
|
|
|
/*
|
|
* Form sockhost such that if the host is of form user@host, only the host
|
|
* portion is copied.
|
|
*/
|
|
void get_sockhost(aClient *cptr, char *host)
|
|
{
|
|
char *s;
|
|
if ((s = (char *)index(host, '@')))
|
|
s++;
|
|
else
|
|
s = host;
|
|
strncpyzt(cptr->sockhost, s, sizeof(cptr->sockhost));
|
|
}
|
|
|
|
void remove_dcc_references(aClient *sptr)
|
|
{
|
|
aClient *acptr;
|
|
Link *lp, *nextlp;
|
|
Link **lpp, *tmp;
|
|
int found;
|
|
|
|
lp = sptr->user->dccallow;
|
|
while(lp)
|
|
{
|
|
nextlp = lp->next;
|
|
acptr = lp->value.cptr;
|
|
for(found = 0, lpp = &(acptr->user->dccallow); *lpp; lpp=&((*lpp)->next))
|
|
{
|
|
if(lp->flags == (*lpp)->flags)
|
|
continue; /* match only opposite types for sanity */
|
|
if((*lpp)->value.cptr == sptr)
|
|
{
|
|
if((*lpp)->flags == DCC_LINK_ME)
|
|
{
|
|
sendto_one(acptr, ":%s %d %s :%s has been removed from "
|
|
"your DCC allow list for signing off",
|
|
me.name, RPL_DCCINFO, acptr->name, sptr->name);
|
|
}
|
|
tmp = *lpp;
|
|
*lpp = tmp->next;
|
|
free_link(tmp);
|
|
found++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!found)
|
|
sendto_realops("[BUG] remove_dcc_references: %s was in dccallowme "
|
|
"list[%d] of %s but not in dccallowrem list!",
|
|
acptr->name, lp->flags, sptr->name);
|
|
|
|
free_link(lp);
|
|
lp = nextlp;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
** exit_client
|
|
** This is old "m_bye". Name changed, because this is not a
|
|
** protocol function, but a general server utility function.
|
|
**
|
|
** This function exits a client of *any* type (user, server, etc)
|
|
** from this server. Also, this generates all necessary prototol
|
|
** messages that this exit may cause.
|
|
**
|
|
** 1) If the client is a local client, then this implicitly
|
|
** exits all other clients depending on this connection (e.g.
|
|
** remote clients having 'from'-field that points to this.
|
|
**
|
|
** 2) If the client is a remote client, then only this is exited.
|
|
**
|
|
** For convenience, this function returns a suitable value for
|
|
** m_funtion return value:
|
|
**
|
|
** FLUSH_BUFFER if (cptr == sptr)
|
|
** 0 if (cptr != sptr)
|
|
*/
|
|
int exit_client(aClient *cptr, aClient *sptr, aClient *from, char *comment)
|
|
{
|
|
aClient *acptr;
|
|
aClient *next;
|
|
time_t on_for;
|
|
ConfigItem_listen *listen_conf;
|
|
static char comment1[HOSTLEN + HOSTLEN + 2];
|
|
static int recurse = 0;
|
|
|
|
if (MyConnect(sptr))
|
|
{
|
|
#ifndef NO_FDLIST
|
|
#define FDLIST_DEBUG
|
|
#ifdef FDLIST_DEBUG
|
|
{
|
|
int i;
|
|
|
|
if (!IsAnOper(sptr))
|
|
{
|
|
for (i = oper_fdlist.last_entry; i; i--)
|
|
{
|
|
if (oper_fdlist.entry[i] == sptr->slot)
|
|
{
|
|
sendto_realops("[BUG] exit_client: oper_fdlist entry while not oper, fd=%d, user='%s'",
|
|
sptr->slot, sptr->name);
|
|
ircd_log(LOG_ERROR, "[BUG] exit_client: oper_fdlist entry while not oper, fd=%d, user='%s'",
|
|
sptr->slot, sptr->name);
|
|
delfrom_fdlist(sptr->slot, &oper_fdlist); /* be kind of enough to fix the problem.. */
|
|
break; /* MUST break here */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
if (IsAnOper(sptr))
|
|
delfrom_fdlist(sptr->slot, &oper_fdlist);
|
|
if (IsServer(sptr))
|
|
delfrom_fdlist(sptr->slot, &serv_fdlist);
|
|
#endif
|
|
if (sptr->class)
|
|
{
|
|
sptr->class->clients--;
|
|
if ((sptr->class->flag.temporary) && !sptr->class->clients && !sptr->class->xrefcount)
|
|
{
|
|
delete_classblock(sptr->class);
|
|
sptr->class = NULL;
|
|
}
|
|
}
|
|
if (IsClient(sptr))
|
|
IRCstats.me_clients--;
|
|
if (sptr->serv && sptr->serv->conf)
|
|
{
|
|
sptr->serv->conf->refcount--;
|
|
Debug((DEBUG_ERROR, "reference count for %s (%s) is now %d",
|
|
sptr->name, sptr->serv->conf->servername, sptr->serv->conf->refcount));
|
|
if (!sptr->serv->conf->refcount
|
|
&& sptr->serv->conf->flag.temporary)
|
|
{
|
|
Debug((DEBUG_ERROR, "deleting temporary block %s", sptr->serv->conf->servername));
|
|
delete_linkblock(sptr->serv->conf);
|
|
sptr->serv->conf = NULL;
|
|
}
|
|
}
|
|
if (IsServer(sptr))
|
|
{
|
|
IRCstats.me_servers--;
|
|
ircd_log(LOG_SERVER, "SQUIT %s (%s)", sptr->name, comment);
|
|
}
|
|
free_pending_net(sptr);
|
|
if (sptr->listener)
|
|
if (sptr->listener->class && !IsOutgoing(sptr))
|
|
{
|
|
listen_conf = (ConfigItem_listen *) sptr->listener->class;
|
|
listen_conf->clients--;
|
|
if (listen_conf->flag.temporary
|
|
&& (listen_conf->clients == 0))
|
|
{
|
|
/* Call listen cleanup */
|
|
listen_cleanup();
|
|
}
|
|
}
|
|
sptr->flags |= FLAGS_CLOSING;
|
|
if (IsPerson(sptr))
|
|
{
|
|
RunHook2(HOOKTYPE_LOCAL_QUIT, sptr, comment);
|
|
sendto_connectnotice(sptr->name, sptr->user, sptr, 1, comment);
|
|
/* Clean out list and watch structures -Donwulff */
|
|
hash_del_watch_list(sptr);
|
|
if (sptr->user && sptr->user->lopt)
|
|
{
|
|
free_str_list(sptr->user->lopt->yeslist);
|
|
free_str_list(sptr->user->lopt->nolist);
|
|
MyFree(sptr->user->lopt);
|
|
}
|
|
on_for = TStime() - sptr->firsttime;
|
|
if (IsHidden(sptr))
|
|
ircd_log(LOG_CLIENT, "Disconnect - (%ld:%ld:%ld) %s!%s@%s [VHOST %s]",
|
|
on_for / 3600, (on_for % 3600) / 60, on_for % 60,
|
|
sptr->name, sptr->user->username,
|
|
sptr->user->realhost, sptr->user->virthost);
|
|
else
|
|
ircd_log(LOG_CLIENT, "Disconnect - (%ld:%ld:%ld) %s!%s@%s",
|
|
on_for / 3600, (on_for % 3600) / 60, on_for % 60,
|
|
sptr->name, sptr->user->username, sptr->user->realhost);
|
|
} else
|
|
if (IsUnknown(sptr))
|
|
{
|
|
RunHook2(HOOKTYPE_UNKUSER_QUIT, sptr, comment);
|
|
}
|
|
|
|
if (sptr->fd >= 0 && !IsConnecting(sptr))
|
|
{
|
|
if (cptr != NULL && sptr != cptr)
|
|
sendto_one(sptr,
|
|
"ERROR :Closing Link: %s %s (%s)",
|
|
get_client_name(sptr, FALSE), cptr->name,
|
|
comment);
|
|
else
|
|
sendto_one(sptr, "ERROR :Closing Link: %s (%s)",
|
|
get_client_name(sptr, FALSE), comment);
|
|
}
|
|
/*
|
|
** Currently only server connections can have
|
|
** depending remote clients here, but it does no
|
|
** harm to check for all local clients. In
|
|
** future some other clients than servers might
|
|
** have remotes too...
|
|
**
|
|
** Close the Client connection first and mark it
|
|
** so that no messages are attempted to send to it.
|
|
** (The following *must* make MyConnect(sptr) == FALSE!).
|
|
** It also makes sptr->from == NULL, thus it's unnecessary
|
|
** to test whether "sptr != acptr" in the following loops.
|
|
*/
|
|
close_connection(sptr);
|
|
}
|
|
|
|
/*
|
|
* Recurse down the client list and get rid of clients who are no
|
|
* longer connected to the network (from my point of view)
|
|
* Only do this expensive stuff if exited==server -Donwulff
|
|
*/
|
|
|
|
if (IsServer(sptr))
|
|
{
|
|
/*
|
|
* Is this right? Not recreateing the split message if
|
|
* we have been called recursivly? I hope so, cuz thats
|
|
* the only way I could make this give the right servers
|
|
* in the quit msg. -Cabal95
|
|
*/
|
|
if (cptr && !recurse)
|
|
{
|
|
/*
|
|
* We are sure as we RELY on sptr->srvptr->name and
|
|
* sptr->name to be less or equal to HOSTLEN
|
|
* Waste of strlcpy/strlcat here
|
|
*/
|
|
(void)strcpy(comment1, sptr->srvptr->name);
|
|
(void)strcat(comment1, " ");
|
|
(void)strcat(comment1, sptr->name);
|
|
}
|
|
/*
|
|
* First, remove the clients on the server itself.
|
|
*/
|
|
for (acptr = client; acptr; acptr = next)
|
|
{
|
|
next = acptr->next;
|
|
if (IsClient(acptr) && (acptr->srvptr == sptr))
|
|
exit_one_client(NULL, acptr,
|
|
&me, comment1, 1);
|
|
}
|
|
|
|
/*
|
|
* Now, go SQUIT off the servers which are down-stream of
|
|
* the one we just lost.
|
|
*/
|
|
recurse++;
|
|
for (acptr = client; acptr; acptr = next)
|
|
{
|
|
next = acptr->next;
|
|
if (IsServer(acptr) && acptr->srvptr == sptr)
|
|
exit_client(sptr, acptr, sptr, comment1); /* RECURSION */
|
|
/*
|
|
* I am not masking SQUITS like I do QUITs. This
|
|
* is probobly something we could easily do, but
|
|
* how much savings is there really in something
|
|
* like that?
|
|
*/
|
|
#ifdef DEBUGMODE
|
|
else if (IsServer(acptr) &&
|
|
(find_server(acptr->serv->up, NULL) == sptr))
|
|
{
|
|
sendto_ops("WARNING, srvptr!=sptr but "
|
|
"find_server did! Server %s on "
|
|
"%s thought it was on %s while "
|
|
"losing %s. Tell coding team.",
|
|
acptr->name, acptr->serv->up,
|
|
acptr->srvptr ? acptr->
|
|
srvptr->name : "<noserver>", sptr->name);
|
|
exit_client(sptr, acptr, sptr, comment1);
|
|
}
|
|
#endif
|
|
}
|
|
recurse--;
|
|
RunHook(HOOKTYPE_SERVER_QUIT, sptr);
|
|
}
|
|
|
|
|
|
/*
|
|
* Finally, clear out the server we lost itself
|
|
*/
|
|
exit_one_client(cptr, sptr, from, comment, recurse);
|
|
return cptr == sptr ? FLUSH_BUFFER : 0;
|
|
}
|
|
|
|
/*
|
|
** Exit one client, local or remote. Assuming all dependants have
|
|
** been already removed, and socket closed for local client.
|
|
*/
|
|
/* DANGER: Ugly hack follows. */
|
|
/* Yeah :/ */
|
|
static void exit_one_client(aClient *cptr, aClient *sptr, aClient *from, char *comment, int split)
|
|
{
|
|
aClient *acptr;
|
|
int i;
|
|
Link *lp;
|
|
Membership *mp;
|
|
/*
|
|
** For a server or user quitting, propagage the information to
|
|
** other servers (except to the one where is came from (cptr))
|
|
*/
|
|
if (IsMe(sptr))
|
|
{
|
|
sendto_ops("ERROR: tried to exit me! : %s", comment);
|
|
return; /* ...must *never* exit self!! */
|
|
}
|
|
else if (IsServer(sptr))
|
|
{
|
|
/*
|
|
** Old sendto_serv_but_one() call removed because we now
|
|
** need to send different names to different servers
|
|
** (domain name matching)
|
|
*/
|
|
for (i = 0; i <= LastSlot; i++)
|
|
{
|
|
|
|
if (!(acptr = local[i]) || !IsServer(acptr) || acptr == cptr || IsMe(acptr)
|
|
|| (DontSendQuit(acptr) && split))
|
|
continue;
|
|
/*
|
|
** SQUIT going "upstream". This is the remote
|
|
** squit still hunting for the target. Use prefixed
|
|
** form. "from" will be either the oper that issued
|
|
** the squit or some server along the path that
|
|
** didn't have this fix installed. --msa
|
|
*/
|
|
if (sptr->from == acptr)
|
|
{
|
|
sendto_one(acptr, ":%s SQUIT %s :%s", from->name, sptr->name, comment);
|
|
}
|
|
else
|
|
{
|
|
sendto_one(acptr, "SQUIT %s :%s", sptr->name, comment);
|
|
}
|
|
}
|
|
}
|
|
else if (!(IsPerson(sptr)))
|
|
/* ...this test is *dubious*, would need
|
|
** some thougth.. but for now it plugs a
|
|
** nasty hole in the server... --msa
|
|
*/
|
|
; /* Nothing */
|
|
else if (sptr->name[0]) /* ...just clean all others with QUIT... */
|
|
{
|
|
/*
|
|
** If this exit is generated from "m_kill", then there
|
|
** is no sense in sending the QUIT--KILL's have been
|
|
** sent instead.
|
|
*/
|
|
if ((sptr->flags & FLAGS_KILLED) == 0)
|
|
{
|
|
if (split == 0)
|
|
sendto_serv_butone_token
|
|
(cptr, sptr->name, MSG_QUIT, TOK_QUIT,
|
|
":%s", comment);
|
|
else
|
|
/*
|
|
* Then this is a split, only old (stupid)
|
|
* clients need to get quit messages
|
|
*/
|
|
sendto_serv_butone_quit(cptr, ":%s QUIT :%s",
|
|
sptr->name, comment);
|
|
}
|
|
/*
|
|
** If a person is on a channel, send a QUIT notice
|
|
** to every client (person) on the same channel (so
|
|
** that the client can show the "**signoff" message).
|
|
** (Note: The notice is to the local clients *only*)
|
|
*/
|
|
if (sptr->user)
|
|
{
|
|
sendto_common_channels(sptr, ":%s QUIT :%s",
|
|
sptr->name, comment);
|
|
|
|
if (!IsULine(sptr) && !split)
|
|
if (sptr->user->server != me_hash)
|
|
sendto_fconnectnotice(sptr->name, sptr->user, sptr, 1, comment);
|
|
if (!MyClient(sptr))
|
|
{
|
|
RunHook2(HOOKTYPE_REMOTE_QUIT, sptr, comment);
|
|
}
|
|
#ifdef JOINTHROTTLE
|
|
cmodej_deluserentries(sptr);
|
|
#endif
|
|
while ((mp = sptr->user->channel))
|
|
remove_user_from_channel(sptr, mp->chptr);
|
|
|
|
/* Clean up invitefield */
|
|
while ((lp = sptr->user->invited))
|
|
del_invite(sptr, lp->value.chptr);
|
|
/* again, this is all that is needed */
|
|
|
|
/* Clean up silencefield */
|
|
while ((lp = sptr->user->silence))
|
|
(void)del_silence(sptr, lp->value.cp);
|
|
|
|
/* Clean up dccallow list and (if needed) notify other clients
|
|
* that have this person on DCCALLOW that the user just left/got removed.
|
|
*/
|
|
remove_dcc_references(sptr);
|
|
|
|
/* For remote clients, we need to check for any outstanding async
|
|
* connects attached to this 'sptr', and set those records to NULL.
|
|
* Why not for local? Well, we already do that in close_connection ;)
|
|
*/
|
|
if (!MyConnect(sptr))
|
|
unrealdns_delreq_bycptr(sptr);
|
|
}
|
|
}
|
|
|
|
/* Remove sptr from the client list */
|
|
if (del_from_client_hash_table(sptr->name, sptr) != 1)
|
|
Debug((DEBUG_ERROR, "%#x !in tab %s[%s] %#x %#x %#x %d %d %#x",
|
|
sptr, sptr->name,
|
|
sptr->from ? sptr->from->sockhost : "??host",
|
|
sptr->from, sptr->next, sptr->prev, sptr->fd,
|
|
sptr->status, sptr->user));
|
|
if (IsRegisteredUser(sptr))
|
|
hash_check_watch(sptr, RPL_LOGOFF);
|
|
remove_client_from_list(sptr);
|
|
return;
|
|
}
|
|
|
|
|
|
void checklist(void)
|
|
{
|
|
aClient *acptr;
|
|
int i, j;
|
|
|
|
if (!(bootopt & BOOT_AUTODIE))
|
|
return;
|
|
for (j = i = 0; i <= LastSlot; i++)
|
|
if (!(acptr = local[i]))
|
|
continue;
|
|
else if (IsClient(acptr))
|
|
j++;
|
|
if (!j)
|
|
{
|
|
exit(0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
void initstats(void)
|
|
{
|
|
bzero((char *)&ircst, sizeof(ircst));
|
|
}
|
|
|
|
void verify_opercount(aClient *orig, char *tag)
|
|
{
|
|
int counted = 0;
|
|
aClient *acptr;
|
|
char text[2048];
|
|
|
|
for (acptr = client; acptr; acptr = acptr->next)
|
|
{
|
|
if (IsOper(acptr) && !IsHideOper(acptr))
|
|
counted++;
|
|
}
|
|
if (counted == IRCstats.operators)
|
|
return;
|
|
sprintf(text, "[BUG] operator count bug! value in /lusers is '%d', we counted '%d', "
|
|
"user='%s', userserver='%s', tag=%s. Corrected. ",
|
|
IRCstats.operators, counted, orig->name ? orig->name : "<null>",
|
|
orig->srvptr ? orig->srvptr->name : "<null>", tag ? tag : "<null>");
|
|
#ifdef DEBUGMODE
|
|
sendto_realops("%s", text);
|
|
#endif
|
|
ircd_log(LOG_ERROR, "%s", text);
|
|
IRCstats.operators = counted;
|
|
}
|
|
|
|
/** Check if the specified hostname does not contain forbidden characters.
|
|
* RETURNS:
|
|
* 1 if ok, 0 if rejected.
|
|
*/
|
|
int valid_host(char *host)
|
|
{
|
|
char *p;
|
|
for (p=host; *p; p++)
|
|
if (!isalnum(*p) && (*p != '_') && (*p != '-') && (*p != '.') && (*p != ':'))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/** Checks if the specified regex (or fast badwords) is valid.
|
|
* returns NULL in case of success [!],
|
|
* pointer to buffer with error message otherwise
|
|
* if check_broadness is 1, the function will attempt to determine
|
|
* if the given regex string is too broad (i.e. matches everything)
|
|
*/
|
|
char *unreal_checkregex(char *s, int fastsupport, int check_broadness)
|
|
{
|
|
int errorcode, errorbufsize, regex=0;
|
|
char *errtmp, *tmp;
|
|
static char errorbuf[512];
|
|
regex_t expr;
|
|
|
|
if (!fastsupport)
|
|
goto Ilovegotos;
|
|
|
|
for (tmp = s; *tmp; tmp++) {
|
|
if (!isalnum(*tmp) && !(*tmp >= 128)) {
|
|
if ((s == tmp) && (*tmp == '*'))
|
|
continue;
|
|
if ((*(tmp + 1) == '\0') && (*tmp == '*'))
|
|
continue;
|
|
regex = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (regex)
|
|
{
|
|
Ilovegotos:
|
|
errorcode = regcomp(&expr, s, REG_ICASE|REG_EXTENDED);
|
|
if (errorcode > 0)
|
|
{
|
|
errorbufsize = regerror(errorcode, &expr, NULL, 0)+1;
|
|
errtmp = MyMalloc(errorbufsize);
|
|
regerror(errorcode, &expr, errtmp, errorbufsize);
|
|
strncpyzt(errorbuf, errtmp, sizeof(errorbuf));
|
|
free(errtmp);
|
|
regfree(&expr);
|
|
return errorbuf;
|
|
}
|
|
if (check_broadness && !regexec(&expr, "", 0, NULL, 0))
|
|
{
|
|
strncpyzt(errorbuf, "Regular expression is too broad", sizeof(errorbuf));
|
|
regfree(&expr);
|
|
return errorbuf;
|
|
}
|
|
regfree(&expr);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
#define SPF_REGEX_FLAGS (REG_ICASE|REG_EXTENDED|REG_NOSUB)
|
|
|
|
/** Allocates a new Spamfilter entry and compiles/fills in the info.
|
|
* NOTE: originally I wanted to integrate both badwords and spamfilter
|
|
* into one function, but that was quickly getting ugly :(.
|
|
*/
|
|
Spamfilter *unreal_buildspamfilter(char *s)
|
|
{
|
|
Spamfilter *e = MyMallocEx(sizeof(Spamfilter));
|
|
|
|
regcomp(&e->expr, s, SPF_REGEX_FLAGS);
|
|
return e;
|
|
}
|
|
|
|
|
|
/*|| BAN ACTION ROUTINES FOLLOW ||*/
|
|
|
|
|
|
/** Converts a banaction string (eg: "kill") to an integer value (eg: BAN_ACT_KILL) */
|
|
int banact_stringtoval(char *s)
|
|
{
|
|
BanActTable *b;
|
|
|
|
for (b = &banacttable[0]; b->value; b++)
|
|
if (!strcasecmp(s, b->name))
|
|
return b->value;
|
|
return 0;
|
|
}
|
|
|
|
/** Converts a banaction character (eg: 'K') to an integer value (eg: BAN_ACT_KILL) */
|
|
int banact_chartoval(char c)
|
|
{
|
|
BanActTable *b;
|
|
|
|
for (b = &banacttable[0]; b->value; b++)
|
|
if (b->character == c)
|
|
return b->value;
|
|
return 0;
|
|
}
|
|
|
|
/** Converts a banaction value (eg: BAN_ACT_KILL) to a character value (eg: 'k') */
|
|
char banact_valtochar(int val)
|
|
{
|
|
BanActTable *b;
|
|
|
|
for (b = &banacttable[0]; b->value; b++)
|
|
if (b->value == val)
|
|
return b->character;
|
|
return '\0';
|
|
}
|
|
|
|
/** Converts a banaction value (eg: BAN_ACT_KLINE) to a string (eg: "kline") */
|
|
char *banact_valtostring(int val)
|
|
{
|
|
BanActTable *b;
|
|
|
|
for (b = &banacttable[0]; b->value; b++)
|
|
if (b->value == val)
|
|
return b->name;
|
|
return "UNKNOWN";
|
|
}
|
|
|
|
/*|| BAN TARGET ROUTINES FOLLOW ||*/
|
|
|
|
/** Extract target flags from string 's'. */
|
|
int spamfilter_gettargets(char *s, aClient *sptr)
|
|
{
|
|
SpamfilterTargetTable *e;
|
|
int flags = 0;
|
|
|
|
for (; *s; s++)
|
|
{
|
|
for (e = &spamfiltertargettable[0]; e->value; e++)
|
|
if (e->character == *s)
|
|
{
|
|
flags |= e->value;
|
|
break;
|
|
}
|
|
if (!e->value && sptr)
|
|
{
|
|
sendnotice(sptr, "Unknown target type '%c'", *s);
|
|
return 0;
|
|
}
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
/** Convert a string with a targetname to an integer value */
|
|
int spamfilter_getconftargets(char *s)
|
|
{
|
|
SpamfilterTargetTable *e;
|
|
|
|
for (e = &spamfiltertargettable[0]; e->value; e++)
|
|
if (!strcmp(s, e->name))
|
|
return e->value;
|
|
return 0;
|
|
}
|
|
|
|
/** Create a string with (multiple) targets from an integer mask */
|
|
char *spamfilter_target_inttostring(int v)
|
|
{
|
|
static char buf[128];
|
|
SpamfilterTargetTable *e;
|
|
char *p = buf;
|
|
|
|
for (e = &spamfiltertargettable[0]; e->value; e++)
|
|
if (v & e->value)
|
|
*p++ = e->character;
|
|
*p = '\0';
|
|
return buf;
|
|
}
|
|
|
|
char *unreal_decodespace(char *s)
|
|
{
|
|
static char buf[512], *i, *o;
|
|
for (i = s, o = buf; (*i) && (o < buf+510); i++)
|
|
if (*i == '_')
|
|
{
|
|
if (i[1] != '_')
|
|
*o++ = ' ';
|
|
else {
|
|
*o++ = '_';
|
|
i++;
|
|
}
|
|
}
|
|
else
|
|
*o++ = *i;
|
|
*o = '\0';
|
|
return buf;
|
|
}
|
|
|
|
char *unreal_encodespace(char *s)
|
|
{
|
|
static char buf[512], *i, *o;
|
|
|
|
if (!s)
|
|
return NULL; /* NULL in = NULL out */
|
|
|
|
for (i = s, o = buf; (*i) && (o < buf+509); i++)
|
|
{
|
|
if (*i == ' ')
|
|
*o++ = '_';
|
|
else if (*i == '_')
|
|
{
|
|
*o++ = '_';
|
|
*o++ = '_';
|
|
}
|
|
else
|
|
*o++ = *i;
|
|
}
|
|
*o = '\0';
|
|
return buf;
|
|
}
|
|
|
|
/** This is basically only used internally by dospamfilter()... */
|
|
char *cmdname_by_spamftarget(int target)
|
|
{
|
|
SpamfilterTargetTable *e;
|
|
|
|
for (e = &spamfiltertargettable[0]; e->value; e++)
|
|
if (e->value == target)
|
|
return e->irccommand;
|
|
return "???";
|
|
}
|
|
|
|
int is_autojoin_chan(char *chname)
|
|
{
|
|
char buf[512];
|
|
char *p, *name;
|
|
|
|
if (OPER_AUTO_JOIN_CHANS)
|
|
{
|
|
strlcpy(buf, OPER_AUTO_JOIN_CHANS, sizeof(buf));
|
|
|
|
for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
|
|
if (!strcasecmp(name, chname))
|
|
return 1;
|
|
}
|
|
|
|
if (AUTO_JOIN_CHANS)
|
|
{
|
|
strlcpy(buf, AUTO_JOIN_CHANS, sizeof(buf));
|
|
|
|
for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
|
|
if (!strcasecmp(name, chname))
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char *getcloak(aClient *sptr)
|
|
{
|
|
if (!*sptr->user->cloakedhost)
|
|
{
|
|
/* need to calculate (first-time) */
|
|
make_virthost(sptr, sptr->user->realhost, sptr->user->cloakedhost, 0);
|
|
}
|
|
|
|
return sptr->user->cloakedhost;
|
|
}
|
|
|
|
/** Kicks all insecure users on a +z channel */
|
|
void kick_insecure_users(aChannel *chptr)
|
|
{
|
|
Member *member, *mb2;
|
|
aClient *cptr;
|
|
char *comment = "Insecure user not allowed on secure channel (+z)";
|
|
|
|
for (member = chptr->members; member; member = mb2)
|
|
{
|
|
mb2 = member->next;
|
|
cptr = member->cptr;
|
|
if (MyClient(cptr) && !IsSecureConnect(cptr) && !IsULine(cptr))
|
|
{
|
|
RunHook5(HOOKTYPE_LOCAL_KICK, &me, &me, cptr, chptr, comment);
|
|
|
|
if ((chptr->mode.mode & MODE_AUDITORIUM) && is_chanownprotop(cptr, chptr))
|
|
{
|
|
sendto_chanops_butone(cptr, chptr, ":%s KICK %s %s :%s", me.name, chptr->chname, cptr->name, comment);
|
|
sendto_prefix_one(cptr, &me, ":%s KICK %s %s :%s", me.name, chptr->chname, cptr->name, comment);
|
|
}
|
|
else
|
|
{
|
|
sendto_channel_butserv(chptr, &me, ":%s KICK %s %s :%s", me.name, chptr->chname, cptr->name, comment);
|
|
}
|
|
|
|
sendto_serv_butone_token(&me, me.name, MSG_KICK, TOK_KICK, "%s %s :%s", chptr->chname, cptr->name, comment);
|
|
|
|
remove_user_from_channel(cptr, chptr);
|
|
}
|
|
}
|
|
}
|