1<?php 2 3/** 4 * Swift Mailer Sendmail Connection component. 5 * Please read the LICENSE file 6 * @author Chris Corbyn <chris@w3style.co.uk> 7 * @package Swift_Connection 8 * @license GNU Lesser General Public License 9 */ 10 11require_once dirname(__FILE__) . "/../ClassLoader.php"; 12Swift_ClassLoader::load("Swift_ConnectionBase"); 13 14/** 15 * Swift Sendmail Connection 16 * @package Swift_Connection 17 * @author Chris Corbyn <chris@w3style.co.uk> 18 */ 19class Swift_Connection_Sendmail extends Swift_ConnectionBase 20{ 21 /** 22 * Constant for auto-detection of paths 23 */ 24 const AUTO_DETECT = -2; 25 /** 26 * Flags for the MTA (options such as bs or t) 27 * @var string 28 */ 29 protected $flags = null; 30 /** 31 * The full path to the MTA 32 * @var string 33 */ 34 protected $path = null; 35 /** 36 * The type of last request sent 37 * For example MAIL, RCPT, DATA 38 * @var string 39 */ 40 protected $request = null; 41 /** 42 * The process handle 43 * @var resource 44 */ 45 protected $proc; 46 /** 47 * I/O pipes for the process 48 * @var array 49 */ 50 protected $pipes; 51 /** 52 * Switches to true for just one command when DATA has been issued 53 * @var boolean 54 */ 55 protected $send = false; 56 /** 57 * The timeout in seconds before giving up 58 * @var int Seconds 59 */ 60 protected $timeout = 10; 61 62 /** 63 * Constructor 64 * @param string The command to execute 65 * @param int The timeout in seconds before giving up 66 */ 67 public function __construct($command="/usr/sbin/sendmail -bs", $timeout=10) 68 { 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 public function setTimeout($secs) 77 { 78 $this->timeout = (int)$secs; 79 } 80 /** 81 * Get the timeout on the process 82 * @return int 83 */ 84 public function getTimeout() 85 { 86 return $this->timeout; 87 } 88 /** 89 * Set the operating flags for the MTA 90 * @param string 91 */ 92 public function setFlags($flags) 93 { 94 $this->flags = $flags; 95 } 96 /** 97 * Get the operating flags for the MTA 98 * @return string 99 */ 100 public 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 public function setPath($path) 109 { 110 if ($path == self::AUTO_DETECT) $path = $this->findSendmail(); 111 $this->path = $path; 112 } 113 /** 114 * Get the path to the binary 115 * @return string 116 */ 117 public 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 public function findSendmail() 127 { 128 $log = Swift_LogContainer::getLog(); 129 if ($log->hasLevel(Swift_Log::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::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::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 public function setCommand($command) 167 { 168 if ($command == self::AUTO_DETECT) $command = $this->findSendmail() . " -bs"; 169 170 if (!strrpos($command, " -")) 171 { 172 throw new Swift_ConnectionException("Cannot set sendmail command with no command line flags. e.g. /usr/sbin/sendmail -t"); 173 } 174 $path = substr($command, 0, strrpos($command, " -")); 175 $flags = substr($command, strrpos($command, " -")+2); 176 $this->setPath($path); 177 $this->setFlags($flags); 178 } 179 /** 180 * Get the sendmail command (path + flags) 181 * @return string 182 */ 183 public function getCommand() 184 { 185 return $this->getPath() . " -" . $this->getFlags(); 186 } 187 /** 188 * Write a command to the open pipe 189 * @param string The command to write 190 * @throws Swift_ConnectionException If the pipe cannot be written to 191 */ 192 protected function pipeIn($command, $end="\r\n") 193 { 194 if (!$this->isAlive()) throw new Swift_ConnectionException("The sendmail process is not alive and cannot be written to."); 195 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."); 196 fflush($this->pipes[0]); 197 } 198 /** 199 * Read data from the open pipe 200 * @return string 201 * @throws Swift_ConnectionException If the pipe is not operating as expected 202 */ 203 protected function pipeOut() 204 { 205 if (strpos($this->getFlags(), "t") !== false) return; 206 if (!$this->isAlive()) throw new Swift_ConnectionException("The sendmail process is not alive and cannot be read from."); 207 $ret = ""; 208 $line = 0; 209 while (true) 210 { 211 $line++; 212 stream_set_timeout($this->pipes[1], $this->timeout); 213 $tmp = @fgets($this->pipes[1]); 214 if ($tmp === false) 215 { 216 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."); 217 } 218 $ret .= trim($tmp) . "\r\n"; 219 if ($tmp{3} == " ") break; 220 } 221 fflush($this->pipes[1]); 222 return $ret = substr($ret, 0, -2); 223 } 224 /** 225 * Read a full response from the buffer (this is spoofed if running in -t mode) 226 * @return string 227 * @throws Swift_ConnectionException Upon failure to read 228 */ 229 public function read() 230 { 231 if (strpos($this->getFlags(), "t") !== false) 232 { 233 switch (strtolower($this->request)) 234 { 235 case null: 236 return "220 Greetings"; 237 case "helo": case "ehlo": 238 return "250 hello"; 239 case "mail": case "rcpt": case "rset": 240 return "250 ok"; 241 case "quit": 242 return "221 bye"; 243 case "data": 244 $this->send = true; 245 return "354 go ahead"; 246 default: 247 return "250 ok"; 248 } 249 } 250 else return $this->pipeOut(); 251 } 252 /** 253 * Write a command to the process (leave off trailing CRLF) 254 * @param string The command to send 255 * @throws Swift_ConnectionException Upon failure to write 256 */ 257 public function write($command, $end="\r\n") 258 { 259 if (strpos($this->getFlags(), "t") !== false) 260 { 261 if (!$this->send && strpos($command, " ")) $command = substr($command, strpos($command, " ")+1); 262 elseif ($this->send) 263 { 264 $this->pipeIn($command); 265 } 266 $this->request = $command; 267 $this->send = (strtolower($command) == "data"); 268 } 269 else $this->pipeIn($command, $end); 270 } 271 /** 272 * Try to start the connection 273 * @throws Swift_ConnectionException Upon failure to start 274 */ 275 public function start() 276 { 277 $log = Swift_LogContainer::getLog(); 278 if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) 279 { 280 $log->add("Trying to start a sendmail process."); 281 } 282 if (!$this->getPath() || !$this->getFlags()) 283 { 284 throw new Swift_ConnectionException("Sendmail cannot be started without a path to the binary including flags."); 285 } 286 if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) 287 { 288 $log->add("Trying to stat the executable '" . $this->getPath() . "'."); 289 } 290 if (!@lstat($this->getPath())) 291 { 292 throw new Swift_ConnectionException( 293 "Sendmail cannot be seen with lstat(). The command given [" . $this->getCommand() . "] does not appear to be valid."); 294 } 295 296 $pipes_spec = array( 297 array("pipe", "r"), 298 array("pipe", "w"), 299 array("pipe", "w") 300 ); 301 302 $this->proc = proc_open($this->getCommand(), $pipes_spec, $this->pipes); 303 304 if (!$this->isAlive()) 305 { 306 throw new Swift_ConnectionException( 307 "The sendmail process failed to start. Please verify that the path exists and PHP has permission to execute it."); 308 } 309 } 310 /** 311 * Try to close the connection 312 */ 313 public function stop() 314 { 315 $log = Swift_LogContainer::getLog(); 316 if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) 317 { 318 $log->add("Terminating sendmail process."); 319 } 320 foreach ((array)$this->pipes as $pipe) 321 { 322 @fclose($pipe); 323 } 324 325 if ($this->proc) 326 { 327 proc_close($this->proc); 328 $this->pipes = null; 329 $this->proc = null; 330 } 331 } 332 /** 333 * Check if the process is still alive 334 * @return boolean 335 */ 336 public function isAlive() 337 { 338 return ($this->proc !== false 339 && is_resource($this->proc) 340 && is_resource($this->pipes[0]) 341 && is_resource($this->pipes[1]) 342 && $this->proc !== null); 343 } 344 /** 345 * Destructor. 346 * Cleans up by stopping any running processes. 347 */ 348 public function __destruct() 349 { 350 $this->stop(); 351 } 352} 353