/* * IRC - Internet Relay Chat, src/channel.c * * Copyright (C) 2000-2003 TR-IRCD Development * Copyright (C) 1990-2002 Past and Present Ircd Coders * Copyright (C) 1990 Jarkko Oikarinen and * University of Oulu, Co 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 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. */ /* * $Id: channel.c,v 1.9 2004/02/24 15:00:30 tr-ircd Exp $ */ #include "struct.h" #include "common.h" #include "sys.h" #include "numeric.h" #include "channel.h" #include "h.h" #include "msg.h" #include "hook.h" #include "event.h" #include "blalloc.h" #include "chanmode.h" #include "usermode.h" #include "s_conf.h" aChannel *channel = NULL; static char buf[BUFSIZE]; static int hookid_sub_from_channel = 0; static int hookid_can_send = 0; static int hookid_can_join = 0; static int hookid_can_send_more = 0; BlockHeap *free_channels; BlockHeap *chan_members; /* INIT function for the hooks used in channelmode modules */ void init_channel(void *unused) { free_channels = BlockHeapCreate((size_t) sizeof(aChannel), CHANNELS_PREALLOCATE); chan_members = BlockHeapCreate((size_t) sizeof(struct ChanMember), CHANNELS_PREALLOCATE * 10); hookid_sub_from_channel = hook_add_event("sub1 from channel"); hookid_can_send = hook_add_event("can send"); hookid_can_join = hook_add_event("can join"); hookid_can_send_more = hook_add_event("can send more"); } /* * we also tell the client if the channel is invalid. */ int check_channelname(aClient *cptr, unsigned char *cn) { aMaskItem *ami; if (!MyClient(cptr)) return 1; /* * We bypass Jupiter Checks, if the client is an operator * * I personally accepted this idea, because, Ircops can also * * use quarantined nicknames, so they ought to be able to join * * jupitered channels */ if (!IsAnOper(cptr)) { ami = find_maskitem((char *) cn, NULL, MASKITEM_JUPITER, 1); if (ami) { send_me_numeric(cptr, ERR_CHANISJUPE, cn, ami->string, ami->reason); return 0; } else if ((ami = find_maskitem((char *) cn, NULL, MASKITEM_JUPITER_CONFIG, 1))) { send_me_numeric(cptr, ERR_CHANISJUPE, cn, ami->string, ami->reason); return 0; } else if (ServerOpts.use_regex) { ami = find_maskitem((char *) cn, NULL, MASKITEM_JUPITER_REGEX, 1); if (ami) { send_me_numeric(cptr, ERR_CHANISJUPE, cn, ami->string, ami->reason); return 0; } } } for (; *cn; cn++) { if (!IsChanChar(*cn)) return 0; } return 1; } /* * make_channel() free_channel() * functions to maintain blockheap of channels. */ aChannel *make_channel(void) { aChannel *chan; chan = (aChannel *) BlockHeapAlloc(free_channels); if (chan == NULL) outofmemory("Make channel"); memset((char *) chan, '\0', sizeof(aChannel)); return chan; } void free_channel(aChannel *chan) { BlockHeapFree(free_channels, chan); } /* * Look for ptr in the linked list pointed to by link. */ aClient *find_user_member(aChannel *chptr, aClient *acptr) { dlink_node *ptr; struct ChanMember *cm; if (acptr) { for (ptr = chptr->members.head; ptr; ptr = ptr->next) { cm = ptr->data; if (cm->client_p == acptr) return acptr; } } return NULL; } /* * * Get Channel block for chname (and allocate a new channel * * block, if it didn't exist before). */ aChannel *create_channel(aClient *cptr, char *chname, int *new, int recurse) { aChannel *chptr; int len; int anew; if (BadPtr(chname)) return NULL; len = strlen(chname); if (MyClient(cptr) && len > CHANNELLEN) { len = CHANNELLEN; *(chname + CHANNELLEN) = '\0'; } if ((chptr = find_channel(chname))) { anew = 0; *new = anew; if (recurse && IsChanLinked(chptr)) return create_channel(cptr, chptr->mode.link, new, 0); return chptr; } chptr = make_channel(); strlcpy_irc(chptr->chname, chname, len); if (channel) channel->prevch = chptr; chptr->prevch = NULL; chptr->nextch = channel; channel = chptr; chptr->tsval = timeofday; add_to_channel_hash_table(chname, chptr); chptr->mode.mode = 0; chptr->mode.limit = 0; chptr->mode.key[0] = '\0'; Count.chan++; anew = 1; *new = anew; return chptr; } /* * adds a user to a channel by adding another link to the channels * member chain. */ int add_user_to_channel(aChannel *chptr, aClient *who, int flags) { dlink_node *ptr; struct hook_data thisdata; struct ChanMember *cm; thisdata.client_p = who; thisdata.channel = chptr; thisdata.check = flags; if (who->user) { chptr->users++; ptr = make_dlink_node(); cm = (struct ChanMember *) BlockHeapAlloc(chan_members); if (cm == NULL) { outofmemory("new chanmember"); return 0; } memset(cm, 0, sizeof(struct ChanMember)); cm->client_p = who; cm->flags = flags; cm->bans = 0; dlinkAdd(cm, ptr, &chptr->members); ptr = make_dlink_node(); dlinkAdd(chptr, ptr, &who->user->channel); who->user->joined++; return 1; } return 0; } void remove_chanmember(aClient *acptr, aChannel *chptr) { dlink_node *ptr, *next_ptr; struct ChanMember *cm; for (ptr = chptr->members.head; ptr; ptr = next_ptr) { next_ptr = ptr->next; cm = ptr->data; if (!cm) dlinkDeleteNode(ptr, &chptr->members); else if (acptr == cm->client_p) { dlinkDelete(ptr, &chptr->members); BlockHeapFree(chan_members, cm); free_dlink_node(ptr); return; } } } /* * * Subtract one user from channel i (and free channel * block, if * channel became empty). */ static void sub1_from_channel(aChannel *chptr) { dlink_node *tmp; struct hook_data thisdata; if (--chptr->users <= 0) { tmp = chptr->invites.head; while (tmp && (tmp = tmp->next)) del_invite(tmp->data, chptr); thisdata.channel = chptr; hook_call_event(hookid_sub_from_channel, &thisdata); if (chptr->prevch) chptr->prevch->nextch = chptr->nextch; else channel = chptr->nextch; if (chptr->nextch) chptr->nextch->prevch = chptr->prevch; del_from_channel_hash_table(chptr->chname, chptr); free_channel(chptr); Count.chan--; } } void remove_user_from_channel(aClient *who, aChannel *chptr) { dlink_node *ptr; remove_chanmember(who, chptr); if (!who->user) return; if ((ptr = dlinkFind(&who->user->channel, chptr))) dlinkDeleteNode(ptr, &who->user->channel); who->user->joined--; sub1_from_channel(chptr); } int can_send(aClient *cptr, aChannel *chptr, char *msg) { int member; int h = 0; int flags = 0; aClient *acptr; struct hook_data thisdata; if (IsServer(cptr) || IsULine(cptr)) return 0; if (IsChanAnon(chptr)) return ERR_CANNOTSENDTOCHAN; if (IsAnOper(cptr)) return 0; if (!MyClient(cptr)) return 0; member = (acptr = find_user_member(chptr, cptr)) ? 1 : 0; if (member) flags = get_flags(cptr, chptr); thisdata.channel = chptr; thisdata.client_p = cptr; thisdata.data = msg; thisdata.check = member; if (!member) { h = hook_call_event(hookid_can_send, &thisdata); if (h) return h; if (is_nuhed(cptr, &chptr->banlist)) { if (is_nuhed(cptr, &chptr->banexlist)) return 0; else return ERR_BANNEDINCHAN; } if (is_nuhed(cptr, &chptr->stoplist)) return ERR_CANNOTSENDTOCHAN; } else if (!flags & CHFL_SPEAKABLE) { h = hook_call_event(hookid_can_send, &thisdata); if (h) return h; if (get_bans(acptr, chptr) || is_nuhed(cptr, &chptr->banlist)) { if (is_nuhed(cptr, &chptr->banexlist)) return 0; else return ERR_BANNEDINCHAN; } if (is_nuhed(cptr, &chptr->stoplist)) { if (is_nuhed(cptr, &chptr->banexlist)) return 0; else return ERR_HOSTMODERATED; } if (!IsChanHidePartQuit(chptr) && msg) { struct hook_data anotherdata; char *parv[3]; parv[0] = cptr->name; parv[1] = chptr->chname; parv[2] = msg; anotherdata.channel = chptr; anotherdata.parc = 3; anotherdata.parv = parv; anotherdata.source_p = cptr; if (hook_call_event(hookid_can_send_more, &anotherdata) != 0) return ERR_CANNOTSENDTOCHAN; } } return 0; } int can_join(aClient *sptr, aChannel *chptr, char *key) { dlink_node *lp; int invited = 0; aChannel *chptr2; aNUH *tmp; dlink_node *ptr; struct hook_data thisdata; if (!sptr->user) return -2; for (lp = sptr->user->invited.head; lp; lp = lp->next) { chptr2 = lp->data; if (chptr2 == chptr) { invited = 1; break; } } if (invited || IsULine(sptr) || IsAnOper(sptr) || is_nuhed(sptr, &chptr->invitelist)) return 0; if (is_nuhed(sptr, &chptr->banlist)) { if (is_nuhed(sptr, &chptr->banexlist)) return 0; else return ERR_BANNEDFROMCHAN; } chptr2 = NULL; for (ptr = chptr->chanbanlist.head; ptr; ptr = ptr->next) { tmp = ptr->data; if (!tmp) continue; chptr2 = find_channel(tmp->nuhstr); /* We do not do link traversal here, because when * can_join is called, the destination channel is * already found. -TimeMr14C */ if (chptr2) if (IsMember(sptr, chptr2)) return ERR_ISINBADCHAN; } thisdata.channel = chptr; thisdata.source_p = sptr; thisdata.data = key; return hook_call_event(hookid_can_join, &thisdata); } void send_topic_burst(aClient *cptr) { aChannel *chptr; aClient *acptr; for (chptr = channel; chptr; chptr = chptr->nextch) { if ((*chptr->chname == '#') && (chptr->topic[0] != '\0')) sendto_one_server(cptr, &me, TOK1_TOPIC, "%H %s %ld :%s", chptr, chptr->topic_nick, chptr->topic_time, chptr->topic); } for (acptr = GlobalClientList; acptr; acptr = acptr->next) { if (!IsPerson(acptr) || acptr->from == cptr) continue; if (acptr->user->away) sendto_one_server(cptr, acptr, TOK1_AWAY, ":%s", acptr->user->away); } } /* add the invite information to the users list of invited channels, * and also to the channels invited users list. * do the same removals by del_invite */ void add_invite(aClient *cptr, aChannel *chptr) { dlink_node *inv; del_invite(cptr, chptr); inv = make_dlink_node(); dlinkAdd(cptr, inv, &chptr->invites); inv = make_dlink_node(); dlinkAdd(chptr, inv, &cptr->user->invited); } void del_invite(aClient *cptr, aChannel *chptr) { dlink_node **inv, *tmp; for (inv = &(chptr->invites.head); (tmp = *inv); inv = &tmp->next) if (tmp->data == cptr) { dlinkDeleteNode(tmp, &chptr->invites); break; } for (inv = &(cptr->user->invited.head); (tmp = *inv); inv = &tmp->next) if (tmp->data == chptr) { dlinkDeleteNode(tmp, &cptr->user->invited); break; } } void send_names(aClient *sptr, aChannel *chptr) { struct ChanMember *cm; int mlen = strlen(me.name) + NICKLEN + 7; int idx, flag = 1, spos; int f = 0; int uflags = 0; char *s; aClient *acptr; dlink_node *ptr; int m = IsMember(sptr, chptr); int o = IsSeeHidden(sptr); int member = (o || IsService(sptr) || m); /* The omembr thing does the following: * it enables an operator to see ops/voices of a channel WHEN they are not * in that channel AND the channel is +x (hideops) * But, when they join the channel, the get the .@%+ things hidden in their * names reply. * * I have been told, that there were opers who /names a channel before they * join the channel. They should get informed who might be operator etc. * They also can see in /whois or /who if a person is an op in that channel. * * If they actually join a channel, they should be treated as normal users * who join a channel. Therefore, if the channel is hiding ops, opers * should also get the hidden names list. * * -TimeMr14C 13/02/2002 */ int omembr = (o && !m); if (PubChannel(chptr)) buf[0] = '='; else if (SecretChannel(chptr)) buf[0] = '@'; else buf[0] = '*'; idx = 1; buf[idx++] = ' '; for (s = chptr->chname; *s; s++) buf[idx++] = *s; buf[idx++] = ' '; buf[idx++] = ':'; /* * If we go through the following loop and never add anything, * we need this to be empty, otherwise spurious things from the * LAST /names call get stuck in there.. - lucas */ buf[idx] = '\0'; spos = idx; /* starting point in buffer for names! */ uflags = get_flags(sptr, chptr); for (ptr = chptr->members.head; ptr; ptr = ptr->next) { cm = ptr->data; if (!cm) continue; acptr = cm->client_p; f = cm->flags; if (IsAnon(acptr)) continue; if (IsInvisible(acptr) && !member) continue; if (IsChanAnon(chptr) && (sptr != acptr)) continue; if (!IsChanHideOps(chptr) || omembr || uflags) { if (f & CHFL_OWNER) buf[idx++] = '.'; if (f & CHFL_CHANOP) buf[idx++] = '@'; if (f & CHFL_HALFOP) buf[idx++] = '%'; if (f & CHFL_VOICE) buf[idx++] = '+'; } for (s = acptr->name; *s; s++) buf[idx++] = *s; buf[idx++] = ' '; buf[idx] = '\0'; flag = 1; if (mlen + idx + NICKLEN > BUFSIZE - 3) { send_me_numeric(sptr, RPL_NAMREPLY, buf); idx = spos; flag = 0; } } if (flag) send_me_numeric(sptr, RPL_NAMREPLY, buf); send_me_numeric(sptr, RPL_ENDOFNAMES, chptr->chname); } void privileged_join(aClient *sptr, aClient *cptr, char *name) { aChannel *chptr; int i = 0, flags = 0; int n = 0; if ((sptr->user->joined >= ChannelConf.max_channels_per_user) && (!IsAnOper(sptr) || (sptr->user->joined >= ChannelConf.max_channels_per_user * 3))) { send_me_numeric(sptr, ERR_TOOMANYCHANNELS, name); return; } chptr = create_channel(sptr, name, &n, 1); flags = n ? CHFL_CHANOP : 0; if (MyConnect(sptr) && (i = can_join(sptr, chptr, NULL))) { send_me_numeric(sptr, i, name); return; } if (IsMember(sptr, chptr)) return; add_user_to_channel(chptr, sptr, flags); if (MyClient(sptr) && (flags == CHFL_CHANOP)) { chptr->tsval = timeofday; sendto_serv_butone(NULL, &me, TOK1_SJOIN, "%T %H +%s :@%~C", chptr, chptr, ((ChannelConf.default_extended_topic_limitation) ? "Tn" : "tn"), sptr); } else { sendto_serv_butone(NULL, sptr, TOK1_SJOIN, "%T %H", chptr, chptr); } sendto_service(SERVICE_SEE_JOINS, 0, sptr, chptr, TOK1_JOIN, ""); sendto_channel_butserv_short(chptr, sptr, TOK1_JOIN); if (flags == CHFL_CHANOP) { char *cmodes_sm = NULL; if (ChannelConf.default_extended_topic_limitation) cmodes_sm = "+Tn"; else cmodes_sm = "+tn"; sendto_channel_butserv(chptr, sptr, TOK1_MODE, 0, "%s", cmodes_sm); chptr->mode.mode |= (ChannelConf.default_extended_topic_limitation ? MODE_EXTOPIC : MODE_TOPICLIMIT); chptr->mode.mode |= MODE_NOPRIVMSGS; } if (MyClient(sptr)) { del_invite(sptr, chptr); if (chptr->topic[0] != '\0') { send_me_numeric(sptr, RPL_TOPIC, name, chptr->topic); send_me_numeric(sptr, RPL_TOPICWHOTIME, name, IsChanHideOps(chptr) ? chptr-> chname : chptr->topic_nick, chptr->topic_time); } send_names(sptr, chptr); } } /* check_splitmode() * * input - * output - * side effects - compares usercount and servercount against their split * values and adjusts splitmode accordingly */ void check_splitmode(void *unused) { GeneralOpts.split = 0; eventDelete(check_splitmode, NULL); } /* * The function which sends the actual channel list back to the user. * Operates by stepping through the hashtable, sending the entries back if * they match the criteria. * cptr = Local client to send the output back to. * numsend = Number (roughly) of lines to send back. Once this number has * been exceeded, send_list will finish with the current hash bucket, * and record that number as the number to start next time send_list * is called for this user. So, this function will almost always send * back more lines than specified by numsend (though not by much, * assuming CH_MAX is was well picked). So be conservative in your choice * of numsend. -Rak */ /* * This function is now changed, there is no limiting via the numsend. * The flood case is prevented due to the new linebuf architecture. * We will push the whole list to the client. * -TimeMr14C */ void send_list(aClient *cptr, LOpts *lopt) { aChannel *chptr; dlink_node *lp, *next_lp; int hashnum; for (hashnum = lopt->starthash; hashnum < CH_MAX; hashnum++) { for (chptr = (aChannel *) hash_get_chan_bucket(hashnum); chptr; chptr = chptr->hnextch) { lopt->starthash = hashnum; if (SecretChannel(chptr) && !IsMember(cptr, chptr) && !IsSeeHidden(cptr)) continue; if ((!lopt->showall) && ((chptr->users < lopt->usermin) || ((lopt->usermax >= 0) && (chptr->users > lopt->usermax)) || ((chptr-> tsval || 1) < lopt->chantimemin) || (chptr->topic_time < lopt->topictimemin) || (chptr->tsval > lopt->chantimemax) || (chptr->topic_time > lopt->topictimemax) || (lopt->nolist.head && find_str_link(&lopt->nolist, chptr->chname)) || (lopt->yeslist.head && !find_str_link(&lopt->yeslist, chptr->chname)))) continue; if (will_exceed_sendq(cptr)) return; send_me_numeric(cptr, RPL_LIST, ShowChannel(cptr, chptr) ? chptr->chname : "*", chptr->users, ShowChannel(cptr, chptr) ? chptr->topic : ""); } } send_me_numeric(cptr, RPL_LISTEND); for (lp = lopt->yeslist.head; lp; lp = next_lp) { next_lp = lp->next; dlinkDeleteNode(lp, &lopt->yeslist); } for (lp = lopt->nolist.head; lp; lp = next_lp) { next_lp = lp->next; dlinkDeleteNode(lp, &lopt->nolist); } lopt->starthash = 0; return; }