File Coverage

blib/lib/Mail/Alias.pm
Criterion Covered Total %
statement 152 250 60.8
branch 36 90 40.0
condition 11 20 55.0
subroutine 20 29 68.9
pod 10 14 71.4
total 229 403 56.8


line stmt bran cond sub pod time code
1             # Mail::Alias.pm
2             #
3             # Version 1.15 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   465 use Carp;
  1         1  
  1         77  
22 1     1   6 use vars qw($VERSION);
  1         2  
  1         1563  
23              
24             $VERSION = 1.15;
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 2     2 1 60 my ($class, $object, $filename);
42 2         5 $class = shift; # Get the class name
43              
44 2         3 $filename = $aliases_file_default; # Use the default filenname
45 2 50       7 if (defined($_[0])) { # Unless a new name was passed as 1st argument
46 2         3 $filename = $_[0];
47             }
48            
49 2         7 $object = { _filename => $filename,
50             _errormsg => "no error reported",
51             _usemem => "0",
52             _usefile=> "1"
53             };
54            
55 2         4 my $self = bless ($object, $class);
56            
57 2         6 $self->_init($filename); # Execute the _init method for the calling class
58              
59            
60 2         13 return $object;
61              
62             }
63              
64              
65             #----------#
66             # _init() #
67             #----------#
68             sub _init {
69 1     1   1 my $self = shift;
70 1         3 $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 1     1 1 2 my $self = shift;
97 1         2 $self->{_usemem} = "1";
98 1         2 $self->{_usefile} = "0";
99 1         2 return;
100             }
101              
102              
103             #----------#
104             # usefile()#
105             #----------#
106              
107             sub usefile {
108 1     1 1 1 my $self = shift;
109 1         4 $self->{_usefile} = "1";
110 1         2 $self->{_usemem} = "0";
111 1         2 return;
112             }
113              
114              
115              
116             #----------#
117             # exists() #
118             #----------#
119             sub exists {
120 3     3 1 8 my ($self, $alias) = @_;
121              
122            
123 3 100       6 if ($self->{_usemem}) {
124 1         4 return defined $self->{$alias};
125              
126             }
127             else {
128            
129 2         4 my ($self, $alias) = @_;
130 2         3 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       55 open (ALIASES_FILE , $aliases_file) || die "ERROR: Can't open $aliases_file\n";
135              
136             # search till alias is found or EOF
137 2         49 while () {
138 25 100       92 if (/^$alias:/i) {
139 1         2 $text_line = $_;
140 1         3 chomp($text_line);
141 1         8 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         10 close ALIASES_FILE;
150 1         5 return undef;
151              
152             }
153             }
154              
155              
156             #----------#
157             # expand() #
158             #----------#
159             sub expand {
160 1     1 1 2 my $me = shift;
161 1         2 my @result = ();
162 1         2 my %done = ();
163 1         2 my $alias;
164 1         2 my @todo = @_;
165              
166 1         3 while($alias = shift(@todo)) {
167 2 50       5 next if(defined $done{$alias});
168 2         3 $done{$alias} = 1;
169 2 100       4 if(defined $me->{$alias}) {
170 1         1 push(@todo,@{$me->{$alias}});
  1         3  
171             }
172             else {
173 1         3 push(@result,$alias);
174             }
175             }
176 1 50       5 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 14 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       30 open (ALIASES_FILE ,">>$aliases_file") || die "ERROR: Can't open $alias_file\n";
206 1         16 print ALIASES_FILE "$alias: $address_string\n";
207 1         28 close ALIASES_FILE;
208 1         8 $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 4 my ($self, @alias_list) = @_;
223 1         2 $filename = $self->{_filename};
224 1         3 my $deleted = undef;
225            
226              
227            
228            
229 1         2 my $working_file = ($filename . ".tmp");
230 1         53 rename ("$filename", "$working_file");
231            
232            
233 1 50       60 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         15 while (defined ($textline = )) {
244 13         16 chomp ($textline);
245            
246            
247 13 100 100     47 if (($textline =~ /^\s*$/) || ($textline =~ /^#/)) {
248 7         32 print NEW_FILE "$textline\n";
249             }
250              
251             else {
252            
253            
254            
255 6 100       11 if (!alias_check ($textline , \@alias_list)) {
256 5         16 print NEW_FILE "$textline\n";
257             }
258            
259             else {
260 1         30 print "DELETING: $textline\n";
261 1         12 $deleted = "1";
262             }
263            
264            
265             }
266            
267             }
268              
269             # Close the files
270 1         9 close EXISTING_FILE;
271 1         57 close NEW_FILE;
272 1         8 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 3 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       3 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 6     6 0 9 my ($list_length, $list_index, $text);
445 6         7 $text = $_[0]; # 1st argument is the line of text
446 6         7 $list = $_[1]; # 2nd argument is an array reference
447              
448             # Extract the first non-whitespace from the text_line
449            
450 6         14 $text =~ /^\s*(\S+)\s+/;
451 6         9 $text = $1;
452 6         14 $text =~ s/://; # Get rid of trailing :
453              
454             # Search for the string
455 6         7 $list_length = @$list;
456              
457 6         11 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 6 100       52 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 5         10 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         7  
  1         703  
484              
485             @ISA = qw(Mail::Alias);
486              
487              
488             #----------#
489             # _init() #
490             #----------#
491             sub _init {
492 1     1   3 my ($self, $filename) = @_;
493              
494 1 50       4 $self->read($filename) if($filename);
495 1         9 $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   0 my $me = shift;
506 0         0 my $file = shift;
507 0         0 my $alias;
508             my $fd;
509 0         0 local *ALIAS;
510              
511 0 0       0 if(ref($file)) {
512 0         0 $fd = $file;
513             }
514             else {
515 0 0       0 open(ALIAS,$file) || croak "Cannot open $file: $!\n";
516 0         0 $fd = \*ALIAS;
517             }
518              
519 0         0 foreach $alias (sort keys %$me) {
520 0 0       0 unless ($alias =~ /^_/) {
521 0         0 my $ln = $alias . ": " . join(", ",@{$me->{$alias}});
  0         0  
522 0         0 $ln =~ s/(.{55,78},)/$1\n\t/g;
523 0         0 print $fd $ln,"\n";
524             }
525             }
526              
527 0 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 1     1   2 my $file = shift;
535 1         2 local *INCLUDE;
536 1         2 my @ln;
537 1         2 local $_;
538 1 50 0     62 open(INCLUDE,$file) or carp "Cannot open file '$file'" and return "";
539 1         37 @ln = grep(/^[^#]/,);
540 1         9 close(INCLUDE);
541 1         3 chomp(@ln);
542 1         7 join(",",@ln);
543             }
544              
545             #--------#
546             # read() #
547             #--------#
548             sub read {
549 1     1   2 my $me = shift;
550 1         1 my $file = shift;
551              
552 1         3 local *ALIAS;
553 1         1 local $_;
554 1 50       28 open(ALIAS,$file) || croak "Cannot open $file: $!\n";
555              
556 1         3 my $group = undef;
557 1         1 my $line = undef;
558              
559 1   66     13 while(defined($_ = ) or defined($line)) {
560 13 100       20 if(defined $_) {
561 12         13 chomp;
562 12 100 100     45 if(defined $line && /^\s/) {
563 1         3 $line .= $_;
564 1         16 next;
565             }
566             }
567 12 100       18 if(defined $line) {
568 4 50       25 if($line =~ s/^([^:]+)://) {
569 4         6 my @resp;
570 4         7 $group = $1;
571 4         12 $group =~ s/(\A\s+|\s+\Z)//g;
572 4         11 $line =~ s/\"?:include:\s*(\S+)\"?/_include_file($1)/eg;
  1         3  
573 4         26 $line =~ s/(\A[\s,]+|[\s,]+\Z)//g;
574              
575 4         11 while(length($line)) {
576 5         18 $line =~ s/\A([^\"][^ \t,]+|\"[^\"]+\")(\s*,\s*)*//;
577 5         23 push(@resp,$1);
578             }
579              
580 4         15 $me->{$group} = \@resp;
581             }
582 4         7 undef $line;
583             }
584 12 100       21 last if (! defined $_);
585 11 100 100     47 next if (/^#/ || /^\s*$/);
586 4         12 $line = $_;
587             }
588 1         10 close(ALIAS);
589             }
590              
591             ###############################
592             package Mail::Alias::Ucbmail; #
593             ###############################
594              
595 1     1   6 use vars qw(@ISA);
  1         2  
  1         63  
596              
597             @ISA = qw(Mail::Alias::Binmail);
598              
599             ###############################
600             package Mail::Alias::Binmail; #
601             ###############################
602              
603 1     1   6 use Carp;
  1         1  
  1         47  
604             #use Mail::Address;
605              
606 1     1   5 use vars qw(@ISA);
  1         1  
  1         542  
607              
608             @ISA = qw(Mail::Alias);
609              
610             #----------#
611             # _init() #
612             #----------#
613             sub _init {
614 0     0     my ($self, $filename) = @_;
615              
616 0 0         $self->read($filename) if($filename);
617 0           $self->usemem; # If Alias::Binmail object, default to memory access
618             }
619              
620              
621             #--------#
622             # read() #
623             #--------#
624             sub read {
625 0     0     my $me = shift;
626 0           my $file = shift;
627              
628 0           local *ALIAS;
629 0           local $_;
630 0 0         open(ALIAS,$file) || croak "Cannot open $file: $!\n";
631              
632 0           while() {
633 0 0         next unless(/^\s*(alias|group)\s+(\S+)\s+(.*)/);
634 0           my($group,$who) = ($2,$3);
635              
636 0           $who =~ s/(\A[\s,]+|[\s,]+\Z)//g;
637              
638 0           my @resp = ();
639              
640 0           while(length($who)) {
641             # $who =~ s/\A([^\"]\S*|\"[^\"]*\")\s*//;
642             # my $ln = $1;
643             # $ln =~ s/\A\s*\"|\"\s*\Z//g;
644 0           $who =~ s/\A\s*(\"?)([^\"]*)\1\s*//;
645 0           push(@resp,$2);
646             # push(@resp,$ln);
647             }
648 0           $me->{$group} = [ @resp ];
649             }
650 0           close(ALIAS);
651             }
652              
653             #---------#
654             # write() #
655             #---------#
656             sub write {
657 0     0     my $me = shift;
658 0           my $file = shift;
659 0           my $alias;
660             my $fd;
661 0           local *ALIAS;
662              
663 0 0         if(ref($file)) {
664 0           $fd = $file;
665             }
666             else {
667 0 0         open(ALIAS,$file) || croak "Cannot open $file: $!\n";
668 0           $fd = \*ALIAS;
669             }
670              
671 0           foreach $alias (sort keys %$me) {
672 0           my @a = @{$me->{$alias}};
  0            
673 0 0         map { $_ = '"' . $_ . '"' if /\s/ } @a;
  0            
674 0 0         unless ($alias =~ /^_/) {
675 0           print $fd "alias $alias ",join(" ",@a),"\n";
676             }
677             }
678              
679 0 0         close(ALIAS) if($fd == \*ALIAS);
680             }
681              
682              
683             #############################
684             # Documentation starts here #
685             #############################
686              
687             =head1 NAME
688              
689             Mail::Alias - Maniulates mail alias files of various formats. Works on files directly or loads files into memory and works on the buffer.
690              
691             =head1 SYNOPSIS
692              
693             use Mail::Alias;
694              
695             =head1 DESCRIPTION
696              
697             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.
698              
699              
700             =head1 CONSTRUCTOR
701              
702             =over 4
703              
704             =item B
705             Alias objects can be created in two ways;
706             With a format specified- Mail::Alias::Sendmail->new([filename])
707             Without a format specified- Mail::Alias->new([filename]}. Format defaults to
708             SENDMAIL
709             In either case, the filename is optional and, if supplied, it will be read in
710             when the object is created. Available formats are Sendmail, Ucbmail, and
711             Binmail.
712              
713             =back
714              
715             =head1 METHODS
716              
717             =over 4
718              
719             =item B
720             Reads an alias file of the specified format into memory. Comments or blank
721             lines are lost upon reading. Due to storage in a hash, ordering of the alias
722             lines is also lost.
723              
724             =item B
725             The current set of aliases contained in the object memory are written to a
726             file using the current format.
727             If a filehandle is passed, data is written to the already opened file. If a
728             filename is passed, it is opened and the memory is written to the file.
729             Note: if passing a filename, include the mode (i.e. to write to a file named
730             aliases pass >aliases). Before writing, the alias lines are sorted
731             alphabetically.
732              
733             =item B
734             Set the current alias file format.
735              
736             =item B
737             Indicates the presence of the passed alias within the object (if using memory
738             access), or the current aliases file (if using direct file access). For
739             direct file access, the return value is the address string for the alias.
740            
741             =item B
742             Expands the passed alias into a list of addresses. Expansion properly handles
743             :include: files, recursion, and continuation lines.Only works when memory
744             access is being used. If the alias is not found in the object, you get back
745             what you sent.
746              
747             =item B
748             Sets or gets the name of the current alias filename for direct access.
749              
750             =item B
751             Adds an alias to an existing Sendmail alias file. The alias and addresses can
752             be passed as two separate arguments (alias, addresses) or as a single line of
753             text (alias: addresses)
754              
755             =item B
756             Deletes the entry for an alias from the current alias file.
757              
758             =item B
759             Replaces the address string entry for an alias in the current alias file.
760              
761             =item B
762             Sets the working mode to use memory (indirect access). Use read(), write() and
763             format() methods.
764              
765             =item B
766             Sets the working mode to use files (direct access). Use append() and delete()
767             methods.
768              
769              
770             =back
771              
772             =head1 MAINTAINER
773              
774             Jonathan Kamens (CPAN author ID: JIK)
775              
776             =head1 COPYRIGHT
777              
778             Copyright (c) 2022 Jonathan Kamens . All rights reserved. This
779             program is free software; you can redistribute it and/or modify it under the
780             same terms as Perl itself.
781              
782             Portions of earlier versions of this program were copyrighted by Tom Zeltwanger
783             and Graham Barr. The current copyright holder extends full authorship rights to
784             both of the previous authors.
785              
786             =cut
787              
788             1;