html/mailcheck_imap.pl


   1 # mailcheck_imap.pl
   2 
   3 # Contains code from centericq.pl (public domain) and imapbiff (GPL) and
   4 # hence this is also GPL'd.
   5 
   6 use vars qw($VERSION %IRSSI);
   7 $VERSION = "0.5";
   8 %IRSSI = (
   9     authors     => "David \"Legooolas\" Gardner",
  10     contact     => "irssi\@icmfp.com",
  11     name        => "mailcheck_imap",
  12     description => "Staturbar item which indicates how many new emails you have in the specified IMAP[S] mailbox",
  13     license     => "GNU GPLv2",
  14     url         => "http://icmfp.com/irssi",
  15 );
  16 
  17 
  18 # TODO:
  19 #
  20 # - command to show status, so we can see if we are currently connected
  21 #  - add to statusbar item to say connected/not
  22 #
  23 # ? get user to type in password instead of storing it in a setting...
  24 #  - eg. /mailcheck_imap_pass <password>
  25 #
  26 # - settings
  27 #  - execute arbitrary command (with /exec?) on new mail?
  28 #   - for 'spoing' or something  ;)
  29 #  - auto-reconnect on/off
  30 #
  31 #
  32 # LATER:
  33 # - show subject/sender/whatever of new mail (customizable)
  34 # - multiple accounts?
  35 # - multiple mailboxes?
  36 
  37 
  38 # Known bugs: segfaults on exit of irssi when script loaded  :/
  39 
  40 
  41 use Irssi;
  42 use Irssi::TextUI;
  43 
  44 
  45 use strict;
  46 use IO::Socket;
  47 
  48 # TODO : avoid requiring SSL when it's not in use?
  49 #if (Irssi::settings_get_bool('mailcheck_imap_use_ssl')) {
  50 #	Irssi::print("Using SSL.") if $debug_msgs;
  51 #	$port = 993;
  52         require IO::Socket::SSL;
  53 #  - you need the package libio-socket-ssl-perl on Debian
  54 #}
  55 
  56 #
  57 # TODO : Set up signal handling for clean shutdown...
  58 #
  59 #$SIG{'ALRM'} = sub { die "socket timeout" };
  60 #$SIG{'QUIT'} = 'cleanup';
  61 #$SIG{'HUP'}  = 'cleanup';
  62 #$SIG{'INT'}  = 'cleanup';
  63 #$SIG{'KILL'} = 'cleanup';
  64 #$SIG{'TERM'} = 'cleanup';
  65 
  66 
  67 
  68 sub draw_box ($$$$) {
  69   my ($title, $text, $footer, $colour) = @_;
  70   my $box = '';
  71   $box .= '%R,--[%n%9%U'.$title.'%U%9%R]%n'."\n";
  72   foreach (split(/\n/, $text)) {
  73     $box .= '%R|%n '.$_."\n";
  74   }
  75   $box .= '%R`--<%n'.$footer.'%R>->%n';
  76   $box =~ s/%.//g unless $colour;
  77   return $box;
  78 }
  79 
  80 
  81 sub show_help() {
  82   my $help = $IRSSI{name}." ".$VERSION."
  83 /mailcheck_imap_help
  84         Display this help.
  85 /mailcheck_imap
  86         Check for new mail immediately, opening the connection if required.
  87 /mailcheck_imap_stop
  88         Close connection to server and stop checking for new mail.
  89 /set mailcheck_imap
  90         Show all mailcheck_imap settings.
  91         Note: You need to set at least host, user and password.
  92 /statusbar <name> add mailcheck_imap
  93         Add statusbar item for mailcheck.
  94 
  95 
  96 Formats in theme for statusbar item:
  97 (number of new mails in $0, total number of message in $1)
  98     sb_mailcheck_imap = \"{sb Mail: $0 new, $1 total}\";
  99     sb_mailcheck_imap_zero = \"{sb Mail: None new, $1 total}\";
 100 
 101 Format in theme for 'new mail arrived' message in current window:
 102 (number of new mails in $0, total number of message in $1)
 103             mailcheck_imap_echo = \"You have $0 new message(s)!\";
 104 
 105 Note: You have to set at least the mailcheck_imap_host, user,
 106             and password settings.
 107 
 108 IMPORTANT NOTE: As this stores the password in your irssi config
 109 file, you should really set the mode of the file to 0600 so that
 110 it's only readable by your user.
 111 ";
 112   my $text = "";
 113   foreach (split(/\n/, $help)) {
 114     $_ =~ s/^\/(.*)$/%9\/$1%9/;
 115     $text .= $_."\n";
 116   }
 117   print CLIENTCRAP draw_box($IRSSI{name}, $text, "Help", 1);
 118 }
 119 
 120 
 121 sub cmd_mailcheck_imap_help {
 122   show_help();
 123 }
 124 
 125 
 126 #
 127 # Global variables.
 128 #
 129 my $handle;
 130 my ($logged_in, $sleep);
 131 my ($last_refresh_time, $refresh_tag);
 132 my ($new_messages, $old_new_messages);
 133 my ($total_messages, $old_total_messages);
 134 
 135 $handle    = 0;
 136 $logged_in = 0;
 137 $old_new_messages = -1;
 138 $old_total_messages = -1;
 139 
 140 
 141 #
 142 # Subroutine to update status, called every N seconds.
 143 #
 144 sub refresh_mailcheck_imap {
 145 
 146   # For now, just print a message and return  :)
 147   Irssi::print("update hit.") if Irssi::settings_get_bool('mailcheck_imap_debug');
 148 
 149   # ensure we have details for the login..
 150   if(!check_details()) {
 151     return 0;
 152   }
 153 
 154   if(!$handle) {
 155     if(!setup_socket()) {
 156       error("Couldn't setup socket to imap server!",0);
 157       return 0;
 158     }
 159   }
 160   Irssi::print("Socket is setup.") if Irssi::settings_get_bool('mailcheck_imap_debug');
 161 
 162   if(!$logged_in) {
 163     if(!login()) {
 164       return 0;
 165     }
 166   }
 167   $new_messages = check_imap("UNSEEN");
 168   $total_messages = check_imap("MESSAGES");
 169 
 170   $new_messages = 0 if (! $new_messages);
 171   $total_messages = 0 if (! $total_messages);
 172 
 173   if ($new_messages eq "-1" || $total_messages eq "-1") {
 174     Irssi::print("check_imap returned an error, no updates.") if Irssi::settings_get_bool('mailcheck_imap_debug');
 175   }
 176 
 177   # update statusbar if changed rather than updating every the time...
 178   if(($new_messages != $old_new_messages) ||
 179      ($total_messages != $old_total_messages)) {
 180     update_statusbar_item();
 181   }
 182 
 183 
 184   # TODO : This doesn't work if you get a sequence such as:
 185   #        check -> arrive, delete, arrive -> check
 186   #        as it is just done on the number of unseen messages and won't know..
 187   if(($new_messages > $old_new_messages) &&
 188      (Irssi::settings_get_bool('mailcheck_imap_echo_new_in_window'))) {
 189     # If set, echo to the current window...
 190     my $theme = Irssi::current_theme();
 191     my $format = $theme->format_expand("{mailcheck_imap_echo}");
 192 
 193     if ($format) {
 194       # use theme-specific look
 195       $format = $theme->format_expand("{mailcheck_imap_echo $new_messages $total_messages}", Irssi::EXPAND_FLAG_IGNORE_REPLACES);
 196     } else {
 197       # use the default look
 198       $format = "mailcheck_imap: You have ".$new_messages." new message(s).";
 199     }
 200 
 201     print CLIENTCRAP $format;
 202   }
 203   $old_new_messages = $new_messages;
 204   $old_total_messages = $total_messages;
 205 
 206   # Adding new timeout to make sure that this function will be called again
 207   if ($refresh_tag) {
 208     Irssi::timeout_remove($refresh_tag);
 209   }
 210   my $time = Irssi::settings_get_int('mailcheck_imap_interval');
 211   $refresh_tag = Irssi::timeout_add($time*1000, 'refresh_mailcheck_imap', undef);
 212 
 213   return 1;
 214 }
 215 
 216 
 217 #
 218 # Subroutine to setup socket handle.
 219 #
 220 sub setup_socket {
 221 	# Set an alarm in case we can not connect or get hung.  Older versions
 222 	# the IO::Socket perl module caused errors with the alarm we set before
 223 	# setting up the socket.  If this program dies in debug mode saying:
 224 	# "Alarm clock", then you can probably fix it by upgrading your perl
 225 	# IO module.
 226 	my ($host,$port);
 227 
 228 	$host = Irssi::settings_get_str('mailcheck_imap_host');
 229 	$port = Irssi::settings_get_int('mailcheck_imap_port');
 230 
 231 	# change port number if SSL enabled and original imap port unchanged
 232 	if($port == 143 && Irssi::settings_get_bool('mailcheck_imap_use_ssl')) {
 233 	  $port = 993;
 234 	}
 235 
 236 	eval {
 237 		alarm 30;
 238 		Irssi::print("mailcheck_imap connecting to mail server...");
 239 
 240 		if (Irssi::settings_get_bool('mailcheck_imap_use_ssl')) {
 241 			Irssi::print("Using ssl...") if Irssi::settings_get_bool('mailcheck_imap_debug');
 242 			$handle = IO::Socket::SSL->new(Proto           => "tcp",
 243 			                               SSL_verify_mode => 0x00,
 244                                                        PeerAddr        => $host,
 245 			                               PeerPort        => $port,
 246 		                               	)
 247 			or error("Can't connect to port $port on $host: $!",0), return 0;
 248 		} else {
 249 			$handle = IO::Socket::INET->new(Proto    => "tcp",
 250 			                                PeerAddr => $host,
 251                                                         PeerPort => $port,
 252 		                               	)
 253 			or error("Can't connect to port $port on $host: $!",0), return 0;
 254 		}
 255 		$handle->autoflush(1);    # So output gets there right away.
 256 		Irssi::print("...done");
 257 		receive();
 258 		alarm 0;
 259 	};
 260 	if ($@) {
 261 		alarm 0;
 262 		if ($@ =~ /timeout/) {
 263 			alarm();
 264 			return 0;
 265 		} else {
 266 			error("$@",0);
 267 			return 0;
 268 		}
 269 	}
 270 	return 1;
 271 }
 272 
 273 #
 274 # Subroutine to login to the mailbox.
 275 #
 276 sub login {
 277   my ($response,$success);
 278   my ($user,$password);
 279 
 280 
 281   $user = Irssi::settings_get_str('mailcheck_imap_user');
 282   $password = Irssi::settings_get_str('mailcheck_imap_password');
 283 
 284 
 285   $logged_in = 0;
 286   # Set an alarm in case we can not connect or get hung.  Older versions
 287   # the IO::Socket perl module caused errors with the alarm we set before
 288   # setting up the socket.  If this program dies in debug mode saying:
 289   # "Alarm clock", then you can probably fix it by upgrading your perl
 290   # IO module.
 291   eval {
 292     alarm 30;
 293     send_data("A001 LOGIN \"$user\" \"$password\"","\"$user\"");
 294     while (1) {
 295       ($success,$response) = receive();
 296       if (! $success) {
 297 	return 0;
 298       }
 299       last if $response =~ /LOGIN|OK/;
 300     }
 301     if ($response =~ /fail|BAD/) {
 302       return 0;
 303     } else {
 304       $logged_in = 1;
 305     }
 306     alarm 0;
 307   };
 308   if ($@) {
 309     alarm 0;
 310     if ($@ =~ /timeout/) {
 311       alarm();
 312       return 0;
 313     } else {
 314       error("$@",0);
 315       return 0;
 316     }
 317   }
 318   # Success!  :D
 319   return 1;
 320 }
 321 
 322 #
 323 # Subroutine that does check of imap mailbox.
 324 #
 325 sub check_imap {
 326   my ($type) = @_;
 327 
 328   #my ($type) = ("MESSAGES");
 329 
 330   my ($response,$success,$num_messages);
 331   # Set an alarm in case we can not connect or get hung.  Older versions
 332   # the IO::Socket perl module caused errors with the alarm we set before
 333   # setting up the socket.  If this program dies in debug mode saying:
 334   # "Alarm clock", then you can probably fix it by upgrading your perl
 335   # IO module.
 336   eval {
 337     alarm 30;
 338     send_data("A003 STATUS INBOX ($type)");
 339     while (1) {
 340       ($success,$response) = receive();
 341       if (! $success) {
 342 	return "-1";
 343       }
 344       last if $response =~ /STATUS\s+.*?\s+\($type/;
 345     }
 346     ($num_messages) = $response =~ /\($type\s+(\d+)\)/;
 347     alarm 0;
 348   };
 349   if ($@) {
 350     alarm 0;
 351     if ($@ =~ /timeout/) {
 352       alarm();
 353       return "-1";
 354     } else {
 355       error("$@",0);
 356       return "-1";
 357     }
 358   }
 359   return $num_messages;
 360 }
 361 
 362 
 363 #
 364 # Subroutine to send a line to the imap server.
 365 # Block everything after $block.
 366 #
 367 sub send_data {
 368 	my ($line,$block) = (@_);
 369 	print $handle "$line\r\n";
 370 	$line =~ s/(.*$block).*/$1 ----/ if ($block);
 371 	Irssi::print("sent: $line") if Irssi::settings_get_bool('mailcheck_imap_debug');
 372 	return 1;
 373 }
 374 
 375 
 376 #
 377 # Subroutine to get a response from the imap server and print.
 378 # that response if in debug mode.
 379 #
 380 sub receive {
 381 	my ($response,$success);
 382 	$response = "";
 383 	$success  = 0;
 384 	chomp($response = <$handle>);
 385 	if ($response) {
 386 		Irssi::print("got: $response") if Irssi::settings_get_bool('mailcheck_imap_debug');
 387 		$success = 1;
 388 	} else {
 389 		Irssi::print("no response!") if Irssi::settings_get_bool('mailcheck_imap_debug');
 390 	}
 391 	return ($success,$response);
 392 }
 393 
 394 #
 395 # Subroutine to display and error message in a text box.
 396 #
 397 sub error {
 398   my ($error,$fatal) = (@_);
 399 
 400   if ($fatal) {
 401     # TODO : Print some useful message and die?
 402     Irssi::print("mailcheck_imap FATAL : $error");
 403     return 0;
 404   } else {
 405     Irssi::print("mailcheck_imap error : $error");
 406 
 407     if ($refresh_tag) {
 408       Irssi::timeout_remove($refresh_tag)
 409     }
 410     my $time = Irssi::settings_get_int('mailcheck_imap_interval');
 411     $refresh_tag = Irssi::timeout_add($time*1000, 'refresh_mailcheck_imap', undef);
 412     $handle = 0;
 413     return 0;
 414   }	
 415 }
 416 
 417 #
 418 # Subroutine to call when alarm times out.
 419 #
 420 sub alarm {
 421   Irssi::print("Alarm went off!") if Irssi::settings_get_bool('mailcheck_imap_debug');
 422   return 1;
 423 }
 424 
 425 
 426 #
 427 # Subroutine to clean up.
 428 #
 429 sub cleanup {
 430   if ($handle) {
 431     send_data("A999 LOGOUT");
 432     $handle->close();
 433   }
 434   Irssi::print("mailcheck_imap logged out.");
 435 }
 436 
 437 
 438 
 439 #######################################################################
 440 # Simply requests a statusbar item redraw.
 441 
 442 sub update_statusbar_item {
 443   Irssi::statusbar_items_redraw('mailcheck_imap');
 444 }
 445 
 446 
 447 #######################################################################
 448 # This is the function called by irssi to obtain the statusbar item.
 449 
 450 sub mailcheck_imap {
 451   my ($item, $get_size_only) = @_;
 452 
 453   my $theme = Irssi::current_theme();
 454   my $format = $theme->format_expand("{sb_mailcheck_imap}");
 455 
 456   if ($format) {
 457     # use theme-specific look
 458     $format = $theme->format_expand("{sb_mailcheck_imap $new_messages $total_messages}", Irssi::EXPAND_FLAG_IGNORE_REPLACES);
 459   } else {
 460     # use the default look
 461     $format = "{sb Mail: ".$new_messages." new, ".$total_messages." total}";
 462   }
 463 
 464   if($new_messages == 0) {
 465     if(Irssi::settings_get_bool('mailcheck_imap_show_zero')) {
 466       $format = $theme->format_expand("{sb_mailcheck_imap_zero $new_messages $total_messages}", Irssi::EXPAND_FLAG_IGNORE_REPLACES);
 467 
 468       if (!$format) {
 469 	# use the default look
 470 	$format = "{sb Mail: None new, ".$total_messages." total}";
 471       }
 472     } else {
 473       $format = "";
 474     }
 475   }
 476 
 477   if (length($format) == 0) {
 478     # nothing to print, so don't print at all
 479     if ($get_size_only) {
 480       $item->{min_size} = $item->{max_size} = 0;
 481     }
 482   } else {
 483     $item->default_handler($get_size_only, $format, undef, 1);
 484   }
 485 }
 486 
 487 
 488 ################################################################################
 489 # Ensure that all required details are filled in:
 490 # host, user, password
 491 sub check_details {
 492   my $host = Irssi::settings_get_str('mailcheck_imap_host');
 493   my $user = Irssi::settings_get_str('mailcheck_imap_user');
 494   my $password = Irssi::settings_get_str('mailcheck_imap_password');
 495 
 496   if(!$host || !$user || !$password) {
 497     show_help();
 498     return 0;
 499   }
 500   return 1;
 501 }
 502 
 503 
 504 ################################################################################
 505 # Immediately check for new mail (updates statusbar item too)
 506 
 507 sub cmd_mailcheck_imap {
 508   refresh_mailcheck_imap();
 509 }
 510 
 511 
 512 ################################################################################
 513 # Kill the connection and stop the refresh.
 514 sub cmd_mailcheck_imap_stop {
 515   if ($refresh_tag) {
 516     Irssi::timeout_remove($refresh_tag);
 517   }
 518   cleanup();
 519 }
 520 
 521 # Also close connection on script unload?
 522 sub sig_command_script_unload ($$$) {
 523   my ($script, $server, $witem) = @_;
 524 
 525   if($script =~ /^mailcheck_imap\.pl$/ ||
 526      $script =~ /^mailcheck_imap/) {
 527     cleanup();
 528   }
 529 }
 530 
 531 Irssi::signal_add_first('command script unload', \&sig_command_script_unload);
 532 
 533 
 534 #######################################################################
 535 # Adding stuff to irssi
 536 
 537 Irssi::settings_add_int('mail', 'mailcheck_imap_interval', 120);
 538 Irssi::settings_add_bool('mail', 'mailcheck_imap_use_ssl', 0);
 539 Irssi::settings_add_bool('mail', 'mailcheck_imap_debug', 0);
 540 Irssi::settings_add_bool('mail', 'mailcheck_imap_show_zero', 0);
 541 Irssi::settings_add_bool('mail', 'mailcheck_imap_echo_new_in_window', 1);
 542 
 543 Irssi::settings_add_str('mail', 'mailcheck_imap_host', '');
 544 Irssi::settings_add_int('mail', 'mailcheck_imap_port', 143);
 545 Irssi::settings_add_str('mail', 'mailcheck_imap_user', '');
 546 Irssi::settings_add_str('mail', 'mailcheck_imap_password', '');
 547 
 548 
 549 Irssi::statusbar_item_register('mailcheck_imap', '{sb $0-}', 'mailcheck_imap');
 550 
 551 Irssi::command_bind('mailcheck_imap_help','cmd_mailcheck_imap_help');
 552 Irssi::command_bind('mailcheck_imap','cmd_mailcheck_imap');
 553 Irssi::command_bind('mailcheck_imap_stop','cmd_mailcheck_imap_stop');
 554 
 555 
 556 #######################################################################
 557 # Startup functions
 558 
 559 # Check that everything is fiiiine and start checking if so
 560 if(check_details()) {
 561   # All is ok, so start running it
 562   refresh_mailcheck_imap();
 563   update_statusbar_item();
 564 }
 565 
 566 
 567 #######################################################################