/************************************************************************ * IRC - Internet Relay Chat, modules/m_nick.c * * Copyright (C) 2000-2003 TR-IRCD Development * * Copyright (C) 1990 Jarkko Oikarinen and * University of Oulu, Co Center * * See file AUTHORS in IRC package for additional names of * the programmers. * * This program is free softwmare; 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 2, 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. */ #include "struct.h" #include "common.h" #include "sys.h" #include "numeric.h" #include "msg.h" #include "channel.h" #include "chanmode.h" #include "s_conf.h" #include "throttle.h" #include "usermode.h" #include "h.h" static char *n_token = TOK1_NICK; static char *c_token = TOK1_CLIENT; static int collide_nicknames(char *nick, int new, int newts, char **parv, aClient *cptr, aClient *sptr); static struct Message n_msgtab[] = { {MSG_NICK, 0, MAXPARA, M_SLOW, 0L, u_nick, u_nick, u_nick, s_nick, m_ignore} }; static struct Message c_msgtab[] = { {MSG_CLIENT, 0, MAXPARA, M_SLOW, 0l, m_ignore, m_ignore, m_ignore, m_client, m_ignore} }; #ifndef STATIC_MODULES char *_version = "$Revision: 1.11 $"; void _modinit(void) { mod_add_cmd(n_msgtab); tok1_msgtab[(u_char) *n_token].msg = n_msgtab; mod_add_cmd(c_msgtab); tok1_msgtab[(u_char) *c_token].msg = c_msgtab; } void _moddeinit(void) { mod_del_cmd(n_msgtab); tok1_msgtab[(u_char) *n_token].msg = NULL; mod_del_cmd(c_msgtab); tok1_msgtab[(u_char) *c_token].msg = NULL; } #else void m_nick_init(void) { mod_add_cmd(n_msgtab); tok1_msgtab[(u_char) *n_token].msg = n_msgtab; mod_add_cmd(c_msgtab); tok1_msgtab[(u_char) *c_token].msg = c_msgtab; } #endif /* clean_nick_name() * * input - nickname * output - none * side effects - walks through the nickname, returning 0 if erroneous */ static int clean_nick_name(char *nick) { if (nick == NULL || *nick == '\0') return 0; /* nicks cant start with a digit or - */ if (*nick == '-' || IsDigit(*nick)) return 0; for (; *nick; nick++) { if (!IsNickChar(*nick)) return 0; } return 1; } /* * * m_nick * parv[0] = sender prefix * parv[1] = nickname * parv[2] = hopcount when new user; TS when nick change * parv[3] = TS * ---- new user only below ---- * parv[4] = umode * parv[5] = username * parv[6] = hostname * parv[7] = server * parv[8] = serviceid * parv[9] = ircname * ---- DT1 protocol change ---- * parv[4] = umode * parv[5] = username * parv[6] = hostname * parv[7] = fakehost * parv[8] = server * parv[9] = serviceid * parv[10] = IP * parv[11] = ircname * -- endif */ int s_nick(aClient *cptr, aClient *sptr, int parc, char *parv[]) { aClient *uplink; char nick[NICKLEN + 2]; long newts = 0; int samenick = 0; int test = 0; /* We do not blame servers by sending them numerics. Since s_nick can * only result from a server, we silently return when parc < 2. * -TimeMr14C */ if (parc < 3) return 0; /* * parc == 2 on a normal client sign on (local) and a normal client nick change * parc == 4 on a normal server-to-server client nick change * parc >= 10 on a normal TS style server-to-server NICK introduction */ if (parc > 3 && parc < 10) { /* * We got the wrong number of params. Someone is trying to trick * us. Kill it. -ThemBones As discussed with ThemBones, not much * point to this code now sending a whack of global kills would * also be more annoying then its worth, just note the problem, * and continue -Dianora */ sendto_lev(SNOTICE_LEV, "IGNORING BAD NICK: %s[%s@%s] on %s (from %s)", parv[1], (parc >= 6) ? parv[5] : "-", (parc >= 7) ? parv[6] : "-", (parc >= 8) ? parv[7] : "-", parv[0]); return 0; } /* Here we had a check after a dot in the hostname. This is removed * due to the fact that operators might have dotless hostnames due to * a DALnet like usage of oper hostmasking feature. * That check did not do anything but crying out loud, so it was of no * effect anyway. 29.04.2002 -TimeMr14C */ strlcpy_irc(nick, parv[1], NICKLEN); /* * if clean_nick_name() returns a null name OR if the server sent a * nick name and clean_nick_name() changed it in some way (due to rules * of nick creation) then reject it. If from a server and we reject * it, and KILL it. -avalon 4/4/92 */ if (clean_nick_name(nick) == 0 || strcmp(nick, parv[1])) { send_me_numeric(cptr, ERR_ERRONEUSNICKNAME, parv[1]); ircstp->is_kill++; sendto_lev(DEBUG_LEV, "Bad Nick: %s From: %s %s", parv[1], parv[0], get_client_name(cptr, FALSE)); sendto_one(cptr, ":%C %s %s :Bad Nick", &me, MSG_KILL, parv[1]); if (sptr != cptr) { sendto_serv_butone(cptr, &me, TOK1_KILL, "%~C :Incoherent Nickname", cptr); sptr->flags |= FLAGS_KILLED; return exit_client(sptr, &me, "Incoherent Nickname"); } return 0; } /* * * Check against nick name collisions. * * * We check against server name list before determining if the * nickname * is present in the nicklist (due to the way the below * for loop is * constructed). -avalon */ test = collide_nicknames(nick, 0, newts, parv, cptr, sptr); if (test != -1) return test; if (parc == 12) { aClient *acptr2; long flag; char *m; u_long newip = 0; uplink = find_server(parv[8]); if (!uplink) { sendto_lev(SNOTICE_LEV, "Remote nick %s on UNKNOWN server %s", nick, parv[8]); return 0; } acptr2 = make_client(cptr); acptr2->servptr = uplink; strcpy(acptr2->name, nick); add_client_to_list(acptr2); add_to_client_hash_table(nick, acptr2); if (IsULine(uplink)) acptr2->protoflags |= PFLAGS_ULINE; acptr2->hopcount = atoi(parv[2]); newts = atol(parv[3]); if (!newts) newts = timeofday; acptr2->tsval = newts; m = &parv[4][1]; while (*m) { if (umodetab[(int) *m].in_use) { flag = umodetab[(int) *m].type; if ((flag == UMODE_o)) Count.oper++; if ((flag & UMODE_i)) Count.invisi++; acptr2->umode |= flag & SEND_UMODES; } m++; } newip = strtoul(parv[10], NULL, 0); IN4_ADDR(acptr2->ip) = newip; /* adding the remote client to the throttle hash. * checking local clients takes place in s_bsd.c * -epi */ if (newip != 0) throttle_check(inetntoa((char *) &acptr2->ip), -1, acptr2->tsval); return do_remote_user(nick, cptr, acptr2, parv[5], parv[6], parv[7], parv[8], strtoul(parv[9], NULL, 0), parv[11]); } else if (sptr->name[0]) { /* * * Client just changing his/her nick. If he/she is * on a * channel, send note of change to all clients * on that channel. * Propagate notice to other servers. */ newts = atol(parv[2]); /* * if the nickname is different, set the TS * AND set it -r. No need to propogate MODE -r and spam * the network on registered nick changes. yuck. - lucas */ if (irc_strcmp(parv[0], nick)) { sptr->tsval = newts ? newts : (long) timeofday; sptr->umode &= ~UMODE_r; } sendto_common_channels(sptr, ":%C %s :%s", sptr, MSG_NICK, nick); if (sptr->user) { add_history(sptr, 1); sendto_serv_butone(cptr, sptr, TOK1_NICK, "%s :%T", nick, sptr); sendto_service(SERVICE_SEE_NICKS, 0, sptr, NULL, TOK1_NICK, "%s :%T", nick, sptr); } del_from_client_hash_table(sptr->name, sptr); samenick = irc_strcmp(sptr->name, nick) ? 0 : 1; if (!samenick) hash_check_watch(sptr, RPL_LOGOFF); strcpy(sptr->name, nick); add_to_client_hash_table(nick, sptr); if (!samenick) hash_check_watch(sptr, RPL_LOGON); return 0; } return 0; } /* This function is a new version of the SERVER NICK line, * it is only exchanged between nativemode servers, and does not * have too many case differentiations. */ /* * * m_nick * parv[0] = sender prefix * parv[1] = nickname * parv[2] = hopcount when new user; TS when nick change * parv[3] = TS * parv[4] = umode * parv[5] = username * parv[6] = hostname * parv[7] = fakehost * parv[8] = language * parv[9] = server OR id/server-pair * parv[10] = serviceid * parv[11] = ircname * -- endif */ int m_client(aClient *cptr, aClient *sptr, int parc, char *parv[]) { aClient *uplink; char nick[NICKLEN + 2]; long newts = 0; int test = 0; if (parc < 2) { send_me_numeric(sptr, ERR_NONICKNAMEGIVEN); return 0; } newts = atol(parv[3]); strlcpy_irc(nick, parv[1], NICKLEN); /* * if clean_nick_name() returns a null name OR if the server sent a * nick name and clean_nick_name() changed it in some way (due to rules * of nick creation) then reject it. If from a server and we reject * it, and KILL it. -avalon 4/4/92 */ if (clean_nick_name(nick) == 0 || strcmp(nick, parv[1])) { send_me_numeric(cptr, ERR_ERRONEUSNICKNAME, parv[1]); ircstp->is_kill++; sendto_lev(DEBUG_LEV, "Bad Nick: %s From: %s %s", parv[1], parv[0], get_client_name(cptr, FALSE)); sendto_one(cptr, ":%C %s %s :Bad Nick", &me, MSG_KILL, parv[1]); if (sptr != cptr) { sendto_serv_butone(cptr, &me, TOK1_KILL, "%~C :Bad Nick", cptr); sptr->flags |= FLAGS_KILLED; return exit_client(sptr, &me, "BadNick"); } return 0; } /* * * Check against nick name collisions. * * * We check against server name list before determining if the * nickname * is present in the nicklist (due to the way the below * for loop is * constructed). -avalon */ test = collide_nicknames(nick, 0, newts, parv, cptr, sptr); if (test != -1) return test; if (parc == 12) { aClient *acptr2; long flag = 0; int pflags = 0; char *m; u_long newid, newip = 0; char *base64id = NULL; char *ipp; char *newhostip = NULL; aClient *bucptr; base64id = &parv[9][1]; ipp = strchr(base64id, '%'); if (ipp) { *ipp++ = '\0'; newhostip = ipp; pflags |= PFLAGS_IPV6HOST; } else { ipp = strchr(base64id, ':'); if (ipp) { *ipp++ = '\0'; newip = base64dec(ipp); } } if ((bucptr = find_by_base64_id(base64id))) { sendto_ops("IDENTITY COLLISION! (%s[%s][%s] <> %s[%s][%s])", sptr->name, sptr->servptr->name, base64id, bucptr->name, bucptr->servptr->name, bucptr->id.string); return exit_client(bucptr, &me, "Identity collision!"); } uplink = find_server_by_base64_id(base64id, &newid); if (uplink) { parv[9] = uplink->name; } else { sendto_lev(SNOTICE_LEV, "Remote nick %s on UNKNOWN server %s", nick, parv[9]); return 0; } acptr2 = make_client(cptr); acptr2->servptr = uplink; strcpy(acptr2->name, nick); add_client_to_list(acptr2); add_to_client_hash_table(nick, acptr2); SetHasID(acptr2); /* this client is identity capable */ acptr2->id.id = newid; /* new identity! */ strlcpy_irc(acptr2->id.string, base64id, 8); add_userid_to_server(uplink, acptr2); if (IsULine(uplink)) pflags |= PFLAGS_ULINE; acptr2->hopcount = atoi(parv[2]); if (!newts) newts = timeofday; acptr2->tsval = newts; m = &parv[4][1]; while (*m) { if (umodetab[(int) *m].in_use) { flag = umodetab[(int) *m].type; if ((flag == UMODE_o)) Count.oper++; if ((flag & UMODE_i)) Count.invisi++; acptr2->umode |= flag & SEND_UMODES; } m++; } if (newip) { IN4_ADDR(acptr2->ip) = newip; throttle_check(inetntoa((char *) &acptr2->ip), -1, acptr2->tsval); } if (newhostip) { strlcpy_irc(acptr2->hostip, newhostip, HOSTIPLEN); throttle_check(newhostip, -1, acptr2->tsval); } acptr2->lang = atoi(parv[8]); acptr2->protoflags |= pflags; return do_remote_user(nick, cptr, acptr2, parv[5], parv[6], parv[7], parv[9], base64dec(parv[10]), parv[11]); } return 0; } /* guaranteed sptr == cptr, !IsServer(cptr), !IsServer(sptr) */ int u_nick(aClient *cptr, aClient *sptr, int parc, char *parv[]) { aMaskItem *ami; aChannel *chptr; dlink_node *lp; char nick[NICKLEN + 2]; long newts = 0; int samenick = 0; int test = 0; if (parc < 2) { send_me_numeric(sptr, ERR_NONICKNAMEGIVEN); return 0; } strlcpy_irc(nick, parv[1], NICKLEN); /* * if clean_nick_name() returns a null name OR if the server sent a * nick name and clean_nick_name() changed it in some way (due to rules * of nick creation) then reject it. If from a server and we reject * it, and KILL it. -avalon 4/4/92 */ if (clean_nick_name(nick) == 0) { send_me_numeric(cptr, ERR_ERRONEUSNICKNAME, parv[1]); return 0; } /* * * Check against nick name collisions. * * * Put this 'if' here so that the nesting goes nicely on the screen * :) * We check against server name list before determining if the * nickname * is present in the nicklist (due to the way the below * for loop is * constructed). -avalon */ test = collide_nicknames(nick, 1, newts, parv, cptr, sptr); if (test != -1) return test; if (sptr->name[0]) { /* * * Client just changing his/her nick. If he/she is * on a * channel, send note of change to all clients * on that channel. * Propagate notice to other servers. */ if ((ami = find_maskitem(nick, NULL, MASKITEM_QUARANTINE, 1)) && !IsAnOper(sptr)) { send_me_numeric(sptr, ERR_ERRONEUSNICKNAME, nick, ami->reason); send_me_numeric(sptr, ERR_EVALUATINGQLINE, ami->string, ami->reason); sendto_lev(QLINE_LEV, "Forbidding Quarantined nick %s from %s. Qline [%s] evaluates into %s", nick, get_client_name(cptr, FALSE), ami->string, nick); return 0; } else if ((ami = find_maskitem(nick, NULL, MASKITEM_QUARANTINE_CONFIG, 1)) && !IsAnOper(sptr)) { send_me_numeric(sptr, ERR_ERRONEUSNICKNAME, nick, ami->reason); send_me_numeric(sptr, ERR_EVALUATINGQLINE, ami->string, ami->reason); sendto_lev(QLINE_LEV, "Forbidding Quarantined nick %s from %s. Qline [%s] evaluates into %s", nick, get_client_name(cptr, FALSE), ami->string, nick); return 0; } else if (ServerOpts.use_regex && !IsAnOper(sptr) && (ami = find_maskitem(nick, NULL, MASKITEM_QUARANTINE_REGEX, 1))) { send_me_numeric(sptr, ERR_ERRONEUSNICKNAME, nick, ami->reason); send_me_numeric(sptr, ERR_EVALUATINGQLINE, ami->string, ami->reason); sendto_lev(QLINE_LEV, "Forbidding Quarantined nick %s from %s. Qline [%s] evaluates into %s", nick, get_client_name(cptr, FALSE), ami->string, nick); return 0; } if (IsPerson(sptr)) { for (lp = sptr->user->channel.head; lp; lp = lp->next) { chptr = lp->data; if (nick_is_nuhed(chptr, nick, sptr, &chptr->banlist) != NULL) { send_me_numeric(sptr, ERR_BANONCHAN, nick, chptr->chname); return 0; } } if (ServerOpts.anti_nick_flood) { if ((sptr->last_nick_time + ServerOpts.max_nick_time) < NOW) sptr->count_nick = 0; sptr->last_nick_time = NOW; sptr->count_nick++; } if (sptr->count_nick <= ServerOpts.max_nick_changes || IsAnOper(sptr)) { /* * if the nickname is different, set the TS * AND set it -r. No need to propogate MODE -r and spam * the network on registered nick changes. yuck. - lucas */ if (irc_strcmp(parv[0], nick)) { sptr->tsval = timeofday; sptr->umode &= ~UMODE_r; } sendto_common_channels(sptr, ":%C %s :%s", sptr, MSG_NICK, nick); if (sptr->user) { add_history(sptr, 1); sendto_serv_butone(cptr, sptr, TOK1_NICK, "%s :%T", nick, sptr); sendto_service(SERVICE_SEE_NICKS, 0, sptr, NULL, TOK1_NICK, "%s :%T", nick, sptr); } } else { send_me_notice(sptr, ":*** Notice -- Too many nick changes. Wait %d seconds" "before trying again", ServerOpts.max_nick_time); return 0; } } del_from_client_hash_table(sptr->name, sptr); samenick = irc_strcmp(sptr->name, nick) ? 0 : 1; if (IsPerson(sptr) && !samenick) hash_check_watch(sptr, RPL_LOGOFF); strcpy(sptr->name, nick); add_to_client_hash_table(nick, sptr); if (IsPerson(sptr) && !samenick) hash_check_watch(sptr, RPL_LOGON); fd_note(sptr->fd, "Nick %s", nick); } else { /* Client setting NICK the first time */ if ((ami = find_maskitem(nick, NULL, MASKITEM_QUARANTINE, 1))) { send_me_numeric(sptr, ERR_ERRONEUSNICKNAME, nick, ami->reason); send_me_numeric(sptr, ERR_EVALUATINGQLINE, ami->string, ami->reason); sendto_lev(QLINE_LEV, "Forbidding Quarantined nick %s from %s. Qline [%s] evaluates into %s", nick, get_client_name(cptr, FALSE), ami->string, nick); return 0; } else if ((ami = find_maskitem(nick, NULL, MASKITEM_QUARANTINE_CONFIG, 1))) { send_me_numeric(sptr, ERR_ERRONEUSNICKNAME, nick, ami->reason); send_me_numeric(sptr, ERR_EVALUATINGQLINE, ami->string, ami->reason); sendto_lev(QLINE_LEV, "Forbidding Quarantined nick %s from %s. Qline [%s] evaluates into %s", nick, get_client_name(cptr, FALSE), ami->string, nick); return 0; } else if (ServerOpts.use_regex && (ami = find_maskitem(nick, NULL, MASKITEM_QUARANTINE_REGEX, 1))) { send_me_numeric(sptr, ERR_ERRONEUSNICKNAME, nick, ami->reason); send_me_numeric(sptr, ERR_EVALUATINGQLINE, ami->string, ami->reason); sendto_lev(QLINE_LEV, "Forbidding Quarantined nick %s from %s. Qline [%s] evaluates into %s", nick, get_client_name(cptr, FALSE), ami->string, nick); return 0; } strcpy(sptr->name, nick); sptr->tsval = timeofday; add_to_client_hash_table(nick, sptr); fd_note(sptr->fd, "Nick %s", nick); if (sptr->user) { /* USER already received, now we have NICK. */ return register_local_user(cptr, sptr, nick, sptr->user->username); } } return 0; } static int collide_nicknames(char *nick, int new, int newts, char **parv, aClient *cptr, aClient *sptr) { aClient *acptr; int sameuser = 0; if (new) { /* nothing else is using this nick, great! */ if (!(acptr = find_client(nick))) return -1; /* * * If acptr == sptr, then we have a client doing a nick * change * between *equivalent* nicknames as far as server * is concerned * (user is changing the case of his/her * nickname or somesuch) */ if (acptr == sptr) { if (strcmp(acptr->name, nick) != 0) return -1; else return 0; } if (IsUnknown(acptr)) { exit_client(acptr, &me, "Overridden by older signon"); return -1; } /* * * NICK is coming from local client connection. Just * send * error reply and ignore the command. * parv[0] is empty on connecting clients */ send_me_numeric(cptr, ERR_NICKNAMEINUSE, nick); return 0; } if (!(acptr = find_client(nick))) return -1; /* * * If acptr == sptr, then we have a client doing a nick * change * between *equivalent* nicknames as far as server * is concerned * (user is changing the case of his/her * nickname or somesuch) */ if (acptr == sptr) { if (strcmp(acptr->name, nick) != 0) return -1; else return 0; } /* * * If the older one is "non-person", the new entry is just * * allowed to overwrite it. Just silently drop non-person, * and * proceed with the nick. This should take care of the * "dormant * nick" way of generating collisions... */ if (IsUnknown(acptr)) { exit_client(acptr, &me, "Overridden by older signon"); return -1; } if (!newts || !acptr->tsval || (newts == acptr->tsval)) { sendto_lev(SKILL_LEV, "Nick collision on %s", acptr->name); ircstp->is_kill++; send_me_numeric(acptr, ERR_NICKCOLLISION, acptr->name); sendto_serv_butone(NULL, &me, TOK1_KILL, "%~C :Nick Collision", acptr); acptr->flags |= FLAGS_KILLED; exit_client(acptr, &me, "Nick collision"); return 0; } else { sameuser = (acptr->user) && irc_strcmp(acptr->user->username, parv[5]) == 0 && irc_strcmp(acptr->user->host, parv[6]) == 0; if ((sameuser && newts < acptr->tsval) || (!sameuser && newts > acptr->tsval)) return 0; else { if (sameuser) sendto_lev(SKILL_LEV, "Nick collision on %s", acptr->name); else sendto_lev(SKILL_LEV, "Nick collision on %s", acptr->name); ircstp->is_kill++; send_me_numeric(acptr, ERR_NICKCOLLISION, acptr->name); sendto_serv_butone(sptr, &me, TOK1_KILL, "%~C :Nick Collision", acptr); acptr->flags |= FLAGS_KILLED; exit_client(acptr, &me, "Nick collision"); return -1; } } /* * * A NICK change has collided (e.g. message type * ":old NICK * new". This requires more complex cleanout. * Both clients must be * purged from this server, the "new" * must be killed from the * incoming connection, and "old" must * be purged from all outgoing * connections. */ if (!newts || !acptr->tsval || (newts == acptr->tsval) || !sptr->user) { sendto_lev(SKILL_LEV, "Nick change collision from %s to %s", sptr->name, acptr->name); ircstp->is_kill++; send_me_numeric(acptr, ERR_NICKCOLLISION, acptr->name); sendto_serv_butone(NULL, &me, TOK1_KILL, "%~C :Nick collision", sptr); ircstp->is_kill++; sendto_serv_butone(NULL, &me, TOK1_KILL, "%~C :Nick collision", acptr); acptr->flags |= FLAGS_KILLED; exit_client(acptr, &me, "Nick collision(new)"); sptr->flags |= FLAGS_KILLED; exit_client(sptr, &me, "Nick collision(old)"); return 0; } else { sameuser = irc_strcmp(acptr->user->username, sptr->user->username) == 0 && irc_strcmp(acptr->user->host, sptr->user->host) == 0; if ((sameuser && newts < acptr->tsval) || (!sameuser && newts > acptr->tsval)) { if (sameuser) sendto_lev(SKILL_LEV, "Nick change collision from %s to %s(older killed)", sptr->name, acptr->name); ircstp->is_kill++; sendto_serv_butone(cptr, &me, TOK1_KILL, "%~C :Nick Collision", sptr); sptr->flags |= FLAGS_KILLED; if (sameuser) exit_client(sptr, &me, "Nick collision(old)"); else exit_client(sptr, &me, "Nick collision(new)"); return 0; } else { sendto_lev(SKILL_LEV, "Nick collision on %s", acptr->name); ircstp->is_kill++; send_me_numeric(acptr, ERR_NICKCOLLISION, acptr->name); sendto_serv_butone(sptr, &me, TOK1_KILL, "%~C :Nick Collision", acptr); acptr->flags |= FLAGS_KILLED; exit_client(acptr, &me, "Nick collision"); return 0; } } }