Accounting.pm 24.5 KB
Newer Older
Nigel Kukard's avatar
Nigel Kukard committed
1
# Accounting module
Wepongo's avatar
Wepongo committed
2
# Copyright (C) 2015, AllWorldIT
Nigel Kukard's avatar
Nigel Kukard committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
# Copyright (C) 2008, LinuxRulz
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


package cbp::modules::Accounting;

use strict;
use warnings;

use POSIX qw( ceil strftime );

use cbp::logging;
Nigel Kukard's avatar
Nigel Kukard committed
28
use awitpt::db::dblayer;
Nigel Kukard's avatar
Nigel Kukard committed
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
use cbp::system;
use cbp::protocols;


# User plugin info
our $pluginInfo = {
	name 			=> "Accounting Plugin",
	priority		=> 10,
	init		 	=> \&init,
	request_process	=> \&check,
#	cleanup		 	=> \&cleanup,
};


# Module configuration
my %config;


# Create a child specific context
sub init {
	my $server = shift;
	my $inifile = $server->{'inifile'};


	# Defaults
	$config{'enable'} = 0;

	# Parse in config
	if (defined($inifile->{'accounting'})) {
		foreach my $key (keys %{$inifile->{'accounting'}}) {
			$config{$key} = $inifile->{'accounting'}->{$key};
		}
	}

	# Check if enabled
	if ($config{'enable'} =~ /^\s*(y|yes|1|on)\s*$/i) {
		$server->log(LOG_NOTICE,"  => Accounting: enabled");
		$config{'enable'} = 1;
		# Enable tracking, we need this to recipients for the message in END-OF-DATA
		$server->{'config'}{'track_sessions'} = 1;
69 70
	} else {
		$server->log(LOG_NOTICE,"  => Accounting: disabled");
Nigel Kukard's avatar
Nigel Kukard committed
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
	}
}




# Do our checks
sub check {
	my ($server,$sessionData) = @_;
	

	# If we not enabled, don't do anything
	return CBP_SKIP if (!$config{'enable'});

	# We only valid in the RCPT and EOM state
	return CBP_SKIP if (!defined($sessionData->{'ProtocolState'}));

	# Check valid state & that we have our policy data
	return CBP_SKIP if (!
				(
					($sessionData->{'ProtocolState'} eq "RCPT" && defined($sessionData->{'Policy'})) ||
					($sessionData->{'ProtocolState'} eq "END-OF-MESSAGE" && defined($sessionData->{'_Recipient_To_Policy'}))
				)
			);

	# Our verdict and data
	my ($verdict,$verdict_data);

	my $now = time();


	#
	# RCPT state
	#   If we in this state we increase the RCPT counters for each key we have
	#   we only do this if we not going to reject the message. We also check if
	#   we have exceeded our size limit. The Size limit is updated in the EOM
	#   stage
	#
	if ($sessionData->{'ProtocolState'} eq "RCPT") {

		# Key tracking list, if hasExceeded is not undef, it will contain the msg
		my %newCounters;  # Indexed by AccountingID
		my @trackingList;
		my $hasExceeded;
		my $exceededAtrack;

		# Loop with priorities, low to high
118
POLICY:		foreach my $priority (sort {$a <=> $b} keys %{$sessionData->{'Policy'}}) {
Nigel Kukard's avatar
Nigel Kukard committed
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167

			# Last if we've exceeded
			last if ($hasExceeded);


			# Loop with each policyID
			foreach my $policyID (@{$sessionData->{'Policy'}->{$priority}}) {

				# Last if we've exceeded
				last if ($hasExceeded);

				# Get accounting records
				my $accountings = getAccountings($server,$policyID);
				# Check if we got a accountings or not
				if (ref($accountings) ne "ARRAY") {
					return $server->protocol_response(PROTO_DB_ERROR);
				}
			
				# Loop with accounting records
				foreach my $accounting (@{$accountings}) {

					# Last if we've exceeded
					last if ($hasExceeded);

					# Grab tracking key
					my $trackKey = getTrackKey($server,$accounting,$sessionData);
					if (!defined($trackKey)) {
						$server->log(LOG_ERR,"[ACCOUNTING] No tracking key found for accounting ID '".$accounting->{'ID'}."'");
						return $server->protocol_response(PROTO_DATA_ERROR);
					}

					# Grab period key
					my $periodKey = getPeriodKey($accounting->{'AccountingPeriod'});
					if (!defined($periodKey)) {
						$server->log(LOG_ERR,"[ACCOUNTING] No period key found for accounting ID '".$accounting->{'ID'}."'");
						return $server->protocol_response(PROTO_DATA_ERROR);
					}

					# Get quota tracking info
					my $atrack = getTrackingInfo($server,$accounting->{'ID'},$trackKey,$periodKey);
					# Check if we got tracking info or not
					if (defined($atrack) && ref($atrack) ne "HASH") {
						return $server->protocol_response(PROTO_DB_ERROR);
					}

					# Check if we have a queue tracking item
					if (defined($atrack)) {
						# Make sure we have initialized the counters
						if (!defined($newCounters{$atrack->{'AccountingID'}})) {
168 169
							$newCounters{$atrack->{'AccountingID'}}->{'MessageCount'} = 0;
							$newCounters{$atrack->{'AccountingID'}}->{'MessageCumulativeSize'} = 0;
Nigel Kukard's avatar
Nigel Kukard committed
170 171 172
						}
	
						# Check for violation
173 174 175 176 177
						if (defined($accounting->{'MessageCountLimit'}) && $accounting->{'MessageCountLimit'} ne "") {
							# If its a valid message count limit
							if ($accounting->{'MessageCountLimit'} =~ /^[0-9]+$/) {
								# Check if we exceeded
								if ($accounting->{'MessageCountLimit'} > 0 && (
178 179
									# Check current counter
									$atrack->{'MessageCount'} >= $accounting->{'MessageCountLimit'}
180 181 182 183 184 185
								)) {
									$hasExceeded = "Policy rejection; Message count exceeded";
								}
							} else {
								$server->log(LOG_ERR,"[ACCOUNTING] The value for MessageCountLimit is invalid for accounting ID  '".$accounting->{'ID'}."'");
							}
Nigel Kukard's avatar
Nigel Kukard committed
186 187 188 189
						}

						# If we've not exceeded, check the message cumulative size
						if (!$hasExceeded) {
190 191 192
							# Bump up count
							$newCounters{$atrack->{'AccountingID'}}->{'MessageCount'}++;

Nigel Kukard's avatar
Nigel Kukard committed
193 194
							# Check for cumulative size violation
							if (defined($accounting->{'MessageCumulativeSizeLimit'}) && 
195 196 197 198 199 200 201 202 203 204
									$accounting->{'MessageCumulativeSizeLimit'} ne "") {
								# If its a valid message count limit
								if ($accounting->{'MessageCumulativeSizeLimit'} =~ /^[0-9]+$/) {
									if ($accounting->{'MessageCumulativeSizeLimit'} > 0 &&
											$atrack->{'MessageCumulativeSize'} > $accounting->{'MessageCumulativeSizeLimit'}) {
										$hasExceeded = "Policy rejection; Cumulative message size exceeded";
									}
								} else {
									$server->log(LOG_ERR,"[ACCOUNTING] The value for MessageCumulativeSizeLimit is invalid for accounting ID  '".$accounting->{'ID'}."'");
								}
Nigel Kukard's avatar
Nigel Kukard committed
205 206
							}
						}
207

Nigel Kukard's avatar
Nigel Kukard committed
208 209 210 211 212 213
					} else {
						$atrack->{'AccountingID'} = $accounting->{'ID'};
						$atrack->{'TrackKey'} = $trackKey;
						$atrack->{'PeriodKey'} = $periodKey;
						$atrack->{'MessageCount'} = 0;
						$atrack->{'MessageCumulativeSize'} = 0;
214
					
Nigel Kukard's avatar
Nigel Kukard committed
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
						# Make sure we have initialized the counters
						if (!defined($newCounters{$atrack->{'AccountingID'}})) {
							$newCounters{$atrack->{'AccountingID'}}->{'MessageCount'} = $atrack->{'MessageCount'};
							$newCounters{$atrack->{'AccountingID'}}->{'MessageCumulativeSize'} = $atrack->{'MessageCumulativeSize'};
						}
							
						# Bump up limit
						$newCounters{$atrack->{'AccountingID'}}->{'MessageCount'}++;
					}
						
					# Setup some stuff we need for logging
					$atrack->{'DBTrackKey'} = $trackKey;
					$atrack->{'DBPeriodKey'} = $periodKey;
					$atrack->{'MessageCountLimit'} = $accounting->{'MessageCountLimit'};
					$atrack->{'MessageCumulativeSizeLimit'} = $accounting->{'MessageCumulativeSizeLimit'};
					$atrack->{'PolicyID'} = $policyID;
					$atrack->{'Verdict'} = $accounting->{'Verdict'};
					$atrack->{'VerdictData'} = $accounting->{'Data'};

					# If we've exceeded setup the atrack which was exceeded
					if ($hasExceeded) {
						$exceededAtrack = $atrack;
					}

					# Save accounting tracking info
					push(@trackingList,$atrack);
241 242 243 244 245 246 247

					# Check if this is the last accounting
					if (defined($accounting->{'LastAccounting'}) && $accounting->{'LastAccounting'} eq "1") {
						last POLICY;
					}
				} # foreach my $accounting (@{$accountings})
			} # foreach my $policyID (@{$sessionData->{'Policy'}->{$priority}})
Nigel Kukard's avatar
Nigel Kukard committed
248 249 250 251 252 253 254 255
		} # foreach my $priority (sort {$a <=> $b} keys %{$sessionData->{'Policy'}})

		# If we have not exceeded, update
		if (!$hasExceeded) {

			# Loop with tracking ID's and update
			foreach my $atrack (@trackingList) {
				# Percent used
256 257
				my $pCountUsage = "/-";
				my $pCumulativeSizeUsage = "/-"; 
Nigel Kukard's avatar
Nigel Kukard committed
258 259
				# If we have additional limits, add to the usage string
				if (defined($atrack->{'MessageCountLimit'}) && $atrack->{'MessageCountLimit'} > 0) {
260
					$pCountUsage = $newCounters{$atrack->{'AccountingID'}}->{'MessageCount'} + $atrack->{'MessageCount'};
Nigel Kukard's avatar
Nigel Kukard committed
261 262
					$pCountUsage .= sprintf('/%s (%.1f%%)',
							$atrack->{'MessageCountLimit'},
263 264
							( ($newCounters{$atrack->{'AccountingID'}}->{'MessageCount'} + $atrack->{'MessageCount'}) /
									$atrack->{'MessageCountLimit'} ) * 100
Nigel Kukard's avatar
Nigel Kukard committed
265 266 267
					);
				}
				if (defined($atrack->{'MessageCumulativeSizeLimit'}) && $atrack->{'MessageCumulativeSizeLimit'} > 0) {
268
					$pCumulativeSizeUsage =  $newCounters{$atrack->{'AccountingID'}}->{'MessageCumulativeSize'} + $atrack->{'MessageCumulativeSize'};
Nigel Kukard's avatar
Nigel Kukard committed
269 270
					$pCumulativeSizeUsage .= sprintf('/%s (%.1f%%)',
							$atrack->{'MessageCumulativeSizeLimit'},
271
							( ($newCounters{$atrack->{'AccountingID'}}->{'MessageCumulativeSize'} + $atrack->{'MessageCumulativeSize'}) / 
Nigel Kukard's avatar
Nigel Kukard committed
272 273 274
								$atrack->{'MessageCumulativeSizeLimit'} ) * 100
					);
				}
Nigel Kukard's avatar
Nigel Kukard committed
275 276

				# Update database
Nigel Kukard's avatar
Nigel Kukard committed
277
				my $sth = DBDo('
Nigel Kukard's avatar
Nigel Kukard committed
278
					UPDATE 
Nigel Kukard's avatar
Nigel Kukard committed
279
						@TP@accounting_tracking
Nigel Kukard's avatar
Nigel Kukard committed
280
					SET
281 282
						MessageCount = MessageCount + ?,
						MessageCumulativeSize = MessageCumulativeSize + ?,
Nigel Kukard's avatar
Nigel Kukard committed
283
						LastUpdate = ?
Nigel Kukard's avatar
Nigel Kukard committed
284
					WHERE
Nigel Kukard's avatar
Nigel Kukard committed
285 286 287 288 289 290 291
						AccountingID = ?
						AND TrackKey = ?
						AND PeriodKey = ?
					',
					$newCounters{$atrack->{'AccountingID'}}->{'MessageCount'},$newCounters{$atrack->{'AccountingID'}}->{'MessageCumulativeSize'},
					$now,$atrack->{'AccountingID'},$atrack->{'TrackKey'},$atrack->{'PeriodKey'}
				);
Nigel Kukard's avatar
Nigel Kukard committed
292
				if (!$sth) {
Nigel Kukard's avatar
Nigel Kukard committed
293
					$server->log(LOG_ERR,"[ACCOUNTING] Failed to update accounting_tracking item: ".awitpt::db::dblayer::Error());
Nigel Kukard's avatar
Nigel Kukard committed
294 295 296 297 298 299
					return $server->protocol_response(PROTO_DB_ERROR);
				}
				
				# If nothing updated, then insert our record
				if ($sth eq "0E0") {
					# Insert into database
Nigel Kukard's avatar
Nigel Kukard committed
300 301
					my $sth = DBDo('
						INSERT INTO @TP@accounting_tracking
Nigel Kukard's avatar
Nigel Kukard committed
302 303
							(AccountingID,TrackKey,PeriodKey,MessageCount,MessageCumulativeSize,LastUpdate)
						VALUES
Nigel Kukard's avatar
Nigel Kukard committed
304 305 306 307 308 309 310
							(?,?,?,?,?,?)
						',
						$atrack->{'AccountingID'},$atrack->{'TrackKey'},$atrack->{'PeriodKey'},
						$newCounters{$atrack->{'AccountingID'}}->{'MessageCount'},
						$newCounters{$atrack->{'AccountingID'}}->{'MessageCumulativeSize'},
						$atrack->{'LastUpdate'}
					);
Nigel Kukard's avatar
Nigel Kukard committed
311
					if (!$sth) {
Nigel Kukard's avatar
Nigel Kukard committed
312
						$server->log(LOG_ERR,"[ACCOUNTING] Failed to insert accounting_tracking item: ".awitpt::db::dblayer::Error());
Nigel Kukard's avatar
Nigel Kukard committed
313 314 315 316 317
						return $server->protocol_response(PROTO_DB_ERROR);
					}
					
					# Log create to mail log
					$server->maillog("module=Accounting, mode=create, host=%s, helo=%s, from=%s, to=%s, reason=accounting_create, policy=%s, accounting=%s, "
Nigel Kukard's avatar
Nigel Kukard committed
318
								."track=%s, period=%s, count=%s, size=%s",
Nigel Kukard's avatar
Nigel Kukard committed
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
							$sessionData->{'ClientAddress'},
							$sessionData->{'Helo'},
							$sessionData->{'Sender'},
							$sessionData->{'Recipient'},
							$atrack->{'PolicyID'},
							$atrack->{'AccountingID'},
							$atrack->{'DBTrackKey'},
							$atrack->{'DBPeriodKey'},
							$pCountUsage,
							$pCumulativeSizeUsage);


				# If we updated ...
				} else {
					# Log update to mail log
334
					$server->maillog("module=Accounting, mode=update, host=%s, helo=%s, from=%s, to=%s, reason=accounting_update, policy=%s, accounting=%s, "
Nigel Kukard's avatar
Nigel Kukard committed
335
								."track=%s, period=%s, count=%s, size=%s",
Nigel Kukard's avatar
Nigel Kukard committed
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
							$sessionData->{'ClientAddress'},
							$sessionData->{'Helo'},
							$sessionData->{'Sender'},
							$sessionData->{'Recipient'},
							$atrack->{'PolicyID'},
							$atrack->{'AccountingID'},
							$atrack->{'DBTrackKey'},
							$atrack->{'DBPeriodKey'},
							$pCountUsage,
							$pCumulativeSizeUsage);
				}
					

				# Remove limit
				delete($newCounters{$atrack->{'AccountingID'}});
			}

		# If we have exceeded, set verdict
		} else {
			# Percent used
356 357 358
			my $pCountUsage = $newCounters{$exceededAtrack->{'AccountingID'}}->{'MessageCount'} + $exceededAtrack->{'MessageCount'};
			my $pCumulativeSizeUsage =  $newCounters{$exceededAtrack->{'AccountingID'}}->{'MessageCumulativeSize'} +
				$exceededAtrack->{'MessageCumulativeSize'};
Nigel Kukard's avatar
Nigel Kukard committed
359 360 361 362
			# If we have additional limits, add to the usage string
			if (defined($exceededAtrack->{'MessageCountLimit'}) && $exceededAtrack->{'MessageCountLimit'} > 0) {
				$pCountUsage .= sprintf('/%s (%.1f%%)',
						$exceededAtrack->{'MessageCountLimit'},
363 364
						( ($newCounters{$exceededAtrack->{'AccountingID'}}->{'MessageCount'} +
								$exceededAtrack->{'MessageCount'}) / $exceededAtrack->{'MessageCountLimit'} ) * 100
Nigel Kukard's avatar
Nigel Kukard committed
365 366 367 368 369 370 371
				);
			} else {
				$pCountUsage .= "/-";
			}
			if (defined($exceededAtrack->{'MessageCumulativeSizeLimit'}) && $exceededAtrack->{'MessageCumulativeSizeLimit'} > 0) {
				$pCumulativeSizeUsage .= sprintf('/%s (%.1f%%)',
						$exceededAtrack->{'MessageCumulativeSizeLimit'},
372 373
						( ($newCounters{$exceededAtrack->{'AccountingID'}}->{'MessageCumulativeSize'} +
								$exceededAtrack->{'MessageCumulativeSize'}) / $exceededAtrack->{'MessageCumulativeSizeLimit'} ) * 100
Nigel Kukard's avatar
Nigel Kukard committed
374 375 376 377
				);
			} else {
				$pCumulativeSizeUsage .= "/-";
			}
Nigel Kukard's avatar
Nigel Kukard committed
378 379 380

			# Log rejection to mail log
			$server->maillog("module=Accounting, action=%s, host=%s, helo=%s, from=%s, to=%s, reason=accounting_match, policy=%s, accounting=%s, "
Nigel Kukard's avatar
Nigel Kukard committed
381
						."track=%s, period=%s, count=%s, size=%s",
Nigel Kukard's avatar
Nigel Kukard committed
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
					lc($exceededAtrack->{'Verdict'}),
					$sessionData->{'ClientAddress'},
					$sessionData->{'Helo'},
					$sessionData->{'Sender'},
					$sessionData->{'Recipient'},
					$exceededAtrack->{'PolicyID'},
					$exceededAtrack->{'AccountingID'},
					$exceededAtrack->{'DBTrackKey'},
					$exceededAtrack->{'DBPeriodKey'},
					$pCountUsage,
					$pCumulativeSizeUsage);

			$verdict = $exceededAtrack->{'Verdict'};
			$verdict_data = (defined($exceededAtrack->{'VerdictData'}) && $exceededAtrack->{'VerdictData'} ne "") 
					? $exceededAtrack->{'VerdictData'} : $hasExceeded;
		}

	#
	# END-OF-MESSAGE state
	#   The Size accounting is updated in this state
	#
	} elsif ($sessionData->{'ProtocolState'} eq "END-OF-MESSAGE") {
		
		# Check if we have recipient to policy mappings
		if (!defined($sessionData->{'_Recipient_To_Policy'})) {
			return CBP_SKIP;
		}

		# Loop with email addies
		foreach my $emailAddy (keys %{$sessionData->{'_Recipient_To_Policy'}}) {

			# Loop with priorities, low to high
414
POLICY:			foreach my $priority (sort {$a <=> $b} keys %{$sessionData->{'_Recipient_To_Policy'}{$emailAddy}}) {
Nigel Kukard's avatar
Nigel Kukard committed
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453

				# Loop with each policyID
				foreach my $policyID (@{$sessionData->{'_Recipient_To_Policy'}{$emailAddy}{$priority}}) {

					# Get accounting records
					my $accountings = getAccountings($server,$policyID);
					# Check if we got a accountings or not
					if (ref($accountings) ne "ARRAY") {
						return $server->protocol_response(PROTO_DB_ERROR);
					}
			
					# Loop with accountings
					foreach my $accounting (@{$accountings}) {

						# HACK: Fool getTrackKey into thinking we actually do have a recipient
						$sessionData->{'Recipient'} = $emailAddy;
	
						# Grab tracking keys
						my $trackKey = getTrackKey($server,$accounting,$sessionData);
						if (!defined($trackKey)) {
							$server->log(LOG_WARN,"[ACCOUNTING] No key found for accounting ID '".$accounting->{'ID'}."'");
							return $server->protocol_response(PROTO_DATA_ERROR);
						}
	
						# Grab period key
						my $periodKey = getPeriodKey($accounting->{'AccountingPeriod'});
						if (!defined($periodKey)) {
							$server->log(LOG_ERR,"[ACCOUNTING] No period key found for accounting ID '".$accounting->{'ID'}."'");
							return $server->protocol_response(PROTO_DATA_ERROR);
						}

						# Get account tracking info
						my $atrack = getTrackingInfo($server,$accounting->{'ID'},$trackKey,$periodKey);
						# Check if we got tracking info or not
						if (ref($atrack) ne "HASH") {
							next; # If not just carry on?
						}

						# Update database
Nigel Kukard's avatar
Nigel Kukard committed
454
						my $sth = DBDo('
Nigel Kukard's avatar
Nigel Kukard committed
455
							UPDATE 
Nigel Kukard's avatar
Nigel Kukard committed
456
								@TP@accounting_tracking
Nigel Kukard's avatar
Nigel Kukard committed
457
							SET
458
								MessageCumulativeSize = MessageCumulativeSize + ?,
Nigel Kukard's avatar
Nigel Kukard committed
459
								LastUpdate = ?
Nigel Kukard's avatar
Nigel Kukard committed
460
							WHERE
Nigel Kukard's avatar
Nigel Kukard committed
461 462 463 464
								AccountingID = ?
								AND TrackKey = ?
								AND PeriodKey = ?
							',
465
							$sessionData->{'Size'},$now,$atrack->{'AccountingID'},$atrack->{'TrackKey'},
Nigel Kukard's avatar
Nigel Kukard committed
466 467
							$atrack->{'PeriodKey'}
						);
Nigel Kukard's avatar
Nigel Kukard committed
468
						if (!$sth) {
Nigel Kukard's avatar
Nigel Kukard committed
469
							$server->log(LOG_ERR,"[ACCOUNTING] Failed to update accounting_tracking item: ".awitpt::db::dblayer::Error());
Nigel Kukard's avatar
Nigel Kukard committed
470 471 472 473
							return $server->protocol_response(PROTO_DB_ERROR);
						}

						# Percent used
Nigel Kukard's avatar
Nigel Kukard committed
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
						my $pCountUsage = $atrack->{'MessageCount'};
						my $pCumulativeSizeUsage =  $atrack->{'MessageCumulativeSize'};
						# If we have additional limits, add to the usage string
						if (defined($accounting->{'MessageCountLimit'}) && $accounting->{'MessageCountLimit'} > 0) {
							$pCountUsage .= sprintf('/%s (%.1f%%)',
									$accounting->{'MessageCountLimit'},
									( $atrack->{'MessageCount'} / $accounting->{'MessageCountLimit'} ) * 100
							);
						} else {
							$pCountUsage .= "/-";
						}
						if (defined($accounting->{'MessageCumulativeSizeLimit'}) && $accounting->{'MessageCumulativeSizeLimit'} > 0) {
							$pCumulativeSizeUsage .= sprintf('/%s (%.1f%%)',
									$accounting->{'MessageCumulativeSizeLimit'},
									( $atrack->{'MessageCumulativeSize'} / 
										$accounting->{'MessageCumulativeSizeLimit'} ) * 100
							);
						} else {
							$pCumulativeSizeUsage .= "/-";
						}
Nigel Kukard's avatar
Nigel Kukard committed
494 495 496

						# Log update to mail log
						$server->maillog("module=Accounting, mode=update, host=%s, helo=%s, from=%s, to=%s, reason=accounting_update, policy=%s, accounting=%s, "
Nigel Kukard's avatar
Nigel Kukard committed
497
									."track=%s, period=%s, count=%s, size=%s",
Nigel Kukard's avatar
Nigel Kukard committed
498 499 500 501 502 503 504 505 506 507
								$sessionData->{'ClientAddress'},
								$sessionData->{'Helo'},
								$sessionData->{'Sender'},
								$emailAddy,
								$policyID,
								$accounting->{'ID'},
								$trackKey,
								$periodKey,
								$pCountUsage,
								$pCumulativeSizeUsage);
508 509 510 511
						# Check if this is the last accounting
						if (defined($accounting->{'LastAccounting'}) && $accounting->{'LastAccounting'} eq "1") {
							last POLICY;
						}
Nigel Kukard's avatar
Nigel Kukard committed
512 513 514 515 516 517 518 519 520
					} # foreach my $accounting (@{$accountings})
				} # foreach my $policyID (@{$sessionData->{'_Recipient_To_Policy'}{$emailAddy}{$priority}})
			} # foreach my $priority (sort {$a <=> $b} keys %{$sessionData->{'_Recipient_To_Policy'}{$emailAddy}})
		} # foreach my $emailAddy (keys %{$sessionData->{'_Recipient_To_Policy'}})

			
	}
	
	# Setup result
521 522 523
	if (!defined($verdict)) {
		return CBP_CONTINUE;
 	} elsif ($verdict eq "") {
Nigel Kukard's avatar
Nigel Kukard committed
524
		$server->maillog("module=Accounting, action=none, host=%s, helo=%s, from=%s, to=%s, reason=no_verdict",
Nigel Kukard's avatar
Nigel Kukard committed
525 526 527 528 529
				$sessionData->{'ClientAddress'},
				$sessionData->{'Helo'},
				$sessionData->{'Sender'},
				$sessionData->{'Recipient'});
		return CBP_CONTINUE;
530
	} elsif ($verdict =~ /^defer$/i) {
Nigel Kukard's avatar
Nigel Kukard committed
531
		return $server->protocol_response(PROTO_DEFER,$verdict_data);
532
	} elsif ($verdict =~ /^hold$/i) {
Nigel Kukard's avatar
Nigel Kukard committed
533 534 535 536 537 538 539 540 541 542 543
		return $server->protocol_response(PROTO_HOLD,$verdict_data);
	} elsif ($verdict =~ /^reject$/i) {
		return $server->protocol_response(PROTO_REJECT,$verdict_data);
	} elsif ($verdict =~ /^discard$/i) {
		return $server->protocol_response(PROTO_DISCARD,$verdict_data);
	} elsif ($verdict =~ /^filter$/i) {
		return $server->protocol_response(PROTO_FILTER,$verdict_data);
	} elsif ($verdict =~ /^redirect$/i) {
		return $server->protocol_response(PROTO_REDIRECT,$verdict_data);
	} else {
		$server->log(LOG_ERR,"[ACCOUNTING] Unknown Verdict specification in access control '$verdict'");
544 545 546 547 548
		$server->maillog("module=Accounting, action=none, host=%s, helo=%s, from=%s, to=%s, reason=invalid_verdict",
				$sessionData->{'ClientAddress'},
				$sessionData->{'Helo'},
				$sessionData->{'Sender'},
				$sessionData->{'Recipient'});
Nigel Kukard's avatar
Nigel Kukard committed
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580
		return $server->protocol_response(PROTO_DATA_ERROR);
	}
}



# Get key from spec and SASL username
sub getSASLUsernameKey
{
	my ($spec,$username) = @_;

	my $key;

	# Very basic, for blank SASL username, just use '-'
	if (!defined($username)) {
		$key = "-";
	} else {
		$key = $username;
	}

	return $key;
}



# Get key from spec and email addy
sub getEmailKey
{
	my ($spec,$addy) = @_;

	my $key;

581 582 583 584 585 586

	# Short-circuit <>
	if ($addy eq '') {
		return "@";
	}

Nigel Kukard's avatar
Nigel Kukard committed
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611
	# We need to track the sender
	if ($spec eq 'user@domain') {
		$key = $addy;

	} elsif ($spec eq 'user@') {
		($key) = ( $addy =~ /^([^@]+@)/ );

	} elsif ($spec eq '@domain') {
		($key) = ( $addy =~ /^(?:[^@]+)(@.*)/ );
	}

	return $key;
}



# Get accounting records from policyID
sub getAccountings
{
	my ($server,$policyID) = @_;


	my @res;

	# Grab quota data
Nigel Kukard's avatar
Nigel Kukard committed
612
	my $sth = DBSelect('
Nigel Kukard's avatar
Nigel Kukard committed
613 614 615 616 617 618 619
		SELECT
			ID,
			Track,
			AccountingPeriod, 
			MessageCountLimit,
			MessageCumulativeSizeLimit,
			Verdict,
620 621
			Data,
			LastAccounting
Nigel Kukard's avatar
Nigel Kukard committed
622
		FROM
Nigel Kukard's avatar
Nigel Kukard committed
623
			@TP@accounting
Nigel Kukard's avatar
Nigel Kukard committed
624
		WHERE
Nigel Kukard's avatar
Nigel Kukard committed
625
			PolicyID = ?
Nigel Kukard's avatar
Nigel Kukard committed
626
			AND Disabled = 0
Nigel Kukard's avatar
Nigel Kukard committed
627 628 629
		',
		$policyID
	);
Nigel Kukard's avatar
Nigel Kukard committed
630
	if (!$sth) {
Nigel Kukard's avatar
Nigel Kukard committed
631
		$server->log(LOG_ERR,"Failed to get accounting data: ".awitpt::db::dblayer::Error());
Nigel Kukard's avatar
Nigel Kukard committed
632 633
		return -1;
	}
634 635 636 637 638
	while (my $quota = hashifyLCtoMC($sth->fetchrow_hashref(),
			qw( ID Track AccountingPeriod MessageCountLimit MessageCumulativeSizeLimit Verdict Data LastAccounting )
	)) {

		push(@res, $quota);
Nigel Kukard's avatar
Nigel Kukard committed
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666
	}

	return \@res;
}



# Get key from session
sub getTrackKey
{
	my ($server,$accounting,$sessionData) = @_;


	my $res;

	# Split off method and splec
	my ($method,$spec) = ($accounting->{'Track'} =~ /^([^:]+)(?::(\S+))?/);
	
	# Lowercase method & spec
	$method = lc($method);
	$spec = lc($spec) if (defined($spec));

	# Track entire policy
	if ($method eq "policy") {
		$res = "Policy";

	# Check TrackSenderIP
	} elsif ($method eq "senderip") {
Nigel Kukard's avatar
Nigel Kukard committed
667
		my $key = getIPKey($spec,$sessionData->{'_ClientAddress'});
Nigel Kukard's avatar
Nigel Kukard committed
668 669 670 671 672 673 674 675 676 677 678

		# Check for no key
		if (defined($key)) {
			$res = "SenderIP:$key";
		} else {
			$server->log(LOG_WARN,"[ACCOUNTING] Unknown key specification in TrackSenderIP");
		}


	# Check TrackSender
	} elsif ($method eq "sender") {
679 680 681 682 683 684 685
		# Check if the sender is blank (<>), it makes no sense at present to work out how its tracked, <> is <>
		my $key;
		if ($sessionData->{'Sender'} ne "") {
			$key = getEmailKey($spec,$sessionData->{'Sender'});
		} else {
			$key = "<>";
		}
Nigel Kukard's avatar
Nigel Kukard committed
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763
	
		# Check for no key
		if (defined($key)) {
			$res = "Sender:$key";
		} else {
			$server->log(LOG_WARN,"[ACCOUNTING] Unknown key specification in TrackSender");
		}


	# Check TrackSASLUsername
	} elsif ($method eq "saslusername") {
		my $key = getSASLUsernameKey($spec,$sessionData->{'SASLUsername'});
	
		# Check for no key
		if (defined($key)) {
			$res = "SASLUsername:$key";
		} else {
			$server->log(LOG_WARN,"[ACCOUNTING] Unknown key specification in TrackSASLUsername");
		}

	# Check TrackRecipient
	} elsif ($method eq "recipient") {
		my $key = getEmailKey($spec,$sessionData->{'Recipient'});
	
		# Check for no key
		if (defined($key)) {
			$res = "Recipient:$key";
		} else {
			$server->log(LOG_WARN,"[ACCOUNTING] Unknown key specification in TrackRecipient");
		}
	
	# Fall-through to catch invalid specs
	} else {
		$server->log(LOG_WARN,"[ACCOUNTING] Invalid tracking specification '".$accounting->{'Track'}."'");
	}


	return $res;
}



# Get period key
sub getPeriodKey
{
	my $period = shift;


	my $key;

	# Pull timestamp
	my @timestamp = localtime();

	# Track per day
	if ($period eq "0") {
		$key = strftime("%Y-%m-%d",@timestamp);

	# Track per week
	} elsif ($period eq "1") {
		$key = strftime("%Y/%V",@timestamp);
	
	# Track per month
	} elsif ($period eq "2") {
		$key = strftime("%Y-%m",@timestamp);
	}

	return $key;
}



# Get tracking info
sub getTrackingInfo
{
	my ($server,$accountID,$trackKey,$periodKey) = @_;
	
	
	# Query accounting info
Nigel Kukard's avatar
Nigel Kukard committed
764
	my $sth = DBSelect('
Nigel Kukard's avatar
Nigel Kukard committed
765 766 767 768 769
		SELECT 
			AccountingID,
			TrackKey, PeriodKey,
			MessageCount, MessageCumulativeSize
		FROM
Nigel Kukard's avatar
Nigel Kukard committed
770
			@TP@accounting_tracking
Nigel Kukard's avatar
Nigel Kukard committed
771
		WHERE
Nigel Kukard's avatar
Nigel Kukard committed
772 773 774 775 776 777
			AccountingID = ?
			AND TrackKey = ?
			AND PeriodKey = ?
		',
		$accountID,$trackKey,$periodKey
	);
Nigel Kukard's avatar
Nigel Kukard committed
778
	if (!$sth) {
Nigel Kukard's avatar
Nigel Kukard committed
779
		$server->log(LOG_ERR,"[ACCOUNTING] Failed to query accounting_tracking: ".awitpt::db::dblayer::Error());
Nigel Kukard's avatar
Nigel Kukard committed
780 781
		return -1;
	}
782 783 784
	my $row = hashifyLCtoMC($sth->fetchrow_hashref(),
			qw( AccountingID TrackKey PeriodKey MessageCount MessageCumulativeSize )
	);
Nigel Kukard's avatar
Nigel Kukard committed
785 786
	DBFreeRes($sth);

787
	return $row;
Nigel Kukard's avatar
Nigel Kukard committed
788 789 790 791 792 793 794 795 796 797 798 799 800
}



## Cleanup function
#sub cleanup
#{
#	my ($server) = @_;
#
#	# Get 30-days ago time
#	my $lastMonth = time() - 2592000;
#
#	# Remove old tracking info from database
Nigel Kukard's avatar
Nigel Kukard committed
801
#	my $sth = DBDo('
Nigel Kukard's avatar
Nigel Kukard committed
802
#		DELETE FROM 
Nigel Kukard's avatar
Nigel Kukard committed
803
#			@TP@accounting_tracking
Nigel Kukard's avatar
Nigel Kukard committed
804
#		WHERE
Nigel Kukard's avatar
Nigel Kukard committed
805 806 807 808
#			LastUpdate < ?
#		',
#		$lastMonth
#	);
Nigel Kukard's avatar
Nigel Kukard committed
809
#	if (!$sth) {
Nigel Kukard's avatar
Nigel Kukard committed
810
#		$server->log(LOG_ERR,"[ACCOUNTING] Failed to remove old accounting tracking records: ".awitpt::db::dblayer::Error());
Nigel Kukard's avatar
Nigel Kukard committed
811
#	}
812
#	$server->log(LOG_INFO,"[ACCOUNTING] Removed ".( $sth ne "0E0" ? $sth : 0)." records from tracking table");
Nigel Kukard's avatar
Nigel Kukard committed
813 814 815 816 817 818
#}



1;
# vim: ts=4