store messages sent through ICBM

This commit is contained in:
Artem Titoulenko 2021-12-18 21:02:47 -05:00
parent ed375e315c
commit 3e69ba3e4c
9 changed files with 280 additions and 8 deletions

View file

@ -6,7 +6,6 @@ import (
"aim-oscar/util"
"context"
"encoding/binary"
"fmt"
"time"
"github.com/pkg/errors"
@ -73,9 +72,8 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna
}
onlineSnac := oscar.NewSNAC(1, 0xf)
uin := fmt.Sprint(user.UIN)
onlineSnac.Data.WriteUint8(uint8(len(uin)))
onlineSnac.Data.WriteString(uin)
onlineSnac.Data.WriteUint8(uint8(len(user.Username)))
onlineSnac.Data.WriteString(user.Username)
onlineSnac.Data.WriteUint16(0) // warning level
user.Status = "active"

View file

@ -1,10 +1,12 @@
package main
import (
"aim-oscar/models"
"aim-oscar/oscar"
"bytes"
"context"
"encoding/binary"
"log"
"github.com/pkg/errors"
"github.com/uptrace/bun"
@ -85,6 +87,109 @@ func (icbm *ICBM) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC)
channelFlap.Data.WriteBinary(channelSnac)
session.Send(channelFlap)
return ctx, nil
// Client wants to send a message to someone through the server
case 0x06:
user := models.UserFromContext(ctx)
if user == nil {
return ctx, errors.New("context should have User")
}
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")
}
if err = models.InsertMessage(ctx, db, msgID, user.Username, to, string(messageContents)); err != nil {
return ctx, errors.Wrap(err, "could not insert message")
}
// 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)
ackSnac.Data.WriteLPString(user.Username)
ackFlap := oscar.NewFLAP(2)
ackFlap.Data.WriteBinary(ackSnac)
return ctx, session.Send(ackFlap)
}
return ctx, nil
}

View file

@ -22,7 +22,7 @@ const CIPHER_LENGTH = 64
const AIM_MD5_STRING = "AOL Instant Messenger (SM)"
type AuthorizationCookie struct {
UIN int
UIN int64
X string
}

View file

@ -53,7 +53,7 @@ func main() {
db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))
// Register our DB models
db.RegisterModel((*models.User)(nil))
db.RegisterModel((*models.User)(nil), (*models.Message)(nil))
// dev: load in fixtures to test against
fixture := dbfixture.New(db, dbfixture.WithRecreateTables())

33
models/Message.go Normal file
View file

@ -0,0 +1,33 @@
package models
import (
"context"
"time"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
type Message struct {
bun.BaseModel `bun:"table:messages"`
MessageID uint64 `bun:",pk,notnull,unique"`
From string
To string
Contents string
CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
DeliveredAt time.Time `bun:",nullzero"`
}
func InsertMessage(ctx context.Context, db *bun.DB, messageId uint64, from string, to string, contents string) error {
msg := &Message{
MessageID: messageId,
From: from,
To: to,
Contents: contents,
}
if _, err := db.NewInsert().Model(msg).Exec(ctx); err != nil {
return errors.Wrap(err, "could not update user")
}
return nil
}

View file

@ -11,7 +11,7 @@ import (
type User struct {
bun.BaseModel `bun:"table:users"`
UIN int `bun:",pk,autoincrement"`
UIN int64 `bun:",pk,autoincrement"`
Email string `bun:",unique"`
Username string `bun:",unique"`
Password string
@ -43,7 +43,7 @@ func UserByUsername(ctx context.Context, db *bun.DB, username string) (*User, er
return user, nil
}
func UserByUIN(ctx context.Context, db *bun.DB, uin int) (*User, error) {
func UserByUIN(ctx context.Context, db *bun.DB, uin int64) (*User, error) {
user := new(User)
if err := db.NewSelect().Model(user).Where("uin = ?", uin).Scan(ctx, user); err != nil {
if err == sql.ErrNoRows {

View file

@ -3,3 +3,5 @@
- username: toof
password: bar
email: toof@plot.club
- model: Message
rows: []

View file

@ -4,12 +4,86 @@ import (
"aim-oscar/util"
"encoding"
"encoding/binary"
"io"
)
// Buffer is a handy byte slice that reads from the front and writes on the end
type Buffer struct {
d []byte
}
// Seek moves the read cursor forward. If the cursor is beyond the length of the byte
// slice then the buffer slice is just replaced with an empty slice
func (b *Buffer) Seek(n int) {
if n > len(b.d) {
b.d = make([]byte, 0)
return
}
b.d = b.d[n:]
}
func (b *Buffer) Read(d []byte) (int, error) {
if len(d) > len(b.d) {
return 0, io.EOF
}
n := copy(d, b.d[0:len(d)])
return n, nil
}
func (b *Buffer) ReadUint8() (uint8, error) {
if len(b.d) < 1 {
return 0, io.EOF
}
ret := uint8(b.d[0])
b.d = b.d[1:]
return ret, nil
}
func (b *Buffer) ReadUint16() (uint16, error) {
if len(b.d) < 2 {
return 0, io.EOF
}
ret := binary.BigEndian.Uint16(b.d[0:2])
b.d = b.d[2:]
return ret, nil
}
func (b *Buffer) ReadUint32() (uint32, error) {
if len(b.d) < 4 {
return 0, io.EOF
}
ret := binary.BigEndian.Uint32(b.d[0:4])
b.d = b.d[4:]
return ret, nil
}
func (b *Buffer) ReadUint64() (uint64, error) {
if len(b.d) < 8 {
return 0, io.EOF
}
ret := binary.BigEndian.Uint64(b.d[0:8])
b.d = b.d[8:]
return ret, nil
}
// ReadLPString reads a length-prefixed string. The first byte should be the string length
// followed by that many bytes. Returns io.EOF if there are less bytes than indicated.
func (b *Buffer) ReadLPString() (string, error) {
length, err := b.ReadUint8()
if err != nil {
return "", nil
}
if len(b.d) < int(length) {
return "", io.EOF
}
str := string(b.d[:length])
b.d = b.d[length:]
return str, nil
}
func (b *Buffer) WriteUint8(x uint8) {
b.d = append(b.d, x)
}
@ -33,6 +107,11 @@ func (b *Buffer) WriteString(x string) {
b.d = append(b.d, []byte(x)...)
}
func (b *Buffer) WriteLPString(x string) {
b.WriteUint8(uint8(len(x)))
b.WriteString(x)
}
func (b *Buffer) Write(x []byte) (int, error) {
b.d = append(b.d, x...)
return len(x), nil

55
oscar/buf_test.go Normal file
View file

@ -0,0 +1,55 @@
package oscar
import "testing"
func fail(t *testing.T, e error, method string) {
if e != nil {
t.Errorf("invalid read from %s: %s", method, e.Error())
}
}
func TestBuffer(t *testing.T) {
b := Buffer{}
b.WriteUint8(uint8(1))
b.WriteUint16(uint16(2))
b.WriteUint32(uint32(3))
b.WriteUint64(uint64(4))
x1, err := b.ReadUint8()
fail(t, err, "ReadUint8")
if x1 != 1 {
t.Errorf("expected ReadUint8 to read 1, got %d", x1)
}
x2, err := b.ReadUint16()
fail(t, err, "ReadUint16")
if x2 != 2 {
t.Errorf("expected ReadUint16 to read 2, got %d", x2)
}
x3, err := b.ReadUint32()
fail(t, err, "ReadUint32")
if x3 != 3 {
t.Errorf("expected ReadUint32 to read 3, got %d", x3)
}
x4, err := b.ReadUint64()
fail(t, err, "ReadUint64")
if x4 != 4 {
t.Errorf("expected ReadUint64 to read 4, got %d", x4)
}
}
func TestBufferLPString(t *testing.T) {
b := Buffer{}
expectedStr := "This is a long string"
b.WriteLPString(expectedStr)
str, err := b.ReadLPString()
fail(t, err, "ReadLPString")
if str != expectedStr {
t.Errorf("expected to read %s, got %s", expectedStr, str)
}
}