diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/battle.go | 1043 | ||||
-rw-r--r-- | src/bot.go | 21 | ||||
-rw-r--r-- | src/db.go | 10 | ||||
-rw-r--r-- | src/main.go | 25 | ||||
-rw-r--r-- | src/r2.go | 3 | ||||
-rw-r--r-- | src/user.go | 60 |
6 files changed, 1026 insertions, 136 deletions
diff --git a/src/battle.go b/src/battle.go index 7aa949a..af7043b 100644 --- a/src/battle.go +++ b/src/battle.go @@ -4,6 +4,7 @@ import ( "fmt" "html/template" "log" + "math/rand" "net/http" "os" "strconv" @@ -11,16 +12,20 @@ import ( "time" "github.com/gorilla/mux" + "github.com/radareorg/r2pipe-go" ) type Battle struct { - ID int - Name string - Bots []Bot - Owners []User - Public bool - Archs []Arch - Bits []Bit + ID int + Name string + Bots []Bot + Owners []User + Public bool + Archs []Arch + Bits []Bit + RawOutput string + MaxRounds int + ArenaSize int } ////////////////////////////////////////////////////////////////////////////// @@ -30,14 +35,18 @@ func BattleGetAll() ([]Battle, error) { return globalState.GetAllBattles() } -func BattleCreate(name string, public bool) (int, error) { - return globalState.InsertBattle(Battle{Name: name, Public: public}) +func BattleCreate(battle Battle, owner User) (int, error) { + return globalState.InsertBattle(battle, owner) } func BattleLinkBot(botid int, battleid int) error { return globalState.LinkBotBattle(botid, battleid) } +func BattleUnlinkAllBotsForUser(userid int, battleid int) error { + return globalState.UnlinkAllBotsForUserFromBattle(userid, battleid) +} + func BattleGetByIdDeep(id int) (Battle, error) { return globalState.GetBattleByIdDeep(id) } @@ -54,44 +63,130 @@ func BattleLinkBitIDs(battleid int, bitIDs []int) error { return globalState.LinkBitIDsToBattle(battleid, bitIDs) } +func BattleLinkOwnerIDs(battleid int, ownerIDs []int) error { + return globalState.LinkOwnerIDsToBattle(battleid, ownerIDs) +} + +func BattleSaveRawOutput(battleid int, rawOutput string) error { + return globalState.UpdateBattleRawOutput(battleid, rawOutput) +} + +func BattleDeleteID(battleid int) error { + return globalState.DeleteBattleByID(battleid) +} + ////////////////////////////////////////////////////////////////////////////// // DATABASE -func (s *State) InsertBattle(battle Battle) (int, error) { - res, err := s.db.Exec("INSERT INTO battles VALUES(NULL,?,?,?);", time.Now(), battle.Name, battle.Public) +func (s *State) InsertBattle(battle Battle, owner User) (int, error) { + // create the battle + res, err := s.db.Exec(` + INSERT INTO battles + VALUES(NULL,?,?,?,?,?,?) + `, time.Now(), + battle.Name, + battle.Public, + battle.RawOutput, + battle.MaxRounds, + battle.ArenaSize) + if err != nil { + log.Println(err) return -1, err } var id int64 if id, err = res.LastInsertId(); err != nil { + log.Println(err) return -1, err } + + // insert the owner into the battle_owner rel + _, err = s.db.Exec("INSERT INTO owner_battle_rel VALUES (?, ?)", owner.ID, battle.ID) + if err != nil { + log.Println(err) + return -1, err + } + return int(id), nil } func (s *State) UpdateBattle(battle Battle) error { - _, err := s.db.Exec("UPDATE battles SET name=?, public=? WHERE id=?", battle.Name, battle.Public, battle.ID) + log.Println("Updating battle:") + log.Println(battle.ArenaSize) + _, err := s.db.Exec(` + UPDATE battles + SET name=?, public=?, arena_size=?, max_rounds=? + WHERE id=?`, + battle.Name, + battle.Public, + battle.ArenaSize, + battle.MaxRounds, + battle.ID) if err != nil { + log.Println(err) return err } + log.Println("Done updating") return nil } func (s *State) LinkBotBattle(botid int, battleid int) error { _, err := s.db.Exec("INSERT INTO bot_battle_rel VALUES (?, ?)", botid, battleid) if err != nil { - log.Println("Error linking bot to battle: ", err) + log.Println(err) + return err + } else { + return nil + } +} + +func (s *State) LinkOwnerBattle(battleid int, ownerid int) error { + _, err := s.db.Exec("INSERT INTO owner_battle_rel VALUES (?, ?)", ownerid, battleid) + if err != nil { + log.Println(err) return err } else { return nil } } +func (s *State) UnlinkAllBotsForUserFromBattle(userid int, battleid int) error { + // get a user with the given id + // for all of their bots + // delete the bots from the bot_battle relation + + // there are some joins to get through the following links: + // bot_battle_rel.bot_id + // -> bot.id + // -> user_bot_rel.bot_id + // -> user_bot_rel.user_id + // -> user.id + + // delete preexisting links + _, err := s.db.Exec(` + DELETE FROM bot_battle_rel + WHERE bot_id IN + (SELECT b.id + FROM bot_battle_rel bb_rel + JOIN bots b ON b.id = bb_rel.bot_id + JOIN user_bot_rel ub_rel ON ub_rel.bot_id = b.id + JOIN users u ON u.id = ub_rel.user_id + WHERE u.id=?)`, userid) + + if err != nil { + log.Println(err) + return err + } + + return nil +} + func (s *State) LinkArchIDsToBattle(battleid int, archIDs []int) error { // delete preexisting links - _, err := s.db.Exec("DELETE FROM arch_battle_rel WHERE battle_id=?;", battleid) + _, err := s.db.Exec("DELETE FROM arch_battle_rel WHERE battle_id=?", battleid) if err != nil { + log.Println(err) return err } @@ -109,7 +204,7 @@ func (s *State) LinkArchIDsToBattle(battleid int, archIDs []int) error { _, err = s.db.Exec(query) if err != nil { - log.Println("LinkArchIDsToBattle err: ", err) + log.Println(err) return err } else { return nil @@ -118,8 +213,9 @@ func (s *State) LinkArchIDsToBattle(battleid int, archIDs []int) error { func (s *State) LinkBitIDsToBattle(battleid int, bitIDs []int) error { // delete preexisting links - _, err := s.db.Exec("DELETE FROM bit_battle_rel WHERE battle_id=?;", battleid) + _, err := s.db.Exec("DELETE FROM bit_battle_rel WHERE battle_id=?", battleid) if err != nil { + log.Println(err) return err } @@ -137,7 +233,36 @@ func (s *State) LinkBitIDsToBattle(battleid int, bitIDs []int) error { _, err = s.db.Exec(query) if err != nil { - log.Println("LinkBitIDsToBattle err: ", err) + log.Println(err) + return err + } else { + return nil + } +} + +func (s *State) LinkOwnerIDsToBattle(battleid int, ownerIDs []int) error { + // delete preexisting links + _, err := s.db.Exec("DELETE FROM owner_battle_rel WHERE battle_id=?", battleid) + if err != nil { + log.Println(err) + return err + } + + // yes, we're building this by hand, but as we only insert int's I'm just confident that whoever + // gets some sqli here just deserves it :D + query := "INSERT INTO owner_battle_rel (user_id, battle_id) VALUES" + for idx, id := range ownerIDs { + query += fmt.Sprintf("(%d, %d)", id, battleid) + if idx != len(ownerIDs)-1 { + query += ", " + } + } + query += ";" + log.Println(query) + + _, err = s.db.Exec(query) + if err != nil { + log.Println(err) return err } else { return nil @@ -148,6 +273,7 @@ func (s *State) GetAllBattles() ([]Battle, error) { rows, err := s.db.Query("SELECT id, name FROM battles;") defer rows.Close() if err != nil { + log.Println(err) return nil, err } @@ -171,6 +297,9 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { var battleid int var battlename string var battlepublic bool + var battlerawoutput string + var battlemaxrounds int + var battlearenasize int var botids string var botnames string @@ -184,21 +313,41 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { var bitids string var bitnames string + var ownerids string + var ownernames string + // battles have associated bots and users, we're fetching 'em all! // This fetches the battles and relates the associated bots, users, archs and bits + // TODO(emile): go deeper! we could fetch battle -> bot -> arch (so fetching the linked arch + // for the given bot) + + // COALESCE is used to set default values + // TODO(emile): do fancy migrations instead of the COALESCE stuff for setting defaults if + // no value is set beforehand + err := s.db.QueryRow(` SELECT DISTINCT - ba.id, ba.name, ba.public, + ba.id, ba.name, ba.public, + COALESCE(ba.raw_output, ""), + COALESCE(ba.max_rounds, 100), + COALESCE(ba.arena_size, 4096), + COALESCE(group_concat(DISTINCT bb.bot_id), ""), COALESCE(group_concat(DISTINCT bo.name), ""), + COALESCE(group_concat(DISTINCT ub.user_id), ""), COALESCE(group_concat(DISTINCT us.name), ""), + COALESCE(group_concat(DISTINCT ab.arch_id), ""), COALESCE(group_concat(DISTINCT ar.name), ""), + COALESCE(group_concat(DISTINCT bitbat.bit_id), ""), - COALESCE(group_concat(DISTINCT bi.name), "") + COALESCE(group_concat(DISTINCT bi.name), ""), + + COALESCE(group_concat(DISTINCT ownerbat.user_id), ""), + COALESCE(group_concat(DISTINCT owner.name), "") FROM battles ba LEFT JOIN bot_battle_rel bb ON bb.battle_id = ba.id @@ -213,26 +362,17 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { LEFT JOIN bit_battle_rel bitbat ON bitbat.battle_id = ba.id LEFT JOIN bits bi ON bi.id = bitbat.bit_id + LEFT JOIN owner_battle_rel ownerbat ON ownerbat.battle_id = ba.id + LEFT JOIN users owner ON owner.id = ownerbat.user_id + WHERE ba.id=? GROUP BY ba.id; - `, id).Scan(&battleid, &battlename, &battlepublic, &botids, &botnames, &userids, &usernames, &archids, &archnames, &bitids, &bitnames) + `, id).Scan(&battleid, &battlename, &battlepublic, &battlerawoutput, &battlemaxrounds, &battlearenasize, &botids, &botnames, &userids, &usernames, &archids, &archnames, &bitids, &bitnames, &ownerids, &ownernames) if err != nil { - log.Println("Err making GetBattleByID query: ", err) + log.Println(err) return Battle{}, err } - log.Println("battleid: ", battleid) - log.Println("battlename: ", battlename) - log.Println("battlepublic: ", battlepublic) - log.Println("botids: ", botids) - log.Println("botnames: ", botnames) - log.Println("userids: ", userids) - log.Println("usernames: ", usernames) - log.Println("archids: ", archids) - log.Println("archnames: ", archnames) - log.Println("bitids: ", bitids) - log.Println("bitnames: ", bitnames) - // The below is a wonderful examle of how golang could profit from macros // I should just have done this all in common lisp tbh. @@ -246,10 +386,10 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { var bots []Bot if botIDList[0] != "" { - for i, _ := range botIDList { + for i := range botIDList { id, err := strconv.Atoi(botIDList[i]) if err != nil { - log.Println("Err handling bots: ", err) + log.Println(err) return Battle{}, err } bots = append(bots, Bot{id, botNameList[i], "", []User{}, []Arch{}, []Bit{}}) @@ -264,10 +404,10 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { var users []User if userIDList[0] != "" { - for i, _ := range userIDList { + for i := range userIDList { id, err := strconv.Atoi(userIDList[i]) if err != nil { - log.Println("Err handling users: ", err) + log.Println(err) return Battle{}, err } users = append(users, User{id, userNameList[i], []byte{}}) @@ -282,10 +422,10 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { var archs []Arch if archIDList[0] != "" { - for i, _ := range archIDList { + for i := range archIDList { id, err := strconv.Atoi(archIDList[i]) if err != nil { - log.Println("Err handling archs: ", err) + log.Println(err) return Battle{}, err } archs = append(archs, Arch{id, archNameList[i], true}) @@ -300,10 +440,10 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { var bits []Bit if bitIDList[0] != "" { - for i, _ := range bitIDList { + for i := range bitIDList { id, err := strconv.Atoi(bitIDList[i]) if err != nil { - log.Println("Err handling bits: ", err) + log.Println(err) return Battle{}, err } bits = append(bits, Bit{id, bitNameList[i], true}) @@ -312,22 +452,74 @@ func (s *State) GetBattleByIdDeep(id int) (Battle, error) { bits = []Bit{} } - // return it all! - switch { - case err != nil: - log.Println("Overall err in the GetBattleByID func: ", err) - return Battle{}, err - default: - return Battle{ - ID: battleid, - Name: battlename, - Bots: bots, - Owners: users, - Public: battlepublic, - Archs: archs, - Bits: bits, - }, nil + // assemble the owners + ownerIDList := strings.Split(ownerids, ",") + ownerNameList := strings.Split(ownernames, ",") + + var owners []User + if ownerIDList[0] != "" { + for i := range ownerIDList { + id, err := strconv.Atoi(ownerIDList[i]) + if err != nil { + log.Println(err) + return Battle{}, err + } + owners = append(owners, User{id, ownerNameList[i], nil}) + } + } else { + owners = []User{} + } + + return Battle{ + ID: battleid, + Name: battlename, + Bots: bots, + Owners: owners, + Public: battlepublic, + Archs: archs, + Bits: bits, + RawOutput: battlerawoutput, + MaxRounds: battlemaxrounds, + ArenaSize: battlearenasize, + }, nil +} + +func (s *State) UpdateBattleRawOutput(battleid int, rawOutput string) error { + _, err := s.db.Exec("UPDATE battles SET raw_output=? WHERE id=?", rawOutput, battleid) + if err != nil { + log.Println(err) + return err + } + return nil +} + +// This deletes a battle and all links to users, bots, architectures and bits +func (s *State) DeleteBattleByID(battleid int) error { + _, err := s.db.Exec(` + DELETE FROM battles WHERE battleid = ?; + + DELETE FROM user_battle_rel WHERE battleid = ?; + DELETE FROM bot_battle_rel WHERE battleid = ?; + DELETE FROM arch_battle_rel WHERE battleid = ?; + DELETE FROM bit_battle_rel WHERE battleid = ?; + `, battleid, battleid, battleid, battleid, battleid) + + if err != nil { + log.Println(err) + return err + } + + _, err = s.db.Exec(` + DELETE FROM battles + WHERE battleid = ? + `, battleid) + + if err != nil { + log.Println(err) + return err } + + return nil } ////////////////////////////////////////////////////////////////////////////// @@ -346,18 +538,25 @@ func battlesHandler(w http.ResponseWriter, r *http.Request) { } data["pagelinknext"] = []Link{ {Name: "new", Target: "/new"}, + {Name: "quick", Target: "/quick"}, } // sessions session, _ := globalState.sessions.Get(r, "session") - username := session.Values["username"].(string) + username := session.Values["username"] - // get the user - user, err := UserGetUserFromUsername(username) - if err != nil { + if username == nil { http.Redirect(w, r, "/login", http.StatusSeeOther) return } else { + // get the user + user, err := UserGetUserFromUsername(username.(string)) + if err != nil { + log.Println(err) + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + data["user"] = user } @@ -368,7 +567,7 @@ func battlesHandler(w http.ResponseWriter, r *http.Request) { // get the template t, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templatesPath)) if err != nil { - log.Printf("Error reading the template Path: %s/*.html", templatesPath) + log.Println(err) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Error reading template file")) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -400,6 +599,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { data["pagelink2"] = Link{Name: "new", Target: "/new"} data["pagelink2options"] = []Link{ {Name: "list", Target: ""}, + {Name: "quick", Target: "/quick"}, } // display errors passed via query parameters @@ -411,6 +611,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { // get data needed user, err := UserGetUserFromUsername(username) if err != nil { + log.Println(err) data["err"] = "Could not fetch the user" } else { data["user"] = user @@ -418,6 +619,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { archs, err := ArchGetAll() if err != nil { + log.Println(err) data["err"] = "Could not fetch the archs" } else { data["archs"] = archs @@ -425,15 +627,24 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { bits, err := BitGetAll() if err != nil { + log.Println(err) data["err"] = "Could not fetch the bits" } else { data["bits"] = bits } + users, err := UserGetAll() + if err != nil { + log.Println(err) + data["err"] = "Could not fetch all users" + } else { + data["users"] = users + } + // get the template t, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templatesPath)) if err != nil { - log.Printf("Error reading the template Path: %s/*.html", templatesPath) + log.Println(err) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 - Error reading template file")) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -441,12 +652,47 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { } // exec! - t.ExecuteTemplate(w, "battleNew", data) + err = t.ExecuteTemplate(w, "battleNew", data) + if err != nil { + log.Println(err) + } case "POST": + data := map[string]interface{}{} + + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"].(string) + + // get data needed + user, err := UserGetUserFromUsername(username) + if err != nil { + log.Println(err) + data["err"] = "Could not fetch the user" + } else { + data["user"] = user + } + // parse the post parameters r.ParseForm() name := r.Form.Get("name") + arenasize, err := strconv.Atoi(r.Form.Get("arena-size")) + if err != nil { + // TODO(emile): use the log_and_redir function in here (and the surrounding code) + + log.Println(err) + msg := "ERROR: Invalid arch id" + http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) + return + } + maxrounds, err := strconv.Atoi(r.Form.Get("max-rounds")) + if err != nil { + // TODO(emile): use the log_and_redir function in here (and the surrounding code) + + log.Println(err) + msg := "ERROR: Invalid arch id" + http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) + return + } var public bool query_public := r.Form.Get("public") @@ -458,10 +704,11 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { var archIDs []int var bitIDs []int - for k, _ := range r.Form { + for k := range r.Form { if strings.HasPrefix(k, "arch-") { id, err := strconv.Atoi(strings.TrimPrefix(k, "arch-")) if err != nil { + log.Println(err) msg := "ERROR: Invalid arch id" http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) return @@ -471,6 +718,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(k, "bit-") { id, err := strconv.Atoi(strings.TrimPrefix(k, "bit-")) if err != nil { + log.Println(err) msg := "ERROR: Invalid bit id" http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) return @@ -482,9 +730,21 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { if name != "" { // create the battle itself log.Println("Creating battle") - battleid, err := BattleCreate(name, public) + newbattle := Battle{ + 0, + name, + nil, + nil, + public, + nil, + nil, + "", + maxrounds, + arenasize, + } + battleid, err := BattleCreate(newbattle, user) if err != nil { - log.Println("Error creating the battle using BattleCreate(): ", err) + log.Println(err) msg := "ERROR: Could not create due to internal reasons" http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) return @@ -493,7 +753,7 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { // link archs to battle err = BattleLinkArchIDs(battleid, archIDs) if err != nil { - log.Println("Error linking the arch ids to the battle: ", err) + log.Println(err) msg := "ERROR: Could not create due to internal reasons" http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) return @@ -502,7 +762,16 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { // link bits to battle err = BattleLinkBitIDs(battleid, bitIDs) if err != nil { - log.Println("Error linking the bit ids to the battle: ", err) + log.Println(err) + msg := "ERROR: Could not create due to internal reasons" + http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) + return + } + + // link owner to battle + err = BattleLinkOwnerIDs(battleid, []int{user.ID}) + if err != nil { + log.Println(err) msg := "ERROR: Could not create due to internal reasons" http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) return @@ -520,14 +789,171 @@ func battleNewHandler(w http.ResponseWriter, r *http.Request) { } } -// TODO(emile): add user creating battle as default owner -// TODO(emile): allow adding other users as owners to battles -// TODO(emile): implement submitting bots -// TODO(emile): implement running the battle -// TODO(emile): add a "start battle now" button -// TODO(emile): add a "battle starts at this time" field into the battle -// TODO(emile): figure out how time is stored and restored with the db -// TODO(emile): do some magic to display the current fight backlog with all info +func battleQuickHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": + // define data + data := map[string]interface{}{} + data["version"] = os.Getenv("VERSION") + + // breadcrumb foo + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"].(string) + data["pagelink1"] = Link{Name: "battle", Target: "/battle"} + data["pagelink1options"] = []Link{ + {Name: "user", Target: "/user"}, + {Name: "bot", Target: "/bot"}, + } + data["pagelink2"] = Link{Name: "quick", Target: "/quick"} + data["pagelink2options"] = []Link{ + {Name: "new", Target: "/new"}, + {Name: "list", Target: ""}, + } + + // display errors passed via query parameters + queryres := r.URL.Query().Get("err") + if queryres != "" { + data["res"] = queryres + } + + // get data needed + user, err := UserGetUserFromUsername(username) + if err != nil { + log.Println(err) + data["err"] = "Could not fetch the user" + } else { + data["user"] = user + } + + // essentiall... ...the list of all bots from which the user can select two that shall + // battle! + bots, err := globalState.GetAllBotsWithUsers() + data["bots"] = bots + + // get the template + t, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templatesPath)) + if err != nil { + log.Println(err) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Error reading template file")) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // exec! + t.ExecuteTemplate(w, "battleQuick", data) + + case "POST": + data := map[string]interface{}{} + + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"].(string) + + // get data needed + user, err := UserGetUserFromUsername(username) + if err != nil { + log.Println(err) + data["err"] = "Could not fetch the user" + } else { + data["user"] = user + } + + // parse the post parameters + r.ParseForm() + + var public bool + query_public := r.Form.Get("public") + if query_public == "on" { + public = true + } + + arenasize, err := strconv.Atoi(r.Form.Get("arena-size")) + if err != nil { + // TODO(emile): use the log_and_redir function in here (and the surrounding code) + + log.Println(err) + msg := "ERROR: Invalid arch id" + http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) + return + } + maxrounds, err := strconv.Atoi(r.Form.Get("max-rounds")) + if err != nil { + // TODO(emile): use the log_and_redir function in here (and the surrounding code) + + log.Println(err) + msg := "ERROR: Invalid arch id" + http.Redirect(w, r, fmt.Sprintf("/battle/new?res=%s", msg), http.StatusSeeOther) + return + } + + // gather the information from the arch and bit selection + var botIDs []int + + for k := range r.Form { + if strings.HasPrefix(k, "bot-") { + id, err := strconv.Atoi(strings.TrimPrefix(k, "bot-")) + if err != nil { + log.Println(err) + msg := "ERROR: Invalid bot id" + http.Redirect(w, r, fmt.Sprintf("/battle/quick?res=%s", msg), http.StatusSeeOther) + return + } + botIDs = append(botIDs, id) + } + } + + // create the battle itself + log.Println("Creating battle") + newbattle := Battle{ + 0, + fmt.Sprintf("quick-%d", rand.Intn(10000)), + nil, + nil, + public, + nil, + nil, + "", + maxrounds, + arenasize, + } + battleid, err := BattleCreate(newbattle, user) + if err != nil { + log.Println(err) + msg := "ERROR: Could not create due to internal reasons" + http.Redirect(w, r, fmt.Sprintf("/battle/quick?res=%s", msg), http.StatusSeeOther) + return + } + + // allow all archs and all bits + + // link bots to battle + + http.Redirect(w, r, fmt.Sprintf("/battle/%d", battleid), http.StatusSeeOther) + + // // link archs to battle + // err = BattleLinkArchIDs(battleid, archIDs) + // if err != nil { + // log.Println(err) + // msg := "ERROR: Could not create due to internal reasons" + // http.Redirect(w, r, fmt.Sprintf("/battle/quick?res=%s", msg), http.StatusSeeOther) + // return + // } + + // // link bits to battle + // err = BattleLinkBitIDs(battleid, bitIDs) + // if err != nil { + // log.Println(err) + // msg := "ERROR: Could not create due to internal reasons" + // http.Redirect(w, r, fmt.Sprintf("/battle/quick?res=%s", msg), http.StatusSeeOther) + // return + // } + + // http.Redirect(w, r, "/battle", http.StatusSeeOther) + return + default: + http.Redirect(w, r, "/", http.StatusMethodNotAllowed) + } +} func battleSingleHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -539,6 +965,9 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { return } + // A partially filled format string (the reason for the redirect is still to be filled later) + redir_target := fmt.Sprintf("/battle/%d?res=%%s", battleid) + switch r.Method { case "GET": // define data @@ -557,16 +986,33 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { } session, _ := globalState.sessions.Get(r, "session") - username := session.Values["username"].(string) + username := session.Values["username"] - viewer, err := UserGetUserFromUsername(username) + if username == nil { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + viewer, err := UserGetUserFromUsername(username.(string)) if err != nil { - data["err"] = "Could not get the id four your username... Please contact an admin" + log_and_redir_with_msg(w, r, err, redir_target, "Could not get the id for your username") + return } data["user"] = viewer + users, err := UserGetAll() + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target, "Could not get the users") + return + } + data["users"] = users + // get the battle including it's users, bots, archs, bits battle, err := BattleGetByIdDeep(int(battleid)) + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target, "Could not get the battle given the id provided") + return + } data["battle"] = battle data["botAmount"] = len(battle.Bots) data["battleCount"] = (len(battle.Bots) * len(battle.Bots)) * 2 @@ -582,10 +1028,9 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { data["pagelink2options"] = opts // get the bots of the user viewing the page, as they might want to submit them - myBots, err := UserGetBotsUsingUsername(username) + myBots, err := UserGetBotsUsingUsername(username.(string)) if err != nil { - log.Println("err: ", err) - http.Redirect(w, r, fmt.Sprintf("/battle/%d", battleid), http.StatusSeeOther) + log_and_redir_with_msg(w, r, err, redir_target, "Could not get your bots") return } data["myBots"] = myBots @@ -593,7 +1038,8 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { // get all architectures and set the enable flag on the ones that are enabled in the battle archs, err := ArchGetAll() if err != nil { - data["err"] = "Could not fetch the archs" + log_and_redir_with_msg(w, r, err, redir_target, "Could not get your bots") + return } else { data["archs"] = archs } @@ -609,7 +1055,8 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { // get all bits and set the enable flag on the ones that are enabled in the battle bits, err := BitGetAll() if err != nil { - data["err"] = "Could not fetch the bits" + log_and_redir_with_msg(w, r, err, redir_target, "Could not fetch the bits") + return } else { data["bits"] = bits } @@ -644,7 +1091,10 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { } // exec! - t.ExecuteTemplate(w, "battleSingle", data) + err = t.ExecuteTemplate(w, "battleSingle", data) + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target, "err rendering template") + } case "POST": log.Println("POST!") @@ -687,10 +1137,29 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { // at this point, we're sure the user is allowed to edit the battle + data := map[string]interface{}{} + + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"].(string) + + // get data needed + user, err := UserGetUserFromUsername(username) + if err != nil { + log.Println(err) + data["err"] = "Could not fetch the user" + } else { + data["user"] = user + } + r.ParseForm() log.Println("r.Form: ", r.Form) form_name := r.Form.Get("name") + arenasize, err := strconv.Atoi(r.Form.Get("arena-size")) + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target, "Invalid Arena size") + return + } var public bool if r.Form.Get("public") == "on" { @@ -700,13 +1169,15 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { // gather the information from the arch and bit selection var archIDs []int var bitIDs []int + var ownerIDs []int - for k, _ := range r.Form { + log.Println(r.Form) + + for k := range r.Form { if strings.HasPrefix(k, "arch-") { id, err := strconv.Atoi(strings.TrimPrefix(k, "arch-")) if err != nil { - msg := "ERROR: Invalid arch id" - http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s#settings", battleid, msg), http.StatusSeeOther) + log_and_redir_with_msg(w, r, err, redir_target, "Invalid Arch ID") return } archIDs = append(archIDs, id) @@ -714,41 +1185,62 @@ func battleSingleHandler(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(k, "bit-") { id, err := strconv.Atoi(strings.TrimPrefix(k, "bit-")) if err != nil { - msg := "ERROR: Invalid bit id" - http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s#settings", battleid, msg), http.StatusSeeOther) + log_and_redir_with_msg(w, r, err, redir_target, "Invalid Bit ID") return } bitIDs = append(bitIDs, id) } + if strings.HasPrefix(k, "owner-") { + id, err := strconv.Atoi(strings.TrimPrefix(k, "owner-")) + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target, "Invalid Owner ID") + return + } + ownerIDs = append(ownerIDs, id) + } + } + + // CHECK THAT THE USER REQUESTING CHANGES IS ALREADY PART OF THE OWNERS + allowedToEdit := false + for _, ownerID := range ownerIDs { + if user.ID == ownerID { + allowedToEdit = true + } } + if allowedToEdit == false { + log_and_redir_with_msg(w, r, err, redir_target+"#settings", "You aren't an owner and aren't allowed to edit the settings") + return + } + + // DATABASE MANIPUTLATION BELOW // link archs to battle err = BattleLinkArchIDs(battleid, archIDs) if err != nil { - log.Println("Error linking the arch ids to the battle: ", err) - msg := "ERROR: Could not create due to internal reasons" - http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s#settings", battleid, msg), http.StatusSeeOther) + log_and_redir_with_msg(w, r, err, redir_target+"#settings", "Could not link arch id to battle") return } // link bits to battle err = BattleLinkBitIDs(battleid, bitIDs) if err != nil { - log.Println("Error linking the bit ids to the battle: ", err) - msg := "ERROR: Could not create due to internal reasons" - http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s#settings", battleid, msg), http.StatusSeeOther) + log_and_redir_with_msg(w, r, err, redir_target+"#settings", "Could not link bit id to battle") + return + } + + // link bits to battle + err = BattleLinkOwnerIDs(battleid, ownerIDs) + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target+"#settings", "Could not link owner id to battle") return } - new_battle := Battle{int(battleid), form_name, []Bot{}, []User{}, public, []Arch{}, []Bit{}} + new_battle := Battle{int(battleid), form_name, []Bot{}, []User{user}, public, []Arch{}, []Bit{}, "", 100, arenasize} log.Println("Updating battle...") err = BattleUpdate(new_battle) if err != nil { - log.Println("err: ", err) - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("500 - Error inserting battle into db")) - http.Error(w, err.Error(), http.StatusInternalServerError) + log_and_redir_with_msg(w, r, err, redir_target+"#settings", "Could not insert battle into db") return } @@ -769,16 +1261,32 @@ func battleSubmitHandler(w http.ResponseWriter, r *http.Request) { return } + redir_target := fmt.Sprintf("/battle/%d?res=%%s", battleid) + switch r.Method { case "POST": r.ParseForm() - log.Println("Adding bot to battle", battleid) + log.Println("Someone submitted the following form:") log.Println(r.Form) + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"] + + if username == nil { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + + user, err := UserGetUserFromUsername(username.(string)) + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target, "Could not get the id for your username") + return + } + // get all the form values that contain the bot that shall be submitted var botIDs []int - for k, _ := range r.Form { + for k := range r.Form { if strings.HasPrefix(k, "bot-") { id, err := strconv.Atoi(strings.TrimPrefix(k, "bot-")) if err != nil { @@ -790,15 +1298,15 @@ func battleSubmitHandler(w http.ResponseWriter, r *http.Request) { } } - log.Println(botIDs) - battle, err := BattleGetByIdDeep(battleid) if err != nil { msg := "ERROR: Couln't get the battle with the given id" http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s", battleid, msg), http.StatusSeeOther) return } - log.Println(battle) + + // clear all bots from that user for that battle before readding them here + BattleUnlinkAllBotsForUser(user.ID, battleid) // for all bots, get their bits and arch and compare them to the one of the battle for _, id := range botIDs { @@ -851,3 +1359,326 @@ func battleSubmitHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusMethodNotAllowed) } } + +// actually run the battle +func battleRunHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + battleid, err := strconv.Atoi(vars["id"]) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Invalid battle id")) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + redir_target := fmt.Sprintf("/battle/%d?res=%%s", battleid) + + switch r.Method { + case "POST": + r.ParseForm() + + log.Printf("running the battle with the id %d", battleid) + log.Println("Someone submitted the following form:") + log.Println(r.Form) + + // fetch the session and get the user + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"] + if username == nil { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + user, err := UserGetUserFromUsername(username.(string)) + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target, "Could not get the id for your username") + return + } + + // Fetch the battle information + // This includes all bots linked to the battle + log.Printf("user %+v wants to run the battle", user) + fullDeepBattle, err := BattleGetByIdDeep(battleid) + + // open radare without input for building the bot + // TODO(emile): configure a variable memsize for the arena + cmd := fmt.Sprintf("malloc://%d", fullDeepBattle.ArenaSize) + r2p1, err := r2pipe.NewPipe(cmd) + if err != nil { + panic(err) + } + defer r2p1.Close() + + var botSources []string + var rawOutput string + + cmd = fmt.Sprintf("pxc %d @ 0x0", fullDeepBattle.ArenaSize) + output, _ := r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n%s\n", cmd, output) + + // TODO(emile): currently hardcoded to two bots, extract this anonymous struct into a named struct and make this work with > 2 bots + runtimeBots := []struct { + Name string + Regs string + BaseAddr int + ArchName string + BitsName string + }{ + {Name: "", Regs: "", BaseAddr: 0, ArchName: "", BitsName: ""}, + {Name: "", Regs: "", BaseAddr: 0, ArchName: "", BitsName: ""}, + } + + rawOutput += "[0x00000000]> # Assembling the bots\n" + + // for each bot involved within the battle, we need to fetch it again, as the deep battle + // fech doesn't fetch that deep (it fetches the batle and the corresponding bots, but only + // their ids and names and not the archs and bits associated) + for i, b := range fullDeepBattle.Bots { + bot, err := BotGetById(b.ID) + if err != nil { + log.Println(err) + } + + runtimeBots[i].Name = bot.Name + + // TODO(emile): a bot can have multiple archs/bits, figure out what to do then + // I've just gone and used the first one, as a bot alwas has at least one... + // ...it has right? + runtimeBots[i].ArchName = bot.Archs[0].Name + runtimeBots[i].BitsName = bot.Bits[0].Name + + // define the command used to assemble the bot + src := strings.ReplaceAll(bot.Source, "\r\n", "; ") + radareCommand := fmt.Sprintf("rasm2 -a %s -b %s \"%+v\"", bot.Archs[0].Name, bot.Bits[0].Name, src) + rawOutput += fmt.Sprintf("; %s\n", radareCommand) + + // assemble the bot + bytecode, err := r2cmd(r2p1, radareCommand) + if err != nil { + http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s", battleid, "err building bot"), http.StatusSeeOther) + return + } + + botSources = append(botSources, bytecode) + } + + // TODO(emile): [L] implement some kind of queue + + rawOutput += "[0x00000000]> # initializing the vm and the stack\n" + cmd = "aei" + output, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n%s", cmd, output) + + cmd = "aeim" + output, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n%s", cmd, output) + + // TODO(emile): random offsets + // place bots + for i, s := range botSources { + + // the address to write the bot to + addr := 50 * (i + 1) + + // store it + runtimeBots[i].BaseAddr = addr + + msg := fmt.Sprintf("# writing bot %d to 0x%d", i, addr) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", msg) + cmd := fmt.Sprintf("wx %s @ 0x%d", s, addr) + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + + // define the instruction point and the stack pointer + rawOutput += "[0x00000000]> # Setting the program counter and the stack pointer\n" + cmd = fmt.Sprintf("aer PC=0x%d", addr) + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + + cmd = fmt.Sprintf("aer SP=SP+0x%d", addr) + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + + // dump the registers of the bot for being able to switch inbetween them + // This is done in order to be able to play one step of each bot at a time, + // but sort of in parallel + rawOutput += "[0x00000000]> # Storing registers\n" + cmd = "aerR" + regs, _ := r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + + initialRegisers := strings.Replace(regs, "\n", ";", -1) + runtimeBots[i].Regs = initialRegisers + } + + for i := range botSources { + // print the memory for some pleasing visuals + cmd = fmt.Sprintf("pxc 100 @ 0x%d", runtimeBots[i].BaseAddr) + output, _ = r2cmd(r2p1, cmd) // print + rawOutput += fmt.Sprintf("[0x00000000]> %s\n%s\n", cmd, output) + } + + output, _ = r2cmd(r2p1, "pxc 100 @ 0x50") // print + fmt.Println(output) + + // define end conditions + rawOutput += "[0x00000000]> # Defining the end conditions\n" + cmd = "e cmd.esil.todo=t theend=1" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + cmd = "e cmd.esil.trap=t theend=1" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + cmd = "e cmd.esil.intr=t theend=1" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + cmd = "e cmd.esil.ioer=t theend=1" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + + // set the end condition to 0 initially + rawOutput += "[0x00000000]> # Initializing the end condition variable\n" + cmd = "f theend=0" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + + currentBotId := 0 + + // TODO(emile): find a sensible default for the max amount of rounds + for i := 0; i < fullDeepBattle.MaxRounds; i++ { + + currentBotId = i % 2 + + rawOutput += fmt.Sprintf("[0x00000000]> ########################################################################\n") + + rawOutput += "[0x00000000]> # Loading the registers\n" + r2cmd(r2p1, runtimeBots[currentBotId].Regs) + + // this is architecture agnostic and just gets the program counter + pc, _ := r2cmd(r2p1, "aer~$(arn PC)~[1]") + + arch, _ := r2cmd(r2p1, "e asm.arch") + bits, _ := r2cmd(r2p1, "e asm.bits") + rawOutput += fmt.Sprintf("[0x00000000]> # ROUND %d, BOT %d (%s), PC=%s, arch=%s, bits=%s\n", i, currentBotId, runtimeBots[currentBotId].Name, pc, arch, bits) + + rawOutput += "[0x00000000]> # setting the architecture accordingly\n" + cmd = fmt.Sprintf("e asm.arch=%s", runtimeBots[currentBotId].ArchName) + output, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n%s", cmd, output) + + cmd = fmt.Sprintf("e asm.bits=%s", runtimeBots[currentBotId].BitsName) + output, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n%s", cmd, output) + + // load registers + // rawOutput += fmt.Sprintf("%+v\n", runtimeBots[currentBotId].Regs) + + // cmd = "dr" + // output, _ = r2cmd(r2p1, cmd) + // rawOutput += fmt.Sprintf("[0x00000000]> %s\n%s\n", cmd, output) + + // _, _ = r2cmd(r2p1, "aes") // step + rawOutput += "[0x00000000]> # Stepping\n" + cmd = "aes" + _, _ = r2cmd(r2p1, cmd) + rawOutput += fmt.Sprintf("[0x00000000]> %s\n", cmd) + + // store the regisers + rawOutput += "[0x00000000]> # Storing the registers\n" + registers, _ := r2cmd(r2p1, "aerR") + registersStripped := strings.Replace(registers, "\n", ";", -1) + runtimeBots[currentBotId].Regs = registersStripped + + // print the arena + rawOutput += "[0x00000000]> # Printing the arena\n" + cmd := fmt.Sprintf("pxc 100 @ 0x%d", runtimeBots[currentBotId].BaseAddr) + output, _ := r2cmd(r2p1, cmd) // print + rawOutput += fmt.Sprintf("[0x00000000]> %s\n%s\n", cmd, output) + // fmt.Println(output) + + // predicate - the end? + rawOutput += "[0x00000000]> # Checking if we've won\n" + pend, _ := r2cmd(r2p1, "?v theend") + status := strings.TrimSpace(pend) + // fixme: on Windows, we sometimes get output *from other calls to r2* + + if status == "0x1" { + log.Printf("[!] Bot %d has died", currentBotId) + } + if status != "0x0" { + log.Printf("[!] Got invalid status '%s' for bot %d", status, currentBotId) + } + } + + BattleSaveRawOutput(battleid, rawOutput) + + msg := "Success!" + http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s#output", battleid, msg), http.StatusSeeOther) + default: + http.Redirect(w, r, "/", http.StatusMethodNotAllowed) + } +} + +// delete a battle +// TODO(emile): finish implementing the deletion of battles +func battleDeleteHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + battleid, err := strconv.Atoi(vars["id"]) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Invalid battle id")) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + redir_target := fmt.Sprintf("/battle/%d?res=%%s", battleid) + + switch r.Method { + case "POST": // can't send a DELETE with pure HTML... + + // get the current user + session, _ := globalState.sessions.Get(r, "session") + username := session.Values["username"] + if username == nil { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + viewer, err := UserGetUserFromUsername(username.(string)) + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target, "Could not get the id for your username") + return + } + + // get the battle + battle, err := BattleGetByIdDeep(int(battleid)) + if err != nil { + log_and_redir_with_msg(w, r, err, redir_target, "Could not get the battle given the id provided") + return + } + + battle_owned_by_requesting_user := false + for _, owner := range battle.Owners { + + // if the requesting users id is equal to an owners id, we're allowed to delete + // the battle + if viewer.ID == owner.ID { + battle_owned_by_requesting_user = true + break + } + } + + // check that the user that created the battle is the current user, if not, return + if battle_owned_by_requesting_user == false { + msg := "You aren't in the owners list of the battle, so you can't delete this battle" + log_and_redir_with_msg(w, r, err, redir_target, msg) + return + } + + BattleDeleteID(battleid) + + msg := "Successfully deleted the battle" + http.Redirect(w, r, fmt.Sprintf("/battle/%d?res=%s", battleid, msg), http.StatusSeeOther) + default: + log.Println("expected POST, got ", r.Method) + http.Redirect(w, r, "/", http.StatusMethodNotAllowed) + } +} diff --git a/src/bot.go b/src/bot.go index a2e8c99..65c1c29 100644 --- a/src/bot.go +++ b/src/bot.go @@ -111,22 +111,15 @@ func (s *State) GetBotById(id int) (Bot, error) { WHERE bo.id=? GROUP BY bo.id; - `, id).Scan(&botid, &botname, &botsource, &ownerids, &ownernames, &archids, &archnames, &bitids, &bitnames) + `, id).Scan(&botid, &botname, &botsource, + &ownerids, &ownernames, + &archids, &archnames, + &bitids, &bitnames) if err != nil { log.Println(err) return Bot{}, err } - // log.Println("botid: ", botid) - // log.Println("botname: ", botname) - // log.Println("botsource: ", botsource) - // log.Println("ownerids: ", ownerids) - // log.Println("ownernames: ", ownernames) - // log.Println("archid: ", archids) - // log.Println("archname: ", archnames) - // log.Println("bitid: ", bitids) - // log.Println("bitname: ", bitnames) - ownerIDList := strings.Split(ownerids, ",") ownerNameList := strings.Split(ownernames, ",") @@ -181,6 +174,7 @@ func (s *State) GetBotById(id int) (Bot, error) { log.Println("ERR4: ", err) return Bot{}, err default: + // log.Printf("returning bot with archs %+v and bits %+v", archs, bits) return Bot{botid, botname, botsource, users, archs, bits}, nil } } @@ -430,6 +424,7 @@ func botSingleHandler(w http.ResponseWriter, r *http.Request) { data["res"] = queryres } + // fetch the session and get the user that made the request session, _ := globalState.sessions.Get(r, "session") username := session.Values["username"].(string) @@ -438,6 +433,7 @@ func botSingleHandler(w http.ResponseWriter, r *http.Request) { data["err"] = "Could not get the id four your username... Please contact an admin" } + // get the bot that was requested bot, err := BotGetById(int(botid)) data["bot"] = bot data["user"] = viewer @@ -449,6 +445,9 @@ func botSingleHandler(w http.ResponseWriter, r *http.Request) { } defer r2p1.Close() + // TODO(emile): improve the archs and bit handling here. I'll use the first one for now, + // but it would be nice to loop over all of them (would be a matrix with archs and bits + // on the axes) src := strings.ReplaceAll(bot.Source, "\r\n", "; ") radareCommand := fmt.Sprintf("rasm2 -a %s -b %s \"%+v\"", bot.Archs[0].Name, bot.Bits[0].Name, src) bytecode, err := r2cmd(r2p1, radareCommand) diff --git a/src/db.go b/src/db.go index 47d8158..8983df9 100644 --- a/src/db.go +++ b/src/db.go @@ -22,7 +22,10 @@ CREATE TABLE IF NOT EXISTS battles ( id INTEGER NOT NULL PRIMARY KEY, created_at DATETIME NOT NULL, name TEXT, - public BOOLEAN + public BOOLEAN, + raw_output TEXT, + max_rounds INTEGER, + arena_size INTEGER ); CREATE TABLE IF NOT EXISTS archs ( id INTEGER NOT NULL PRIMARY KEY, @@ -89,6 +92,11 @@ CREATE TABLE IF NOT EXISTS user_battle_rel ( battle_id INTEGER, PRIMARY KEY(user_id, battle_id) ); +CREATE TABLE IF NOT EXISTS owner_battle_rel ( + user_id INTEGER, + battle_id INTEGER, + PRIMARY KEY(user_id, battle_id) +); CREATE TABLE IF NOT EXISTS bot_battle_rel ( bot_id INTEGER, battle_id INTEGER, diff --git a/src/main.go b/src/main.go index 65a3212..c789cf8 100644 --- a/src/main.go +++ b/src/main.go @@ -30,7 +30,7 @@ func initFlags() { flag.StringVar(&logFilePath, "logfilepath", "./server.log", "The path to the log file") flag.StringVar(&databasePath, "databasepath", "./main.db", "The path to the main database") - flag.StringVar(&sessiondbPath, "sessiondbpath", "./sesions.db", "The path to the session database") + flag.StringVar(&sessiondbPath, "sessiondbpath", "./sessions.db", "The path to the session database") flag.StringVar(&templatesPath, "templates", "./templates", "The path to the templates used") } @@ -86,11 +86,30 @@ func main() { auth_needed.HandleFunc("/user/{id}", userHandler) auth_needed.HandleFunc("/user/{id}/profile", profileHandler) - auth_needed.HandleFunc("/battle", battlesHandler) + r.HandleFunc("/battle", battlesHandler) + r.HandleFunc("/battle/{id}", battleSingleHandler) auth_needed.HandleFunc("/battle/new", battleNewHandler) - auth_needed.HandleFunc("/battle/{id}", battleSingleHandler) + auth_needed.HandleFunc("/battle/quick", battleQuickHandler) auth_needed.HandleFunc("/battle/{id}/submit", battleSubmitHandler) + auth_needed.HandleFunc("/battle/{id}/run", battleRunHandler) + auth_needed.HandleFunc("/battle/{id}/delete", battleDeleteHandler) log.Printf("[i] HTTP Server running on %s:%d\n", host, port) log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), r)) } + +/* Convenience functions */ + +// log_and_redir_with_msg takes a few args, let's go through them one by one: +// - w: the response writer +// - r: the initial request +// - err: the error that occurred, this one will be logged +// - target: the target of the redirect, this must be a format string with some format parameter +// receiving a string, for example `/battles/?err=%s`, the `%s` format string will then be +// filled with the message +// - msg: the message to print after being redirected +func log_and_redir_with_msg(w http.ResponseWriter, r *http.Request, err error, target string, msg string) { + log.Println(err) + http.Redirect(w, r, fmt.Sprintf(target, msg), http.StatusSeeOther) + return +} diff --git a/src/r2.go b/src/r2.go index 6ecd24b..d736f59 100644 --- a/src/r2.go +++ b/src/r2.go @@ -7,10 +7,7 @@ import ( ) func r2cmd(r2p *r2pipe.Pipe, input string) (string, error) { - - log.Println("---") log.Printf("> %s\n", input) - log.Println("---") // send a command buf1, err := r2p.Cmd(input) diff --git a/src/user.go b/src/user.go index 48bc299..cbedf03 100644 --- a/src/user.go +++ b/src/user.go @@ -71,6 +71,10 @@ func UserGetAll() ([]User, error) { return globalState.GetAllUsers() } +func UserGetUsernameCount(username string) (int, error) { + return globalState.GetUsernameCount(username) +} + ////////////////////////////////////////////////////////////////////////////// // DATABASE @@ -119,7 +123,9 @@ func (s *State) UpdateUserUsername(id int, new_username string) error { // Links the given bot to the given user in the user_bot_rel table func (s *State) LinkUserBot(username string, botid int) error { - _, err := s.db.Exec("INSERT INTO user_bot_rel VALUES ((SELECT id FROM users WHERE name=?), ?)", username, botid) + _, err := s.db.Exec(` + INSERT INTO user_bot_rel + VALUES ((SELECT id FROM users WHERE name=?), ?)`, username, botid) if err != nil { return err } else { @@ -151,8 +157,13 @@ func (s *State) GetUserFromUsername(username string) (User, error) { } // Returns the bots belonging to the given user +// TODO(emile): Also fetch the bits and the archs for displaying in the single battle page. In order to do so, join in both those tables func (s *State) GetUserBotsUsername(username string) ([]Bot, error) { - rows, err := s.db.Query("SELECT id, name, source FROM bots b LEFT JOIN user_bot_rel ub ON ub.bot_id = b.id WHERE ub.user_id=(SELECT id FROM users WHERE name=?)", username) + rows, err := s.db.Query(` + SELECT id, name, source + FROM bots b + LEFT JOIN user_bot_rel ub ON ub.bot_id = b.id + WHERE ub.user_id=(SELECT id FROM users WHERE name=?)`, username) defer rows.Close() if err != nil { return nil, err @@ -174,7 +185,11 @@ func (s *State) GetUserBotsUsername(username string) ([]Bot, error) { // Returns the bots belonging to the given user func (s *State) GetUserBotsId(id int) ([]Bot, error) { - rows, err := s.db.Query("SELECT id, name, source FROM bots b LEFT JOIN user_bot_rel ub ON ub.bot_id = b.id WHERE ub.user_id=?", id) + rows, err := s.db.Query(` + SELECT id, name, source + FROM bots b + LEFT JOIN user_bot_rel ub ON ub.bot_id = b.id + WHERE ub.user_id=?`, id) defer rows.Close() if err != nil { return nil, err @@ -216,15 +231,36 @@ func (s *State) GetAllUsers() ([]User, error) { return users, nil } +func (s *State) GetUsernameCount(username string) (int, error) { + rows, err := s.db.Query(` + SELECT COUNT(*) + FROM users + WHERE name=?`, username) + defer rows.Close() + if err != nil { + return -1, err + } + + var count int + rows.Next() + if err := rows.Scan(&count); err != nil { + return -1, err + } + if err = rows.Err(); err != nil { + return -1, err + } + return count, nil +} + +// return globalState.GetUsernameCount(username) + ////////////////////////////////////////////////////////////////////////////// // HTTP func loginHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": - log.Println("GET /login") // define data - log.Println("[d] Defining breadcrumbs") data := map[string]interface{}{} data["version"] = os.Getenv("VERSION") data["pagelink1"] = Link{"login", "/login"} @@ -236,7 +272,6 @@ func loginHandler(w http.ResponseWriter, r *http.Request) { } // session foo - log.Println("[d] Getting session") session, _ := globalState.sessions.Get(r, "session") username := session.Values["username"] @@ -245,7 +280,6 @@ func loginHandler(w http.ResponseWriter, r *http.Request) { log.Printf("[d] Getting the user %s\n", username.(string)) user, err := UserGetUserFromUsername(username.(string)) if user.Name == "" { - log.Println("no user found") } else if err != nil { log.Println(err) msg := "Error: could not get the user for given username" @@ -257,14 +291,12 @@ func loginHandler(w http.ResponseWriter, r *http.Request) { } // display errors passed via query parameters - log.Println("[d] Getting previous results") queryres := r.URL.Query().Get("res") if queryres != "" { data["res"] = queryres } // get the template - log.Println("[d] Getting the template") t, err := template.ParseGlob(fmt.Sprintf("%s/*.html", templatesPath)) if err != nil { log.Printf("Error reading the template Path: %s/*.html", templatesPath) @@ -275,7 +307,6 @@ func loginHandler(w http.ResponseWriter, r *http.Request) { } // exec! - log.Println("[d] Executing the template") t.ExecuteTemplate(w, "login", data) case "POST": @@ -337,8 +368,6 @@ func registerHandler(w http.ResponseWriter, r *http.Request) { {Name: "login/", Target: "/login"}, } - log.Println(username) - if username != nil { data["logged_in"] = true } @@ -381,6 +410,13 @@ func registerHandler(w http.ResponseWriter, r *http.Request) { return } + // Fetch all users and check that there isn't another user with the name here + if _, err := UserGetUsernameCount(username); err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 - Oi', Backend here! That username has already been taken!")) + return + } + // if we've got a password, hash it and store it and create a User if password1 != "" { passwordHash := argon2.IDKey([]byte(password1), []byte(os.Getenv("SALT")), 1, 64*1024, 4, 32) |