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