mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-27 19:46:37 +02:00
513 lines
13 KiB
C
513 lines
13 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.
|
|
*/
|
|
|
|
/* -- Jto -- 03 Jun 1990
|
|
* Changed the order of defines...
|
|
*/
|
|
|
|
/* parse.c 2.33 1/30/94 (C) 1988 University of Oulu, Computing Center and Jarkko Oikarinen */
|
|
|
|
#include <string.h>
|
|
#include "struct.h"
|
|
#include "common.h"
|
|
|
|
#undef RAWDEBUG
|
|
|
|
char backupbuf[8192];
|
|
|
|
#define MSGTAB
|
|
#include "msg.h"
|
|
#undef MSGTAB
|
|
#include "sys.h"
|
|
#include "numeric.h"
|
|
#include "h.h"
|
|
#include "proto.h"
|
|
|
|
/*
|
|
* NOTE: parse() should not be called recursively by other functions!
|
|
*/
|
|
static char *para[MAXPARA + 2];
|
|
|
|
static char sender[HOSTLEN + 1];
|
|
static int cancel_clients(aClient *, aClient *, char *);
|
|
static void remove_unknown(aClient *, char *);
|
|
/*
|
|
** Find a client (server or user) by name.
|
|
**
|
|
** *Note*
|
|
** Semantics of this function has been changed from
|
|
** the old. 'name' is now assumed to be a null terminated
|
|
** string and the search is the for server and user.
|
|
*/
|
|
aClient *find_client(char *name, aClient *cptr)
|
|
{
|
|
if (cptr == NULL || IsServer(cptr))
|
|
{
|
|
aClient *acptr;
|
|
|
|
if ((acptr = hash_find_id(name, NULL)) != NULL)
|
|
return acptr;
|
|
}
|
|
|
|
return hash_find_client(name, NULL);
|
|
}
|
|
|
|
aClient inline *find_nickserv(char *name, aClient *cptr)
|
|
{
|
|
if (name)
|
|
cptr = hash_find_nickserver(name, cptr);
|
|
|
|
return cptr;
|
|
}
|
|
|
|
|
|
/*
|
|
** Find server by name.
|
|
**
|
|
** This implementation assumes that server and user names
|
|
** are unique, no user can have a server name and vice versa.
|
|
** One should maintain separate lists for users and servers,
|
|
** if this restriction is removed.
|
|
**
|
|
** *Note*
|
|
** Semantics of this function has been changed from
|
|
** the old. 'name' is now assumed to be a null terminated
|
|
** string.
|
|
*/
|
|
aClient inline *find_server(char *name, aClient *cptr)
|
|
{
|
|
if (name)
|
|
{
|
|
aClient *acptr;
|
|
|
|
if ((acptr = find_client(name, NULL)) != NULL && (IsServer(acptr) || IsMe(acptr)))
|
|
return acptr;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
aClient inline *find_name(char *name, aClient *cptr)
|
|
{
|
|
aClient *c2ptr = cptr;
|
|
|
|
if (!collapse(name))
|
|
return c2ptr;
|
|
|
|
if ((c2ptr = hash_find_server(name, cptr)))
|
|
return (c2ptr);
|
|
if (!index(name, '*'))
|
|
return c2ptr;
|
|
list_for_each_entry(c2ptr, &client_list, client_node)
|
|
{
|
|
if (!IsServer(c2ptr) && !IsMe(c2ptr))
|
|
continue;
|
|
if (match(name, c2ptr->name) == 0)
|
|
break;
|
|
if (index(c2ptr->name, '*'))
|
|
if (match(c2ptr->name, name) == 0)
|
|
break;
|
|
}
|
|
return (c2ptr ? c2ptr : cptr);
|
|
}
|
|
|
|
/*
|
|
** Find person by (nick)name.
|
|
*/
|
|
aClient *find_person(char *name, aClient *cptr)
|
|
{
|
|
aClient *c2ptr;
|
|
|
|
c2ptr = find_client(name, cptr);
|
|
|
|
if (c2ptr && IsClient(c2ptr) && c2ptr->user)
|
|
return c2ptr;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void ban_flooder(aClient *cptr)
|
|
{
|
|
/* place_host_ban also takes care of removing any other clients with same host/ip */
|
|
place_host_ban(cptr, BAN_ACT_ZLINE, "Flood from unknown connection", UNKNOWN_FLOOD_BANTIME);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This routine adds fake lag if needed.
|
|
*/
|
|
void parse_addlag(aClient *cptr, int cmdbytes)
|
|
{
|
|
if (!IsServer(cptr) && !IsNoFakeLag(cptr) &&
|
|
#ifdef FAKELAG_CONFIGURABLE
|
|
!(cptr->local->class && (cptr->local->class->options & CLASS_OPT_NOFAKELAG)) &&
|
|
#endif
|
|
!ValidatePermissionsForPath("privacy:fakelag",cptr,NULL,NULL,NULL))
|
|
{
|
|
cptr->local->since += (1 + cmdbytes/90);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* parse a buffer.
|
|
*
|
|
* NOTE: parse() should not be called recusively by any other fucntions!
|
|
*/
|
|
int parse(aClient *cptr, char *buffer, char *bufend)
|
|
{
|
|
Hook *h;
|
|
int buf_len = 0;
|
|
aClient *from = cptr;
|
|
char *ch, *s;
|
|
int len, i, numeric = 0, paramcount;
|
|
#ifdef DEBUGMODE
|
|
time_t then, ticks;
|
|
int retval;
|
|
#endif
|
|
aCommand *cmptr = NULL;
|
|
|
|
for(h = Hooks[HOOKTYPE_PACKET]; h; h = h->next) {
|
|
buf_len = (int)(bufend - buffer);
|
|
(*(h->func.intfunc))(from, &me, &buffer, &buf_len);
|
|
if(!buffer) return 0;
|
|
bufend = buffer + buf_len;
|
|
}
|
|
|
|
Debug((DEBUG_ERROR, "Parsing: %s (from %s)", buffer,
|
|
(*cptr->name ? cptr->name : "*")));
|
|
if (IsDead(cptr))
|
|
return 0;
|
|
|
|
if ((cptr->local->receiveK >= UNKNOWN_FLOOD_AMOUNT) && IsUnknown(cptr))
|
|
{
|
|
sendto_snomask(SNO_FLOOD, "Flood from unknown connection %s detected",
|
|
cptr->local->sockhost);
|
|
ban_flooder(cptr);
|
|
return FLUSH_BUFFER;
|
|
}
|
|
|
|
/* this call is a bit obsolete? - takes up CPU */
|
|
backupbuf[0] = '\0';
|
|
strlcpy(backupbuf, buffer, sizeof(backupbuf));
|
|
#if defined(DEBUGMODE) && defined(RAWCMDLOGGING)
|
|
ircd_log(LOG_ERROR, "<- %s: %s", cptr->name, backupbuf);
|
|
#endif
|
|
s = sender;
|
|
*s = '\0';
|
|
for (ch = buffer; *ch == ' '; ch++)
|
|
;
|
|
//para[0] = from->name;
|
|
para[0] = (char *)0xDEADBEEF; /* helps us catch bugs :) -- 1/2 */
|
|
if (*ch == ':' || *ch == '@')
|
|
{
|
|
/*
|
|
** Copy the prefix to 'sender' assuming it terminates
|
|
** with SPACE (or NULL, which is an error, though).
|
|
*/
|
|
for (++ch, i = 0; *ch && *ch != ' '; ++ch)
|
|
if (s < (sender + sizeof(sender) - 1))
|
|
*s++ = *ch; /* leave room for NULL */
|
|
*s = '\0';
|
|
/*
|
|
** Actually, only messages coming from servers can have
|
|
** the prefix--prefix silently ignored, if coming from
|
|
** a user client...
|
|
**
|
|
** ...sigh, the current release "v2.2PL1" generates also
|
|
** null prefixes, at least to NOTIFY messages (e.g. it
|
|
** puts "sptr->nickname" as prefix from server structures
|
|
** where it's null--the following will handle this case
|
|
** as "no prefix" at all --msa (": NOTICE nick ...")
|
|
*/
|
|
if (*sender && IsServer(cptr))
|
|
{
|
|
from = find_client(sender, (aClient *)NULL);
|
|
if (!from && index(sender, '@'))
|
|
from = find_nickserv(sender, (aClient *)NULL);
|
|
//para[0] = sender;
|
|
para[0] = (char *)0xDEADBEEF; /* helps us catch bugs :) -- 2/2 */
|
|
|
|
/* Hmm! If the client corresponding to the
|
|
* prefix is not found--what is the correct
|
|
* action??? Now, I will ignore the message
|
|
* (old IRC just let it through as if the
|
|
* prefix just wasn't there...) --msa
|
|
*/
|
|
|
|
if (!from)
|
|
{
|
|
Debug((DEBUG_ERROR,
|
|
"Unknown prefix (%s)(%s) from (%s)",
|
|
sender, buffer, cptr->name));
|
|
ircstp->is_unpf++;
|
|
remove_unknown(cptr, sender);
|
|
return -1;
|
|
}
|
|
if (from->from != cptr)
|
|
{
|
|
ircstp->is_wrdi++;
|
|
Debug((DEBUG_ERROR,
|
|
"Message (%s) coming from (%s)",
|
|
buffer, cptr->name));
|
|
return cancel_clients(cptr, from, ch);
|
|
}
|
|
}
|
|
while (*ch == ' ')
|
|
ch++;
|
|
}
|
|
|
|
if (*ch == '\0')
|
|
{
|
|
ircstp->is_empt++;
|
|
Debug((DEBUG_NOTICE, "Empty message from host %s:%s",
|
|
cptr->name, from->name));
|
|
if (!IsServer(cptr))
|
|
cptr->local->since++; /* 1s fake lag */
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
** Extract the command code from the packet. Point s to the end
|
|
** of the command code and calculate the length using pointer
|
|
** arithmetic. Note: only need length for numerics and *all*
|
|
** numerics must have paramters and thus a space after the command
|
|
** code. -avalon
|
|
*/
|
|
s = (char *)index(ch, ' '); /* s -> End of the command code */
|
|
len = (s) ? (s - ch) : 0;
|
|
if (len == 3 &&
|
|
isdigit(*ch) && isdigit(*(ch + 1)) && isdigit(*(ch + 2)))
|
|
{
|
|
cmptr = NULL;
|
|
numeric = (*ch - '0') * 100 + (*(ch + 1) - '0') * 10
|
|
+ (*(ch + 2) - '0');
|
|
paramcount = MAXPARA;
|
|
ircstp->is_num++;
|
|
}
|
|
else
|
|
{
|
|
int flags = 0;
|
|
int bytes = bufend - ch;
|
|
if (s)
|
|
*s++ = '\0';
|
|
if (!IsRegistered(from))
|
|
flags |= M_UNREGISTERED;
|
|
if (IsPerson(from))
|
|
flags |= M_USER;
|
|
if (IsServer(from))
|
|
flags |= M_SERVER;
|
|
if (IsShunned(from))
|
|
flags |= M_SHUN;
|
|
if (IsVirus(from))
|
|
flags |= M_VIRUS;
|
|
if (IsOper(from))
|
|
flags |= M_OPER;
|
|
cmptr = find_Command(ch, IsServer(cptr) ? 1 : 0, flags);
|
|
if (!cmptr)
|
|
{
|
|
/*
|
|
** Note: Give error message *only* to recognized
|
|
** persons. It's a nightmare situation to have
|
|
** two programs sending "Unknown command"'s or
|
|
** equivalent to each other at full blast....
|
|
** If it has got to person state, it at least
|
|
** seems to be well behaving. Perhaps this message
|
|
** should never be generated, though... --msa
|
|
** Hm, when is the buffer empty -- if a command
|
|
** code has been found ?? -Armin
|
|
** This error should indeed not be sent in case
|
|
** of notices -- Syzop.
|
|
*/
|
|
if (!IsRegistered(cptr) && stricmp(ch, "NOTICE")) {
|
|
sendto_one(from, ":%s %d %s :You have not registered",
|
|
me.name, ERR_NOTREGISTERED, ch);
|
|
parse_addlag(cptr, bytes);
|
|
return -1;
|
|
}
|
|
if (IsShunned(cptr))
|
|
return -1;
|
|
|
|
if (buffer[0] != '\0')
|
|
{
|
|
if (IsPerson(from))
|
|
sendto_one(from,
|
|
":%s %d %s %s :Unknown command",
|
|
me.name, ERR_UNKNOWNCOMMAND,
|
|
from->name, ch);
|
|
Debug((DEBUG_ERROR, "Unknown (%s) from %s",
|
|
ch, get_client_name(cptr, TRUE)));
|
|
parse_addlag(cptr, bytes);
|
|
}
|
|
ircstp->is_unco++;
|
|
return (-1);
|
|
}
|
|
if (cmptr->flags != 0) { /* temporary until all commands are updated */
|
|
|
|
/* Logic in comparisions below is a bit complicated, see notes */
|
|
|
|
/* If you're a user, and this command does not permit users or opers, deny */
|
|
if ((flags & M_USER) && !(cmptr->flags & M_USER) && !(cmptr->flags & M_OPER))
|
|
{
|
|
sendto_one(cptr, rpl_str(ERR_NOTFORUSERS), me.name,
|
|
from->name, cmptr->cmd);
|
|
parse_addlag(cptr, bytes);
|
|
return -1;
|
|
}
|
|
|
|
/* If you're a server, but command doesn't want servers, deny */
|
|
if ((flags & M_SERVER) && !(cmptr->flags & M_SERVER))
|
|
return -1;
|
|
}
|
|
|
|
/* If you're a user, but not an operator, and this requires operators, deny */
|
|
if ((cmptr->flags & M_OPER) && (flags & M_USER) && !(flags & M_OPER))
|
|
{
|
|
sendto_one(cptr, rpl_str(ERR_NOPRIVILEGES),
|
|
me.name, from->name);
|
|
parse_addlag(cptr, bytes);
|
|
return -1;
|
|
}
|
|
paramcount = cmptr->parameters;
|
|
cmptr->bytes += bytes;
|
|
if (!(cmptr->flags & M_NOLAG))
|
|
parse_addlag(cptr, 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;
|
|
if (cmptr == NULL)
|
|
return (do_numeric(numeric, cptr, from, i, para));
|
|
cmptr->count++;
|
|
if (IsRegisteredUser(cptr) && (cmptr->flags & M_RESETIDLE))
|
|
cptr->local->last = TStime();
|
|
|
|
#ifndef DEBUGMODE
|
|
if (cmptr->flags & M_ALIAS)
|
|
return (*cmptr->func) (cptr, from, i, para, cmptr->cmd);
|
|
else
|
|
{
|
|
if (!cmptr->overriders)
|
|
return (*cmptr->func) (cptr, from, i, para);
|
|
return (*cmptr->overridetail->func) (cmptr->overridetail, cptr, from, i, para);
|
|
}
|
|
#else
|
|
then = clock();
|
|
if (cmptr->flags & M_ALIAS)
|
|
retval = (*cmptr->func) (cptr, from, i, para, cmptr->cmd);
|
|
else
|
|
{
|
|
if (!cmptr->overriders)
|
|
retval = (*cmptr->func) (cptr, from, i, para);
|
|
else
|
|
retval = (*cmptr->overridetail->func) (cmptr->overridetail, cptr, from, i, para);
|
|
}
|
|
if (retval != FLUSH_BUFFER)
|
|
{
|
|
ticks = (clock() - then);
|
|
if (IsServer(cptr))
|
|
cmptr->rticks += ticks;
|
|
else
|
|
cmptr->lticks += ticks;
|
|
cptr->local->cputime += ticks;
|
|
}
|
|
|
|
return retval;
|
|
#endif
|
|
}
|
|
|
|
static int cancel_clients(aClient *cptr, aClient *sptr, char *cmd)
|
|
{
|
|
if (IsServer(cptr) || IsServer(sptr) || IsMe(sptr)) return 0;
|
|
return exit_client(cptr, cptr, &me, "Fake prefix");
|
|
}
|
|
|
|
static void remove_unknown(aClient *cptr, char *sender)
|
|
{
|
|
if (!IsRegistered(cptr) || IsClient(cptr))
|
|
return;
|
|
/*
|
|
* Not from a server so don't need to worry about it.
|
|
*/
|
|
if (!IsServer(cptr))
|
|
return;
|
|
|
|
#ifdef DEVELOP
|
|
sendto_ops("Killing %s (%s)", sender, backupbuf);
|
|
return;
|
|
#endif
|
|
/*
|
|
* 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) || index(sender, '.'))
|
|
sendto_one(cptr, ":%s SQUIT %s :(Unknown prefix (%s) from %s)",
|
|
me.name, sender, sender, cptr->name);
|
|
else
|
|
sendto_one(cptr, ":%s KILL %s :%s (%s(?) <- %s)",
|
|
me.name, sender, me.name, sender, cptr->name);
|
|
}
|