imapsync.pl 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561
  1. #!/usr/bin/perl
  2. # $Header: /mhub4/sources/imap-tools/imapsync.pl,v 1.27 2012/03/01 05:32:58 rick Exp $
  3. #######################################################################
  4. # Program name imapsync.pl #
  5. # Written by Rick Sanders #
  6. # #
  7. # Description #
  8. # #
  9. # imapsync is a utility for synchronizing a user's account on two #
  10. # IMAP servers. When supplied with host/user/password information #
  11. # for two IMAP hosts imapsync does the following: #
  12. # 1. Adds any messages on the 1st host which aren't on the 2nd #
  13. # 2. Deletes any messages from the 2nd which aren't on the 1st #
  14. # 3. Sets the message flags on the 2nd to match the 1st's flags#
  15. # #
  16. # imapsync is called like this: #
  17. # ./imapsync -S host1/user1/password1 -D host2/user2/password2 #
  18. # #
  19. # Optional arguments: #
  20. # -d debug #
  21. # -L logfile #
  22. # -m mailbox list (sync only certain mailboxes,see usage notes) #
  23. #######################################################################
  24. use Socket;
  25. use IO::Socket;
  26. use IO::Socket::INET;
  27. use FileHandle;
  28. use Fcntl;
  29. use Getopt::Std;
  30. #################################################################
  31. # Main program. #
  32. #################################################################
  33. init();
  34. # Get list of all messages on the source host by Message-Id
  35. #
  36. connectToHost($sourceHost, \$src) or exit;
  37. login($sourceUser,$sourcePwd, $sourceHost,$src) or exit;
  38. namespace( $src, \$srcPrefix, \$srcDelim, $opt_x );
  39. connectToHost( $destHost, \$dst ) or exit;
  40. login( $destUser,$destPwd, $destHost, $dst ) or exit;
  41. namespace( $dst, \$dstPrefix, \$dstDelim, $opt_y );
  42. # Create mailboxes on the dst if they don't already exist
  43. my @source_mbxs = getMailboxList( $src );
  44. # Exclude certain ones if that's what the user wants
  45. exclude_mbxs( \@source_mbxs ) if $excludeMbxs;
  46. map_mbx_names( \%mbx_map, $srcDelim, $dstDelim );
  47. createDstMbxs( \@source_mbxs, $dst );
  48. # Check for new messages and existing ones with new flags
  49. $adds=$updates=$deletes=0;
  50. ($added,$updated) = check_for_adds( \@source_mbxs, \%REVERSE, $src, $dst );
  51. # Remove messages from the dst that no longer exist on the src
  52. $deleted = check_for_deletes( \%REVERSE, $dst, $src );
  53. logout( $src );
  54. logout( $dst );
  55. Log("\nSummary of results");
  56. Log(" Added $added");
  57. Log(" Updated $updated");
  58. Log(" Deleted $deleted");
  59. exit;
  60. sub init {
  61. $os = $ENV{'OS'};
  62. processArgs();
  63. $timeout = 60 unless $timeout;
  64. # Open the logFile
  65. #
  66. if ( $logfile ) {
  67. if ( !open(LOG, ">> $logfile")) {
  68. print STDOUT "Can't open $logfile: $!\n";
  69. }
  70. select(LOG); $| = 1;
  71. }
  72. Log("$0 starting\n");
  73. # Determine whether we have SSL support via openSSL and IO::Socket::SSL
  74. $ssl_installed = 1;
  75. eval 'use IO::Socket::SSL';
  76. if ( $@ ) {
  77. $ssl_installed = 0;
  78. }
  79. $debug = 1;
  80. }
  81. #
  82. # sendCommand
  83. #
  84. # This subroutine formats and sends an IMAP protocol command to an
  85. # IMAP server on a specified connection.
  86. #
  87. sub sendCommand
  88. {
  89. local($fd) = shift @_;
  90. local($cmd) = shift @_;
  91. print $fd "$cmd\r\n";
  92. if ($showIMAP) { Log (">> $cmd",2); }
  93. }
  94. #
  95. # readResponse
  96. #
  97. # This subroutine reads and formats an IMAP protocol response from an
  98. # IMAP server on a specified connection.
  99. #
  100. sub readResponse
  101. {
  102. local($fd) = shift @_;
  103. $response = <$fd>;
  104. chop $response;
  105. $response =~ s/\r//g;
  106. push (@response,$response);
  107. if ($showIMAP) { Log ("<< $response",2); }
  108. }
  109. #
  110. # Log
  111. #
  112. # This subroutine formats and writes a log message to STDERR.
  113. #
  114. sub Log {
  115. my $str = shift;
  116. # If a logfile has been specified then write the output to it
  117. # Otherwise write it to STDOUT
  118. if ( $logfile ) {
  119. ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime;
  120. if ($year < 99) { $yr = 2000; }
  121. else { $yr = 1900; }
  122. $line = sprintf ("%.2d-%.2d-%d.%.2d:%.2d:%.2d %s %s\n",
  123. $mon + 1, $mday, $year + $yr, $hour, $min, $sec,$$,$str);
  124. print LOG "$line";
  125. } else {
  126. print STDOUT "$str\n";
  127. }
  128. print STDOUT "$str\n" if $opt_Q;
  129. }
  130. # insertMsg
  131. #
  132. # This routine inserts an RFC822 messages into a user's folder
  133. #
  134. sub insertMsg {
  135. local ($conn, $mbx, *message, $flags, $date, $msgid) = @_;
  136. local ($lenx);
  137. Log("Inserting message $msgid") if $debug;
  138. $lenx = length($message);
  139. $totalBytes = $totalBytes + $lenx;
  140. $totalMsgs++;
  141. $flags = flags( $flags );
  142. fixup_date( \$date );
  143. sendCommand ($conn, "1 APPEND \"$mbx\" ($flags) \"$date\" \{$lenx\}");
  144. readResponse ($conn);
  145. if ( $response !~ /^\+/ ) {
  146. Log ("unexpected APPEND response: $response");
  147. # next;
  148. push(@errors,"Error appending message to $mbx for $user");
  149. return 0;
  150. }
  151. print $conn "$message\r\n";
  152. undef @response;
  153. while ( 1 ) {
  154. readResponse ($conn);
  155. if ( $response =~ /^1 OK/i ) {
  156. last;
  157. }
  158. elsif ( $response !~ /^\*/ ) {
  159. Log ("unexpected APPEND response: $response");
  160. # next;
  161. return 0;
  162. }
  163. }
  164. return 1;
  165. }
  166. # Make a connection to an IMAP host
  167. sub connectToHost {
  168. my $host = shift;
  169. my $conn = shift;
  170. Log("Connecting to $host") if $verbose;
  171. ($host,$port) = split(/:/, $host);
  172. $port = 143 unless $port;
  173. # We know whether to use SSL for ports 143 and 993. For any
  174. # other ones we'll have to figure it out.
  175. $mode = sslmode( $host, $port );
  176. if ( $mode eq 'SSL' ) {
  177. unless( $ssl_installed == 1 ) {
  178. warn("You must have openSSL and IO::Socket::SSL installed to use an SSL connection");
  179. Log("You must have openSSL and IO::Socket::SSL installed to use an SSL connection");
  180. exit;
  181. }
  182. Log("Attempting an SSL connection") if $verbose;
  183. $$conn = IO::Socket::SSL->new(
  184. Proto => "tcp",
  185. SSL_verify_mode => 0x00,
  186. PeerAddr => $host,
  187. PeerPort => $port,
  188. );
  189. unless ( $$conn ) {
  190. $error = IO::Socket::SSL::errstr();
  191. Log("Error connecting to $host: $error");
  192. warn("Error connecting to $host: $error");
  193. exit;
  194. }
  195. } else {
  196. # Non-SSL connection
  197. Log("Attempting a non-SSL connection") if $debug;
  198. $$conn = IO::Socket::INET->new(
  199. Proto => "tcp",
  200. PeerAddr => $host,
  201. PeerPort => $port,
  202. );
  203. unless ( $$conn ) {
  204. Log("Error connecting to $host:$port: $@");
  205. exit;
  206. }
  207. }
  208. Log("Connected to $host on port $port");
  209. return 1;
  210. }
  211. sub sslmode {
  212. my $host = shift;
  213. my $port = shift;
  214. my $mode;
  215. # Determine whether to make an SSL connection
  216. # to the host. Return 'SSL' if so.
  217. if ( $port == 143 ) {
  218. # Standard non-SSL port
  219. return '';
  220. } elsif ( $port == 993 ) {
  221. # Standard SSL port
  222. return 'SSL';
  223. }
  224. unless ( $ssl_installed ) {
  225. # We don't have SSL installed on this machine
  226. return '';
  227. }
  228. # For any other port we need to determine whether it supports SSL
  229. my $conn = IO::Socket::SSL->new(
  230. Proto => "tcp",
  231. SSL_verify_mode => 0x00,
  232. PeerAddr => $host,
  233. PeerPort => $port,
  234. );
  235. if ( $conn ) {
  236. close( $conn );
  237. $mode = 'SSL';
  238. } else {
  239. $mode = '';
  240. }
  241. return $mode;
  242. }
  243. # trim
  244. #
  245. # remove leading and trailing spaces from a string
  246. sub trim {
  247. local (*string) = @_;
  248. $string =~ s/^\s+//;
  249. $string =~ s/\s+$//;
  250. return;
  251. }
  252. # login
  253. #
  254. # login in at the source host with the user's name and password
  255. #
  256. sub login {
  257. my $user = shift;
  258. my $pwd = shift;
  259. my $host = shift;
  260. my $conn = shift;
  261. Log("Authenticating to $host as $user");
  262. sendCommand ($conn, "1 LOGIN \"$user\" \"$pwd\"");
  263. while (1) {
  264. readResponse ( $conn );
  265. last if $response =~ /^1 OK/i;
  266. if ($response =~ /^1 NO|^1 BAD/i) {
  267. Log ("unexpected LOGIN response: $response");
  268. return 0;
  269. }
  270. }
  271. Log("Logged in as $user") if $debug;
  272. return 1;
  273. }
  274. # logout
  275. #
  276. # log out from the host
  277. #
  278. sub logout {
  279. my $conn = shift;
  280. undef @response;
  281. sendCommand ($conn, "1 LOGOUT");
  282. while ( 1 ) {
  283. readResponse ($conn);
  284. if ( $response =~ /^1 OK/i ) {
  285. last;
  286. }
  287. elsif ( $response !~ /^\*/ ) {
  288. Log ("unexpected LOGOUT response: $response");
  289. last;
  290. }
  291. }
  292. close $conn;
  293. return;
  294. }
  295. # getMailboxList
  296. #
  297. # get a list of the user's mailboxes from the source host
  298. #
  299. sub getMailboxList {
  300. my $conn = shift;
  301. my $delim = shift;
  302. my @mbxs;
  303. my @mailboxes;
  304. # Get a list of the user's mailboxes
  305. #
  306. if ( $mbxList ) {
  307. # The user has supplied a list of mailboxes so only processes
  308. # the ones in that list
  309. @mbxs = split(/,/, $mbxList);
  310. foreach $mbx ( @mbxs ) {
  311. # trim( *mbx );
  312. push( @mailboxes, $mbx );
  313. }
  314. return @mailboxes;
  315. }
  316. Log("Get list of mailboxes") if $verbose;
  317. sendCommand ($conn, "1 LIST \"\" *");
  318. undef @response;
  319. while ( 1 ) {
  320. readResponse ($conn);
  321. if ( $response =~ /^1 OK/i ) {
  322. last;
  323. }
  324. elsif ( $response !~ /^\*/ ) {
  325. Log ("unexpected response: $response");
  326. return 0;
  327. }
  328. }
  329. @mbxs = ();
  330. for $i (0 .. $#response) {
  331. $response[$i] =~ s/\s+/ /;
  332. if ( $response[$i] =~ /"$/ ) {
  333. $response[$i] =~ /\* LIST \((.*)\) "(.+)" "(.+)"/i;
  334. $mbx = $3;
  335. } elsif ( $response[$i] =~ /\* LIST \((.*)\) NIL (.+)/i ) {
  336. $mbx = $2;
  337. } else {
  338. $response[$i] =~ /\* LIST \((.*)\) "(.+)" (.+)/i;
  339. $mbx = $3;
  340. }
  341. $mbx =~ s/^\s+//; $mbx =~ s/\s+$//;
  342. if ($response[$i] =~ /NOSELECT/i) {
  343. if ( $include_nosel_mbxs ) {
  344. $nosel_mbxs{"$mbx"} = 1;
  345. } else {
  346. Log("$mbx is set NOSELECT, skipping it");
  347. next;
  348. }
  349. }
  350. if ($mbx =~ /^\#|^Public Folders/i) {
  351. # Skip public mbxs
  352. next;
  353. }
  354. push ( @mbxs, $mbx ) if $mbx ne '';
  355. }
  356. return @mbxs;
  357. }
  358. # exclude_mbxs
  359. #
  360. # Exclude certain mailboxes from the list if the user
  361. # has provided an exclude list with the -e argument
  362. sub exclude_mbxs {
  363. my $mbxs = shift;
  364. my @new_list;
  365. my %exclude;
  366. foreach my $exclude ( split(/,/, $excludeMbxs ) ) {
  367. $exclude{"$exclude"} = 1;
  368. }
  369. foreach my $mbx ( @$mbxs ) {
  370. next if $exclude{"$mbx"};
  371. push( @new_list, $mbx );
  372. }
  373. @$mbxs = @new_list;
  374. }
  375. # getMsgList
  376. #
  377. # Get a list of the user's messages in the indicated mailbox on
  378. # the source host
  379. #
  380. sub getMsgList {
  381. my $mailbox = shift;
  382. my $msgs = shift;
  383. my $conn = shift;
  384. my $seen;
  385. my $empty;
  386. my $msgnum;
  387. my $from;
  388. my $flags;
  389. my $msgid;
  390. # Get a list of the msgs in this mailbox
  391. @$msgs = ();
  392. trim( *mailbox );
  393. sendCommand ($conn, "1 EXAMINE \"$mailbox\"");
  394. undef @response;
  395. $empty=0;
  396. while ( 1 ) {
  397. readResponse ( $conn );
  398. if ( $response =~ / 0 EXISTS/i ) { $empty=1; }
  399. if ( $response =~ /^1 OK/i ) {
  400. # print STDERR "response $response\n";
  401. last;
  402. }
  403. elsif ( $response !~ /^\*/ ) {
  404. Log ("unexpected response: $response");
  405. # print STDERR "Error: $response\n";
  406. return 0;
  407. }
  408. }
  409. return if $empty;
  410. sendCommand ( $conn, "1 FETCH 1:* (uid flags internaldate body[header.fields (From Date Message-ID Subject)])");
  411. undef @response;
  412. while ( 1 ) {
  413. readResponse ( $conn );
  414. if ( $response =~ /^1 OK/i ) {
  415. # print STDERR "response $response\n";
  416. last;
  417. }
  418. last if $response =~ /^1 NO|^1 BAD/;
  419. }
  420. @$msgs = ();
  421. $flags = '';
  422. for $i (0 .. $#response) {
  423. last if $response[$i] =~ /^1 OK FETCH complete/i;
  424. if ($response[$i] =~ /FLAGS/) {
  425. # Get the list of flags
  426. $response[$i] =~ /FLAGS \(([^\)]*)/;
  427. $flags = $1;
  428. $flags =~ s/\\Recent|\\Forwarded//ig;
  429. }
  430. # if ( $response[$i] =~ /^Message-ID:\s*\<(.+)\>/i ) {
  431. # Consider the < and > to be part of the message-id.
  432. if ( $response[$i] =~ /^Message-ID:\s*(.+)/i ) {
  433. $msgid = $1;
  434. }
  435. if ( $response[$i] =~ /INTERNALDATE/) {
  436. $response[$i] =~ /INTERNALDATE (.+) BODY/i;
  437. $date = $1;
  438. $date =~ /"(.+)"/;
  439. $date = $1;
  440. $date =~ s/"//g;
  441. }
  442. # if ( $response[$i] =~ /\* (.+) [^FETCH]/ ) {
  443. if ( $response[$i] =~ /\* (.+) FETCH/ ) {
  444. ($msgnum) = split(/\s+/, $1);
  445. }
  446. if ( $msgnum and $date and $msgid ) {
  447. push (@$msgs,"$msgid||||||$msgnum||||||$flags||||||$date");
  448. $msgnum=$msgid=$date=$flags='';
  449. }
  450. }
  451. }
  452. # getDatedMsgList
  453. #
  454. # Get a list of the user's messages in a mailbox on
  455. # the host which were sent after the specified date
  456. #
  457. sub getDatedMsgList {
  458. my $mailbox = shift;
  459. my $date = shift;
  460. my $msgs = shift;
  461. my $conn = shift;
  462. my ($seen, $empty, @list,$msgid);
  463. my $loops;
  464. # Get a list of messages sent after the specified date
  465. my @list;
  466. my @msgs;
  467. if ( $date !~ /-/ ) {
  468. # Delta in days, convert to DD-MMM-YYYY
  469. $date = get_date( $sync_since );
  470. }
  471. sendCommand ($conn, "1 SELECT \"$mailbox\"");
  472. while ( 1 ) {
  473. readResponse ($conn);
  474. if ( $response =~ / EXISTS/i) {
  475. $response =~ /\* ([^EXISTS]*)/;
  476. Log(" There are $1 messages in $mailbox");
  477. } elsif ( $response =~ /^1 OK/i ) {
  478. last;
  479. } elsif ( $response !~ /^\*/ ) {
  480. Log ("unexpected SELECT response: $response");
  481. return 0;
  482. }
  483. if ( $loops++ > 1000 ) {
  484. Log("No response to SELECT command, skipping this mailbox");
  485. last;
  486. }
  487. }
  488. #
  489. # Get list of messages sent before the reference date
  490. #
  491. Log("Get messages sent after $date") if $debug;
  492. $nums = "";
  493. sendCommand ($conn, "1 SEARCH SENTSINCE \"$date\"");
  494. while ( 1 ) {
  495. readResponse ($conn);
  496. if ( $response =~ /^1 OK/i ) {
  497. last;
  498. }
  499. elsif ( $response =~ /^\*\s+SEARCH/i ) {
  500. ($nums) = ($response =~ /^\*\s+SEARCH\s+(.*)/i);
  501. }
  502. elsif ( $response !~ /^\*/ ) {
  503. Log ("unexpected SEARCH response: $response");
  504. return;
  505. }
  506. }
  507. if ( $nums eq "" ) {
  508. Log (" $mailbox has no messages sent after $date") if $debug;
  509. return;
  510. }
  511. # Log(" Msgnums for messages in $mailbox sent after $date $nums") if $debug;
  512. $nums =~ s/\s+/ /g;
  513. @msgList = ();
  514. @msgList = split(/ /, $nums);
  515. if ($#msgList == -1) {
  516. # No msgs in this mailbox
  517. return 1;
  518. }
  519. @$msgs = ();
  520. for $num (@msgList) {
  521. sendCommand ( $conn, "1 FETCH $num (uid flags internaldate body[header.fields (Message-Id Date)])");
  522. undef @response;
  523. while ( 1 ) {
  524. readResponse ( $conn );
  525. if ( $response =~ /^1 OK/i ) {
  526. last;
  527. }
  528. last if $response =~ /^1 NO|^1 BAD|^\* BYE/;
  529. }
  530. $flags = '';
  531. foreach $_ ( @response ) {
  532. last if /^1 OK FETCH complete/i;
  533. if ( /FLAGS/ ) {
  534. # Get the list of flags
  535. /FLAGS \(([^\)]*)/;
  536. $flags = $1;
  537. $flags =~ s/\\Recent|\\Forwarded//ig;
  538. }
  539. if ( /Message-Id:\s*(.+)/i ) {
  540. $msgid = $1;
  541. }
  542. if ( /INTERNALDATE/) {
  543. /INTERNALDATE (.+) BODY/i;
  544. $date = $1;
  545. $date =~ /"(.+)"/;
  546. $date = $1;
  547. $date =~ s/"//g;
  548. }
  549. if ( /\* (.+) FETCH/ ) {
  550. ($msgnum) = split(/\s+/, $1);
  551. }
  552. if ( $msgid and $msgnum and $date and $msgid ) {
  553. push (@$msgs,"$msgid||||||$msgnum||||||$flags||||||$date");
  554. $msgnum=$msgid=$date=$flags='';
  555. }
  556. }
  557. }
  558. # @msgs = ();
  559. # @$msgs = @list;
  560. return 1;
  561. }
  562. sub createMbx {
  563. my $mbx = shift;
  564. my $conn = shift;
  565. my $created;
  566. # Create the mailbox if necessary
  567. sendCommand ($conn, "1 CREATE \"$mbx\"");
  568. while ( 1 ) {
  569. readResponse ($conn);
  570. if ( $response =~ /^1 OK/i ) {
  571. $created = 1;
  572. last;
  573. }
  574. last if $response =~ /already exists/i;
  575. if ( $response =~ /^1 NO|^1 BAD/ ) {
  576. Log ("Error creating $mbx: $response");
  577. last;
  578. }
  579. }
  580. Log("Created mailbox $mbx") if $created;
  581. }
  582. sub fetchMsg {
  583. my $msgnum = shift;
  584. my $conn = shift;
  585. my $message;
  586. sendCommand( $conn, "1 FETCH $msgnum (rfc822)");
  587. while (1) {
  588. readResponse ($conn);
  589. if ( $response =~ /^1 BAD|^1 NO/i ) {
  590. Log("Unexpected FETCH response: $response");
  591. return '';
  592. }
  593. if ( $response =~ /^1 OK/i ) {
  594. $size = length($message);
  595. last;
  596. }
  597. elsif ($response =~ /message number out of range/i) {
  598. Log ("Error fetching uid $uid: out of range",2);
  599. $stat=0;
  600. last;
  601. }
  602. elsif ($response =~ /Bogus sequence in FETCH/i) {
  603. Log ("Error fetching uid $uid: Bogus sequence in FETCH",2);
  604. $stat=0;
  605. last;
  606. }
  607. elsif ( $response =~ /message could not be processed/i ) {
  608. Log("Message could not be processed, skipping it ($user,msgnum $msgnum,$dstMbx)");
  609. push(@errors,"Message could not be processed, skipping it ($user,msgnum $msgnum,$dstMbx)");
  610. $stat=0;
  611. last;
  612. }
  613. elsif
  614. ($response =~ /^\*\s+$msgnum\s+FETCH\s+\(.*RFC822\s+\{[0-9]+\}/i) {
  615. ($len) = ($response =~ /^\*\s+$msgnum\s+FETCH\s+\(.*RFC822\s+\{([0-9]+)\}/i);
  616. $cc = 0;
  617. $message = "";
  618. while ( $cc < $len ) {
  619. $n = 0;
  620. $n = read ($conn, $segment, $len - $cc);
  621. if ( $n == 0 ) {
  622. Log ("unable to read $len bytes");
  623. return 0;
  624. }
  625. $message .= $segment;
  626. $cc += $n;
  627. }
  628. }
  629. }
  630. return $message;
  631. }
  632. sub fetchMsgFlags {
  633. my $msgnum = shift;
  634. my $conn = shift;
  635. my $flags;
  636. # Read the IMAP flags for a message
  637. sendCommand( $conn, "1 FETCH $msgnum (flags)");
  638. while (1) {
  639. readResponse ($conn);
  640. if ( $response =~ /^1 OK|^1 BAD|^1 NO/i ) {
  641. last;
  642. }
  643. if ( $response =~ /\* $msgnum FETCH \(FLAGS \((.+)\)\)/i ) {
  644. $flags = $1;
  645. Log(" $msgnum - flags $flags") if $verbose;
  646. }
  647. }
  648. return $flags;
  649. }
  650. sub usage {
  651. print STDOUT "usage:\n";
  652. print STDOUT " imapsync -S sourceHost/sourceUser/sourcePassword\n";
  653. print STDOUT " -D destHost/destUser/destPassword\n";
  654. print STDOUT " -d debug\n";
  655. print STDOUT " -L logfile\n";
  656. print STDOUT " -s <since> Sync messages since this date (DD-MMM-YYYY) or number of days ago\n";
  657. print STDOUT " -m mailbox list (eg \"Inbox, Drafts, Notes\". Default is all mailboxes)\n";
  658. print STDOUT " -e exclude mailbox list\n";
  659. print STDOUT " -n do not delete messages from destination\n";
  660. exit;
  661. }
  662. sub processArgs {
  663. if ( !getopts( "dvS:D:L:m:e:hIx:y:FM:s:nNQ" ) ) {
  664. usage();
  665. }
  666. ($sourceHost,$sourceUser,$sourcePwd) = split(/\//, $opt_S);
  667. ($destHost, $destUser, $destPwd) = split(/\//, $opt_D);
  668. $mbxList = $opt_m;
  669. $excludeMbxs = $opt_e;
  670. $logfile = $opt_L;
  671. $mbx_map_fn = $opt_M;
  672. $sync_since = $opt_s;
  673. $no_deletes = 1 if $opt_n;
  674. $debug = 1 if $opt_d;
  675. $verbose = 1 if $opt_v;
  676. $showIMAP = 1 if $opt_I;
  677. $include_nosel_mbxs = 1 if $opt_N;
  678. usage() if $opt_h;
  679. }
  680. sub findMsg {
  681. my $msgid = shift;
  682. my $conn = shift;
  683. my $msgnum;
  684. # Search a mailbox on the server for a message by its msgid.
  685. Log(" Search for $msgid") if $verbose;
  686. sendCommand ( $conn, "1 SEARCH header Message-Id \"$msgid\"");
  687. while (1) {
  688. readResponse ($conn);
  689. if ( $response =~ /\* SEARCH /i ) {
  690. ($dmy, $msgnum) = split(/\* SEARCH /i, $response);
  691. ($msgnum) = split(/ /, $msgnum);
  692. }
  693. last if $response =~ /^1 OK|^1 NO|^1 BAD/;
  694. last if $response =~ /complete/i;
  695. }
  696. if ( $verbose ) {
  697. Log("$msgid was not found") unless $msgnum;
  698. }
  699. return $msgnum;
  700. }
  701. sub deleteMsg {
  702. my $conn = shift;
  703. my $msgnum = shift;
  704. my $rc;
  705. # Mark a message for deletion by setting \Deleted flag
  706. Log(" msgnum is $msgnum") if $verbose;
  707. sendCommand ( $conn, "1 STORE $msgnum +FLAGS (\\Deleted)");
  708. while (1) {
  709. readResponse ($conn);
  710. if ( $response =~ /^1 OK/i ) {
  711. $rc = 1;
  712. Log(" Marked $msgid for delete") if $verbose;
  713. last;
  714. }
  715. if ( $response =~ /^1 BAD|^1 NO/i ) {
  716. Log("Error setting \Deleted flag for msg $msgnum: $response");
  717. $rc = 0;
  718. last;
  719. }
  720. }
  721. return $rc;
  722. }
  723. sub expungeMbx {
  724. my $conn = shift;
  725. my $mbx = shift;
  726. my $status;
  727. my $loops;
  728. # Remove the messages from a mailbox
  729. Log("Expunging $mbx mailbox") if $verbose;
  730. sendCommand ( $conn, "1 SELECT \"$mbx\"");
  731. while (1) {
  732. readResponse ($conn);
  733. if ( $response =~ /^1 OK/ ) {
  734. $status = 1;
  735. last;
  736. }
  737. if ( $response =~ /^1 NO|^1 BAD/i ) {
  738. Log("Error selecting mailbox $mbx: $response");
  739. last;
  740. }
  741. if ( $loops++ > 1000 ) {
  742. Log("No response to SELECT command, skipping this mailbox");
  743. last;
  744. }
  745. }
  746. return unless $status;
  747. sendCommand ( $conn, "1 EXPUNGE");
  748. while (1) {
  749. readResponse ($conn);
  750. last if $response =~ /^1 OK/;
  751. if ( $response =~ /^1 BAD|^1 NO/i ) {
  752. print "Error expunging messages: $response\n";
  753. last;
  754. }
  755. }
  756. }
  757. sub check_for_adds {
  758. my $source_mbxs = shift;
  759. my $REVERSE = shift;
  760. my $src = shift;
  761. my $dst = shift;
  762. my @sourceMsgs;
  763. # Compare the contents of the user's mailboxes on the source
  764. # with those on the destination. Add any new messages to the
  765. # destination and update if necessary the flags on the existing
  766. # ones.
  767. Log("Checking for adds & updates");
  768. my $added=$updated=0;
  769. foreach my $src_mbx ( @$source_mbxs ) {
  770. Log("Mailbox $src_mbx");
  771. if ( $include_nosel_mbxs ) {
  772. # If a mailbox was 'Noselect' on the src but the user wants
  773. # it created as a regular folder on the dst then do so. They
  774. # don't hold any messages so after creating them we don't need
  775. # to do anything else.
  776. next if $nosel_mbxs{"$src_mbx"};
  777. }
  778. expungeMbx( $src, $src_mbx );
  779. $dst_mbx = mailboxName( $src_mbx,$srcPrefix,$srcDelim,$dstPrefix,$dstDelim );
  780. # Record the association between source and dest mailboxes
  781. $$REVERSE{"$dst_mbx"} = $src_mbx;
  782. selectMbx( $src_mbx, $src, 'EXAMINE' );
  783. @sourceMsgs=();
  784. if ( $sync_since ) {
  785. getDatedMsgList( $src_mbx, $sync_since, \@sourceMsgs, $src );
  786. } else {
  787. getMsgList( $src_mbx, \@sourceMsgs, $src );
  788. }
  789. if ( $verbose ) {
  790. Log("src_mbx $src_mbx has the following messages");
  791. foreach $_ ( @sourceMsgs ) {
  792. Log(" $_");
  793. }
  794. }
  795. selectMbx( $dst_mbx, $dst, 'SELECT' );
  796. my $msgcount = $#sourceMsgs + 1;
  797. Log("$src_mbx has $msgcount messages");
  798. foreach $_ ( @sourceMsgs ) {
  799. Log(" $_") if $verbose;
  800. ($msgid,$msgnum,$src_flags,$date) = split(/\|\|\|\|\|\|/, $_,5);
  801. next if $src_flags =~ /\\Deleted/; # Don't sync deleted messages
  802. Log("Searching on dst in $dst_mbx for $msgid ($msgnum)") if $verbose;
  803. my $dst_msgnum = findMsg( $msgid, $dst );
  804. if ( !$dst_msgnum ) {
  805. # The msg doesn't exist in the mailbox on the dst, need to add it.
  806. $message = fetchMsg( $msgnum, $src );
  807. next unless $message;
  808. Log(" Need to insert $msgnum") if $verbose;
  809. insertMsg( $dst, $dst_mbx, *message, $src_flags, $date, $msgid );
  810. $added++;
  811. } else {
  812. # The message exists, see if the flags have changed.
  813. Log(" msgnum=$msgnum exists, fetch its flags") if $verbose;
  814. $dst_flags = fetchMsgFlags( $dst_msgnum, $dst );
  815. sort_flags( \$src_flags );
  816. sort_flags( \$dst_flags );
  817. unless ( $dst_flags eq $src_flags ) {
  818. Log(" Updating the flags") if $verbose;
  819. setFlags( $dst_msgnum, $src_flags, $dst_flags, $dst );
  820. $updated++;
  821. }
  822. }
  823. }
  824. }
  825. return ($added,$updated);
  826. }
  827. sub check_for_deletes {
  828. my $REVERSE = shift;
  829. my $dst = shift;
  830. my $src = shift;
  831. my $deleted=0;
  832. my $total_deletes=0;
  833. # Delete any messages on the dst that are no longer on the src.
  834. return 0 if $no_deletes;
  835. Log("Checking for messages to delete on the dst");
  836. if ( %mbx_map ) {
  837. # Reverse the mbx mapping
  838. my $new_map;
  839. while( my($src,$dst) = each( %mbx_map ) ) {
  840. $new_map{"$dst"} = $src;
  841. }
  842. %mbx_map = %new_map;
  843. }
  844. while( my($src,$dst) = each( %mbx_map ) ) {
  845. Log("Mapping $src == > $dst");
  846. }
  847. my @dst_mbxs = getMailboxList( $dst );
  848. exclude_mbxs( \@dst_mbxs ) if $excludeMbxs;
  849. foreach my $dst_mbx ( @dst_mbxs ) {
  850. Log("Checking $dst_mbx for deletes") if $verbose;
  851. $deleted=0;
  852. ## $src_mbx = mailboxName( $dst_mbx,$dstPrefix,$dstDelim,$srcPrefix,$srcDelim );
  853. $src_mbx = $$REVERSE{"$dst_mbx"};
  854. if ( $sync_since ) {
  855. getDatedMsgList( $dst_mbx, $sync_since, \@dstMsgs, $dst );
  856. } else {
  857. getMsgList( $dst_mbx, \@dstMsgs, $dst );
  858. }
  859. selectMbx( $dst_mbx, $dst, 'SELECT' );
  860. selectMbx( $src_mbx, $src, 'EXAMINE' );
  861. foreach $_ ( @dstMsgs ) {
  862. ($msgid,$dst_msgnum,$dst_flags,$date) = split(/\|\|\|\|\|\|/, $_,5);
  863. if ( $verbose ) {
  864. Log(" msgid $msgid");
  865. Log(" dst msgnum $dst_msgnum");
  866. Log(" dst_mbx $dst_mbx");
  867. }
  868. my $src_msgnum = findMsg( $msgid, $src );
  869. if ( !$src_msgnum ) {
  870. Log("Deleting $msgid from $dst_mbx on the dst");
  871. if ( deleteMsg( $dst, $dst_msgnum ) ) {
  872. # Need to expunge messages from this mailbox when we're done
  873. $total_deletes++;
  874. $deleted=1;
  875. }
  876. }
  877. }
  878. expungeMbx( $dst, $dst_mbx ) if $deleted;
  879. }
  880. return $total_deletes;
  881. }
  882. sub namespace {
  883. my $conn = shift;
  884. my $prefix = shift;
  885. my $delimiter = shift;
  886. my $mbx_delim = shift;
  887. my $namespace;
  888. # Query the server with NAMESPACE so we can determine its
  889. # mailbox prefix (if any) and hierachy delimiter.
  890. if ( $mbx_delim ) {
  891. # The user has supplied a mbx delimiter and optionally a prefix.
  892. Log("Using user-supplied mailbox hierarchy delimiter $mbx_delim");
  893. ($$delimiter,$$prefix) = split(/\s+/, $mbx_delim);
  894. return;
  895. }
  896. @response = ();
  897. sendCommand( $conn, "1 NAMESPACE");
  898. while ( 1 ) {
  899. readResponse( $conn );
  900. if ( $response =~ /^1 OK/i ) {
  901. last;
  902. } elsif ( $response =~ /NO|BAD/i ) {
  903. Log("Unexpected response to NAMESPACE command: $response");
  904. $namespace = 0;
  905. last;
  906. }
  907. }
  908. # if ( !$namespace and !$opt_x ) {
  909. # # Not implemented yet. Needs more testing
  910. # # NAMESPACE is not supported by the server so try to
  911. # # figure out the mbx delimiter and prefix
  912. # $$delimiter = get_mbx_delimiter( $conn );
  913. # $$prefix = get_mbx_prefix( $delimiter, $conn );
  914. #
  915. # return;
  916. # }
  917. foreach $_ ( @response ) {
  918. if ( /NAMESPACE/i ) {
  919. my $i = index( $_, '((' );
  920. my $j = index( $_, '))' );
  921. my $val = substr($_,$i+2,$j-$i-3);
  922. ($val) = split(/\)/, $val);
  923. ($$prefix,$$delimiter) = split( / /, $val );
  924. $$prefix =~ s/"//g;
  925. $$delimiter =~ s/"//g;
  926. last;
  927. }
  928. last if /^1 NO|^1 BAD/;
  929. }
  930. if ( $verbose ) {
  931. Log("prefix $$prefix");
  932. Log("delim $$delimiter");
  933. }
  934. }
  935. sub mailboxName {
  936. my $srcmbx = shift;
  937. my $srcPrefix = shift;
  938. my $srcDelim = shift;
  939. my $dstPrefix = shift;
  940. my $dstDelim = shift;
  941. my $direction = shift;
  942. my $dstmbx;
  943. # Adjust the mailbox name if the source and destination server
  944. # have different mailbox prefixes or hierarchy delimiters.
  945. # Change the mailbox name if the user has supplied mapping rules.
  946. if ( $mbx_map{"$srcmbx"} ) {
  947. $srcmbx = $mbx_map{"$srcmbx"}
  948. }
  949. $dstmbx = $srcmbx;
  950. if ( $srcDelim ne $dstDelim ) {
  951. # Need to substitute the dst's hierarchy delimiter for the src's one
  952. $srcDelim = '\\' . $srcDelim if $srcDelim eq '.';
  953. $dstDelim = "\\" . $dstDelim if $dstDelim eq '.';
  954. $dstmbx =~ s#$srcDelim#$dstDelim#g;
  955. $dstmbx =~ s/\\//g;
  956. }
  957. if ( $srcPrefix ne $dstPrefix ) {
  958. # Replace the source prefix with the dest prefix
  959. $dstmbx =~ s#^$srcPrefix## if $srcPrefix;
  960. if ( $dstPrefix ) {
  961. $dstmbx = "$dstPrefix$dstmbx" unless uc($srcmbx) eq 'INBOX';
  962. }
  963. $dstDelim = '\.' if $dstDelim eq '.';
  964. $dstmbx =~ s#^$dstDelim##;
  965. }
  966. return $dstmbx;
  967. }
  968. sub flags {
  969. my $flags = shift;
  970. my @newflags;
  971. my $newflags;
  972. # Make sure the flags list contains only standard
  973. # IMAP flags.
  974. return unless $flags;
  975. $flags =~ s/\\Recent|\\Forwarded//ig;
  976. foreach $_ ( split(/\s+/, $flags) ) {
  977. next unless substr($_,0,1) eq '\\';
  978. push( @newflags, $_ );
  979. }
  980. $newflags = join( ' ', @newflags );
  981. $newflags =~ s/\\Deleted//ig if $opt_r;
  982. $newflags =~ s/^\s+|\s+$//g;
  983. return $newflags;
  984. }
  985. sub createDstMbxs {
  986. my $mbxs = shift;
  987. my $dst = shift;
  988. # Create a corresponding mailbox on the dst for each one
  989. # on the src.
  990. foreach my $mbx ( @$mbxs ) {
  991. $dstmbx = mailboxName( $mbx,$srcPrefix,$srcDelim,$dstPrefix,$dstDelim );
  992. createMbx( $dstmbx, $dst ) unless mbxExists( $dstmbx, $dst );
  993. }
  994. }
  995. sub mbxExists {
  996. my $mbx = shift;
  997. my $conn = shift;
  998. my $status = 1;
  999. my $loops;
  1000. # Determine whether a mailbox exists
  1001. sendCommand ($conn, "1 SELECT \"$mbx\"");
  1002. while (1) {
  1003. readResponse ($conn);
  1004. last if $response =~ /^1 OK/i;
  1005. if ( $response =~ /^1 NO|^1 BAD/ ) {
  1006. $status = 0;
  1007. last;
  1008. }
  1009. if ( $loops++ > 1000 ) {
  1010. Log("No response to SELECT command, skipping this mailbox");
  1011. last;
  1012. }
  1013. }
  1014. return $status;
  1015. }
  1016. sub sort_flags {
  1017. my $flags = shift;
  1018. my @newflags;
  1019. my $newflags;
  1020. # Make sure the flags list contains only standard
  1021. # IMAP flags. Sort the list to make comparision
  1022. # easier.
  1023. return unless $$flags;
  1024. $$flags =~ s/\\Recent|\\Forwarded//ig;
  1025. foreach $_ ( split(/\s+/, $$flags) ) {
  1026. next unless substr($_,0,1) eq '\\';
  1027. push( @newflags, $_ );
  1028. }
  1029. @newflags = sort @newflags;
  1030. $newflags = join( ' ', @newflags );
  1031. $newflags =~ s/^\s+|\s+$//g;
  1032. $$flags = $newflags;
  1033. }
  1034. sub setFlags {
  1035. my $msgnum = shift;
  1036. my $new_flags = shift;
  1037. my $old_flags = shift;
  1038. my $conn = shift;
  1039. my $rc;
  1040. # Set the message flags as indicated.
  1041. if ( $verbose ) {
  1042. Log("old flags $old_flags");
  1043. Log("new flags $new_flags");
  1044. }
  1045. # Clear the old flags
  1046. sendCommand ( $conn, "1 STORE $msgnum -FLAGS ($old_flags)");
  1047. while (1) {
  1048. readResponse ($conn);
  1049. if ( $response =~ /^1 OK/i ) {
  1050. $rc = 1;
  1051. last;
  1052. }
  1053. if ( $response =~ /^1 BAD|^1 NO/i ) {
  1054. Log("Error setting flags for msg $msgnum: $response");
  1055. $rc = 0;
  1056. last;
  1057. }
  1058. }
  1059. # Set the new flags
  1060. sendCommand ( $conn, "1 STORE $msgnum +FLAGS ($new_flags)");
  1061. while (1) {
  1062. readResponse ($conn);
  1063. if ( $response =~ /^1 OK/i ) {
  1064. $rc = 1;
  1065. last;
  1066. }
  1067. if ( $response =~ /^1 BAD|^1 NO/i ) {
  1068. Log("Error setting flags for msg $msgnum: $response");
  1069. $rc = 0;
  1070. last;
  1071. }
  1072. }
  1073. }
  1074. sub selectMbx {
  1075. my $mbx = shift;
  1076. my $conn = shift;
  1077. my $type = shift;
  1078. my $status;
  1079. my $loops;
  1080. # Select the mailbox. Type is either SELECT (R/W) or EXAMINE (R).
  1081. sendCommand( $conn, "1 $type \"$mbx\"");
  1082. while ( 1 ) {
  1083. readResponse( $conn );
  1084. if ( $response =~ /^1 OK/i ) {
  1085. $status = 1;
  1086. last;
  1087. } elsif ( $response =~ /does not exist/i ) {
  1088. $status = 0;
  1089. last;
  1090. } elsif ( $response =~ /^1 NO|^1 BAD/i ) {
  1091. Log("Unexpected response to SELECT/EXAMINE $mbx command: $response");
  1092. last;
  1093. }
  1094. if ( $loops++ > 1000 ) {
  1095. Log("No response to $type command, skipping this mailbox");
  1096. last;
  1097. }
  1098. }
  1099. return $status;
  1100. }
  1101. sub map_mbx_names {
  1102. my $mbx_map = shift;
  1103. my $srcDelim = shift;
  1104. my $dstDelim = shift;
  1105. # The -M <file> argument causes imapcopy to read the
  1106. # contents of a file with mappings between source and
  1107. # destination mailbox names. This permits the user to
  1108. # to change the name of a mailbox when copying messages.
  1109. #
  1110. # The lines in the file should be formatted as:
  1111. # <source mailbox name>: <destination mailbox name>
  1112. # For example:
  1113. # Drafts/2008/Save: Draft_Messages/2008/Save
  1114. # Action Items: Inbox
  1115. #
  1116. # Note that if the names contain non-ASCII characters such
  1117. # as accents or diacritical marks then the Perl module
  1118. # Unicode::IMAPUtf7 module must be installed.
  1119. return unless $mbx_map_fn;
  1120. unless ( open(MAP, "<$mbx_map_fn") ) {
  1121. Log("Error opening mbx map file $mbx_map_fn: $!");
  1122. exit;
  1123. }
  1124. $use_utf7 = 0;
  1125. while( <MAP> ) {
  1126. chomp;
  1127. s/[\r\n]$//; # In case we're on Windows
  1128. s/^\s+//;
  1129. next if /^#/;
  1130. next unless $_;
  1131. ($srcmbx,$dstmbx) = split(/\s*:\s*/, $_);
  1132. # Unless the mailbox name is entirely ASCII we'll have to use
  1133. # the Modified UTF-7 character set.
  1134. $use_utf7 = 1 unless isAscii( $srcmbx );
  1135. $use_utf7 = 1 unless isAscii( $dstmbx );
  1136. $srcmbx =~ s/\//$srcDelim/g;
  1137. $dstmbx =~ s/\//$dstDelim/g;
  1138. $$mbx_map{"$srcmbx"} = $dstmbx;
  1139. }
  1140. close MAP;
  1141. if ( $use_utf7 ) {
  1142. eval 'use Unicode::IMAPUtf7';
  1143. if ( $@ ) {
  1144. Log("At least one mailbox map contains non-ASCII characters. This means you");
  1145. Log("have to install the Perl Unicode::IMAPUtf7 module in order to map mailbox ");
  1146. Log("names between the source and destination servers.");
  1147. print "At least one mailbox map contains non-ASCII characters. This means you\n";
  1148. print "have to install the Perl Unicode::IMAPUtf7 module in order to map mailbox\n";
  1149. print "names between the source and destination servers.\n";
  1150. exit;
  1151. }
  1152. }
  1153. my %temp;
  1154. foreach $srcmbx ( keys %$mbx_map ) {
  1155. Log("map has $srcmbx");
  1156. $dstmbx = $$mbx_map{"$srcmbx"};
  1157. Log("Mapping src:$srcmbx to dst:$dstmbx");
  1158. if ( $use_utf7 ){
  1159. # Encode the name in Modified UTF-7 charset
  1160. $srcmbx = Unicode::IMAPUtf7::imap_utf7_encode( $srcmbx );
  1161. $dstmbx = Unicode::IMAPUtf7::imap_utf7_encode( $dstmbx );
  1162. }
  1163. $temp{"$srcmbx"} = $dstmbx;
  1164. }
  1165. %$mbx_map = %temp;
  1166. %temp = ();
  1167. }
  1168. sub isAscii {
  1169. my $str = shift;
  1170. my $ascii = 1;
  1171. # Determine whether a string contains non-ASCII characters
  1172. my $test = $str;
  1173. $test=~s/\P{IsASCII}/?/g;
  1174. $ascii = 0 unless $test eq $str;
  1175. return $ascii;
  1176. }
  1177. sub get_date {
  1178. my $days = shift;
  1179. my $time = time();
  1180. my @months = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
  1181. # Generate a date in DD-MMM-YYYY format. The 'days' parameter
  1182. # indicates how many days to go back from the present date.
  1183. my ($sec,$min,$hr,$mday,$mon,$year,$wday,$yday,$isdst) =
  1184. localtime( $time - $days*86400 );
  1185. $mday = '0' . $mday if length( $mday ) == 1;
  1186. my $month = $months[$mon];
  1187. my $date = $mday . '-' . $month . '-' . ($year+1900);
  1188. return $date;
  1189. }
  1190. sub fixup_date {
  1191. my $date = shift;
  1192. # Make sure the hrs part of the date is 2 digits. At least
  1193. # one IMAP server expects this.
  1194. $$date =~ s/^\s+//;
  1195. $$date =~ /(.+) (.+):(.+):(.+) (.+)/;
  1196. my $hrs = $2;
  1197. return if length( $hrs ) == 2;
  1198. my $newhrs = '0' . $hrs if length( $hrs ) == 1;
  1199. $$date =~ s/ $hrs/ $newhrs/;
  1200. }
  1201. sub get_mbx_prefix {
  1202. my $delim = shift;
  1203. my $conn = shift;
  1204. my %prefixes;
  1205. my @prefixes;
  1206. # Not implemented yet.
  1207. # Try to figure out whether the server has a mailbox prefix
  1208. # and if so what it is.
  1209. $$delim = "\\." if $$delim eq '.';
  1210. my @mbxs = getMailboxList( $conn );
  1211. my $num_mbxs = $#mbxs + 1;
  1212. foreach $mbx ( @mbxs ) {
  1213. next if uc( $mbx ) eq 'INBOX';
  1214. ($prefix,$rest) = split(/$$delim/, $mbx);
  1215. $prefixes{"$prefix"}++;
  1216. }
  1217. my $num_prefixes = keys %prefixes;
  1218. if ( $num_prefixes == 1 ) {
  1219. while(($$prefix,$count) = each(%prefixes)) {
  1220. push( @prefixes, "$$prefix|$count");
  1221. }
  1222. ($$prefix,$count) = split(/\|/, pop @prefixes);
  1223. $num_mbxs--; # Because we skipped the INBOX
  1224. if ( $num_mbxs != $count ) {
  1225. # Did not find a prefix
  1226. $$prefix = '';
  1227. }
  1228. }
  1229. $$delim =~ s/\\//;
  1230. $$prefix .= $$delim if $$prefix;
  1231. Log("Determined prefix to be $$prefix") if $debug;
  1232. return $$prefix;
  1233. }
  1234. sub get_mbx_delimiter {
  1235. my $conn = shift;
  1236. my $delimiter;
  1237. # Not implemented yet.
  1238. # Determine the mailbox hierarchy delimiter
  1239. sendCommand ($conn, "1 LIST \"\" INBOX");
  1240. undef @response;
  1241. while ( 1 ) {
  1242. readResponse ($conn);
  1243. if ( $response =~ /INBOX/i ) {
  1244. my @terms = split(/\s+/, $response );
  1245. $delimiter = $terms[3];
  1246. $delimiter =~ s/"//g;
  1247. }
  1248. last if $response =~ /^1 OK|^1 BAD|^1 NO/;
  1249. last if $response !~ /^\*/;
  1250. }
  1251. Log("Determined delimiter to be $delimiter") if $debug;
  1252. return $delimiter;
  1253. }