From: Perry Lorier Date: Wed, 13 Mar 2002 09:19:21 +0000 (+0000) Subject: Author: Carlo Wood (run@alinoe.com> (Via Isomer> X-Git-Url: http://git.pk910.de/?p=ircu2.10.12-pk.git;a=commitdiff_plain;h=2f1b0ed8f610deeb5743b041835a81d96b2275fb Author: Carlo Wood (run@alinoe.com> (Via Isomer> Log message: This patch finishes everything described on http://www.xs4all.nl/~carlo17/irc/cpass.html Todo: - deal with brute force (dictionary) attacks on apass. - backwards compatibility (two phase upgrade). - education issues (add URLs in messages). git-svn-id: file:///home/klmitch/undernet-ircu/undernet-ircu-svn/ircu2/trunk@673 c9e4aea6-c8fd-4c43-8297-357d70d61c8c --- diff --git a/ChangeLog b/ChangeLog index d950668..d2c7544 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,48 @@ +2002-03-13 Carlo Wood + + * include/channel.h: CHFL_CHANNEL_MANAGER, new local + channel flag set when someone creates a channel or joins + using the Apass. IsChannelManager(), SetChannelManager(): + macros to manipulate new channel flag. + channel_modes: Added new argument to avoid calling + find_member_link more often than needed. + + * include/numeric.h: RPL_APASSWARN, ERR_NOTLOWEROPLEVEL, + ERR_NOTMANAGER, ERR_CHANSECURED, ERR_UPASSSET, + ERR_UPASSNOTSET: new numeric replies. + + * ircd/channel.c: is_level0_op: removed. + member_can_send_to_channel: disallow channel manager + to talk. channel_modes: show upass to level0 ops. + mode_parse_upass: Only the channel manager is allowed + to change the upass. Only allow to set upass when apass + is also set. mode_parse_apass: Don't allow to change the + Apass if the channel is older than 48 hours. Only allow + to remove the apass when upass is not set. Send clear + warnings regarding the importance of apass. + mode_process_clients: Don't change the oplevel of an opped + member in a channel where upass is not set. + + * ircd/destruct_event.c: exec_expired_destruct_events: + Bug fix: send DESTRUCT message when destructing a channel. + + * ircd/m_destruct.c: ms_destruct: Bug fix: use self as + prefix for DESTRUCT message. + + * ircd/m_join.c: m_join: Handle apass and upass. + + * ircd/m_kick.c: m_kick: Don't allow to kick member with + a higher or equal op-level. + + * ircd/m_mode.c: m_mode: Now pass member to channel_modes. + ms_mode: Allow server to do modes on channels with apass + set. + + * ircd/s_err.c: RPL_APASSWARN, ERR_NOTLOWEROPLEVEL, + ERR_NOTMANAGER, ERR_CHANSECURED, ERR_UPASSSET, + ERR_UPASSNOTSET: new numeric replies. + + 2002-03-10 Joseph Bongaarts * ircd/m_kill.c: Last of the bug fixes for do_kill() diff --git a/include/channel.h b/include/channel.h index 131e11d..861a010 100644 --- a/include/channel.h +++ b/include/channel.h @@ -69,6 +69,7 @@ struct Client; #define CHFL_SILENCE_IPMASK 0x2000 /* silence mask is an IP-number mask */ #define CHFL_BURST_ALREADY_OPPED 0x04000 /* In oob BURST, but was already joined and opped */ #define CHFL_BURST_ALREADY_VOICED 0x08000 /* In oob BURST, but was already joined and voiced */ +#define CHFL_CHANNEL_MANAGER 0x10000 /* Set when creating channel or using Apass */ #define CHFL_OVERLAP (CHFL_CHANOP | CHFL_VOICE) #define CHFL_BANVALIDMASK (CHFL_BANVALID | CHFL_BANNED) @@ -186,6 +187,7 @@ struct Membership { #define IsServOpOk(x) ((x)->status & CHFL_SERVOPOK) #define IsBurstJoined(x) ((x)->status & CHFL_BURST_JOINED) #define IsVoicedOrOpped(x) ((x)->status & CHFL_VOICED_OR_OPPED) +#define IsChannelManager(x) ((x)->status & CHFL_CHANNEL_MANAGER) #define SetBanned(x) ((x)->status |= CHFL_BANNED) #define SetBanValid(x) ((x)->status |= CHFL_BANVALID) @@ -193,6 +195,7 @@ struct Membership { #define SetServOpOk(x) ((x)->status |= CHFL_SERVOPOK) #define SetBurstJoined(x) ((x)->status |= CHFL_BURST_JOINED) #define SetZombie(x) ((x)->status |= CHFL_ZOMBIE) +#define SetChannelManager(x) ((x)->status |= CHFL_CHANNEL_MANAGER) #define SetOpLevel(x, v) (void)((x)->oplevel = (v)) #define ClearBanned(x) ((x)->status &= ~CHFL_BANNED) @@ -299,7 +302,8 @@ extern int LocalChanOperMode; */ extern void clean_channelname(char* name); extern void channel_modes(struct Client *cptr, char *mbuf, char *pbuf, - int buflen, struct Channel *chptr); + int buflen, struct Channel *chptr, + struct Membership *member); extern int set_mode(struct Client* cptr, struct Client* sptr, struct Channel* chptr, int parc, char* parv[], char* mbuf, char* pbuf, char* npbuf, int* badop); diff --git a/include/numeric.h b/include/numeric.h index 40343af..f4c2f48 100644 --- a/include/numeric.h +++ b/include/numeric.h @@ -63,6 +63,7 @@ extern const struct Numeric* get_error_numeric(int err); #define RPL_MAP 15 /* Undernet extension */ #define RPL_MAPMORE 16 /* Undernet extension */ #define RPL_MAPEND 17 /* Undernet extension */ +#define RPL_APASSWARN 30 /* Undernet extension */ /* RPL_YOURID 42 IRCnet extension */ /* RPL_ATTEMPTINGJUNC 50 aircd extension */ /* RPL_ATTEMPTINGREROUTE 51 aircd extension */ @@ -444,8 +445,12 @@ extern const struct Numeric* get_error_numeric(int err); ERR_WHOSYNTAX 522 dalnet ERR_WHOLIMEXCEED 523 dalnet */ -#define ERR_NOTLOWEROPLEVEL 550 -#define ERR_LASTERROR 551 +#define ERR_NOTLOWEROPLEVEL 550 /* Undernet extension */ +#define ERR_NOTMANAGER 551 /* Undernet extension */ +#define ERR_CHANSECURED 552 /* Undernet extension */ +#define ERR_UPASSSET 553 /* Undernet extension */ +#define ERR_UPASSNOTSET 554 /* Undernet extension */ +#define ERR_LASTERROR 555 /* RPL_LOGON 600 dalnet,unreal RPL_LOGOFF 601 dalnet,unreal diff --git a/ircd/channel.c b/ircd/channel.c index 1c3d491..a3a7617 100644 --- a/ircd/channel.c +++ b/ircd/channel.c @@ -621,11 +621,6 @@ int is_chan_op(struct Client *cptr, struct Channel *chptr) return 0; } -int is_level0_op(struct Client *cptr, struct Channel *chptr) -{ - return 0; -} - int is_zombie(struct Client *cptr, struct Channel *chptr) { struct Membership* member; @@ -652,6 +647,10 @@ int member_can_send_to_channel(struct Membership* member) { assert(0 != member); + /* Discourage using the Apass to get op. They should use the upass. */ + if (IsChannelManager(member) && *member->channel->mode.upass) + return 0; + if (IsVoicedOrOpped(member)) return 1; /* @@ -721,7 +720,7 @@ const char* find_no_nickchange_channel(struct Client* cptr) * with the parameters in pbuf. */ void channel_modes(struct Client *cptr, char *mbuf, char *pbuf, int buflen, - struct Channel *chptr) + struct Channel *chptr, struct Membership *member) { int previous_parameter = 0; @@ -774,7 +773,7 @@ void channel_modes(struct Client *cptr, char *mbuf, char *pbuf, int buflen, *mbuf++ = 'u'; if (previous_parameter) strcat(pbuf, " "); - if (is_level0_op(cptr, chptr) || IsServer(cptr)) { + if (IsServer(cptr) || (member && IsChanOp(member) && OpLevel(member) == 0)) { strcat(pbuf, chptr->mode.upass); } else strcat(pbuf, "*"); @@ -824,7 +823,7 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr) lp2 = chptr->banlist; *modebuf = *parabuf = '\0'; - channel_modes(cptr, modebuf, parabuf, sizeof(parabuf), chptr); + channel_modes(cptr, modebuf, parabuf, sizeof(parabuf), chptr, 0); for (first = 1; full; first = 0) /* Loop for multiple messages */ { @@ -2255,6 +2254,20 @@ mode_parse_upass(struct ParseState *state, int *flag_p) return; } + /* If they are not the channel manager, they are not allowed to change it */ + if (MyUser(state->sptr) && !IsChannelManager(state->member)) { + if (*state->chptr->mode.apass) { + send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname, + "Use /JOIN", state->chptr->chname, "."); + } else { + send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname, + "Re-create the channel. The channel must be *empty* for", + TStime() - state->chptr->creationtime >= 171000 ? "48 contigious hours" : "a minute or two", + "before it can be recreated."); + } + return; + } + if (state->done & DONE_UPASS) /* allow upass to be set only once */ return; state->done |= DONE_UPASS; @@ -2277,8 +2290,13 @@ mode_parse_upass(struct ParseState *state, int *flag_p) if (!state->mbuf) return; - /* can't add a upass if one is set, nor can one remove the wrong upass */ if (!(state->flags & MODE_PARSE_FORCE)) + /* can't add the upass while apass is not set */ + if (state->dir == MODE_ADD && !*state->chptr->mode.apass) { + send_reply(state->sptr, ERR_UPASSNOTSET, state->chptr->chname, state->chptr->chname); + return; + } + /* can't add a upass if one is set, nor can one remove the wrong upass */ if ((state->dir == MODE_ADD && *state->chptr->mode.upass) || (state->dir == MODE_DEL && ircd_strcmp(state->chptr->mode.upass, t_str))) { @@ -2336,6 +2354,25 @@ mode_parse_apass(struct ParseState *state, int *flag_p) return; } + /* Don't allow to change the Apass if the channel is older than 48 hours. */ + if (TStime() - state->chptr->creationtime >= 172800 && !IsAnOper(state->sptr)) { + send_reply(state->sptr, ERR_CHANSECURED, state->chptr->chname); + return; + } + + /* If they are not the channel manager, they are not allowed to change it */ + if (MyUser(state->sptr) && !IsChannelManager(state->member)) { + if (*state->chptr->mode.apass) { + send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname, + "Use /JOIN", state->chptr->chname, "."); + } else { + send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname, + "Re-create the channel. The channel must be *empty* for", + "at least a whole minute", "before it can be recreated."); + } + return; + } + if (state->done & DONE_APASS) /* allow apass to be set only once */ return; state->done |= DONE_APASS; @@ -2358,14 +2395,19 @@ mode_parse_apass(struct ParseState *state, int *flag_p) if (!state->mbuf) return; - /* can't add a apass if one is set, nor can one remove the wrong apass */ - if (!(state->flags & MODE_PARSE_FORCE)) + if (!(state->flags & MODE_PARSE_FORCE)) { + /* can't remove the apass while upass is still set */ + if (state->dir == MODE_DEL && *state->chptr->mode.upass) { + send_reply(state->sptr, ERR_UPASSSET, state->chptr->chname, state->chptr->chname); + return; + } + /* can't add an apass if one is set, nor can one remove the wrong apass */ if ((state->dir == MODE_ADD && *state->chptr->mode.apass) || - (state->dir == MODE_DEL && - ircd_strcmp(state->chptr->mode.apass, t_str))) { + (state->dir == MODE_DEL && ircd_strcmp(state->chptr->mode.apass, t_str))) { send_reply(state->sptr, ERR_KEYSET, state->chptr->chname); return; } + } if (!(state->flags & MODE_PARSE_WIPEOUT) && state->dir == MODE_ADD && !ircd_strcmp(state->chptr->mode.apass, t_str)) @@ -2381,10 +2423,30 @@ mode_parse_apass(struct ParseState *state, int *flag_p) modebuf_mode_string(state->mbuf, state->dir | flag_p[0], t_str, 0); if (state->flags & MODE_PARSE_SET) { - if (state->dir == MODE_ADD) /* set the new apass */ + if (state->dir == MODE_ADD) { /* set the new apass */ + /* Make it VERY clear to the user that this is a one-time password */ ircd_strncpy(state->chptr->mode.apass, t_str, PASSLEN); - else /* remove the old apass */ + if (MyUser(state->sptr)) { + send_reply(state->sptr, RPL_APASSWARN, + "Channel Admin password (+A) set to '", state->chptr->mode.apass, "'. ", + "Are you SURE you want to use this as Admin password? ", + "You will NOT be able to change this password anymore once the channel is more than 48 hours old!"); + send_reply(state->sptr, RPL_APASSWARN, + "Use \"/MODE ", state->chptr->chname, " -A ", state->chptr->mode.apass, + "\" to remove the password and then immediatly set a new one. " + "IMPORTANT: YOU CANNOT RECOVER THIS PASSWORD, EVER; " + "WRITE THE PASSWORD DOWN (don't store this rescue password on disk)! " + "Now set the channel user password (+u)."); + } + } else { /* remove the old apass */ *state->chptr->mode.apass = '\0'; + if (MyUser(state->sptr)) + send_reply(state->sptr, RPL_APASSWARN, + "WARNING: You removed the channel Admin password MODE (+A). ", + "If you would disconnect or leave the channel without setting a new password then you will ", + "not be able to set it again and lose ownership of this channel! ", + "SET A NEW PASSWORD NOW!", ""); + } } } @@ -2730,8 +2792,13 @@ mode_process_clients(struct ParseState *state) /* set op-level of member being opped */ if ((state->cli_change[i].flag & (MODE_ADD | MODE_CHANOP)) == (MODE_ADD | MODE_CHANOP)) { - int old_level = (state->member == NULL) ? -1 : OpLevel(state->member); - SetOpLevel(member, old_level == MAXOPLEVEL ? MAXOPLEVEL : (old_level + 1)); + /* If on a channel with upass set, someone with level x gives ops to someone else, + then that person gets level x-1. On other channels, where upass is not set, + the level stays the same. */ + int level_increment = *state->chptr->mode.upass ? 1 : 0; + /* Someone being opped by a server gets op-level 0 */ + int old_level = (state->member == NULL) ? -level_increment : OpLevel(state->member); + SetOpLevel(member, old_level == MAXOPLEVEL ? MAXOPLEVEL : (old_level + level_increment)); } /* accumulate the change */ diff --git a/ircd/destruct_event.c b/ircd/destruct_event.c index a7412ff..799e6bd 100644 --- a/ircd/destruct_event.c +++ b/ircd/destruct_event.c @@ -25,6 +25,8 @@ #include "ircd_alloc.h" #include "ircd.h" #include "ircd_events.h" +#include "send.h" +#include "msg.h" #include #include @@ -126,6 +128,8 @@ void exec_expired_destruct_events(struct Event* ev) while (*list_bottom && TStime() >= (*list_bottom)->expires) { struct Channel* chptr = (*list_bottom)->chptr; + /* Send DESTRUCT message */ + sendcmdto_serv_butone(&me, CMD_DESTRUCT, 0, "%s %Tu", chptr->chname, chptr->creationtime); remove_destruct_event(chptr); destruct_channel(chptr); } diff --git a/ircd/m_destruct.c b/ircd/m_destruct.c index a81c6d8..4104af1 100644 --- a/ircd/m_destruct.c +++ b/ircd/m_destruct.c @@ -117,7 +117,7 @@ int ms_destruct(struct Client* cptr, struct Client* sptr, int parc, char* parv[] } /* Pass on DESTRUCT message and ALSO bounce it back! */ - sendcmdto_serv_butone(sptr, CMD_DESTRUCT, 0, "%s %Tu", parv[1], chanTS); + sendcmdto_serv_butone(&me, CMD_DESTRUCT, 0, "%s %Tu", parv[1], chanTS); /* Remove the empty channel. */ remove_destruct_event(chptr); diff --git a/ircd/m_join.c b/ircd/m_join.c index 50ddfc7..ef5c44a 100644 --- a/ircd/m_join.c +++ b/ircd/m_join.c @@ -167,6 +167,7 @@ int m_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) struct Channel *chptr; struct JoinBuf join; struct JoinBuf create; + struct ModeBuf mbuf; struct Gline *gline; unsigned int flags = 0; int i; @@ -219,9 +220,24 @@ int m_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) } if (chptr) { + int is_level0_op = 0; + if (!BadPtr(keys) && *chptr->mode.apass) { + /* Don't use compall for the apass, only a single key is allowed. + Test Apass first in case someone set Apass and upass equal. */ + if (strcmp(chptr->mode.apass, keys) == 0) { + is_level0_op = 1; + flags &= ~CHFL_DEOPPED; + flags |= CHFL_CHANOP | CHFL_CHANNEL_MANAGER; + } + else if (*chptr->mode.upass && strcmp(chptr->mode.upass, keys) == 0) { + is_level0_op = 1; + flags &= ~CHFL_DEOPPED; + flags |= CHFL_CHANOP; + } + } if (check_target_limit(sptr, chptr, chptr->chname, 0)) continue; /* exceeded target limit */ - else if ((i = can_join(sptr, chptr, keys))) { + else if (!is_level0_op && (i = can_join(sptr, chptr, keys))) { if (i > MAGIC_OPER_OVERRIDE) { /* oper overrode mode */ switch (i - MAGIC_OPER_OVERRIDE) { case ERR_CHANNELISFULL: /* figure out which mode */ @@ -251,6 +267,16 @@ int m_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) } /* else if ((i = can_join(sptr, chptr, keys))) */ joinbuf_join(&join, chptr, flags); + if (is_level0_op) + { + joinbuf_flush(&join); + modebuf_init(&mbuf, &me, cptr, chptr, + MODEBUF_DEST_CHANNEL | /* Send mode to channel */ + MODEBUF_DEST_SERVER); /* And send it to the other servers */ + modebuf_mode_client(&mbuf, + MODE_ADD | MODE_CHANOP, sptr); /* Give ops to the level0 op */ + modebuf_flush(&mbuf); + } } else if (!(chptr = get_channel(sptr, name, CGT_CREATE))) continue; /* couldn't get channel */ else if (check_target_limit(sptr, chptr, chptr->chname, 1)) { @@ -259,7 +285,7 @@ int m_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) destruct_channel(chptr); /* created it... */ continue; } else - joinbuf_join(&create, chptr, flags); + joinbuf_join(&create, chptr, flags | CHFL_CHANNEL_MANAGER); del_invite(sptr, chptr); diff --git a/ircd/m_kick.c b/ircd/m_kick.c index 51c848e..4a72005 100644 --- a/ircd/m_kick.c +++ b/ircd/m_kick.c @@ -107,6 +107,7 @@ int m_kick(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) struct Client *who; struct Channel *chptr; struct Membership *member = 0; + struct Membership* member2; char *name, *comment; cli_flags(sptr) &= ~FLAGS_TS8; @@ -120,7 +121,8 @@ int m_kick(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) if (!(chptr = get_channel(sptr, name, CGT_NO_CREATE))) return send_reply(sptr, ERR_NOSUCHCHANNEL, name); - if (!is_chan_op(sptr, chptr) || IsModelessChannel(name)) + if (!(member2 = find_member_link(chptr, sptr)) || IsZombie(member2) + || !IsChanOp(member2) || IsModelessChannel(name)) return send_reply(sptr, ERR_CHANOPRIVSNEEDED, name); if (!(who = find_chasing(sptr, parv[2], 0))) @@ -138,6 +140,12 @@ int m_kick(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) if (!(member = find_member_link(chptr, who)) || IsZombie(member)) return send_reply(sptr, ERR_USERNOTINCHANNEL, cli_name(who), chptr->chname); + /* Don't allow to kick member with a higher or equal op-level */ + if (OpLevel(member) <= OpLevel(member2)) + return send_reply(sptr, ERR_NOTLOWEROPLEVEL, cli_name(who), chptr->chname, + OpLevel(member2), OpLevel(member), "kick", + OpLevel(member) == OpLevel(member2) ? "the same" : "a higher"); + /* We rely on ircd_snprintf to truncate the comment */ comment = EmptyString(parv[parc - 1]) ? parv[0] : parv[parc - 1]; diff --git a/ircd/m_mode.c b/ircd/m_mode.c index fc77ac7..474f39b 100644 --- a/ircd/m_mode.c +++ b/ircd/m_mode.c @@ -118,19 +118,21 @@ m_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) cli_flags(sptr) &= ~FLAGS_TS8; + member = find_member_link(chptr, sptr); + if (parc < 3) { char modebuf[MODEBUFLEN]; char parabuf[MODEBUFLEN]; *modebuf = *parabuf = '\0'; modebuf[1] = '\0'; - channel_modes(sptr, modebuf, parabuf, sizeof(parabuf), chptr); + channel_modes(sptr, modebuf, parabuf, sizeof(parabuf), chptr, member); send_reply(sptr, RPL_CHANNELMODEIS, chptr->chname, modebuf, parabuf); send_reply(sptr, RPL_CREATIONTIME, chptr->chname, chptr->creationtime); return 0; } - if (!(member = find_member_link(chptr, sptr)) || !IsChanOp(member)) { + if (!member || !IsChanOp(member)) { if (IsLocalChannel(chptr->chname) && HasPriv(sptr, PRIV_MODE_LCHAN)) { modebuf_init(&mbuf, sptr, cptr, chptr, (MODEBUF_DEST_CHANNEL | /* Send mode to channel */ @@ -178,10 +180,13 @@ ms_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) MODEBUF_DEST_SERVER | /* Send mode to servers */ MODEBUF_DEST_HACK4)); /* Send a HACK(4) message */ else + /* Servers need to be able to op people who join using the Apass + * or upass, therefore we accept modes for channels with an Apass + * without generating a HACK3. */ modebuf_init(&mbuf, sptr, cptr, chptr, (MODEBUF_DEST_CHANNEL | /* Send mode to clients */ - MODEBUF_DEST_SERVER | /* Send mode to servers */ - MODEBUF_DEST_HACK3)); /* Send a HACK(3) message */ + MODEBUF_DEST_SERVER | /* Send mode to servers */ + (*chptr->mode.apass ? 0 : MODEBUF_DEST_HACK3))); mode_parse(&mbuf, cptr, sptr, chptr, parc - 2, parv + 2, (MODE_PARSE_SET | /* Set the mode */ diff --git a/ircd/s_err.c b/ircd/s_err.c index 5ad955c..c9f6296 100644 --- a/ircd/s_err.c +++ b/ircd/s_err.c @@ -92,7 +92,7 @@ static Numeric replyTable[] = { /* 029 */ { 0 }, /* 030 */ - { 0 }, + { RPL_APASSWARN, ":%s%s%s%s%s", "030" }, /* 031 */ { 0 }, /* 032 */ @@ -1134,13 +1134,13 @@ static Numeric replyTable[] = { /* 550 */ { ERR_NOTLOWEROPLEVEL, "%s %s %hu %hu :Cannot %s someone with %s op-level", "550" }, /* 551 */ - { 0 }, + { ERR_NOTMANAGER, "%s :You must be channel Admin to add or remove a password. %s %s %s", "551" }, /* 552 */ - { 0 }, + { ERR_CHANSECURED, "%s :Channel is older than 48 hours and secured. Cannot change Admin pass anymore", "552" }, /* 553 */ - { 0 }, + { ERR_UPASSSET, "%s :Cannot remove Admin pass (+A) while User pass (+u) is still set. First use /MODE %s -u ", "553" }, /* 554 */ - { 0 }, + { ERR_UPASSNOTSET, "%s :Cannot set user pass (+u) while Admin pass (+A) is not set. First use /MODE %s +A ", "554" }, /* 555 */ { 0 }, /* 556 */