2021-12-24 12:41:16 -05:00
|
|
|
package services
|
2021-12-18 16:48:13 -05:00
|
|
|
|
|
|
|
import (
|
2021-12-19 01:14:25 -05:00
|
|
|
"aim-oscar/aimerror"
|
2021-12-18 21:02:47 -05:00
|
|
|
"aim-oscar/models"
|
2021-12-18 16:48:13 -05:00
|
|
|
"aim-oscar/oscar"
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/binary"
|
2021-12-18 21:02:47 -05:00
|
|
|
"log"
|
2021-12-18 16:48:13 -05:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/uptrace/bun"
|
|
|
|
)
|
|
|
|
|
2021-12-19 01:14:25 -05:00
|
|
|
type ICBM struct {
|
|
|
|
CommCh chan *models.Message
|
|
|
|
}
|
2021-12-18 16:48:13 -05:00
|
|
|
|
|
|
|
type icbmKey string
|
|
|
|
|
|
|
|
func (s icbmKey) String() string {
|
|
|
|
return "icbm-" + string(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
channelKey = icbmKey("channel")
|
|
|
|
)
|
|
|
|
|
|
|
|
func NewContextWithChannel(ctx context.Context, c *channel) context.Context {
|
|
|
|
return context.WithValue(ctx, channelKey, c)
|
|
|
|
}
|
|
|
|
|
|
|
|
func ChannelFromContext(ctx context.Context) *channel {
|
|
|
|
s := ctx.Value(channelKey)
|
|
|
|
if s == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return s.(*channel)
|
|
|
|
}
|
|
|
|
|
|
|
|
type channel struct {
|
|
|
|
ID uint16
|
|
|
|
MessageFlags uint32
|
|
|
|
MaxMessageSnacSize uint16
|
|
|
|
MaxSenderWarningLevel uint16
|
|
|
|
MaxReceiverWarningLevel uint16
|
|
|
|
MinimumMessageInterval uint16
|
|
|
|
Unknown uint16
|
|
|
|
}
|
|
|
|
|
2021-12-19 01:14:25 -05:00
|
|
|
func (icbm *ICBM) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC) (context.Context, error) {
|
2021-12-18 16:48:13 -05:00
|
|
|
session, _ := oscar.SessionFromContext(ctx)
|
|
|
|
|
|
|
|
switch snac.Header.Subtype {
|
|
|
|
// Client is telling us about their ICBM capabilities
|
|
|
|
case 0x02:
|
|
|
|
/*
|
|
|
|
xx xx word channel to setup
|
|
|
|
xx xx xx xx dword message flags
|
|
|
|
xx xx word max message snac size
|
|
|
|
xx xx word max sender warning level
|
|
|
|
xx xx word max receiver warning level
|
|
|
|
xx xx word minimum message interval (sec)
|
|
|
|
00 00 word unknown parameter (also seen 03 E8)
|
|
|
|
*/
|
|
|
|
|
|
|
|
channel := channel{}
|
|
|
|
r := bytes.NewReader(snac.Data.Bytes())
|
|
|
|
if err := binary.Read(r, binary.BigEndian, &channel); err != nil {
|
|
|
|
return ctx, errors.Wrap(err, "could not read channel settings")
|
|
|
|
}
|
|
|
|
|
|
|
|
newCtx := NewContextWithChannel(ctx, &channel)
|
|
|
|
return newCtx, nil
|
|
|
|
|
|
|
|
// Client asks about the ICBM capabilities we set for them
|
|
|
|
case 0x04:
|
|
|
|
channel := ChannelFromContext(ctx)
|
|
|
|
channelSnac := oscar.NewSNAC(4, 5)
|
|
|
|
channelSnac.Data.WriteUint16(uint16(channel.ID))
|
|
|
|
channelSnac.Data.WriteUint32(channel.MessageFlags)
|
|
|
|
channelSnac.Data.WriteUint16(channel.MaxMessageSnacSize)
|
|
|
|
channelSnac.Data.WriteUint16(channel.MaxSenderWarningLevel)
|
|
|
|
channelSnac.Data.WriteUint16(channel.MaxReceiverWarningLevel)
|
|
|
|
channelSnac.Data.WriteUint16(channel.MinimumMessageInterval)
|
|
|
|
channelSnac.Data.WriteUint16(channel.Unknown)
|
|
|
|
|
|
|
|
channelFlap := oscar.NewFLAP(2)
|
|
|
|
channelFlap.Data.WriteBinary(channelSnac)
|
|
|
|
session.Send(channelFlap)
|
|
|
|
|
2021-12-18 21:02:47 -05:00
|
|
|
return ctx, nil
|
|
|
|
|
|
|
|
// Client wants to send a message to someone through the server
|
|
|
|
case 0x06:
|
|
|
|
user := models.UserFromContext(ctx)
|
|
|
|
if user == nil {
|
2021-12-19 01:14:25 -05:00
|
|
|
return ctx, aimerror.NoUserInSession
|
2021-12-18 21:02:47 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
msgID, _ := snac.Data.ReadUint64()
|
|
|
|
msgChannel, _ := snac.Data.ReadUint16()
|
|
|
|
to, _ := snac.Data.ReadLPString()
|
|
|
|
|
|
|
|
if msgChannel != 1 {
|
|
|
|
log.Printf("Message for unsupported channel %d", msgChannel)
|
|
|
|
return ctx, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
tlvs, err := oscar.UnmarshalTLVs(snac.Data.Bytes())
|
|
|
|
if err != nil {
|
|
|
|
return ctx, errors.Wrap(err, "could not unmarshal message tlvs")
|
|
|
|
}
|
|
|
|
|
|
|
|
messageTLV := oscar.FindTLV(tlvs, 0x2)
|
|
|
|
if messageTLV == nil {
|
|
|
|
return ctx, errors.New("missing messageTLV 0x2")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse fragment (array of required capabilities, yawn)
|
|
|
|
messageTLVData := oscar.Buffer{}
|
|
|
|
messageTLVData.Write(messageTLV.Data)
|
|
|
|
|
|
|
|
fragmentNum, err := messageTLVData.ReadUint8()
|
|
|
|
if err != nil {
|
|
|
|
return ctx, errors.Wrap(err, "could not read fragment identifier")
|
|
|
|
} else if fragmentNum != 5 {
|
|
|
|
return ctx, errors.New("expected first fragment identifier to be 5")
|
|
|
|
}
|
|
|
|
|
|
|
|
fragmentVersion, err := messageTLVData.ReadUint8()
|
|
|
|
if err != nil {
|
|
|
|
return ctx, errors.Wrap(err, "could not read fragment version")
|
|
|
|
} else if fragmentVersion != 1 {
|
|
|
|
return ctx, errors.New("expected first fragment version to be 1")
|
|
|
|
}
|
|
|
|
|
|
|
|
fragmentLength, err := messageTLVData.ReadUint16()
|
|
|
|
if err != nil {
|
|
|
|
return ctx, errors.Wrap(err, "could not read fragment data length")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip over all the capabilities
|
|
|
|
messageTLVData.Seek(int(fragmentLength))
|
|
|
|
|
|
|
|
// This should be the start of the message contents fragment
|
|
|
|
fragmentNum, err = messageTLVData.ReadUint8()
|
|
|
|
if err != nil {
|
|
|
|
return ctx, errors.Wrap(err, "could not read fragment identifier")
|
|
|
|
} else if fragmentNum != 1 {
|
|
|
|
return ctx, errors.New("expected second fragment identifier to be 1")
|
|
|
|
}
|
|
|
|
|
|
|
|
fragmentVersion, err = messageTLVData.ReadUint8()
|
|
|
|
if err != nil {
|
|
|
|
return ctx, errors.Wrap(err, "could not read fragment version")
|
|
|
|
} else if fragmentVersion != 1 {
|
|
|
|
return ctx, errors.New("expected second fragment version to be 1")
|
|
|
|
}
|
|
|
|
|
|
|
|
fragmentLength, err = messageTLVData.ReadUint16()
|
|
|
|
if err != nil {
|
|
|
|
return ctx, errors.Wrap(err, "could not read second fragment data length")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip over the charset + language
|
|
|
|
messageTLVData.Seek(4)
|
|
|
|
|
|
|
|
messageContents := make([]byte, fragmentLength-4)
|
|
|
|
n, err := messageTLVData.Read(messageContents)
|
|
|
|
if err != nil {
|
|
|
|
return ctx, errors.Wrap(err, "could not read message contents from fragment")
|
|
|
|
}
|
|
|
|
if n < int(fragmentLength)-4 {
|
|
|
|
return ctx, errors.New("read insufficient data from message fragment")
|
|
|
|
}
|
|
|
|
|
2021-12-26 21:56:59 -05:00
|
|
|
var message *models.Message
|
|
|
|
|
|
|
|
// TLV 0x6 is the client telling the server to store the message if the recipient is offline
|
|
|
|
saveofflineTLV := oscar.FindTLV(tlvs, 6)
|
|
|
|
if saveofflineTLV != nil {
|
2022-01-16 14:43:48 -05:00
|
|
|
message, err = models.InsertMessage(ctx, db, msgID, user.ScreenName, to, string(messageContents))
|
2021-12-26 21:56:59 -05:00
|
|
|
if err != nil {
|
|
|
|
return ctx, errors.Wrap(err, "could not insert message")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
message = &models.Message{
|
|
|
|
Cookie: msgID,
|
2022-01-16 14:43:48 -05:00
|
|
|
From: user.ScreenName,
|
2021-12-26 21:56:59 -05:00
|
|
|
To: to,
|
|
|
|
Contents: string(messageContents),
|
|
|
|
}
|
2021-12-18 21:02:47 -05:00
|
|
|
}
|
|
|
|
|
2021-12-18 22:25:05 -05:00
|
|
|
// Fire the message off into the communication channel to get delivered
|
2021-12-19 01:14:25 -05:00
|
|
|
icbm.CommCh <- message
|
2021-12-18 22:25:05 -05:00
|
|
|
|
2021-12-18 21:02:47 -05:00
|
|
|
// The Client usually wants a response that the server got the message. It checks that the message
|
|
|
|
// back has the same message ID that was sent and the user it was sent to.
|
|
|
|
ackTLV := oscar.FindTLV(tlvs, 3)
|
|
|
|
if ackTLV != nil {
|
|
|
|
ackSnac := oscar.NewSNAC(4, 0xc)
|
|
|
|
ackSnac.Data.WriteUint64(msgID)
|
|
|
|
ackSnac.Data.WriteUint16(2)
|
2022-01-16 14:43:48 -05:00
|
|
|
ackSnac.Data.WriteLPString(user.ScreenName)
|
2021-12-18 21:02:47 -05:00
|
|
|
ackFlap := oscar.NewFLAP(2)
|
|
|
|
ackFlap.Data.WriteBinary(ackSnac)
|
|
|
|
return ctx, session.Send(ackFlap)
|
|
|
|
}
|
|
|
|
|
2021-12-18 16:48:13 -05:00
|
|
|
return ctx, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return ctx, nil
|
|
|
|
}
|