1#!/usr/local/bin/perl 2 3# $Header: /mhub4/sources/imap-tools/mbxIMAPsync.pl,v 1.1 2008/10/18 15:09:25 rick Exp $ 4 5use Socket; 6use FileHandle; 7use Fcntl; 8use Getopt::Std; 9 10 ###################################################################### 11 # Program name mbxIMAPsync.pl # 12 # Written by Rick Sanders # 13 # Date 12 Feb 2004 # 14 # # 15 # Description # 16 # # 17 # mbxIMAPsync is used to synchronize the contents of a Unix # 18 # mailfiles with an IMAP mailbox. The user supplies the location # 19 # & name of the Unix mailbox (eg /var/mail/rfs) and the hostname, # 20 # username, & password of the IMAP account along with the name # 21 # of the IMAP mailbox. For example: # 22 # # 23 # ./mbxIMAPsync.pl -f /var/mail/rfs -i imapsrv/rfs/mypass -m INBOX # 24 # # 25 # mbxIMAPsync compares the messages in the mailfile with those in # 26 # the IMAP mailbox by Message-Id and adds the ones in the mailfile # 27 # which are not in the IMAP mailbox. Then it looks for messages # 28 # in the IMAP mailbox which are not in the mailfile and removes # 29 # them from the IMAP mailbox. # 30 # # 31 # See the Usage() for available options. # 32 ###################################################################### 33 34 &init(); 35 36 &connectToHost($imapHost, 'IMAP'); 37 &login($imapUser,$imapPwd, 'IMAP'); 38 39 # Get list of msgs in the mailfile by Message-Id 40 41 $added=$purged=0; 42 print STDOUT "Processing $mailfile\n"; 43 print STDOUT "Checking for messages to add\n"; 44 @msgs = &readMbox( $mailfile ); 45 foreach $msg ( @msgs ) { 46 @msgid = grep( /^Message-ID:/i, @$msg ); 47 ($label,$msgid) = split(/:/, $msgid[0]); 48 chomp $msgid; 49 &trim( *msgid ); 50 $mailfileMsgs{"$msgid"} = '1'; 51 push( @sourceMsgs, $msgid ); 52 53 if ( !&findMsg( $msgid, $mbx, 'IMAP' ) ) { 54 # print STDOUT "Need to add msgid >$msgid<\n"; 55 my $message; 56 57 foreach $_ ( @$msg ) { chop $_; $message .= "$_\r\n"; } 58 59 if ( &insertMsg($mbx, \$message, $flags, $date, 'IMAP') ) { 60 $added++; 61 print STDOUT " Added $msgid\n"; 62 } 63 } 64 } 65 66 # Remove any messages from the IMAP mailbox that no longer 67 # exist in the mailfile 68 69 print STDOUT "Checking for messages to purge\n"; 70 &getMsgList( $mbx, \@imapMsgs, 'IMAP' ); 71 foreach $msgid ( @imapMsgs ) { 72 if ( $mailfileMsgs{"$msgid"} eq '' ) { 73 if ( &deleteMsg($msgid, $mbx, 'IMAP') ) { 74 &Log(" Marked $msgid for deletion"); 75 print STDOUT " Marked msgid $msgid for deletion\n"; 76 $deleted++; 77 } 78 } 79 } 80 81 if ( $deleted ) { 82 # Need to purge the deleted messages 83 $purged = &expungeMbx( $mbx, 'IMAP' ); 84 } 85 86 &Log("Done"); 87 &Log("Added $added messages to IMAP mailbox $mbx"); 88 &Log("Purged $purged messages from IMAP mailbox $mbx"); 89 90 print STDOUT "\nAdded $added messages to IMAP mailbox $mbx\n"; 91 print STDOUT "Purged $purged messages from IMAP mailbox $mbx\n"; 92 93 exit; 94 95 96sub init { 97 98 if ( ! getopts('f:m:i:L:dx') ) { 99 &usage(); 100 exit; 101 } 102 103 ($imapHost,$imapUser,$imapPwd) = split(/\//, $opt_i); 104 $mailfile = $opt_f; 105 $mbx = $opt_m; 106 $logfile = $opt_L; 107 $debug = 1 if $opt_d; 108 109 if ( $logfile ) { 110 if ( ! open (LOG, ">> $logfile") ) { 111 print "Can't open logfile $logfile: $!\n"; 112 $logfile = ''; 113 } 114 } 115 Log("\nThis is mbxIMAPsync\n"); 116 117 if ( !-e $mailfile ) { 118 &Log("$mailfile does not exist"); 119 exit; 120 } 121 122} 123 124sub usage { 125 126 print "Usage: mbxIMAPsync.pl\n"; 127 print " -f <location of mailfiles>\n"; 128 print " -i imapHost/imapUser/imapPassword\n"; 129 print " -m <IMAP mailbox>\n"; 130 print " [-L <logfile>]\n"; 131 print " [-d debug]\n"; 132 133} 134 135sub readMbox { 136 137my $file = shift; 138my @mail = (); 139my $mail = []; 140my $blank = 1; 141local *FH; 142local $_; 143 144 &Log("Reading the mailfile") if $debug; 145 open(FH,"< $file") or die "Can't open $file"; 146 147 while(<FH>) { 148 if($blank && /\AFrom .*\d{4}/) { 149 push(@mail, $mail) if scalar(@{$mail}); 150 $mail = [ $_ ]; 151 $blank = 0; 152 } 153 else { 154 $blank = m#\A\Z#o ? 1 : 0; 155 push(@{$mail}, $_); 156 } 157 } 158 159 push(@mail, $mail) if scalar(@{$mail}); 160 close(FH); 161 162 return wantarray ? @mail : \@mail; 163} 164 165sub Log { 166 167my $line = shift; 168my $msg; 169 170 ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime (time); 171 $msg = sprintf ("%.2d-%.2d-%.4d.%.2d:%.2d:%.2d %s", 172 $mon + 1, $mday, $year + 1900, $hour, $min, $sec, $line); 173 174 if ( $logfile ) { 175 print LOG "$msg\n"; 176 } else { 177 print "$line\n"; 178 } 179 180} 181 182# connectToHost 183# 184# Make an IMAP4 connection to a host 185# 186sub connectToHost { 187 188my $host = shift; 189my $conn = shift; 190 191 &Log("Connecting to $host") if $debug; 192 193 $sockaddr = 'S n a4 x8'; 194 ($name, $aliases, $proto) = getprotobyname('tcp'); 195 $port = 143; 196 197 if ($host eq "") { 198 &Log ("no remote host defined"); 199 close LOG; 200 exit (1); 201 } 202 203 ($name, $aliases, $type, $len, $serverAddr) = gethostbyname ($host); 204 if (!$serverAddr) { 205 &Log ("$host: unknown host"); 206 close LOG; 207 exit (1); 208 } 209 210 # Connect to the IMAP4 server 211 # 212 213 $server = pack ($sockaddr, &AF_INET, $port, $serverAddr); 214 if (! socket($conn, &PF_INET, &SOCK_STREAM, $proto) ) { 215 &Log ("socket: $!"); 216 close LOG; 217 exit (1); 218 } 219 if ( ! connect( $conn, $server ) ) { 220 &Log ("connect: $!"); 221 return 0; 222 } 223 224 select( $conn ); $| = 1; 225 while (1) { 226 &readResponse ( $conn ); 227 if ( $response =~ /^\* OK/i ) { 228 last; 229 } 230 else { 231 &Log ("Can't connect to host on port $port: $response"); 232 return 0; 233 } 234 } 235 &Log ("connected to $host") if $debug; 236 237 select( $conn ); $| = 1; 238 return 1; 239} 240 241# 242# login in at the source host with the user's name and password 243# 244sub login { 245 246my $user = shift; 247my $pwd = shift; 248my $conn = shift; 249 250 &Log("Logging in as $user") if $debug; 251 $rsn = 1; 252 &sendCommand ($conn, "$rsn LOGIN $user $pwd"); 253 while (1) { 254 &readResponse ( $conn ); 255 if ($response =~ /^$rsn OK/i) { 256 last; 257 } 258 elsif ($response =~ /NO/) { 259 &Log ("unexpected LOGIN response: $response"); 260 return 0; 261 } 262 } 263 &Log("Logged in as $user") if $debug; 264 265 return 1; 266} 267 268 269# logout 270# 271# log out from the host 272# 273sub logout { 274 275my $conn = shift; 276 277 ++$lsn; 278 undef @response; 279 &sendCommand ($conn, "$lsn LOGOUT"); 280 while ( 1 ) { 281 &readResponse ($conn); 282 if ( $response =~ /^$lsn OK/i ) { 283 last; 284 } 285 elsif ( $response !~ /^\*/ ) { 286 &Log ("unexpected LOGOUT response: $response"); 287 last; 288 } 289 } 290 close $conn; 291 return; 292} 293 294# readResponse 295# 296# This subroutine reads and formats an IMAP protocol response from an 297# IMAP server on a specified connection. 298# 299 300sub readResponse 301{ 302 local($fd) = shift @_; 303 304 $response = <$fd>; 305 chop $response; 306 $response =~ s/\r//g; 307 push (@response,$response); 308 if ($debug) { &Log ("<< $response",2); } 309} 310 311# 312# sendCommand 313# 314# This subroutine formats and sends an IMAP protocol command to an 315# IMAP server on a specified connection. 316# 317 318sub sendCommand 319{ 320 local($fd) = shift @_; 321 local($cmd) = shift @_; 322 323 print $fd "$cmd\r\n"; 324 325 if ($showIMAP) { &Log (">> $cmd",2); } 326} 327 328# 329sub insertMsg { 330 331my $mbx = shift; 332my $message = shift; 333my $flags = shift; 334my $date = shift; 335my $conn = shift; 336my ($lsn,$lenx); 337 338 &Log(" Inserting message into mailbox $mbx") if $debug; 339 $lenx = length($$message); 340 341 # Create the mailbox unless we have already done so 342 ++$lsn; 343 if ($destMbxs{"$mbx"} eq '') { 344 &Log("creating mailbox $mbx") if $debug; 345 &sendCommand (IMAP, "$lsn CREATE \"$mbx\""); 346 while ( 1 ) { 347 &readResponse (IMAP); 348 if ( $response =~ /^$rsn OK/i ) { 349 last; 350 } 351 elsif ( $response !~ /^\*/ ) { 352 if (!($response =~ /already exists|reserved mailbox name/i)) { 353 &Log ("WARNING: $response"); 354 } 355 last; 356 } 357 } 358 } 359 360 $destMbxs{"$mbx"} = '1'; 361 362 ++$lsn; 363 $flags =~ s/\\Recent//i; 364 365 # &sendCommand (IMAP, "$lsn APPEND \"$mbx\" ($flags) \"$date\" \{$lenx\}"); 366 &sendCommand (IMAP, "$lsn APPEND \"$mbx\" \{$lenx\}"); 367 &readResponse (IMAP); 368 if ( $response !~ /^\+/ ) { 369 &Log ("unexpected APPEND response: $response"); 370 # next; 371 push(@errors,"Error appending message to $mbx for $user"); 372 return 0; 373 } 374 375 print IMAP "$$message\r\n"; 376 377 undef @response; 378 while ( 1 ) { 379 &readResponse (IMAP); 380 if ( $response =~ /^$lsn OK/i ) { 381 last; 382 } 383 elsif ( $response !~ /^\*/ ) { 384 &Log ("unexpected APPEND response: $response"); 385 # next; 386 return 0; 387 } 388 } 389 390 return 1; 391} 392 393# getMsgList 394# 395# Get a list of the user's messages in the indicated mailbox on 396# the IMAP host 397# 398sub getMsgList { 399 400my $mailbox = shift; 401my $msgs = shift; 402my $conn = shift; 403my $seen; 404my $empty; 405my $msgnum; 406 407 &Log("Getting list of msgs in $mailbox") if $debug; 408 &trim( *mailbox ); 409 &sendCommand ($conn, "$rsn EXAMINE \"$mailbox\""); 410 undef @response; 411 $empty=0; 412 while ( 1 ) { 413 &readResponse ( $conn ); 414 if ( $response =~ / 0 EXISTS/i ) { $empty=1; } 415 if ( $response =~ /^$rsn OK/i ) { 416 # print STDERR "response $response\n"; 417 last; 418 } 419 elsif ( $response !~ /^\*/ ) { 420 &Log ("unexpected response: $response"); 421 # print STDERR "Error: $response\n"; 422 return 0; 423 } 424 } 425 426 &sendCommand ( $conn, "$rsn FETCH 1:* (uid flags internaldate body[header.fields (Message-Id)])"); 427 undef @response; 428 while ( 1 ) { 429 &readResponse ( $conn ); 430 if ( $response =~ /^$rsn OK/i ) { 431 # print STDERR "response $response\n"; 432 last; 433 } 434 elsif ( $XDXDXD ) { 435 &Log ("unexpected response: $response"); 436 &Log ("Unable to get list of messages in this mailbox"); 437 push(@errors,"Error getting list of $user's msgs"); 438 return 0; 439 } 440 } 441 442 # Get a list of the msgs in the mailbox 443 # 444 undef @msgs; 445 undef $flags; 446 for $i (0 .. $#response) { 447 $seen=0; 448 $_ = $response[$i]; 449 450 last if /OK FETCH complete/; 451 452 if ( $response[$i] =~ /FETCH \(UID / ) { 453 $response[$i] =~ /\* ([^FETCH \(UID]*)/; 454 $msgnum = $1; 455 } 456 457 if ($response[$i] =~ /FLAGS/) { 458 # Get the list of flags 459 $response[$i] =~ /FLAGS \(([^\)]*)/; 460 $flags = $1; 461 $flags =~ s/\\Recent//i; 462 } 463 if ( $response[$i] =~ /INTERNALDATE ([^\)]*)/ ) { 464 ### $response[$i] =~ /INTERNALDATE (.+) ([^BODY]*)/i; 465 $response[$i] =~ /INTERNALDATE (.+) BODY/i; 466 $date = $1; 467 $date =~ s/"//g; 468 } 469 if ( $response[$i] =~ /^Message-Id:/i ) { 470 ($label,$msgid) = split(/: /, $response[$i]); 471 push (@$msgs,$msgid); 472 } 473 } 474} 475 476# trim 477# 478# remove leading and trailing spaces from a string 479sub trim { 480 481local (*string) = @_; 482 483 $string =~ s/^\s+//; 484 $string =~ s/\s+$//; 485 486 return; 487} 488 489 490sub findMsg { 491 492my $msgid = shift; 493my $mbx = shift; 494my $conn = shift; 495my $msgnum; 496my $noSuchMbx; 497 498 &Log("Searching for $msgid in $mbx") if $debug; 499 &sendCommand ( $conn, "1 SELECT \"$mbx\""); 500 while (1) { 501 &readResponse ($conn); 502 if ( $response =~ /^1 NO/ ) { 503 $noSuchMbx = 1; 504 last; 505 } 506 last if $response =~ /^1 OK/; 507 } 508 return '' if $noSuchMbx; 509 510 &Log("Search for $msgid") if $debug; 511 &sendCommand ( $conn, "$rsn SEARCH header Message-Id \"$msgid\""); 512 while (1) { 513 &readResponse ($conn); 514 if ( $response =~ /\* SEARCH /i ) { 515 ($dmy, $msgnum) = split(/\* SEARCH /i, $response); 516 ($msgnum) = split(/ /, $msgnum); 517 } 518 519 last if $response =~ /^1 OK/; 520 last if $response =~ /complete/i; 521 } 522 523 if ( $msgnum ) { 524 &Log("Message exists") if $debug; 525 } else { 526 &Log("Message does not exist") if $debug; 527 } 528 529 return $msgnum; 530} 531 532sub deleteMsg { 533 534my $msgid = shift; 535my $mbx = shift; 536my $conn = shift; 537my $rc; 538 539 &Log("Deleting message $msgid") if $debug; 540 $msgnum = &findMsg( $msgid, $mbx, $conn ); 541 542 &sendCommand ( $conn, "1 STORE $msgnum +FLAGS (\\Deleted)"); 543 while (1) { 544 &readResponse ($conn); 545 if ( $response =~ /^1 OK/i ) { 546 $rc = 1; 547 &Log(" Marked $msgid for delete"); 548 last; 549 } 550 551 if ( $response =~ /^1 BAD|^1 NO/i ) { 552 &Log("Error setting \Deleted flag for msg $msgnum: $response"); 553 $rc = 0; 554 last; 555 } 556 } 557 558 return $rc; 559 560} 561 562sub expungeMbx { 563 564my $mbx = shift; 565my $conn = shift; 566my $purged=0; 567 568 &Log("Purging $mbx") if $debug; 569 &sendCommand ( $conn, "1 SELECT \"$mbx\""); 570 while (1) { 571 &readResponse ($conn); 572 last if $response =~ /^1 OK/; 573 574 if ( $response =~ /^1 NO|^1 BAD/i ) { 575 &Log("Error selecting mailbox $mbx: $response"); 576 last; 577 } 578 } 579 580 &sendCommand ( $conn, "1 EXPUNGE"); 581 while (1) { 582 &readResponse ($conn); 583 last if $response =~ /^1 OK/; 584 $purged++ if $response =~ /EXPUNGE/i; 585 586 if ( $response =~ /^1 BAD|^1 NO/i ) { 587 print STDOUT "Error expunging messages: $response\n"; 588 last; 589 } 590 } 591 592 return $purged; 593 594} 595 596