mirror of
https://github.com/amigan/aim-oscar-server.git
synced 2024-11-21 12:09:48 -05:00
services are set up, client shows buddy list
This commit is contained in:
parent
197e5a2f62
commit
06d3b84017
10 changed files with 379 additions and 47 deletions
113
0x01_generic_service_controls.go
Normal file
113
0x01_generic_service_controls.go
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"aim-oscar/models"
|
||||||
|
"aim-oscar/oscar"
|
||||||
|
"aim-oscar/util"
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
)
|
||||||
|
|
||||||
|
var versions map[uint16]uint16
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
versions = make(map[uint16]uint16)
|
||||||
|
versions[1] = 3
|
||||||
|
versions[4] = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenericServiceControls struct{}
|
||||||
|
|
||||||
|
func (g *GenericServiceControls) 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 rate limits for all services
|
||||||
|
case 0x06:
|
||||||
|
rateSnac := oscar.NewSNAC(1, 7)
|
||||||
|
rateSnac.Data.WriteUint16(1) // one rate class
|
||||||
|
|
||||||
|
// Define a Rate Class
|
||||||
|
rc := oscar.Buffer{}
|
||||||
|
rc.WriteUint16(1) // ID
|
||||||
|
rc.WriteUint32(80) // Window Size
|
||||||
|
rc.WriteUint32(2500) // Clear level
|
||||||
|
rc.WriteUint32(2000) // Alert level
|
||||||
|
rc.WriteUint32(1500) // Limit level
|
||||||
|
rc.WriteUint32(800) // Disconnect level
|
||||||
|
rc.WriteUint32(3400) // Current level (fake)
|
||||||
|
rc.WriteUint32(6000) // Max level
|
||||||
|
rc.WriteUint32(0) // Last time ?
|
||||||
|
rc.WriteUint8(0) // Current state ?
|
||||||
|
rateSnac.Data.Write(rc.Bytes())
|
||||||
|
|
||||||
|
// Define a Rate Group
|
||||||
|
rg := oscar.Buffer{}
|
||||||
|
rg.WriteUint16(1) // ID
|
||||||
|
|
||||||
|
// TODO: make actual rate groups instead of this hack. I can't tell which subtypes are supported so
|
||||||
|
// make it set rate limits for everything family for all subtypes under 0x21.
|
||||||
|
rg.WriteUint16(2 * 0x21) // Number of rate groups
|
||||||
|
for family := range versions {
|
||||||
|
for subtype := 0; subtype < 0x21; subtype++ {
|
||||||
|
rg.WriteUint16(family)
|
||||||
|
rg.WriteUint16(uint16(subtype))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rateSnac.Data.Write(rg.Bytes())
|
||||||
|
|
||||||
|
rateFlap := oscar.NewFLAP(2)
|
||||||
|
rateFlap.Data.WriteBinary(rateSnac)
|
||||||
|
return ctx, session.Send(rateFlap)
|
||||||
|
|
||||||
|
// Client wants their own online information
|
||||||
|
case 0x0e:
|
||||||
|
user := models.UserFromContext(ctx)
|
||||||
|
if user == nil {
|
||||||
|
return ctx, errors.New("expecting user in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
onlineSnac := oscar.NewSNAC(1, 0xf)
|
||||||
|
uin := fmt.Sprint(user.UIN)
|
||||||
|
onlineSnac.Data.WriteUint8(uint8(len(uin)))
|
||||||
|
onlineSnac.Data.WriteString(uin)
|
||||||
|
onlineSnac.Data.WriteUint16(0) // warning level
|
||||||
|
|
||||||
|
tlvs := []*oscar.TLV{
|
||||||
|
oscar.NewTLV(0x01, util.Dword(0x80)), // User Class
|
||||||
|
oscar.NewTLV(0x06, util.Dword(0x0001|0x0100)), // User Status (TODO: update status in DB)
|
||||||
|
oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(SRV_HOST)))), // External IP
|
||||||
|
oscar.NewTLV(0x0f, util.Dword(0x0)), // Idle Time (TODO: track idle time)
|
||||||
|
oscar.NewTLV(0x03, util.Dword(uint32(time.Now().Unix()))), // Client Signon Time
|
||||||
|
oscar.NewTLV(0x01e, util.Dword(0x0)), // Unknown value
|
||||||
|
oscar.NewTLV(0x05, util.Dword(uint32(time.Now().Unix()))), // Member since
|
||||||
|
}
|
||||||
|
|
||||||
|
onlineSnac.Data.WriteUint16(uint16(len(tlvs)))
|
||||||
|
for _, tlv := range tlvs {
|
||||||
|
onlineSnac.Data.WriteBinary(tlv)
|
||||||
|
}
|
||||||
|
|
||||||
|
onlineFlap := oscar.NewFLAP(2)
|
||||||
|
onlineFlap.Data.WriteBinary(onlineSnac)
|
||||||
|
return ctx, session.Send(onlineFlap)
|
||||||
|
|
||||||
|
// Client wants to know the versions of all of the services offered
|
||||||
|
case 0x17:
|
||||||
|
versionsSnac := oscar.NewSNAC(1, 0x18)
|
||||||
|
for family, version := range versions {
|
||||||
|
versionsSnac.Data.WriteUint16(family)
|
||||||
|
versionsSnac.Data.WriteUint16(version)
|
||||||
|
}
|
||||||
|
versionsFlap := oscar.NewFLAP(2)
|
||||||
|
versionsFlap.Data.WriteBinary(versionsSnac)
|
||||||
|
return ctx, session.Send(versionsFlap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx, nil
|
||||||
|
}
|
92
0x04_ICBM.go
Normal file
92
0x04_ICBM.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"aim-oscar/oscar"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ICBM struct{}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (icbm *ICBM) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC) (context.Context, error) {
|
||||||
|
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)
|
||||||
|
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx, nil
|
||||||
|
}
|
|
@ -21,7 +21,48 @@ import (
|
||||||
const CIPHER_LENGTH = 64
|
const CIPHER_LENGTH = 64
|
||||||
const AIM_MD5_STRING = "AOL Instant Messenger (SM)"
|
const AIM_MD5_STRING = "AOL Instant Messenger (SM)"
|
||||||
|
|
||||||
type AuthorizationRegistrationService struct {
|
type AuthorizationCookie struct {
|
||||||
|
UIN int
|
||||||
|
X string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthorizationRegistrationService struct{}
|
||||||
|
|
||||||
|
func AuthenticateFLAPCookie(ctx context.Context, db *bun.DB, flap *oscar.FLAP) (*models.User, error) {
|
||||||
|
// Otherwise this is a protocol negotiation from the client. They're likely trying to connect
|
||||||
|
// and sending a cookie to verify who they are.
|
||||||
|
tlvs, err := oscar.UnmarshalTLVs(flap.Data.Bytes()[4:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "authentication request missing TLVs")
|
||||||
|
}
|
||||||
|
|
||||||
|
cookieTLV := oscar.FindTLV(tlvs, 0x6)
|
||||||
|
if cookieTLV == nil {
|
||||||
|
return nil, errors.New("authentication request missing Cookie TLV 0x6")
|
||||||
|
}
|
||||||
|
|
||||||
|
auth := AuthorizationCookie{}
|
||||||
|
if err := json.Unmarshal(cookieTLV.Data, &auth); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not unmarshal cookie")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := models.UserByUIN(ctx, db, auth.UIN)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not get User by UIN")
|
||||||
|
}
|
||||||
|
|
||||||
|
h := md5.New()
|
||||||
|
io.WriteString(h, user.Cipher)
|
||||||
|
io.WriteString(h, user.Password)
|
||||||
|
io.WriteString(h, AIM_MD5_STRING)
|
||||||
|
expectedPasswordHash := fmt.Sprintf("%x", h.Sum(nil))
|
||||||
|
|
||||||
|
// Make sure the hash passed in matches the one from the DB
|
||||||
|
if expectedPasswordHash != auth.X {
|
||||||
|
return nil, errors.New("unexpected cookie hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthorizationRegistrationService) GenerateCipher() string {
|
func (a *AuthorizationRegistrationService) GenerateCipher() string {
|
||||||
|
@ -33,7 +74,12 @@ func (a *AuthorizationRegistrationService) GenerateCipher() string {
|
||||||
return base32.StdEncoding.EncodeToString(randomBytes)[:CIPHER_LENGTH]
|
return base32.StdEncoding.EncodeToString(randomBytes)[:CIPHER_LENGTH]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar.Session, snac *oscar.SNAC) error {
|
func (a *AuthorizationRegistrationService) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC) (context.Context, error) {
|
||||||
|
session, err := oscar.SessionFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
util.PanicIfError(err)
|
||||||
|
}
|
||||||
|
|
||||||
switch snac.Header.Subtype {
|
switch snac.Header.Subtype {
|
||||||
// Request MD5 Auth Key
|
// Request MD5 Auth Key
|
||||||
case 0x06:
|
case 0x06:
|
||||||
|
@ -42,14 +88,13 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
|
||||||
|
|
||||||
usernameTLV := oscar.FindTLV(tlvs, 1)
|
usernameTLV := oscar.FindTLV(tlvs, 1)
|
||||||
if usernameTLV == nil {
|
if usernameTLV == nil {
|
||||||
return errors.New("missing username TLV")
|
return ctx, errors.New("missing username TLV")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the user
|
// Fetch the user
|
||||||
ctx := context.Background()
|
|
||||||
user, err := models.UserByUsername(ctx, db, string(usernameTLV.Data))
|
user, err := models.UserByUsername(ctx, db, string(usernameTLV.Data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return ctx, err
|
||||||
}
|
}
|
||||||
if user == nil {
|
if user == nil {
|
||||||
snac := oscar.NewSNAC(0x17, 0x03)
|
snac := oscar.NewSNAC(0x17, 0x03)
|
||||||
|
@ -57,13 +102,13 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
|
||||||
snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4}))
|
snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4}))
|
||||||
resp := oscar.NewFLAP(2)
|
resp := oscar.NewFLAP(2)
|
||||||
resp.Data.WriteBinary(snac)
|
resp.Data.WriteBinary(snac)
|
||||||
return session.Send(resp)
|
return ctx, session.Send(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create cipher for this user
|
// Create cipher for this user
|
||||||
user.Cipher = a.GenerateCipher()
|
user.Cipher = a.GenerateCipher()
|
||||||
if err = user.Update(ctx, db); err != nil {
|
if err = user.Update(ctx, db); err != nil {
|
||||||
return err
|
return ctx, err
|
||||||
}
|
}
|
||||||
|
|
||||||
snac := oscar.NewSNAC(0x17, 0x07)
|
snac := oscar.NewSNAC(0x17, 0x07)
|
||||||
|
@ -72,7 +117,7 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
|
||||||
|
|
||||||
resp := oscar.NewFLAP(2)
|
resp := oscar.NewFLAP(2)
|
||||||
resp.Data.WriteBinary(snac)
|
resp.Data.WriteBinary(snac)
|
||||||
return session.Send(resp)
|
return ctx, session.Send(resp)
|
||||||
|
|
||||||
// Client Authorization Request
|
// Client Authorization Request
|
||||||
case 0x02:
|
case 0x02:
|
||||||
|
@ -81,14 +126,14 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
|
||||||
|
|
||||||
usernameTLV := oscar.FindTLV(tlvs, 1)
|
usernameTLV := oscar.FindTLV(tlvs, 1)
|
||||||
if usernameTLV == nil {
|
if usernameTLV == nil {
|
||||||
return errors.New("missing username TLV 0x1")
|
return ctx, errors.New("missing username TLV 0x1")
|
||||||
}
|
}
|
||||||
|
|
||||||
username := string(usernameTLV.Data)
|
username := string(usernameTLV.Data)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
user, err := models.UserByUsername(ctx, db, username)
|
user, err := models.UserByUsername(ctx, db, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return ctx, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
|
@ -97,12 +142,12 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
|
||||||
snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4}))
|
snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4}))
|
||||||
resp := oscar.NewFLAP(2)
|
resp := oscar.NewFLAP(2)
|
||||||
resp.Data.WriteBinary(snac)
|
resp.Data.WriteBinary(snac)
|
||||||
return session.Send(resp)
|
return ctx, session.Send(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordHashTLV := oscar.FindTLV(tlvs, 0x25)
|
passwordHashTLV := oscar.FindTLV(tlvs, 0x25)
|
||||||
if passwordHashTLV == nil {
|
if passwordHashTLV == nil {
|
||||||
return errors.New("missing password hash TLV 0x25")
|
return ctx, errors.New("missing password hash TLV 0x25")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute password has that we expect the client to send back if the password was right
|
// Compute password has that we expect the client to send back if the password was right
|
||||||
|
@ -123,7 +168,7 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
|
||||||
|
|
||||||
// Tell them to leave
|
// Tell them to leave
|
||||||
discoFlap := oscar.NewFLAP(4)
|
discoFlap := oscar.NewFLAP(4)
|
||||||
return session.Send(discoFlap)
|
return ctx, session.Send(discoFlap)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send BOS response + cookie
|
// Send BOS response + cookie
|
||||||
|
@ -131,10 +176,7 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
|
||||||
authSnac.Data.WriteBinary(usernameTLV)
|
authSnac.Data.WriteBinary(usernameTLV)
|
||||||
authSnac.Data.WriteBinary(oscar.NewTLV(0x5, []byte(SRV_ADDRESS)))
|
authSnac.Data.WriteBinary(oscar.NewTLV(0x5, []byte(SRV_ADDRESS)))
|
||||||
|
|
||||||
cookie, err := json.Marshal(struct {
|
cookie, err := json.Marshal(AuthorizationCookie{
|
||||||
UIN int
|
|
||||||
X string
|
|
||||||
}{
|
|
||||||
UIN: user.UIN,
|
UIN: user.UIN,
|
||||||
X: fmt.Sprintf("%x", expectedPasswordHash),
|
X: fmt.Sprintf("%x", expectedPasswordHash),
|
||||||
})
|
})
|
||||||
|
@ -148,8 +190,9 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
|
||||||
|
|
||||||
// Tell them to leave
|
// Tell them to leave
|
||||||
discoFlap := oscar.NewFLAP(4)
|
discoFlap := oscar.NewFLAP(4)
|
||||||
return session.Send(discoFlap)
|
session.Send(discoFlap)
|
||||||
|
return ctx, session.Disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return ctx, nil
|
||||||
}
|
}
|
||||||
|
|
43
main.go
43
main.go
|
@ -23,7 +23,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SRV_HOST = ""
|
SRV_HOST = "10.0.1.2"
|
||||||
SRV_PORT = "5190"
|
SRV_PORT = "5190"
|
||||||
SRV_ADDRESS = SRV_HOST + ":" + SRV_PORT
|
SRV_ADDRESS = SRV_HOST + ":" + SRV_PORT
|
||||||
)
|
)
|
||||||
|
@ -68,12 +68,40 @@ func main() {
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
handler := oscar.NewHandler(func(session *oscar.Session, flap *oscar.FLAP) {
|
handler := oscar.NewHandler(func(ctx context.Context, flap *oscar.FLAP) context.Context {
|
||||||
|
session, err := oscar.SessionFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
util.PanicIfError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user := models.UserFromContext(ctx); user != nil {
|
||||||
|
fmt.Printf("%s (%v) ->\n%+v\n", user.Username, session.RemoteAddr(), flap)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%v ->\n%+v\n", session.RemoteAddr(), flap)
|
||||||
|
}
|
||||||
|
|
||||||
if flap.Header.Channel == 1 {
|
if flap.Header.Channel == 1 {
|
||||||
// Is this a hello?
|
// Is this a hello?
|
||||||
if bytes.Equal(flap.Data.Bytes(), []byte{0, 0, 0, 1}) {
|
if bytes.Equal(flap.Data.Bytes(), []byte{0, 0, 0, 1}) {
|
||||||
return
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user, err := AuthenticateFLAPCookie(ctx, db, flap)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not authenticate cookie: %s", err)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
ctx = models.NewContextWithUser(ctx, user)
|
||||||
|
|
||||||
|
// Send available services
|
||||||
|
servicesSnac := oscar.NewSNAC(1, 3)
|
||||||
|
servicesSnac.Data.WriteUint16(0x1)
|
||||||
|
servicesSnac.Data.WriteUint16(0x4)
|
||||||
|
servicesFlap := oscar.NewFLAP(2)
|
||||||
|
servicesFlap.Data.WriteBinary(servicesSnac)
|
||||||
|
session.Send(servicesFlap)
|
||||||
|
|
||||||
|
return ctx
|
||||||
} else if flap.Header.Channel == 2 {
|
} else if flap.Header.Channel == 2 {
|
||||||
snac := &oscar.SNAC{}
|
snac := &oscar.SNAC{}
|
||||||
err := snac.UnmarshalBinary(flap.Data.Bytes())
|
err := snac.UnmarshalBinary(flap.Data.Bytes())
|
||||||
|
@ -89,13 +117,20 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if service, ok := services[snac.Header.Family]; ok {
|
if service, ok := services[snac.Header.Family]; ok {
|
||||||
err = service.HandleSNAC(db, session, snac)
|
newCtx, err := service.HandleSNAC(ctx, db, snac)
|
||||||
util.PanicIfError(err)
|
util.PanicIfError(err)
|
||||||
|
return newCtx
|
||||||
}
|
}
|
||||||
|
} else if flap.Header.Channel == 4 {
|
||||||
|
session.Disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ctx
|
||||||
})
|
})
|
||||||
|
|
||||||
RegisterService(0x17, &AuthorizationRegistrationService{})
|
RegisterService(0x17, &AuthorizationRegistrationService{})
|
||||||
|
RegisterService(0x01, &GenericServiceControls{})
|
||||||
|
RegisterService(0x04, &ICBM{})
|
||||||
|
|
||||||
exitChan := make(chan os.Signal, 1)
|
exitChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(exitChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGABRT)
|
signal.Notify(exitChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGABRT)
|
||||||
|
|
|
@ -17,6 +17,16 @@ type User struct {
|
||||||
Cipher string
|
Cipher string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type userKey string
|
||||||
|
|
||||||
|
func (s userKey) String() string {
|
||||||
|
return "user-" + string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
currentUser = userKey("user")
|
||||||
|
)
|
||||||
|
|
||||||
func UserByUsername(ctx context.Context, db *bun.DB, username string) (*User, error) {
|
func UserByUsername(ctx context.Context, db *bun.DB, username string) (*User, error) {
|
||||||
user := new(User)
|
user := new(User)
|
||||||
if err := db.NewSelect().Model(user).Where("username = ?", username).Scan(ctx, user); err != nil {
|
if err := db.NewSelect().Model(user).Where("username = ?", username).Scan(ctx, user); err != nil {
|
||||||
|
@ -28,6 +38,29 @@ 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) {
|
||||||
|
user := new(User)
|
||||||
|
if err := db.NewSelect().Model(user).Where("uin = ?", uin).Scan(ctx, user); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, errors.Wrap(err, "could not fetch user")
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContextWithUser(ctx context.Context, user *User) context.Context {
|
||||||
|
return context.WithValue(ctx, currentUser, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserFromContext(ctx context.Context) *User {
|
||||||
|
v := ctx.Value(currentUser)
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.(*User)
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) Update(ctx context.Context, db *bun.DB) error {
|
func (u *User) Update(ctx context.Context, db *bun.DB) error {
|
||||||
if _, err := db.NewUpdate().Model(u).WherePK("uin").Exec(ctx); err != nil {
|
if _, err := db.NewUpdate().Model(u).WherePK("uin").Exec(ctx); err != nil {
|
||||||
return errors.Wrap(err, "could not update user")
|
return errors.Wrap(err, "could not update user")
|
||||||
|
|
|
@ -2,27 +2,31 @@ package oscar
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"aim-oscar/util"
|
"aim-oscar/util"
|
||||||
"fmt"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HandlerFunc func(*Session, *FLAP)
|
type HandlerFunc func(context.Context, *FLAP) context.Context
|
||||||
|
|
||||||
type Handler struct{ fn HandlerFunc }
|
type Handler struct{ handle HandlerFunc }
|
||||||
|
|
||||||
func NewHandler(fn HandlerFunc) *Handler {
|
func NewHandler(fn HandlerFunc) *Handler {
|
||||||
return &Handler{
|
return &Handler{
|
||||||
fn: fn,
|
handle: fn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) Handle(conn net.Conn) {
|
func (h *Handler) Handle(conn net.Conn) {
|
||||||
session := NewSession(conn)
|
ctx := NewContextWithSession(context.Background(), conn)
|
||||||
buf := make([]byte, 1024)
|
session, _ := SessionFromContext(ctx)
|
||||||
|
|
||||||
|
buf := make([]byte, 2048)
|
||||||
for {
|
for {
|
||||||
if !session.GreetedClient {
|
if !session.GreetedClient {
|
||||||
// send a hello
|
// send a hello
|
||||||
|
@ -35,7 +39,13 @@ func (h *Handler) Handle(conn net.Conn) {
|
||||||
|
|
||||||
n, err := conn.Read(buf)
|
n, err := conn.Read(buf)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
log.Println("Read Error: ", err.Error())
|
if strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
|
log.Printf("%v disconnected", conn.RemoteAddr())
|
||||||
|
session.Disconnect()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("OSCAR Read Error: ", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +56,7 @@ func (h *Handler) Handle(conn net.Conn) {
|
||||||
// Try to parse all of the FLAPs in the buffer if we have enough bytes to
|
// Try to parse all of the FLAPs in the buffer if we have enough bytes to
|
||||||
// fill a FLAP header
|
// fill a FLAP header
|
||||||
for len(buf) >= 6 && buf[0] == 0x2a {
|
for len(buf) >= 6 && buf[0] == 0x2a {
|
||||||
dataLength := util.Word(buf[4:6])
|
dataLength := binary.BigEndian.Uint16(buf[4:6])
|
||||||
flapLength := int(dataLength) + 6
|
flapLength := int(dataLength) + 6
|
||||||
if len(buf) < flapLength {
|
if len(buf) < flapLength {
|
||||||
log.Printf("not enough data, only %d bytes\n", len(buf))
|
log.Printf("not enough data, only %d bytes\n", len(buf))
|
||||||
|
@ -58,8 +68,7 @@ func (h *Handler) Handle(conn net.Conn) {
|
||||||
util.PanicIfError(errors.Wrap(err, "could not unmarshal FLAP"))
|
util.PanicIfError(errors.Wrap(err, "could not unmarshal FLAP"))
|
||||||
}
|
}
|
||||||
buf = buf[flapLength:]
|
buf = buf[flapLength:]
|
||||||
fmt.Printf("%v ->\n%+v\n", conn.RemoteAddr(), flap)
|
ctx = h.handle(ctx, flap)
|
||||||
h.fn(session, flap)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,14 +20,14 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
Conn net.Conn
|
conn net.Conn
|
||||||
SequenceNumber uint16
|
SequenceNumber uint16
|
||||||
GreetedClient bool
|
GreetedClient bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSession(conn net.Conn) *Session {
|
func NewSession(conn net.Conn) *Session {
|
||||||
return &Session{
|
return &Session{
|
||||||
Conn: conn,
|
conn: conn,
|
||||||
SequenceNumber: 0,
|
SequenceNumber: 0,
|
||||||
GreetedClient: false,
|
GreetedClient: false,
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ func NewContextWithSession(ctx context.Context, conn net.Conn) context.Context {
|
||||||
return context.WithValue(ctx, currentSession, session)
|
return context.WithValue(ctx, currentSession, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CurrentSession(ctx context.Context) (session *Session, err error) {
|
func SessionFromContext(ctx context.Context) (session *Session, err error) {
|
||||||
s := ctx.Value(currentSession)
|
s := ctx.Value(currentSession)
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil, errors.New("no session in context")
|
return nil, errors.New("no session in context")
|
||||||
|
@ -46,6 +46,10 @@ func CurrentSession(ctx context.Context) (session *Session, err error) {
|
||||||
return s.(*Session), nil
|
return s.(*Session), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Session) RemoteAddr() net.Addr {
|
||||||
|
return s.conn.RemoteAddr()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Session) Send(flap *FLAP) error {
|
func (s *Session) Send(flap *FLAP) error {
|
||||||
s.SequenceNumber += 1
|
s.SequenceNumber += 1
|
||||||
flap.Header.SequenceNumber = s.SequenceNumber
|
flap.Header.SequenceNumber = s.SequenceNumber
|
||||||
|
@ -54,7 +58,11 @@ func (s *Session) Send(flap *FLAP) error {
|
||||||
return errors.Wrap(err, "could not marshal message")
|
return errors.Wrap(err, "could not marshal message")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("-> %v\n%s\n\n", s.Conn.RemoteAddr(), util.PrettyBytes(bytes))
|
fmt.Printf("-> %v\n%s\n\n", s.conn.RemoteAddr(), util.PrettyBytes(bytes))
|
||||||
_, err = s.Conn.Write(bytes)
|
_, err = s.conn.Write(bytes)
|
||||||
return errors.Wrap(err, "could not write to client connection")
|
return errors.Wrap(err, "could not write to client connection")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Session) Disconnect() error {
|
||||||
|
return s.conn.Close()
|
||||||
|
}
|
||||||
|
|
|
@ -43,8 +43,8 @@ func (t *TLV) UnmarshalBinary(data []byte) error {
|
||||||
if len(data) < 4 {
|
if len(data) < 4 {
|
||||||
return io.ErrUnexpectedEOF
|
return io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
t.Type = util.Word(data[:2])
|
t.Type = binary.BigEndian.Uint16(data[:2])
|
||||||
t.DataLength = util.Word(data[2:4])
|
t.DataLength = binary.BigEndian.Uint16(data[2:4])
|
||||||
if len(data) < 4+int(t.DataLength) {
|
if len(data) < 4+int(t.DataLength) {
|
||||||
return io.ErrUnexpectedEOF
|
return io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"aim-oscar/oscar"
|
"aim-oscar/oscar"
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/uptrace/bun"
|
"github.com/uptrace/bun"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
HandleSNAC(*bun.DB, *oscar.Session, *oscar.SNAC) error
|
HandleSNAC(context.Context, *bun.DB, *oscar.SNAC) (context.Context, error)
|
||||||
}
|
}
|
||||||
|
|
10
util/util.go
10
util/util.go
|
@ -53,12 +53,10 @@ func PanicIfError(err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Word(b []byte) uint16 {
|
func Word(x uint16) []byte {
|
||||||
var _ = b[1]
|
return []byte{byte(x >> 8), byte(x & 0xf)}
|
||||||
return uint16(b[1]) | uint16(b[0])<<8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DWord(b []byte) uint32 {
|
func Dword(x uint32) []byte {
|
||||||
var _ = b[3]
|
return []byte{byte(x >> 24), byte(x >> 16), byte(x >> 8), byte(x & 0xf)}
|
||||||
return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue