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 }