# sendmail-lib.pl
# Functions for managing sendmail aliases, domains and mappings.
# Only sendmail versions 8.8 and above are supported

do '../web-lib.pl';
&init_config();
require '../ui-lib.pl';
%access = &get_module_acl();
$features_access = $access{'opts'} && $access{'cws'} && $access{'masq'} && $access{'trusts'} && $access{'vmode'} && $access{'amode'} && $access{'omode'} && $access{'cgs'} && $access{'relay'} && $access{'mailers'} && $access{'access'} && $access{'domains'};
$config{'perpage'} ||= 20;	# a value of 0 can cause problems

# get_sendmailcf()
# Parses sendmail.cf and return a reference to an array of options.
# Each line is a single character directive, followed by a list of values?
sub get_sendmailcf
{
if (!@sendmailcf_cache) {
	local($lnum, $i);
	$lnum = 0; $i = 0;
	open(CF, $config{'sendmail_cf'});
	while(<CF>) {
		s/^#.*$//g;	# remove comments
		s/\r|\n//g;	# remove newlines
		if (/^(\S)(\s*(.*))$/) {
			local(%opt);
			$opt{'type'} = $1;
			$opt{'value'} = $3;
			$opt{'values'} = [ split(/\s+/, $2) ];
			$opt{'line'} = $lnum;
			$opt{'eline'} = $opt{'line'};
			$opt{'pos'} = $i++;
			push(@sendmailcf_cache, \%opt);
			}
		$lnum++;
		}
	close(CF);
	}
return \@sendmailcf_cache;
}

# check_sendmail_version(&config)
# Is the sendmail config file a usable version?
sub check_sendmail_version
{
local $ver = &find_type("V", $_[0]);
return $ver && $ver->{'value'} =~ /^(\d+)/ && $1 >= 7 ? $1 : undef;
}

# get_sendmail_version(&out)
# Returns the actual sendmail executable version, if it is available
sub get_sendmail_version
{
local $version;
local $out;
open(OUT, "$config{'sendmail_path'} -d0 -bv 2>&1 </dev/null |");
while(1) {
	local $rmask;
	vec($rmask, fileno(OUT), 1) = 1;
	local $sel = select($rmask, undef, undef, 2);
	last if (!$sel);
	local $line = <OUT>;
	last if (!$line);
	if ($line =~ /version\s+(\S+)/i) {
		$version = $1;
		}
	$out .= $line;
	}
close(OUT);
${$_[0]} = $out if ($_[0]);
return $version;
}

# save_directives(&config, &oldvalues, &newvalues)
# Given 2 arrays of directive structures, this function will replace the
# old ones with the new. If the old list is empty, new directives are added
# to the end of the config file. If the new list is empty, all old directives
# are removed. If both exist, new ones replace old..
sub save_directives
{
local(@old) = @{$_[1]};
local(@new) = @{$_[2]};
$lref = &read_file_lines($config{'sendmail_cf'});
for($i=0; $i<@old || $i<@new; $i++) {
	if ($i >= @old) {
		# A new directive has been added.. put it at the end of the file
		$new[$i]->{'line'} = scalar(@$lref);
		$new[$i]->{'eline'} = $new[$i]->{'line'}+1;
		push(@$lref, &directive_line($new[$i]));
		push(@{$_[0]}, $new[$i]);
		}
	elsif ($i >= @new) {
		# A directive was deleted
		$ol = $old[$i]->{'eline'} - $old[$i]->{'line'} + 1;
		splice(@$lref, $old[$i]->{'line'}, $ol);
		&renumber_list($_[0], $old[$i], -$ol);
		splice(@{$_[0]}, &indexof($old[$i], @{$_[0]}), 1);
		}
	else {
		# A directive was changed
		$ol = $old[$i]->{'eline'} - $old[$i]->{'line'} + 1;
		splice(@$lref, $old[$i]->{'line'}, $ol,
		       &directive_line($new[$i]));
		$new[$i]->{'line'} = $new[$i]->{'eline'} = $old[$i]->{'line'};
		&renumber_list($_[0], $old[$i], 1-$ol);
		$_[0]->[&indexof($old[$i], @{$_[0]})] = $new[$i];
		}
	}
}

# directive_line(&details)
sub directive_line
{
return $_[0]->{'type'}.join(' ', @{$_[0]->{'values'}});
}

# find_type(name, &config)
# Returns an array of config directives of some type
sub find_type
{
local($c, @rv);
foreach $c (@{$_[1]}) {
	if ($c->{'type'} eq $_[0]) {
		push(@rv, $c);
		}
	}
return @rv ? wantarray ? @rv : $rv[0]
           : wantarray ? () : undef;
}

# find_option(name, &config)
# Returns the structure and value of some option directive
sub find_option
{
local(@opts, $o);
@opts = &find_type("O", $_[1]);
foreach $o (@opts) {
	if ($o->{'value'} =~ /^\s*([^=]+)=(.*)$/ && $1 eq $_[0]) {
		# found it.. return
		return wantarray ? ($o, $2) : $2;
		}
	}
return undef;
}

# find_type2(type1, type2, &config)
# Returns the structure and value of some directive
sub find_type2
{
local @types = &find_type($_[0], $_[2]);
local $t;
foreach $t (@types) {
	if ($t->{'value'} =~ /^(\S)(.*)$/ && $1 eq $_[1]) {
		return ($t, $2);
		}
	}
return undef;
}

# restart_sendmail()
# Send a SIGHUP to sendmail
sub restart_sendmail
{
local($pid);
if (open(PID, $config{'sendmail_pid'})) {
	chop($pid = <PID>);
	close(PID);
	if ($pid) { &kill_logged('HUP', $pid); }
	}
else { &kill_logged('HUP', &find_byname("sendmail")); }
}

# run_makemap(textfile, dbmfile, type)
sub run_makemap
{
local($out);
$out = &backquote_logged("$config{'makemap_path'} $_[2] $_[1] <\"$_[0]\" 2>&1");
if ($?) { &error("makemap failed : <pre>$out</pre>"); }
}

# find_textfile(config, dbm)
sub find_textfile
{
local($conf, $dbm) = @_;
if ($conf) { return $conf; }
elsif (!$dbm) { return undef; }
elsif ($dbm =~ /^(.*)\.(db|dbm|pag|dir|hash)$/i && -r $1) {
	# Database is like /etc/virtusertable.db, text is /etc/virtusertable
	return $1;
	}
elsif ($dbm =~ /^(.*)\.(db|dbm|pag|dir|hash)$/i && -r "$1.txt") {
	# Database is like /etc/virtusertable.db, text is /etc/virtusertable.txt
	return "$1.txt";
	}
elsif (-r "$dbm.txt") {
	# Database is like /etc/virtusertable, text is /etc/virtusertable.txt
	return "$dbm.txt";
	}
else {
	# Text and database have same name
	return $dbm;
	}
}

# mailq_dir($conf)
sub mailq_dir
{
local ($opt, $mqueue) = &find_option("QueueDirectory", $_[0]);
local @rv;
if (!$mqueue) { @rv = ( "/var/spool/mqueue" ); }
elsif ($mqueue =~ /\*|\?/) {
	@rv = split(/\s+/, `echo $mqueue`);
	}
else {
	@rv = ( $mqueue );
	}
push(@rv, split(/\s+/, $config{'queue_dirs'}));
return @rv;
}

sub sort_by_domain
{
local ($a1, $a2, $b1, $b2);
if ($a->{'from'} =~ /^(.*)\@(.*)$/ && (($a1, $a2) = ($1, $2)) &&
    $b->{'from'} =~ /^(.*)\@(.*)$/ && (($b1, $b2) = ($1, $2))) {
	return $a2 cmp $b2 ? $a2 cmp $b2 : $a1 cmp $b1;
	}
else {
	return $a->{'from'} cmp $b->{'from'};
	}
}

# can_view_qfile(&mail)
# Returns 1 if some queued message can be viewed, 0 if not
sub can_view_qfile
{
return 1 if (!$access{'qdoms'});
local $re = $access{'qdoms'};
if ($access{'qdomsmode'} == 0) {
	return $_[0]->{'header'}->{'from'} =~ /$re/i;
	}
elsif ($access{'qdomsmode'} == 0) {
	return $_[0]->{'header'}->{'to'} =~ /$re/i;
	}
else {
	return $_[0]->{'header'}->{'from'} =~ /$re/i ||
	       $_[0]->{'header'}->{'to'} =~ /$re/i;
	}
}

# renumber_list(&list, &position-object, offset)
sub renumber_list
{
return if (!$_[2]);
local $e;
foreach $e (@{$_[0]}) {
	if (!defined($e->{'file'}) || $e->{'file'} eq $_[1]->{'file'}) {
		$e->{'line'} += $_[2] if ($e->{'line'} > $_[1]->{'line'});
		$e->{'eline'} += $_[2] if (defined($e->{'eline'}) &&
					   $e->{'eline'} > $_[1]->{'eline'});
		}
	}
}

1;

