#include "ircd_defs.h"
#include "ircd_features.h"
#include "ircd_log.h"
-#include "ircd_policy.h"
#include "ircd_reply.h"
#include "ircd_snprintf.h"
#include "ircd_string.h"
* Create a string of form "foo!bar@123.456.789.123" given foo, bar and the
* IP-number as the parameters. If NULL, they become "*".
*/
-#define NUI_BUFSIZE (NICKLEN + USERLEN + 16 + 3)
+#define NUI_BUFSIZE (NICKLEN + USERLEN + SOCKIPLEN + 4)
static char *make_nick_user_ip(char *ipbuf, char *nick, char *name,
- struct in_addr ip)
+ const struct irc_in_addr *ip)
{
- ircd_snprintf(0, ipbuf, NUI_BUFSIZE, "%s!%s@%s", nick, name,
- ircd_ntoa((const char*) &ip));
+ ircd_snprintf(0, ipbuf, NUI_BUFSIZE, "%s!%s@%s", nick, name, ircd_ntoa(ip));
return ipbuf;
}
* who then will educate them on the use of Apass/upass.
*/
+ if (feature_bool(FEAT_OPLEVELS)) {
if (TStime() - chptr->creationtime < 172800) /* Channel younger than 48 hours? */
schedule_destruct_event_1m(chptr); /* Get rid of it in approximately 4-5 minutes */
else
schedule_destruct_event_48h(chptr); /* Get rid of it in approximately 48 hours */
+ } else
+ destruct_channel(chptr);
return 0;
}
assert(0 != ban->value.ban.banstr);
strcpy(ban->value.ban.banstr, banid);
-#ifdef HEAD_IN_SAND_BANWHO
- if (IsServer(cptr))
+ if (IsServer(cptr) && feature_bool(FEAT_HIS_BANWHO))
DupString(ban->value.ban.who, cli_name(&me));
else
-#endif
DupString(ban->value.ban.who, cli_name(cptr));
assert(0 != ban->value.ban.who);
struct Membership* member)
{
struct SLink* tmp;
+ char tmphost[HOSTLEN + 1];
char nu_host[NUH_BUFSIZE];
char nu_realhost[NUH_BUFSIZE];
char nu_ip[NUI_BUFSIZE];
s = make_nick_user_host(nu_host, cli_name(cptr), (cli_user(cptr))->username,
(cli_user(cptr))->host);
- if (HasHiddenHost(cptr))
- sr = make_nick_user_host(nu_realhost, cli_name(cptr),
- (cli_user(cptr))->username,
- cli_user(cptr)->realhost);
+ if (IsAccount(cptr))
+ {
+ if (HasHiddenHost(cptr))
+ {
+ sr = make_nick_user_host(nu_realhost, cli_name(cptr),
+ (cli_user(cptr))->username,
+ cli_user(cptr)->realhost);
+ }
+ else
+ {
+ ircd_snprintf(0, tmphost, HOSTLEN, "%s.%s",
+ cli_user(cptr)->account, feature_str(FEAT_HIDDEN_HOST));
+ sr = make_nick_user_host(nu_realhost, cli_name(cptr),
+ cli_user(cptr)->username,
+ tmphost);
+ }
+ }
for (tmp = chptr->banlist; tmp; tmp = tmp->next) {
if ((tmp->flags & CHFL_BAN_IPMASK)) {
if (!ip_s)
ip_s = make_nick_user_ip(nu_ip, cli_name(cptr),
- (cli_user(cptr))->username, cli_ip(cptr));
+ (cli_user(cptr))->username, &cli_ip(cptr));
if (match(tmp->value.ban.banstr, ip_s) == 0)
break;
}
- else if (match(tmp->value.ban.banstr, s) == 0)
+ if (match(tmp->value.ban.banstr, s) == 0)
break;
else if (sr && match(tmp->value.ban.banstr, sr) == 0)
break;
member->prev_member->next_member = member->next_member;
else
member->channel->members = member->next_member;
-
+
+ /*
+ * If this is the last delayed-join user, may have to clear WASDELJOINS.
+ */
+ if (IsDelayedJoin(member))
+ CheckDelayedJoins(chptr);
+
/*
* unlink client channel list
*/
return 0;
}
-int member_can_send_to_channel(struct Membership* member)
+int member_can_send_to_channel(struct Membership* member, int reveal)
{
assert(0 != member);
*/
if (MyUser(member->user) && is_banned(member->user, member->channel, member))
return 0;
+
+ if (IsDelayedJoin(member) && reveal)
+ RevealDelayedJoin(member);
+
return 1;
}
-int client_can_send_to_channel(struct Client *cptr, struct Channel *chptr)
+int client_can_send_to_channel(struct Client *cptr, struct Channel *chptr, int reveal)
{
struct Membership *member;
assert(0 != cptr);
member = find_channel_member(cptr, chptr);
/*
- * You can't speak if your off channel, if the channel is modeless, or
- * +n (no external messages) or +m (moderated).
+ * You can't speak if you're off channel, and it is +n (no external messages)
+ * or +m (moderated).
*/
if (!member) {
if ((chptr->mode.mode & (MODE_NOPRIVMSGS|MODE_MODERATED)) ||
- IsModelessChannel(chptr->chname) ||
((chptr->mode.mode & MODE_REGONLY) && !IsAccount(cptr)))
return 0;
else
return !is_banned(cptr, chptr, NULL);
}
- return member_can_send_to_channel(member);
+ return member_can_send_to_channel(member, reveal);
}
/*
* find_no_nickchange_channel
- * if a member and not opped or voiced and banned
+ * if a member and not (opped or voiced) and (banned or moderated)
* return the name of the first channel banned on
*/
const char* find_no_nickchange_channel(struct Client* cptr)
struct Membership* member;
for (member = (cli_user(cptr))->channel; member;
member = member->next_channel) {
- if (!IsVoicedOrOpped(member) && is_banned(cptr, member->channel, member))
+ if (!IsVoicedOrOpped(member) &&
+ (is_banned(cptr, member->channel, member) ||
+ (member->channel->mode.mode & MODE_MODERATED)))
return member->channel->chname;
}
}
*mbuf++ = 'n';
if (chptr->mode.mode & MODE_REGONLY)
*mbuf++ = 'r';
+ if (chptr->mode.mode & MODE_DELJOINS)
+ *mbuf++ = 'D';
+ else if (MyUser(cptr) && (chptr->mode.mode & MODE_WASDELJOINS))
+ *mbuf++ = 'd';
if (chptr->mode.limit) {
*mbuf++ = 'l';
ircd_snprintf(0, pbuf, buflen, "%u", chptr->mode.limit);
int opped_members_index = 0;
struct Membership** opped_members = NULL;
int last_oplevel = 0;
+ int feat_oplevels = (chptr->mode.mode & MODE_APASS) != 0;
assert(0 != cptr);
assert(0 != chptr);
* Do we have a nick with a new mode ?
* Or are we starting a new BURST line?
*/
- if (new_mode)
+ if (new_mode || !feat_oplevels)
{
/*
* This means we are at the _first_ member that has only
tbuf[loc++] = 'v';
if (IsChanOp(member)) /* flag_cnt == 2 or 3 */
{
- /* append the absolute value of the oplevel */
- loc += ircd_snprintf(0, tbuf + loc, sizeof(tbuf) - loc, "%u", member->oplevel);
- last_oplevel = member->oplevel;
+ /* append the absolute value of the oplevel */
+ if (feat_oplevels)
+ loc += ircd_snprintf(0, tbuf + loc, sizeof(tbuf) - loc, "%u", last_oplevel = member->oplevel);
+ else
+ tbuf[loc++] = 'o';
}
tbuf[loc] = '\0';
msgq_append(&me, mb, tbuf);
that didn't fit (full==1) */
if (opped_members)
MyFree(opped_members);
+ if (feature_bool(FEAT_TOPIC_BURST) && (chptr->topic[0] != '\0'))
+ sendcmdto_one(&me, CMD_TOPIC, cptr, "%H %Tu %Tu :%s", chptr,
+ chptr->creationtime, chptr->topic_time, chptr->topic);
}
/*
int can_join(struct Client *sptr, struct Channel *chptr, char *key)
{
- struct SLink *lp;
int overrideJoin = 0;
/*
* Now a user CAN escape anything if invited -- Isomer
*/
- for (lp = (cli_user(sptr))->invited; lp; lp = lp->next)
- if (lp->value.chptr == chptr)
- return 0;
+ if (IsInvited(sptr, chptr))
+ return 0;
/* An oper can force a join on a local channel using "OVERRIDE" as the key.
a HACK(4) notice will be sent if he would not have been supposed
to join normally. */
if (IsLocalChannel(chptr->chname) && HasPriv(sptr, PRIV_WALK_LCHAN) &&
- !BadPtr(key) && compall("OVERRIDE",key) == 0)
+ !BadPtr(key) && compall("OVERRIDE",chptr->mode.key) != 0 &&
+ compall("OVERRIDE",key) == 0)
overrideJoin = MAGIC_OPER_OVERRIDE;
if (chptr->mode.mode & MODE_INVITEONLY)
{
for (; chptr; chptr = chptr->next)
{
- if (!cli_user(cptr) || (!(HasPriv(cptr, PRIV_LIST_CHAN) && IsAnOper(cptr)) &&
- SecretChannel(chptr) && !find_channel_member(cptr, chptr)))
+ if (!cli_user(cptr))
+ continue;
+ if (!(HasPriv(cptr, PRIV_LIST_CHAN) && IsAnOper(cptr)) &&
+ SecretChannel(chptr) && !find_channel_member(cptr, chptr))
continue;
if (chptr->users > args->min_users && chptr->users < args->max_users &&
chptr->creationtime > args->min_time &&
chptr->creationtime < args->max_time &&
- (!args->topic_limits || (*chptr->topic &&
+ (!(args->flags & LISTARG_TOPICLIMITS) || (*chptr->topic &&
chptr->topic_time > args->min_topic_time &&
chptr->topic_time < args->max_topic_time)))
{
- if (ShowChannel(cptr,chptr))
+ if ((args->flags & LISTARG_SHOWSECRET) || ShowChannel(cptr,chptr))
send_reply(cptr, RPL_LIST, chptr->chname, chptr->users,
chptr->topic);
chptr = chptr->next;
* of course, str2 is not NULL)
*/
static void
-build_string(char *strptr, int *strptr_i, char *str1, char *str2, char c)
+build_string(char *strptr, int *strptr_i, const char *str1,
+ const char *str2, char c)
{
if (c)
strptr[(*strptr_i)++] = c;
MODE_INVITEONLY, 'i',
MODE_NOPRIVMSGS, 'n',
MODE_REGONLY, 'r',
+ MODE_DELJOINS, 'D',
+ MODE_WASDELJOINS, 'd',
/* MODE_KEY, 'k', */
/* MODE_BAN, 'b', */
/* MODE_LIMIT, 'l', */
if (mbuf->mb_add == 0 && mbuf->mb_rem == 0 && mbuf->mb_count == 0)
return 0;
- /* Ok, if we were given the OPMODE flag, hide the source if its a user */
- if (mbuf->mb_dest & MODEBUF_DEST_OPMODE && !IsServer(mbuf->mb_source))
+ /* Ok, if we were given the OPMODE flag, or its a server, hide the source.
+ */
+ if (mbuf->mb_dest & MODEBUF_DEST_OPMODE || IsServer(mbuf->mb_source))
app_source = &me;
else
app_source = mbuf->mb_source;
if (mbuf->mb_dest & MODEBUF_DEST_HACK2)
sendto_opmask_butone(0, SNO_HACK2, "HACK(2): %s MODE %s %s%s%s%s%s%s "
"[%Tu]",
-#ifdef HEAD_IN_SAND_SNOTICES
- cli_name(mbuf->mb_source),
-#else
- cli_name(app_source),
-#endif
+ cli_name(feature_bool(FEAT_HIS_SNOTICES) ?
+ mbuf->mb_source : app_source),
mbuf->mb_channel->chname,
rembuf_i ? "-" : "", rembuf, addbuf_i ? "+" : "",
addbuf, remstr, addstr,
if (mbuf->mb_dest & MODEBUF_DEST_HACK3)
sendto_opmask_butone(0, SNO_HACK3, "BOUNCE or HACK(3): %s MODE %s "
"%s%s%s%s%s%s [%Tu]",
-#ifdef HEAD_IN_SAND_SNOTICES
- cli_name(mbuf->mb_source),
-#else
- cli_name(app_source),
-#endif
+ cli_name(feature_bool(FEAT_HIS_SNOTICES) ?
+ mbuf->mb_source : app_source),
mbuf->mb_channel->chname, rembuf_i ? "-" : "",
rembuf, addbuf_i ? "+" : "", addbuf, remstr, addstr,
mbuf->mb_channel->creationtime);
if (mbuf->mb_dest & MODEBUF_DEST_HACK4)
sendto_opmask_butone(0, SNO_HACK4, "HACK(4): %s MODE %s %s%s%s%s%s%s "
"[%Tu]",
-#ifdef HEAD_IN_SAND_SNOTICES
- cli_name(mbuf->mb_source),
-#else
- cli_name(app_source),
-#endif
+ cli_name(feature_bool(FEAT_HIS_SNOTICES) ?
+ mbuf->mb_source : app_source),
mbuf->mb_channel->chname,
rembuf_i ? "-" : "", rembuf, addbuf_i ? "+" : "",
addbuf, remstr, addstr,
addbuf_i ? "+" : "", addbuf, remstr, addstr);
if (mbuf->mb_dest & MODEBUF_DEST_CHANNEL)
- sendcmdto_channel_butserv_butone(app_source, CMD_MODE, mbuf->mb_channel, NULL,
+ sendcmdto_channel_butserv_butone(app_source, CMD_MODE, mbuf->mb_channel, NULL, 0,
"%H %s%s%s%s%s%s", mbuf->mb_channel,
rembuf_i ? "-" : "", rembuf,
addbuf_i ? "+" : "", addbuf, remstr, addstr);
assert(0 != chan);
assert(0 != dest);
+ if (IsLocalChannel(chan->chname)) dest &= ~MODEBUF_DEST_SERVER;
+
mbuf->mb_add = 0;
mbuf->mb_rem = 0;
mbuf->mb_source = source;
assert(0 != (mode & (MODE_ADD | MODE_DEL)));
mode &= (MODE_ADD | MODE_DEL | MODE_PRIVATE | MODE_SECRET | MODE_MODERATED |
- MODE_TOPICLIMIT | MODE_INVITEONLY | MODE_NOPRIVMSGS | MODE_REGONLY);
+ MODE_TOPICLIMIT | MODE_INVITEONLY | MODE_NOPRIVMSGS | MODE_REGONLY |
+ MODE_DELJOINS | MODE_WASDELJOINS);
if (!(mode & ~(MODE_ADD | MODE_DEL))) /* don't add empty modes... */
return;
int
modebuf_flush(struct ModeBuf *mbuf)
{
+ struct Membership *memb;
+
+ /* Check if MODE_WASDELJOINS should be set */
+ if (!(mbuf->mb_channel->mode.mode & (MODE_DELJOINS | MODE_WASDELJOINS))
+ && (mbuf->mb_rem & MODE_DELJOINS)) {
+ for (memb = mbuf->mb_channel->members; memb; memb = memb->next_member) {
+ if (IsDelayedJoin(memb)) {
+ mbuf->mb_channel->mode.mode |= MODE_WASDELJOINS;
+ mbuf->mb_add |= MODE_WASDELJOINS;
+ mbuf->mb_rem &= ~MODE_WASDELJOINS;
+ break;
+ }
+ }
+ }
+
return modebuf_flush_int(mbuf, 1);
}
/* MODE_BAN, 'b', */
MODE_LIMIT, 'l',
MODE_REGONLY, 'r',
+ MODE_DELJOINS, 'D',
0x0, 0x0
};
unsigned int add;
int i, bufpos = 0, len;
int *flag_p;
char *key = 0, limitbuf[20];
- char *apass, *upass;
+ char *apass = 0, *upass = 0;
assert(0 != mbuf);
assert(0 != buf);
state->parc--;
state->max_args--;
+ if ((int)t_limit<0) /* don't permit a negative limit */
+ return;
+
if (!(state->flags & MODE_PARSE_WIPEOUT) &&
(!t_limit || t_limit == state->chptr->mode.limit))
return;
return;
}
+ /* Can't remove a limit that's not there */
+ if (state->dir == MODE_DEL && !state->chptr->mode.limit)
+ return;
+
+ /* Skip if this is a burst and a lower limit than this is set already */
+ if ((state->flags & MODE_PARSE_BURST) &&
+ (state->chptr->mode.mode & flag_p[0]) &&
+ (state->chptr->mode.limit < t_limit))
+ return;
+
if (state->done & DONE_LIMIT) /* allow limit to be set only once */
return;
state->done |= DONE_LIMIT;
return;
state->done |= DONE_KEY;
- t_len = KEYLEN + 1;
+ t_len = KEYLEN;
/* clean up the key string */
s = t_str;
- while (*++s > ' ' && *s != ':' && --t_len)
- ;
+ while (*s > ' ' && *s != ':' && *s != ',' && t_len--)
+ s++;
*s = '\0';
if (!*t_str) { /* warn if empty */
if (!state->mbuf)
return;
+ /* Skip if this is a burst, we have a key already and the new key is
+ * after the old one alphabetically */
+ if ((state->flags & MODE_PARSE_BURST) &&
+ *(state->chptr->mode.key) &&
+ ircd_strcmp(state->chptr->mode.key, t_str) <= 0)
+ return;
+
/* can't add a key if one is set, nor can one remove the wrong key */
if (!(state->flags & MODE_PARSE_FORCE))
if ((state->dir == MODE_ADD && *state->chptr->mode.key) ||
} else if (!mmatch(t_str, ban->value.ban.banstr))
ban->flags |= MODE_DEL; /* mark ban for deletion: overlapping */
- if (!ban->next && (newban->flags & MODE_ADD)) {
+ if (!ban->next && (newban->flags & MODE_ADD))
+ {
ban->next = newban; /* add our ban with its flags */
break; /* get out of loop */
}
} else {
if (state->flags & MODE_PARSE_SET && MyUser(state->sptr) &&
(len > (feature_int(FEAT_AVBANLEN) * feature_int(FEAT_MAXBANS)) ||
- count >= feature_int(FEAT_MAXBANS))) {
+ count > feature_int(FEAT_MAXBANS))) {
send_reply(state->sptr, ERR_BANLISTFULL, state->chptr->chname,
ban->value.ban.banstr);
count--;
continue;
}
+ if (feature_bool(FEAT_OPLEVELS)) {
/* don't allow to deop members with an op level that is <= our own level */
if (state->sptr != state->cli_change[i].client /* but allow to deop oneself */
&& state->member
}
}
}
+ }
/* set op-level of member being opped */
if ((state->cli_change[i].flag & (MODE_ADD | MODE_CHANOP)) ==
SetOpLevel(member, old_level == MAXOPLEVEL ? MAXOPLEVEL : (old_level + level_increment));
}
- /* accumulate the change */
- modebuf_mode_client(state->mbuf, state->cli_change[i].flag,
- state->cli_change[i].client);
-
/* actually effect the change */
if (state->flags & MODE_PARSE_SET) {
if (state->cli_change[i].flag & MODE_ADD) {
+ if (IsDelayedJoin(member))
+ RevealDelayedJoin(member);
member->status |= (state->cli_change[i].flag &
(MODE_CHANOP | MODE_VOICE));
if (state->cli_change[i].flag & MODE_CHANOP)
member->status &= ~(state->cli_change[i].flag &
(MODE_CHANOP | MODE_VOICE));
}
+
+ /* accumulate the change */
+ modebuf_mode_client(state->mbuf, state->cli_change[i].flag,
+ state->cli_change[i].client);
} /* for (i = 0; state->cli_change[i].flags; i++) */
}
state->add &= ~MODE_SECRET;
state->del |= MODE_SECRET;
}
+ if (flag_p[0] & MODE_DELJOINS) {
+ state->add &= ~MODE_WASDELJOINS;
+ state->del |= MODE_WASDELJOINS;
+ }
} else {
state->add &= ~flag_p[0];
state->del |= flag_p[0];
MODE_BAN, 'b',
MODE_LIMIT, 'l',
MODE_REGONLY, 'r',
+ MODE_DELJOINS, 'D',
MODE_ADD, '+',
MODE_DEL, '-',
0x0, 0x0
break;
case 'A': /* deal with Admin passes */
+ if (feature_bool(FEAT_OPLEVELS))
mode_parse_apass(&state, flag_p);
break;
case 'u': /* deal with user passes */
+ if (feature_bool(FEAT_OPLEVELS))
mode_parse_upass(&state, flag_p);
break;
joinbuf_join(struct JoinBuf *jbuf, struct Channel *chan, unsigned int flags)
{
unsigned int len;
+ int is_local;
assert(0 != jbuf);
return;
}
+ is_local = IsLocalChannel(chan->chname);
+
if (jbuf->jb_type == JOINBUF_TYPE_PART ||
jbuf->jb_type == JOINBUF_TYPE_PARTALL) {
+ struct Membership *member = find_member_link(chan, jbuf->jb_source);
+ if (IsUserParting(member))
+ return;
+ SetUserParting(member);
+
/* Send notification to channel */
- if (!(flags & CHFL_ZOMBIE))
- sendcmdto_channel_butserv_butone(jbuf->jb_source, CMD_PART, chan, NULL,
+ if (!(flags & (CHFL_ZOMBIE | CHFL_DELAYED)))
+ sendcmdto_channel_butserv_butone(jbuf->jb_source, CMD_PART, chan, NULL, 0,
(flags & CHFL_BANNED || !jbuf->jb_comment) ?
":%H" : "%H :%s", chan, jbuf->jb_comment);
else if (MyUser(jbuf->jb_source))
* the original m_part.c */
if (jbuf->jb_type == JOINBUF_TYPE_PARTALL ||
- IsLocalChannel(chan->chname)) /* got to remove user here */
+ is_local) /* got to remove user here */
remove_user_from_channel(jbuf->jb_source, chan);
} else {
/* Add user to channel */
- add_user_to_channel(chan, jbuf->jb_source, flags, 0);
+ if ((chan->mode.mode & MODE_DELJOINS) && !(flags & CHFL_VOICED_OR_OPPED))
+ add_user_to_channel(chan, jbuf->jb_source, flags | CHFL_DELAYED, 0);
+ else
+ add_user_to_channel(chan, jbuf->jb_source, flags, 0);
/* send notification to all servers */
- if (jbuf->jb_type != JOINBUF_TYPE_CREATE && !IsLocalChannel(chan->chname))
+ if (jbuf->jb_type != JOINBUF_TYPE_CREATE && !is_local)
sendcmdto_serv_butone(jbuf->jb_source, CMD_JOIN, jbuf->jb_connect,
"%H %Tu", chan, chan->creationtime);
- /* Send the notification to the channel */
- sendcmdto_channel_butserv_butone(jbuf->jb_source, CMD_JOIN, chan, NULL, ":%H", chan);
+ if (!((chan->mode.mode & MODE_DELJOINS) && !(flags & CHFL_VOICED_OR_OPPED))) {
+ /* Send the notification to the channel */
+ sendcmdto_channel_butserv_butone(jbuf->jb_source, CMD_JOIN, chan, NULL, 0, "%H", chan);
- /* send an op, too, if needed */
- if (!MyUser(jbuf->jb_source) && jbuf->jb_type == JOINBUF_TYPE_CREATE &&
- !IsModelessChannel(chan->chname))
- sendcmdto_channel_butserv_butone(jbuf->jb_source, CMD_MODE, chan, NULL, "%H +o %C",
- chan, jbuf->jb_source);
+ /* send an op, too, if needed */
+ if (!MyUser(jbuf->jb_source) && jbuf->jb_type == JOINBUF_TYPE_CREATE)
+ sendcmdto_channel_butserv_butone(jbuf->jb_source, CMD_MODE, chan, NULL, 0, "%H +o %C",
+ chan, jbuf->jb_source);
+ } else if (MyUser(jbuf->jb_source))
+ sendcmdto_one(jbuf->jb_source, CMD_JOIN, jbuf->jb_source, ":%H", chan);
}
- if (jbuf->jb_type == JOINBUF_TYPE_PARTALL || IsLocalChannel(chan->chname))
+ if (jbuf->jb_type == JOINBUF_TYPE_PARTALL ||
+ jbuf->jb_type == JOINBUF_TYPE_JOIN || is_local)
return; /* don't send to remote */
/* figure out if channel name will cause buffer to be overflowed */
return 0;
}
+
+/* Returns TRUE (1) if client is invited, FALSE (0) if not */
+int IsInvited(struct Client* cptr, const void* chptr)
+{
+ struct SLink *lp;
+
+ for (lp = (cli_user(cptr))->invited; lp; lp = lp->next)
+ if (lp->value.chptr == chptr)
+ return 1;
+ return 0;
+}
+
+/* RevealDelayedJoin: sends a join for a hidden user */
+
+void RevealDelayedJoin(struct Membership *member) {
+ ClearDelayedJoin(member);
+ sendcmdto_channel_butserv_butone(member->user, CMD_JOIN, member->channel, member->user, 0, ":%H",
+ member->channel);
+ CheckDelayedJoins(member->channel);
+}
+
+/* CheckDelayedJoins: checks and clear +d if necessary */
+
+void CheckDelayedJoins(struct Channel *chan) {
+ struct Membership *memb2;
+
+ if (chan->mode.mode & MODE_WASDELJOINS) {
+ for (memb2=chan->members;memb2;memb2=memb2->next_member)
+ if (IsDelayedJoin(memb2))
+ break;
+
+ if (!memb2) {
+ /* clear +d */
+ chan->mode.mode &= ~MODE_WASDELJOINS;
+ sendcmdto_channel_butserv_butone(&me, CMD_MODE, chan, NULL, 0,
+ "%H -d", chan);
+ }
+ }
+}