v4_handler.go 76 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372
  1. package icq_legacy
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/binary"
  6. "fmt"
  7. "log/slog"
  8. "net"
  9. "strconv"
  10. "github.com/mk6i/open-oscar-server/state"
  11. )
  12. // V4Handler handles ICQ V4 protocol packets
  13. // V4 packet format (from wumpus documentation):
  14. // VERSION(2) + RANDOM(2) + ZEROS(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4) + DATA...
  15. // Total header: 20 bytes
  16. // V4 uses XOR encryption similar to V3 but with different positions
  17. type V4Handler struct {
  18. sessions *LegacySessionManager
  19. service LegacyService
  20. sender PacketSender
  21. packetBuilder V4PacketBuilder // Packet builder for constructing V4 protocol packets
  22. dispatcher MessageDispatcher // Central dispatcher for cross-protocol messaging
  23. logger *slog.Logger
  24. }
  25. // NewV4Handler creates a new V4 protocol handler
  26. func NewV4Handler(
  27. sessions *LegacySessionManager,
  28. service LegacyService,
  29. sender PacketSender,
  30. packetBuilder V4PacketBuilder,
  31. logger *slog.Logger,
  32. ) *V4Handler {
  33. return &V4Handler{
  34. sessions: sessions,
  35. service: service,
  36. sender: sender,
  37. packetBuilder: packetBuilder,
  38. dispatcher: nil, // Set later via SetDispatcher to avoid circular dependency
  39. logger: logger,
  40. }
  41. }
  42. // SetSender sets the packet sender (for circular dependency resolution)
  43. func (h *V4Handler) SetSender(sender PacketSender) {
  44. h.sender = sender
  45. }
  46. // SetDispatcher sets the message dispatcher for cross-protocol messaging
  47. func (h *V4Handler) SetDispatcher(dispatcher MessageDispatcher) {
  48. h.dispatcher = dispatcher
  49. }
  50. // V4 packet offsets (from wumpus documentation)
  51. const (
  52. v4OffsetVersion = 0
  53. v4OffsetRandom = 2
  54. v4OffsetZero = 4
  55. v4OffsetCommand = 6
  56. v4OffsetSeq1 = 8
  57. v4OffsetSeq2 = 10
  58. v4OffsetUIN = 12
  59. v4OffsetCheckcode = 16
  60. v4HeaderSize = 20
  61. )
  62. // Handle processes a V4 protocol packet
  63. func (h *V4Handler) Handle(session *LegacySession, addr *net.UDPAddr, packet []byte) error {
  64. if len(packet) < v4HeaderSize {
  65. return fmt.Errorf("V4 packet too short: %d bytes", len(packet))
  66. }
  67. h.logger.Debug("raw V4 packet before decryption",
  68. "hex", fmt.Sprintf("%X", packet),
  69. "len", len(packet),
  70. )
  71. // Make a copy for decryption
  72. decrypted := make([]byte, len(packet))
  73. copy(decrypted, packet)
  74. // Decrypt the V4 packet
  75. if err := h.decryptV4Packet(decrypted); err != nil {
  76. h.logger.Debug("V4 decryption failed", "err", err)
  77. return err
  78. }
  79. h.logger.Debug("V4 packet after decryption",
  80. "hex", fmt.Sprintf("%X", decrypted),
  81. )
  82. // Parse V4 header
  83. command := binary.LittleEndian.Uint16(decrypted[v4OffsetCommand:])
  84. seq1 := binary.LittleEndian.Uint16(decrypted[v4OffsetSeq1:])
  85. seq2 := binary.LittleEndian.Uint16(decrypted[v4OffsetSeq2:])
  86. uin := binary.LittleEndian.Uint32(decrypted[v4OffsetUIN:])
  87. // Extract data after header
  88. var data []byte
  89. if len(decrypted) > v4HeaderSize {
  90. data = decrypted[v4HeaderSize:]
  91. }
  92. h.logger.Info("V4 packet received",
  93. "command", fmt.Sprintf("0x%04X", command),
  94. "uin", uin,
  95. "seq1", seq1,
  96. "seq2", seq2,
  97. "data_len", len(data),
  98. "addr", addr.String(),
  99. "session_found", session != nil,
  100. )
  101. // Update session activity if we have one
  102. if session != nil {
  103. session.UpdateActivity()
  104. session.SeqNumClient = seq1
  105. }
  106. // If no session exists and this is not a login/registration/ack command,
  107. // send NOT_CONNECTED to force the client to reconnect.
  108. // V4 keep-alive already does a UIN-based session lookup fallback,
  109. // but all other commands silently return nil. This catches them.
  110. if session == nil {
  111. switch command {
  112. case ICQLegacyCmdAck, ICQLegacyCmdFirstLogin,
  113. ICQLegacyCmdGetDeps, ICQLegacyCmdLogin,
  114. ICQLegacyCmdRegRequestInfo, ICQLegacyCmdRegNewUserInfo:
  115. // Allow these through - they don't require a session
  116. default:
  117. h.logger.Info("V4 packet from unknown session, sending NOT_CONNECTED",
  118. "command", fmt.Sprintf("0x%04X", command),
  119. "uin", uin,
  120. "addr", addr.String(),
  121. )
  122. return h.sendNotConnected(addr, seq2, uin)
  123. }
  124. }
  125. // Handle V4 commands (same commands as V3, just different packet format)
  126. switch command {
  127. case ICQLegacyCmdFirstLogin:
  128. return h.handleFirstLogin(addr, seq1, seq2, uin)
  129. case ICQLegacyCmdRegRequestInfo:
  130. return h.handleRegRequestInfo(addr, seq1, seq2, uin)
  131. case ICQLegacyCmdRegNewUserInfo:
  132. return h.handleRegNewUserInfo(addr, seq1, seq2, uin, data)
  133. case ICQLegacyCmdLogin:
  134. return h.handleLogin(session, addr, seq1, seq2, uin, data)
  135. case ICQLegacyCmdGetDeps:
  136. return h.handleGetDeps(addr, seq1, seq2, uin, data)
  137. case ICQLegacyCmdAck:
  138. h.logger.Debug("received V4 ACK", "seq1", seq1, "seq2", seq2)
  139. return nil
  140. case ICQLegacyCmdKeepAlive, ICQLegacyCmdKeepAlive2:
  141. return h.handlePing(session, addr, seq1, seq2, uin)
  142. case ICQLegacyCmdLogoff:
  143. return h.handleLogoff(session, seq1, seq2, uin)
  144. case ICQLegacyCmdContactList:
  145. return h.handleContactList(session, seq1, seq2, uin, data)
  146. case ICQLegacyCmdSetStatus:
  147. return h.handleSetStatus(session, seq1, seq2, uin, data)
  148. case ICQLegacyCmdThruServer, ICQLegacyCmdAuthorize: // 0x010E and 0x0456 (authorize)
  149. return h.handleMessage(session, seq1, seq2, uin, data)
  150. case ICQLegacyCmdUserAdd:
  151. return h.handleUserAdd(session, seq1, seq2, uin, data)
  152. case ICQLegacyCmdSysMsgReq:
  153. return h.handleOfflineMsgReq(session, seq1, seq2, uin)
  154. case ICQLegacyCmdUserGetInfo:
  155. return h.handleGetInfo(session, seq1, seq2, uin, data)
  156. case ICQLegacyCmdInfoReq:
  157. return h.handleInfoReq(session, seq1, seq2, uin, data)
  158. case ICQLegacyCmdExtInfoReq:
  159. return h.handleExtInfoReq(session, seq1, seq2, uin, data)
  160. case ICQLegacyCmdSetBasicInfo: // 0x050A - V4-specific update basic info
  161. return h.handleUpdateBasic(session, seq1, seq2, uin, data)
  162. case ICQLegacyCmdUpdateDetail: // 0x04B0 - update extended info
  163. return h.handleUpdateDetail(session, seq1, seq2, uin, data)
  164. case ICQLegacyCmdSearchStart: // 0x05C8 - not yet implemented
  165. h.logger.Debug("V4 search not yet implemented", "uin", uin)
  166. return h.sendAck(addr, seq1, seq2, uin)
  167. case ICQLegacyCmdSearchUIN: // 0x041A - search by UIN (old)
  168. return h.handleSearchByUIN(session, seq1, seq2, uin, data)
  169. case ICQLegacyCmdSearchUser: // 0x0424 - search by name/email (old)
  170. return h.handleSearchByName(session, seq1, seq2, uin, data)
  171. case ICQLegacyCmdSysMsgDoneAck: // 0x0442 - offline messages acknowledged
  172. h.logger.Debug("V4 offline msg done ack", "uin", uin)
  173. return nil
  174. case ICQLegacyCmdVisibleList: // 0x06AE - visible list
  175. h.logger.Debug("V4 visible list received (stub)", "uin", uin)
  176. return h.sendAck(addr, seq1, seq2, uin)
  177. case ICQLegacyCmdInvisibleList: // 0x06A4 - invisible list
  178. h.logger.Debug("V4 invisible list received (stub)", "uin", uin)
  179. return h.sendAck(addr, seq1, seq2, uin)
  180. case ICQLegacyCmdMetaUser: // 0x064A - META commands (V4+)
  181. h.logger.Debug("V4 META command not yet implemented", "uin", uin)
  182. return h.sendAck(addr, seq1, seq2, uin)
  183. default:
  184. h.logger.Debug("unhandled V4 command",
  185. "command", fmt.Sprintf("0x%04X", command),
  186. "uin", uin,
  187. )
  188. // Send ACK for unknown commands
  189. return h.sendAck(addr, seq1, seq2, uin)
  190. }
  191. }
  192. // decryptV4Packet decrypts a V4 packet in place
  193. // From wumpus code in v4-notes.txt:
  194. // count = ((length + 3) / 4 + 3) / 4
  195. // This gives the number of DWORDs to encrypt
  196. // Skip DWORD 4 (checkcode at offset 16-19)
  197. func (h *V4Handler) decryptV4Packet(packet []byte) error {
  198. if len(packet) < v4HeaderSize {
  199. return fmt.Errorf("packet too short for V4 decryption")
  200. }
  201. // Get checkcode from position 16-19 (little-endian)
  202. checkcode := binary.LittleEndian.Uint32(packet[v4OffsetCheckcode:])
  203. // Calculate encryption key
  204. // key = packetLen * 0x66756B65 + checkcode
  205. packetLen := len(packet)
  206. key := uint32(packetLen)*0x66756B65 + checkcode
  207. // Calculate number of DWORDs to decrypt using wumpus formula:
  208. // count = ((length + 3) / 4 + 3) / 4
  209. count := ((packetLen+3)/4 + 3) / 4
  210. // Decrypt each DWORD, but skip DWORD 4 (checkcode at offset 16)
  211. for i := 0; i < count; i++ {
  212. pos := i * 4
  213. // Skip checkcode DWORD (position 16-19, i.e., DWORD 4)
  214. if i == 4 {
  215. continue
  216. }
  217. // Stop if we've gone past the packet
  218. if pos >= packetLen {
  219. break
  220. }
  221. tableIdx := pos & 0xFF
  222. xorVal := key + uint32(V4Table[tableIdx])
  223. // XOR up to 4 bytes
  224. for j := 0; j < 4 && pos+j < packetLen; j++ {
  225. packet[pos+j] ^= byte(xorVal >> (j * 8))
  226. }
  227. }
  228. // Restore version bytes (they should be 04 00)
  229. packet[0] = 0x04
  230. packet[1] = 0x00
  231. return nil
  232. }
  233. // handleFirstLogin processes the first login packet (0x04EC)
  234. // This is sent by clients starting the registration wizard
  235. // Server should respond with registration info (admin notes)
  236. func (h *V4Handler) handleFirstLogin(addr *net.UDPAddr, seq1, seq2 uint16, uin uint32) error {
  237. h.logger.Info("V4 first login packet - registration request",
  238. "uin", uin,
  239. "seq1", seq1,
  240. "seq2", seq2,
  241. "addr", addr.String(),
  242. )
  243. // Send ACK first
  244. _ = h.sendAck(addr, seq1, seq2, uin)
  245. // Send registration info with admin notes
  246. // This tells the client that registration is enabled and provides any admin notes
  247. return h.sendRegisterInfo(addr, seq2, uin)
  248. }
  249. // handleRegRequestInfo processes the registration info request (0x05DC)
  250. // Client is asking for admin notes and registration status
  251. func (h *V4Handler) handleRegRequestInfo(addr *net.UDPAddr, seq1, seq2 uint16, uin uint32) error {
  252. h.logger.Info("V4 registration info request",
  253. "uin", uin,
  254. "seq1", seq1,
  255. "seq2", seq2,
  256. "addr", addr.String(),
  257. )
  258. // Send ACK first
  259. _ = h.sendAck(addr, seq1, seq2, uin)
  260. // Send registration info
  261. return h.sendRegisterInfo(addr, seq2, uin)
  262. }
  263. // handleRegNewUserInfo processes the registration form submission (0x05E6)
  264. // Client is submitting their registration data
  265. func (h *V4Handler) handleRegNewUserInfo(addr *net.UDPAddr, seq1, seq2 uint16, uin uint32, data []byte) error {
  266. ctx := context.Background()
  267. h.logger.Info("V4 registration form received",
  268. "uin", uin,
  269. "seq1", seq1,
  270. "seq2", seq2,
  271. "data_len", len(data),
  272. "addr", addr.String(),
  273. )
  274. // Send ACK first
  275. _ = h.sendAck(addr, seq1, seq2, uin)
  276. // Parse registration data
  277. // Format from client.html (0x05e6):
  278. // CLIENT_VERSION(2) + NICK_LEN(2) + NICK + FNAME_LEN(2) + FNAME + LNAME_LEN(2) + LNAME + EMAIL_LEN(2) + EMAIL + ...
  279. if len(data) < 10 {
  280. h.logger.Debug("V4 registration data too short", "len", len(data))
  281. return h.sendNotConnected(addr, seq2, uin)
  282. }
  283. offset := 0
  284. // Read client version (2 bytes)
  285. // clientVersion := binary.LittleEndian.Uint16(data[offset : offset+2])
  286. offset += 2
  287. // Read nickname
  288. if offset+2 > len(data) {
  289. return h.sendNotConnected(addr, seq2, uin)
  290. }
  291. nickLen := binary.LittleEndian.Uint16(data[offset : offset+2])
  292. offset += 2
  293. if nickLen > 32 || offset+int(nickLen) > len(data) {
  294. return h.sendNotConnected(addr, seq2, uin)
  295. }
  296. nickname := string(data[offset : offset+int(nickLen)])
  297. if len(nickname) > 0 && nickname[len(nickname)-1] == 0 {
  298. nickname = nickname[:len(nickname)-1]
  299. }
  300. offset += int(nickLen)
  301. // Read first name
  302. if offset+2 > len(data) {
  303. return h.sendNotConnected(addr, seq2, uin)
  304. }
  305. fnameLen := binary.LittleEndian.Uint16(data[offset : offset+2])
  306. offset += 2
  307. if fnameLen > 32 || offset+int(fnameLen) > len(data) {
  308. return h.sendNotConnected(addr, seq2, uin)
  309. }
  310. firstName := string(data[offset : offset+int(fnameLen)])
  311. if len(firstName) > 0 && firstName[len(firstName)-1] == 0 {
  312. firstName = firstName[:len(firstName)-1]
  313. }
  314. offset += int(fnameLen)
  315. // Read last name
  316. if offset+2 > len(data) {
  317. return h.sendNotConnected(addr, seq2, uin)
  318. }
  319. lnameLen := binary.LittleEndian.Uint16(data[offset : offset+2])
  320. offset += 2
  321. if lnameLen > 32 || offset+int(lnameLen) > len(data) {
  322. return h.sendNotConnected(addr, seq2, uin)
  323. }
  324. lastName := string(data[offset : offset+int(lnameLen)])
  325. if len(lastName) > 0 && lastName[len(lastName)-1] == 0 {
  326. lastName = lastName[:len(lastName)-1]
  327. }
  328. offset += int(lnameLen)
  329. // Read email
  330. if offset+2 > len(data) {
  331. return h.sendNotConnected(addr, seq2, uin)
  332. }
  333. emailLen := binary.LittleEndian.Uint16(data[offset : offset+2])
  334. offset += 2
  335. if emailLen > 64 || offset+int(emailLen) > len(data) {
  336. return h.sendNotConnected(addr, seq2, uin)
  337. }
  338. email := string(data[offset : offset+int(emailLen)])
  339. if len(email) > 0 && email[len(email)-1] == 0 {
  340. email = email[:len(email)-1]
  341. }
  342. h.logger.Info("V4 registration data parsed",
  343. "nickname", nickname,
  344. "firstName", firstName,
  345. "lastName", lastName,
  346. "email", email,
  347. )
  348. // Generate a random password for the new user
  349. password := h.generatePassword(6)
  350. // Create the new user account
  351. newUIN, err := h.service.RegisterNewUser(ctx, nickname, firstName, lastName, email, password)
  352. if err != nil {
  353. h.logger.Error("V4 registration failed", "err", err)
  354. return h.sendNotConnected(addr, seq2, uin)
  355. }
  356. h.logger.Info("V4 registration successful",
  357. "newUIN", newUIN,
  358. "nickname", nickname,
  359. )
  360. // Send registration OK with the new UIN and password
  361. return h.sendRegistrationOK(addr, seq2, newUIN, password)
  362. }
  363. // generatePassword generates a random alphanumeric password
  364. func (h *V4Handler) generatePassword(length int) string {
  365. const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  366. password := make([]byte, length)
  367. for i := range password {
  368. password[i] = charset[i%len(charset)] // Simple deterministic for now
  369. }
  370. return string(password)
  371. }
  372. // handleGetDeps processes the pre-auth pseudo-login packet (0x03F2).
  373. // Historically called "get departments list" in iserverd (from its Users_Deps database table).
  374. // This is actually the main login packet in V4 - it contains the credentials
  375. //
  376. // Refactored to use service layer (AuthenticateUser) and packet builder.
  377. // Following the OSCAR pattern: unmarshal -> call service -> build response
  378. func (h *V4Handler) handleGetDeps(addr *net.UDPAddr, seq1, seq2 uint16, uin uint32, data []byte) error {
  379. ctx := context.Background()
  380. // 1. Unmarshal packet to typed struct
  381. // V4 getdeps data format: UIN(4) + PWD_LEN(2) + PASSWORD
  382. if len(data) < 6 {
  383. h.logger.Debug("V4 getdeps packet too short", "len", len(data))
  384. return nil
  385. }
  386. offset := 0
  387. // Read UIN (4 bytes)
  388. dataUIN := binary.LittleEndian.Uint32(data[offset : offset+4])
  389. offset += 4
  390. // Read password length (2 bytes)
  391. pwdLen := binary.LittleEndian.Uint16(data[offset : offset+2])
  392. offset += 2
  393. if pwdLen > 20 {
  394. pwdLen = 20
  395. }
  396. // Read password
  397. if offset+int(pwdLen) > len(data) {
  398. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seq1, seq2, uin))
  399. }
  400. password := string(data[offset : offset+int(pwdLen)])
  401. if len(password) > 0 && password[len(password)-1] == 0 {
  402. password = password[:len(password)-1]
  403. }
  404. h.logger.Info("V4 getdeps (login with credentials)",
  405. "uin", dataUIN,
  406. "password_len", len(password),
  407. )
  408. // 2. Call service layer with typed request
  409. authReq := AuthRequest{
  410. UIN: dataUIN,
  411. Password: password,
  412. Version: ICQLegacyVersionV4,
  413. }
  414. loginOK, err := h.service.ValidateCredentials(ctx, authReq.UIN, authReq.Password)
  415. if err != nil {
  416. h.logger.Error("V4 getdeps authentication error", "err", err, "uin", dataUIN)
  417. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seq1, seq2, uin))
  418. }
  419. if !loginOK {
  420. h.logger.Info("V4 login failed - invalid credentials", "uin", dataUIN)
  421. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seq1, seq2, uin))
  422. }
  423. // 3. Send ACK using packet builder
  424. _ = h.sender.SendPacket(addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
  425. // 4. Create session using dataUIN (the actual login UIN from the packet data),
  426. // not the header UIN which may be 0 or stale from a previous session.
  427. // TODO: parse and store direct connection info (IP, port, DC version, DC type)
  428. // from the V4 login packet so that presence notifications can include real
  429. // connection details when ICQ_LEGACY_DIRECT_CONNECTIONS includes V4.
  430. newSession, err := h.sessions.CreatePendingSession(dataUIN, addr, ICQLegacyVersionV4)
  431. if err != nil {
  432. h.logger.Error("failed to create V4 session", "err", err, "uin", dataUIN)
  433. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seq1, seq2, uin))
  434. }
  435. newSession.Password = password
  436. // 5. Send pre-auth response (historically "departments list")
  437. // GETDEPS (0x03F2) is a "pseudo-login" - it validates credentials but only returns
  438. // the pre-auth response, NOT a full login reply.
  439. if err := h.sendDeptsListWithCheckcode(newSession, seq2); err != nil {
  440. h.logger.Error("failed to send depts list", "err", err, "uin", uin)
  441. return err
  442. }
  443. h.logger.Info("V4 login successful",
  444. "uin", uin,
  445. "session_id", newSession.SessionID,
  446. )
  447. return nil
  448. }
  449. // handleLogin processes login packet (0x03E8)
  450. //
  451. // In the V4 3-phase flow (FirstLogin -> GetDeps -> Login), the session was
  452. // already created during handleGetDeps. We reuse that session here so the
  453. // server seq counter is continuous (depts list got seq=0, login reply gets
  454. // seq=1). Creating a fresh session would reset seq to 0, and the client
  455. // ignores login replies with seq1=0 (same as ACK seq).
  456. //
  457. // Refactored to use service layer (AuthenticateUser) and packet builder.
  458. func (h *V4Handler) handleLogin(session *LegacySession, addr *net.UDPAddr, seq1, seq2 uint16, uin uint32, data []byte) error {
  459. ctx := context.Background()
  460. // 1. Send ACK using packet builder
  461. _ = h.sender.SendPacket(addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
  462. // 2. Unmarshal packet to typed struct
  463. // V4 login data format (from matt-v4.txt):
  464. // TIMESTAMP(4) + TCP_PORT(4) + PWD_LEN(2) + PASSWORD + VERSION(4) + IP(4) + FLAG(1) + STATUS(4) + ...
  465. if len(data) < 10 {
  466. h.logger.Debug("V4 login packet too short", "len", len(data))
  467. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seq1, seq2, uin))
  468. }
  469. offset := 0
  470. // Skip timestamp (4 bytes)
  471. offset += 4
  472. // Read TCP port (4 bytes)
  473. port := binary.LittleEndian.Uint32(data[offset : offset+4])
  474. offset += 4
  475. // Read password length (2 bytes)
  476. if offset+2 > len(data) {
  477. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seq1, seq2, uin))
  478. }
  479. pwdLen := binary.LittleEndian.Uint16(data[offset : offset+2])
  480. offset += 2
  481. if pwdLen > 20 {
  482. pwdLen = 20
  483. }
  484. // Read password
  485. if offset+int(pwdLen) > len(data) {
  486. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seq1, seq2, uin))
  487. }
  488. password := string(data[offset : offset+int(pwdLen)])
  489. if len(password) > 0 && password[len(password)-1] == 0 {
  490. password = password[:len(password)-1]
  491. }
  492. h.logger.Info("V4 login attempt",
  493. "uin", uin,
  494. "port", port,
  495. "password_len", len(password),
  496. )
  497. // Read remaining fields to extract status
  498. // After password: UNKNOWN1(4) + IP(4) + FLAG(1) + STATUS(4)
  499. requestedStatus := uint32(ICQLegacyStatusOnline) // default
  500. if offset+4+4+1+4 <= len(data) {
  501. offset += 4 // skip UNKNOWN1
  502. offset += 4 // skip local IP
  503. offset += 1 // skip FLAG byte
  504. requestedStatus = binary.LittleEndian.Uint32(data[offset : offset+4])
  505. }
  506. h.logger.Debug("V4 login requested status",
  507. "uin", uin,
  508. "status", fmt.Sprintf("0x%08X", requestedStatus),
  509. )
  510. // 3. Call service layer with typed request
  511. authReq := AuthRequest{
  512. UIN: uin,
  513. Password: password,
  514. Status: requestedStatus,
  515. TCPPort: port,
  516. Version: ICQLegacyVersionV4,
  517. }
  518. authResult, err := h.service.AuthenticateUser(ctx, authReq)
  519. if err != nil {
  520. h.logger.Error("V4 login authentication error", "err", err, "uin", uin)
  521. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seq1, seq2, uin))
  522. }
  523. if !authResult.Success {
  524. h.logger.Info("V4 login failed - invalid credentials", "uin", uin, "error_code", authResult.ErrorCode)
  525. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seq1, seq2, uin))
  526. }
  527. // 4. Reuse the session created during handleGetDeps if it exists.
  528. // This preserves the server seq counter (depts list used seq=0, so
  529. // login reply will correctly get seq=1).
  530. existingSession := h.sessions.GetSession(uin)
  531. if existingSession != nil {
  532. // FirstLogin flow: session already exists from handleGetDeps.
  533. // Depts list was already sent there, just update metadata and
  534. // send the login reply.
  535. existingSession, err = h.sessions.AttachOSCARSession(uin, addr, authResult.oscarSession)
  536. if err != nil {
  537. h.logger.Error("failed to attach V4 OSCAR session", "err", err, "uin", uin)
  538. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seq1, seq2, uin))
  539. }
  540. existingSession.Password = password
  541. existingSession.SetStatus(requestedStatus)
  542. loginReplyPkt := h.packetBuilder.BuildLoginReply(existingSession, seq2)
  543. if err := h.sender.SendToSession(existingSession, loginReplyPkt); err != nil {
  544. return err
  545. }
  546. h.logger.Info("V4 login successful (firstlogin flow)",
  547. "uin", uin,
  548. "session_id", existingSession.SessionID,
  549. )
  550. h.notifyContactsUserOnline(existingSession)
  551. if err := h.service.NotifyUserOnline(ctx, existingSession.UIN, existingSession.GetStatus()); err != nil {
  552. h.logger.Debug("V4 failed to notify OSCAR clients of online", "uin", existingSession.UIN, "err", err)
  553. }
  554. } else {
  555. // Direct login flow: client sent Login (0x03E8) without prior
  556. // GetDeps. Just create session and send login reply directly.
  557. // TODO: parse and store direct connection info from V4 login packet.
  558. newSession, err := h.sessions.CreateSession(uin, addr, ICQLegacyVersionV4, authResult.oscarSession)
  559. if err != nil {
  560. h.logger.Error("failed to create V4 session", "err", err, "uin", uin)
  561. return h.sender.SendPacket(addr, h.packetBuilder.BuildBadPassword(seq1, seq2, uin))
  562. }
  563. newSession.Password = password
  564. newSession.SetStatus(requestedStatus)
  565. loginReplyPkt := h.packetBuilder.BuildLoginReply(newSession, seq2)
  566. if err := h.sender.SendToSession(newSession, loginReplyPkt); err != nil {
  567. return err
  568. }
  569. h.logger.Info("V4 login successful (direct flow)",
  570. "uin", uin,
  571. "session_id", newSession.SessionID,
  572. )
  573. h.notifyContactsUserOnline(newSession)
  574. if err := h.service.NotifyUserOnline(ctx, newSession.UIN, newSession.GetStatus()); err != nil {
  575. h.logger.Debug("V4 failed to notify OSCAR clients of online", "uin", newSession.UIN, "err", err)
  576. }
  577. }
  578. return nil
  579. }
  580. // handlePing processes keep-alive packets
  581. func (h *V4Handler) handlePing(session *LegacySession, addr *net.UDPAddr, seq1, seq2 uint16, uin uint32) error {
  582. h.logger.Debug("V4 keep-alive received",
  583. "uin", uin,
  584. "seq1", seq1,
  585. "seq2", seq2,
  586. "addr", addr.String(),
  587. "session_found", session != nil,
  588. )
  589. if session == nil {
  590. // Try to find session by UIN instead of address
  591. session = h.sessions.GetSession(uin)
  592. if session != nil {
  593. h.logger.Debug("V4 session found by UIN, updating address",
  594. "uin", uin,
  595. "old_addr", session.Addr.String(),
  596. "new_addr", addr.String(),
  597. )
  598. // Update session address
  599. h.sessions.UpdateSessionAddr(uin, addr)
  600. } else {
  601. h.logger.Debug("V4 session not found by UIN either", "uin", uin)
  602. return h.sendNotConnected(addr, seq2, uin)
  603. }
  604. }
  605. session.UpdateActivity()
  606. return h.sendAck(addr, seq1, seq2, uin)
  607. }
  608. // handleLogoff processes logout
  609. // Notifies legacy contacts and OSCAR clients that this user went offline,
  610. // then removes the session.
  611. func (h *V4Handler) handleLogoff(session *LegacySession, seq1, seq2 uint16, uin uint32) error {
  612. if session == nil {
  613. return nil
  614. }
  615. h.logger.Info("V4 logout", "uin", uin)
  616. // Notify legacy contacts that this user is going offline BEFORE removing session
  617. h.notifyContactsUserOffline(session)
  618. // Notify OSCAR clients that this user went offline
  619. ctx := context.Background()
  620. if err := h.service.NotifyUserOffline(ctx, uin); err != nil {
  621. h.logger.Debug("V4 failed to notify OSCAR clients of offline", "uin", uin, "err", err)
  622. }
  623. h.sessions.RemoveSession(uin)
  624. return nil
  625. }
  626. // handleContactList processes contact list
  627. //
  628. // Refactored to use service layer (ProcessContactList) and packet builder.
  629. // Following the OSCAR pattern: unmarshal -> call service -> send notifications
  630. func (h *V4Handler) handleContactList(session *LegacySession, seq1, seq2 uint16, uin uint32, data []byte) error {
  631. if session == nil {
  632. return nil
  633. }
  634. ctx := context.Background()
  635. // 1. Send ACK using packet builder
  636. _ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
  637. // 2. Unmarshal packet to typed request struct
  638. req, err := h.parseContactListPacket(data, uin)
  639. if err != nil {
  640. h.logger.Debug("V4 contact list parse error", "uin", uin, "err", err)
  641. // Send contact list done even on parse error
  642. return h.sender.SendToSession(session, h.packetBuilder.BuildContactListDone(session.NextServerSeqNum(), seq2, session.UIN))
  643. }
  644. h.logger.Debug("V4 contact list",
  645. "uin", uin,
  646. "count", len(req.Contacts),
  647. )
  648. // Store contact list in session (handler responsibility - session management)
  649. session.SetContactList(req.Contacts)
  650. // 3. Call service layer with typed request
  651. result, err := h.service.ProcessContactList(ctx, session.Instance, req)
  652. if err != nil {
  653. h.logger.Error("V4 contact list processing failed", "uin", uin, "err", err)
  654. // Still send contact list done on error
  655. return h.sender.SendToSession(session, h.packetBuilder.BuildContactListDone(session.NextServerSeqNum(), seq2, session.UIN))
  656. }
  657. // 4. Send online notifications based on service result using packet builder
  658. for _, contact := range result.OnlineContacts {
  659. if contact.Online {
  660. if h.dispatcher != nil {
  661. // Use central dispatcher - routes to correct protocol based on session's version
  662. _ = h.dispatcher.SendUserOnline(session, contact.UIN, contact.Status)
  663. } else {
  664. onlinePkt := h.packetBuilder.BuildUserOnline(
  665. session.NextServerSeqNum(),
  666. contact.UIN,
  667. contact.Status,
  668. )
  669. // Set the recipient UIN in the packet (offset 8-11)
  670. binary.LittleEndian.PutUint32(onlinePkt[8:12], session.UIN)
  671. _ = h.sender.SendToSession(session, onlinePkt)
  672. }
  673. }
  674. }
  675. h.notifyContactsUserOnline(session)
  676. // 5. Send contact list done using packet builder
  677. return h.sender.SendToSession(session, h.packetBuilder.BuildContactListDone(session.NextServerSeqNum(), seq2, session.UIN))
  678. }
  679. // parseContactListPacket parses a V4 contact list packet into a typed ContactListRequest struct.
  680. // V4 format (from licq CPU_ContactList): COUNT(1) + UIN(4)*COUNT
  681. // Note: V4 does NOT have a timestamp prefix - the data starts immediately with the count byte.
  682. func (h *V4Handler) parseContactListPacket(data []byte, ownerUIN uint32) (ContactListRequest, error) {
  683. req := ContactListRequest{
  684. UIN: ownerUIN,
  685. Contacts: make([]uint32, 0),
  686. }
  687. if len(data) < 1 {
  688. return req, fmt.Errorf("contact list packet too short: %d bytes", len(data))
  689. }
  690. // Contact count (1 byte) - no timestamp prefix in V4
  691. offset := 0
  692. count := int(data[offset])
  693. offset++
  694. // Parse contact UINs
  695. for i := 0; i < count && offset+4 <= len(data); i++ {
  696. contactUIN := binary.LittleEndian.Uint32(data[offset : offset+4])
  697. req.Contacts = append(req.Contacts, contactUIN)
  698. offset += 4
  699. }
  700. return req, nil
  701. }
  702. // handleSetStatus processes status change
  703. //
  704. // Refactored to use service layer (ProcessStatusChange) and packet builder.
  705. // Following the OSCAR pattern: unmarshal -> call service -> broadcast
  706. func (h *V4Handler) handleSetStatus(session *LegacySession, seq1, seq2 uint16, uin uint32, data []byte) error {
  707. if session == nil {
  708. return nil
  709. }
  710. ctx := context.Background()
  711. // 1. Send ACK using packet builder
  712. _ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
  713. // 2. Unmarshal packet to typed request struct
  714. // V4 status change format (from licq CPU_SetStatus): STATUS(4)
  715. // Note: V4 does NOT have a timestamp prefix - data starts with the status value.
  716. if len(data) < 4 {
  717. return nil
  718. }
  719. newStatus := binary.LittleEndian.Uint32(data[0:4])
  720. oldStatus := session.GetStatus()
  721. h.logger.Debug("V4 status change",
  722. "uin", uin,
  723. "old_status", fmt.Sprintf("0x%08X", oldStatus),
  724. "new_status", fmt.Sprintf("0x%08X", newStatus),
  725. )
  726. // Update session status (handler responsibility - session management)
  727. session.SetStatus(newStatus)
  728. // 3. Call service layer with typed request
  729. statusReq := StatusChangeRequest{
  730. UIN: uin,
  731. NewStatus: newStatus,
  732. OldStatus: oldStatus,
  733. }
  734. statusResult, err := h.service.ProcessStatusChange(ctx, statusReq)
  735. if err != nil {
  736. h.logger.Debug("V4 failed to process status change", "err", err)
  737. return nil
  738. }
  739. h.logger.Debug("V4 status change processed",
  740. "uin", uin,
  741. "notify_count", len(statusResult.NotifyTargets),
  742. )
  743. // 4. Broadcast status change to contacts
  744. // The service layer returns the list of users to notify
  745. for _, target := range statusResult.NotifyTargets {
  746. targetSession := h.sessions.GetSession(target.UIN)
  747. if targetSession != nil {
  748. if h.dispatcher != nil {
  749. _ = h.dispatcher.SendStatusChange(targetSession, uin, newStatus)
  750. } else {
  751. _ = h.sendUserStatus(targetSession, uin, newStatus)
  752. }
  753. }
  754. }
  755. return nil
  756. }
  757. // handleMessage processes through-server messages (0x010E, 0x0456)
  758. // Format: TIMESTAMP(4) + TO_UIN(4) + MSG_TYPE(2) + MSG_LEN(2) + MESSAGE
  759. //
  760. // Refactored to use service layer (ProcessMessage) and packet builder.
  761. // Following the OSCAR pattern: unmarshal -> call service -> route
  762. func (h *V4Handler) handleMessage(session *LegacySession, seq1, seq2 uint16, uin uint32, data []byte) error {
  763. if session == nil {
  764. return nil
  765. }
  766. ctx := context.Background()
  767. // 1. Send ACK using packet builder
  768. _ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
  769. // 2. Unmarshal packet to typed request struct
  770. req, err := h.parseMessagePacket(data, uin)
  771. if err != nil {
  772. h.logger.Debug("V4 message parse error", "from", uin, "err", err)
  773. return nil
  774. }
  775. h.logger.Debug("V4 message received",
  776. "from", req.FromUIN,
  777. "to", req.ToUIN,
  778. "type", fmt.Sprintf("0x%04X", req.MsgType),
  779. "msg_len", len(req.Message),
  780. )
  781. // 3. Call service layer with typed request
  782. result, err := h.service.ProcessMessage(ctx, session, req)
  783. if err != nil {
  784. h.logger.Error("V4 message processing failed",
  785. "from", req.FromUIN,
  786. "to", req.ToUIN,
  787. "err", err,
  788. )
  789. return nil
  790. }
  791. // 4. Route message based on service result
  792. if result.TargetOnline {
  793. // Target is online - forward message via dispatcher or direct send
  794. targetSession := h.sessions.GetSession(req.ToUIN)
  795. if targetSession != nil {
  796. if h.dispatcher != nil {
  797. _ = h.dispatcher.SendOnlineMessage(targetSession, req.FromUIN, req.MsgType, req.Message)
  798. } else {
  799. msgPkt := h.packetBuilder.BuildOnlineMessage(
  800. targetSession.NextServerSeqNum(),
  801. req.FromUIN,
  802. req.MsgType,
  803. req.Message,
  804. )
  805. // Set the recipient UIN in the packet (offset 8-11)
  806. binary.LittleEndian.PutUint32(msgPkt[8:12], targetSession.UIN)
  807. _ = h.sender.SendToSession(targetSession, msgPkt)
  808. }
  809. h.logger.Debug("V4 message forwarded",
  810. "from", req.FromUIN,
  811. "to", req.ToUIN,
  812. "target_version", targetSession.Version,
  813. )
  814. }
  815. } else if result.StoredOffline {
  816. // Message was stored for offline delivery by the service layer
  817. h.logger.Debug("V4 target user offline, message stored",
  818. "from", req.FromUIN,
  819. "to", req.ToUIN,
  820. )
  821. } else {
  822. h.logger.Debug("V4 target user offline, message not delivered",
  823. "from", req.FromUIN,
  824. "to", req.ToUIN,
  825. )
  826. }
  827. return nil
  828. }
  829. // parseMessagePacket parses a V4 message packet into a typed MessageRequest struct.
  830. // V4 format (from licq CPU_ThroughServer): TO_UIN(4) + MSG_TYPE(2) + MSG_LEN(2) + MESSAGE
  831. // Note: V4 does NOT have a timestamp prefix - data starts with the destination UIN.
  832. func (h *V4Handler) parseMessagePacket(data []byte, fromUIN uint32) (MessageRequest, error) {
  833. req := MessageRequest{
  834. FromUIN: fromUIN,
  835. }
  836. if len(data) < 8 {
  837. return req, fmt.Errorf("message packet too short: %d bytes", len(data))
  838. }
  839. offset := 0
  840. // Target UIN (4 bytes)
  841. req.ToUIN = binary.LittleEndian.Uint32(data[offset : offset+4])
  842. offset += 4
  843. // Message type (2 bytes)
  844. req.MsgType = binary.LittleEndian.Uint16(data[offset : offset+2])
  845. offset += 2
  846. // Message length (2 bytes)
  847. msgLen := binary.LittleEndian.Uint16(data[offset : offset+2])
  848. offset += 2
  849. // Message content
  850. if msgLen > 0 && offset+int(msgLen) <= len(data) {
  851. req.Message = string(data[offset : offset+int(msgLen)])
  852. // Remove null terminator if present
  853. if len(req.Message) > 0 && req.Message[len(req.Message)-1] == 0 {
  854. req.Message = req.Message[:len(req.Message)-1]
  855. }
  856. }
  857. return req, nil
  858. }
  859. // handleUserAdd processes user add to contact list (0x053C)
  860. // Sends "you were added" notification to target user
  861. func (h *V4Handler) handleUserAdd(session *LegacySession, seq1, seq2 uint16, uin uint32, data []byte) error {
  862. if session == nil {
  863. return nil
  864. }
  865. _ = h.sendAck(session.Addr, seq1, seq2, uin)
  866. // V4 format (from licq CPU_AddUser): TARGET_UIN(4)
  867. // Note: V4 does NOT have a timestamp prefix.
  868. if len(data) < 4 {
  869. return nil
  870. }
  871. targetUIN := binary.LittleEndian.Uint32(data[0:4])
  872. h.logger.Debug("V4 user add",
  873. "from", uin,
  874. "target", targetUIN,
  875. )
  876. contacts := session.GetContactList()
  877. contacts = append(contacts, targetUIN)
  878. session.SetContactList(contacts)
  879. ctx := context.Background()
  880. if _, err := h.service.ProcessUserAdd(ctx, session.Instance, UserAddRequest{
  881. FromUIN: uin,
  882. TargetUIN: targetUIN,
  883. }); err != nil {
  884. h.logger.Debug("V4 user add service call failed", "err", err)
  885. }
  886. targetSession := h.sessions.GetSession(targetUIN)
  887. if targetSession != nil {
  888. // Send target's online status to the user who added them
  889. if h.dispatcher != nil {
  890. _ = h.dispatcher.SendUserOnline(session, targetUIN, targetSession.GetStatus())
  891. } else {
  892. _ = h.sendUserOnline(session, targetUIN, targetSession.GetStatus())
  893. }
  894. // Send "you were added" notification to target user
  895. // Message format: nick#FE#first#FE#last#FE#email#FE#auth
  896. youWereAddedMsg := fmt.Sprintf("%d\xFE\xFE\xFE\xFE0", uin)
  897. if h.dispatcher != nil {
  898. _ = h.dispatcher.SendOnlineMessage(targetSession, uin, ICQLegacyMsgAdded, youWereAddedMsg)
  899. } else {
  900. _ = h.sendOnlineMessage(targetSession, uin, ICQLegacyMsgAdded, youWereAddedMsg, 0)
  901. }
  902. // Also send the adder's online status to the target.
  903. // The target receives "you were added" but won't see the
  904. // adder as online unless we explicitly tell them. Without
  905. // this, the target's client shows the adder as offline
  906. // even though they're connected.
  907. if h.dispatcher != nil {
  908. _ = h.dispatcher.SendUserOnline(targetSession, uin, session.GetStatus())
  909. } else {
  910. _ = h.sendUserOnline(targetSession, uin, session.GetStatus())
  911. }
  912. h.logger.Debug("V4 sent 'you were added' notification",
  913. "from", uin,
  914. "to", targetUIN,
  915. )
  916. }
  917. return nil
  918. }
  919. // handleGetInfo processes user info request
  920. func (h *V4Handler) handleGetInfo(session *LegacySession, seq1, seq2 uint16, uin uint32, data []byte) error {
  921. if session == nil {
  922. return nil
  923. }
  924. _ = h.sendAck(session.Addr, seq1, seq2, uin)
  925. // V4 format (from licq CPU_GetUserBasicInfo): TARGET_UIN(4)
  926. // Note: V4 does NOT have a timestamp/subsequence prefix (unlike V2).
  927. if len(data) < 4 {
  928. return nil
  929. }
  930. targetUIN := binary.LittleEndian.Uint32(data[0:4])
  931. h.logger.Debug("V4 get info request",
  932. "from", uin,
  933. "target", targetUIN,
  934. )
  935. // Send basic info
  936. _ = h.sendBasicInfo(session, seq2, targetUIN)
  937. return nil
  938. }
  939. // handleInfoReq processes old-style user info request (0x0460)
  940. // Format: TARGET_UIN(4) - no timestamp prefix
  941. // Response: 0x0118 (INFO_REPLY)
  942. func (h *V4Handler) handleInfoReq(session *LegacySession, seq1, seq2 uint16, uin uint32, data []byte) error {
  943. if session == nil {
  944. return nil
  945. }
  946. _ = h.sendAck(session.Addr, seq1, seq2, uin)
  947. // Parse - format: TARGET_UIN(4) only
  948. if len(data) < 4 {
  949. h.logger.Debug("V4 info request too short", "len", len(data))
  950. return nil
  951. }
  952. targetUIN := binary.LittleEndian.Uint32(data[0:4])
  953. h.logger.Debug("V4 info request (0x0460)",
  954. "from", uin,
  955. "target", targetUIN,
  956. )
  957. // Send basic info response (0x0118 - ICQLegacySrvInfoReply)
  958. return h.sendBasicInfoResponse(session, seq2, targetUIN)
  959. }
  960. // handleExtInfoReq processes extended info request (0x046A)
  961. // Format: TARGET_UIN(4) - no timestamp prefix
  962. // Response: 0x0122 (EXT_INFO_REPLY) - NOT 0x0118!
  963. func (h *V4Handler) handleExtInfoReq(session *LegacySession, seq1, seq2 uint16, uin uint32, data []byte) error {
  964. if session == nil {
  965. return nil
  966. }
  967. _ = h.sendAck(session.Addr, seq1, seq2, uin)
  968. // Parse - format: TARGET_UIN(4) only
  969. if len(data) < 4 {
  970. h.logger.Debug("V4 ext info request too short", "len", len(data))
  971. return nil
  972. }
  973. targetUIN := binary.LittleEndian.Uint32(data[0:4])
  974. h.logger.Debug("V4 ext info request (0x046A)",
  975. "from", uin,
  976. "target", targetUIN,
  977. )
  978. // Send extended info response (0x0122 - ICQLegacySrvExtInfoReply)
  979. return h.sendExtInfoResponse(session, seq2, targetUIN)
  980. }
  981. // handleSearchByUIN processes old-style search by UIN (0x041A)
  982. // V4 format: TARGET_UIN(4) - no timestamp/subsequence prefix
  983. // Response: 0x008C (SEARCH_FOUND) + 0x00A0 (SEARCH_DONE)
  984. // From licq ProcessUdpPacket case ICQ_CMDxRCV_SEARCHxFOUND:
  985. //
  986. // UIN(4) + NICK_LEN(2) + NICK + FIRST_LEN(2) + FIRST + LAST_LEN(2) + LAST + EMAIL_LEN(2) + EMAIL + AUTH(1)
  987. func (h *V4Handler) handleSearchByUIN(session *LegacySession, seq1, seq2 uint16, uin uint32, data []byte) error {
  988. if session == nil {
  989. return nil
  990. }
  991. _ = h.sendAck(session.Addr, seq1, seq2, uin)
  992. if len(data) < 4 {
  993. h.logger.Debug("V4 search by UIN - data too short", "uin", uin)
  994. return h.sendSearchEnd(session, seq2, false)
  995. }
  996. ctx := context.Background()
  997. targetUIN := binary.LittleEndian.Uint32(data[0:4])
  998. h.logger.Info("V4 search by UIN",
  999. "uin", uin,
  1000. "target_uin", targetUIN,
  1001. )
  1002. result, err := h.service.SearchByUIN(ctx, targetUIN)
  1003. if err != nil {
  1004. h.logger.Debug("V4 search by UIN - error", "target_uin", targetUIN, "err", err)
  1005. return h.sendSearchEnd(session, seq2, false)
  1006. }
  1007. if result == nil {
  1008. h.logger.Debug("V4 search by UIN - not found", "target_uin", targetUIN)
  1009. return h.sendSearchEnd(session, seq2, false)
  1010. }
  1011. h.logger.Info("V4 search by UIN - found",
  1012. "target_uin", targetUIN,
  1013. "nickname", result.Nickname,
  1014. )
  1015. _ = h.sendSearchFound(session, seq2, result)
  1016. return h.sendSearchEnd(session, seq2, false)
  1017. }
  1018. // handleSearchByName processes old-style search by name/email (0x0424)
  1019. // V4 format (from licq CPU_StartSearch): NICK_LEN(2) + NICK + FIRST_LEN(2) + FIRST + LAST_LEN(2) + LAST + EMAIL_LEN(2) + EMAIL
  1020. // Response: 0x008C (SEARCH_FOUND) for each result + 0x00A0 (SEARCH_DONE)
  1021. func (h *V4Handler) handleSearchByName(session *LegacySession, seq1, seq2 uint16, uin uint32, data []byte) error {
  1022. if session == nil {
  1023. return nil
  1024. }
  1025. _ = h.sendAck(session.Addr, seq1, seq2, uin)
  1026. h.logger.Info("V4 search by name", "uin", uin, "data_len", len(data))
  1027. // TODO: parse search parameters and call h.service.SearchByName
  1028. return h.sendSearchEnd(session, seq2, false)
  1029. }
  1030. // sendSearchFound sends a search result (0x008C) to the session.
  1031. // V3 server packet format with checkcode.
  1032. // Data format (from licq): UIN(4) + NICK_LEN(2) + NICK + FIRST_LEN(2) + FIRST +
  1033. //
  1034. // LAST_LEN(2) + LAST + EMAIL_LEN(2) + EMAIL + AUTH(1)
  1035. func (h *V4Handler) sendSearchFound(session *LegacySession, seq2 uint16, result *LegacyUserSearchResult) error {
  1036. if result == nil {
  1037. return nil
  1038. }
  1039. nick := truncateField(result.Nickname, 20, h.logger, "nickname", result.UIN)
  1040. first := truncateField(result.FirstName, 64, h.logger, "first_name", result.UIN)
  1041. last := truncateField(result.LastName, 64, h.logger, "last_name", result.UIN)
  1042. email := truncateField(result.Email, 64, h.logger, "email", result.UIN)
  1043. if nick == "" {
  1044. nick = fmt.Sprintf("%d", result.UIN)
  1045. }
  1046. // header(16) + UIN(4) + nick(2+len+1) + first(2+len+1) + last(2+len+1) + email(2+len+1) + auth(1)
  1047. pktSize := 16 + 4 + 2 + len(nick) + 1 + 2 + len(first) + 1 + 2 + len(last) + 1 + 2 + len(email) + 1 + 1
  1048. pkt := make([]byte, pktSize)
  1049. offset := 0
  1050. // Header
  1051. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  1052. offset += 2
  1053. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvSearchFound) // 0x008C
  1054. offset += 2
  1055. binary.LittleEndian.PutUint16(pkt[offset:], session.NextServerSeqNum())
  1056. offset += 2
  1057. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  1058. offset += 2
  1059. binary.LittleEndian.PutUint32(pkt[offset:], session.UIN)
  1060. offset += 4
  1061. checkcode := h.calculateV4Checkcode(pkt)
  1062. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  1063. offset += 4
  1064. // UIN of found user
  1065. binary.LittleEndian.PutUint32(pkt[offset:], result.UIN)
  1066. offset += 4
  1067. // Nick (length-prefixed, null-terminated)
  1068. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(nick)+1))
  1069. offset += 2
  1070. copy(pkt[offset:], nick)
  1071. offset += len(nick)
  1072. pkt[offset] = 0
  1073. offset++
  1074. // First name
  1075. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(first)+1))
  1076. offset += 2
  1077. copy(pkt[offset:], first)
  1078. offset += len(first)
  1079. pkt[offset] = 0
  1080. offset++
  1081. // Last name
  1082. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(last)+1))
  1083. offset += 2
  1084. copy(pkt[offset:], last)
  1085. offset += len(last)
  1086. pkt[offset] = 0
  1087. offset++
  1088. // Email
  1089. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(email)+1))
  1090. offset += 2
  1091. copy(pkt[offset:], email)
  1092. offset += len(email)
  1093. pkt[offset] = 0
  1094. offset++
  1095. // Auth flag
  1096. pkt[offset] = result.AuthRequired
  1097. offset++
  1098. h.logger.Debug("sending V4 search found (0x008C)",
  1099. "to", session.UIN,
  1100. "found_uin", result.UIN,
  1101. "hex", fmt.Sprintf("%X", pkt[:offset]),
  1102. )
  1103. return h.sender.SendToSession(session, pkt[:offset])
  1104. }
  1105. // sendSearchEnd sends search done marker (0x00A0) to the session.
  1106. // V3 server packet format with checkcode.
  1107. // Data format (from licq): MORE(1)
  1108. func (h *V4Handler) sendSearchEnd(session *LegacySession, seq2 uint16, more bool) error {
  1109. pkt := make([]byte, 17)
  1110. offset := 0
  1111. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  1112. offset += 2
  1113. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvSearchDone) // 0x00A0
  1114. offset += 2
  1115. binary.LittleEndian.PutUint16(pkt[offset:], session.NextServerSeqNum())
  1116. offset += 2
  1117. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  1118. offset += 2
  1119. binary.LittleEndian.PutUint32(pkt[offset:], session.UIN)
  1120. offset += 4
  1121. checkcode := h.calculateV4Checkcode(pkt)
  1122. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  1123. offset += 4
  1124. var moreByte byte
  1125. if more {
  1126. moreByte = 0x01
  1127. }
  1128. pkt[offset] = moreByte
  1129. offset++
  1130. h.logger.Debug("sending V4 search done (0x00A0)",
  1131. "to", session.UIN,
  1132. "more", more,
  1133. "hex", fmt.Sprintf("%X", pkt[:offset]),
  1134. )
  1135. return h.sender.SendToSession(session, pkt[:offset])
  1136. }
  1137. // handleUpdateBasic processes V4 update basic info (0x050A)
  1138. // V4 format (from licq CPU_UpdatePersonalBasicInfo, no subsequence prefix):
  1139. //
  1140. // NICK_LEN(2) + NICK + FNAME_LEN(2) + FNAME + LNAME_LEN(2) + LNAME + EMAIL_LEN(2) + EMAIL + AUTH(1)
  1141. //
  1142. // Response: 0x01E0 (ICQLegacySrvUpdatedBasicV4) on success, 0x01EA on failure
  1143. func (h *V4Handler) handleUpdateBasic(session *LegacySession, seq1, seq2 uint16, uin uint32, data []byte) error {
  1144. if session == nil {
  1145. return nil
  1146. }
  1147. _ = h.sendAck(session.Addr, seq1, seq2, uin)
  1148. // Parse: NICK_LEN(2)+NICK + FNAME_LEN(2)+FNAME + LNAME_LEN(2)+LNAME + EMAIL_LEN(2)+EMAIL + AUTH(1)
  1149. if len(data) >= 4 {
  1150. r := bytes.NewReader(data)
  1151. alias, _ := ParseLegacyString(r, true)
  1152. first, _ := ParseLegacyString(r, true)
  1153. last, _ := ParseLegacyString(r, true)
  1154. email, _ := ParseLegacyString(r, true)
  1155. var auth uint8
  1156. _ = binary.Read(r, binary.LittleEndian, &auth)
  1157. ctx := context.Background()
  1158. info := state.ICQBasicInfo{
  1159. Nickname: alias,
  1160. FirstName: first,
  1161. LastName: last,
  1162. EmailAddress: email,
  1163. }
  1164. if err := h.service.UpdateBasicInfo(ctx, uin, info); err != nil {
  1165. h.logger.Error("V4 update basic failed", "uin", uin, "err", err)
  1166. }
  1167. if err := h.service.SetAuthMode(ctx, uin, auth == 1); err != nil {
  1168. h.logger.Error("V4 set auth mode failed", "uin", uin, "err", err)
  1169. }
  1170. }
  1171. pkt := make([]byte, 16)
  1172. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  1173. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvUpdatedBasicV4)
  1174. binary.LittleEndian.PutUint16(pkt[4:6], session.NextServerSeqNum())
  1175. binary.LittleEndian.PutUint16(pkt[6:8], seq2)
  1176. binary.LittleEndian.PutUint32(pkt[8:12], session.UIN)
  1177. checkcode := h.calculateV4Checkcode(pkt)
  1178. binary.LittleEndian.PutUint32(pkt[12:16], checkcode)
  1179. return h.sender.SendToSession(session, pkt)
  1180. }
  1181. // handleUpdateDetail processes V4 update extended info (0x04B0)
  1182. // V4 format (from licq CPU_UpdatePersonalExtInfo, no subsequence prefix):
  1183. //
  1184. // CITY_LEN(2) + CITY + COUNTRY(2) + TIMEZONE(1) + STATE_LEN(2) + STATE +
  1185. // AGE(2) + SEX(1) + PHONE_LEN(2) + PHONE + HOMEPAGE_LEN(2) + HOMEPAGE +
  1186. // ABOUT_LEN(2) + ABOUT + ZIPCODE(4)
  1187. //
  1188. // Response: 0x00C8 on success, 0x00D2 on failure
  1189. func (h *V4Handler) handleUpdateDetail(session *LegacySession, seq1, seq2 uint16, uin uint32, data []byte) error {
  1190. if session == nil {
  1191. return nil
  1192. }
  1193. _ = h.sendAck(session.Addr, seq1, seq2, uin)
  1194. // Parse: CITY_LEN(2)+CITY + COUNTRY(2) + TIMEZONE(1) + STATE_LEN(2)+STATE +
  1195. // AGE(2) + SEX(1) + PHONE_LEN(2)+PHONE + HOMEPAGE_LEN(2)+HOMEPAGE +
  1196. // ABOUT_LEN(2)+ABOUT + ZIPCODE(4)
  1197. if len(data) >= 4 {
  1198. r := bytes.NewReader(data)
  1199. city, _ := ParseLegacyString(r, true)
  1200. var country uint16
  1201. _ = binary.Read(r, binary.LittleEndian, &country)
  1202. var timezone uint8
  1203. _ = binary.Read(r, binary.LittleEndian, &timezone)
  1204. st, _ := ParseLegacyString(r, true)
  1205. var age uint16
  1206. _ = binary.Read(r, binary.LittleEndian, &age)
  1207. var sex uint8
  1208. _ = binary.Read(r, binary.LittleEndian, &sex)
  1209. phone, _ := ParseLegacyString(r, true)
  1210. homepage, _ := ParseLegacyString(r, true)
  1211. about, _ := ParseLegacyString(r, true)
  1212. ctx := context.Background()
  1213. // Read existing basic info to avoid overwriting nick/first/last/email
  1214. existing, err := h.service.GetFullUserInfo(ctx, uin)
  1215. if err == nil && existing != nil {
  1216. existing.ICQInfo.Basic.City = city
  1217. existing.ICQInfo.Basic.CountryCode = country
  1218. existing.ICQInfo.Basic.State = st
  1219. existing.ICQInfo.Basic.Phone = phone
  1220. if err := h.service.UpdateBasicInfo(ctx, uin, existing.ICQInfo.Basic); err != nil {
  1221. h.logger.Error("V4 update detail basic failed", "uin", uin, "err", err)
  1222. }
  1223. }
  1224. // Merge more info to avoid overwriting birthday/languages
  1225. if existing != nil {
  1226. existing.ICQInfo.More.Gender = uint16(sex)
  1227. existing.ICQInfo.More.HomePageAddr = homepage
  1228. if err := h.service.UpdateMoreInfo(ctx, uin, existing.ICQInfo.More); err != nil {
  1229. h.logger.Error("V4 update detail more failed", "uin", uin, "err", err)
  1230. }
  1231. }
  1232. if about != "" {
  1233. if err := h.service.SetNotes(ctx, uin, about); err != nil {
  1234. h.logger.Error("V4 update detail about failed", "uin", uin, "err", err)
  1235. }
  1236. }
  1237. }
  1238. pkt := make([]byte, 16)
  1239. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  1240. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvUpdatedDetail)
  1241. binary.LittleEndian.PutUint16(pkt[4:6], session.NextServerSeqNum())
  1242. binary.LittleEndian.PutUint16(pkt[6:8], seq2)
  1243. binary.LittleEndian.PutUint32(pkt[8:12], session.UIN)
  1244. checkcode := h.calculateV4Checkcode(pkt)
  1245. binary.LittleEndian.PutUint32(pkt[12:16], checkcode)
  1246. return h.sender.SendToSession(session, pkt)
  1247. }
  1248. // handleOfflineMsgReq processes offline message request
  1249. // Delivers stored offline messages then sends "done" marker.
  1250. // From licq: client sends 0x044C, server responds with 0x00DC for each
  1251. // offline message, then 0x00E6 (SYS_MSG_DONE).
  1252. // Offline message format (from licq ProcessUdpPacket case ICQ_CMDxRCV_SYSxMSGxOFFLINE):
  1253. //
  1254. // UIN(4) + YEAR(2) + MONTH(1) + DAY(1) + HOUR(1) + MIN(1) + MSG_TYPE(2) + MSG_LEN(2) + MSG
  1255. func (h *V4Handler) handleOfflineMsgReq(session *LegacySession, seq1, seq2 uint16, uin uint32) error {
  1256. if session == nil {
  1257. return nil
  1258. }
  1259. _ = h.sendAck(session.Addr, seq1, seq2, uin)
  1260. h.logger.Debug("V4 offline message request", "uin", uin)
  1261. ctx := context.Background()
  1262. // Retrieve stored offline messages from the service layer
  1263. messages, err := h.service.GetOfflineMessages(ctx, uin)
  1264. if err != nil {
  1265. h.logger.Debug("V4 failed to get offline messages", "uin", uin, "err", err)
  1266. // Still send done even on error
  1267. return h.sendOfflineMsgDone(session, seq2)
  1268. }
  1269. // Send each offline message as SYS_MSG_OFFLINE (0x00DC)
  1270. for _, msg := range messages {
  1271. _ = h.sendOfflineMessage(session, msg)
  1272. }
  1273. // Acknowledge (delete) offline messages after delivery
  1274. if len(messages) > 0 {
  1275. if err := h.service.AckOfflineMessages(ctx, uin); err != nil {
  1276. h.logger.Debug("V4 failed to ack offline messages", "uin", uin, "err", err)
  1277. }
  1278. }
  1279. return h.sendOfflineMsgDone(session, seq2)
  1280. }
  1281. // sendOfflineMessage sends a single offline message (0x00DC) to the session.
  1282. // Format: header(16) + FROM_UIN(4) + YEAR(2) + MONTH(1) + DAY(1) + HOUR(1) + MIN(1) + MSG_TYPE(2) + MSG_LEN(2) + MSG
  1283. func (h *V4Handler) sendOfflineMessage(session *LegacySession, msg LegacyOfflineMessage) error {
  1284. msgBytes := []byte(msg.Message)
  1285. pktSize := 16 + 4 + 2 + 1 + 1 + 1 + 1 + 2 + 2 + len(msgBytes) + 1
  1286. pkt := make([]byte, pktSize)
  1287. offset := 0
  1288. // Header
  1289. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  1290. offset += 2
  1291. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvSysMsgOffline) // 0x00DC
  1292. offset += 2
  1293. binary.LittleEndian.PutUint16(pkt[offset:], session.NextServerSeqNum())
  1294. offset += 2
  1295. binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq2
  1296. offset += 2
  1297. binary.LittleEndian.PutUint32(pkt[offset:], session.UIN)
  1298. offset += 4
  1299. // Calculate and add checkcode
  1300. checkcode := h.calculateV4Checkcode(pkt)
  1301. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  1302. offset += 4
  1303. // From UIN
  1304. binary.LittleEndian.PutUint32(pkt[offset:], msg.FromUIN)
  1305. offset += 4
  1306. // Timestamp: YEAR(2) + MONTH(1) + DAY(1) + HOUR(1) + MIN(1)
  1307. binary.LittleEndian.PutUint16(pkt[offset:], uint16(msg.Timestamp.Year()))
  1308. offset += 2
  1309. pkt[offset] = byte(msg.Timestamp.Month())
  1310. offset++
  1311. pkt[offset] = byte(msg.Timestamp.Day())
  1312. offset++
  1313. pkt[offset] = byte(msg.Timestamp.Hour())
  1314. offset++
  1315. pkt[offset] = byte(msg.Timestamp.Minute())
  1316. offset++
  1317. // Message type
  1318. binary.LittleEndian.PutUint16(pkt[offset:], msg.MsgType)
  1319. offset += 2
  1320. // Message length (including null terminator)
  1321. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(msgBytes)+1))
  1322. offset += 2
  1323. // Message content
  1324. copy(pkt[offset:], msgBytes)
  1325. offset += len(msgBytes)
  1326. pkt[offset] = 0 // null terminator
  1327. offset++
  1328. h.logger.Debug("sending V4 offline message",
  1329. "to", session.UIN,
  1330. "from", msg.FromUIN,
  1331. "type", fmt.Sprintf("0x%04X", msg.MsgType),
  1332. )
  1333. return h.sender.SendToSession(session, pkt[:offset])
  1334. }
  1335. // V4 packet sending helpers
  1336. // Server sends V3 format packets to V4 clients (confirmed in documentation)
  1337. // V3 packets need a checkcode at the end
  1338. // calculateV4Checkcode calculates the checkcode for server packets to V4 clients
  1339. // Based on reverse engineering of ICQ 98a client (encrypt_v4_packet and validate_v4_checkcode)
  1340. //
  1341. // The client validates checkcode as follows:
  1342. //
  1343. // checkA = (byte[8] << 24) | (byte[4] << 16) | (byte[2] << 8) | byte[6]
  1344. // result = checkA ^ checkcode
  1345. // valid if: (result >> 16) & 0xFF == ~packet[result >> 24]
  1346. // and: result & 0xFF == ~table[(result >> 8) & 0xFF]
  1347. //
  1348. // So we construct checkcode such that after XOR with checkA, the result contains:
  1349. //
  1350. // byte 3 (bits 24-31): packetOfs - offset into packet
  1351. // byte 2 (bits 16-23): ~packet[packetOfs] - inverted byte at that offset
  1352. // byte 1 (bits 8-15): tableOfs - offset into table
  1353. // byte 0 (bits 0-7): ~table[tableOfs] - inverted byte at that table offset
  1354. func (h *V4Handler) calculateV4Checkcode(packet []byte) uint32 {
  1355. if len(packet) < 12 {
  1356. return 0
  1357. }
  1358. // checkA: pack bytes 8, 4, 2, 6 from packet (MSB to LSB)
  1359. checkA := uint32(packet[8])<<24 | uint32(packet[4])<<16 | uint32(packet[2])<<8 | uint32(packet[6])
  1360. // Choose offsets (we use fixed values, but could be random)
  1361. // Avoid offset 0x10-0x13 (checkcode position) - use offset 4 (seq1 low byte)
  1362. packetOfs := byte(4)
  1363. tableOfs := byte(0)
  1364. // Get values and INVERT them (this is what the client expects)
  1365. packetVal := ^packet[packetOfs] // inverted
  1366. tableVal := ^V4Table[tableOfs] // inverted
  1367. // Build checkB with inverted values
  1368. checkB := uint32(packetOfs)<<24 | uint32(packetVal)<<16 | uint32(tableOfs)<<8 | uint32(tableVal)
  1369. // checkcode = checkA ^ checkB
  1370. result := checkA ^ checkB
  1371. h.logger.Debug("V4 checkcode calculation",
  1372. "byte8", fmt.Sprintf("0x%02X", packet[8]),
  1373. "byte4", fmt.Sprintf("0x%02X", packet[4]),
  1374. "byte2", fmt.Sprintf("0x%02X", packet[2]),
  1375. "byte6", fmt.Sprintf("0x%02X", packet[6]),
  1376. "checkA", fmt.Sprintf("0x%08X", checkA),
  1377. "packetOfs", packetOfs,
  1378. "packetVal_inverted", fmt.Sprintf("0x%02X", packetVal),
  1379. "tableOfs", tableOfs,
  1380. "tableVal_inverted", fmt.Sprintf("0x%02X", tableVal),
  1381. "checkB", fmt.Sprintf("0x%08X", checkB),
  1382. "checkcode", fmt.Sprintf("0x%08X", result),
  1383. )
  1384. return result
  1385. }
  1386. // sendAck sends ACK packet in V3 format with checkcode
  1387. func (h *V4Handler) sendAck(addr *net.UDPAddr, seq1, seq2 uint16, uin uint32) error {
  1388. pkt := make([]byte, 16)
  1389. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  1390. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvAck)
  1391. binary.LittleEndian.PutUint16(pkt[4:6], seq1)
  1392. binary.LittleEndian.PutUint16(pkt[6:8], seq2)
  1393. binary.LittleEndian.PutUint32(pkt[8:12], uin)
  1394. // Calculate and add checkcode
  1395. checkcode := h.calculateV4Checkcode(pkt)
  1396. binary.LittleEndian.PutUint32(pkt[12:16], checkcode)
  1397. h.logger.Debug("sending V4 ACK",
  1398. "to", addr.String(),
  1399. "seq1", seq1,
  1400. "seq2", seq2,
  1401. "uin", uin,
  1402. "checkcode", fmt.Sprintf("0x%08X", checkcode),
  1403. "hex", fmt.Sprintf("%X", pkt),
  1404. )
  1405. return h.sender.SendPacket(addr, pkt)
  1406. }
  1407. // sendNotConnected sends not connected error
  1408. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + ZERO(4)
  1409. func (h *V4Handler) sendNotConnected(addr *net.UDPAddr, seq2 uint16, uin uint32) error {
  1410. pkt := make([]byte, 16)
  1411. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  1412. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvNotConnected)
  1413. binary.LittleEndian.PutUint16(pkt[4:6], 0)
  1414. binary.LittleEndian.PutUint16(pkt[6:8], seq2)
  1415. binary.LittleEndian.PutUint32(pkt[8:12], uin)
  1416. binary.LittleEndian.PutUint32(pkt[12:16], 0) // Zero field
  1417. return h.sender.SendPacket(addr, pkt)
  1418. }
  1419. // notifyContactsUserOnline notifies all contacts who have this user in their list that we're online
  1420. func (h *V4Handler) notifyContactsUserOnline(session *LegacySession) {
  1421. if h.dispatcher == nil {
  1422. return
  1423. }
  1424. contactsToNotify := h.sessions.NotifyContactsOfStatus(session)
  1425. h.logger.Debug("V4 notifying contacts of user online",
  1426. "uin", session.UIN,
  1427. "contacts_to_notify", contactsToNotify,
  1428. )
  1429. for _, contactUIN := range contactsToNotify {
  1430. contactSession := h.sessions.GetSession(contactUIN)
  1431. if contactSession != nil {
  1432. _ = h.dispatcher.SendUserOnline(contactSession, session.UIN, session.GetStatus())
  1433. }
  1434. }
  1435. }
  1436. // notifyContactsUserOffline notifies all contacts who have this user in their list that we're offline
  1437. func (h *V4Handler) notifyContactsUserOffline(session *LegacySession) {
  1438. if h.dispatcher == nil {
  1439. return
  1440. }
  1441. contactsToNotify := h.sessions.NotifyContactsOfStatus(session)
  1442. h.logger.Debug("V4 notifying contacts of user offline",
  1443. "uin", session.UIN,
  1444. "contacts_to_notify", contactsToNotify,
  1445. )
  1446. for _, contactUIN := range contactsToNotify {
  1447. contactSession := h.sessions.GetSession(contactUIN)
  1448. if contactSession != nil {
  1449. _ = h.dispatcher.SendUserOffline(contactSession, session.UIN)
  1450. }
  1451. }
  1452. }
  1453. // sendUserOnline sends user online notification
  1454. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4) + DATA
  1455. // From iserverd v3_send_user_online() - V3 and V4 share the same server packet format.
  1456. // Data: UIN(4) + IP(4) + PORT(4) + REAL_IP(4) + DC_TYPE(1) + STATUS(2) + ESTATUS(2) + DCVER(2) + UNKNOWN(2)
  1457. // Total data: 25 bytes, total packet: 41 bytes
  1458. func (h *V4Handler) sendUserOnline(session *LegacySession, uin uint32, status uint32) error {
  1459. pkt := make([]byte, 41)
  1460. offset := 0
  1461. // Header
  1462. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  1463. offset += 2
  1464. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvUserOnline)
  1465. offset += 2
  1466. binary.LittleEndian.PutUint16(pkt[offset:], session.NextServerSeqNum())
  1467. offset += 2
  1468. binary.LittleEndian.PutUint16(pkt[offset:], 0)
  1469. offset += 2
  1470. binary.LittleEndian.PutUint32(pkt[offset:], session.UIN)
  1471. offset += 4
  1472. // Calculate and add checkcode
  1473. checkcode := h.calculateV4Checkcode(pkt)
  1474. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  1475. offset += 4
  1476. // UIN of user who came online
  1477. binary.LittleEndian.PutUint32(pkt[offset:], uin)
  1478. offset += 4
  1479. // IP address (0 for privacy - V3 clients crash on non-V3 direct connect)
  1480. binary.LittleEndian.PutUint32(pkt[offset:], 0)
  1481. offset += 4
  1482. // TCP port (4 bytes, 0 for privacy)
  1483. binary.LittleEndian.PutUint32(pkt[offset:], 0)
  1484. offset += 4
  1485. // Internal/real IP (4 bytes, 0 for privacy)
  1486. binary.LittleEndian.PutUint32(pkt[offset:], 0)
  1487. offset += 4
  1488. // DC type (1 byte)
  1489. pkt[offset] = 0
  1490. offset++
  1491. // Status (low 16 bits)
  1492. binary.LittleEndian.PutUint16(pkt[offset:], uint16(status&0xFFFF))
  1493. offset += 2
  1494. // Extended status (high 16 bits)
  1495. binary.LittleEndian.PutUint16(pkt[offset:], uint16(status>>16))
  1496. offset += 2
  1497. // DC version (2 bytes)
  1498. binary.LittleEndian.PutUint16(pkt[offset:], 0)
  1499. offset += 2
  1500. // Unknown (2 bytes)
  1501. binary.LittleEndian.PutUint16(pkt[offset:], 0)
  1502. offset += 2
  1503. return h.sender.SendToSession(session, pkt[:offset])
  1504. }
  1505. // sendDeptsListWithCheckcode sends the pre-auth response with proper checkcode for V4 clients.
  1506. // Historically called "departments list" in iserverd (from its Users_Deps database table).
  1507. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4) + DATA
  1508. // From iserverd v3_send_depslist() - this completes the login sequence for V4 clients
  1509. func (h *V4Handler) sendDeptsListWithCheckcode(session *LegacySession, seq2 uint16) error {
  1510. // Packet: header(16) + DEPLIST_VERSION(4) + COUNT(4) + UNKNOWN(4) = 28 bytes
  1511. pkt := make([]byte, 28)
  1512. offset := 0
  1513. // V3 Header
  1514. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  1515. offset += 2
  1516. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvUserDepsList) // 0x0032
  1517. offset += 2
  1518. binary.LittleEndian.PutUint16(pkt[offset:], session.NextServerSeqNum()) // server seq1
  1519. offset += 2
  1520. binary.LittleEndian.PutUint16(pkt[offset:], seq2) // client's seq2
  1521. offset += 2
  1522. binary.LittleEndian.PutUint32(pkt[offset:], session.UIN)
  1523. offset += 4
  1524. // Calculate and add checkcode
  1525. checkcode := h.calculateV4Checkcode(pkt)
  1526. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  1527. offset += 4
  1528. // Pre-auth response data (simplified - empty list)
  1529. // DEPLIST_VERSION(4) + COUNT(4) + UNKNOWN(4)
  1530. binary.LittleEndian.PutUint32(pkt[offset:], 1) // version
  1531. offset += 4
  1532. binary.LittleEndian.PutUint32(pkt[offset:], 0) // count (empty list)
  1533. offset += 4
  1534. binary.LittleEndian.PutUint32(pkt[offset:], 0x002A0002) // unknown fields from iserverd
  1535. offset += 4
  1536. h.logger.Debug("sending V4 depts list (0x0032)",
  1537. "uin", session.UIN,
  1538. "seq2", seq2,
  1539. "hex", fmt.Sprintf("%X", pkt[:offset]),
  1540. )
  1541. return h.sender.SendToSession(session, pkt[:offset])
  1542. }
  1543. // sendDeptsListUnsolicited sends the pre-auth response as an unsolicited notification (seq1=0).
  1544. // Historically called "departments list" in iserverd.
  1545. // This is used during direct login (0x03E8) to set the client's internal flag before
  1546. // sending the login reply. Using seq1=0 ensures the login reply can still get seq1=1.
  1547. //
  1548. // sendRegisterInfo sends registration info (admin notes) to the client
  1549. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4) + DATA
  1550. // From iserverd v3_send_registration_info(): sends 0x037A with admin notes
  1551. func (h *V4Handler) sendRegisterInfo(addr *net.UDPAddr, seq2 uint16, uin uint32) error {
  1552. // Admin notes message - tells the user about registration
  1553. adminNotes := "Welcome to Open OSCAR Server!\nRegistration is enabled.\x00"
  1554. // Build packet
  1555. // Header (16 bytes) + NOTES_LEN(2) + NOTES + REG_ENABLED(1) + UNKNOWN(4)
  1556. pktSize := 16 + 2 + len(adminNotes) + 1 + 4
  1557. pkt := make([]byte, pktSize)
  1558. offset := 0
  1559. // V3 Header
  1560. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  1561. offset += 2
  1562. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvRegisterInfo)
  1563. offset += 2
  1564. binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq1 = 0 for server-initiated
  1565. offset += 2
  1566. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  1567. offset += 2
  1568. binary.LittleEndian.PutUint32(pkt[offset:], uin)
  1569. offset += 4
  1570. // Calculate and add checkcode
  1571. checkcode := h.calculateV4Checkcode(pkt)
  1572. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  1573. offset += 4
  1574. // Admin notes length and string
  1575. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(adminNotes)))
  1576. offset += 2
  1577. copy(pkt[offset:], adminNotes)
  1578. offset += len(adminNotes)
  1579. // Registration enabled flag (1 = enabled)
  1580. pkt[offset] = 0x01
  1581. offset++
  1582. // Unknown fields (from iserverd: 0x0002, 0x002A)
  1583. binary.LittleEndian.PutUint16(pkt[offset:], 0x0002)
  1584. offset += 2
  1585. binary.LittleEndian.PutUint16(pkt[offset:], 0x002A)
  1586. offset += 2
  1587. h.logger.Debug("sending V4 registration info",
  1588. "uin", uin,
  1589. "seq2", seq2,
  1590. "admin_notes_len", len(adminNotes),
  1591. "hex", fmt.Sprintf("%X", pkt[:offset]),
  1592. )
  1593. return h.sender.SendPacket(addr, pkt[:offset])
  1594. }
  1595. // sendRegistrationOK sends registration success with the new UIN
  1596. // V4 client (licq) expects ICQ_CMDxRCV_NEWxUIN (0x0046) - NOT 0x0384.
  1597. // The client reads the new UIN from the header's UIN field (offset 8-11),
  1598. // then calls icqLogon() to start the login sequence.
  1599. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4)
  1600. func (h *V4Handler) sendRegistrationOK(addr *net.UDPAddr, seq2 uint16, newUIN uint32, password string) error {
  1601. pkt := make([]byte, 16)
  1602. offset := 0
  1603. // V3 Header - UIN field contains the NEW UIN
  1604. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  1605. offset += 2
  1606. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvNewUIN) // 0x0046
  1607. offset += 2
  1608. binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq1 = 0 for server-initiated
  1609. offset += 2
  1610. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  1611. offset += 2
  1612. binary.LittleEndian.PutUint32(pkt[offset:], newUIN) // NEW UIN in header
  1613. offset += 4
  1614. // Calculate and add checkcode
  1615. checkcode := h.calculateV4Checkcode(pkt)
  1616. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  1617. offset += 4
  1618. h.logger.Info("sending V4 new UIN (0x0046)",
  1619. "newUIN", newUIN,
  1620. "seq2", seq2,
  1621. "hex", fmt.Sprintf("%X", pkt[:offset]),
  1622. )
  1623. return h.sender.SendPacket(addr, pkt[:offset])
  1624. }
  1625. // sendBasicInfo sends basic user info response
  1626. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4) + DATA
  1627. // Data format: TARGET_UIN(4) + NICK_LEN(2) + NICK + FNAME_LEN(2) + FNAME + LNAME_LEN(2) + LNAME + EMAIL_LEN(2) + EMAIL + AUTH(1)
  1628. func (h *V4Handler) sendBasicInfo(session *LegacySession, seq2 uint16, targetUIN uint32) error {
  1629. nick := fmt.Sprintf("%d", targetUIN)
  1630. // header(16) + TARGET_UIN(4) + nick(2+len+1) + fname(2+1) + lname(2+1) + email(2+1) + auth(1)
  1631. pktSize := 16 + 4 + 2 + len(nick) + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 1
  1632. pkt := make([]byte, pktSize)
  1633. offset := 0
  1634. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  1635. offset += 2
  1636. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvInfoReply) // 0x0118
  1637. offset += 2
  1638. binary.LittleEndian.PutUint16(pkt[offset:], session.NextServerSeqNum())
  1639. offset += 2
  1640. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  1641. offset += 2
  1642. binary.LittleEndian.PutUint32(pkt[offset:], session.UIN)
  1643. offset += 4
  1644. // Calculate checkcode
  1645. checkcode := h.calculateV4Checkcode(pkt)
  1646. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  1647. offset += 4
  1648. // TARGET_UIN - the UIN of the user whose info this is
  1649. binary.LittleEndian.PutUint32(pkt[offset:], targetUIN)
  1650. offset += 4
  1651. // Nick
  1652. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(nick)+1))
  1653. offset += 2
  1654. copy(pkt[offset:], nick)
  1655. offset += len(nick)
  1656. pkt[offset] = 0
  1657. offset++
  1658. // First name (empty)
  1659. binary.LittleEndian.PutUint16(pkt[offset:], 1)
  1660. offset += 2
  1661. pkt[offset] = 0
  1662. offset++
  1663. // Last name (empty)
  1664. binary.LittleEndian.PutUint16(pkt[offset:], 1)
  1665. offset += 2
  1666. pkt[offset] = 0
  1667. offset++
  1668. // Email (empty)
  1669. binary.LittleEndian.PutUint16(pkt[offset:], 1)
  1670. offset += 2
  1671. pkt[offset] = 0
  1672. offset++
  1673. // Auth flag
  1674. pkt[offset] = 0
  1675. offset++
  1676. return h.sender.SendToSession(session, pkt[:offset])
  1677. }
  1678. // sendBasicInfoResponse sends basic user info response (0x0118) with checkcode
  1679. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4) + DATA
  1680. // Data format (verified via Ghidra RE of ICQ98a client handler FUN_00429b22):
  1681. //
  1682. // TARGET_UIN(4) + NICK_LEN(2) + NICK + FNAME_LEN(2) + FNAME + LNAME_LEN(2) + LNAME + EMAIL_LEN(2) + EMAIL + STATUS(1) + AUTH(1)
  1683. //
  1684. // NOTE: Client expects 0x0118 (ICQLegacySrvInfoReply), NOT 0x02E4 - verified via Ghidra RE
  1685. // NOTE: There are TWO bytes at the end - STATUS and AUTH - verified via Ghidra RE of FUN_00429b22
  1686. func (h *V4Handler) sendBasicInfoResponse(session *LegacySession, seq2 uint16, targetUIN uint32) error {
  1687. ctx := context.Background()
  1688. // Try to get actual user info from the service
  1689. nick := fmt.Sprintf("%d", targetUIN)
  1690. firstName := ""
  1691. lastName := ""
  1692. email := ""
  1693. statusByte := byte(0) // Status byte (stored at offset 0xd1 in client user object)
  1694. authMode := byte(0) // Auth mode (stored at offset 0x911 in client user object)
  1695. info, err := h.service.GetUserInfo(ctx, targetUIN)
  1696. if err == nil && info != nil {
  1697. if info.Nickname != "" {
  1698. nick = info.Nickname
  1699. }
  1700. firstName = info.FirstName
  1701. lastName = info.LastName
  1702. email = info.Email
  1703. }
  1704. // Truncate fields to prevent crashing old ICQ clients
  1705. nick = truncateField(nick, 20, h.logger, "nickname", targetUIN)
  1706. firstName = truncateField(firstName, 64, h.logger, "first_name", targetUIN)
  1707. lastName = truncateField(lastName, 64, h.logger, "last_name", targetUIN)
  1708. email = truncateField(email, 64, h.logger, "email", targetUIN)
  1709. // Calculate packet size: header(16) + TARGET_UIN(4) + nick(2+len+1) + fname(2+len+1) + lname(2+len+1) + email(2+len+1) + status(1) + auth(1)
  1710. pktSize := 16 + 4 + (2 + len(nick) + 1) + (2 + len(firstName) + 1) + (2 + len(lastName) + 1) + (2 + len(email) + 1) + 1 + 1
  1711. pkt := make([]byte, pktSize)
  1712. offset := 0
  1713. // V3 Header with checkcode
  1714. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  1715. offset += 2
  1716. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvInfoReply) // 0x0118
  1717. offset += 2
  1718. binary.LittleEndian.PutUint16(pkt[offset:], session.NextServerSeqNum())
  1719. offset += 2
  1720. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  1721. offset += 2
  1722. binary.LittleEndian.PutUint32(pkt[offset:], session.UIN)
  1723. offset += 4
  1724. // Calculate and add checkcode - client validates this!
  1725. checkcode := h.calculateV4Checkcode(pkt)
  1726. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  1727. offset += 4
  1728. // TARGET_UIN - the UIN of the user whose info this is (required by client!)
  1729. binary.LittleEndian.PutUint32(pkt[offset:], targetUIN)
  1730. offset += 4
  1731. // NickName (length + string + null)
  1732. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(nick)+1))
  1733. offset += 2
  1734. copy(pkt[offset:], nick)
  1735. offset += len(nick)
  1736. pkt[offset] = 0
  1737. offset++
  1738. // First name (length + string + null)
  1739. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(firstName)+1))
  1740. offset += 2
  1741. copy(pkt[offset:], firstName)
  1742. offset += len(firstName)
  1743. pkt[offset] = 0
  1744. offset++
  1745. // Last name (length + string + null)
  1746. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(lastName)+1))
  1747. offset += 2
  1748. copy(pkt[offset:], lastName)
  1749. offset += len(lastName)
  1750. pkt[offset] = 0
  1751. offset++
  1752. // Email (length + string + null)
  1753. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(email)+1))
  1754. offset += 2
  1755. copy(pkt[offset:], email)
  1756. offset += len(email)
  1757. pkt[offset] = 0
  1758. offset++
  1759. // Status byte (read first by client, stored at offset 0xd1)
  1760. pkt[offset] = statusByte
  1761. offset++
  1762. // Auth mode (read second by client, stored at offset 0x911)
  1763. pkt[offset] = authMode
  1764. offset++
  1765. h.logger.Debug("sending V4 basic info response",
  1766. "to", session.UIN,
  1767. "target", targetUIN,
  1768. "nick", nick,
  1769. "seq2", seq2,
  1770. "hex", fmt.Sprintf("%X", pkt[:offset]),
  1771. )
  1772. return h.sender.SendToSession(session, pkt[:offset])
  1773. }
  1774. // sendExtInfoResponse sends extended user info response (0x0122) with checkcode
  1775. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4) + DATA
  1776. // Data format (verified via Ghidra RE of ICQ98a client handler FUN_00429c84):
  1777. //
  1778. // TARGET_UIN(4) + CITY_LEN(2) + CITY + COUNTRY(2) + TIMEZONE(1) +
  1779. // STATE_LEN(2) + STATE + AGE(2) + GENDER(1) +
  1780. // PHONE_LEN(2) + PHONE + HOMEPAGE_LEN(2) + HOMEPAGE + ABOUT_LEN(2) + ABOUT + ZIPCODE(4)
  1781. func (h *V4Handler) sendExtInfoResponse(session *LegacySession, seq2 uint16, targetUIN uint32) error {
  1782. ctx := context.Background()
  1783. // Default values
  1784. city := ""
  1785. country := uint16(0)
  1786. timezone := byte(0)
  1787. state := ""
  1788. age := uint16(0)
  1789. gender := byte(0)
  1790. phone := ""
  1791. homepage := ""
  1792. about := ""
  1793. zipcode := uint32(0)
  1794. // Try to get full user info from the service
  1795. user, err := h.service.GetFullUserInfo(ctx, targetUIN)
  1796. if err == nil && user != nil {
  1797. city = user.ICQInfo.Basic.City
  1798. country = user.ICQInfo.Basic.CountryCode
  1799. timezone = user.ICQInfo.Basic.GMTOffset
  1800. state = user.ICQInfo.Basic.State
  1801. phone = user.ICQInfo.Basic.Phone
  1802. homepage = user.ICQInfo.More.HomePageAddr
  1803. about = user.ICQInfo.Notes.Notes
  1804. // Calculate age from birth year
  1805. if user.ICQInfo.More.BirthYear > 0 {
  1806. currentYear := uint16(2026) // Current year
  1807. age = currentYear - user.ICQInfo.More.BirthYear
  1808. }
  1809. // Gender (ICQ uses 1=female, 2=male, 0=unspecified)
  1810. gender = byte(user.ICQInfo.More.Gender)
  1811. // Zip code - stored as string, sent as uint32 in V3/V4 protocol
  1812. if user.ICQInfo.Basic.ZIPCode != "" {
  1813. if z, err := strconv.ParseUint(user.ICQInfo.Basic.ZIPCode, 10, 32); err == nil {
  1814. zipcode = uint32(z)
  1815. }
  1816. }
  1817. }
  1818. // Truncate fields to prevent crashing old ICQ clients
  1819. city = truncateField(city, 64, h.logger, "city", targetUIN)
  1820. state = truncateField(state, 64, h.logger, "state", targetUIN)
  1821. phone = truncateField(phone, 30, h.logger, "phone", targetUIN)
  1822. homepage = truncateField(homepage, 127, h.logger, "homepage", targetUIN)
  1823. about = truncateField(about, 450, h.logger, "about", targetUIN)
  1824. // Calculate packet size: header(16) + TARGET_UIN(4) + city(2+len+1) + country(2) + timezone(1) +
  1825. // state(2+len+1) + age(2) + gender(1) + phone(2+len+1) + homepage(2+len+1) + about(2+len+1) + unknown(4)
  1826. pktSize := 16 + 4 + (2 + len(city) + 1) + 2 + 1 + (2 + len(state) + 1) + 2 + 1 +
  1827. (2 + len(phone) + 1) + (2 + len(homepage) + 1) + (2 + len(about) + 1) + 4
  1828. pkt := make([]byte, pktSize)
  1829. offset := 0
  1830. // V3 Header with checkcode
  1831. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  1832. offset += 2
  1833. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvExtInfoReply) // 0x0122
  1834. offset += 2
  1835. binary.LittleEndian.PutUint16(pkt[offset:], session.NextServerSeqNum())
  1836. offset += 2
  1837. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  1838. offset += 2
  1839. binary.LittleEndian.PutUint32(pkt[offset:], session.UIN)
  1840. offset += 4
  1841. // Calculate and add checkcode - client validates this!
  1842. checkcode := h.calculateV4Checkcode(pkt)
  1843. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  1844. offset += 4
  1845. // TARGET_UIN
  1846. binary.LittleEndian.PutUint32(pkt[offset:], targetUIN)
  1847. offset += 4
  1848. // City (length + string + null)
  1849. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(city)+1))
  1850. offset += 2
  1851. copy(pkt[offset:], city)
  1852. offset += len(city)
  1853. pkt[offset] = 0
  1854. offset++
  1855. // Country code
  1856. binary.LittleEndian.PutUint16(pkt[offset:], country)
  1857. offset += 2
  1858. // Timezone
  1859. pkt[offset] = timezone
  1860. offset++
  1861. // State (length + string + null)
  1862. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(state)+1))
  1863. offset += 2
  1864. copy(pkt[offset:], state)
  1865. offset += len(state)
  1866. pkt[offset] = 0
  1867. offset++
  1868. // Age
  1869. binary.LittleEndian.PutUint16(pkt[offset:], age)
  1870. offset += 2
  1871. // Gender
  1872. pkt[offset] = gender
  1873. offset++
  1874. // Phone (length + string + null)
  1875. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(phone)+1))
  1876. offset += 2
  1877. copy(pkt[offset:], phone)
  1878. offset += len(phone)
  1879. pkt[offset] = 0
  1880. offset++
  1881. // Homepage (length + string + null)
  1882. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(homepage)+1))
  1883. offset += 2
  1884. copy(pkt[offset:], homepage)
  1885. offset += len(homepage)
  1886. pkt[offset] = 0
  1887. offset++
  1888. // About (length + string + null)
  1889. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(about)+1))
  1890. offset += 2
  1891. copy(pkt[offset:], about)
  1892. offset += len(about)
  1893. pkt[offset] = 0
  1894. offset++
  1895. // Zip code (DWORD at end - verified via licq source: packet >> zipcode)
  1896. binary.LittleEndian.PutUint32(pkt[offset:], zipcode)
  1897. offset += 4
  1898. h.logger.Debug("sending V4 ext info response",
  1899. "to", session.UIN,
  1900. "target", targetUIN,
  1901. "seq2", seq2,
  1902. "hex", fmt.Sprintf("%X", pkt[:offset]),
  1903. )
  1904. return h.sender.SendToSession(session, pkt[:offset])
  1905. }
  1906. // sendOfflineMsgDone sends end of offline messages
  1907. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4)
  1908. // seq2 should be the seq2 of the client's offline msg request, or 0 if unsolicited
  1909. func (h *V4Handler) sendOfflineMsgDone(session *LegacySession, clientSeq2 uint16) error {
  1910. pkt := make([]byte, 16)
  1911. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  1912. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvSysMsgDone)
  1913. binary.LittleEndian.PutUint16(pkt[4:6], session.NextServerSeqNum())
  1914. binary.LittleEndian.PutUint16(pkt[6:8], clientSeq2) // seq2 of offline msg request, or 0 if unsolicited
  1915. binary.LittleEndian.PutUint32(pkt[8:12], session.UIN)
  1916. // Calculate and add checkcode
  1917. checkcode := h.calculateV4Checkcode(pkt)
  1918. binary.LittleEndian.PutUint32(pkt[12:16], checkcode)
  1919. h.logger.Debug("sending V4 offline msg done",
  1920. "uin", session.UIN,
  1921. "seq2", clientSeq2,
  1922. "hex", fmt.Sprintf("%X", pkt),
  1923. )
  1924. return h.sender.SendToSession(session, pkt)
  1925. }
  1926. // sendUserStatus sends user status change notification
  1927. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4) + DATA
  1928. // From server.html: User changed his status (command 0x01a4)
  1929. func (h *V4Handler) sendUserStatus(session *LegacySession, uin uint32, status uint32) error {
  1930. pkt := make([]byte, 24)
  1931. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  1932. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvUserStatus)
  1933. binary.LittleEndian.PutUint16(pkt[4:6], session.NextServerSeqNum())
  1934. binary.LittleEndian.PutUint16(pkt[6:8], 0)
  1935. binary.LittleEndian.PutUint32(pkt[8:12], session.UIN)
  1936. // Calculate and add checkcode
  1937. checkcode := h.calculateV4Checkcode(pkt)
  1938. binary.LittleEndian.PutUint32(pkt[12:16], checkcode)
  1939. // User status data
  1940. binary.LittleEndian.PutUint32(pkt[16:20], uin)
  1941. binary.LittleEndian.PutUint32(pkt[20:24], status)
  1942. h.logger.Debug("sending V4 user status",
  1943. "uin", session.UIN,
  1944. "target_uin", uin,
  1945. "status", fmt.Sprintf("0x%08X", status),
  1946. "hex", fmt.Sprintf("%X", pkt),
  1947. )
  1948. return h.sender.SendToSession(session, pkt)
  1949. }
  1950. // sendOnlineMessage sends an online system message to a user
  1951. // V3 server packet format with checkcode
  1952. // Format: header(16) + FROM_UIN(4) + MSG_TYPE(2) + MSG_LEN(2) + MESSAGE
  1953. func (h *V4Handler) sendOnlineMessage(session *LegacySession, fromUIN uint32, msgType uint16, message string, seq2 uint16) error {
  1954. // Calculate packet size
  1955. msgBytes := []byte(message)
  1956. pktSize := 16 + 4 + 2 + 2 + len(msgBytes) + 1
  1957. pkt := make([]byte, pktSize)
  1958. offset := 0
  1959. // Header
  1960. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  1961. offset += 2
  1962. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvSysMsgOnline) // 0x0104
  1963. offset += 2
  1964. binary.LittleEndian.PutUint16(pkt[offset:], session.NextServerSeqNum())
  1965. offset += 2
  1966. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  1967. offset += 2
  1968. binary.LittleEndian.PutUint32(pkt[offset:], session.UIN)
  1969. offset += 4
  1970. // Calculate and add checkcode
  1971. checkcode := h.calculateV4Checkcode(pkt)
  1972. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  1973. offset += 4
  1974. // From UIN
  1975. binary.LittleEndian.PutUint32(pkt[offset:], fromUIN)
  1976. offset += 4
  1977. // Message type (mask high byte for V3 clients)
  1978. binary.LittleEndian.PutUint16(pkt[offset:], msgType&0x00FF)
  1979. offset += 2
  1980. // Message length (including null terminator)
  1981. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(msgBytes)+1))
  1982. offset += 2
  1983. // Message content
  1984. copy(pkt[offset:], msgBytes)
  1985. offset += len(msgBytes)
  1986. pkt[offset] = 0 // null terminator
  1987. offset++
  1988. h.logger.Debug("V4 sending online message",
  1989. "to", session.UIN,
  1990. "from", fromUIN,
  1991. "type", fmt.Sprintf("0x%04X", msgType),
  1992. "msg_len", len(msgBytes),
  1993. )
  1994. return h.sender.SendToSession(session, pkt[:offset])
  1995. }
  1996. // sendUserOffline sends user offline notification with V4 checkcode
  1997. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4) + DATA
  1998. // Data: OFFLINE_UIN(4)
  1999. func (h *V4Handler) sendUserOffline(session *LegacySession, uin uint32) error {
  2000. pkt := make([]byte, 20)
  2001. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  2002. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvUserOffline)
  2003. binary.LittleEndian.PutUint16(pkt[4:6], session.NextServerSeqNum())
  2004. binary.LittleEndian.PutUint16(pkt[6:8], 0)
  2005. binary.LittleEndian.PutUint32(pkt[8:12], session.UIN)
  2006. // Calculate and add checkcode
  2007. checkcode := h.calculateV4Checkcode(pkt)
  2008. binary.LittleEndian.PutUint32(pkt[12:16], checkcode)
  2009. // UIN of the user who went offline
  2010. binary.LittleEndian.PutUint32(pkt[16:20], uin)
  2011. h.logger.Debug("sending V4 user offline notification",
  2012. "to", session.UIN,
  2013. "offline_uin", uin,
  2014. )
  2015. return h.sender.SendToSession(session, pkt)
  2016. }