mIRC Homepage

kickban identical nicks

Posted By: Simo

kickban identical nicks - 27/11/21 03:18 PM

i was messing with this code for a while to get identical nicks on join but the loops seem messed up and i cant seem to be able to fix properly
the idea is to store nicks on join and if more than 2 or more identical nicks join to get the stored nicks and check against the patern meaning the first 5 chars of the nick that triggered
the code

Code

On !^*:JOIN:#: {
  if ($nick(#,$me,@&~%) || o isin $usermode) {  
    var %clonednicks.flood = $left($nick,5)

    set -u30 %clnicks1 $addtok(%clnicks1,$nick,44) 

    if (%clonesjoin. [ $+ [ # ] $+ . $+ [ %clonednicks.flood ] ] == $null) {
      set -u3 %clonesjoin. [ $+ [ # ] $+ . $+ [ %clonednicks.flood ] ] 1       
    }
    else { inc %clonesjoin. [ $+ [ # ] $+ . $+ [ %clonednicks.flood ] ] }
    if (%clonesjoin. [ $+ [ # ] $+ . $+ [ %clonednicks.flood ] ] > 1) {



      var %axel = 1  |  while ($gettok(%clnicks1,%axel,44)) {  if (%clonednicks.flood isin $gettok(%clnicks1,%axel,44))  {  set -u30 %clnicks2 $addtok(%clnicks2,$gettok(%clnicks1,%axel,44),44)   }  |  inc %axel   } 



      if (%modechan1MR != $true) { .raw mode  $chan +MRb  $+(%clonednicks.flood,*!*@*) |  echo $chan 01,08 ( cloned nicks Flood ) !!!!! Channel Locked !!!!!   | set -u15 %modechan1MR $true | .timermjs1RM 1 30 mode $safe2018($chan) -MR  }

      if ($nick(#,$me,@%&~) || o isin $usermode) { var %mcln 20 | while (%clnicks2) { kick $chan $gettok(%clnicks2,1- %mcln,44) Cloned Nicks )  Detected | %clnicks2 = $deltok(%clnick2 } }

    }
  }  
}



the idea is to have something like this as end result :
Posted By: maroon

Re: kickban identical nicks - 30/11/21 03:32 AM

This looks a lot like code that someone was asking about in the help channel. It's different than the last time they pastebin'ed it, but it still has problems from an overview look at it, as there's no point in trying to load it since I'm not in a channel where I'm encountering this kind of thing. And I wouldn't want to try running it, due to all the false positives that can easily trigger it.

You didn't give an idea about what's wrong with the code. Whether it just spits out errors left and right, or it does nothing, or it does things it shouldn't etc.

So I'll just make a mostly generic post about how to debug your own scripts.

--

While you're trying to get rid of the wrinkles, you should include some debugging messages to help see if/when things are going wrong. There should be some echoes to identify when the script is reaching certain key rows, so you can see if it's reaching a line that it shouldn't, or vice-versa. The echoes should include $scriptline so you can see which debug message is being displayed, as well as $nopath($script) so you can find stray debug messages that don't get removed when they should.

Using the -s switch with /set /inc etc is a great way to send debugging info without creating another echo statement, and is helpful when creating compound variable names, to not only see what number/string it's being set to, but also exactly what kind of compound variable name is being created. Sometimes things don't happen because an extra/missing square brace causes the wrong compound name to be created, or causes the string to contain things they shouldn't.

For some of the debugging echoes, it's helpful to show contents of variable names to help identify why the code is branching the wrong direction when it reaches an if() or while(). You can even mimic an if() or while() command using an echo immediately above it, though you rarely can simply copypaste the if() command following an "echo -s" and expect it to be intelligible. The parsing rules are different within an echo than they are within commands, for things like how they handle text that's touching parenthesis and commas, so you might need to insert extra spaces.

It also helps to include ;semicolon comments in your code while making it work, because it helps get you up to speed when you look at it the next day. Specifically, when you have several { section } of if() conditionals, it helps to be able to look at a closing curly brace and see what it's the close of, especially for a section that covers a dozen or more rows

Also, even though mSL lets you get away with not putting parenthesis around things which other languages would insist on, but you're better off using parenthesis unless you're coding something in one of those small-code challenges.

For one thing, parenthesis give visual breaks that make it easier to read the code, so you can identify the sections of the command more easily. For another thing, the presence/absence of parenthesis can affect how the $v1 and $v2 values are filled. For example, in your first if() you have parenthesis around the outer condition, but don't have it around each of the twin conditions inside it. This can cause $v1 and $v2 to be filled differently than if you had parenthesis around the twins. For example, take a look at:

//if ($left(0,1) isin abc || o isin foo) echo -a match $v1 vs $v2 | else echo -a nomatch $v1 vs $v2
//if ($left(0,1) isin abc || z isin foo) echo -a match $v1 vs $v2 | else echo -a nomatch $v1 vs $v2
//if (($left(0,1) isin abc) || (o isin foo)) echo -a match $v1 vs $v2 | else echo -a nomatch $v1 vs $v2

In the 1st 2 examples, the 1st twin is always false, however $v1 and $v2 are being correctly filled only when both twins are false. However the 3rd example correctly fills $v1 and $v2 based on the 2nd condition which was the final condition evaluated and actually caused the code to branch that direction. If you have $v1 and $v2 shown in one of your debugging echoes, the lack of parenthesis can cause you to be mis-informed as to why the code branched the way it did.

It's just personal preference, and I know it's syntax common in other languages, but I like to avoid the "%variable = string" syntax because of how mSL is designed. It's always a visual speedbump for me when I'm trying to read mSL code, because I'm expecting to see an actual command name at the front of each command, so my first reaction is seeing an unrecognized %commandname. Also, when I see "%name = string", I then need to look to see whether the intent is to modify a local /var created above, or whether this is intentionally trying to modify a global variable that should have somehow been created elsewhere in the code for later events to deal with. By always using /set or /var, there's no ambiguity, and it makes it easier for me to see when a command is using a variable name that it assumes is blank without properly initializing it to defend against global variables of the same name - created by other scripts because people use common names like %color or because the script didn't clean up after itself.

Having code be more complicated also makes it harder to read through when looking for errors.

var %axel = 1 | while ($gettok(%clnicks1,%axel,44)) { if (%clonednicks.flood isin $gettok(%clnicks1,%axel,44)) { set -u30 %clnicks2 $addtok(%clnicks2,$gettok(%clnicks1,%axel,44),44) } | inc %axel }

For example in the above, $gettok(%clnicks1,%axel,44) is used 3 times when only once is needed. If there's another if() between the if() which creates the $v1 and when you need to use the string, you can park it into %v1 or some other temp name until you need it. When you you use it the 2nd time, that string is already in $v1 from the while(), and when using it the 3rd time it's already in the $v2 created by the most recent if(), so there's no need to use the $gettok again. It would make the code shorter and easier for you to read, as well as making it much quicker for the engine to retrive the string stored in $v2 instead of telling $gettok to parse the original string again. And again. Though be sure to have the choice of $v1 or $v2 reflect the most recent conditional, and you can often rearrange the left/right halves of evaluations to keep the string always in the same $v1 instead of bouncing between them. Even something like "if ($null == $gettok(%clnicks1,%axel,44)" is legit if you're wanting to keep the value staying in $v2.

Also, if your code is working correctly, %clnicks2 is a temporary variable that should contain a list of nicks only for the duration needed to build the list for kicking, and you're wiping the nicks out of the list as soon as you kick them. That makes it pointless to /set a global variable with the -uN trigger for unsetting it, so you can just use /var to create the temporary variable. I always use /var unless I'm needing to create a global variable so an alias subroutine can see it without needing to pass it as an argument, or if it needs to be seen later on in another $event.

Also, %clnicks2 should be empty when it reaches that code, so if there's a possibility that it's not, you should be initializing it by unsetting it or setting the local copy of it to be blank. That also means you don't need the extra slowdown of using $addtok, when you can modify your code a little to avoid it, like:

var %axel = 1 | while ($gettok(%clnicks1,%axel,44)) { if (%clonednicks.flood isin $v1) { var %clnicks2 %clnicks2 $+ , $+ $v2 } | inc %axel }

if (%modechan1MR != $true) { .raw mode $chan +MRb $+(%clonednicks.flood,*!*@*) | echo $chan 01,08 ( cloned nicks Flood %clnicks2 ) !!!!! Channel Locked !!!!!  | set -u15 %modechan1MR $true | .timermjs1RM 1 30 mode $safe2018($chan) -MR }

var %mcln 20 | while ($gettok(%clnicks2,1- %mcln,44) != $null) { kick $chan $v1 Cloned Nicks ) Detected | var %clnicks2 = $deltok(%clnick2,1- %mcln,44) } }

I added %clnicks2 to the echo message just to identify which of the recent joins was triggering it, and what's the magnitude of the flood, as well as making it easier to notice that someone's nick has been vacuumed up in the flood cleanup when they shouldn't have been.

I used the "!= $null" even though it's not needed, because that makes it easier for me to read my code, and also helps avoid the pitfall of treating tokens of 0 and $false the same as blank. It's not a problem here since nicks cannot begin with 0 under normal circumstances, but sometimes I see people using this against $1- in an ON TEXT event, which causes a failure when a word in the middle of the string is 0 or $false. Also, some IRCD's have the server give an alphanumeric nick to avoid a nick collision, and I suppose it's remotely possible that such a nick could be 00000ABC.

The above code creates a leading comma, but since the variable is now accessed only by $gettok, it's invisible, and the 1st $deltok removes it.

As an aside for code that's difficult to handle because there's a compound variable name that needs to contain several different variables, the code can be a lot simpler by storing the variables instead as hashtable items. Once you know how to use hashtables, you don't need to use square braces at all when creating items with /hadd nor when using $hget to retrieve their values. You can simply use $+($network,.,$chan,.,$nick) in both situations.

If it makes your code simpler to deal with, you can also have compound variable names used to preserve data between triggering events, then immediately transfer them to a simpler local variable name when needing to manipulate the data.

--

Now for design issues I see from looking at your code.

%clnicks1 is a variable that exists as soon as someone joins the channel, and it doesn't vanish until there's a 30 seconds interval when nobody joins. Until that interval happens, it keeps growing with tokens, so there's a potential for this to encounter $maxlenl if it's a large channel that naturally has a lot of legit joins without a 30 seconds pause.

It looks like you're doing an XY problem by giving us only part of your code, as evidenced by how you only gave us part of a $deltok, which would have created an error message that wouldn't have required posting for help about it. Unless your unstated problem is that the code never even reaches the $deltok in order to cause that error.

You have an identical if(this||that) inside the top one. It doesn't cause an error, but it would always be $true since your code can't do anything to cause it to become $false.

It also looks like the existing design is a great way to get yourself kicked for flooding, because of how it sends un-neccessary kick commands to the server. It kicks nicks even after you've already sent a command to kick them.

When RobertSmith joins, nothing happens except adding Rober to the list of nick prefixes who joined since the last time there was a 30-seconds interval where nobody was joining. Then as soon as RobertJones joins, it goes into panic mode and assumes that the channel is under attack. While it won't kick anyone in channel who's been in there a while, that "while" can be a long time if there hasn't been a 30 seconds interval which allows %clnicks1 to go away.

It's also vulnerable to some trickster activity. Someone could have a clone waiting in the wings for an @op to timeout and rejoin, or just waiting for anyone to join. As soon as the @op rejoins the channel, or after some random person joins the channel, they have their nickchange ready and have it join the channel too, and this gets the @op or RandomDude kicked, even if they've already been opped or voiced, since this code doesn't check the status of their victim, nor does it have any whitelist.

It doesn't even check if it's trying to kick someone who's no longer in the channel, and unless there's additional code not included, someone is also exempt from having their flood clones kicked if they wait until after joining then change their nick again. When I run this next snippet against #libera I get over 80 nicks who share the 1st 5 letters of their nick with someone else.

//var %i 1 , %chan #libera | hfree -w foo | hmake foo | while ($nick(%chan,%i)) { hinc foo $left($v1,5) | inc %i } | var -s %i 1 | while ($hget(foo,%i).item != $null) { var %v1 $v1 | if ($hget(foo,%v1) != 1) echo -a %v1 $v1 | inc %i } | echo -a nicks affected: $calc( $nick(%chan,0) - $hget(foo,0).item )

While some of them are intentional clones, many of them are not, including the dozen Guest nicks. So I'm afraid this design is going to be doing a lot of kicking that it shouldn't, since the chances are excellent that 2 unrelated people with similar nicks can join the channel at near the same time since the last time there was a 31 seconds gap between joins, especially when boomeranging from a netsplit. Plus, all the flooder needs to do is start coming in with dissimilar nicks.

In fact, one of the most easy ways for this to trigger a false positive is the common habit of having mIRC configured so that $anick is the same as $mnick except for appending _away to it. When nicks timeout, it's very common for them to rejoin the server and the channels before the server has killed the old nick for not responding, so I'm surprised you're not getting these kinds of innocents being hit by your drive-by kicks. smile

But even when it's doing the intended job against a legit flooder: When foobar1 joins, nothing happens except fooba is added to the list of recent joins. When foobar2 joins, then it tries to kick both foobar1 and foobar2. It's reasonable to assume that flooders can join in a short interval before the server can respond to perform the kick, so there's a chance that foobar3 joins before the server can perform the kick. Since foobar1 and foobar2 are still in %clnicks1, this means that it would send another kick attempt against foobar1 and foobar2, as well as against foobar3. The solution isn't to remove nicks from the list as soon as you kick them, because then foobar3 would no longer be flagged.

Now I look at:

if (%clonesjoin. [ $+ [ # ] $+ . $+ [ %clonednicks.flood ] ] > number)

Based on your script's logic, there should be no need to kick multiple nicks unless the clones counter is <= 'number+1'. When the counter exceeds number+1, it should assume that clones 1 thru number+1 were already kicked, and since you're adding nicks to the list in the order they're joining, you'd only need to kick the final nick in the list who matched %clonednicks.flood, which would by definition also be $nick
Posted By: Simo

Re: kickban identical nicks - 03/12/21 05:29 PM

someone helped me with this and came up with this wich seems to do a better job except it doesnt stack all detected cloned nicks and send in 1 or 2 go

Code

ON !^*:JOIN:#test: {
  if (!$nick($chan,$me,@&~%)) && (o !isin $usermode) { return }

  var %hash_1 = JCLONES_ $+ $chan
  var %hash_2 = JCLONESNICKS_ $+ $chan
  var %nick_5chars = $left($nick,5)

  hinc -mu3 %hash_1 %nick_5chars 1
  hadd -mu3 %hash_2 $nick $mask($fulladdress,4)
  var %nick_total = $hget(%hash_1,%nick_5chars)

  if (%nick_total > 1) {

    var %t = $hget(%hash_2,0).item
    if (%t) {
      var %i = 1
      while (%i <= %t) {
        var %n = $hget(%hash_2,%i).item
        var %h = $gettok($hget(%hash_2,%n),1,32)

        if (%n) && (%h) && (* $+ %nick_5chars $+ * iswm %n) {
          var %total_nicks = $addtok(%total_nicks,%n,44)
          var %total_hosts = $addtok(%total_hosts,%h,32)
        }
        inc %i
      }
      if (!$Timer(#)) { .raw -q MODE $chan +RMb $+(*,%nick_5chars,?*!*@*) | .Timer $+ $chan 1 5 MODE $chan -MR  }
      if (%total_hosts) { putmode $chan $+(+,$str(b,$numtok(%total_hosts,32))) %total_hosts }
      if (%total_nicks) { .raw -q KICK $chan %total_nicks IDenticaL NICK(s) }
    }
    if ($hget(%hash_1)) { hfree $v1 }
    if ($hget(%hash_2)) { hfree $v1 }
  }
}


and it doesnt seem get all identical nicks only in pairs of 2 so if 3 will join with same first 5 chars in nick only first 2 will get detected same goes for 5 7 9 11 13 15 17 and so on
the last one never gets detected and the nicks arent stacked as ircd allows stacked kicks and bans
Posted By: Talon

Re: kickban identical nicks - 03/12/21 10:51 PM

You need to delay by some interval to execute the check, instead of immediately checking for clones. The problem you're facing is it detects > 1 match, and deals with them. so 1-2 is already gone before you test #3, testing #3 fails because you already kick/banned 1-2, so there can't be more than 1 match...

You also have some potential exploits here, since there's no error checking on nicknames. You probably don't want to kick anyone with some form of channel access mode (op/halfop/voice/etc...) and you also should ensure that the nickname is NOT you, with the current setup, if I were to launch 3 clones with the beginning nick matching the first 5 characters of your nickname, you're also gonna kick yourself!

There's another problem here, IRCd's have a limitation on multiple modes/targets and this is defined by raw 005 MODES=X and TARGMAX=X. If you get a rather lengthly list, how you're kicking based on just a tokenized list on 44, your kick could potentially have two outcomes... miss some because the list is beyond the maximum targets allowed, or potentially ignored all-together because the targets list is too long.

IMPORTANT NOTE: As far as I know, mIRC does not have an $identifier for all things passed in 005, one of which is TARGMAX, this has a potential to BREAK because I just assumed $modespl and TARGMAX-kick are identical.

Example 005 line from an IRCd:
AWAYLEN=360 BOT=B CASEMAPPING=ascii CHANLIMIT=#:50 CHANMODES=beI,kLf,lHJ,psmntirzMQNRTOVKDdGPZSCc CHANNELLEN=32 CHANTYPES=# CLIENTTAGDENY=*,-draft/typing,-typing DEAF=d ELIST=MNUCT EXCEPTS EXTBAN=~,ptmTSOcarnqjf HCN INVEX KICKLEN=360 KNOCK MAP MAXCHANNELS=50 MAXLIST=b:100,e:100,I:100 MAXNICKLEN=30 MINNICKLEN=0 MODES=12 NAMESX NETWORK=xxxxx NICKLEN=30 PREFIX=(qaohv)~&@%+ QUITLEN=360 SAFELIST SILENCE=30 STATUSMSG=~&@%+ TARGMAX=DCCALLOW:,ISON:,JOIN:,KICK:4,KILL:,LIST:,NAMES:1,NOTICE:1,PART:,PRIVMSG:4,SAJOIN:,SAPART:,TAGMSG:1,USERHOST:,USERIP:,WATCH:,WHOIS:1,WHOWAS:1 TOPICLEN=360 UHNAMES USERIP WALLCHOPS WATCH=128 are supported by this server

Here you will see a list of maximum targets for KICK is 4, yet MODES is 12. You may need to parse raw 005 yourself and log the maximum number of allowable targets for kick and change the variable: %FixMeFrom005 from "$modespl" for this value located above the last while loop of this example, or just set this to a lower number, like from this server, just set it to 4, instead of $modespl.

the targmax portion:
TARGMAX=DCCALLOW:,ISON:,JOIN:,KICK:4,KILL:,LIST:,NAMES:1,NOTICE:1,PART:,PRIVMSG:4,SAJOIN:,SAPART:,TAGMSG:1,USERHOST:,USERIP:,WATCH:,WHOIS:1,WHOWAS:1

Here is a re-worked version that "should" do what you're after, based on what I gather from the code you posted, and the example paste of the end result you wish to see as an outcome. More than one similar nickname (first 5 characters matching) joining within 3 seconds. I also used $ialchan rather than a custom data-storage (hashtable/ini/flatfile) so there is no global variables or storage tables. For this to work, /ial MUST be on (who doesn't have this on anymore?)

NOTE: there is one potential unfortunate side-effect with the method I used (ialchan), there's no way to determine if a nicks idle was set due to a recent join, or from recently spoken (action or text) so it "could" potentially kick someone who matches the pattern, has no channel access list (op/voice/etc..) and has spoken within the 3sec timeframe of the join flood, but this "should" be a very rare occurrence.

I heavily commented what it is doing so you can follow the method on how and why it works.

Code
on *:JOIN:#: {
  ;=== Are we some form of op or ircop?
  if ($nick(#,$me,@&~%) || o isin $usermode) { 
 
    ;=== Is the nickname that joined at least 5 characters?
    if ($len($nick) >= 5) { 
 
      ;=== Define variables for the pattern and get number of matches for pattern.
      var %pattern = $+($mid($nick,1,5),?*!*@*) , %matches = $ialchan(%pattern,#,0)
 
      ;=== Do we have more than one match?
      if (%matches > 1) {
 
        ;=== Define a variable to house our timer name, get its tokens if it exists already and append our pattern to it.
        var %timer = $+(-clones-,$cid,-,#) , %tokens = $addtok($gettok($timer(%timer).com,2-,32),%pattern,32)
 
        ;=== (Re)set the timer to execute in 1/4 of a second (250 ms) to filter clones with our new pattern(s).
        ; THIS is an important step! if we don't delay, we could miss a few! say 3 join in succession, this will trigger on #2 (> 1)
        ; which will kick 1-2, which means next join trigger on #3 will only have 1 match because we already dealt with #1-2!
        ; by simply delaying a little bit we can nab rapid succession joins that match all in one go!
        $+(timer,%timer) -m 1 250 FilterClones # %tokens
      }
    }
  }
}
alias -l FilterClones {
  var %chan = $1 , %patterns = $2- , %total = $numtok(%patterns,32)
 
  ;=== Are we still some form of op or ircop?
  if ($nick(%chan,$me,@&~%) || o isin $usermode) { 
 
    ;=== Iterate over our passed patterns
    var %x = 0
    while (%x < %total) {
      inc %x
 
      ;=== Define variables for our pattern, and number of pattern matches
      var %pattern = $gettok(%patterns,%x,32) , %matches = $ialchan(%pattern,%chan,0)
 
      ;=== Do we have more than one match?
      if (%matches > 1) {
 
        ;=== clear out results/bans (potential leftover from last pattern) and iterate over our matches
        var %y = 0 , %result = $null , %bans = $null
        while (%y < %matches) {
          inc %y
 
          ;=== Define variables for the address, nickname, and the nicknames idle time.
          var %address = $ialchan(%pattern,%chan,%y) , %nick = $ialchan(%pattern,%chan,%y).nick , %idle = $nick(%chan,%nick).idle
 
          ;=== Is our matching nick NOT some form of access level or $me, and idle for <= 3 seconds? If so they either 
          ; just joined, or spoken. Add to results/bans lists. NOTE: We probably don't want nicks that just spoken, but this is
          ; an unfortunate side-effect of this method that should be a rare case, they must match the pattern, and 
          ; have spoken within the 3 sec timeframe of the join flood.
          if (!$nick(%chan,%nick,$prefix) && %nick != $me && %idle <= 3) { var %result = $addtok(%result,%nick,44) , %bans = $addtok(%bans,$mask(%address,2),32) }
        }
 
        ;=== Get the number of results
        var %results = $numtok(%result,44)
 
        ;=== Do we have more than one nick left that met all of the above criteria?
        if (%results > 1) {
 
          ;=== set mode +MR and ban the pattern
          mode %chan +MRb %pattern
 
          ;=== set a timer to unlock modes "MR" in 5 seconds
          $+(.timer,-unlock-,$cid,-,%chan) 1 5 mode %chan -MR
 
          ;=== Iterate bans by modespl and ban them
          while ($gettok(%bans,$+(1-,$modespl),32)) { 
            mode %chan $+(+,$str(b,$numtok($v1,32))) $v1
            var %bans = $gettok(%bans,$+($calc($modespl +1),-),32)
          }
 
          ;=== Iterate nicks by TARGMAX-kick and kick them
          var %FixMeFrom005 = $modespl
          while ($gettok(%result,$+(1-,%FixMeFrom005),44)) { 
            kick %chan $v1 IDenticaL NICK(s)
            var %result = $gettok(%result,$+($calc(%FixMeFrom005 +1),-),44)
          }
        }
      }
    }
  }
}
Posted By: Simo

Re: kickban identical nicks - 04/12/21 11:09 AM

thanks talon that does exactly as we expected it to thanks, much apreciated
© 2022 mIRC Discussion Forums