#!/usr/bin/perl -w # # rawpop3.pl # # Manages a POP3 account at a low level # # Sébastien Millet, March 2002, March 2003. # Copyright Sébastien Millet. # # Developed and tested under Perl 5.6.0 - Tk800.024 # under RedHat 7.1 (Kernel 2.4.2-2) and Windows XP. # use strict; use Tk; use Socket; my $VERSION = 0.1; # *************************************************************** # *** ************************************************* # *** POD ************************************************* # *** ************************************************* # *************************************************************** =head1 NAME RAWPOP3 - A Tk based script that enables raw POP3 account access. =head1 DESCRIPTION This script is to be used to access a POP3 account for those who want to control exactly what is done between the client and the server. For example you can delete messages before they are downloaded. =head1 README This script is to be used to access a POP3 account for those who want to control exactly what is done between the client and the server. For example you can delete messages before they are downloaded. =head1 PREREQUISITES This script requires the C module, the C module and the C module. =pod OSNAMES any =pod SCRIPT CATEGORIES Mail =cut # *************************************************************** # *** ******************************************* # *** CONSTANTS ******************************************* # *** ******************************************* # *************************************************************** # Name of configuration file my $CONFIG_FILENAME = \".rawpop3-cf"; # First line of config file my $FV = \"RAWPOP_CFGFILE_VERSION"; # Version of current CFG file my $CFG_CUR_VERSION = \"1.0"; # File name when a message is saved my $MSG_RECORD_FILE = \"message"; # End Of Line sequence when dealing with a SOCK filehandler (=> TCP connection) my $EOL = \"\015\012"; # Thousand separator my $THOUSAND_SEPARATOR = \" "; # Month list my %months_list = ("Jan" => 0, "Feb" => 1, "Mar" => 2, "Apr" => 3, "May" => 4, "Mai" => 4, "Jun" => 5, "Jul" => 6, "Aug" => 7, "Sep" => 8, "Oct" => 9, "Nov" => 10, "Dec" => 11); my %weekdays_list = ("Mon" => 0, "Tue" => 1, "Wed" => 2, "Thu" => 3, "Fri" => 4, "Sat" => 5, "Sun" => 6); # Possible log levels my $LL_NONE = \0; my $LL_ERROR = \1; my $LL_WARNING = \2; my $LL_NORMAL = \3; my $LL_VERBOSE = \4; my $LL_DEBUG = \5; # Possible states of the message list my $ML_EMPTY = \0; my $ML_FILLED = \1; # Possible state of a given message in the message list my $MSG_SIZEONLY = \0; my $MSG_DETAILED = \1; my $MSG_DOWNLOADED = \2; # Possible actions when reading a message my $ACTION_DETAIL = \0; my $ACTION_DOWNLOAD = \1; # Size of columns in the list my $CS_NUMBER = \6; my $CS_SIZE = \12; my $CT_SIZE = \5; my $CT_DATE = \5; my $CT_FROM = \40; my $CT_SUBJECT = \40; # When a message is older than 6 months ago, then display only its year in # the "posted date" field of the message list. Otherwise display the day and month. my $NB_MONTHS_DETAIL = \6; # **************************************************************** # *** ******************************************* # *** UI OBJECTS ******************************************* # *** ******************************************* # **************************************************************** # Main window my $t_mw; my $menu_bar; my $mb1; my $mb2; my $mb3; # A few frames in the main window my $t_f0; my $t_f1; my $t_f3; my $t_f4; # Control that gets focus at program start-up my $t_focus_ctrl; # List of messages my $t_mlb; # Font of messages list my $t_mlb_font; # Detail of a given message my $t_msg; # Display status of POP3 connection state my $t_conn_status; # Display status line (bottom of main window) my $t_status; # Log window my $t_ml; # Log frame (inside log window) my $t_fl; # Log text (inside log frame of the log window) my $t_log_text; # ********************************************************************** # *** ******************************************* # *** GLOBAL VARIABLES ******************************************* # *** ******************************************* # ********************************************************************** # Used to to do one shot actions at program start-up my $g_program_is_starting = 1; # Current log level my $g_cur_log_level = $$LL_DEBUG; # Is the log window displayed ? my $g_disp_log = 1; # Default POP3 account informations (server, port, user, password) my $g_pop3_server = ""; my $g_pop3_port = 110; my $g_pop3_username = ""; my $g_pop3_password = ""; my $g_curconn_server; my $g_curconn_port; # Some configuration check boxes my $g_getlist_when_connect = 0; my $g_detail_all_when_connect = 0; # State of the POP3 connection my $g_pop3_is_connected = 0; # List of messages read from the POP3 server my @g_ml; # State of this list my $g_mlstate = $$ML_EMPTY; # Number of elements in the list my $g_nbelems; # ********************************************************************* # *** ******************************************* # *** UI CONSTRUCTION ******************************************* # *** ******************************************* # ********************************************************************* # Create main window $t_mw=MainWindow->new; $t_mw->title("POP3 Low Level Account Manager"); $t_mw->bind("", [\&e_visibility, Ev('s')]); $t_mw->OnDestroy(\&e_destroy); # Create menu $menu_bar = $t_mw->Menu(-type => "menubar"); $mb1 = $t_mw->Menu(-type => "normal"); $mb1->add("checkbutton", -label => "Display log window", -variable => \$g_disp_log, -command => \&c_change_disp_log_window); $mb1->add("command", -label => "Connect / Disconnect", -command => \&c_switch_pop3_connect); $mb1->add("separator"); $mb1->add("command", -label => "Exit", -command => sub { exit; }); $mb2 = $t_mw->Menu(-type => "normal"); $mb2->add("command", -label => "Get Message List", -command => \&c_get_list); $mb2->add("command", -label => "Select all", -command => \&c_select_all); $mb2->add("separator"); $mb2->add("command", -label => "Detail selected messages", -command => sub { &c_detail_or_download_selected_messages($$ACTION_DETAIL); }); $mb2->add("command", -label => "Download selected messages", -command => sub { &c_detail_or_download_selected_messages($$ACTION_DOWNLOAD); }); $mb2->add("command", -label => "Display", -command => \&c_display_msg); $mb2->add("command", -label => "Save", -command => \&c_save_msg); $mb2->add("command", -label => "Delete selected messages", -command => \&c_delete_selected_messages); $mb3 = $t_mw->Menu(-type => "normal"); $mb3->add("command", -label => "Eubahoute...", -command => \&c_about); $menu_bar->add("cascade", -menu => $mb1, -label => "File"); $menu_bar->add("cascade", -menu => $mb2, -label => "Message"); $menu_bar->add("cascade", -menu => $mb3, -label => "help"); $t_mw->configure(-menu => $menu_bar); # Create frames in main window $t_f0 = $t_mw->Frame(-relief => "ridge", -borderwidth => 2) ->pack(-fill => "x"); $t_f1 = $t_mw->Frame(-relief => "ridge", -borderwidth => 2) ->pack(-fill => "x"); $t_f3 = $t_mw->Frame(-relief => "ridge", -borderwidth => 2) ->pack(-fill => "x"); $t_f4 = $t_mw->Frame(-relief => "flat", -borderwidth => 2) ->pack(-fill => "both", -expand => 1); # Populate frames $t_f0->Checkbutton(-text => "Display log window", -variable => \$g_disp_log, -command => \&c_change_disp_log_window) ->pack(-side => "left", -padx => 2, -pady => 2); $t_f0->Checkbutton(-text => "Get message list upon connection", -variable => \$g_getlist_when_connect) ->pack(-side => "left", -padx => 2, -pady => 2); $t_f0->Checkbutton(-text => "Detail all messages upon connection", -variable => \$g_detail_all_when_connect) ->pack(-side => "left", -padx => 2, -pady => 2); $t_f1->Label(-text => "POP3 server") ->pack(-side => "left", -padx => 8, -pady => 2); $t_focus_ctrl = $t_f1->Entry(-textvariable => \$g_pop3_server, -background => "white", -relief => "flat") ->pack(-side => "left", -expand => 1, -fill => "x", -padx => 8, -pady => 2); $t_f1->Label(-text => "POP3 port") ->pack(-side => "left", -padx => 8, -pady => 2); $t_f1->Entry(-textvariable => \$g_pop3_port, -background => "white", -width => 5, -justify => "right", -relief => "flat") ->pack(-side => "left", -padx => 8, -pady => 2); $t_f1->Label(-text => "User name") ->pack(-side => "left", -padx => 8, -pady => 2); $t_f1->Entry(-textvariable => \$g_pop3_username, -background => "white", -relief => "flat") ->pack(-side => "left", -expand => 1, -fill => "x", -padx => 8, -pady => 2); $t_f1->Label(-text => "Password") ->pack(-side => "left", -padx => 8, -pady => 2); $t_f1->Entry(-textvariable => \$g_pop3_password, -background => "white", -width => 8, -relief => "flat", -show => "*") ->pack(-side => "left", -padx => 8, -pady => 2); $t_f3->Button(-text => "Connect / Disconnect", -command => \&c_switch_pop3_connect) ->pack(-side => "left", -padx => 8, -pady => 2); $t_f3->Button(-text => "Get message list", -command => \&c_get_list) ->pack(-side => "left", -padx => 8, -pady => 2); $t_f3->Button(-text => "Select all", -command => \&c_select_all) ->pack(-side => "left", -padx => 8, -pady => 2); $t_f3->Button(-text => "Detail", -command => sub { &c_detail_or_download_selected_messages($$ACTION_DETAIL); }) ->pack(-side => "left", -padx => 8, -pady => 2); $t_f3->Button(-text => "Download", -command => sub { &c_detail_or_download_selected_messages($$ACTION_DOWNLOAD); }) ->pack(-side => "left", -padx => 8, -pady => 2); $t_f3->Button(-text => "Display", -command => \&c_display_msg) ->pack(-side => "left", -padx => 8, -pady => 2); $t_f3->Button(-text => "Save", -command => \&c_save_msg) ->pack(-side => "left", -padx => 8, -pady => 2); $t_f3->Button(-text => "Delete", -command => \&c_delete_selected_messages) ->pack(-side => "left", -padx => 8, -pady => 2); $t_f3->Button(-text => "Exit", -command => sub { exit; }) ->pack(-side => "right", -padx => 8, -pady => 2); $t_conn_status = $t_mw->Label(-width => 15, -background => "darkblue", -foreground => "white") ->pack(-side => "left", -padx => 2, -pady => 2); $t_status = $t_mw->Label ->pack(-side => "left", -padx => 2, -pady => 2); $t_mlb = $t_f4->Scrolled('Listbox', -scrollbars => "e") ->pack(-side => "top", -padx => 2, -pady => 2, -fill => "both"); $t_mlb_font = $t_mw->fontCreate(-size => 8, -family => "courier"); $t_mlb->configure(-relief => "ridge", -selectmode => "extended", -font => $t_mlb_font, -height => 8); $t_msg = $t_f4->Scrolled('Text', -scrollbars => "se") ->pack(-side => "bottom", -padx => 2, -pady => 2, -fill => "both", -expand => 1); $t_msg->configure(-relief => "ridge", -height => 8); # We're not connected at the beginning &pop3_switch_conn_state(0); # Read current POP3 server, port, account name and password, whenever the config file exists &read_config; # Focus starts on POP3 server name $t_focus_ctrl->focus(); $t_focus_ctrl->icursor('end'); $t_focus_ctrl->selectionRange(0, 'end'); # Looping... MainLoop; exit; # ********************************************************************* # *** ******************************************* # *** EVENTS HANDLERS ******************************************* # *** ******************************************* # ********************************************************************* # # Display ABOUT window # Usage: called by UI # # -> If someone can explain me how to easily make a given window be MODAL, I'm interested. # sub c_about { my $w; $w = $t_mw->Toplevel; $w->title("About rawpop3.pl"); $w->Label(-text => "\nrawpop3.pl version $VERSION\n\nAccess a POP3 account at a low level.\n" . "Enable deletion of messages before they are downloaded.\n\n" . "Copyright 2002, 2003 Sébastien Millet, sebastien.millet2\@libertysurf.fr\n\n") ->pack(-side => "top", -padx => 8); $w->Button(-text => "Ok", -command => sub { $w->destroy; }) ->pack(-side => "bottom", -pady => 8); $w->resizable(0, 0); } # # Display the content of a given message # Usage: called by UI # sub c_display_msg { my @sel; my $idx; @sel = $t_mlb->curselection; if ($#sel == 0) { $idx = $sel[0]; &msg_display($idx); } } # # Save the selected message's content # Usage: called by UI # sub c_save_msg { my @sel; my $idx; @sel = $t_mlb->curselection; if ($#sel == 0) { $idx = $sel[0]; &save_msg($idx); } } # # Let the log window appear (if checked) at program start-up # Usage: called by EV # sub e_visibility { my ($w, $h, $x, $y); if ($_[0] == $t_mw) { if ($g_program_is_starting) { ($w, $h, $x, $y) = $t_mw->geometry =~ m/^(\d+)x(\d+)\+(\d+)\+(\d+)$/; $t_mw->geometry("+$x" . '+0'); &switch_disp_log_window($g_disp_log); $g_program_is_starting = 0; } } } # # Manage the destruction of the main window (close the POP3 connection, if so). # Usage: called by EV # sub e_destroy { my $err_str; if ($g_pop3_is_connected) { &pop3_disconnect($err_str); } &write_config; } # # Display the log window or make it disappear. # Usage: called by UI # sub c_change_disp_log_window { &switch_disp_log_window($g_disp_log); } # # Connect to the POP3 server and get list of messages (get only index and size informations). # Usage: called by UI # sub c_get_list { my $e; my $err_str; if (!$g_pop3_is_connected) { &my_log("unable to get list of messages: no POP3 connection is established", $$LL_ERROR); return; } &empty_mlb; $t_mw->idletasks; if ($e = &pop3_update_msg_list($err_str)) { &my_log($err_str, $$LL_ERROR); } else { &update_mlb_from_msg_list; } } # # Select all elements in the list. # Usage: called by UI # sub c_select_all { $t_mlb->selectionSet(0, "end"); } # # Connect (if not connected) or disconnect (if connected) to/from the POP3 server, # using pop3_connect or pop3_disconnect procedure. # Usage: called by UI # sub c_switch_pop3_connect { my $e; my $err_str; if (!$g_pop3_is_connected) { $g_pop3_server = "" if !defined($g_pop3_server); $g_pop3_port = 0 if !defined($g_pop3_port); $g_pop3_username = "" if !defined($g_pop3_username); $g_pop3_password = "" if !defined($g_pop3_password); if ($e = pop3_connect($g_pop3_server, $g_pop3_port, $g_pop3_username, $g_pop3_password, $err_str)) { &my_log($err_str, $$LL_ERROR); return; } &pop3_switch_conn_state(1); if ($g_getlist_when_connect || $g_detail_all_when_connect) { c_get_list; } if ($g_detail_all_when_connect) { &c_select_all; &c_detail_or_download_selected_messages($$ACTION_DETAIL); } } else { if ($e = &pop3_disconnect($err_str)) { &my_log($err_str, $$LL_ERROR); # don't return here since we consider there is no POP3 connection any longer } else { &my_log("disconnected from $g_curconn_server:$g_curconn_port", $$LL_NORMAL); } &pop3_switch_conn_state(0); } } # # Detail selected messages. # Usage: called by UI # sub c_detail_or_download_selected_messages { my $action = $_[0]; my @sel; my $msgi; my $i; @sel = $t_mlb->curselection; foreach $i (@sel) { $msgi = $g_ml[$i][1]; &msg_detail_or_download($i, $msgi, $action); # Update visible list $t_mlb->delete($i); $t_mlb->insert($i, &get_formatted_line(@{$g_ml[$i]})); $t_mw->idletasks; } } # # Delete selected messages # Usage: called by UI # sub c_delete_selected_messages { my @sel; my $msgi; my $i; my $shift; my $err_str; my $e; @sel = $t_mlb->curselection; $shift = 0; foreach $i (@sel) { $msgi = $g_ml[$i - $shift][1]; &my_log("will delete message \#$msgi", $$LL_VERBOSE); if ($e = &msg_delete($i - $shift, $msgi, $err_str)) { &my_log("unable to delete message \#$msgi: $err_str", $$LL_ERROR); } else { &my_log("deleted message \#$msgi", $$LL_NORMAL); # Update internal list splice(@g_ml, $i - $shift, 1); # Update visible list $t_mlb->delete($i - $shift); $t_mw->idletasks; # Because of message deletion, everything in lists is shifted by one $shift++; } } } # **************************************************************** # *** ******************************************* # *** PROCEDURES ******************************************* # *** ******************************************* # **************************************************************** # # Read configuration file. # This function sets values of GLOBAL variables. # Usage: # # &read_config; # sub read_config { my $l; my $v; open(CFG, "<$$CONFIG_FILENAME") or return; $l = ; if (($v) = $l =~ m/^$$FV\s?=\s?(.*)$/i) { if ($v eq $$CFG_CUR_VERSION) { $g_pop3_server = ; $g_pop3_port = ; $g_pop3_username = ; $g_pop3_password = ; $g_getlist_when_connect = ; $g_detail_all_when_connect = ; chomp $g_pop3_server if defined($g_pop3_server); chomp $g_pop3_port if defined($g_pop3_port); chomp $g_pop3_username if defined($g_pop3_username); chomp $g_pop3_password if defined($g_pop3_password); chomp $g_getlist_when_connect if defined($g_getlist_when_connect); chomp $g_detail_all_when_connect if defined($g_detail_all_when_connect); } } close CFG; } # # Write the configuration file. # This function uses values of GLOBAL variables. # Usage: # # &write_config; # sub write_config { open(CFG, ">$$CONFIG_FILENAME") or return; $g_pop3_server = "" if !defined($g_pop3_server); $g_pop3_port = "" if !defined($g_pop3_port); $g_pop3_username = "" if !defined($g_pop3_username); $g_pop3_password = "" if !defined($g_pop3_password); print(CFG "$$FV=$$CFG_CUR_VERSION\n$g_pop3_server\n$g_pop3_port\n$g_pop3_username\n$g_pop3_password\n" . "$g_getlist_when_connect\n$g_detail_all_when_connect\n"); close CFG; } # # Make a message display in the text control # Usage: # # &msg_display($g_ml_indice); # sub msg_display { my $i = $_[0]; my $s; $t_msg->delete("0.0", "end"); if ($g_ml[$i][0] == $$MSG_SIZEONLY) { $t_msg->insert("0.0", ""); } elsif ($g_ml[$i][0] == $$MSG_DETAILED) { $t_msg->insert("0.0", $g_ml[$i][6]); } elsif($g_ml[$i][0] == $$MSG_DOWNLOADED) { $t_msg->insert("0.0", $g_ml[$i][6] . "\n" . $g_ml[$i][7]); } } # # Save a message # Usage: # # &save_msg($g_ml_indice); # sub save_msg { my $idx = $_[0]; &my_log("will save message #$idx into \"$$MSG_RECORD_FILE\"", $$LL_NORMAL); if ($g_ml[$idx][0] == $$MSG_SIZEONLY) { &my_log("At least message headers should be downloaded", $$LL_ERROR); return; } open(MSG, ">$$MSG_RECORD_FILE") or &my_log("unable to open file $$MSG_RECORD_FILE: $!", $$LL_ERROR), return; if ($g_ml[$idx][0] == $$MSG_DETAILED) { print(MSG $g_ml[$idx][6]); } elsif ($g_ml[$idx][0] == $$MSG_DOWNLOADED) { print(MSG $g_ml[$idx][6] . "\n" . $g_ml[$idx][7]); } close MSG; &my_log("Message #$idx successfully saved into \"$$MSG_RECORD_FILE\"", $$LL_NORMAL); } # # Delete a given message, identified by its POP3 number. # Usage: # # $errno = msg_delete($g_ml_indice, $msg_pop3_number, $err_str); # sub msg_delete { my $i = $_[0]; my $msgi = $_[1]; my $e; $e = &pop3_send_recv_and_ctrl("DELE $msgi", "+OK", $_[2]); return 0; } # # Detail or download a given message, identified by its POP3 number. # Usage: # # &msg_detail_or_download($g_ml_indice, $msg_pop3_number, $action); # sub msg_detail_or_download { my $i = $_[0]; my $msgi = $_[1]; my $action = $_[2]; my $v1; my $v2; my $instr; my $e; my $cont; my $line1; my $err_str; my $l; my $is_in_body; my $last_is_in_body; my ($fld_from, $fld_sender, $fld_return_path, $fld_subject, $fld_date); my ($displayed_from, $displayed_date); my $msg_headers; my $msg_body; my (@parsed_date, @cur_date); my ($cur_y, $cur_m, $msg_y, $msg_m); $v1 = $action == $$ACTION_DETAIL ? "gather informations of" : "download"; $v2 = $action == $$ACTION_DETAIL ? "detailed": "downloaded"; $instr = $action == $$ACTION_DETAIL ? "TOP $msgi 0" : "RETR $msgi"; &my_log("will $v1 message \#$msgi", $$LL_VERBOSE); if ($e = &pop3_send_recv_and_ctrl($instr, "+OK", $err_str)) { &my_log("unable to $v1 message \#$msgi: $err_str", $$LL_ERROR); return; } $cont = 1; $line1 = ""; $fld_from = ""; $fld_return_path = ""; $fld_sender = ""; $fld_subject = ""; $fld_date = ""; $msg_headers = ""; $is_in_body = 0; # The following loop analyzes a given line only after concatenation (lines beginning with # a space (or tab) character are merged with the preceeding line). while ($cont) { if ($e = &sock_recv($l, $err_str)) { &my_log($err_str, $$LL_ERROR); $cont = 0; } else { $cont = 0 if $l =~ m/^\.$/; $last_is_in_body = $is_in_body; $is_in_body = 1 if $l eq ""; # Remove trailing spaces $l =~ s/\s+$// if !$is_in_body; $msg_headers .= "$l\n" if $cont && !$is_in_body; $msg_body .= "$l\n" if $cont && $is_in_body && $last_is_in_body; if (!$is_in_body || (!$last_is_in_body && $is_in_body)) { if ((!$cont || $l =~ m/^\S/ || $l eq "") && $line1 ne "") { # Identify fields $fld_from = $1 if $line1 =~ m/^From:\s+(.*)$/i; $fld_return_path = $1 if $line1 =~ m/^Return-path:\s+(.*)$/i; $fld_sender = $1 if $line1 =~ m/^Sender:\s+(.*)$/i; $fld_subject = $1 if $line1 =~ m/^Subject:\s+(.*)$/i; $fld_date = $1 if $line1 =~ m/^Date:\s+(.*)$/i; $line1 = ""; } # Replace leading space or tab sequences with a single space character $l =~ s/^\s+/ /; $line1 .= $l; } } } $displayed_from = $fld_sender if $fld_sender ne ""; $displayed_from = $fld_return_path if $fld_return_path ne ""; $displayed_from = $fld_from if $fld_from ne ""; $displayed_from =~ s/[^<]*<([^>]+)>/$1/; @parsed_date = &parse_rfc822_date($fld_date); @cur_date = localtime(); $cur_y = $cur_date[5] + 1900; $cur_m = $cur_date[4]; $msg_y = $parsed_date[1]; $msg_m = $parsed_date[2]; if ($parsed_date[1] == -1) { $displayed_date = "n/a"; } elsif (12 * $msg_y + $msg_m + $$NB_MONTHS_DETAIL >= 12 * $cur_y + $cur_m) { $displayed_date = sprintf("%02d/%02d", $parsed_date[3], $parsed_date[2] + 1); } else { $displayed_date = sprintf("%04d", $parsed_date[1]); } $g_ml[$i][3] = $displayed_date; $g_ml[$i][4] = $displayed_from; $g_ml[$i][5] = $fld_subject; $g_ml[$i][6] = $msg_headers; $g_ml[$i][7] = $msg_body if $action == $$ACTION_DOWNLOAD; $g_ml[$i][0] = $action == $$ACTION_DETAIL ? $$MSG_DETAILED : $$MSG_DOWNLOADED; &my_log("$v2 message \#$msgi", $$LL_NORMAL); } # # Analyze the date as written in RFC822 message headers. # Usage: # # @d = &parse_rfc822_date($the_string); # # Return a list of eight elements: (weekday, year, month, day, hour, minute, second, zone). # zone is a decimal indicating the shift from GMT. # month belongs to [0..11], where 0 = January, 1 = February, ... # weekday belongs to [0..6], where 0 = monday, 1 = Tuesday, ... # day belongs to [1..31]. # sub parse_rfc822_date { my ($wd, $d, $mo, $y, $y1, $h, $mi, $s, $z); my ($sign, $sh, $sm); if (($wd, $d, $mo, $y, $y1, $h, $mi, $s, $z) = $_[0] =~ m/^(\w\w\w)?\s*,?\s*(\d\d?)\s+(\w\w\w)\s+((\d\d)?\d\d)\s+(\d\d?):(\d\d?):(\d\d?)\s+((\+|-)?\d\d\d\d)?/) { $y += 2000 if $y < 100; $wd = $weekdays_list{$wd} if defined($wd); $wd = "n/a" if !defined($wd); $wd = -1 if !defined($wd); $mo = $months_list{$mo}; $mo = -1 if !defined($mo); if (defined($z)) { $z = "+" . $z if $z =~ m/^\d/; ($sign, $sh, $sm) = $z =~ m/(\+|-)(\d\d)(\d\d)/; $z = $sh + $sm / 60; $z = -$z if $sign eq "-"; } } else { $wd = -1; $d = -1; $mo = -1; $y = -1; $h = -1; $mi = -1; $s = -1; $z = ""; } return ($wd, $y, $mo, $d, $h, $mi, $s, $z); } # # Format a number with a separator each 3 digits. # Usage: # # $s = &fnb($number); # sub fnb { my $n = $_[0]; $n =~ s/(\d)(?=(\d\d\d)+(\D|$))/$1$$THOUSAND_SEPARATOR/g; return $n; } # # Right-justify a text for a given total length. # Usage: # # $s = &right_justify($length, $string); # sub right_justify { return sprintf("%" . $_[0] . "s", $_[1]); } # # Left-justify a text for a given total length. # Usage: # # $s = &left_justify($length, $string); # sub left_justify { my $r; my $p; my $s; $p = ""; $p = $_[0] if defined($_[0]); $s = ""; $s = $_[1] if defined($_[1]); $r = sprintf("%-" . $p . "s", $s); return substr($r, 0, $p); } # # Empty all elements of the message list, in the window # Usage: # # &empty_mlb; # sub empty_mlb { @g_ml = (); $g_mlstate = $$ML_EMPTY; $t_mlb->delete(0, "end"); } # # Update the main window's list control according to the content of # g_ml() array. # Usage: # # &update_mlb_from_msg_list; # sub update_mlb_from_msg_list { my $i; for ($i = 0; $i <= $g_nbelems - 1; $i++) { $t_mlb->insert("end", &get_formatted_line(@{$g_ml[$i]})); } } # # Format an element of the messages list (@g_ml) so it can be displayed. # Usage: # # $formatted_string = &get_formatted_line(@{$g_ml[$a_given_indice]}); # sub get_formatted_line { my $s; my $c; my $a; $s = $_[0]; $c = "- unknown state -"; if ($s == $$MSG_SIZEONLY) { $c = &right_justify($$CS_NUMBER, $_[1]) . " " . &right_justify($$CS_SIZE, &fnb($_[2])); } elsif ($s == $$MSG_DETAILED || $s == $$MSG_DOWNLOADED) { $a = " "; $a = "L" if $s == $$MSG_DOWNLOADED; $c = &right_justify($$CT_SIZE, &human_size($_[2])) . " " . $a . " " . &left_justify($$CT_DATE, $_[3]) . " " . &left_justify($$CT_FROM, $_[4]) . " " . &left_justify($$CT_SUBJECT, $_[5]) } return $c; } # # Return the size printed in a human way (followed by b when less than 1024 bytes, followed by k when # less than 1024^2 bytes, followed by m when less than 1024^3, followed by g when less than 1024^4. # Usage: # # $the_string = &human_size($the_size); # sub human_size { my $n = $_[0]; my $factor = "b"; while ($n >= 1024) { $n = int($n / 1024); $factor = "g" if $factor eq "m"; $factor = "m" if $factor eq "k"; $factor = "k" if $factor eq "b"; } return "$n$factor"; } # # Create the log window or delete it, depending on $new_value. # Usage: # # &switch_disp_log_window($new_value) # # If $new_value is non null, make the log window appear, otherwise make it disappear. # sub switch_disp_log_window { my $new_value = $_[0]; my $h; my $x; my $y; my $z; if ($new_value) { # Create log window $t_ml = $t_mw->Toplevel; $x = $t_mw->rootx; $y = $t_mw->rooty; $h = $t_mw->height; $z = $y + $h; $t_ml->geometry("+$x+$z"); $t_ml->title("Log"); # Create frame $t_fl = $t_ml->Frame(-relief => "ridge", -borderwidth => 2) ->pack(-expand => 1, -fill => "both"); # Create text widget in the frame $t_log_text = $t_fl->Scrolled('Text', -scrollbars => "e") ->pack(-expand => 1, -fill => "both"); $t_log_text->configure(-height => 12); # Make the log window appear behind main window $t_ml->lower($t_mw); # Whenever the log window is closed, update $g_disp_log accordingly $t_ml->OnDestroy(sub { $g_disp_log = 0; }); } else { $t_ml->destroy; } $g_disp_log = $new_value; } # # Change the state of the variable $g_pop3_is_connected. Update UI accordingly. # Usage: # # &pop3_switch_conn_state($new_state); # sub pop3_switch_conn_state { my $new_value = $_[0]; $g_pop3_is_connected = $new_value; $t_conn_status->configure(-text => $new_value ? "Connected" : "Disconnected"); } # # Update the list of server messages. # Usage: # # $errno = &pop3_update_msg_list($err_str); # # This proc assumes the connection with the POP3 server has been established. # Return 0 if the connection was successful. # Return a non-zero value if the connection failed. In that case, $err_str contains a # description of the error. # sub pop3_update_msg_list { my $e; my $nb; my $cont; my $l; my $msg_idx; my $msg_size; my $total_size = 0; @g_ml = (); $g_mlstate = $$ML_EMPTY; if (!$g_pop3_is_connected) { $_[0] = "unable to get list of messages: no POP3 conenction is established"; return 1997; } return $e if $e = &pop3_send_recv_and_ctrl("LIST", "+OK", $_[0]); $cont = 1; $nb = 0; while ($cont) { return $e if $e = &sock_recv($l, $_[0]); if (($msg_idx, $msg_size) = $l =~ m/(\d+)\s+(\d+)/) { $nb++; push(@g_ml, [$$MSG_SIZEONLY, $msg_idx, $msg_size]); $total_size += $msg_size; } elsif ($l =~ m/^\.$/) { $cont = 0; $g_mlstate = $$ML_FILLED; $g_nbelems = $nb; &my_log("$g_nbelems message(s) of " . &fnb($total_size) . " byte(s)", $$LL_NORMAL); } else { $_[0] = "unable to parse answer from server, answer = \"$l\""; return 1999; } } } # # Connect to a POP3 server. # Usage: # # $errno = pop3_connect($remote, $port, $user_name, $user_password, $err_str); # # Return 0 if the connection was successful. # Return a non-zero value if the connection failed. In that case, $err_str contains a # description of the error. # sub pop3_connect { my $remote = $_[0]; my $port = $_[1]; my $uname = $_[2]; my $upwd = $_[3]; my $e; my $nb_messages; my $answer; my $is_ok = 0; &my_log("will attempt to connect to $remote:$port", $$LL_VERBOSE); return $e if $e = &tcp_connect($remote, $port, $_[4]); &my_log("connected to $remote:$port", $$LL_NORMAL); $g_curconn_server = $remote; $g_curconn_port = $port; if (!($e = &pop3_recv_and_ctrl("+OK", $_[4]))) { if (!($e = &pop3_send_recv_and_ctrl("USER $uname", "+OK", $_[4]))) { if (!($e = &pop3_send_recv_and_ctrl("PASS $upwd", "+OK", $_[4], $answer, "PASS xxxxxxxx"))) { if (($nb_messages) = $answer =~ m/^\+OK\s+(\d+)/i) { &my_log("$nb_messages message(s) on the server", $$LL_NORMAL); } else { &my_log("unknown message count on the server", $$LL_NORMAL); } $is_ok = 1; } } } if (!$is_ok) { &tcp_close; } return $e; } # # Close the current POP3 connection. # Usage: # # $errno = &pop3_disconnect($err_str); # # Return 0 if it is OK. # Return a non-zero value otherwise, and write an error message in $err_str. # sub pop3_disconnect { my $e; $e = &pop3_send_recv_and_ctrl("QUIT", "+OK", $_[0]); &tcp_close; return $e; } # # Receive a string from the server and control the server response. # Usage: # # $errno = &pop3_recv_and_ctrl($expected_answer, $err_str [, $answer]); # # Return 0 if it is OK. # Return a non-zero value otherwise, and write an error message in $err_str. # $answer is optional, if specified, it gives the string returned by the server. # sub pop3_recv_and_ctrl { my $expected_answer = $_[0]; my $l; my $e; return $e if $e = &sock_recv($l, $_[1]); $_[2] = $l; return $e if $e = &pop3_ctrl($expected_answer, $l, $_[1]); } # # Send a string, receive the answer and control whether the answer is correct. # Usage: # # $errno = &pop3_send_recv_and_ctrl($sent_str, $expected_answer, $err_str [, $answer] [, $log_string]); # # Return 0 if it is OK. # Return a non-zero value otherwise, and write an error message in $err_str. # sub pop3_send_recv_and_ctrl { my ($sent_str, $expected_answer) = @_; my $e; return $e if $e = &sock_send($sent_str, $_[2], $_[4]); return $e if $e = &pop3_recv_and_ctrl($expected_answer, $_[2], $_[3]); } # # Control whether the response of the POP3 server is the one requested. # Usage: # # $errno = $pop3_ctrl($expected_answer, $answer, $err_str); # # Return 0 if it is OK. # Return a non-zero value otherwise, and write an error message in $err_str. # sub pop3_ctrl { my ($expected_answer, $answer) = @_; if ($answer =~ m/^\Q$expected_answer\E/i) { return 0; } else { $_[2] = "Expected \"$expected_answer\" from remote but received \"$answer\""; return 1; } } # # Log a line into the log window. # Usage: # # &my_log($line, $log_level); # sub my_log { my ($l, $level) = @_; my $prefixe = ""; my @when = localtime(); my $header; if ($g_disp_log) { $header = sprintf("%02d/%02d/%04d %02d:%02d:%02d", $when[3], $when[4] + 1, $when[5] + 1900, $when[2], $when[1], $when[0]); if ($level <= $g_cur_log_level) { $prefixe = "** ERROR: " if $level == $$LL_ERROR; $prefixe = "** WARNING: " if $level == $$LL_WARNING; $t_log_text->insert("end", "$header $prefixe$l\n"); $t_log_text->see("end"); $t_ml->idletasks; } } if ($level <= $$LL_NORMAL) { $t_status->configure(-text => "$prefixe$l"); $t_mw->idletasks; } } # # Close the current established TCP connection. # Usage: # # &tcp_close() # sub tcp_close { close SOCK; } # # Connect to a remote host. # Usage: # # $errno = &tcp_connect($remote, $port, $err_str) # # Return 0 if connection succeeds. # Return a non-zero value otherwise, and if so, $err_str is the error description. # sub tcp_connect { my ($remote, $port) = @_; my $iaddr; my $paddr; my $proto; my $oldfh; $iaddr = inet_aton($remote) or $_[2] = "no host: \"$remote\"", return 1; $paddr = sockaddr_in($port, $iaddr); $proto = getprotobyname('tcp'); socket(SOCK, PF_INET, SOCK_STREAM, $proto) or $_[2] = "socket: $!", return 2; connect(SOCK, $paddr) or $_[2] = "connect: $!", return 3; $oldfh = select(SOCK); $| = 1; $/ = $$EOL; select($oldfh); $_[2] = ""; return 0; } # # Send a line to the SOCK fh. Do NOT include final newline sequence in the parameter. # Usage: # # $errno = &sock_send($l, $err_str [, $log_string]); # # If write is successful, return 0. # If write fails, return a non-zero value and $err_str contains an error description. # sub sock_send { my $l = $_[0]; print(SOCK "$l$$EOL") or $_[1] = $!, return 1; $l = $_[2] if defined($_[2]); &my_log("\>\>\> $l", $$LL_DEBUG); return 0; } # # Receive a line from the SOCK fh. Strips final newline sequence from the return value. # Usage: # # $errno = &sock_recv($l, $err_str)); # # If reading is successful, return 0 and $l contains the line. # If reading fails, return a non-zero value and $err_str contains an error description. # sub sock_recv { my $l; $l = ; defined($l) or $_[1] = $!, return 1; chomp $l; &my_log("\<\<\< $l", $$LL_DEBUG); $_[0] = $l; return 0; }