mirror of
https://github.com/amigan/aim-oscar-server.git
synced 2025-02-28 10:22:35 -05:00
store messages sent through ICBM
This commit is contained in:
parent
ed375e315c
commit
3e69ba3e4c
9 changed files with 280 additions and 8 deletions
|
@ -6,7 +6,6 @@ import (
|
||||||
"aim-oscar/util"
|
"aim-oscar/util"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -73,9 +72,8 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna
|
||||||
}
|
}
|
||||||
|
|
||||||
onlineSnac := oscar.NewSNAC(1, 0xf)
|
onlineSnac := oscar.NewSNAC(1, 0xf)
|
||||||
uin := fmt.Sprint(user.UIN)
|
onlineSnac.Data.WriteUint8(uint8(len(user.Username)))
|
||||||
onlineSnac.Data.WriteUint8(uint8(len(uin)))
|
onlineSnac.Data.WriteString(user.Username)
|
||||||
onlineSnac.Data.WriteString(uin)
|
|
||||||
onlineSnac.Data.WriteUint16(0) // warning level
|
onlineSnac.Data.WriteUint16(0) // warning level
|
||||||
|
|
||||||
user.Status = "active"
|
user.Status = "active"
|
||||||
|
|
105
0x04_ICBM.go
105
0x04_ICBM.go
|
@ -1,10 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"aim-oscar/models"
|
||||||
"aim-oscar/oscar"
|
"aim-oscar/oscar"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/uptrace/bun"
|
"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)
|
channelFlap.Data.WriteBinary(channelSnac)
|
||||||
session.Send(channelFlap)
|
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
|
return ctx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ const CIPHER_LENGTH = 64
|
||||||
const AIM_MD5_STRING = "AOL Instant Messenger (SM)"
|
const AIM_MD5_STRING = "AOL Instant Messenger (SM)"
|
||||||
|
|
||||||
type AuthorizationCookie struct {
|
type AuthorizationCookie struct {
|
||||||
UIN int
|
UIN int64
|
||||||
X string
|
X string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
main.go
2
main.go
|
@ -53,7 +53,7 @@ func main() {
|
||||||
db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))
|
db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))
|
||||||
|
|
||||||
// Register our DB models
|
// Register our DB models
|
||||||
db.RegisterModel((*models.User)(nil))
|
db.RegisterModel((*models.User)(nil), (*models.Message)(nil))
|
||||||
|
|
||||||
// dev: load in fixtures to test against
|
// dev: load in fixtures to test against
|
||||||
fixture := dbfixture.New(db, dbfixture.WithRecreateTables())
|
fixture := dbfixture.New(db, dbfixture.WithRecreateTables())
|
||||||
|
|
33
models/Message.go
Normal file
33
models/Message.go
Normal 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
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
bun.BaseModel `bun:"table:users"`
|
bun.BaseModel `bun:"table:users"`
|
||||||
UIN int `bun:",pk,autoincrement"`
|
UIN int64 `bun:",pk,autoincrement"`
|
||||||
Email string `bun:",unique"`
|
Email string `bun:",unique"`
|
||||||
Username string `bun:",unique"`
|
Username string `bun:",unique"`
|
||||||
Password string
|
Password string
|
||||||
|
@ -43,7 +43,7 @@ func UserByUsername(ctx context.Context, db *bun.DB, username string) (*User, er
|
||||||
return user, nil
|
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)
|
user := new(User)
|
||||||
if err := db.NewSelect().Model(user).Where("uin = ?", uin).Scan(ctx, user); err != nil {
|
if err := db.NewSelect().Model(user).Where("uin = ?", uin).Scan(ctx, user); err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
|
|
|
@ -3,3 +3,5 @@
|
||||||
- username: toof
|
- username: toof
|
||||||
password: bar
|
password: bar
|
||||||
email: toof@plot.club
|
email: toof@plot.club
|
||||||
|
- model: Message
|
||||||
|
rows: []
|
||||||
|
|
79
oscar/buf.go
79
oscar/buf.go
|
@ -4,12 +4,86 @@ import (
|
||||||
"aim-oscar/util"
|
"aim-oscar/util"
|
||||||
"encoding"
|
"encoding"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Buffer is a handy byte slice that reads from the front and writes on the end
|
||||||
type Buffer struct {
|
type Buffer struct {
|
||||||
d []byte
|
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) {
|
func (b *Buffer) WriteUint8(x uint8) {
|
||||||
b.d = append(b.d, x)
|
b.d = append(b.d, x)
|
||||||
}
|
}
|
||||||
|
@ -33,6 +107,11 @@ func (b *Buffer) WriteString(x string) {
|
||||||
b.d = append(b.d, []byte(x)...)
|
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) {
|
func (b *Buffer) Write(x []byte) (int, error) {
|
||||||
b.d = append(b.d, x...)
|
b.d = append(b.d, x...)
|
||||||
return len(x), nil
|
return len(x), nil
|
||||||
|
|
55
oscar/buf_test.go
Normal file
55
oscar/buf_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue