File Coverage

blib/lib/Mail/Alias.pm
Criterion Covered Total %
statement 92 247 37.2
branch 17 86 19.7
condition 3 17 17.6
subroutine 15 29 51.7
pod 10 14 71.4
total 137 393 34.8


line stmt bran cond sub pod time code
1             # Mail::Alias.pm
2             #
3             # Version 1.13 Date: 26 February 2022
4             #
5             # Copyright (c) 2022 Jonathan Kamens . All rights reserved.
6             # This program is free software; you can redistribute it and/or
7             # modify it under the same terms as Perl itself.
8             #
9             # Portions of earlier versions of this program were copyrighted by Tom
10             # Zeltwanger and Graham Barr. The current copyright holder extends full
11             # authorship rights to both of the previous authors.
12              
13             # PERLDOC documentation is found at the end of this file
14              
15              
16              
17             ##################################
18             package Mail::Alias; #
19             ##################################
20              
21 1     1   477 use Carp;
  1         2  
  1         74  
22 1     1   6 use vars qw($VERSION);
  1         1  
  1         1497  
23              
24             $VERSION = 1.13;
25 0     0 0 0 sub Version { $VERSION }
26              
27              
28             # Global variable initialization
29             my $alias_error = ""; # String used for returning error messages
30             my $aliases_file_default = "/etc/mail/aliases"; # The default aliases file name
31             my $max_alias_length = "40"; # The max number of characters in aliases
32             my $alias_nochar = "@[]"; # Characters not allowed in aliases
33              
34              
35             #-------------#
36             # new() method#
37             #-------------#
38              
39             sub new {
40              
41 1     1 1 76 my ($class, $object, $filename);
42 1         2 $class = shift; # Get the class name
43              
44 1         2 $filename = $aliases_file_default; # Use the default filenname
45 1 50       4 if (defined($_[0])) { # Unless a new name was passed as 1st argument
46 1         2 $filename = $_[0];
47             }
48            
49 1         4 $object = { _filename => $filename,
50             _errormsg => "no error reported",
51             _usemem => "0",
52             _usefile=> "1"
53             };
54            
55 1         2 my $self = bless ($object, $class);
56            
57 1         3 $self->_init($filename); # Execute the _init method for the calling class
58              
59            
60 1         2 return $object;
61              
62             }
63              
64              
65             #----------#
66             # _init() #
67             #----------#
68             sub _init {
69 1     1   2 my $self = shift;
70 1         2 $self->usefile; # If Alias object, default to file access
71            
72             }
73              
74              
75              
76             #----------#
77             # format() #
78             #----------#
79             sub format {
80 0     0 1 0 my $me = shift;
81 0         0 my $fmt = shift;
82 0         0 my $pkg = "Mail::Alias::" . $fmt;
83              
84             croak "Unknown format '$fmt'"
85 0 0       0 unless @{$pkg . "::ISA"};
  0         0  
86              
87 0         0 bless $me, $pkg;
88             }
89              
90              
91             #----------#
92             # usemem() #
93             #----------#
94              
95             sub usemem {
96 0     0 1 0 my $self = shift;
97 0         0 $self->{_usemem} = "1";
98 0         0 $self->{_usefile} = "0";
99 0         0 return;
100             }
101              
102              
103             #----------#
104             # usefile()#
105             #----------#
106              
107             sub usefile {
108 1     1 1 1 my $self = shift;
109 1         5 $self->{_usefile} = "1";
110 1         1 $self->{_usemem} = "0";
111 1         2 return;
112             }
113              
114              
115              
116             #----------#
117             # exists() #
118             #----------#
119             sub exists {
120 2     2 1 47 my ($self, $alias) = @_;
121              
122            
123 2 50       5 if ($self->{_usemem}) {
124 0         0 return defined $self->{$alias};
125              
126             }
127             else {
128            
129 2         3 my ($self, $alias) = @_;
130 2         4 my ($text_line) = undef; # Temp storage of the line from the alias file
131              
132 2         2 $aliases_file = $self->{_filename};
133              
134 2 50       49 open (ALIASES_FILE , $aliases_file) || die "ERROR: Can't open $aliases_file\n";
135              
136             # search till alias is found or EOF
137 2         38 while () {
138 13 100       56 if (/^$alias:/i) {
139 1         2 $text_line = $_;
140 1         3 chomp($text_line);
141 1         7 close ALIASES_FILE;
142 1         5 return $text_line;
143             }
144              
145             }
146              
147             # If you got here, the EOF was hit - returns undefined
148 1         5 $self->{_errormsg} = "ERROR: There is no alias $alias in $aliases_file";
149 1         9 close ALIASES_FILE;
150 1         5 return undef;
151              
152             }
153             }
154              
155              
156             #----------#
157             # expand() #
158             #----------#
159             sub expand {
160 0     0 1 0 my $me = shift;
161 0         0 my @result = ();
162 0         0 my %done = ();
163 0         0 my $alias;
164 0         0 my @todo = @_;
165              
166 0         0 while($alias = shift(@todo)) {
167 0 0       0 next if(defined $done{$alias});
168 0         0 $done{$alias} = 1;
169 0 0       0 if(defined $me->{$alias}) {
170 0         0 push(@todo,@{$me->{$alias}});
  0         0  
171             }
172             else {
173 0         0 push(@result,$alias);
174             }
175             }
176 0 0       0 wantarray ? @result : \@result;
177             }
178              
179              
180              
181             #---------------------------------#
182             # Alias::append() Method #
183             # Version 1.0 8/19/00 #
184             #---------------------------------#
185              
186             sub append {
187              
188 1     1 1 24 my $return_string;
189 1         3 my ($self, $alias, $address_string) = @_;
190              
191             # Die if no alias was passed
192 1 50       3 unless ($alias) {
193 0         0 die "ERROR: Alias::append requires an Alias argument\n";
194             }
195            
196 1         2 $aliases_file = $self->{_filename};
197            
198 1 50       2 if ($self->exists($alias)) {
199 0         0 $self->{_errormsg} = "ERROR: $alias is already in the file $aliases_file\n";
200 0         0 undef ($return_string);
201             }
202            
203             else {
204              
205 1 50       29 open (ALIASES_FILE ,">>$aliases_file") || die "ERROR: Can't open $alias_file\n";
206 1         7 print ALIASES_FILE "$alias: $address_string\n";
207 1         23 close ALIASES_FILE;
208 1         6 $return_string = "1"; # Successfully added the alias
209              
210             } # ELSE
211              
212             }
213              
214              
215             #------------------------------#
216             # Alias::delete() Method #
217             # Version 1.0 8/13/00#
218             #------------------------------#
219              
220             sub delete {
221            
222 1     1 1 28 my ($self, @alias_list) = @_;
223 1         2 $filename = $self->{_filename};
224 1         2 my $deleted = undef;
225            
226              
227            
228            
229 1         2 my $working_file = ($filename . ".tmp");
230 1         37 rename ("$filename", "$working_file");
231            
232            
233 1 50       44 open (NEW_FILE ,">$filename")
234             || die "ERROR: Can't open $filename\n";
235            
236            
237 1 50       24 open (EXISTING_FILE , "$working_file")
238             || die "ERROR: Can't open $working_file\n";
239              
240            
241            
242              
243 1         23 while (defined ($textline = )) {
244 7         10 chomp ($textline);
245            
246            
247 7 100 100     32 if (($textline =~ /^\s*$/) || ($textline =~ /^#/)) {
248 4         23 print NEW_FILE "$textline\n";
249             }
250              
251             else {
252            
253            
254            
255 3 100       10 if (!alias_check ($textline , \@alias_list)) {
256 2         8 print NEW_FILE "$textline\n";
257             }
258            
259             else {
260 1         26 print "DELETING: $textline\n";
261 1         10 $deleted = "1";
262             }
263            
264            
265             }
266            
267             }
268              
269             # Close the files
270 1         9 close EXISTING_FILE;
271 1         33 close NEW_FILE;
272 1         6 return $deleted;
273            
274             } # end delete
275              
276              
277             #------------------------------#
278             # Alias::update() Method #
279             # Version 1.0 8/13/00#
280             #------------------------------#
281              
282             sub update {
283              
284 0     0 1 0 my ($self, $alias, $address_string) = @_;
285 0         0 my ($found_it, $alias_line);
286              
287 0         0 undef $found_it;
288              
289             # Form the alias line from the passed arguments
290 0 0       0 if ($address_string) { # If there is a second argument passed
291 0         0 $alias_line = "$alias" . ": " . " $address_string";
292             }
293             else {
294 0         0 $alias_line = $alias; # The whole alias line is in $alias
295 0         0 $alias_line =~ /^(\S+)\s*:\s*(\S*)$/; # Extract the alias from the alias_line
296 0         0 $alias = $1;
297             }
298            
299            
300 0         0 $filename = $self->{_filename}; # Get the name of the aliases_file to be updated
301              
302            
303            
304 0         0 my $working_file = ($filename . ".tmp");
305 0         0 rename ("$filename", "$working_file");
306            
307            
308 0 0       0 open (NEW_FILE ,">$filename")
309             || die "ERROR: Can't open $filename\n";
310            
311            
312 0 0       0 open (EXISTING_FILE , "$working_file")
313             || die "ERROR: Can't open $working_file\n";
314              
315            
316            
317 0         0 while (defined ($textline = )) { # For every line
318            
319             # If line is blank or comment, just write it out
320 0         0 chomp ($textline);
321            
322 0 0 0     0 if (($textline =~ /^\s+$/) || ($textline =~ /^#/)) {
323 0         0 print NEW_FILE "$textline\n";
324             }
325              
326             else { # Process alias lines here
327            
328            
329 0 0       0 if ($textline =~ /^$alias:/i) {
330 0         0 print NEW_FILE "$alias_line\n";
331 0         0 $found_it = "1";
332             }
333            
334             else {
335            
336 0         0 print NEW_FILE "$textline\n";
337            
338             }
339              
340              
341             }
342            
343             }
344              
345            
346 0         0 close EXISTING_FILE;
347 0         0 close NEW_FILE;
348              
349 0         0 return $found_it;
350              
351             } # end update
352              
353              
354             #-------------------#
355             # valid_alias Method#
356             #-------------------#
357             # valid_alias performs validation of the alias passed as an argument.
358             # Return 1 if success and UNDEF if the test fails
359              
360             sub valid_alias {
361              
362 0     0 0 0 my ($self, $alias) = @_; # Get the alias
363 0         0 my $return_string = 1; # Set return for success
364              
365 0 0 0     0 if (($alias =~ /[$alias_nochar]/) || (length($alias) > $max_alias_length))
366 0         0 { undef($return_string)
367             }
368            
369 0         0 return $return_string;
370              
371             }
372              
373              
374             #------------------#
375             # alias_file Method#
376             #------------------#
377             # alias_file returns the complete path to the alias file that is being operated upon
378             # by the Mail::Alias methods.
379             # If a filename is passed as an argument, it is set to be the new filename for
380             # all future operations. The file must exist or nothing is done.
381              
382             sub alias_file {
383              
384 1     1 1 44 my ($self, $newname) = @_; # Get the new name if one was passed
385              
386             # If an argument was passed, make it the new $aliases_file value and return
387 1 50       14 if ($newname) {
388              
389            
390 0 0       0 if (-e $newname) {
391 0         0 $self->{_filename} = $newname;
392 0         0 return "$newname";
393             }
394              
395             else {
396            
397 0         0 $self->{_errormsg} = "ERROR: $newname does not exist\n";
398 0         0 return undef;
399             }
400            
401             }
402            
403              
404             # If no argument, just return the current working aliases file pathname
405             else {
406              
407 1         4 return $self->{_filename};
408             }
409            
410             }
411              
412              
413             #------------#
414             # error_check#
415             #------------#
416             # Returns the last error message in a text string
417             # This method can be used after any method failed (i.e. returned UNDEF)
418              
419             sub error_check {
420              
421 0     0 0 0 my $self = shift;
422 0         0 my $return_string;
423            
424            
425 0         0 $return_string = $self->{_errormsg};
426            
427            
428 0         0 $self->{_errormsg} = "No error found";
429            
430 0         0 return $return_string;
431              
432             }
433              
434              
435             #------------#
436             # alias_check#
437             #------------#
438             # Check a line of text to see if it begins with any alias in the alias_list
439             # Return the matching alias if found or UNDEF if no match exists
440             # Alias matching is not case sensitive
441              
442             sub alias_check {
443             # Define variables and get arguments
444 3     3 0 3 my ($list_length, $list_index, $text);
445 3         6 $text = $_[0]; # 1st argument is the line of text
446 3         3 $list = $_[1]; # 2nd argument is an array reference
447              
448             # Extract the first non-whitespace from the text_line
449            
450 3         7 $text =~ /^\s*(\S+)\s+/;
451 3         5 $text = $1;
452 3         8 $text =~ s/://; # Get rid of trailing :
453              
454             # Search for the string
455 3         6 $list_length = @$list;
456              
457 3         4 for ($list_index = 0; $list_index < $list_length; $list_index++) {
458              
459             # Check each alias for a match with the beginning of the text line
460             # to get a match, the alias must be:
461             # the first non-whitespace on the line
462             # followed by whitespace or a : character
463 3 100       32 if ($text =~ /^\s*$$list[$list_index]:?\s*$/i) {
464 1         3 return $$list[$list_index]; # Return the matching string from the list
465             }
466            
467             }
468            
469            
470 2         4 return undef;
471            
472             }
473              
474              
475             #############################################################
476             package Mail::Alias::Sendmail; #
477             # Defines the Sendmail alias class read() and write() #
478             #############################################################
479              
480 1     1   7 use Carp;
  1         2  
  1         55  
481             #use Mail::Address;
482              
483 1     1   13 use vars qw(@ISA);
  1         2  
  1         692  
484              
485             @ISA = qw(Mail::Alias);
486              
487              
488             #----------#
489             # _init() #
490             #----------#
491             sub _init {
492 0     0     my ($self, $filename) = @_;
493              
494 0 0         $self->read($filename) if($filename);
495 0           $self->usemem; # If Alias::Sendmail object, default to memory access
496              
497             }
498              
499              
500             #---------#
501             # write() #
502             #---------#
503              
504             sub write {
505 0     0     my $me = shift;
506 0           my $file = shift;
507 0           my $alias;
508             my $fd;
509 0           local *ALIAS;
510              
511 0 0         if(ref($file)) {
512 0           $fd = $file;
513             }
514             else {
515 0 0         open(ALIAS,$file) || croak "Cannot open $file: $!\n";
516 0           $fd = \*ALIAS;
517             }
518              
519 0           foreach $alias (sort keys %$me) {
520 0 0         unless ($alias =~ /^_/) {
521 0           my $ln = $alias . ": " . join(", ",@{$me->{$alias}});
  0            
522 0           $ln =~ s/(.{55,78},)/$1\n\t/g;
523 0           print $fd $ln,"\n";
524             }
525             }
526              
527 0 0         close(ALIAS) if($fd == \*ALIAS);
528             }
529              
530             #-----------------------------------------------------------#
531             # _include_file Local sub for expanding :include: files #
532             #-----------------------------------------------------------#
533             sub _include_file {
534 0     0     my $file = shift;
535 0           local *INC;
536 0           my @ln;
537 0           local $_;
538 0 0 0       open(INC,$file) or carp "Cannot open file '$file'" and return "";
539 0           @ln = grep(/^[^#]/,);
540 0           close(INC);
541 0           chomp(@ln);
542 0           join(",",@ln);
543             }
544              
545             #--------#
546             # read() #
547             #--------#
548             sub read {
549 0     0     my $me = shift;
550 0           my $file = shift;
551              
552 0           local *ALIAS;
553 0           local $_;
554 0 0         open(ALIAS,$file) || croak "Cannot open $file: $!\n";
555              
556 0           my $group = undef;
557 0           my $line = undef;
558              
559 0           while() {
560 0           chomp;
561 0 0 0       if(defined $line && /^\s/) {
562 0           $line .= $_;
563             }
564             else {
565 0 0         if(defined $line) {
566 0 0         if($line =~ s/^([^:]+)://) {
567 0           my @resp;
568 0           $group = $1;
569 0           $group =~ s/(\A\s+|\s+\Z)//g;
570 0           $line =~ s/\"?:include:(\S+)\"?/_include_file($1)/eg;
  0            
571 0           $line =~ s/(\A[\s,]+|[\s,]+\Z)//g;
572              
573 0           while(length($line)) {
574 0           $line =~ s/\A([^\"][^ \t,]+|\"[^\"]+\")(\s*,\s*)*//;
575 0           push(@resp,$1);
576             }
577              
578 0           $me->{$group} = \@resp;
579             }
580 0           undef $line;
581             }
582 0 0 0       next if (/^#/ || /^\s*$/);
583 0           $line = $_;
584             }
585             }
586 0           close(ALIAS);
587             }
588              
589             ###############################
590             package Mail::Alias::Ucbmail; #
591             ###############################
592              
593 1     1   7 use vars qw(@ISA);
  1         1  
  1         53  
594              
595             @ISA = qw(Mail::Alias::Binmail);
596              
597             ###############################
598             package Mail::Alias::Binmail; #
599             ###############################
600              
601 1     1   6 use Carp;
  1         9  
  1         48  
602             #use Mail::Address;
603              
604 1     1   5 use vars qw(@ISA);
  1         1  
  1         494  
605              
606             @ISA = qw(Mail::Alias);
607              
608             #----------#
609             # _init() #
610             #----------#
611             sub _init {
612 0     0     my ($self, $filename) = @_;
613              
614 0 0         $self->read($filename) if($filename);
615 0           $self->usemem; # If Alias::Binmail object, default to memory access
616             }
617              
618              
619             #--------#
620             # read() #
621             #--------#
622             sub read {
623 0     0     my $me = shift;
624 0           my $file = shift;
625              
626 0           local *ALIAS;
627 0           local $_;
628 0 0         open(ALIAS,$file) || croak "Cannot open $file: $!\n";
629              
630 0           while() {
631 0 0         next unless(/^\s*(alias|group)\s+(\S+)\s+(.*)/);
632 0           my($group,$who) = ($2,$3);
633              
634 0           $who =~ s/(\A[\s,]+|[\s,]+\Z)//g;
635              
636 0           my @resp = ();
637              
638 0           while(length($who)) {
639             # $who =~ s/\A([^\"]\S*|\"[^\"]*\")\s*//;
640             # my $ln = $1;
641             # $ln =~ s/\A\s*\"|\"\s*\Z//g;
642 0           $who =~ s/\A\s*(\"?)([^\"]*)\1\s*//;
643 0           push(@resp,$2);
644             # push(@resp,$ln);
645             }
646 0           $me->{$group} = [ @resp ];
647             }
648 0           close(ALIAS);
649             }
650              
651             #---------#
652             # write() #
653             #---------#
654             sub write {
655 0     0     my $me = shift;
656 0           my $file = shift;
657 0           my $alias;
658             my $fd;
659 0           local *ALIAS;
660              
661 0 0         if(ref($file)) {
662 0           $fd = $file;
663             }
664             else {
665 0 0         open(ALIAS,$file) || croak "Cannot open $file: $!\n";
666 0           $fd = \*ALIAS;
667             }
668              
669 0           foreach $alias (sort keys %$me) {
670 0           my @a = @{$me->{$alias}};
  0            
671 0 0         map { $_ = '"' . $_ . '"' if /\s/ } @a;
  0            
672 0 0         unless ($alias =~ /^_/) {
673 0           print $fd "alias $alias ",join(" ",@a),"\n";
674             }
675             }
676              
677 0 0         close(ALIAS) if($fd == \*ALIAS);
678             }
679              
680              
681             #############################
682             # Documentation starts here #
683             #############################
684              
685             =head1 NAME
686              
687             Mail::Alias - Maniulates mail alias files of various formats. Works on files directly or loads files into memory and works on the buffer.
688              
689             =head1 SYNOPSIS
690              
691             use Mail::Alias;
692              
693             =head1 DESCRIPTION
694              
695             C can read various formats of mail alias. Once an object has been created it can be used to expand aliases and output in another format.
696              
697              
698             =head1 CONSTRUCTOR
699              
700             =over 4
701              
702             =item B
703             Alias objects can be created in two ways;
704             With a format specified- Mail::Alias::Sendmail->new([filename])
705             Without a format specified- Mail::Alias->new([filename]}. Format defaults to
706             SENDMAIL
707             In either case, the filename is optional and, if supplied, it will be read in
708             when the object is created. Available formats are Sendmail, Ucbmail, and
709             Binmail.
710              
711             =back
712              
713             =head1 METHODS
714              
715             =over 4
716              
717             =item B
718             Reads an alias file of the specified format into memory. Comments or blank
719             lines are lost upon reading. Due to storage in a hash, ordering of the alias
720             lines is also lost.
721              
722             =item B
723             The current set of aliases contained in the object memory are written to a
724             file using the current format.
725             If a filehandle is passed, data is written to the already opened file. If a
726             filename is passed, it is opened and the memory is written to the file.
727             Note: if passing a filename, include the mode (i.e. to write to a file named
728             aliases pass >aliases). Before writing, the alias lines are sorted
729             alphabetically.
730              
731             =item B
732             Set the current alias file format.
733              
734             =item B
735             Indicates the presence of the passed alias within the object (if using memory
736             access), or the current aliases file (if using direct file access). For
737             direct file access, the return value is the address string for the alias.
738            
739             =item B
740             Expands the passed alias into a list of addresses. Expansion properly handles
741             :include: files, recursion, and continuation lines.Only works when memory
742             access is being used. If the alias is not found in the object, you get back
743             what you sent.
744              
745             =item B
746             Sets or gets the name of the current alias filename for direct access.
747              
748             =item B
749             Adds an alias to an existing Sendmail alias file. The alias and addresses can
750             be passed as two separate arguments (alias, addresses) or as a single line of
751             text (alias: addresses)
752              
753             =item B
754             Deletes the entry for an alias from the current alias file.
755              
756             =item B
757             Replaces the address string entry for an alias in the current alias file.
758              
759             =item B
760             Sets the working mode to use memory (indirect access). Use read(), write() and
761             format() methods.
762              
763             =item B
764             Sets the working mode to use files (direct access). Use append() and delete()
765             methods.
766              
767              
768             =back
769              
770             =head1 MAINTAINER
771              
772             Jonathan Kamens (CPAN author ID: JIK)
773              
774             =head1 COPYRIGHT
775              
776             Copyright (c) 2022 Jonathan Kamens . All rights reserved. This
777             program is free software; you can redistribute it and/or modify it under the
778             same terms as Perl itself.
779              
780             Portions of earlier versions of this program were copyrighted by Tom Zeltwanger
781             and Graham Barr. The current copyright holder extends full authorship rights to
782             both of the previous authors.
783              
784             =cut
785              
786             1;