Hybrid Oper Script Collection Design Document By Joost "Garion" Vunderink Creation date: 05 March 2003 Last update: 09 March 2003 by Garion. Update history: 20030309 - [Garion] Added ServerNotice module and ho_lusercount script. Expanded script/module explanation. ----------------------------------------------------------------------- Introduction. The Hybrid Oper Script Collection (HOSC) is a collection of irssi (http://irssi.org/) scripts to make opering on a hybrid server a lot easier. The main target of these scripts are opers on EFnet servers. This document is for the v2 of the HOSC. The first set of scripts evolved so rapidly that it was time to stop the insanity and properly design the second generation. WARNING: the old scripts are not intended for use on multiple servers. Use them in that way on your own risk. ----------------------------------------------------------------------- Description. HOSC will consist of many scripts that each are dedicated to a single task. Not a huge script that does everything. To make things consistent between scripts: - modules will be used for common functions; - formats will be used so all messages look the same; - templates will be used for all scripts. All files will be stored in a CVS repository. ----------------------------------------------------------------------- Contributors. Currently I am the main coder of the project. JamesOff, zapi and AilleCat are helping with coding, and Jaded will hopefully help with this document. ----------------------------------------------------------------------- List of modules and scripts. Modules: ClientStorage Stores clients and searches in clients. DCCChat DCC Chat interface. Kliner Facilitates placing K-lines. Link Links hosc clients together for information sharing. Pending Pending K-line list. ServerNotice Server Notice translation. Tools Basic tools and common functions. Scripts: ho_challenge Allows (automatic) opering via /CHALLENGE. ho_channel Deals with channels. ho_gline Facilitates the use of G-lines. ho_hammer Deals with clients reconnecting too fast. ho_iline Deals with clients trying to connect in full I-lines. ho_jupe Deals with clients joining juped channels. ho_killreconnect Reconnects irssi if killed by an oper. ho_lusercount Lusercount statusbar item. ho_mkill Mass kill command. ho_nikita Client monitor. ho_operwall Facilitates the use of operwall and locops. ho_reformat Reformats server notices. ho_track Keeps track of anomalies in connecting clients. ----------------------------------------------------------------------- Common properties of all scripts. Each script must be multiple server ready. Each script - shall provide a /NAME similar to the name of the script - has /NAME VERSION to show the version. - has /NAME STATUS to show the current status. - has /NAME HELP to show help. - has /NAME INTRO to show an introduction, i.e. how to get the script working. - has /NAME ENABLE/DISABLE as master enable setting. - has a setting for the output window of its messages. - has a list of servers it works on. - has settings per server (tricky!). - might have to be able to write its settings to disk for persistence. Settings of each script: ho__* servers - the space-separated list of servertags on which the script will work ----------------------------------------------------------------------- Settings. If all scripts are to be multiple-server ready, using ho_script_* for settings will not suffice. For example, you might want to use a time of 1440 minutes for K-lines on one server and 10080 on another server. It might be necessary to create a Settings class, which will keep its data in a file. Need to investigate that. Ok, just thought about this in the shower, and the best idea seems to create a Settings class and have one $s per loaded script, which is associated with a unique file per script. In the settings object, settings per server tag are stored. These settings can be accessed by: $s->{$servertag}->{$setting} As an example: /qoper set efnet enable ON /qoper set efnet nick Garion /qoper set efnet usermode +bcdfiklnorsuwxyz Adding/deletion of server: hmmm, how? Addition can be done implicitly, so doing a set on a new server will add the servertag. Deletion: /qoper set efnet -delete ? ----------------------------------------------------------------------- Detailed description of each script and module. Below follows a description per script/module. ----------------------------------------------------------------------- ClientStorage This is an object that contains a hashtable which holds all clients connected to the server. The key of each item in the hashtable is the nickname of the client, and the value is a reference to an array with the data of the client. This clientinfo array contains: $client[NICK] - nickname $client[USER] - username $client[HOST] - hostname $client[REAL] - realname (gecos) $client[IP] - ip address $client[CLASS] - user class $client[TIME] - time of connection (seconds since 1 jan 1970) These 7 constants NICK..TIME are exported by this module. Clients can be added/removed/modified. Information of each client consists of nick, user, host, ip address, class, realname, and time of connection. Also provides searching (regexp, glob) in all these properties. This module only works for _one_ server, so in scripts you must create one ClientStorage object per server you want to use it on. * add(%clienthash) Adds a client to the hash. %clienthash has the following keys: nick, user, host, ip, class, real. If a client is added whose nickname is already present, the data of that client is updated. If the nick+user+host are equal, and the new realname is empty, the realname is not updated (this happens if you have some clients already in your hash via connect notices and then do a /TRACE). * remove($nick) Removes the client with nickname $nick from the hash. * nickchange($oldnick, $newnick) Changes the nickname of client $oldnick to $newnick. * exists($nick) Returns 1 if there is a client with nick $nick in the hash, returns 0 otherwise. * get_number() Returns the number of clients in the hash. * clear() Clears the whole hash. * match(%args) Matches all clients with these regexps. Possible keys in the argument hash: nick - regexp for nick match user - regexp for user match host - regexp for host match real - regexp for real match ip - regexp for ip match class - regexp for class match nickisuser - set to 1 if the nickname must be equal to the username (ignoring whether there is a ~ in the username or not) Empty regexps are of course ignored. Returns an array of references to clientinfo objects. * matchmask($hostmask) Matches all clients with this hostmask (*!*huk@*.tilde.kek). Returns an array of references to client info objects matching this hostmask. ----------------------------------------------------------------------- DCCChat Er, yes, provide DCC interface with user/pass crap. Not to be developed shortly. ----------------------------------------------------------------------- Kliner This module can be used in any script that uses K-lines. You can set the time and the reason of the K-lines that are placed via this object. A Kliner objects holds a reference to the SERVER the K-lines are placed on. $kliner->set_server($server); $kliner->set_time(10080); $kliner->set_reason("all your base are belong to me"); $kliner->set_kline_all_for_unidented(1); $kliner->set_kline_all_for_idented(0); for my $hostmask (@hostmasks) { $kliner->kline($server, $hostmak); } It would probably be a good idea for the Kliner class to export some settings to irssi. I was thinking of these settings: - whether to K-line *@h for all non-idented hostmask - whether to K-line *@h or *user@h - whether to K-line @full.host.name or @*.host.name Let's call these settings allnonident, klineuser, hostwildcard for now. These names suck and they should be improved :) This means that if you create a new Kliner object like this: # $server is a reference to a SERVER object my $kliner = hosc::Kliner->new($server, "myscript"); where 'myscript' is the name of your script, the constructor of the Kliner object will export these settings to irssi: ho_myscript_kliner_allnonident ho_myscript_kliner_klineuser ho_myscript_kliner_hostwildcard This means that all scripts that use the Kliner object automatically have the settings for the K-lines exactly the same. This is imho a Good Thing(tm). ----------------------------------------------------------------------- Pending This is a list of pending K-lines. Each K-line has a hostmask, time, reason, and source (who added it to the pending list). K-lines can be added to and removed from the list, and with a single command, all pending K-lines can be placed. Members: * server The server that this Pending list is for. This is a SERVER object. Functions: * set_time($time) Sets the time of any K-lines added after this function. Setting the time to 0 means permanent K-lines. Default: 1440. * set_reason($reason) Sets the reason of any K-lins added after this function. Default: 'drones/flooding". * add($hostmask) Adds a K-line with the stored time and reason. * lock Locks the pending K-line list so no new K-lines can be added. All new K-lines will be put in a buffer array. * unlock Unlocks the pending K-line list and adds the K-lines in the buffer array to the pending list, if any. * place/exec Places all pending K-lines. ----------------------------------------------------------------------- ServerNotice Each version of each ircd has slightly different server notices for each message. This module takes a server notice and returns a hash with the relevant information from that server notice. Functions: * translate($notice) Takes the string $notice and tries to translate it. If successful, a hash is returned with the type of the notice, and the relevant data belonging to that notice. Idea: create an array of %translations: %translation = ( 'regex' => 'Client connecting: (.*) \((.*)@(.*)\) \[(.*)\] {(.*)}', 'type' => 'client_connect', 'fields' => ['nick', 'user', 'host', 'ip', 'class'] ); And then during processing: my $text = undef; my $type = undef; foreach my $notice (keys $translation) { if ($notice =~ /$notices{$notice}->{'regex'}/) { $type = $notices->{$notice}->{'type'}; for(my $i = 0; $i < scalar($notices{$notice}->{'fields'}); $i++) { $text .= '"\''.$notices{$notice}->{'fields'}[$i].'\' => $'.$i.','; } } } my %noticedata; eval '%noticedata = ('.$text.');'; if ($@) { # error } else { $noticedata{type} = $type; return %noticedata; } ----------------------------------------------------------------------- Tools Some basic tools, like testing if regexps are valid, printing messages to windows, getting window references from names, etc. Functions: * test_regexps($re1[, $re2]+) Tests whether the given regexp(s) is/are valid. Returns 1 if they ALL are valid, and 0 if at least 1 is non-valid. "Valid" means that it's a legal regexp, right number of ()'s, etc. Use in script: if (!test_regexps($nickre, $userre, $hostre)) { Irssi::print("Invalid regexp(s)."); return; } * get_window_by_name($name) Looks for the window named $name. If this exists, a reference to this window is returned. If not, a reference to the active window is returned. This means that a window reference obtained by my $win = get_window_by_name("name"); always is a valid window reference and no if (defined($win)) check is necessary before using it to print to. * get_msglevel($level) Returns the integer message level value for the string $level. Possible values for $level are "HILIGHT", "MSG", "CRAP" and "NONE". If $level is any other string, the level for "CRAP" is returned. If $level is an integer and equal to one of the integers representing the "HILIGHT", "MSG" or "NONE" levels, $level itself is returned. If $level is a different integer, the level for "CRAP" is returned. * print($message[, $winname[, $msglevel]]) Prints $message to $winname with message level $msglevel, using default HOSC formatting. $msglevel is one of the strings "HILIGHT", "MSG", "CRAP" or "NONE", with default value "CRAP". The intrinsic integer values that irssi uses (MSGLEVEL_*) can be used as well. $winname is the name of the window this message is printed to. If this window is not found, or this argument is omitted, the message is sent to the active window. Note: use a format for this? ho_tools_message_default? So people can change that if they want? ----------------------------------------------------------------------- ho_challenge Allows (automatic) opering via /CHALLENGE (i.e. encrypted). ----------------------------------------------------------------------- ho_channels Some channel tools. Can do /WHO and then show a sorted list of users, per server. ----------------------------------------------------------------------- ho_gline Whenever a G-line is requested, this script stores that request and makes it possible to support it with /GLINE . After the G-line has been triggered, it's removed from the interal G-line list. After N minutes, an untriggered G-line is removed as well. This script is in beta stadium. It's being tested by me, Garion, and I found some annoyances which prevent a quick release. Most annoying things are: - the order of the final request notice and the trigger notice seems to be random - sometimes a request appears of a G-line which has already been triggered. ----------------------------------------------------------------------- ho_hammer Keeps track of users that reconnect more than X times in Y seconds and then sets an automatic K-line either on *user@host or *@host. Settings: ho_hammer_* warning_count - the number of times a client must reconnect before a warning is shown in irssi. warning_time - the time within the client must reconnect warning_count times before the warning is shown. violation_count - the number of times a client must reconnect before the client is K-lined. violation_time - the time within the client must reconnect violation_count times before the K-line is placed. Thoughts: * How to store this data? Previous version stored the connection times in a hash table: key = hostname, value = array of integers (time in unix seconds). I think both connects and quits should be taken into account: whenever a client quits, check when it connected, and if that was within N seconds, store this. Hm. * K-line *@h or *u@h? Perhaps first u@h and when it switches ident, K-line *@h? ----------------------------------------------------------------------- ho_iline If a client connects more than X times in Y seconds in a full I-line: - a warning can be shown in irssi - a temp K-line can be placed on *@host See ho_hammer for more information. This script will be almost identical to ho_hammer. ----------------------------------------------------------------------- ho_jupe If a client joins more than X juped channels in Y seconds: - a warning can be shown in irssi - a notice can be sent to the violating client - a temp K-line can be placed on *user@host - a temp K-line can be placed on *@host See ho_hammer for more information. This script will be almost identical to ho_hammer. ----------------------------------------------------------------------- ho_killreconnect Reconnects irssi if killed by an oper. Does not do anything else. Functions: none. Settings: none. ----------------------------------------------------------------------- ho_lusercount Does a /lusers upon load and then keeps track of the number of lusers on the server over time using connection/exit server notices. Adds a statusbar item with the number of lusers in it. Requires usermode +c. Settings: ho_lusercount_* servers - tags of the servers to keep the count on calibration_time - amount of minutes between calibration /LUSERS. Set to 0 to disable. Formats: ho_lusercount_* statusbar - the format of the statusbar item Thoughts: * Change contents of statusbar item to "set +c" for each server that the user is not +c on? ----------------------------------------------------------------------- ho_mkill Provides a /mkill command. This command kills users in a channel with a certain hostmask. Opers are always excluded automatically; ops are excluded unless a flag is given to kill ops as well. /MKILL [-ops] [-opers] [-YES] [:][reason] Kills all users in the channel in this window matching the given hostmask. By default, ops and opers are not targeted, and if you omit the -YES flag, the only thing that happens is that the list of nicks that will be killed is printed. The : before the reason can be omitted. Flags: -YES : actually kills the clients -ops : include channel ops -opers : include opers /MKILL [nick2 [nick3 [nick4..]]] :[reason] Kills these nicks with the specified reason. /MKILL [-opers] [-cycle] <#channel> Kills all users except opers in #channel. If -opers is specified, kills all users. If -cycle is specifiel, cycles (part/join) the channel after all clients have been killed. I wonder what this is useful for. Thoughts: - The bool is_this_nick_an_oper is not very reliable. One should always do a /WHO before using /MKILL on a channel. Perhaps we should find a way to do this automatically. Yups, this is indeed a good idea. Execute /who from inside the script. Already done it in a script, need to find out where it was again. With thanks to Borys :) ----------------------------------------------------------------------- ho_nikita The ultimate search and destroy script. Will hopefully do as much as TCM does, and more. Stores all clients connected to the server. Allows regexp and glob searching through clients' nick, user, host, and realname. Things like $nick =~ /$regexp/ && $user eq $nick are also possible. Can do the following actions after a search: show number of results, show all results, add all results to the Pending K-line list. Has a DCC interface with username/hostmask/password access so only one nikita script needs to run per server. This will be the largest script by far. See below for more details. Hm, about the command names: there must be a command for regexp searching, and a command for hostmask searching. Any ideas for the best names? Or use 'find' for both, checking if there is a "@" in the search data? /nikita find [options] [action [parameters]] /nikita find [options] [action [parameters]] Options: -i applies /i to each regexp. /nikita channel [#channel] [action [parameters]] /nikita autokline [[options] ] /nikita autokline [[options] ] /nikita clear|empty Clears the client hash. /nikita reset Clears the client hash and executes /TRACE to fill it again. Settings: ho_nikita_* trace_on_oper - bool, whether to perform a /trace automatically after opering up. ----------------------------------------------------------------------- ho_operwall Requires a window named "operwall" and a window named "locops". All operwalls go to window operwall, and all locops go to window locops. Messages typed in window operwall are sent as operwall, and ditto for window locops. In case of multiple servers, make sure that each operwall is shown only once. Make it possible to prepends locops with the tag of the server it's being sent on. Or perhaps make 1 locops window per server? Sending of locops on multiple servers is easy, just use ^X to switch between servers and then type your locops. Settings: Formats: ho_operwall_* operwall - $0 nick, $1 servertag, $2 message locops - $0 nick, $1 servertag, $2 message ----------------------------------------------------------------------- ho_reformat Matches each incoming server notice with a set of regular expressions. If one of them matches, the matched substrings are put in a format and this new message is sent to one or more named windows (or to the active window, or discarded completely). Commands: /reformat list [windowname] Shows a list of all reformattings, or all reformattings to the window in the argument. /reformat create Creates all named windows that are present in the config file but not in the client. /reformat inject Injects a fake notice event into irssi. Use this for testing. ----------------------------------------------------------------------- ho_track Keeps track of the number of connections per minute. If the number of connections in the past minute are significantly higher than the average, a warning is shown. Also calculates the "dronishness" of a nickname. If over a certain limit, a warning is shown. Commands: /track average [value] Without argument, shows the current average of clients connecting per minute. With argument, sets the average to that value. ----------------------------------------------------------------------- Comments. Currently, irssi doesn't particularly like multiple-file scripts. Especially if you load a script that in turn loads a module, and you install a new version of the module, you need to restart irssi (!) to load the new module. I have talked to cras about this and I think he solved that. But I'm not sure yet. I'll test this and if it doesn't work, make sure that this is fixed. -----------------------------------------------------------------------