#!/usr/bin/perl

#VERSION=16

sub get_domain_owner
{
	my ($domain) = @_;
	my $username="";
	open(DOMAINOWNERS,"/etc/virtual/domainowners");
	while (<DOMAINOWNERS>)
	{
		$_ =~ s/\n//;
		my ($dmn,$usr) = split(/: /, $_);
		if ($dmn eq $domain)
		{
			close(DOMAINOWNERS);
			return $usr;
		}
	}
	close(DOMAINOWNERS);

	return -1;
}

sub safe_name
{
	my ($name) =  @_;

	if ($name =~ /\//)
	{
		return 0;
	}

	if ($name =~ /\\/)
	{
		return 0;
	}
	
	if ($name =~ /\|/)
	{
		return 0;
	}

	if ($name =~ /\.\./)
	{
		return 0;
	}

	return 1;
}

# hit_limit_user
# checks to see if a username has hit the send limit.
# returns:
#	-1 for "there is no limit"
#	0  for "still under the limit"
#	1  for "at the limit"
#	2  for "over the limit"

sub hit_limit_user
{
	my($username) = @_;

	my $count = 0;
	my $email_limit = 0;

	if (!safe_name($username))
	{
		return 2;
	}

	if (open (LIMIT, "/etc/virtual/limit_$username"))
	{
		$email_limit = int(<LIMIT>);
		close(LIMIT);
	}
	else
	{
		open (LIMIT, "/etc/virtual/limit");
		$email_limit = int(<LIMIT>);
		close(LIMIT);
	}

	if ($email_limit > 0)
	{
		#check this users limit
		$count = (stat("/etc/virtual/usage/$username"))[7];

		#this is their last email.
		if ($count == $email_limit)
		{
			return 1;
		}

		if ($count > $email_limit)
		{
			return 2;
		}

		return 0;
	}

	return -1;
}

# hit_limit_email
# same idea as hit_limit_user, except we check the limits (if any) for per-email accounts.

sub hit_limit_email
{
	my($user,$domain) = @_;

	if (!safe_name($user) || !safe_name($domain))
	{
		return 2;
	}

	my $user_email_limit = 0;
	if (open (LIMIT, "/etc/virtual/$domain/limit/$user"))
	{
		$user_email_limit = int(<LIMIT>);
		close(LIMIT);
	}
	else
	{
		if (open (LIMIT, "/etc/virtual/user_limit"))
		{
			$user_email_limit = int(<LIMIT>);
			close(LIMIT);
		}
	}

	if ($user_email_limit > 0)
	{
		my $count = 0;
		$count = (stat("/etc/virtual/$domain/usage/$user"))[7];
		if ($count == $user_email_limit)
		{
			return 1;
		}
		if ($count > $user_email_limit)
		{
			return 2;
		}
		return 0;
	}

	return -1;
}

#smtpauth
#called by exim to verify if an smtp user is allowed to
#send email through the server
#possible success:
# user is in /etc/virtual/domain.com/passwd and password matches
# user is in /etc/passwd and password matches in /etc/shadow

sub smtpauth
{
	$username	= Exim::expand_string('$1');
	$password	= Exim::expand_string('$2');
	$extra		= Exim::expand_string('$3');
	$domain		= "";
	$unixuser	= 1;

	#check for netscape that offsets the login/pass by one
	if (length($extra) > 0 )
	{
		if ($username eq "" || $username eq $password)
		{
			$username = $password;
			$password = $extra;
		}
	}

	if (!safe_name($username))
	{
		Exim::log_write("SMTPAuth: Invalid username: $username");
		return "no";
	}

	if ($username =~ /\@/)
	{
		$unixuser = 0;
		($username,$domain) = split(/\@/, $username);
		if ($domain eq "") { return "no"; }
	}

	if ($unixuser == 1)
	{
		#the username passed doesn't have a domain, so its a system account
		$homepath = (getpwnam($username))[7];
		if ($homepath eq "") { return 0; }
		open(PASSFILE, "< $homepath/.shadow") || return "no";
		$crypted_pass = <PASSFILE>;
		close PASSFILE;

		if ($crypted_pass eq crypt($password, $crypted_pass))
		{
			my $limit_check = hit_limit_user($username);
			if ($limit_check > 1)
			{
				die("The email send limit for $username has been reached\n");
			}

			return "yes";
		}
		else { return "no"; }
	}
	else
	{
		#the username contain a domain, which is now in $domain.
		#this is a pure virtual pop account.

		open(PASSFILE, "< /etc/virtual/$domain/passwd") || return "no";
		while (<PASSFILE>)
		{
			($test_user,$test_pass) = split(/:/,$_);
			$test_pass =~ s/\n//g; #snip out the newline at the end
			if ($test_user eq $username)
			{
				if ($test_pass eq crypt($password, $test_pass))
				{
					close PASSFILE;

					my $domain_owner = get_domain_owner($domain);
					if ($domain_owner != -1)
					{
						my $limit_check = hit_limit_user($domain_owner);
						if ($limit_check > 1)
						{
							die("The email send limit for $domain_owner has been reached\n");
						}

						$limit_check = hit_limit_email($username, $domain);
						if ($limit_check > 1)
						{
							die("The email send limit for $username\@${domain} has been reached\n");
						}
					}

					return "yes";
				}
			}
		}
		close PASSFILE;
		return "no";
	}

	return "no";
}

sub find_uid_apache
{
	my ($work_path) = @_;
	my @pw;
	
	# $pwd will probably look like '/home/username/domains/domain.com/public_html'
	# it may or may not use /home though. others are /usr/home, but it's ultimately
	# specified in the /etc/passwd file.  We *could* parse through it, but for efficiency
	# reasons, we'll only check /home and /usr/home ..   if they change it, they can
	# manually adjust if needed.

	@dirs = split(/\//, $work_path);
	foreach $dir (@dirs)
	{
		# check the dir name for a valid user
		# get the home dir for that user
		# compare it with the first part of the work_path

		if ( (@pw = getpwnam($dir))  )
		{
			if ($work_path =~/^$pw[7]/)
			{
				return $pw[2];
			}
		}
	}
	return -1;
}

sub find_uid_auth_id
{
	# this will be passwed either
	# 'username' or 'user@domain.com'

	my ($auth_id) = @_;
	my $unixuser = 1;
	my $domain = "";
	my $user = "";
	my $username = $auth_id;
	my @pw;

	if (!safe_name($username))
	{
		Exim::log_write("find_uid_auth_id: Invalid username: $username");
		return "-1";
	}

	if ($auth_id =~ /\@/)
	{
		$unixuser = 0;
		($user,$domain) = split(/\@/, $auth_id);
		if ($domain eq "") { return "-1"; }
        }

	if (!$unixuser)
	{
		# we need to take $domain and get the user from /etc/virtual/domainowners
		# once we find it, set $username
		my $u = get_domain_owner($domain);;
		if ($u != -1)
		{
			$username = $u;
		}
	}

	#log_str("username found from $auth_id: $username:\n");

	if ( (@pw = getpwnam($username))  )
	{
		return $pw[2];
	}

	return -1;
}

sub find_uid_sender
{
	my $sender_address = Exim::expand_string('$sender_address');

	my ($user,$domain) = split(/\@/, $sender_address);

	my $primary_hostname = Exim::expand_string('$primary_hostname');
	if ( $domain eq $primary_hostname )
	{
		@pw = getpwnam($user);
		return $pw[2];
	}

	my $username = get_domain_owner($domain);

	if ( (@pw = getpwnam($username))  )
	{
		return $pw[2];
	}

	return -1;
}

sub find_uid
{
        my $uid = Exim::expand_string('$originator_uid');
	my $username = getpwuid($uid);
        my $auth_id = Exim::expand_string('$authenticated_id');
        my $work_path = $ENV{'PWD'};

	if ($username eq "apache" || $username eq "nobody" || $username eq "webapps")
	{
		$uid = find_uid_apache($work_path);
		if ($uid != -1) { return $uid; }
	}
	
	$uid = find_uid_auth_id($auth_id);
	if ($uid != -1) { return $uid; }

	# we don't want to rely on this, but it's all thats left.
	return find_uid_sender;
}

sub uid_exempt
{
        my ($uid) = @_;
        if ($uid == 0) { return 1; }

        my $name = getpwuid($uid);
        if ($name eq "root") { return 1; }
        if ($name eq "diradmin") { return 1; }

        return 0;
}


#check_limits
#used to enforce limits for the number of emails sent
#by a user.  It also logs the bandwidth of the data
#for received mail.

sub check_limits
{
	#find the curent user
	$uid = find_uid();

	#log_str("Found uid: $uid\n");

	if (uid_exempt($uid)) { return "yes"; }

	my $name="";

	#check this users limit
	$name = getpwuid($uid);

	if (!defined($name))
	{
		#possibly the sender-verify
		$name = "unknown";
		#return "yes";
	}

	my $count = 0;
	my $email_limit = 0;
	if (open (LIMIT, "/etc/virtual/limit_$name"))
	{
		$email_limit = int(<LIMIT>);
		close(LIMIT);
	}
	else
	{
		open (LIMIT, "/etc/virtual/limit");
		$email_limit = int(<LIMIT>);
		close(LIMIT);
	}

	my $sender_address 	= Exim::expand_string('$sender_address');
	my $authenticated_id	= Exim::expand_string('$authenticated_id');
	my $sender_host_address	= Exim::expand_string('$sender_host_address');
	my $mid 		= Exim::expand_string('$message_id');
	my $message_size	= Exim::expand_string('$message_size');
	my $local_part		= Exim::expand_string('$local_part');
	my $domain		= Exim::expand_string('$domain');
	my $timestamp		= time();
	my $is_retry = 0;

	if ($email_limit > 0)
	{
		#check this users limit
		$count = (stat("/etc/virtual/usage/$name"))[7];

		if ($count > $email_limit)
		{
			die("You ($name) have reached your daily email limit of $email_limit emails\n");
		}

		if ($mid ne "")
		{
			if (! -d "/etc/virtual/usage/${name}_ids")
			{
				mkdir("/etc/virtual/usage/${name}_ids", 0770);
			}

			my $mid_char = substr($mid, 0, 1);

			if (! -d "/etc/virtual/usage/${name}_ids/$mid_char")
			{
				mkdir("/etc/virtual/usage/${name}_ids/$mid_char", 0770);
			}
			
			if (! -d "/etc/virtual/usage/${name}_ids/$mid_char/$mid")
			{
				mkdir("/etc/virtual/usage/${name}_ids/$mid_char/$mid", 0770);
			}

			my $dest_str = get_b64_string("$local_part-$domain");
			my $id_file = "/etc/virtual/usage/${name}_ids/$mid_char/$mid/$dest_str";

			if (-f $id_file)
			{
				$is_retry = 1;
			}
			else
			{
				open(IDF, ">>$id_file");
				print IDF "log_time=$timestamp\n";
				close(IDF);
				chmod (0660, $id_file);
			}
		}

		#this is their last email.
		if (($count == $email_limit) && ($is_retry != 1))
		{
			#taddle on the dataskq
			#note that the sender_address here is only the person who sent the last email
			#it doesnt meant that they have sent all the spam
			#this action=limit will trigger a check on usage/user.bytes, and DA will try and figure it out.
			open(TQ, ">>/etc/virtual/mail_task.queue");
			print TQ "action=limit&username=$name&count=$count&limit=$email_limit&email=$sender_address&authenticated_id=$authenticated_id&sender_host_address=$sender_host_address&log_time=$timestamp\n";
			close(TQ);
			chmod (0660, "/etc/virtual/mail_task.queue");
		}

		if ($is_retry != 1)
		{
			open(USAGE, ">>/etc/virtual/usage/$name");
			print USAGE "1";
			close(USAGE);
			chmod (0660, "/etc/virtual/usage/$name");
		}
	}

	if ( ($authenticated_id ne "") && ($is_retry != 1) )
	{
		my $user="";
		my $domain="";
		($user, $domain) = (split(/@/, $authenticated_id));

		if (!safe_name($authenticated_id))
		{
			Exim::log_write("check_limits: Invalid username: $authenticated_id");
			return "no";
		}

		if ($domain ne "")
		{
			my $user_email_limit = 0;
			if (open (LIMIT, "/etc/virtual/$domain/limit/$user"))
			{
				$user_email_limit = int(<LIMIT>);
				close(LIMIT);
			}
			else
			{
				if (open (LIMIT, "/etc/virtual/user_limit"))
				{
					$user_email_limit = int(<LIMIT>);
					close(LIMIT);
				}
			}

			if ($user_email_limit > 0)
			{
				$count = 0;
				$count = (stat("/etc/virtual/$domain/usage/$user"))[7];

				if ($count == $user_email_limit)
				{
					open(TQ, ">>/etc/virtual/mail_task.queue");
					print TQ "action=userlimit&username=$name&count=$count&limit=$user_email_limit&email=$sender_address&authenticated_id=$authenticated_id&sender_host_address=$sender_host_address&log_time=$timestamp\n";
					close(TQ);
					chmod (0660, "/etc/virtual/mail_task.queue");
				}

				if ($count > $user_email_limit)
				{
					die("Your E-Mail ($authenticated_id) has reached it's daily email limit of $user_email_limit emails\n");
				}

				if (! -d "/etc/virtual/$domain/usage")
				{
					mkdir("/etc/virtual/$domain/usage", 0770);
				}

				if (-d "/etc/virtual/$domain/usage")
				{
					open(USAGE, ">>/etc/virtual/$domain/usage/$user");
					print USAGE "1";
					close(USAGE);
					chmod (0660, "/etc/virtual/$domain/usage/$user");
				}
			}
		}
	}

	log_bandwidth($uid,"type=email&email=$sender_address&method=outgoing&id=$mid&authenticated_id=$authenticated_id&sender_host_address=$sender_host_address&log_time=$timestamp&message_size=$message_size&local_part=$local_part&domain=$domain");

	return "yes"
}

sub log_email
{
	my($lp,$dmn) = @_;

	#log_str("logging $lp\@$dmn\n");
	my $user = get_domain_owner($dmn);
	if ($user == -1) { return "no"; }

	my $mid = Exim::expand_string('$message_id');
	my $timestamp           = time();

	if ( (@pw = getpwnam($user))  )
	{
		log_bandwidth($pw[2],"type=email&email=$lp\@$dmn&method=incoming&log_time=$timestamp&id=$mid");
	}

	return "yes";
}

sub save_virtual_user
{
	my $dmn = Exim::expand_string('$domain');
	my $lp  = Exim::expand_string('$local_part');
	my $usr = "";
	my $pss = "";
	my $entry = "";

	if (!safe_name($dmn) || !safe_name($lp))
	{
		Exim::log_write("save_virtual_user: Invalid username: $lp or domain: $dmn");
		return "no";
	}

	open (PASSWD, "/etc/virtual/$dmn/passwd") || return "no";

	while ($entry = <PASSWD>) {
		($usr,$pss) = split(/:/,$entry);
		if ($usr eq $lp) {
			close(PASSWD);
			log_email($lp, $dmn);
			return "yes";
		}
	}
	close (PASSWD);

	return "no";
}

sub log_bandwidth
{
	my ($uid,$data) = @_;
	my $name = getpwuid($uid);

	if (uid_exempt($uid)) { return; }

	if ($name eq "") { $name = "unknown"; }

	my $bytes = Exim::expand_string('$message_size');

	if ($bytes == -1) { return; }

	my $work_path = $ENV{'PWD'};

	open (BYTES, ">>/etc/virtual/usage/$name.bytes");
	print BYTES "$bytes=$data&path=$work_path\n";
	close(BYTES);
	chmod (0660, "/etc/virtual/usage/$name.bytes");
}


sub get_b64_string
{
	my ($str) = @_;
	
	eval
	{
		require MIME::Base64;
		MIME::Base64->import();
	};

	unless($@)
	{
		my $enc = MIME::Base64::encode_base64($str);
		# an evil newline is added. get rid of it.
		$enc =~ s/\n//;
		return $enc;
	}

	return $str;
}

sub log_str
{
	my ($str) = @_;

	open (LOG, ">> /tmp/test.txt");

	print LOG $str;

	close(LOG);
}
