1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-06-29 04:56:37 +02:00
Files
unrealircd/src/channel.c
T

1586 lines
37 KiB
C

/* Unreal Internet Relay Chat Daemon, src/channel.c
* 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 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.
*/
/*
* 23 Jun 1999
* Changing unsigned int modes to long
* --- Sts - 28 May 1999
Incorporated twilight mode system
*/
/* -- Jto -- 09 Jul 1990
* Bug fix
*/
/* -- Jto -- 03 Jun 1990
* Moved m_channel() and related functions from s_msg.c to here
* Many changes to start changing into string channels...
*/
/* -- Jto -- 24 May 1990
* Moved is_full() from list.c
*/
#include "struct.h"
#include "common.h"
#include "sys.h"
#include "numeric.h"
#include "channel.h"
#include "msg.h" /* For TOK_*** and MSG_*** strings */
#include "hash.h" /* For CHANNELHASHSIZE */
#include "h.h"
#include "proto.h"
#include "modules.h"
#include <string.h>
ID_Copyright
("(C) 1990 University of Oulu, Computing Center and Jarkko Oikarinen");
long opermode = 0;
aChannel *channel = NullChn;
extern char backupbuf[];
extern ircstats IRCstats;
/* Some forward declarations */
char *clean_ban_mask(char *, int, aClient *);
void channel_modes(aClient *cptr, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size, aChannel *chptr);
void sub1_from_channel(aChannel *);
void clean_channelname(char *);
void del_invite(aClient *, aChannel *);
/*
* some buffers for rebuilding channel/nick lists with ,'s
*/
static char nickbuf[BUFSIZE], buf[BUFSIZE];
MODVAR char modebuf[BUFSIZE], parabuf[BUFSIZE];
#include "sjoin.h"
#define MODESYS_LINKOK /* We do this for a TEST */
aCtab cFlagTab[] = {
{MODE_LIMIT, 'l', 1, 1},
{MODE_VOICE, 'v', 1, 1},
{MODE_HALFOP, 'h', 0, 1},
{MODE_CHANOP, 'o', 0, 1},
{MODE_PRIVATE, 'p', 0, 0},
{MODE_SECRET, 's', 0, 0},
{MODE_MODERATED, 'm', 1, 0},
{MODE_NOPRIVMSGS, 'n', 1, 0},
{MODE_TOPICLIMIT, 't', 1, 0},
{MODE_INVITEONLY, 'i', 1, 0},
{MODE_KEY, 'k', 1, 1},
{MODE_RGSTR, 'r', 0, 0},
{MODE_CHANPROT, 'a', 0, 1},
{MODE_CHANOWNER, 'q', 0, 1},
{MODE_BAN, 'b', 1, 1},
{MODE_EXCEPT, 'e', 1, 0}, /* exception ban */
{MODE_INVEX, 'I', 1, 0}, /* exception ban */
{0x0, 0x0, 0x0}
};
#define BADOP_BOUNCE 1
#define BADOP_USER 2
#define BADOP_SERVER 3
#define BADOP_OVERRIDE 4
char cmodestring[512];
inline int op_can_override(aClient *sptr)
{
#ifndef NO_OPEROVERRIDE
if (!IsOper(sptr))
return 0;
if (MyClient(sptr) && !OPCanOverride(sptr))
return 0;
return 1;
#else
return 0;
#endif
}
void make_cmodestr(void)
{
char *p = &cmodestring[0];
aCtab *tab = &cFlagTab[0];
int i;
while (tab->mode != 0x0)
{
*p = tab->flag;
p++;
tab++;
}
for (i=0; i <= Channelmode_highest; i++)
if (Channelmode_Table[i].flag)
*p++ = Channelmode_Table[i].flag;
*p = '\0';
}
int Halfop_mode(long mode)
{
aCtab *tab = &cFlagTab[0];
while (tab->mode != 0x0)
{
if (tab->mode == mode)
return (tab->halfop == 1 ? TRUE : FALSE);
tab++;
}
return TRUE;
}
/*
* return the length (>=0) of a chain of links.
*/
static int list_length(Link *lp)
{
int count = 0;
for (; lp; lp = lp->next)
count++;
return count;
}
Member *find_member_link(Member *lp, aClient *ptr)
{
if (ptr)
while (lp)
{
if (lp->cptr == ptr)
return (lp);
lp = lp->next;
}
return NULL;
}
Membership *find_membership_link(Membership *lp, aChannel *ptr)
{
if (ptr)
while (lp)
{
if (lp->chptr == ptr)
return (lp);
lp = lp->next;
}
return NULL;
}
/*
* Member functions
*/
Member *make_member(void)
{
Member *lp;
unsigned int i;
if (freemember == NULL)
{
for (i = 1; i <= (4072/sizeof(Member)); ++i)
{
lp = (Member *)MyMallocEx(sizeof(Member));
lp->cptr = NULL;
lp->flags = 0;
lp->next = freemember;
freemember = lp;
}
}
lp = freemember;
freemember = freemember->next;
lp->next = NULL;
return lp;
}
void free_member(Member *lp)
{
if (!lp)
return;
moddata_free_member(lp);
memset(lp, 0, sizeof(Member));
lp->next = freemember;
lp->cptr = NULL;
lp->flags = 0;
freemember = lp;
}
/*
* Membership functions
*/
Membership *make_membership(int local)
{
Membership *lp = NULL;
MembershipL *lp2 = NULL;
unsigned int i;
if (!local)
{
if (freemembership == NULL)
{
for (i = 1; i <= (4072/sizeof(Membership)); i++)
{
lp = (Membership *)MyMallocEx(sizeof(Membership));
lp->next = freemembership;
freemembership = lp;
}
lp = freemembership;
freemembership = lp->next;
}
else
{
lp = freemembership;
freemembership = freemembership->next;
}
bzero(lp, sizeof(Membership));
}
else
{
if (freemembershipL == NULL)
{
for (i = 1; i <= (4072/sizeof(MembershipL)); i++)
{
lp2 = (MembershipL *)MyMalloc(sizeof(MembershipL));
lp2->next = (Membership *) freemembershipL;
freemembershipL = lp2;
}
lp2 = freemembershipL;
freemembershipL = (MembershipL *) lp2->next;
}
else
{
lp2 = freemembershipL;
freemembershipL = (MembershipL *) freemembershipL->next;
}
bzero(lp2, sizeof(MembershipL));
}
if (local)
{
return ((Membership *) lp2);
}
return lp;
}
void free_membership(Membership *lp, int local)
{
if (lp)
{
moddata_free_membership(lp);
if (!local)
{
memset(lp, 0, sizeof(Membership));
lp->next = freemembership;
freemembership = lp;
}
else
{
memset(lp, 0, sizeof(Membership));
lp->next = (Membership *) freemembershipL;
freemembershipL = (MembershipL *) lp;
}
}
}
/*
** find_chasing
** Find the client structure for a nick name (user) using history
** mechanism if necessary. If the client is not found, an error
** message (NO SUCH NICK) is generated. If the client was found
** through the history, chasing will be 1 and otherwise 0.
*/
aClient *find_chasing(aClient *sptr, char *user, int *chasing)
{
aClient *who = find_client(user, (aClient *)NULL);
if (chasing)
*chasing = 0;
if (who)
{
if (!IsServer(who))
return who;
else
return NULL;
}
if (!(who = get_history(user, (long)KILLCHASETIMELIMIT)))
{
sendto_one(sptr, err_str(ERR_NOSUCHNICK),
me.name, sptr->name, user);
return NULL;
}
if (chasing)
*chasing = 1;
if (!IsServer(who))
return who;
else return NULL;
}
/*
* add_listmode - Add a listmode (+beI) with the specified banid to
* the specified channel.
*/
int add_listmode(Ban **list, aClient *cptr, aChannel *chptr, char *banid)
{
Ban *ban;
int cnt = 0, len;
if (MyClient(cptr))
(void)collapse(banid);
len = strlen(banid);
if (!*list && ((len > MAXBANLENGTH) || (MAXBANS < 1)))
{
sendto_one(cptr, err_str(ERR_BANLISTFULL),
me.name, cptr->name, chptr->chname, banid);
return -1;
}
for (ban = *list; ban; ban = ban->next)
{
len += strlen(ban->banstr);
if (MyClient(cptr))
if ((len > MAXBANLENGTH) || (++cnt >= MAXBANS))
{
sendto_one(cptr, err_str(ERR_BANLISTFULL),
me.name, cptr->name, chptr->chname, banid);
return -1;
}
else
{
#ifdef SOCALLEDSMARTBANNING
/* Temp workaround added in b19. -- Syzop */
if (!mycmp(ban->banstr, banid) || (!strchr(banid, '\\') && !strchr(ban->banstr, '\\')))
if (!match(ban->banstr, banid))
return -1;
#endif
if (!mycmp(ban->banstr, banid))
return -1;
}
else if (!mycmp(ban->banstr, banid))
return -1;
}
ban = make_ban();
bzero((char *)ban, sizeof(Ban));
ban->next = *list;
ban->banstr = strdup(banid);
ban->who = strdup(cptr->name);
ban->when = TStime();
*list = ban;
return 0;
}
/*
* del_listmode - delete a listmode (+beI) from a channel
* that matches the specified banid.
*/
int del_listmode(Ban **list, aChannel *chptr, char *banid)
{
Ban **ban;
Ban *tmp;
if (!banid)
return -1;
for (ban = list; *ban; ban = &((*ban)->next))
{
if (mycmp(banid, (*ban)->banstr) == 0)
{
tmp = *ban;
*ban = tmp->next;
MyFree(tmp->banstr);
MyFree(tmp->who);
free_ban(tmp);
return 0;
}
}
return -1;
}
/*
* IsMember - returns 1 if a person is joined
* Moved to struct.h
*/
/* Those pointers can be used by extended ban modules so they
* don't have to do 4 make_nick_user_host()'s all the time:
*/
char *ban_realhost = NULL, *ban_virthost = NULL, *ban_cloakhost = NULL, *ban_ip = NULL;
/** is_banned - Check if a user is banned on a channel.
* @param sptr Client to check (can be remote client)
* @param chptr Channel to check
* @param type Type of ban to check for (BANCHK_*)
* @returns A pointer to the ban struct if banned, otherwise NULL.
* @comments Simple wrapper for is_banned_with_nick()
*/
inline Ban *is_banned(aClient *sptr, aChannel *chptr, int type)
{
return is_banned_with_nick(sptr, chptr, type, sptr->name);
}
/** ban_check_mask - Checks if the current user in ban checking (ban_ip, etc) matches the specified n!u@h mask -or- run an extended ban.
* @param sptr Client to check (can be remote client)
* @param chptr Channel to check
* @param banstr Mask string to check user
* @param type Type of ban to check for (BANCHK_*)
* @param no_extbans 0 to check extbans, nonzero to disable extban checking.
* @returns Nonzero if the mask/extban succeeds. Zero if it doesn't.
* @comments This is basically extracting the mask and extban check from is_banned_with_nick, but with being a bit more strict in what an extban is.
* Strange things could happen if this is called outside standard ban checking.
*/
inline int ban_check_mask(aClient *sptr, aChannel *chptr, char *banstr, int type, int no_extbans)
{
Extban *extban = NULL;
if (!no_extbans && banstr[0] == '~' && banstr[1] != '\0' && banstr[2] == ':')
{
/* Is an extended ban. */
extban = findmod_by_bantype(banstr[1]);
if (!extban)
{
return 0;
}
else
{
return extban->is_banned(sptr, chptr, banstr, type);
}
}
else
{
/* Is a n!u@h mask. */
return extban_is_banned_helper(banstr);
}
}
/** is_banned_with_nick - Check if a user is banned on a channel.
* @param sptr Client to check (can be remote client)
* @param chptr Channel to check
* @param type Type of ban to check for (BANCHK_*)
* @param nick Nick of the user
* @returns A pointer to the ban struct if banned, otherwise NULL.
*/
Ban *is_banned_with_nick(aClient *sptr, aChannel *chptr, int type, char *nick)
{
Ban *tmp, *tmp2;
char *s;
static char realhost[NICKLEN + USERLEN + HOSTLEN + 24];
static char cloakhost[NICKLEN + USERLEN + HOSTLEN + 24];
static char virthost[NICKLEN + USERLEN + HOSTLEN + 24];
static char nuip[NICKLEN + USERLEN + HOSTLEN + 24];
Extban *extban;
if (!IsPerson(sptr) || !chptr->banlist)
return NULL;
ban_realhost = realhost;
ban_ip = ban_virthost = ban_cloakhost = NULL;
/* Might it be possible in the future to include the possiblity for SupportNICKIP(sptr->from), SupportCLK(sptr->from)? -- aquanight */
/* Nope, because servers not directly connected to the server in question have no idea about the capabilities at all.
* However, there's no need for a MyConnect() requirement, just check if GetIP() is non-NULL and
* if sptr->user->cloakedhost contains anything... -- Syzop
*/
if (GetIP(sptr))
{
make_nick_user_host_r(nuip, nick, sptr->user->username, GetIP(sptr));
ban_ip = nuip;
}
if (*sptr->user->cloakedhost)
{
make_nick_user_host_r(cloakhost, nick, sptr->user->username, sptr->user->cloakedhost);
ban_cloakhost = cloakhost;
}
if (IsSetHost(sptr) && strcmp(sptr->user->realhost, sptr->user->virthost))
{
make_nick_user_host_r(virthost, nick, sptr->user->username, sptr->user->virthost);
ban_virthost = virthost;
}
make_nick_user_host_r(realhost, nick, sptr->user->username, sptr->user->realhost);
/* We now check +b first, if a +b is found we then see if there is a +e.
* If a +e was found we return NULL, if not, we return the ban.
*/
for (tmp = chptr->banlist; tmp; tmp = tmp->next)
{
if (!ban_check_mask(sptr, chptr, tmp->banstr, type, 0))
continue;
/* Ban found, now check for +e */
for (tmp2 = chptr->exlist; tmp2; tmp2 = tmp2->next)
{
if (ban_check_mask(sptr, chptr, tmp2->banstr, type, 0))
return NULL; /* except matched */
}
break; /* ban found and not on except */
}
return (tmp);
}
int extban_is_banned_helper(char *buf)
{
if ((match(buf, ban_realhost) == 0) ||
(ban_virthost && (match(buf, ban_virthost) == 0)) ||
(ban_ip && (match(buf, ban_ip) == 0)) ||
(ban_cloakhost && (match(buf, ban_cloakhost) == 0)) )
return 1;
return 0;
}
/*
* Checks if the "user" IRC is banned, used by +mu.
*/
static int is_irc_banned(aChannel *chptr)
{
Ban *tmp;
/* Check for this user, ident/host are "illegal" on purpose */
char *check = "IRC!\001@\001";
for (tmp = chptr->banlist; tmp; tmp = tmp->next)
if (match(tmp->banstr, check) == 0)
{
/* Ban found, now check for +e */
for (tmp = chptr->exlist; tmp; tmp = tmp->next)
if (match(tmp->banstr, check) == 0)
return 0; /* In exception list */
return 1;
}
return 0;
}
/*
* adds a user to a channel by adding another link to the channels member
* chain.
*/
void add_user_to_channel(aChannel *chptr, aClient *who, int flags)
{
Member *ptr;
Membership *ptr2;
if (who->user)
{
ptr = make_member();
ptr->cptr = who;
ptr->flags = flags;
ptr->next = chptr->members;
chptr->members = ptr;
chptr->users++;
ptr2 = make_membership(MyClient(who));
/* we should make this more efficient --stskeeps
is now, as we only use it in membership */
ptr2->chptr = chptr;
ptr2->next = who->user->channel;
ptr2->flags = flags;
who->user->channel = ptr2;
who->user->joined++;
RunHook2(HOOKTYPE_JOIN_DATA,who,chptr);
}
}
void remove_user_from_channel(aClient *sptr, aChannel *chptr)
{
Member **curr; Membership **curr2;
Member *tmp; Membership *tmp2;
Member *lp = chptr->members;
/* find 1st entry in list that is not user */
for (; lp && (lp->cptr == sptr); lp = lp->next);
for (;;)
{
for (curr = &chptr->members; (tmp = *curr); curr = &tmp->next)
if (tmp->cptr == sptr)
{
*curr = tmp->next;
free_member(tmp);
break;
}
for (curr2 = &sptr->user->channel; (tmp2 = *curr2); curr2 = &tmp2->next)
if (tmp2->chptr == chptr)
{
*curr2 = tmp2->next;
free_membership(tmp2, MyClient(sptr));
break;
}
sptr->user->joined--;
if (lp)
break;
if (chptr->members)
sptr = chptr->members->cptr;
else
break;
sub1_from_channel(chptr);
}
sub1_from_channel(chptr);
}
long get_access(aClient *cptr, aChannel *chptr)
{
Membership *lp;
if (chptr)
if ((lp = find_membership_link(cptr->user->channel, chptr)))
return lp->flags;
return 0;
}
int is_chan_op(aClient *cptr, aChannel *chptr)
{
Membership *lp;
/* chanop/halfop ? */
if (IsServer(cptr))
return 1;
if (chptr)
if ((lp = find_membership_link(cptr->user->channel, chptr)))
#ifdef PREFIX_AQ
return ((lp->flags & (CHFL_CHANOP|CHFL_CHANPROT|CHFL_CHANOWNER)));
#else
return ((lp->flags & CHFL_CHANOP));
#endif
return 0;
}
int has_voice(aClient *cptr, aChannel *chptr)
{
Membership *lp;
if (IsServer(cptr))
return 1;
if (chptr)
if ((lp = find_membership_link(cptr->user->channel, chptr)))
return (lp->flags & CHFL_VOICE);
return 0;
}
int is_halfop(aClient *cptr, aChannel *chptr)
{
Membership *lp;
if (IsServer(cptr))
return 1;
if (chptr)
if ((lp = find_membership_link(cptr->user->channel, chptr)))
if (!(lp->flags & CHFL_CHANOP))
return (lp->flags & CHFL_HALFOP);
return 0;
}
int is_chanowner(aClient *cptr, aChannel *chptr)
{
Membership *lp;
if (IsServer(cptr))
return 1;
if (chptr)
if ((lp = find_membership_link(cptr->user->channel, chptr)))
return (lp->flags & CHFL_CHANOWNER);
return 0;
}
int is_chanownprotop(aClient *cptr, aChannel *chptr) {
Membership *lp;
if (IsServer(cptr))
return 1;
if (chptr)
if ((lp = find_membership_link(cptr->user->channel, chptr)))
if (lp->flags & (CHFL_CHANOWNER|CHFL_CHANPROT|CHFL_CHANOP))
return 1;
return 0;
}
int is_skochanop(aClient *cptr, aChannel *chptr) {
Membership *lp;
if (IsServer(cptr))
return 1;
if (chptr)
if ((lp = find_membership_link(cptr->user->channel, chptr)))
#ifdef PREFIX_AQ
if (lp->flags & (CHFL_CHANOWNER|CHFL_CHANPROT|CHFL_CHANOP|CHFL_HALFOP))
#else
if (lp->flags & (CHFL_CHANOP|CHFL_HALFOP))
#endif
return 1;
return 0;
}
int is_chanprot(aClient *cptr, aChannel *chptr)
{
Membership *lp;
if (chptr)
if ((lp = find_membership_link(cptr->user->channel, chptr)))
return (lp->flags & CHFL_CHANPROT);
return 0;
}
int can_send(aClient *cptr, aChannel *chptr, char *msgtext, int notice)
{
Membership *lp;
int member, i = 0;
Hook *h;
/*
* #0000053 by |savage|, speedup
*/
if (!MyClient(cptr))
{
return 0;
}
member = IsMember(cptr, chptr);
if (chptr->mode.mode & MODE_NOPRIVMSGS && !member)
return (CANNOT_SEND_NOPRIVMSGS);
lp = find_membership_link(cptr->user->channel, chptr);
if (chptr->mode.mode & MODE_MODERATED && !op_can_override(cptr) &&
(!lp
|| !(lp->flags & (CHFL_CHANOP | CHFL_VOICE | CHFL_CHANOWNER |
CHFL_HALFOP | CHFL_CHANPROT))))
{
return (CANNOT_SEND_MODERATED);
}
for (h = Hooks[HOOKTYPE_CAN_SEND]; h; h = h->next)
{
i = (*(h->func.intfunc))(cptr,chptr,msgtext,lp,notice);
if (i != HOOK_CONTINUE)
break;
}
if (i != HOOK_CONTINUE)
return i;
/* Makes opers able to talk thru bans -Stskeeps suggested by The_Cat */
if (IsOper(cptr) && OPCanOverride(cptr))
return 0;
if ((!lp
|| !(lp->flags & (CHFL_CHANOP | CHFL_VOICE | CHFL_CHANOWNER |
CHFL_HALFOP | CHFL_CHANPROT))) && MyClient(cptr)
&& is_banned(cptr, chptr, BANCHK_MSG))
return (CANNOT_SEND_BAN);
return 0;
}
/*
* write the "simple" list of channel modes for channel chptr onto buffer mbuf
* with the parameters in pbuf.
*/
/* TODO: this function has many security issues and needs an audit, maybe even a recode */
void channel_modes(aClient *cptr, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size, aChannel *chptr)
{
aCtab *tab = &cFlagTab[0];
char bcbuf[1024];
int ismember;
int i;
if (!(mbuf_size && pbuf_size)) return;
ismember = (IsMember(cptr, chptr) || IsServer(cptr) || IsULine(cptr)) ? 1 : 0;
*pbuf = '\0';
*mbuf++ = '+';
mbuf_size--;
/* Paramless first */
while (mbuf_size && tab->mode != 0x0)
{
if ((chptr->mode.mode & tab->mode))
if (!tab->parameters) {
*mbuf++ = tab->flag;
mbuf_size--;
}
tab++;
}
for (i=0; i <= Channelmode_highest; i++)
{
if (!mbuf_size) break;
if (Channelmode_Table[i].flag && !Channelmode_Table[i].paracount &&
(chptr->mode.extmode & Channelmode_Table[i].mode)) {
*mbuf++ = Channelmode_Table[i].flag;
mbuf_size--;
}
}
if (chptr->mode.limit)
{
if (mbuf_size) {
*mbuf++ = 'l';
mbuf_size--;
}
if (ismember) {
ircsnprintf(pbuf, pbuf_size, "%d ", chptr->mode.limit);
pbuf_size-=strlen(pbuf);
pbuf+=strlen(pbuf);
}
}
if (*chptr->mode.key)
{
if (mbuf_size) {
*mbuf++ = 'k';
mbuf_size--;
}
if (ismember && pbuf_size) {
ircsnprintf(pbuf, pbuf_size, "%s ", chptr->mode.key);
pbuf_size-=strlen(pbuf);
pbuf+=strlen(pbuf);
}
}
for (i=0; i <= Channelmode_highest; i++)
{
if (Channelmode_Table[i].flag && Channelmode_Table[i].paracount &&
(chptr->mode.extmode & Channelmode_Table[i].mode)) {
char flag = Channelmode_Table[i].flag;
if (mbuf_size) {
*mbuf++ = flag;
mbuf_size--;
}
if (ismember)
{
ircsnprintf(pbuf, pbuf_size, "%s ", cm_getparameter(chptr, flag));
pbuf_size-=strlen(pbuf);
pbuf+=strlen(pbuf);
}
}
}
/* Remove the trailing space from the parameters -- codemastr */
if (*pbuf) pbuf[strlen(pbuf)-1]=0;
if (!mbuf_size) mbuf--;
*mbuf++ = '\0';
return;
}
int DoesOp(char *modebuf)
{
modebuf--; /* Is it possible that a mode starts with o and not +o ? */
while (*++modebuf)
if (*modebuf == 'h' || *modebuf == 'o'
|| *modebuf == 'v' || *modebuf == 'q')
return (1);
return 0;
}
int sendmodeto_one(aClient *cptr, char *from, char *name, char *mode, char *param, TS creationtime)
{
if ((IsServer(cptr) && DoesOp(mode) && creationtime) ||
IsULine(cptr))
sendto_one(cptr, ":%s MODE %s %s %s %lu", from,
name, mode, param, creationtime);
else
sendto_one(cptr, ":%s MODE %s %s %s", from, name, mode, param);
return 0;
}
char *pretty_mask(char *mask)
{
char *cp;
char *user;
char *host;
if ((user = index((cp = mask), '!')))
*user++ = '\0';
if ((host = rindex(user ? user : cp, '@')))
{
*host++ = '\0';
if (!user)
return make_nick_user_host(NULL, cp, host);
}
else if (!user && index(cp, '.'))
return make_nick_user_host(NULL, NULL, cp);
return make_nick_user_host(cp, user, host);
}
char *trim_str(char *str, int len)
{
int l;
if (!str)
return NULL;
if ((l = strlen(str)) > len)
{
str += l - len;
*str = '*';
}
return str;
}
/* clean_ban_mask: makes a proper banmask
* RETURNS: pointer to correct banmask or NULL in case of error
* NOTES:
* - A pointer is returned to a static buffer, which is overwritten
* on next clean_ban_mask or make_nick_user_host call.
* - mask is fragged in some cases, this could be bad.
*/
char *clean_ban_mask(char *mask, int what, aClient *cptr)
{
char *cp;
char *user;
char *host;
Extban *p;
cp = index(mask, ' ');
if (cp)
*cp = '\0';
/* Strip any ':' at beginning coz that desynchs clients/banlists */
for (; (*mask && (*mask == ':')); mask++);
if (!*mask)
return NULL;
/* Extended ban? */
if ((*mask == '~') && mask[1] && (mask[2] == ':'))
{
if (RESTRICT_EXTENDEDBANS && MyClient(cptr) && !IsAnOper(cptr))
{
if (!strcmp(RESTRICT_EXTENDEDBANS, "*"))
{
sendnotice(cptr, "Setting/removing of extended bans has been disabled");
return NULL;
}
if (strchr(RESTRICT_EXTENDEDBANS, mask[1]))
{
sendnotice(cptr, "Setting/removing of extended bantypes '%s' has been disabled",
RESTRICT_EXTENDEDBANS);
return NULL;
}
}
p = findmod_by_bantype(mask[1]);
if (!p)
{
/* extended bantype not supported, what to do?
* Here are the rules:
* - if from a remote client/server: allow it (easy upgrading,
* no desynch)
* - if from a local client trying to REMOVE the extban,
* allow it too (so you don't get "unremovable" extbans).
*/
if (!MyClient(cptr) || (what == MODE_DEL))
return mask; /* allow it */
return NULL; /* reject */
}
if (p->conv_param)
return p->conv_param(mask);
/* else, do some basic sanity checks and cut it off at 80 bytes */
if ((cp[1] != ':') || (cp[2] == '\0'))
return NULL; /* require a ":<char>" after extban type */
if (strlen(mask) > 80)
mask[80] = '\0';
return mask;
}
if ((*mask == '~') && !strchr(mask, '@'))
return NULL; /* not an extended ban and not a ~user@host ban either. */
if ((user = index((cp = mask), '!')))
*user++ = '\0';
if ((host = rindex(user ? user : cp, '@')))
{
*host++ = '\0';
if (!user)
return make_nick_user_host(NULL, trim_str(cp,USERLEN),
trim_str(host,HOSTLEN));
}
else if (!user && index(cp, '.'))
return make_nick_user_host(NULL, NULL, trim_str(cp,HOSTLEN));
return make_nick_user_host(trim_str(cp,NICKLEN), trim_str(user,USERLEN),
trim_str(host,HOSTLEN));
}
int find_invex(aChannel *chptr, aClient *sptr)
{
/* This routine is basically a copy-paste of is_banned_with_nick, with modifications, for invex */
Ban *inv;
char *s;
static char realhost[NICKLEN + USERLEN + HOSTLEN + 24];
static char cloakhost[NICKLEN + USERLEN + HOSTLEN + 24];
static char virthost[NICKLEN + USERLEN + HOSTLEN + 24];
static char nuip[NICKLEN + USERLEN + HOSTLEN + 24];
Extban *extban;
if (!IsPerson(sptr) || !chptr->invexlist)
return 0;
ban_realhost = realhost;
ban_ip = ban_virthost = ban_cloakhost = NULL;
if (GetIP(sptr))
{
make_nick_user_host_r(nuip, sptr->name, sptr->user->username, GetIP(sptr));
ban_ip = nuip;
}
if (*sptr->user->cloakedhost)
{
make_nick_user_host_r(cloakhost, sptr->name, sptr->user->username, sptr->user->cloakedhost);
ban_cloakhost = cloakhost;
}
if (IsSetHost(sptr) && strcmp(sptr->user->realhost, sptr->user->virthost))
{
make_nick_user_host_r(virthost, sptr->name, sptr->user->username, sptr->user->virthost);
ban_virthost = virthost;
}
make_nick_user_host_r(realhost, sptr->name, sptr->user->username, sptr->user->realhost);
for (inv = chptr->invexlist; inv; inv = inv->next)
if (ban_check_mask(sptr, chptr, inv->banstr, BANCHK_JOIN, 0))
return 1;
return 0;
}
/*
** Remove bells and commas from channel name
*/
void clean_channelname(char *cn)
{
u_char *ch = (u_char *)cn;
for (; *ch; ch++)
/* Don't allow any control chars, the space, the comma,
* or the "non-breaking space" in channel names.
* Might later be changed to a system where the list of
* allowed/non-allowed chars for channels was a define
* or some such.
* --Wizzu
*/
if (*ch < 33 || *ch == ',' || *ch == ':' || *ch == 160)
{
*ch = '\0';
return;
}
}
/*
** Get Channel block for i (and allocate a new channel
** block, if it didn't exists before).
*/
aChannel *get_channel(aClient *cptr, char *chname, int flag)
{
aChannel *chptr;
int len;
if (BadPtr(chname))
return NULL;
len = strlen(chname);
if (MyClient(cptr) && len > CHANNELLEN)
{
len = CHANNELLEN;
*(chname + CHANNELLEN) = '\0';
}
if ((chptr = find_channel(chname, (aChannel *)NULL)))
return (chptr);
if (flag == CREATE)
{
chptr = (aChannel *)MyMalloc(sizeof(aChannel) + len);
bzero((char *)chptr, sizeof(aChannel));
strlcpy(chptr->chname, chname, len + 1);
if (channel)
channel->prevch = chptr;
chptr->topic = NULL;
chptr->topic_nick = NULL;
chptr->prevch = NULL;
chptr->nextch = channel;
chptr->creationtime = MyClient(cptr) ? TStime() : (TS)0;
channel = chptr;
(void)add_to_channel_hash_table(chname, chptr);
IRCstats.channels++;
RunHook2(HOOKTYPE_CHANNEL_CREATE, cptr, chptr);
}
return chptr;
}
/*
* Slight changes in routine, now working somewhat symmetrical:
* First try to remove the client & channel pair to avoid duplicates
* Second check client & channel invite-list lengths and remove tail
* Finally add new invite-links to both client and channel
* Should U-lined clients have higher limits? -Donwulff
*/
void add_invite(aClient *from, aClient *to, aChannel *chptr)
{
Link *inv, *tmp;
del_invite(to, chptr);
/*
* delete last link in chain if the list is max length
*/
if (list_length(to->user->invited) >= MAXCHANNELSPERUSER)
{
for (tmp = to->user->invited; tmp->next; tmp = tmp->next)
;
del_invite(to, tmp->value.chptr);
}
/* We get pissy over too many invites per channel as well now,
* since otherwise mass-inviters could take up some major
* resources -Donwulff
*/
if (list_length(chptr->invites) >= MAXCHANNELSPERUSER)
{
for (tmp = chptr->invites; tmp->next; tmp = tmp->next)
;
del_invite(tmp->value.cptr, chptr);
}
/*
* add client to the beginning of the channel invite list
*/
inv = make_link();
inv->value.cptr = to;
inv->next = chptr->invites;
chptr->invites = inv;
/*
* add channel to the beginning of the client invite list
*/
inv = make_link();
inv->value.chptr = chptr;
inv->next = to->user->invited;
to->user->invited = inv;
RunHook3(HOOKTYPE_INVITE, from, to, chptr);
}
/*
* Delete Invite block from channel invite list and client invite list
*/
void del_invite(aClient *cptr, aChannel *chptr)
{
Link **inv, *tmp;
for (inv = &(chptr->invites); (tmp = *inv); inv = &tmp->next)
if (tmp->value.cptr == cptr)
{
*inv = tmp->next;
free_link(tmp);
break;
}
for (inv = &(cptr->user->invited); (tmp = *inv); inv = &tmp->next)
if (tmp->value.chptr == chptr)
{
*inv = tmp->next;
free_link(tmp);
break;
}
}
/*
** Subtract one user from channel i (and free channel
** block, if channel became empty).
*/
void sub1_from_channel(aChannel *chptr)
{
Ban *ban;
Link *lp;
bool should_destroy = true;
--chptr->users;
if (chptr->users <= 0)
{
chptr->users = 0;
/*
* Now, find all invite links from channel structure
*/
RunHook2(HOOKTYPE_CHANNEL_DESTROY, chptr, &should_destroy);
if (!should_destroy)
return;
while ((lp = chptr->invites))
del_invite(lp->value.cptr, chptr);
while (chptr->banlist)
{
ban = chptr->banlist;
chptr->banlist = ban->next;
MyFree(ban->banstr);
MyFree(ban->who);
free_ban(ban);
}
while (chptr->exlist)
{
ban = chptr->exlist;
chptr->exlist = ban->next;
MyFree(ban->banstr);
MyFree(ban->who);
free_ban(ban);
}
while (chptr->invexlist)
{
ban = chptr->invexlist;
chptr->invexlist = ban->next;
MyFree(ban->banstr);
MyFree(ban->who);
free_ban(ban);
}
/* free extcmode params */
extcmode_free_paramlist(chptr->mode.extmodeparams);
if (chptr->mode_lock)
MyFree(chptr->mode_lock);
if (chptr->topic)
MyFree(chptr->topic);
if (chptr->topic_nick)
MyFree(chptr->topic_nick);
if (chptr->prevch)
chptr->prevch->nextch = chptr->nextch;
else
channel = chptr->nextch;
if (chptr->nextch)
chptr->nextch->prevch = chptr->prevch;
(void)del_from_channel_hash_table(chptr->chname, chptr);
IRCstats.channels--;
MyFree((char *)chptr);
}
}
void send_user_joins(aClient *cptr, aClient *user)
{
Membership *lp;
aChannel *chptr;
int cnt = 0, len = 0, clen;
char *mask;
snprintf(buf, sizeof buf, ":%s JOIN ", user->name);
len = strlen(buf);
for (lp = user->user->channel; lp; lp = lp->next)
{
chptr = lp->chptr;
if ((mask = index(chptr->chname, ':')))
if (match(++mask, cptr->name))
continue;
if (*chptr->chname == '&')
continue;
clen = strlen(chptr->chname);
if (clen + 1 + len > BUFSIZE - 3)
{
if (cnt)
{
buf[len - 1] = '\0';
sendto_one(cptr, "%s", buf);
}
snprintf(buf, sizeof buf, ":%s JOIN ", user->name);
len = strlen(buf);
cnt = 0;
}
(void)strlcpy(buf + len, chptr->chname, sizeof buf-len);
cnt++;
len += clen;
if (lp->next)
{
len++;
(void)strlcat(buf, ",", sizeof buf);
}
}
if (*buf && cnt)
sendto_one(cptr, "%s", buf);
return;
}
/*
* rejoin_leave:
* sends a PART to all channels (to local users only)
* TODO: use QUIT instead of PART if configured to do so
*/
void rejoin_leave(aClient *sptr)
{
Membership *tmp;
aChannel *chptr;
Hook *h;
char *comment = "Changing host";
int i = 0;
for (tmp = sptr->user->channel; tmp; tmp = tmp->next)
{
tmp->flags &= ~CHFL_REJOINING;
chptr = tmp->chptr;
if (!chptr)
continue; /* Possible? */
/* If the user is banned, don't do it */
if (is_banned(sptr, chptr, BANCHK_JOIN))
continue;
/* Ok, we will now part/quit/whatever the user, so tag it.. */
tmp->flags |= CHFL_REJOINING;
for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
{
i = (*(h->func.intfunc))(sptr,chptr);
if (i != 0)
break;
}
if ((i != 0) &&
!(tmp->flags & (CHFL_CHANOWNER|CHFL_CHANPROT|CHFL_CHANOP|CHFL_HALFOP|CHFL_VOICE)))
{
sendto_chanops_butone(sptr, chptr, ":%s!%s@%s PART %s :%s", sptr->name, sptr->user->username, GetHost(sptr), chptr->chname, comment);
} else
sendto_channel_butserv_butone(chptr, sptr, sptr, ":%s PART %s :%s", sptr->name, chptr->chname, comment);
}
}
/*
* rejoin_joinandmode:
* sends a JOIN and a MODE (if needed) to restore qaohv modes (to local users only)
*/
void rejoin_joinandmode(aClient *sptr)
{
Membership *tmp;
aChannel *chptr;
int i, j = 0, n, flags;
Hook *h;
int k = 0;
char flagbuf[8]; /* For holding "qohva" and "*~@%+" */
for (tmp = sptr->user->channel; tmp; tmp = tmp->next)
{
flags = tmp->flags;
chptr = tmp->chptr;
if (!chptr)
continue; /* Is it possible? */
/* If the user is banned, don't do it */
if (!(flags & CHFL_REJOINING))
continue;
for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next)
{
k = (*(h->func.intfunc))(sptr,chptr);
if (k != 0)
break;
}
if ((k != 0) &&
!(flags & (CHFL_CHANOWNER|CHFL_CHANPROT|CHFL_CHANOP|CHFL_HALFOP|CHFL_VOICE)))
{
sendto_chanops_butone(sptr, chptr, ":%s!%s@%s JOIN :%s", sptr->name, sptr->user->username, GetHost(sptr), chptr->chname);
} else
sendto_channel_butserv_butone(chptr, sptr, sptr, ":%s JOIN :%s", sptr->name, chptr->chname);
/* Set the modes (if any) */
if (flags)
{
char *p = flagbuf;
if (flags & MODE_CHANOP)
*p++ = 'o';
if (flags & MODE_VOICE)
*p++ = 'v';
if (flags & MODE_HALFOP)
*p++ = 'h';
if (flags & MODE_CHANOWNER)
*p++ = 'q';
if (flags & MODE_CHANPROT)
*p++ = 'a';
*p = '\0';
parabuf[0] = '\0';
n = strlen(flagbuf);
if (n)
{
for (i=0; i < n; i++)
{
strcat(parabuf, sptr->name);
if (i < n - 1)
strcat(parabuf, " ");
}
sendto_channel_butserv_butone(chptr, &me, sptr, ":%s MODE %s +%s %s",
me.name, chptr->chname, flagbuf, parabuf);
}
}
tmp->flags &= ~CHFL_REJOINING; /* esthetics.. ;) */
}
}
/* set_channel_mlock()
*
* inputs - client, source, channel, params
* output -
* side effects - channel mlock is changed / MLOCK is propagated
*/
void set_channel_mlock(aClient *cptr, aClient *sptr, aChannel *chptr, const char *newmlock, int propagate)
{
if (chptr->mode_lock)
MyFree(chptr->mode_lock);
chptr->mode_lock = (newmlock != NULL) ? strdup(newmlock) : NULL;
if (propagate)
{
sendto_server(cptr,0, 0, ":%s MLOCK %lu %s :%s",
cptr->name, chptr->creationtime, chptr->chname,
BadPtr(chptr->mode_lock) ? "" : chptr->mode_lock);
}
}
/** Parse a channelmode line.
* @in pm A ParseMode struct, used to return values and to maintain internal state.
* @in modebuf_in Buffer pointing to mode characters (eg: +snk-l)
* @in parabuf_in Buffer pointing to all parameters (eg: key 123)
* @retval Returns 1 if we have valid data to return, 0 if at end of mode line.
* @example
*
* ParseMode pm;
* int ret;
* for (ret = parse_chanmode(&pm, modebuf, parabuf); ret; ret = parse_chanmode(&pm, NULL, NULL))
* {
* ircd_log(LOG_ERROR, "Got %c%c %s",
* pm.what == MODE_ADD ? '+' : '-',
* pm.modechar,
* pm.param ? pm.param : "");
* }
*/
int parse_chanmode(ParseMode *pm, char *modebuf_in, char *parabuf_in)
{
if (modebuf_in)
{
/* Initialize */
memset(pm, 0, sizeof(ParseMode));
pm->modebuf = modebuf_in;
pm->parabuf = parabuf_in;
pm->what = MODE_ADD;
}
while(1)
{
if (*pm->modebuf == '\0')
return 0;
else if (*pm->modebuf == '+')
{
pm->what = MODE_ADD;
pm->modebuf++;
continue;
}
else if (*pm->modebuf == '-')
{
pm->what = MODE_DEL;
pm->modebuf++;
continue;
}
else
{
aCtab *tab = &cFlagTab[0];
int i;
int eatparam = 0;
/* Set some defaults */
pm->extm = NULL;
pm->modechar = *pm->modebuf;
pm->param = NULL;
while (tab->mode != 0x0)
{
if (tab->flag == *pm->modebuf)
break;
tab++;
}
if (tab->mode)
{
/* INTERNAL MODE */
if (tab->parameters)
{
if ((pm->what == MODE_DEL) && (tab->flag == 'l'))
eatparam = 0; /* -l is special: no parameter required */
else
eatparam = 1; /* all other internal parameter modes do require a parameter on unset */
}
} else {
/* EXTENDED CHANNEL MODE */
int found = 0;
for (i=0; i <= Channelmode_highest; i++)
if (Channelmode_Table[i].flag == *pm->modebuf)
break;
if (!found)
{
/* Not found. Will be ignored, just move on.. */
pm->modebuf++;
continue;
}
pm->extm = &Channelmode_Table[i];
if (Channelmode_Table[i].paracount == 1)
{
if (pm->what == MODE_ADD)
eatparam = 1;
else if (Channelmode_Table[i].unset_with_param)
eatparam = 1;
/* else 0 (if MODE_DEL && !unset_with_param) */
}
}
if (eatparam)
{
/* Hungry.. */
if (pm->parabuf && *pm->parabuf)
{
char *start, *end;
for (; *pm->parabuf == ' '; pm->parabuf++); /* skip whitespace */
start = pm->parabuf;
if (*pm->parabuf == '\0')
{
pm->modebuf++;
continue; /* invalid, got mode but no parameter available */
}
end = strchr(start, ' ');
/* copy start .. end (where end may be null, then just copy all) */
if (end)
{
pm->parabuf = end + 1; /* point to next param, or \0 */
if (end - start + 1 > sizeof(pm->buf))
end = start + sizeof(pm->buf); /* 'never' reached */
strlcpy(pm->buf, start, end - start + 1);
}
else
{
strlcpy(pm->buf, start, sizeof(pm->buf));
pm->parabuf = pm->parabuf + strlen(pm->parabuf); /* point to \0 at end */
}
pm->param = pm->buf;
} else {
pm->modebuf++;
continue; /* invalid, got mode but no parameter available */
}
}
}
pm->modebuf++; /* advance pointer */
return 1;
}
}