From e6c7fcda7dcec36a9f4f15039693258ee15f65b5 Mon Sep 17 00:00:00 2001 From: Bram Matthys Date: Fri, 25 Oct 2019 09:32:30 +0200 Subject: [PATCH] Move "real command" stuff to src/api-command.c and move dopacket() to src/parse.c. Also re-order functions in parse.c so they appear in logical order (1->2->3->4) rather than various helper functions first and some random order. --- include/h.h | 1 + src/api-command.c | 99 ++++++++++++++++++++ src/bsd.c | 115 ------------------------ src/packet.c | 147 ------------------------------ src/parse.c | 225 +++++++++++++++++++++++++++++++++++++--------- 5 files changed, 285 insertions(+), 302 deletions(-) diff --git a/include/h.h b/include/h.h index 3a1b42478..ba56a480e 100644 --- a/include/h.h +++ b/include/h.h @@ -948,3 +948,4 @@ extern int should_show_connect_info(Client *client); extern void send_invalid_channelname(Client *client, char *channelname); extern int is_extended_ban(const char *str); extern int valid_sid(char *name); +extern void parse_client_queued(Client *client); diff --git a/src/api-command.c b/src/api-command.c index cda314c17..65fec53d4 100644 --- a/src/api-command.c +++ b/src/api-command.c @@ -128,3 +128,102 @@ void CommandDel(Command *command) { CommandDelX(command, command->cmd); } + +/** Calls the specified command for the user, as if it was received + * that way on IRC. + * @param client Client that is the source. + * @param mtags Message tags for this command. + * @param cmd Command to run, eg "JOIN". + * @param parc Parameter count plus 1. + * @param parv Parameter array. + * @note Make sure you terminate the last parv[] parameter with NULL, + * this can easily be forgotten, but certain functions depend on it, + * you risk crashes otherwise. + * @note Once do_cmd() has returned, be sure to check IsDead(client) to + * see if the client has been killed. This may happen due to various + * reasons, including spamfilter kicking in or some other security + * measure. + * @note Do not pass insane parameters. The combined size of all parameters + * should not exceed 510 bytes, since that is what all code expects. + * Similarly, you should not exceed MAXPARA for parc. + */ +void do_cmd(Client *client, MessageTag *mtags, char *cmd, int parc, char *parv[]) +{ + RealCommand *cmptr; + + cmptr = find_Command_simple(cmd); + if (cmptr) + (*cmptr->func) (client, mtags, parc, parv); +} + +/**** This is the "real command" API ***** + * Perhaps one day we will merge the two, if possible. + */ + +RealCommand *CommandHash[256]; /* one per letter */ + +void init_CommandHash(void) +{ + memset(CommandHash, 0, sizeof(CommandHash)); + CommandAdd(NULL, MSG_ERROR, cmd_error, MAXPARA, CMD_UNREGISTERED|CMD_SERVER); + CommandAdd(NULL, MSG_VERSION, cmd_version, MAXPARA, CMD_UNREGISTERED|CMD_USER|CMD_SERVER); + CommandAdd(NULL, MSG_INFO, cmd_info, MAXPARA, CMD_USER); + CommandAdd(NULL, MSG_DNS, cmd_dns, MAXPARA, CMD_USER); + CommandAdd(NULL, MSG_REHASH, cmd_rehash, MAXPARA, CMD_USER|CMD_SERVER); + CommandAdd(NULL, MSG_RESTART, cmd_restart, 2, CMD_USER); + CommandAdd(NULL, MSG_DIE, cmd_die, MAXPARA, CMD_USER); + CommandAdd(NULL, MSG_DALINFO, cmd_dalinfo, MAXPARA, CMD_USER); + CommandAdd(NULL, MSG_CREDITS, cmd_credits, MAXPARA, CMD_USER); + CommandAdd(NULL, MSG_LICENSE, cmd_license, MAXPARA, CMD_USER); + CommandAdd(NULL, MSG_MODULE, cmd_module, MAXPARA, CMD_USER); +} + +RealCommand *add_Command_backend(char *cmd) +{ + RealCommand *c = safe_alloc(sizeof(RealCommand)); + + safe_strdup(c->cmd, cmd); + + /* Add in hash with hash value = first byte */ + AddListItem(c, CommandHash[toupper(*cmd)]); + + return c; +} + +static inline RealCommand *find_Cmd(char *cmd, int flags) +{ + RealCommand *p; + for (p = CommandHash[toupper(*cmd)]; p; p = p->next) { + if ((flags & CMD_UNREGISTERED) && !(p->flags & CMD_UNREGISTERED)) + continue; + if ((flags & CMD_SHUN) && !(p->flags & CMD_SHUN)) + continue; + if ((flags & CMD_VIRUS) && !(p->flags & CMD_VIRUS)) + continue; + if ((flags & CMD_ALIAS) && !(p->flags & CMD_ALIAS)) + continue; + if (!strcasecmp(p->cmd, cmd)) + return p; + } + return NULL; +} + +RealCommand *find_Command(char *cmd, short token, int flags) +{ + Debug((DEBUG_NOTICE, "FindCommand %s", cmd)); + + return find_Cmd(cmd, flags); +} + +RealCommand *find_Command_simple(char *cmd) +{ + RealCommand *c; + + for (c = CommandHash[toupper(*cmd)]; c; c = c->next) + { + if (!strcasecmp(c->cmd, cmd)) + return c; + } + + return NULL; +} diff --git a/src/bsd.c b/src/bsd.c index 61ae0644b..51ac89cc9 100644 --- a/src/bsd.c +++ b/src/bsd.c @@ -1060,121 +1060,6 @@ void proceed_normal_client_handshake(Client *client, struct hostent *he) finish_auth(client); } -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->since - TStime() < 10) - return 0; - return 1; -} - -/* -** read_packet -** -** Read a 'packet' of data from a connection and process it. Read in 8k -** chunks to give a better performance rating (for server connections). -** Do some tricky stuff for client connections to make sure they don't do -** any flooding >:-) -avalon -** If 'doread' is set to 0 then we don't actually read (no recv()), -** however we still check if we need to dequeue anything from the recvQ. -** This is necessary, since we may have put something on the recvQ due -** to fake lag. -- Syzop -** With new I/O code, things work differently. Surprise! -** read_one_packet() reads packets in and dumps them as quickly as -** possible into the client's DBuf. Then we parse data out of the DBuf, -** after we're done reading crap. -** -- nenolod -*/ -static 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) && - (TStime() - client->local->firsttime < 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; - } -} - -/** 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. - * @notes 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) > UNKNOWN_FLOOD_AMOUNT*1024)) - { - sendto_snomask(SNO_FLOOD, "Flood from unknown connection %s detected", - client->local->sockhost); - if (!killsafely) - ban_flooder(client); - else - dead_socket(client, "Flood from unknown connection"); - return 0; - } - - /* excess flood check */ - if (IsUser(client) && DBufLength(&client->local->recvQ) > get_recvq(client)) - { - sendto_snomask(SNO_FLOOD, - "*** Flood -- %s!%s@%s (%d) exceeds %d recvQ", - client->name[0] ? client->name : "*", - client->user ? client->user->username : "*", - client->user ? client->user->realhost : "*", - DBufLength(&client->local->recvQ), get_recvq(client)); - if (!killsafely) - exit_client(client, NULL, "Excess Flood"); - else - dead_socket(client, "Excess Flood"); - return 0; - } - - return 1; -} - void read_packet(int fd, int revents, void *data) { Client *client = data; diff --git a/src/packet.c b/src/packet.c index 21c3d84fe..99910d090 100644 --- a/src/packet.c +++ b/src/packet.c @@ -20,150 +20,3 @@ */ #include "unrealircd.h" - -RealCommand *CommandHash[256]; /* one per letter */ - -/* -** 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) -{ - me.local->receiveB += length; /* Update bytes received */ - client->local->receiveB += length; - if (client->local->receiveB > 1023) - { - client->local->receiveK += (client->local->receiveB >> 10); - client->local->receiveB &= 0x03ff; /* 2^10 = 1024, 3ff = 1023 */ - } - if (me.local->receiveB > 1023) - { - me.local->receiveK += (me.local->receiveB >> 10); - me.local->receiveB &= 0x03ff; - } - - me.local->receiveM += 1; /* Update messages received */ - client->local->receiveM += 1; - - parse(client, buffer, length); -} - -void init_CommandHash(void) -{ -#ifdef DEVELOP_DEBUG - RealCommand *p; - int i; - long chainlength; -#endif - - memset(CommandHash, 0, sizeof(CommandHash)); - CommandAdd(NULL, MSG_ERROR, cmd_error, MAXPARA, CMD_UNREGISTERED|CMD_SERVER); - CommandAdd(NULL, MSG_VERSION, cmd_version, MAXPARA, CMD_UNREGISTERED|CMD_USER|CMD_SERVER); - CommandAdd(NULL, MSG_INFO, cmd_info, MAXPARA, CMD_USER); - CommandAdd(NULL, MSG_DNS, cmd_dns, MAXPARA, CMD_USER); - CommandAdd(NULL, MSG_REHASH, cmd_rehash, MAXPARA, CMD_USER|CMD_SERVER); - CommandAdd(NULL, MSG_RESTART, cmd_restart, 2, CMD_USER); - CommandAdd(NULL, MSG_DIE, cmd_die, MAXPARA, CMD_USER); - CommandAdd(NULL, MSG_DALINFO, cmd_dalinfo, MAXPARA, CMD_USER); - CommandAdd(NULL, MSG_CREDITS, cmd_credits, MAXPARA, CMD_USER); - CommandAdd(NULL, MSG_LICENSE, cmd_license, MAXPARA, CMD_USER); - CommandAdd(NULL, MSG_MODULE, cmd_module, MAXPARA, CMD_USER); - -#ifdef DEVELOP_DEBUG - for (i = 0; i <= 255; i++) - { - chainlength = 0; - for (p = CommandHash[i]; p; p = p->next) - chainlength++; - if (chainlength) - fprintf(stderr, "%c chainlength = %i\r\n", - i, chainlength); - } -#endif -} - -RealCommand *add_Command_backend(char *cmd) -{ - RealCommand *c = safe_alloc(sizeof(RealCommand)); - - safe_strdup(c->cmd, cmd); - - /* Add in hash with hash value = first byte */ - AddListItem(c, CommandHash[toupper(*cmd)]); - - return c; -} - -static inline RealCommand *find_Cmd(char *cmd, int flags) -{ - RealCommand *p; - for (p = CommandHash[toupper(*cmd)]; p; p = p->next) { - if ((flags & CMD_UNREGISTERED) && !(p->flags & CMD_UNREGISTERED)) - continue; - if ((flags & CMD_SHUN) && !(p->flags & CMD_SHUN)) - continue; - if ((flags & CMD_VIRUS) && !(p->flags & CMD_VIRUS)) - continue; - if ((flags & CMD_ALIAS) && !(p->flags & CMD_ALIAS)) - continue; - if (!strcasecmp(p->cmd, cmd)) - return p; - } - return NULL; -} - -RealCommand *find_Command(char *cmd, short token, int flags) -{ - Debug((DEBUG_NOTICE, "FindCommand %s", cmd)); - - return find_Cmd(cmd, flags); -} - -RealCommand *find_Command_simple(char *cmd) -{ - RealCommand *p; - - for (p = CommandHash[toupper(*cmd)]; p; p = p->next) { - if (!strcasecmp(p->cmd, cmd)) - return (p); - } - - return NULL; -} - -/** Calls the specified command for the user, as if it was received - * that way on IRC. - * @param client Client that is the source. - * @param mtags Message tags for this command. - * @param cmd Command to run, eg "JOIN". - * @param parc Parameter count plus 1. - * @param parv Parameter array. - * @note Make sure you terminate the last parv[] parameter with NULL, - * this can easily be forgotten, but certain functions depend on it, - * you risk crashes otherwise. - * @note Once do_cmd() has returned, be sure to check IsDead(client) to - * see if the client has been killed. This may happen due to various - * reasons, including spamfilter kicking in or some other security - * measure. - * @note Do not pass insane parameters. The combined size of all parameters - * should not exceed 510 bytes, since that is what all code expects. - * Similarly, you should not exceed MAXPARA for parc. - */ -void do_cmd(Client *client, MessageTag *mtags, char *cmd, int parc, char *parv[]) -{ - RealCommand *cmptr; - - cmptr = find_Command_simple(cmd); - if (cmptr) - (*cmptr->func) (client, mtags, parc, parv); -} diff --git a/src/parse.c b/src/parse.c index 074604c1a..ff1ab2f4a 100644 --- a/src/parse.c +++ b/src/parse.c @@ -28,61 +28,137 @@ char backupbuf[8192]; static char *para[MAXPARA + 2]; +/* Forward declarations of functions that are local (static) */ static int do_numeric(int, Client *, MessageTag *, int, char **); static void cancel_clients(Client *, Client *, char *); static void remove_unknown(Client *, char *); +static void parse2(Client *client, Client **fromptr, MessageTag *mtags, char *ch); +static void parse_addlag(Client *client, int cmdbytes); +static int client_lagged_up(Client *client); -/** 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. +/** 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. + * @notes 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. */ -void ban_flooder(Client *client) +int process_packet(Client *client, char *readbuf, int length, int killsafely) { - if (find_tkl_exception(TKL_UNKNOWN_DATA_FLOOD, client)) + 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) > UNKNOWN_FLOOD_AMOUNT*1024)) { - /* 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, "Flood from unknown connection"); + sendto_snomask(SNO_FLOOD, "Flood from unknown connection %s detected", + client->local->sockhost); + if (!killsafely) + ban_flooder(client); + else + dead_socket(client, "Flood from unknown connection"); + return 0; } - else + + /* excess flood check */ + if (IsUser(client) && DBufLength(&client->local->recvQ) > get_recvq(client)) { - /* place_host_ban also takes care of removing any other clients with same host/ip */ - place_host_ban(client, BAN_ACT_ZLINE, "Flood from unknown connection", UNKNOWN_FLOOD_BANTIME); + sendto_snomask(SNO_FLOOD, + "*** Flood -- %s!%s@%s (%d) exceeds %d recvQ", + client->name[0] ? client->name : "*", + client->user ? client->user->username : "*", + client->user ? client->user->realhost : "*", + DBufLength(&client->local->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) && + (TStime() - client->local->firsttime < 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; } } -/** 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 cmdbytes Number of bytes in the command. - */ -void parse_addlag(Client *client, int cmdbytes) +/* +** 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) { - 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)) + me.local->receiveB += length; /* Update bytes received */ + client->local->receiveB += length; + if (client->local->receiveB > 1023) { - client->local->since += (1 + cmdbytes/90); - } + client->local->receiveK += (client->local->receiveB >> 10); + client->local->receiveB &= 0x03ff; /* 2^10 = 1024, 3ff = 1023 */ + } + if (me.local->receiveB > 1023) + { + me.local->receiveK += (me.local->receiveB >> 10); + me.local->receiveB &= 0x03ff; + } + + me.local->receiveM += 1; /* Update messages received */ + client->local->receiveM += 1; + + parse(client, buffer, length); } -void parse2(Client *client, Client **fromptr, MessageTag *mtags, char *ch); /** Parse an incoming line. * A line was received previously, buffered via dbuf, now popped from the dbuf stack, @@ -167,7 +243,7 @@ void parse(Client *cptr, char *buffer, int length) * @param mtags Message tags received for this message. * @param ch The incoming line received (buffer), excluding message tags. */ -void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, char *ch) +static void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, char *ch) { Client *from = cptr; char *s; @@ -449,6 +525,75 @@ void parse2(Client *cptr, Client **fromptr, MessageTag *mtags, char *ch) #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. + */ +void ban_flooder(Client *client) +{ + if (find_tkl_exception(TKL_UNKNOWN_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, "Flood from unknown connection"); + } + else + { + /* place_host_ban also takes care of removing any other clients with same host/ip */ + place_host_ban(client, BAN_ACT_ZLINE, "Flood from unknown connection", UNKNOWN_FLOOD_BANTIME); + } +} + +/** 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 cmdbytes Number of bytes in the command. + */ +void parse_addlag(Client *client, int cmdbytes) +{ + 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)) + { + client->local->since += (1 + cmdbytes/90); + } +} + +/** 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->since - TStime() < 10) + return 0; + return 1; +} + + /** Numeric received from a connection. * @param numeric The numeric code (range 000-999) * @param cptr The client