v1_handler.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. package icq_legacy
  2. import (
  3. "context"
  4. "encoding/binary"
  5. "fmt"
  6. "log/slog"
  7. "net"
  8. )
  9. // V1Handler handles ICQ V1 protocol packets.
  10. // V1 uses the same wire format as V2 for most commands, so this embeds
  11. // V2Handler. The key difference is that V1's CMD_GET_DEPS (0x03F2) uses
  12. // the standard V2 10-byte header (VERSION+CMD+SEQ+UIN), whereas V2 clients
  13. // that send 0x03F2 use a V3-style 12-byte header. V1Handler overrides
  14. // Handle to intercept 0x03F2 and parse it with the correct layout.
  15. type V1Handler struct {
  16. *V2Handler
  17. }
  18. // NewV1Handler creates a new V1 protocol handler.
  19. func NewV1Handler(
  20. sessions *LegacySessionManager,
  21. service LegacyService,
  22. sender PacketSender,
  23. logger *slog.Logger,
  24. ) *V1Handler {
  25. v1pb := NewV1PacketBuilder()
  26. return &V1Handler{
  27. V2Handler: &V2Handler{
  28. BaseHandler: BaseHandler{
  29. sessions: sessions,
  30. service: service,
  31. sender: sender,
  32. logger: logger,
  33. },
  34. packetBuilder: v1pb,
  35. },
  36. }
  37. }
  38. // Handle processes a V1 protocol packet.
  39. // Intercepts CMD_GET_DEPS (0x03F2) to parse with V1's 10-byte header layout,
  40. // then delegates everything else to V2Handler.
  41. func (h *V1Handler) Handle(session *LegacySession, addr *net.UDPAddr, packet []byte) error {
  42. // Peek at the command field (bytes 2-4) to check for 0x03F2
  43. if len(packet) >= 4 {
  44. cmd := binary.LittleEndian.Uint16(packet[2:4])
  45. if cmd == ICQLegacyCmdGetDeps {
  46. return h.handleV1Login(session, addr, packet)
  47. }
  48. }
  49. // Everything else uses the same format as V2
  50. return h.V2Handler.Handle(session, addr, packet)
  51. }
  52. // handleV1Login processes the V1 login packet (0x03F2).
  53. // V1 clients use 0x03F2 as their actual login command (not a pre-auth step).
  54. // The packet uses the standard 10-byte header: VERSION(2) + CMD(2) + SEQ(2) + UIN(4)
  55. // Data payload: PWD_LEN(2) + PASSWORD(variable, null-terminated) + STATUS(4)
  56. //
  57. // Unlike V2 clients which send 0x03E8 (CMD_LOGIN) directly, and V3+ clients
  58. // which use 0x03F2 as a pre-auth step before 0x03E8, V1 clients use 0x03F2
  59. // as their only login command and expect SRV_HELLO (0x005A) in response.
  60. func (h *V1Handler) handleV1Login(session *LegacySession, addr *net.UDPAddr, packet []byte) error {
  61. ctx := context.Background()
  62. // V1 header: VERSION(2) + CMD(2) + SEQ(2) + UIN(4) = 10 bytes
  63. if len(packet) < 12 { // 10-byte header + at least 2 bytes for pwd_len
  64. h.logger.Debug("V1 login packet too short", "len", len(packet))
  65. return nil
  66. }
  67. seqNum := binary.LittleEndian.Uint16(packet[4:6])
  68. uin := binary.LittleEndian.Uint32(packet[6:10])
  69. data := packet[10:]
  70. h.logger.Debug("V1 login packet (0x03F2)",
  71. "addr", addr.String(),
  72. "seq", seqNum,
  73. "uin", uin,
  74. "data_len", len(data),
  75. "data_hex", fmt.Sprintf("%X", data),
  76. )
  77. // Parse password from data: PWD_LEN(2) + PASSWORD
  78. if len(data) < 2 {
  79. return nil
  80. }
  81. pwdLen := binary.LittleEndian.Uint16(data[0:2])
  82. offset := 2
  83. if pwdLen == 0 || pwdLen > 20 || offset+int(pwdLen) > len(data) {
  84. h.logger.Debug("invalid password in V1 login", "pwd_len", pwdLen)
  85. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seqNum, ICQLegacyVersionV1))
  86. }
  87. password := string(data[offset : offset+int(pwdLen)])
  88. if len(password) > 0 && password[len(password)-1] == 0 {
  89. password = password[:len(password)-1]
  90. }
  91. offset += int(pwdLen)
  92. // Parse status (4 bytes) if present
  93. var status uint32
  94. if offset+4 <= len(data) {
  95. status = binary.LittleEndian.Uint32(data[offset : offset+4])
  96. }
  97. h.logger.Info("V1 login attempt",
  98. "uin", uin,
  99. "password_len", len(password),
  100. "status", fmt.Sprintf("0x%08X", status),
  101. )
  102. // Authenticate using service layer
  103. authReq := AuthRequest{
  104. UIN: uin,
  105. Password: password,
  106. Status: status,
  107. Version: ICQLegacyVersionV1,
  108. }
  109. authResult, err := h.service.AuthenticateUser(ctx, authReq)
  110. if err != nil || !authResult.Success {
  111. h.logger.Info("V1 login failed - invalid credentials", "uin", uin)
  112. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seqNum, ICQLegacyVersionV1))
  113. }
  114. // Create session
  115. newSession, err := h.sessions.CreateSession(uin, addr, ICQLegacyVersionV1, authResult.oscarSession)
  116. if err != nil {
  117. h.logger.Error("failed to create V1 session", "err", err, "uin", uin)
  118. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seqNum, ICQLegacyVersionV1))
  119. }
  120. newSession.SetStatus(status)
  121. newSession.Password = password
  122. // Send ACK
  123. ackPkt := h.packetBuilder.BuildAck(seqNum, ICQLegacyVersionV1)
  124. if err := h.sender.SendToSession(newSession, ackPkt); err != nil {
  125. return err
  126. }
  127. // Send SRV_HELLO (0x005A) login reply — same as V2 gets for CMD_LOGIN
  128. loginReplyPkt := h.packetBuilder.BuildLoginReply(newSession, seqNum)
  129. if err := h.sender.SendToSession(newSession, loginReplyPkt); err != nil {
  130. return err
  131. }
  132. h.logger.Info("V1 login successful",
  133. "uin", uin,
  134. "session_id", newSession.SessionID,
  135. )
  136. // Notify contacts that user is online
  137. if err := h.service.NotifyStatusChange(ctx, uin, status); err != nil {
  138. h.logger.Debug("failed to notify status change", "err", err)
  139. }
  140. return nil
  141. }
  142. // SetSender sets the packet sender (for circular dependency resolution).
  143. func (h *V1Handler) SetSender(sender PacketSender) {
  144. h.V2Handler.SetSender(sender)
  145. }
  146. // SetDispatcher sets the message dispatcher for cross-protocol message routing.
  147. func (h *V1Handler) SetDispatcher(dispatcher MessageDispatcher) {
  148. h.V2Handler.SetDispatcher(dispatcher)
  149. }