From 298b97f19923a75b5d95ca8f73ebd4c2f2984dca Mon Sep 17 00:00:00 2001 From: Artem Titoulenko Date: Fri, 24 Dec 2021 12:41:16 -0500 Subject: [PATCH] a little more organization --- Readme.md | 3 +- main.go | 59 ++++++------------- message_delivery_routine.go | 4 +- online_routine.go | 4 +- service_manager.go | 20 +++++++ .../0x01_generic_service_controls.go | 31 +++++----- .../0x02_location_services.go | 2 +- .../0x03_buddy_list_management.go | 2 +- 0x04_ICBM.go => services/0x04_ICBM.go | 2 +- ...0x17_authorization_registration_service.go | 8 ++- session_manager.go | 42 +++++++++++++ 11 files changed, 111 insertions(+), 66 deletions(-) create mode 100644 service_manager.go rename 0x01_generic_service_controls.go => services/0x01_generic_service_controls.go (89%) rename 0x02_location_services.go => services/0x02_location_services.go (99%) rename 0x03_buddy_list_management.go => services/0x03_buddy_list_management.go (99%) rename 0x04_ICBM.go => services/0x04_ICBM.go (99%) rename 0x17_authorization_registration_service.go => services/0x17_authorization_registration_service.go (96%) create mode 100644 session_manager.go diff --git a/Readme.md b/Readme.md index ede1106..b049a8f 100644 --- a/Readme.md +++ b/Readme.md @@ -8,7 +8,8 @@ Run your own AIM chat server, managing users and groups. Hook up a vintage clien - [x] Add buddies - [x] See buddy online/away status - [x] Chat with buddy -- [ ] Set away status +- [x] Set away status +- [ ] See away status - [ ] Look up buddy - [ ] Buddy icons - [ ] Rate limiting + warn system diff --git a/main.go b/main.go index d61a829..2afc34b 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "aim-oscar/models" "aim-oscar/oscar" + "aim-oscar/services" "aim-oscar/util" "bytes" "context" @@ -12,7 +13,6 @@ import ( "net" "os" "os/signal" - "sync" "syscall" "time" @@ -25,37 +25,11 @@ import ( ) const ( - SRV_HOST = "10.0.1.2" + SRV_HOST = "0.0.0.0" SRV_PORT = "5190" SRV_ADDRESS = SRV_HOST + ":" + SRV_PORT ) -var services map[uint16]Service - -// Map username to session -var sessions map[string]*oscar.Session -var sessionsMutex = &sync.RWMutex{} - -func getSession(username string) *oscar.Session { - sessionsMutex.RLock() - s, ok := sessions[username] - sessionsMutex.RUnlock() - - if ok { - return s - } - return nil -} - -func init() { - services = make(map[uint16]Service) - sessions = make(map[string]*oscar.Session) -} - -func RegisterService(family uint16, service Service) { - services[family] = service -} - func main() { // Set up the DB sqldb, err := sql.Open(sqliteshim.ShimName, "file:aim.db") @@ -86,14 +60,23 @@ func main() { } defer listener.Close() + sessionManager := NewSessionManager() + // Goroutine that listens for messages to deliver and tries to find a user socket to push them to - commCh, messageRoutine := MessageDelivery() + commCh, messageRoutine := MessageDelivery(sessionManager) go messageRoutine(db) // Goroutine that listens for users who change their online status and notifies their buddies - onlineCh, onlineRoutine := OnlineNotification() + onlineCh, onlineRoutine := OnlineNotification(sessionManager) go onlineRoutine(db) + serviceManager := NewServiceManager() + serviceManager.RegisterService(0x01, &services.GenericServiceControls{OnlineCh: onlineCh}) + serviceManager.RegisterService(0x02, &services.LocationServices{OnlineCh: onlineCh}) + serviceManager.RegisterService(0x03, &services.BuddyListManagement{}) + serviceManager.RegisterService(0x04, &services.ICBM{CommCh: commCh}) + serviceManager.RegisterService(0x17, &services.AuthorizationRegistrationService{}) + handleCloseFn := func(ctx context.Context, session *oscar.Session) { log.Printf("%v disconnected", session.RemoteAddr()) @@ -108,6 +91,8 @@ func main() { if true { onlineCh <- user } + + sessionManager.RemoveSession(user.Username) } } @@ -121,7 +106,7 @@ func main() { fmt.Printf("%s (%v) ->\n%+v\n", user.Username, session.RemoteAddr(), flap) user.LastActivityAt = time.Now() ctx = models.NewContextWithUser(ctx, user) - sessions[user.Username] = session + sessionManager.SetSession(user.Username, session) } else { fmt.Printf("%v ->\n%+v\n", session.RemoteAddr(), flap) } @@ -132,7 +117,7 @@ func main() { return ctx } - user, err := AuthenticateFLAPCookie(ctx, db, flap) + user, err := services.AuthenticateFLAPCookie(ctx, db, flap) if err != nil { log.Printf("Could not authenticate cookie: %s", err) return ctx @@ -141,7 +126,7 @@ func main() { // Send available services servicesSnac := oscar.NewSNAC(1, 3) - for family := range versions { + for family := range services.ServiceVersions { servicesSnac.Data.WriteUint16(family) } @@ -164,7 +149,7 @@ func main() { fmt.Printf("%s\n\n", util.PrettyBytes(snac.Data.Bytes())) } - if service, ok := services[snac.Header.Family]; ok { + if service, ok := serviceManager.GetService(snac.Header.Family); ok { newCtx, err := service.HandleSNAC(ctx, db, snac) util.PanicIfError(err) return newCtx @@ -179,12 +164,6 @@ func main() { handler := oscar.NewHandler(handleFn, handleCloseFn) - RegisterService(0x01, &GenericServiceControls{OnlineCh: onlineCh}) - RegisterService(0x02, &LocationServices{OnlineCh: onlineCh}) - RegisterService(0x03, &BuddyListManagement{}) - RegisterService(0x04, &ICBM{CommCh: commCh}) - RegisterService(0x17, &AuthorizationRegistrationService{}) - exitChan := make(chan os.Signal, 1) signal.Notify(exitChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGABRT) go func() { diff --git a/message_delivery_routine.go b/message_delivery_routine.go index 4fd1667..2c523cc 100644 --- a/message_delivery_routine.go +++ b/message_delivery_routine.go @@ -12,7 +12,7 @@ import ( type routineFn func(db *bun.DB) -func MessageDelivery() (chan *models.Message, routineFn) { +func MessageDelivery(sm *SessionManager) (chan *models.Message, routineFn) { commCh := make(chan *models.Message, 1) routine := func(db *bun.DB) { @@ -26,7 +26,7 @@ func MessageDelivery() (chan *models.Message, routineFn) { } log.Printf("got a message: %s", message) - if s := getSession(message.To); s != nil { + if s := sm.GetSession(message.To); s != nil { messageSnac := oscar.NewSNAC(4, 7) messageSnac.Data.WriteUint64(message.Cookie) messageSnac.Data.WriteUint16(1) diff --git a/online_routine.go b/online_routine.go index 94bb36a..0916150 100644 --- a/online_routine.go +++ b/online_routine.go @@ -12,7 +12,7 @@ import ( "github.com/uptrace/bun" ) -func OnlineNotification() (chan *models.User, routineFn) { +func OnlineNotification(sm *SessionManager) (chan *models.User, routineFn) { commCh := make(chan *models.User, 1) routine := func(db *bun.DB) { @@ -47,7 +47,7 @@ func OnlineNotification() (chan *models.User, routineFn) { } log.Printf("telling %s that %s has a new status: %d!", buddy.Source.Username, user.Username, user.Status) - if s := getSession(buddy.Source.Username); s != nil { + if s := sm.GetSession(buddy.Source.Username); s != nil { onlineSnac := oscar.NewSNAC(3, 0xb) onlineSnac.Data.WriteLPString(user.Username) onlineSnac.Data.WriteUint16(0) // TODO: user warning level diff --git a/service_manager.go b/service_manager.go new file mode 100644 index 0000000..686566c --- /dev/null +++ b/service_manager.go @@ -0,0 +1,20 @@ +package main + +type ServiceManager struct { + services map[uint16]Service +} + +func NewServiceManager() *ServiceManager { + return &ServiceManager{ + services: make(map[uint16]Service), + } +} + +func (sm *ServiceManager) RegisterService(family uint16, service Service) { + sm.services[family] = service +} + +func (sm *ServiceManager) GetService(family uint16) (Service, bool) { + s, ok := sm.services[family] + return s, ok +} diff --git a/0x01_generic_service_controls.go b/services/0x01_generic_service_controls.go similarity index 89% rename from 0x01_generic_service_controls.go rename to services/0x01_generic_service_controls.go index d0fc2bf..72664a9 100644 --- a/0x01_generic_service_controls.go +++ b/services/0x01_generic_service_controls.go @@ -1,4 +1,4 @@ -package main +package services import ( "aim-oscar/aimerror" @@ -13,19 +13,20 @@ import ( "github.com/uptrace/bun" ) -var versions map[uint16]uint16 +var ServiceVersions map[uint16]uint16 func init() { - versions = make(map[uint16]uint16) - versions[1] = 3 - versions[2] = 1 - versions[3] = 1 - versions[4] = 1 - versions[17] = 1 + ServiceVersions = make(map[uint16]uint16) + ServiceVersions[1] = 3 + ServiceVersions[2] = 1 + ServiceVersions[3] = 1 + ServiceVersions[4] = 1 + ServiceVersions[17] = 1 } type GenericServiceControls struct { - OnlineCh chan *models.User + OnlineCh chan *models.User + ServerHostname string } func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC) (context.Context, error) { @@ -63,7 +64,7 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna tlvs := []*oscar.TLV{ oscar.NewTLV(1, util.Word(0)), // TODO: user class oscar.NewTLV(0x06, util.Dword(uint32(user.Status))), // TODO: User Status - oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(SRV_HOST)))), // External IP + oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(g.ServerHostname)))), // External IP oscar.NewTLV(0x0f, util.Dword(uint32(time.Since(user.LastActivityAt).Seconds()))), // Idle Time oscar.NewTLV(0x03, util.Dword(uint32(time.Now().Unix()))), // Client Signon Time oscar.NewTLV(0x05, util.Dword(uint32(user.CreatedAt.Unix()))), // Member since @@ -111,8 +112,8 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna // 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(uint16(len(versions)) * 0x21) // Number of rate groups - for family := range versions { + rg.WriteUint16(uint16(len(ServiceVersions)) * 0x21) // Number of rate groups + for family := range ServiceVersions { for subtype := 0; subtype < 0x21; subtype++ { rg.WriteUint16(family) rg.WriteUint16(uint16(subtype)) @@ -144,7 +145,7 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna tlvs := []*oscar.TLV{ oscar.NewTLV(0x01, util.Dword(0)), // User Class oscar.NewTLV(0x06, util.Dword(uint32(user.Status))), // TODO: User Status - oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(SRV_HOST)))), // External IP + oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(g.ServerHostname)))), // External IP oscar.NewTLV(0x0f, util.Dword(uint32(time.Since(user.LastActivityAt).Seconds()))), // Idle Time oscar.NewTLV(0x03, util.Dword(uint32(time.Now().Unix()))), // Client Signon Time oscar.NewTLV(0x01e, util.Dword(0x0)), // Unknown value @@ -160,10 +161,10 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna onlineFlap.Data.WriteBinary(onlineSnac) return models.NewContextWithUser(ctx, user), session.Send(onlineFlap) - // Client wants to know the versions of all of the services offered + // Client wants to know the ServiceVersions of all of the services offered case 0x17: versionsSnac := oscar.NewSNAC(1, 0x18) - for family, version := range versions { + for family, version := range ServiceVersions { versionsSnac.Data.WriteUint16(family) versionsSnac.Data.WriteUint16(version) } diff --git a/0x02_location_services.go b/services/0x02_location_services.go similarity index 99% rename from 0x02_location_services.go rename to services/0x02_location_services.go index 065cb1e..4d9a3e8 100644 --- a/0x02_location_services.go +++ b/services/0x02_location_services.go @@ -1,4 +1,4 @@ -package main +package services import ( "aim-oscar/aimerror" diff --git a/0x03_buddy_list_management.go b/services/0x03_buddy_list_management.go similarity index 99% rename from 0x03_buddy_list_management.go rename to services/0x03_buddy_list_management.go index 9353503..6d7326b 100644 --- a/0x03_buddy_list_management.go +++ b/services/0x03_buddy_list_management.go @@ -1,4 +1,4 @@ -package main +package services import ( "aim-oscar/aimerror" diff --git a/0x04_ICBM.go b/services/0x04_ICBM.go similarity index 99% rename from 0x04_ICBM.go rename to services/0x04_ICBM.go index 12c0ad0..c21692a 100644 --- a/0x04_ICBM.go +++ b/services/0x04_ICBM.go @@ -1,4 +1,4 @@ -package main +package services import ( "aim-oscar/aimerror" diff --git a/0x17_authorization_registration_service.go b/services/0x17_authorization_registration_service.go similarity index 96% rename from 0x17_authorization_registration_service.go rename to services/0x17_authorization_registration_service.go index b177315..a7c41f5 100644 --- a/0x17_authorization_registration_service.go +++ b/services/0x17_authorization_registration_service.go @@ -1,4 +1,4 @@ -package main +package services import ( "bytes" @@ -26,7 +26,9 @@ type AuthorizationCookie struct { X string } -type AuthorizationRegistrationService struct{} +type AuthorizationRegistrationService struct { + ServerHostname string +} 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 @@ -174,7 +176,7 @@ func (a *AuthorizationRegistrationService) HandleSNAC(ctx context.Context, db *b // Send BOS response + cookie authSnac := oscar.NewSNAC(0x17, 0x3) authSnac.Data.WriteBinary(usernameTLV) - authSnac.Data.WriteBinary(oscar.NewTLV(0x5, []byte(SRV_ADDRESS))) + authSnac.Data.WriteBinary(oscar.NewTLV(0x5, []byte(a.ServerHostname))) cookie, err := json.Marshal(AuthorizationCookie{ UIN: user.UIN, diff --git a/session_manager.go b/session_manager.go new file mode 100644 index 0000000..7f72015 --- /dev/null +++ b/session_manager.go @@ -0,0 +1,42 @@ +package main + +import ( + "aim-oscar/oscar" + "sync" +) + +type SessionManager struct { + sessions map[string]*oscar.Session + mutex *sync.RWMutex +} + +func NewSessionManager() *SessionManager { + sm := &SessionManager{ + sessions: make(map[string]*oscar.Session), + mutex: &sync.RWMutex{}, + } + return sm +} + +func (sm *SessionManager) SetSession(username string, session *oscar.Session) { + sm.mutex.Lock() + sm.sessions[username] = session + sm.mutex.Unlock() +} + +func (sm *SessionManager) GetSession(username string) *oscar.Session { + sm.mutex.RLock() + s, ok := sm.sessions[username] + sm.mutex.RUnlock() + + if ok { + return s + } + return nil +} + +func (sm *SessionManager) RemoveSession(username string) { + sm.mutex.Lock() + sm.sessions[username] = nil + sm.mutex.Unlock() +}