diff --git a/0x01_generic_service_controls.go b/0x01_generic_service_controls.go index fe63bb7..e3b82b7 100644 --- a/0x01_generic_service_controls.go +++ b/0x01_generic_service_controls.go @@ -78,14 +78,19 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna onlineSnac.Data.WriteString(uin) onlineSnac.Data.WriteUint16(0) // warning level + user.Status = "active" + if err := user.Update(ctx, db); err != nil { + return ctx, errors.Wrap(err, "could not set user as active") + } + 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 + oscar.NewTLV(0x01, util.Dword(0x80)), // User Class + oscar.NewTLV(0x06, util.Dword(0x0001|0x0100)), // User Status + oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(SRV_HOST)))), // 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 + oscar.NewTLV(0x05, util.Dword(uint32(user.CreatedAt.Unix()))), // Member since } onlineSnac.Data.WriteUint16(uint16(len(tlvs))) diff --git a/main.go b/main.go index f18f457..56757e9 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "syscall" "time" + "github.com/pkg/errors" "github.com/uptrace/bun" "github.com/uptrace/bun/dbfixture" "github.com/uptrace/bun/dialect/sqlitedialect" @@ -68,7 +69,19 @@ func main() { } defer listener.Close() - handler := oscar.NewHandler(func(ctx context.Context, flap *oscar.FLAP) context.Context { + handleCloseFn := func(ctx context.Context, session *oscar.Session) { + log.Printf("%v disconnected", session.RemoteAddr()) + + user := models.UserFromContext(ctx) + if user != nil { + user.Status = "offline" + if err := user.Update(ctx, db); err != nil { + log.Print(errors.Wrap(err, "could not set user as active")) + } + } + } + + handleFn := func(ctx context.Context, flap *oscar.FLAP) context.Context { session, err := oscar.SessionFromContext(ctx) if err != nil { util.PanicIfError(err) @@ -76,6 +89,8 @@ func main() { if user := models.UserFromContext(ctx); user != nil { fmt.Printf("%s (%v) ->\n%+v\n", user.Username, session.RemoteAddr(), flap) + user.LastActivityAt = time.Now() + ctx = models.NewContextWithUser(ctx, user) } else { fmt.Printf("%v ->\n%+v\n", session.RemoteAddr(), flap) } @@ -123,10 +138,13 @@ func main() { } } else if flap.Header.Channel == 4 { session.Disconnect() + handleCloseFn(ctx, session) } return ctx - }) + } + + handler := oscar.NewHandler(handleFn, handleCloseFn) RegisterService(0x17, &AuthorizationRegistrationService{}) RegisterService(0x01, &GenericServiceControls{}) diff --git a/models/User.go b/models/User.go index d339249..950d39f 100644 --- a/models/User.go +++ b/models/User.go @@ -3,18 +3,23 @@ package models import ( "context" "database/sql" + "time" "github.com/pkg/errors" "github.com/uptrace/bun" ) type User struct { - bun.BaseModel `bun:"table:users"` - UIN int `bun:",pk,autoincrement"` - Email string `bun:",unique"` - Username string `bun:",unique"` - Password string - Cipher string + bun.BaseModel `bun:"table:users"` + UIN int `bun:",pk,autoincrement"` + Email string `bun:",unique"` + Username string `bun:",unique"` + Password string + Cipher string + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` + UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` + Status string + LastActivityAt time.Time `bin:"-"` } type userKey string diff --git a/oscar/server.go b/oscar/server.go index 5d5acc3..cb586bb 100644 --- a/oscar/server.go +++ b/oscar/server.go @@ -13,12 +13,17 @@ import ( ) type HandlerFunc func(context.Context, *FLAP) context.Context +type HandleCloseFn func(context.Context, *Session) -type Handler struct{ handle HandlerFunc } +type Handler struct { + handle HandlerFunc + handleClose HandleCloseFn +} -func NewHandler(fn HandlerFunc) *Handler { +func NewHandler(fn HandlerFunc, handleClose HandleCloseFn) *Handler { return &Handler{ - handle: fn, + handle: fn, + handleClose: handleClose, } } @@ -40,8 +45,8 @@ func (h *Handler) Handle(conn net.Conn) { n, err := conn.Read(buf) if err != nil && err != io.EOF { if strings.Contains(err.Error(), "use of closed network connection") { - log.Printf("%v disconnected", conn.RemoteAddr()) session.Disconnect() + h.handleClose(ctx, session) return }