package main import ( "aim-oscar/models" "aim-oscar/oscar" "aim-oscar/util" "bytes" "context" "database/sql" "fmt" "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" "github.com/uptrace/bun/driver/sqliteshim" "github.com/uptrace/bun/extra/bundebug" ) const ( SRV_HOST = "10.0.1.2" SRV_PORT = "5190" SRV_ADDRESS = SRV_HOST + ":" + SRV_PORT ) var services map[uint16]Service func init() { services = make(map[uint16]Service) } func RegisterService(family uint16, service Service) { services[family] = service } func main() { // Set up the DB sqldb, err := sql.Open(sqliteshim.ShimName, "file::memory:?cache=shared") if err != nil { 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), (*models.Message)(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 { panic(err) } listener, err := net.Listen("tcp", SRV_ADDRESS) if err != nil { fmt.Println("Error listening: ", err.Error()) os.Exit(1) } defer listener.Close() 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) } 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) } if flap.Header.Channel == 1 { // Is this a hello? if bytes.Equal(flap.Data.Bytes(), []byte{0, 0, 0, 1}) { 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 { snac := &oscar.SNAC{} err := snac.UnmarshalBinary(flap.Data.Bytes()) util.PanicIfError(err) fmt.Printf("%+v\n", snac) if tlvs, err := oscar.UnmarshalTLVs(snac.Data.Bytes()); err == nil { for _, tlv := range tlvs { fmt.Printf("%+v\n", tlv) } } else { fmt.Printf("%s\n\n", util.PrettyBytes(snac.Data.Bytes())) } if service, ok := services[snac.Header.Family]; ok { newCtx, err := service.HandleSNAC(ctx, db, snac) util.PanicIfError(err) return newCtx } } else if flap.Header.Channel == 4 { session.Disconnect() handleCloseFn(ctx, session) } return ctx } handler := oscar.NewHandler(handleFn, handleCloseFn) RegisterService(0x17, &AuthorizationRegistrationService{}) RegisterService(0x01, &GenericServiceControls{}) RegisterService(0x04, &ICBM{}) exitChan := make(chan os.Signal, 1) signal.Notify(exitChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGABRT) go func() { <-exitChan fmt.Println("Shutting down") os.Exit(1) }() fmt.Println("OSCAR listening on " + SRV_ADDRESS) for { conn, err := listener.Accept() if err != nil { fmt.Println("Error accepting connection: ", err.Error()) os.Exit(1) } log.Printf("Connection from %v", conn.RemoteAddr()) go handler.Handle(conn) } }