123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 |
- <?php
- /**
- * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
- // Code below modified from https://github.com/axllent/fake-smtp/blob/f0856f8a0df6f4ca5a573cf31428c09ebc5b9ea3/fakeSMTP.php,
- // which is under the MIT license (https://github.com/axllent/fake-smtp/blob/f0856f8a0df6f4ca5a573cf31428c09ebc5b9ea3/LICENSE)
- /**
- * fakeSMTP - A PHP / inetd fake smtp server.
- * Allows client<->server interaction
- * The comunication is based upon the SMPT standards defined in http://www.lesnikowski.com/mail/Rfc/rfc2821.txt
- */
- class fakeSMTP {
- public $logFile = false;
- public $serverHello = 'fakeSMTP ESMTP PHP Mail Server Ready';
- public function __construct($fd) {
- $this->mail = [];
- $this->mail['ipaddress'] = false;
- $this->mail['emailSender'] = '';
- $this->mail['emailRecipients'] = [];
- $this->mail['emailSubject'] = false;
- $this->mail['rawEmail'] = false;
- $this->mail['emailHeaders'] = false;
- $this->mail['emailBody'] = false;
- $this->fd = $fd;
- }
- public function receive() {
- $hasValidFrom = false;
- $hasValidTo = false;
- $receivingData = false;
- $header = true;
- $this->reply('220 '.$this->serverHello);
- $this->mail['ipaddress'] = $this->detectIP();
- while ($data = fgets($this->fd)) {
- $data = preg_replace('@\r\n@', "\n", $data);
- if (!$receivingData) {
- $this->log($data);
- }
- if (!$receivingData && preg_match('/^MAIL FROM:\s?<(.*)>/i', $data, $match)) {
- if (preg_match('/(.*)@\[.*\]/i', $match[1]) || $match[1] != '' || $this->validateEmail($match[1])) {
- $this->mail['emailSender'] = $match[1];
- $this->reply('250 2.1.0 Ok');
- $hasValidFrom = true;
- } else {
- $this->reply('551 5.1.7 Bad sender address syntax');
- }
- } elseif (!$receivingData && preg_match('/^RCPT TO:\s?<(.*)>/i', $data, $match)) {
- if (!$hasValidFrom) {
- $this->reply('503 5.5.1 Error: need MAIL command');
- } else {
- if (preg_match('/postmaster@\[.*\]/i', $match[1]) || $this->validateEmail($match[1])) {
- array_push($this->mail['emailRecipients'], $match[1]);
- $this->reply('250 2.1.5 Ok');
- $hasValidTo = true;
- } else {
- $this->reply('501 5.1.3 Bad recipient address syntax '.$match[1]);
- }
- }
- } elseif (!$receivingData && preg_match('/^RSET$/i', trim($data))) {
- $this->reply('250 2.0.0 Ok');
- $hasValidFrom = false;
- $hasValidTo = false;
- } elseif (!$receivingData && preg_match('/^NOOP$/i', trim($data))) {
- $this->reply('250 2.0.0 Ok');
- } elseif (!$receivingData && preg_match('/^VRFY (.*)/i', trim($data), $match)) {
- $this->reply('250 2.0.0 '.$match[1]);
- } elseif (!$receivingData && preg_match('/^DATA/i', trim($data))) {
- if (!$hasValidTo) {
- $this->reply('503 5.5.1 Error: need RCPT command');
- } else {
- $this->reply('354 Ok Send data ending with <CRLF>.<CRLF>');
- $receivingData = true;
- }
- } elseif (!$receivingData && preg_match('/^(HELO|EHLO)/i', $data)) {
- $this->reply('250 HELO '.$this->mail['ipaddress']);
- } elseif (!$receivingData && preg_match('/^QUIT/i', trim($data))) {
- break;
- } elseif (!$receivingData) {
- //~ $this->reply('250 Ok');
- $this->reply('502 5.5.2 Error: command not recognized');
- } elseif ($receivingData && $data == ".\n") {
- /* Email Received, now let's look at it */
- $receivingData = false;
- $this->reply('250 2.0.0 Ok: queued as '.$this->generateRandom(10));
- $splitmail = explode("\n\n", $this->mail['rawEmail'], 2);
- if (count($splitmail) == 2) {
- $this->mail['emailHeaders'] = $splitmail[0];
- $this->mail['emailBody'] = $splitmail[1];
- $headers = preg_replace("/ \s+/", ' ', preg_replace("/\n\s/", ' ', $this->mail['emailHeaders']));
- $headerlines = explode("\n", $headers);
- for ($i = 0; $i < count($headerlines); $i++) {
- if (preg_match('/^Subject: (.*)/i', $headerlines[$i], $matches)) {
- $this->mail['emailSubject'] = trim($matches[1]);
- }
- }
- } else {
- $this->mail['emailBody'] = $splitmail[0];
- }
- set_time_limit(5); // Just run the exit to prevent open threads / abuse
- } elseif ($receivingData) {
- $this->mail['rawEmail'] .= $data;
- }
- }
- /* Say good bye */
- $this->reply('221 2.0.0 Bye '.$this->mail['ipaddress']);
- fclose($this->fd);
- }
- public function log($s) {
- if ($this->logFile) {
- file_put_contents($this->logFile, trim($s)."\n", FILE_APPEND);
- }
- }
- private function reply($s) {
- $this->log("REPLY:$s");
- fwrite($this->fd, $s . "\r\n");
- }
- private function detectIP() {
- $raw = explode(':', stream_socket_get_name($this->fd, true));
- return $raw[0];
- }
- private function validateEmail($email) {
- return preg_match('/^[_a-z0-9-+]+(\.[_a-z0-9-+]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/', strtolower($email));
- }
- private function generateRandom($length = 8) {
- $password = '';
- $possible = '2346789BCDFGHJKLMNPQRTVWXYZ';
- $maxlength = strlen($possible);
- $i = 0;
- for ($i = 0; $i < $length; $i++) {
- $char = substr($possible, mt_rand(0, $maxlength - 1), 1);
- if (!strstr($password, $char)) {
- $password .= $char;
- }
- }
- return $password;
- }
- }
- $socket = stream_socket_server('tcp://127.0.0.1:2525', $errno, $errstr);
- if (!$socket) {
- exit();
- }
- while ($fd = stream_socket_accept($socket)) {
- $fakeSMTP = new fakeSMTP($fd);
- $fakeSMTP->receive();
- }
- fclose($socket);
|