File Coverage

blib/lib/Mail/Ezmlm.pm
Criterion Covered Total %
statement 12 439 2.7
branch 0 334 0.0
condition 0 200 0.0
subroutine 4 46 8.7
pod 4 36 11.1
total 20 1055 1.9


line stmt bran cond sub pod time code
1             # ===========================================================================
2             # Ezmlm.pm - version 0.08.2 - 10/15/2008
3             # $Id: Ezmlm.pm 453 2008-10-16 01:22:44Z lars $
4             #
5             # Object methods for ezmlm mailing lists
6             #
7             # Copyright (C) 1999-2005, Guy Antony Halse, All Rights Reserved.
8             # Copyright (C) 2005-2008, Lars Kruse, All Rights Reserved.
9             # Please send bug reports and comments to ezmlm-web@sumpfralle.de.
10             #
11             # Redistribution and use in source and binary forms, with or without
12             # modification, are permitted provided that the following conditions are
13             # met:
14             #
15             # Redistributions of source code must retain the above copyright notice,
16             # this list of conditions and the following disclaimer.
17             #
18             # Redistributions in binary form must reproduce the above copyright notice,
19             # this list of conditions and the following disclaimer in the documentation
20             # and/or other materials provided with the distribution.
21             #
22             # Neither name Guy Antony Halse nor the names of any contributors
23             # may be used to endorse or promote products derived from this software
24             # without specific prior written permission.
25             #
26             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
27             # IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
28             # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29             # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
30             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36             # POSSIBILITY OF SUCH DAMAGE.
37             #
38             # ==========================================================================
39             # POD is at the end of this file. Search for '=head' to find it
40             package Mail::Ezmlm;
41              
42 1     1   1012 use strict;
  1         2  
  1         44  
43 1     1   6 use vars qw($QMAIL_BASE $EZMLM_BASE $MYSQL_BASE $VERSION @ISA @EXPORT @EXPORT_OK);
  1         2  
  1         117  
44 1     1   5 use Carp;
  1         11  
  1         93  
45 1     1   1075 use Text::ParseWords;
  1         2000  
  1         9699  
46              
47             require Exporter;
48              
49             @ISA = qw(Exporter);
50             # Items to export into callers namespace by default. Note: do not export
51             # names by default without a very good reason. Use EXPORT_OK instead.
52             # Do not simply export all your public functions/methods/constants.
53             @EXPORT = qw(
54            
55             );
56             $VERSION = '0.08.2';
57              
58             require 5.005;
59              
60             # == Begin site dependant variables ==
61             $EZMLM_BASE = '/usr/local/bin/ezmlm'; #Autoinserted by Makefile.PL
62             $QMAIL_BASE = '/var/qmail'; #Autoinserted by Makefile.PL
63             $MYSQL_BASE = ''; #Autoinserted by Makefile.PL
64             # == End site dependant variables ==
65              
66             # == check the ezmlm-make path ==
67             $EZMLM_BASE = '/usr/local/bin/ezmlm' unless (-e "$EZMLM_BASE/ezmlm-make");
68             $EZMLM_BASE = '/usr/local/bin/ezmlm-idx' unless (-e "$EZMLM_BASE/ezmlm-make");
69             $EZMLM_BASE = '/usr/local/bin' unless (-e "$EZMLM_BASE/ezmlm-make");
70             $EZMLM_BASE = '/usr/bin/ezmlm' unless (-e "$EZMLM_BASE/ezmlm-make");
71             $EZMLM_BASE = '/usr/bin/ezmlm-idx' unless (-e "$EZMLM_BASE/ezmlm-make");
72             $EZMLM_BASE = '/usr/bin' unless (-e "$EZMLM_BASE/ezmlm-make");
73              
74             # == clean up the path for taint checking ==
75             local $ENV{'PATH'} = $EZMLM_BASE;
76              
77             # == Initialiser - Returns a reference to the object ==
78             sub new {
79 0     0 1   my($class, $list) = @_;
80 0           my $self = {};
81 0   0       bless $self, ref $class || $class || 'Mail::Ezmlm';
82 0 0 0       $self->setlist($list) if(defined($list) && $list);
83 0           return $self;
84             }
85              
86             # == Make a new mailing list and set it to current ==
87             sub make {
88 0     0 1   my($self, %list) = @_;
89 0           my($VHOST, $comandline, $hostname);
90              
91             # Do we want to use command line switches
92 0           my $commandline = '';
93 0 0         $commandline = '-' . $list{'-switches'} if(defined($list{'-switches'}));
94 0           my @commandline;
95 0           foreach ("ewords('\s+', 1, $commandline)) {
96 0 0         next if (!defined($_));
97             # untaint input
98 0           $_ =~ s/['"]//g;
99 0           $_ =~ m/^([\w _\/,\.\@:'"-]*)$/;
100 0 0         if ($_ =~ /^\s*$/) {
101 0           push @commandline, "";
102             } else {
103 0           push @commandline, $1;
104             }
105             }
106              
107             # These three variables are essential
108 0 0 0       ($self->_seterror(-1, 'must define -dir in a make()') && return 0) unless(defined($list{'-dir'}));
109 0 0 0       ($self->_seterror(-1, 'must define -qmail in a make()') && return 0) unless(defined($list{'-qmail'}));
110 0 0 0       ($self->_seterror(-1, 'must define -name in a make()') && return 0) unless(defined($list{'-name'}));
111              
112             # Determine hostname if it is not supplied
113 0           $hostname = $self->_getdefaultdomain;
114 0 0         if(defined($list{'-host'})) {
115 0 0         $VHOST = 1 unless ($list{'-host'} eq $hostname);
116             } else {
117 0           $list{'-host'} = $hostname;
118             }
119              
120             # does the mailing list directory already exist?
121 0 0         if (-e $list{'-dir'}) {
122 0           $self->_seterror(-1,
123             '-the mailing list directory already exists: ' . $list{'-dir'});
124 0           return undef;
125             }
126              
127             # Attempt to make the list if we can.
128 0 0         if (system("$EZMLM_BASE/ezmlm-make", @commandline, $list{'-dir'}, $list{'-qmail'}, $list{'-name'}, $list{'-host'}) != 0) {
129 0           $self->_seterror($?, '-failed to create mailing list - check your webserver\'s log file for details');
130 0           return undef;
131             }
132              
133             # Sort out the DIR/inlocal problem if necessary
134 0 0         if(defined($VHOST)) {
135 0 0         unless(defined($list{'-user'})) {
136 0 0 0       ($self->_seterror(-1, '-user must match virtual host user in make()') && return 0) unless($list{'-user'} = $self->_getvhostuser($list{'-host'}));
137             }
138              
139 0 0 0       open(INLOCAL, ">$list{'-dir'}/inlocal") || ($self->_seterror(-1, 'unable to read inlocal in make()') && return 0);
140 0           print INLOCAL $list{'-user'} . '-' . $list{'-name'} . "\n";
141 0           close INLOCAL;
142             }
143              
144 0           $self->_seterror(undef);
145 0           return $self->setlist($list{'-dir'});
146             }
147              
148             # == Update the current list ==
149             sub update {
150 0     0 0   my($self, $switches) = @_;
151 0           my($outhost, $inlocal);
152              
153             # Do we have the command line switches
154 0 0 0       ($self->_seterror(-1, 'nothing to update()') && return 0) unless(defined($switches));
155 0           $switches = '-e' . $switches;
156 0           my @switch_list;
157              
158 0           foreach ("ewords('\s+', 1, $switches)) {
159 0 0         next if (!defined($_));
160             # untaint input
161 0           $_ =~ s/['"]//g;
162 0           $_ =~ m/^([\w _\/,\.\@:'"-]*)$/;
163 0 0         if ($_ =~ /^\s*$/) {
164 0           push @switch_list, "";
165             } else {
166 0           push @switch_list, $1;
167             }
168             }
169              
170             # can we actually alter this list;
171 0 0 0       ($self->_seterror(-1, 'must setlist() before you update()') && return 0) unless(defined($self->{'LIST_NAME'}));
172             # check for important files: 'config' (idx < v5.0) or 'flags' (idx >= 5.0)
173 0 0 0       ($self->_seterror(-1, "$self->{'LIST_NAME'} does not appear to be a valid list in update()") && return 0) unless((-e "$self->{'LIST_NAME'}/config") || (-e "$self->{'LIST_NAME'}/flags"));
      0        
174              
175             # Work out if this is a vhost.
176 0 0 0       open(OUTHOST, "<$self->{'LIST_NAME'}/outhost") || ($self->_seterror(-1, 'unable to read outhost in update()') && return 0);
177 0           chomp($outhost = );
178 0           close(OUTHOST);
179              
180             # Save the contents of inlocal if it is a vhost
181 0 0         unless($outhost eq $self->_getdefaultdomain) {
182 0 0 0       open(INLOCAL, "<$self->{'LIST_NAME'}/inlocal") || ($self->_seterror(-1, 'unable to read inlocal in update()') && return 0);
183 0           chomp($inlocal = );
184 0           close(INLOCAL);
185             }
186              
187             # Attempt to update the list if we can.
188 0 0 0       system("$EZMLM_BASE/ezmlm-make", @switch_list, $self->{'LIST_NAME'}) == 0
189             || ($self->_seterror($?) && return undef);
190            
191             # Sort out the DIR/inlocal problem if necessary
192 0 0         if(defined($inlocal)) {
193 0 0 0       open(INLOCAL, ">$self->{'LIST_NAME'}/inlocal") || ($self->_seterror(-1, 'unable to write inlocal in update()') && return 0);
194 0           print INLOCAL "$inlocal\n";
195 0           close INLOCAL;
196             }
197              
198 0           $self->_seterror(undef);
199 0           return $self->{'LIST_NAME'};
200             }
201              
202             # == Get a list of options for the current list ==
203             sub getconfig {
204 0     0 0   my($self) = @_;
205 0           my($options);
206              
207             # Read the config file
208 0 0         if(-e $self->{LIST_NAME} . "/flags") {
    0          
209             # this file exists since ezmlm-idx-5.0.0
210             # 'config' is not authorative anymore since that version
211 0           $options = $self->_getconfig_idx5();
212             } elsif(open(CONFIG, "<" . $self->{LIST_NAME} . "/config")) {
213             # 'config' contains the authorative information
214 0           while() {
215 0 0         if (/^F:-(\w+)/) {
    0          
216 0           $options = $1;
217             } elsif (/^(\d):(.+)$/) {
218 0           my $opt_num = $1;
219 0           my $value = $2;
220 0 0         $options .= " -$opt_num '$value'" if ($value =~ /\S/);
221             }
222             }
223 0           close CONFIG;
224             } else {
225             # Try manually - this will ignore all string settings, that can only be found
226             # in the config file
227 0           $options = $self->_getconfigmanual();
228             }
229              
230 0 0 0       ($self->_seterror(-1, 'unable to read configuration in getconfig()') && return undef) unless (defined($options));
231              
232 0           $self->_seterror(undef);
233 0           return $options;
234             }
235              
236             # == Return the name of the current list ==
237             sub thislist {
238 0     0 0   my($self) = shift;
239 0           $self->_seterror(undef);
240 0           return $self->{'LIST_NAME'};
241             }
242              
243             # == Set the current mailing list ==
244             sub setlist {
245 0     0 0   my($self, $list) = @_;
246 0 0         if ($list =~ m/^([\w\d\_\-\.\/\@]+)$/) {
247 0           $list = $1;
248 0 0         if (-e "$list/lock") {
249 0           $self->_seterror(undef);
250 0           return $self->{'LIST_NAME'} = $list;
251             } else {
252 0           $self->_seterror(-1, "$list does not appear to be a valid list in setlist()");
253 0           return undef;
254             }
255             } else {
256 0           $self->_seterror(-1, "$list contains tainted data in setlist()");
257 0           return undef;
258             }
259             }
260              
261             # == Output the subscribers to $stream ==
262             sub list {
263 0     0 1   my($self, $stream, $part) = @_;
264 0 0         $stream = *STDOUT unless (defined($stream));
265 0 0         if(defined($part)) {
266 0           print $stream $self->subscribers($part);
267             } else {
268 0           print $stream $self->subscribers;
269             }
270             }
271              
272             # == Return an array of subscribers ==
273             sub subscribers {
274 0     0 1   my($self, $part) = @_;
275 0           my(@subscribers);
276 0 0 0       ($self->_seterror(-1, 'must setlist() before returning subscribers()') && return undef) unless(defined($self->{'LIST_NAME'}));
277 0 0 0       if(defined($part) && $part) {
278 0 0 0       ($self->_seterror(-1, "$part part of $self->{'LIST_NAME'} does not appear to exist in subscribers()") && return undef) unless(-e "$self->{'LIST_NAME'}/$part");
279 0 0         @subscribers = map { s/[\r\n]// && $_ } sort `$EZMLM_BASE/ezmlm-list $self->{'LIST_NAME'}/$part`;
  0            
280             } else {
281 0 0         @subscribers = map { s/[\r\n]// && $_ } sort `$EZMLM_BASE/ezmlm-list $self->{'LIST_NAME'}`;
  0            
282             }
283              
284 0 0         if($?) {
285 0           $self->_seterror($?, 'error during ezmlm-list in subscribers()');
286 0 0         return (scalar @subscribers ? @subscribers : undef);
287             } else {
288 0           $self->_seterror(undef);
289 0           return @subscribers;
290             }
291             }
292              
293             # == Subscribe users to the current list ==
294             sub sub {
295 0     0 0   my($self, @addresses) = @_;
296 0 0 0       ($self->_seterror(-1, 'sub() must be called with at least one address') && return 0) unless @addresses;
297 0 0 0       my($part) = pop @addresses unless ($#addresses < 1 or $addresses[$#addresses] =~ /\@/);
298 0           my($address);
299 0 0 0       ($self->_seterror(-1, 'must setlist() before sub()') && return 0) unless(defined($self->{'LIST_NAME'}));
300              
301 0 0 0       if(defined($part) && $part) {
302 0 0 0       ($self->_seterror(-1, "$part of $self->{'LIST_NAME'} does not appear to exist in sub()") && return 0) unless(-e "$self->{'LIST_NAME'}/$part");
303 0           foreach $address (@addresses) {
304 0 0         next unless $self->_checkaddress($address);
305 0 0 0       system("$EZMLM_BASE/ezmlm-sub", "$self->{'LIST_NAME'}/$part", $address) == 0 ||
306             ($self->_seterror($?) && return undef);
307             }
308             } else {
309 0           foreach $address (@addresses) {
310 0 0         next unless $self->_checkaddress($address);
311 0 0 0       system("$EZMLM_BASE/ezmlm-sub", $self->{'LIST_NAME'}, $address) == 0 ||
312             ($self->_seterror($?) && return undef);
313             }
314             }
315 0           $self->_seterror(undef);
316 0           return 1;
317             }
318              
319             # == Unsubscribe users from a list ==
320             sub unsub {
321 0     0 0   my($self, @addresses) = @_;
322 0 0 0       ($self->_seterror(-1, 'unsub() must be called with at least one address') && return 0) unless @addresses;
323 0 0 0       my($part) = pop @addresses unless ($#addresses < 1 or $addresses[$#addresses] =~ /\@/);
324 0           my($address);
325 0 0 0       ($self->_seterror(-1, 'must setlist() before unsub()') && return 0) unless(defined($self->{'LIST_NAME'}));
326              
327 0 0 0       if(defined($part) && $part) {
328 0 0 0       ($self->_seterror(-1, "$part of $self->{'LIST_NAME'} does not appear to exist in unsub()") && return 0) unless(-e "$self->{'LIST_NAME'}/$part");
329 0           foreach $address (@addresses) {
330 0 0         next unless $self->_checkaddress($address);
331 0 0 0       system("$EZMLM_BASE/ezmlm-unsub", "$self->{'LIST_NAME'}/$part", $address) == 0 ||
332             ($self->_seterror($?) && return undef);
333             }
334             } else {
335 0           foreach $address (@addresses) {
336 0 0         next unless $self->_checkaddress($address);
337 0 0 0       system("$EZMLM_BASE/ezmlm-unsub", $self->{'LIST_NAME'}, $address) == 0 ||
338             ($self->_seterror($?) && return undef);
339             }
340             }
341 0           $self->_seterror(undef);
342 0           return 1;
343             }
344              
345             # == Test whether people are subscribed to the list ==
346             sub issub {
347 0     0 0   my($self, @addresses) = @_;
348 0 0 0       my($part) = pop @addresses unless ($#addresses < 1 or $addresses[$#addresses] =~ /\@/);
349 0           my($address, $issub); $issub = 1;
  0            
350 0 0 0       ($self->_seterror(-1, 'must setlist() before issub()') && return 0) unless(defined($self->{'LIST_NAME'}));
351              
352 0           local $ENV{'SENDER'};
353              
354 0 0 0       if(defined($part) && $part) {
355 0 0 0       ($self->_seterror(-1, "$part of $self->{'LIST_NAME'} does not appear to exist in issub()") && return 0) unless(-e "$self->{'LIST_NAME'}/$part");
356 0           foreach $address (@addresses) {
357 0           $ENV{'SENDER'} = $address;
358 0 0         undef($issub) if ((system("$EZMLM_BASE/ezmlm-issubn", "$self->{'LIST_NAME'}/$part") / 256) != 0)
359             }
360             } else {
361 0           foreach $address (@addresses) {
362 0           $ENV{'SENDER'} = $address;
363 0 0         undef($issub) if ((system("$EZMLM_BASE/ezmlm-issubn", $self->{'LIST_NAME'}) / 256) != 0)
364             }
365             }
366              
367 0           $self->_seterror(undef);
368 0           return $issub;
369             }
370              
371             # == Is the list posting moderated ==
372             # DEPRECATED: useless - you should better check the appropriate config flag
373             sub ismodpost {
374 0     0 0   my($self) = @_;
375 0 0 0       ($self->_seterror(-1, 'must setlist() before ismodpost()') && return 0) unless(defined($self->{'LIST_NAME'}));
376 0           $self->_seterror(undef);
377 0           return -e "$self->{'LIST_NAME'}/modpost";
378             }
379              
380             # == Is the list subscriber moderated ==
381             # DEPRECATED: useless - you should better check the appropriate config flag
382             sub ismodsub {
383 0     0 0   my($self) = @_;
384 0 0 0       ($self->_seterror(-1, 'must setlist() before ismodsub()') && return 0) unless(defined($self->{'LIST_NAME'}));
385 0           $self->_seterror(undef);
386 0           return -e "$self->{'LIST_NAME'}/modsub";
387             }
388              
389             # == Is the list remote adminable ==
390             # DEPRECATED: useless - you should better check the appropriate config flag
391             sub isremote {
392 0     0 0   my($self) = @_;
393 0 0 0       ($self->_seterror(-1, 'must setlist() before isremote()') && return 0) unless(defined($self->{'LIST_NAME'}));
394 0           $self->_seterror(undef);
395 0           return -e "$self->{'LIST_NAME'}/remote";
396             }
397              
398             # == Does the list have a kill list ==
399             # DEPRECATED: useless - you should better check the appropriate config flag
400             sub isdeny {
401 0     0 0   my($self) = @_;
402 0 0 0       ($self->_seterror(-1, 'must setlist() before isdeny()') && return 0) unless(defined($self->{'LIST_NAME'}));
403 0           $self->_seterror(undef);
404 0           return -e "$self->{'LIST_NAME'}/deny";
405             }
406              
407             # == Does the list have an allow list ==
408             # DEPRECATED: useless - the allow list is always created automatically
409             sub isallow {
410 0     0 0   my($self) = @_;
411 0 0 0       ($self->_seterror(-1, 'must setlist() before isallow()') && return 0) unless(defined($self->{'LIST_NAME'}));
412 0           $self->_seterror(undef);
413 0           return -e "$self->{'LIST_NAME'}/allow";
414             }
415              
416             # == Is this a digested list ==
417             # DEPRECATED: useless - you should better check the appropriate config flag
418             sub isdigest {
419 0     0 0   my($self) = @_;
420 0 0 0       ($self->_seterror(-1, 'must setlist() before isdigest()') && return 0) unless(defined($self->{'LIST_NAME'}));
421 0           $self->_seterror(undef);
422 0           return -e "$self->{'LIST_NAME'}/digest";
423             }
424              
425             # == retrieve file contents ==
426             sub getpart {
427 0     0 0   my($self, $part) = @_;
428 0           my(@contents, $content);
429             # check for the file in the list directory first
430 0           my $filename = $self->{'LIST_NAME'} . "/$part";
431             # check for default file in config directory, if necessary
432             # BEWARE: get_config_dir and get_lang may _not_ cause an eternal loop :)
433 0 0 0       $filename = $self->get_config_dir() . '/' . $self->get_lang() . "/$part"
      0        
      0        
434             if (!(-e "$filename") && (get_version() >= 5) &&
435             ($part ne 'conf-etc') && ($part ne 'conf-lang'));
436 0 0         if (open(PART, "<$filename")) {
437 0           while() {
438 0 0         unless ( /^#/ ) {
439 0           chomp($contents[$#contents++] = $_);
440 0           $content .= $_;
441             }
442             }
443 0           close PART;
444 0 0         if(wantarray) {
445 0           return @contents;
446             } else {
447 0           return $content;
448             }
449 0 0         } ($self->_seterror($?) && return undef);
450             }
451              
452             # == set files contents ==
453             sub setpart {
454 0     0 0   my($self, $part, @content) = @_;
455 0           my($line);
456 0 0         if(open(PART, ">$self->{'LIST_NAME'}/$part")) {
457 0           foreach $line (@content) {
458 0           $line =~ s/[\r]//g; $line =~ s/\n$//;
  0            
459 0           print PART "$line\n";
460             }
461 0           close PART;
462 0           return 1;
463 0 0         } ($self->_seterror($?) && return undef);
464             }
465              
466             # == get the configuration directory for this list (idx >= 5.0) ==
467             # return '/etc/ezmlm' for idx < 5.0
468             sub get_config_dir {
469 0     0 0   my $self = shift;
470 0           my $conf_dir;
471 0 0 0       if ((get_version() >= 5) && (ref $self) && (-e "$self->{'LIST_NAME'}/conf-etc")) {
      0        
472 0           chomp($conf_dir = $self->getpart('conf-etc'));
473             } else {
474 0           $conf_dir = '/etc/ezmlm';
475             }
476 0           return $conf_dir;
477             }
478              
479             # == set the configuration directory for this list (idx >= 5.0) ==
480             # return without error for idx < 5.0
481             sub set_config_dir {
482 0     0 0   my ($self, $conf_dir) = @_;
483 0 0         return (0==0) if (get_version() < 5);
484 0           $self->setpart('conf-etc', "$conf_dir");
485             }
486              
487              
488             # == get list of available languages (for idx >= 5.0) ==
489             # return empty list for idx < 5.0
490             sub get_available_languages {
491 0     0 0   my $self = shift;
492 0           my @langs = ();
493 0 0         return @langs if (get_version() < 5);
494              
495 0 0         $self->_seterror(undef) if (ref $self);
496              
497             # check for language directories
498 0           my $conf_dir;
499 0 0         if (ref $self) {
500 0 0 0       ($self->_seterror(-1, 'could not retrieve configuration directory') && return 0)
501             unless ($conf_dir = $self->get_config_dir());
502             } else {
503 0           $conf_dir = get_config_dir();
504             }
505 0 0         if (opendir DIR, "$conf_dir") {
506 0           my @dirs;
507 0           @dirs = grep !/^\./, readdir DIR;
508 0           closedir DIR;
509 0           my $item;
510 0           foreach $item (@dirs) {
511 0 0         push (@langs, $item) if (-e "$conf_dir/$item/text");
512             }
513 0           return @langs;
514             } else {
515 0 0         $self->_seterror(-1, 'could not access configuration directory') if (ref $self);
516 0           return undef;
517             }
518             }
519              
520              
521             # == get the selected language of the list (idx >= 5.0) ==
522             # return empty string for idx < 5.0
523             sub get_lang {
524 0     0 0   my ($self) = shift;
525 0           my $lang;
526 0 0         return '' if (get_version() < 5);
527 0 0         if (-e "$self->{'LIST_NAME'}/conf-lang") {
528 0           chomp($lang = $self->getpart('conf-lang'));
529             } else {
530 0           $lang = 'default';
531             }
532 0           return $lang;
533             }
534              
535              
536             # == set the selected language of the list (idx >= 5.0) ==
537             # return without error for idx < 5.0
538             sub set_lang {
539 0     0 0   my ($self, $lang) = @_;
540 0 0         return (0==0) if (get_version() < 5);
541 0 0 0       if (($lang eq 'default') || ($lang eq '')) {
542 0 0         return 1 if (unlink "$self->{'LIST_NAME'}/conf-lang");
543             } else {
544 0 0         return 1 if ($self->setpart('conf-lang', "$lang"));
545             }
546 0           return 0;
547             }
548              
549              
550             # == get the selected charset of the list ==
551             # return default value (us-ascii) if no charset is specified
552             sub get_charset {
553 0     0 0   my ($self) = shift;
554 0           my $charset;
555 0           $charset = $self->getpart('charset');
556 0 0         $charset = '' unless defined($charset);
557             # default if no 'charset' file exists
558 0 0         $charset = 'us-ascii' if ($charset eq '');
559 0           return $charset;
560             }
561              
562              
563             # == set the selected charset of the list (idx >= 5.0) ==
564             # remove list' specific charset file, if the default charset of the current language
565             # was chosen
566             sub set_charset {
567 0     0 0   my ($self, $charset) = @_;
568             # first: remove current charset
569 0           unlink "$self->{'LIST_NAME'}/charset";
570             # second: get default value of the current language
571 0           my $default_charset = $self->getpart('charset');
572             # last: create new charset file only if the selected charset is not the default anyway
573 0 0 0       if (($charset eq $default_charset) || ($charset !~ /\S/)) {
574             # do not write the specific charset, as the default charset of the language is
575             # sufficient
576 0           return 1;
577             } else {
578 0 0         return 1 if ($self->setpart('charset', "$charset"));
579             }
580 0           return 0;
581             }
582              
583              
584             # == get list of available text files ==
585             sub get_available_text_files {
586 0     0 0   my ($self) = shift;
587 0           my @files;
588             my $item;
589 0           my %seen = ();
590            
591             # customized text files of this list (idx >= 5.0)
592             # OR text files of this list (idx < 5.0)
593 0 0         if (opendir DIR, "$self->{'LIST_NAME'}/text") {
594 0           my @local_files = grep !/^\./, readdir DIR;
595 0           closedir DIR;
596 0           foreach $item (@local_files) {
597 0 0         unless ($seen{$item}) {
598 0           push (@files, $item);
599 0           $seen{$item} = 1;
600             }
601             }
602             }
603              
604             # default text files (only idx >= 5.0)
605 0 0         if (get_version() >= 5) {
606 0           my $dirname = $self->get_config_dir . '/' . $self->get_lang() . '/text';
607 0 0         $dirname = $self->get_config_dir . '/default/text' unless (-e $dirname);
608 0 0         if (opendir GLOBDIR, $dirname) {
609 0           my @global_files = grep !/^\./, readdir GLOBDIR;
610 0           closedir GLOBDIR;
611 0           foreach $item (@global_files) {
612 0 0         unless ($seen{$item}) {
613 0           push (@files, $item);
614 0           $seen{$item} = 1;
615             }
616             }
617             }
618             }
619              
620 0 0         if ($#files > 0) {
621 0           return @files;
622             } else {
623 0           $self->_seterror(-1, 'no textfiles found');
624 0           return undef;
625             }
626             }
627              
628             # == get text file content ==
629             sub get_text_content {
630 0     0 0   my ($self, $textfile) = @_;
631              
632 0 0         if (-e "$self->{'LIST_NAME'}/text/$textfile") {
    0          
633 0           return $self->getpart("text/$textfile");
634             } elsif (get_version() >= 5) {
635 0           my $filename = $self->get_config_dir() . '/' . $self->get_lang() . "/text/$textfile";
636 0 0         $filename = "/etc/ezmlm/default/$textfile" unless (-e "$filename");
637 0           my @contents;
638             my $content;
639 0 0         if (open(PART, "<$filename")) {
640 0           while() {
641 0           chomp($contents[$#contents++] = $_);
642 0           $content .= $_;
643             }
644 0           close PART;
645 0 0         if(wantarray) {
646 0           return @contents;
647             } else {
648 0           return $content;
649             }
650             } else {
651 0           $self->_seterror($?, "could not open $filename");
652 0           return undef;
653             }
654             } else {
655 0           $self->_seterror(-1, "could not get the text file ($textfile)");
656 0           return undef;
657             }
658             }
659              
660              
661             # == set text file content ==
662             sub set_text_content {
663 0     0 0   my ($self, $textfile, @content) = @_;
664 0 0         mkdir "$self->{'LIST_NAME'}/text" unless (-e "$self->{'LIST_NAME'}/text");
665 0 0         return 1 if ($self->setpart("text/$textfile", @content));
666 0           return 0;
667             }
668              
669              
670             # == check if specified text file is customized or default (for idx >= 5.0) ==
671             # return whether the text file exists in the list's directory (false) or not (true)
672             # empty filename returns false
673             sub is_text_default {
674 0     0 0   my ($self, $textfile) = @_;
675 0 0         return (0==1) if ($textfile eq '');
676 0 0         if (-e "$self->{'LIST_NAME'}/text/$textfile") {
677 0           return (1==0);
678             } else {
679 0           return (0==0);
680             }
681             }
682              
683              
684             # == remove non-default text file (for idx >= 5.0) ==
685             # return without error for idx < 5
686             # otherwise: remove customized text file from the list's directory
687             sub reset_text {
688 0     0 0   my ($self, $textfile) = @_;
689 0 0         return if (get_version() < 5);
690 0 0         return if ($textfile eq '');
691 0 0         return if ($textfile =~ /[^\w_\.-]/);
692 0 0         return if ($self->is_text_default($textfile));
693 0 0 0       ($self->_seterror(-1, "could not remove customized text file ($textfile)") && return 0)
694             unless unlink("$self->{'LIST_NAME'}/text/$textfile");
695 0           return 1;
696             }
697              
698              
699             # == return an error message if appropriate ==
700             sub errmsg {
701 0     0 0   my($self) = @_;
702 0           return $self->{'ERRMSG'};
703             }
704              
705             sub errno {
706 0     0 0   my($self) = @_;
707 0           return $self->{'ERRNO'};
708             }
709              
710             # == Test the compatiblity of the module ==
711             # return 0 for a valid version
712             # return the version string for an invalid version
713             sub check_version {
714 0     0 0   my $self = shift;
715 0           my $version = `$EZMLM_BASE/ezmlm-make -V 2>&1`;
716 0 0         $self->_seterror(undef) if (ref $self);
717              
718             # ezmlm-idx is necessary
719 0 0         if (get_version() >= 4) {
720 0           return 0;
721             } else {
722 0           return $version;
723             }
724             }
725              
726             # == get the major ezmlm version ==
727             # return values:
728             # 0 => unknown version
729             # 3 => ezmlm v0.53
730             # 4 => ezmlm-idx v0.4*
731             # 5 => ezmlm-idx v5.0
732             # 5.1 => ezmlm-idx v5.1
733             # 6 => ezmlm-idx v6.*
734             # 7 => ezmlm-idx v7.*
735             sub get_version {
736 0     0 0   my ($ezmlm, $idx);
737 0           my $version = `$EZMLM_BASE/ezmlm-make -V 2>&1`;
738              
739 0 0         $version = $1 if ($version =~ m/^[^:]*:\s+(.*)$/);
740 0 0         $ezmlm = $1 if ($version =~ m/ezmlm-([\d\.]+)$/);
741 0 0         $idx = $1 if ($version =~ m/ezmlm-idx-([\d\.]+)$/);
742              
743 0 0         if (defined($ezmlm)) {
    0          
744 0           return 3;
745             } elsif (defined($idx)) {
746 0 0 0       if (($idx =~ m/^(\d)/) && ($1 >= 7)) {
    0 0        
    0 0        
    0 0        
    0 0        
      0        
747             # version 6.0 or higher
748 0           return 7;
749             } elsif (($idx =~ m/^(\d)/) && ($1 == 6)){
750 0           return 6;
751             } elsif (($idx =~ m/^(\d)\.(\d)/) && ($1 >= 5) && ($2 == 1)) {
752             # version 5.1
753 0           return 5.1;
754             } elsif (($idx =~ m/^(\d)/) && ($1 >= 5)) {
755             # version 5.0
756 0           return 5;
757             } elsif (($idx =~ m/^0\.(\d)/) && ($1 >= 0)) {
758             # version 0.4xx
759 0           return 4;
760             } else {
761 0           return 0;
762             }
763             } else {
764 0           return 0;
765             }
766             }
767              
768             # == Create SQL Database tables if defined for a list ==
769             sub createsql {
770 0     0 0   my($self) = @_;
771              
772 0 0 0       ($self->_seterror(-1, 'MySQL must be compiled into Ezmlm for createsql() to work') && return 0) unless(defined($MYSQL_BASE) && $MYSQL_BASE);
      0        
773 0 0 0       ($self->_seterror(-1, 'must setlist() before isdigest()') && return 0) unless(defined($self->{'LIST_NAME'}));
774 0           my($config) = $self->getconfig();
775              
776 0 0         if($config =~ m/-6\s+'(.+?)'\s*/){
777 0           my($sqlsettings) = $1;
778 0           my($host, $port, $user, $password, $database, $table) = split(':', $sqlsettings, 6);
779              
780 0 0 0       ($self->_seterror(-1, 'error in list configuration while trying createsql()') && return 0)
      0        
      0        
      0        
      0        
      0        
781             unless (defined($host) && defined($port) && defined($user)
782             && defined($password) && defined($database) && defined($table));
783              
784 0 0 0       system("$EZMLM_BASE/ezmlm-mktab -d $table | $MYSQL_BASE/mysql -h$host -P$port -u$user -p$password -f $database") == 0 ||
785             ($self->_seterror($?) && return undef);
786              
787             } else {
788 0           $self->_seterror(-1, 'config for thislist() must include SQL options');
789 0           return 0;
790             }
791              
792 0 0         ($self->_seterror(undef) && return 1);
793              
794             }
795              
796              
797             # == Internal function to set the error to return ==
798             sub _seterror {
799 0     0     my($self, $no, $mesg) = @_;
800              
801 0 0 0       if(defined($no) && $no) {
802 0 0         if($no < 0) {
803 0           $self->{'ERRNO'} = -1;
804 0   0       $self->{'ERRMSG'} = $mesg || 'An undefined error occoured';
805             } else {
806 0           $self->{'ERRNO'} = $no / 256;
807 0   0       $self->{'ERRMSG'} = $! || $mesg || 'An undefined error occoured in a system() call';
808             }
809             } else {
810 0           $self->{'ERRNO'} = 0;
811 0           $self->{'ERRMSG'} = undef;
812             }
813 0           return 1;
814             }
815              
816             # == Internal function to test for valid email addresses ==
817             sub _checkaddress {
818 0     0     my($self, $address) = @_;
819 0 0         return 1 unless defined($address);
820 0 0         return 0 unless ($address =~ m/^(\S+\@\S+\.\S+)$/);
821 0           $_[1] = $1;
822 0           return 1;
823             }
824              
825             # == Internal function to work out a list configuration (idx >= v5.0) ==
826             sub _getconfig_idx5 {
827 0     0     my($self) = @_;
828 0           my ($options, %optionfiles);
829 0           my ($file, $opt_num, $temp);
830              
831             # read flag file (available since ezmlm-idx 5.0.0)
832 0           chomp($options = $self->getpart('flags'));
833             # remove prefixed '-'
834 0           $options =~ s/^-//;
835              
836             # since ezmlm-idx v5, we have to read the config
837             # values from different files
838             # first: preset a array with "filename" and "option_number"
839 0           %optionfiles = (
840             'sublist', 0,
841             'fromheader', 3,
842             'tstdigopts', 4,
843             'owner', 5,
844             'sql', 6,
845             'modpost', 7,
846             'modsub', 8,
847             'remote', 9);
848 0           while (($file, $opt_num) = each(%optionfiles)) {
849 0 0         if (-e "$self->{'LIST_NAME'}/$file") {
850 0           chomp($temp = $self->getpart($file));
851 0           $temp =~ m/^(.*)$/m; # take only the first line
852 0           $temp = $1;
853             # the 'owner' setting can be ignored if it is a path (starts with '/')
854 0 0 0       unless (($opt_num == 5) && ($temp =~ m#^/#)) {
855 0 0         $options .= " -$opt_num '$temp'" if ($temp =~ /\S/);
856             }
857             }
858             }
859              
860 0           return $options;
861             }
862              
863             # == Internal function to work out a list configuration manually (idx < v5.0.0 ) ==
864             sub _getconfigmanual {
865             # use this function for strange lists without
866             # 'config' (idx < v5.0) and 'flags' (idx >= v5.0)
867 0     0     my($self) = @_;
868 0           my ($savedollarslash, $options, $manager, $editor, $i);
869              
870             # Read the whole of DIR/editor and DIR/manager in
871 0           $savedollarslash = $/;
872 0           undef $/;
873             # $/ = \0777;
874              
875 0 0 0       open (EDITOR, "<$self->{'LIST_NAME'}/editor") || ($self->_seterror($?) && return undef);
876 0 0 0       open (MANAGER, "<$self->{'LIST_NAME'}/manager") || ($self->_seterror($?) && return undef);
877 0           $editor = ; $manager = ;
  0            
878 0           close(EDITOR), close(MANAGER);
879              
880 0           $/ = $savedollarslash;
881            
882 0           $options = '';
883 0 0         $options .= 'a' if (-e "$self->{'LIST_NAME'}/archived");
884 0 0         $options .= 'd' if (-e "$self->{'LIST_NAME'}/digest");
885 0 0         $options .= 'f' if (-e "$self->{'LIST_NAME'}/prefix");
886 0 0         $options .= 'g' if ($manager =~ /ezmlm-get -\w*s/ );
887 0 0         $options .= 'i' if (-e "$self->{'LIST_NAME'}/indexed");
888 0 0 0       $options .= 'k' if (-e "$self->{'LIST_NAME'}/blacklist" || -e "$self->{'LIST_NAME'}/deny");
889 0 0         $options .= 'l' if ($manager =~ /ezmlm-manage -\w*l/ );
890 0 0         $options .= 'm' if (-e "$self->{'LIST_NAME'}/modpost");
891 0 0         $options .= 'n' if ($manager =~ /ezmlm-manage -\w*e/ );
892 0 0         $options .= 'p' if (-e "$self->{'LIST_NAME'}/public");
893 0 0         $options .= 'q' if ($manager =~ /ezmlm-request/ );
894 0 0         $options .= 'r' if (-e "$self->{'LIST_NAME'}/remote");
895 0 0         $options .= 's' if (-e "$self->{'LIST_NAME'}/modsub");
896 0 0         $options .= 't' if (-e "$self->{'LIST_NAME'}/text/trailer");
897 0 0 0       $options .= 'u' if (($options !~ /m/ && $editor =~ /ezmlm-issubn \'/ )
      0        
898             || $editor =~ /ezmlm-gate/ );
899 0 0 0       $options .= 'x' if (-e "$self->{'LIST_NAME'}/extra" || -e "$self->{'LIST_NAME'}/allow");
900              
901             # Add the unselected options too
902             # but we will skip invalid options (any of 'cevz')
903 0           foreach $i ('a' .. 'z') {
904 0 0 0       $options .= uc($i) unless (('cevz' =~ /$i/) || ($options =~ /$i/i))
905             }
906            
907             # there is no way to get the other string settings, that are only
908             # defined in 'config' - sorry ...
909            
910 0           return $options;
911             }
912              
913             # == Internal Function to try to determine the vhost user ==
914             sub _getvhostuser {
915 0     0     my($self, $hostname) = @_;
916 0           my($username);
917              
918 0 0 0       open(VD, "<$QMAIL_BASE/control/virtualdomains") || ($self->_seterror($?) && return undef);
919 0           while() {
920 0 0         last if(($username) = /^\s*$hostname:(\w+)$/);
921             }
922 0           close VD;
923              
924 0           return $username;
925             }
926              
927             # == Internal function to work out default host name ==
928             sub _getdefaultdomain {
929 0     0     my($self) = @_;
930 0           my($hostname);
931              
932 0 0 0       open (GETHOST, "<$QMAIL_BASE/control/defaultdomain")
      0        
933             || open (GETHOST, "<$QMAIL_BASE/control/me")
934             || ($self->_seterror($?) && return undef);
935 0           chomp($hostname = );
936 0           close GETHOST;
937              
938 0           return $hostname;
939             }
940              
941             1;
942             __END__