#!/usr/local/bin/perl

# make sure .qmail-user and user/.qmail contain something
# smelling valid
# Copyright 2008 Jeremy Kister.  http://jeremy.kister.net./

use strict;
my %paths;
my ($y,$m,$d) = (localtime())[5,4,3];
$y += 1900;
$m++;
my $mon = sprintf('%.2d', $m);
my $day = sprintf('%.2d', $d);

my $date = $y . $mon . $day;

my(%dir,%nuked);

opendir(D, '/var/qmail/queue/mess/') || die "cannot opendir queue/mess: $!\n";
my @subdirs = (grep {!/^\./} readdir D); # conf-split
closedir D;

# trim log,
my @log;
if(open(LOG, "/export/home/misc_logs/nukedforwards")){
   while(<LOG>){
      push @log, $_;
   }
   close LOG;
   my $i=1;
   open(LOG, ">/export/home/misc_logs/nukedforwards.tmp") || die "cannot write to nukedforwards.tmp: $!\n";
   foreach my $line (reverse @log){
      print LOG $line;
      last if($i == 10000);
      $i++;
   }
   close LOG;

   rename("/export/home/misc_logs/nukedforwards.tmp","/export/home/misc_logs/nukedforwards")
     || die "cannot rename tmp: $!\n";
}
## end trim log


open(LOG, ">>/export/home/misc_logs/nukedforwards") || die "cannot open nukedforwards for append: $!\n";


our($opt_v,$opt_f);
use Getopt::Std;
getopts('v');

print "gathering mx authoritative data..\n";
open(A, '/var/qmail/users/assign') || die "cannot open assign: $!\n";
while(<A>){
   if(/^\+.+\-:([^:]+):\d+:\d+:([^:]+):-::$/){
		next if($1 eq 'graves.com' || $1 eq 'lists.broadviewnet.net');
      if((-f "$2/vpasswd") && (! -l $2)){
         $paths{$1} = $2;
      }
   }
}
close A;
my %copy = %paths;

while(my($domain,$dir) = each %paths){
	print "working on $domain -> $dir\n" if($opt_v);
	if(opendir(D, $dir)){
		# open vpasswd, look for .qmail inside Maildirs
		if(open(V, "${dir}/vpasswd")){
			my $n=0;
			while(<V>){
				$n++;
			   if(/^([^:]+):(?:[^:]+)?:\d+:\d+:[^:]*:([^:]+):/){
         		my $mailbox = $1;
         		my $maildir = $2;
					if(open(F, "$maildir/.qmail")){
						my $l=0;
						while(<F>){
							$l++;
							next if(/^#/);
							chomp;
							my $code = verify($_,%copy);
							unless($code == 0){
								warn "Bad line in $maildir/.qmail:$l\n" if($opt_v);
								print LOG "[${date}]: Bad line in $maildir/.qmail:$l\n";
							}
						}
						close F;
					}else{
						unless(-d $maildir){
							warn "No maildir for $mailbox\@$domain ($dir)\n" if($opt_v);
							print LOG "[${date}]: No maildir for $mailbox\@$domain ($dir)\n";
						}
					}
				}else{
					warn "Invalid syntax at ${dir}/vpasswd:$n\n" if($opt_v);
					print LOG "[${date}]: Invalid syntax at ${dir}/vpasswd:$n\n";
				}
			}
			close V;
		}else{
			warn "cannot open ${dir}/vpasswd: $!\n"  if($opt_v);
			print LOG "[${date}]: cannot open ${dir}/vpasswd: $!\n";
		}
		foreach my $file (grep {/^\.qmail-/} readdir D){
			my $tfile = $file;
			$tfile =~ s/^.qmail-//;
			if($tfile =~ /\./){
				warn "Illegal character in ${dir}/${file}\n" if($opt_v);
				print LOG "[${date}]: Illegal character in ${dir}/${file}\n";
			}elsif($tfile =~ /-owner/){
				next; # i know these are wrong and causing problems, but ignore for now
			}
			if(open(F, "${dir}/${file}")){
				my $n=0;
				while(<F>){
					$n++;
					# should make sure .qmail-default contains >=1 valid command
					next if(/^#/);
					chomp;
					my $code = verify($_,%copy);
					if($code == 2){
						warn "unlinked ${dir}/${file}\n" if($opt_v);
						print LOG "[${date}]: unlinked ${dir}/${file}\n";
						unlink("${dir}/${file}") || die "cannot unlink $dir/$file: $!\n";
					}elsif($code != 0){
						warn "Bad Config in ${dir}/${file}:$n\n" if($opt_v);
						print LOG "[${date}]: Bad Config in ${dir}/${file}:$n\n";
					}
				}
				close F;
			}else{
				warn "could not open ${dir}/${file}: $!\n" if($opt_v);
				print LOG "[${date}]: could not open ${dir}/${file}: $!\n";
			}
		}
		closedir D;
	}else{
		warn "error with $domain: $dir does not exist\n"  if($opt_v);
		print LOG "[${date}]: error with $domain: $dir does not exist\n";
	}
}

sub verify {
	my $line = shift;
	my %copy = shift;

	if($line =~ /^([\/\.]\S+\/)$/){
		my $maildir = $1;
		my $code = (-d $maildir) ? 0 : 2;
		return $code;
	}elsif($line =~ /\|\s*(\S+)/){
		my $program = $1;
		# piping to a program
		unless($program =~ /vdelivermail|forward|esec.pl|ezmlm|autorespond|circuits-config|wreq|receive|openticket.pl/){
			return 1;
		}
		if(-x $program){
			return 0;
		}else{
			return 1;
		}
	}elsif($line =~ /^&?(([a-z0-9_\.\+\-\=\?\^])+\@(([a-z0-9\-])+\.)+([a-z0-9]{2,4})+)$/i){
		my $addr = $1;
		my($user,$d) = split(/\@/, $addr) || die "bad addr: $addr\n";
		my $code=1; # assume bad, prove it's good.
		if(exists($copy{$d})){
			# must check if valid via .qmail-username, (vpasswd + Maildir), Maildir/.qmail, or .qmail-default
			if(-f "$copy{$d}/.qmail-${user}"){
				$code=0; # dont need to check it, done above.
			}elsif(-f "$copy{$d}/${user}/Maildir/.qmail"){
				$code=0; # dont need to check it, done above.
			}elsif(-d "$copy{$d}/${user}/Maildir/"){
				$code=0;
			}elsif(open(F, "$copy{$d}/.qmail-default")){
				my $n=0;
				while(<F>){
					chop;
					$n++;
					if(/(\S+\/)$/){
						my $default=$1;
						if(-d $default){
							$code=0;
						}else{
							warn "Invalid directory at $copy{$d}/.qmail-default:$n\n" if($opt_v);
							print LOG "[${date}]: Invalid directory at $copy{$d}/.qmail-default:$n\n";
						}
					}
				}
				close F;
			}
		}else{
			$code=0; # we dont do mail for that domain
		}

		return $code;
	}else{
		print "unknown: $line\n";
		return 1; #unknown
	}
}
