#
# Copyright (c) 2010 by Nils Görs <weechatter@arcor.de>
#
# checks POP3 server for mails and display mail headers
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Config:
# Add [mail] to your weechat.bar.status.items
#
# 0.1: initial version
# Thanks to Trashlord for the hint with hook_process()
#
# This script needs following perl modules:
# Tie::IxHash
# Mail::POP3Client
# IO::Socket::SSL
# MIME::Base64
# Crypt::Rijndael
# You can install them using "cpan"
# do not sort my hash
use Tie::IxHash;
my %pop3_accounts;
my %mail_counts;
# user@mail.de|pop3.server:port
tie %pop3_accounts, Tie::IxHash;
###
#use strict;
use Mail::POP3Client;
use MIME::Base64;
use Crypt::Rijndael;
use Encode;
#"✉"
my $prgname = "pop3_mail";
my $version = "0.1";
my $description = "check POP3 server for mails and display mail header";
my $item_name = "mail";
# -------------------------------[ config ]-------------------------------------
my $default_pop3list = "pop3list.txt";
my %default_options = ("refresh" => "10", # interval in minutes to check pop3 accounts
"pop3_timeout" => "20", # timeout for pop3_server (in seconds)
"show_header" => "From|Subject",
"passphrase" => "enter passphrase",
"delete_passphrase_on_exit" => "on",
);
# ------------------------------[ internal ]-----------------------------------
my %Hooks = (); # space for my hooks
my %mailcount = (); # how many mails for all accounts and in how many accounts?
my $bar_item = "";
my $filename = ();
# -------------------------------[ hook_process() command ]-------------------------------------
# arguments: user, password, server, port, timeout, (no)header
if ($#ARGV == 5 ) { # six arguments given?
my $user = $ARGV[0];
my $password = $ARGV[1];
my $pop3server = $ARGV[2];
my $port = $ARGV[3];
my $timeout = $ARGV[4];
my $header = $ARGV[5];
if ($port eq "995") {
$use_ssl = 1;
} else {
$use_ssl = 0;
}
my $pop3 = new Mail::POP3Client(
#DEBUG => 1,
USER => $user,
PASSWORD => $password,
HOST => $pop3server,
TIMEOUT => $timeout,
# PORT => $port,
USESSL => $use_ssl, # if true use port 995 otherwise port 110
);
my $num_mesg = $pop3->Count; # how many messages are there?
if ($header ne "noheader"){
for( $i = 1; $i <= $pop3->Count(); $i++ ) {
foreach( $pop3->Head( $i ) ) {
my $decoded = encode("utf-8",decode("MIME-Header",$_));
/^($header):\s+/i && print "$decoded\n"; # decode MIME and encode to UTF8
}
print "\n";
}
}else{
print "$num_mesg\n";
}
$pop3->Close();
exit;
}
# -------------------------------[ weechat program starts here ]-------------------------------------
# check out config settings
sub toggled_by_set{
my ( $pointer, $option, $value ) = @_;
my $plugin_name = "plugins.var.perl.$prgname.";
if ($option eq $plugin_name."refresh"){
$default_options{refresh} = $value;
if (!weechat::config_is_set_plugin("refresh")){
$default_options{refresh} = 10; #default 10 minutes
weechat::config_set_plugin("refresh", $default_options{refresh});
}
if ($default_options{refresh} ne "0"){
if (defined $Hooks{timer}) {
unhook_timer();
hook_timer();
return weechat::WEECHAT_RC_OK;
}
}
if ($default_options{refresh} eq "0"){
if (defined $Hooks{timer}) {
unhook_timer();
}
}else{
if (not defined $Hooks{timer}){
weechat::config_set_plugin("refresh", "0") unless hook_timer(); # fall back to '0', if hook fails
}
}
}elsif ($option eq $plugin_name."pop3_timeout"){
$default_options{pop3_timeout} = $value;
}elsif ($option eq $plugin_name."show_header"){
$default_options{show_header} = $value;
}elsif ($option eq $plugin_name."passphrase"){
return weechat::WEECHAT_RC_OK if (check_passphrase_length($value) eq 1);
$default_options{passphrase} = $value;
}elsif ($option eq $plugin_name."delete_passphrase_on_exit"){
$default_options{delete_passphrase_on_exit} = $value;
}
return weechat::WEECHAT_RC_OK;
}
sub hook_timer{
count_messages();
$Hooks{timer} = weechat::hook_timer($default_options{refresh} * 1000 * 60, 0, 0, "count_messages", ""); # period * millisec(1000) * second(60) * minutes(60)
if ($Hooks{timer} eq '')
{
weechat::print("",weechat::prefix("error")."can't enable $prgname, hook failed.");
return 0;
}
# $bar_item = weechat::bar_item_new($item_name, "show_mail","");
weechat::bar_item_update($item_name);
return 1;
}
sub unhook_timer{
weechat::bar_item_remove($bar_item);
$bar_item = "";
if (defined $Hooks{timer}){
weechat::unhook($Hooks{timer});
delete $Hooks{timer};
}
}
# user commands
sub user_cmd{
my ($getargs) = ($_[2]);
return weechat::WEECHAT_RC_OK if (check_passphrase_length($default_options{passphrase}) eq 1);
if ($getargs eq ""){
weechat::command("","/help $prgname");
return weechat::WEECHAT_RC_OK;
}
if ($getargs eq "list"){ # list all accounts
weechat::print("","POP3 accounts:");
my $i = 1;
my $x = keys %pop3_accounts;
if ($x eq 0){
weechat::print("","no accounts added yet");
}else{
while ( my($key,$password) = each %pop3_accounts) {
my ($user,$pop3server) = split (/\|/,$key); # format : username|servername:port
# $password = decode_Rijndael($password);
$i = sprintf("%03d",$i);
my ($servername, $port) = split(/:/,$pop3server);
$port = $port . weechat::color("chat") . " (" . weechat::color("chat_delimiters") ."ssl" . weechat::color("chat") . ")" if ($port eq "995");
# get number of emails
my $account = $user.$servername;
my $num_mesg;
if (defined $mail_counts{$account}){
$num_mesg = $mail_counts{$account};
}else{
$num_mesg = 0;
}
$num_mesg = sprintf("% 3d",$num_mesg);
# print infos
weechat::print( "",
" ["
. weechat::color("chat_delimiters")
. $i
. weechat::color("chat")
. "]"
. " mails: "
. weechat::color("chat_delimiters")
. $num_mesg
. weechat::color("chat")
. " user: "
. weechat::color("chat_delimiters")
. $user . weechat::color("chat")
. " server: "
. weechat::color("chat_delimiters")
. $servername
. weechat::color("chat")
. " port: "
. weechat::color("chat_delimiters")
. $port
. weechat::color("chat"));
# . " password: " . $password);
$i = $i + 1;
}
}
return weechat::WEECHAT_RC_OK;
}
count_messages() if ($getargs eq "check");
my ( $cmd, $arg ) = ( $getargs =~ /(.*?)\s+(.*?)/ );
return weechat::WEECHAT_RC_OK if (not defined $cmd);
if ($cmd eq "list" and defined $arg){
( $cmd, $arg ) = ( $getargs =~ /(.*?)\s+(\d.*)/ );
if (not defined $cmd){
weechat::print("",weechat::prefix("error")."$prgname: wrong arguments");
return weechat::WEECHAT_RC_OK;
}
if ($cmd eq "list"){ # display messages on server for account
my $i = 1;
if ($arg !~ /^-?\d+(?:[\.,]\d+)?$/){ # only numbers for account?
weechat::print("",weechat::prefix("error")."$prgname: invalid account number");
return weechat::WEECHAT_RC_OK;
}
foreach my $key ( keys %pop3_accounts ) {
my $password = $pop3_accounts{$key};
if ($arg == $i){
get_message_header($key,$password);
return weechat::WEECHAT_RC_OK;
}
$i = $i + 1;
}
return weechat::WEECHAT_RC_OK;
}
}
if ($cmd eq "del" and defined($arg)){ # check command and if argument exists
( $cmd, $arg ) = ( $getargs =~ /(.*?)\s+(\d.*)/ );
my $i = 1;
foreach my $key ( keys %pop3_accounts ) {
if ($arg == $i){
delete $pop3_accounts{$key}; # delete user account
save_file();
last;
}
$i = $i + 1;
}
unhook_timer();
hook_timer();
}
if ($cmd eq "add" and defined($arg)){
my ( $cmd, $user, $host ,$password ) = ( $getargs =~ /(.*?)\s(.*?)\s(.*?)\s(.*)/ );
if (not defined $password or not defined $host or index($host,":") eq -1){
weechat::print("",weechat::prefix("error")."$prgname: wrong arguments given to add account: <username[\@host]> <servername:port> <password>");
return weechat::WEECHAT_RC_OK;
}
$user = $user."|".$host; # username@adress|host
$password = encode_Rijndael($password);
chomp($password);
$pop3_accounts{$user} = $password; # add new user account with password
save_file();
unhook_timer();
hook_timer();
}
return weechat::WEECHAT_RC_OK;
}
sub init{
if ( weechat::config_get_plugin("pop3_list") eq "" ) {
my $wd = weechat::info_get( "weechat_dir", "" );
$wd =~ s/\/$//;
weechat::config_set_plugin("pop3_list", $wd . "/" . $default_pop3list );
}
read_file();
# get absolute path of script
my $infolist_pnt = weechat::infolist_get("perl_script","",$prgname);
weechat::infolist_next($infolist_pnt);
$filename = weechat::infolist_string($infolist_pnt,"filename");
weechat::infolist_free($infolist_pnt);
#set default config
foreach my $option (keys %default_options) {
if ( !weechat::config_is_set_plugin($option) ) {
weechat::config_set_plugin($option, $default_options{$option});
}else {
$default_options{$option} = weechat::config_get_plugin($option);
}
}
return if (check_passphrase_length($default_options{passphrase}) eq 1);
}
sub show_mail {
if ( (keys %pop3_accounts) == 1 ){
return sprintf ("✉: %d", $mailcount{mails_over_all});
}else{
return sprintf ("✉: %d (%d)", $mailcount{mails_over_all}, $mailcount{accounts_with_mails});
}
}
# -------------------------------[ hook_process callback ]-------------------------------------
sub hook_process_cb{
my ($data, $command, $return_code, $out, $err) = @_;
return weechat::WEECHAT_RC_OK if ( $return_code > 0 ); # something went wrong!
my (undef, undef, $nick, undef, $server, undef) = split /\s+/, $command, 6;
my $account = $nick.$server;
chomp($out); # kill LF
$mailcount{num_mesg} = $out; # save number of messages
$mailcount{num_mesg} = 0 if ($out == -1); # something wrong with account!
$mailcount{accounts_with_mails} = $mailcount{accounts_with_mails} + 1 if ($out > 0); # how many accounts have mails?
$mail_counts{$account} = $mailcount{num_mesg}; # save number of mails for the account
$mailcount{mails_over_all} = $mailcount{mails_over_all} + $mailcount{num_mesg}; # all counted messages from all accounts
if ( $bar_item ne "" and $mailcount{mails_over_all} == 0 or not defined $mailcount{mails_over_all} ){
weechat::bar_item_remove($bar_item);
$bar_item = "";
}elsif ( ($mailcount{mails_over_all} > 0) and ($bar_item eq "") ){
$bar_item = weechat::bar_item_new($item_name, "show_mail","");
}
weechat::bar_item_update($item_name);
return weechat::WEECHAT_RC_OK;
}
# mail header
sub hook_process_cb2{
my ($data, $command, $return_code, $out, $err) = @_;
return weechat::WEECHAT_RC_OK if ( $return_code > 0 );
my (undef, undef, $nick, undef, $server, undef) = split /\s+/, $command, 6;
my @array=split(/\n\n/,$out);
my $i = 1;
weechat::print( "", "mails for \"$nick\" on server \"$server\":");
foreach (@array) {
weechat::print( "", weechat::color("chat_delimiters")
. "[" . weechat::color("chat_buffer")
. " mail " . $i
. weechat::color("chat_delimiters")
. " ]" . weechat::color("chat")
. "\n$_" );
$i++;
}
return weechat::WEECHAT_RC_OK;
}
# -------------------------------[ POP3 routines ]-------------------------------------
sub count_messages{
$mailcount{num_mesg} = 0;
$mailcount{mails_over_all} = 0;
$mailcount{accounts_with_mails} = 0;
foreach my $key ( keys %pop3_accounts ) {
my ($user,$pop3server,$port) = split (/:|\|/,$key); # format : username|servername:port
my $password = decode_Rijndael($pop3_accounts{$key});
weechat::hook_process("perl $filename $user $password $pop3server $port $default_options{pop3_timeout} noheader", 1100 * $default_options{pop3_timeout},"hook_process_cb","");
}
return weechat::WEECHAT_RC_OK;
}
sub get_message_header {
my ($key,$password) = @_;
my $i;
my $use_ssl = 0;
if ($default_options{show_header} eq "" or not defined $default_options{show_header}){
weechat::print("",weechat::prefix("error")."$prgname: option \"plugins.var.perl.$prgname.show_header\" not set.");
return;
}
my ($user,$pop3server,$port) = split (/:|\|/,$key); # format : username|servername:port
my $account = $user.$pop3server;
if (not defined $mail_counts{$account} or $mail_counts{$account} eq 0){
weechat::print("",weechat::prefix("error")."$prgname: no mails on server \"$pop3server\" for \"$user\"");
return;
}
$password = decode_Rijndael($password);
weechat::hook_process("perl $filename $user $password $pop3server $port $default_options{pop3_timeout} \"$default_options{show_header}\"", 1100 * $default_options{pop3_timeout},"hook_process_cb2","");
}
# -------------------------------[ AES encode / decode: key must be 128, 192 or 256 bits long ]-------------------------------------
sub encode_Rijndael{
my ( $plaintext ) = @_; # Your string to be encrypted
my $base64 = "";
my $rcipher = Crypt::Rijndael->new ($default_options{passphrase}, Crypt::Rijndael::MODE_CBC());
$rcipher->set_iv($default_options{passphrase}); # You may wish this IV to be something different from the Secret Key
if(length($plaintext) % 16 != 0 ) {
$plaintext .= ' ' x (16 - (length($plaintext) % 16)); }
my $rencrypted = $rcipher->encrypt($plaintext); # encrypt
$base64 = encode_base64($rencrypted); # base64 it
return $base64;
}
sub decode_Rijndael{
my ( $base64 ) = @_; # enctypted base64 string
my $rcipher = Crypt::Rijndael->new ($default_options{passphrase}, Crypt::Rijndael::MODE_CBC());
$rcipher->set_iv($default_options{passphrase}); # You may wish this IV to be something different from the Secret Key
$base64 = decode_base64($base64);
# if(length($plaintext) % 16 != 0 ) {
# $plaintext .= ' ' x (16 - (length($plaintext) % 16)); }
my $rencrypted = $rcipher->decrypt($base64);
$rencrypted =~ s/\s+\z//; # remove space from end of string
return $rencrypted;
}
sub check_passphrase_length{
my ( $passphrase ) = @_;
my $len = length($passphrase);
if (! ($len == 16) or ($len == 24) or ($len == 32) ){
weechat::print("",weechat::prefix("error")."$prgname: wrong key length: passphrase must be 128, 192 or 256 bits long");
return 1; #false
}
return 0; #true
}
# -------------------------------[ load, save, shutdown, debug routine ]-------------------------------------
sub save_file {
my $x = keys %pop3_accounts;
if ($x ne 0){ # entries in pop3_accounts?
my $pop3list = weechat::config_get_plugin( "pop3_list" );
open (WL, ">", $pop3list) || DEBUG("write pop3_list: $!");
while ( my($user,$passwort) = each %pop3_accounts) {
print WL "$user $passwort\n";
}
close WL;
}else{
my $pop3list = weechat::config_get_plugin( "pop3_list" );
unlink($pop3list);
}
}
sub read_file {
my $pop3list = weechat::config_get_plugin("pop3_list");
return unless -e $pop3list;
open (WL, "<", $pop3list) || DEBUG("$pop3list: $!");
while (<WL>) {
chomp; # kill LF
my ( $user, $password ) = split / /; # <user> <password>
if (not defined $user){
close WL;
weechat::print("",weechat::prefix("error")."$prgname: $pop3list is not valid...");
return;
}
$pop3_accounts{$user} = $password if length $_;
}
close WL;
}
sub shutdown{
# remove my hooks
if (defined $Hooks{timer}) {
weechat::unhook($Hooks{timer});
}
if (defined $Hooks{command}) {
weechat::unhook($Hooks{command});
}
if (defined $Hooks{config}) {
weechat::unhook($Hooks{config});
}
if ($default_options{delete_passphrase_on_exit} eq "on"){
weechat::config_unset_plugin("passphrase","");
}
return weechat::WEECHAT_RC_OK;
}
sub DEBUG {weechat::print('', "***\t" . $_[0]);}
# first function called by a WeeChat-script.
weechat::register($prgname, "Nils Görs <weechatter\@arcor.de>", $version,
"GPL3", $description, "shutdown", "");
init();
$Hooks{config} = weechat::hook_config( "plugins.var.perl.$prgname.*", "toggled_by_set", "" );
$Hooks{command} = weechat::hook_command($prgname, $description,
"[list <account>] | [add user(\@host) server:port password] | [del number] | check",
"list : display account(s)\n".
"list <number>: display mail header(s) for specified account\n".
"add : add new account (template: <username> <server:port> <password>)\n".
"del <number> : delete an account\n".
"check : check POP3 account(s) manually\n".
"\n".
"This script is using Rijndael(AES) encryption to protect your pop3 password(s) in config file.\n".
"Keep in mind that the passphrase is not encrypted. Use /rmodifier function to hide passphrase.\n".
"\n".
"Options:\n".
"plugins.var.perl.$prgname.passphrase : to encrypt pop3 passwords in config file (default: empty)\n".
"plugins.var.perl.$prgname.pop3_list : file to store account, server and password (default: ~/.weechat/pop3list.txt)\n".
"plugins.var.perl.$prgname.pop3timeout : set a timeout value for socket operations (default: 20 seconds)\n".
"plugins.var.perl.$prgname.refresh : checks pop3 account (default: 10 minutes)\n".
"plugins.var.perl.$prgname.show_header : displays mail headers (default: From|Subject)\n".
"plugins.var.perl.$prgname.delete_passphrase_on_exit : delete passphrase on exit (default: on)\n".
"You have to edit option \"delete_passphrase_on_exit\" manually each time when weechat or script (re)starts. Switching option to \"off\" will keep passphrase in weechat-config.\n".
"\n".
"Add item [mail] to your \"weechat.bar.status.items\"\n".
"\n".
"Examples:\n".
"Add account with hostname and ssl/tls protocol to monitore:\n".
"/$prgname add myuser\@host.de pop3.server.de:995 mypassword\n".
"Add account without hostname and without ssl/tls protocol to monitore:\n".
"/$prgname add myuser pop3.server.de:110 mypassword\n".
"Delete account with number 001 from list:\n".
"/$prgname del 1\n",
"add|del|list|check", "user_cmd", "");
hook_timer() if ($default_options{refresh} ne "0");
weechat::bar_item_update($item_name);