package main import ( "aim-oscar/aimerror" "aim-oscar/models" "aim-oscar/oscar" "aim-oscar/util" "context" "time" "github.com/pkg/errors" "github.com/uptrace/bun" ) type LocationServices struct { OnlineCh chan *models.User } func (s *LocationServices) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC) (context.Context, error) { session, _ := oscar.SessionFromContext(ctx) switch snac.Header.Subtype { // Client wants to know the limits/permissions for Location services case 0x02: paramsSnac := oscar.NewSNAC(2, 3) paramsSnac.Data.WriteBinary(oscar.NewTLV(1, util.Word(256))) // Max profile length TODO: error if user sends more paramsFlap := oscar.NewFLAP(2) paramsFlap.Data.WriteBinary(paramsSnac) return ctx, session.Send(paramsFlap) // Client set profile/away message case 0x04: user := models.UserFromContext(ctx) if user == nil { return ctx, aimerror.NoUserInSession } tlvs, err := oscar.UnmarshalTLVs(snac.Data.Bytes()) if err != nil { return nil, errors.Wrap(err, "authentication request missing TLVs") } awayMessageTLV := oscar.FindTLV(tlvs, 0x4) if awayMessageTLV != nil { // Away message encoding is set in TLV 0x3 awayMessageMimeTLV := oscar.FindTLV(tlvs, 0x3) if awayMessageMimeTLV == nil { return nil, errors.New("missing away message mime TLV 0x3") } user.AwayMessage = string(awayMessageTLV.Data) user.AwayMessageEncoding = string(awayMessageMimeTLV.Data) } profileTLV := oscar.FindTLV(tlvs, 0x2) if profileTLV != nil { profileMimeTLV := oscar.FindTLV(tlvs, 0x1) if profileMimeTLV == nil { return nil, errors.New("missing away message mime TLV 0x3") } user.Profile = string(profileTLV.Data) user.ProfileEncoding = string(profileMimeTLV.Data) } if user.AwayMessage == "" { user.Status = models.UserStatusOnline } else { user.Status = models.UserStatusAway } if err := user.Update(ctx, db); err != nil { return ctx, errors.Wrap(err, "could not set away message") } s.OnlineCh <- user return models.NewContextWithUser(ctx, user), nil // Client is asking for user information like profile, away message, online state case 0x5: requestType, err := snac.Data.ReadUint16() if err != nil { return ctx, errors.Wrap(err, "missing request type") } requestedUsername, err := snac.Data.ReadLPString() if err != nil { return ctx, errors.Wrap(err, "missing requested username") } requestedUser, err := models.UserByUsername(ctx, db, requestedUsername) if err != nil { return ctx, aimerror.FetchingUser(err, requestedUsername) } respSnac := oscar.NewSNAC(2, 6) respSnac.Data.WriteLPString(requestedUser.Username) respSnac.Data.WriteUint16(0) // TODO: warning level tlvs := []*oscar.TLV{ oscar.NewTLV(1, util.Dword(0x80)), // user class // oscar.NewTLV(6, util.Dword(uint32(requestedUser.Status))), // user status // oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(SRV_HOST)))), // user external IP oscar.NewTLV(0x0f, util.Dword(uint32(time.Since(requestedUser.LastActivityAt).Seconds()))), // idle time oscar.NewTLV(0x03, util.Dword(uint32(time.Now().Unix()))), // TODO: signon time // oscar.NewTLV(0x05, util.Dword(uint32(requestedUser.CreatedAt.Unix()))), // member since } respSnac.Data.WriteUint16(uint16(len(tlvs))) // number of TLVs for _, tlv := range tlvs { respSnac.Data.WriteBinary(tlv) } // General info (Profile) if requestType == 1 { respSnac.Data.WriteBinary(oscar.NewTLV(1, util.LPString(requestedUser.ProfileEncoding))) respSnac.Data.WriteBinary(oscar.NewTLV(2, util.LPString(requestedUser.Profile))) } // Request Type 2 = online status, no TLVs // Away message if requestType == 3 { respSnac.Data.WriteBinary(oscar.NewTLV(3, util.LPString(requestedUser.AwayMessageEncoding))) respSnac.Data.WriteBinary(oscar.NewTLV(4, util.LPString(requestedUser.AwayMessage))) } // TODO: Request Type 4 - User capabilities respFlap := oscar.NewFLAP(2) respFlap.Data.WriteBinary(respSnac) return ctx, session.Send(respFlap) case 0xb: /* Nobody seems to know what this client request is for - http://iserverd.khstu.ru/oscar/snac_02_0b.html - https://bugs.bitlbee.org/browser/protocols/oscar/info.c?rev=b7d3cc34f68dab7b8f7d0777711317b334fc2219#L572 But the one dump that exists looks like a TLV 0x1 with empty data */ unknownSnac := oscar.NewSNAC(2, 0xc) unknownSnac.Data.WriteUint16(1) unknownSnac.Data.WriteUint16(0) unknownFlap := oscar.NewFLAP(2) unknownFlap.Data.WriteBinary(unknownSnac) return ctx, session.Send(unknownFlap) } return ctx, nil }