mirror of
git://git.acid.vegas/unrealircd.git
synced 2024-12-26 00:06:38 +00:00
783 lines
24 KiB
C
783 lines
24 KiB
C
/************************************************************************
|
|
* Unreal Internet Relay Chat Daemon, src/parse.c
|
|
* Copyright (C) 1990 Jarkko Oikarinen and
|
|
* University of Oulu, Computing Center
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/** @file
|
|
* @brief Main line parsing functions - for incoming lines from clients.
|
|
*/
|
|
#include "unrealircd.h"
|
|
|
|
/** Last (or current) command that we processed. Useful for post-mortem. */
|
|
char backupbuf[8192];
|
|
|
|
static char *para[MAXPARA + 2];
|
|
|
|
/* Forward declarations of functions that are local (static) */
|
|
static int do_numeric(int, Client *, MessageTag *, int, const char **);
|
|
static void cancel_clients(Client *, Client *, char *);
|
|
static void remove_unknown(Client *, char *);
|
|
static void parse2(Client *client, Client **fromptr, MessageTag *mtags, int mtags_bytes, char *ch);
|
|
static void parse_addlag(Client *client, int command_bytes, int mtags_bytes);
|
|
static int client_lagged_up(Client *client);
|
|
static void ban_handshake_data_flooder(Client *client);
|
|
|
|
/** Put a packet in the client receive queue and process the data (if
|
|
* the 'fake lag' rules permit doing so).
|
|
* @param client The client
|
|
* @param readbuf The read buffer
|
|
* @param length The length of the data
|
|
* @param killsafely If 1 then we may call exit_client() if the client
|
|
* is flooding. If 0 then we use dead_socket().
|
|
* @returns 1 in normal circumstances, 0 if client was killed.
|
|
* @note If killsafely is 1 and the return value is 0 then
|
|
* the client was killed - IsDead() is true.
|
|
* If this is a problem, then set killsafely to 0 when calling.
|
|
*/
|
|
int process_packet(Client *client, char *readbuf, int length, int killsafely)
|
|
{
|
|
dbuf_put(&client->local->recvQ, readbuf, length);
|
|
|
|
/* parse some of what we have (inducing fakelag, etc) */
|
|
parse_client_queued(client);
|
|
|
|
/* We may be killed now, so check for it.. */
|
|
if (IsDead(client))
|
|
return 0;
|
|
|
|
/* flood from unknown connection */
|
|
if (IsUnknown(client) && (DBufLength(&client->local->recvQ) > iConf.handshake_data_flood_amount))
|
|
{
|
|
unreal_log(ULOG_INFO, "flood", "HANDSHAKE_DATA_FLOOD", client,
|
|
"Handshake data flood detected from $client.details [$client.ip]");
|
|
if (!killsafely)
|
|
ban_handshake_data_flooder(client);
|
|
else
|
|
dead_socket(client, "Handshake data flood detected");
|
|
return 0;
|
|
}
|
|
|
|
/* excess flood check */
|
|
if (IsUser(client) && DBufLength(&client->local->recvQ) > get_recvq(client))
|
|
{
|
|
unreal_log(ULOG_INFO, "flood", "RECVQ_EXCEEDED", client,
|
|
"Flood from $client.details [$client.ip] exceeds class::recvq ($recvq > $class_recvq) (Client sending too much data)",
|
|
log_data_integer("recvq", DBufLength(&client->local->recvQ)),
|
|
log_data_integer("class_recvq", get_recvq(client)));
|
|
if (!killsafely)
|
|
exit_client(client, NULL, "Excess Flood");
|
|
else
|
|
dead_socket(client, "Excess Flood");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/** Parse any queued data for 'client', if permitted.
|
|
* @param client The client.
|
|
*/
|
|
void parse_client_queued(Client *client)
|
|
{
|
|
int dolen = 0;
|
|
char buf[READBUFSIZE];
|
|
|
|
if (IsDNSLookup(client))
|
|
return; /* we delay processing of data until the host is resolved */
|
|
|
|
if (IsIdentLookup(client))
|
|
return; /* we delay processing of data until identd has replied */
|
|
|
|
if (!IsUser(client) && !IsServer(client) && (iConf.handshake_delay > 0) &&
|
|
!IsNoHandshakeDelay(client) &&
|
|
!IsUnixSocket(client) &&
|
|
(TStime() - client->local->creationtime < iConf.handshake_delay))
|
|
{
|
|
return; /* we delay processing of data until set::handshake-delay is reached */
|
|
}
|
|
|
|
while (DBufLength(&client->local->recvQ) && !client_lagged_up(client))
|
|
{
|
|
dolen = dbuf_getmsg(&client->local->recvQ, buf);
|
|
|
|
if (dolen == 0)
|
|
return;
|
|
|
|
dopacket(client, buf, dolen);
|
|
|
|
if (IsDead(client))
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** dopacket
|
|
** client - pointer to client structure for which the buffer data
|
|
** applies.
|
|
** buffer - pointr to the buffer containing the newly read data
|
|
** length - number of valid bytes of data in the buffer
|
|
**
|
|
** Note:
|
|
** It is implicitly assumed that dopacket is called only
|
|
** with client of "local" variation, which contains all the
|
|
** necessary fields (buffer etc..)
|
|
**
|
|
** Rewritten for linebufs, 19th May 2013. --kaniini
|
|
*/
|
|
void dopacket(Client *client, char *buffer, int length)
|
|
{
|
|
client->local->traffic.bytes_received += length;
|
|
me.local->traffic.bytes_received += length;
|
|
|
|
client->local->traffic.messages_received++;
|
|
me.local->traffic.messages_received++;
|
|
|
|
parse(client, buffer, length);
|
|
}
|
|
|
|
|
|
/** Parse an incoming line.
|
|
* A line was received previously, buffered via dbuf, now popped from the dbuf stack,
|
|
* and we should now process it.
|
|
* @param cptr The client from which the message was received
|
|
* @param buffer The buffer
|
|
* @param length The length of the buffer
|
|
* @note parse() cannot not be called recusively by any other functions!
|
|
*/
|
|
void parse(Client *cptr, char *buffer, int length)
|
|
{
|
|
Hook *h;
|
|
Client *from = cptr;
|
|
char *ch;
|
|
int i, ret;
|
|
MessageTag *mtags = NULL;
|
|
int mtags_bytes = 0;
|
|
|
|
/* Take extreme care in this function, as messages can be up to READBUFSIZE
|
|
* in size, which is 8192 at the time of writing.
|
|
* This, while all the rest of the IRCd code assumes a maximum length
|
|
* of BUFSIZE, which is 512 (including NUL byte).
|
|
*/
|
|
for (h = Hooks[HOOKTYPE_PACKET]; h; h = h->next)
|
|
{
|
|
(*(h->func.intfunc))(from, &me, NULL, &buffer, &length);
|
|
if (!buffer)
|
|
return;
|
|
}
|
|
|
|
if (IsDeadSocket(cptr))
|
|
return;
|
|
|
|
if ((cptr->local->traffic.bytes_received >= iConf.handshake_data_flood_amount) && IsUnknown(cptr))
|
|
{
|
|
unreal_log(ULOG_INFO, "flood", "HANDSHAKE_DATA_FLOOD", cptr,
|
|
"Handshake data flood detected from $client.details [$client.ip]");
|
|
ban_handshake_data_flooder(cptr);
|
|
return;
|
|
}
|
|
|
|
/* This stores the last executed command in 'backupbuf', useful for debugging crashes */
|
|
strlcpy(backupbuf, buffer, sizeof(backupbuf));
|
|
|
|
#if defined(RAWCMDLOGGING)
|
|
unreal_log(ULOG_INFO, "rawtraffic", "TRAFFIC_IN", cptr,
|
|
"<- $client: $data",
|
|
log_data_string("data", backupbuf));
|
|
#endif
|
|
|
|
/* This poisons unused para elements that code should never access */
|
|
for (i = 0; i < MAXPARA+2; i++)
|
|
para[i] = (char *)DEADBEEF_ADDR;
|
|
|
|
/* First, skip any whitespace */
|
|
for (ch = buffer; *ch == ' '; ch++)
|
|
;
|
|
|
|
/* Now, parse message tags, if any */
|
|
if (*ch == '@')
|
|
{
|
|
char *start = ch;
|
|
parse_message_tags(cptr, &ch, &mtags);
|
|
if (ch - start > 0)
|
|
mtags_bytes = ch - start;
|
|
/* Skip whitespace again */
|
|
for (; *ch == ' '; ch++)
|
|
;
|
|
}
|
|
|
|
parse2(cptr, &from, mtags, mtags_bytes, ch);
|
|
|
|
if (IsDead(cptr))
|
|
RunHook(HOOKTYPE_POST_COMMAND, NULL, mtags, ch);
|
|
else
|
|
RunHook(HOOKTYPE_POST_COMMAND, from, mtags, ch);
|
|
|
|
free_message_tags(mtags);
|
|
return;
|
|
}
|
|
|
|
/** Parse the remaining line - helper function for parse().
|
|
* @param cptr The client from which the message was received
|
|
* @param from The sender, this may be changed by parse2() when
|
|
* the message has a sender, eg :xyz PRIVMSG ..
|
|
* @param mtags Message tags received for this message.
|
|
* @param mtags_bytes The length of all message tags.
|
|
* @param ch The incoming line received (buffer), excluding message tags.
|
|
*/
|
|
static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, int mtags_bytes, char *ch)
|
|
{
|
|
Client *from = cptr;
|
|
char *s;
|
|
int len, i, numeric = 0, paramcount;
|
|
#ifdef DEBUGMODE
|
|
time_t then, ticks;
|
|
int retval;
|
|
#endif
|
|
RealCommand *cmptr = NULL;
|
|
int bytes;
|
|
|
|
*fromptr = cptr; /* The default, unless a source is specified (and permitted) */
|
|
|
|
/* The remaining part should never be more than 510 bytes
|
|
* (that is 512 minus CR LF, as specified in RFC1459 section 2.3).
|
|
* If it is too long, then we cut it off here.
|
|
*/
|
|
if (strlen(ch) > 510)
|
|
{
|
|
ch[510] = '\0';
|
|
}
|
|
|
|
para[0] = (char *)DEADBEEF_ADDR; /* helps us catch bugs :) */
|
|
|
|
if (*ch == ':' || *ch == '@')
|
|
{
|
|
char sender[HOSTLEN + 1];
|
|
s = sender;
|
|
*s = '\0';
|
|
|
|
/* Deal with :sender ... */
|
|
for (++ch, i = 0; *ch && *ch != ' '; ++ch)
|
|
{
|
|
if (s < sender + sizeof(sender) - 1)
|
|
*s++ = *ch;
|
|
}
|
|
*s = '\0';
|
|
|
|
/* For servers we lookup the sender and change 'from' accordingly.
|
|
* For other clients we ignore the sender.
|
|
*/
|
|
if (*sender && IsServer(cptr))
|
|
{
|
|
from = find_client(sender, NULL);
|
|
|
|
if (!from && strchr(sender, '@'))
|
|
from = hash_find_nickatserver(sender, NULL);
|
|
|
|
/* Sender not found. Possibly a ghost, so kill it.
|
|
* This can happen in normal circumstances. For example
|
|
* in case of A-B-C where we are B. If a KILL came from C
|
|
* for a client on A and we processed it at B, then until
|
|
* A has processed it we may still receive messages from A
|
|
* about it's soon-to-be-killed-client (all due to lag).
|
|
*/
|
|
if (!from)
|
|
{
|
|
ircstats.is_unpf++;
|
|
remove_unknown(cptr, sender);
|
|
return;
|
|
}
|
|
/* This is more severe. The server gave a source of a client
|
|
* that cannot exist from that direction.
|
|
* Eg in case of a topology of A-B-C-D and we are B,
|
|
* we got a message from A with ":D MODE...".
|
|
* In that case we send a SQUIT to that direction telling to
|
|
* unlink D from that side. This will likely lead to a
|
|
* problematic situation, though.
|
|
* This is, by the way, also why we try to prevent this situation
|
|
* in the first place by using PROTOCTL SERVERS=...
|
|
* in which case we reject such a flawed link very early
|
|
* in the server handshake process. -- Syzop
|
|
*/
|
|
if (from->direction != cptr)
|
|
{
|
|
ircstats.is_wrdi++;
|
|
cancel_clients(cptr, from, ch);
|
|
return;
|
|
}
|
|
*fromptr = from; /* Update source client */
|
|
}
|
|
while (*ch == ' ')
|
|
ch++;
|
|
}
|
|
|
|
RunHook(HOOKTYPE_PRE_COMMAND, from, mtags, ch);
|
|
|
|
if (*ch == '\0')
|
|
{
|
|
if (!IsServer(cptr))
|
|
cptr->local->fake_lag++; /* 1s fake lag */
|
|
return;
|
|
}
|
|
|
|
/* Recalculate string length, now that we have skipped the sender */
|
|
bytes = strlen(ch);
|
|
|
|
/* Now let's figure out the command (or numeric)... */
|
|
s = strchr(ch, ' '); /* s -> End of the command code */
|
|
len = (s) ? (s - ch) : 0;
|
|
|
|
if (len == 3 && isdigit(*ch) && isdigit(*(ch + 1)) && isdigit(*(ch + 2)))
|
|
{
|
|
/* Numeric (eg: 311) */
|
|
cmptr = NULL;
|
|
numeric = (*ch - '0') * 100 + (*(ch + 1) - '0') * 10 + (*(ch + 2) - '0');
|
|
paramcount = MAXPARA;
|
|
ircstats.is_num++;
|
|
parse_addlag(cptr, bytes, mtags_bytes);
|
|
}
|
|
else
|
|
{
|
|
/* Command (eg: PRIVMSG) */
|
|
int flags = 0;
|
|
if (s)
|
|
*s++ = '\0';
|
|
|
|
/* Set the appropriate flags for the command lookup */
|
|
if (!IsRegistered(from))
|
|
flags |= CMD_UNREGISTERED;
|
|
if (IsUser(from))
|
|
flags |= CMD_USER;
|
|
if (IsServer(from))
|
|
flags |= CMD_SERVER;
|
|
if (IsShunned(from))
|
|
flags |= CMD_SHUN;
|
|
if (IsVirus(from))
|
|
flags |= CMD_VIRUS;
|
|
if (IsOper(from))
|
|
flags |= CMD_OPER;
|
|
if (IsControl(from))
|
|
flags |= CMD_CONTROL;
|
|
cmptr = find_command(ch, flags);
|
|
if (!cmptr || !(cmptr->flags & CMD_NOLAG))
|
|
{
|
|
/* Add fake lag (doing this early in the code, so we don't forget) */
|
|
parse_addlag(cptr, bytes, mtags_bytes);
|
|
}
|
|
if (!cmptr)
|
|
{
|
|
if (IsControl(from))
|
|
{
|
|
sendto_one(from, NULL, "ERROR UNKNOWN_COMMAND: %s", ch);
|
|
sendto_one(from, NULL, "END 1");
|
|
return;
|
|
}
|
|
/* Don't send error messages in response to NOTICEs
|
|
* in pre-connection state.
|
|
*/
|
|
if (!IsRegistered(cptr) && strcasecmp(ch, "NOTICE"))
|
|
{
|
|
sendnumericfmt(from, ERR_NOTREGISTERED, ":You have not registered");
|
|
return;
|
|
}
|
|
/* If the user is shunned then don't send anything back in case
|
|
* of an unknown command, since we want to save data.
|
|
*/
|
|
if (IsShunned(cptr))
|
|
return;
|
|
|
|
if (ch[0] != '\0')
|
|
{
|
|
if (IsUser(from))
|
|
{
|
|
sendto_one(from, NULL, ":%s %d %s %s :Unknown command",
|
|
me.name, ERR_UNKNOWNCOMMAND,
|
|
from->name, ch);
|
|
}
|
|
}
|
|
ircstats.is_unco++;
|
|
return;
|
|
}
|
|
if (cmptr->flags != 0) { /* temporary until all commands are updated */
|
|
|
|
/* Logic in comparisons below is a bit complicated, see notes */
|
|
|
|
/* If you're a user, and this command does not permit users or opers, deny */
|
|
if ((flags & CMD_USER) && !(cmptr->flags & CMD_USER) && !(cmptr->flags & CMD_OPER))
|
|
{
|
|
if (cmptr->flags & CMD_UNREGISTERED)
|
|
sendnumeric(cptr, ERR_ALREADYREGISTRED); /* only for unregistered phase */
|
|
else
|
|
sendnumeric(cptr, ERR_NOTFORUSERS, cmptr->cmd); /* really never for users */
|
|
return;
|
|
}
|
|
|
|
/* If you're a server, but command doesn't want servers, deny */
|
|
if ((flags & CMD_SERVER) && !(cmptr->flags & CMD_SERVER))
|
|
return;
|
|
}
|
|
|
|
/* If you're a user, but not an operator, and this requires operators, deny */
|
|
if ((cmptr->flags & CMD_OPER) && (flags & CMD_USER) && !(flags & CMD_OPER))
|
|
{
|
|
sendnumeric(cptr, ERR_NOPRIVILEGES);
|
|
return;
|
|
}
|
|
paramcount = cmptr->parameters;
|
|
cmptr->bytes += bytes;
|
|
}
|
|
/*
|
|
** Must the following loop really be so devious? On
|
|
** surface it splits the message to parameters from
|
|
** blank spaces. But, if paramcount has been reached,
|
|
** the rest of the message goes into this last parameter
|
|
** (about same effect as ":" has...) --msa
|
|
*/
|
|
|
|
/* Note initially true: s==NULL || *(s-1) == '\0' !! */
|
|
|
|
i = 0;
|
|
if (s)
|
|
{
|
|
/*
|
|
if (paramcount > MAXPARA)
|
|
paramcount = MAXPARA;
|
|
We now use functions to create commands, so we can just check this
|
|
once when the command is created rather than each time the command
|
|
is used -- codemastr
|
|
*/
|
|
for (;;)
|
|
{
|
|
/*
|
|
** Never "FRANCE " again!! ;-) Clean
|
|
** out *all* blanks.. --msa
|
|
*/
|
|
while (*s == ' ')
|
|
*s++ = '\0';
|
|
|
|
if (*s == '\0')
|
|
break;
|
|
if (*s == ':')
|
|
{
|
|
/*
|
|
** The rest is single parameter--can
|
|
** include blanks also.
|
|
*/
|
|
para[++i] = s + 1;
|
|
break;
|
|
}
|
|
para[++i] = s;
|
|
if (i >= paramcount)
|
|
break;
|
|
for (; *s != ' ' && *s; s++)
|
|
;
|
|
}
|
|
}
|
|
para[++i] = NULL;
|
|
|
|
/* Check if one of the message tags are rejected by spamfilter */
|
|
if (MyConnect(from) && !IsServer(from) && match_spamfilter_mtags(from, mtags, cmptr ? cmptr->cmd : NULL))
|
|
return;
|
|
|
|
if (cmptr == NULL)
|
|
{
|
|
do_numeric(numeric, from, mtags, i, (const char **)para);
|
|
return;
|
|
}
|
|
cmptr->count++;
|
|
if (IsUser(cptr) && (cmptr->flags & CMD_RESETIDLE))
|
|
cptr->local->idle_since = TStime();
|
|
|
|
/* Now ready to execute the command */
|
|
#ifndef DEBUGMODE
|
|
if (cmptr->flags & CMD_ALIAS)
|
|
{
|
|
(*cmptr->aliasfunc) (from, mtags, i, (const char **)para, cmptr->cmd);
|
|
} else {
|
|
if (!cmptr->overriders)
|
|
(*cmptr->func) (from, mtags, i, (const char **)para);
|
|
else
|
|
(*cmptr->overriders->func) (cmptr->overriders, from, mtags, i, (const char **)para);
|
|
}
|
|
#else
|
|
then = clock();
|
|
if (cmptr->flags & CMD_ALIAS)
|
|
{
|
|
(*cmptr->aliasfunc) (from, mtags, i, (const char **)para, cmptr->cmd);
|
|
} else {
|
|
if (!cmptr->overriders)
|
|
(*cmptr->func) (from, mtags, i, (const char **)para);
|
|
else
|
|
(*cmptr->overriders->func) (cmptr->overriders, from, mtags, i, (const char **)para);
|
|
}
|
|
if (!IsDead(cptr))
|
|
{
|
|
ticks = (clock() - then);
|
|
if (IsServer(cptr))
|
|
cmptr->rticks += ticks;
|
|
else
|
|
cmptr->lticks += ticks;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/** Ban user that is "flooding from an unknown connection".
|
|
* This is basically a client sending lots of data but not registering.
|
|
* Note that "lots" in terms of IRC is a few KB's, since more is rather unusual.
|
|
* @param client The client.
|
|
*/
|
|
static void ban_handshake_data_flooder(Client *client)
|
|
{
|
|
if (find_tkl_exception(TKL_HANDSHAKE_DATA_FLOOD, client))
|
|
{
|
|
/* If the user is exempt we will still KILL the client, since it is
|
|
* clearly misbehaving. We just won't ZLINE the host, so it won't
|
|
* affect any other connections from the same IP address.
|
|
*/
|
|
exit_client(client, NULL, "Handshake data flood detected");
|
|
}
|
|
else
|
|
{
|
|
/* place_host_ban also takes care of removing any other clients with same host/ip */
|
|
place_host_ban(client, iConf.handshake_data_flood_ban_action, "Handshake data flood detected", iConf.handshake_data_flood_ban_time);
|
|
}
|
|
}
|
|
|
|
/** Add "fake lag" if needed.
|
|
* The main purpose of fake lag is to create artificial lag when
|
|
* processing incoming data from the client. So, if a client sends
|
|
* a lot of commands, then next command will be processed at a rate
|
|
* of 1 per second, or even slower. The exact algorithm is defined in this function.
|
|
*
|
|
* Servers are exempt from fake lag, so are IRCOps and clients tagged as
|
|
* 'no fake lag' by services (rarely used). Finally, there is also an
|
|
* option called class::options::nofakelag which exempts fakelag.
|
|
* Exemptions should be granted with extreme care, since a client will
|
|
* be able to flood at full speed causing potentially many Mbits or even
|
|
* GBits of data to be sent out to other clients.
|
|
*
|
|
* @param client The client.
|
|
* @param command_bytes Command length in bytes (excluding message tagss)
|
|
* @param mtags_bytes Length of message tags in bytes
|
|
*/
|
|
void parse_addlag(Client *client, int command_bytes, int mtags_bytes)
|
|
{
|
|
FloodSettings *settings = get_floodsettings_for_user(client, FLD_LAG_PENALTY);
|
|
|
|
if (!IsServer(client) && !IsNoFakeLag(client) &&
|
|
#ifdef FAKELAG_CONFIGURABLE
|
|
!(client->local->class && (client->local->class->options & CLASS_OPT_NOFAKELAG)) &&
|
|
#endif
|
|
!ValidatePermissionsForPath("immune:lag",client,NULL,NULL,NULL))
|
|
{
|
|
int lag_penalty = settings->period[FLD_LAG_PENALTY];
|
|
int lag_penalty_bytes = settings->limit[FLD_LAG_PENALTY];
|
|
|
|
client->local->fake_lag_msec += (1 + (command_bytes/lag_penalty_bytes) + (mtags_bytes/lag_penalty_bytes)) * lag_penalty;
|
|
|
|
/* This code takes into account not only the msecs we just calculated
|
|
* but also any leftover msec from previous lagging up.
|
|
*/
|
|
client->local->fake_lag += (client->local->fake_lag_msec / 1000);
|
|
client->local->fake_lag_msec = client->local->fake_lag_msec % 1000;
|
|
}
|
|
}
|
|
|
|
/* Add extra fake lag to client, such as after a failed oper attempt.
|
|
*/
|
|
void add_fake_lag(Client *client, long msec)
|
|
{
|
|
if (!MyConnect(client))
|
|
return;
|
|
|
|
client->local->fake_lag_msec += msec;
|
|
client->local->fake_lag += (client->local->fake_lag_msec / 1000);
|
|
client->local->fake_lag_msec = client->local->fake_lag_msec % 1000;
|
|
}
|
|
|
|
/** Returns 1 if the client is lagged up and data should NOT be parsed.
|
|
* See also parse_addlag() for more information on "fake lag".
|
|
* @param client The client to check
|
|
* @returns 1 if client is lagged up and data should not be parsed, 0 otherwise.
|
|
*/
|
|
static int client_lagged_up(Client *client)
|
|
{
|
|
if (client->status < CLIENT_STATUS_UNKNOWN)
|
|
return 0;
|
|
if (IsServer(client))
|
|
return 0;
|
|
if (ValidatePermissionsForPath("immune:lag",client,NULL,NULL,NULL))
|
|
return 0;
|
|
if (client->local->fake_lag - TStime() < 10)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
/** Numeric received from a connection.
|
|
* @param numeric The numeric code (range 000-999)
|
|
* @param cptr The client
|
|
* @param recv_mtags Received message tags
|
|
* @param parc Parameter count
|
|
* @param parv Parameters
|
|
* @note In general you should NOT send anything back if you receive
|
|
* a numeric, this to prevent creating loops.
|
|
*/
|
|
static int do_numeric(int numeric, Client *client, MessageTag *recv_mtags, int parc, const char *parv[])
|
|
{
|
|
Client *acptr;
|
|
Channel *channel;
|
|
char *nick, *p;
|
|
int i;
|
|
char buffer[BUFSIZE];
|
|
char targets[BUFSIZE];
|
|
|
|
if ((numeric < 0) || (numeric > 999))
|
|
return -1;
|
|
|
|
if (MyConnect(client) && !IsServer(client) && !IsUser(client) && IsHandshake(client) && client->server && !IsServerSent(client))
|
|
{
|
|
/* This is an outgoing server connect that is currently not yet IsServer() but in 'unknown' state.
|
|
* We need to handle a few responses here.
|
|
*/
|
|
|
|
/* STARTTLS: unknown command */
|
|
if ((numeric == 451) && (parc > 2) && strstr(parv[1], "STARTTLS"))
|
|
{
|
|
if (client->server->conf && (client->server->conf->outgoing.options & CONNECT_INSECURE))
|
|
start_server_handshake(client);
|
|
else
|
|
reject_insecure_server(client);
|
|
return 0;
|
|
}
|
|
|
|
/* STARTTLS failed */
|
|
if (numeric == 691)
|
|
{
|
|
unreal_log(ULOG_WARNING, "link", "STARTTLS_FAILED", client,
|
|
"Switching from plaintext to TLS via STARTTLS failed for server $client, this is unusual. "
|
|
"Please check the other side of the link for errors.");
|
|
reject_insecure_server(client);
|
|
return 0;
|
|
}
|
|
|
|
/* STARTTLS OK */
|
|
if (numeric == 670)
|
|
{
|
|
int ret = client_starttls(client);
|
|
if (ret < 0)
|
|
{
|
|
unreal_log(ULOG_WARNING, "link", "STARTTLS_FAILED", client,
|
|
"Switching from plaintext to TLS via STARTTLS failed for server $client, this is unusual.");
|
|
reject_insecure_server(client);
|
|
return ret;
|
|
}
|
|
/* We don't call start_server_handshake() here. First the TLS handshake will
|
|
* be completed, then completed_connection() will be called for a second time,
|
|
* which will call completed_connection() from there.
|
|
*/
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Other than the (strange) code from above, we actually
|
|
* don't process numerics from non-servers. So return here.
|
|
*/
|
|
if ((parc < 2) || BadPtr(parv[1]) || !IsServer(client))
|
|
return 0;
|
|
|
|
/* Remap low number numerics. */
|
|
if (numeric < 100)
|
|
numeric += 100;
|
|
|
|
/* Convert parv[] back to a string 'buffer', since that is
|
|
* what we use in the sendto_* functions below.
|
|
*/
|
|
concat_params(buffer, sizeof(buffer), parc, parv);
|
|
|
|
/* Now actually process the numeric, IOTW: send it on */
|
|
strlcpy(targets, parv[1], sizeof(targets));
|
|
for (nick = strtoken(&p, targets, ","); nick; nick = strtoken(&p, NULL, ","))
|
|
{
|
|
if ((acptr = find_client(nick, NULL)))
|
|
{
|
|
if (!IsMe(acptr) && IsUser(acptr))
|
|
{
|
|
if (MyConnect(acptr) && isdigit(*nick))
|
|
{
|
|
/* Hack to prevent leaking UID */
|
|
char *skip = strchr(buffer, ' ');
|
|
if (skip)
|
|
{
|
|
sendto_prefix_one(acptr, client, recv_mtags, ":%s %d %s %s",
|
|
client->name, numeric, acptr->name, skip+1);
|
|
} /* else.. malformed (no content) */
|
|
} else {
|
|
sendto_prefix_one(acptr, client, recv_mtags, ":%s %d %s",
|
|
client->name, numeric, buffer);
|
|
}
|
|
}
|
|
else if (IsServer(acptr) && acptr->direction != client->direction)
|
|
sendto_prefix_one(acptr, client, recv_mtags, ":%s %d %s",
|
|
client->name, numeric, buffer);
|
|
}
|
|
else if ((acptr = find_server_quick(nick)))
|
|
{
|
|
if (!IsMe(acptr) && acptr->direction != client->direction)
|
|
sendto_prefix_one(acptr, client, recv_mtags, ":%s %d %s",
|
|
client->name, numeric, buffer);
|
|
}
|
|
else if ((channel = find_channel(nick)))
|
|
{
|
|
sendto_channel(channel, client, client->direction,
|
|
0, 0, SEND_ALL, recv_mtags,
|
|
":%s %d %s", client->name, numeric, buffer);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// FIXME: aren't we exiting the wrong client?
|
|
static void cancel_clients(Client *cptr, Client *client, char *cmd)
|
|
{
|
|
if (IsServer(cptr) || IsServer(client) || IsMe(client))
|
|
return;
|
|
exit_client(cptr, NULL, "Fake prefix");
|
|
}
|
|
|
|
static void remove_unknown(Client *client, char *sender)
|
|
{
|
|
if (!IsRegistered(client) || IsUser(client))
|
|
return;
|
|
/*
|
|
* Not from a server so don't need to worry about it.
|
|
*/
|
|
if (!IsServer(client))
|
|
return;
|
|
|
|
/*
|
|
* Do kill if it came from a server because it means there is a ghost
|
|
* user on the other server which needs to be removed. -avalon
|
|
*/
|
|
if ((isdigit(*sender) && strlen(sender) <= SIDLEN) || strchr(sender, '.'))
|
|
sendto_one(client, NULL, ":%s SQUIT %s :Unknown prefix (%s) from %s",
|
|
me.id, sender, sender, client->name);
|
|
else
|
|
sendto_one(client, NULL, ":%s KILL %s :Ghost user", me.id, sender);
|
|
}
|