Skip to content

Commit 27cee55

Browse files
committed
First prototype of logmail endpoint
1 parent 6c3edcf commit 27cee55

File tree

9 files changed

+256
-7
lines changed

9 files changed

+256
-7
lines changed

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ tests/ export-ignore
55
composer.json export-ignore
66
composer.lock export-ignore
77
docker-compose.yaml export-ignore
8-
phpstan.neon.dist export-ignore
8+
phpstan.dist.neon export-ignore
99
rector.php export-ignore
1010
phpunit.xml export-ignore

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010
/composer.lock
1111
/.php-cs-fixer.cache
1212
/.phpunit.cache
13-
/phpstan.neon
13+
/.phpstan-cache
14+
/phpstan.neon

MailScanner_perl_scripts/MailWatch.pm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ use File::Basename;
6363
my $dirname = dirname(__FILE__);
6464
require $dirname . '/MailWatchConf.pm';
6565
my $api_base_url = mailwatch_get_api_base_url();
66-
my $api_endpoint = $api_base_url . '/api/mailscanner.php';
66+
my $api_endpoint = $api_base_url . '/api/logmail.php';
6767
my $api_key = mailwatch_get_api_key();
6868
my $api_max_retries = mailwatch_get_api_max_retries();
6969
my $api_retry_delay = mailwatch_get_api_retry_delay();

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"php-parallel-lint/php-console-highlighter": "^1.0.0",
1515
"rector/rector": "^2",
1616
"phpunit/phpunit": "^10.5",
17-
"symfony/var-dumper": "^6|^7"
17+
"symfony/var-dumper": "^6|^7|^8"
1818
},
1919
"suggest": {
2020
"ext-ldap": "For LDAP support",

mailscanner/api/MailLogEntry.php

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
class MailLogEntry
4+
{
5+
public $timestamp;
6+
public $id;
7+
public $size;
8+
public $from;
9+
public $from_domain;
10+
public $to;
11+
public $to_domain;
12+
public $subject;
13+
public $clientip;
14+
public $archiveplaces;
15+
public $isspam;
16+
public $ishigh;
17+
public $issaspam;
18+
public $isrblspam;
19+
public $spamwhitelisted;
20+
public $spamblacklisted;
21+
public $sascore;
22+
public $spamreport;
23+
public $virusinfected;
24+
public $nameinfected;
25+
public $otherinfected;
26+
public $reports;
27+
public $ismcp;
28+
public $ishighmcp;
29+
public $issamcp;
30+
public $mcpwhitelisted;
31+
public $mcpblacklisted;
32+
public $mcpsascore;
33+
public $mcpreport;
34+
public $hostname;
35+
public $date;
36+
public $time;
37+
public $headers;
38+
public $quarantined;
39+
public $rblspamreport;
40+
public $token;
41+
public $messageid;
42+
43+
public function __construct($data)
44+
{
45+
$this->timestamp = $data['timestamp'] ?? null;
46+
$this->id = $data['id'] ?? null;
47+
$this->size = $data['size'] ?? null;
48+
$this->from = $data['from'] ?? null;
49+
$this->from_domain = $data['from_domain'] ?? null;
50+
$this->to = $data['to'] ?? null;
51+
$this->to_domain = $data['to_domain'] ?? null;
52+
$this->subject = $data['subject'] ?? null;
53+
$this->clientip = $data['clientip'] ?? null;
54+
$this->archiveplaces = $data['archiveplaces'] ?? null;
55+
$this->isspam = $data['isspam'] ?? 0;
56+
$this->ishigh = $data['ishigh'] ?? 0;
57+
$this->issaspam = $data['issaspam'] ?? 0;
58+
$this->isrblspam = $data['isrblspam'] ?? 0;
59+
$this->spamwhitelisted = $data['spamwhitelisted'] ?? 0;
60+
$this->spamblacklisted = $data['spamblacklisted'] ?? 0;
61+
$this->sascore = $data['sascore'] ?? 0.00;
62+
$this->spamreport = $data['spamreport'] ?? '';
63+
$this->virusinfected = $data['virusinfected'] ?? 0;
64+
$this->nameinfected = $data['nameinfected'] ?? 0;
65+
$this->otherinfected = $data['otherinfected'] ?? 0;
66+
$this->reports = $data['reports'] ?? '';
67+
$this->ismcp = $data['ismcp'] ?? 0;
68+
$this->ishighmcp = $data['ishighmcp'] ?? 0;
69+
$this->issamcp = $data['issamcp'] ?? 0;
70+
$this->mcpwhitelisted = $data['mcpwhitelisted'] ?? 0;
71+
$this->mcpblacklisted = $data['mcpblacklisted'] ?? 0;
72+
$this->mcpsascore = $data['mcpsascore'] ?? 0.00;
73+
$this->mcpreport = $data['mcpreport'] ?? '';
74+
$this->hostname = $data['hostname'] ?? '';
75+
$this->date = $data['date'] ?? null;
76+
$this->time = $data['time'] ?? null;
77+
$this->headers = $data['headers'] ?? '';
78+
$this->quarantined = $data['quarantined'] ?? 0;
79+
$this->rblspamreport = $data['rblspamreport'] ?? '';
80+
$this->token = $data['token'] ?? '';
81+
$this->messageid = $data['messageid'] ?? '';
82+
}
83+
84+
/**
85+
* @return bool
86+
*/
87+
public function isValid()
88+
{
89+
if (
90+
empty($this->timestamp)
91+
|| empty($this->id)
92+
|| empty($this->size)
93+
|| empty($this->from)
94+
|| empty($this->to)
95+
|| empty($this->subject)
96+
|| empty($this->clientip)
97+
) {
98+
return false;
99+
}
100+
101+
return true;
102+
}
103+
}

mailscanner/api/logmail.php

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
// all response are JSON encoded
4+
header('Content-Type: application/json; charset=UTF-8');
5+
6+
require_once __DIR__ . '/../conf.php';
7+
8+
if (!defined('API_KEY')) {
9+
http_response_code(401); // Unauthorized
10+
echo json_encode(['error' => 'Unauthorized - Set an API KEY to use this API']);
11+
exit;
12+
}
13+
14+
require_once __DIR__ . '/../database.php';
15+
require_once __DIR__ . '/MailLogEntry.php';
16+
17+
/**
18+
* @param ?string $apiKey
19+
*
20+
* @return bool
21+
*/
22+
function isValidApiKey($apiKey)
23+
{
24+
if (null === $apiKey) {
25+
return false;
26+
}
27+
28+
if (!defined('API_KEY')) {
29+
return false;
30+
}
31+
32+
return API_KEY === $apiKey;
33+
}
34+
35+
/**
36+
* @return ?string
37+
*/
38+
function getApiKeyToken()
39+
{
40+
if (isset($_SERVER['HTTP_X_MAILWATCH_API_KEY'])) {
41+
return $_SERVER['HTTP_X_MAILWATCH_API_KEY'];
42+
}
43+
44+
return null;
45+
}
46+
47+
// Check if is POST request
48+
if ('POST' !== $_SERVER['REQUEST_METHOD']) {
49+
http_response_code(405); // Method Not Allowed
50+
echo json_encode(['error' => 'Method Not Allowed']);
51+
exit;
52+
}
53+
54+
// Verify API key
55+
$apiKeyToken = getApiKeyToken();
56+
if (null === $apiKeyToken || !isValidApiKey($apiKeyToken)) {
57+
http_response_code(401); // Unauthorized
58+
echo json_encode(['error' => 'Unauthorized']);
59+
exit;
60+
}
61+
62+
// Get JSON payload
63+
$json = file_get_contents('php://input');
64+
$data = json_decode($json, true);
65+
66+
if (JSON_ERROR_NONE !== json_last_error()) {
67+
http_response_code(400); // Bad Request
68+
echo json_encode(['error' => 'Invalid JSON']);
69+
exit;
70+
}
71+
$mailLogEntry = new MailLogEntry($data);
72+
if (!$mailLogEntry->isValid()) {
73+
http_response_code(400); // Bad Request
74+
echo json_encode(['error' => 'Invalid data']);
75+
exit;
76+
}
77+
78+
// Prepare insert query
79+
$dbLink = Database::connect(DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT);
80+
81+
$query = 'INSERT INTO maillog (timestamp, id, size, from_address, from_domain, to_address, to_domain, subject, clientip, archive, isspam, ishighspam, issaspam, isrblspam, spamwhitelisted, spamblacklisted, sascore, spamreport, virusinfected, nameinfected, otherinfected, report, ismcp, ishighmcp, issamcp, mcpwhitelisted, mcpblacklisted, mcpsascore, mcpreport, hostname, date, time, headers, quarantined, rblspamreport, token, messageid)
82+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
83+
84+
$stmt = $dbLink->prepare($query);
85+
if (!$stmt) {
86+
http_response_code(500); // Internal Server Error
87+
echo json_encode(['error' => 'Failed to prepare statement']);
88+
exit;
89+
}
90+
$stmt->bind_param(
91+
'ssisssssssiiiiiiiiiiiissiiiissssssss',
92+
$mailLogEntry->timestamp,
93+
$mailLogEntry->id,
94+
$mailLogEntry->size,
95+
$mailLogEntry->from,
96+
$mailLogEntry->from_domain,
97+
$mailLogEntry->to,
98+
$mailLogEntry->to_domain,
99+
$mailLogEntry->subject,
100+
$mailLogEntry->clientip,
101+
$mailLogEntry->archiveplaces,
102+
$mailLogEntry->isspam,
103+
$mailLogEntry->ishigh,
104+
$mailLogEntry->issaspam,
105+
$mailLogEntry->isrblspam,
106+
$mailLogEntry->spamwhitelisted,
107+
$mailLogEntry->spamblacklisted,
108+
$mailLogEntry->sascore,
109+
$mailLogEntry->spamreport,
110+
$mailLogEntry->virusinfected,
111+
$mailLogEntry->nameinfected,
112+
$mailLogEntry->otherinfected,
113+
$mailLogEntry->reports,
114+
$mailLogEntry->ismcp,
115+
$mailLogEntry->ishighmcp,
116+
$mailLogEntry->issamcp,
117+
$mailLogEntry->mcpwhitelisted,
118+
$mailLogEntry->mcpblacklisted,
119+
$mailLogEntry->mcpsascore,
120+
$mailLogEntry->mcpreport,
121+
$mailLogEntry->hostname,
122+
$mailLogEntry->date,
123+
$mailLogEntry->time,
124+
$mailLogEntry->headers,
125+
$mailLogEntry->quarantined,
126+
$mailLogEntry->rblspamreport,
127+
$mailLogEntry->token,
128+
$mailLogEntry->messageid
129+
);
130+
131+
if ($stmt->execute()) {
132+
http_response_code(201); // Created
133+
echo json_encode(['success' => 'Data inserted successfully']);
134+
} else {
135+
http_response_code(500); // Internal Server Error
136+
echo json_encode(['error' => 'Failed to insert data']);
137+
}
138+
139+
$stmt->close();
140+
$dbLink->close();

mailscanner/conf.php.example

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ define('SESSION_TIMEOUT', 600);
5555
// define('MAXMIND_LICENSE_KEY', 'mylicensekey');
5656
// define('MAXMIND_ACCOUNT_ID', 'myaccountid');
5757

58+
// API KEY - Allow autentication for MailScanner perl module
59+
// generate a secure unique key, like an UUID or a complex password
60+
// define('API_KEY', 'change-me-api-key');
61+
5862
// Database settings
5963
//
6064
// As this file might be publically readable. It might be very userful to
@@ -327,6 +331,6 @@ define('RESET_LINK_EXPIRE', 1);
327331
define('STATUSGRAPH_INTERVAL', 60);
328332

329333
//Allow domain admins to create/edit/delete other domain admins from the same domain (not recommended, only for backward compatibility)
330-
//define('ENABLE_SUPER_DOMAIN_ADMINS',true);
334+
//define('ENABLE_SUPER_DOMAIN_ADMINS', true);
331335
//Allow the username of domain admins and normal users to not be in mail format (not recommended, only for backward compatibility)
332-
//define('ALLOW_NO_USER_DOMAIN',true);
336+
//define('ALLOW_NO_USER_DOMAIN', true);

mailscanner/functions.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
}
8484
}
8585

86-
// Load the lang file or en if the spicified language is not available
86+
// Load the lang file or en if the specified language is not available
8787
if (!is_file(__DIR__ . '/languages/' . $langCode . '.php')) {
8888
$lang = require __DIR__ . '/languages/en.php';
8989
} else {

phpstan.dist.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ includes:
44
parameters:
55
level: 5
66
phpVersion: 80100
7+
tmpDir: %currentWorkingDirectory%/.phpstan-cache
78
paths:
89
- mailscanner
910
- tools

0 commit comments

Comments
 (0)