1 #!/usr/bin/perl -wT
2 # BEGIN SURETEC TAGGED BLOCK {{{
3 #=============================================================================
4 #
5 # FILE: create_dovecot_shares
6 #
7 # USAGE: create_dovecot_shares --help
8 #
9 # DESCRIPTION: Create Dovecot shares and put symlinks into user Maildir
10 #
11 # OPTIONS: --username
12 # --group
13 # --clean
14 # --dry-run
15 # --History
16 # --share-with
17 # --maildir
18 # --override
19 # --prefix
20 # --restore
21 # --skip
22 # --Version
23 # --verbose
24 # --man
25 # --help
26 #
27 # REQUIREMENTS: Data::Dumper
28 # File::Find
29 # Fcntl
30 # Getopt::Long
31 # List::Util
32 # Pod::Usage
33 # POSIX
34 # Term::ANSIColor
35 # Storable
36 #
37 # BUGS: N/A
38 # NOTES: N/A
39 # AUTHOR: Gavin Henry (GH), <ghenry@suretecsystems.com>
40 # COMPANY: Suretec Systems Ltd. - http://www.suretecsystems.com
41 # SUPPORT: <support@suretecsystems.com>
42 # VERSION: 1.01
43 # CREATED: 26/05/06
44 # UPDATED: 29/05/06
45 #=============================================================================
46 # END SURETEC TAGGED BLOCK }}}
47
48 # Untaint environment
49 $ENV{'PATH'} = '/usr/local/bin:/usr/bin:/bin';
50 $ENV{'SHELL'} = '/bin/bash';
51 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
52
53 # Returned by Perl::MinimumVersion 0.13
54 require 5.006;
55
56 use strict;
57 use warnings;
58 use Data::Dumper;
59 use File::Find;
60 use Fcntl qw(:mode);
61 use Getopt::Long;
62 use List::Util qw(min);
63 use Pod::Usage;
64 use POSIX qw(strftime);
65 use Term::ANSIColor qw(:constants);
66 use Storable qw(nstore retrieve);
67
68 # Unbuffer output
69 $| = 1;
70
71 # Normally use Readonly for this, but want to keep to Core Modules
72 our $VERSION = '1.01';
73 our $SHARED = '/dovecot-shared';
74 our $PROGNAME = 'create_dovecot_shares';
75 our $SAVED = '/var/cache/dovecot_shares.hist';
76 our $HUMAN_RUN_DATE = strftime "%a %b %e %H:%M:%S %Y", localtime;
77 our $NOW = time;
78
79 #-----------------------------------------------------------------------------
80 # Standard Suretec method for parsing program arguments
81 #
82 # "perldoc Getopt::Long" for more info
83 #-----------------------------------------------------------------------------
84
85 die "Sorry, you need to be the root user - Suretec.\n" if ( $< != 0 );
86
87 my %options;
88 my ( @users, @share_with, @maildir, @skip, );
89
90 GetOptions(
91 \%options,
92 qw( group=s override dry-run prefix=s restore:s History clean Version
93 verbose help|? man ),
94 'username=s' => \@users,
95 'share-with=s' => \@share_with,
96 'maildir=s' => \@maildir,
97 'skip=s' => \@skip,
98 );
99
100 pod2usage(1) if $options{help} or ( !keys %options );
101 pod2usage( -verbose => 2 ) if $options{man};
102
103 print "This is $PROGNAME version $VERSION\n" if $options{Version};
104
105 #-----------------------------------------------------------------------------
106 # Begin
107 #-----------------------------------------------------------------------------
108 my %SAVE;
109
110 # we only use $File::Find:name, so we set no_chdir here
111 my %find_options = (
112 untaint => 1,
113 untaint_pattern => qr{^([-+@\w\s&!\.\{\}#]+)$},
114 no_chdir => '1',
115 wanted => \&emails_and_dirs,
116 );
117
118 # Load previous save
119 my $SAVE = retrieve("$SAVED") if -e $SAVED;
120
121 # Reset counter
122 my $run;
123 if ( defined $SAVE->{counter} ) {
124
125 # We only keep a history of 10 runs
126 $SAVE->{counter} = 0 if $SAVE->{counter} == 10;
127
128 # Track from the counter
129 $run = $SAVE->{counter};
130 }
131
132 # New run
133 ++$run and ++$SAVE->{counter};
134
135 @share_with = split_args(@share_with);
136 @users = split_args(@users);
137 @maildir = split_args(@maildir);
138 @skip = split_args(@skip);
139
140 # Initialise directory and file start no. for save_previous
141 my $dir_num = 0;
142 my $file_num = 0;
143
144 #-----------------------------------------------------------------------------
145 # Here we are checking the group we got passed
146 #-----------------------------------------------------------------------------
147 my $share_group;
148 if ( $options{group} ) {
149
150 die "Need username and user to share!\n" if ( !@share_with or !@users );
151
152 die "Group: '$options{group}' does not exist! (typo?)\n"
153 if !defined getgrnam( $options{group} );
154
155 # getgrnam returns ($name, $passwd, $gid, $members)
156 # i.e $group_details[0] is $name etc. etc.
157 my @group_details = getgrnam( $options{group} );
158
159 # Check they are members
160 for my $user (@users) {
161
162 # A user, by default is a member of their own group
163 next if $group_details[0] eq $user;
164
165 die "Username: '$user' is not a valid user!\n"
166 if !defined getpwnam($user);
167
168 die "User '$user' is not a member of the '$options{group}' group\n"
169 if not $group_details[3] =~ m{$user};
170 }
171
172 die "Refusing to create a dovecot share with the share group set to"
173 . " 'root' (gid: $group_details[2])!\n"
174 if $group_details[2] == 0;
175
176 $share_group = $group_details[2];
177 }
178
179 #-----------------------------------------------------------------------------
180 # Here we start the actual share creation
181 #-----------------------------------------------------------------------------
182 my @homes;
183 if (@users) {
184 die "Need users to share with!\n" if !@share_with;
185
186 for my $u (@share_with) {
187 die "Can not share with '$u': User '$u' does not exist! (typo?)\n"
188 if !defined getpwnam($u);
189 }
190
191 die "Need to set group for shares! Please provide --group e.g."
192 . " --group=sharedmail\n"
193 if !$options{group};
194
195 @homes = map { '/home/' . clean_user($_) } @users;
196 find( \%find_options, @homes ); # this populates %SAVE if it's not
197 # retreived above
198
199 # We only want to use these Maildirs if passed
200 if (@maildir) {
201 for my $maildir (@maildir) {
202 die "Maildir Directory: '$maildir' doesn't exist!\n"
203 if -d !$maildir;
204 create_share($maildir);
205 }
206 exit 0;
207 }
208
209 # $SAVE has been populated by File::Find
210 for my $dir ( keys %{ $SAVE->{run}{$run}{directory} } ) {
211 create_share( $SAVE->{run}{$run}{directory}{$dir}{dir}{name} );
212 }
213 }
214
215 #-----------------------------------------------------------------------------
216 # Here we are printing to STDOUT, everything in the History file
217 #-----------------------------------------------------------------------------
218 if ( $options{History} ) {
219 my $SAVE = retrieve($SAVED)
220 if -e $SAVED
221 or die "History file: '$SAVED' does not exist\n"
222 . "Has $PROGNAME been run before?\n";
223
224 local $Data::Dumper::Varname = 'Dovecot-share History:';
225 local $Data::Dumper::Sortkeys = 1;
226 print Dumper($SAVE), "\n";
227 exit 0;
228 }
229
230 #-----------------------------------------------------------------------------
231 # Here we are restoring from the history file on the file system
232 #-----------------------------------------------------------------------------
233 if ( defined $options{restore} ) { # restore:s sets an empty string ''.
234 my $SAVE = retrieve($SAVED) # so if no run passed, we restore
235 if -e $SAVED # last run
236 or die "History file: '$SAVED' does not exist\n"
237 . "Has $PROGNAME been run before?\n";
238
239 my $restore;
240 if ( $options{restore} =~ m{\d+} ) {
241 $restore = $options{restore};
242 print "Restoring run: '$restore'\n";
243 }
244 else {
245
246 # Would use one of the various Date::* modules from
247 # the CPAN here, but don't want to use any non-core
248 # modules
249 my @runs;
250 for my $run_num ( keys %{ $SAVE->{run} } ) {
251 push @runs,
252 ( [ $run_num, $SAVE->{run}{$run_num}{restore_check_date} ] );
253 }
254
255 my @elapsed_time;
256 my %run_with;
257 for my $time (@runs) {
258 my $difference = $NOW - $time->[1]; # restore_check_date above
259 $run_with{ $time->[0] } = $difference; # Save, to find run below
260 push @elapsed_time, $difference;
261
262 }
263 my $last_run = min @elapsed_time; # Closest run to $NOW
264
265 for my $to_restore ( keys %run_with ) {
266 $restore = $to_restore
267 if $run_with{$to_restore} == $last_run; # Find the run key
268 }
269
270 my $seconds = $last_run % 60;
271 $last_run = ( $last_run - $seconds ) / 60;
272 my $minutes = $last_run % 60;
273 $last_run = ( $last_run - $minutes ) / 60;
274 my $hours = $last_run % 24;
275 $last_run = ( $last_run - $hours ) / 24;
276 my $days = $last_run % 7;
277 my $weeks = ( $last_run - $days ) / 7;
278
279 print "Restoring the most recent run (run '$restore'), which was:\n"
280 . " $weeks week/s, $days day/s, $hours hour/s,"
281 . " $minutes minute/s and $seconds second/s ago.\n";
282 }
283
284 for my $dir ( keys %{ $SAVE->{run}{$restore}{directory} } ) {
285 if ( $SAVE->{run}{$restore}{directory}{$dir}{dir}{name} ) {
286 print BOLD GREEN, "***** DRY RUN *****\n", RESET;
287 print BOLD GREEN, "Restoring $SAVE->{run}{$restore}{directory}{$dir}{dir}{name}"
288 . " with uid:"
289 . " '$SAVE->{run}{$restore}{directory}{$dir}{uid}', "
290 . "gid '$SAVE->{run}{$restore}{directory}{$dir}{gid}', "
291 . "and mode '$SAVE->{run}{$restore}{directory}{$dir}{mode}'\n", RESET
292 if $options{verbose};
293
294 restore_emails_and_dirs(
295 $SAVE->{run}{$restore}{directory}{$dir}{uid},
296 $SAVE->{run}{$restore}{directory}{$dir}{gid},
297 $SAVE->{run}{$restore}{directory}{$dir}{dir}{name},
298 $SAVE->{run}{$restore}{directory}{$dir}{mode}
299 );
300 }
301
302 for my $file (
303 keys %{ $SAVE->{run}{$restore}{directory}{$dir}{dir}{file} } )
304 {
305 if ( $SAVE->{run}{$restore}{directory}{$dir}{dir}{file}{$file}
306 {name} )
307 {
308 print BOLD GREEN,
309 "Restoring $SAVE->{run}{$restore}{directory}{$dir}{dir}{file}{$file}{name}"
310 . " with uid:"
311 . " '$SAVE->{run}{$restore}{directory}{$dir}{dir}{file}{$file}{uid}', "
312 . "gid '$SAVE->{run}{$restore}{directory}{$dir}{dir}{file}{$file}{gid}', "
313 . "and mode '$SAVE->{run}{$restore}{directory}{$dir}{dir}{file}{$file}{mode}'\n", RESET
314 if $options{verbose};
315
316 restore_emails_and_dirs(
317 $SAVE->{run}{$restore}{directory}{$dir}{dir}{file}{$file}
318 {uid},
319 $SAVE->{run}{$restore}{directory}{$dir}{dir}{file}{$file}
320 {gid},
321 $SAVE->{run}{$restore}{directory}{$dir}{dir}{file}{$file}
322 {name},
323 $SAVE->{run}{$restore}{directory}{$dir}{dir}{file}{$file}
324 {mode}
325 );
326
327 }
328 }
329 }
330 exit 0;
331 }
332
333 #-----------------------------------------------------------------------------
334 # Here we are removing the history file on the file system
335 #-----------------------------------------------------------------------------
336 if ( $options{clean} ) {
337 print
338 "Are you sure you want to remove file: '$SAVED' ? [y/n (Enter to cancel)] ";
339 chomp( my $answer = <STDIN> );
340
341 if ( $answer eq 'y' ) {
342 unlink($SAVED)
343 or die "Failed to delete '$SAVED': $!\n";
344 print "Deleted $SAVED\n";
345 exit 0;
346 }
347 else {
348 print "Done.\n";
349 exit 0;
350 }
351 }
352
353 #-----------------------------------------------------------------------------
354 # clean_user($username)
355 #
356 # Untaint a username, and check it's valid
357 #-----------------------------------------------------------------------------
358 sub clean_user {
359 my $unclean_user = shift;
360 if ( $unclean_user =~ qr{^([-+@\w.]+)$} ) {
361 my $clean = $1;
362 return $clean;
363 }
364 else {
365 die "Username: '$unclean_user' invalid!\n";
366 }
367
368 return;
369 }
370
371 #-----------------------------------------------------------------------------
372 # clean_dir($dir)
373 #
374 # Untaint a directory
375 #-----------------------------------------------------------------------------
376 sub clean_dir {
377 my $unclean_dir = shift;
378 my ($clean_dir) = $unclean_dir =~ /^([-+@\w.\/\s&!\.]+)$/;
379 return $clean_dir;
380 }
381
382 #-----------------------------------------------------------------------------
383 # clean_file($dir)
384 #
385 # Untaint a file
386 #-----------------------------------------------------------------------------
387 sub clean_file {
388 my $unclean_file = shift;
389 my ($clean_file) = $unclean_file =~ /^([-+@\w.\/\s,&!\.:=_]+)$/;
390 return $clean_file;
391 }
392
393 #-----------------------------------------------------------------------------
394 # split_args(@args_to_split)
395 #
396 # Split up our commandline args - ghenry,john,james etc.
397 #-----------------------------------------------------------------------------
398 sub split_args {
399 my @to_split = @_;
400 my @split = split( /,/, join( ',', @to_split ) );
401 return @split;
402 }
403
404 #-----------------------------------------------------------------------------
405 # restore_emails_and_dirs($uid, $group, $to_change, $mode)
406 #
407 # Function for restoring changes made during dovecot shares
408 #-----------------------------------------------------------------------------
409 sub restore_emails_and_dirs {
410 my $uid = shift;
411 my $group = shift;
412 my $to_change = shift;
413 my $mode = shift;
414
415 return if $options{'dry-run'};
416
417 chown $uid, $group, $to_change
418 or warn "Couldn't change ownership of '$to_change': $!\n";
419
420 chmod oct($mode), $to_change
421 or warn "Couldn't change permissions of '$to_change': $!\n";
422
423 return;
424 }
425
426 #-----------------------------------------------------------------------------
427 # emails_and_dirs
428 #
429 # Callback for File::Find when creating dovecot shares
430 #-----------------------------------------------------------------------------
431 sub emails_and_dirs {
432
433 # Only files in Maildir
434 return if $File::Find::name !~ m{Maildir};
435
436 # No dovecot system files
437 return
438 if $File::Find::name =~ m{subscriptions|dovecot|public|control|index};
439
440 # No symlinks
441 return if -l $File::Find::name;
442
443 # Record permissions we have found right away
444 my ( $mode, $uid, $gid ) = ( stat($File::Find::name) )[ 2, 4, 5 ];
445
446 $mode = sprintf "%04o", S_IMODE($mode);
447
448 save_previous( $run, $File::Find::name, $mode, $uid, $gid );
449
450 return;
451 }
452
453 #-----------------------------------------------------------------------------
454 # create_share($maildir)
455 #
456 # Main function for creating dovecot shares
457 #-----------------------------------------------------------------------------
458 sub create_share {
459 my $dir = shift;
460
461 # untaint $dir
462 $dir =~ /^([-+@\w.\/\s&!\.\{\}#]+)$/;
463 $dir = $1;
464
465 # Grabbing the new, cur and tmp folders here
466 my @change_perms_on;
467 push @change_perms_on, $dir;
468
469 # We don't want to add a dovecot-shared to new, cur or tmp dirs
470 return if $dir =~ m{(new|cur|tmp)$};
471
472 if ( ( -e $dir . $SHARED ) && !$options{override} ) {
473 print BOLD GREEN, "***** DRY RUN *****\n", RESET
474 if $options{'dry-run'};
475 print RED,
476 "Directory: $dir already shared (try --override)," . " skipping!\n",
477 RESET;
478 }
479 else {
480
481 # Check for dirs to skip straight away
482 if (@skip) {
483 for my $skip (@skip) {
484 return if $dir =~ m{$skip$};
485 }
486 }
487
488 print BOLD GREEN, "***** DRY RUN *****\n", RESET
489 if $options{'dry-run'};
490
491 for my $u (@share_with) {
492 print GREEN, "Sharing Maildir: $dir with user: '$u'\n", RESET;
493 }
494
495 return if $options{'dry-run'};
496
497 # Save to filesystem before we do anything, so we can
498 # do a --restore in case we mess up. $SAVE is setup in
499 # emails_and_dirs()
500 nstore( $SAVE, $SAVED )
501 or die "Can't save history: $!\n";
502
503 # More untainting
504 my $dovecot_file = clean_dir( $dir . $SHARED );
505
506 # Remove dovecot-shared, only if --override is set, as we enter
507 # this branch if dovecot-shared exists
508 if ( -e $dovecot_file ) {
509 unlink $dovecot_file
510 or die "Can't remove '$dovecot_file: $!\n";
511 }
512
513 open my $DOVECOT_SHARED, ">>", $dovecot_file
514 or die "Couldn't create $dovecot_file: $!\n";
515 close $DOVECOT_SHARED;
516
517 if ( -e $dovecot_file && $options{verbose} ) {
518 print "Created $dovecot_file\n";
519 }
520
521 # pick off the owner of the dir and use that for all perm changes
522 my $uid = ( stat($dir) )[4];
523
524 # untaint $uid
525 $uid =~ /^([\d]+)$/;
526 $uid = $1;
527
528 # untaint $share_group
529 $share_group =~ /^([\d]+)$/;
530 $share_group = $1;
531
532 # change perms on dirs and dovecot-shared
533 for my $to_change ( $dir, $dovecot_file, @homes, @change_perms_on ) {
534 change_perms( $uid, $to_change, $share_group );
535 }
536
537 # change_perms on e-mails, so they can be read
538 for my $dir ( keys %{ $SAVE->{run}{$run}{directory} } ) {
539 for my $file (
540 keys %{ $SAVE->{run}{$run}{directory}{$dir}{dir}{file} } )
541 {
542 change_perms(
543 $uid,
544 clean_file(
545 $SAVE->{run}{$run}{directory}{$dir}{dir}{file}{$file}
546 {name}
547 ),
548 $share_group
549 );
550 }
551 }
552
553 # create the symlink
554 for my $user (@share_with) {
555 for my $orig_user (@users) {
556 my $prefix = $options{prefix} || ucfirst($orig_user);
557
558 # grab the maildir
559 my ($orig_maildir) = $dir =~ m{Maildir/(.*)$};
560 $orig_maildir = '.TOP-INBOX' if !defined $orig_maildir;
561
562 # biiiiggggg concatenation :-(
563 my $symlink =
564 clean_dir(
565 '/home/' . $user . '/Maildir/.' . $prefix . $orig_maildir );
566
567 next if -l $symlink; # Don't create same symlink
568
569 symlink $dir, $symlink
570 or warn
571 "Couldn't create symlink in '/home/$user/Maildir/': $!\n";
572
573 if ( -l $symlink && $options{verbose} ) {
574 print "Symlink at: '$symlink'\n";
575 }
576 }
577 }
578 }
579 return;
580 }
581
582 #-----------------------------------------------------------------------------
583 # change_perms($uid, $to_change, $share_group)
584 #
585 # Function for changing File Permissions
586 #-----------------------------------------------------------------------------
587 sub change_perms {
588 my $uid = shift;
589 my $to_change = shift;
590 my $share_group = shift;
591
592 print "Changing ownership and permissions of: $to_change"
593 . " to: 'rwxrws--- $uid $share_group'\n"
594 if $options{verbose};
595
596 chown $uid, $share_group, $to_change
597 or warn "Couldn't change ownership of '$to_change': $!\n";
598
599 # can use 02770 here without oct, but keeps similarity bewteen
600 # command line chmod
601 chmod oct(2770), $to_change
602 or warn "Couldn't change permissions of '$to_change': $!\n";
603
604 return;
605 }
606
607 #-----------------------------------------------------------------------------
608 # save_previous($run, $dir_or_file, $mode, $uid, $gid)
609 #
610 # Function for saving File Permissions etc. before making changes for the
611 # Dovecot Shares
612 #-----------------------------------------------------------------------------
613 sub save_previous {
614 my $run = shift;
615 my $dir_or_file = shift;
616 my $mode = shift;
617 my $uid = shift;
618 my $gid = shift;
619
620 # Setup our dates
621 $SAVE->{run}{$run}{run_date} = $HUMAN_RUN_DATE;
622 $SAVE->{run}{$run}{restore_check_date} = $NOW;
623
624 if ( -d $dir_or_file ) {
625 ++$dir_num;
626 $SAVE->{run}{$run}{directory}{$dir_num}{dir}{name} = $dir_or_file;
627 $SAVE->{run}{$run}{directory}{$dir_num}{mode} = $mode;
628 $SAVE->{run}{$run}{directory}{$dir_num}{uid} = $uid;
629 $SAVE->{run}{$run}{directory}{$dir_num}{gid} = $gid;
630 }
631 elsif ( -f $dir_or_file ) {
632 ++$file_num;
633 for my $dir ( keys %{ $SAVE->{run}{$run}{directory} } ) {
634 if (
635 $dir_or_file =~ $SAVE->{run}{$run}{directory}{$dir}{dir}{name} )
636 {
637 $SAVE->{run}{$run}{directory}{$dir}{dir}{file}{$file_num}
638 {name} = $dir_or_file;
639 $SAVE->{run}{$run}{directory}{$dir}{dir}{file}{$file_num}
640 {mode} = $mode;
641 $SAVE->{run}{$run}{directory}{$dir}{dir}{file}{$file_num}{uid} =
642 $uid;
643 $SAVE->{run}{$run}{directory}{$dir}{dir}{file}{$file_num}{gid} =
644 $gid;
645 }
646 }
647 }
648 else {
649 return;
650 }
651 }
652
653 # {{{ Documentation
654 __END__
655
656 =pod
657
658 =head1 NAME
659
660 create_dovecot_shares - Create Dovecot shares and put symlinks into user Maildirs
661
662 =head1 VERSION
663
664 This document describes create_dovecot_shares version 1.01
665
666 =head1 SYNOPSIS
667
668 [root@suretec home]$ create_dovecot_shares [OPTIONS]
669
670 INPUT OPTIONS:
671 --username Username/Usernames you want to share the Maildir of, comma seperated
672 --group Group for Maildir share ownership
673 --dry-run Doesn't make any changes, just shows what would happen
674 --share-with List of users to share with (creates symlinks in their Maildir), comma seperated
675 --maildir List or Maildirs to share, or all found in home directory if not specified
676 --override Overrides all existing shares (dovecot-shared file and perms) if found
677 --skip Skips existing share with same name if found, comma seperated
678 --prefix Prefix for shares. Default is username.INBOX etc.
679 --restore Restore to before changes. Takes a run number, e.g. 5 runs back
680
681
682 OUTPUT OPTIONS:
683 --History Prints Summary of last Create Share runs (we keep 10.)
684 --clean Removes the dovecot_shares.hist file from F</var/cache>
685 --Version Print program version
686 --verbose Print in fine detail what is happening
687 --help List this help
688 --man List the full create_dovecot_shares manpage
689
690 EXAMPLES:
691 [root@suretec home]$ create_dovecot_shares --username=ghenry --group=support --skip=/home/ghenry/Maildir,/home/ghenry/Maildir/.Sent --share-with=john,admin --dry-run
692
693 [root@suretec home]$ create_dovecot_shares --username=ghenry,john,jack --group=accounts --share-with=philip --override
694
695 [root@suretec home]$ create_dovecot_shares --username=ghenry --share-with=john --group=accounts --prefix=OFFICE
696
697 [root@suretec home]$ create_dovecot_shares --username=ghenry --group=accounts --maildir=/home/john/Maildir/.Sent
698
699 [root@suretec home]$ create_dovecot_shares --restore 5
700
701 [root@suretec home]$ create_dovecot_shares --History
702
703 =head1 DESCRIPTION
704
705 Creating lots of F<dovecot-shared> files, changing permissions and creating symlinks is
706 a pain, especially when dealing with more than a handle of users.
707
708 C<create_dovecot_shares> helps.
709
710 It modifies users home directories and Maildirs (permissions) for sharing via
711 Dovecot, and creates symlinks into the Maildir you want the shares accessed
712 from.
713
714 There's even a C<restore> option, to roll back any changes you made with a
715 history of 10 runs.
716
717 See L<"SYNOPSIS"> for examples.
718
719 Must be root.
720
721 =head1 README
722
723 Creating lots of F<dovecot-shared> files for the Dovecot IMAP Server, changing permissions and creating symlinks is a pain, especially when dealing with more than a handle of users.
724
725 create_dovecot_shares helps
726
727 =head1 CONFIGURATION AND ENVIRONMENT
728
729 This script requires no configuration files or environment variables.
730
731 It does set:
732
733 $ENV{'PATH'}= '/usr/local/bin:/usr/bin:/bin';
734 $ENV{'SHELL'}= '/bin/bash';
735 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
736
737 =head1 PREREQUISITES
738
739 =over
740
741 =item *
742 L<Data::Dumper>
743
744 =item *
745 L<File::Find>
746
747 =item *
748 L<Fcntl>
749
750 =item *
751 L<Getopt::Long>
752
753 =item *
754 L<List::Util>
755
756 =item *
757 L<Pod::Usage>
758
759 =item *
760 L<POSIX>
761
762 =item *
763 L<Term::ANSIColor>
764
765 =item *
766 L<Storable>
767
768 =back
769
770 =head1 OSNAMES
771
772 linux
773
774 =head1 SCRIPT CATEGORIES
775
776 Mail
777
778 =head1 INCOMPATIBILITIES
779
780 None reported.
781
782 =head1 BUGS AND LIMITATIONS
783
784 No bugs have been reported.
785
786 Please report any bugs or feature requests to C<support@suretecsystems.com> or
787 call the number on L<http://www.suretecsystems.com/contact>.
788
789 =head1 AUTHOR
790
791 Gavin Henry, C<< <ghenry@suretecsystems.com> >>
792
793 =head1 LICENCE AND COPYRIGHT
794
795 Copyright (c) 2006, Suretec Systems Ltd. L<http://www.suretecsystems.com>
796
797 Copyright (c) 2006, Gavin Henry, C<< <ghenry@suretecsystems.com> >>. All rights reserved.
798
799 This module is free software; you can redistribute it and/or
800 modify it under the terms of the GPL V2. See perldoc L<perlgpl>.
801
802 =head1 DISCLAIMER OF WARRANTY
803
804 BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
805 FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
806 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
807 PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
808 EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
809 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
810 ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
811 YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
812 NECESSARY SERVICING, REPAIR, OR CORRECTION.
813
814 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
815 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
816 REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
817 LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
818 OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
819 THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
820 RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
821 FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
822
823 =begin pod_to_ignore
824 # }}}
825
syntax highlighted by Code2HTML, v. 0.9.1