internal/honeypot/sip/sip.go

package sip

import (
	"bufio"
	"context"
	"fmt"
	"log/slog"
	"net"
	"strings"
	"sync"
	"time"

	"honeypot/internal/database"
	"honeypot/internal/honeypot"
	"honeypot/internal/logger"
	"honeypot/internal/types"
	"honeypot/internal/utils"
)

const (
	HoneypotType  = types.HoneypotTypeSIP
	HoneypotLabel = "SIP"
)

// Config holds the configuration for the SIP honeypot.
type Config struct {
	ListenAddr string
	Ports      []uint16
}

// sipHoneypot implements the honeypot.Honeypot interface.
type sipHoneypot struct {
	config Config
	logger *slog.Logger
}

// New creates a new SIP honeypot instance.
func New(cfg Config) honeypot.Honeypot {
	// Register fields for top-N tracking
	logger.RegisterTopNField("sip", "method")
	logger.RegisterTopNField("sip", "user_agent")
	logger.RegisterTopNField("sip", "from")
	logger.RegisterTopNField("sip", "to")
	logger.RegisterTopNField("sip", "auth_username")

	return &sipHoneypot{
		config: cfg,
	}
}

// Name returns the name of this honeypot.
func (h *sipHoneypot) Name() types.HoneypotType {
	return HoneypotType
}

// Label returns the label of this honeypot.
func (h *sipHoneypot) Label() string {
	return HoneypotLabel
}

// Start starts the SIP honeypot server.
func (h *sipHoneypot) Start(ctx context.Context, l *slog.Logger) error {
	h.logger = l
	var wg sync.WaitGroup

	for _, port := range h.config.Ports {
		if port == 0 {
			continue
		}
		// Start both TCP and UDP listeners for SIP
		wg.Add(2)
		go func(p uint16) {
			defer wg.Done()
			h.listenTCP(ctx, p)
		}(port)
		go func(p uint16) {
			defer wg.Done()
			h.listenUDP(ctx, p)
		}(port)
	}

	wg.Wait()
	logger.LogInfo(h.logger, HoneypotType, "honeypot shutdown complete", nil)
	return nil
}

func (h *sipHoneypot) listenTCP(ctx context.Context, port uint16) {
	addr := utils.BuildAddress(h.config.ListenAddr, port)
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		logger.LogError(h.logger, HoneypotType, "listen_tcp_failed", err, []any{"addr", addr})
		return
	}
	defer ln.Close()

	go func() {
		<-ctx.Done()
		ln.Close()
	}()

	logger.LogInfo(h.logger, HoneypotType, "honeypot tcp listening", []any{"port", port})

	for {
		conn, err := ln.Accept()
		if err != nil {
			if ctx.Err() != nil {
				return
			}
			continue
		}
		go h.handleTCPConn(conn)
	}
}

func (h *sipHoneypot) handleTCPConn(conn net.Conn) {
	defer conn.Close()
	conn.SetDeadline(time.Now().Add(10 * time.Second))

	reader := bufio.NewReader(conn)
	// Detect TLS handshake on non-TLS port
	peek, _ := reader.Peek(1)
	if len(peek) > 0 && peek[0] == 0x16 {
		h.logTLSHandshake(conn.RemoteAddr(), conn.LocalAddr())
		return
	}
	for {
		msg, err := h.readSIPMessage(reader)
		if err != nil {
			break
		}
		h.processMessage(msg, conn.RemoteAddr(), conn.LocalAddr())
		// In SIP over TCP, multiple requests can be sent over one connection
	}
}

func (h *sipHoneypot) listenUDP(ctx context.Context, port uint16) {
	addr := utils.BuildAddress(h.config.ListenAddr, port)
	pc, err := net.ListenPacket("udp", addr)
	if err != nil {
		logger.LogError(h.logger, HoneypotType, "listen_udp_failed", err, []any{"addr", addr})
		return
	}
	defer pc.Close()

	go func() {
		<-ctx.Done()
		pc.Close()
	}()

	logger.LogInfo(h.logger, HoneypotType, "honeypot udp listening", []any{"port", port})

	buf := make([]byte, 4096)
	for {
		n, remoteAddr, err := pc.ReadFrom(buf)
		if err != nil {
			if ctx.Err() != nil {
				return
			}
			continue
		}

		msg := h.parseSIPMessage(string(buf[:n]))
		if msg.Method == "" {
			continue
		}

		h.processMessage(msg, remoteAddr, pc.LocalAddr())
	}
}

type sipMessage struct {
	Method  string
	URI     string
	Headers map[string]string
	Raw     string
}

func (h *sipHoneypot) readSIPMessage(r *bufio.Reader) (sipMessage, error) {
	line, err := r.ReadString('\n')
	if err != nil {
		return sipMessage{}, err
	}

	parts := strings.Fields(line)
	if len(parts) < 3 {
		return sipMessage{}, fmt.Errorf("invalid SIP request line")
	}

	msg := sipMessage{
		Method:  parts[0],
		URI:     parts[1],
		Headers: make(map[string]string),
		Raw:     line,
	}

	for {
		line, err = r.ReadString('\n')
		if err != nil {
			break
		}
		msg.Raw += line
		trimmed := strings.TrimSpace(line)
		if trimmed == "" {
			break
		}
		if idx := strings.Index(line, ":"); idx != -1 {
			key := strings.TrimSpace(line[:idx])
			val := strings.TrimSpace(line[idx+1:])
			msg.Headers[key] = val
		}
	}
	return msg, nil
}

func (h *sipHoneypot) parseSIPMessage(raw string) sipMessage {
	lines := strings.Split(raw, "\n")
	if len(lines) == 0 {
		return sipMessage{}
	}

	parts := strings.Fields(lines[0])
	if len(parts) < 3 {
		return sipMessage{}
	}

	msg := sipMessage{
		Method:  parts[0],
		URI:     parts[1],
		Headers: make(map[string]string),
		Raw:     raw,
	}

	for i := 1; i < len(lines); i++ {
		line := strings.TrimSpace(lines[i])
		if line == "" {
			break
		}
		if idx := strings.Index(line, ":"); idx != -1 {
			key := strings.TrimSpace(line[:idx])
			val := strings.TrimSpace(line[idx+1:])
			msg.Headers[key] = val
		}
	}
	return msg
}

func (h *sipHoneypot) processMessage(msg sipMessage, remoteAddr, localAddr net.Addr) {
	remoteHost, remotePort := utils.SplitAddr(remoteAddr.String(), h.logger)
	_, dstPort := utils.SplitAddr(localAddr.String(), h.logger)

	fields := map[string]any{
		"method":     msg.Method,
		"uri":        msg.URI,
		"user_agent": msg.Headers["User-Agent"],
		"from":       msg.Headers["From"],
		"to":         msg.Headers["To"],
		"call_id":    msg.Headers["Call-ID"],
	}

	event := types.EventRequest
	if msg.Method == "REGISTER" || msg.Method == "INVITE" {
		if auth, ok := msg.Headers["Authorization"]; ok {
			event = types.EventAuthAttempt
			fields["authorization"] = auth
			// Extract username from auth header
			if idx := strings.Index(auth, "username=\""); idx != -1 {
				username := auth[idx+10:]
				if endIdx := strings.Index(username, "\""); endIdx != -1 {
					fields["auth_username"] = username[:endIdx]
				}
			}
		}
	}

	logger.LogEvent(h.logger, types.LogEvent{
		Type:       HoneypotType,
		Event:      event,
		RemoteAddr: remoteHost,
		RemotePort: remotePort,
		DstPort:    dstPort,
		Fields:     fields,
	})
}

func (h *sipHoneypot) logTLSHandshake(remoteAddr, localAddr net.Addr) {
	remoteHost, remotePort := utils.SplitAddr(remoteAddr.String(), h.logger)
	_, dstPort := utils.SplitAddr(localAddr.String(), h.logger)

	logger.LogEvent(h.logger, types.LogEvent{
		Type:       HoneypotType,
		Event:      types.EventTLSHandshake,
		RemoteAddr: remoteHost,
		RemotePort: remotePort,
		DstPort:    dstPort,
		Fields:     map[string]interface{}{"message": "TLS handshake attempt on non-TLS port"},
	})
}

// GetScores returns the scored ip addresses for the honeypot.
func (h *sipHoneypot) GetScores(db *database.Database, interval string) honeypot.ScoreMap {
	// get SIP scores
	query := fmt.Sprintf(`
	SELECT remote_addr, COUNT(*) as count
	FROM honeypot_events
	WHERE type = 'sip'
	AND time >= NOW() - INTERVAL %s
	GROUP BY remote_addr
	`, interval)

	rows, err := db.DB.Query(query)
	if err != nil {
		return honeypot.ScoreMap{}
	}
	defer rows.Close()

	scores := make(honeypot.ScoreMap)
	for rows.Next() {
		var remoteAddr string
		var count int
		if err := rows.Scan(&remoteAddr, &count); err != nil {
			return honeypot.ScoreMap{}
		}
		scores[remoteAddr] = honeypot.Score{
			Score: uint(count * 100),
			Tags:  []types.Tag{types.TagAuthAttempt},
		}
	}
	return scores
}

func (h *sipHoneypot) Ports() []uint16 {
	return h.config.Ports
}