#!/usr/bin/perl -w use strict; use Net::SMTP; use IMAP::Client; use Date::Parse; use MIME::Base64; use File::Type; use File::Spec; my $mailaddr= "yourmail\@provider.bla"; my $imapserver= "imap.provider.bla"; my $imapuser= $mailaddr; my $imappass= "secret"; my $smtpserver= "smtp.provider.bla"; my $smtpfrom= $mailaddr; my %options; my @optlist= qw(help list retrieve save verbose); sub connect_login { my $cl= IMAP::Client->new($imapserver); die "Could not create IMAP client" unless ref($cl); $cl->connect(PeerAddr => $imapserver) or die "Could not connect to $imapserver: " . $cl->error(); $cl->debuglevel($options{"debug"} || 0); $cl->errorstyle("LAST"); $cl->capability_checking(1); $cl->login($imapuser, $imappass) or die "Could not log in: " . $cl->error(); $cl->examine("INBOX") or die "Could not examine INBOX: " . $cl->error(); return $cl; } sub list { my @terms= @_; my $set= "1:*"; my %result; my $cl= connect_login(); if( @terms ) { my @messages; for my $term (@terms) { push @messages, ($cl->search("SUBJECT $term")); # There is no error return code for search() in IMAP::Client 0.13 } unless( @messages ) { $cl->logout(); exit; } $set= join(",", @messages); } %result= $cl->fetch($set, undef, "ENVELOPE", "RFC822.SIZE"); for my $msgnum (sort {$a <=> $b} keys(%result)) { my $msg= $result{$msgnum}; print "Subject: ", $msg->{"ENVELOPE"}->{"SUBJECT"}, "\n", "Date: ", $msg->{"ENVELOPE"}->{"DATE"}, "\n", "Size: ", $msg->{"RFC822"}->{"SIZE"}, " octets\n\n"; } $cl->logout(); } my @monthname= ( "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" , "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec" ); sub retrieve { my ($term, @datewords)= @_; my $date= join(" ", @datewords); my $msgnum; my %result; my $data; my $name; my $mdate; my (undef, undef, undef, $day, $month, $year)= strptime($date); die "Could not parse date $date." unless( defined($day) && defined($month) && defined($year) ); $date= sprintf("%02d-%s-%04d", $day, $monthname[$month], $year+1900); my $cl= connect_login(); # This relies on the fact that the server returns earlier messages first, # which seems to be true ($msgnum)= ($cl->search("SUBJECT $term SENTSINCE $date")); %result= $cl->fetch($msgnum, { "body" => 2, peek => 1 }, "ENVELOPE"); $data= $result{$msgnum}->{"BODY"}->{2}->{"BODY"}; (undef, undef, undef, $day, $month, $year)= strptime($result{$msgnum}->{"ENVELOPE"}->{"DATE"}); if( defined($day) && defined($month) && defined($year) ) { $mdate= sprintf("%02d-%s-%04d", $day, $monthname[$month], $year+1900); } else { print STDERR "Could not parse mail envelope date. Using requested date" . " instead.\n"; $mdate= $date; } if( $cl->imap_send("FETCH $msgnum BODYSTRUCTURE") && (my @struct= $cl->imap_receive()) ) { $name= $1 if $struct[0] =~ /\"filename\"\s+\"([^"]+)/i; } $cl->logout(); if( !$name ) { print STDERR "Could not obtain file name from BODYSTRUCTURE. ", "Using search term instead.\n"; $name= $term; } $name .= ".$mdate"; open OUT, ">$name" or die "Could not open $name for output"; print OUT MIME::Base64::decode_base64($data); close OUT; } sub save { my ($name)= @_; my $data; my $mimet; my $bound= "----8<----snip----8<----"; open IN, "<$name" or die "Could not open $name"; { local $/; $data= ; die "Could not read $name: $!" unless defined($data); } close IN; (undef, undef, $name)= File::Spec->splitpath($name); my $ft= File::Type->new(); $mimet= $ft->checktype_contents($data); # Connect to IMAP server to authenticate: my $cl= connect_login(); $cl->logout(); my $sm= Net::SMTP->new($smtpserver, "Debug" => $options{"debug"}) or die "Could not create SMTP object"; $sm->mail($smtpfrom) or die "Error on MAIL command"; $sm->to($mailaddr) or die "Error on RCPT TO command"; $sm->data() or die "Error on DATA command"; $sm->datasend("From: $smtpfrom\r\nTo: $mailaddr\r\nSubject: $name\r\n" . "MIME-Version: 1.0\r\n" . "Content-Type: multipart/mixed; boundary=\"$bound\"\r\n"); $sm->datasend("\r\n--$bound\r\nContent-Type: text/plain\r\n" . "Content-Transfer-Encoding: quoted-printable\r\n\r\n" . "\r\n\r\n"); $sm->datasend("--$bound\r\nContent-Type: $mimet; name=\"$name\"\r\n" . "Content-Transfer-Encoding: base64\r\n" . "Content-Disposition: attachment; filename=\"$name\"\r\n\r\n" . MIME::Base64::encode_base64($data) . "--$bound--\r\n"); $sm->quit(); } sub help { print < Available options: -s, --save Send a mail to the mailbox with as attachment (default operation also without options) -l, --list List mails containing one of in their subject. Without argument, lists all. -r, --retrieve Retrieves attachment of the message with <1st arg> in the subject which is just younger than the date in . -d, --debug Print out conversation with IMAP/SMTP server EOF } if( @ARGV == 0 ) { help(); exit 0; } while( @ARGV && $ARGV[0] =~ s/^-// ) { my $opt= shift @ARGV; my $existent; if( $opt =~ s/^-// ) { ($existent)= grep $_ eq $opt, @optlist; $opt= "--" . $opt; } elsif( length($opt) == 1 ) { ($existent)= grep substr($_, 0, 1) eq $opt, @optlist; $opt= "-" . $opt; } unless( $existent ) { print STDERR "Unknown option $opt.\n"; help(); exit 1; } $options{$existent}= 1; } my $totalcmds= ($options{"list"} || 0) + ($options{"retrieve"} || 0) + ($options{"help"} || 0) + ($options{"save"} || 0); if( $totalcmds > 1 ) { print STDERR "Only one of -l, -r, -s or -h may be given.\n"; help(); exit 1; } if( $options{"help"} ) { help(); exit 0; } elsif( $options{"list"} ) { list( @ARGV ); } elsif( $options{"retrieve"} ) { if( @ARGV < 2 ) { print STDERR "--retrieve requires a keyword and a date argument.\n"; help(); exit 1; } retrieve(@ARGV); } else { if( @ARGV != 1 ) { print STDERR "--save takes exactly one argument.\n"; help(); exit 1; } save($ARGV[0]); }