#!/usr/bin/perl -T

# Show the configurable variables of the amavisd-new and its conf file.
#
# Usage:
#   amavisdconf [-d | -n] [-c conf-file] [variable]
#
# Options:
#  (none) show the value as will be used by amavisd;
#   -d  show default value as in the absence of the config file;
#   -n  only show variable if its value is different from the default.
#   -c conf-file ... use the specified file instead of /etc/amavisd.conf
#
# If a variable is specified, show only that variable, otherwise show
# all configurable variables. (Note: the leading $, @ or % must be included
# with the variable. Make sure to protect $ from shell evaluation,
# e.g. by enclosing variable name in single quotes:
#   $ amavisdconf -d '$forward_method'
#   $ amavisdconf -n
# 
# Author: Mark Martinec <mark.martinec@ijs.si>
# 2002-12-20  0.50 initial version
# 2002-12-23  1.00 suppport the new read_hash() and read_text()

package Amavis::Lookup::RE;
  use strict;
  sub new($$) { my($class) = shift;  bless [@_], $class }

package AmavisdConf;
  use strict;
  use POSIX qw(uname);
  BEGIN {
    use vars qw($VERSION @confvars %boolvars);
    $VERSION = '1.12';
    @confvars = qw(
	    $myversion
	    $DEBUG @debug_sender_acl
	    $daemonize $pid_file $lock_file
	    $daemon_user $daemon_group $daemon_chroot_dir $path
	    $DO_SYSLOG $SYSLOG_LEVEL $LOGFILE $log_level
	    $TEMPBASE
	    $max_servers $max_requests $child_timeout
	    $warnvirussender $warnvirusrecip $warnspamsender
	    $log_templ
	    $unix_socketname $inet_socket_port $inet_socket_bind @inet_acl
	    $myhostname $localhost_name
	    $insert_received_line
	    $mta_in_type $gets_addr_in_quoted_form
	    $mta_out_type $forward_method
	    $relayhost_is_client
	    $X_HEADER_TAG $X_HEADER_LINE $remove_existing_x_scanned_headers
	    $QUARANTINEDIR %local_delivery_aliases
	    $final_virus_destiny $final_banned_destiny $final_spam_destiny
	    $recipient_delimiter $replace_existing_extension
	    $localpart_is_case_sensitive
	    $addr_extension_banned $addr_extension_virus $addr_extension_spam
	    $smtpd_recipient_limit
	    $MAXLEVELS $MAXFILES
	    $MIN_EXPANSION_QUOTA $MIN_EXPANSION_FACTOR
	    $MAX_EXPANSION_QUOTA $MAX_EXPANSION_FACTOR
	    $bypass_decode_parts $banned_filename_re
	    $keep_decoded_original_re
	    %bypass_virus_checks @bypass_virus_checks_acl $bypass_virus_checks_re
	    %bypass_spam_checks @bypass_spam_checks_acl $bypass_spam_checks_re
	    %virus_lovers @virus_lovers_acl $virus_lovers_re
	    %banned_files_lovers @banned_files_lovers_acl
	    %spam_lovers @spam_lovers_acl $spam_lovers_re
	    %whitelist_sender @whitelist_sender_acl $whitelist_sender_re
	    %blacklist_sender @blacklist_sender_acl $blacklist_sender_re
	    $viruses_that_fake_sender_re
	    @lookup_sql_dsn
	    @local_domains $local_domains_re
	    $notify_method
	    $mailfrom_notify_sender
	    $mailfrom_notify_admin
	    $mailfrom_notify_recip
	    $mailfrom_notify_spamadmin
	    $mailfrom_to_quarantine
	    $hdrfrom_notify_sender
	    $hdrfrom_notify_admin
	    $hdrfrom_notify_spamadmin
	    %virus_admin %spam_admin $virus_admin $spam_admin $mailto
	    $warn_offsite
	    $virus_quarantine_to $spam_quarantine_to
	    $arc $gzip $bzip2 $file $lha $unarj $uncompress $unrar $zoo
	    $sa_kill_level_deflt $sa_tag_level_deflt
	    $sa_spam_subject_tag $helpers_home
	    $sa_local_tests_only $sa_debug $sa_mail_body_size_limit
	    $sa_auto_whitelist $can_truncate
	    $notify_sender_templ
	    $notify_virus_sender_templ $notify_spam_sender_templ
	    $notify_virus_admin_templ  $notify_spam_admin_templ
	    $notify_virus_recips_templ $notify_spam_recips_templ
	    @av_scanners
  )};
  use vars @confvars;
  %boolvars = map {$_=>1} qw(
	    $DEBUG $DO_SYSLOG $daemonize
	    $warnvirussender $warnvirusrecip $warnspamsender $warn_offsite
	    $insert_received_line $gets_addr_in_quoted_form
	    $relayhost_is_client $remove_existing_x_scanned_headers
	    $replace_existing_extension $localpart_is_case_sensitive
	    $bypass_decode_parts $sa_local_tests_only $sa_debug
            $sa_auto_whitelist $can_truncate
  );

  sub new_RE { Amavis::Lookup::RE->new(@_) };

  $DEBUG = 0; $daemonize = 1; $max_servers =  2; $max_requests = 10;
  $child_timeout = 8*60; $can_truncate = 1; $SYSLOG_LEVEL = "mail.info";
  $inet_socket_bind = '127.0.0.1'; @inet_acl = qw( 127.0.0.1 );
  $gets_addr_in_quoted_form = 0; $TEMPBASE = "/var/amavis";
  $notify_method  = 'smtp:127.0.0.1:10025';
  $forward_method = 'smtp:127.0.0.1:10025';
  $insert_received_line = 1; $smtpd_recipient_limit = 1000;
  $localhost_name = 'localhost'; $sa_local_tests_only = 0; $sa_debug = 0;
  $final_virus_destiny  =  0; $final_banned_destiny =  1;
  $final_spam_destiny = -1; $recipient_delimiter = '+';
  $replace_existing_extension = 1; $localpart_is_case_sensitive = 0;
  $myhostname = (uname)[1];

  sub ext_repr($) {
    my($a) = @_;
    return '(' . join(',', map {ext_repr(\$_)} @$a) . ')' if ref($a) eq 'ARRAY';
    return '(' . join(',', map {ext_repr(\$_)} %$a) . ')' if ref($a) eq 'HASH';
    local($_) = $$a;
    return 'undef'  if !defined;
    return $_  if /^[-+]?\d+(\.\d*)?$/;  # numeric
    return do{s/(['\\])/\\$1/g; "'$_'"}  if !ref;
    return "qr$_"  if ref eq 'Regexp';
    return '[' . join(',', map {ext_repr(\$_)} @$_) . ']' if ref eq 'ARRAY';
    return '{' . join(',', map {ext_repr(\$_)} %$_) . '}' if ref eq 'HASH';
    return 'new_RE(' .join(',', map {ext_repr(\$_)} @$_).')'
      if ref eq 'Amavis::Lookup::RE';
    return $_;
  }

  sub read_text($) {
    my($filename) = @_;
    local(*INP); my($str);
    open(INP, $filename) or die "Cannot open file $filename for reading: $!";
    while(<INP>) { $str .= $_ }
    close(INP) or die "Cannot close file $filename: $!";
    $str;
  }

  sub read_hash($$) {
    my($hashref,$filename,$keep_case) = @_;
    local(*INP);
    open(INP, $filename) or die "Cannot open file $filename for reading: $!";
    while(<INP>) {   # carefully handle comments, # within "" does not count
      chomp; my($line);
      for my $t (/\G (" (?: \\" | [^"] )* " | [^#"]+ | . ) /gcx) {
	    last if $t eq '#';
	    $line .= $t;
      }
      $line =~ s/^\s+//; $line =~ s/\s+$//; # trim leading and trailing space
      next  if $line eq '';
      my($addr) = unquote_rfc2821_local($line);
      $addr = lc($addr)  if !$keep_case;
      $hashref->{$addr} = 1;
    }
    close(INP) or die "Cannot close file $filename: $!";
  }

  sub split_address($) {
    my($mailbox) = @_;
    $mailbox =~ /^ (.*?) ( \@ (?:  \[  (?: \\. | [^\[\]\\] )*  \]
                                |  [^@"<>\[\]\\\s] )*
                         ) $/xs ? ($1,$2) : ($mailbox,'');
  }

  sub unquote_rfc2821_local($) {
    my($mailbox) = @_;
    if ($mailbox =~ /^ \s* < ( .* ) > \s* $/xs) { $mailbox = $1 }
    my($localpart,$domain) = split_address($mailbox);
    $localpart =~ s/ " | \\(.) | \\$ /$1/xsg;  # unquote quoted-pairs
    $localpart . $domain;
  }

  my($what,$onevar);
  my($config_file) = '/etc/amavisd.conf';
  while (@ARGV) {
    if (@ARGV >= 1 && $ARGV[0] =~ /^-([nd])$/) {
      $what = $1; shift @ARGV;
    } elsif (@ARGV >= 2 && $ARGV[0] eq '-c') {
      shift @ARGV; $config_file = shift @ARGV;
      $config_file = $1  if $config_file =~ m{^([A-Za-z0-9/._=+-]+)$};  # untaint
    } elsif (@ARGV >= 1) {
      $onevar = shift @ARGV;
      if (grep($_ eq $onevar, @confvars) && $onevar =~ /^(.+)$/) {
        $onevar = $1;
      } else { print "No such variable $onevar\n"; exit }
    } else {
      print "Usage: $0 [-d | -n] [-c conf-file] [variable]\n";
      exit;
    }
  }
  my(%saved_confscalars) = map {$_=>eval($_)} grep {/^\$/} @confvars;
  my(%saved_confarrays)  = map {$_=>[eval($_)]} grep {/^[%@]/} @confvars;
  if ($what eq 'd') {
    for (keys %saved_confscalars) { $saved_confscalars{$_} = "\000" };
    for (keys %saved_confarrays)  { $saved_confarrays{$_} = ["\000"] };
  } else {
    -e($config_file) or die "Cannot find config file $config_file";
    -r(_)            or die "Cannot read config file $config_file";
    do $config_file;  # override variables with settings from conf file
    $@ eq '' or die "Error in config file $config_file: $@";
  }
  $helpers_home = $TEMPBASE             if !defined $helpers_home;
  $pid_file  = "$TEMPBASE/amavisd.pid"  if !defined $pid_file;
  $lock_file = "$TEMPBASE/amavisd.lock" if !defined $lock_file;
  $hdrfrom_notify_sender = "amavisd-new <postmaster\@$myhostname>"
    if !defined $hdrfrom_notify_sender;
  $hdrfrom_notify_admin = $mailfrom_notify_admin ne ''
    ? $mailfrom_notify_admin : $hdrfrom_notify_sender
    if !defined $hdrfrom_notify_admin;
  $hdrfrom_notify_spamadmin = $mailfrom_notify_spamadmin ne ''
    ? $mailfrom_notify_spamadmin : $hdrfrom_notify_sender
    if !defined $hdrfrom_notify_spamadmin;

  if (defined $onevar) { @confvars = ($onevar) }
  for my $v (grep {/^\$/} @confvars) {   # scalars
     my($sv,$cv) = ($saved_confscalars{$v}, eval($v));
     if ($boolvars{$v}) {
       next  if $what eq 'n' && ($sv?1:0) == ($cv?1:0);
     } else {
       next  if $what eq 'n' && defined($sv) == defined($cv) && $sv eq $cv;
     }
     printf("%s = %s;\n", $v, ext_repr(\$cv));
  }
  for my $v (grep {/^[%@]/} @confvars) {   # arrays and hashes
     my(@sv) = @{$saved_confarrays{$v}};
     my(@cv) = eval($v);
     next  if $what eq 'n' && @sv == @cv && !grep {$sv[$_] ne $cv[$_]} 0..$#cv;
     printf("%s = %s;\n", $v, ext_repr(\@cv));
  }
