From 73093a32a31f6a246261c8b4b920b352877a746d Mon Sep 17 00:00:00 2001 From: Synchro Date: Fri, 2 Aug 2013 16:19:01 +0200 Subject: [PATCH] Add fake pop server and POP-before-SMTP tests. Update POP before SMTP example to use new static method. --- changelog.md | 4 ++ class.pop3.php | 56 ++++++++++----- examples/pop_before_smtp.phps | 6 +- test/fakepopserver.sh | 125 ++++++++++++++++++++++++++++++++++ test/phpmailerTest.php | 49 ++++++++++++- test/runfakepopserver.sh | 9 +++ 6 files changed, 227 insertions(+), 22 deletions(-) create mode 100755 test/fakepopserver.sh create mode 100755 test/runfakepopserver.sh diff --git a/changelog.md b/changelog.md index 33cbaf66..ce0e1b29 100644 --- a/changelog.md +++ b/changelog.md @@ -35,6 +35,10 @@ * Introduce autoloader * Allow overriding of SMTP class * Overhaul of PHPDocs +* Fix broken Q-encoding +* Czech language update (Thanks to @nemelu) +* Removal of excess blank lines in messages +* Added fake POP server and unit tests for POP-before-SMTP ## Version 5.2.6 (April 11th 2013) * Reflect move to PHPMailer GitHub organisation at https://github.com/PHPMailer/PHPMailer diff --git a/class.pop3.php b/class.pop3.php index 83f2292e..fee5f84f 100644 --- a/class.pop3.php +++ b/class.pop3.php @@ -56,6 +56,7 @@ class POP3 * POP3 Carriage Return + Line Feed. * @type string * @access public + * @deprecated Use the constant instead */ public $CRLF = "\r\n"; @@ -123,10 +124,14 @@ class POP3 */ private $error; + /** + * Line break constant + */ + const CRLF = "\r\n"; + /** * Constructor. * @access public - * @access private */ public function __construct() { @@ -135,6 +140,21 @@ class POP3 $this->error = null; } + /** + * Simple static wrapper for all-in-one POP before SMTP + * @param $host + * @param bool $port + * @param bool $tval + * @param string $username + * @param string $password + * @return bool + */ + public static function popBeforeSmtp($host, $port = false, $tval = false, $username = '', $password = '') + { + $pop = new POP3; + return $pop->authorise($host, $port, $tval, $username, $password); + } + /** * Authenticate with a POP3 server. * A connect, login, disconnect sequence @@ -151,14 +171,14 @@ class POP3 public function authorise($host, $port = false, $tval = false, $username = '', $password = '', $debug_level = 0) { $this->host = $host; - // If no port value is passed, retrieve it - if ($port == false) { + // If no port value provided, use default + if ($port === false) { $this->port = $this->POP3_PORT; } else { $this->port = $port; } - // If no port value is passed, retrieve it - if ($tval == false) { + // If no timeout value provided, use default + if ($tval === false) { $this->tval = $this->POP3_TIMEOUT; } else { $this->tval = $tval; @@ -177,7 +197,7 @@ class POP3 return true; } } - // We need to disconnect regardless if the login succeeded + // We need to disconnect regardless of whether the login succeeded $this->disconnect(); return false; } @@ -274,14 +294,13 @@ class POP3 if (empty($password)) { $password = $this->password; } - $pop_username = "USER $username" . $this->CRLF; - $pop_password = "PASS $password" . $this->CRLF; - // send the Username - $this->sendString($pop_username); + + // Send the Username + $this->sendString("USER $username" . self::CRLF); $pop3_response = $this->getResponse(); if ($this->checkResponse($pop3_response)) { - // send the Password - $this->sendString($pop_password); + // Send the Password + $this->sendString("PASS $password" . self::CRLF); $pop3_response = $this->getResponse(); if ($this->checkResponse($pop3_response)) { return true; @@ -297,7 +316,9 @@ class POP3 public function disconnect() { $this->sendString('QUIT'); - fclose($this->pop_conn); + //The QUIT command may cause the daemon to exit, which will kill our connection + //So ignore errors here + @fclose($this->pop_conn); } /** @@ -309,8 +330,7 @@ class POP3 */ private function getResponse($size = 128) { - $pop3_response = fgets($this->pop_conn, $size); - return $pop3_response; + return fgets($this->pop_conn, $size); } /** @@ -321,8 +341,10 @@ class POP3 */ private function sendString($string) { - $bytes_sent = fwrite($this->pop_conn, $string, strlen($string)); - return $bytes_sent; + if ($this->pop_conn) { + return fwrite($this->pop_conn, $string, strlen($string)); + } + return 0; } /** diff --git a/examples/pop_before_smtp.phps b/examples/pop_before_smtp.phps index 68a96b85..d4012cf8 100644 --- a/examples/pop_before_smtp.phps +++ b/examples/pop_before_smtp.phps @@ -8,12 +8,10 @@ authorise('pop3.yourdomain.com', 110, 30, 'username', 'password', 1); +//Authenticate via POP3 //Now you should be clear to submit messages over SMTP for a while //Only applies if your host supports POP-before-SMTP +$pop = POP3::popBeforeSmtp('pop3.example.com', 110, 30, 'username', 'password', 1); //Create a new PHPMailer instance //Passing true to the constructor enables the use of exceptions for error handling diff --git a/test/fakepopserver.sh b/test/fakepopserver.sh new file mode 100755 index 00000000..36e0af47 --- /dev/null +++ b/test/fakepopserver.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash + +# Fake POP3 server +# By Marcus Bointon +# Based on code by 'Frater' found at http://www.linuxquestions.org/questions/programming-9/fake-pop3-server-to-capture-pop3-passwords-933733 +# Does not actually serve any mail, but supports commands sufficient to test POP-before SMTP +# Can be run directly from a shell like this: +# mkfifo fifo; nc -l 1100 fifo; rm fifo +# It will accept any user name and will return a positive response for the password 'test' + +# Licensed under the GNU Lesser General Public License: http://www.gnu.org/copyleft/lesser.html + +# Enable debug output +#set -xv +export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin + +LOGFOLDER=/tmp + +LOGFILE=${LOGFOLDER}/fakepop.log + +LOGGING=1 +DEBUG=1 +TIMEOUT=10 + +POP_USER= +POP_PASSWRD=test + +LINES=1 +BREAK=0 + +write_log () { + if [ ${LINES} -eq 1 ] ; then + echo '---' >>${LOGFILE} + fi + let LINES+=1 + [ ${LOGGING} = 0 ] || echo -e "`date '+%b %d %H:%M'` pop3 $*" >>${LOGFILE} +} + +ANSWER="+OK Fake POP3 Service Ready" + +while [ ${BREAK} -eq 0 ] ; do + echo -en "${ANSWER}\r\n" + + REPLY="" + + #Input appears in $REPLY + read -t ${TIMEOUT} + + ANSWER="+OK " + COMMAND="" + ARGS="" + TIMEOUT=30 + + if [ "$REPLY" ] ; then + write_log "RAW input: '`echo "${REPLY}" | tr -cd '[ -~]'`'" + + COMMAND="`echo "${REPLY}" | awk '{print $1}' | tr -cd '\40-\176' | tr 'a-z' 'A-Z'`" + ARGS="`echo "${REPLY}" | tr -cd '\40-\176' | awk '{for(i=2;i<=NF;i++){printf "%s ", $i};printf "\n"}' | sed 's/ $//'`" + + write_log "Command: \"${COMMAND}\"" + write_log "Arguments: \"${ARGS}\"" + + case "$COMMAND" in + QUIT) + break + ;; + USER) + if [ -n "${ARGS}" ] ; then + POP_USER="${ARGS}" + ANSWER="+OK Please send PASS command" + fi + ;; + AUTH) + ANSWER="+OK \r\n." + ;; + CAPA) + ANSWER="+OK Capabilities include\r\nUSER\r\nCAPA\r\n." + ;; + PASS) + if [ "${POP_PASSWRD}" == "${ARGS}" ] ; then + ANSWER="+OK Logged in.\r\n" + AUTH=1 + else + ANSWER="-ERR Login failed\r\n" + fi + ;; + LIST) + if [ "${AUTH}" = 0 ] ; then + ANSWER="-ERR Not authenticated" + else + if [ -z "${ARGS}" ] ; then + ANSWER="+OK No messages, really\r\n." + else + ANSWER="-ERR No messages, no list, no status" + fi + fi + ;; + RSET) + ANSWER="+OK Resetting or whatever\r\n." + ;; + LAST) + if [ "${AUTH}" = 0 ] ; then + ANSWER="-ERR Not authenticated" + else + ANSWER="+OK 0" + fi + ;; + STAT) + if [ "${AUTH}" = 0 ] ; then + ANSWER="-ERR Not authenticated" + else + ANSWER="+OK 0 0" + fi + ;; + NOOP) + ANSWER="+OK Hang on, doing nothing" + ;; + esac + else + echo "+OK Connection timed out\r\n" + break + fi +done + +echo "+OK Bye!\r\n" diff --git a/test/phpmailerTest.php b/test/phpmailerTest.php index c82621b5..1cf08b79 100644 --- a/test/phpmailerTest.php +++ b/test/phpmailerTest.php @@ -58,6 +58,13 @@ class PHPMailerTest extends PHPUnit_Framework_TestCase */ public $INCLUDE_DIR = '../'; + /** + * PIDs of any processes we need to kill + * @type array + * @access private + */ + private $pids = array(); + /** * Run before each test is started. */ @@ -121,6 +128,11 @@ class PHPMailerTest extends PHPUnit_Framework_TestCase $this->Mail = null; $this->ChangeLog = array(); $this->NoteLog = array(); + + foreach ($this->pids as $pid) { + $p = escapeshellarg($pid); + shell_exec("ps $p && kill -TERM $p"); + } } @@ -1136,7 +1148,7 @@ EOT; } /** - * Encoding tests + * Encoding and charset tests */ public function testEncodings() { @@ -1170,6 +1182,13 @@ EOT; ); } + public function testBase64() + { + $this->Mail->Subject .= ': Base-64 encoding'; + $this->Mail->Encoding = 'base64'; + $this->buildBody(); + $this->assertTrue($this->Mail->send(), 'Base64 encoding failed'); + } /** * S/MIME Signing tests */ @@ -1259,6 +1278,34 @@ EOT; $this->assertEquals($target, PHPMailer::normalizeBreaks($mixedsrc), 'Mixed break reformatting failed'); } + /** + * Use a fake POP3 server to test POP-before-SMTP auth + */ + public function testPopBeforeSmtp() + { + //Start a fake POP server + $pid = shell_exec('nohup ./runfakepopserver.sh >/dev/null 2>/dev/null & printf "%u" $!'); + $this->pids[] = $pid; + + //Test a known-good login + $this->assertTrue( + POP3::popBeforeSmtp('localhost', 1100, 10, 'user', 'test'), + 'POP before SMTP failed' + ); + //Kill the fake server + shell_exec('kill -TERM '.escapeshellarg($pid)); + + $pid = shell_exec('nohup ./runfakepopserver.sh >/dev/null 2>/dev/null & printf "%u" $!'); + $this->pids[] = $pid; + + //Test a known-bad login + $this->assertFalse( + POP3::popBeforeSmtp('localhost', 1100, 10, 'user', 'xxx'), + 'POP before SMTP should have failed' + ); + shell_exec('kill -TERM '.escapeshellarg($pid)); + } + /** * Miscellaneous calls to improve test coverage and some small tests */ diff --git a/test/runfakepopserver.sh b/test/runfakepopserver.sh new file mode 100755 index 00000000..0aac64c6 --- /dev/null +++ b/test/runfakepopserver.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# Run the fake pop server from bash +# Idea from http://blog.ale-re.net/2007/09/ipersimple-remote-shell-with-netcat.html +# Defaults to port 1100 so it can be run by unpriv users and not clash with a real server + +mkfifo fifo +nc -l 1100 fifo +rm fifo \ No newline at end of file