/*
* NeoIRCd: NeoStats Group. Based on Hybird7
* channel.c: Controls channels.
*
* Copyright (C) 2002 by the past and present ircd coders, and others.
*
* 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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* $Id: channel.c,v 1.17 2003/03/06 14:01:49 fishwaldo Exp $
*/
#include "stdinc.h"
#include "tools.h"
#include "channel.h"
#include "channel_mode.h"
#include "client.h"
#include "common.h"
#include "hash.h"
#include "irc_string.h"
#include "ircd.h"
#include "list.h"
#include "numeric.h"
#include "s_serv.h" /* captab */
#include "s_user.h"
#include "send.h"
#include "whowas.h"
#include "s_conf.h" /* ConfigFileEntry, ConfigChannel */
#include "event.h"
#include "memory.h"
#include "balloc.h"
#include "resv.h"
#include "s_log.h"
struct config_channel_entry ConfigChannel;
struct Channel *GlobalChannelList = NULL;
BlockHeap *channel_heap;
BlockHeap *ban_heap;
BlockHeap *topic_heap;
static void free_channel_list(dlink_list * list);
static int sub1_from_channel(struct Channel *);
static void destroy_channel(struct Channel *);
static void delete_members(struct Channel *chptr, dlink_list * list);
static void send_mode_list(struct Client *client_p, char *chname,
dlink_list *top, char flag, int clear);
static int check_banned(struct Channel *chptr, struct Client *who,
char *s, char *s2, char *s3);
static char buf[BUFSIZE];
static char modebuf[MODEBUFLEN], parabuf[MODEBUFLEN];
/*
* init_channels
*
* Initializes the channel blockheap
*/
static void channelheap_garbage_collect(void *unused)
{
BlockHeapGarbageCollect(channel_heap);
BlockHeapGarbageCollect(ban_heap);
BlockHeapGarbageCollect(topic_heap);
}
void init_channels(void)
{
channel_heap = BlockHeapCreate(sizeof(struct Channel), CHANNEL_HEAP_SIZE);
ban_heap = BlockHeapCreate(sizeof(struct Ban), BAN_HEAP_SIZE);
topic_heap = BlockHeapCreate(TOPICLEN+1 + USERHOST_REPLYLEN, TOPIC_HEAP_SIZE);
eventAddIsh("channelheap_garbage_collect", channelheap_garbage_collect,
NULL, 45);
}
/*
* add_user_to_channel
*
* inputs - pointer to channel to add client to
* - pointer to client (who) to add
* - flags for chanops etc
* output - none
* side effects - adds a user to a channel by adding another link to the
* channels member chain.
*/
void
add_user_to_channel(struct Channel *chptr, struct Client *who, int flags)
{
dlink_node *ptr;
dlink_node *lptr = NULL;
if (who->user)
{
ptr = make_dlink_node();
if (MyClient(who))
lptr = make_dlink_node();
switch (flags)
{
default:
case MODE_PEON:
dlinkAdd(who, ptr, &chptr->peons);
if (MyClient(who))
dlinkAdd(who, lptr, &chptr->locpeons);
break;
case MODE_CHANOP:
dlinkAdd(who, ptr, &chptr->chanops);
if (MyClient(who))
dlinkAdd(who, lptr, &chptr->locchanops);
break;
case MODE_HALFOP:
dlinkAdd(who, ptr, &chptr->halfops);
if (MyClient(who))
dlinkAdd(who, lptr, &chptr->lochalfops);
break;
case MODE_VOICE:
dlinkAdd(who, ptr, &chptr->voiced);
if (MyClient(who))
dlinkAdd(who, lptr, &chptr->locvoiced);
break;
case MODE_CHANOP|MODE_VOICE:
dlinkAdd(who, ptr, &chptr->chanops);
if (MyClient(who))
dlinkAdd(who, lptr, &chptr->locchanops);
break;
case MODE_ADMIN:
dlinkAdd(who, ptr, &chptr->chanadmins);
if (MyClient(who))
dlinkAdd(who, lptr, &chptr->locchanadmins);
break;
}
if(flags & MODE_DEOPPED)
{
dlink_node *dptr;
dptr = make_dlink_node();
dlinkAdd(who, dptr, &chptr->deopped);
}
chptr->users++;
if (MyClient(who))
chptr->locusers++;
ptr = make_dlink_node();
dlinkAdd(chptr, ptr, &who->user->channel);
who->user->joined++;
}
}
/*
* remove_user_from_channel
*
* inputs - pointer to channel to remove client from
* - pointer to client (who) to remove
* output - did the channel get destroyed
* side effects - deletes an user from a channel by removing a link in the
* channels member chain.
* sets a vchan_id if the last user is just leaving
*/
int
remove_user_from_channel(struct Channel *chptr, struct Client *who)
{
dlink_node *ptr;
dlink_node *next_ptr;
/* last user in the channel.. set a vchan_id incase we need it */
if ((ptr = find_user_link(&chptr->peons, who)))
dlinkDelete(ptr, &chptr->peons);
else if ((ptr = find_user_link(&chptr->chanops, who)))
dlinkDelete(ptr, &chptr->chanops);
else if ((ptr = find_user_link(&chptr->voiced, who)))
dlinkDelete(ptr, &chptr->voiced);
else if ((ptr = find_user_link(&chptr->halfops, who)))
dlinkDelete(ptr, &chptr->halfops);
else if ((ptr = find_user_link(&chptr->chanadmins, who)))
dlinkDelete(ptr, &chptr->chanadmins);
else {
assert(0 == 1); /* This ain't supposed to happen */
}
free_dlink_node(ptr);
if((ptr = find_user_link(&chptr->deopped, who)))
{
dlinkDelete(ptr, &chptr->deopped);
free_dlink_node(ptr);
}
if (MyClient(who))
{
if ((ptr = find_user_link(&chptr->locpeons, who)))
dlinkDelete(ptr, &chptr->locpeons);
else if ((ptr = find_user_link(&chptr->locchanops, who)))
dlinkDelete(ptr, &chptr->locchanops);
else if ((ptr = find_user_link(&chptr->locvoiced, who)))
dlinkDelete(ptr, &chptr->locvoiced);
else if ((ptr = find_user_link(&chptr->lochalfops, who)))
dlinkDelete(ptr, &chptr->lochalfops);
else if ((ptr = find_user_link(&chptr->locchanadmins, who)))
dlinkDelete(ptr, &chptr->locchanadmins);
else
assert(1 == 0);
free_dlink_node(ptr);
}
chptr->users_last = CurrentTime;
if (who->user != NULL)
{
DLINK_FOREACH_SAFE(ptr, next_ptr, who->user->channel.head)
{
if (ptr->data == chptr)
{
dlinkDelete(ptr, &who->user->channel);
free_dlink_node(ptr);
who->user->joined--;
break;
}
}
}
if (MyClient(who))
{
if (chptr->locusers > 0)
chptr->locusers--;
}
return(sub1_from_channel(chptr));
}
/*
* inputs -
* output - NONE
* side effects -
*/
static void
send_members(struct Client *client_p, char *lmodebuf, char *lparabuf,
struct Channel *chptr, dlink_list * list, char *op_flag)
{
dlink_node *ptr;
dlink_node *next_ptr;
int tlen; /* length of t (temp pointer) */
int mlen; /* minimum length */
int cur_len = 0; /* current length */
struct Client *target_p;
int data_to_send = 0;
char *t; /* temp char pointer */
cur_len = mlen = ircsprintf(buf, ":%s SJOIN %lu %s %s %s:", me.name,
(unsigned long)chptr->channelts,
chptr->chname, lmodebuf, lparabuf);
t = buf + mlen;
DLINK_FOREACH_SAFE(ptr, next_ptr, list->head)
{
target_p = ptr->data;
ircsprintf(t, "%s%s ", op_flag, target_p->name);
tlen = strlen(t);
cur_len += tlen;
t += tlen;
data_to_send = 1;
if (cur_len > (BUFSIZE - 80))
{
data_to_send = 0;
sendto_one(client_p, "%s", buf);
cur_len = mlen;
t = buf + mlen;
}
}
if (data_to_send)
{
sendto_one(client_p, "%s", buf);
}
}
/*
* send_channel_modes
*
* inputs - pointer to client client_p
* - pointer to channel pointer
* output - NONE
* side effects - send "client_p" a full list of the modes for channel chptr.
*/
void
send_channel_modes(struct Client *client_p, struct Channel *chptr)
{
if (*chptr->chname != '#')
return;
*modebuf = *parabuf = '\0';
channel_modes(chptr, client_p, modebuf, parabuf);
send_members(client_p, modebuf, parabuf, chptr, &chptr->chanops, "@");
send_members(client_p, modebuf, parabuf, chptr, &chptr->halfops, "%");
send_members(client_p, modebuf, parabuf, chptr, &chptr->chanadmins, "!");
send_members(client_p, modebuf, parabuf, chptr, &chptr->voiced, "+");
send_members(client_p, modebuf, parabuf, chptr, &chptr->peons, "");
send_mode_list(client_p, chptr->chname, &chptr->banlist, 'b', 0);
send_mode_list(client_p, chptr->chname, &chptr->exceptlist, 'e', 0);
send_mode_list(client_p, chptr->chname, &chptr->invexlist, 'I', 0);
}
/*
* send_mode_list
* inputs - client pointer to server
* - pointer to channel
* - pointer to top of mode link list to send
* - char flag flagging type of mode i.e. 'b' 'e' etc.
* - clear (remove all current modes, for ophiding, etc)
* output - NONE
* side effects - sends +b/+e/+I
*
*/
static void
send_mode_list(struct Client *client_p,
char *chname, dlink_list * top, char flag, int clear)
{
dlink_node *lp;
struct Ban *banptr;
char mbuf[MODEBUFLEN];
char pbuf[MODEBUFLEN];
int tlen;
int mlen;
int cur_len;
char *mp;
char *pp;
int count;
ircsprintf(buf, ":%s MODE %s ", me.name, chname);
cur_len = mlen = (strlen(buf) + 2);
count = 0;
mp = mbuf;
*mp++ = (clear ? '-' : '+');
*mp = '\0';
pp = pbuf;
DLINK_FOREACH(lp, top->head)
{
banptr = lp->data;
tlen = strlen(banptr->banstr);
tlen++;
if ((count >= MAXMODEPARAMS) || ((cur_len + tlen + 2) > MODEBUFLEN))
{
sendto_one(client_p, "%s%s %s", buf, mbuf, pbuf);
mp = mbuf;
*mp++ = (clear ? '-' : '+');
*mp = '\0';
pp = pbuf;
cur_len = mlen;
count = 0;
}
*mp++ = flag;
*mp = '\0';
ircsprintf(pp, "%s ", banptr->banstr);
pp += tlen;
cur_len += tlen;
count++;
}
if (count != 0)
sendto_one(client_p, "%s%s %s", buf, mbuf, pbuf);
}
/*
* check_channel_name
* inputs - channel name
* output - true (1) if name ok, false (0) otherwise
* side effects - check_channel_name - check channel name for
* invalid characters
*/
int
check_channel_name(const char *name)
{
assert(name != NULL);
if(name == NULL)
return 0;
for (; *name; ++name)
{
if (!IsChanChar(*name))
return 0;
}
return 1;
}
/*
* sub1_from_channel
*
* inputs - pointer to channel to remove client from
* output - did the channel get destroyed
* side effects - remove one user from chptr. if the
* channel is now empty, and it is not already
* scheduled for destruction, schedule it
*/
static int
sub1_from_channel(struct Channel *chptr)
{
if (--chptr->users <= 0)
{
#ifdef INVARIANTS
assert(chptr->users == 0);
#endif
chptr->users = 0; /* if chptr->users < 0, make sure it sticks at 0
* It should never happen but...
*/
/* persistent channel */
/* NeoIRCd caches all channels */
// if ((chptr->channelts + ConfigChannel.persist_time) < CurrentTime)
// {
// destroy_channel(chptr);
// return (1);
// }
}
return (0);
}
/*
* free_channel_list
*
* inputs - pointer to dlink_list
* output - NONE
* side effects -
*/
static void
free_channel_list(dlink_list * list)
{
dlink_node *ptr;
dlink_node *next_ptr;
struct Ban *actualBan;
DLINK_FOREACH_SAFE(ptr, next_ptr, list->head)
{
actualBan = ptr->data;
MyFree(actualBan->banstr);
MyFree(actualBan->who);
BlockHeapFree(ban_heap, actualBan);
free_dlink_node(ptr);
}
}
/*
* cleanup_channels
*
* inputs - not used
* output - none
* side effects - persistent channels... vchans get a long long timeout
*/
void
cleanup_channels(void *unused)
{
struct Channel *chptr;
struct Channel *next_chptr;
if (uplink != NULL)
{
/* XXX The assert disapears when NDEBUG is set */
assert(MyConnect(uplink) == 1);
if (!MyConnect(uplink))
{
ilog(L_ERROR, "non-local uplink [%s]", uplink->name);
uplink = NULL;
}
}
for (chptr = GlobalChannelList; chptr; chptr = next_chptr)
{
next_chptr = chptr->nextch;
{
if(chptr->users == 0)
{
if((chptr->users_last + ConfigChannel.persist_time) < CurrentTime)
{
if(uplink && IsCapable(uplink, CAP_LL))
sendto_one(uplink, ":%s DROP %s", me.name, chptr->chname);
destroy_channel(chptr);
}
}
else
{
if ((CurrentTime - chptr->users_last >= CLEANUP_CHANNELS_TIME))
{
if (uplink
&& IsCapable(uplink, CAP_LL) && (chptr->locusers == 0))
{
sendto_one(uplink, ":%s DROP %s", me.name, chptr->chname);
destroy_channel(chptr);
}
}
}
}
}
}
/*
* destroy_channel
* inputs - channel pointer
* output - none
* side effects - walk through this channel, and destroy it.
*/
static void
destroy_channel(struct Channel *chptr)
{
dlink_node *ptr;
dlink_node *m;
/* Walk through all the dlink's pointing to members of this channel,
* then walk through each client found from each dlink, removing
* any reference it has to this channel.
* Finally, free now unused dlink's
*
* For LazyLinks, note that the channel need not be empty, it only
* has to be empty of local users.
*/
delete_members(chptr, &chptr->chanadmins);
delete_members(chptr, &chptr->chanops);
delete_members(chptr, &chptr->voiced);
delete_members(chptr, &chptr->peons);
delete_members(chptr, &chptr->halfops);
delete_members(chptr, &chptr->locchanadmins);
delete_members(chptr, &chptr->locchanops);
delete_members(chptr, &chptr->locvoiced);
delete_members(chptr, &chptr->locpeons);
delete_members(chptr, &chptr->lochalfops);
while ((ptr = chptr->invites.head))
del_invite(chptr, ptr->data);
/* free all bans/exceptions/denies */
free_channel_list(&chptr->banlist);
free_channel_list(&chptr->exceptlist);
free_channel_list(&chptr->invexlist);
/* Free the topic */
free_topic(chptr);
/* This should be redundant at this point but JIC */
chptr->banlist.head = chptr->exceptlist.head = chptr->invexlist.head = NULL;
chptr->banlist.tail = chptr->exceptlist.tail = chptr->invexlist.tail = NULL;
if (chptr->prevch)
chptr->prevch->nextch = chptr->nextch;
else
GlobalChannelList = chptr->nextch;
if (chptr->nextch)
chptr->nextch->prevch = chptr->prevch;
del_from_channel_hash_table(chptr->chname, chptr);
if (ServerInfo.hub == 1)
{
DLINK_FOREACH(m, lazylink_channels.head)
{
if (m->data != chptr)
continue;
dlinkDelete(m, &lazylink_channels);
free_dlink_node(m);
break;
}
}
BlockHeapFree(channel_heap, chptr);
Count.chan--;
}
/*
* delete_members
*
* inputs - pointer to list (on channel)
* output - none
* side effects - delete members of this list
*/
static void
delete_members(struct Channel *chptr, dlink_list * list)
{
dlink_node *ptr;
dlink_node *next_ptr;
dlink_node *ptr_ch;
dlink_node *next_ptr_ch;
struct Client *who;
DLINK_FOREACH_SAFE(ptr, next_ptr, list->head)
{
next_ptr = ptr->next;
who = (struct Client *)ptr->data;
if (who->user != NULL)
{
/* remove reference to chptr from who */
DLINK_FOREACH_SAFE (ptr_ch, next_ptr_ch, who->user->channel.head)
{
if (ptr_ch->data == chptr)
{
dlinkDelete(ptr_ch, &who->user->channel);
free_dlink_node(ptr_ch);
who->user->joined--;
break;
}
}
}
/* remove reference to who from chptr */
dlinkDelete(ptr, list);
free_dlink_node(ptr);
}
}
/*
* channel_member_names
*
* inputs - pointer to client struct requesting names
* - pointer to channel block
* - pointer to name of channel
* - show ENDOFNAMES numeric or not
* (don't want it with /names with no params)
* output - none
* side effects - lists all names on given channel
*/
void
channel_member_names(struct Client *source_p,
struct Channel *chptr,
char *name_of_channel, int show_eon)
{
int mlen;
int sublists_done = 0;
int tlen;
int cur_len;
char lbuf[BUFSIZE];
char *t;
int reply_to_send = NO;
dlink_node *members_ptr[NUMLISTS];
char show_flags[NUMLISTS][2];
struct Client *who;
int is_member;
int i;
/* Find users on same channel (defined by chptr) */
if (ShowChannel(source_p, chptr))
{
ircsprintf(lbuf, form_str(RPL_NAMREPLY),
me.name, source_p->name, channel_pub_or_secret(chptr));
mlen = strlen(lbuf);
ircsprintf(lbuf + mlen, " %s :", name_of_channel);
mlen = strlen(lbuf);
cur_len = mlen;
t = lbuf + cur_len;
set_channel_mode_flags(show_flags, chptr, source_p);
members_ptr[0] = chptr->chanops.head;
members_ptr[1] = chptr->halfops.head;
members_ptr[2] = chptr->voiced.head;
members_ptr[3] = chptr->chanadmins.head;
members_ptr[4] = chptr->peons.head;
is_member = IsMember(source_p, chptr);
/* Note: This code will show one chanop followed by one voiced followed
* by one halfop followed by one peon followed by one chanop...
* XXX - this is very predictable, randomise it later.
*/
while (sublists_done != (1 << NUMLISTS) - 1)
{
for (i = 0; i < NUMLISTS; i++)
{
if (members_ptr[i] != NULL)
{
who = members_ptr[i]->data;
if (IsInvisible(who) && !is_member)
{
/* We definitely need this code -A1kmm. */
members_ptr[i] = members_ptr[i]->next;
continue;
}
reply_to_send = YES;
if (who == source_p && is_voiced(chptr, who)
&& chptr->mode.mode & MODE_HIDEOPS)
ircsprintf(t, "+%s ", who->name);
else
ircsprintf(t, "%s%s ", show_flags[i], who->name);
tlen = strlen(t);
cur_len += tlen;
t += tlen;
if ((cur_len + NICKLEN) > (BUFSIZE - 3))
{
sendto_one(source_p, "%s", lbuf);
reply_to_send = NO;
cur_len = mlen;
t = lbuf + mlen;
}
members_ptr[i] = members_ptr[i]->next;
}
else
{
sublists_done |= 1 << i;
}
}
}
if (reply_to_send)
sendto_one(source_p, "%s", lbuf);
}
if (show_eon)
sendto_one(source_p, form_str(RPL_ENDOFNAMES), me.name,
source_p->name, name_of_channel);
}
/*
* channel_pub_or_secret
*
* inputs - pointer to channel
* output - string pointer "=" if public, "@" if secret else "*"
* side effects - NONE
*/
char *
channel_pub_or_secret(struct Channel *chptr)
{
if (PubChannel(chptr))
return ("=");
else if (SecretChannel(chptr))
return ("@");
return ("*");
}
/*
* add_invite
*
* inputs - pointer to channel block
* - pointer to client to add invite to
* output - none
* side effects - adds client to invite list
*
* This one is ONLY used by m_invite.c
*/
void
add_invite(struct Channel *chptr, struct Client *who)
{
dlink_node *inv;
del_invite(chptr, who);
/*
* delete last link in chain if the list is max length
*/
if (dlink_list_length(&who->user->invited) >=
ConfigChannel.max_chans_per_user)
{
del_invite(chptr, who);
}
/*
* add client to channel invite list
*/
inv = make_dlink_node();
dlinkAdd(who, inv, &chptr->invites);
/*
* add channel to the end of the client invite list
*/
inv = make_dlink_node();
dlinkAdd(chptr, inv, &who->user->invited);
}
/*
* del_invite
*
* inputs - pointer to dlink_list
* - pointer to client to remove invites from
* output - none
* side effects - Delete Invite block from channel invite list
* and client invite list
*
*/
void
del_invite(struct Channel *chptr, struct Client *who)
{
dlink_node *ptr;
DLINK_FOREACH(ptr, chptr->invites.head)
{
if (ptr->data == who)
{
dlinkDelete(ptr, &chptr->invites);
free_dlink_node(ptr);
break;
}
}
DLINK_FOREACH(ptr, who->user->invited.