PageRenderTime 57ms CodeModel.GetById 6ms RepoModel.GetById 0ms app.codeStats 0ms

/swiftmailer/lib/Swift/Connection/Sendmail.php

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