<commit_before>package Qpsmtpd;
use strict;
use vars qw($VERSION $TraceLevel $Spool_dir $Size_threshold);

use Sys::Hostname;
use Qpsmtpd::Constants;

#use DashProfiler;

$VERSION = "0.83";

my $git;

if (-e ".git") {
    local $ENV{PATH} = "/usr/bin:/usr/local/bin:/opt/local/bin/";
    $git = `git describe`;
    $git && chomp $git;
}

my $hooks = {};
my %defaults = (
		  me      => hostname,
		  timeout => 1200,
		  );
my $_config_cache = {};
my %config_dir_memo;

#DashProfiler->add_profile("qpsmtpd");
#my $SAMPLER = DashProfiler->prepare("qpsmtpd");
my $LOGGING_LOADED = 0;

sub _restart {
  my $self = shift;
  my %args = @_;
  if ($args{restart}) {
    # reset all global vars to defaults
    $self->clear_config_cache;
    $hooks           = {};
    $LOGGING_LOADED  = 0;
    %config_dir_memo = ();
    $TraceLevel      = LOGWARN;
    $Spool_dir       = undef;
    $Size_threshold  = undef;
  }
}


sub DESTROY {
    #warn $_ for DashProfiler->profile_as_text("qpsmtpd");
}

sub version { $VERSION . ($git ? "/$git" : "") };

sub TRACE_LEVEL { $TraceLevel }; # leave for plugin compatibility


sub hooks { $hooks; }

sub load_logging {
  # need to do this differently that other plugins so as to
  # not trigger logging activity
  return if $LOGGING_LOADED;
  my $self = shift;
  return if $hooks->{"logging"};
  my $configdir = $self->config_dir("logging");
  my $configfile = "$configdir/logging";
  my @loggers = $self->_config_from_file($configfile,'logging');

  $configdir = $self->config_dir('plugin_dirs');
  $configfile = "$configdir/plugin_dirs";
  my @plugin_dirs = $self->_config_from_file($configfile,'plugin_dirs');
  unless (@plugin_dirs) {
    my ($name) = ($0 =~ m!(.*?)/([^/]+)$!);
    @plugin_dirs = ( "$name/plugins" );
  }

  my @loaded;
  for my $logger (@loggers) {
    push @loaded, $self->_load_plugin($logger, @plugin_dirs);
  }

  foreach my $logger (@loaded) {
    $self->log(LOGINFO, "Loaded $logger");
  }

  $configdir = $self->config_dir("loglevel");
  $configfile = "$configdir/loglevel";
  $TraceLevel = $self->_config_from_file($configfile,'loglevel');

  unless (defined($TraceLevel) and $TraceLevel =~ /^\d+$/) {
    $TraceLevel = LOGWARN; # Default if no loglevel file found.
  }

  $LOGGING_LOADED = 1;

  return @loggers;
}

sub trace_level {
  my $self = shift;
  return $TraceLevel;
}

sub init_logger { # needed for compatibility purposes
  shift->trace_level();
}

sub log {
  my ($self, $trace, @log) = @_;
  $self->varlog($trace,join(" ",@log));
}

sub varlog {
  my ($self, $trace) = (shift,shift);
  my ($hook, $plugin, @log);
  if ( $#_ == 0 ) { # log itself
    (@log) = @_;
  }
  elsif ( $#_ == 1 ) { # plus the hook
    ($hook, @log) = @_;
  }
  else { # called from plugin
    ($hook, $plugin, @log) = @_;
  }

  $self->load_logging; # in case we already don't have this loaded yet

  my ($rc) = $self->run_hooks_no_respond("logging", $trace, $hook, $plugin, @log);

  unless ( $rc and $rc == DECLINED or $rc == OK ) {
    # no logging plugins registered so fall back to STDERR
    warn join(" ", $$ .
      (defined $plugin ? " $plugin plugin ($hook):" :
       defined $hook   ? " running plugin ($hook):"  : ""),
      @log), "\n"
    if $trace <= $TraceLevel;
  }
}

sub clear_config_cache {
    $_config_cache = {};
}

#
# method to get the configuration.  It just calls get_qmail_config by
# default, but it could be overwritten to look configuration up in a
# database or whatever.
#
sub config {
  my ($self, $c, $type) = @_;

  #my $timer = $SAMPLER->("config", undef, 1);
  if ($_config_cache->{$c}) {
      return wantarray ? @{$_config_cache->{$c}} : $_config_cache->{$c}->[0];
  }

  $_config_cache->{$c} = [$defaults{$c}] if exists($defaults{$c});

  #warn "SELF->config($c) ", ref $self;

  my ($rc, @config) = $self->run_hooks_no_respond("config", $c);
  @config = () unless $rc == OK;

  if (wantarray) {
      @config = $self->get_qmail_config($c, $type) unless @config;
      @config = $defaults{$c} if (!@config and $defaults{$c});
      return @config;
  }
  else {
      return $config[0] if defined($config[0]);
      my $val = $self->get_qmail_config($c, $type);
      return $val if defined($val);
      return $defaults{$c};
  }
}

sub config_dir {
  my ($self, $config) = @_;
  if (exists $config_dir_memo{$config}) {
      return $config_dir_memo{$config};
  }
  my $configdir = ($ENV{QMAIL} || '/var/qmail') . '/control';
  my ($path) = ($ENV{PROCESS} ? $ENV{PROCESS} : $0) =~ m!(.*?)/([^/]+)$!;
  $configdir = "$path/config" if (-e "$path/config/$config");
  if (exists $ENV{QPSMTPD_CONFIG}) {
    $ENV{QPSMTPD_CONFIG} =~ /^(.*)$/; # detaint
    $configdir = $1 if -e "$1/$config";
  }<commit_msg>fixed issue #29: config now caches returned value<commit_after>package Qpsmtpd;
use strict;
use vars qw($VERSION $TraceLevel $Spool_dir $Size_threshold);

use Sys::Hostname;
use Qpsmtpd::Constants;

#use DashProfiler;

$VERSION = "0.83";

my $git;

if (-e ".git") {
    local $ENV{PATH} = "/usr/bin:/usr/local/bin:/opt/local/bin/";
    $git = `git describe`;
    $git && chomp $git;
}

my $hooks = {};
my %defaults = (
		  me      => hostname,
		  timeout => 1200,
		  );
my $_config_cache = {};
my %config_dir_memo;

#DashProfiler->add_profile("qpsmtpd");
#my $SAMPLER = DashProfiler->prepare("qpsmtpd");
my $LOGGING_LOADED = 0;

sub _restart {
  my $self = shift;
  my %args = @_;
  if ($args{restart}) {
    # reset all global vars to defaults
    $self->clear_config_cache;
    $hooks           = {};
    $LOGGING_LOADED  = 0;
    %config_dir_memo = ();
    $TraceLevel      = LOGWARN;
    $Spool_dir       = undef;
    $Size_threshold  = undef;
  }
}


sub DESTROY {
    #warn $_ for DashProfiler->profile_as_text("qpsmtpd");
}

sub version { $VERSION . ($git ? "/$git" : "") };

sub TRACE_LEVEL { $TraceLevel }; # leave for plugin compatibility


sub hooks { $hooks; }

sub load_logging {
  # need to do this differently that other plugins so as to
  # not trigger logging activity
  return if $LOGGING_LOADED;
  my $self = shift;
  return if $hooks->{"logging"};
  my $configdir = $self->config_dir("logging");
  my $configfile = "$configdir/logging";
  my @loggers = $self->_config_from_file($configfile,'logging');

  $configdir = $self->config_dir('plugin_dirs');
  $configfile = "$configdir/plugin_dirs";
  my @plugin_dirs = $self->_config_from_file($configfile,'plugin_dirs');
  unless (@plugin_dirs) {
    my ($name) = ($0 =~ m!(.*?)/([^/]+)$!);
    @plugin_dirs = ( "$name/plugins" );
  }

  my @loaded;
  for my $logger (@loggers) {
    push @loaded, $self->_load_plugin($logger, @plugin_dirs);
  }

  foreach my $logger (@loaded) {
    $self->log(LOGINFO, "Loaded $logger");
  }

  $configdir = $self->config_dir("loglevel");
  $configfile = "$configdir/loglevel";
  $TraceLevel = $self->_config_from_file($configfile,'loglevel');

  unless (defined($TraceLevel) and $TraceLevel =~ /^\d+$/) {
    $TraceLevel = LOGWARN; # Default if no loglevel file found.
  }

  $LOGGING_LOADED = 1;

  return @loggers;
}

sub trace_level {
  my $self = shift;
  return $TraceLevel;
}

sub init_logger { # needed for compatibility purposes
  shift->trace_level();
}

sub log {
  my ($self, $trace, @log) = @_;
  $self->varlog($trace,join(" ",@log));
}

sub varlog {
  my ($self, $trace) = (shift,shift);
  my ($hook, $plugin, @log);
  if ( $#_ == 0 ) { # log itself
    (@log) = @_;
  }
  elsif ( $#_ == 1 ) { # plus the hook
    ($hook, @log) = @_;
  }
  else { # called from plugin
    ($hook, $plugin, @log) = @_;
  }

  $self->load_logging; # in case we already don't have this loaded yet

  my ($rc) = $self->run_hooks_no_respond("logging", $trace, $hook, $plugin, @log);

  unless ( $rc and $rc == DECLINED or $rc == OK ) {
    # no logging plugins registered so fall back to STDERR
    warn join(" ", $$ .
      (defined $plugin ? " $plugin plugin ($hook):" :
       defined $hook   ? " running plugin ($hook):"  : ""),
      @log), "\n"
    if $trace <= $TraceLevel;
  }
}

sub clear_config_cache {
    $_config_cache = {};
}

#
# method to get the configuration.  It just calls get_qmail_config by
# default, but it could be overwritten to look configuration up in a
# database or whatever.
#
sub config {
  my ($self, $c, $type) = @_;

  $self->log(LOGDEBUG, "in config($c)");

  # first try the cache 
  # XXX - is this always the right thing to do? what if a config hook
  # can return different values on subsequent calls?
  if ($_config_cache->{$c}) {
      $self->log(LOGDEBUG, "config($c) returning (@{$_config_cache->{$c}}) from cache");
      return wantarray ? @{$_config_cache->{$c}} : $_config_cache->{$c}->[0];
  }

  # then run the hooks
  my ($rc, @config) = $self->run_hooks_no_respond("config", $c);
  $self->log(LOGDEBUG, "config($c): hook returned ($rc, @config) ");
  if ($rc == OK) {
      $self->log(LOGDEBUG, "setting _config_cache for $c to [@config] from hooks and returning it");
      $_config_cache->{$c} = \@config;
      return wantarray ? @{$_config_cache->{$c}} : $_config_cache->{$c}->[0];
  }

  # and then get_qmail_config
  @config = $self->get_qmail_config($c, $type);
  if (@config) {
      $self->log(LOGDEBUG, "setting _config_cache for $c to [@config] from get_qmail_config and returning it");
      $_config_cache->{$c} = \@config;
      return wantarray ? @{$_config_cache->{$c}} : $_config_cache->{$c}->[0];
  }

  # finally we use the default if there is any:
  if (exists($defaults{$c})) {
      $self->log(LOGDEBUG, "setting _config_cache for $c to @{[$defaults{$c}]} from defaults and returning it");
      $_config_cache->{$c} = [$defaults{$c}];
      return wantarray ? @{$_config_cache->{$c}} : $_config_cache->{$c}->[0];
  }
  return;

}

sub config_dir {
  my ($self, $config) = @_;
  if (exists $config_dir_memo{$config}) {
      return $config_dir_memo{$config};
  }
  my $configdir = ($ENV{QMAIL} || '/var/qmail') . '/control';
  my ($path) = ($ENV{PROCESS} ? $ENV{PROCESS} : $0) =~ m!(.*?)/([^/]+)$!;
  $configdir = "$path/config" if (-e "$path/config/$config");
  if (exists $ENV{QPSMTPD_CONFIG}) {
    $ENV{QPSMTPD_CONFIG} =~ /^(.*)$/; # detaint
    $configdir = $1 if -e "$1/$config";
  }
{'fraction_non_alphanumeric': 0.188294413242684, 'fraction_numerical': 0.004138338752586462, 'mean_word_length': 3.4674295774647885, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 76, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7079410', 'n_tokens_mistral': 3795, 'n_tokens_neox': 3700, 'n_words': 915}