Структура документа:

Описание

Вступление

Теория

Функции

Константы

Прочие настройки

Примеры  
(так же содержатся в examples/example1.tcl, examples/example2.tcl & examples/example3.tcl)

Wrapper

DESCRIPTION - Описание UAFS.tcl - Universal Anti-Flood Script

Скрипт UAFS.tcl for eggdrop представляет собой средство для контроля количества событий в определенный промежуток времени. Применительно к eggdrop'y, события, как правило, представляют собой команды пользователя, посланные боту, хотя в общем случае это могут быть и joins/parts/nick changes/all messages.

Вступление:

Рассмотрим пример:есть скрипт который выдает новости с neowin.net по запросу пользователя.Доступ к скрипту имеют все пользователи данного канала.Легко представить себе ситуацию когда кучка хакеров-полуросликов решает завалить бота запросами которые он и будет обслуживать не жалея своих сил (встроенный антифлуд не сработает - маски то разные!). В этом случае имеются как минимум 2 фактора из-за которых хотелось бы ввести ограничение на пользование скриптом по временному признаку,то есть по частоте:

  1. При обращении к ресурсу,бот принимает/посылает определенное количество информации с сервера/на сервер,что выражается в количестве потребленного траффика и нагрузке на канал [передачи данных],в некоторых случаях это может быть критично, если, к примеру, бот сидит на медленном канале [передачи данных].
  2. Если бот выдает новости на канал, то во-первых, он создает флуд на канале, во-вторых забивает свою очередь сообщений, которая, как известно, по умолчанию не резиновая ибо бот защищает себя от excess flood'a (это применимо и к приватным сообщениям/нотисам). В следствии этого остальные функции бота могут быть недоступны, грубо говоря получается маленький DoS. Несмотря на то что данный вопрос намного шире и частично решается подгонкой конфигурации ircd тем или иным образом под конкретного бота, проблему он показывает ясно.

Для предотвращения этих а также других нежелательных ситуаций и необходимо вводить ограничения,но к сожалению, по умолчанию данную функциональность eggdrop не поддерживает, поэтому на свет появился UAFS.tcl, призванный упростить жизнь другим разработчикам и избавить от изобретания колеса.

Ну и как вся эта хуйня [censored] работает? Как бля [censored] её юзать нах [censored]? 
(вместо введения :)

Поразмышляв некоторое время над схожим вопросом,только вместо "работает" было - "будет работать", я пришел к выводу что необходимо реализовать внешний интерфейс, или, если угодно внешнюю функцию, которая и будет проверять есть флуд,али нет. Но как внешняя функция узнает, по какому критерию нужно проверять? Сколько событий и за какой промежуток времени можно допустить? Правильно,нужно ей об этом сообщить, то есть вызвать ее с параметром задающим легетимное кол-во событий в определенный отрезок времени. Хорошо, с этим определились,но вот незадача - пользователи идентифицируются минимум тремя параметрами в виде nick!ident@host а не только nick'om как нам бы хотелось, значит надо еще и по маске сравнивать? а если сравнивать по маске,значит уже имеющиеся данные о масках надо где-то хранить? да и получать их нужно, лишние строчки кода писать в нашей локальной функции вызванной триггером. То есть мы сами получаем,запоминаем храним, а внешняя функция только проверяет на временной критерий? Хм...ну ладно,пускай так будет, но что делать с другой проблемой - если использовать ограничение более чем в одном месте (более чем одной локальной процедуре ) да еще и с разными критериями - как внешняя функция сможет различить кто желает от нее проверки? Легче тогда уж хранить все данные в своей процедуре,в ней же и проверять, а на внешную хуй [censored] забить, нахуй [censored] она сдалась. Но получаем изобретение колеса - каждый будет все реализовывать по-своему и еще раз по-своему и еще раз по-своему...кто-то скажет - ну тык,йопт, пусь реализовывает если его чиста прет и вставляет,на что вполне резонно можно заметить -  а нахуйа [censored] ему сдался eggdrop & tcl - писал бы своего бота и свой язык, раз его так чиста прет, в общем не наш это метод Федя,не наш. Как написанно в умной книжке Dan'a Appelman'a по VB.Net - повторное использование кода - мантра программиста, что и так ясно, если вы канешна настоящий программист по убеждению - то есть обладаете достаточным запасом логики и лени. В общем, такого рода рассуждения завели меня в небольшой тупик - вроде и надо людям помоч,а вроде как хуйня [censored] выходит. К моему удивлению, выход нашелся довольно быстро - повспоминав Win32 API, я пришел к выводу что решением может послужить использование handlers ( по-русски "манипуляторов", всё из той же умной книжки ) для однозначной идентификации настроек флуд контроля...допустим, регистрируются настройки флуда, флагов игнорирования, строгости проверки, способ сравнения масок в другой внешней функции, она отдает нам какой-либо идентификатор который в дальнейшем и используется, и пусть сама она при проверке все это дело извлекает, вычисляет,проверяет, на то она и отдельная функция. Такая идея мне понравилась (не знаю как вам), поэтому все так и реализованно. Предвижу вопрос - ну и нахуй [censored] нужно было всё это расписывать да еще и на столько строчек - и,  предвидя, отвечаю - дабы объяснить кажущуюся излишнюю трудность использования скрипта, которой очень часто приходится платить за расширение функциональности и гибкости конечного решения. 

Теперь,когда ты, уважаемый читатель, подкован в теоретическом смысле, перейдем к практической реализации сией гениальной задумки. Итого, скрипт реализует несколько функций (в квадратных скобках необязательные параметры):

Функции:

  1. Первая и самая главная:

    Функция registerhandle производит регистрацию уникального идентификатора ( ufh ) с настройками флуд контроля.

    registerhandle
    floodsettings [hostmask type] [ignore flags] [callback_function] [strict mode]

    Parameters:
    Обязательным параметром является только floodsettings в формате times:seconds то есть сколько раз разрешенно в определенное количество времени (в секундах).

    Если
    тип хостмаски не указан, ей будет присвоено значение по умолчанию ( masks(host) ).

    ignore flags - флаги при наличии которых функция isflood будет игнорировать проверку и возвращать 0.Флаги имеют стандартный формат eggdrop'a. Если флаги пользователя совпадают с ignore flags функция не заполняет массив временных меток и не создает запись о пользовательской маске

    Callback function
    , тип маски хоста, флаги игнорирования как и strict mode являются необязательными.
    Если функция обратного вызова не указана, она не будет вызвана при возникновении флуда.

    параметр strict mode определяет, как функция isflood будет обрабатывать проверки на флуд,после того как флуд обнаружен. По умолчанию выставляется в 1,то есть после того как функция определила флуд, требуется чтобы истек интервал времени заданный в floodsettings. 

    Например:
    floodsettings = 5:30, пользователь превышает этот лимит, был kick'нут с канала и зашел обратно и инициировал проверку на флуд (например использовал команду бота), функция isflood проверит истекли ли 30 секунд после того как данный пользователь был помечен как флудер. Если этот промежуток времени еще не истек, функция isflood возвратит 1,то есть флуд обнаружен. Если strictmode установлен в 0, такая проверка не делается. На внутреннем уровне это реализованно через заполнение массива временных меток ( timestamps ), т.е. по умолчанию после обнаружения флуда данный массив полностью забивается временем когда произошел флуд, если же strictmode равен 0 то изменение массива не производится и он содержит штампы последовательных срабатываний.

    Возвращаемые значения:
    Значения больше 0 - идентификатор ufh, функция зарегистрировала манипулятор удачно.
    Отрицательные значения возвращаются в случае каких-либо ошибок.
    Расшифровка кодов ошибок:
     -1 - общая ошибка,в текущей версии не используется
     -2 - ошибка в функции обратного вызова - не существует ( возвращается если checkforcallback выставлено в 1 )
     -3 - ошибка в формате floodsettings
     -4 - ошибка в формате hostmask type - такой тип не определен
     -5 - ошибка в формате strictmode

    примеры внизу

  2. unregisterhandle

    Функция unregisterhandle уничтожает заданный манипулятор ( ufh ), удаляя все временные метки и маски пользоватей связанные с этим ufh .

    unregisterhandle ufh

    Parameters:
    ufh - идентификатор настроек полученный из registerhandle

    Возвращаемые значения:
    1 - идентификатор успешно удален.
    -11 - ошибка, такой идентификатор не существует.

  3. isflood

    Функция isflood производит проверку на флуд

    isflood
    ufh nick uhost [chan] [ignore flags]

    Parameters:
    ufh  - идентификатор настроек полученный из registerhandle 

    nick -
    псевдоним пользователя

    uhost -
    хост пользователя в формате ident@hostname

    chan -
    имя канала. Опциональный параметр, кроме того chan не обязательно должен быть именем реального канала, т.к. функция никаких действий по отношению к пользователю и каналу не предпринимает (если не задан параметр ignore flags). Данный параметр является своего рода идентификатором. По умолчанию равен all,  то есть если проверяются команды/private query/notices и параметр chan опущен,  события будут регистрироваться на виртуальный канал all и при вызове callback function передается так же all !

    ignore flags -
    флаги при наличии которых функция будет игнорировать проверку и возвращать 0.Флаги имеют стандартный формат eggdrop'a. Если флаги пользователя совпадают с ignore flags функция не заполняет массив временных меток и не создает запись о пользовательской маске. Обратите внимание, что флаги переданные при вызове isflood имеют больший приоритет чем флаги указанные в registerhandle !

    Возвращаемые значения:
    1 если флуд обнаружен
    0 если флуд не обнаружен
    В случае ошибки возвращаются отрицательные значения

    Расшифровка кодов ошибок:
     -21 - такой ufh не существует

    -22 - ошибка при создании маски - неверный формат uhost  либо nick

    примеры внизу
  4. peacetime

    Функция peacetime определяет интервал времени ( в секундах ) по истечении которого isflood вызванная с теми же параметрами ( ufh nick uhost chan ) вернет 0, т.е. с помощью этой функции можно определить через какой промежуток времени событие должно произойти чтобы не быть расцененным как флуд, полезно для уведомления пользователей о том сколько нужно подождать чтобы снова воспользоваться ботом.

    peacetime
    ufh nick uhost [chan]

    Parameters:
    ufh  - идентификатор настроек полученный из registerhandle 

    nick -
    псевдоним пользователя

    uhost -
    хост пользователя в формате ident@hostname

    chan -
    имя канала. Опциональный параметр, кроме того chan не обязательно должен быть именем реального канала, т.к. функция никаких действий по отношению к пользователю и каналу не предпринимает (если не задан параметр ignore flags). Данный параметр является своего рода идентификатором. По умолчанию равен all,  то есть если проверяются команды/private query/notices и параметр chan опущен,  события будут отнесенны к виртуальному каналу all.

    Возвращаемые значения:
    Время в секундах 
    В случае ошибки возвращаются отрицательные значения

    Расшифровка кодов ошибок:
     -31 - такой ufh не существует

    -32 - ошибка при создании маски - неверный формат uhost  либо nick

    примеры внизу
  5. formatmessage
    Функция возвращает описание ошибки по ее числовому коду

    formatmessage code

    Parameters:
    code - код ошибки возвращенный одной из предыдущих функций, если такого кода не определенно, возвращается 
    "there is no message for such error code"

Константы:

  1. Типы хостмасок

    masks(fullmask) -
    полная маска вида nick!ident@hostname.
    masks(nickhost) - маска вида nick!*@hostname.
    masks(identhost) - маска вида *!ident@hostname.
    masks(host) - маска вида *!*@hostname.
  2. Коды ошибок
    -1 Register handle: general errror
    -2 Register handle: callback function doesn't exist
    -3 Register handle: floodsettings have wrong format
    -4 Register handle: such hostmask type is not supported or not defined
    -5 Register handle: strict mode setting format error
    -11 Unregister handle: such ufh doesn't exist
    -21 isflood: no such ufh exists
    -22 isflood: error while creating usermask,check nick & uhost values
    -31 isflood: no such ufh exists
    -32 isflood: error while creating usermask,check nick & uhost values
    -41 CreateMask: there is no such hostmask type
    -42 CreateMask: uhost variable is not valid
    -43 CreateMask: nick variable is not valid

Прочие настройки:

  1. checkforcallback
    В случае если checkforcallback выставлено в 1 и callback function переданная в registerhandle не существует, registerhandle возвратит ошибку. По умолчанию стоит в 0, изменяется путем правки скрипта.

Примеры:

  1. Самый простой
    #example tcl for uafs
    namespace eval beer {

    #registering handle 
    set beer_ufh [::uafs::registerhandle "3:40"]
    #is all okay?
    if {$beer_ufh<0} { putlog "unable to register handler";die "unable to register handler beer_ufh"}
    #putlog $beer_ufh

    proc beerproc { nick uhost hand chan rest } {
    variable beer_ufh;global lastbind
    set bla [::uafs::isflood $beer_ufh $nick $uhost $chan]
    if {$bla=="1"} {
    set piu [::uafs::peacetime $beer_ufh $nick $uhost $chan]
    putserv "NOTICE $nick :You are flooder! next time u may use $lastbind after [duration $piu]"
    } else {
    putserv "PRIVMSG $chan :here is your beer $nick"
    }
    }
    }

    bind pub - "!beer" ::beer::beerproc
    putlog "example tcl for uafs loaded"
  2. Чуть посложнее:
    #example2 tcl for uafs
    namespace eval tea {

    #registering handle 
    set tea_ufh [::uafs::registerhandle "3:40"]
    #is all okay?
    if {$tea_ufh<0} { putlog "unable to register handler";die "unable to register handler tea_ufh"}

    proc teaproc { nick uhost hand chan rest } {
    variable tea_ufh;global lastbind
    set bla [::uafs::isflood $tea_ufh $nick $uhost $chan]
    if {$bla=="1"} {
    if {[botisop] || [botishalfop]} {
    putserv "kick $chan $nick :flooding with \"$lastbind\" command!"
    }
    } else {
    putserv "PRIVMSG $chan :here is your tea $nick"
    }
    }
    }

    bind pub - "!tea" ::tea::teaproc
    putlog "example2 tcl for uafs loaded"
  3. Самый большой и сложный,и самый красивый :)
    #example3 tcl for uafs,the most complex one
    namespace eval money {

    #registering handle 
    set money_ufh [::uafs::registerhandle "3:40" $::uafs::masks(host) "" "" "0"]
    #how many floods in 2 minutes will be allowed
    set money_flood_ufh [::uafs::registerhandle "2:120"]
    #is all okay?
    if {$money_ufh<0 || $money_flood_ufh<0} { putlog "unable to register handler";die "unable to register handler money_ufh or money_flood_ufh"}

    proc moneyproc { nick uhost hand chan rest } {
    variable money_ufh;variable money_flood_ufh;global lastbind;global botnick
    set bla [::uafs::isflood $money_ufh $nick $uhost $chan]
    if {$bla=="1"} {
    set blabla [::uafs::isflood $money_flood_ufh $nick $uhost $chan]
    if {$blabla=="1"} {
    #user has flooded us several times!

    set mask "[string tolower [::uafs::createmask $nick $uhost $::uafs::masks(host)]]"
    #checking if mask was created ok?
    if {[string is integer $ufhmask]} {
    #error while creating mask,looks like some params are not valid
    return "-1"
    }
    newchanban $chan $mask $botnick "flooding with $lastbind at $chan" 120
    } else {
    if {[botisop] || [botishalfop]} {
    putserv "kick $chan $nick :Stop flooding me with \"$lastbind\" bastard! Calm down and wait for [duration [::uafs::peacetime $money_ufh $nick $uhost $chan]]"
    }
    }
    } else {
    putserv "PRIVMSG $chan :I can't give u money, $nick"
    }
    }
    }

    bind pub - "!money" ::money::moneyproc
    putlog "example3 tcl for uafs loaded"
  4. Самый разжеванный и понятный ( я надеюсь :)
    #PM message na forume from aNt^N!0
    #NOTE: PM'ing isn't good,use email next time :)

    #задача:
    #Привет! Можешь помочь с одним скриптом, пожалуйста.
    #Когда кто-нибудь ввёл воторой раз в течение 5 минут "!кручу", бот говорил,
    #чтобы подождал 5 минут, если он начинает флудить: предпреждение, 2 кика, 
    #потом бан на 1 минуту!

    #example4.tcl for uafs
    #образцово-показательный пример
    namespace eval twist {

    set infoline "example4.tcl for uafs,was born with aNt^N!0 request"
    set counter "0"


    #опрделяем собственный putlog в нашем пространстве имен
    proc putlog { text } {
    if {$text!=""} {
    ::putlog "--- TWIST script: $text"
    }
    }

    #проверяем - загружен ли uafs,пользуемся тем что tcl производит
    #"ленивые" сравнения - то есть не проверяет все выражение,а по частям.
    #таким образом, если переменной ::uafs::numver не существует, проверка
    #ее значения не будет произведена.
    if {[info exists "::uafs::numver"] && $::uafs::numver>="000004"} {
    #загружен,регистрируем наш handler
    #с параметрами - 1 флуд в 300 секунд,тип хостмаски - 
    #masks(host) - маска вида *!*@hostname, игнорировать пользователей
    #с флагом +f глобальный или +f на канале
    set ufh [::uafs::registerhandle "1:300" $::uafs::masks(host) "f|f"]
    if {$ufh>0} {
    #регистрация прошла успешно,делаем биндинг
    bind pub - "!twist" ::twist::twistproc

    } else {
    #в случае ошибки выводим сообщение в лог с кодом ошибки и описанием
    putlog "registerhandle failed. error code \"$ufh\",error info: \"[::uafs::formatmessage $ufh]\""
    }

    } else {
    putlog "--------------------------------------------------"
    putlog "| No UAFS.tcl installed or too old version found |"
    putlog "| Install UAFS.tcl first and load it before |"
    putlog "| loading this script! |"
    putlog "--------------------------------------------------"
    }

    proc twistproc { nick uhost hand chan rest } {
    variable ufh;variable counter
    global lastbind botnick
    #делаем проверку на флуд
    set flooded [::uafs::isflood $ufh $nick $uhost $chan]
    if {$flooded=="1"} {
    #флуд обнаружен, увеличиваем счетчик флудов на еденицу...
    incr counter
    switch -exact -- $counter {
    "1" {
    #юзер флудит 1ый раз...
    putserv "NOTICE $nick :Next time you may use \"$lastbind\" after [duration [::uafs::peacetime $ufh $nick $uhost $chan]]"
    }
    "2" {
    #2ой раз...
    putserv "NOTICE $nick :Achtung! Stop flooding me with \"$lastbind\""
    }
    "3" -
    "4" {
    #3ий и 4ый разы совмещены в одну команду...дофлудится ведь :)

    #является ли бот опом или халфопом?
    if {[botisop] || [botishalfop]} {
    #раз да,то кикаем.
    putserv "kick $chan $nick :Stop flooding me with \"$lastbind\""
    }
    }
    default {
    #все остальные разы флуда обрабатываются здесь...

    #создаем маску для бана.
    set mask "[string tolower [::uafs::createmask $nick $uhost $::uafs::masks(host)]]"
    #ставим бан.
    newchanban $chan $mask $botnick "Flooding with $lastbind at $chan" 1
    }
    }
    } elseif {$flooded=="0"} {
    #раз флуда нет, выставляем счетчик в 0,чтобы обнулять результаты
    set counter "0"
    ##########################################################
    #
    # здесь выполняется вся полезная работа скрипта :)
    putserv "PRIVMSG $chan :$nick twisting!"
    #
    ##########################################################
    } else {
    #в случае какой-либо ошибки isflood возвращает ее код в виде отрицательного значения
    #выводим этот код и описание пользователю
    putlog "isflood check failed for ufh \"$ufh\", error info: \"[::uafs::formatmessage $ufh]\", though this shouldn't happen"
    }
    }
    }


    putlog "$::twist::infoline loaded"