/var/www/www.irssi.org-old/scripts/html/autoopper.pl


   1 use Irssi;
   2 use POSIX;
   3 use strict;
   4 use Socket;
   5 use vars qw($VERSION %IRSSI);
   6 
   7 $VERSION = "3.7";
   8 %IRSSI = (
   9 	authors     => 'Toni Salomäki',
  10 	name        => 'autoopper',
  11 	contact     => 'Toni@IRCNet',
  12 	description => 'Auto-op script with dynamic address support and random delay',
  13 	license     => 'GNU GPLv2 or later',
  14 	url         => 'http://vinku.dyndns.org/irssi_scripts/'
  15 );
  16 
  17 # This is a script to auto-op people on a certain channel (all or, represented with *).
  18 # Users are auto-opped on join with random delay.
  19 # There is a possibility to use dns aliases (for example dyndns.org) for getting the correct address.
  20 # The auto-op list is stored into ~/.irssi/autoop
  21 #
  22 # To get the dynamic addresses to be refreshed automatically, set value to autoop_dynamic_refresh (in hours)
  23 # The value will be used next time the script is loaded (at startup or manual load)
  24 #
  25 # NOTICE: the datafile is in completely different format than in 1.0 and this version cannot read it. Sorry.
  26 #
  27 
  28 # COMMANDS:
  29 #
  30 # autoop_show - Displays list of auto-opped hostmasks & channels
  31 #               The current address of dynamic host is displayed in parenthesis
  32 #
  33 # autoop_add - Add new auto-op. Parameters hostmask, channel (or *) and dynamic flag
  34 #
  35 #    Dynamic flag has 3 different values:
  36 #      0: treat host as a static ip
  37 #      1: treat host as an alias for dynamic ip
  38 #      2: treat host as an alias for dynamic ip, but do not resolve the ip (not normally needed)
  39 #
  40 # autoop_del - Remove auto-op
  41 #
  42 # autoop_save - Save auto-ops to file (done normally automatically)
  43 #
  44 # autoop_load - Load auto-ops from file (use this if you have edited the autoop -file manually)
  45 #
  46 # autoop_check - Check all channels and op people needed
  47 #
  48 # autoop_dynamic - Refresh dynamic addresses (automatically if parameter set)
  49 #
  50 # Data is stored in ~/.irssi/autoop
  51 # format: host	channels	flag
  52 # channels separated with comma
  53 # one host per line
  54 
  55 my (%oplist);
  56 my (@opitems);
  57 srand();
  58 
  59 #resolve dynamic host
  60 sub resolve_host {
  61 	my ($host, $dyntype) = @_;
  62 
  63 	if (my $iaddr = inet_aton($host)) {
  64 		if ($dyntype ne "2") {
  65 			if (my $newhost = gethostbyaddr($iaddr, AF_INET)) {
  66 				return $newhost;
  67 			} else {
  68 				return inet_ntoa($iaddr);
  69 			}
  70 		} else {
  71 			return inet_ntoa($iaddr);
  72 		}
  73 	}
  74 	return "error";
  75 }
  76 
  77 # return list of dynamic hosts with real addresses
  78 sub fetch_dynamic_hosts {
  79 	my %hostcache;
  80 	my $resultext;
  81 	foreach my $item (@opitems) {
  82 		next if ($item->{dynamic} ne "1" && $item->{dynamic} ne "2");
  83 
  84 		my (undef, $host) = split(/\@/, $item->{mask}, 2);
  85 
  86 		# fetch the host's real address (if not cached)
  87 		unless ($hostcache{$host}) {
  88 			$hostcache{$host} = resolve_host($host, $item->{dynamic});
  89 			$resultext .= $host . "\t" . $hostcache{$host} . "\n";
  90 		}
  91 	}
  92 	chomp $resultext;
  93 	return $resultext;
  94 }
  95 
  96 # fetch real addresses for dynamic hosts
  97 sub cmd_change_dynamic_hosts {
  98 	pipe READ, WRITE;
  99 	my $pid = fork();
 100 
 101 	unless (defined($pid)) {
 102 		Irssi::print("Can't fork - aborting");
 103 		return;
 104 	}
 105 
 106 	if ($pid > 0) {
 107 		# the original process, just add a listener for pipe
 108 		close (WRITE);
 109 		Irssi::pidwait_add($pid);
 110 		my $target = {fh => \*READ, tag => undef};
 111 		$target->{tag} = Irssi::input_add(fileno(READ), INPUT_READ, \&read_dynamic_hosts, $target);
 112 	} else {
 113 		# the new process, fetch addresses and write to the pipe
 114 		print WRITE fetch_dynamic_hosts;
 115 		close (READ);
 116 		close (WRITE);
 117 		POSIX::_exit(1);
 118 	}
 119 }
 120 
 121 # get dynamic hosts from pipe and change them to users
 122 sub read_dynamic_hosts {
 123  	my $target = shift;
 124 	my $rh = $target->{fh};
 125 	my %hostcache;
 126 
 127 	while (<$rh>) {
 128 		chomp;
 129 		my ($dynhost, $realhost, undef) = split (/\t/, $_, 3);
 130 		$hostcache{$dynhost} = $realhost;
 131 	}
 132 
 133 	close($target->{fh});
 134 	Irssi::input_remove($target->{tag});
 135 
 136 	my $mask;
 137 	my $count = 0;
 138 	undef %oplist if (%oplist);
 139 
 140 	foreach my $item (@opitems) {
 141 		if ($item->{dynamic} eq "1" || $item->{dynamic} eq "2") {
 142 			my ($user, $host) = split(/\@/, $item->{mask}, 2);
 143 
 144 			$count++ if ($item->{dynmask} ne $hostcache{$host});
 145 			$item->{dynmask} = $hostcache{$host};
 146 			$mask = $user . "\@" . $hostcache{$host};
 147 		} else {
 148 			$mask = $item->{mask};
 149 		}
 150 
 151 		foreach my $channel (split (/,/,$item->{chan})) {
 152 			$oplist{$channel} .= "$mask ";
 153 		}
 154 	}
 155 	chop %oplist;
 156 	Irssi::print("$count dynamic hosts changed") if ($count > 0);
 157 }
 158 
 159 # Save data to file
 160 sub cmd_save_autoop {
 161 	my $file = Irssi::get_irssi_dir."/autoop";
 162 	open FILE, "> $file" or return;
 163 
 164 	foreach my $item (@opitems) {
 165 		printf FILE ("%s\t%s\t%s\n", $item->{mask}, $item->{chan}, $item->{dynamic});
 166 	}
 167 
 168 	close FILE;
 169 	Irssi::print("Auto-op list saved to $file");
 170 }
 171 
 172 # Load data from file
 173 sub cmd_load_autoop {
 174 	my $file = Irssi::get_irssi_dir."/autoop";
 175 	open FILE, "< $file" or return;
 176 	undef @opitems if (@opitems);
 177 
 178 	while (<FILE>) {
 179 		chomp;
 180 		my ($mask, $chan, $dynamic, undef) = split (/\t/, $_, 4);
 181 		my $item = {mask=>$mask, chan=>$chan, dynamic=>$dynamic, dynmask=>undef};
 182 		push (@opitems, $item);
 183 	}
 184 
 185 	close FILE;
 186 	Irssi::print("Auto-op list reloaded from $file");
 187 	cmd_change_dynamic_hosts;
 188 }
 189 
 190 # Show who's being auto-opped
 191 sub cmd_show_autoop {
 192 	my %list;
 193 	foreach my $item (@opitems) {
 194 		foreach my $channel (split (/,/,$item->{chan})) {
 195 			$list{$channel} .= "\n" . $item->{mask};
 196 			$list{$channel} .= " (" . $item->{dynmask} . ")" if ($item->{dynmask});
 197 		}
 198 	}
 199 
 200 	Irssi::print("All channels:" . $list{"*"}) if (exists $list{"*"});
 201 	delete $list{"*"}; #this is already printed, so remove it
 202 	foreach my $channel (sort (keys %list)) {
 203 		Irssi::print("$channel:" . $list{$channel});
 204 	}
 205 }
 206 
 207 # Add new auto-op
 208 sub cmd_add_autoop {
 209 	my ($data) = @_;
 210 	my ($mask, $chan, $dynamic, undef) = split(" ", $data, 4);
 211 	my $found = 0;
 212 
 213 	if ($chan eq "" || $mask eq "" || !($mask =~ /.+!.+@.+/)) {
 214 		Irssi::print("Invalid hostmask. It must contain both ! and @.") if (!($mask =~ /.+!.+@.+/));
 215 		Irssi::print("Usage: /autoop_add <hostmask> <*|#channel> [dynflag]");
 216 		Irssi::print("Dynflag: 0 normal, 1 dynamic, 2 dynamic without resolving");
 217 		return;
 218 	}
 219 
 220 	foreach my $item (@opitems) {
 221 		next unless ($item->{mask} eq $mask);
 222 		$found = 1;
 223 		$item->{chan} .= ",$chan";
 224 		last;
 225 	}
 226 
 227 	if ($found == 0) {
 228 		$dynamic = "0" unless ($dynamic eq "1" || $dynamic eq "2");
 229 		my $item = {mask=>$mask, chan=>$chan, dynamic=>$dynamic, dynmask=>undef};
 230 		push (@opitems, $item);
 231 	}
 232 
 233 	$oplist{$chan} .= " $mask";
 234 
 235 	Irssi::print("Added auto-op: $chan: $mask");
 236 }
 237 
 238 # Remove autoop
 239 sub cmd_del_autoop {
 240 	my ($data) = @_;
 241 	my ($mask, $channel, undef) = split(" ", $data, 3);
 242 
 243 	if ($channel eq "" || $mask eq "") {
 244 		Irssi::print("Usage: /autoop_del <hostmask> <*|#channel>");
 245 		return;
 246 	}
 247 
 248 	my $i=0;
 249 	foreach my $item (@opitems) {
 250 		if ($item->{mask} eq $mask) {
 251 			if ($channel eq "*" || $item->{chan} eq $channel) {
 252 				splice @opitems, $i, 1;
 253 				Irssi::print("Removed: $mask");
 254 			} else {
 255 				my $newchan;
 256 				foreach my $currchan (split (/,/,$item->{chan})) {
 257 					if ($channel eq $currchan) {
 258 						Irssi::print("Removed: $channel from $mask");
 259 					} else {
 260 						$newchan .= $currchan . ",";
 261 					}
 262 				}
 263 				chop $newchan;
 264 				Irssi::print("Couldn't remove $channel from $mask") if ($item->{chan} eq $newchan);
 265 				$item->{chan} = $newchan;
 266 			}
 267 			last;
 268 		}
 269 		$i++;
 270 	}
 271 }
 272 
 273 # Do the actual opping
 274 sub do_autoop {
 275 	my $target = shift;
 276 
 277 	Irssi::timeout_remove($target->{tag});
 278 
 279 	# nick has to be fetched again, because $target->{nick}->{op} is not updated
 280 	my $nick = $target->{chan}->nick_find($target->{nick}->{nick});
 281 
 282 	# if nick is changed during delay, it will probably be lost here...
 283 	if ($nick->{nick} ne "") {
 284 		if ($nick->{host} eq $target->{nick}->{host}) {
 285 			$target->{chan}->command("op " . $nick->{nick}) unless ($nick->{op});
 286 		} else {
 287 			Irssi::print("Host changed for nick during delay: " . $nick->{nick});
 288 		}
 289 	}
 290 	undef $target;
 291 }
 292 
 293 # Someone joined, might be multiple person. Check if opping is needed
 294 sub event_massjoin {
 295 	my ($channel, $nicklist) = @_;
 296 	my @nicks = @{$nicklist};
 297 
 298 	return if (!$channel->{chanop});
 299 
 300 	my $masks = $oplist{"*"} . " " . $oplist{$channel->{name}};
 301 
 302 	foreach my $nick (@nicks) {
 303 		my $host = $nick->{host};
 304 		$host=~ s/^~//g; # remove this if you don't want to strip ~ from username (no ident)
 305 		next unless ($channel->{server}->masks_match($masks, $nick->{nick}, $host));
 306 
 307 		my $min_delay = Irssi::settings_get_int("autoop_min_delay");
 308 		my $max_delay = Irssi::settings_get_int("autoop_max_delay") - $min_delay;
 309 		my $delay = int(rand($max_delay)) + $min_delay;
 310 
 311 		my $target = {nick => $nick, chan => $channel, tag => undef};
 312 
 313 		$target->{tag} = Irssi::timeout_add($delay, 'do_autoop', $target);
 314 	}
 315 
 316 }
 317 
 318 # Check channel op status
 319 sub do_channel_check {
 320 	my $target = shift;
 321 
 322 	Irssi::timeout_remove($target->{tag});
 323 
 324 	my $channel = $target->{chan};
 325 	my $server = $channel->{server};
 326 	my $masks = $oplist{"*"} . " " . $oplist{$channel->{name}};
 327 	my $nicks = "";
 328 
 329 	foreach my $nick ($channel->nicks()) {
 330 		next if ($nick->{op});
 331 
 332 		my $host = $nick->{host};
 333 		$host=~ s/^~//g; # remove this if you don't want to strip ~ from username (no ident)
 334 
 335 		if ($server->masks_match($masks, $nick->{nick}, $host)) {
 336 			$nicks = $nicks . " " . $nick->{nick};
 337 		}
 338 	}
 339 	$channel->command("op" . $nicks) unless ($nicks eq "");
 340 
 341 	undef $target;
 342 }
 343 
 344 #check people needing opping after getting ops
 345 sub event_nickmodechange {
 346 	my ($channel, $nick, $setby, $mode, $type) = @_;
 347 
 348 	return unless (($mode eq '@') && ($type eq '+'));
 349 
 350 	my $server = $channel->{server};
 351 
 352 	return unless ($server->{nick} eq $nick->{nick});
 353 
 354 	my $min_delay = Irssi::settings_get_int("autoop_min_delay");
 355 	my $max_delay = Irssi::settings_get_int("autoop_max_delay") - $min_delay;
 356 	my $delay = int(rand($max_delay)) + $min_delay;
 357 
 358 	my $target = {chan => $channel, tag => undef};
 359 
 360 	$target->{tag} = Irssi::timeout_add($delay, 'do_channel_check', $target);
 361 }
 362 
 363 #Check all channels / all users if someone needs to be opped
 364 sub cmd_autoop_check {
 365 	my ($data, $server, $witem) = @_;
 366 
 367 	foreach my $channel ($server->channels()) {
 368 		Irssi::print("Checking: " . $channel->{name});
 369 		next if (!$channel->{chanop});
 370 
 371 		my $masks = $oplist{"*"} . " " . $oplist{$channel->{name}};
 372 
 373 		foreach my $nick ($channel->nicks()) {
 374 			next if ($nick->{op});
 375 
 376 			my $host = $nick->{host};
 377 			$host=~ s/^~//g; # remove this if you don't want to strip ~ from username (no ident)
 378 
 379 			if ($server->masks_match($masks, $nick->{nick}, $host)) {
 380 				$channel->command("op " . $nick->{nick}) if (!$nick->{op});
 381 			}
 382 		}
 383 	}
 384 }
 385 
 386 #Set dynamic refresh period.
 387 sub set_dynamic_refresh {
 388 	my $refresh = Irssi::settings_get_int("autoop_dynamic_refresh");
 389 	return if ($refresh == 0);
 390 
 391 	Irssi::print("Dynamic host refresh set for $refresh hours");
 392 	Irssi::timeout_add($refresh*3600000, 'cmd_change_dynamic_hosts', undef);
 393 }
 394 
 395 Irssi::command_bind('autoop_show', 'cmd_show_autoop');
 396 Irssi::command_bind('autoop_add', 'cmd_add_autoop');
 397 Irssi::command_bind('autoop_del', 'cmd_del_autoop');
 398 Irssi::command_bind('autoop_save', 'cmd_save_autoop');
 399 Irssi::command_bind('autoop_load', 'cmd_load_autoop');
 400 Irssi::command_bind('autoop_check', 'cmd_autoop_check');
 401 Irssi::command_bind('autoop_dynamic', 'cmd_change_dynamic_hosts');
 402 Irssi::signal_add_last('massjoin', 'event_massjoin');
 403 Irssi::signal_add_last('setup saved', 'cmd_save_autoop');
 404 Irssi::signal_add_last('setup reread', 'cmd_load_autoop');
 405 Irssi::signal_add_last("nick mode changed", "event_nickmodechange");
 406 Irssi::settings_add_int('autoop', 'autoop_max_delay', 15000);
 407 Irssi::settings_add_int('autoop', 'autoop_min_delay', 1000);
 408 Irssi::settings_add_int('autoop', 'autoop_dynamic_refresh', 0);
 409 
 410 
 411 cmd_load_autoop;
 412 set_dynamic_refresh;