You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

418 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
/**
* SMTP诊断工具
* 帮助诊断SMTP配置问题
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
/**
* 获取PHP和服务器环境信息
*/
function nenghui_get_smtp_diagnostic_info() {
$info = [];
// PHP版本
$info['php_version'] = PHP_VERSION;
// 检查必要的PHP扩展
$info['extensions'] = [
'openssl' => extension_loaded('openssl'),
'sockets' => extension_loaded('sockets'),
'curl' => extension_loaded('curl'),
'mbstring' => extension_loaded('mbstring')
];
// 检查OpenSSL支持的协议
if (extension_loaded('openssl')) {
$info['openssl_version'] = OPENSSL_VERSION_TEXT;
$info['stream_crypto_methods'] = stream_get_transports();
}
// 检查allow_url_fopen
$info['allow_url_fopen'] = ini_get('allow_url_fopen') ? 'Yes' : 'No';
// 检查用户代理
$info['user_agent'] = ini_get('user_agent');
// 检查默认socket超时
$info['default_socket_timeout'] = ini_get('default_socket_timeout');
return $info;
}
/**
* 测试端口连接
*/
function nenghui_test_port_connection($host, $port, $timeout = 10) {
$result = [
'success' => false,
'message' => '',
'time' => 0,
'methods_tried' => []
];
// 方法1: 使用fsockopen
$start_time = microtime(true);
$socket = @fsockopen($host, $port, $errno, $errstr, $timeout);
$end_time = microtime(true);
$result['time'] = round(($end_time - $start_time) * 1000, 2);
if ($socket) {
$result['success'] = true;
$result['message'] = "✅ 成功连接到 {$host}:{$port} (fsockopen, 耗时: {$result['time']}ms)";
$result['methods_tried'][] = 'fsockopen: 成功';
fclose($socket);
return $result;
} else {
$result['methods_tried'][] = "fsockopen: 失败 - {$errstr} ({$errno})";
}
// 方法2: 使用stream_socket_client
$start_time = microtime(true);
$context = stream_context_create([
'socket' => [
'bindto' => '0:0',
]
]);
$socket = @stream_socket_client(
"tcp://{$host}:{$port}",
$errno2,
$errstr2,
$timeout,
STREAM_CLIENT_CONNECT,
$context
);
$end_time = microtime(true);
$time2 = round(($end_time - $start_time) * 1000, 2);
if ($socket) {
$result['success'] = true;
$result['message'] = "✅ 成功连接到 {$host}:{$port} (stream_socket_client, 耗时: {$time2}ms)";
$result['methods_tried'][] = 'stream_socket_client: 成功';
$result['time'] = $time2;
fclose($socket);
return $result;
} else {
$result['methods_tried'][] = "stream_socket_client: 失败 - {$errstr2} ({$errno2})";
}
// 方法3: 使用curl (如果可用)
if (function_exists('curl_init')) {
$start_time = microtime(true);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://{$host}:{$port}");
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
$response = curl_exec($ch);
$curl_error = curl_error($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$connect_time = curl_getinfo($ch, CURLINFO_CONNECT_TIME) * 1000;
curl_close($ch);
$end_time = microtime(true);
$time3 = round(($end_time - $start_time) * 1000, 2);
if ($response !== false || $http_code > 0) {
$result['success'] = true;
$result['message'] = "✅ 成功连接到 {$host}:{$port} (curl, 耗时: {$time3}ms, HTTP状态: {$http_code})";
$result['methods_tried'][] = 'curl: 成功';
$result['time'] = $time3;
return $result;
} else {
$result['methods_tried'][] = "curl: 失败 - {$curl_error}";
}
}
// 所有方法都失败
$result['message'] = "❌ 无法连接到 {$host}:{$port}\n";
$result['message'] .= "尝试的连接方法:\n";
foreach ($result['methods_tried'] as $method) {
$result['message'] .= "{$method}\n";
}
// 添加诊断建议
$result['message'] .= "\n🔍 可能的原因:\n";
// 针对端口25的特殊提示
if ($port == 25) {
$result['message'] .= "• ⚠️ 端口25被封禁大多数云服务商阿里云、腾讯云等默认封禁端口25\n";
$result['message'] .= "• 🔧 解决方案改用端口587TLS或465SSL\n";
$result['message'] .= "• 📧 建议配置使用STARTTLS加密方式端口587\n";
} else {
$result['message'] .= "• 服务器防火墙阻止了端口 {$port}\n";
}
$result['message'] .= "• SMTP服务未启动或未监听此端口\n";
$result['message'] .= "• 网络连接问题或DNS解析失败\n";
$result['message'] .= "• 服务器IP地址或域名不正确\n";
// 添加云服务商特殊说明
if ($port == 25) {
$result['message'] .= "\n💡 云服务商端口限制说明:\n";
$result['message'] .= "• 阿里云ECS默认封禁25端口无法申请解封\n";
$result['message'] .= "• 腾讯云CVM默认封禁25端口企业用户可申请解封\n";
$result['message'] .= "• 建议使用端口587或465配合TLS/SSL加密\n";
}
return $result;
}
/**
* 测试SSL/TLS连接
*/
function nenghui_test_ssl_connection($host, $port, $crypto_method = 'tls') {
$result = [
'success' => false,
'message' => '',
'certificate_info' => null
];
$context = stream_context_create([
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
]
]);
$protocol = ($crypto_method === 'ssl') ? 'ssl' : 'tls';
$connection_string = "{$protocol}://{$host}:{$port}";
$socket = @stream_socket_client(
$connection_string,
$errno,
$errstr,
10,
STREAM_CLIENT_CONNECT,
$context
);
if ($socket) {
$result['success'] = true;
$result['message'] = "成功建立 {$crypto_method} 连接到 {$host}:{$port}";
// 获取证书信息
$cert = stream_context_get_params($socket);
if (isset($cert['options']['ssl']['peer_certificate'])) {
$cert_info = openssl_x509_parse($cert['options']['ssl']['peer_certificate']);
$result['certificate_info'] = [
'subject' => $cert_info['subject']['CN'] ?? 'Unknown',
'issuer' => $cert_info['issuer']['CN'] ?? 'Unknown',
'valid_from' => date('Y-m-d H:i:s', $cert_info['validFrom_time_t']),
'valid_to' => date('Y-m-d H:i:s', $cert_info['validTo_time_t'])
];
}
fclose($socket);
} else {
$result['message'] = "无法建立 {$crypto_method} 连接到 {$host}:{$port} - {$errstr} ({$errno})";
}
return $result;
}
/**
* DNS解析测试
*/
function nenghui_test_dns_resolution($host) {
$result = [
'success' => false,
'message' => '',
'ip_addresses' => []
];
// 测试DNS解析
$ip_addresses = @gethostbynamel($host);
if ($ip_addresses && !empty($ip_addresses)) {
$result['success'] = true;
$result['ip_addresses'] = $ip_addresses;
$result['message'] = "✅ DNS解析成功: {$host}" . implode(', ', $ip_addresses);
} else {
$result['message'] = "❌ DNS解析失败: 无法解析域名 {$host}";
$result['message'] .= "\n🔍 可能的原因:\n";
$result['message'] .= "• DNS服务器配置问题\n";
$result['message'] .= "• 域名不存在或已过期\n";
$result['message'] .= "• 网络连接问题\n";
}
return $result;
}
/**
* 网络连通性测试
*/
function nenghui_test_network_connectivity() {
$result = [
'success' => false,
'message' => '',
'tests' => []
];
// 测试常见的公共DNS服务器
$test_hosts = [
'8.8.8.8' => 'Google DNS',
'114.114.114.114' => '114 DNS',
'223.5.5.5' => '阿里DNS'
];
$successful_tests = 0;
foreach ($test_hosts as $ip => $name) {
$start_time = microtime(true);
$socket = @fsockopen($ip, 53, $errno, $errstr, 5);
$end_time = microtime(true);
$time = round(($end_time - $start_time) * 1000, 2);
if ($socket) {
$result['tests'][] = "{$name} ({$ip}): 连接成功 ({$time}ms)";
$successful_tests++;
fclose($socket);
} else {
$result['tests'][] = "{$name} ({$ip}): 连接失败 - {$errstr}";
}
}
if ($successful_tests > 0) {
$result['success'] = true;
$result['message'] = "✅ 网络连通性正常 ({$successful_tests}/" . count($test_hosts) . " 测试通过)";
} else {
$result['message'] = "❌ 网络连通性异常 (所有测试都失败)";
$result['message'] .= "\n🔍 可能的原因:\n";
$result['message'] .= "• 服务器网络连接问题\n";
$result['message'] .= "• 防火墙阻止了出站连接\n";
$result['message'] .= "• DNS服务配置问题\n";
}
return $result;
}
/**
* 运行完整的SMTP诊断
*/
function nenghui_run_smtp_diagnostic() {
if (!current_user_can('manage_options')) {
wp_die('权限不足');
}
$smtp_host = get_option('nenghui_smtp_host');
$smtp_port = get_option('nenghui_smtp_port', '587');
$smtp_secure = get_option('nenghui_smtp_secure', 'tls');
$diagnostic_results = [];
// 1. 系统环境检查
$diagnostic_results['system'] = nenghui_get_smtp_diagnostic_info();
// 2. 网络连通性测试
$diagnostic_results['network'] = nenghui_test_network_connectivity();
// 3. DNS解析测试
if (!empty($smtp_host)) {
$diagnostic_results['dns'] = nenghui_test_dns_resolution($smtp_host);
// 4. 端口连接测试
$diagnostic_results['port_test'] = nenghui_test_port_connection($smtp_host, $smtp_port);
// 5. SSL/TLS连接测试
if ($smtp_secure !== 'none') {
$diagnostic_results['ssl_test'] = nenghui_test_ssl_connection($smtp_host, $smtp_port, $smtp_secure);
}
}
// 显示诊断结果
add_action('admin_notices', function() use ($diagnostic_results) {
$output = '<div class="notice notice-info is-dismissible">';
$output .= '<h3>SMTP诊断报告</h3>';
// 系统环境
$output .= '<h4>系统环境</h4>';
$output .= '<ul>';
$output .= '<li>PHP版本: ' . esc_html($diagnostic_results['system']['php_version']) . '</li>';
foreach ($diagnostic_results['system']['extensions'] as $ext => $loaded) {
$status = $loaded ? '✓' : '✗';
$color = $loaded ? 'green' : 'red';
$output .= '<li style="color: ' . $color . ';">扩展 ' . $ext . ': ' . $status . '</li>';
}
if (isset($diagnostic_results['system']['openssl_version'])) {
$output .= '<li>OpenSSL版本: ' . esc_html($diagnostic_results['system']['openssl_version']) . '</li>';
}
$output .= '</ul>';
// 网络连通性测试
if (isset($diagnostic_results['network'])) {
$output .= '<h4>网络连通性测试</h4>';
$color = $diagnostic_results['network']['success'] ? 'green' : 'red';
$output .= '<p style="color: ' . $color . ';">' . nl2br(esc_html($diagnostic_results['network']['message'])) . '</p>';
if (!empty($diagnostic_results['network']['tests'])) {
$output .= '<ul>';
foreach ($diagnostic_results['network']['tests'] as $test) {
$output .= '<li>' . esc_html($test) . '</li>';
}
$output .= '</ul>';
}
}
// DNS解析测试
if (isset($diagnostic_results['dns'])) {
$output .= '<h4>DNS解析测试</h4>';
$color = $diagnostic_results['dns']['success'] ? 'green' : 'red';
$output .= '<p style="color: ' . $color . ';">' . nl2br(esc_html($diagnostic_results['dns']['message'])) . '</p>';
if (!empty($diagnostic_results['dns']['ip_addresses'])) {
$output .= '<p><strong>解析到的IP地址:</strong></p>';
$output .= '<ul>';
foreach ($diagnostic_results['dns']['ip_addresses'] as $ip) {
$output .= '<li>' . esc_html($ip) . '</li>';
}
$output .= '</ul>';
}
}
// 端口连接测试
if (isset($diagnostic_results['port_test'])) {
$output .= '<h4>端口连接测试</h4>';
$color = $diagnostic_results['port_test']['success'] ? 'green' : 'red';
$output .= '<p style="color: ' . $color . ';">' . nl2br(esc_html($diagnostic_results['port_test']['message'])) . '</p>';
}
// SSL/TLS连接测试
if (isset($diagnostic_results['ssl_test'])) {
$output .= '<h4>SSL/TLS连接测试</h4>';
$color = $diagnostic_results['ssl_test']['success'] ? 'green' : 'red';
$output .= '<p style="color: ' . $color . ';">' . esc_html($diagnostic_results['ssl_test']['message']) . '</p>';
if ($diagnostic_results['ssl_test']['certificate_info']) {
$cert = $diagnostic_results['ssl_test']['certificate_info'];
$output .= '<p><strong>证书信息:</strong></p>';
$output .= '<ul>';
$output .= '<li>主题: ' . esc_html($cert['subject']) . '</li>';
$output .= '<li>颁发者: ' . esc_html($cert['issuer']) . '</li>';
$output .= '<li>有效期: ' . esc_html($cert['valid_from']) . ' 至 ' . esc_html($cert['valid_to']) . '</li>';
$output .= '</ul>';
}
}
$output .= '</div>';
echo $output;
});
}
// 处理诊断请求
if (isset($_GET['nenghui_smtp_diagnostic']) && $_GET['nenghui_smtp_diagnostic'] === '1') {
add_action('admin_init', 'nenghui_run_smtp_diagnostic');
}
?>