PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/core/network/protocol/smtp.class.php

https://github.com/konfirm/konsolidate
PHP | 416 lines | 160 code | 44 blank | 212 comment | 41 complexity | 92bc06d206d97cbf89e3bcdce7a01612 MD5 | raw file
  1. <?php
  2. /*
  3. * ________ ___
  4. * / / /\ /\ Konsolidate
  5. * ____/ /___/ \/ \
  6. * / /\ / http://www.konsolidate.nl
  7. * /___ ___/ \ /
  8. * \ / /\ \ / \ Class: CoreNetworkProtocolSMTP
  9. * \/___/ \___\/ \ Tier: Core
  10. * \ \ /\ \ /\ / Module: Network/Protocol/SMTP
  11. * \___\/ \___\/ \/
  12. * \ \ / $Rev$
  13. * \___ ___\/ $Author$
  14. * \ \ / $Date$
  15. * \___\/
  16. */
  17. /**
  18. * Basic implementatin of the SMTP protocol
  19. * @name CoreNetworkProtocolSMTP
  20. * @type class
  21. * @package Konsolidate
  22. * @author Rogier Spieker <rogier@konsolidate.nl>
  23. */
  24. class CoreNetworkProtocolSMTP extends Konsolidate
  25. {
  26. /**
  27. * Normal SMTP headers
  28. * @name _validheader
  29. * @type array
  30. * @access protected
  31. * @note all dynamically set properties not contained in this array will be preceeded by "X-"
  32. */
  33. protected $_validheader;
  34. /**
  35. * All properties that need to be taken care of differently from setting it as (custom) header
  36. * @name _noautoheader
  37. * @type array
  38. * @access protected
  39. */
  40. protected $_noautoheader;
  41. /**
  42. * The socket connection resource
  43. * @name _socket
  44. * @type resource
  45. * @access protected
  46. */
  47. protected $_socket;
  48. /**
  49. * Array containing all explicit headers
  50. * @name _header
  51. * @type array
  52. * @access protected
  53. */
  54. protected $_header;
  55. /**
  56. * constructor
  57. * @name __construct
  58. * @type constructor
  59. * @access public
  60. * @param object parent object
  61. * @return object
  62. * @syntax object = &new CoreNetworkProtocolSMTP( object parent )
  63. * @note This object is constructed by one of Konsolidates modules
  64. */
  65. public function __construct( $oParent )
  66. {
  67. parent::__construct( $oParent );
  68. $this->_validheader = Array( "to", "cc", "bcc", "from", "date", "subject", "reply-to", "return-path", "message-id", "mime-version", "content-type", "charset" );
  69. $this->_noautoheader = Array( "to", "from", "server", "domain", "port", "sender", "recipient", "body", "status", "message" );
  70. $this->_header = Array();
  71. $this->server = $this->get( "/Config/Mail/server", ini_get( "SMTP" ) );
  72. $this->domain = $this->get( "/Config/Mail/domain", CoreTool::arrVal( $_SERVER, "HTTP_HOST", "localhost" ) );
  73. $this->port = 25;
  74. $this->from = "konsolidate@{$this->domain}";
  75. $this->subject = "No subject";
  76. $this->mailer = "Konsolidate (" . get_class( $this ) . ")";
  77. $this->date = gmdate( "r" );
  78. }
  79. /**
  80. * Add header data
  81. * @name addHeader
  82. * @type method
  83. * @access public
  84. * @param string headername
  85. * @param string headervalue
  86. * @return void
  87. * @syntax void CoreNetworkProtocolSMTP->addHeader( string headername, string headervalue )
  88. */
  89. public function addHeader( $sKey, $sValue )
  90. {
  91. $this->_header[ ( !in_array( strToLower( $sKey ), $this->_validheader ) ? "X-" : "" ) . ucfirst( $sKey ) ] = $sValue;
  92. }
  93. /**
  94. * Open connection to the SMTP server
  95. * @name connect
  96. * @type method
  97. * @access public
  98. * @param string hostname [optional, default 'localhost']
  99. * @param int portnumber [optional, default 25]
  100. * @param int timeout (ms) [optional, default 30]
  101. * @return void
  102. * @syntax void CoreNetworkProtocolSMTP->connect( [ string hostname [, int portnumber [, int timeout ] ] ] )
  103. */
  104. public function connect( $sHost="localhost", $nPort=25, $nTimeout=30 )
  105. {
  106. $this->_socket = $this->instance( "/Network/Socket" );
  107. if ( $this->_socket->connect( $sHost, $nPort, "tcp", $nTimeout ) )
  108. {
  109. $nStatus = $this->_getResponseStatus();
  110. return empty( $nStatus ) || $nStatus == 220;
  111. }
  112. return false;
  113. }
  114. /**
  115. * Read the SMTP server response, place the status code in $this->status (public) and the response message in $this->message (public)
  116. * @name _getResponseStatus
  117. * @type method
  118. * @access protected
  119. * @return int statuscode
  120. * @syntax void CoreNetworkProtocolSMTP->_getReponseStatus()
  121. */
  122. protected function _getResponseStatus()
  123. {
  124. $sResponse = trim( $this->_socket->read( 512 ) );
  125. list( $this->status, $this->message ) = explode( " ", $sResponse, 2 );
  126. return (int) $this->status;
  127. }
  128. /**
  129. * @name _createRecipientList
  130. * @type method
  131. * @access protected
  132. * @return string
  133. */
  134. protected function _createRecipientList( $aCollection )
  135. {
  136. $sReturn = "";
  137. foreach( $aCollection as $sEmail=>$sName )
  138. $sReturn .= ( !empty( $sReturn ) ? "," : "" ) . ( !is_null( $sName ) ? "{$sName}<{$sEmail}>" : $sEmail );
  139. return $sReturn;
  140. }
  141. /**
  142. * Send a command string to the SMTP server
  143. * @name _command
  144. * @type method
  145. * @access protected
  146. * @param string command
  147. * @return mixed int statuscode or bool if failed
  148. * @syntax void CoreNetworkProtocolSMTP->_command( string command )
  149. */
  150. protected function _command( $sCommand )
  151. {
  152. if ( $this->_socket->write( "{$sCommand}\r\n" ) )
  153. return $this->_getResponseStatus();
  154. return false;
  155. }
  156. // SMTP Command wrappers
  157. // NOTE: RFC 821 only partially implemented!!
  158. /**
  159. * Send the 'AUTH LOGIN' command to the server, triggering the authentication flow
  160. * @name authLogin
  161. * @type method
  162. * @access public
  163. * @param string username
  164. * @param string password
  165. * @returns bool success
  166. * @syntax void CoreNetworkProtocolSMTP->authLogin(string username, string password)
  167. * @note use the status/message properties for reporting/checking/logging
  168. */
  169. public function authLogin( $sUsername, $sPassword )
  170. {
  171. $nResponse = $this->_command("AUTH LOGIN");
  172. if ( $nResponse == 334 )
  173. $nResponse = $this->_command( base64_encode( $sUsername ) );
  174. if ( $nResponse == 334 )
  175. return $this->_command( base64_encode( $sPassword ) ) == 235;
  176. return false;
  177. }
  178. /**
  179. * Send 'HELO'/'EHLO' (handshake) command to the SMTP server
  180. * @name helo
  181. * @type method
  182. * @access public
  183. * @param string domain [optional, default $_SERVER[ 'SERVER_NAME' ] or $this->server]
  184. * @returns bool success
  185. * @syntax void CoreNetworkProtocolSMTP->helo( [ string domain, [ bool enfore EHLO ] ] )
  186. * @note use the status/message properties for reporting/checking/logging
  187. */
  188. public function helo( $sDomain=null, $bEHLO=false )
  189. {
  190. if ( empty( $sDomain ) )
  191. $sDomain = CoreTool::arrVal( "SERVER_NAME", $_SERVER, $this->server );
  192. return ( ( !$bEHLO && $this->_command( "HELO {$sDomain}" ) == 250 ) || $this->_command( "EHLO {$sDomain}" ) == 250 );
  193. }
  194. /**
  195. * Send 'MAIL FROM' (sender) command to the SMTP server
  196. * @name mailFrom
  197. * @type method
  198. * @access public
  199. * @param string senderemail
  200. * @param string sendername [optional, omitted if empty]
  201. * @return bool success
  202. * @syntax void CoreNetworkProtocolSMTP->mailFrom( string email [, string name ] )
  203. * @note use the status/message properties for reporting/checking/logging
  204. */
  205. public function mailFrom( $sEmail, $sName=null )
  206. {
  207. $this->addHeader( "From", ( !is_null( $sName ) ? "{$sName} <{$sEmail}>" : $sEmail ) );
  208. return $this->_command( "MAIL FROM: <{$sEmail}>" ) == 250;
  209. }
  210. /**
  211. * Send 'RCPT TO' (recipient) command to the SMTP server
  212. * @name rcptTo
  213. * @type method
  214. * @access public
  215. * @param string recipientemail
  216. * @param string recipientname [optional, omitted if empty]
  217. * @return bool success
  218. * @syntax void CoreNetworkProtocolSMTP->rcptTo( string email [, string name ] )
  219. * @note use the status/message properties for reporting/checking/logging
  220. */
  221. public function rcptTo( $aCollection, $sHeaderName="To" )
  222. {
  223. $this->addHeader( $sHeaderName, $this->_createRecipientList( $aCollection ) );
  224. $bReturn = true;
  225. foreach( $aCollection as $sEmail=>$sName )
  226. $bReturn &= $this->_command( "RCPT TO: <{$sEmail}>" ) == 250;
  227. return $bReturn;
  228. }
  229. /**
  230. * Send 'VRFY' (verify) command to the SMTP server
  231. * @name vrfy
  232. * @type method
  233. * @access public
  234. * @param string recipientemail
  235. * @return bool success
  236. * @syntax void CoreNetworkProtocolSMTP->vrfy( string email )
  237. * @note use the status/message properties for reporting/checking/logging
  238. * Don't rely on this method!
  239. * Most mailservers have disabled the VRFY command for it was used by spammers to build lists of valid addresses,
  240. * even if it is enabled, be prepared for it to accept everything you fire at it (catch-all).
  241. */
  242. public function vrfy( $sEmail )
  243. {
  244. $nStatus = $this->_command( "VRFY {$sEmail}" );
  245. return $nStatus == 250 || $nStatus == 251;
  246. }
  247. /**
  248. * Send 'DATA' (message body) command to the SMTP server and send the data
  249. * @name data
  250. * @type method
  251. * @access public
  252. * @param string data
  253. * @return bool success
  254. * @syntax void CoreNetworkProtocolSMTP->data( string data )
  255. * @note use the status/message properties for reporting/checking/logging
  256. */
  257. public function data( $sData )
  258. {
  259. if ( $this->_command( "DATA" ) == 354 )
  260. {
  261. uksort( $this->_header, Array( $this, "_headerSort" ) );
  262. foreach( $this->_header as $sKey=>$sValue )
  263. $this->_socket->write( "{$sKey}: {$sValue}\r\n" );
  264. // The SMTP protocol removes any dot which is the first character on a line, this is resolved by simply adding a dot.
  265. $this->_socket->write( str_replace( "\n.", "\n..", "\r\n{$sData}\r\n" ) );
  266. return $this->_command( "." ) == 250;
  267. }
  268. return false;
  269. }
  270. /**
  271. * Send 'QUIT' (hangup) command to the SMTP server
  272. * @name data
  273. * @type method
  274. * @access public
  275. * @return bool success
  276. * @syntax void CoreNetworkProtocolSMTP->quit()
  277. * @note use the status/message properties for reporting/checking/logging
  278. */
  279. public function quit()
  280. {
  281. if ( $this->_command( "QUIT" ) == 221 )
  282. return true;
  283. return false;
  284. }
  285. /**
  286. * Verify the domain of an e-mail address for having a MX server present (basically validating the e-mail domain)
  287. * @name verify
  288. * @type method
  289. * @access public
  290. * @param string email
  291. * @param bool useVRFY [optional, default false]
  292. * @return bool success
  293. * @syntax void CoreNetworkProtocolSMTP->verify( string email [, bool useVRFY ] )
  294. * @see vrfy
  295. */
  296. public function verify( $sAddress, $bVRFY=false )
  297. {
  298. $sServer = substr( $sAddress, strpos( $sAddress, "@" ) + 1 );
  299. dns_get_mx( $sServer, $aMX );
  300. if ( count( $aMX ) <= 0 )
  301. return false;
  302. if ( !$bVRFY )
  303. return true;
  304. $sServer = $aMX[ 0 ];
  305. if ( !is_object( $this->_socket ) && !$this->connect( $sServer, 25 ) )
  306. return false;
  307. return $this->vrfy( $sAddress );
  308. }
  309. /**
  310. * Send the prepared email
  311. * @name send
  312. * @type method
  313. * @access public
  314. * @param string username (optional)
  315. * @param string password (optional)
  316. * @return bool success
  317. * @syntax void CoreNetworkProtocolSMTP->send()
  318. * @note use the status/message properties for reporting/checking/logging
  319. */
  320. public function send( $sUsername=null, $sPassword=null )
  321. {
  322. foreach( $this->_property as $sKey=>$mValue )
  323. if ( !in_array( strToLower( $sKey ), $this->_noautoheader ) )
  324. {
  325. if ( is_array( $mValue ) )
  326. {
  327. $sHeader = "";
  328. foreach( $mValue as $sValue )
  329. $sHeader .= ( !empty( $sHeader ) ? ", " : "" ) . $sValue;
  330. if ( !empty( $sHeader ) )
  331. $this->addHeader( $sKey, $sHeader );
  332. }
  333. elseif ( !empty( $mValue ) )
  334. {
  335. $this->addHeader( $sKey, $mValue );
  336. }
  337. }
  338. if ( !$this->connect( $this->server, $this->port ) )
  339. return false;
  340. if ( !$this->helo( $this->domain, $sUsername && $sPassword ) )
  341. return false;
  342. if ( $sUsername && $sPassword && !$this->authLogin( $sUsername, $sPassword ) )
  343. return false;
  344. if ( !$this->mailFrom( $this->from, $this->sender ) )
  345. return false;
  346. foreach( Array( "to", "cc", "bcc" ) as $sType )
  347. {
  348. $mValue = $this->$sType;
  349. if ( !empty( $mValue ) && !$this->rcptTo( $mValue, $sType ) )
  350. return false;
  351. }
  352. if ( !$this->data( $this->body ) )
  353. return false;
  354. return $this->quit();
  355. }
  356. /**
  357. * Order the headers for better/nicer output
  358. * @name _headerSort
  359. * @type method
  360. * @access protected
  361. * @param string A
  362. * @param string B
  363. * @return int order
  364. * @syntax void CoreNetworkProtocolSMTP->_headerSort( string A, string B )
  365. * @note used as array sort function
  366. */
  367. protected function _headerSort( $sA, $sB )
  368. {
  369. return array_search( strToLower( $sA ), $this->_validheader ) > array_search( strToLower( $sB ), $this->_validheader ) ? 1 : -1;
  370. }
  371. }
  372. ?>