PageRenderTime 21ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/pronto/extlib/swift/Swift/Connection/Sendmail.php

https://github.com/jvinet/pronto
PHP | 380 lines | 295 code | 6 blank | 79 comment | 9 complexity | d7c56d8fe44e02b03a62e9899f5a913b MD5 | raw file
  1. <?php
  2. /**
  3. * Swift Mailer Sendmail Connection component.
  4. * Please read the LICENSE file
  5. * @author Chris Corbyn <chris@w3style.co.uk>
  6. * @package Swift_Connection
  7. * @license GNU Lesser General Public License
  8. */
  9. require_once dirname(__FILE__) . "/../ClassLoader.php";
  10. Swift_ClassLoader::load("Swift_ConnectionBase");
  11. if (!defined("SWIFT_SENDMAIL_AUTO_DETECT")) define("SWIFT_SENDMAIL_AUTO_DETECT", -2);
  12. //Sorry guys, it has to be done and it's not my lazy coding, blame PHP4/proc_open()
  13. $GLOBALS["_SWIFT_PROC"] = array();
  14. /**
  15. * Swift Sendmail Connection
  16. * @package Swift_Connection
  17. * @author Chris Corbyn <chris@w3style.co.uk>
  18. */
  19. class Swift_Connection_Sendmail extends Swift_ConnectionBase
  20. {
  21. /**
  22. * Constant for auto-detection of paths
  23. */
  24. var $AUTO_DETECT = SWIFT_SENDMAIL_AUTO_DETECT;
  25. /**
  26. * Flags for the MTA (options such as bs or t)
  27. * @var string
  28. */
  29. var $flags = null;
  30. /**
  31. * The full path to the MTA
  32. * @var string
  33. */
  34. var $path = null;
  35. /**
  36. * The type of last request sent
  37. * For example MAIL, RCPT, DATA
  38. * @var string
  39. */
  40. var $request = null;
  41. /**
  42. * The process handle
  43. * @var resource
  44. */
  45. var $proc;
  46. /**
  47. * I/O pipes for the process
  48. * @var array
  49. */
  50. var $pipes;
  51. /**
  52. * Switches to true for just one command when DATA has been issued
  53. * @var boolean
  54. */
  55. var $send = false;
  56. /**
  57. * The timeout in seconds before giving up
  58. * @var int Seconds
  59. */
  60. var $timeout = 10;
  61. /**
  62. * Constructor
  63. * @param string The command to execute
  64. * @param int The timeout in seconds before giving up
  65. */
  66. function Swift_Connection_Sendmail($command="/usr/sbin/sendmail -bs", $timeout=10)
  67. {
  68. if(PHP_VERSION < 5) register_shutdown_function(array(&$this, "__destruct"));
  69. $this->setCommand($command);
  70. $this->setTimeout($timeout);
  71. }
  72. /**
  73. * Set the timeout on the process
  74. * @param int The number of seconds
  75. */
  76. function setTimeout($secs)
  77. {
  78. $this->timeout = (int)$secs;
  79. }
  80. /**
  81. * Get the timeout on the process
  82. * @return int
  83. */
  84. function getTimeout()
  85. {
  86. return $this->timeout;
  87. }
  88. /**
  89. * Set the operating flags for the MTA
  90. * @param string
  91. */
  92. function setFlags($flags)
  93. {
  94. $this->flags = $flags;
  95. }
  96. /**
  97. * Get the operating flags for the MTA
  98. * @return string
  99. */
  100. function getFlags()
  101. {
  102. return $this->flags;
  103. }
  104. /**
  105. * Set the path to the binary
  106. * @param string The path (must be absolute!)
  107. */
  108. function setPath($path)
  109. {
  110. if ($path == $this->AUTO_DETECT) $path = $this->findSendmail();
  111. $this->path = $path;
  112. }
  113. /**
  114. * Get the path to the binary
  115. * @return string
  116. */
  117. function getPath()
  118. {
  119. return $this->path;
  120. }
  121. /**
  122. * For auto-detection of sendmail path
  123. * Thanks to "Joe Cotroneo" for providing the enhancement
  124. * @return string
  125. */
  126. function findSendmail()
  127. {
  128. $log =& Swift_LogContainer::getLog();
  129. if ($log->hasLevel(SWIFT_LOG_EVERYTHING))
  130. {
  131. $log->add("Sendmail path auto-detection in progress. Trying `which sendmail`");
  132. }
  133. $path = @trim(shell_exec('which sendmail'));
  134. if (!is_executable($path))
  135. {
  136. if ($log->hasLevel(SWIFT_LOG_EVERYTHING))
  137. {
  138. $log->add("No luck so far, trying some common paths...");
  139. }
  140. $common_locations = array(
  141. '/usr/bin/sendmail',
  142. '/usr/lib/sendmail',
  143. '/var/qmail/bin/sendmail',
  144. '/bin/sendmail',
  145. '/usr/sbin/sendmail',
  146. '/sbin/sendmail'
  147. );
  148. foreach ($common_locations as $path)
  149. {
  150. if (is_executable($path)) return $path;
  151. }
  152. if ($log->hasLevel(SWIFT_LOG_EVERYTHING))
  153. {
  154. $log->add("Falling back to /usr/sbin/sendmail (but it doesn't look good)!");
  155. }
  156. //Fallback (swift will still throw an error)
  157. return "/usr/sbin/sendmail";
  158. }
  159. else return $path;
  160. }
  161. /**
  162. * Set the sendmail command (path + flags)
  163. * @param string Command
  164. * @throws Swift_ConnectionException If the command is not correctly structured
  165. */
  166. function setCommand($command)
  167. {
  168. if ($command == $this->AUTO_DETECT) $command = $this->findSendmail() . " -bs";
  169. if (!strrpos($command, " -"))
  170. {
  171. Swift_Errors::trigger(new Swift_ConnectionException(
  172. "Cannot set sendmail command with no command line flags. e.g. /usr/sbin/sendmail -t"));
  173. return;
  174. }
  175. $path = substr($command, 0, strrpos($command, " -"));
  176. $flags = substr($command, strrpos($command, " -")+2);
  177. $this->setPath($path);
  178. $this->setFlags($flags);
  179. }
  180. /**
  181. * Get the sendmail command (path + flags)
  182. * @return string
  183. */
  184. function getCommand()
  185. {
  186. return $this->getPath() . " -" . $this->getFlags();
  187. }
  188. /**
  189. * Write a command to the open pipe
  190. * @param string The command to write
  191. * @throws Swift_ConnectionException If the pipe cannot be written to
  192. */
  193. function pipeIn($command, $end="\r\n")
  194. {
  195. if (!$this->isAlive())
  196. {
  197. Swift_Errors::trigger(new Swift_ConnectionException(
  198. "The sendmail process is not alive and cannot be written to."));
  199. return;
  200. }
  201. if (!@fwrite($this->pipes[0], $command . $end) && !empty($command))
  202. {
  203. Swift_Errors::trigger(new Swift_ConnectionException(
  204. "The sendmail process did not allow the command '" . $command . "' to be sent."));
  205. }
  206. fflush($this->pipes[0]);
  207. }
  208. /**
  209. * Read data from the open pipe
  210. * @return string
  211. * @throws Swift_ConnectionException If the pipe is not operating as expected
  212. */
  213. function pipeOut()
  214. {
  215. if (strpos($this->getFlags(), "t") !== false) return;
  216. if (!$this->isAlive())
  217. {
  218. Swift_Errors::trigger(new Swift_ConnectionException("The sendmail process is not alive and cannot be read from."));
  219. return;
  220. }
  221. $ret = "";
  222. $line = 0;
  223. while (true)
  224. {
  225. $line++;
  226. stream_set_timeout($this->pipes[1], $this->timeout);
  227. $tmp = @fgets($this->pipes[1]);
  228. if ($tmp === false)
  229. {
  230. Swift_Errors::trigger(new Swift_ConnectionException(
  231. "There was a problem reading line " . $line . " of a sendmail SMTP response. The response so far was:<br />[" . $ret . "]. It appears the process has died."));
  232. return;
  233. }
  234. $ret .= trim($tmp) . "\r\n";
  235. if ($tmp{3} == " ") break;
  236. }
  237. fflush($this->pipes[1]);
  238. return $ret = substr($ret, 0, -2);
  239. }
  240. /**
  241. * Read a full response from the buffer (this is spoofed if running in -t mode)
  242. * @return string
  243. * @throws Swift_ConnectionException Upon failure to read
  244. */
  245. function read()
  246. {
  247. if ($this->getFlags() == "t")
  248. {
  249. switch (strtolower($this->request))
  250. {
  251. case null:
  252. return "220 Greetings";
  253. case "helo": case "ehlo":
  254. return "250 hello";
  255. case "mail": case "rcpt": case "rset":
  256. return "250 ok";
  257. case "quit":
  258. return "221 bye";
  259. case "data":
  260. $this->send = true;
  261. return "354 go ahead";
  262. default:
  263. return "250 ok";
  264. }
  265. }
  266. else return $this->pipeOut();
  267. }
  268. /**
  269. * Write a command to the process (leave off trailing CRLF)
  270. * @param string The command to send
  271. * @throws Swift_ConnectionException Upon failure to write
  272. */
  273. function write($command, $end="\r\n")
  274. {
  275. if (strpos($this->getFlags(), "t") !== false)
  276. {
  277. if (!$this->send && strpos($command, " ")) $command = substr($command, strpos($command, " ")+1);
  278. elseif ($this->send)
  279. {
  280. $this->pipeIn($command, $end);
  281. }
  282. $this->request = $command;
  283. $this->send = (strtolower($command) == "data");
  284. }
  285. else $this->pipeIn($command);
  286. }
  287. /**
  288. * Try to start the connection
  289. * @throws Swift_ConnectionException Upon failure to start
  290. */
  291. function start()
  292. {
  293. $log =& Swift_LogContainer::getLog();
  294. if ($log->hasLevel(SWIFT_LOG_EVERYTHING))
  295. {
  296. $log->add("Trying to start a sendmail process.");
  297. }
  298. if (!$this->getPath() || !$this->getFlags())
  299. {
  300. Swift_Errors::trigger(new Swift_ConnectionException(
  301. "Sendmail cannot be started without a path to the binary including flags."));
  302. return;
  303. }
  304. if ($log->hasLevel(SWIFT_LOG_EVERYTHING))
  305. {
  306. $log->add("Trying to stat the executable '" . $this->getPath() . "'.");
  307. }
  308. if (!@lstat($this->getPath()))
  309. {
  310. Swift_Errors::trigger(new Swift_ConnectionException(
  311. "Sendmail cannot be seen with lstat(). The command given [" . $this->getCommand() . "] does not appear to be valid."));
  312. return;
  313. }
  314. $pipes_spec = array(
  315. array("pipe", "r"),
  316. array("pipe", "w"),
  317. array("pipe", "w")
  318. );
  319. $i = count($GLOBALS["_SWIFT_PROC"]);
  320. $GLOBALS["_SWIFT_PROC"][$i] = proc_open($this->getCommand(), $pipes_spec, $this->pipes);
  321. $this->proc =& $GLOBALS["_SWIFT_PROC"][$i];
  322. if (!$this->isAlive())
  323. {
  324. Swift_Errors::trigger(new Swift_ConnectionException(
  325. "The sendmail process failed to start. Please verify that the path exists and PHP has permission to execute it."));
  326. return;
  327. }
  328. }
  329. /**
  330. * Try to close the connection
  331. */
  332. function stop()
  333. {
  334. $log =& Swift_LogContainer::getLog();
  335. if ($log->hasLevel(SWIFT_LOG_EVERYTHING))
  336. {
  337. $log->add("Terminating sendmail process.");
  338. }
  339. foreach ((array)$this->pipes as $pipe)
  340. {
  341. @fclose($pipe);
  342. }
  343. if ($this->proc)
  344. {
  345. proc_close($this->proc);
  346. $this->pipes = null;
  347. $this->proc = null;
  348. }
  349. }
  350. /**
  351. * Check if the process is still alive
  352. * @return boolean
  353. */
  354. function isAlive()
  355. {
  356. return (is_resource($this->proc)
  357. && is_resource($this->pipes[0])
  358. && is_resource($this->pipes[1])
  359. && $this->proc !== null);
  360. }
  361. /**
  362. * Destructor.
  363. * Cleans up by stopping any running processes.
  364. */
  365. function __destruct()
  366. {
  367. $this->stop();
  368. }
  369. }