summaryrefslogtreecommitdiffstats
path: root/scripts/autoopper.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/autoopper.pl')
-rw-r--r--scripts/autoopper.pl412
1 files changed, 412 insertions, 0 deletions
diff --git a/scripts/autoopper.pl b/scripts/autoopper.pl
new file mode 100644
index 0000000..61882f3
--- /dev/null
+++ b/scripts/autoopper.pl
@@ -0,0 +1,412 @@
+use strict;
+use Irssi;
+use POSIX;
+use Socket;
+use vars qw($VERSION %IRSSI);
+
+$VERSION = "3.7";
+%IRSSI = (
+ authors => 'Toni Salomäki',
+ name => 'autoopper',
+ contact => 'Toni@IRCNet',
+ description => 'Auto-op script with dynamic address support and random delay',
+ license => 'GNU GPLv2 or later',
+ url => 'http://vinku.dyndns.org/irssi_scripts/'
+);
+
+# This is a script to auto-op people on a certain channel (all or, represented with *).
+# Users are auto-opped on join with random delay.
+# There is a possibility to use dns aliases (for example dyndns.org) for getting the correct address.
+# The auto-op list is stored into ~/.irssi/autoop
+#
+# To get the dynamic addresses to be refreshed automatically, set value to autoop_dynamic_refresh (in hours)
+# The value will be used next time the script is loaded (at startup or manual load)
+#
+# NOTICE: the datafile is in completely different format than in 1.0 and this version cannot read it. Sorry.
+#
+
+# COMMANDS:
+#
+# autoop_show - Displays list of auto-opped hostmasks & channels
+# The current address of dynamic host is displayed in parenthesis
+#
+# autoop_add - Add new auto-op. Parameters hostmask, channel (or *) and dynamic flag
+#
+# Dynamic flag has 3 different values:
+# 0: treat host as a static ip
+# 1: treat host as an alias for dynamic ip
+# 2: treat host as an alias for dynamic ip, but do not resolve the ip (not normally needed)
+#
+# autoop_del - Remove auto-op
+#
+# autoop_save - Save auto-ops to file (done normally automatically)
+#
+# autoop_load - Load auto-ops from file (use this if you have edited the autoop -file manually)
+#
+# autoop_check - Check all channels and op people needed
+#
+# autoop_dynamic - Refresh dynamic addresses (automatically if parameter set)
+#
+# Data is stored in ~/.irssi/autoop
+# format: host channels flag
+# channels separated with comma
+# one host per line
+
+my (%oplist);
+my (@opitems);
+srand();
+
+#resolve dynamic host
+sub resolve_host {
+ my ($host, $dyntype) = @_;
+
+ if (my $iaddr = inet_aton($host)) {
+ if ($dyntype ne "2") {
+ if (my $newhost = gethostbyaddr($iaddr, AF_INET)) {
+ return $newhost;
+ } else {
+ return inet_ntoa($iaddr);
+ }
+ } else {
+ return inet_ntoa($iaddr);
+ }
+ }
+ return "error";
+}
+
+# return list of dynamic hosts with real addresses
+sub fetch_dynamic_hosts {
+ my %hostcache;
+ my $resultext;
+ foreach my $item (@opitems) {
+ next if ($item->{dynamic} ne "1" && $item->{dynamic} ne "2");
+
+ my (undef, $host) = split(/\@/, $item->{mask}, 2);
+
+ # fetch the host's real address (if not cached)
+ unless ($hostcache{$host}) {
+ $hostcache{$host} = resolve_host($host, $item->{dynamic});
+ $resultext .= $host . "\t" . $hostcache{$host} . "\n";
+ }
+ }
+ chomp $resultext;
+ return $resultext;
+}
+
+# fetch real addresses for dynamic hosts
+sub cmd_change_dynamic_hosts {
+ pipe READ, WRITE;
+ my $pid = fork();
+
+ unless (defined($pid)) {
+ Irssi::print("Can't fork - aborting");
+ return;
+ }
+
+ if ($pid > 0) {
+ # the original process, just add a listener for pipe
+ close (WRITE);
+ Irssi::pidwait_add($pid);
+ my $target = {fh => \*READ, tag => undef};
+ $target->{tag} = Irssi::input_add(fileno(READ), INPUT_READ, \&read_dynamic_hosts, $target);
+ } else {
+ # the new process, fetch addresses and write to the pipe
+ print WRITE fetch_dynamic_hosts;
+ close (READ);
+ close (WRITE);
+ POSIX::_exit(1);
+ }
+}
+
+# get dynamic hosts from pipe and change them to users
+sub read_dynamic_hosts {
+ my $target = shift;
+ my $rh = $target->{fh};
+ my %hostcache;
+
+ while (<$rh>) {
+ chomp;
+ my ($dynhost, $realhost, undef) = split (/\t/, $_, 3);
+ $hostcache{$dynhost} = $realhost;
+ }
+
+ close($target->{fh});
+ Irssi::input_remove($target->{tag});
+
+ my $mask;
+ my $count = 0;
+ undef %oplist if (%oplist);
+
+ foreach my $item (@opitems) {
+ if ($item->{dynamic} eq "1" || $item->{dynamic} eq "2") {
+ my ($user, $host) = split(/\@/, $item->{mask}, 2);
+
+ $count++ if ($item->{dynmask} ne $hostcache{$host});
+ $item->{dynmask} = $hostcache{$host};
+ $mask = $user . "\@" . $hostcache{$host};
+ } else {
+ $mask = $item->{mask};
+ }
+
+ foreach my $channel (split (/,/,$item->{chan})) {
+ $oplist{$channel} .= "$mask ";
+ }
+ }
+ chop %oplist;
+ Irssi::print("$count dynamic hosts changed") if ($count > 0);
+}
+
+# Save data to file
+sub cmd_save_autoop {
+ my $file = Irssi::get_irssi_dir."/autoop";
+ open FILE, ">", "$file" or return;
+
+ foreach my $item (@opitems) {
+ printf FILE ("%s\t%s\t%s\n", $item->{mask}, $item->{chan}, $item->{dynamic});
+ }
+
+ close FILE;
+ Irssi::print("Auto-op list saved to $file");
+}
+
+# Load data from file
+sub cmd_load_autoop {
+ my $file = Irssi::get_irssi_dir."/autoop";
+ open FILE, "<","$file" or return;
+ undef @opitems if (@opitems);
+
+ while (<FILE>) {
+ chomp;
+ my ($mask, $chan, $dynamic, undef) = split (/\t/, $_, 4);
+ my $item = {mask=>$mask, chan=>$chan, dynamic=>$dynamic, dynmask=>undef};
+ push (@opitems, $item);
+ }
+
+ close FILE;
+ Irssi::print("Auto-op list reloaded from $file");
+ cmd_change_dynamic_hosts;
+}
+
+# Show who's being auto-opped
+sub cmd_show_autoop {
+ my %list;
+ foreach my $item (@opitems) {
+ foreach my $channel (split (/,/,$item->{chan})) {
+ $list{$channel} .= "\n" . $item->{mask};
+ $list{$channel} .= " (" . $item->{dynmask} . ")" if ($item->{dynmask});
+ }
+ }
+
+ Irssi::print("All channels:" . $list{"*"}) if (exists $list{"*"});
+ delete $list{"*"}; #this is already printed, so remove it
+ foreach my $channel (sort (keys %list)) {
+ Irssi::print("$channel:" . $list{$channel});
+ }
+}
+
+# Add new auto-op
+sub cmd_add_autoop {
+ my ($data) = @_;
+ my ($mask, $chan, $dynamic, undef) = split(" ", $data, 4);
+ my $found = 0;
+
+ if ($chan eq "" || $mask eq "" || !($mask =~ /.+!.+@.+/)) {
+ Irssi::print("Invalid hostmask. It must contain both ! and @.") if (!($mask =~ /.+!.+@.+/));
+ Irssi::print("Usage: /autoop_add <hostmask> <*|#channel> [dynflag]");
+ Irssi::print("Dynflag: 0 normal, 1 dynamic, 2 dynamic without resolving");
+ return;
+ }
+
+ foreach my $item (@opitems) {
+ next unless ($item->{mask} eq $mask);
+ $found = 1;
+ $item->{chan} .= ",$chan";
+ last;
+ }
+
+ if ($found == 0) {
+ $dynamic = "0" unless ($dynamic eq "1" || $dynamic eq "2");
+ my $item = {mask=>$mask, chan=>$chan, dynamic=>$dynamic, dynmask=>undef};
+ push (@opitems, $item);
+ }
+
+ $oplist{$chan} .= " $mask";
+
+ Irssi::print("Added auto-op: $chan: $mask");
+}
+
+# Remove autoop
+sub cmd_del_autoop {
+ my ($data) = @_;
+ my ($mask, $channel, undef) = split(" ", $data, 3);
+
+ if ($channel eq "" || $mask eq "") {
+ Irssi::print("Usage: /autoop_del <hostmask> <*|#channel>");
+ return;
+ }
+
+ my $i=0;
+ foreach my $item (@opitems) {
+ if ($item->{mask} eq $mask) {
+ if ($channel eq "*" || $item->{chan} eq $channel) {
+ splice @opitems, $i, 1;
+ Irssi::print("Removed: $mask");
+ } else {
+ my $newchan;
+ foreach my $currchan (split (/,/,$item->{chan})) {
+ if ($channel eq $currchan) {
+ Irssi::print("Removed: $channel from $mask");
+ } else {
+ $newchan .= $currchan . ",";
+ }
+ }
+ chop $newchan;
+ Irssi::print("Couldn't remove $channel from $mask") if ($item->{chan} eq $newchan);
+ $item->{chan} = $newchan;
+ }
+ last;
+ }
+ $i++;
+ }
+}
+
+# Do the actual opping
+sub do_autoop {
+ my $target = shift;
+
+ Irssi::timeout_remove($target->{tag});
+
+ # nick has to be fetched again, because $target->{nick}->{op} is not updated
+ my $nick = $target->{chan}->nick_find($target->{nick}->{nick});
+
+ # if nick is changed during delay, it will probably be lost here...
+ if ($nick->{nick} ne "") {
+ if ($nick->{host} eq $target->{nick}->{host}) {
+ $target->{chan}->command("op " . $nick->{nick}) unless ($nick->{op});
+ } else {
+ Irssi::print("Host changed for nick during delay: " . $nick->{nick});
+ }
+ }
+ undef $target;
+}
+
+# Someone joined, might be multiple person. Check if opping is needed
+sub event_massjoin {
+ my ($channel, $nicklist) = @_;
+ my @nicks = @{$nicklist};
+
+ return if (!$channel->{chanop});
+
+ my $masks = $oplist{"*"} . " " . $oplist{$channel->{name}};
+
+ foreach my $nick (@nicks) {
+ my $host = $nick->{host};
+ $host=~ s/^~//g; # remove this if you don't want to strip ~ from username (no ident)
+ next unless ($channel->{server}->masks_match($masks, $nick->{nick}, $host));
+
+ my $min_delay = Irssi::settings_get_int("autoop_min_delay");
+ my $max_delay = Irssi::settings_get_int("autoop_max_delay") - $min_delay;
+ my $delay = int(rand($max_delay)) + $min_delay;
+
+ my $target = {nick => $nick, chan => $channel, tag => undef};
+
+ $target->{tag} = Irssi::timeout_add($delay, 'do_autoop', $target);
+ }
+
+}
+
+# Check channel op status
+sub do_channel_check {
+ my $target = shift;
+
+ Irssi::timeout_remove($target->{tag});
+
+ my $channel = $target->{chan};
+ my $server = $channel->{server};
+ my $masks = $oplist{"*"} . " " . $oplist{$channel->{name}};
+ my $nicks = "";
+
+ foreach my $nick ($channel->nicks()) {
+ next if ($nick->{op});
+
+ my $host = $nick->{host};
+ $host=~ s/^~//g; # remove this if you don't want to strip ~ from username (no ident)
+
+ if ($server->masks_match($masks, $nick->{nick}, $host)) {
+ $nicks = $nicks . " " . $nick->{nick};
+ }
+ }
+ $channel->command("op" . $nicks) unless ($nicks eq "");
+
+ undef $target;
+}
+
+#check people needing opping after getting ops
+sub event_nickmodechange {
+ my ($channel, $nick, $setby, $mode, $type) = @_;
+
+ return unless (($mode eq '@') && ($type eq '+'));
+
+ my $server = $channel->{server};
+
+ return unless ($server->{nick} eq $nick->{nick});
+
+ my $min_delay = Irssi::settings_get_int("autoop_min_delay");
+ my $max_delay = Irssi::settings_get_int("autoop_max_delay") - $min_delay;
+ my $delay = int(rand($max_delay)) + $min_delay;
+
+ my $target = {chan => $channel, tag => undef};
+
+ $target->{tag} = Irssi::timeout_add($delay, 'do_channel_check', $target);
+}
+
+#Check all channels / all users if someone needs to be opped
+sub cmd_autoop_check {
+ my ($data, $server, $witem) = @_;
+
+ foreach my $channel ($server->channels()) {
+ Irssi::print("Checking: " . $channel->{name});
+ next if (!$channel->{chanop});
+
+ my $masks = $oplist{"*"} . " " . $oplist{$channel->{name}};
+
+ foreach my $nick ($channel->nicks()) {
+ next if ($nick->{op});
+
+ my $host = $nick->{host};
+ $host=~ s/^~//g; # remove this if you don't want to strip ~ from username (no ident)
+
+ if ($server->masks_match($masks, $nick->{nick}, $host)) {
+ $channel->command("op " . $nick->{nick}) if (!$nick->{op});
+ }
+ }
+ }
+}
+
+#Set dynamic refresh period.
+sub set_dynamic_refresh {
+ my $refresh = Irssi::settings_get_int("autoop_dynamic_refresh");
+ return if ($refresh == 0);
+
+ Irssi::print("Dynamic host refresh set for $refresh hours");
+ Irssi::timeout_add($refresh*3600000, 'cmd_change_dynamic_hosts', undef);
+}
+
+Irssi::command_bind('autoop_show', 'cmd_show_autoop');
+Irssi::command_bind('autoop_add', 'cmd_add_autoop');
+Irssi::command_bind('autoop_del', 'cmd_del_autoop');
+Irssi::command_bind('autoop_save', 'cmd_save_autoop');
+Irssi::command_bind('autoop_load', 'cmd_load_autoop');
+Irssi::command_bind('autoop_check', 'cmd_autoop_check');
+Irssi::command_bind('autoop_dynamic', 'cmd_change_dynamic_hosts');
+Irssi::signal_add_last('massjoin', 'event_massjoin');
+Irssi::signal_add_last('setup saved', 'cmd_save_autoop');
+Irssi::signal_add_last('setup reread', 'cmd_load_autoop');
+Irssi::signal_add_last("nick mode changed", "event_nickmodechange");
+Irssi::settings_add_int('autoop', 'autoop_max_delay', 15000);
+Irssi::settings_add_int('autoop', 'autoop_min_delay', 1000);
+Irssi::settings_add_int('autoop', 'autoop_dynamic_refresh', 0);
+
+
+cmd_load_autoop;
+set_dynamic_refresh;