/** * Initiates the connection to the server * * @return void */ private function connect() { if ($this->connection) { return; } $fqdn = fEmail::getFQDN(); fCore::startErrorCapture(E_WARNING); $host = $this->secure ? 'tls://' . $this->host : $this->host; $this->connection = fsockopen($host, $this->port, $error_int, $error_string, $this->timeout); foreach (fCore::stopErrorCapture('#ssl#i') as $error) { throw new fConnectivityException('There was an error connecting to the server. A secure connection was requested, but was not available. Try a non-secure connection instead.'); } if (!$this->connection) { throw new fConnectivityException('There was an error connecting to the server'); } stream_set_timeout($this->connection, $this->timeout); $response = $this->read('#^220 #'); if (!$this->find($response, '#^220[ -]#')) { throw new fConnectivityException('Unknown SMTP welcome message, %1$s, from server %2$s on port %3$s', join("\r\n", $response), $this->host, $this->port); } // Try sending the ESMTP EHLO command, but fall back to normal SMTP HELO $response = $this->write('EHLO ' . $fqdn, '#^250 #m'); if ($this->find($response, '#^500#')) { $response = $this->write('HELO ' . $fqdn, 1); } // If STARTTLS is available, use it if (!$this->secure && extension_loaded('openssl') && $this->find($response, '#^250[ -]STARTTLS#')) { $response = $this->write('STARTTLS', '#^220 #'); $affirmative = $this->find($response, '#^220[ -]#'); if ($affirmative) { do { if (isset($res)) { sleep(0.1); } $res = stream_socket_enable_crypto($this->connection, TRUE, STREAM_CRYPTO_METHOD_TLS_CLIENT); } while ($res === 0); } if (!$affirmative || $res === FALSE) { throw new fConnectivityException('Error establishing secure connection'); } $response = $this->write('EHLO ' . $fqdn, '#^250 #m'); } $this->max_size = 0; if ($match = $this->find($response, '#^250[ -]SIZE\\s+(\\d+)$#')) { $this->max_size = $match[0][1]; } $this->pipelining = (bool) $this->find($response, '#^250[ -]PIPELINING$#'); $auth_methods = array(); if ($match = $this->find($response, '#^250[ -]AUTH[ =](.*)$#')) { $auth_methods = array_map('strtoupper', explode(' ', $match[0][1])); } if (!$auth_methods || !$this->username) { return; } if (in_array('DIGEST-MD5', $auth_methods)) { $response = $this->write('AUTH DIGEST-MD5', 1); $this->handleErrors($response); $match = $this->find($response, '#^334 (.*)$#'); $challenge = base64_decode($match[0][1]); preg_match_all('#(?<=,|^)(\\w+)=("[^"]+"|[^,]+)(?=,|$)#', $challenge, $matches, PREG_SET_ORDER); $request_params = array(); foreach ($matches as $_match) { $request_params[$_match[1]] = $_match[2][0] == '"' ? substr($_match[2], 1, -1) : $_match[2]; } $missing_qop_auth = !isset($request_params['qop']) || !in_array('auth', explode(',', $request_params['qop'])); $missing_nonce = empty($request_params['nonce']); if ($missing_qop_auth || $missing_nonce) { throw new fUnexpectedException('The SMTP server %1$s on port %2$s claims to support DIGEST-MD5, but does not seem to provide auth functionality', $this->host, $this->port); } if (!isset($request_params['realm'])) { $request_params['realm'] = ''; } // Algorithm from http://www.ietf.org/rfc/rfc2831.txt $realm = $request_params['realm']; $nonce = $request_params['nonce']; $cnonce = fCryptography::randomString('32', 'hexadecimal'); $nc = '00000001'; $digest_uri = 'smtp/' . $this->host; $a1 = md5($this->username . ':' . $realm . ':' . $this->password, TRUE) . ':' . $nonce . ':' . $cnonce; $a2 = 'AUTHENTICATE:' . $digest_uri; $response = md5(md5($a1) . ':' . $nonce . ':' . $nc . ':' . $cnonce . ':auth:' . md5($a2)); $response_params = array('charset=utf-8', 'username="******"', 'realm="' . $realm . '"', 'nonce="' . $nonce . '"', 'nc=' . $nc, 'cnonce="' . $cnonce . '"', 'digest-uri="' . $digest_uri . '"', 'response=' . $response, 'qop=auth'); $response = $this->write(base64_encode(join(',', $response_params)), 2); } elseif (in_array('CRAM-MD5', $auth_methods)) { $response = $this->write('AUTH CRAM-MD5', 1); $match = $this->find($response, '#^334 (.*)$#'); $challenge = base64_decode($match[0][1]); $response = $this->write(base64_encode($this->username . ' ' . fCryptography::hashHMAC('md5', $challenge, $this->password)), 1); } elseif (in_array('LOGIN', $auth_methods)) { $response = $this->write('AUTH LOGIN', 1); $this->write(base64_encode($this->username), 1); $response = $this->write(base64_encode($this->password), 1); } elseif (in_array('PLAIN', $auth_methods)) { $response = $this->write('AUTH PLAIN ' . base64_encode($this->username . "" . $this->username . "" . $this->password), 1); } if ($this->find($response, '#^535[ -]#')) { throw new fValidationException('The username and password provided were not accepted for the SMTP server %1$s on port %2$s', $this->host, $this->port); } if (!array_filter($response)) { throw new fConnectivityException('No response was received for the authorization request'); } }