fscan/plugins/services/pop3/connector.go
ZacharyZcR 4a3f281b6b refactor: 统一Plugins目录大小写为小写
- 将所有Plugins路径重命名为plugins
- 修复Git索引与实际文件系统大小写不一致问题
- 确保跨平台兼容性和路径一致性
2025-08-12 13:08:06 +08:00

269 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package pop3
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"net"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
)
// POP3Connector POP3连接器实现
type POP3Connector struct {
host string
port int
}
// POP3Connection POP3连接结构
type POP3Connection struct {
conn net.Conn
reader *bufio.Reader
isTLS bool
banner string
username string
password string
}
// NewPOP3Connector 创建POP3连接器
func NewPOP3Connector() *POP3Connector {
return &POP3Connector{}
}
// Connect 建立POP3连接
func (c *POP3Connector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
timeout := time.Duration(common.Timeout) * time.Second
address := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 结果通道
type connResult struct {
conn *POP3Connection
isTLS bool
banner string
err error
}
resultChan := make(chan connResult, 1)
// 在协程中尝试连接
go func() {
// 首先尝试普通连接
conn, err := common.WrapperTcpWithTimeout("tcp", address, timeout)
if err == nil {
banner, authErr := c.readBanner(conn, timeout)
if authErr == nil {
pop3Conn := &POP3Connection{
conn: conn,
reader: bufio.NewReader(conn),
isTLS: false,
banner: banner,
}
select {
case <-ctx.Done():
conn.Close()
case resultChan <- connResult{pop3Conn, false, banner, nil}:
}
return
}
conn.Close()
}
// 如果普通连接失败尝试TLS连接(端口995)
if info.Ports == "995" {
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
dialer := &net.Dialer{Timeout: timeout}
tlsConn, tlsErr := tls.DialWithDialer(dialer, "tcp", address, tlsConfig)
if tlsErr == nil {
banner, authErr := c.readBanner(tlsConn, timeout)
if authErr == nil {
pop3Conn := &POP3Connection{
conn: tlsConn,
reader: bufio.NewReader(tlsConn),
isTLS: true,
banner: banner,
}
select {
case <-ctx.Done():
tlsConn.Close()
case resultChan <- connResult{pop3Conn, true, banner, nil}:
}
return
}
tlsConn.Close()
}
}
select {
case <-ctx.Done():
case resultChan <- connResult{nil, false, "", fmt.Errorf(i18n.GetText("pop3_connection_failed"), err)}:
}
}()
// 等待连接结果
select {
case result := <-resultChan:
if result.err != nil {
return nil, result.err
}
return result.conn, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
// Authenticate 进行POP3认证
func (c *POP3Connector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
pop3Conn, ok := conn.(*POP3Connection)
if !ok {
return fmt.Errorf("无效的POP3连接类型")
}
timeout := time.Duration(common.Timeout) * time.Second
// 发送用户名
pop3Conn.conn.SetDeadline(time.Now().Add(timeout))
_, err := pop3Conn.conn.Write([]byte(fmt.Sprintf("USER %s\r\n", cred.Username)))
if err != nil {
return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err)
}
// 读取用户名响应
pop3Conn.conn.SetDeadline(time.Now().Add(timeout))
response, err := pop3Conn.reader.ReadString('\n')
if err != nil {
return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err)
}
if !strings.Contains(response, "+OK") {
return fmt.Errorf("用户名无效: %s", strings.TrimSpace(response))
}
// 短暂延迟
time.Sleep(300 * time.Millisecond)
// 发送密码
pop3Conn.conn.SetDeadline(time.Now().Add(timeout))
_, err = pop3Conn.conn.Write([]byte(fmt.Sprintf("PASS %s\r\n", cred.Password)))
if err != nil {
return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err)
}
// 读取密码响应
pop3Conn.conn.SetDeadline(time.Now().Add(timeout))
response, err = pop3Conn.reader.ReadString('\n')
if err != nil {
return fmt.Errorf(i18n.GetText("pop3_auth_failed"), err)
}
if strings.Contains(response, "+OK") {
// 认证成功,保存凭据信息
pop3Conn.username = cred.Username
pop3Conn.password = cred.Password
return nil
}
return fmt.Errorf("认证失败: %s", strings.TrimSpace(response))
}
// Close 关闭POP3连接
func (c *POP3Connector) Close(conn interface{}) error {
if pop3Conn, ok := conn.(*POP3Connection); ok {
// 尝试优雅退出
if pop3Conn.conn != nil {
pop3Conn.conn.Write([]byte("QUIT\r\n"))
pop3Conn.conn.Close()
}
return nil
}
return fmt.Errorf("无效的POP3连接类型")
}
// readBanner 读取POP3服务器横幅信息
func (c *POP3Connector) readBanner(conn net.Conn, timeout time.Duration) (string, error) {
reader := bufio.NewReader(conn)
conn.SetDeadline(time.Now().Add(timeout))
response, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("读取横幅失败: %v", err)
}
// 检查是否为正常的POP3响应
if strings.Contains(response, "+OK") {
return strings.TrimSpace(response), nil
}
// 检查错误响应
if strings.Contains(strings.ToLower(response), "error") ||
strings.Contains(strings.ToLower(response), "too many") {
return "", fmt.Errorf("服务器拒绝连接: %s", strings.TrimSpace(response))
}
return strings.TrimSpace(response), nil
}
// GetConnectionInfo 获取连接信息
func (conn *POP3Connection) GetConnectionInfo() map[string]interface{} {
info := map[string]interface{}{
"protocol": "POP3",
"tls": conn.isTLS,
"banner": conn.banner,
}
if conn.username != "" {
info["username"] = conn.username
info["authenticated"] = true
}
return info
}
// IsAlive 检查连接是否仍然有效
func (conn *POP3Connection) IsAlive() bool {
if conn.conn == nil {
return false
}
// 尝试发送NOOP命令检查连接
conn.conn.SetDeadline(time.Now().Add(5 * time.Second))
_, err := conn.conn.Write([]byte("NOOP\r\n"))
if err != nil {
return false
}
response, err := conn.reader.ReadString('\n')
if err != nil {
return false
}
return strings.Contains(response, "+OK")
}
// ExecuteCommand 执行POP3命令
func (conn *POP3Connection) ExecuteCommand(command string) (string, error) {
if conn.conn == nil {
return "", fmt.Errorf("连接未建立")
}
timeout := time.Duration(common.Timeout) * time.Second
conn.conn.SetDeadline(time.Now().Add(timeout))
// 发送命令
_, err := conn.conn.Write([]byte(command + "\r\n"))
if err != nil {
return "", fmt.Errorf("发送命令失败: %v", err)
}
// 读取响应
response, err := conn.reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("读取响应失败: %v", err)
}
return strings.TrimSpace(response), nil
}