00001 <?php
00002
00017
00018
00019
00020 import('mail.Mail');
00021
00022 class SMTPMailer {
00023
00025 var $server;
00026
00028 var $port;
00029
00031 var $auth;
00032
00034 var $username;
00035
00037 var $password;
00038
00040 var $socket;
00041
00045 function SMTPMailer() {
00046 $this->server = Config::getVar('email', 'smtp_server');
00047 $this->port = Config::getVar('email', 'smtp_port');
00048 $this->auth = Config::getVar('email', 'smtp_auth');
00049 $this->username = Config::getVar('email', 'smtp_username');
00050 $this->password = Config::getVar('email', 'smtp_password');
00051 if (!$this->server)
00052 $this->server = 'localhost';
00053 if (!$this->port)
00054 $this->port = 25;
00055 }
00056
00065 function mail(&$mail, $recipients, $subject, $body, $headers = '') {
00066
00067 if (!$this->connect())
00068 return false;
00069
00070 if (!$this->receive('220'))
00071 return $this->disconnect('Did not receive expected 220');
00072
00073
00074 if (!$this->send($this->auth ? 'EHLO' : 'HELO', Request::getServerHost()))
00075 return $this->disconnect('Could not send HELO/HELO');
00076
00077 if (!$this->receive('250'))
00078 return $this->disconnect('Did not receive expected 250 (1)');
00079
00080 if ($this->auth) {
00081
00082 if (!$this->authenticate())
00083 return $this->disconnect('Could not authenticate');
00084 }
00085
00086
00087 $sender = $mail->getEnvelopeSender();
00088 if (!isset($sender) || empty($sender)) {
00089 $from = $mail->getFrom();
00090 if (isset($from['email']) && !empty($from['email']))
00091 $sender = $from['email'];
00092 else
00093 $sender = get_current_user() . '@' . Request::getServerHost();
00094 }
00095
00096 if (!$this->send('MAIL', 'FROM:<' . $sender . '>'))
00097 return $this->disconnect('Could not send sender');
00098
00099 if (!$this->receive('250'))
00100 return $this->disconnect('Did not receive expected 250 (2)');
00101
00102
00103 $rcpt = array();
00104 if (($addrs = $mail->getRecipients()) !== null)
00105 $rcpt = array_merge($rcpt, $addrs);
00106 if (($addrs = $mail->getCcs()) !== null)
00107 $rcpt = array_merge($rcpt, $addrs);
00108 if (($addrs = $mail->getBccs()) !== null)
00109 $rcpt = array_merge($rcpt, $addrs);
00110 foreach ($rcpt as $addr) {
00111 if (!$this->send('RCPT', 'TO:<' . $addr['email'] .'>'))
00112 return $this->disconnect('Could not send recipients');
00113 if (!$this->receive(array('250', '251')))
00114 return $this->disconnect('Did not receive expected 250 or 251');
00115 }
00116
00117
00118 if (!$this->send('DATA'))
00119 return $this->disconnect('Could not send DATA');
00120
00121 if (!$this->receive('354'))
00122 return $this->disconnect('Did not receive expected 354');
00123
00124 if (!$this->send('To:', empty($recipients) ? 'undisclosed-recipients:;' : $recipients))
00125 return $this->disconnect('Could not send recipients (2)');
00126
00127 if (!$this->send('Subject:', $subject))
00128 return $this->disconnect('Could not send subject');
00129
00130 $lines = explode(MAIL_EOL, $headers);
00131 for ($i = 0, $num = count($lines); $i < $num; $i++) {
00132 if (preg_match('/^bcc:/i', $lines[$i]))
00133 continue;
00134 if (!$this->send($lines[$i]))
00135 return $this->disconnect('Could not send headers');
00136 }
00137
00138 if (!$this->send(''))
00139 return $this->disconnect('Could not send CR');
00140
00141 $lines = explode(MAIL_EOL, $body);
00142 for ($i = 0, $num = count($lines); $i < $num; $i++) {
00143 if (substr($lines[$i], 0, 1) == '.')
00144 $lines[$i] = '.' . $lines[$i];
00145 if (!$this->send($lines[$i]))
00146 return $this->disconnect('Could not send body');
00147 }
00148
00149
00150 if (!$this->send('.'))
00151 return $this->disconnect('Could not send EOT');
00152
00153 if (!$this->receive('250'))
00154 return $this->disconnect('Did not receive expected 250 (3)');
00155
00156
00157 return $this->disconnect();
00158 }
00159
00164 function connect() {
00165 $this->socket = fsockopen($this->server, $this->port, $errno, $errstr, 30);
00166 if (!$this->socket)
00167 return false;
00168 return true;
00169 }
00170
00176 function disconnect($error = '') {
00177 if (!$this->send('QUIT') || !$this->receive('221') && empty($error)) {
00178 $error = 'Unable to disconnect from mail server';
00179 }
00180 fclose($this->socket);
00181
00182 if (!empty($error)) {
00183 error_log('OJS SMTPMailer: ' . $error);
00184 return false;
00185 }
00186 return true;
00187 }
00188
00195 function send($command, $data = '') {
00196 $ret = @fwrite($this->socket, $command . (empty($data) ? '' : ' ' . $data) . "\r\n");
00197 if ($ret !== false)
00198 return true;
00199 return false;
00200 }
00201
00207 function receive($expected) {
00208 return $this->receiveData($expected, $data);
00209 }
00210
00217 function receiveData($expected, &$data) {
00218 do {
00219 $line = @fgets($this->socket);
00220 } while($line !== false && substr($line, 3, 1) != ' ');
00221
00222 if ($line !== false) {
00223 $response = substr($line, 0, 3);
00224 $data = substr($line, 4);
00225 if ((is_array($expected) && in_array($response, $expected)) || ($response === $expected))
00226 return true;
00227 }
00228 return false;
00229 }
00230
00235 function authenticate() {
00236 switch (strtoupper($this->auth)) {
00237 case 'PLAIN':
00238 return $this->authenticate_plain();
00239 case 'LOGIN':
00240 return $this->authenticate_login();
00241 case 'CRAM-MD5':
00242 return $this->authenticate_cram_md5();
00243 case 'DIGEST-MD5':
00244 return $this->authenticate_digest_md5();
00245 default:
00246 return true;
00247 }
00248 }
00249
00254 function authenticate_plain() {
00255 $authString = $this->username . chr(0x00) . $this->username . chr(0x00) . $this->password;
00256 if (!$this->send('AUTH', 'PLAIN ' . base64_encode($authString)))
00257 return false;
00258 return $this->receive('235');
00259 }
00260
00265 function authenticate_login() {
00266 if (!$this->send('AUTH', 'LOGIN'))
00267 return false;
00268 if (!$this->receive('334'))
00269 return false;
00270 if (!$this->send(base64_encode($this->username)))
00271 return false;
00272 if (!$this->receive('334'))
00273 return false;
00274 if (!$this->send(base64_encode($this->password)))
00275 return false;
00276 return $this->receive('235');
00277 }
00278
00283 function authenticate_cram_md5() {
00284 if (!$this->send('AUTH', 'CRAM-MD5'))
00285 return false;
00286 if (!$this->receiveData('334', $digest))
00287 return false;
00288 $authString = $this->username . ' ' . $this->hmac_md5(base64_decode($digest), $this->password);
00289 if (!$this->send(base64_encode($authString)))
00290 return false;
00291 return $this->receive('235');
00292 }
00293
00298 function authenticate_digest_md5() {
00299 if (!$this->send('AUTH', 'DIGEST-MD5'))
00300 return false;
00301 if (!$this->receiveData('334', $data))
00302 return false;
00303
00304
00305 $challenge = array();
00306 $data = base64_decode($data);
00307 while(!empty($data)) {
00308 @list($key, $rest) = explode('=', $data, 2);
00309 if ($rest[0] != '"') {
00310 @list($value, $data) = explode(',', $rest, 2);
00311 } else {
00312 @list($value, $data) = explode('"', substr($rest, 1), 2);
00313 $data = substr($data, 1);
00314 }
00315 if (!empty($value))
00316 $challenge[$key] = $value;
00317 }
00318
00319 $realms = explode(',', $challenge['realm']);
00320 if (empty($realms))
00321 $realm = $this->server;
00322 else
00323 $realm = $realms[0];
00324 $qop = 'auth';
00325 $nc = '00000001';
00326 $uri = 'smtp/' . $this->server;
00327 $cnonce = md5(uniqid(mt_rand(), true));
00328
00329 $a1 = pack('H*', md5($this->username . ':' . $realm . ':' . $this->password)) . ':' . $challenge['nonce'] . ':' . $cnonce;
00330
00331
00332 if (isset($authzid))
00333 $a1 .= ':' . $authzid;
00334
00335 $a2 = 'AUTHENTICATE:' . $uri;
00336
00337
00338 if ($qop == 'auth-int' || $qop == 'auth-int')
00339 $a2 .= ':00000000000000000000000000000000';
00340
00341 $response = md5(md5($a1) . ':' . ($challenge['nonce'] . ':' . $nc . ':' . $cnonce. ':' . $qop . ':' . md5($a2)));
00342
00343 $authString = sprintf('charset=utf-8,username="%s",realm="%s",nonce="%s",nc=%s,cnonce="%s",digest-uri="%s",response=%s,qop=%s', $this->username, $realm, $challenge['nonce'], $nc, $cnonce, $uri, $response, $qop);
00344 if (!$this->send(base64_encode($authString)))
00345 return false;
00346 if (!$this->receive('334'))
00347 return false;
00348 if (!$this->send(''))
00349 return false;
00350 return $this->receive('235');
00351 }
00352
00361 function hmac($hashfn, $blocksize, $data, $key) {
00362 if (strlen($key) > $blocksize)
00363 $key = pack('H*', $hashfn($key));
00364 $key = str_pad($key, $blocksize, chr(0x00));
00365 $ipad = str_repeat(chr(0x36), $blocksize);
00366 $opad = str_repeat(chr(0x5C), $blocksize);
00367 $hmac = pack('H*', $hashfn(($key ^ $opad) . pack('H*', $hashfn(($key ^ $ipad) . $data))));
00368 return bin2hex($hmac);
00369 }
00370
00375 function hmac_md5($data, $key = '') {
00376 return $this->hmac('md5', 64, $data, $key);
00377 }
00378
00379 }
00380
00381 ?>