|
|
<?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'] .= "• 🔧 解决方案:改用端口587(TLS)或465(SSL)\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');
|
|
|
}
|
|
|
|
|
|
?>
|