Created
May 7, 2023 17:13
-
-
Save ellisgl/ab632f0f19474093e9e7b9adc1412ff6 to your computer and use it in GitHub Desktop.
Simple SMTP server in PHP - Saves attachments - was used to get stuff from a network scanner.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
include_once('vendor/autoload.php'); | |
use ZBateson\MailMimeParser\Header\AddressHeader; | |
use ZBateson\MailMimeParser\MailMimeParser; | |
use ZBateson\MailMimeParser\Message\IMimePart; | |
const SMTP_RESP_220 = '220 localhost ESMTP PHP8.1 Ready'; | |
const SMTP_RESP_221 = '221 localhost ESMTP PHP8.1 Service closing transmission channel'; | |
const SMTP_RESP_250 = '250 OK'; | |
const SMTP_RESP_354 = '354 Start mail input; end with <CRLF>.<CRLF>'; | |
const SMTP_RESP_500 = '500 Syntax error, command unrecognized'; | |
const SMTP_RESP_501 = '501 Syntax error in parameters or arguments'; | |
/** | |
* Log to screen. | |
* | |
* @param string $message | |
* | |
* @return void | |
*/ | |
function logIt(string $message): void | |
{ | |
$dateTime = (new DateTime())->format(\DateTimeInterface::ATOM); | |
echo "[$dateTime] $message\n"; | |
} | |
// Start listening on port 25 | |
$socket = stream_socket_server('tcp://0.0.0.0:25', $errCode, $errStr); | |
if (!$socket) { | |
throw new RuntimeException($errStr, $errCode); | |
} | |
echo "Listening on port 25...\n"; | |
$parser = new MailMimeParser(); | |
while (1) { | |
// Accept incoming connections | |
$buffer = ''; | |
$client = @stream_socket_accept($socket, 5); | |
if ($client) { | |
logIt("ACCEPTED: " . stream_socket_get_name($client, true)); | |
// Send the greeting message. | |
fwrite($client, SMTP_RESP_220 . "\r\n"); | |
while ($data = fread($client, 1024)) { | |
$buffer .= $data; | |
$pos = strpos($buffer, "\r\n"); | |
if ($pos !== false) { | |
$line = substr($buffer, 0, $pos); | |
$buffer = substr($buffer, $pos + 2); | |
// Parse SMTP command | |
$command = strtoupper(substr($line, 0, 4)); | |
$args = trim(substr($line, 4)); | |
// Handle SMTP commands | |
switch ($command) { | |
case 'HELO': | |
case 'EHLO': | |
logit('GOT: HELO/EHLO'); | |
fwrite($client, SMTP_RESP_250 . "\r\n"); | |
break; | |
case 'MAIL': | |
logit('GOT: MAIL'); | |
if (preg_match('/^FROM:\s*(<[^>]+>)/i', $args, $matches)) { | |
fwrite($client, SMTP_RESP_250 . "\r\n"); | |
} else { | |
fwrite($client, SMTP_RESP_501 . "\r\n"); | |
} | |
break; | |
case 'RCPT': | |
logIt('GOT: RCPT'); | |
if (preg_match('/^TO:\s*(<[^>]+>)/i', $args, $matches)) { | |
fwrite($client, SMTP_RESP_250 . "\r\n"); | |
} else { | |
fwrite($client, SMTP_RESP_501 . "\r\n"); | |
} | |
break; | |
case 'DATA': | |
logIt('GOT: DATA'); | |
fwrite($client, SMTP_RESP_354 . "\r\n"); | |
// Create a temporary memory stream to hold the email data. | |
$tempStream = fopen('php://temp', 'r+'); | |
// Copy the email data from the client socket stream to the temporary memory stream. | |
stream_copy_to_stream($client, $tempStream); | |
// Reset the temporary memory stream pointer to the beginning. | |
rewind($tempStream); | |
// Parse the email data incrementally using the MailMimeParser. | |
$message = $parser->parse($tempStream, true); | |
echo "{$message->getHeader('Message-ID')}\n"; | |
echo "{$message->getHeader('Subject')}\n"; | |
echo "{$message->getHeader('From')}\n"; | |
/** @var AddressHeader | null $to */ | |
$to = $message->getHeader('To'); | |
foreach ($to->getAddresses() as $address) { | |
echo "TO: {$address->getName()} {$address->getEmail()}\n"; | |
} | |
// Save all attachments. | |
/** @var IMimePart[] $attachments */ | |
$attachments = $message->getAllAttachmentParts(); | |
$attachmentCount = count($attachments); | |
$loopIndex = 1; | |
foreach ($attachments as $index => $part) { | |
$filename = $part->getHeaderParameter( | |
'Content-Type', | |
'name', | |
$part->getHeaderParameter( | |
'Content-Disposition', | |
'filename', | |
'__unknown_file_name_' . $index, | |
), | |
); | |
$out = fopen(__DIR__ . '/data/' . $filename, 'w+'); | |
$str = $part->getBinaryContentResourceHandle(); | |
stream_copy_to_stream($str, $out); | |
fclose($str); | |
fclose($out); | |
echo "ATTACHMENT $loopIndex: $filename\n"; | |
++$loopIndex; | |
} | |
$response = "250 Message received\r\n"; | |
fwrite($client, $response); | |
logIt('DATA FINISHED'); | |
break; | |
case 'QUIT': | |
logIt('GOT: QUIT'); | |
fwrite($client, SMTP_RESP_221 . "\r\n"); | |
break; | |
default: | |
logIt("GOT: UNKNOWN [$line]"); | |
$response = SMTP_RESP_500 . "\r\n"; | |
fwrite($client, $response); | |
} | |
} | |
} | |
fclose($client); | |
logIt('Disconnected'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This was just a quick script. I'm working on making a full on library (that works) for this. This has spawned a couple other projects for other libraries for concepts that I haven't see before.