/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;