= 4 && $line[3] === ' ') { break; } } return $data; } /** 发送命令并校验响应码 */ function fb_smtp_cmd($fp, $cmd, $codes, &$err, $hideLog = false) { if ($cmd !== null && $cmd !== '') { fwrite($fp, $cmd . "\r\n"); } $resp = fb_smtp_read($fp); $code = (int)substr(ltrim($resp), 0, 3); $want = is_array($codes) ? $codes : [(int)$codes]; if (!in_array($code, $want, true)) { $err = ($hideLog ? '(命令内容已隐藏) ' : '') . trim($resp); return false; } return true; } /** 邮件头编码(UTF-8 -> =?UTF-8?B?...?=) */ function fb_mail_header($s) { return '=?UTF-8?B?' . base64_encode((string)$s) . '?='; } /** * 发送一封 HTML 邮件 * @return array [bool ok, string err] */ function fb_smtp_send(array $s, $to, $subject, $html) { $err = ''; $host = (string)$s['host']; $port = (int)$s['port']; $secure = strtolower(isset($s['secure']) ? (string)$s['secure'] : ''); $timeout= (int)(isset($s['timeout']) ? $s['timeout'] : 15); $verify = !isset($s['verify_ssl']) || $s['verify_ssl']; if ($host === '' || $port <= 0) { return [false, 'SMTP 参数不完整']; } if (!filter_var($to, FILTER_VALIDATE_EMAIL)) { return [false, '收件地址非法']; } $sslOpt = $verify ? ['verify_peer' => true, 'verify_peer_name' => true] : ['verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true]; $ctx = stream_context_create(['ssl' => $sslOpt]); $remote = (($secure === 'ssl' || $secure === 'smtps') ? 'ssl://' : '') . $host . ':' . $port; $fp = @stream_socket_client($remote, $eno, $estr, $timeout, STREAM_CLIENT_CONNECT, $ctx); if (!$fp) { return [false, '连接失败: ' . $estr . ' (' . $eno . ')']; } stream_set_timeout($fp, $timeout); $hello = isset($_SERVER['HTTP_HOST']) ? (string)$_SERVER['HTTP_HOST'] : 'localhost'; $hello = preg_replace('/[^A-Za-z0-9\.\-]/', '', $hello); if ($hello === '') { $hello = 'localhost'; } if (!fb_smtp_cmd($fp, null, 220, $err)) { fclose($fp); return [false, '服务器问候失败: ' . $err]; } if (!fb_smtp_cmd($fp, 'EHLO ' . $hello, 250, $err)) { fclose($fp); return [false, 'EHLO 失败: ' . $err]; } if ($secure === 'tls' || $secure === 'starttls') { if (!fb_smtp_cmd($fp, 'STARTTLS', 220, $err)) { fclose($fp); return [false, 'STARTTLS 失败: ' . $err]; } if (!@stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { fclose($fp); return [false, 'TLS 握手失败']; } if (!fb_smtp_cmd($fp, 'EHLO ' . $hello, 250, $err)) { fclose($fp); return [false, 'EHLO(TLS) 失败: ' . $err]; } } $user = (string)(isset($s['user']) ? $s['user'] : ''); if ($user !== '') { if (!fb_smtp_cmd($fp, 'AUTH LOGIN', 334, $err)) { fclose($fp); return [false, 'AUTH LOGIN 不受支持: ' . $err]; } if (!fb_smtp_cmd($fp, base64_encode($user), 334, $err, true)) { fclose($fp); return [false, '用户名未被接受: ' . $err]; } if (!fb_smtp_cmd($fp, base64_encode((string)$s['pass']), 235, $err, true)) { fclose($fp); return [false, '认证失败(账号 / 密码 / 授权码): ' . $err]; } } $from = !empty($s['from']) ? (string)$s['from'] : $user; if (!filter_var($from, FILTER_VALIDATE_EMAIL)) { fclose($fp); return [false, '发件地址非法']; } if (!fb_smtp_cmd($fp, 'MAIL FROM:<' . $from . '>', 250, $err)) { fclose($fp); return [false, 'MAIL FROM 失败: ' . $err]; } if (!fb_smtp_cmd($fp, 'RCPT TO:<' . $to . '>', [250, 251], $err)) { fclose($fp); return [false, 'RCPT TO 失败: ' . $err]; } if (!fb_smtp_cmd($fp, 'DATA', 354, $err)) { fclose($fp); return [false, 'DATA 失败: ' . $err]; } $fromName = isset($s['from_name']) ? (string)$s['from_name'] : ''; $headers = []; $headers[] = 'Date: ' . date('r'); $headers[] = 'From: ' . ($fromName !== '' ? fb_mail_header($fromName) . ' ' : '') . '<' . $from . '>'; $headers[] = 'To: <' . $to . '>'; $headers[] = 'Subject: ' . fb_mail_header($subject); $headers[] = 'MIME-Version: 1.0'; $headers[] = 'Content-Type: text/html; charset=UTF-8'; $headers[] = 'Content-Transfer-Encoding: base64'; $headers[] = 'Message-ID: <' . bin2hex(random_bytes(12)) . '@' . $hello . '>'; $headers[] = 'X-Mailer: FeedLite'; // 正文用 base64,天然不含以 "." 开头的行,因此无需 dot-stuffing $body = chunk_split(base64_encode($html), 76, "\r\n"); $data = rtrim(implode("\r\n", $headers) . "\r\n\r\n" . $body, "\r\n"); if (fwrite($fp, $data . "\r\n.\r\n") === false) { fclose($fp); return [false, '写入邮件正文失败']; } if (!fb_smtp_cmd($fp, null, 250, $err)) { fclose($fp); return [false, '投递失败: ' . $err]; } @fb_smtp_cmd($fp, 'QUIT', 221, $e2); fclose($fp); return [true, '']; } /** 渲染通知邮件正文 */ function fb_notify_html($thread, $reply, $isAdmin) { $base = fb_base_url(); $url = $base . '/thread.php?id=' . (int)$thread['id']; $site = (string)fb_cfg('site_name'); $name = (string)$thread['author_name']; $title = (string)$thread['title']; $rname = $isAdmin ? '官方' : (string)$reply['author_name']; $rbody = nl2br(htmlspecialchars((string)$reply['body'], ENT_QUOTES, 'UTF-8')); $safeName = htmlspecialchars($name, ENT_QUOTES, 'UTF-8'); $safeTitle = htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); $safeRName = htmlspecialchars($rname, ENT_QUOTES, 'UTF-8'); return '
' . $safeName . ',你好:
' . '你在「' . htmlspecialchars($site, ENT_QUOTES, 'UTF-8') . '」提交的问题 《' . $safeTitle . '》 有了新的回复:
' . '' . '' . '' . '' . $safeRName . ' 回复:' . $rbody . '
若按钮无法点击,请复制链接到浏览器打开:
' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '
此邮件由系统自动发送,请勿直接回复。
' . '' . $name . ',你好:
' . '感谢你反馈 《' . $title . '》。我们已经核实过,' . '该情况并不是应用的问题,因此这条反馈已被关闭(不会公开显示)。
' . '你当时反馈的内容:
' . '如果上面的说明没能解决你的疑问,可以直接回复本邮件,我们会继续跟进;' . '也可以发邮件到 ' . $mail . ' 补充说明。
' : '如果上面的说明没能解决你的疑问,可以直接回复本邮件,我们会继续跟进。
') . '此邮件由「' . htmlspecialchars($site, ENT_QUOTES, 'UTF-8') . '」自动发送。
' . '