├── .gitignore ├── .gitmodules ├── INDEV ├── LICENSE ├── README.md ├── VERSION ├── bin ├── ircd ├── ircd_forever └── mkpasswd ├── db └── README ├── doc ├── cmodes.md ├── config.md ├── history.md ├── index.md ├── ircv3.md ├── modules.md ├── oper_flags.md ├── oper_notices.md ├── technical │ ├── api │ │ ├── channel.md │ │ ├── message.md │ │ ├── modes.md │ │ ├── pool.md │ │ └── user.md │ ├── proto │ │ ├── jelp.md │ │ ├── outgoing.md │ │ ├── protocol.md │ │ └── ts6.md │ └── rfc-2812 ├── ts6.md └── umodes.md ├── etc ├── class.conf ├── default.conf ├── ircd.conf.example └── ircd.motd.example ├── extra └── atheme │ └── juno.c ├── juno ├── juno-start.bat ├── modules ├── AddUserTest.module │ ├── AddUserTest.json │ └── AddUserTest.pm ├── Alias.module │ ├── Alias.json │ └── Alias.pm ├── Ban │ ├── Ban.module │ │ ├── Ban.json │ │ ├── Ban.pm │ │ ├── Info.module │ │ │ ├── Info.json │ │ │ └── Info.pm │ │ ├── JELP.module │ │ │ ├── JELP.json │ │ │ └── JELP.pm │ │ └── TS6.module │ │ │ ├── TS6.json │ │ │ └── TS6.pm │ ├── Dline.module │ │ ├── Dline.json │ │ └── Dline.pm │ ├── Kline.module │ │ ├── Kline.json │ │ └── Kline.pm │ └── Resv.module │ │ ├── Resv.json │ │ └── Resv.pm ├── Base │ ├── AddUser.module │ │ ├── AddUser.json │ │ └── AddUser.pm │ ├── Capabilities.module │ │ ├── Capabilities.json │ │ └── Capabilities.pm │ ├── ChannelModes.module │ │ ├── ChannelModes.json │ │ └── ChannelModes.pm │ ├── Matchers.module │ │ ├── Matchers.json │ │ └── Matchers.pm │ ├── OperNotices.module │ │ ├── OperNotices.json │ │ └── OperNotices.pm │ ├── RegistrationCommands.module │ │ ├── RegistrationCommands.json │ │ └── RegistrationCommands.pm │ ├── UserCommands.module │ │ ├── UserCommands.json │ │ └── UserCommands.pm │ ├── UserModes.module │ │ ├── UserModes.json │ │ └── UserModes.pm │ └── UserNumerics.module │ │ ├── UserNumerics.json │ │ └── UserNumerics.pm ├── Channel │ ├── Access.module │ │ ├── Access.json │ │ └── Access.pm │ ├── Fantasy.module │ │ ├── Fantasy.json │ │ └── Fantasy.pm │ ├── Forward.module │ │ ├── Forward.json │ │ └── Forward.pm │ ├── Invite.module │ │ ├── Invite.json │ │ └── Invite.pm │ ├── JoinThrottle.module │ │ ├── JoinThrottle.json │ │ └── JoinThrottle.pm │ ├── Key.module │ │ ├── Key.json │ │ └── Key.pm │ ├── Knock.module │ │ ├── Knock.json │ │ └── Knock.pm │ ├── LargeList.module │ │ ├── LargeList.json │ │ └── LargeList.pm │ ├── Limit.module │ │ ├── Limit.json │ │ └── Limit.pm │ ├── ModeSync.module │ │ ├── Desync.module │ │ │ ├── Desync.json │ │ │ └── Desync.pm │ │ ├── JELP.module │ │ │ ├── JELP.json │ │ │ └── JELP.pm │ │ ├── ModeSync.json │ │ └── ModeSync.pm │ ├── Mute.module │ │ ├── Mute.json │ │ └── Mute.pm │ ├── NoColor.module │ │ ├── NoColor.json │ │ └── NoColor.pm │ ├── OpModerate.module │ │ ├── OpModerate.json │ │ └── OpModerate.pm │ ├── OperOnly.module │ │ ├── OperOnly.json │ │ └── OperOnly.pm │ ├── Permanent.module │ │ ├── Permanent.json │ │ └── Permanent.pm │ ├── RegisteredOnly.module │ │ ├── RegisteredOnly.json │ │ └── RegisteredOnly.pm │ ├── SSLOnly.module │ │ ├── SSLOnly.json │ │ └── SSLOnly.pm │ ├── Secret.module │ │ ├── Secret.json │ │ └── Secret.pm │ └── TopicAdditions.module │ │ ├── TopicAdditions.json │ │ └── TopicAdditions.pm ├── Cloak.module │ ├── Charybdis.module │ │ ├── Charybdis.json │ │ └── Charybdis.pm │ ├── Cloak.json │ └── Cloak.pm ├── Configuration │ └── Set.module │ │ ├── Set.json │ │ └── Set.pm ├── Core │ ├── ChannelModes.module │ │ ├── ChannelModes.json │ │ └── ChannelModes.pm │ ├── Core.module │ │ ├── Core.json │ │ └── Core.pm │ ├── Matchers.module │ │ ├── Matchers.json │ │ └── Matchers.pm │ ├── OperNotices.module │ │ ├── OperNotices.json │ │ └── OperNotices.pm │ ├── RegistrationCommands.module │ │ ├── RegistrationCommands.json │ │ └── RegistrationCommands.pm │ ├── UserCommands.module │ │ ├── UserCommands.json │ │ └── UserCommands.pm │ ├── UserModes.module │ │ ├── UserModes.json │ │ └── UserModes.pm │ └── UserNumerics.module │ │ ├── UserNumerics.json │ │ └── UserNumerics.pm ├── DNSBL.module │ ├── DNSBL.json │ └── DNSBL.pm ├── Eval.module │ ├── Eval.json │ └── Eval.pm ├── Git.module │ ├── Git.json │ └── Git.pm ├── Grant.module │ ├── Grant.json │ └── Grant.pm ├── Ident.module │ ├── Ident.json │ └── Ident.pm ├── JELP │ ├── Base.module │ │ ├── Base.json │ │ └── Base.pm │ ├── Incoming.module │ │ ├── Incoming.json │ │ └── Incoming.pm │ ├── JELP.module │ │ ├── JELP.json │ │ └── JELP.pm │ ├── Outgoing.module │ │ ├── Outgoing.json │ │ └── Outgoing.pm │ └── Registration.module │ │ ├── Registration.json │ │ └── Registration.pm ├── LOLCAT.module │ ├── LOLCAT.json │ └── LOLCAT.pm ├── Lastfm.module │ ├── Daemon.module │ │ └── Daemon.pm │ └── Lastfm.pm ├── Modules.module │ ├── Modules.json │ └── Modules.pm ├── Monitor.module │ ├── Monitor.json │ └── Monitor.pm ├── Reload.module │ ├── Reload.json │ └── Reload.pm ├── Resolve.module │ ├── Resolve.json │ └── Resolve.pm ├── SASL │ └── SASL.module │ │ ├── JELP.module │ │ ├── JELP.json │ │ └── JELP.pm │ │ ├── SASL.json │ │ ├── SASL.pm │ │ └── TS6.module │ │ ├── TS6.json │ │ └── TS6.pm ├── TS6 │ ├── Base.module │ │ ├── Base.json │ │ └── Base.pm │ ├── Incoming.module │ │ ├── Incoming.json │ │ └── Incoming.pm │ ├── Outgoing.module │ │ ├── Outgoing.json │ │ └── Outgoing.pm │ ├── Registration.module │ │ ├── Registration.json │ │ └── Registration.pm │ ├── TS6.module │ │ ├── TS6.json │ │ └── TS6.pm │ └── Utils.module │ │ ├── Utils.json │ │ └── Utils.pm └── ircd.module │ ├── channel.module │ ├── channel.json │ └── channel.pm │ ├── connection.module │ ├── connection.json │ └── connection.pm │ ├── ircd.json │ ├── ircd.pm │ ├── message.module │ ├── message.json │ └── message.pm │ ├── modes.module │ ├── modes.json │ └── modes.pm │ ├── pool.module │ ├── pool.json │ └── pool.pm │ ├── server.module │ ├── linkage.module │ │ ├── linkage.json │ │ └── linkage.pm │ ├── protocol.module │ │ ├── protocol.json │ │ └── protocol.pm │ ├── server.json │ └── server.pm │ ├── user.module │ ├── user.json │ └── user.pm │ └── utils.module │ ├── utils.json │ └── utils.pm └── var └── log └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | xcuserdata 3 | .DS_Store 4 | */build/* 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | 20 | etc/*.conf 21 | !etc/default.conf 22 | !etc/class.conf 23 | etc/*.db 24 | etc/*.pid 25 | etc/HUP 26 | etc/ssl/ 27 | .gedit 28 | dev/ 29 | *~ 30 | *.old 31 | db/*.db 32 | var/log/*.log 33 | PRIVATE_INFORMATION 34 | *.icloud 35 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/evented-configuration"] 2 | path = lib/evented-configuration 3 | url = git://github.com/cooper/evented-configuration.git 4 | [submodule "lib/evented-database"] 5 | path = lib/evented-database 6 | url = git://github.com/cooper/evented-database.git 7 | [submodule "lib/evented-object"] 8 | path = lib/evented-object 9 | url = git://github.com/cooper/evented-object.git 10 | [submodule "lib/evented-api-engine"] 11 | path = lib/evented-api-engine 12 | url = https://github.com/cooper/evented-api-engine.git 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Mitchell Cooper 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 12 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 13.37 2 | -------------------------------------------------------------------------------- /bin/ircd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # Copyright (c) 2010-17, Mitchell Cooper 3 | use warnings; use strict; use 5.010; 4 | foreach ([qw(__WARN__ WARNING)], [qw(TERM terminate )], [qw(KILL terminate)], 5 | [qw(INT terminate)], [qw(PIPE signalpipe)], [qw(HUP signalhup)]) { 6 | my ($sig, $func) = @$_; $SIG{$sig} = sub { safe_call($func, @_) } } 7 | our ($run_dir, $loop, $api, $pool, %v); local $0 = 'juno'; 8 | BEGIN { 9 | defined($run_dir = shift @ARGV) or die "No directory specified"; 10 | die "Run directory does not exist" unless -d $run_dir; 11 | chdir $run_dir or die "Can't access run directory"; 12 | foreach ($run_dir, map { "$run_dir/lib$_" } ('', qw(/evented-object/lib 13 | /evented-api-engine/lib))) { unshift @INC, $_ } 14 | require Evented::API::Engine; 15 | } 16 | sub get_version { $_ = $api->_slurp('ircd', "$run_dir/VERSION"); chomp; $_ } 17 | sub safe_call { my $f = shift; (ircd->can($f) or return)->(@_) } 18 | $api = Evented::API::Engine->new( 19 | mod_inc => ['modules', 'lib/evented-api-engine/mod']); 20 | our $VERSION = get_version() or die "Can't read VERSION"; 21 | $api->load_module('ircd') or exit 1; 22 | ircd::loop(); 23 | -------------------------------------------------------------------------------- /bin/ircd_forever: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use warnings; 4 | use strict; 5 | use 5.010; 6 | 7 | my $run_dir = shift @ARGV; 8 | 9 | open my $pidfh, '>', "$run_dir/etc/juno_forever.pid" 10 | or die "Can't write $run_dir/etc/juno_forever.pid"; 11 | 12 | # become a daemon. 13 | if (!shift @ARGV) { 14 | say 'Becoming a daemon...'; 15 | open STDIN, '<', '/dev/null' or die "Can't read /dev/null: $!"; 16 | open STDOUT, '>', '/dev/null' or die "Can't write /dev/null: $!"; 17 | open STDERR, '>', '/dev/null' or die "Can't write /dev/null: $!"; 18 | my $pid = fork; 19 | 20 | if ($pid) { 21 | say $pidfh $pid; 22 | close $pidfh; 23 | exit; 24 | POSIX::setsid(); 25 | } 26 | } 27 | 28 | # don't become a daemon. 29 | else { 30 | say $pidfh $$; 31 | close $pidfh; 32 | } 33 | 34 | do { 35 | my $time_before = time; 36 | system "$run_dir/bin/ircd $run_dir NOFORK @ARGV"; 37 | my $time_after = time; 38 | 39 | # the IRCd crashed in 10 seconds or less. 40 | if ($time_after - $time_before <= 10) { 41 | say 'IRCd crashed in less than 10 seconds. Taking a break.'; 42 | sleep 30; 43 | } 44 | 45 | } while -f "$run_dir/etc/juno.pid"; -------------------------------------------------------------------------------- /bin/mkpasswd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # Copyright (c) 2011-14, Mitchell Cooper 3 | 4 | use warnings; 5 | use strict; 6 | use feature 'switch'; 7 | 8 | $| = 1; 9 | 10 | my @all = qw(Digest::SHA Digest::MD5); 11 | my @valid_types; 12 | 13 | foreach my $type (@all) { 14 | print "checking for $type... "; 15 | if (eval "require $type; 1") { 16 | push @valid_types, $type; 17 | print "yes\n" 18 | } 19 | else { 20 | print "no\n" 21 | } 22 | } 23 | 24 | print <<'EOF' 25 | 26 | This script is used to generate encrypted passwords in forms that are supported by juno-ircd. 27 | These passwords are used in the configuration such as in "connect" and "oper" blocks. 28 | 29 | EOF 30 | ; 31 | 32 | if (!scalar @valid_types) { 33 | print "but it looks like you have none of the following supported modules installed:\n"; 34 | foreach my $type (@all) { 35 | print "$type\n" 36 | } 37 | exit 1 38 | } 39 | 40 | print "Please enter your plaintext password now.\n[password] "; 41 | 42 | my $password = ; 43 | chomp $password; 44 | 45 | print "\nThe following encryption modules were found on your system:\n"; 46 | my $i = 0; 47 | foreach my $type (@valid_types) { 48 | print "[$i] $type\n"; 49 | $i++; 50 | } 51 | $i = 0; 52 | 53 | print "\nPlease enter the number corresponding with the encryption module you would like to use.\n[number] "; 54 | my $type = ; 55 | chomp $type; 56 | 57 | if (!defined $valid_types[$type]) { 58 | print "invalid selection.\n"; 59 | exit 1 60 | } 61 | 62 | $type = $valid_types[$type]; 63 | 64 | given ($type) { 65 | when ('Digest::SHA') { sha(); } 66 | when ('Digest::MD5') { 67 | $password = Digest::MD5::md5_hex($password); 68 | done('md5'); 69 | } 70 | } 71 | 72 | sub sha { 73 | print "\nThe module you have selected supports the following types:\n"; 74 | my @hex_types = qw|sha1 sha224 sha256 sha384 sha512|; 75 | foreach my $hex_type (@hex_types) { 76 | print "[$i] $hex_type\n"; 77 | $i++ 78 | } 79 | print "\nPlease enter the number corresponding with the encryption type you would like to use.\n[number] "; 80 | my $hex_type = ; 81 | chomp $type; 82 | 83 | if (!defined $hex_types[$hex_type]) { 84 | print "invalid selection.\n"; 85 | exit 1 86 | } 87 | 88 | $hex_type = $hex_types[$hex_type]; 89 | 90 | given ($hex_type) { 91 | when ('sha1') { 92 | $password = Digest::SHA::sha1_hex($password); 93 | done('sha1'); 94 | } 95 | when ('sha224') { 96 | $password = Digest::SHA::sha224_hex($password); 97 | done('sha224'); 98 | } 99 | when ('sha256') { 100 | $password = Digest::SHA::sha256_hex($password); 101 | done('sha256'); 102 | } 103 | when ('sha384') { 104 | $password = Digest::SHA::sha384_hex($password); 105 | done('sha384'); 106 | } 107 | when ('sha512') { 108 | $password = Digest::SHA::sha512_hex($password); 109 | done('sha512'); 110 | } 111 | } 112 | } 113 | 114 | sub done { 115 | my $final_type = shift; 116 | print "\nYou're all done!\nPassword: $password\nType: $final_type\n\n"; 117 | exit 118 | } 119 | -------------------------------------------------------------------------------- /db/README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooper/juno/b2fc18a10d5cf85e494b9393fcebd5aaea326e62/db/README -------------------------------------------------------------------------------- /doc/cmodes.md: -------------------------------------------------------------------------------- 1 | # Channel modes 2 | 3 | Listed here are all supported channel modes and their default letters and 4 | types. Letters can be changed in the [configuration](config.md#channel-modes). 5 | 6 | ## Supported 7 | 8 | ``` 9 | no_ext = [ mode_normal, 'n' ] # no external channel messages (n) 10 | protect_topic = [ mode_normal, 't' ] # only operators can set the topic (t) 11 | invite_only = [ mode_normal, 'i' ] # you must be invited to join (i) 12 | free_invite = [ mode_normal, 'g' ] # you do not need op to invite (g) 13 | free_forward = [ mode_normal, 'F' ] # you do not need op in forwad channel (F) 14 | oper_only = [ mode_normal, 'O' ] # you need to be an ircop to join (O) 15 | moderated = [ mode_normal, 'm' ] # only voiced and up may speak (m) 16 | secret = [ mode_normal, 's' ] # secret channel (s) 17 | private = [ mode_normal, 'p' ] # private channel, hide and no knocks (p) 18 | ban = [ mode_list, 'b' ] # channel ban (b) 19 | mute = [ mode_list, 'Z' ] # channel mute ban (Z) 20 | except = [ mode_list, 'e' ] # ban exception (e) 21 | invite_except = [ mode_list, 'I' ] # invite-only exception (I) 22 | access = [ mode_list, 'A' ] # Channel::Access module list mode (A) 23 | limit = [ mode_pset, 'l' ] # Channel user limit mode (l) 24 | forward = [ mode_pset, 'f' ] # Channel forward mode (f) 25 | key = [ mode_key, 'k' ] # Channel key mode (k) 26 | permanent = [ mode_normal, 'P' ] # do not destroy channel when empty (P) 27 | reg_only = [ mode_normal, 'r' ] # only registered users can join (r) 28 | ssl_only = [ mode_normal, 'S' ] # only SSL users can join (S) 29 | strip_colors = [ mode_normal, 'c' ] # strip mIRC color codes from messages (c) 30 | op_moderated = [ mode_normal, 'z' ] # send blocked messages to channel ops (z) 31 | join_throttle = [ mode_pset, 'j' ] # limit join frequency N:T (j) 32 | no_forward = [ mode_normal, 'Q' ] # do not forward users to this channel (Q) 33 | large_banlist = [ mode_normal, 'L' ] # allow lots of entries on lists (L) 34 | owner = [ 'q', '~', 2 ] # channel owner (q) 35 | admin = [ 'a', '&', 1 ] # channel administrator (a) 36 | op = [ 'o', '@', 0 ] # channel operator (o) 37 | halfop = [ 'h', '%', -1, 0 ] # channel half-operator (h) 38 | voice = [ 'v', '+', -2 ] # voiced channel member (v) 39 | ``` 40 | 41 | ## Not yet supported 42 | 43 | These mode names are reserved for future use and possibly tracked by juno when 44 | linking to other IRCd, but they are not yet supported by juno. 45 | 46 | ``` 47 | no_nicks = [ mode_normal, 'N' ] 48 | admin_only = [ mode_normal, 'W' ] 49 | censor = [ mode_list, 'g' ] 50 | ``` 51 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | * [README](../README.md) - getting started 4 | * [History](history.md) - timeline of this project 5 | 6 | Features 7 | * [Configuration](config.md) - list of all available configuration options 8 | * [Modules](modules.md) - list of modules and their functions 9 | * [Channel modes](cmodes.md) - list of channel modes 10 | * [User modes](umodes.md) - list of user modes 11 | * [IRCv3](ircv3.md) - list of supported client protocol extensions 12 | 13 | IRC operators 14 | * [Operator privilege flags](oper_flags.md) - list of operator capabilities 15 | * [Operator notice flags](oper_notices.md) - list of server notices 16 | 17 | Guides 18 | * [Installation](../README.md#installation) - getting juno and its dependencies 19 | * [SSL setup](../README.md#ssl-setup) - using SSL with juno 20 | * [Operation](../README.md#operation) - running juno 21 | * [Upgrading](../README.md#upgrading) - upgrading to newer versions 22 | * [Using juno with TS6](ts6.md) - details how to use juno with TS6 software 23 | 24 | ## Technical 25 | 26 | Linking protocols 27 | * [JELP](technical/proto/jelp.md) - JELP linking protocol specification 28 | * [TS6](technical/proto/ts6.md) - TS6 linking protocol specification 29 | 30 | APIs 31 | * [user](technical/api/user.md) - Represents an IRC user 32 | * [channel](technical/api/channel.md) - Represents an IRC channel 33 | * [message](technical/api/message.md) - Represents an IRC message 34 | -------------------------------------------------------------------------------- /doc/ircv3.md: -------------------------------------------------------------------------------- 1 | # IRCv3 2 | 3 | juno supports the following [IRCv3](http://ircv3.net) extensions. 4 | 5 | ### IRCv3.2 6 | 7 | * [capability negotiation](http://ircv3.net/specs/core/capability-negotiation-3.2.html) 8 | * [cap-notify](http://ircv3.net/specs/extensions/cap-notify-3.2.html) 9 | * [monitor](http://ircv3.net/specs/core/monitor-3.2.html) 10 | * [message-tags](http://ircv3.net/specs/core/message-tags-3.2.html) 11 | * [batch](http://ircv3.net/specs/extensions/batch-3.2.html) 12 | * [netsplit and netjoin](http://ircv3.net/specs/extensions/batch/netsplit-3.2.html) 13 | * [account-tag](http://ircv3.net/specs/extensions/account-tag-3.2.html) 14 | * [chghost](http://ircv3.net/specs/extensions/chghost-3.2.html) 15 | * [echo-message](http://ircv3.net/specs/extensions/echo-message-3.2.html) 16 | * [invite-notify](http://ircv3.net/specs/extensions/invite-notify-3.2.html) 17 | * [sasl](http://ircv3.net/specs/extensions/sasl-3.2.html) 18 | * [userhost-in-names](http://ircv3.net/specs/extensions/userhost-in-names-3.2.html) 19 | 20 | ### IRCv3.1 21 | 22 | * [account-notify](http://ircv3.net/specs/extensions/account-notify-3.1.html) 23 | * [extended-join](http://ircv3.net/specs/extensions/extended-join-3.1.html) 24 | * [away-notify](http://ircv3.net/specs/extensions/away-notify-3.1.html) 25 | * [multi-prefix](http://ircv3.net/specs/extensions/multi-prefix-3.1.html) 26 | -------------------------------------------------------------------------------- /doc/oper_flags.md: -------------------------------------------------------------------------------- 1 | # IRC operator flags 2 | 3 | Listed here are all available privileges for IRC operators. 4 | 5 | Anything with (g) in front of it means that the 'g' prefix is optional to 6 | specify whether the privilege works remotely. 7 | 8 | ### all 9 | 10 | Those with all are gods, capable of anything. 11 | 12 | ### grant 13 | 14 | Use `GRANT` to add and remove oper privileges from a user. Note that this allows 15 | the user to apply any flags to himself as well. 16 | 17 | ### (g)rehash 18 | 19 | Reload the server configuration with `REHASH`. 20 | 21 | ### see_invisible 22 | 23 | See invisible (+i) users where they would otherwise be hidden. 24 | 25 | ### see_hidden 26 | 27 | Show hidden servers in commands like `MAP` and `LINKS`. 28 | 29 | ### see_hosts 30 | 31 | Show users' hostnames and IP addresses in `WHO` and `WHOIS`. 32 | 33 | ### see_secret 34 | 35 | See secret (+s) and private (+p) channels where they would otherwise be hidden. 36 | 37 | ### (g)squit 38 | 39 | Disconnect uplinks from the server with `SQUIT`. 40 | 41 | ### (g)connect 42 | 43 | Establish uplinks to the server with `CONNECT`. 44 | 45 | ### (g)kill 46 | 47 | Remove a user from the server with the `KILL` command. 48 | 49 | ### modules 50 | 51 | Use `MODLOAD`, `MODUNLOAD`, and `MODRELOAD` commands. 52 | 53 | ### (g)update 54 | 55 | Update the server git repository with `UPDATE`. 56 | 57 | ### (g)checkout 58 | 59 | Switch the server git repository to a different release with `CHECKOUT`. 60 | 61 | ### (g)reload 62 | 63 | Reload the server with the `RELOAD` command. 64 | 65 | ### kline 66 | 67 | Add and remove K-Lines with `KLINE` and `UNKLINE`. 68 | 69 | ### dline 70 | 71 | Add and remove D-Lines with `DLINE` and `UNDLINE`. 72 | 73 | ### resv 74 | 75 | Add and remove channel and nickname reservations with `RESV` and `UNRESV`. 76 | 77 | ### list_bans 78 | 79 | View K-Lines, D-Lines, etc. with the `BANS` command. 80 | 81 | ### (g)confget 82 | 83 | Use `CONFGET` to view the server configuration. 84 | 85 | ### (g)confset 86 | 87 | Use `CONFSET` to dynamically modify the server configuration. 88 | 89 | ### set_permanent 90 | 91 | Mark channels as permanent (+P). 92 | 93 | ### set_large_banlist 94 | 95 | Enable large channel ban lists (+L). 96 | 97 | ### modesync 98 | 99 | Fix channel desyncs with the `MODESYNC` command. 100 | -------------------------------------------------------------------------------- /doc/oper_notices.md: -------------------------------------------------------------------------------- 1 | # Oper notice flags 2 | 3 | Listed here are all available oper notice flags. 4 | 5 | Too lazy to write a proper description for these, so here they are. I will do 6 | that eventually though, probably after adding notice classes/categories. 7 | 8 | Opers with `all` receive all notices. 9 | 10 | ## Local connections 11 | ``` 12 | new_connection => '%s (%d) {%s}' 13 | connection_terminated => '%s (%s): %s' 14 | connection_invalid => '%s: %s' 15 | ``` 16 | 17 | ## Users 18 | ``` 19 | new_user => '%s [%s] {%s}' 20 | user_quit => '%s [%s] from %s (%s)' 21 | user_opered => '%s gained flags on %s: %s' 22 | user_deopered => '%s is no longer an IRC operator' 23 | user_killed => '%s killed by %s (%s)' 24 | user_nick_change => '%s is now known as %s' 25 | user_join => '%s joined %s' 26 | user_part => '%s parted %s (%s)' 27 | user_part_all => '%s parted all channels: %s' 28 | user_kick => '%s was kicked from %s by %s (%s)' 29 | user_mask_change => '%s switched from (%s@%s) to (%s@%s)' 30 | user_identifier_taken => '%s introduced %s with UID %s, which is already taken by %s' 31 | user_saved => '%s was spared in the midst of a nick collision (was %s)' 32 | user_logged_in => '%s is now logged in as %s' 33 | user_logged_out => '%s logged out (was %s)' 34 | ``` 35 | 36 | ## Servers 37 | ``` 38 | new_server => '%s ircd %s, proto %s [%s] parent: %s' 39 | server_closing => 'Received SQUIT from %s; dropping link (%s)' 40 | server_quit => '%s quit from parent %s (%s)' 41 | server_burst => '%s is bursting information' 42 | server_endburst => '%s finished burst, %d seconds elapsed' 43 | connect => '%s issued CONNECT for %s to %s' 44 | connect_attempt => '%s (%s) on port %d (Attempt %d)' 45 | connect_cancel => '%s canceled auto connect for %s' 46 | connect_fail => 'Can\'t connect to %s: %s' 47 | connect_success => 'Connection established to %s' 48 | squit => '%s issued SQUIT for %s from %s' 49 | server_reintroduced => '%s attempted to introduce %s which already exists' 50 | server_identifier_taken => '%s attempted to introduce %s as SID %d, which is already taken by %s' 51 | server_protocol_warning => '%s %s' 52 | server_protocol_error => '%s %s; dropping link' 53 | ``` 54 | 55 | ## Bans 56 | ``` 57 | kline => 'K-Line for %s added by %s, will expire %s (%s) [%s]' 58 | kline_delete => 'K-Line for %s deleted by %s [%s]' 59 | kline_expire => 'K-Line for %s expired after %s [%s]' 60 | dline => 'D-Line for %s added by %s, will expire %s (%s) [%s]' 61 | dline_delete => 'D-Line for %s deleted by %s [%s]' 62 | dline_expire => 'D-Line for %s expired after %s [%s]' 63 | resv => 'Reserve for %s added by %s, will expire %s (%s) [%s]' 64 | resv_delete => 'Reserve for %s deleted by %s [%s]' 65 | resv_expire => 'Reserve for %s expired after %s [%s]' 66 | ``` 67 | 68 | ## Server management 69 | ``` 70 | reload => '%s %s by %s' 71 | update_fail => 'update to %s git reposity by %s failed' 72 | update => '%s git repository updated to version %s successfully by %s' 73 | grant => '%s granted flags to %s: %s' 74 | ungrant => '%s removed flags from %s: %s' 75 | module_load => '%s loaded %s (%s)' 76 | module_unload => '%s unloaded %s' 77 | module_reload => '%s reloaded %s' 78 | ``` 79 | 80 | ## Modes 81 | ``` 82 | user_mode_unknown => 'Attempted to set %s, but this mode is not defined on %s; ignored' 83 | channel_mode_unknown => 'Attempted to set %s, but this mode is not defined on %s; ignored' 84 | ``` 85 | 86 | ## Miscellaneous 87 | ``` 88 | perl_warning => '%s' 89 | exception => '%s' 90 | rehash => '%s is rehashing the server' 91 | rehash_fail => 'Configuration error: %s' 92 | rehash_success => 'Server configuration rehashed successfully' 93 | ``` 94 | -------------------------------------------------------------------------------- /doc/technical/api/modes.md: -------------------------------------------------------------------------------- 1 | # modes 2 | 3 | Represents a series of mode changes. Docs coming soon. 4 | -------------------------------------------------------------------------------- /doc/technical/api/pool.md: -------------------------------------------------------------------------------- 1 | # pool 2 | 3 | Pool of IRC objects. Docs coming soon. 4 | -------------------------------------------------------------------------------- /doc/technical/proto/outgoing.md: -------------------------------------------------------------------------------- 1 | # Server outgoing events 2 | 3 | These events are hooked onto by server protocol modules. Each responder returns 4 | a message string to be sent to uplinks. 5 | 6 | Docs coming soon. 7 | -------------------------------------------------------------------------------- /doc/technical/proto/protocol.md: -------------------------------------------------------------------------------- 1 | # server::protocol 2 | 3 | Provides common functions for server-to-server protocols. Docs coming soon. 4 | -------------------------------------------------------------------------------- /doc/umodes.md: -------------------------------------------------------------------------------- 1 | # User modes 2 | 3 | Listed here are all available user modes and their default letters. The letters 4 | can be changed in the [configuration](config.md#user-modes). 5 | 6 | ``` 7 | ircop = 'o' # IRC operator (o) 8 | invisible = 'i' # invisible mode (i) 9 | ssl = 'z' # SSL connection (z) 10 | registered = 'r' # registered nickname (r) 11 | service = 'S' # network service (S) 12 | deaf = 'D' # does not receive channel messages (D) 13 | admin = 'a' # server administrator (a) 14 | wallops = 'w' # receives wallops (w) 15 | bot = 'B' # marks user as a bot (B) 16 | cloak = 'x' # hostname cloaking (x) 17 | ``` 18 | -------------------------------------------------------------------------------- /etc/ircd.motd.example: -------------------------------------------------------------------------------- 1 | 2 | Yes. 3 | It really is an IRC daemon. 4 | It's written in Perl. 5 | 6 | ... 7 | 8 | You can breathe again. 9 | There. Very good. 10 | 11 | -------------------------------------------------------------------------------- /juno-start.bat: -------------------------------------------------------------------------------- 1 | SET dir=%~dp0 2 | perl %dir%/bin/ircd %dir% NOFORK -------------------------------------------------------------------------------- /modules/AddUserTest.module/AddUserTest.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "AddUser" 9 | ] 10 | }, 11 | "description" : "AddUser API test", 12 | "name" : "AddUserTest", 13 | "package" : "M::AddUserTest", 14 | "version" : "1.3" 15 | } 16 | -------------------------------------------------------------------------------- /modules/AddUserTest.module/AddUserTest.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017, Mitchell Cooper 2 | # 3 | # @name: "AddUserTest" 4 | # @package: "M::AddUserTest" 5 | # @description: "AddUser API test" 6 | # 7 | # @depends.bases+ 'AddUser' 8 | # 9 | # @author.name: "Mitchell Cooper" 10 | # @author.website: "https://github.com/cooper" 11 | # 12 | package M::AddUserTest; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | our ($api, $mod, $pool); 19 | 20 | sub init { 21 | my $u = $mod->add_user('addusertest', 22 | nick => 'AddUserTest', 23 | cloak => 'add.user.test' 24 | ) or return; 25 | $u->handle('JOIN #k'); 26 | $u->handle('PRIVMSG #k :Hello'); 27 | } 28 | 29 | $mod 30 | -------------------------------------------------------------------------------- /modules/Alias.module/Alias.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserCommands" 9 | ] 10 | }, 11 | "description" : "support for command aliases", 12 | "name" : "Alias", 13 | "package" : "M::Alias", 14 | "version" : "3" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Alias.module/Alias.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-Mac-mini.local 4 | # Sat Aug 16 14:04:36 EDT 2014 5 | # Alias.pm 6 | # 7 | # @name: 'Alias' 8 | # @package: 'M::Alias' 9 | # @description: 'support for command aliases' 10 | # 11 | # @depends.bases+ 'UserCommands' 12 | # 13 | # @author.name: 'Mitchell Cooper' 14 | # @author.website: 'https://github.com/cooper' 15 | # 16 | package M::Alias; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | our ($api, $mod, $pool, $conf); 23 | my %current_aliases; 24 | 25 | sub init { 26 | $pool->on('rehash_after' => \&update_aliases, 'update.aliases'); 27 | return update_aliases(); 28 | } 29 | 30 | sub update_aliases { 31 | my (@remove, @keep); 32 | my %new_aliases = $conf->hash_of_block('aliases'); 33 | $new_aliases{ uc $_ } = $new_aliases{$_} for keys %new_aliases; 34 | 35 | # these ones were removed from the configuration or have been changed. 36 | foreach my $command (keys %current_aliases) { 37 | $command = uc $command; 38 | 39 | # it was removed from the configuration 40 | if (!length $new_aliases{$command}) { 41 | push @remove, $command; 42 | } 43 | 44 | # the format has changed 45 | elsif ($new_aliases{$command} ne $current_aliases{$command}) { 46 | push @remove, $command; 47 | } 48 | 49 | # otherwise it's unchanged 50 | else { 51 | push @keep, $command; 52 | } 53 | 54 | } 55 | 56 | # remove missing or modified aliases. 57 | delete_alias($_) foreach @remove; 58 | 59 | # add new aliases. 60 | delete @new_aliases{@keep}; 61 | add_alias($_, $new_aliases{$_}) foreach keys %new_aliases; 62 | 63 | return 1; 64 | } 65 | 66 | sub delete_alias { 67 | my $alias = shift; 68 | $alias = uc $alias; 69 | $mod->delete_user_command($alias); 70 | delete $current_aliases{$alias}; 71 | } 72 | 73 | sub add_alias { 74 | my ($alias, $format) = @_; 75 | $alias = uc $alias; 76 | # first, generate a format string for sprintf. 77 | 78 | my $var_name = my $var_type = my $sprintf_fmt = ''; 79 | my ($in_variable, @variable_order, %variables); 80 | foreach my $char (split //, "$format\0") { 81 | 82 | # dollar starts a variable. 83 | if ($char eq '$') { 84 | $in_variable = 1; 85 | next; 86 | } 87 | 88 | # we are in a variable. 89 | if ($in_variable) { 90 | 91 | # - in a variable indicates it should be : 92 | if ($char eq '-') { 93 | $var_type = ':'; 94 | next; 95 | } 96 | 97 | # white space terminates a variable. 98 | my $is_whitespace = $char =~ m/\s/; 99 | if ($char eq "\0" || $is_whitespace) { 100 | 101 | # force numeric context. 102 | $var_name += 0; 103 | 104 | push @variable_order, $var_name; 105 | $variables{$var_name} = $var_type ||= '*'; 106 | 107 | # add to the format. 108 | # if it's :, it needs the sentinel. 109 | $sprintf_fmt .= $var_type eq ':' ? ':%s' : '%s'; 110 | $sprintf_fmt .= $char if $is_whitespace; 111 | 112 | $var_name = $var_type = ''; 113 | undef $in_variable; 114 | next; 115 | } 116 | 117 | # other character. 118 | $var_name .= $char; 119 | next; 120 | 121 | } 122 | 123 | # not in a variable. this is just part of the message format. 124 | next if $char eq "\0"; 125 | $sprintf_fmt .= $char eq '%' ? '%%' : $char; 126 | 127 | } 128 | 129 | # then, generate a parameter string for juno. 130 | my @params = map { $variables{$_} } sort keys %variables; 131 | my $param_fmt = join ' ', @params; 132 | 133 | # here is the code for the alias command handler. 134 | my $code = sub { 135 | my ($user, $event, @args) = @_; 136 | 137 | # put the arguments in the correct order for the format. 138 | @args = map { $args[$_ - 1] } @variable_order; 139 | 140 | # do the command string. 141 | $user->handle(sprintf $sprintf_fmt, @args); 142 | 143 | }; 144 | 145 | # attach it. 146 | $mod->register_user_command_new( 147 | name => $alias, 148 | code => $code, 149 | params => $param_fmt, 150 | desc => 'command alias' 151 | ) or return; 152 | 153 | # remember it. 154 | $current_aliases{$alias} = $format; 155 | 156 | return 1; 157 | } 158 | 159 | $mod 160 | -------------------------------------------------------------------------------- /modules/Ban/Ban.module/Ban.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserCommands", 9 | "OperNotices" 10 | ], 11 | "modules" : [ 12 | "API::Methods" 13 | ] 14 | }, 15 | "description" : "provides an interface for user and server banning", 16 | "name" : "Ban", 17 | "package" : "M::Ban", 18 | "version" : "24.4" 19 | } 20 | -------------------------------------------------------------------------------- /modules/Ban/Ban.module/Info.module/Info.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "objective representation of a ban", 7 | "name" : "Ban::Info", 8 | "package" : "M::Ban::Info", 9 | "version" : "4.7" 10 | } 11 | -------------------------------------------------------------------------------- /modules/Ban/Ban.module/JELP.module/JELP.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "JELP::Base" 9 | ] 10 | }, 11 | "description" : "JELP ban propagation", 12 | "name" : "Ban::JELP", 13 | "package" : "M::Ban::JELP", 14 | "version" : "5.1" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Ban/Ban.module/TS6.module/TS6.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "TS6::Base" 9 | ] 10 | }, 11 | "description" : "TS6 ban propagation", 12 | "name" : "Ban::TS6", 13 | "package" : "M::Ban::TS6", 14 | "version" : "10.6" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Ban/Dline.module/Dline.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "Ban" 9 | ] 10 | }, 11 | "description" : "ban connections from server by IP", 12 | "name" : "Ban::Dline", 13 | "package" : "M::Ban::Dline", 14 | "version" : "1.8" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Ban/Dline.module/Dline.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-MacBook-Pro.local 4 | # Sat Feb 15 17:58:20 EST 2015 5 | # Dline.pm 6 | # 7 | # @name: 'Ban::Dline' 8 | # @package: 'M::Ban::Dline' 9 | # @description: 'ban connections from server by IP' 10 | # 11 | # @author.name: 'Mitchell Cooper' 12 | # @author.website: 'https://github.com/cooper' 13 | # 14 | # @depends.modules+ 'Ban' 15 | # 16 | package M::Ban::Dline; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | our ($api, $mod, $pool); 23 | my $KILL_CONN; 24 | 25 | sub init { 26 | $KILL_CONN = $mod->get_ban_action('kill'); 27 | $mod->register_ban_type( 28 | name => 'dline', # ban type 29 | add_cmd => 'dline', # add command 30 | del_cmd => 'undline', # delete command 31 | reason => 'D-Lined', # reason prefix 32 | conn_code => \&conn_matches, # connection matcher 33 | match_code => \&_match, # match checker 34 | ); 35 | } 36 | 37 | sub _match { 38 | my $str = shift; 39 | 40 | # TODO: (#146) don't allow non-IPs 41 | 42 | # does it match a user? 43 | if (my $user = $pool->lookup_user_nick($str)) { 44 | return $user->{ip}; 45 | } 46 | 47 | return $str; 48 | } 49 | 50 | sub conn_matches { 51 | my ($conn, $ban) = @_; 52 | return $KILL_CONN 53 | if utils::irc_match($conn->{ip}, $ban->match); 54 | return; 55 | } 56 | 57 | $mod 58 | -------------------------------------------------------------------------------- /modules/Ban/Kline.module/Kline.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "Ban" 9 | ] 10 | }, 11 | "description" : "ban users from server by hostmask", 12 | "name" : "Ban::Kline", 13 | "package" : "M::Ban::Kline", 14 | "version" : "1.7" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Ban/Kline.module/Kline.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-MacBook-Pro.local 4 | # Sat Feb 14 17:58:20 EST 2015 5 | # Kline.pm 6 | # 7 | # @name: 'Ban::Kline' 8 | # @package: 'M::Ban::Kline' 9 | # @description: 'ban users from server by hostmask' 10 | # 11 | # @author.name: 'Mitchell Cooper' 12 | # @author.website: 'https://github.com/cooper' 13 | # 14 | # @depends.modules+ 'Ban' 15 | # 16 | package M::Ban::Kline; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | use utils qw(irc_match); 23 | 24 | our ($api, $mod, $pool); 25 | my $KILL_CONN; 26 | 27 | sub init { 28 | $KILL_CONN = $mod->get_ban_action('kill'); 29 | $mod->register_ban_type( 30 | name => 'kline', # ban type 31 | add_cmd => 'kline', # add command 32 | del_cmd => 'unkline', # delete command 33 | reason => 'K-Lined', # reason prefix 34 | user_code => \&user_or_conn_matches, # user matcher 35 | conn_code => \&user_or_conn_matches, # connection matcher 36 | match_code => \&_match, # match checker 37 | ); 38 | } 39 | 40 | sub _match { 41 | my $str = shift; 42 | 43 | # does it match a user? 44 | if (my $user = $pool->lookup_user_nick($str)) { 45 | return '*@'.$user->{host}; 46 | } 47 | 48 | # user[@]host only 49 | my @parts = utils::pretty_mask_parts($str); 50 | my $result = "$parts[1]\@$parts[2]"; 51 | 52 | return if $result eq '*@*'; 53 | return $result; 54 | } 55 | 56 | sub user_or_conn_matches { 57 | my ($user_conn, $ban) = @_; 58 | my ($ident, $host, $ip) = @$user_conn{ qw(ident host ip) }; 59 | return unless length $ident; 60 | 61 | return $KILL_CONN 62 | if irc_match("$ident\@$host", $ban->match); 63 | 64 | return $KILL_CONN 65 | if irc_match("$ident\@$ip", $ban->match); 66 | 67 | return; 68 | } 69 | 70 | $mod 71 | -------------------------------------------------------------------------------- /modules/Ban/Resv.module/Resv.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "Ban" 9 | ] 10 | }, 11 | "description" : "reserve nicknames matching a mask", 12 | "name" : "Ban::Resv", 13 | "package" : "M::Ban::Resv", 14 | "version" : "1.6" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Ban/Resv.module/Resv.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # @name: 'Ban::Resv' 4 | # @package: 'M::Ban::Resv' 5 | # @description: 'reserve nicknames matching a mask' 6 | # 7 | # @author.name: 'Mitchell Cooper' 8 | # @author.website: 'https://github.com/cooper' 9 | # 10 | # @depends.modules+ 'Ban' 11 | # 12 | package M::Ban::Resv; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | use utils qw(conf); 19 | 20 | our ($api, $mod, $pool); 21 | 22 | sub init { 23 | $mod->register_ban_type( 24 | name => 'resv', # ban type 25 | hname => 'reserve', # human-readable name 26 | add_cmd => 'resv', # add command 27 | del_cmd => 'unresv' , # delete command 28 | reason => 'Reserved', # reason prefix 29 | activate_code => \&activate_resv, # activation code 30 | disable_code => \&expire_resv, # expire code 31 | match_code => \&_match # match checker 32 | ); 33 | } 34 | 35 | sub _match { 36 | my $str = shift; 37 | return $str; 38 | } 39 | 40 | sub activate_resv { 41 | my $ban = shift; 42 | 43 | # force local users to part if the channel exists 44 | # and channels:resv_force_part is enabled. 45 | my $channel = $pool->lookup_channel($ban->match); 46 | if ($channel && conf('channels', 'resv_force_part')) { 47 | foreach my $user ($channel->all_local_users) { 48 | # ($user, $reason, $quiet) 49 | $channel->do_part($user, $ban->hr_reason, 1); 50 | } 51 | } 52 | 53 | $pool->add_resv($ban->match, $ban->expires); 54 | } 55 | 56 | sub expire_resv { 57 | my $ban = shift; 58 | $pool->delete_resv($ban->match); 59 | } 60 | 61 | $mod 62 | -------------------------------------------------------------------------------- /modules/Base/AddUser.module/AddUser.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "API::Methods" 9 | ] 10 | }, 11 | "description" : "virtual user support", 12 | "name" : "Base::AddUser", 13 | "package" : [ 14 | "M::Base::AddUser", 15 | "M::Base::AddUser::User" 16 | ], 17 | "version" : "4.2" 18 | } 19 | -------------------------------------------------------------------------------- /modules/Base/AddUser.module/AddUser.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-17, Mitchell Cooper 2 | # 3 | # @name: "Base::AddUser" 4 | # @package+ "M::Base::AddUser" 5 | # @package+ "M::Base::AddUser::User" 6 | # @description: "virtual user support" 7 | # 8 | # @depends.modules+ "API::Methods" 9 | # 10 | # @author.name: "Mitchell Cooper" 11 | # @author.website: "https://github.com/cooper" 12 | # 13 | package M::Base::AddUser; 14 | 15 | use warnings; 16 | use strict; 17 | use 5.010; 18 | 19 | use utils qw(broadcast); 20 | 21 | our ($api, $mod, $pool, $me); 22 | my %unloaded_users; # ID to user object 23 | 24 | sub init { 25 | $mod->register_module_method('add_user') or return; 26 | 27 | # on module unload, quit users 28 | $api->on('module.unload' => \&on_unload, 'adduser.unload'); 29 | 30 | # on change start, remember which fake users we had 31 | $pool->on(change_start => sub { 32 | my $state = shift; 33 | %unloaded_users = (); 34 | $state->{adduser} = \%unloaded_users; 35 | }, 'adduser.change'); 36 | 37 | # on change end, any users remaining here were not reloaded, 38 | # so dispose of them 39 | $pool->on(change_end => sub { 40 | my $state = shift; 41 | return if !$state->{adduser}; 42 | %unloaded_users = %{ $state->{adduser} }; 43 | delete_user($_) for values %unloaded_users; 44 | %unloaded_users = (); 45 | }, 'adduser.change'); 46 | } 47 | 48 | # Creates a fake user 49 | # 50 | # my $user = $mod->add_user($id, 51 | # nick => nickname defaults to the auto-generated UID 52 | # ident => usernae defaults to nick or 'user' 53 | # host => real host defaults to local server name 54 | # cloak => visible host defaults to host or local server name 55 | # real => real name defaults to nick or 'realname' 56 | # ip => IP address defaults to 0. must not start with colon 57 | # ) 58 | # 59 | # Note that this method can fail and return nothing 60 | # 61 | sub add_user { 62 | my ($mod, $event, $id, %opts) = @_; 63 | L("ADDUSER"); 64 | # already existed at this ID 65 | if (my $exists = delete $unloaded_users{$id}) { 66 | 67 | # same owner; we can preserve it 68 | if ($exists->{adduser_owner} eq $mod->name) { 69 | update_user($exists, %opts); 70 | $mod->list_store_add(virtual_users => $exists->id); 71 | return $exists; 72 | } 73 | 74 | # otherwise, dispose of it now 75 | L("User already exists by ID '$id' from $$exists{adduser_owner}"); 76 | delete_user($exists); 77 | } 78 | 79 | # Safe point - New user will be created now 80 | 81 | # create 82 | my $user = $pool->new_user( 83 | # nick => defaults to UID 84 | # cloak => defaults to host 85 | ident => $opts{nick} || 'user', 86 | host => ($opts{server} || $me)->name, 87 | real => $opts{nick} || 'realname', 88 | ip => '0', 89 | %opts, 90 | fake => 1, 91 | source => $me->id, 92 | adduser_id => $id, 93 | adduser_owner => $mod->name 94 | ) or return; 95 | bless $user, 'M::Base::AddUser::User'; 96 | L('New virtual user '.$user->full); 97 | 98 | # propagate 99 | broadcast(new_user => $user); 100 | $user->fire('initially_propagated'); 101 | $user->{initially_propagated}++; 102 | 103 | # simulate initialization 104 | $user->{init_complete}++; 105 | 106 | $mod->list_store_add(virtual_users => $user->id); 107 | return $user; 108 | } 109 | 110 | sub update_user { 111 | my ($user, %opts) = @_; 112 | return if !$user->{fake}; 113 | 114 | # quietly overwrite these fields if they're present 115 | # TODO: these fields should be added to user info update for propagation 116 | for (qw/host ip real/) { 117 | $user->{$_} = $opts{$_} if length $opts{$_}; 118 | # note that there's no reliable way to propagate changes to 119 | # real host or ip 120 | } 121 | 122 | # change ident/cloak 123 | # this is silently ignored if neither have changed 124 | $user->get_mask_changed( 125 | $opts{ident} // $user->{ident}, 126 | $opts{cloak} // $opts{host} // $user->{cloak} 127 | ); 128 | 129 | # change nick if needed 130 | if (length $opts{nick} && $opts{nick} ne $user->name) { 131 | $user->do_nick($opts{nick}); 132 | } 133 | } 134 | 135 | sub delete_user { 136 | my $user = shift; 137 | return if !$user->{fake}; 138 | $user->quit('Unloaded'); 139 | } 140 | 141 | sub on_unload { 142 | my $mod = shift; 143 | foreach my $uid ($mod->list_store_items('virtual_users')) { 144 | my $user = $pool->lookup_user($uid) or next; 145 | next if !$user->{fake}; 146 | $unloaded_users{ $user->{adduser_id} } = $user; 147 | } 148 | } 149 | 150 | package M::Base::AddUser::User; 151 | 152 | use parent 'user'; 153 | 154 | $mod 155 | -------------------------------------------------------------------------------- /modules/Base/Capabilities.module/Capabilities.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "API::Methods" 9 | ] 10 | }, 11 | "description" : "provides an interface for client capabilities", 12 | "name" : "Base::Capabilities", 13 | "package" : "M::Base::Capabilities", 14 | "version" : "15.5" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Base/ChannelModes.module/ChannelModes.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "API::Methods" 9 | ] 10 | }, 11 | "name" : "Base::ChannelModes", 12 | "package" : "M::Base::ChannelModes", 13 | "version" : "12.58" 14 | } 15 | -------------------------------------------------------------------------------- /modules/Base/ChannelModes.module/ChannelModes.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-14, Mitchell Cooper 2 | # 3 | # @name: "Base::ChannelModes" 4 | # @package: "M::Base::ChannelModes" 5 | # 6 | # @depends.modules+ "API::Methods" 7 | # 8 | # @author.name: "Mitchell Cooper" 9 | # @author.website: "https://github.com/cooper" 10 | # 11 | package M::Base::ChannelModes; 12 | 13 | use warnings; 14 | use strict; 15 | use 5.010; 16 | 17 | use utils 'cols'; 18 | 19 | our ($api, $mod, $pool, $me); 20 | 21 | sub init { 22 | 23 | # register methods. 24 | $mod->register_module_method('register_channel_mode_block') or return; 25 | 26 | # module events. 27 | $api->on('module.unload' => \&on_unload, 'void.channel.modes'); 28 | $api->on('module.init' => \&module_init, '%channel_modes'); 29 | 30 | return 1; 31 | } 32 | 33 | ########################### 34 | ### PROVIDED MODE TYPES ### 35 | ########################### 36 | 37 | my %mode_types = ( 38 | normal => \&cmode_normal, 39 | banlike => \&cmode_banlike 40 | ); 41 | 42 | sub cmode_normal { 43 | my ($channel, $mode) = @_; 44 | return $mode->{has_basic_status}; 45 | } 46 | 47 | sub cmode_banlike { 48 | my ($channel, $mode, %opts) = @_; 49 | _cmode_banlike( 50 | $opts{list}, 51 | $opts{reply}, 52 | $opts{show_mode}, 53 | $channel, 54 | $mode 55 | ); 56 | } 57 | 58 | # for banlike modes. 59 | # not to be used directly. 60 | # 61 | # $list the name used internally (e.g. mute) 62 | # $reply the name used in numerics (e.g. QUIET) 63 | # $show_letter whether to show the letter in the RPL_ENDOF* 64 | # $channel channel object 65 | # $mode mode fire info 66 | # 67 | sub _cmode_banlike { 68 | my ($list, $reply, $show_letter, $channel, $mode) = @_; 69 | my $local_user = $mode->{source}; 70 | undef $local_user if !$local_user->isa('user') || !$local_user->is_local; 71 | 72 | # local user viewing the list 73 | if ($local_user && !length $mode->{param}) { 74 | 75 | # consider the numeric reply names and whether to send the mode letter. 76 | my $name = uc($reply)."LIST"; 77 | my @channel_letter = $channel->name; 78 | push @channel_letter, $me->cmode_letter($list) if $show_letter; 79 | 80 | # send each list item. 81 | $local_user->numeric("RPL_$name" => 82 | @channel_letter, 83 | $_->[0], 84 | $_->[1]{setby}, 85 | $_->[1]{time} 86 | ) foreach $channel->list_elements($list, 1); 87 | 88 | # end of list. 89 | $local_user->numeric("RPL_ENDOF$name" => @channel_letter); 90 | 91 | return 1; 92 | } 93 | 94 | # needs privs from this point on 95 | if (!$mode->{has_basic_status}) { 96 | $mode->{send_no_privs} = 1; 97 | return; 98 | } 99 | 100 | # remove prefixing colons 101 | $mode->{param} = cols($mode->{param}); 102 | if (!length $mode->{param}) { 103 | return; 104 | } 105 | 106 | # removing an entry 107 | if (!$mode->{state}) { 108 | $channel->remove_from_list($list, $mode->{param}); 109 | return 1; 110 | } 111 | 112 | # adding an entry 113 | 114 | # it's full! this limitation only applies to local users 115 | if ($local_user && $channel->list_is_full($list)) { 116 | $local_user->numeric(ERR_BANLISTFULL => 117 | $channel->name, $mode->{param}, $list); 118 | return; 119 | } 120 | 121 | # add it 122 | $channel->add_to_list($list, $mode->{param}, 123 | setby => $mode->{source}->full, 124 | time => time 125 | ); 126 | 127 | return 1; 128 | } 129 | 130 | #################### 131 | ### REGISTRATION ### 132 | #################### 133 | 134 | sub register_channel_mode_block { 135 | my ($mod, $event, %opts) = @_; 136 | 137 | # make sure all required options are present. 138 | foreach my $what (qw|name code|) { 139 | next if exists $opts{$what}; 140 | $opts{name} ||= 'unknown'; 141 | L("Channel mode block $opts{name} does not have '$what' option"); 142 | return; 143 | } 144 | 145 | # register the mode block. 146 | $opts{name} = lc $opts{name}; 147 | $pool->register_channel_mode_block( 148 | $opts{name}, 149 | $mod->name, 150 | $opts{code} 151 | ); 152 | 153 | # register stringifiers 154 | foreach (keys %opts) { 155 | next unless m/^str_(\w+)$/; 156 | my ($proto, $code) = ($1, $opts{$_}); 157 | $pool->on("cmode.stringify.$opts{name}.$proto" => sub { 158 | my ($event, $channel, $param) = @_; 159 | $event->{proto} = $proto; 160 | $event->{string} = $code->($channel, $param); 161 | $event->stop; # the first one wins 162 | }, 163 | priority => $proto eq '1459' ? 0 : 100, # 1459 comes last 164 | name => $proto, 165 | _caller => $mod->package 166 | ); 167 | } 168 | 169 | D("'$opts{name}' registered"); 170 | $mod->list_store_add('channel_modes', $opts{name}); 171 | } 172 | 173 | sub on_unload { 174 | my ($mod, $event) = @_; 175 | 176 | # delete all mode blocks. 177 | $pool->delete_channel_mode_block($_, $mod->name) 178 | foreach $mod->list_store_items('channel_modes'); 179 | 180 | return 1; 181 | } 182 | 183 | # a module is being initialized. 184 | sub module_init { 185 | my $mod = shift; 186 | my %cmodes = $mod->get_symbol('%channel_modes'); 187 | 188 | # register each mode block. 189 | foreach my $name (keys %cmodes) { 190 | my $hashref = $cmodes{$name}; 191 | ref $hashref eq 'HASH' or next; 192 | 193 | # find the code. fall back to cmode_normal. 194 | my $real_code = $hashref->{code} || 195 | $mode_types{ $hashref->{type} } || 196 | \&cmode_normal; 197 | 198 | 199 | # store it. 200 | $mod->register_channel_mode_block( 201 | name => $name, 202 | code => sub { $real_code->(@_, %$hashref) }, 203 | %$hashref 204 | ) or return; 205 | 206 | } 207 | 208 | return 1; 209 | } 210 | 211 | $mod 212 | -------------------------------------------------------------------------------- /modules/Base/Matchers.module/Matchers.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "API::Methods" 9 | ] 10 | }, 11 | "name" : "Base::Matchers", 12 | "package" : "M::Base::Matchers", 13 | "version" : "11.24" 14 | } 15 | -------------------------------------------------------------------------------- /modules/Base/Matchers.module/Matchers.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # @name: "Base::Matchers" 4 | # @package: "M::Base::Matchers" 5 | # 6 | # @depends.modules+ "API::Methods" 7 | # 8 | # @author.name: "Mitchell Cooper" 9 | # @author.website: "https://github.com/cooper" 10 | # 11 | package M::Base::Matchers; 12 | 13 | use warnings; 14 | use strict; 15 | use 5.010; 16 | 17 | our ($api, $mod, $pool); 18 | 19 | sub init { 20 | 21 | # register methods. 22 | $mod->register_module_method('register_matcher') or return; 23 | 24 | return 1; 25 | } 26 | 27 | sub register_matcher { 28 | my ($mod, $event, %opts) = @_; 29 | 30 | # register the event. 31 | $opts{name} = lc $opts{name}; 32 | $pool->on( 33 | user_match => $opts{code}, 34 | %opts, 35 | _caller => $mod->package 36 | ) or return; 37 | 38 | D("'$opts{name}' registered"); 39 | $mod->list_store_add('matchers', $opts{name}); 40 | return $opts{name}; 41 | } 42 | 43 | $mod 44 | -------------------------------------------------------------------------------- /modules/Base/OperNotices.module/OperNotices.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "API::Methods" 9 | ] 10 | }, 11 | "name" : "Base::OperNotices", 12 | "package" : "M::Base::OperNotices", 13 | "version" : "11.24" 14 | } 15 | -------------------------------------------------------------------------------- /modules/Base/OperNotices.module/OperNotices.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # @name: "Base::OperNotices" 4 | # @package: "M::Base::OperNotices" 5 | # 6 | # @depends.modules+ "API::Methods" 7 | # 8 | # @author.name: "Mitchell Cooper" 9 | # @author.website: "https://github.com/cooper" 10 | # 11 | package M::Base::OperNotices; 12 | 13 | use warnings; 14 | use strict; 15 | use 5.010; 16 | 17 | our ($api, $mod, $pool); 18 | 19 | sub init { 20 | 21 | # register methods. 22 | $mod->register_module_method('register_oper_notice') or return; 23 | 24 | # module events. 25 | $api->on('module.unload' => \&on_unload, 'void.oper.notices'); 26 | $api->on('module.init' => \&module_init, '%oper_notices'); 27 | 28 | return 1; 29 | } 30 | 31 | sub register_oper_notice { 32 | my ($mod, $event, %opts) = @_; 33 | 34 | # make sure all required options are present. 35 | foreach my $what (qw|name format|) { 36 | next if exists $opts{$what}; 37 | $opts{name} ||= 'unknown'; 38 | L("Oper notice '$opts{name}' does not have '$what' option"); 39 | return; 40 | } 41 | 42 | # register the notice. 43 | $opts{name} = lc $opts{name}; 44 | $pool->register_notice( 45 | $mod->name, 46 | $opts{name}, 47 | $opts{format} // $opts{code} 48 | ) or return; 49 | 50 | D("'$opts{name}' registered"); 51 | $mod->list_store_add('oper_notices', $opts{name}); 52 | return 1; 53 | } 54 | 55 | sub on_unload { 56 | my ($mod, $event) = @_; 57 | $pool->delete_notice($mod->name, $_) foreach $mod->list_store_items('oper_notices'); 58 | return 1; 59 | } 60 | 61 | # a module is being initialized. 62 | sub module_init { 63 | my $mod = shift; 64 | my %notices = $mod->get_symbol('%oper_notices'); 65 | $mod->register_oper_notice( 66 | name => $_, 67 | format => $notices{$_} 68 | ) || return foreach keys %notices; 69 | return 1; 70 | } 71 | 72 | $mod 73 | -------------------------------------------------------------------------------- /modules/Base/RegistrationCommands.module/RegistrationCommands.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "API::Methods" 9 | ] 10 | }, 11 | "description" : "provides an interface for client registration commands", 12 | "name" : "Base::RegistrationCommands", 13 | "package" : "M::Base::RegistrationCommands", 14 | "version" : "12.34" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Base/RegistrationCommands.module/RegistrationCommands.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-Mac-mini.local 4 | # Thu Jun 19 18:05:14 EDT 2014 5 | # RegistrationCommands.pm 6 | # 7 | # @name: 'Base::RegistrationCommands' 8 | # @package: 'M::Base::RegistrationCommands' 9 | # @description: 'provides an interface for client registration commands' 10 | # 11 | # @depends.modules+ 'API::Methods' 12 | # 13 | # @author.name: 'Mitchell Cooper' 14 | # @author.website: 'https://github.com/cooper' 15 | # 16 | package M::Base::RegistrationCommands; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | our ($api, $mod, $pool); 23 | 24 | sub init { 25 | 26 | # register method. 27 | $mod->register_module_method('register_registration_command') or return; 28 | 29 | # module events. 30 | $api->on('module.init' => \&module_init, '%registration_commands'); 31 | 32 | return 1; 33 | } 34 | 35 | sub register_registration_command { 36 | my ($mod, $event, %opts) = @_; 37 | 38 | # fallback to the name of the command for the callback name. 39 | $opts{cb_name} //= ($opts{proto} ? "$opts{proto}." : '').$opts{name}; 40 | 41 | # make sure all required options are present. 42 | foreach my $what (qw|name code|) { 43 | next if exists $opts{$what}; 44 | $opts{name} ||= 'unknown'; 45 | L("registration command '$opts{name}' does not have '$what' option"); 46 | return; 47 | } 48 | 49 | # parameter check callback. 50 | my $command = uc delete $opts{name}; 51 | my $event_name = "connection.message_$command"; 52 | my $params = $opts{parameters} || $opts{params}; 53 | $pool->on($event_name => sub { 54 | my ($conn, $event, $msg) = @_; 55 | 56 | # not the right protocol. 57 | return 1 if $opts{proto} && !$conn->possibly_protocol($opts{proto}); 58 | 59 | # there are enough. 60 | return 1 if $msg->params >= $params; 61 | 62 | # not enough. 63 | $conn->numeric(ERR_NEEDMOREPARAMS => $command); 64 | $event->stop; 65 | 66 | }, 67 | name => 'parameter.check.'.$opts{cb_name}, 68 | priority => 1000, 69 | with_eo => 1, 70 | _caller => $mod->package 71 | ) or return if $params; 72 | 73 | # wrapper. 74 | my $code = sub { 75 | my ($conn, $event, $msg) = @_; 76 | 77 | # prevent execution after registration. 78 | return if $conn->{type} && !$opts{after_reg}; 79 | 80 | # only allow a specific protocol. 81 | return if $opts{proto} && !$conn->possibly_protocol($opts{proto}); 82 | 83 | # arguments. 84 | my @args = $msg->params; 85 | unshift @args, $msg if $opts{with_msg}; 86 | unshift @args, $msg->data if $opts{with_data}; 87 | 88 | $opts{code}($conn, $event, @args); 89 | $event->cancel('ERR_UNKNOWNCOMMAND'); 90 | $event->stop unless $opts{continue_handlers}; # prevent later user/server handlers. 91 | }; 92 | 93 | # attach the callback. 94 | my $result = $pool->on($event_name => $code, 95 | name => $opts{cb_name}, 96 | with_eo => 1, 97 | priority => 500, 98 | %opts, 99 | _caller => $mod->package 100 | ); 101 | 102 | D("$command ($opts{cb_name}) registered"); 103 | $mod->list_store_add('registration_commands', $command); 104 | return $result; 105 | } 106 | 107 | # a module is being initialized. 108 | sub module_init { 109 | my $mod = shift; 110 | my %commands = $mod->get_symbol('%registration_commands'); 111 | $mod->register_registration_command( 112 | name => $_, 113 | %{ $commands{$_} } 114 | ) or return foreach keys %commands; 115 | return 1; 116 | } 117 | 118 | $mod 119 | -------------------------------------------------------------------------------- /modules/Base/UserCommands.module/UserCommands.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "API::Methods" 9 | ] 10 | }, 11 | "name" : "Base::UserCommands", 12 | "package" : "M::Base::UserCommands", 13 | "version" : "13.46" 14 | } 15 | -------------------------------------------------------------------------------- /modules/Base/UserCommands.module/UserCommands.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # @name: "Base::UserCommands" 4 | # @package: "M::Base::UserCommands" 5 | # 6 | # @depends.modules+ "API::Methods" 7 | # 8 | # @author.name: "Mitchell Cooper" 9 | # @author.website: "https://github.com/cooper" 10 | # 11 | package M::Base::UserCommands; 12 | 13 | use warnings; 14 | use strict; 15 | use 5.010; 16 | 17 | use utils qw(col trim); 18 | use Scalar::Util qw(looks_like_number); 19 | 20 | our ($api, $mod, $pool); 21 | my $props = $Evented::Object::props; 22 | 23 | sub init { 24 | 25 | # register methods. 26 | $mod->register_module_method('register_user_command_new') or return; 27 | $mod->register_module_method('delete_user_command') or return; 28 | 29 | # module events. 30 | $api->on('module.init' => \&module_init, '%user_commands'); 31 | 32 | return 1; 33 | } 34 | 35 | ########### 36 | ### NEW ### 37 | ########### 38 | 39 | sub register_user_command_new { 40 | my ($mod, $event, %opts) = @_; 41 | $opts{description} //= $opts{desc}; 42 | $opts{parameters} //= $opts{params}; 43 | 44 | # make sure all required options are present. 45 | foreach my $what (qw|name description code|) { 46 | next if defined $opts{$what}; 47 | $opts{name} ||= 'unknown'; 48 | L("user command $opts{name} does not have '$what' option"); 49 | return; 50 | } 51 | 52 | # attach the event. 53 | my $command = uc $opts{name}; 54 | $pool->on("user.message_$command" => \&_handle_command, 55 | priority => 0, # registration commands are 500 priority 56 | with_eo => 1, 57 | name => $command, 58 | %opts, 59 | _caller => $mod->package, 60 | data => { 61 | parameters => $opts{parameters}, 62 | cb_code => $opts{code} 63 | }); 64 | 65 | $mod->list_store_add('user_commands', $command); 66 | return 1; 67 | } 68 | 69 | sub delete_user_command { 70 | my ($mod, $event, $command) = @_; 71 | $command = uc $command; 72 | # ->list_store_remove... 73 | $pool->delete_callback("user.message_$command", $command); 74 | } 75 | 76 | sub _handle_command { 77 | my ($user, $event, $msg) = @_; 78 | $event->cancel('ERR_UNKNOWNCOMMAND'); 79 | $msg->{source} = $user; 80 | 81 | # figure parameters. 82 | my ($ok, @params); 83 | if (my $params = $event->callback_data('parameters')) { 84 | # $msg->{_event} = $event; 85 | ($ok, @params) = $msg->parse_params($params); 86 | if (!$ok) { 87 | my $cmd = $msg->command; 88 | D("Unsatisfied parameters for $cmd [$params] -> [@params]"); 89 | return; 90 | } 91 | } 92 | 93 | # call actual callback. 94 | $event->callback_data('cb_code')->($user, $event, @params); 95 | 96 | } 97 | 98 | sub module_init { 99 | my $mod = shift; 100 | my %commands = $mod->get_symbol('%user_commands'); 101 | $mod->register_user_command_new( 102 | name => $_, 103 | %{ $commands{$_} } 104 | ) or return foreach keys %commands; 105 | return 1; 106 | } 107 | 108 | $mod 109 | -------------------------------------------------------------------------------- /modules/Base/UserModes.module/UserModes.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "API::Methods" 9 | ] 10 | }, 11 | "name" : "Base::UserModes", 12 | "package" : "M::Base::UserModes", 13 | "version" : "11.24" 14 | } 15 | -------------------------------------------------------------------------------- /modules/Base/UserModes.module/UserModes.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # @name: "Base::UserModes" 4 | # @package: "M::Base::UserModes" 5 | # 6 | # @depends.modules+ "API::Methods" 7 | # 8 | # @author.name: "Mitchell Cooper" 9 | # @author.website: "https://github.com/cooper" 10 | # 11 | package M::Base::UserModes; 12 | 13 | use warnings; 14 | use strict; 15 | use 5.010; 16 | 17 | our ($api, $mod, $pool); 18 | 19 | sub init { 20 | 21 | # register methods. 22 | $mod->register_module_method('register_user_mode_block') or return; 23 | 24 | # module unload event. 25 | $api->on('module.unload' => \&on_unload, 'void.user.modes'); 26 | 27 | return 1; 28 | } 29 | 30 | sub register_user_mode_block { 31 | my ($mod, $event, %opts) = @_; 32 | 33 | # make sure all required options are present. 34 | foreach my $what (qw|name code|) { 35 | next if exists $opts{$what}; 36 | $opts{name} ||= 'unknown'; 37 | L("user mode block '$opts{name}' does not have '$what' option"); 38 | return 39 | } 40 | 41 | # register the mode block. 42 | $opts{name} = lc $opts{name}; 43 | $pool->register_user_mode_block( 44 | $opts{name}, 45 | $mod->name, 46 | $opts{code} 47 | ); 48 | 49 | D("'$opts{name}' registered"); 50 | $mod->list_store_add('user_modes', $opts{name}); 51 | return 1; 52 | } 53 | 54 | sub on_unload { 55 | my ($mod, $event) = @_; 56 | $pool->delete_user_mode_block($_, $mod->name) 57 | foreach $mod->list_store_items('user_modes'); 58 | 59 | return 1; 60 | } 61 | 62 | $mod 63 | -------------------------------------------------------------------------------- /modules/Base/UserNumerics.module/UserNumerics.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "API::Methods" 9 | ] 10 | }, 11 | "name" : "Base::UserNumerics", 12 | "package" : "M::Base::UserNumerics", 13 | "version" : "11.34" 14 | } 15 | -------------------------------------------------------------------------------- /modules/Base/UserNumerics.module/UserNumerics.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # @name: "Base::UserNumerics" 4 | # @package: "M::Base::UserNumerics" 5 | # 6 | # @depends.modules+ "API::Methods" 7 | # 8 | # @author.name: "Mitchell Cooper" 9 | # @author.website: "https://github.com/cooper" 10 | # 11 | package M::Base::UserNumerics; 12 | 13 | use warnings; 14 | use strict; 15 | use 5.010; 16 | 17 | our ($api, $mod, $pool); 18 | 19 | sub init { 20 | 21 | # register methods. 22 | $mod->register_module_method('register_user_numeric') or return; 23 | 24 | # module events. 25 | $api->on('module.unload' => \&on_unload, 'void.user.numerics'); 26 | $api->on('module.init' => \&module_init, '%user_numerics'); 27 | 28 | return 1; 29 | } 30 | 31 | sub register_user_numeric { 32 | my ($mod, $event, %opts) = @_; 33 | 34 | # make sure all required options are present. 35 | foreach my $what (qw|name number format|) { 36 | next if exists $opts{$what}; 37 | $opts{name} ||= 'unknown'; 38 | L("User numeric $opts{name} does not have '$what' option"); 39 | return; 40 | } 41 | 42 | # register the numeric. 43 | $pool->register_numeric( 44 | $mod->name, 45 | $opts{name}, 46 | $opts{number}, 47 | $opts{format} // $opts{code}, 48 | $opts{allow_conn} || $opts{allow_connection} 49 | ) or return; 50 | 51 | D("$opts{name} $opts{number} registered"); 52 | $mod->list_store_add('user_numerics', $opts{name}); 53 | return 1; 54 | } 55 | 56 | sub on_unload { 57 | my ($mod, $event) = @_; 58 | $pool->delete_numeric($mod->name, $_) foreach $mod->list_store_items('user_numerics'); 59 | return 1; 60 | } 61 | 62 | # a module is being initialized. 63 | sub module_init { 64 | my $mod = shift; 65 | my %numerics = $mod->get_symbol('%user_numerics'); 66 | $mod->register_user_numeric( 67 | name => $_, 68 | number => $numerics{$_}[0], 69 | format => $numerics{$_}[1] 70 | ) || return foreach keys %numerics; 71 | return 1; 72 | } 73 | 74 | 75 | $mod 76 | -------------------------------------------------------------------------------- /modules/Channel/Access.module/Access.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes", 9 | "UserCommands", 10 | "UserNumerics" 11 | ] 12 | }, 13 | "description" : "implements channel access mode", 14 | "name" : "Channel::Access", 15 | "package" : "M::Channel::Access", 16 | "version" : "5.8" 17 | } 18 | -------------------------------------------------------------------------------- /modules/Channel/Fantasy.module/Fantasy.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "channel fantasy commands", 7 | "name" : "Channel::Fantasy", 8 | "package" : "M::Channel::Fantasy", 9 | "version" : "5.1" 10 | } 11 | -------------------------------------------------------------------------------- /modules/Channel/Fantasy.module/Fantasy.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-14, Mitchell Cooper 2 | # 3 | # @name: "Channel::Fantasy" 4 | # @package: "M::Channel::Fantasy" 5 | # @description: "channel fantasy commands" 6 | # 7 | # @author.name: "Mitchell Cooper" 8 | # @author.website: "https://github.com/cooper" 9 | # 10 | package M::Channel::Fantasy; 11 | 12 | use warnings; 13 | use strict; 14 | use 5.010; 15 | 16 | use utils qw(conf); 17 | 18 | our ($api, $mod, $pool); 19 | 20 | sub init { 21 | 22 | # all local commands. cancel if using fantasy and fantasy isn't allowed. 23 | $pool->on('user.message' => \&local_message, 24 | with_eo => 1, 25 | name => 'fantasy.stopper', 26 | priority => 100 # this is higher than the normal handler priority 0 27 | ); 28 | 29 | # catch local PRIVMSGs after the main handler. 30 | $pool->on('user.message_PRIVMSG' => \&local_privmsg, 31 | with_eo => 1, 32 | after => 'PRIVMSG', 33 | name => 'fantasy.privmsg' 34 | ); 35 | 36 | return 1; 37 | } 38 | 39 | my $FANTASY_OK = 1; 40 | 41 | # called on all user messages 42 | sub local_message { 43 | my ($user, $event, $msg) = @_; 44 | return unless $user->is_local; 45 | 46 | # this was not a fantasy command. 47 | return $FANTASY_OK 48 | if !$event->data('is_fantasy'); 49 | 50 | # fantasy commands are permitted by the configuration, 51 | # and the client is not marked as a bot. 52 | return $FANTASY_OK 53 | if conf(['channels', 'fantasy'], lc $msg->command) 54 | && !$user->is_mode('bot'); 55 | 56 | # otherwise, stop the execution of the command. 57 | $event->stop('fantasy_not_allowed'); 58 | return; 59 | 60 | } 61 | 62 | # called on local PRIVMSG message 63 | sub local_privmsg { 64 | my ($user, $event, $msg) = @_; 65 | return unless $user->is_local; 66 | 67 | # the PRIVMSG must have been successful. 68 | return unless $event->return_of('PRIVMSG'); 69 | 70 | # only care about channels. 71 | my ($channel, $message) = @$msg{'target', 'message'}; 72 | return if !$channel || !$channel->isa('channel'); 73 | 74 | # only care about messages starting with !. 75 | $message =~ m/^!(\w+)\s*(.*)$/ or return; 76 | my ($cmd, $args) = (lc $1, $2); 77 | 78 | # prevents e.g. !privmsg !privmsg or !lolcat !kick. 79 | # I doubt this is needed anymore since we use message_PRIVMSG now. 80 | # surely we could detect is_fantasy from the $event instead. that's how 81 | # we'll do it once the prefix is customizable. 82 | my $second_p = (split /\s+/, $message, 2)[1]; 83 | return if defined $second_p && substr($second_p, 0, 1) eq '!'; 84 | 85 | # handle the command. 86 | return $user->handle_with_opts( 87 | "$cmd $$channel{name} $args", 88 | is_fantasy => 1 89 | ); 90 | 91 | } 92 | 93 | $mod 94 | -------------------------------------------------------------------------------- /modules/Channel/Forward.module/Forward.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Matthew Barksdale", 4 | "website" : "https://github.com/mattwb65" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes", 9 | "UserNumerics" 10 | ] 11 | }, 12 | "description" : "adds channel forwarding abilities", 13 | "name" : "Channel::Forward", 14 | "package" : "M::Channel::Forward", 15 | "version" : "3.9" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Channel/Forward.module/Forward.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014, matthew 2 | # 3 | # Created on mattbook 4 | # Wed Oct 15 12:16:56 EDT 2014 5 | # Forward.pm 6 | # 7 | # @name: 'Channel::Forward' 8 | # @package: 'M::Channel::Forward' 9 | # @description: 'adds channel forwarding abilities' 10 | # 11 | # @depends.bases+ 'ChannelModes', 'UserNumerics' 12 | # 13 | # @author.name: 'Matthew Barksdale' 14 | # @author.website: 'https://github.com/mattwb65' 15 | # 16 | package M::Channel::Forward; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | use utils qw(cut_to_limit cols); 23 | 24 | our ($api, $mod, $pool); 25 | 26 | our %user_numerics = ( 27 | ERR_LINKCHAN => [ 470, '%s %s :Forwarding to another channel' ] 28 | ); 29 | 30 | our %channel_modes = ( 31 | forward => { code => \&cmode_forward }, 32 | free_forward => { type => 'normal' }, 33 | no_forward => { type => 'normal' } 34 | ); 35 | 36 | sub init { 37 | 38 | # Hook on the cant_join event to forward users if needed. 39 | $pool->on('user.cant_join' => \&on_user_cant_join, 'join.fail.forward'); 40 | 41 | return 1; 42 | } 43 | 44 | sub cmode_forward { 45 | my ($channel, $mode) = @_; 46 | $mode->{has_basic_status} or return; 47 | 48 | # setting. 49 | if ($mode->{state}) { 50 | 51 | # sanity checking 52 | my $f_ch_name = $mode->{param} = 53 | cols(cut_to_limit('forward', $mode->{param})); 54 | return if !length $f_ch_name; 55 | 56 | # first thing's first. make sure the channel name is valid. 57 | if (!utils::validchan($f_ch_name)) { 58 | D("Invalid forward channel name for $$channel{name}: $f_ch_name"); 59 | return; 60 | } 61 | 62 | # when the source is a user, we have to check that the channel exists 63 | # and that the user is permitted to set it as a forward target. 64 | my $source = $mode->{source}; 65 | if ($source->isa('user')) { 66 | my $f_chan = $pool->lookup_channel($f_ch_name); 67 | 68 | # channel does not exist. 69 | if (!$f_chan) { 70 | $source->numeric(ERR_NOSUCHCHANNEL => $mode->{param}); 71 | return; 72 | } 73 | 74 | # forwarding to the same channel. 75 | return if $f_chan == $channel; 76 | 77 | # make sure that, unless the channel is marked as free forward, 78 | # the user has the necessary status. 79 | my $permission_ok = 80 | $mode->{force} || $f_chan->user_has_basic_status($source); 81 | if (!$f_chan->is_mode('free_forward') && !$permission_ok) { 82 | $source->numeric(ERR_CHANOPRIVSNEEDED => $f_chan->name); 83 | return; 84 | } 85 | } 86 | 87 | $channel->set_mode('forward', $mode->{param}); 88 | } 89 | 90 | # unsetting. 91 | else { 92 | return unless $channel->is_mode('forward'); 93 | $channel->unset_mode('forward'); 94 | } 95 | 96 | return 1; 97 | } 98 | 99 | # attempt to do a forward maybe. 100 | sub on_user_cant_join { 101 | my ($user, $event, $channel) = @_; 102 | return unless $channel->is_mode('forward'); 103 | my $f_ch_name = $channel->mode_parameter('forward'); 104 | 105 | # We need the channel object, unfortunately it is not always the case that 106 | # we are being forwarded to a channel that already exists. 107 | my ($f_chan, $new) = $pool->lookup_or_create_channel($f_ch_name); 108 | 109 | # Check if the forward channel is the same as the original 110 | return if $f_chan == $channel; 111 | 112 | # Check if the forward channel is marked as no forward 113 | if ($f_chan->is_mode('no_forward')) { 114 | # if we just created this channel, dispose of it. 115 | $channel->destroy_maybe; 116 | return; 117 | } 118 | 119 | # Check if we're even able to join the channel to be forwarded to 120 | my $can_fire = $user->fire(can_join => $f_chan); 121 | if ($can_fire->stopper) { 122 | # if we just created this channel, dispose of it. 123 | $channel->destroy_maybe; 124 | return; 125 | } 126 | 127 | # Safe point - we are definitely joining the forward channel. 128 | 129 | # Let the user know we're forwarding... 130 | $user->numeric(ERR_LINKCHAN => $channel->name, $f_chan->name); 131 | 132 | # force the join. 133 | $f_chan->attempt_join($user, $new, undef, 1); 134 | 135 | # stopping cant_join cancels the original channel's error message. 136 | $event->stop; 137 | } 138 | 139 | $mod 140 | -------------------------------------------------------------------------------- /modules/Channel/Invite.module/Invite.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserCommands", 9 | "ChannelModes", 10 | "UserNumerics", 11 | "Capabilities" 12 | ] 13 | }, 14 | "description" : "channel invitations", 15 | "name" : "Channel::Invite", 16 | "package" : "M::Channel::Invite", 17 | "version" : "6.2" 18 | } 19 | -------------------------------------------------------------------------------- /modules/Channel/JoinThrottle.module/JoinThrottle.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes", 9 | "UserNumerics" 10 | ] 11 | }, 12 | "description" : "channel mode to prevent join flooding", 13 | "name" : "Channel::JoinThrottle", 14 | "package" : "M::Channel::JoinThrottle", 15 | "version" : "1.9" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Channel/JoinThrottle.module/JoinThrottle.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-17, Mitchell Cooper 2 | # 3 | # @name: "Channel::JoinThrottle" 4 | # @package: "M::Channel::JoinThrottle" 5 | # @description: "channel mode to prevent join flooding" 6 | # 7 | # @depends.bases+ 'ChannelModes', 'UserNumerics' 8 | # 9 | # @author.name: "Mitchell Cooper" 10 | # @author.website: "https://github.com/cooper" 11 | # 12 | package M::Channel::JoinThrottle; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | our ($api, $mod, $me, $pool); 19 | 20 | our %channel_modes = (join_throttle => { 21 | code => \&cmode_throttle, 22 | str_1459 => \&cmode_throttle_str, 23 | str_client => \&cmode_throttle_str_jelp, 24 | str_jelp => \&cmode_throttle_str_jelp 25 | }); 26 | 27 | our %user_numerics = (ERR_THROTTLE => 28 | [ 480, '%s :Cannot join channel - throttle exceeded, try again later' ] 29 | ); 30 | 31 | sub init { 32 | $pool->on('channel.user_joined' => 33 | \&on_user_joined, 'update.join.throttle'); 34 | $pool->on('user.can_join' => 35 | \&on_user_can_join, 'check.join.throttle'); 36 | return 1; 37 | } 38 | 39 | # $channel->mode_parameter('join_throttle') = { 40 | # joins => number of joins permitted 41 | # secs => in this number of seconds 42 | # time => number of seconds to lock the channel (optional) 43 | # count => number of joins since the last reset 44 | # reset => time at which the counter should be reset 45 | # locked => time at which the channel should be unlocked 46 | # } 47 | # 48 | sub cmode_throttle { 49 | my ($channel, $mode) = @_; 50 | $mode->{has_basic_status} or return; 51 | 52 | # always allow unsetting. 53 | if (!$mode->{state}) { 54 | return 1; 55 | } 56 | 57 | # normalize/check if valid. 58 | if ($mode->{param} =~ m/^(\d+):(\d+)(?::(\d+))?$/) { 59 | return if !$1 || !$2; 60 | $mode->{param} = { 61 | joins => $1, 62 | secs => $2, 63 | time => $3 64 | }; 65 | return 1; 66 | } 67 | 68 | return; 69 | } 70 | 71 | # for RFC1459, joins:sec 72 | sub cmode_throttle_str { 73 | my ($throttle) = @_; 74 | return "$$throttle{joins}:$$throttle{secs}"; 75 | } 76 | 77 | # for JELP and client protocol, joins:sec:locktime 78 | sub cmode_throttle_str_jelp { 79 | my ($throttle) = @_; 80 | return &cmode_throttle_str 81 | if !length $throttle->{time}; 82 | return "$$throttle{joins}:$$throttle{secs}:$$throttle{time}"; 83 | } 84 | 85 | sub on_user_joined { 86 | my ($channel, $event, $user) = @_; 87 | 88 | # we're not interested in joins during burst. 89 | return if $user->location->{is_burst}; 90 | 91 | # we're not interested if the channel has no throttle 92 | # or if the channel is already locked. 93 | my $throttle = $channel->mode_parameter('join_throttle'); 94 | return if !$throttle || $throttle->{locked}; 95 | 96 | # time to reset the counter. 97 | if (time >= ($throttle->{reset} || 0)) { 98 | $throttle->{count} = 0; 99 | $throttle->{reset} = time() + $throttle->{secs}; 100 | } 101 | 102 | # increment the counter. if it is more than the max number of joins, 103 | # lock the channel. 104 | if (++$throttle->{count} >= $throttle->{joins}) { 105 | my $secs_to_lock = $throttle->{time} || 60; 106 | $throttle->{count} = 0; 107 | $throttle->{locked} = time() + $secs_to_lock; 108 | D("join throttle: locking $$channel{name} for $secs_to_lock seconds") 109 | } 110 | } 111 | 112 | sub on_user_can_join { 113 | my ($user, $event, $channel, $key) = @_; 114 | 115 | # we're only interested when the channel is locked. 116 | my $throttle = $channel->mode_parameter('join_throttle'); 117 | return if !$throttle || !$throttle->{locked}; 118 | 119 | # time to unlock the channel. 120 | if (time >= $throttle->{locked}) { 121 | D("join throttle: unlocking $$channel{name}"); 122 | delete $throttle->{locked}; 123 | return; 124 | } 125 | 126 | # user has invite. 127 | return if $channel->user_has_invite($user); 128 | 129 | $event->{error_reply} = [ ERR_THROTTLE => $channel->name ]; 130 | $event->stop('join throttle active'); 131 | } 132 | 133 | $mod 134 | -------------------------------------------------------------------------------- /modules/Channel/Key.module/Key.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Matthew Barksdale", 4 | "website" : "https://github.com/mattwb65" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes", 9 | "UserNumerics" 10 | ] 11 | }, 12 | "description" : "adds channel key mode", 13 | "name" : "Channel::Key", 14 | "package" : "M::Channel::Key", 15 | "version" : "3.3" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Channel/Key.module/Key.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014, matthew 2 | # 3 | # Created on mattbook 4 | # Wed Jul 2 01:25:56 EDT 2014 5 | # Key.pm 6 | # 7 | # @name: 'Channel::Key' 8 | # @package: 'M::Channel::Key' 9 | # @description: 'adds channel key mode' 10 | # 11 | # @depends.bases+ 'ChannelModes', 'UserNumerics' 12 | # 13 | # @author.name: 'Matthew Barksdale' 14 | # @author.website: 'https://github.com/mattwb65' 15 | # 16 | package M::Channel::Key; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | use utils qw(cut_to_limit cols); 23 | 24 | our ($api, $mod, $pool); 25 | 26 | # user numerics 27 | our %user_numerics = ( 28 | ERR_BADCHANNELKEY => [ 481, '%s :Invalid channel key' ], 29 | ERR_KEYSET => [ 467, '%s :Channel key already set' ] 30 | ); 31 | 32 | # channel mode block 33 | our %channel_modes = ( 34 | key => { code => \&cmode_key } 35 | ); 36 | 37 | sub init { 38 | 39 | # Hook on the can_join event to prevent joining a channel without valid key 40 | $pool->on('user.can_join' => \&on_user_can_join, 'has.key'); 41 | 42 | return 1; 43 | } 44 | 45 | sub cmode_key { 46 | my ($channel, $mode) = @_; 47 | $mode->{has_basic_status} or return; 48 | 49 | # setting. 50 | if ($mode->{state}) { 51 | 52 | # sanity checking 53 | $mode->{param} = fix_key($mode->{param}); 54 | return if !length $mode->{param}; 55 | 56 | $channel->set_mode('key', $mode->{param}); 57 | } 58 | 59 | # unsetting. 60 | else { 61 | return unless $channel->is_mode('key'); 62 | # show -k * for security 63 | $mode->{param} = '*'; 64 | $channel->unset_mode('key'); 65 | } 66 | 67 | return 1; 68 | } 69 | 70 | # charybdis@8fed90ba8a221642ae1f0fd450e8e580a79061fb/ircd/chmode.cc#L556 71 | sub fix_key { 72 | my $in = shift; 73 | my $out = ''; 74 | for (split //, $in) { 75 | next if /[\r\n\s:,]/; 76 | next if ord() < 13; 77 | $out .= $_; 78 | } 79 | return cut_to_limit('key', $out); 80 | } 81 | 82 | sub on_user_can_join { 83 | my ($user, $event, $channel, $key) = @_; 84 | return unless $channel->is_mode('key'); 85 | return if $channel->user_has_invite($user); 86 | return if defined $key && $channel->mode_parameter('key') eq $key; 87 | $event->{error_reply} = [ ERR_BADCHANNELKEY => $channel->name ]; 88 | $event->stop; 89 | } 90 | 91 | $mod 92 | -------------------------------------------------------------------------------- /modules/Channel/Knock.module/Knock.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserCommands", 9 | "UserNumerics" 10 | ] 11 | }, 12 | "description" : "request to join a restricted channel", 13 | "name" : "Channel::Knock", 14 | "package" : "M::Channel::Knock", 15 | "version" : "1.2" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Channel/Knock.module/Knock.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # @name: "Channel::Knock" 4 | # @package: "M::Channel::Knock" 5 | # @description: "request to join a restricted channel" 6 | # 7 | # @depends.bases+ 'UserCommands', 'UserNumerics' 8 | # 9 | # @author.name: "Mitchell Cooper" 10 | # @author.website: "https://github.com/cooper" 11 | # 12 | package M::Channel::Knock; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | use List::Util qw(first); 19 | use utils qw(ref_to_list broadcast); 20 | 21 | our ($api, $mod, $me, $pool); 22 | 23 | our %user_commands = (KNOCK => { 24 | desc => 'request to join a restricted channel', 25 | params => 'channel', 26 | code => \&knock 27 | }); 28 | 29 | our %user_numerics = ( 30 | RPL_KNOCKDLVR => [ 711, '%s :Your knock was delivered' ], 31 | RPL_KNOCK => [ 710, '%s %s :has asked for an invite' ], 32 | ERR_CHANOPEN => [ 713, '%s :Channel is open' ], 33 | ERR_KNOCKONCHAN => [ 714, '%s :You\'re already on that channel' ] 34 | ); 35 | 36 | sub init { 37 | 38 | # add KNOCK to RPL_ISUPPORT 39 | $me->on(supported => sub { 40 | my ($event, $supported, $yes) = @_; 41 | $supported->{KNOCK} = $yes; 42 | }); 43 | 44 | # knock events 45 | $pool->on('user.can_knock' => \&on_user_can_knock, 'check.banned'); 46 | $pool->on('channel.knock' => \&on_channel_knock, 'notify.channel'); 47 | 48 | return 1; 49 | } 50 | 51 | sub knock { 52 | my ($user, $event, $channel) = @_; 53 | 54 | # already there 55 | if ($channel->has_user($user)) { 56 | $user->numeric(ERR_KNOCKONCHAN => $channel->name); 57 | return; 58 | } 59 | 60 | # not restricted 61 | if (not first { $channel->is_mode($_ ) } qw(invite_only key limit)) { 62 | $user->numeric(ERR_CHANOPEN => $channel->name); 63 | return; 64 | } 65 | 66 | # fire can_knock. if canceled, we can't. 67 | # callbacks may set {error_reply} to a numeric. 68 | my $fire = $user->fire(can_knock => $channel); 69 | if ($fire->stopper) { 70 | my $err = $fire->{error_reply}; 71 | $user->numeric(ref_to_list($err)) if $err; 72 | return; 73 | } 74 | 75 | # TODO: (#153) check for ridiculous knocking behavior. 76 | # too many knocks to the channel or too many from this user. 77 | # send ERR_TOOMANYKNOCK on fail. 78 | 79 | $channel->fire(knock => $user); 80 | broadcast(knock => $user, $channel); 81 | } 82 | 83 | sub on_user_can_knock { 84 | my ($user, $event, $channel) = @_; 85 | return unless $channel->user_is_banned($user); 86 | $event->{error_reply} = 87 | [ ERR_CANNOTSENDTOCHAN => $channel->name, "You're banned" ]; 88 | $event->stop('banned'); 89 | } 90 | 91 | sub on_channel_knock { 92 | my ($channel, $event, $user) = @_; 93 | 94 | # unless the channel is free invite, only notify ops 95 | my @notify = $channel->real_local_users; 96 | @notify = grep { $channel->user_has_basic_status($_) } @notify 97 | unless $channel->is_mode('free_invite'); 98 | $_->numeric(RPL_KNOCK => $channel->name, $user->full) for @notify; 99 | 100 | # TODO: (#153) increment the knock count on this channel 101 | 102 | # notify the user that the knock was delivered if he's local 103 | $user->numeric(RPL_KNOCKDLVR => $channel->name) 104 | if $user->is_local; 105 | } 106 | 107 | $mod 108 | -------------------------------------------------------------------------------- /modules/Channel/LargeList.module/LargeList.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "increase the number of bans permitted in a channel", 7 | "name" : "Channel::LargeList", 8 | "package" : "M::Channel::LargeList", 9 | "version" : "0.4" 10 | } 11 | -------------------------------------------------------------------------------- /modules/Channel/LargeList.module/LargeList.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017, Mitchell Cooper 2 | # 3 | # @name: "Channel::LargeList" 4 | # @package: "M::Channel::LargeList" 5 | # @description: "increase the number of bans permitted in a channel" 6 | # 7 | # @author.name: "Mitchell Cooper" 8 | # @author.website: "https://github.com/cooper" 9 | # 10 | package M::Channel::LargeList; 11 | 12 | use warnings; 13 | use strict; 14 | use 5.010; 15 | 16 | use utils qw(conf); 17 | 18 | our ($api, $mod, $me, $pool); 19 | 20 | # channel mode block 21 | our %channel_modes = ( 22 | large_banlist => { code => \&cmode_large_banlist } 23 | ); 24 | 25 | sub init { 26 | $pool->on('channel.max_list_entries' => 27 | \&channel_max_list_entries, 'large.ban.list'); 28 | return 1; 29 | } 30 | 31 | sub channel_max_list_entries { 32 | my ($channel, $event) = @_; 33 | return unless $channel->is_mode('large_banlist'); 34 | $event->{max} = conf('channels', 'max_bans_large'); 35 | } 36 | 37 | sub cmode_large_banlist { 38 | my ($channel, $mode) = @_; 39 | 40 | # user must be a channel op 41 | $mode->{has_basic_status} or return; 42 | 43 | # always OK for non-users and if force enabled 44 | return 1 if $mode->{force} || !$mode->{source}->isa('user'); 45 | 46 | # user must be an IRC cop with set_large_banlist 47 | return $mode->{source}->has_flag('set_large_banlist'); 48 | } 49 | 50 | $mod 51 | -------------------------------------------------------------------------------- /modules/Channel/Limit.module/Limit.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Matthew Barksdale", 4 | "website" : "https://github.com/mattwb65" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes", 9 | "UserNumerics" 10 | ] 11 | }, 12 | "description" : "adds channel user limit mode", 13 | "name" : "Channel::Limit", 14 | "package" : "M::Channel::Limit", 15 | "version" : "3.1" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Channel/Limit.module/Limit.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014, matthew 2 | # 3 | # Created on mattbook 4 | # Sun Jun 29 14:49:55 EDT 2014 5 | # Limit.pm 6 | # 7 | # @name: 'Channel::Limit' 8 | # @package: 'M::Channel::Limit' 9 | # @description: 'adds channel user limit mode' 10 | # 11 | # @depends.bases+ 'ChannelModes', 'UserNumerics' 12 | # 13 | # @author.name: 'Matthew Barksdale' 14 | # @author.website: 'https://github.com/mattwb65' 15 | # 16 | package M::Channel::Limit; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | use Scalar::Util 'looks_like_number'; 22 | 23 | our ($api, $mod, $pool); 24 | my $MAX = ~0 >> 1; 25 | 26 | # numerics 27 | our %user_numerics = ( 28 | ERR_CHANNELISFULL => [ 471, '%s :Channel is full' ] 29 | ); 30 | 31 | # channel mode block 32 | our %channel_modes = ( 33 | limit => { code => \&cmode_limit } 34 | ); 35 | 36 | sub init { 37 | 38 | # Hook on the can_join event to prevent joining a channel that is full 39 | $pool->on('user.can_join' => \&on_user_can_join, 'has.limit'); 40 | 41 | return 1; 42 | } 43 | 44 | sub cmode_limit { 45 | my ($channel, $mode) = @_; 46 | $mode->{has_basic_status} or return; 47 | 48 | # always allow unsetting. 49 | return 1 if !$mode->{state}; 50 | 51 | # not a number. 52 | return if !looks_like_number($mode->{param}); 53 | 54 | # determine the value. 55 | $mode->{param} = int $mode->{param}; 56 | $mode->{param} = $MAX if $mode->{param} > $MAX; 57 | 58 | # it's negative or nan. 59 | return if $mode->{param} <= 0; 60 | return if $mode->{param} eq 'nan'; 61 | 62 | return 1; 63 | } 64 | 65 | sub on_user_can_join { 66 | my ($user, $event, $channel) = @_; 67 | return unless $channel->is_mode('limit'); 68 | return if 1 + scalar $channel->users <= $channel->mode_parameter('limit'); 69 | return if $channel->user_has_invite($user); 70 | $event->{error_reply} = [ ERR_CHANNELISFULL => $channel->name ]; 71 | $event->stop('channel_full'); 72 | } 73 | 74 | $mod 75 | -------------------------------------------------------------------------------- /modules/Channel/ModeSync.module/Desync.module/Desync.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserCommands", 9 | "OperNotices" 10 | ] 11 | }, 12 | "description" : "provides the MODESYNC user command to fix desyncs", 13 | "name" : "Channel::ModeSync::Desync", 14 | "package" : "M::Channel::ModeSync::Desync", 15 | "version" : "0.8" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Channel/ModeSync.module/Desync.module/Desync.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # @name: 'Channel::ModeSync::Desync' 4 | # @package: 'M::Channel::ModeSync::Desync' 5 | # @description: 'provides the MODESYNC user command to fix desyncs' 6 | # 7 | # @depends.bases+ 'UserCommands', 'OperNotices' 8 | # 9 | # @author.name: 'Mitchell Cooper' 10 | # @author.website: 'https://github.com/cooper' 11 | # 12 | package M::Channel::ModeSync::Desync; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | use utils qw(gnotice broadcast); 19 | 20 | our ($api, $mod, $pool, $me); 21 | 22 | our %user_commands = (MODESYNC => { 23 | code => \&modesync, 24 | desc => 'fix channel mode desyncs', 25 | params => '-oper(modesync) channel' 26 | }); 27 | 28 | our %oper_notices = ( 29 | modesync => '%s issued MODESYNC for %s' 30 | ); 31 | 32 | sub modesync { 33 | my ($user, $event, $channel) = @_; 34 | gnotice($user, modesync => $user->notice_info, $channel->name); 35 | 36 | # send a MODEREQ to * 37 | # ($source_serv, $ch_maybe, $serv_maybe, $modes_maybe) 38 | broadcast(modereq => $me, $channel, undef, undef); 39 | 40 | # send a MODEREP with my own modes to * 41 | # ($source_serv, $channel, $serv_maybe, $mode_string) 42 | my (undef, $mode_str) = $channel->mode_string_all($me); 43 | broadcast(moderep => 44 | $me, $channel, 45 | undef, # reply to * 46 | $mode_str 47 | ); 48 | } 49 | 50 | $mod 51 | -------------------------------------------------------------------------------- /modules/Channel/ModeSync.module/JELP.module/JELP.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "JELP mode synchronization", 7 | "name" : "Channel::ModeSync::JELP", 8 | "package" : "M::Channel::ModeSync::JELP", 9 | "version" : "0.7" 10 | } 11 | -------------------------------------------------------------------------------- /modules/Channel/ModeSync.module/JELP.module/JELP.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # @name: 'Channel::ModeSync::JELP' 4 | # @package: 'M::Channel::ModeSync::JELP' 5 | # @description: 'JELP mode synchronization' 6 | # 7 | # @author.name: 'Mitchell Cooper' 8 | # @author.website: 'https://github.com/cooper' 9 | # 10 | package M::Channel::ModeSync::JELP; 11 | 12 | use warnings; 13 | use strict; 14 | use 5.010; 15 | 16 | our ($api, $mod, $pool); 17 | 18 | our %jelp_incoming_commands = ( 19 | MODEREQ => { 20 | # : MODEREQ |* |* |* 21 | params => '-source(server) * * *', 22 | code => \&in_modereq 23 | }, 24 | MODEREP => { 25 | # : MODEREP |* : 26 | params => '-source(server) channel * *', 27 | code => \&in_moderep 28 | } 29 | ); 30 | 31 | our %jelp_outgoing_commands = ( 32 | modereq => \&out_modereq, 33 | moderep => \&out_moderep 34 | ); 35 | 36 | my ($handle_modereq, $handle_moderep); 37 | 38 | sub init { 39 | $handle_modereq = M::Channel::ModeSync->can('handle_modereq') or return; 40 | $handle_moderep = M::Channel::ModeSync->can('handle_moderep') or return; 41 | return 1; 42 | } 43 | 44 | sub in_modereq { 45 | my ($server, $msg, $source_serv, $ch_name, $target, $modes) = @_; 46 | undef $ch_name if $ch_name eq '*'; 47 | undef $target if $target eq '*'; 48 | undef $modes if $modes eq '*'; 49 | 50 | # find a target server maybe. 51 | $target = $pool->lookup_server($target) or return 52 | if defined $target; 53 | 54 | # find a channel maybe. 55 | # abort if a name was provided and we can't find it. 56 | my $ch_maybe = $pool->lookup_channel($ch_name) or return 57 | if defined $ch_name; 58 | 59 | return $handle_modereq->($msg, $source_serv, $ch_maybe, $target, $modes); 60 | } 61 | 62 | # :69 MODEREP #k 100 :+n 63 | sub in_moderep { 64 | my ($server, $msg, $source_serv, $channel, $target, $mode_str) = @_; 65 | undef $target if $target eq '*'; 66 | 67 | # find a target server maybe. 68 | $target = $pool->lookup_server($target) or return 69 | if defined $target; 70 | 71 | return $handle_moderep->($msg, $source_serv, $channel, $target, $mode_str); 72 | } 73 | 74 | sub out_modereq { 75 | my ($to_server, $source_serv, $ch_maybe, $serv_maybe, $modes_maybe) = @_; 76 | sprintf ':%s MODEREQ %s %s %s', 77 | $source_serv->id, 78 | $ch_maybe ? $ch_maybe->name : '*', 79 | $serv_maybe ? $serv_maybe->id : '*', 80 | length $modes_maybe ? $modes_maybe : '*'; 81 | } 82 | 83 | sub out_moderep { 84 | my ($to_server, $source_serv, $channel, $serv_maybe, $mode_str) = @_; 85 | sprintf ':%s MODEREP %s %s :%s', 86 | $source_serv->id, 87 | $channel->name, 88 | $serv_maybe ? $serv_maybe->id : '*', 89 | $mode_str; 90 | } 91 | 92 | $mod 93 | -------------------------------------------------------------------------------- /modules/Channel/ModeSync.module/ModeSync.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "improves channel mode synchronization", 7 | "name" : "Channel::ModeSync", 8 | "package" : "M::Channel::ModeSync", 9 | "version" : "2.4" 10 | } 11 | -------------------------------------------------------------------------------- /modules/Channel/ModeSync.module/ModeSync.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-Mac-mini.local 4 | # Fri Aug 8 22:43:11 EDT 2014 5 | # ModeSync.pm 6 | # 7 | # @name: 'Channel::ModeSync' 8 | # @package: 'M::Channel::ModeSync' 9 | # @description: 'improves channel mode synchronization' 10 | # 11 | # @author.name: 'Mitchell Cooper' 12 | # @author.website: 'https://github.com/cooper' 13 | # 14 | package M::Channel::ModeSync; 15 | 16 | use warnings; 17 | use strict; 18 | use 5.010; 19 | 20 | use utils qw(broadcast); 21 | 22 | our ($api, $mod, $pool, $me); 23 | 24 | sub init { 25 | $mod->add_companion_submodule('JELP::Base', 'JELP'); 26 | $mod->add_companion_submodule('Base::UserCommands', 'Desync'); 27 | $pool->on(cmodes_changed => \&cmodes_changed, 'modesync'); 28 | return 1; 29 | } 30 | 31 | sub cmodes_changed { 32 | my (undef, $fire, $added, $removed) = @_; 33 | 34 | # first, locally unset the modes which were removed 35 | for my $channel ($pool->channels) { 36 | my $all_modes = modes->new; 37 | for my $name (@$removed) { 38 | 39 | # get all modes of this name 40 | my $modes = $channel->modes_with($name); 41 | next if !@$modes; 42 | 43 | # invert the state 44 | for (0..$#$modes) { 45 | next if $_ % 2; 46 | $modes->[$_] = '-'.$modes->[$_]; 47 | } 48 | 49 | $all_modes->push_modes($modes); 50 | } 51 | 52 | # commit the changes 53 | $channel->do_modes_local( 54 | $me, $all_modes, 55 | 1, # force 56 | undef, # not over protocol 57 | 1, # organize 58 | 1 # allow unloaded modes (see issue #63) 59 | ); 60 | } 61 | 62 | # consider: now that we're done with this mode, we could tell the pool to 63 | # abandon it. currently the temp code stays until overwritten. I wouldn't 64 | # feel good about deleting it here because then it would still remain when 65 | # ModeSync isn't loaded. it's not that big of a deal regardless. 66 | 67 | # send out a MODEREQ targeting all channels and all servers affected by 68 | # the new mode letters. 69 | my $letters = ''; 70 | foreach (@$added) { 71 | my ($name, $letter, $type) = @$_; 72 | $letters .= $letter; 73 | } 74 | 75 | broadcast(modereq => $me, undef, undef, $letters) 76 | if length $letters; 77 | } 78 | 79 | # handle_modereq() 80 | # 81 | # $source_serv server object source 82 | # 83 | # $ch_maybe channel object or undef if it is for all channels 84 | # 85 | # $target server object target or undef if it is network-wide 86 | # 87 | # $modes string of mode letters in the perspective of the source server 88 | # or undef if requesting all modes 89 | # 90 | sub handle_modereq { 91 | my ($msg, $source_serv, $ch_maybe, $target, $modes) = @_; 92 | 93 | # if the modes are missing, a channel must be present. 94 | # this indicates a network-wide sync of all modes on a channel. 95 | return if !$ch_maybe && !defined $modes; 96 | 97 | my @forward_args = (modereq => $source_serv, $ch_maybe, $target, $modes); 98 | 99 | # this is not for me; forward. 100 | if ($target && $target != $me) { 101 | return $msg->forward_to($target, @forward_args); 102 | } 103 | 104 | # Safe point - we will handle this mode request. 105 | my @channels = $ch_maybe ? $ch_maybe : $pool->channels; 106 | foreach my $channel (@channels) { 107 | 108 | # map mode letters in the perspective of $source_serv to names. 109 | my @mode_names = defined $modes ? 110 | map $source_serv->cmode_name($_), split //, $modes : 111 | 'inf'; # inf indicates all modes 112 | 113 | # construct a mode string in the perspective of $me with these modes. 114 | my (undef, $mode_str) = $channel->mode_string_with($me, @mode_names); 115 | next if !length $mode_str; 116 | 117 | $source_serv->forward(moderep => 118 | $me, $channel, 119 | defined $modes ? $source_serv : undef, # reply to the server or * 120 | $mode_str 121 | ); 122 | } 123 | 124 | # unless this was addressed specifically to me, forward. 125 | $msg->broadcast(@forward_args) if !defined $target; 126 | 127 | return 1; 128 | } 129 | 130 | # handle_moderep() 131 | # 132 | # $source_serv server object source 133 | # 134 | # $ch_maybe channel object (required) 135 | # 136 | # $target server object target or undef if it is network-wide 137 | # 138 | # $mode_str mode string in the perspective of $source_serv, including 139 | # any possible parameters 140 | # 141 | sub handle_moderep { 142 | my ($msg, $source_serv, $channel, $target, $mode_str) = @_; 143 | 144 | my @forward_args = (moderep => $source_serv, $channel, $target, $mode_str); 145 | 146 | # this is not for me; forward. 147 | if ($target && $target != $me) { 148 | return $msg->forward_to($target, @forward_args); 149 | } 150 | 151 | # Safe point - we will handle this mode reply. 152 | 153 | # store the modes before any changes and the incoming modes. 154 | my $old_modes = $channel->all_modes; 155 | my $new_modes = modes->new_from_string($me, $mode_str, 1); 156 | 157 | # determine the difference between the old modes and the new ones. 158 | my $changes = modes::difference($old_modes, $new_modes, 1); 159 | 160 | # do the modes. 161 | # ($source, $modes, $force, $organize) 162 | $channel->do_modes_local($source_serv, $changes, 1, 1); 163 | 164 | # unless this was addressed specifically to me, forward. 165 | $msg->broadcast(@forward_args) if !defined $target; 166 | 167 | return 1; 168 | } 169 | 170 | $mod 171 | -------------------------------------------------------------------------------- /modules/Channel/Mute.module/Mute.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes", 9 | "UserNumerics" 10 | ] 11 | }, 12 | "description" : "adds channel mute ban", 13 | "name" : "Channel::Mute", 14 | "package" : "M::Channel::Mute", 15 | "version" : "1.5" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Channel/Mute.module/Mute.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-MacBook-Pro.local 4 | # Thu Oct 16 16:15:38 EDT 2014 5 | # Mute.pm 6 | # 7 | # @name: 'Channel::Mute' 8 | # @package: 'M::Channel::Mute' 9 | # @description: 'adds channel mute ban' 10 | # 11 | # @depends.bases+ 'ChannelModes', 'UserNumerics' 12 | # 13 | # @author.name: 'Mitchell Cooper' 14 | # @author.website: 'https://github.com/cooper' 15 | # 16 | package M::Channel::Mute; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | our ($api, $mod, $pool); 23 | 24 | # numerics 25 | our %user_numerics = ( 26 | RPL_QUIETLIST => [ 728, '%s %s %s %s %d' ], 27 | RPL_ENDOFQUIETLIST => [ 729, '%s %s :End of channel mute list' ] 28 | ); 29 | 30 | # channel mode block 31 | our %channel_modes = ( 32 | mute => { 33 | type => 'banlike', 34 | list => 'mute', # name of the list mode 35 | reply => 'quiet', # reply numerics to send 36 | show_mode => 1 # show the mode letter in replies 37 | } 38 | ); 39 | 40 | sub init { 41 | 42 | # message blocking event - muted and no voice? 43 | $pool->on('user.can_message_channel' => \&on_user_can_message_channel, 44 | name => 'stop.muted.users', 45 | with_eo => 1, 46 | priority => 10 47 | ); 48 | 49 | return 1; 50 | } 51 | 52 | sub on_user_can_message_channel { 53 | my ($user, $event, $channel, $message_ref, $type) = @_; 54 | 55 | # has voice. 56 | return if $channel->user_get_highest_level($user) >= $channel::LEVEL_SPEAK_MOD; 57 | 58 | # not muted. 59 | return unless $channel->list_matches('mute', $user); 60 | 61 | # has an exception. 62 | return if $channel->list_matches('except', $user); 63 | 64 | $event->{error_reply} = 65 | [ ERR_CANNOTSENDTOCHAN => $channel->name, "You're muted" ]; 66 | $event->stop('muted'); 67 | } 68 | 69 | $mod 70 | -------------------------------------------------------------------------------- /modules/Channel/NoColor.module/NoColor.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Matt Barksdale", 4 | "website" : "https://github.com/mattwb65" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes" 9 | ] 10 | }, 11 | "description" : "Adds mode to strip colors from channel messages", 12 | "name" : "Channel::NoColor", 13 | "package" : "M::Channel::NoColor", 14 | "version" : "0.6" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Channel/NoColor.module/NoColor.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, matthew 2 | # 3 | # Created on MacBook-Pro 4 | # Fri Jul 29 20:57:09 EDT 2016 5 | # NoColor.pm 6 | # 7 | # @name: 'Channel::NoColor' 8 | # @package: 'M::Channel::NoColor' 9 | # @description: 'Adds mode to strip colors from channel messages' 10 | # 11 | # @depends.bases+ 'ChannelModes' 12 | # 13 | # @author.name: 'Matt Barksdale' 14 | # @author.website: 'https://github.com/mattwb65' 15 | # 16 | package M::Channel::NoColor; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | our ($api, $mod, $pool); 23 | 24 | # channel modes 25 | our %channel_modes = ( 26 | strip_colors => { type => 'normal' } 27 | ); 28 | 29 | 30 | sub init { 31 | # Hook on to the can_message_channel event to strip colors. 32 | $pool->on('user.can_message_channel' => 33 | \&on_user_can_message_channel, 'strip.colors'); 34 | return 1; 35 | } 36 | 37 | sub on_user_can_message_channel { 38 | my ($user, $event, $channel, $message) = @_; 39 | # No need to strip color if the channel isn't +c 40 | return unless $channel->is_mode('strip_colors'); 41 | # Borrowed from IRC::Utils 42 | $$message =~ s/\x03(?:,\d{1,2}|\d{1,2}(?:,\d{1,2})?)?//g; # mIRC 43 | $$message =~ s/\x04[0-9a-fA-F]{0,6}//ig; # RGB 44 | $$message =~ s/\x1B\[.*?[\x00-\x1F\x40-\x7E]//g; # ECMA-48 45 | $$message =~ s/[\x02\x1f\x16\x1d\x11\x06]//g; # Formatting 46 | $$message =~ s/\x0f//g; # Cancellation 47 | } 48 | 49 | $mod 50 | -------------------------------------------------------------------------------- /modules/Channel/OpModerate.module/OpModerate.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes" 9 | ] 10 | }, 11 | "description" : "adds a channel mode to send blocked messages to ops", 12 | "name" : "Channel::OpModerate", 13 | "package" : "M::Channel::OpModerate", 14 | "version" : "1.2" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Channel/OpModerate.module/OpModerate.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # @name: 'Channel::OpModerate' 4 | # @package: 'M::Channel::OpModerate' 5 | # @description: 'adds a channel mode to send blocked messages to ops' 6 | # 7 | # @depends.bases+ 'ChannelModes' 8 | # 9 | # @author.name: 'Mitchell Cooper' 10 | # @author.website: 'https://github.com/cooper' 11 | # 12 | package M::Channel::OpModerate; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | our ($api, $mod, $pool); 19 | 20 | our %channel_modes = ( 21 | op_moderated => { type => 'normal' } 22 | ); 23 | 24 | sub init { 25 | 26 | # catch blocked messages 27 | $pool->on('user.cant_message_channel' => \&message_blocked, 28 | name => 'op.moderate', 29 | with_eo => 1 30 | ); 31 | 32 | return 1; 33 | } 34 | 35 | sub message_blocked { 36 | my ($user, $event, $channel, $message, $can_fire, $lccommand) = @_; 37 | 38 | # we only care if the user is local and the channel is +z 39 | return unless $channel->is_mode('op_moderated'); 40 | return unless $user->is_local; 41 | 42 | $channel->do_privmsgnotice( 43 | $lccommand, # command 44 | $user, # source 45 | $message, # text 46 | force => 1, # force it! we know it got blocked once 47 | op_moderated => 1 # send to ops only 48 | ); 49 | 50 | # stopping the event tells can_message callbacks NOT to send error 51 | # numerics to the user. 52 | $event->stop; 53 | } 54 | 55 | $mod 56 | -------------------------------------------------------------------------------- /modules/Channel/OperOnly.module/OperOnly.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Matthew Barksdale", 4 | "website" : "https://github.com/mattwb65" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes", 9 | "UserNumerics" 10 | ] 11 | }, 12 | "description" : "adds oper only mode", 13 | "name" : "Channel::OperOnly", 14 | "package" : "M::Channel::OperOnly", 15 | "version" : "2.1" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Channel/OperOnly.module/OperOnly.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014, matthew 2 | # 3 | # Created on mattbook 4 | # Wed Oct 15 15:48:55 EDT 2014 5 | # OperOnly.pm 6 | # 7 | # @name: 'Channel::OperOnly' 8 | # @package: 'M::Channel::OperOnly' 9 | # @description: 'adds oper only mode' 10 | # 11 | # @depends.bases+ 'ChannelModes', 'UserNumerics' 12 | # 13 | # @author.name: 'Matthew Barksdale' 14 | # @author.website: 'https://github.com/mattwb65' 15 | # 16 | package M::Channel::OperOnly; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | our ($api, $mod, $pool); 23 | 24 | # numerics 25 | our %user_numerics = ( 26 | ERR_OPERONLY => [ 520, '%s :Channel is IRC Operator only' ] 27 | ); 28 | 29 | # channel mode block 30 | our %channel_modes = ( 31 | oper_only => { code => \&cmode_oper_only } 32 | ); 33 | 34 | sub init { 35 | 36 | # Hook on the can_join event to prevent joining a channel that is oper only 37 | $pool->on('user.can_join' => \&on_user_can_join, 'is.oper.only'); 38 | 39 | return 1; 40 | } 41 | 42 | # can +O be set? 43 | sub cmode_oper_only { 44 | my ($channel, $mode) = @_; 45 | $mode->{has_basic_status} or return; 46 | 47 | # servers can always set +O 48 | return 1 if !$mode->{source}->isa('user'); 49 | 50 | # a user only can if he's an IRC cop 51 | return $mode->{source}->is_mode('ircop'); 52 | 53 | } 54 | 55 | # can a user join the channel? 56 | sub on_user_can_join { 57 | my ($user, $event, $channel) = @_; 58 | 59 | # we're not concerned with a -O channel 60 | return unless $channel->is_mode('oper_only'); 61 | 62 | # user must be an IRC cop 63 | return if $user->is_mode('ircop'); 64 | 65 | $event->{error_reply} = 66 | [ ERR_OPERONLY => $channel->name ]; 67 | $event->stop('channel_oper_only'); 68 | } 69 | 70 | 71 | 72 | $mod 73 | -------------------------------------------------------------------------------- /modules/Channel/Permanent.module/Permanent.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes" 9 | ] 10 | }, 11 | "description" : "adds permanent channel support", 12 | "name" : "Channel::Permanent", 13 | "package" : "M::Channel::Permanent", 14 | "version" : "0.7" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Channel/Permanent.module/Permanent.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # @name: 'Channel::Permanent' 4 | # @package: 'M::Channel::Permanent' 5 | # @description: 'adds permanent channel support' 6 | # 7 | # @depends.bases+ 'ChannelModes' 8 | # 9 | # @author.name: 'Mitchell Cooper' 10 | # @author.website: 'https://github.com/cooper' 11 | # 12 | package M::Channel::Permanent; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | our ($api, $mod, $pool); 19 | 20 | our %channel_modes = ( 21 | permanent => { code => \&cmode_permanent } 22 | ); 23 | 24 | sub init { 25 | $pool->on('channel.can_destroy' => \&on_can_destroy, 'channel.permanent'); 26 | } 27 | 28 | # prevent non-opers from (un)setting the mode 29 | sub cmode_permanent { 30 | my ($channel, $mode) = @_; 31 | $mode->{has_basic_status} or return; 32 | 33 | # if not force, user must have set_permanent 34 | my $user = $mode->{source}; 35 | undef $user if !$user || !$user->isa('user'); 36 | if (!$mode->{force} && $user && !$user->has_flag('set_permanent')) { 37 | return; 38 | } 39 | 40 | return 1; 41 | } 42 | 43 | # stop channel destruction if permanent 44 | sub on_can_destroy { 45 | my ($channel, $event) = @_; 46 | $event->stop('permanent') if $channel->is_mode('permanent'); 47 | } 48 | 49 | $mod 50 | -------------------------------------------------------------------------------- /modules/Channel/RegisteredOnly.module/RegisteredOnly.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Matt Barksdale", 4 | "website" : "https://github.com/mattwb65" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes", 9 | "UserNumerics" 10 | ] 11 | }, 12 | "description" : "Adds mode to allow only registered users to join", 13 | "name" : "Channel::RegisteredOnly", 14 | "package" : "M::Channel::RegisteredOnly", 15 | "version" : "0.8" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Channel/RegisteredOnly.module/RegisteredOnly.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, matthew 2 | # 3 | # Created on MacBook-Pro 4 | # Fri Jul 29 20:57:09 EDT 2016 5 | # RegisteredOnly.pm 6 | # 7 | # @name: 'Channel::RegisteredOnly' 8 | # @package: 'M::Channel::RegisteredOnly' 9 | # @description: 'Adds mode to allow only registered users to join' 10 | # 11 | # @depends.bases+ 'ChannelModes', 'UserNumerics' 12 | # 13 | # @author.name: 'Matt Barksdale' 14 | # @author.website: 'https://github.com/mattwb65' 15 | # 16 | package M::Channel::RegisteredOnly; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | our ($api, $mod, $pool); 23 | 24 | # numerics 25 | our %user_numerics = ( 26 | ERR_NEEDREGGEDNICK => [ 477, '%s :Cannot join channel - you need to be identified with services'] 27 | ); 28 | 29 | # channel modes 30 | our %channel_modes = ( 31 | reg_only => { type => 'normal' } 32 | ); 33 | 34 | 35 | sub init { 36 | # Hook on to the can_join event to prevent joining a channel that is registered users only. 37 | $pool->on('user.can_join' => \&on_user_can_join, 'is.registered.user'); 38 | return 1; 39 | } 40 | 41 | sub on_user_can_join { 42 | my ($user, $event, $channel) = @_; 43 | # A user can join a channel that isn't +r 44 | return unless $channel->is_mode('reg_only'); 45 | # User has invite 46 | return if $channel->user_has_invite($user); 47 | # User must be registered otherwise 48 | return if exists $user->{account}; 49 | # Let them know they can't join if they're not registered 50 | $event->{error_reply} = [ ERR_NEEDREGGEDNICK => $channel->name ]; 51 | $event->stop('channel_reg_only'); 52 | } 53 | 54 | $mod 55 | -------------------------------------------------------------------------------- /modules/Channel/SSLOnly.module/SSLOnly.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Matt Barksdale", 4 | "website" : "https://github.com/mattwb65" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes" 9 | ] 10 | }, 11 | "description" : "Adds mode to allow only ssl users to join", 12 | "name" : "Channel::SSLOnly", 13 | "package" : "M::Channel::SSLOnly", 14 | "version" : "0.5" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Channel/SSLOnly.module/SSLOnly.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, matthew 2 | # 3 | # Created on MacBook-Pro 4 | # Fri Jul 29 20:57:09 EDT 2016 5 | # SSLOnly.pm 6 | # 7 | # @name: 'Channel::SSLOnly' 8 | # @package: 'M::Channel::SSLOnly' 9 | # @description: 'Adds mode to allow only ssl users to join' 10 | # 11 | # @depends.bases+ 'ChannelModes' 12 | # 13 | # @author.name: 'Matt Barksdale' 14 | # @author.website: 'https://github.com/mattwb65' 15 | # 16 | package M::Channel::SSLOnly; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | our ($api, $mod, $pool); 23 | 24 | 25 | # channel modes 26 | our %channel_modes = ( 27 | ssl_only => { type => 'normal' } 28 | ); 29 | 30 | 31 | sub init { 32 | # Hook on to the can_join event to prevent joining a channel that is ssl users only. 33 | $pool->on('user.can_join' => \&on_user_can_join, 'is.ssl.user'); 34 | return 1; 35 | } 36 | 37 | sub on_user_can_join { 38 | my ($user, $event, $channel) = @_; 39 | # A user can join a channel that isn't +S 40 | return unless $channel->is_mode('ssl_only'); 41 | # User must be connected via ssl otherwise 42 | return if $user->{ssl}; 43 | # Let them know they can't join if they're not on ssl 44 | $user->server_notice("Only users using SSL can join this channel!"); 45 | $event->stop('channel_ssl_only'); 46 | } 47 | 48 | $mod 49 | -------------------------------------------------------------------------------- /modules/Channel/Secret.module/Secret.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Matthew Barksdale", 4 | "website" : "https://github.com/mattwb65" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes" 9 | ] 10 | }, 11 | "description" : "allows a channel to be marked secret or private", 12 | "name" : "Channel::Secret", 13 | "package" : "M::Channel::Secret", 14 | "version" : "2.9" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Channel/Secret.module/Secret.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014, matthew 2 | # 3 | # Created on mattbook 4 | # Wed Jul 2 02:28:01 EDT 2014 5 | # Secret.pm 6 | # 7 | # @name: 'Channel::Secret' 8 | # @package: 'M::Channel::Secret' 9 | # @description: 'allows a channel to be marked secret or private' 10 | # 11 | # @depends.bases+ 'ChannelModes' 12 | # 13 | # @author.name: 'Matthew Barksdale' 14 | # @author.website: 'https://github.com/mattwb65' 15 | # 16 | package M::Channel::Secret; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | our ($api, $mod, $pool); 23 | 24 | # channel mode blocks 25 | our %channel_modes = ( 26 | secret => { type => 'normal' }, 27 | private => { type => 'normal' } 28 | ); 29 | 30 | sub init { 31 | 32 | # Hook on the show_in_list, show_in_whois, and show_in_names events to 33 | # prevent secret or private channels from showing 34 | $pool->on('channel.show_in_list' => 35 | \&show_in_list, 36 | 'channel.secret.show_in_list' 37 | ); 38 | $pool->on('channel.show_in_whois' => 39 | \&show_in_whois, 40 | 'channel.secret.show_in_whois' 41 | ); 42 | 43 | # names_character allows us to change the "=" in NAMES to "@" or "*" 44 | # for secret and private channels respectively 45 | $pool->on('channel.names_character' => 46 | \&names_character, 47 | 'channel.secret.names_character' 48 | ); 49 | 50 | # prevent users from knocking on private channels. 51 | $pool->on('user.can_knock' => 52 | \&on_user_can_knock, 53 | 'private.channel' 54 | ); 55 | 56 | return 1; 57 | } 58 | 59 | my $SHOW_IT; 60 | 61 | sub show_in_list { 62 | my ($channel, $event, $user) = @_; 63 | 64 | # if it's neither secret nor private, we are not concerned with this. 65 | return $SHOW_IT 66 | if !$channel->is_mode('secret') && !$channel->is_mode('private'); 67 | 68 | # if the user asking has super powers, show it. 69 | return $SHOW_IT 70 | if $user->has_flag('see_secret'); 71 | 72 | # if it is secret or private, but this guy's in there, show it. 73 | return $SHOW_IT 74 | if $channel->has_user($user); 75 | 76 | $event->stop; 77 | } 78 | 79 | sub show_in_whois { 80 | my ($channel, $event, $quser, $ruser) = @_; 81 | 82 | # $quser = the one being queried 83 | # $ruser = the one requesting the info 84 | 85 | # if it's not secret, we are not concerned with this 86 | # because private channels show up in WHOIS. 87 | return $SHOW_IT 88 | unless $channel->is_mode('secret'); 89 | 90 | # if the user asking has super powers, show it. 91 | return $SHOW_IT 92 | if $ruser->has_flag('see_secret'); 93 | 94 | # if it is secret, but the one requesting it is in there, show it. 95 | return $SHOW_IT 96 | if $channel->has_user($ruser); 97 | 98 | $event->stop; 99 | } 100 | 101 | sub show_in_names { 102 | my ($channel, $event, $quser, $ruser) = @_; 103 | 104 | # $quser = the one being queried 105 | # $ruser = the one requesting the info 106 | 107 | # if it's neither secret nor private, we are not concerned with this. 108 | return $SHOW_IT 109 | if !$channel->is_mode('secret') && !$channel->is_mode('private'); 110 | 111 | # if it is secret or private, but this guy's in there, show it. 112 | return $SHOW_IT 113 | if $channel->has_user($ruser); 114 | 115 | $event->stop; 116 | } 117 | 118 | # override the character in NAMES 119 | sub names_character { 120 | my ($channel, $event, $c) = @_; 121 | # $c is a string reference with the current character 122 | $$c = "*" if $channel->is_mode('private'); 123 | $$c = "@" if $channel->is_mode('secret'); # more important than private 124 | } 125 | 126 | sub on_user_can_knock { 127 | my ($user, $event, $channel) = @_; 128 | return unless $channel->is_mode('private'); 129 | $event->{error_reply} = 130 | [ ERR_CANNOTSENDTOCHAN => $channel->name, 'Channel is private' ]; 131 | $event->stop('banned'); 132 | } 133 | 134 | $mod 135 | -------------------------------------------------------------------------------- /modules/Channel/TopicAdditions.module/TopicAdditions.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "Core::UserCommands" 9 | ] 10 | }, 11 | "description" : "additional topic management commands", 12 | "name" : "Channel::TopicAdditions", 13 | "package" : "M::Channel::TopicAdditions", 14 | "version" : "1" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Channel/TopicAdditions.module/TopicAdditions.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-MacBook-Pro.local 4 | # Tue Dec 30 21:42:38 EST 2014 5 | # TopicAdditions.pm 6 | # 7 | # @name: 'Channel::TopicAdditions' 8 | # @package: 'M::Channel::TopicAdditions' 9 | # @description: 'additional topic management commands' 10 | # 11 | # @depends.modules+ 'Core::UserCommands' 12 | # 13 | # @author.name: 'Mitchell Cooper' 14 | # @author.website: 'https://github.com/cooper' 15 | # 16 | package M::Channel::TopicAdditions; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | our ($api, $mod, $pool); 23 | 24 | our %user_commands = ( 25 | TOPICPREPEND => { 26 | desc => 'add a phrase to the beginning of the topic', 27 | params => 'channel :', 28 | code => \&cmd_prepend 29 | }, 30 | TOPICAPPEND => { 31 | desc => 'add a phrase to the end of a topic', 32 | params => 'channel :', 33 | code => \&cmd_append 34 | } 35 | ); 36 | 37 | sub cmd_prepend { 38 | my ($user, $event, $channel, $new) = @_; 39 | $new = "$new | $$channel{topic}{topic}" if $channel->{topic}; 40 | return $user->handle("TOPIC $$channel{name} :$new"); 41 | } 42 | 43 | sub cmd_append { 44 | my ($user, $event, $channel, $new) = @_; 45 | $new = "$$channel{topic}{topic} | $new" if $channel->{topic}; 46 | return $user->handle("TOPIC $$channel{name} :$new"); 47 | } 48 | 49 | $mod 50 | -------------------------------------------------------------------------------- /modules/Cloak.module/Charybdis.module/Charybdis.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "hostname cloaking compatible with charybdis", 7 | "name" : "Cloak::Charybdis", 8 | "package" : "M::Cloak::Charybdis", 9 | "version" : "4.4" 10 | } 11 | -------------------------------------------------------------------------------- /modules/Cloak.module/Charybdis.module/Charybdis.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Based on ip_cloaking_4.0.c from charybdis: 4 | # Written originally by nenolod, altered to use FNV by Elizabeth in 2008 5 | # 6 | # @name: 'Cloak::Charybdis' 7 | # @package: 'M::Cloak::Charybdis' 8 | # @description: 'hostname cloaking compatible with charybdis' 9 | # 10 | # @author.name: "Mitchell Cooper" 11 | # @author.website: "https://github.com/cooper" 12 | # 13 | package M::Cloak::Charybdis; 14 | 15 | use warnings; 16 | use strict; 17 | use 5.010; 18 | 19 | use utils qw(fnv); 20 | 21 | our ($api, $mod, $pool, $me); 22 | 23 | my @ip_char_table = 'g'..'z'; 24 | my @b26_alphabet = 'a'..'z'; 25 | 26 | sub cloak { 27 | my ($host, $user) = @_; 28 | return do_host_cloak_ip($host) if $host eq $user->{ip}; 29 | return do_host_cloak_host($host); 30 | } 31 | 32 | sub do_host_cloak_ip { 33 | my $in = shift; 34 | my $accum = fnv($in); 35 | my ($sepcount, $totalcount, $ipv6) = (0, 0); 36 | 37 | # ipv6, count colons 38 | if (index($in, ':') != -1) { 39 | $ipv6++; 40 | $totalcount = () = $in =~ /:/g 41 | } 42 | 43 | # has to be ipv4, then 44 | elsif (index($in, '.') == -1) { 45 | return; 46 | } 47 | 48 | my ($i, $out) = (-1, $in); 49 | for my $char (split //, $out) { $i++; 50 | 51 | if ($char eq ':' || $char eq '.') { 52 | $sepcount++; 53 | next; 54 | } 55 | 56 | if ($ipv6 && $sepcount < $totalcount / 2) { 57 | next; 58 | } 59 | 60 | if (!$ipv6 && $sepcount < 2) { 61 | next; 62 | } 63 | 64 | substr($out, $i, 1) = $ip_char_table[ (ord($char) + $accum) % 20 ]; 65 | $accum = ($accum << 1) | ($accum >> 31); 66 | $accum &= 0xffffffff; 67 | } 68 | 69 | return $out; 70 | } 71 | 72 | sub do_host_cloak_host { 73 | my $in = shift; 74 | my $accum = fnv($in); 75 | 76 | # pass 1: scramble first section of hostname using base26 77 | # alphabet toasted against the FNV hash of the string. 78 | # 79 | # numbers are not changed at this time, only letters. 80 | # 81 | my ($i, $out) = (-1, $in); 82 | for my $char (split //, $out) { $i++; 83 | last if $char eq '.'; 84 | next if $char =~ m/\d/ || $char eq '-'; 85 | 86 | substr($out, $i, 1) = $b26_alphabet[ (ord($char) + $accum) % 26 ]; 87 | 88 | # Rotate one bit to avoid all digits being turned odd or even 89 | $accum = ($accum << 1) | ($accum >> 31); 90 | $accum &= 0xffffffff; 91 | } 92 | 93 | # pass 2: scramble each number in the address 94 | $i = -1; 95 | for my $char (split //, $out) { $i++; 96 | if ($char =~ m/\d/) { 97 | my $c = ord('0') + (ord($char) + $accum) % 10; 98 | substr($out, $i, 1) = chr $c; 99 | } 100 | $accum = ($accum << 1) | ($accum >> 31); 101 | $accum &= 0xffffffff; 102 | } 103 | 104 | return $out; 105 | } 106 | 107 | $mod 108 | -------------------------------------------------------------------------------- /modules/Cloak.module/Cloak.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserModes" 9 | ] 10 | }, 11 | "description" : "hostname cloaking", 12 | "name" : "Cloak", 13 | "package" : "M::Cloak", 14 | "version" : "1.9" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Cloak.module/Cloak.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # 4 | # @name: 'Cloak' 5 | # @package: 'M::Cloak' 6 | # @description: 'hostname cloaking' 7 | # 8 | # @depends.bases+ 'UserModes' 9 | # 10 | # @author.name: "Mitchell Cooper" 11 | # @author.website: "https://github.com/cooper" 12 | # 13 | package M::Cloak; 14 | 15 | use warnings; 16 | use strict; 17 | use 5.010; 18 | 19 | use utils qw(broadcast); 20 | 21 | our ($api, $mod, $pool, $me); 22 | my $cloak_func; 23 | 24 | sub init { 25 | 26 | # add mode block 27 | $mod->register_user_mode_block( 28 | name => 'cloak', 29 | code => \&umode_cloak 30 | ) or return; 31 | 32 | # for now, only charybdis-style is supported 33 | my $chary = $mod->load_submodule('Charybdis') or return; 34 | $cloak_func = $chary->can('cloak'); 35 | 36 | return 1; 37 | } 38 | 39 | sub umode_cloak { 40 | my ($user, $state) = @_; 41 | return 1 if !$user->is_local; 42 | return enable_cloak($user) if $state; 43 | return disable_cloak($user); 44 | } 45 | 46 | sub enable_cloak { 47 | my $user = shift; 48 | return if length $user->{cloak_enabled}; 49 | 50 | # crypt 51 | my $new_host = $cloak_func->($user->{host}, $user); 52 | 53 | # just an extra check to make sure something's changed 54 | if (!length $new_host || $new_host eq $user->{cloak}) { 55 | return; 56 | } 57 | 58 | # OK, cloaking is enabled. but it may not necessarily be applied. 59 | $user->{cloak_enabled} = $new_host; 60 | 61 | # apply the cloak - but only if the real host is active. 62 | if ($user->{cloak} eq $user->{host}) { 63 | $user->get_mask_changed($user->{ident}, $new_host); 64 | 65 | # tell other servers if the user has been propagated 66 | broadcast(chghost => $me, $user, $new_host) 67 | if $user->{init_complete}; 68 | } 69 | 70 | return 1; 71 | } 72 | 73 | sub disable_cloak { 74 | my $user = shift; 75 | return unless $user->{cloak_enabled}; 76 | 77 | # only reset the real host if the cloak is still current 78 | if ($user->{cloak} eq $user->{cloak_enabled}) { 79 | 80 | # apply the real host 81 | $user->get_mask_changed(@$user{'ident', 'host'}); 82 | 83 | # tell other servers if the user has been propagated 84 | broadcast(chghost => $me, $user, $user->{host}) 85 | if $user->{init_complete}; 86 | 87 | } 88 | 89 | delete $user->{cloak_enabled}; 90 | return 1; 91 | } 92 | 93 | $mod 94 | -------------------------------------------------------------------------------- /modules/Configuration/Set.module/Set.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserCommands" 9 | ], 10 | "modules" : [ 11 | "JELP::Base" 12 | ] 13 | }, 14 | "description" : "set and fetch configuration values across servers", 15 | "name" : "Configuration::Set", 16 | "package" : "M::Configuration::Set", 17 | "version" : "6.3" 18 | } 19 | -------------------------------------------------------------------------------- /modules/Core/ChannelModes.module/ChannelModes.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "ChannelModes", 9 | "Capabilities" 10 | ] 11 | }, 12 | "description" : "the core set of channel modes", 13 | "name" : "Core::ChannelModes", 14 | "package" : "M::Core::ChannelModes", 15 | "version" : "12.58" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Core/ChannelModes.module/ChannelModes.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-16, Mitchell Cooper 2 | # 3 | # @name: "Core::ChannelModes" 4 | # @package: "M::Core::ChannelModes" 5 | # @description: "the core set of channel modes" 6 | # 7 | # @depends.bases+ 'ChannelModes', 'Capabilities' 8 | # 9 | # @author.name: "Mitchell Cooper" 10 | # @author.website: "https://github.com/cooper" 11 | # 12 | package M::Core::ChannelModes; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | our ($api, $mod, $me, $pool); 19 | 20 | our %channel_modes = ( 21 | 22 | # simple modes 23 | no_ext => { type => 'normal' }, 24 | protect_topic => { type => 'normal' }, 25 | moderated => { type => 'normal' }, 26 | 27 | # banlike modes 28 | ban => { 29 | type => 'banlike', 30 | list => 'ban', 31 | reply => 'ban' 32 | }, 33 | except => { 34 | type => 'banlike', 35 | list => 'except', 36 | reply => 'except' 37 | } 38 | 39 | ); 40 | 41 | sub init { 42 | 43 | # register status channel modes. 44 | register_statuses($_) or return foreach 45 | sort { $b <=> $a } keys %ircd::channel_mode_prefixes; 46 | 47 | # add multi-prefix capability. 48 | $mod->register_capability('multi-prefix'); 49 | 50 | # add channel message restrictions. 51 | add_message_restrictions(); 52 | 53 | return 1; 54 | } 55 | 56 | ######################## 57 | # STATUS CHANNEL MODES # 58 | ######################## 59 | 60 | # status modes 61 | sub register_statuses { 62 | my $level = shift; 63 | 64 | # determine the weight needed to set/unset 65 | my ($name, $set_weight) = @{ $ircd::channel_mode_prefixes{$level} }[2, 3]; 66 | $set_weight //= $level; 67 | 68 | # add the mode block 69 | $mod->register_channel_mode_block( name => $name, code => sub { 70 | my ($channel, $mode) = @_; 71 | my $source = $mode->{source}; 72 | my $t_user = $mode->{param}; 73 | 74 | # make sure the user is on the channel. 75 | if (!$channel->has_user($t_user)) { 76 | $source->numeric(ERR_USERNOTINCHANNEL => 77 | $t_user->{nick}, $channel->name 78 | ) if $source->isa('user') && $source->is_local; 79 | return; 80 | } 81 | 82 | # if we're not forcing the change, and the source user is local, 83 | # check that he has the proper permissions. 84 | if (!$mode->{force} && $source->is_local) { 85 | 86 | # check 1: see if he has basic status and that he is not trying 87 | # to set a status with greater weight than his own. 88 | my $check1 = sub { 89 | 90 | # no basic status 91 | return unless $channel->user_has_basic_status($source); 92 | 93 | # the target user has a higher status. 94 | # only check this if $set_weight is not provided. 95 | unless (defined $set_weight) { 96 | return if $channel->user_get_highest_level($source) < 97 | $channel->user_get_highest_level($t_user); 98 | } 99 | 100 | return 1; 101 | }; 102 | 103 | # check 2: see that the source user has at least the specified 104 | # weight which is required to set this mode. 105 | my $check2 = sub { 106 | 107 | # the source's highest status is not enough to set/unset. 108 | my $needed = $set_weight // $level; 109 | return if $channel->user_get_highest_level($source) < $needed; 110 | 111 | return 1; 112 | }; 113 | 114 | # the above test(s) failed 115 | if (!&$check1 || !&$check2) { 116 | $mode->{send_no_privs} = 1; 117 | return; 118 | } 119 | 120 | } 121 | 122 | # add or remove from the list. 123 | $mode->{param} = $t_user; 124 | my $do = $mode->{state} ? 'add_to_list' : 'remove_from_list'; 125 | $channel->$do($name, $t_user); 126 | 127 | return 1; 128 | 129 | }) or return; 130 | 131 | return 1 132 | } 133 | 134 | sub add_message_restrictions { 135 | 136 | # not in channel and no external messages? 137 | $pool->on('user.can_message_channel' => sub { 138 | my ($user, $event, $channel, $message_ref, $type) = @_; 139 | 140 | # not internal only, or user is in channel. 141 | return unless $channel->is_mode('no_ext'); 142 | return if $channel->has_user($user); 143 | 144 | # no external messages. 145 | $event->{error_reply} = 146 | [ ERR_CANNOTSENDTOCHAN => $channel->name, 'No external messages' ]; 147 | $event->stop('no_ext'); 148 | 149 | }, name => 'no.external.messages', with_eo => 1, priority => 30); 150 | 151 | # moderation and no voice? 152 | $pool->on('user.can_message_channel' => sub { 153 | my ($user, $event, $channel, $message_ref, $type) = @_; 154 | 155 | # not moderated, or the user has proper status. 156 | return unless $channel->is_mode('moderated'); 157 | return if $channel->user_get_highest_level($user) 158 | >= $channel::LEVEL_SPEAK_MOD; 159 | 160 | # no external messages. 161 | $event->{error_reply} = 162 | [ ERR_CANNOTSENDTOCHAN => $channel->name, 'Channel is moderated' ]; 163 | $event->stop('moderated'); 164 | 165 | }, name => 'moderated', with_eo => 1, priority => 20); 166 | 167 | # banned and no voice? 168 | $pool->on('user.can_message_channel' => sub { 169 | my ($user, $event, $channel, $message_ref, $type) = @_; 170 | 171 | # not banned, or the user has overriding status. 172 | return if $channel->user_get_highest_level($user) 173 | >= $channel::LEVEL_SPEAK_MOD; 174 | return unless $channel->list_matches('ban', $user); 175 | return if $channel->list_matches('except', $user); 176 | 177 | $event->{error_reply} = 178 | [ ERR_CANNOTSENDTOCHAN => $channel->name, "You're banned" ]; 179 | $event->stop('banned'); 180 | 181 | }, name => 'stop.banned.users', with_eo => 1, priority => 10); 182 | 183 | } 184 | 185 | $mod 186 | -------------------------------------------------------------------------------- /modules/Core/Core.module/Core.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "Core::UserModes", 9 | "Core::UserCommands", 10 | "Core::UserNumerics", 11 | "Core::ChannelModes", 12 | "Core::OperNotices", 13 | "Core::Matchers", 14 | "Core::RegistrationCommands" 15 | ] 16 | }, 17 | "description" : "the core components of the ircd", 18 | "name" : "Core", 19 | "package" : "M::Core", 20 | "version" : "11.63" 21 | } 22 | -------------------------------------------------------------------------------- /modules/Core/Core.module/Core.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-16, Mitchell Cooper 2 | # 3 | # @name: "Core" 4 | # @package: "M::Core" 5 | # @description: "the core components of the ircd" 6 | # 7 | # @depends.modules+ qw(Core::UserModes Core::UserCommands Core::UserNumerics) 8 | # @depends.modules+ qw(Core::ChannelModes Core::OperNotices Core::Matchers) 9 | # @depends.modules+ qw(Core::RegistrationCommands) 10 | # 11 | # @author.name: "Mitchell Cooper" 12 | # @author.website: "https://github.com/cooper" 13 | # 14 | package M::Core; 15 | 16 | use warnings; 17 | use strict; 18 | use 5.010; 19 | 20 | our $mod; 21 | -------------------------------------------------------------------------------- /modules/Core/Matchers.module/Matchers.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "Matchers" 9 | ] 10 | }, 11 | "description" : "the core set of mask matchers", 12 | "name" : "Core::Matchers", 13 | "package" : "M::Core::Matchers", 14 | "version" : "11.63" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Core/Matchers.module/Matchers.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-16, Mitchell Cooper 2 | # 3 | # @name: "Core::Matchers" 4 | # @package: "M::Core::Matchers" 5 | # @description: "the core set of mask matchers" 6 | # 7 | # @depends.bases+ 'Matchers' 8 | # 9 | # @author.name: "Mitchell Cooper" 10 | # @author.website: "https://github.com/cooper" 11 | # 12 | package M::Core::Matchers; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | use utils qw(irc_match); 19 | 20 | our ($api, $mod, $me); 21 | 22 | sub init { 23 | $mod->register_matcher( 24 | name => 'standard', 25 | code => \&standard_matcher 26 | ) or return; 27 | 28 | $mod->register_matcher( 29 | name => 'oper', 30 | code => \&oper_matcher 31 | ) or return; 32 | 33 | return 1; 34 | } 35 | 36 | sub standard_matcher { 37 | my ($event, $user, @list) = @_; 38 | foreach my $mask ($user->full, $user->fullreal, $user->fullip) { 39 | return $event->{matched} = 1 if irc_match($mask, @list); 40 | } 41 | return; 42 | } 43 | 44 | sub oper_matcher { 45 | my ($event, $user, @list) = @_; 46 | return unless $user->is_mode('ircop'); 47 | 48 | foreach my $item (@list) { 49 | 50 | # just check if opered. 51 | return $event->{matched} = 1 if $item eq '$o'; 52 | 53 | # match a specific oper flag. 54 | next unless $item =~ m/^\$o:(.+)/; 55 | return $event->{matched} = 1 if $user->has_flag($1); 56 | 57 | } 58 | 59 | return; 60 | } 61 | 62 | $mod 63 | -------------------------------------------------------------------------------- /modules/Core/OperNotices.module/OperNotices.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "OperNotices" 9 | ] 10 | }, 11 | "description" : "the core set of oper notices", 12 | "name" : "Core::OperNotices", 13 | "package" : "M::Core::OperNotices", 14 | "version" : "13.05" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Core/OperNotices.module/OperNotices.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-16, Mitchell Cooper 2 | # 3 | # @name: "Core::OperNotices" 4 | # @package: "M::Core::OperNotices" 5 | # @description: "the core set of oper notices" 6 | # 7 | # @depends.bases+ 'OperNotices' 8 | # 9 | # @author.name: "Mitchell Cooper" 10 | # @author.website: "https://github.com/cooper" 11 | # 12 | package M::Core::OperNotices; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | our ($api, $mod, $me); 19 | 20 | our %oper_notices = ( 21 | 22 | # connections 23 | new_connection => '%s (%d) {%s}', 24 | connection_terminated => '%s (%s): %s', 25 | connection_invalid => '%s: %s', 26 | 27 | # users 28 | new_user => '%s [%s] {%s}', 29 | user_quit => '%s [%s] from %s (%s)', 30 | user_opered => '%s gained flags on %s: %s', 31 | user_deopered => '%s is no longer an IRC operator', 32 | user_killed => '%s killed by %s (%s)', 33 | user_nick_change => '%s is now known as %s', 34 | user_part_all => '%s parted all channels: %s', 35 | user_mask_change => '%s switched from (%s@%s) to (%s@%s)', 36 | user_identifier_taken => '%s introduced %s with UID %s, which is already taken by %s', 37 | user_saved => '%s was spared in the midst of a nick collision (was %s)', 38 | user_logged_in => '%s is now logged in as %s', 39 | user_logged_out => '%s logged out (was %s)', 40 | user_mode_unknown => 'Attempted to set %s, but this mode is not defined on %s; ignored', 41 | 42 | # channels 43 | channel_join => '%s joined %s', 44 | channel_part => '%s parted %s (%s)', 45 | channel_kick => '%s was kicked from %s by %s (%s)', 46 | channel_mode_unknown => 'Attempted to set %s, but this mode is not defined on %s; ignored', 47 | 48 | # servers 49 | new_server => '%s ircd = %s, proto = %s [%s] parent: %s', 50 | server_closing => 'Received SQUIT from %s; dropping link (%s)', 51 | server_quit => '%s quit from parent %s (%s)', 52 | server_burst => '%s is bursting information', 53 | server_endburst => '%s finished burst, %d seconds elapsed', 54 | connect => '%s issued CONNECT for %s to %s', 55 | connect_attempt => '%s (%s) on port %d (Attempt %d)', 56 | connect_cancel => '%s canceled auto connect for %s', 57 | connect_fail => 'Can\'t connect to %s: %s', 58 | connect_success => 'Connection established to %s', 59 | squit => '%s issued SQUIT for %s from %s', 60 | server_reintroduced => '%s attempted to introduce %s which already exists', 61 | server_identifier_taken => '%s attempted to introduce %s as SID %d, which is already taken by %s', 62 | server_not_responding => '%s has not replied to ping for %d seconds', 63 | server_protocol_warning => '%s %s', 64 | server_protocol_error => '%s %s; dropping link', 65 | 66 | # miscellaneous 67 | oper_message => 'From %s: %s', 68 | perl_warning => '%s', 69 | exception => '%s', 70 | rehash => '%s is rehashing the server', 71 | rehash_fail => 'Configuration error: %s', 72 | rehash_success => 'Server configuration rehashed successfully' 73 | 74 | ); 75 | 76 | $mod 77 | -------------------------------------------------------------------------------- /modules/Core/RegistrationCommands.module/RegistrationCommands.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "RegistrationCommands", 9 | "UserNumerics" 10 | ] 11 | }, 12 | "description" : "the core set of pre-registration commands", 13 | "name" : "Core::RegistrationCommands", 14 | "package" : "M::Core::RegistrationCommands", 15 | "version" : "10.1" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Core/UserCommands.module/UserCommands.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserCommands", 9 | "Capabilities" 10 | ] 11 | }, 12 | "description" : "the core set of user commands", 13 | "name" : "Core::UserCommands", 14 | "package" : "M::Core::UserCommands", 15 | "version" : "22.4" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Core/UserModes.module/UserModes.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserModes" 9 | ] 10 | }, 11 | "description" : "the core set of user modes", 12 | "name" : "Core::UserModes", 13 | "package" : "M::Core::UserModes", 14 | "version" : "11.73" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Core/UserModes.module/UserModes.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-16, Mitchell Cooper 2 | # 3 | # @name: "Core::UserModes" 4 | # @package: "M::Core::UserModes" 5 | # @description: "the core set of user modes" 6 | # 7 | # @depends.bases+ 'UserModes' 8 | # 9 | # @author.name: "Mitchell Cooper" 10 | # @author.website: "https://github.com/cooper" 11 | # 12 | package M::Core::UserModes; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | use utils qw(notice); 19 | 20 | our ($api, $mod, $me); 21 | 22 | my %umodes = ( 23 | 24 | # settable by any user 25 | invisible => \&umode_normal, 26 | wallops => \&umode_normal, 27 | deaf => \&umode_normal, 28 | bot => \&umode_normal, 29 | 30 | # requires the power of a greater being 31 | admin => \&umode_never, 32 | ssl => \&umode_never, 33 | service => \&umode_never, 34 | registered => \&umode_never, 35 | 36 | # special rules 37 | ircop => \&umode_ircop 38 | 39 | ); 40 | 41 | sub init { 42 | $mod->register_user_mode_block( 43 | name => $_, 44 | code => $umodes{$_} 45 | ) || return foreach keys %umodes; 46 | undef %umodes; 47 | return 1; 48 | } 49 | 50 | ############## 51 | # USER MODES # 52 | ############## 53 | 54 | sub umode_ircop { 55 | my ($user, $state) = @_; 56 | return if $state; # /never/ allow setting ircop 57 | 58 | # but always allow unsetting 59 | notice(user_deopered => $user->notice_info); 60 | $user->clear_flags; 61 | delete $user->{oper}; 62 | 63 | return 1; 64 | } 65 | 66 | # SSL can never be set or unset without force. 67 | # network service can never be set or unset without force. 68 | # etc. 69 | sub umode_never { 70 | return; 71 | } 72 | 73 | sub umode_normal { 74 | return 1; 75 | } 76 | 77 | $mod 78 | -------------------------------------------------------------------------------- /modules/Core/UserNumerics.module/UserNumerics.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserNumerics" 9 | ] 10 | }, 11 | "description" : "the core set of user numerics", 12 | "name" : "Core::UserNumerics", 13 | "package" : "M::Core::UserNumerics", 14 | "version" : "13.31" 15 | } 16 | -------------------------------------------------------------------------------- /modules/DNSBL.module/DNSBL.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Matthew Barksdale", 4 | "website" : "https://github.com/mattwb65" 5 | }, 6 | "description" : "Adds DNSBL checking on new connections", 7 | "name" : "DNSBL", 8 | "package" : "M::DNSBL", 9 | "version" : "1.9" 10 | } 11 | -------------------------------------------------------------------------------- /modules/DNSBL.module/DNSBL.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Matthew Barksdale 2 | # 3 | # Created on mattmini 4 | # Wed May 13 17:21:41 EDT 2015 5 | # DNSBL.pm 6 | # 7 | # @name: 'DNSBL' 8 | # @package: 'M::DNSBL' 9 | # @description: 'Adds DNSBL checking on new connections' 10 | # 11 | # @author.name: 'Matthew Barksdale' 12 | # @author.website: 'https://github.com/mattwb65' 13 | # 14 | package M::DNSBL; 15 | 16 | use warnings; 17 | use strict; 18 | use 5.010; 19 | 20 | use Socket qw(AF_INET AF_INET6 SOCK_STREAM inet_pton); 21 | use List::Util qw(first); 22 | use utils qw(looks_like_ipv6 string_to_seconds ref_to_list); 23 | 24 | our ($api, $mod, $pool, $conf, %cache); 25 | 26 | sub init { 27 | $pool->on('connection.new', \&connection_new, 'check.dnsbl'); 28 | } 29 | 30 | # on new connection, check each applicable DNSBL 31 | sub connection_new { 32 | my ($conn, $event) = @_; 33 | return if $conn->{goodbye}; 34 | my @lists = $conf->names_of_block('dnsbl') or return; 35 | 36 | my ($expanded, $ipv6); 37 | my $ip = $conn->ip; 38 | 39 | # check if the IP has already been cached 40 | if (my $cached = $cache{$ip}) { 41 | my ($expire_time, $list_name, $reason) = @$cached; 42 | return dnsbl_bad($conn, $list_name, $reason) 43 | if time < $expire_time; 44 | delete $cache{$ip}; 45 | } 46 | 47 | # IPv6 48 | if ($ipv6 = looks_like_ipv6($ip)) { 49 | my $addr = inet_pton(AF_INET6, $ip); 50 | $expanded = join '.', reverse map { split // } unpack('H4' x 8, $addr); 51 | } 52 | 53 | # IPv4 54 | else { 55 | my $addr = inet_pton(AF_INET, $ip); 56 | $expanded = join '.', reverse unpack('C' x 4, $addr); 57 | } 58 | 59 | check_conn_against_list($conn, $expanded, $ipv6, $_) for @lists; 60 | } 61 | 62 | sub check_conn_against_list { 63 | my ($conn, $expanded, $ipv6, $list_name) = @_; 64 | my %blacklist = $conf->hash_of_block([ 'dnsbl', $list_name ]); 65 | my $full_host = "$expanded.$blacklist{host}"; 66 | 67 | # stop here if the DNSBL does not support the address family 68 | my $n = $ipv6 ? 6 : 4; 69 | return if !$blacklist{"ipv$n"}; 70 | 71 | # postpone registration until this is done 72 | $conn->reg_wait("dnsbl_$list_name"); 73 | 74 | # do the initial request 75 | my $f = $::loop->resolver->getaddrinfo( 76 | host => $full_host, 77 | service => '', 78 | socktype => SOCK_STREAM, 79 | timeout => $blacklist{timeout} || 3 80 | ); 81 | 82 | $conn->adopt_future("dnsbl1_$list_name" => $f); 83 | $f->on_done(sub { got_reply1($conn, $list_name, @_) }); 84 | $f->on_fail(sub { dnsbl_ok($conn, $list_name) }); 85 | } 86 | 87 | # got first reply 88 | sub got_reply1 { 89 | my ($conn, $list_name, $addr) = @_; 90 | 91 | # do the second request 92 | my $f = $::loop->resolver->getnameinfo( 93 | addr => $addr->{addr}, 94 | numerichost => 1 95 | ); 96 | 97 | $conn->adopt_future("dnsbl2_$list_name" => $f); 98 | $f->on_done(sub { got_reply2($conn, $list_name, @_) }); 99 | $f->on_fail(sub { dnsbl_ok($conn, $list_name) }); 100 | } 101 | 102 | # got second reply 103 | sub got_reply2 { 104 | my ($conn, $list_name, $ip) = @_; 105 | my %blacklist = $conf->hash_of_block([ 'dnsbl', $list_name ]); 106 | 107 | # extract the last portion of the IP address 108 | my $response = (unpack('C' x 4, $ip))[-1]; 109 | my @matches = ref_to_list($blacklist{matches}); 110 | 111 | # if no responses are specified, any response works. 112 | # otherwise, see if any of the provided responses are the one we got. 113 | if (!@matches || first { $_ == $response } @matches) { 114 | return dnsbl_bad( 115 | $conn, $list_name, 116 | $blacklist{reason}, $blacklist{duration} 117 | ); 118 | } 119 | 120 | dnsbl_ok($conn); 121 | } 122 | 123 | # called when the connection is good-to-go. 124 | sub dnsbl_ok { 125 | my ($conn, $list_name) = @_; 126 | D("$$conn{ip} is not listed on $list_name"); 127 | finish($conn, $list_name); 128 | } 129 | 130 | # called when the connection is blacklisted. 131 | sub dnsbl_bad { 132 | my ($conn, $list_name, $reason, $duration) = @_; 133 | L("$$conn{ip} is listed on $list_name!"); 134 | 135 | # inject variables in reason 136 | $reason ||= "Your host is listed on $list_name."; 137 | $reason =~ s/%ip/$$conn{ip}/g; 138 | $reason =~ s/%host/$$conn{host}/g; 139 | 140 | # store in cache 141 | if ($duration) { 142 | my $expire_time = time() + string_to_seconds($duration); 143 | $cache{ $conn->ip } = [ $expire_time, $list_name, $reason ]; 144 | } 145 | 146 | # drop the connection 147 | $conn->done($reason); 148 | 149 | finish($conn, $list_name); 150 | } 151 | 152 | # called when a DNSBL lookup is complete, regardless of status. 153 | sub finish { 154 | my ($conn, $list_name) = @_; 155 | $conn->abandon_future("dnsbl1_$list_name"); 156 | $conn->abandon_future("dnsbl2_$list_name"); 157 | $conn->reg_continue("dnsbl_$list_name"); 158 | } 159 | 160 | $mod 161 | -------------------------------------------------------------------------------- /modules/Eval.module/Eval.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserCommands" 9 | ] 10 | }, 11 | "description" : "evaluate Perl code", 12 | "name" : "Eval", 13 | "package" : "M::Eval", 14 | "version" : "8.1" 15 | } 16 | -------------------------------------------------------------------------------- /modules/Eval.module/Eval.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-Mac-mini.local 4 | # Thu Aug 7 19:33:03 EDT 2014 5 | # Eval.pm 6 | # 7 | # @name: 'Eval' 8 | # @package: 'M::Eval' 9 | # @description: 'evaluate Perl code' 10 | # 11 | # @depends.bases+ 'UserCommands' 12 | # 13 | # @author.name: 'Mitchell Cooper' 14 | # @author.website: 'https://github.com/cooper' 15 | # 16 | package M::Eval; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | use utils qw(trim); 23 | 24 | our ($api, $mod, $me, $pool, $conf); 25 | 26 | my %allowed; 27 | our $depth = 1; 28 | 29 | our %user_commands = (EVAL => { 30 | code => \&_eval, 31 | desc => 'evaluate Perl code', 32 | params => '* :(opt)' 33 | }); 34 | 35 | # consider: perhaps we should use IO::Async::File to automatically 36 | # reload the evalers file every now and then. or maybe it's not worth it. 37 | sub init { 38 | load(); 39 | return 1; 40 | } 41 | 42 | # load the eval configuration. 43 | sub load { 44 | %allowed = (); 45 | open my $fh, '<', "$::run_dir/etc/evalers.conf" or return; 46 | while (my $oper_name = <$fh>) { 47 | chomp $oper_name; 48 | $allowed{$oper_name} = 1; 49 | } 50 | close $fh; 51 | } 52 | 53 | sub _eval { 54 | my ($user, $event, $ch_name, $code) = @_; 55 | 56 | # unauthorized attempt. 57 | if (!user_authorized($user)) { 58 | $user->numeric(ERR_NOPRIVILEGES => 'eval'); 59 | return; 60 | } 61 | 62 | # find destination and code 63 | my $channel = $pool->lookup_channel($ch_name); 64 | $code = join(' ', $ch_name, $code // '') unless $channel; 65 | return if !length $code; 66 | 67 | # create a function to write to the destination 68 | my $write = sub { 69 | my ($text, $pfx) = @_; 70 | $user or return; 71 | if ($channel) { 72 | $channel->notice_all("$pfx: $text", undef, undef, 1); 73 | return; 74 | } 75 | $user->server_notice(eval => "$pfx: $text"); 76 | }; 77 | 78 | # start eval block. 79 | if ($code eq 'BLOCK') { 80 | $user->{eval_block} = []; 81 | return 1; 82 | } 83 | 84 | # stop eval black. 85 | elsif ($code eq 'END') { 86 | my $block = delete $user->{eval_block} or return; 87 | $code = join "\n", @$block; 88 | } 89 | 90 | # if there is an eval block in the works, use it. 91 | elsif ($user->{eval_block}) { 92 | push @{ $user->{eval_block} }, $code; 93 | return 1; 94 | } 95 | 96 | # evaluate. 97 | my ($error, $result); 98 | my $passed = eval { 99 | 100 | # catch timeouts 101 | local $SIG{ALRM} = sub { die "Timed out\n" }; 102 | 103 | # catch warnings 104 | local $SIG{__WARN__} = sub { 105 | $user && $channel or return; 106 | my @lines = split "\n", shift; my $i = 1; 107 | $write->($_, $#lines ? 'Warning'.$i++ : 'Warning') for @lines; 108 | }; 109 | 110 | # do the eval 111 | alarm 10; 112 | my $r = eval $code; 113 | alarm 0; 114 | 115 | # store the results 116 | $error = $@; 117 | $result = $r; 118 | 119 | !length $error 120 | }; 121 | 122 | # determine the results 123 | my $pfx = 'Result'; 124 | if (!$passed) { 125 | $result = trim($error || $@ || 'unknown'); 126 | $pfx = 'Exception'; 127 | } 128 | $result //= "(undef)"; 129 | my @result = split "\n", "$result\n", -1; 130 | pop @result; 131 | 132 | # send the result. 133 | my $i = 0; 134 | foreach (@result) { 135 | $i++; 136 | my $line = length() ? $_ : '(empty string)'; 137 | my $pfx = $#result ? "$pfx$i" : $pfx; 138 | $write->($line, $pfx); 139 | } 140 | 141 | undef $write; 142 | return 1; 143 | } 144 | 145 | # is a user authorized to eval? 146 | sub user_authorized { 147 | my $user = shift; 148 | return unless $user->is_local && $user->is_mode('ircop') && defined $user->{oper}; 149 | return $allowed{ lc $user->{oper} }; 150 | } 151 | 152 | ############################# 153 | ### convenience functions ### 154 | ############################# 155 | 156 | use utils qw(conf ref_to_list); 157 | 158 | sub Dumper { 159 | ircd::load_or_reload('Data::Dumper', 0) or return; 160 | my $d = Data::Dumper->new([ @_ ]); 161 | return trim($d->Maxdepth($depth)->Dump); 162 | } 163 | sub Dump; *Dump = *Dumper; 164 | 165 | sub nick ($) { $pool->lookup_user (@_) || $pool->lookup_user_nick (@_) } 166 | sub serv ($) { $pool->lookup_server (@_) || $pool->lookup_server_mask (@_) } 167 | sub chan ($) { $pool->lookup_channel (@_) } 168 | 169 | 170 | 171 | $mod 172 | -------------------------------------------------------------------------------- /modules/Git.module/Git.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserCommands", 9 | "OperNotices" 10 | ], 11 | "modules" : [ 12 | "JELP::Base" 13 | ] 14 | }, 15 | "description" : "git repository management", 16 | "name" : "Git", 17 | "package" : "M::Git", 18 | "version" : "4.8" 19 | } 20 | -------------------------------------------------------------------------------- /modules/Grant.module/Grant.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserCommands", 9 | "OperNotices" 10 | ] 11 | }, 12 | "description" : "grant privileges to a user", 13 | "name" : "Grant", 14 | "package" : "M::Grant", 15 | "version" : "3.8" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Grant.module/Grant.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-Mac-mini.local 4 | # Fri Aug 8 15:47:20 EDT 2014 5 | # Grant.pm 6 | # 7 | # @name: 'Grant' 8 | # @package: 'M::Grant' 9 | # @description: 'grant privileges to a user' 10 | # 11 | # @depends.bases+ 'UserCommands', 'OperNotices' 12 | # 13 | # @author.name: 'Mitchell Cooper' 14 | # @author.website: 'https://github.com/cooper' 15 | # 16 | package M::Grant; 17 | 18 | use warnings; 19 | use strict; 20 | use 5.010; 21 | 22 | use utils qw(gnotice simplify broadcast); 23 | 24 | our ($api, $mod, $pool, $me); 25 | 26 | our %user_commands = ( 27 | GRANT => { 28 | code => \&grant, 29 | desc => 'grant privileges to a user', 30 | params => '-oper(grant) user ...' 31 | }, 32 | UNGRANT => { 33 | code => \&ungrant, 34 | desc => "revoke a user's privileges", 35 | params => '-oper(grant) user ...' 36 | } 37 | ); 38 | 39 | our %oper_notices = ( 40 | grant => '%s granted flags to %s: %s', 41 | ungrant => '%s removed flags from %s: %s' 42 | ); 43 | 44 | sub grant { 45 | my ($user, $event, $t_user, @flags) = @_; 46 | @flags = simplify(@flags) or return; 47 | 48 | # notice 49 | gnotice($user, grant => 50 | $user->notice_info, $t_user->notice_info, "@flags"); 51 | 52 | # local user 53 | if ($t_user->is_local) { 54 | 55 | # add the flags 56 | @flags = $user->add_flags(@flags); 57 | $user->update_flags; 58 | 59 | # tell other servers 60 | broadcast(oper => $user, @flags); 61 | } 62 | 63 | # send out FOPER. 64 | else { 65 | $t_user->forward(force_oper => $me, $t_user, @flags); 66 | } 67 | 68 | return 1; 69 | } 70 | 71 | sub ungrant { 72 | my ($user, $event, $t_user, @flags) = @_; 73 | 74 | # removing all flags. 75 | @flags = $t_user->flags if $flags[0] eq '*'; 76 | @flags = simplify(@flags); 77 | 78 | # none removed. 79 | if (!@flags) { 80 | $user->server_notice(grant => "User doesn't have any of those flags"); 81 | return; 82 | } 83 | 84 | # notice 85 | gnotice($user, ungrant => 86 | $user->notice_info, $t_user->notice_info, "@flags"); 87 | 88 | # local user 89 | if ($t_user->is_local) { 90 | 91 | # remove the flags 92 | @flags = $user->remove_flags(@flags); 93 | $user->update_flags; 94 | 95 | # tell other servers 96 | broadcast(oper => $user, map { "-$_" } @flags); 97 | } 98 | 99 | # send out FOPER. 100 | else { 101 | $t_user->forward(foper => 102 | $me, $t_user, 103 | map { "-$_" } @flags 104 | ); 105 | } 106 | 107 | 108 | return 1; 109 | } 110 | 111 | $mod 112 | -------------------------------------------------------------------------------- /modules/Ident.module/Ident.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "resolve idents", 7 | "name" : "Ident", 8 | "package" : "M::Ident", 9 | "version" : "12.9" 10 | } 11 | -------------------------------------------------------------------------------- /modules/JELP/Base.module/Base.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "API::Methods" 9 | ] 10 | }, 11 | "name" : "JELP::Base", 12 | "package" : "M::JELP::Base", 13 | "version" : "16.83" 14 | } 15 | -------------------------------------------------------------------------------- /modules/JELP/Incoming.module/Incoming.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "JELP::Base" 9 | ] 10 | }, 11 | "description" : "basic set of JELP command handlers", 12 | "name" : "JELP::Incoming", 13 | "package" : "M::JELP::Incoming", 14 | "version" : "33.33" 15 | } 16 | -------------------------------------------------------------------------------- /modules/JELP/JELP.module/JELP.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "JELP::Incoming", 9 | "JELP::Outgoing", 10 | "JELP::Registration" 11 | ] 12 | }, 13 | "description" : "juno extensible linking protocol", 14 | "name" : "JELP", 15 | "package" : "M::JELP", 16 | "version" : "8.04" 17 | } 18 | -------------------------------------------------------------------------------- /modules/JELP/JELP.module/JELP.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-16, Mitchell Cooper 2 | # 3 | # @name: "JELP" 4 | # @package: "M::JELP" 5 | # @description: "juno extensible linking protocol" 6 | # 7 | # @depends.modules+ 'JELP::Incoming', 'JELP::Outgoing', 'JELP::Registration' 8 | # 9 | # @author.name: "Mitchell Cooper" 10 | # @author.website: "https://github.com/cooper" 11 | # 12 | package M::JELP; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | our $mod; 19 | -------------------------------------------------------------------------------- /modules/JELP/Outgoing.module/Outgoing.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "JELP::Base" 9 | ] 10 | }, 11 | "description" : "basic set of JELP outgoing commands", 12 | "name" : "JELP::Outgoing", 13 | "package" : "M::JELP::Outgoing", 14 | "version" : "24.95" 15 | } 16 | -------------------------------------------------------------------------------- /modules/JELP/Registration.module/Registration.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "RegistrationCommands" 9 | ], 10 | "modules" : [ 11 | "JELP::Base" 12 | ] 13 | }, 14 | "description" : "JELP registration commands", 15 | "name" : "JELP::Registration", 16 | "package" : "M::JELP::Registration", 17 | "version" : "3.7" 18 | } 19 | -------------------------------------------------------------------------------- /modules/JELP/Registration.module/Registration.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-Mac-mini.local 4 | # Sat Aug 9 23:11:08 EDT 2014 5 | # Registration.pm 6 | # 7 | # @name: 'JELP::Registration' 8 | # @package: 'M::JELP::Registration' 9 | # @description: 'JELP registration commands' 10 | # 11 | # @depends.bases+ 'RegistrationCommands' 12 | # @depends.modules+ 'JELP::Base' 13 | # 14 | # @author.name: 'Mitchell Cooper' 15 | # @author.website: 'https://github.com/cooper' 16 | # 17 | package M::JELP::Registration; 18 | 19 | use warnings; 20 | use strict; 21 | use 5.010; 22 | 23 | use utils qw(conf notice irc_match irc_lc); 24 | 25 | our ($api, $mod, $pool); 26 | 27 | my ($JELP_CURRENT, $JELP_MIN) =( 28 | $M::JELP::Base::JELP_CURRENT, 29 | $M::JELP::Base::JELP_MIN 30 | ); 31 | 32 | our %registration_commands = ( 33 | SERVER => { 34 | code => \&rcmd_server, 35 | params => 6, 36 | proto => 'jelp' 37 | }, 38 | PASS => { 39 | code => \&rcmd_pass, 40 | params => 1, 41 | proto => 'jelp' 42 | }, 43 | READY => { 44 | code => \&rcmd_ready, 45 | proto => 'jelp', 46 | after_reg => 1 47 | } 48 | ); 49 | 50 | ########################### 51 | ### SERVER REGISTRATION ### 52 | ########################### 53 | 54 | sub rcmd_server { 55 | my ($conn, $event, @args) = @_; 56 | $conn->{desc} = pop @args; 57 | $conn->{$_} = shift @args for qw(sid name proto ircd); 58 | my $their_time = shift @args; 59 | 60 | # check for errors 61 | if (my $err = $conn->verify) { 62 | $conn->done($err); 63 | return; 64 | } 65 | 66 | # bad JELP version 67 | if ($conn->{proto} < $JELP_MIN) { 68 | my $ver_str = "($$conn{proto} < $JELP_MIN)"; 69 | notice(server_protocol_error => 70 | "$$conn{name} ($$conn{sid})", 71 | 'will not be linked due to an incompatible JELP version '.$ver_str 72 | ); 73 | $conn->done("Incompatible JELP version $ver_str"); 74 | return; 75 | } 76 | 77 | # check if the time delta is enormous 78 | server::protocol::check_ts_delta($conn, time, $their_time) 79 | or return; 80 | 81 | # hidden? 82 | my $sub = \substr($conn->{desc}, 0, 4); 83 | if (length $sub && $$sub eq '(H) ') { 84 | $$sub = ''; 85 | $conn->{hidden} = 1; 86 | } 87 | 88 | # if this was by our request (as in an autoconnect or /connect or something) 89 | # don't accept any server except the one we asked for. 90 | if (length $conn->{want} && 91 | irc_lc($conn->{want}) ne irc_lc($conn->{name})) { 92 | $conn->done('Unexpected server'); 93 | return; 94 | } 95 | 96 | # find a matching server. 97 | if (defined(my $addrs = 98 | conf([ 'connect', $conn->{name} ], 'address'))) { 99 | $addrs = [$addrs] if !ref $addrs; 100 | if (!irc_match($conn->{ip}, @$addrs)) { 101 | $conn->done('Invalid credentials'); 102 | notice(connection_invalid => 103 | $conn->{ip}, 'IP does not match configuration'); 104 | return; 105 | } 106 | } 107 | 108 | # no such server. 109 | else { 110 | $conn->done('Invalid credentials'); 111 | notice(connection_invalid => 112 | $conn->{ip}, 'No block for this server'); 113 | return; 114 | } 115 | 116 | # send my own SERVER if I haven't already. 117 | if (!$conn->{i_sent_server}) { 118 | M::JELP::Base::send_server_server($conn); 119 | } 120 | 121 | # otherwise, I am going to expose my password. 122 | # this means that I was the one that issued the connect. 123 | else { 124 | M::JELP::Base::send_server_pass($conn); 125 | } 126 | 127 | # made it. 128 | $conn->fire('looks_like_server'); 129 | $conn->reg_continue('id1'); 130 | return 1; 131 | 132 | } 133 | 134 | sub rcmd_pass { 135 | my ($conn, $event, @args) = @_; 136 | $conn->{pass} = shift @args; 137 | 138 | # moron hasn't sent SERVER yet. 139 | my $name = $conn->{name}; 140 | return if !length $name; 141 | 142 | $conn->{link_type} = 'jelp'; 143 | 144 | # check for valid password. 145 | my $password = utils::crypt( 146 | $conn->{pass}, 147 | conf(['connect', $name ], 'encryption') 148 | ); 149 | if ($password ne conf(['connect', $name], 'receive_password')) { 150 | $conn->done('Invalid credentials'); 151 | notice(connection_invalid => 152 | $conn->{ip}, 'Received invalid password'); 153 | return; 154 | } 155 | 156 | # send my own PASS if I haven't already. 157 | # this is postponed so that the burst will not be triggered until 158 | # hostname resolve, ident, etc. are done. 159 | $conn->on(ready_done => sub { 160 | my $c = shift; 161 | M::JELP::Base::send_server_pass($c); 162 | $c->send('READY'); 163 | }, name => 'jelp.send.password', with_eo => 1) 164 | if !$conn->{i_sent_pass}; 165 | 166 | $conn->reg_continue('id2'); 167 | return 1; 168 | } 169 | 170 | sub _send_burst { 171 | my $conn = shift; 172 | 173 | # the server is registered on our end. 174 | my $server = $conn->server or return; 175 | 176 | # send the burst. 177 | if (!$server->{i_sent_burst}) { 178 | $server->send_burst; 179 | return 1; 180 | } 181 | 182 | # already did? 183 | L("$$server{name} is requesting burst, but I already sent it"); 184 | return; 185 | 186 | } 187 | 188 | # the server is ready to receive burst. 189 | sub rcmd_ready { 190 | my ($conn, $event) = @_; 191 | 192 | # the server is registered, and we haven't sent burst. do so. 193 | _send_burst($conn) and return; 194 | 195 | # the server is not yet registered; postpone the burst. 196 | $conn->on(ready_done => \&_send_burst, with_eo => 1) 197 | unless $conn->{ready}; 198 | 199 | } 200 | 201 | $mod 202 | -------------------------------------------------------------------------------- /modules/LOLCAT.module/LOLCAT.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserCommands" 9 | ] 10 | }, 11 | "description" : "SPEEK LIEK A LOLCATZ!", 12 | "name" : "LOLCAT", 13 | "package" : "M::LOLCAT", 14 | "version" : "1" 15 | } 16 | -------------------------------------------------------------------------------- /modules/LOLCAT.module/LOLCAT.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-16, Mitchell Cooper 2 | # 3 | # @name: "LOLCAT" 4 | # @package: "M::LOLCAT" 5 | # @description: "SPEEK LIEK A LOLCATZ!" 6 | # 7 | # @depends.bases+ 'UserCommands' 8 | # 9 | # @author.name: "Mitchell Cooper" 10 | # @author.website: "https://github.com/cooper" 11 | # 12 | package M::LOLCAT; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | use Acme::LOLCAT qw(translate); 19 | use utils qw(col); 20 | 21 | our ($api, $mod, $pool); 22 | 23 | our %user_commands = (LOLCAT => { 24 | desc => 'SPEEK LIEK A LOLCATZ!', 25 | params => 'channel :', 26 | code => \&lolcat 27 | }); 28 | 29 | sub lolcat { 30 | my ($user, $event, $channel, $msg) = @_; 31 | $msg = translate($msg); 32 | $user->handle("ECHO $$channel{name} :$msg"); 33 | } 34 | 35 | $mod 36 | -------------------------------------------------------------------------------- /modules/Lastfm.module/Daemon.module/Daemon.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-Mac-mini.local 4 | # Thu Jun 26 18:27:19 EDT 2014 5 | # Daemon.pm 6 | # 7 | # @name: 'Lastfm::Daemon' 8 | # @package: 'M::Lastfm::Daemon' 9 | # 10 | # @author.name: 'Mitchell Cooper' 11 | # @author.website: 'https://github.com/cooper' 12 | # 13 | package M::Lastfm::Daemon; 14 | 15 | use warnings; 16 | use strict; 17 | use 5.010; 18 | 19 | our ($api, $mod, $pool); 20 | 21 | sub init { 22 | } 23 | 24 | $mod 25 | 26 | -------------------------------------------------------------------------------- /modules/Lastfm.module/Lastfm.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-Mac-mini.local 4 | # Thu Jun 26 18:26:51 EDT 2014 5 | # Lastfm.pm 6 | # 7 | # @name: 'Lastfm' 8 | # @package: 'M::Lastfm' 9 | # @description: 'last.fm now playing' 10 | # 11 | # @author.name: 'Mitchell Cooper' 12 | # @author.website: 'https://github.com/cooper' 13 | # 14 | package M::Lastfm; 15 | 16 | use warnings; 17 | use strict; 18 | use 5.010; 19 | 20 | our ($api, $mod, $pool, $es); 21 | 22 | sub init { 23 | $es = $mod->spawn_submodule('Daemon') or return; 24 | } 25 | 26 | sub ucmd_lastfm { 27 | my ($user, $event, $username) = @_; 28 | 29 | $es->do(fetch_np => $username, sub { 30 | # fire_then will be defined in Evented::Socket 31 | # sub Evented::Object::Collection::fire_then maybe 32 | }); 33 | 34 | 35 | # I am unsure if this is a good idea. it's not really what I had in mind 36 | # originally, but it does not need to be more complex without reason. 37 | 38 | } 39 | 40 | $mod 41 | 42 | -------------------------------------------------------------------------------- /modules/Modules.module/Modules.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserCommands", 9 | "OperNotices" 10 | ] 11 | }, 12 | "description" : "manage modules from IRC", 13 | "name" : "Modules", 14 | "package" : "M::Modules", 15 | "version" : "7.7" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Modules.module/Modules.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009-16, Mitchell Cooper 2 | # 3 | # @name: "Modules" 4 | # @package: "M::Modules" 5 | # @description: "manage modules from IRC" 6 | # 7 | # @depends.bases+ 'UserCommands', 'OperNotices' 8 | # 9 | # @author.name: "Mitchell Cooper" 10 | # @author.website: "https://github.com/cooper" 11 | # 12 | package M::Modules; 13 | 14 | use warnings; 15 | use strict; 16 | use 5.010; 17 | 18 | use utils qw(notice irc_match); 19 | 20 | our ($api, $mod, $me, $pool); 21 | 22 | our %user_commands = ( 23 | MODULES => { 24 | desc => 'display a list of loaded modules', 25 | params => '*(opt)', 26 | code => \&modules 27 | }, 28 | MODLOAD => { 29 | desc => 'load a module', 30 | params => '-oper(modules) *', 31 | code => \&modload 32 | }, 33 | MODUNLOAD => { 34 | desc => 'unload a module', 35 | params => '-oper(modules) *', 36 | code => \&modunload 37 | }, 38 | MODRELOAD => { 39 | desc => 'unload and then load a module', 40 | params => '-oper(modules) *', 41 | code => \&modreload 42 | } 43 | ); 44 | 45 | sub init { 46 | 47 | # oper notices. 48 | $mod->register_oper_notice( 49 | name => shift @$_, 50 | format => shift @$_ 51 | ) or return foreach ( 52 | [ module_load => '%s loaded %s (%s)' ], 53 | [ module_unload => '%s unloaded %s' ], 54 | [ module_reload => '%s reloaded %s' ] 55 | ); 56 | 57 | return 1; 58 | } 59 | 60 | my $indent; 61 | sub display_module { 62 | my ($user, $module) = @_; 63 | $indent = 0; 64 | my $say = sub { 65 | $user->server_notice(q( ).(' ' x $indent).($_ // '')) foreach @_; 66 | }; 67 | 68 | $say->(undef, sprintf "\2%s\2 %s", $module->name, $module->{version}); 69 | $indent++; 70 | $say->(ucfirst $module->{description}) if $module->{description}; 71 | 72 | # say the items of each array store. 73 | my @array_stores = grep { 74 | ref $module->retrieve($_) eq 'ARRAY' or 75 | ref $module->retrieve($_) eq 'HASH' 76 | } keys %{ $module->{store} }; 77 | 78 | foreach my $store (@array_stores) { 79 | next if $store eq 'managed_events'; 80 | (my $pretty = ucfirst $store) =~ s/_/ /g; 81 | $say->($pretty); 82 | 83 | # fetch the items. for a hash, use keys. 84 | my @items = ref $module->{store}{$store} eq 'HASH' ? 85 | keys %{ $module->{store}{$store} } : 86 | $module->list_store_items($store); 87 | @items = sort @items; 88 | 89 | # while we remove each item. 90 | my @lines = ''; 91 | my $current = 0; 92 | while (my $item = shift @items) { 93 | 94 | # if the length of the line will be over 50, no. 95 | if (length "$lines[$current], $item" > 50) { 96 | $current++; 97 | $lines[$current] //= ''; 98 | redo; 99 | } 100 | 101 | $lines[$current] .= ", $item"; 102 | } 103 | $indent++; 104 | $say->($_) foreach map { substr $_, 2, length() - 2 } @lines; 105 | $indent--; 106 | } 107 | 108 | # display submodules intended. 109 | display_module($user, $_) foreach @{ $module->{submodules} || [] }; 110 | 111 | $indent--; 112 | } 113 | 114 | sub modules { 115 | my ($user, $event, $query) = (shift, shift, shift // '*'); 116 | $user->server_notice('modules', 'Loaded IRCd modules'); 117 | 118 | # find matching modules. 119 | my @matches = 120 | sort { $a->name cmp $b->name } 121 | grep { irc_match($_->name, $query) } 122 | grep { !$_->{parent} } 123 | @{ $api->{loaded} }; 124 | display_module($user, $_) foreach @matches; 125 | 126 | $user->server_notice(' ') if @matches; 127 | $user->server_notice('modules', 'End of modules list'); 128 | return 1; 129 | } 130 | 131 | sub modload { 132 | my ($user, $event, $mod_name) = @_; 133 | $user->server_notice(modload => "Loading module \2$mod_name\2."); 134 | 135 | # attempt. 136 | my $result = wrap(sub { $api->load_module($mod_name) }, $user); 137 | 138 | # failure. 139 | if (!$result) { 140 | $user->server_notice(modload => 'Module failed to load.'); 141 | return; 142 | } 143 | 144 | # success. 145 | notice($user, module_load => $user->notice_info, $result->name, $result->VERSION); 146 | return 1; 147 | 148 | } 149 | 150 | sub modunload { 151 | my ($user, $event, $mod_name) = @_; 152 | $user->server_notice(modunload => "Unloading module \2$mod_name\2."); 153 | 154 | # attempt. 155 | my $result = wrap(sub { $api->unload_module($mod_name) }, $user); 156 | 157 | # failure. 158 | if (!$result) { 159 | $user->server_notice(modunload => 'Module failed to unload.'); 160 | return; 161 | } 162 | 163 | # success. 164 | notice($user, module_unload => $user->notice_info, $result); 165 | return 1; 166 | 167 | } 168 | 169 | sub modreload { 170 | my ($user, $event, $mod_name) = @_; 171 | $user->server_notice(modreload => "Reloading module \2$mod_name\2."); 172 | 173 | # attempt. 174 | my $result = wrap(sub { $api->reload_module($mod_name) }, $user); 175 | 176 | # failure. 177 | if (!$result) { 178 | $user->server_notice(modreload => 'Module failed to reload.'); 179 | return; 180 | } 181 | 182 | # success. 183 | notice($user, module_reload => $user->notice_info, $mod_name); 184 | return 1; 185 | 186 | } 187 | 188 | sub wrap { 189 | my ($code, $user) = @_; 190 | 191 | # attach logging callback 192 | my $cb = $api->on(log => sub { $user->server_notice("- $_[1]") }); 193 | 194 | # store old state 195 | my $previous_state = ircd::change_start(); 196 | 197 | # do the code 198 | my $result = $code->(); 199 | 200 | # accept changes 201 | ircd::change_end($previous_state); 202 | 203 | # remove logging callback 204 | $api->delete_callback(log => $cb->{name}); 205 | 206 | return $result; 207 | } 208 | 209 | $mod 210 | -------------------------------------------------------------------------------- /modules/Monitor.module/Monitor.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserNumerics", 9 | "UserCommands" 10 | ] 11 | }, 12 | "description" : "provides client availability notifications", 13 | "name" : "Monitor", 14 | "package" : "M::Monitor", 15 | "version" : "1.7" 16 | } 17 | -------------------------------------------------------------------------------- /modules/Reload.module/Reload.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "UserCommands", 9 | "OperNotices" 10 | ], 11 | "modules" : [ 12 | "JELP::Base" 13 | ] 14 | }, 15 | "description" : "reload the entire IRCd in one command", 16 | "name" : "Reload", 17 | "package" : "M::Reload", 18 | "version" : "7.5" 19 | } 20 | -------------------------------------------------------------------------------- /modules/Reload.module/Reload.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-14, Mitchell Cooper 2 | # 3 | # @name: "Reload" 4 | # @package: "M::Reload" 5 | # @description: "reload the entire IRCd in one command" 6 | # 7 | # @depends.bases+ 'UserCommands', 'OperNotices' 8 | # @depends.modules+ 'JELP::Base' 9 | # 10 | # @author.name: "Mitchell Cooper" 11 | # @author.website: "https://github.com/cooper" 12 | # 13 | package M::Reload; 14 | 15 | use warnings; 16 | use strict; 17 | 18 | use utils qw(notice gnotice); 19 | 20 | our ($api, $mod, $me, $pool); 21 | 22 | # RELOAD command. 23 | our %user_commands = (RELOAD => { 24 | code => \&cmd_reload, 25 | desc => 'reload the entire IRCd', 26 | params => '-oper(reload) ...(opt)' 27 | }); 28 | 29 | sub init { 30 | 31 | # allow RELOAD to work remotely. 32 | $mod->register_global_command(name => 'reload'); 33 | 34 | # notices. 35 | $mod->register_oper_notice( 36 | name => 'reload', 37 | format => '%s %s by %s' 38 | ) or return; 39 | 40 | return 1; 41 | } 42 | 43 | sub cmd_reload { 44 | my ($user, $event, @rest) = @_; 45 | my ($verbose, $debug_verbose); 46 | 47 | # the second arg might be verbosity flags 48 | if (length $rest[1]) { 49 | $verbose++ if $rest[1] =~ m/v/; 50 | $verbose++, $debug_verbose++ if $rest[1] =~ m/d/; 51 | } 52 | 53 | # server parameter? 54 | if (my $server_mask_maybe = shift @rest) { 55 | my @servers = $pool->lookup_server_mask($server_mask_maybe); 56 | 57 | # no priv. 58 | if (!$user->has_flag('greload')) { 59 | $user->numeric(ERR_NOPRIVILEGES => 'greload'); 60 | return; 61 | } 62 | 63 | # no matches. 64 | if (!@servers) { 65 | $user->numeric(ERR_NOSUCHSERVER => $server_mask_maybe); 66 | return; 67 | } 68 | 69 | # use forward_global_command() to send it out. 70 | my $matched = server::protocol::forward_global_command( 71 | \@servers, 72 | ircd_reload => $user, $rest[1], $server::protocol::INJECT_SERVERS 73 | ) if @servers; 74 | my %matched = $matched ? %$matched : (); 75 | 76 | # if $me is not in %done, we're not reloading locally. 77 | return 1 if !$matched{$me}; 78 | 79 | } 80 | 81 | # Safe point - we're definitely going to reload locally 82 | 83 | my $old_v = $ircd::VERSION; 84 | my $prefix = $user->is_local ? '' : "[$$me{name}] "; 85 | 86 | # log to user if debug. 87 | my $cb; 88 | $cb = $api->on(log => sub { $user->server_notice("$prefix- $_[1]") }, 89 | name => 'cmd.reload', 90 | permanent => 1 # we will remove it manually afterward 91 | ) if $debug_verbose; 92 | 93 | # redefine Evented::Object before anything else. 94 | $ircd::disable_warnings++; 95 | { 96 | no warnings 'redefine'; 97 | do $_ foreach grep { /^Evented\/Object/ } keys %INC; 98 | } 99 | $ircd::disable_warnings--; 100 | 101 | # store old state 102 | my $previous_state = ircd::change_start(); 103 | 104 | # determine modules loaded beforehand. 105 | my @mods_loaded = @{ $api->{loaded} }; 106 | my $num = scalar @mods_loaded; 107 | 108 | # put bases last. 109 | my $ircd = $api->get_module('ircd'); 110 | my (@not_bases, @bases); 111 | foreach my $module (@mods_loaded) { 112 | 113 | # ignore ircd, unloaded modules, and submodules. 114 | next if $module == $ircd; 115 | next if $module->{UNLOADED}; 116 | next if $module->{parent}; 117 | 118 | my $is_base = $module->name =~ m/Base/; 119 | push @not_bases, $module if !$is_base; 120 | push @bases, $module if $is_base; 121 | } 122 | 123 | # reload ircd first, non-bases, then bases. 124 | # 125 | # 1. ircd -- do the actual server upgrade 126 | # 2. non-bases -- reload normal modules, allowing bases to react 127 | # 3. bases -- reload bases, now that module.* events have been fired 128 | # 129 | $api->reload_module($ircd, @not_bases, @bases); 130 | my $new_v = ircd->VERSION; 131 | 132 | 133 | # module summary. 134 | $user->server_notice("$prefix- Module summary") if $verbose; 135 | my $reloaded = 0; 136 | foreach my $old_m (@mods_loaded) { 137 | my $new_m = $api->get_module($old_m->name); 138 | 139 | # not loaded. 140 | if (!$new_m) { 141 | $user->server_notice( 142 | "$prefix - NOT LOADED: $$old_m{name}{full}" 143 | ); 144 | next; 145 | } 146 | 147 | # version change. 148 | if ($new_m->{version} > $old_m->{version}) { 149 | $user->server_notice( 150 | "$prefix - Upgraded: $$new_m{name}{full} ". 151 | "($$old_m{version} -> $$new_m{version})" 152 | ) if $verbose; 153 | next; 154 | } 155 | 156 | $reloaded++; 157 | } 158 | 159 | # find new modules. 160 | NEW: foreach my $new_m (@{ $api->{loaded} }) { 161 | foreach my $old_m (@mods_loaded) { 162 | next NEW if $old_m->name eq $new_m->name; 163 | } 164 | $user->server_notice( 165 | "$prefix - Loaded: $$new_m{name}{full}" 166 | ) if $verbose; 167 | } 168 | 169 | $user->server_notice( 170 | "$prefix - $reloaded modules reloaded and not upgraded" 171 | ) if $verbose; 172 | 173 | # difference in version. 174 | my $info; 175 | if ($new_v != $old_v) { 176 | my $amnt = sprintf('%.f', ($new_v - $::VERSION) * 100); 177 | my $direction = $new_v < $old_v ? 'downgraded' : 'upgraded'; 178 | $info = "$direction from $old_v to $new_v (up $amnt versions since start)"; 179 | } 180 | else { 181 | $info = 'reloaded'; 182 | } 183 | 184 | # accept changes 185 | ircd::change_end($previous_state); 186 | 187 | $api->delete_callback('log', $cb->{name}) if $verbose; 188 | gnotice($user, reload => $me->name, $info, $user->notice_info); 189 | return 1; 190 | } 191 | 192 | $mod 193 | -------------------------------------------------------------------------------- /modules/Resolve.module/Resolve.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "resolve hostnames", 7 | "name" : "Resolve", 8 | "package" : "M::Resolve", 9 | "version" : "5.1" 10 | } 11 | -------------------------------------------------------------------------------- /modules/Resolve.module/Resolve.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Mitchell Cooper 2 | # 3 | # @name: "Resolve" 4 | # @package: "M::Resolve" 5 | # @description: "resolve hostnames" 6 | # 7 | # @author.name: "Mitchell Cooper" 8 | # @author.website: "https://github.com/cooper" 9 | # 10 | package M::Resolve; 11 | 12 | use warnings; 13 | use strict; 14 | 15 | use utils 'safe_ip'; 16 | use Socket qw(SOCK_STREAM); 17 | 18 | our ($api, $mod, $me, $pool); 19 | 20 | sub init { 21 | 22 | # hook onto new connections to resolve host 23 | $pool->on('connection.new' => \&connection_new, 'resolve.hostname'); 24 | 25 | return 1; 26 | } 27 | 28 | # on new connection, attempt to resolve 29 | sub connection_new { 30 | my ($conn, $event) = @_; 31 | $conn->early_reply(NOTICE => ':*** Looking up your hostname...'); 32 | resolve_address($conn); 33 | } 34 | 35 | # Step 1: getnameinfo() 36 | sub resolve_address { 37 | my $conn = shift; 38 | return if $conn->{goodbye}; 39 | 40 | # prevent connection registration from completing. 41 | $conn->reg_wait('resolve'); 42 | 43 | # peername -> human-readable hostname 44 | my $resolve_future = $::loop->resolver->getnameinfo( 45 | addr => $conn->sock->peername 46 | ); 47 | 48 | my $timeout_future = $::loop->timeout_future(after => 3); 49 | my $f = Future->wait_any($resolve_future, $timeout_future); 50 | 51 | $f->on_done(sub { on_got_host1($conn, @_ ) }); 52 | $f->on_fail(sub { on_error ($conn, shift) }); 53 | $conn->adopt_future(resolve_host1 => $f); 54 | } 55 | 56 | # Step 2: getaddrinfo() 57 | # got human-readable hostname 58 | sub on_got_host1 { 59 | my ($conn, $host) = @_; 60 | $host = safe_ip($host); 61 | 62 | # temporarily store the host. 63 | $conn->{resolve_host} = $host; 64 | 65 | # getnameinfo() spit out the IP. 66 | # we need better IP comparison probably. 67 | if ($conn->{ip} eq $host) { 68 | return on_error($conn, 'getnameinfo() spit out IP'); 69 | } 70 | 71 | # human readable hostname -> binary address 72 | my $resolve_future = $::loop->resolver->getaddrinfo( 73 | host => $host, 74 | service => '', 75 | socktype => SOCK_STREAM, 76 | timeout => 3 77 | ); 78 | 79 | my $timeout_future = $::loop->timeout_future(after => 3); 80 | my $f = Future->wait_any($resolve_future, $timeout_future); 81 | 82 | $f->on_done(sub { on_got_addr($conn, @_ ) }); 83 | $f->on_fail(sub { on_error ($conn, shift) }); 84 | $conn->adopt_future(resolve_addr => $f); 85 | } 86 | 87 | # Step 3: getnameinfo() 88 | # got binary representation of address 89 | sub on_got_addr { 90 | my ($conn, $addr) = @_; 91 | 92 | # binary address -> human-readable hostname 93 | my $resolve_future = $::loop->resolver->getnameinfo( 94 | addr => $addr->{addr}, 95 | socktype => SOCK_STREAM 96 | ); 97 | 98 | my $timeout_future = $::loop->timeout_future(after => 3); 99 | my $f = Future->wait_any($resolve_future, $timeout_future); 100 | 101 | $f->on_done(sub { on_got_host2($conn, @_ ) }); 102 | $f->on_fail(sub { on_error ($conn, shift) }); 103 | $conn->adopt_future(resolve_host2 => $f); 104 | } 105 | 106 | # Step 4: Set the host 107 | # got human-readable hostname 108 | sub on_got_host2 { 109 | my ($conn, $host) = @_; 110 | 111 | # they match. 112 | if ($conn->{resolve_host} eq $host) { 113 | $conn->early_reply(NOTICE => ':*** Found your hostname'); 114 | $conn->{host} = safe_ip(delete $conn->{resolve_host}); 115 | $conn->fire('found_hostname'); 116 | _finish($conn); 117 | return 1; 118 | } 119 | 120 | # not the same. 121 | return on_error($conn, "No match ($host)"); 122 | 123 | } 124 | 125 | # called on error 126 | sub on_error { 127 | my ($conn, $err) = (shift, shift // 'unknown error'); 128 | $conn->early_reply(NOTICE => ":*** Couldn't resolve your hostname"); 129 | D("Lookup for $$conn{ip} failed: $err"); 130 | _finish($conn); 131 | return; 132 | } 133 | 134 | # call with either success or failure 135 | sub _finish { 136 | my $conn = shift; 137 | return if $conn->{goodbye}; 138 | 139 | # delete futures that might be left 140 | $conn->abandon_future($_) 141 | for qw(resolve_host1 resolve_host2 resolve_addr); 142 | 143 | delete $conn->{resolve_host}; 144 | $conn->reg_continue('resolve'); 145 | } 146 | 147 | $mod 148 | -------------------------------------------------------------------------------- /modules/SASL/SASL.module/JELP.module/JELP.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "JELP SASL implementation", 7 | "name" : "SASL::JELP", 8 | "package" : "M::SASL::JELP", 9 | "version" : "1.3" 10 | } 11 | -------------------------------------------------------------------------------- /modules/SASL/SASL.module/SASL.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Matthew Barksdale", 4 | "website" : "https://github.com/mattwb65" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "Capabilities", 9 | "RegistrationCommands", 10 | "UserNumerics" 11 | ] 12 | }, 13 | "description" : "Provides SASL authentication", 14 | "name" : "SASL", 15 | "package" : "M::SASL", 16 | "version" : "8.2" 17 | } 18 | -------------------------------------------------------------------------------- /modules/SASL/SASL.module/TS6.module/TS6.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "TS6 SASL implementation", 7 | "name" : "SASL::TS6", 8 | "package" : "M::SASL::TS6", 9 | "version" : "2.6" 10 | } 11 | -------------------------------------------------------------------------------- /modules/TS6/Base.module/Base.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "API::Methods", 9 | "TS6::Utils" 10 | ] 11 | }, 12 | "description" : "programming interface for TS6", 13 | "name" : "TS6::Base", 14 | "package" : "M::TS6::Base", 15 | "version" : "4.8" 16 | } 17 | -------------------------------------------------------------------------------- /modules/TS6/Incoming.module/Incoming.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "TS6::Base" 9 | ] 10 | }, 11 | "description" : "basic set of TS6 command handlers", 12 | "name" : "TS6::Incoming", 13 | "package" : "M::TS6::Incoming", 14 | "version" : "31.2" 15 | } 16 | -------------------------------------------------------------------------------- /modules/TS6/Outgoing.module/Outgoing.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "TS6::Utils", 9 | "TS6::Base" 10 | ] 11 | }, 12 | "description" : "basic set of TS6 outgoing commands", 13 | "name" : "TS6::Outgoing", 14 | "package" : "M::TS6::Outgoing", 15 | "version" : "15.6" 16 | } 17 | -------------------------------------------------------------------------------- /modules/TS6/Registration.module/Registration.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "bases" : [ 8 | "RegistrationCommands" 9 | ], 10 | "modules" : [ 11 | "TS6::Utils", 12 | "TS6::Base" 13 | ] 14 | }, 15 | "description" : "registration commands for TS6 protocol", 16 | "name" : "TS6::Registration", 17 | "package" : "M::TS6::Registration", 18 | "version" : "8.6" 19 | } 20 | -------------------------------------------------------------------------------- /modules/TS6/TS6.module/TS6.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "depends" : { 7 | "modules" : [ 8 | "TS6::Utils", 9 | "TS6::Base", 10 | "TS6::Incoming", 11 | "TS6::Outgoing", 12 | "TS6::Registration" 13 | ] 14 | }, 15 | "description" : "TS version 6 linking protocol", 16 | "name" : "TS6", 17 | "package" : "M::TS6", 18 | "version" : "0.3" 19 | } 20 | -------------------------------------------------------------------------------- /modules/TS6/TS6.module/TS6.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-Mac-mini.local 4 | # Fri Aug 8 22:43:03 EDT 2014 5 | # TS6.pm 6 | # 7 | # @name: 'TS6' 8 | # @package: 'M::TS6' 9 | # @description: 'TS version 6 linking protocol' 10 | # 11 | # @depends.modules+ qw(TS6::Utils TS6::Base TS6::Incoming) 12 | # @depends.modules+ qw(TS6::Outgoing TS6::Registration) 13 | # 14 | # @author.name: 'Mitchell Cooper' 15 | # @author.website: 'https://github.com/cooper' 16 | # 17 | package M::TS6; 18 | 19 | use warnings; 20 | use strict; 21 | use 5.010; 22 | 23 | our $mod; 24 | -------------------------------------------------------------------------------- /modules/TS6/Utils.module/Utils.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "utilities for the TS6 protocol", 7 | "name" : "TS6::Utils", 8 | "package" : "M::TS6::Utils", 9 | "version" : "12.4" 10 | } 11 | -------------------------------------------------------------------------------- /modules/TS6/Utils.module/Utils.pm: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016, Mitchell Cooper 2 | # 3 | # Created on Mitchells-Mac-mini.local 4 | # Sat Aug 9 16:03:41 EDT 2014 5 | # Utils.pm 6 | # 7 | # @name: 'TS6::Utils' 8 | # @package: 'M::TS6::Utils' 9 | # @description: 'utilities for the TS6 protocol' 10 | # 11 | # @author.name: 'Mitchell Cooper' 12 | # @author.website: 'https://github.com/cooper' 13 | # 14 | package M::TS6::Utils; 15 | 16 | use warnings; 17 | use strict; 18 | use 5.010; 19 | 20 | use Scalar::Util 'blessed'; 21 | use List::Util 'min'; 22 | use utils qw(import ref_to_list); 23 | 24 | our ($api, $mod, $pool, $conf); 25 | 26 | ################### 27 | ### juno -> TS6 ### 28 | ################### 29 | 30 | *ts6_message = *M::TS6::Base::ts6_message; 31 | 32 | # convert an object to its ID. 33 | sub ts6_id { 34 | my $obj = shift; 35 | blessed $obj or return; 36 | 37 | # cached. 38 | return $obj->{ts6_id} if length $obj->{ts6_id}; 39 | return $obj->{ts6_uid} if length $obj->{ts6_uid}; 40 | return $obj->{ts6_sid} if length $obj->{ts6_sid}; 41 | 42 | my $r; 43 | if ($obj->isa('user')) { $r = ts6_uid($obj->{uid}) } 44 | elsif ($obj->isa('server')) { $r = ts6_sid($obj->{sid}) } 45 | elsif ($obj->isa('channel')) { $r = $obj->{name} } 46 | 47 | $obj->{ts6_id} = $r if length $r; 48 | return $r; 49 | } 50 | 51 | # convert an SID. 52 | sub ts6_sid { 53 | my $sid = shift; 54 | 55 | # short SIDs can be represented normally. 56 | if (length $sid < 8) { 57 | return sprintf '%03d', $sid; 58 | } 59 | 60 | # longer SIDs are assumed to be generated by sid_from_ts6(). 61 | # convert them back to juno format. 62 | my $new = ''; 63 | while ($sid =~ s/(\d{1,3})$//) { 64 | $new = chr($1).$new; 65 | } 66 | 67 | return $new; 68 | } 69 | 70 | # convert a full UID. 71 | sub ts6_uid { 72 | my $uid = shift; 73 | my ($sid, $id) = ($uid =~ m/^([0-9]+)([a-z]+)$/); 74 | return ts6_sid($sid).ts6_uid_u($id); 75 | } 76 | 77 | # convert just the alphabetic portion of a UID. 78 | sub ts6_uid_u { 79 | my $id = shift; 80 | return ts6_id_n(utils::a2n($id)); 81 | } 82 | 83 | # get nth ts6 ID. 84 | sub ts6_id_n { 85 | my $n = shift() - 1; 86 | my @chars = ('A') x 6; 87 | my $i = 0; 88 | for my $pow (reverse 0..5) { 89 | my $amnt = 36 ** $pow; 90 | while ($n >= $amnt) { 91 | if ($chars[$i] eq 'Z') { $chars[$i] = '0' } 92 | elsif ($chars[$i] eq '9') { $chars[$i] = 'A' } 93 | else { $chars[$i]++ } 94 | $n -= $amnt; 95 | } 96 | $i++; 97 | } 98 | return join '', @chars; 99 | } 100 | 101 | ################### 102 | ### TS6 -> juno ### 103 | ################### 104 | 105 | # TS6 SID or UID -> object 106 | sub obj_from_ts6 { 107 | my $id = shift; 108 | if (length $id == 3) { return $pool->lookup_server(sid_from_ts6($id)) } 109 | if (length $id == 9) { return $pool->lookup_user (uid_from_ts6($id)) } 110 | return; 111 | } 112 | 113 | # TS6 UID -> user 114 | sub user_from_ts6 { 115 | my $user = obj_from_ts6(shift); 116 | return unless blessed $user && $user->isa('user'); 117 | return $user; 118 | } 119 | 120 | # TS6 SID -> server 121 | sub server_from_ts6 { 122 | my $server = obj_from_ts6(shift); 123 | return unless blessed $server && $server->isa('server'); 124 | return $server; 125 | } 126 | 127 | # TS6 SID -> juno SID 128 | sub sid_from_ts6 { 129 | my $sid = shift; 130 | return unless $sid =~ m/^[0-9A-Z]{3}$/; 131 | if ($sid =~ m/[A-Z]/) { 132 | return join('', map { sprintf '%03d', ord } split //, $sid) + 0; 133 | } 134 | return $sid + 0; 135 | } 136 | 137 | # TS6 UID -> juno UID 138 | # e.g. 000AAAAAA -> 0a 139 | sub uid_from_ts6 { 140 | my $uid = shift; 141 | my ($sid, $id) = ($uid =~ m/^([0-9A-Z]{3})([0-9A-Z]{6})$/); 142 | defined $sid && defined $id or return; 143 | return sid_from_ts6($sid).uid_u_from_ts6($id); 144 | } 145 | 146 | # TS6 ID -> juno ID 147 | # e.g. AAAAAA -> a 148 | # thanks to @Hakkin for helping with this 149 | sub uid_u_from_ts6 { utils::n2a(&uid_n_from_ts6) } 150 | sub uid_n_from_ts6 { 151 | my $dec = 0; 152 | my @chars = split //, shift or return; 153 | for my $i (0..5) { 154 | my $ord = ord $chars[$i]; 155 | my $add = $ord > 57 ? -65 : -22; 156 | my $p = $ord + $add; 157 | $p *= 36 ** (5 - $i) if $i < 5; 158 | $dec += $p; 159 | } 160 | return ++$dec; 161 | } 162 | 163 | 164 | # convert juno level to prefix. 165 | # returns empty string if there is no equivalent. 166 | sub ts6_prefix { 167 | my ($server, $level) = @_; 168 | foreach my $prefix (keys %{ $server->{ircd_prefixes} || {} }) { 169 | my ($letter, $lvl) = ref_to_list($server->{ircd_prefixes}{$prefix}); 170 | next unless $level == $lvl; 171 | return $prefix; 172 | } 173 | return ''; 174 | } 175 | 176 | # returns the first level which is less than or equal to the prefix's level. 177 | # e.g. 2 (owner) to charybdis would be 0 (op). 178 | sub ts6_closest_level { 179 | my ($server, $level) = @_; 180 | 181 | # find the lowest supported level. 182 | my %supported_levels = map { $_->[1] => 1 } 183 | values %{ $server->{ircd_prefixes} || {} }; 184 | my $lowest = min keys %supported_levels; 185 | 186 | # if the requested level is lower than the lowest supported, 187 | # we cannot do anything. 188 | return -inf if $level < $lowest; 189 | 190 | # starting with $level, go backwards to $lowest 191 | for (reverse $lowest .. $level) { 192 | return $_ if $supported_levels{$_}; 193 | } 194 | 195 | return -inf; 196 | } 197 | 198 | # convert juno levels to prefixes, removing duplicates. 199 | sub ts6_prefixes { 200 | my ($server, @levels) = @_; 201 | my ($prefixes, %done) = ''; 202 | foreach my $level (@levels) { 203 | my $prefix = ts6_prefix($server, $level); 204 | next if $done{$prefix}; 205 | $prefixes .= $prefix; 206 | $done{$prefix} = 1; 207 | } 208 | return $prefixes; 209 | } 210 | 211 | # TS6 prefix -> mode letter 212 | sub mode_from_prefix_ts6 { 213 | my ($server, $prefix) = @_; 214 | my $p = $server->{ircd_prefixes}{$prefix}; 215 | return $p ? $p->[0] : ''; 216 | } 217 | 218 | # TS6 prefix -> level 219 | sub level_from_prefix_ts6 { 220 | my ($server, $prefix) = @_; 221 | my $p = $server->{ircd_prefixes}{$prefix}; 222 | return $p ? $p->[1] : -inf; 223 | } 224 | 225 | $mod 226 | -------------------------------------------------------------------------------- /modules/ircd.module/channel.module/channel.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "represents an IRC channel", 7 | "name" : "ircd::channel", 8 | "no_bless" : 1, 9 | "package" : "channel", 10 | "preserve_sym" : 1, 11 | "version" : "13.27" 12 | } 13 | -------------------------------------------------------------------------------- /modules/ircd.module/connection.module/connection.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "represents a connection to the server", 7 | "name" : "ircd::connection", 8 | "no_bless" : 1, 9 | "package" : "connection", 10 | "preserve_sym" : 1, 11 | "version" : "13.28" 12 | } 13 | -------------------------------------------------------------------------------- /modules/ircd.module/ircd.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "main IRCd module", 7 | "name" : "ircd", 8 | "no_bless" : 1, 9 | "package" : "ircd", 10 | "preserve_sym" : 1, 11 | "version" : "2.7" 12 | } 13 | -------------------------------------------------------------------------------- /modules/ircd.module/message.module/message.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "represents an IRC message", 7 | "name" : "ircd::message", 8 | "no_bless" : 1, 9 | "package" : "message", 10 | "preserve_sym" : 1, 11 | "version" : "13.34" 12 | } 13 | -------------------------------------------------------------------------------- /modules/ircd.module/modes.module/modes.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "represents a sequence of IRC mode changes", 7 | "name" : "ircd::modes", 8 | "no_bless" : 1, 9 | "package" : "modes", 10 | "preserve_sym" : 1, 11 | "version" : "13.27" 12 | } 13 | -------------------------------------------------------------------------------- /modules/ircd.module/pool.module/pool.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "manage IRC objects", 7 | "name" : "ircd::pool", 8 | "no_bless" : 1, 9 | "package" : "pool", 10 | "preserve_sym" : 1, 11 | "version" : "13.27" 12 | } 13 | -------------------------------------------------------------------------------- /modules/ircd.module/server.module/linkage.module/linkage.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "manages server connections", 7 | "name" : "ircd::server::linkage", 8 | "no_bless" : 1, 9 | "package" : "server::linkage", 10 | "preserve_sym" : 1, 11 | "version" : "13.18" 12 | } 13 | -------------------------------------------------------------------------------- /modules/ircd.module/server.module/protocol.module/protocol.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "common code for server to server protocols", 7 | "name" : "ircd::server::protocol", 8 | "no_bless" : 1, 9 | "package" : "server::protocol", 10 | "preserve_sym" : 1, 11 | "version" : "13.24" 12 | } 13 | -------------------------------------------------------------------------------- /modules/ircd.module/server.module/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "represents an IRC server", 7 | "name" : "ircd::server", 8 | "no_bless" : 1, 9 | "package" : "server", 10 | "preserve_sym" : 1, 11 | "version" : "13.27" 12 | } 13 | -------------------------------------------------------------------------------- /modules/ircd.module/user.module/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "represents an IRC user", 7 | "name" : "ircd::user", 8 | "no_bless" : 1, 9 | "package" : "user", 10 | "preserve_sym" : 1, 11 | "version" : "13.27" 12 | } 13 | -------------------------------------------------------------------------------- /modules/ircd.module/utils.module/utils.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : { 3 | "name" : "Mitchell Cooper", 4 | "website" : "https://github.com/cooper" 5 | }, 6 | "description" : "provides convenience and utility functions", 7 | "name" : "ircd::utils", 8 | "no_bless" : 1, 9 | "package" : "utils", 10 | "preserve_sym" : 1, 11 | "version" : "2.5" 12 | } 13 | -------------------------------------------------------------------------------- /var/log/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooper/juno/b2fc18a10d5cf85e494b9393fcebd5aaea326e62/var/log/README.md --------------------------------------------------------------------------------