/************************************************************************ * 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 "unrealircd.h" char backupbuf[8192]; /* * 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 *); 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("immune:lag",cptr,NULL,NULL,NULL)) { cptr->local->since += (1 + cmdbytes/90); } } int parse2(aClient *cptr, aClient **fromptr, MessageTag *mtags, char *ch); /* * parse a buffer. * * NOTE: parse() cannot not be called recusively by any other functions! */ int parse(aClient *cptr, char *buffer, int length) { Hook *h; int buf_len = 0; aClient *from = cptr; char *ch; int i, ret; #ifdef DEBUGMODE time_t then, ticks; int retval; #endif aCommand *cmptr = NULL; MessageTag *mtags = NULL; for (h = Hooks[HOOKTYPE_PACKET]; h; h = h->next) { (*(h->func.intfunc))(from, &me, NULL, &buffer, &length); if(!buffer) return 0; } 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 stores the last executed command in 'backupbuf', useful for debugging crashes */ strlcpy(backupbuf, buffer, sizeof(backupbuf)); #if defined(DEBUGMODE) && defined(RAWCMDLOGGING) ircd_log(LOG_ERROR, "<- %s: %s", cptr->name, 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 == '@') { parse_message_tags(cptr, &ch, &mtags); /* Skip whitespace again */ for (; *ch == ' '; ch++) ; } ret = parse2(cptr, &from, mtags, ch); if (ret == FLUSH_BUFFER) RunHook3(HOOKTYPE_POST_COMMAND, NULL, mtags, ch); else RunHook3(HOOKTYPE_POST_COMMAND, from, mtags, ch); free_message_tags(mtags); return ret; } int parse2(aClient *cptr, aClient **fromptr, MessageTag *mtags, char *ch) { aClient *from = cptr; char *s; int len, i, numeric = 0, paramcount; #ifdef DEBUGMODE time_t then, ticks; int retval; #endif aCommand *cmptr = NULL; int bytes; *fromptr = cptr; /* The default, unless a source is specified (and permitted) */ s = sender; *s = '\0'; /* 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 == '@') { /* ** 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, NULL); if (!from && index(sender, '@')) from = hash_find_nickatserver(sender, NULL); /* 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) { ircstp->is_unpf++; remove_unknown(cptr, sender); return -1; } if (from->from != cptr) { ircstp->is_wrdi++; return cancel_clients(cptr, from, ch); } *fromptr = from; /* Update source client */ } while (*ch == ' ') ch++; } RunHook3(HOOKTYPE_PRE_COMMAND, from, mtags, 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); } /* Recalculate string length, now that we have skipped the sender */ bytes = strlen(ch); /* ** 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; 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")) { sendnumericfmt(from, ERR_NOTREGISTERED, "You have not registered"); parse_addlag(cptr, bytes); return -1; } if (IsShunned(cptr)) return -1; if (ch[0] != '\0') { if (IsPerson(from)) sendto_one(from, NULL, ":%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)) { sendnumeric(cptr, ERR_NOTFORUSERS, 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)) { sendnumeric(cptr, ERR_NOPRIVILEGES); 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, mtags, i, para)); cmptr->count++; if (IsRegisteredUser(cptr) && (cmptr->flags & M_RESETIDLE)) cptr->local->last = TStime(); #ifndef DEBUGMODE if (cmptr->flags & M_ALIAS) { return (*cmptr->aliasfunc) (cptr, from, mtags, i, para, cmptr->cmd); } else { if (!cmptr->overriders) return (*cmptr->func) (cptr, from, mtags, i, para); return (*cmptr->overridetail->func) (cmptr->overridetail, cptr, from, mtags, i, para); } #else then = clock(); if (cmptr->flags & M_ALIAS) { retval = (*cmptr->aliasfunc) (cptr, from, mtags, i, para, cmptr->cmd); } else { if (!cmptr->overriders) retval = (*cmptr->func) (cptr, from, mtags, i, para); else retval = (*cmptr->overridetail->func) (cmptr->overridetail, cptr, from, mtags, 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, NULL, "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, NULL, ":%s SQUIT %s :(Unknown prefix (%s) from %s)", me.name, sender, sender, cptr->name); else sendto_one(cptr, NULL, ":%s KILL %s :%s (%s(?) <- %s)", me.name, sender, me.name, sender, cptr->name); }