html/securemsg.pl
1 # Thanks to Geert and Karl "Sique" Siegemund on their work on the whitelist.pl script that is the basis for my work.
2 #
3 # Supports multiple servers
4 #
5 # /set securemsg_nicks phyber etc
6 # nicks that are allowed to msg us (whitelist checks for a valid nick before a valid host)
7 #
8 # /set securemsg_hosts *!*@*isp.com *!ident@somewhere.org
9 # Hosts that are allowed to message us, space delimited
10 #
11 # /sm add nick <list of nicks>
12 # puts new nicks into the whitelist_nicks list
13 #
14 # /sm add host <list of hosts>
15 # puts new hosts into the whitelist_hosts list
16 #
17 # /sm del nick <list of nicks>
18 # removes the nicks from whitelist_nicks
19 #
20 # /sm del host <list of hosts>
21 # removes the hosts from whitelist_hosts
22 #
23 # /sm nicks
24 # shows the current whitelist_nicks
25 #
26 # /sm hosts
27 # shows the current whitelist_hosts
28 #
29 # /sm accept <nick> [message]
30 # accept chat from nick
31 #
32 # /sm reject <nick> [message]
33 # reject chat from nick
34 #
35 # /sm clear <nick>
36 # clears messages from nick without ignoring
37 #
38 ##
39
40 use strict;
41 use Irssi;
42 use Irssi::Irc;
43 use Irssi::UI;
44 use Irssi::TextUI;
45
46
47 use vars qw($VERSION %IRSSI);
48 $VERSION = "2.1";
49 my $APPVERSION = "Securemsg v2.1";
50 %IRSSI = (
51 authors => "Jari Matilainen, a lot of code borrowed from whitelist.pl by David O\'Rourke and Karl Siegemund",
52 contact => "vague`!#irssi\@freenode on irc ",
53 name => "securemsg",
54 description => "An irssi adaptation of securequery.mrc found in the Acidmax mIRC script. :), now with multiserver support",
55 licence => "GPLv2",
56 changed => "10.09.2007 11:30pm GST"
57 );
58
59 my $whitenick;
60 my $whitehost;
61 my $tstamp;
62 my %messages = ();
63
64 # A mapping to convert simple regexp (* and ?) into Perl regexp
65 my %htr = ( );
66 foreach my $i (0..255) {
67 my $ch = chr($i);
68 $htr{$ch} = "\Q$ch\E";
69 }
70 $htr{'?'} = '.';
71 $htr{'*'} = '.*';
72
73 # A list of settings we can use and change
74 my %types = ( 'nicks' => 'securemsg_nicks',
75 'hosts' => 'securemsg_hosts' );
76
77 sub lc_host($) {
78 my ($host) = @_;
79 $host =~ s/(.+)\@(.+)/sprintf("%s@%s", $1, lc($2));/eg;
80 return $host;
81 }
82
83 sub timestamp {
84 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
85 my @timestamp = ($year+1900,$mon+1,$mday,$hour,$min,$sec);
86 if($timestamp[1]<10) {
87 $timestamp[1] = "0".$timestamp[1];
88 }
89 if($timestamp[2]<10) {
90 $timestamp[2] = "0".$timestamp[2];
91 }
92 if($timestamp[3]<10) {
93 $timestamp[3] = "0".$timestamp[3];
94 }
95 if($timestamp[4]<10) {
96 $timestamp[4] = "0".$timestamp[4];
97 }
98 $tstamp = "$timestamp[0]/$timestamp[1]/$timestamp[2] $timestamp[3]:$timestamp[4] ";
99 }
100
101 # This one gets called from IRSSI if we get a private message (PRIVMSG)
102 sub securemsg_check {
103 my ($server, $msg, $nick, $address) = @_;
104 my $nicks = Irssi::settings_get_str('securemsg_nicks');
105 my $hosts = Irssi::settings_get_str('securemsg_hosts');
106 my $mmsg = Irssi::settings_get_str('securemsg_customhold');
107 my $hostmask = "$nick!$address";
108 $nick = lc($nick);
109 return if $server->ignore_check($nick,"","","",MSGLEVEL_MSGS);
110
111 # Are we already talking?
112 return if $server->query_find($nick);
113 return if $server->{nick} eq $nick;
114
115 # Nicks are the easiest to handle with the least computational effort.
116 # So do them before hosts and networks.
117 foreach my $whitenick (split(/\s+/, "$nicks")) {
118 $nick = lc($nick);
119 $whitenick = lc($whitenick);
120 # Simple check first: Is the nick itself whitelisted?
121 return if ($nick eq $whitenick);
122 }
123
124 # Hostmasks are somewhat more sophisticated, because they allow wildcards
125 foreach my $whitehost (split(/\s+/, "$hosts")) {
126 # Allow if the hostmask matches
127 return if $server->mask_match_address($whitehost,"*",$hostmask);
128 }
129
130 # stop if the message isn't from a whitelisted address
131 # print a notice if that setting is enabled
132 # this could flood your status window if someone is flooding you with messages
133
134 if ((!defined $mmsg) || ($mmsg eq " ") || ($mmsg eq "")) {
135 $mmsg = "Please standby for acknowledgement. I am using «$APPVERSION» for irssi. You will be notified if accepted. Until then your messages will be ignored.";
136 }
137 else {
138 $mmsg = $mmsg." «$APPVERSION»";
139 }
140
141 $server->command("^NOTICE $nick $mmsg") if(!exists $messages{$nick}{$server->{tag}});
142
143 #Save message from $nick
144 timestamp();
145 my $channel;
146 my @channels = $server->channels();
147 foreach my $chan (@channels) {
148 if($chan->nick_find_mask($nick)) {
149 $channel = $chan;
150 last;
151 }
152 }
153
154 my $tmpmsg = $tstamp."<".$nick."!".(($channel)?$channel->{name}:"none")."@".lc($server->{tag})."> ".$msg;
155 push @{$messages{$nick}{lc($server->{tag})}{messages}},$tmpmsg;
156
157 refresh_securemsg();
158 Irssi::command_bind("sm accept $nick",\&cmd_accept);
159 Irssi::command_bind("sm reject $nick",\&cmd_reject);
160 Irssi::command_bind("sm accept $nick net",\&cmd_accept);
161 Irssi::command_bind("sm reject $nick net",\&cmd_reject);
162 Irssi::command_bind("sm accept $nick net ".lc($server->{tag}),\&cmd_accept);
163 Irssi::command_bind("sm reject $nick net ".lc($server->{tag}),\&cmd_reject);
164 Irssi::command_bind("sm show $nick",\&cmd_show);
165 Irssi::command_bind("sm show $nick net ",\&cmd_show);
166 Irssi::command_bind("sm show $nick net ".lc($server->{tag}),\&cmd_show);
167 Irssi::signal_stop();
168 return;
169 }
170
171 sub usage {
172 print("Usage: sm add|del <nick>|<host> list of nicks/hosts | sm nicks|hosts | sm accept|reject <nick> [net <chatnet>] [message] | sm show <nick> [net <chatnet>] | sm help");
173 }
174
175 sub cmd_accept {
176 my ($args, $active_server, $witem) = @_;
177 my ($nick, $rest) = split /\s+/, $args, 2;
178 my ($arg, $net);
179 my $server;
180 my $mmsg = Irssi::settings_get_str('securemsg_customaccept');
181 my $msg;
182
183 if($rest =~ /^net/) {
184 ($arg, $net, $rest) = split /\s+/, $rest, 3;
185 }
186
187 $nick = lc($nick);
188 $net = lc($net);
189
190 if((!defined $nick) || ($nick eq "")) {
191 usage;
192 return;
193 }
194
195 return if(!exists $messages{$nick});
196
197 if((!defined $rest) || ($rest eq " ") || ($rest eq "")) {
198 if((!defined $mmsg) || ($mmsg eq " ") || ($mmsg eq "")) {
199 $rest = "Hold on, I'm switching windows...";
200 }
201 else {
202 $rest = $mmsg;
203 }
204 }
205
206 if(defined $net && !(($net eq " ") || ($net eq ""))) {
207 foreach (keys %{$messages{$nick}}) {
208 if(lc($_) eq $net) {
209 $server = Irssi::server_find_tag($_);
210 last;
211 }
212 }
213 }
214 elsif(keys(%{$messages{$nick}}) == 1) {
215 foreach (keys %{$messages{$nick}}) {
216 $server = Irssi::server_find_tag($_);
217 }
218 }
219 else {
220 print("You have to specify a chatnet, for example /sm accept john net EFNet");
221 return;
222 }
223
224 $server->command("MSG $nick $rest");
225
226 my $query = $server->query_find($nick);
227 $query->set_active();
228 foreach $msg (@{$messages{$nick}{lc($server->{tag})}{messages}}) {
229 $query->print("%B-%W!%B-%n $msg", MSGLEVEL_CLIENTCRAP);
230 }
231
232 if(keys(%{$messages{$nick}}) > 1) {
233 delete $messages{$nick}{lc($server->{tag})};
234 }
235 else {
236 delete $messages{$nick};
237 }
238
239 Irssi::command_unbind("sm accept $nick",\&cmd_accept);
240 Irssi::command_unbind("sm reject $nick",\&cmd_reject);
241 Irssi::command_unbind("sm accept $nick net",\&cmd_accept);
242 Irssi::command_unbind("sm reject $nick net",\&cmd_reject);
243 Irssi::command_unbind("sm accept $nick net ".lc($server->{tag}),\&cmd_accept);
244 Irssi::command_unbind("sm reject $nick net ".lc($server->{tag}),\&cmd_reject);
245 Irssi::command_unbind("sm show $nick",\&cmd_show);
246 Irssi::command_unbind("sm show $nick net ",\&cmd_show);
247 Irssi::command_unbind("sm show $nick net ".lc($server->{tag}),\&cmd_show);
248 refresh_securemsg();
249 }
250
251 sub cmd_reject {
252 my ($args, $active_server, $winit) = @_;
253 my ($nick, $rest) = split /\s+/, $args, 2;
254 my $time = Irssi::settings_get_str('securemsg_ignoretime');
255 my ($arg, $net);
256 my $server;
257
258 if($rest =~ /^net/) {
259 ($arg, $net, $rest) = split /\s+/, $rest, 3;
260 }
261
262 if((!defined $time) || ($time eq "") || ($time eq " ")) {
263 $time = "600";
264 }
265
266 $nick = lc($nick);
267 $net = lc($net);
268
269 if((!defined $nick) || ($nick eq "")) {
270 usage;
271 return;
272 }
273
274 if(defined $net && !(($net eq " ") || ($net eq ""))) {
275 foreach (keys %{$messages{$nick}}) {
276 if(lc($_) eq $net) {
277 $server = Irssi::server_find_tag($_);
278 last;
279 }
280 }
281 }
282 elsif(keys(%{$messages{$nick}}) == 1) {
283 foreach (keys %{$messages{$nick}}) {
284 $server = Irssi::server_find_tag($_);
285 }
286 }
287 else {
288 print("You have to specify a chatnet, for example /sm reject john net EFNet");
289 return;
290 }
291
292 if((defined $rest) && !(($rest eq " ") || ($rest eq ""))) {
293 $server->command("^NOTICE $nick $rest");
294 }
295
296 if(keys(%{$messages{$nick}}) > 1) {
297 delete $messages{$nick}{lc($server->{tag})};
298 }
299 else {
300 delete $messages{$nick};
301 }
302
303 Irssi::command_unbind("sm accept $nick",\&cmd_accept);
304 Irssi::command_unbind("sm reject $nick",\&cmd_reject);
305 Irssi::command_unbind("sm accept $nick net",\&cmd_accept);
306 Irssi::command_unbind("sm reject $nick net",\&cmd_reject);
307 Irssi::command_unbind("sm accept $nick net ".lc($server->{tag}),\&cmd_accept);
308 Irssi::command_unbind("sm reject $nick net ".lc($server->{tag}),\&cmd_reject);
309 Irssi::command_unbind("sm show $nick",\&cmd_show);
310 Irssi::command_unbind("sm show $nick net ",\&cmd_show);
311 Irssi::command_unbind("sm show $nick net ".lc($server->{tag}),\&cmd_show);
312 $server->command("^IGNORE -time $time $nick MSGS DCCMSGS NOTICES");
313 refresh_securemsg();
314 }
315
316 sub cmd_add {
317 my ($args, $server, $witem) = @_;
318 my $str = '';
319 my @list = ( );
320 my ($type, $rest) = split /\s+/, $args, 2;
321
322 # What type of settings we want to change?
323 if (($type eq "nick") || ($type eq "host")) {
324 $type = $type."s";
325 }
326 my $settings = $types{$type};
327
328 # If we didn't get a syntactically correct command, put out an error
329 if(!defined $settings && defined $type) {
330 usage;
331 return;
332 }
333
334 # Get the current value of the setting we want to change
335 my $str = Irssi::settings_get_str($settings) if defined $settings;
336 # What are we doing?
337 # Add the list to the end
338 $str .= " $rest";
339 # Convert into an array
340 @list = split /\s+/, $str;
341 # Make the array unique (see Perl FAQ)
342 undef my %saw;
343 @list = grep(!$saw{$_}++, @list);
344 # Put the array together
345 $str = join ' ', @list;
346
347 print "SecureMsg ${type}: $str";
348 Irssi::settings_set_str($settings, $str);
349 }
350
351 sub cmd_del {
352 my ($args, $server, $witem) = @_;
353 my @list = ( );
354 my ($type, $rest) = split /\s+/, $args, 2;
355
356 # What type of settings we want to change?
357 if (($type eq "nick") || ($type eq "host")) {
358 $type = $type."s";
359 }
360 my $settings = $types{$type};
361
362 # If we didn't get a syntactically correct command, put out an error
363 if(!defined $settings && defined $type) {
364 usage;
365 return;
366 }
367
368 my $str = Irssi::settings_get_str($settings);
369
370 # Convert the list into an array
371 @list = split /\s+/, $str;
372 # Escape all letters to protect the Perl Regexp special characters
373 $rest =~ s/(.)/$htr{$1}/g;
374 # Convert the removal list into a Perl regexp
375 $rest =~ s/\s+/$|^/g;
376 # Use grep() to filter out all occurences of the removal list
377 $str = join(' ', grep {!/^$rest$/} @list);
378
379 print "SecureMsg ${type}: $str";
380 Irssi::settings_set_str($settings, $str);
381 }
382
383 sub cmd_nicks {
384 print "SecureMsg nicks: ".Irssi::settings_get_str($types{nicks});
385 }
386
387 sub cmd_hosts {
388 print "SecureMsg hosts: ".Irssi::settings_get_str($types{hosts});
389 }
390
391 sub cmd_help {
392 # usage;
393 print ( <<EOF
394 Commands:
395 SM HELP - SHOWS THIS HELP
396 SM ADD NICK|HOST <nicks>|<hosts> - ADDS/DELETES A SPACE SEPARATED LIST OF NICKS OR HOSTS
397 SM NICKS|HOSTS - DISPLAYS THE CURRENT WHITELISTED NICKS OR HOSTS
398 SM ACCEPT <nick> [net <chatnet>] [message] - ALLOWS MSG'S FROM nick
399 SM REJECT <nick> [net <chatnet>] [message] - DOESN'T ALLOW MESSAGES FROM nick
400 SM SHOW <nick> [net <chatnet>] - SHOWS CURRENT MESSAGES FROM nick WITHOUT ACCEPTING OR REJECTING nick
401 EOF
402 );
403 }
404
405 sub cmd_show {
406 my ($args, $server, $witem) = @_;
407 my ($nick, $rest) = split /\s+/, $args, 2;
408 my ($arg, $net);
409 my $server;
410
411 if($rest =~ /^net/) {
412 ($arg, $net) = split /\s+/, $rest, 2;
413 }
414
415 $net = lc($net);
416
417 if((!defined $nick) || (!exists $messages{$nick})) {
418 usage;
419 return;
420 }
421
422 if(defined $net && !(($net eq " ") || ($net eq ""))) {
423 foreach (keys %{$messages{$nick}}) {
424 if($_ eq $net) {
425 $server = Irssi::server_find_tag($_);
426 last;
427 }
428 }
429 }
430 elsif(keys(%{$messages{$nick}}) == 1) {
431 foreach (keys %{$messages{$nick}}) {
432 $server = Irssi::server_find_tag($_);
433 }
434 }
435 else {
436 print("You have to specify a chatnet, for example /sm reject john net EFNet");
437 return;
438 }
439
440 foreach my $msg (@{$messages{$nick}{lc($server->{tag})}{messages}}) {
441 print CLIENTCRAP "%B-%W!%B-%n $msg";
442 }
443 }
444
445 sub securemsg {
446 my ($item,$get_size_only) = @_;
447 my $result = 0;
448 my $nicks = "";
449 foreach my $nick (keys %messages) {
450 foreach my $tag (keys %{$messages{$nick}}) {
451 if ($nicks eq "") {
452 if(keys %{$messages{$nick}} > 1) {
453 $nicks = $nick."@".$tag;
454 }
455 else {
456 $nicks = $nick;
457 }
458 }
459 else {
460 if(keys %{$messages{$nick}} > 1) {
461 $nicks = $nicks.", ".$nick."@".$tag;
462 }
463 else {
464 $nicks = $nicks.", ".$nick;
465 }
466 }
467 $result++;
468 }
469 }
470 if ($result ne 0) {
471 $result = $result." - \%_$nicks\%_";
472 }
473 $item->default_handler($get_size_only, undef, $result, 0);
474 }
475
476 sub refresh_securemsg {
477 Irssi::statusbar_items_redraw('securemsg');
478 }
479
480 foreach (keys(%types)) {
481 Irssi::settings_add_str('SecureMsg', $types{$_}, '');
482 }
483
484 Irssi::settings_add_str('Securemsg', 'securemsg_customhold', '');
485 Irssi::settings_add_str('Securemsg', 'securemsg_customaccept', '');
486 Irssi::settings_add_str('Securemsg', 'securemsg_ignoretime', '');
487
488 Irssi::signal_add_first('message private', \&securemsg_check);
489 Irssi::statusbar_item_register('securemsg', '{sb msgs: $0-}', 'securemsg');
490
491 Irssi::command_bind 'sm' => sub {
492 my ( $data, $server, $item ) = @_;
493 $data =~ s/\s+$//g;
494 Irssi::command_runsub ('sm', $data, $server, $item ) ;
495 };
496 Irssi::command_bind('sm add',\&cmd_add);
497 Irssi::command_bind('sm del',\&cmd_del);
498 Irssi::command_bind('sm nicks',\&cmd_nicks);
499 Irssi::command_bind('sm hosts',\&cmd_hosts);
500 Irssi::command_bind('sm show',\&cmd_show);
501 Irssi::command_bind('sm accept',\&cmd_accept);
502 Irssi::command_bind('sm reject',\&cmd_reject);
503 Irssi::command_bind('sm help',\&cmd_help);
504 Irssi::command_bind('sm add host',\&cmd_add);
505 Irssi::command_bind('sm add nick',\&cmd_add);
506 Irssi::command_bind('sm del host',\&cmd_del);
507 Irssi::command_bind('sm del nick',\&cmd_del);
508
509 refresh_securemsg();