1use strict; 2use Irssi; 3use POSIX; 4use Socket; 5use 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 55my (%oplist); 56my (@opitems); 57srand(); 58 59#resolve dynamic host 60sub 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 78sub 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 97sub 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 122sub 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 160sub 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 173sub 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 191sub 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 208sub 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 239sub 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 274sub 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 294sub 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 319sub 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 345sub 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 364sub 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. 387sub 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 395Irssi::command_bind('autoop_show', 'cmd_show_autoop'); 396Irssi::command_bind('autoop_add', 'cmd_add_autoop'); 397Irssi::command_bind('autoop_del', 'cmd_del_autoop'); 398Irssi::command_bind('autoop_save', 'cmd_save_autoop'); 399Irssi::command_bind('autoop_load', 'cmd_load_autoop'); 400Irssi::command_bind('autoop_check', 'cmd_autoop_check'); 401Irssi::command_bind('autoop_dynamic', 'cmd_change_dynamic_hosts'); 402Irssi::signal_add_last('massjoin', 'event_massjoin'); 403Irssi::signal_add_last('setup saved', 'cmd_save_autoop'); 404Irssi::signal_add_last('setup reread', 'cmd_load_autoop'); 405Irssi::signal_add_last("nick mode changed", "event_nickmodechange"); 406Irssi::settings_add_int('autoop', 'autoop_max_delay', 15000); 407Irssi::settings_add_int('autoop', 'autoop_min_delay', 1000); 408Irssi::settings_add_int('autoop', 'autoop_dynamic_refresh', 0); 409 410 411cmd_load_autoop; 412set_dynamic_refresh; 413