/** * login * * The script checks provided name and password against remote server. * * This is done by transmitting the user name and the password * while opening a FTP session to the server. * * @param string the nickname of the user * @param string the submitted password * @return TRUE on succesful authentication, FALSE othewise */ function login($name, $password) { global $context; // we need some parameters if (!isset($this->attributes['authenticator_parameters']) || !$this->attributes['authenticator_parameters']) { Logger::error(i18n::s('Please provide parameters to the authenticator.')); return FALSE; } // prepare network parameters $server = $this->attributes['authenticator_parameters']; if (strstr($server, ':')) { list($server, $port) = explode(':', $server, 2); } else { $port = 21; } // open network socket if (!($handle = Safe::fsockopen($server, $port))) { Logger::error(sprintf(i18n::s('Impossible to connect to %.'), $this->attributes['authenticator_parameters'])); return FALSE; } // read welcome banner if (!($line = fgets($handle, 256)) || !strstr($line, '2')) { fclose($handle); Logger::error(sprintf(i18n::s('Invalid banner message from %s.'), $this->attributes['authenticator_parameters'])); return FALSE; } // submit name fputs($handle, "USER {$username}\r\n"); if (!($line = fgets($handle, 256)) || !strstr($line, '3')) { fclose($handle); Logger::error(sprintf(i18n::s('Impossible to submit name to %s.'), $this->attributes['authenticator_parameters'])); return FALSE; } // submit password fputs($handle, "PASS {$password}\r\n"); if (!($line = fgets($handle, 256)) || !strstr($line, '2')) { fclose($handle); Logger::error(sprintf(i18n::s('Impossible to submit password to %s.'), $this->attributes['authenticator_parameters'])); return FALSE; } // close ftp session fputs($handle, "QUIT\r\n"); fclose($handle); // this is a valid user return TRUE; }
/** * attempt to use the trackback interface * * @param string some text, extracted from the target site, to extract the broker URL, if any * @param string the source address * @param string the target address from which the text has been extracted * @param string title of the source page * @param string excerpt of the source page * @param string blog name of the source page * @return TRUE if the target site has been pinged back, FALSE otherwise * * @link http://www.movabletype.org/docs/mttrackback.html TrackBack Technical Specification */ public static function ping_as_trackback($text, $source, $target, $title = '', $excerpt = '', $blog_name = '') { global $context; // extract all rdf blocks preg_match_all('/<rdf:RDF(.*)<\\/rdf:RDF>/iUs', $text, $blocks); // nothing to do if (!@count($blocks[1])) { return FALSE; } // look for the broker $broker = array(); foreach ($blocks[1] as $block) { // seek the trackback interface if (!preg_match('/(dc:identifier|about)="' . preg_quote($target, '/') . '/mi', $block)) { continue; } // extract the broker link if (preg_match('/trackback:ping="([^"]+)"/mi', $block, $broker)) { break; } } // trackback interface not supported at this page if (!isset($broker[1])) { return FALSE; } // parse the broker URL $items = @parse_url($broker[1]); // no host, assume it's us if (!($host = $items['host'])) { $host = $context['host_name']; } // no port, assume the standard if (!isset($items['port']) || !($port = $items['port'])) { $port = 80; } // outbound web is not authorized if (isset($context['without_outbound_http']) && $context['without_outbound_http'] == 'Y') { if (isset($context['debug_trackback']) && $context['debug_trackback'] == 'Y') { Logger::remember('links/links.php: Links::ping_as_trackback()', 'Outbound HTTP is not authorized.', 'debug'); } return FALSE; } // connect to the server if (!($handle = Safe::fsockopen($host, $port, $errno, $errstr, 30))) { if (isset($context['debug_trackback']) && $context['debug_trackback'] == 'Y') { Logger::remember('links/links.php: Links::ping_as_trackback()', sprintf('Impossible to connect to %s.', $host . ':' . $port), 'debug'); } return FALSE; } // ensure enough execution time Safe::set_time_limit(30); // build the path, including any query $path = $items['path']; if (isset($items['query']) && $items['query']) { $path .= '?' . $items['query']; } // encode the content $data = 'title=' . urlencode($title) . '&url=' . urlencode($source) . '&excerpt=' . urlencode($excerpt) . '&blog_name=' . urlencode($blog_name); $headers = 'Content-Type: application/x-www-form-urlencoded' . CRLF . 'Content-Length: ' . strlen($data) . CRLF; // actual trackback, through HTTP POST $request = "POST " . $path . " HTTP/1.0" . CRLF . 'Host: ' . $host . CRLF . "User-Agent: YACS (www.yacs.fr)" . CRLF . "Connection: close" . CRLF . $headers . CRLF . $data; // save the request if debug mode if (isset($context['debug_trackback']) && $context['debug_trackback'] == 'Y') { Logger::remember('links/links.php: Links::ping_as_trackback() request', str_replace("\r\n", "\n", $request), 'debug'); } // submit the request fputs($handle, $request); // we are interested only in the very first bytes of the response $code = fread($handle, 15); fclose($handle); // save the response if debug mode if (isset($context['debug_trackback']) && $context['debug_trackback'] == 'Y') { Logger::remember('links/links.php: Links::ping_as_trackback() response', $code . '...', 'debug'); } // check HTTP status if (!preg_match('/^HTTP\\/[0-9\\.]+ 200/', $code)) { return FALSE; } // successful trackback if (isset($context['debug_trackback']) && $context['debug_trackback'] == 'Y') { Logger::remember('links/links.php: Links::ping_as_trackback() success', $broker[1], 'debug'); } return TRUE; }
/** * validate a link * * This function submits a HTTP request to the target server to check that the page actually exists * * @param the link to validate * @return A date if Last-Modified has been provided, or TRUE if the link is reachable, FALSE otherwise */ function validate($url) { global $context; // parse this url $items = @parse_url($url); // assume the link is correct if not http if ($items['scheme'] && $items['scheme'] != 'http') { return TRUE; } // no host, assume it's us if (!($host = $items['host'])) { $host = $context['host_name']; } // sometime parse_url() adds a '_' $host = rtrim($host, '_'); // no port, assume the standard if (!($port = $items['port'])) { $port = 80; } // assume the link is correct when outbound web is not authorized if (isset($context['without_outbound_http']) && $context['without_outbound_http'] == 'Y') { return TRUE; } // open a network connection -- wait for up to 10 seconds for the TCP connection if (!($handle = Safe::fsockopen($host, $port, $errno, $errstr, 10))) { if ($context['with_debug'] == 'Y') { logger::remember('links/link.php: ' . $host . ':' . $port . ' is not reachable', $url, 'debug'); } return FALSE; } // ensure enough execution time Safe::set_time_limit(30); // build the path $path = $items['path']; if (!$path) { $path = '/'; } // sometime parse_url() adds a '_' $path = rtrim($path, '_'); // include any query if ($items['query']) { $path .= '?' . $items['query']; } // send an HTTP request fputs($handle, 'HEAD ' . $path . " HTTP/1.0" . CRLF . 'Host: ' . $host . CRLF . "User-Agent: YACS (www.yacs.fr)" . CRLF . "Connection: close" . CRLF . CRLF); // we are interested into the header only $response = ''; while (!feof($handle) && strlen($response) < 5242880) { // ask for Ethernet-sized chunks $chunk = fread($handle, 1500); // split on headers boundary $here = strpos($chunk, CRLF . CRLF); if ($here !== FALSE) { $chunk = substr($chunk, 0, $here); $response .= $chunk; break; } // gather header information $response .= $chunk; } fclose($handle); // split headers into lines $lines = explode(CRLF, $response); // ensure we have a valid HTTP status line if (!preg_match('/^HTTP\\/[0-9\\.]+ 20\\d /', $lines[0])) { if ($context['with_debug'] == 'Y') { logger::remember('links/link.php: bad status: ' . $lines[0], $url, 'debug'); } return FALSE; } // scan lines for "Last-Modified" header foreach ($lines as $line) { if (preg_match('/^Last-Modified: (.*?)/', $line, $matches)) { // return the stamp for this link return date("Y-m-d H:i:s", strtotime($matches[1])); } } // no date, but the link has been validated anyway return TRUE; }
/** * get a list of remote resources * * This function performs a REST call against a web services that provides a RSS-encoded response. * * Minimum example: * [php] * $result = Call::list_resources($url); * if(!$result[0]) * echo $result[1]; // error message * else * ... // use call result from $result[1] * [/php] * * @param string the url to use * @param array the parameters to transmit * @return an array of which the first value indicates call success or failure * * @see search.php */ public static function list_resources($url, $parameters = NULL) { global $context; // encode the request $data = ''; foreach ($parameters as $label => $value) { if ($data) { $data .= '&'; } $data .= urlencode($label) . '=' . urlencode($value); } $headers = ''; $headers .= 'Content-Type: application/x-www-form-urlencoded' . CRLF; $headers .= 'Content-Length: ' . strlen($data) . CRLF; // parse the target URL $items = @parse_url($url); // no host, assume it's us if (!($host = $items['host'])) { $host = $context['host_name']; } // no port, assume the standard if (!isset($items['port']) || !($port = $items['port'])) { $port = 80; } // outbound web is not authorized if (isset($context['without_outbound_http']) && $context['without_outbound_http'] == 'Y') { return array(FALSE, 'Outbound HTTP is not authorized.'); } // connect to the server if (!($handle = Safe::fsockopen($host, $port, $errno, $errstr, 30))) { return array(FALSE, sprintf('Impossible to connect to %s.', $host . ':' . $port)); } // ensure enough execution time Safe::set_time_limit(30); // build the path, including any query $path = $items['path']; if ($items['query']) { $path .= '?' . $items['query']; } // build an HTTP request $request = "POST " . $path . " HTTP/1.0" . CRLF . 'Host: ' . $host . CRLF . "Accept-Encoding: gzip" . CRLF . "User-Agent: YACS (www.yacs.fr)" . CRLF . "Connection: close" . CRLF . $headers . CRLF . $data; // save the request if debug mode if ($context['debug_call'] == 'Y') { Logger::remember('services/call.php: Call::list_resources() request', str_replace("\r\n", "\n", $request), 'debug'); } // submit the request fputs($handle, $request); // get everything by Ethernet-sized chunks $response = ''; while (!feof($handle) && strlen($response) < 5242880) { $response .= fread($handle, 1500); } fclose($handle); // ensure we have a valid HTTP status line if (preg_match('/^HTTP/', $response) && !preg_match('/^HTTP\\/[0-9\\.]+ 200 /', $response)) { $lines = explode("\n", $response, 2); return array(FALSE, 'Unexpected HTTP status "' . $lines[0] . '"'); } // separate headers from body list($headers, $content) = explode(CRLF . CRLF, $response, 2); // uncompress payload if necessary if (preg_match('/Content-Encoding: \\s*gzip/i', $headers)) { $content = gzinflate(substr($content, 10)); } // save the response if debug mode if ($context['debug_call'] == 'Y') { Logger::remember('services/call.php: Call::list_resources() response', str_replace("\r\n", "\n", $headers . "\n\n" . $content), 'debug'); } // we understand only text responses if (!preg_match('/^Content-Type: text/m', $headers)) { return array(FALSE, 'Impossible to process not-textual response'); } // passthrough if not xml if (!preg_match('/^Content-Type: text\\/xml/m', $headers)) { return $content; } // select a codec handler include_once $context['path_to_root'] . 'services/codec.php'; include_once $context['path_to_root'] . 'services/rss_codec.php'; $codec = new RSS_Codec(); if (!is_object($codec)) { return array(FALSE, 'Impossible to load codec RSS_Codec'); } // decode the result return $codec->import_response($content, $headers, $parameters); }
/** * process all messages from one mailbox * * This is original code compliant to RFC 1939 for the authentication, * fetching and processing of messages queued in a POP3 mailbox. * * @param array of mailbox attributes ($server, $account, $password) * @return the number of processed messages */ public static function process_queue($queue) { global $context; // useless if we don't have a valid database connection if (!$context['connection']) { return 0; } // make queue parameters available $context['mail_queue'] = $queue; // use queue parameters to connect to the server list($server, $account, $password, $allowed, $match, $section, $options, $hooks, $prefix, $suffix) = $queue; // no host, assume it's us if (!$server) { $server = $context['host_name']; } // assume the standard pop3 socket $port = 110; // use alternate port if required to do so if (preg_match('/^(.+):([0-9]+)$/', $server, $matches)) { $server = $matches[1]; $port = intval($matches[2]); } // ensure that we can support tls communications if (isset($server) && !strncmp($server, 'ssl://', 6) && is_callable('extension_loaded') && !extension_loaded('openssl')) { Logger::remember('agents/messages.php: Load the OpenSSL extension to support secured transmissions to mail server ' . $server); return 0; } // open a network connection if (!($handle = Safe::fsockopen($server, $port, $errno, $errstr, 10))) { Logger::remember('agents/messages.php: ' . sprintf('Impossible to connect to %s', $server)); return 0; } // ensure enough execution time Safe::set_time_limit(30); // get server banner if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: Impossible to get banner of ' . $server); fclose($handle); return 0; } if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP <-', rtrim($reply), 'debug'); } // expecting an OK if (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Mail service is closed at ' . $server, rtrim($reply)); fclose($handle); return 0; } // maybe the server accepts APOP $stamp = ''; if (preg_match('/<.+@.+>/U', $reply, $matches)) { $stamp = $matches[0]; } // we will go with APOP, only if explicitly allowed $authenticated = FALSE; if ($stamp && preg_match('/\\bwith_apop\\b/i', $options)) { // the digest if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP stamp', $stamp . $password, 'debug'); } $hash = md5($stamp . $password); // send user name and hash $request = 'APOP ' . $account . ' ' . $hash; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to APOP command at ' . $server); fclose($handle); return 0; } if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP <-', rtrim($reply), 'debug'); } if (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Impossible to authenticate account ' . $account . ' at ' . $server, rtrim($reply)); } else { $authenticated = TRUE; } } // we will transmit the password in clear if (!$authenticated) { // send user name $request = 'USER ' . $account; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to USER command at ' . $server); fclose($handle); return 0; } if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP <-', rtrim($reply), 'debug'); } if (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Unknown account ' . $account . ' at ' . $server, rtrim($reply)); fclose($handle); return 0; } // send password $request = 'PASS ' . $password; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to PASS command at ' . $server); fclose($handle); return 0; } if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP <-', rtrim($reply), 'debug'); } if (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Invalid password for account ' . $account . ' at ' . $server, rtrim($reply)); fclose($handle); return 0; } } // ask for information $request = 'STAT'; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to STAT command at ' . $server); fclose($handle); return 0; } if (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Rejected command STAT at ' . $server, 'reply="' . rtrim($reply) . '"'); fclose($handle); return 0; } // evaluate queue size $tokens = explode(' ', $reply); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP <-', rtrim($reply), 'debug'); } $queue_size = @$tokens[1]; // nothing to do if (!$queue_size) { fclose($handle); return 0; } // limit the number of messages processed on each tick if ($queue_size > 10) { $queue_size = 10; } // process messages one by one for ($index = 1; $index <= $queue_size; $index++) { // ask for the message $request = 'RETR ' . $index; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to RETR command at ' . $server); fclose($handle); return $index - 1; } if (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Rejected command RETR at ' . $server, rtrim($reply)); fclose($handle); return $index - 1; } // fetch one message at a time $message = ''; while (!feof($handle)) { // ensure enough execution time Safe::set_time_limit(30); // get a chunk (up to ten 1500-byte Ethernet packets) $chunk = fread($handle, 16384); // look for message end if (preg_match("/(.*)\\.\r\n\$/s", $chunk, $matches)) { $message .= $matches[1]; break; } // not yet at the end $message .= $chunk; } // suppress the message from the mailbox before entering into the database $request = 'DELE ' . $index; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to DELE command at ' . $server); } elseif (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Rejected command DELE at ' . $server, rtrim($reply)); } // file the message if in debug mode if ($context['debug_messages'] == 'Y' && Safe::make_path('temporary/agents')) { Safe::file_put_contents('temporary/agents/' . uniqid('message_'), $message); } // process the message Messages::process_message($message); } // close the session to actually purge the queue $request = 'QUIT'; fputs($handle, $request . CRLF); if ($context['debug_messages'] == 'Y') { Logger::remember('agents/messages.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('agents/messages.php: No reply to QUIT command at ' . $server); } elseif (strncmp($reply, '+OK', 3)) { Logger::remember('agents/messages.php: Rejected command QUIT at ' . $server, rtrim($reply)); } if ($queue_size > 0) { Logger::remember('agents/messages.php: ' . $queue_size . ' message(s) have been processed from ' . $server); } fclose($handle); return $queue_size; }
/** * scan a file for viruses * * This function connects to ClamAV daemon, if possible, to scan the referred file. * * @param string absolute path of the file to scan * @return string 'Y' if the file has been infected, '?' if clamav is not available, or 'N' if no virus has been found */ public static function has_virus($file) { global $context; // file scanning must be configured if (!isset($context['clamav_check']) || $context['clamav_check'] === 'N') { return 'N'; } // we can't connect to clamav daemon $server = 'localhost'; if (!($handle = Safe::fsockopen($server, 3310, $errno, $errstr, 1))) { if ($context['with_debug'] == 'Y') { Logger::remember('files/files.php: Unable to connect to CLAMAV daemon', '', 'debug'); } return '?'; } // ensure enough execution time Safe::set_time_limit(30); // scan uploaded file $request = 'SCAN ' . $file; fputs($handle, $request . CRLF); if ($context['with_debug'] == 'Y') { Logger::remember('files/files.php: CLAMAV ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('files/files.php: No reply to SCAN command at ' . $server); fclose($handle); return '?'; } if ($context['with_debug'] == 'Y') { Logger::remember('files/files.php: CLAMAV <-', $reply, 'debug'); } // file has been infected! if (!stripos($reply, ': ok')) { Logger::remember('files/files.php: Infected upload by ' . Surfer::get_name()); fclose($handle); return 'Y'; } // everything is ok fclose($handle); return 'N'; }
/** * login * * The script checks provided name and password against remote server. * * This is done by posting the user name and the password * to the web server. * * @param string the nickname of the user * @param string the submitted password * @return TRUE on succesful authentication, FALSE othewise */ function login($name, $password) { global $context; // we need some parameters if (!isset($this->attributes['authenticator_parameters']) || !$this->attributes['authenticator_parameters']) { Logger::error(i18n::s('Please provide parameters to the authenticator.')); return FALSE; } // extract parameters $parameters = explode(" ", $this->attributes['authenticator_parameters']); // ensure a minimum number of parameters if (count($parameters) != 3) { Logger::error(i18n::s('Provide expected parameters to the REST POST authenticator.')); return FALSE; } // parse URL format if (!($url = $parameters[0])) { Logger::error(i18n::s('Wrong format of the URL target for the HTTP authenticator.')); return FALSE; } // prepare raw POST payload $payload = urlencode($parameters[1]) . "=" . urlencode($name) . "&" . urlencode($parameters[2]) . "=" . urlencode($password); // submit credentials to the authenticating server include_once $context['path_to_root'] . 'services/call.php'; // build an HTTP request $request = "POST " . $url . " HTTP/1.0" . CRLF . 'Host: ' . $host . CRLF . "Accept-Encoding: gzip" . CRLF . "User-Agent: YACS (www.yacs.fr)" . CRLF . "Connection: close" . CRLF . "Content-Type: application/x-www-form-urlencoded" . CRLF . "Content-Length: " . strlen($payload) . CRLF . CRLF . $payload; // parse the target URL $items = @parse_url($url); // no host, assume it's us if (!isset($items['host']) || !($host = $items['host'])) { $host = $context['host_name']; } // no port, assume the standard if (!isset($items['port']) || !($port = $items['port'])) { $port = 80; } // outbound web is not authorized if (isset($context['without_outbound_http']) && $context['without_outbound_http'] == 'Y') { Logger::error(i18n::s('Outbound HTTP is not authorized.')); return FALSE; } // connect to the server if (!($handle = Safe::fsockopen($host, $port, $errno, $errstr, 30))) { Logger::error(sprintf(i18n::s('Impossible to connect to %.'), $items['host'] . ':' . $items['port'])); return FALSE; } // ensure enough execution time Safe::set_time_limit(30); // build the path, including any query $path = $items['path']; if (!$path) { $path = '/'; } if (isset($items['query']) && $items['query']) { $path .= '?' . $items['query']; } // submit the request fputs($handle, $request); // get everything by Ethernet-sized chunks $response = ''; while (!feof($handle) && strlen($response) < 5242880) { $response .= fread($handle, 1500); } fclose($handle); // ensure we have a valid HTTP status line if (preg_match('/^HTTP\\/[0-9\\.]+ 200 /', $response)) { return TRUE; } // failed authentication return FALSE; }
/** * connect to the mail server * * This function opens a network connection to the server, authenticate if required to do so, * and set $context['mail_handle'] to be used for actual transmissions. * * If parameter $context['mail_variant'] is set to 'smtp', a SMTP connection is * established with the computer specified in $context['mail_server']. If some credentials * are provided in $context['mail_account'] and $context['mail_password'], they are * transmitted to the server as per protocol extension. CRAM-MD5, LOGIN and PLAIN authentication * schemes have been implemented. * * @link http://tools.ietf.org/rfc/rfc2104.txt HMAC * @link http://www.fehcom.de/qmail/smtpauth.html * * If parameter $context['mail_variant'] is set to 'pop3', and if credentials have been * set in $context['mail_account'] and in $context['mail_password'], a POP3 connection * is made to the mail server just to authenticate, and then a SMTP connection * is established to actually transmit messages. If a secured communication has been * configured for SMTP, then a secured POP3 communication is performed on port 995. Else * a vanilla POP3 transaction is done on regular port 110. * * For any other value of $context['mail_variant'], or if the parameter is not set, * the function relies on the PHP mail() function to do the job. If the parameter * $context['mail_server'] is set, it overloads php.ini settings. Therefore you can change * the SMTP server used for transmission without the needs to edit the php.ini file. * * The parameter $context['mail_server'] can call for SSL/TLS support, or use a specific * port number, as in the following examples: * * [snippet] * ssl://mail.server.com * mail.server.com:234 * [/snippet] * * @return mixed the socket handle itself, of FALSE on error * * @see control/configure.php */ private static function connect() { global $context; // we already have an open handle if (isset($context['mail_handle'])) { return $context['mail_handle']; } // email services have to be activated if (!isset($context['with_email']) || $context['with_email'] != 'Y') { Logger::error(i18n::s('E-mail has not been enabled on this system.')); return FALSE; } // define target smtp server $port = 25; if (isset($context['mail_server'])) { $server = $context['mail_server']; // use alternate port if required to do so if (preg_match('/^(.+):([0-9]+)$/', $server, $matches)) { $server = $matches[1]; $port = intval($matches[2]); } } // ensure that we can support tls communications if (isset($server) && !strncmp($server, 'ssl://', 6) && is_callable('extension_loaded') && !extension_loaded('openssl')) { logger::remember('shared/mailer.php: Load the OpenSSL extension to support secured transmissions to mail server ' . $server); return FALSE; } // go for POP authentication if (isset($server) && isset($context['mail_variant']) && $context['mail_variant'] == 'pop3') { // authenticate to a pop3 server if (isset($context['mail_account']) && isset($context['mail_password'])) { // select which port to use if (strncmp($server, 'ssl://', 6)) { $pop3_port = 110; } else { $pop3_port = 995; } // open a network connection if (!($handle = Safe::fsockopen($server, $pop3_port, $errno, $errstr, 10))) { if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: fsockopen:', $errstr . ' (' . $errno . ')', 'debug'); } Logger::remember('shared/mailer.php: ' . sprintf('Impossible to connect to %s', $server . ':' . $pop3_port)); return FALSE; } // ensure enough execution time Safe::set_time_limit(30); // get server banner if (($reply = fgets($handle)) === FALSE) { Logger::remember('shared/mailer.php: Impossible to get banner of ' . $server); fclose($handle); return FALSE; } if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: POP <-', $reply, 'debug'); } // expecting an OK if (strncmp($reply, '+OK', 3)) { Logger::remember('shared/mailer.php: Mail service is closed at ' . $server, $reply); fclose($handle); return FALSE; } // send user name $request = 'USER ' . $context['mail_account']; fputs($handle, $request . CRLF); if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('shared/mailer.php: No reply to USER command at ' . $server); fclose($handle); return FALSE; } if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: POP <-', $reply, 'debug'); } if (strncmp($reply, '+OK', 3)) { Logger::remember('shared/mailer.php: Unknown account ' . $context['mail_account'] . ' at ' . $server, $reply); fclose($handle); return FALSE; } // send password $request = 'PASS ' . $context['mail_password']; fputs($handle, $request . CRLF); if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: POP ->', $request, 'debug'); } // expecting an OK if (($reply = fgets($handle)) === FALSE) { Logger::remember('shared/mailer.php: No reply to PASS command at ' . $server); fclose($handle); return FALSE; } if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: POP <-', $reply, 'debug'); } if (strncmp($reply, '+OK', 3)) { Logger::remember('shared/mailer.php: Invalid password for account ' . $account . ' at ' . $server, $reply); fclose($handle); return FALSE; } // we just wanted to authenticate fclose($handle); } } // we manage directly the SMTP transaction if (isset($server) && isset($context['mail_variant']) && ($context['mail_variant'] == 'pop3' || $context['mail_variant'] == 'smtp')) { // open a network connection if (!($handle = Safe::fsockopen($server, $port, $errno, $errstr, 10))) { if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: fsockopen:', $errstr . ' (' . $errno . ')', 'debug'); } Logger::remember('shared/mailer.php: ' . sprintf('Impossible to connect to %s', $server . ':' . $port)); return FALSE; } // ensure enough execution time Safe::set_time_limit(30); // get server banner if (($response = Mailer::parse_response($handle, 220)) === FALSE) { Logger::remember('shared/mailer.php: Impossible to get banner of ' . $server); fclose($handle); return FALSE; } // provide our logical name if (strpos($response, 'ESMTP')) { $request = 'EHLO ' . $context['host_name']; } else { $request = 'HELO ' . $context['host_name']; } fputs($handle, $request . CRLF); if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: SMTP ->', $request, 'debug'); } // expecting a welcome message if (($response = Mailer::parse_response($handle, 250)) === FALSE) { Logger::remember('shared/mailer.php: Command EHLO has been rejected at ' . $server); fclose($handle); return FALSE; } // authenticate as per SMTP protocol extension if (isset($context['mail_account']) && isset($context['mail_password']) && preg_match('/^AUTH (.+)$/m', $response, $matches)) { // CRAM-MD5 -- the preferred method if (strpos($matches[1], 'CRAM-MD5') !== FALSE) { // get the challenge $request = 'AUTH CRAM-MD5'; fputs($handle, $request . CRLF); if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: SMTP ->', $request, 'debug'); } if (($response = Mailer::parse_response($handle, 334)) === FALSE) { Logger::remember('shared/mailer.php: Command AUTH has been rejected at ' . $server); fclose($handle); return FALSE; } $challenge = base64_decode($response); // from password to a 64 bytes block if (strlen($context['mail_password']) < 64) { $key = str_pad($context['mail_password'], 64, chr(0)); } elseif (strlen($context['mail_password']) > 64) { $key = str_pad(pack('H32', md5($context['mail_password'])), 64, chr(0)); } else { $key = $context['mail_password']; } // compute HMAC-MD5 $inner = $key ^ str_repeat(chr(0x36), 64); $outer = $key ^ str_repeat(chr(0x5c), 64); $digest = md5($outer . pack('H32', md5($inner . $challenge))); // answer the challenge $request = base64_encode($context['mail_account'] . ' ' . $digest); fputs($handle, $request . CRLF); if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: SMTP ->', $request, 'debug'); } // LOGIN } elseif (strpos($matches[1], 'LOGIN') !== FALSE) { $request = 'AUTH LOGIN'; fputs($handle, $request . CRLF); if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: SMTP ->', $request, 'debug'); } if (Mailer::parse_response($handle, 334) === FALSE) { Logger::remember('shared/mailer.php: Command AUTH has been rejected at ' . $server); fclose($handle); return FALSE; } $request = base64_encode($context['mail_account']); fputs($handle, $request . CRLF); if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: SMTP ->', $request, 'debug'); } if (Mailer::parse_response($handle, 334) === FALSE) { Logger::remember('shared/mailer.php: Command AUTH has been rejected at ' . $server); fclose($handle); return FALSE; } $request = base64_encode($context['mail_password']); fputs($handle, $request . CRLF); if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: SMTP ->', $request, 'debug'); } // PLAIN } elseif (strpos($matches[1], 'PLAIN') !== FALSE) { $request = 'AUTH PLAIN ' . base64_encode("" . $context['mail_account'] . "" . $context['mail_password']); fputs($handle, $request . CRLF); if ($context['debug_mail'] == 'Y') { Logger::remember('shared/mailer.php: SMTP ->', $request, 'debug'); } } // expecting an OK if (Mailer::parse_response($handle, 235) === FALSE) { Logger::remember('shared/mailer.php: Command AUTH has been rejected at ' . $server); fclose($handle); return FALSE; } } // ready to submit messages $context['mail_handle'] = $handle; return $handle; // rely on system settings and PHP } elseif (is_callable('mail')) { // set the SMTP server if ($server) { Safe::ini_set('SMTP', $server); } // set the SMTP sender if (isset($context['mail_from']) && $context['mail_from']) { Safe::ini_set('sendmail_from', $context['mail_from']); } // ready to submit messages $context['mail_handle'] = TRUE; return TRUE; } // no SMTP configuration return FALSE; }