move oscar server into it's own module

This commit is contained in:
Artem Titoulenko 2021-12-16 19:45:32 -05:00
parent d4d7f71c66
commit a5d86c47e5
13 changed files with 207 additions and 184 deletions

View file

@ -8,16 +8,19 @@ import (
"encoding/base32" "encoding/base32"
"io" "io"
"aim-oscar/models"
"aim-oscar/oscar"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"aim-oscar/models"
) )
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 AuthorizationRegistrationService struct {
db *bun.DB
}
func (a *AuthorizationRegistrationService) GenerateCipher() string { func (a *AuthorizationRegistrationService) GenerateCipher() string {
randomBytes := make([]byte, 64) randomBytes := make([]byte, 64)
@ -28,74 +31,74 @@ 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 *Session, snac *SNAC) error { func (a *AuthorizationRegistrationService) HandleSNAC(session *oscar.Session, snac *oscar.SNAC) error {
switch snac.Header.Subtype { switch snac.Header.Subtype {
// Request MD5 Auth Key // Request MD5 Auth Key
case 0x06: case 0x06:
tlvs, err := UnmarshalTLVs(snac.Data.Bytes()) tlvs, err := oscar.UnmarshalTLVs(snac.Data.Bytes())
panicIfError(err) panicIfError(err)
usernameTLV := FindTLV(tlvs, 1) usernameTLV := oscar.FindTLV(tlvs, 1)
if usernameTLV == nil { if usernameTLV == nil {
return errors.New("missing username TLV") return errors.New("missing username TLV")
} }
// Fetch the user // Fetch the user
ctx := context.Background() ctx := context.Background()
user, err := models.UserByUsername(ctx, db, string(usernameTLV.Data)) user, err := models.UserByUsername(ctx, a.db, string(usernameTLV.Data))
if err != nil { if err != nil {
return err return err
} }
if user == nil { if user == nil {
snac := NewSNAC(0x17, 0x03) snac := oscar.NewSNAC(0x17, 0x03)
snac.Data.WriteBinary(usernameTLV) snac.Data.WriteBinary(usernameTLV)
snac.Data.WriteBinary(NewTLV(0x08, []byte{0, 4})) snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4}))
resp := NewFLAP(2) resp := oscar.NewFLAP(2)
resp.Data.WriteBinary(snac) resp.Data.WriteBinary(snac)
return session.Send(resp) return 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, a.db); err != nil {
return err return err
} }
snac := NewSNAC(0x17, 0x07) snac := oscar.NewSNAC(0x17, 0x07)
snac.Data.WriteUint16(uint16(len(user.Cipher))) snac.Data.WriteUint16(uint16(len(user.Cipher)))
snac.Data.WriteString(user.Cipher) snac.Data.WriteString(user.Cipher)
resp := NewFLAP(2) resp := oscar.NewFLAP(2)
resp.Data.WriteBinary(snac) resp.Data.WriteBinary(snac)
return session.Send(resp) return session.Send(resp)
// Client Authorization Request // Client Authorization Request
case 0x02: case 0x02:
tlvs, err := UnmarshalTLVs(snac.Data.Bytes()) tlvs, err := oscar.UnmarshalTLVs(snac.Data.Bytes())
panicIfError(err) panicIfError(err)
usernameTLV := FindTLV(tlvs, 1) usernameTLV := oscar.FindTLV(tlvs, 1)
if usernameTLV == nil { if usernameTLV == nil {
return errors.New("missing username TLV 0x1") return 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, a.db, username)
if err != nil { if err != nil {
return err return err
} }
if user == nil { if user == nil {
snac := NewSNAC(0x17, 0x03) snac := oscar.NewSNAC(0x17, 0x03)
snac.Data.WriteBinary(usernameTLV) snac.Data.WriteBinary(usernameTLV)
snac.Data.WriteBinary(NewTLV(0x08, []byte{0, 4})) snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4}))
resp := NewFLAP(2) resp := oscar.NewFLAP(2)
resp.Data.WriteBinary(snac) resp.Data.WriteBinary(snac)
return session.Send(resp) return session.Send(resp)
} }
passwordHashTLV := FindTLV(tlvs, 0x25) passwordHashTLV := oscar.FindTLV(tlvs, 0x25)
if passwordHashTLV == nil { if passwordHashTLV == nil {
return errors.New("missing password hash TLV 0x25") return errors.New("missing password hash TLV 0x25")
} }
@ -108,29 +111,29 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *Sessi
if !bytes.Equal(expectedPasswordHash, passwordHashTLV.Data) { if !bytes.Equal(expectedPasswordHash, passwordHashTLV.Data) {
// Tell the client this was a bad password // Tell the client this was a bad password
badPasswordSnac := NewSNAC(0x17, 0x03) badPasswordSnac := oscar.NewSNAC(0x17, 0x03)
badPasswordSnac.Data.WriteBinary(usernameTLV) badPasswordSnac.Data.WriteBinary(usernameTLV)
badPasswordSnac.Data.WriteBinary(NewTLV(0x08, []byte{0, 4})) badPasswordSnac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4}))
badPasswordFlap := NewFLAP(2) badPasswordFlap := oscar.NewFLAP(2)
badPasswordFlap.Data.WriteBinary(badPasswordSnac) badPasswordFlap.Data.WriteBinary(badPasswordSnac)
session.Send(badPasswordFlap) session.Send(badPasswordFlap)
// Tell tem to leave // Tell tem to leave
discoFlap := NewFLAP(4) discoFlap := oscar.NewFLAP(4)
return session.Send(discoFlap) return session.Send(discoFlap)
} }
// Send BOS response + cookie // Send BOS response + cookie
authSnac := NewSNAC(0x17, 0x3) authSnac := oscar.NewSNAC(0x17, 0x3)
authSnac.Data.WriteBinary(usernameTLV) authSnac.Data.WriteBinary(usernameTLV)
authSnac.Data.WriteBinary(NewTLV(0x5, []byte("10.0.1.2:5191"))) authSnac.Data.WriteBinary(oscar.NewTLV(0x5, []byte("10.0.1.2:5191")))
authSnac.Data.WriteBinary(NewTLV(0x6, []byte(`{"hello": "uwu"}`))) authSnac.Data.WriteBinary(oscar.NewTLV(0x6, []byte(`{"hello": "uwu"}`)))
authFlap := NewFLAP(2) authFlap := oscar.NewFLAP(2)
authFlap.Data.WriteBinary(authSnac) authFlap.Data.WriteBinary(authSnac)
session.Send(authFlap) session.Send(authFlap)
// Tell tem to leave // Tell tem to leave
discoFlap := NewFLAP(4) discoFlap := oscar.NewFLAP(4)
return session.Send(discoFlap) return session.Send(discoFlap)
} }

96
main.go
View file

@ -2,17 +2,17 @@ package main
import ( import (
"aim-oscar/models" "aim-oscar/models"
"aim-oscar/oscar"
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"io"
"log" "log"
"net" "net"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"time"
"github.com/pkg/errors"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"github.com/uptrace/bun/dbfixture" "github.com/uptrace/bun/dbfixture"
"github.com/uptrace/bun/dialect/sqlitedialect" "github.com/uptrace/bun/dialect/sqlitedialect"
@ -26,11 +26,7 @@ const (
SRV_ADDRESS = SRV_HOST + ":" + SRV_PORT SRV_ADDRESS = SRV_HOST + ":" + SRV_PORT
) )
var services = make(map[uint16]Service) var db *bun.DB
func init() {
services[0x17] = &AuthorizationRegistrationService{}
}
func main() { func main() {
// Set up the DB // Set up the DB
@ -39,12 +35,16 @@ func main() {
panic(err) panic(err)
} }
db := bun.NewDB(sqldb, sqlitedialect.New()) db := bun.NewDB(sqldb, sqlitedialect.New())
db.SetConnMaxIdleTime(15 * time.Second)
db.SetConnMaxLifetime(1 * time.Minute)
// Print all queries to stdout. // Print all queries to stdout.
db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))
// Register our DB models
db.RegisterModel((*models.User)(nil)) db.RegisterModel((*models.User)(nil))
// dev: load in fixtures to test against
fixture := dbfixture.New(db, dbfixture.WithRecreateTables()) fixture := dbfixture.New(db, dbfixture.WithRecreateTables())
err = fixture.Load(context.Background(), os.DirFS("models"), "fixtures.yml") err = fixture.Load(context.Background(), os.DirFS("models"), "fixtures.yml")
if err != nil { if err != nil {
@ -58,6 +58,9 @@ func main() {
} }
defer listener.Close() defer listener.Close()
handler := oscar.NewHandler()
handler.RegisterService(0x17, &AuthorizationRegistrationService{db: db})
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)
go func() { go func() {
@ -74,84 +77,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
session := NewSession(conn)
log.Printf("Connection from %v", conn.RemoteAddr()) log.Printf("Connection from %v", conn.RemoteAddr())
go handler.Handle(conn)
go handleTCPConnection(db, session, conn)
}
}
func handleTCPConnection(db *bun.DB, session *Session, conn net.Conn) {
// defer (func() {
// if err := recover(); err != nil {
// log.Printf("Error handling message: %+v\n", err.(error))
// }
// conn.Close()
// log.Printf("Closed connection to %v", conn.RemoteAddr())
// })()
buf := make([]byte, 1024)
for {
if !session.GreetedClient {
// send a hello
hello := NewFLAP(1)
hello.Data.Write([]byte{0, 0, 0, 1})
err := session.Send(hello)
panicIfError(err)
session.GreetedClient = true
}
n, err := conn.Read(buf)
if err != nil && err != io.EOF {
log.Println("Read Error: ", err.Error())
return
}
if n == 0 {
return
}
// Try to parse all of the FLAPs in the buffer if we have enough bytes to
// fill a FLAP header
for len(buf) >= 6 && buf[0] == 0x2a {
dataLength := Word(buf[4:6])
flapLength := int(dataLength) + 6
if len(buf) < flapLength {
log.Printf("not enough data, only %d bytes\n", len(buf))
break
}
flap := &FLAP{}
if err := flap.UnmarshalBinary(buf[:flapLength]); err != nil {
panicIfError(errors.Wrap(err, "could not unmarshal FLAP"))
}
buf = buf[flapLength:]
fmt.Printf("%v ->\n%+v\n", conn.RemoteAddr(), flap)
handleMessage(db, session, flap)
}
}
}
func handleMessage(db *bun.DB, session *Session, flap *FLAP) {
if flap.Header.Channel == 1 {
} else if flap.Header.Channel == 2 {
snac := &SNAC{}
err := snac.UnmarshalBinary(flap.Data.Bytes())
panicIfError(err)
fmt.Printf("%+v\n", snac)
if tlvs, err := UnmarshalTLVs(snac.Data.Bytes()); err == nil {
for _, tlv := range tlvs {
fmt.Printf("%+v\n", tlv)
}
} else {
fmt.Printf("%s\n\n", prettyBytes(snac.Data.Bytes()))
}
if service, ok := services[snac.Header.Family]; ok {
err = service.HandleSNAC(db, session, snac)
panicIfError(err)
}
} }
} }

View file

@ -1,4 +1,4 @@
package main package oscar
import ( import (
"encoding" "encoding"

View file

@ -1,4 +1,4 @@
package main package oscar
import ( import (
"bytes" "bytes"

View file

@ -1,4 +1,4 @@
package main package oscar
import ( import (
"reflect" "reflect"

89
oscar/server.go Normal file
View file

@ -0,0 +1,89 @@
package oscar
import (
"fmt"
"io"
"log"
"net"
"github.com/pkg/errors"
)
type Handler struct {
services map[uint16]Service
}
func NewHandler() *Handler {
return &Handler{
services: make(map[uint16]Service),
}
}
func (h *Handler) RegisterService(family uint16, service Service) {
h.services[family] = service
}
func (h *Handler) Handle(conn net.Conn) {
session := NewSession(conn)
buf := make([]byte, 1024)
for {
if !session.GreetedClient {
// send a hello
hello := NewFLAP(1)
hello.Data.Write([]byte{0, 0, 0, 1})
err := session.Send(hello)
panicIfError(err)
session.GreetedClient = true
}
n, err := conn.Read(buf)
if err != nil && err != io.EOF {
log.Println("Read Error: ", err.Error())
return
}
if n == 0 {
return
}
// Try to parse all of the FLAPs in the buffer if we have enough bytes to
// fill a FLAP header
for len(buf) >= 6 && buf[0] == 0x2a {
dataLength := Word(buf[4:6])
flapLength := int(dataLength) + 6
if len(buf) < flapLength {
log.Printf("not enough data, only %d bytes\n", len(buf))
break
}
flap := &FLAP{}
if err := flap.UnmarshalBinary(buf[:flapLength]); err != nil {
panicIfError(errors.Wrap(err, "could not unmarshal FLAP"))
}
buf = buf[flapLength:]
fmt.Printf("%v ->\n%+v\n", conn.RemoteAddr(), flap)
if flap.Header.Channel == 1 {
} else if flap.Header.Channel == 2 {
snac := &SNAC{}
err := snac.UnmarshalBinary(flap.Data.Bytes())
panicIfError(err)
fmt.Printf("%+v\n", snac)
if tlvs, err := UnmarshalTLVs(snac.Data.Bytes()); err == nil {
for _, tlv := range tlvs {
fmt.Printf("%+v\n", tlv)
}
} else {
fmt.Printf("%s\n\n", prettyBytes(snac.Data.Bytes()))
}
if service, ok := h.services[snac.Header.Family]; ok {
err = service.HandleSNAC(session, snac)
panicIfError(err)
}
}
}
}
}

5
oscar/service.go Normal file
View file

@ -0,0 +1,5 @@
package oscar
type Service interface {
HandleSNAC(*Session, *SNAC) error
}

View file

@ -1,4 +1,4 @@
package main package oscar
import ( import (
"context" "context"

View file

@ -1,4 +1,4 @@
package main package oscar
import ( import (
"bytes" "bytes"

View file

@ -1,4 +1,4 @@
package main package oscar
import ( import (
"encoding" "encoding"

64
oscar/util.go Normal file
View file

@ -0,0 +1,64 @@
package oscar
import (
"encoding/hex"
"fmt"
"strconv"
"strings"
)
// splitBy splits string in chunks of n
// taken from: https://stackoverflow.com/a/69403603
func splitBy(s string, n int) []string {
var ss []string
for i := 1; i < len(s); i++ {
if i%n == 0 {
ss = append(ss, s[:i])
s = s[i:]
i = 1
}
}
ss = append(ss, s)
return ss
}
func prettyBytes(bytes []byte) string {
hexStr := hex.EncodeToString(bytes)
rows := splitBy(hexStr, 16)
res := ""
for _, row := range rows {
byteGroups := splitBy(row, 2)
// Align string view to full 16 bytes + spaces
res += fmt.Sprintf("%-23s", strings.Join(byteGroups, " "))
res += " |"
for _, r := range byteGroups {
n, err := strconv.ParseInt(r, 16, 8)
if err != nil || (n < 32 || n > 126) {
res += "."
} else {
res += string(rune(n))
}
}
res += "|\n"
}
return strings.TrimSpace(res)
}
func panicIfError(err error) {
if err != nil {
panic(err)
}
}
func Word(b []byte) uint16 {
var _ = b[1]
return uint16(b[1]) | uint16(b[0])<<8
}
func DWord(b []byte) uint32 {
var _ = b[3]
return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
}

View file

@ -1,7 +0,0 @@
package main
import "github.com/uptrace/bun"
type Service interface {
HandleSNAC(*bun.DB, *Session, *SNAC) error
}

57
util.go
View file

@ -1,64 +1,7 @@
package main package main
import (
"encoding/hex"
"fmt"
"strconv"
"strings"
)
// splitBy splits string in chunks of n
// taken from: https://stackoverflow.com/a/69403603
func splitBy(s string, n int) []string {
var ss []string
for i := 1; i < len(s); i++ {
if i%n == 0 {
ss = append(ss, s[:i])
s = s[i:]
i = 1
}
}
ss = append(ss, s)
return ss
}
func prettyBytes(bytes []byte) string {
hexStr := hex.EncodeToString(bytes)
rows := splitBy(hexStr, 16)
res := ""
for _, row := range rows {
byteGroups := splitBy(row, 2)
// Align string view to full 16 bytes + spaces
res += fmt.Sprintf("%-23s", strings.Join(byteGroups, " "))
res += " |"
for _, r := range byteGroups {
n, err := strconv.ParseInt(r, 16, 8)
if err != nil || (n < 32 || n > 126) {
res += "."
} else {
res += string(rune(n))
}
}
res += "|\n"
}
return strings.TrimSpace(res)
}
func panicIfError(err error) { func panicIfError(err error) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
} }
func Word(b []byte) uint16 {
var _ = b[1]
return uint16(b[1]) | uint16(b[0])<<8
}
func DWord(b []byte) uint32 {
var _ = b[3]
return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
}