diff options
Diffstat (limited to 'scripts/autoopper.pl')
| -rw-r--r-- | scripts/autoopper.pl | 412 | 
1 files changed, 412 insertions, 0 deletions
| diff --git a/scripts/autoopper.pl b/scripts/autoopper.pl new file mode 100644 index 0000000..435c696 --- /dev/null +++ b/scripts/autoopper.pl @@ -0,0 +1,412 @@ +use Irssi; +use POSIX; +use strict; +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; | 
