This patch expands the support for $include references added by Matt to the
plugin configuration to cover all config files, and adds circular-reference
checks as a precaution.

I kept the syntax but loosened the parsing a little (any amount and type of
whitespace after the '$include' is okay).

The rationale, besides uniformity, is that when packing qpsmtpd for a
distribution, it's helpful to be able to have one portion of a given
configuration auto-generated or updated by new package versions, without
requiring the admin to hand-merge against the new version of the file on every
update.

Diffed from Qpsmtpd.pm from 0.30rc2 with a couple of other small changes, so
line numbers will probably fuzz against trunk.

-- 
Devin  \ aqua(at)devin.com, IRC:Requiem; http://www.devin.com
Carraway \ 1024D/E9ABFCD2: 13E7 199E DD1E 65F0 8905 2E43 5395 CA0D E9AB FCD2
diff -aruN qpsmtpd-0.30rc2.configdir/lib/Qpsmtpd.pm 
qpsmtpd-0.30rc2.configincludes/lib/Qpsmtpd.pm
--- qpsmtpd-0.30rc2.configdir/lib/Qpsmtpd.pm    2005-07-06 01:02:43.000000000 
-0700
+++ qpsmtpd-0.30rc2.configincludes/lib/Qpsmtpd.pm       2005-07-09 
17:21:32.000000000 -0700
@@ -170,15 +170,72 @@
 }
 
 sub _config_from_file {
-  my ($self, $configfile, $config) = @_;
+  my ($self, $configfile, $config, $visited) = @_;
   return unless -e $configfile;
+
+  $visited ||= [];
+  push @{$visited}, $configfile;
+
   open CF, "<$configfile" or warn "$$ could not open configfile $configfile: 
$!" and return;
   my @config = <CF>;
   chomp @config;
   @config = grep { length($_) and $_ !~ m/^\s*#/ and $_ =~ m/\S/} @config;
   close CF;
-  #$self->log(10, "returning get_config for $config 
",Data::Dumper->Dump([EMAIL PROTECTED], [qw(config)]));
+
+  my $pos = 0;
+  while ($pos < @config) {
+    # recursively pursue an $include reference, if found.  An inclusion which
+    # begins with a leading slash is interpreted as a path to a file and will
+    # supercede the usual config path resolution.  Otherwise, the normal
+    # config_dir() lookup is employed (the location in which the inclusion
+    # appeared receives no special precedence; possibly it should, but it'd
+    # be complicated beyond justifiability for so simple a config system.
+    if ($config[$pos] =~ /^\s*\$include\s+(\S+)\s*$/) {
+      splice @config, $pos, 1; # remove the $include line
+      my $includedir = '';
+      my $inclusion = $1;
+      if ($inclusion !~ /^\//) {
+        $includedir = $self->config_dir($inclusion);
+        $inclusion = "$includedir/$inclusion";
+      }
+      if (grep($_ eq $inclusion, @{$visited})) {
+        $self->log(LOGERROR, "Circular \$include reference in config 
$config:");
+        $self->log(LOGERROR, "From $visited->[0]:");
+        $self->log(LOGERROR, "  includes $_")
+        for (@{$visited}[1..$#{$visited}], $inclusion);
+          return wantarray ? () : undef;
+      }
+
+      my @includes;
+      if (-d $inclusion) {
+        # $include references a directory
+        $self->log(LOGDEBUG, "inclusion of directory $inclusion from 
$configfile");
+        push @{$visited}, $inclusion;
+       if (opendir(INCD, $inclusion)) {
+          @includes = map { "$inclusion/$_" }
+            (grep { -f "$inclusion/$_" and !/^\./ } readdir INCD);
+          closedir INCD;
+        } else {
+          $self->log(LOGERROR, "Couldn't open directory $inclusion,".
+                               " referenced from $configfile ($!)");
+        }
+      } else {
+        $self->log(LOGDEBUG, "inclusion of file $inclusion from $configfile");
+        @includes = ( $inclusion );
+      }
+
+      for my $inc (@includes) {
+        my @insertion = $self->_config_from_file($inc, $config, $visited);
+        splice @config, $pos, 0, @insertion;
+        $pos += @insertion;
+      }
+    } else {
+      $pos++;
+    }
+  }
+
   $self->{_config_cache}->{$config} = [EMAIL PROTECTED];
+
   return wantarray ? @config : $config[0];
 }
 
@@ -207,28 +264,6 @@
   for my $plugin_line (@plugins) {
     my ($plugin, @args) = split ' ', $plugin_line;
     
-    if (lc($plugin) eq '$include') {
-      my $inc = shift @args;
-      my $config_dir = $self->config_dir($inc);
-      if (-d "$config_dir/$inc") {
-        $self->log(LOGDEBUG, "Loading include dir: $config_dir/$inc");
-        opendir(DIR, "$config_dir/$inc") || die "opendir($config_dir/$inc): 
$!";
-        my @plugconf = sort grep { -f $_ } map { "$config_dir/$inc/$_" } grep 
{ !/^\./ } readdir(DIR);
-        closedir(DIR);
-        foreach my $f (@plugconf) {
-            push @ret, $self->_load_plugins($dir, $self->_config_from_file($f, 
"plugins"));
-        }
-      }
-      elsif (-f "$config_dir/$inc") {
-        $self->log(LOGDEBUG, "Loading include file: $config_dir/$inc");
-        push @ret, $self->_load_plugins($dir, 
$self->_config_from_file("$config_dir/$inc", "plugins"));
-      }
-      else {
-        $self->log(LOGCRIT, "CRITICAL PLUGIN CONFIG ERROR: Include 
$config_dir/$inc not found");
-      }
-      next;
-    }
-
     my $plugin_name = $plugin;
     $plugin =~ s/:\d+$//;       # after this point, only used for filename
 

Reply via email to