From a5d86c47e5c1690a7a96f2cbd26941c27a768dbe Mon Sep 17 00:00:00 2001 From: Artem Titoulenko Date: Thu, 16 Dec 2021 19:45:32 -0500 Subject: [PATCH] move oscar server into it's own module --- 0x17_authorization_registration_service.go | 61 +++++++------- main.go | 96 +++------------------- buf.go => oscar/buf.go | 2 +- flap.go => oscar/flap.go | 2 +- flap_test.go => oscar/flap_test.go | 2 +- oscar/server.go | 89 ++++++++++++++++++++ oscar/service.go | 5 ++ session.go => oscar/session.go | 2 +- snac.go => oscar/snac.go | 2 +- tlv.go => oscar/tlv.go | 2 +- oscar/util.go | 64 +++++++++++++++ service.go | 7 -- util.go | 57 ------------- 13 files changed, 207 insertions(+), 184 deletions(-) rename buf.go => oscar/buf.go (98%) rename flap.go => oscar/flap.go (99%) rename flap_test.go => oscar/flap_test.go (98%) create mode 100644 oscar/server.go create mode 100644 oscar/service.go rename session.go => oscar/session.go (98%) rename snac.go => oscar/snac.go (98%) rename tlv.go => oscar/tlv.go (99%) create mode 100644 oscar/util.go delete mode 100644 service.go diff --git a/0x17_authorization_registration_service.go b/0x17_authorization_registration_service.go index f63779f..703677b 100644 --- a/0x17_authorization_registration_service.go +++ b/0x17_authorization_registration_service.go @@ -8,16 +8,19 @@ import ( "encoding/base32" "io" + "aim-oscar/models" + "aim-oscar/oscar" + "github.com/pkg/errors" "github.com/uptrace/bun" - - "aim-oscar/models" ) const CIPHER_LENGTH = 64 const AIM_MD5_STRING = "AOL Instant Messenger (SM)" -type AuthorizationRegistrationService struct{} +type AuthorizationRegistrationService struct { + db *bun.DB +} func (a *AuthorizationRegistrationService) GenerateCipher() string { randomBytes := make([]byte, 64) @@ -28,74 +31,74 @@ func (a *AuthorizationRegistrationService) GenerateCipher() string { 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 { // Request MD5 Auth Key case 0x06: - tlvs, err := UnmarshalTLVs(snac.Data.Bytes()) + tlvs, err := oscar.UnmarshalTLVs(snac.Data.Bytes()) panicIfError(err) - usernameTLV := FindTLV(tlvs, 1) + usernameTLV := oscar.FindTLV(tlvs, 1) if usernameTLV == nil { return errors.New("missing username TLV") } // Fetch the user 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 { return err } if user == nil { - snac := NewSNAC(0x17, 0x03) + snac := oscar.NewSNAC(0x17, 0x03) snac.Data.WriteBinary(usernameTLV) - snac.Data.WriteBinary(NewTLV(0x08, []byte{0, 4})) - resp := NewFLAP(2) + snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4})) + resp := oscar.NewFLAP(2) resp.Data.WriteBinary(snac) return session.Send(resp) } // Create cipher for this user user.Cipher = a.GenerateCipher() - if err = user.Update(ctx, db); err != nil { + if err = user.Update(ctx, a.db); err != nil { return err } - snac := NewSNAC(0x17, 0x07) + snac := oscar.NewSNAC(0x17, 0x07) snac.Data.WriteUint16(uint16(len(user.Cipher))) snac.Data.WriteString(user.Cipher) - resp := NewFLAP(2) + resp := oscar.NewFLAP(2) resp.Data.WriteBinary(snac) return session.Send(resp) // Client Authorization Request case 0x02: - tlvs, err := UnmarshalTLVs(snac.Data.Bytes()) + tlvs, err := oscar.UnmarshalTLVs(snac.Data.Bytes()) panicIfError(err) - usernameTLV := FindTLV(tlvs, 1) + usernameTLV := oscar.FindTLV(tlvs, 1) if usernameTLV == nil { return errors.New("missing username TLV 0x1") } username := string(usernameTLV.Data) ctx := context.Background() - user, err := models.UserByUsername(ctx, db, username) + user, err := models.UserByUsername(ctx, a.db, username) if err != nil { return err } if user == nil { - snac := NewSNAC(0x17, 0x03) + snac := oscar.NewSNAC(0x17, 0x03) snac.Data.WriteBinary(usernameTLV) - snac.Data.WriteBinary(NewTLV(0x08, []byte{0, 4})) - resp := NewFLAP(2) + snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4})) + resp := oscar.NewFLAP(2) resp.Data.WriteBinary(snac) return session.Send(resp) } - passwordHashTLV := FindTLV(tlvs, 0x25) + passwordHashTLV := oscar.FindTLV(tlvs, 0x25) if passwordHashTLV == nil { 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) { // Tell the client this was a bad password - badPasswordSnac := NewSNAC(0x17, 0x03) + badPasswordSnac := oscar.NewSNAC(0x17, 0x03) badPasswordSnac.Data.WriteBinary(usernameTLV) - badPasswordSnac.Data.WriteBinary(NewTLV(0x08, []byte{0, 4})) - badPasswordFlap := NewFLAP(2) + badPasswordSnac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4})) + badPasswordFlap := oscar.NewFLAP(2) badPasswordFlap.Data.WriteBinary(badPasswordSnac) session.Send(badPasswordFlap) // Tell tem to leave - discoFlap := NewFLAP(4) + discoFlap := oscar.NewFLAP(4) return session.Send(discoFlap) } // Send BOS response + cookie - authSnac := NewSNAC(0x17, 0x3) + authSnac := oscar.NewSNAC(0x17, 0x3) authSnac.Data.WriteBinary(usernameTLV) - authSnac.Data.WriteBinary(NewTLV(0x5, []byte("10.0.1.2:5191"))) - authSnac.Data.WriteBinary(NewTLV(0x6, []byte(`{"hello": "uwu"}`))) - authFlap := NewFLAP(2) + authSnac.Data.WriteBinary(oscar.NewTLV(0x5, []byte("10.0.1.2:5191"))) + authSnac.Data.WriteBinary(oscar.NewTLV(0x6, []byte(`{"hello": "uwu"}`))) + authFlap := oscar.NewFLAP(2) authFlap.Data.WriteBinary(authSnac) session.Send(authFlap) // Tell tem to leave - discoFlap := NewFLAP(4) + discoFlap := oscar.NewFLAP(4) return session.Send(discoFlap) } diff --git a/main.go b/main.go index bcd4f37..d99140e 100644 --- a/main.go +++ b/main.go @@ -2,17 +2,17 @@ package main import ( "aim-oscar/models" + "aim-oscar/oscar" "context" "database/sql" "fmt" - "io" "log" "net" "os" "os/signal" "syscall" + "time" - "github.com/pkg/errors" "github.com/uptrace/bun" "github.com/uptrace/bun/dbfixture" "github.com/uptrace/bun/dialect/sqlitedialect" @@ -26,11 +26,7 @@ const ( SRV_ADDRESS = SRV_HOST + ":" + SRV_PORT ) -var services = make(map[uint16]Service) - -func init() { - services[0x17] = &AuthorizationRegistrationService{} -} +var db *bun.DB func main() { // Set up the DB @@ -39,12 +35,16 @@ func main() { panic(err) } db := bun.NewDB(sqldb, sqlitedialect.New()) + db.SetConnMaxIdleTime(15 * time.Second) + db.SetConnMaxLifetime(1 * time.Minute) // Print all queries to stdout. db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) + // Register our DB models db.RegisterModel((*models.User)(nil)) + // dev: load in fixtures to test against fixture := dbfixture.New(db, dbfixture.WithRecreateTables()) err = fixture.Load(context.Background(), os.DirFS("models"), "fixtures.yml") if err != nil { @@ -58,6 +58,9 @@ func main() { } defer listener.Close() + handler := oscar.NewHandler() + handler.RegisterService(0x17, &AuthorizationRegistrationService{db: db}) + exitChan := make(chan os.Signal, 1) signal.Notify(exitChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGABRT) go func() { @@ -74,84 +77,7 @@ func main() { os.Exit(1) } - session := NewSession(conn) log.Printf("Connection from %v", conn.RemoteAddr()) - - 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) - } + go handler.Handle(conn) } } diff --git a/buf.go b/oscar/buf.go similarity index 98% rename from buf.go rename to oscar/buf.go index bbfe58a..578f98e 100644 --- a/buf.go +++ b/oscar/buf.go @@ -1,4 +1,4 @@ -package main +package oscar import ( "encoding" diff --git a/flap.go b/oscar/flap.go similarity index 99% rename from flap.go rename to oscar/flap.go index ca42097..346b5b4 100644 --- a/flap.go +++ b/oscar/flap.go @@ -1,4 +1,4 @@ -package main +package oscar import ( "bytes" diff --git a/flap_test.go b/oscar/flap_test.go similarity index 98% rename from flap_test.go rename to oscar/flap_test.go index ff7635f..39933e0 100644 --- a/flap_test.go +++ b/oscar/flap_test.go @@ -1,4 +1,4 @@ -package main +package oscar import ( "reflect" diff --git a/oscar/server.go b/oscar/server.go new file mode 100644 index 0000000..cc42fcf --- /dev/null +++ b/oscar/server.go @@ -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) + } + } + } + } +} diff --git a/oscar/service.go b/oscar/service.go new file mode 100644 index 0000000..3c65c4b --- /dev/null +++ b/oscar/service.go @@ -0,0 +1,5 @@ +package oscar + +type Service interface { + HandleSNAC(*Session, *SNAC) error +} diff --git a/session.go b/oscar/session.go similarity index 98% rename from session.go rename to oscar/session.go index f60ca9f..692c898 100644 --- a/session.go +++ b/oscar/session.go @@ -1,4 +1,4 @@ -package main +package oscar import ( "context" diff --git a/snac.go b/oscar/snac.go similarity index 98% rename from snac.go rename to oscar/snac.go index 68c715e..f235371 100644 --- a/snac.go +++ b/oscar/snac.go @@ -1,4 +1,4 @@ -package main +package oscar import ( "bytes" diff --git a/tlv.go b/oscar/tlv.go similarity index 99% rename from tlv.go rename to oscar/tlv.go index 2d3fe2e..0fc7fea 100644 --- a/tlv.go +++ b/oscar/tlv.go @@ -1,4 +1,4 @@ -package main +package oscar import ( "encoding" diff --git a/oscar/util.go b/oscar/util.go new file mode 100644 index 0000000..8fe18e0 --- /dev/null +++ b/oscar/util.go @@ -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 +} diff --git a/service.go b/service.go deleted file mode 100644 index 05b7738..0000000 --- a/service.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "github.com/uptrace/bun" - -type Service interface { - HandleSNAC(*bun.DB, *Session, *SNAC) error -} diff --git a/util.go b/util.go index 50a26e8..d9a06cf 100644 --- a/util.go +++ b/util.go @@ -1,64 +1,7 @@ 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) { 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 -}