X-Git-Url: http://git.pk910.de/?a=blobdiff_plain;f=ircd%2Fchannel.c;h=9de97d3755d5f65c516c8e6bfc2eeab9e2a74d64;hb=73286bcf466860bb2d095e97b1a16e8a135b51d3;hp=decfd78975f7d8137083d5b1216403de61821dac;hpb=bf31de248f88b4c7311d2c1f199944b0dde605da;p=ircu2.10.12-pk.git diff --git a/ircd/channel.c b/ircd/channel.c index decfd78..9de97d3 100644 --- a/ircd/channel.c +++ b/ircd/channel.c @@ -68,6 +68,8 @@ static unsigned int membershipAllocCount; static struct Membership* membershipFreeList; /** Freelist for struct Ban*'s */ static struct Ban* free_bans; +/** Freelist for struct MemberFlood*'s */ +static struct MemberFlood* free_MemberFlood; /** Number of ban structures allocated. */ static size_t bans_alloc; /** Number of ban structures in use. */ @@ -489,6 +491,7 @@ void add_user_to_channel(struct Channel* chptr, struct Client* who, member->user = who; member->channel = chptr; member->status = flags; + member->flood = NULL; SetOpLevel(member, oplevel); member->next_member = chptr->members; @@ -549,6 +552,8 @@ static int remove_member_from_channel(struct Membership* member) --(cli_user(member->user))->joined; + member->flood = NULL; + member->next_member = membershipFreeList; membershipFreeList = member; @@ -891,6 +896,13 @@ void channel_modes(struct Client *cptr, char *mbuf, char *pbuf, int buflen, strcat(pbuf, chptr->mode.altchan); previous_parameter = 1; } + if (*chptr->mode.noflood) { + *mbuf++ = 'f'; + if (previous_parameter) + strcat(pbuf, " "); + strcat(pbuf, chptr->mode.noflood); + previous_parameter = 1; + } if (*chptr->mode.key) { *mbuf++ = 'k'; if (previous_parameter) @@ -1268,36 +1280,12 @@ static void send_ban_list(struct Client* cptr, struct Channel* chptr) assert(0 != cptr); assert(0 != chptr); - for (lp = chptr->banlist; lp; lp = lp->next) { - if(!(lp->flags & BAN_EXCEPTION)) - send_reply(cptr, RPL_BANLIST, chptr->chname, lp->banstr, - lp->who, lp->when); - } + for (lp = chptr->banlist; lp; lp = lp->next) + send_reply(cptr, RPL_BANLIST, chptr->chname, lp->banstr, lp->who, lp->when); send_reply(cptr, RPL_ENDOFBANLIST, chptr->chname); } -/** send an exceptionlist to a client for a channel - * - * @param cptr Client to send the exceptionlist to. - * @param chptr Channel whose exceptionlist to send. - */ -static void send_exception_list(struct Client* cptr, struct Channel* chptr) -{ - struct Ban* lp; - - assert(0 != cptr); - assert(0 != chptr); - - for (lp = chptr->banlist; lp; lp = lp->next) { - if(lp->flags & BAN_EXCEPTION) - send_reply(cptr, RPL_EXCEPTIONLIST, chptr->chname, lp->banstr, - lp->who, lp->when); - } - - send_reply(cptr, RPL_ENDOFEXCEPTIONLIST, chptr->chname); -} - /** Get a channel block, creating if necessary. * Get Channel block for chname (and allocate a new channel * block, if it didn't exists before). @@ -1599,8 +1587,8 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all) MODE_PERSIST, 'z', MODE_NOCOLOUR, 'c', MODE_NOCTCP, 'C', -/* MODE_EXCEPTION, 'e', */ /* MODE_ALTCHAN, 'F', */ +/* MODE_NOFLOOD, 'f', */ MODE_ACCESS, 'a', MODE_NOAMSGS, 'M', MODE_NONOTICE, 'N', @@ -1697,14 +1685,14 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all) bufptr[(*bufptr_i)++] = MB_TYPE(mbuf, i) & MODE_CHANOP ? 'o' : 'v'; totalbuflen -= IRCD_MAX(9, tmp) + 1; } - } else if (MB_TYPE(mbuf, i) & (MODE_BAN | MODE_EXCEPTION | MODE_APASS | MODE_UPASS | MODE_ALTCHAN)) { + } else if (MB_TYPE(mbuf, i) & (MODE_BAN | MODE_APASS | MODE_UPASS | MODE_ALTCHAN | MODE_NOFLOOD)) { tmp = strlen(MB_STRING(mbuf, i)); if ((totalbuflen - tmp) <= 0) /* don't overflow buffer */ MB_TYPE(mbuf, i) |= MODE_SAVE; /* save for later */ else { char mode_char; - switch(MB_TYPE(mbuf, i) & (MODE_BAN | MODE_EXCEPTION | MODE_APASS | MODE_UPASS | MODE_ALTCHAN)) + switch(MB_TYPE(mbuf, i) & (MODE_BAN | MODE_APASS | MODE_UPASS | MODE_ALTCHAN | MODE_NOFLOOD)) { case MODE_APASS: mode_char = 'A'; @@ -1712,12 +1700,12 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all) case MODE_UPASS: mode_char = 'U'; break; - case MODE_EXCEPTION: - mode_char = 'e'; - break; case MODE_ALTCHAN: mode_char = 'F'; break; + case MODE_NOFLOOD: + mode_char = 'f'; + break; default: mode_char = 'b'; break; @@ -1795,7 +1783,7 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all) build_string(strptr, strptr_i, cli_name(MB_CLIENT(mbuf, i)), 0, ' '); /* deal with bans... */ - else if (MB_TYPE(mbuf, i) & (MODE_BAN | MODE_EXCEPTION)) + else if (MB_TYPE(mbuf, i) & (MODE_BAN)) build_string(strptr, strptr_i, MB_STRING(mbuf, i), 0, ' '); /* deal with keys... */ @@ -1820,6 +1808,9 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all) else if (MB_TYPE(mbuf, i) & MODE_ALTCHAN) build_string(strptr, strptr_i, MB_STRING(mbuf, i), 0, ' '); + else if ((MB_TYPE(mbuf, i) & (MODE_ADD | MODE_NOFLOOD)) == + (MODE_ADD | MODE_NOFLOOD)) + build_string(strptr, strptr_i, MB_STRING(mbuf, i), 0, ' '); } /* send the messages off to their destination */ @@ -1908,7 +1899,7 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all) build_string(strptr, strptr_i, NumNick(MB_CLIENT(mbuf, i)), ' '); /* deal with modes that take strings */ - else if (MB_TYPE(mbuf, i) & (MODE_KEY | MODE_BAN | MODE_EXCEPTION | MODE_APASS | MODE_UPASS | MODE_ALTCHAN)) + else if (MB_TYPE(mbuf, i) & (MODE_KEY | MODE_BAN | MODE_APASS | MODE_UPASS | MODE_ALTCHAN | MODE_NOFLOOD)) build_string(strptr, strptr_i, MB_STRING(mbuf, i), 0, ' '); /* @@ -2216,20 +2207,20 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf) MODE_PERSIST, 'z', MODE_NOCOLOUR, 'c', MODE_NOCTCP, 'C', -/* MODE_EXCEPTION, 'e', */ MODE_NOAMSGS, 'M', MODE_NONOTICE, 'N', MODE_QUARANTINE, 'Q', MODE_ALTCHAN, 'F', MODE_ACCESS, 'a', MODE_AUDITORIUM, 'u', + MODE_NOFLOOD, 'f', 0x0, 0x0 }; ulong64 add; int i, bufpos = 0, len; ulong64 *flag_p; char *key = 0, limitbuf[20], accessbuf[20]; - char *apass = 0, *upass = 0, *altchan = 0; + char *apass = 0, *upass = 0, *altchan = 0, *noflood = 0; assert(0 != mbuf); assert(0 != buf); @@ -2240,7 +2231,7 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf) for (i = 0; i < mbuf->mb_count; i++) { /* find keys and limits */ if (MB_TYPE(mbuf, i) & MODE_ADD) { - add |= MB_TYPE(mbuf, i) & (MODE_KEY | MODE_LIMIT | MODE_APASS | MODE_UPASS | MODE_ALTCHAN | MODE_ACCESS); + add |= MB_TYPE(mbuf, i) & (MODE_KEY | MODE_LIMIT | MODE_APASS | MODE_UPASS | MODE_ALTCHAN | MODE_ACCESS | MODE_NOFLOOD); if (MB_TYPE(mbuf, i) & MODE_KEY) /* keep strings */ key = MB_STRING(mbuf, i); @@ -2254,6 +2245,8 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf) apass = MB_STRING(mbuf, i); else if (MB_TYPE(mbuf, i) & MODE_ALTCHAN) altchan = MB_STRING(mbuf, i); + else if (MB_TYPE(mbuf, i) & MODE_NOFLOOD) + noflood = MB_STRING(mbuf, i); } } @@ -2279,6 +2272,8 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf) build_string(buf, &bufpos, apass, 0, ' '); else if (buf[i] == 'F') build_string(buf, &bufpos, altchan, 0, ' '); + else if (buf[i] == 'f') + build_string(buf, &bufpos, noflood, 0, ' '); } buf[bufpos] = '\0'; @@ -2327,9 +2322,9 @@ mode_invite_clear(struct Channel *chan) #define DONE_KEY_DEL 0x80 /**< We've removed the key */ #define DONE_UPASS_DEL 0x100 /**< We've removed the user pass */ #define DONE_APASS_DEL 0x200 /**< We've removed the admin pass */ -#define DONE_EXCEPTIONLIST 0x400 /**< We've sent the exception list */ #define DONE_ALTCHAN 0x800 /**< We've set the altchan */ #define DONE_ACCESS 0x1000 /**< We've set the access */ +#define DONE_NOFLOOD 0x2000 /**< We've set the noflood options */ struct ParseState { struct ModeBuf *mbuf; @@ -2601,6 +2596,123 @@ mode_parse_altchan(struct ParseState *state, ulong64 *flag_p) } } +static void +mode_parse_noflood(struct ParseState *state, ulong64 *flag_p) +{ + char *t_str; + char *tmp; + unsigned int count = 0, time = 0, flags = 0; + + if (state->dir == MODE_ADD) { /* convert arg only if adding noflood */ + if (MyUser(state->sptr) && state->max_args <= 0) /* too many args? */ + return; + + if (state->parc <= 0) { /* warn if not enough args */ + if (MyUser(state->sptr)) + need_more_params(state->sptr, "MODE +f"); + return; + } + + t_str = state->parv[state->args_used++]; /* grab arg */ + state->parc--; + state->max_args--; + + tmp = t_str; + + if(tmp[0] == '!') { + if(state->flags & MODE_PARSE_FORCE) flags |= FLFL_NOFLOOD; + else t_str++; //simply ignore if it's not an opmode + tmp++; + } + if(tmp[0] == '+' || tmp[0] == '@') { + if(tmp[0] == '+') flags |= FLFL_VOICE; + if(tmp[0] == '@') flags |= FLFL_CHANOP; + tmp++; + } + char *p; + for(p = tmp; p[0]; p++) { + if(p[0] == ':') { + char tmpchar = p[0]; + p[0] = '\0'; + count = strtoul(tmp,0,10); + p[0] = tmpchar; + p++; + time = strtoul(p,0,10); + break; + } + } + if(count <= 0 || time <= 0 || count > 100 || time > 600) return; + + if (!(state->flags & MODE_PARSE_WIPEOUT) && + (!t_str || t_str == state->chptr->mode.noflood)) + return; + } else + t_str = state->chptr->mode.noflood; + + /* If they're not an oper, they can't change modes */ + if (state->flags & (MODE_PARSE_NOTOPER | MODE_PARSE_NOTMEMBER)) { + send_notoper(state); + return; + } + + /* Can't remove a noflood that's not there */ + if (state->dir == MODE_DEL && !*state->chptr->mode.noflood) + return; + + /* Skip if this is a burst and a lower noflood than this is set already */ + if ((state->flags & MODE_PARSE_BURST) && + *(state->chptr->mode.noflood)) + return; + + if (state->done & DONE_NOFLOOD) /* allow noflood to be set only once */ + return; + state->done |= DONE_NOFLOOD; + + if (!state->mbuf) + return; + + if (!(state->flags & MODE_PARSE_WIPEOUT) && state->dir == MODE_ADD && + !ircd_strcmp(state->chptr->mode.noflood, t_str)) + return; /* no change */ + + if (state->flags & MODE_PARSE_BOUNCE) { + if (*state->chptr->mode.noflood) /* reset old noflood */ + modebuf_mode_string(state->mbuf, MODE_DEL | flag_p[0], state->chptr->mode.noflood, 0); + else /* remove new bogus noflood */ + modebuf_mode_string(state->mbuf, MODE_ADD | flag_p[0], t_str, 0); + } else /* send new noflood */ + modebuf_mode_string(state->mbuf, state->dir | flag_p[0], t_str, 0); + + if (state->flags & MODE_PARSE_SET) { + if (state->dir == MODE_DEL) /* remove the old noflood */ + *state->chptr->mode.noflood = '\0'; + else + ircd_strncpy(state->chptr->mode.noflood, t_str, CHANNELLEN); + } + + if (state->dir == MODE_ADD) { + unsigned int noflood_value = time; + noflood_value <<= 10; + noflood_value |= count; + noflood_value <<= 3; + noflood_value |= flags; + state->chptr->mode.noflood_value = noflood_value; + } else { + //removed the mode so free all flood objects + struct Membership *member; + for(member = state->chptr->members; member; member = member->next_member) { + struct MemberFlood *floodnode; + for(floodnode = member->flood;floodnode ; floodnode = floodnode->next_memberflood) { + if(floodnode->next_memberflood == NULL) break; + } //simply walk to the end + if(!floodnode) continue; + floodnode->next_memberflood = free_MemberFlood; + free_MemberFlood = floodnode; + member->flood = NULL; + } + } +} + static void mode_parse_quarantine(struct ParseState *state, ulong64 *flag_p) { @@ -3153,19 +3265,9 @@ mode_parse_ban(struct ParseState *state, ulong64 *flag_p) struct Ban *ban, *newban; if (state->parc <= 0) { /* Not enough args, send ban list */ - if (MyUser(state->sptr)) { - if (*flag_p == MODE_EXCEPTION) { - if (!(state->done & DONE_EXCEPTIONLIST)) { - send_exception_list(state->sptr, state->chptr); - state->done |= DONE_EXCEPTIONLIST; - } - } - else { - if (!(state->done & DONE_BANLIST)) { - send_ban_list(state->sptr, state->chptr); - state->done |= DONE_BANLIST; - } - } + if (MyUser(state->sptr) && !(state->done & DONE_BANLIST)) { + send_ban_list(state->sptr, state->chptr); + state->done |= DONE_BANLIST; } return; } @@ -3242,8 +3344,7 @@ mode_process_bans(struct ParseState *state) } else if (ban->flags & BAN_DEL) { /* Deleted a ban? */ char *bandup; DupString(bandup, ban->banstr); - modebuf_mode_string(state->mbuf, MODE_DEL | ((ban->flags & BAN_EXCEPTION) ? MODE_EXCEPTION : MODE_BAN), - bandup, 1); + modebuf_mode_string(state->mbuf, MODE_DEL | MODE_BAN, bandup, 1); if (state->flags & MODE_PARSE_SET) { /* Ok, make it take effect */ if (prevban) /* clip it out of the list... */ @@ -3282,14 +3383,13 @@ mode_process_bans(struct ParseState *state) char *bandup; /* add the ban to the buffer */ DupString(bandup, ban->banstr); - modebuf_mode_string(state->mbuf, MODE_ADD | ((ban->flags & BAN_EXCEPTION) ? MODE_EXCEPTION : MODE_BAN), - bandup, 1); + modebuf_mode_string(state->mbuf, MODE_ADD | MODE_BAN, bandup, 1); if (state->flags & MODE_PARSE_SET) { /* create a new ban */ newban = make_ban(ban->banstr); strcpy(newban->who, ban->who); newban->when = ban->when; - newban->flags = ban->flags & (BAN_IPMASK | BAN_EXCEPTION); + newban->flags = ban->flags & BAN_IPMASK; newban->next = state->chptr->banlist; /* and link it in */ state->chptr->banlist = newban; @@ -3632,13 +3732,13 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr, MODE_PERSIST, 'z', MODE_NOCOLOUR, 'c', MODE_NOCTCP, 'C', -/* MODE_EXCEPTION, 'e',*/ MODE_NOAMSGS, 'M', MODE_NONOTICE, 'N', MODE_QUARANTINE, 'Q', MODE_ALTCHAN, 'F', MODE_ACCESS, 'a', MODE_AUDITORIUM, 'u', + MODE_NOFLOOD, 'f', MODE_ADD, '+', MODE_DEL, '-', 0x0, 0x0 @@ -3709,6 +3809,9 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr, break; case 'F': mode_parse_altchan(&state, flag_p); + break; + case 'f': + mode_parse_noflood(&state, flag_p); break; case 'Q': if(IsNetServ(state.sptr) && IsSecurityServ(state.sptr)) @@ -3725,9 +3828,6 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr, if (IsServer(cptr) || feature_bool(FEAT_OPLEVELS)) mode_parse_upass(&state, flag_p); break; - -/* case 'e': - if(MyUser(state.sptr) && !(state.flags & MODE_PARSE_FORCE) && !feature_bool(FEAT_EXCEPT_ENABLE)) break;*/ case 'b': /* deal with bans */ mode_parse_ban(&state, flag_p); break; @@ -3856,6 +3956,9 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr, if (state.chptr->mode.altchan && !(state.done & DONE_ALTCHAN)) modebuf_mode_string(state.mbuf, MODE_DEL | MODE_ALTCHAN, state.chptr->mode.altchan, 0); + if (state.chptr->mode.noflood && !(state.done & DONE_NOFLOOD)) + modebuf_mode_string(state.mbuf, MODE_DEL | MODE_NOFLOOD, + state.chptr->mode.noflood, 0); if (*state.chptr->mode.key && !(state.done & DONE_KEY_DEL)) modebuf_mode_string(state.mbuf, MODE_DEL | MODE_KEY, state.chptr->mode.key, 0); @@ -4159,7 +4262,7 @@ void RevealDelayedJoinIfNeeded(struct Client *sptr, struct Channel *chptr) int ext_amsg_block(struct Client *cptr, struct Channel *chptr, const char *msg) { int amsg_time; - + /* First on every message we check whether the mechanism is enabled. * If it is enabled, we check: * - whether the channel has MODE_NOAMSGS @@ -4173,6 +4276,60 @@ int ext_amsg_block(struct Client *cptr, struct Channel *chptr, const char *msg) */ amsg_time = feature_int(FEAT_NOAMSG_TIME); if(amsg_time > 0) { + /* first of all strip the message (filter out invisible content) */ + char *stripped_message = MyMalloc(BUFSIZE + 1); + strcpy(stripped_message, msg); + char p = stripped_message[0]; + int p_pos = 0; + int is_visible = 1, is_ccode = 0, i = 0, j = 0; + char codes[5]; + for(i = 0; p != '\n'; p = stripped_message[++i]) { + if(p == 3) { + j = 0; + is_ccode = 1; + } else if(is_ccode) { + if((p >= 48 && p <= 57) || p == 44) { + if(is_ccode == 1) { + if(p == 44) { + is_ccode = 2; + codes[j++] = 0; + j = 0; + } else + codes[j++] = p; + } else { + //compare + if(p != codes[j++]) { + is_ccode = 3; + } + } + } else { + //END of color code... + is_ccode = 0; + if(is_ccode != 1 && codes[j] != 0) is_ccode = 3; + if(is_ccode == 1) { + codes[j] = 0; + int k; + for(k = 0; k < j-1; k++) { + if(codes[k] != 48) { + is_visible = 1; + goto normalchar; + } + } + is_visible = 0; + } else if(is_ccode == 2) { + is_visible = 0; + } else if(is_ccode == 3) { + is_visible = 1; + goto normalchar; + } + } + } else { + normalchar: + if(is_visible) + stripped_message[p_pos++] = p; + } + } + stripped_message[p_pos++] = 0; /* Allocate a new buffer if there is none, yet. */ if(!cli_user(cptr)->lastmsg) { cli_user(cptr)->lastmsg = MyMalloc(BUFSIZE + 1); @@ -4180,17 +4337,75 @@ int ext_amsg_block(struct Client *cptr, struct Channel *chptr, const char *msg) } if((chptr->mode.mode & MODE_NOAMSGS) && ((cli_user(cptr)->lastmsg_time + amsg_time) >= CurrentTime) && - (strcmp(cli_user(cptr)->lastmsg, msg) == 0)) { + (strcmp(cli_user(cptr)->lastmsg, stripped_message) == 0)) { cli_user(cptr)->lastmsg_time = CurrentTime; cli_user(cptr)->lastmsg_num++; + MyFree(stripped_message); if(cli_user(cptr)->lastmsg_num >= feature_int(FEAT_NOAMSG_NUM)) return 1; else return 0; } /* Message did not match so update the data. */ cli_user(cptr)->lastmsg_time = CurrentTime; cli_user(cptr)->lastmsg_num = 0; - strcpy(cli_user(cptr)->lastmsg, msg); + strcpy(cli_user(cptr)->lastmsg, stripped_message); + MyFree(stripped_message); } return 0; } +/** Extended flood check. + * The channelmode MODE_NOFLOOD prevents users from flooding the channel. + * + * This function returns 0 if the message may pass and 1 if the message should + * be blocked. + * For this function to work properly it must be called on every PRIVMSG which + * is sent by any user. + * + * --pk910 2011/7/1 + */ +int ext_noflood_block(struct Client *cptr, struct Channel *chptr) { + if(chptr->mode.noflood == NULL || !chptr->mode.noflood) return 0; + struct Membership *member = find_member_link(chptr, cptr); + if(!member) return 0; //TODO: we've no check for -n channels implemented, yet + //check if this user is really affected by +f + unsigned int flags = (chptr->mode.noflood_value & 0x00000007); //0000 0000 0000 0000 0000 0000 0000 0111 = 0x00000007 >> 0 + unsigned int count = (chptr->mode.noflood_value & 0x00001ff8) >> 3; //0000 0000 0000 0000 0001 1111 1111 1000 = 0x00001ff8 >> 3 + int time = (chptr->mode.noflood_value & 0x07ffe000) >> 13; //0000 0111 1111 1111 1110 0000 0000 0000 = 0x07ffe000 >> 13 + if(count == 0 || time == 0) return 0; + if(!(flags & FLFL_NOFLOOD) && HasPriv(cptr, PRIV_FLOOD)) + return 0; + if(!(flags & FLFL_CHANOP) && (member->status & CHFL_CHANOP)) + return 0; + if(!(flags & (FLFL_CHANOP | FLFL_VOICE)) && (member->status & CHFL_VOICE)) + return 0; + int floodcount = 0; + struct MemberFlood *floodnode, *prev_floodnode; + for (floodnode = member->flood; floodnode; floodnode = floodnode->next_memberflood) { + if(floodnode->time + time > CurrentTime) { + if(floodcount == 0 && floodnode != member->flood) { + //free all before + prev_floodnode->next_memberflood = free_MemberFlood; + free_MemberFlood = prev_floodnode; + member->flood = floodnode; + } + floodcount++; + } + prev_floodnode = floodnode; + } + Debug((DEBUG_INFO, "floodcount: %i", floodcount)); + if(floodcount >= count) return 1; //blocked! + //add a new floodnode :) + if(free_MemberFlood) { + floodnode = free_MemberFlood; + free_MemberFlood = floodnode->next_memberflood; + } else + floodnode = (struct MemberFlood*) MyMalloc(sizeof(struct MemberFlood)); + floodnode->time = CurrentTime; + floodnode->next_memberflood = NULL; + if(floodcount > 0) + prev_floodnode->next_memberflood = floodnode; + else + member->flood = floodnode; + return 0; +} +