Subversion Repositories sysadmin_scripts

Rev

Rev 40 | Rev 50 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 40 Rev 42
Line 24... Line 24...
24
#
24
#
25
#    You should have received a copy of the GNU General Public License
25
#    You should have received a copy of the GNU General Public License
26
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
26
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
27
#
27
#
28
#    for required libraries
28
#    for required libraries
29
#    apt-get -y install libnet-imap-simple-ssl-perl libyaml-tiny-perl libhash-merge-simple-perl libclone-perl
29
#    apt-get -y install libnet-imap-simple-ssl-perl libyaml-tiny-perl libhash-merge-simple-perl libclone-perl libdate-manip-perl libemail-simple-perl
30
 
30
 
31
use strict;
31
use strict;
32
use warnings;
32
use warnings;
33
use Net::IMAP::Simple; # libnet-imap-simple-ssl-perl
33
use Net::IMAP::Simple; # libnet-imap-simple-ssl-perl
34
use POSIX; # to get floor and ceil
34
use POSIX; # to get floor and ceil
35
use YAML::Tiny; # apt-get libyaml-tiny-perl under debian
35
use YAML::Tiny; # apt-get libyaml-tiny-perl under debian
36
use Clone 'clone'; # libclone-perl
36
use Clone 'clone'; # libclone-perl
37
use Hash::Merge::Simple qw/ merge clone_merge /; # libhash-merge-simple-perl
37
use Hash::Merge::Simple qw/ merge clone_merge /; # libhash-merge-simple-perl
-
 
38
use Date::Manip; # libdate-manip-perl
-
 
39
use Email::Simple; # libemail-simple-perl
-
 
40
use Date::Parse;
-
 
41
 
38
 
42
 
39
use Data::Dumper;
43
use Data::Dumper;
40
 
44
 
41
# globals
45
# globals
42
my $CONFIG_FILE_NAME = 'archiveIMAP.yaml';
46
my $CONFIG_FILE_NAME = 'archiveIMAP.yaml';
Line 85... Line 89...
85
                                 'Trash'
89
                                 'Trash'
86
                               ],
90
                               ],
87
                  # if 1, after successful copy to target, remove from source
91
                  # if 1, after successful copy to target, remove from source
88
                  'deleteOnSuccess' => 0
92
                  'deleteOnSuccess' => 0
89
               },
93
               },
90
         
-
 
91
      # if 1, does a dry run showing what would have happened
94
      # if 1, does a dry run showing what would have happened
92
      'testing' => 0,
95
      'testing' => 0,
93
      # if 0, will not be processed
96
      # if 0, will not be processed
94
      'enabled' => 1,
97
      'enabled' => 1,
95
   }
98
   }
96
   };
99
};
97
 
100
 
98
 
101
 
99
#
102
#
100
# find where the script is actually located as cfg should be there
103
# find where the script is actually located as cfg should be there
101
#
104
#
Line 164... Line 167...
164
   my $age = shift;
167
   my $age = shift;
165
   # allow modifier to age which contains 'Y' (years) or 'M' (months)
168
   # allow modifier to age which contains 'Y' (years) or 'M' (months)
166
   # Simply set multiplier to the correct value, then multiply the value
169
   # Simply set multiplier to the correct value, then multiply the value
167
   $age = lc( $age );
170
   $age = lc( $age );
168
   if ( $age =~ m/([0-9.]+)([a-z])/ ) {
171
   if ( $age =~ m/([0-9.]+)([a-z])/ ) {
-
 
172
      # ~0 is the maximum integer which can be stored. Shifting right one gives max unsigned integer
169
      my $multiplier = ($2 == 'y' ? 365.2425 : ( $2 == 'm' ? 30.5 : MAXINT) );
173
      my $multiplier = ($2 eq 'y' ? 365.2425 : ( $2 eq 'm' ? 30.5 : ~0 >> 1) );
170
      $age = floor( $1 * $multiplier);
174
      $age = floor( $1 * $multiplier);
171
   }
175
   }
172
   my @months = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
176
   my @months = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
173
   my @now = localtime(time - 24 * 60 * 60 * $age);
177
   my @now = localtime(time - 24 * 60 * 60 * $age);
174
   $now[4] = @months[$now[4]];
178
   $now[4] = @months[$now[4]];
175
   $now[5] += 1900;
179
   $now[5] += 1900;
176
   my $date = sprintf( "%d-%s-%d", $now[3],$now[4],$now[5] ) ; # '1-Jan-2014';
180
   my $date = sprintf( "%d-%s-%d", $now[3],$now[4],$now[5] ) ; # '1-Jan-2014';
177
   return $date;
181
   return $date;
178
}
182
}
179
 
183
 
-
 
184
 
-
 
185
# calculateTargetFolder
-
 
186
# we are passed the target and the message
-
 
187
# pattern is carot (^) separated and may contain
-
 
188
# special placeholders <path>, <year>, <month>
-
 
189
# anything else is inserted directly
-
 
190
sub calculateTargetFolder {
-
 
191
   my ( $message, $source, $target ) = @_;
-
 
192
   # we may be sorting by date
-
 
193
   my $email = Email::Simple->new( join( '', @$message ) );
-
 
194
   my $msgDate = $email->header('Date');
-
 
195
   my @t = strptime( $msgDate );
-
 
196
   my $month = $t[4]+1;
-
 
197
   $month = '0' . $month if $month < 10;
-
 
198
   my $year = $t[5]+1900;
-
 
199
   # now, build the path on the new machine
-
 
200
   my $sourceFolder = join ( $target->{'separator'}, @{ $source->{'source folder list'} } );
-
 
201
   my $targetPattern = join( $target->{'separator'}, @{ $target->{'hierachy pattern'} } );
-
 
202
 
-
 
203
   $targetPattern =~ s/<path>/$sourceFolder/gi;
-
 
204
   $targetPattern =~ s/<month>/$month/gi;
-
 
205
   $targetPattern =~ s/<year>/$year/gi;
-
 
206
   # return the string we created, separated by
-
 
207
   # the delimiters for the target
-
 
208
   return $targetPattern;
-
 
209
}
-
 
210
 
-
 
211
 
-
 
212
# If folder has messages that match the criteria, move them to target
-
 
213
# creates the target folder if necessary
-
 
214
#
-
 
215
sub processFolder {
-
 
216
   my ( $source, $target, $folder, $TESTING ) = @_;
-
 
217
   #$dateBefore, $sourceDelimiter, $targetDelimiter, $deleteOnSuccess
-
 
218
   print "\n=== Processing $folder\n";
-
 
219
   my $sourceAccount = $source->{'connection'};
-
 
220
   my $targetAccount = $target->{'connection'};
-
 
221
   my $numMessages = 0;
-
 
222
   $sourceAccount->expunge_mailbox( $folder ); # clean it up so we don't copy deleted messages
-
 
223
   $sourceAccount->select( $folder ) or die "Could not connect to folder $folder\n"; # move into the correct folder for the source
-
 
224
   my @ids = $sourceAccount->search_sent_before( $source->{'before date'} ); # use sent_before to get the sent date from message
-
 
225
#   print join( "\n\t\t\t", @ids ) . "\n";
-
 
226
   return 0 unless @ids; # we have nothing to copy, so exit
-
 
227
   # make life easier by precalculating some paths as array pointers
-
 
228
   my @sourceFolders = split( '\\' . $source->{'separator'}, $folder );
-
 
229
   my @pattern = split '\\^', $target->{'hierarchy'};
-
 
230
   $source->{'source folder list'} = \@sourceFolders;
-
 
231
   $target->{'hierachy pattern'} = \@pattern;
-
 
232
#   print "\n\nDumping information\n";
-
 
233
#   print "Source Folder $folder\n";
-
 
234
#   print "Source Separator $source->{'separator'}\n";
-
 
235
#   print Dumper( $source->{'source folder list'} ) . "\n";
-
 
236
#   print "Target Separator $target->{'separator'}\n";
-
 
237
#   print "Target Hierarchy $target->{'hierarchy'}\n";
-
 
238
#   print Dumper( $target->{'hierachy pattern'} ) . "\n";
-
 
239
 
-
 
240
#   print 'Source => ' . join( "\n", @sourceFolders ) . "\n";
-
 
241
#   print 'Pattern => ' . join( "\n", @pattern ) . "\n";
-
 
242
   
-
 
243
 
-
 
244
   # process each message to be done
-
 
245
   foreach my $id ( @ids ) {
-
 
246
      # get the flags
-
 
247
      my @flags = $sourceAccount->msg_flags( $id );
-
 
248
      # get the message
-
 
249
      my $message = $sourceAccount->get( $id ) or die $sourceAccount->errstr;
-
 
250
      # calculate where we are going to move this to
-
 
251
      my $targetFolder = &calculateTargetFolder( $message, $source, $target );
-
 
252
      if ( $TESTING ) {
-
 
253
         print "Would have moved message to $targetFolder\n";
-
 
254
         next;
-
 
255
      }
-
 
256
      if ( $target->{'connection'}->select( $folder ) || &makeFolder( $target->{'connection'}, $folder, $target->{'separator'} ) ) {
-
 
257
         if ( $target->{'connection'}->put( $folder, $message, @flags ) ) {
-
 
258
            $source->{'connection'}->delete( $id ) if ( $source->{'deleteOnSuccess'} ) ;
-
 
259
            $numMessages++;
-
 
260
         } else {
-
 
261
            die "Could not write to target, aborting\n$target->{'connection'}->errstr\n";
-
 
262
         }
-
 
263
      } else {
-
 
264
         warn "\t\t$folder not found in target and could not create it\n";
-
 
265
      }
-
 
266
 
-
 
267
   }
-
 
268
   return $numMessages;
-
 
269
}
-
 
270
 
-
 
271
   
-
 
272
 
180
#
273
#
181
# Get a list of all folders to be processed
274
# Get a list of all folders to be processed
182
# currently, it just weeds out items in the ignore list
275
# currently, it just weeds out items in the ignore list
183
#
276
#
184
sub getFolders {
277
sub getFolders {
Line 190... Line 283...
190
   # (^|(\.))((Trash)|(Drafts)|(Junk))((\.)|$)
283
   # (^|(\.))((Trash)|(Drafts)|(Junk))((\.)|$)
191
   # which basically says ignore those folders, but not substrings of them
284
   # which basically says ignore those folders, but not substrings of them
192
   # ie, Junk02 would not be filtered but Junk would
285
   # ie, Junk02 would not be filtered but Junk would
193
   my $ignoreRegex = "(^|($separator))((" . join( ")\|(", @$ignore ) . "))(($separator)|\$)";
286
   my $ignoreRegex = "(^|($separator))((" . join( ")\|(", @$ignore ) . "))(($separator)|\$)";
194
   # read all mailboxes and filter them with above regex into @boxes
287
   # read all mailboxes and filter them with above regex into @boxes
195
   my @boxes = grep{ ! /$ignoreRegex/ } $imap->mailboxes;
288
   my @boxes = grep{ ! /$ignoreRegex/i } $imap->mailboxes;
196
   return \@boxes;
289
   return \@boxes;
197
}
290
}
198
 
291
 
199
#
292
#
200
# make a folder on the IMAP account. The folder is assumed to be the
293
# make a folder on the IMAP account. The folder is assumed to be the
Line 226... Line 319...
226
   
319
   
227
#
320
#
228
# Delete an IMAP folder
321
# Delete an IMAP folder
229
#
322
#
230
sub deleteAFolder {
323
sub deleteAFolder {
231
   my ($sourceAccount, $folder, $separator, $systemFolders) = @_;
324
   my ($source, $folder, $TESTING ) = @_;
-
 
325
   my $sourceAccount = $source->{'connection'};
-
 
326
   my $separator = $source->{'separator'};
232
   return 1 if $folder eq 'INBOX'; # do NOT mess with INBOX
327
   return 1 if $folder eq 'INBOX'; # do NOT mess with INBOX
233
   return 2 if $sourceAccount->select($folder) > 0; # do not mess with it if it still has messages in it
328
   return 2 if $sourceAccount->select($folder) > 0; # do not mess with it if it still has messages in it
234
   return 3 if $sourceAccount->mailboxes( $folder . $separator . '*' ); # do not mess with it if it has subfolders
329
   return 3 if $sourceAccount->mailboxes( $folder . $separator . '*' ); # do not mess with it if it has subfolders
235
   return 4 if ( ref ( $systemFolders ) eq 'ARRAY' ) && ( grep{$_ eq $folder} @$systemFolders );
330
   return 4 if $source->{'system folders'}->{lc $folder}; # do not mess with system folders
236
   print "\n\t\tDeleting empty folder $folder";
331
   print "\n\t\tDeleting empty folder $folder" . ( $TESTING ? ' Dry Run' : '' );
-
 
332
   return if $TESTING;
237
   $sourceAccount->folder_unsubscribe($folder);
333
   $sourceAccount->folder_unsubscribe($folder);
238
   $sourceAccount->delete_mailbox( $folder );
334
   $sourceAccount->delete_mailbox( $folder );
239
}
335
}
240
 
336
 
241
 
337
 
242
# main process loop to handle one account
338
# main process loop to handle one account
243
#
339
#
244
sub processAccount {
340
sub processAccount {
245
   my $account = shift;
341
   my $account = shift;
246
 
342
 
247
   my $date = &getDate( $age );
343
   next unless $account->{'enabled'}; # blow it off if it is not enabled
248
   print "\t" . ( $deleteOnSuccess ? 'Moving' : 'Copying' ) . " all messages before $date\n";
344
   my $TESTING = $account->{'testing'}; # create mini global if we should test this account
249
   
345
   
250
   # open and log into both source and target, and get the separator used
346
   # open and log into both source and target, and get the separator used
-
 
347
   foreach my $acct ( 'target','source' ) {
251
   my $sourceAccount = &openIMAPConnection( $$source{'server'}, $$source{'username'}, $$source{'password'} );
348
      $account->{$acct}->{'connection'} = &openIMAPConnection( $account->{$acct}->{'server'}, $account->{$acct}->{'username'}, $account->{$acct}->{'password'} );
-
 
349
      unless ( $account->{$acct}->{'connection'} ) {
252
   my $targetAccount = &openIMAPConnection( $$target{'server'}, $$target{'username'}, $$target{'password'} );
350
         warn "Unable to open $acct for $account->{$acct}->{username}, aborting move: $!\n";
-
 
351
         return -1;
-
 
352
      }
253
   $$source{'separator'} = $sourceAccount->separator unless $$source{'separator'};
353
      $account->{$acct}->{'separator'} = $account->{$acct}->{'connection'}->separator unless $account->{$acct}->{'separator'};
-
 
354
   }
-
 
355
 
-
 
356
   # just being set up for convenience and readability
-
 
357
   my $source = $account->{'source'};
-
 
358
   my $target = $account->{'target'};
-
 
359
 
-
 
360
   my %temp = map{ lc($_) => 1 } @{$source->{'system'}};
-
 
361
   $source->{'system folders'} = \%temp;
-
 
362
 
254
   $$target{'separator'} = $targetAccount->separator unless $$target{'separator'};
363
   $source->{'before date'} = &getDate( $account->{'age'} );
-
 
364
   print "\t" . ( $source->{'deleteOnSuccess'} ? 'Moving' : 'Copying' ) . " all messages before $source->{'before date'}\n";
255
 
365
 
256
   # get a list of all folders to be processed on the source
366
   # get a list of all folders to be processed on the source
257
   $$source{'folders'} = &getFolders( $sourceAccount, $ignore, $$source{'separator'} );
367
   $source->{'folders'} = &getFolders( $source->{'connection'}, $source->{'ignore'}, $source->{'separator'} );
258
   #print Dumper( $targetAccount );
-
 
259
   #die;
368
 
-
 
369
 
260
   my $folderList = $$source{'folders'};
370
   my $folderList = $source->{'folders'};
261
   my $count = 0; # count the number of messages processed
371
   my $count = 0; # count the number of messages processed
262
   my $processedCount = 0; # count the number of folders processed
372
   my $processedCount = 0; # count the number of folders processed
263
   foreach my $folder ( @$folderList ) {
373
   foreach my $folder ( @$folderList ) {
264
      print "\t$folder";
-
 
265
      my $messages;
374
      my $messages;
266
      $messages = &processFolder( $sourceAccount, $targetAccount, $folder, $date, $$source{'separator'}, $$target{'separator'}, $deleteOnSuccess );
375
      $messages = &processFolder( $source, $target, $folder, $TESTING ); #, $date, $$source{'separator'}, $$target{'separator'}, $deleteOnSuccess );
-
 
376
 
267
      $TESTING ? print "Would expunge $folder\n" : $sourceAccount->expunge_mailbox( $folder );
377
      $TESTING ? print "Would expunge $folder\n" : $source->{'connection'}->expunge_mailbox( $folder );
268
      # delete folder if empty and client has requested it.
378
      # delete folder if empty and client has requested it.
269
      ( $TESTING ? print "Would delete $folder\n" : &deleteAFolder( $sourceAccount, $folder, $$source{'separator'}, $$source{'systemfolder'} ) ) if ( $deleteEmptyFolders );
379
      ( $TESTING ? print "Would delete $folder\n" : &deleteAFolder( $source, $folder, $TESTING ) ) if $account->{'source'}->{'deleteEmptyFolders'};
270
      print "\n\t\t$messages processed\n";
380
      print "\n\t\t$messages processed\n";
271
      $count += $messages;
381
      $count += $messages;
272
      $processedCount++;
382
      $processedCount++;
273
      # next line used only for testing. Dies after 5 folders on first account
383
      # next line used only for testing. Dies after 5 folders on first account
274
      # last if $processedCount > 5;
384
      last if $processedCount > 5;
275
   }
385
   }
-
 
386
 
276
   $sourceAccount->quit;
387
   $source->{'connection'}->quit;
277
   $targetAccount->quit;
388
   $target->{'connection'}->quit;
278
   return $count;
389
   return $count;
279
}
390
}
280
 
391
 
281
 
392
 
282
 
393
 
Line 298... Line 409...
298
$processed{'Accounts'} = 0;
409
$processed{'Accounts'} = 0;
299
$processed{'Messages'} = 0;
410
$processed{'Messages'} = 0;
300
 
411
 
301
# grab only the accounts for simplicity
412
# grab only the accounts for simplicity
302
my $accounts = $config->{'accounts'};
413
my $accounts = $config->{'accounts'};
-
 
414
 
-
 
415
print Dumper( $config ) ; die;
-
 
416
 
303
# now, process each in turn
417
# now, process each in turn
304
foreach my $account ( keys %$accounts ) {
418
foreach my $account ( keys %$accounts ) {
305
   # talk to user
419
   # talk to user
306
   print "Processing account $account\n";
420
   print "Processing account $account\n";
307
   # do the account. This is the main worker bee
421
   # do the account. This is the main worker bee
308
   $accounts->{$account}->{'processed'} = &processAccount( $accounts->{$account} );
422
   $accounts->{$account}->{'processed'} = &processAccount( $accounts->{$account} );
309
   print "Done, $accounts->{$account}->{'processed'} messages copied\n";
423
   print "Done, $accounts->{$account}->{'processed'} messages copied\n";
310
   $processed{'Accounts'}++;
424
   $processed{'Accounts'}++;
311
   $processed{'Messages'} += $accounts->{$account}->{'processed'};
425
   $processed{'Messages'} += $accounts->{$account}->{'processed'};
-
 
426
   # free up space we allocated since we stored a bunch of stuff in there, and we don't need it anymore
-
 
427
   $accounts->{$account} = undef; 
312
} # foreach loop
428
} # foreach loop
313
 
429
 
314
print "$processed{'Accounts'} accounts processed, $processed{'Messages'} messages\n";
430
print "$processed{'Accounts'} accounts processed, $processed{'Messages'} messages\n";
315
 
431
 
316
 
432