package main
import (
"fmt"
"html/template"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/radareorg/r2pipe-go"
)
type Bot struct {
ID int
Name string
Source string
Users []User
Archs []Arch
Bits []Bit
}
//////////////////////////////////////////////////////////////////////////////
// GENERAL PURPOSE
func BotCreate(name string, source string) (int, error) {
return globalState.InsertBot(Bot{Name: name, Source: source})
}
func BotUpdate(botid int, name string, source string) error {
return globalState.UpdateBot(Bot{ID: botid, Name: name, Source: source})
}
func BotGetById(id int) (Bot, error) {
return globalState.GetBotById(id)
}
func BotGetAll() ([]Bot, error) {
return globalState.GetAllBot()
}
func BotLinkArchIDs(botid int, archIDs []int) error {
return globalState.LinkArchIDsToBot(botid, archIDs)
}
func BotLinkBitIDs(botid int, bitIDs []int) error {
return globalState.LinkBitIDsToBot(botid, bitIDs)
}
//////////////////////////////////////////////////////////////////////////////
// DATABASE
func (s *State) InsertBot(bot Bot) (int, error) {
res, err := s.db.Exec("INSERT INTO bots VALUES(NULL,?,?,?);", time.Now(), bot.Name, bot.Source)
if err != nil {
return 0, err
}
var id int64
if id, err = res.LastInsertId(); err != nil {
return 0, err
}
return int(id), nil
}
func (s *State) UpdateBot(bot Bot) error {
_, err := s.db.Exec("UPDATE bots SET name=?, source=? WHERE id=?", bot.Name, bot.Source, bot.ID)
if err != nil {
return err
}
return nil
}
func (s *State) GetBotById(id int) (Bot, error) {
var botid int
var botname string
var botsource string
var ownerids string
var ownernames string
var archids string
var archnames string
var bitids string
var bitnames string
err := s.db.QueryRow(`
SELECT
bo.id, bo.name, bo.source,
COALESCE(group_concat(ub.user_id), ""),
COALESCE(group_concat(us.name), ""),
COALESCE(group_concat(ab.arch_id), ""),
COALESCE(group_concat(ar.name), ""),
COALESCE(group_concat(bb.bit_id), ""),
COALESCE(group_concat(bi.name), "")
FROM bots bo
LEFT JOIN user_bot_rel ub ON ub.bot_id = bo.id
LEFT JOIN users us ON us.id = ub.user_id
LEFT JOIN arch_bot_rel ab ON ab.bot_id = bo.id
LEFT JOIN archs ar ON ar.id = ab.arch_id
LEFT JOIN bit_bot_rel bb ON bb.bot_id = bo.id
LEFT JOIN bits bi ON bi.id = bb.bit_id
WHERE bo.id=?
GROUP BY bo.id;
`, 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, ",")
var users []User
for i, _ := range ownerIDList {
id, err := strconv.Atoi(ownerIDList[i])
if err != nil {
log.Println("ERR1: ", err)
return Bot{}, err
}
users = append(users, User{ID: id, Name: ownerNameList[i], PasswordHash: nil})
}
// assemble the archs
archIDList := strings.Split(archids, ",")
archNameList := strings.Split(archnames, ",")
var archs []Arch
if archIDList[0] != "" {
for i, _ := range archIDList {
id, err := strconv.Atoi(archIDList[i])
if err != nil {
log.Println("Err handling archs: ", err)
return Bot{}, err
}
archs = append(archs, Arch{id, archNameList[i], true})
}
} else {
archs = []Arch{}
}
// assemble the bits
bitIDList := strings.Split(bitids, ",")
bitNameList := strings.Split(bitnames, ",")
var bits []Bit
if bitIDList[0] != "" {
for i, _ := range bitIDList {
id, err := strconv.Atoi(bitIDList[i])
if err != nil {
log.Println("Err handling bits: ", err)
return Bot{}, err
}
bits = append(bits, Bit{id, bitNameList[i], true})
}
} else {
bits = []Bit{}
}
switch {
case err != nil:
log.Println("ERR4: ", err)
return Bot{}, err
default:
return Bot{botid, botname, botsource, users, archs, bits}, nil
}
}
func (s *State) UpdateBotSource(name string, source string) error {
_, err := s.db.Exec("UPDATE bots SET source=? WHERE name=?", source, name)
if err != nil {
return err
} else {
return nil
}
}
func (s *State) UpdateBotName(orig_name string, new_name string) error {
_, err := s.db.Exec("UPDATE bots SET name=? WHERE name=?", new_name, orig_name)
if err != nil {
return err
} else {
return nil
}
}
// Returns the users belonging to the given bot
func (s *State) GetBotUsers(botid int) ([]User, error) {
rows, err := s.db.Query("SELECT id, name FROM users u LEFT JOIN user_bot_rel ub ON ub.user_id = u.id WHERE ub.bot_id=?", botid)
defer rows.Close()
if err != nil {
return nil, err
}
var users []User
for rows.Next() {
var user User
if err := rows.Scan(&user.ID, &user.Name); err != nil {
return users, err
}
users = append(users, user)
}
if err = rows.Err(); err != nil {
return users, err
}
return users, nil
}
// Returns the users belonging to the given bot
func (s *State) GetAllBot() ([]Bot, error) {
rows, err := s.db.Query("SELECT id, name FROM bots;")
defer rows.Close()
if err != nil {
return nil, err
}
var bots []Bot
for rows.Next() {
var bot Bot
if err := rows.Scan(&bot.ID, &bot.Name); err != nil {
return bots, err
}
bots = append(bots, bot)
}
if err = rows.Err(); err != nil {
return bots, err
}
return bots, nil
}
// Returns the users belonging to the given bot
func (s *State) GetAllBotsWithUsers() ([]Bot, error) {
rows, err := s.db.Query(`SELECT
b.id, b.name, b.source, group_concat(ub.user_id), group_concat(u.name)
FROM bots b
LEFT JOIN user_bot_rel ub ON ub.bot_id = b.id
LEFT JOIN users u ON ub.user_id = u.id
GROUP BY b.id;`)
defer rows.Close()
if err != nil {
return nil, err
}
var bots []Bot
for rows.Next() {
var bot Bot
var userIDListString string
var usernameListString string
err := rows.Scan(&bot.ID, &bot.Name, &bot.Source, &userIDListString, &usernameListString)
if err != nil {
return nil, err
}
userIDList := strings.Split(userIDListString, ",")
usernameList := strings.Split(usernameListString, ",")
var users []User
for i, _ := range userIDList {
id, err := strconv.Atoi(userIDList[i])
if err != nil {
return nil, err
}
users = append(users, User{ID: id, Name: usernameList[i], PasswordHash: nil})
}
bot.Users = users
bots = append(bots, bot)
}
if err = rows.Err(); err != nil {
return bots, err
}
return bots, nil
}
func (s *State) LinkArchIDsToBot(botid int, archIDs []int) error {
// delete preexisting links
_, err := s.db.Exec("DELETE FROM arch_bot_rel WHERE bot_id=?;", botid)
if err != nil {
log.Println("Error deleting old arch bot link: ", 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 arch_bot_rel (arch_id, bot_id) VALUES"
for idx, id := range archIDs {
query += fmt.Sprintf("(%d, %d)", id, botid)
if idx != len(archIDs)-1 {
query += ", "
}
}
query += ";"
log.Println(query)
_, err = s.db.Exec(query)
if err != nil {
log.Println("LinkArchIDsToBot err: ", err)
return err
} else {
return nil
}
}
func (s *State) LinkBitIDsToBot(botid int, bitIDs []int) error {
// delete preexisting links
_, err := s.db.Exec("DELETE FROM bit_bot_rel WHERE bot_id=?;", botid)
if err != nil {
log.Println("Error deleting old bit bot link: ", 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 bit_bot_rel (bit_id, bot_id) VALUES"
for idx, id := range bitIDs {
query += fmt.Sprintf("(%d, %d)", id, botid)
if idx != len(bitIDs)-1 {
query += ", "
}
}
query += ";"
log.Println(query)
_, err = s.db.Exec(query)
if err != nil {
log.Println("LinkBitIDsToBot err: ", err)
return err
} else {
return nil
}
}
//////////////////////////////////////////////////////////////////////////////
// HTTP
func botsHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
// define data
data := map[string]interface{}{}
data["version"] = os.Getenv("VERSION")
session, _ := globalState.sessions.Get(r, "session")
username := session.Values["username"]
data["pagelink1"] = Link{"bot", "/bot"}
data["pagelink1options"] = []Link{
{Name: "user", Target: "/user"},
{Name: "battle", Target: "/battle"},
}
data["pagelinknext"] = []Link{
{Name: "new", Target: "/new"},
}
if username == nil {
http.Redirect(w, r, "/login", http.StatusMethodNotAllowed)
}
user, err := UserGetUserFromUsername(username.(string))
if err != nil {
data["err"] = "Could not fetch the user"
} else {
data["user"] = user
}
bots, err := globalState.GetAllBotsWithUsers()
data["bots"] = bots
// 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)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error reading template file"))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// exec!
t.ExecuteTemplate(w, "bots", data)
default:
http.Redirect(w, r, "/", http.StatusMethodNotAllowed)
}
}
func botSingleHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
botid, err := strconv.Atoi(vars["id"])
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Invalid bot id"))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
switch r.Method {
case "GET":
// define data
data := map[string]interface{}{}
data["version"] = os.Getenv("VERSION")
data["pagelink1"] = Link{"bot", "/bot"}
data["pagelink1options"] = []Link{
{Name: "user", Target: "/user"},
{Name: "battle", Target: "/battle"},
}
// display errors passed via query parameters
log.Println("[d] Getting previous results")
queryres := r.URL.Query().Get("res")
if queryres != "" {
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)
viewer, err := UserGetUserFromUsername(username)
if err != nil {
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
// open radare without input for building the bot
r2p1, err := r2pipe.NewPipe("--")
if err != nil {
panic(err)
}
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)
if err != nil {
data["err"] = "Error assembling the bot"
http.Redirect(w, r, fmt.Sprintf("/bot/%d", botid), http.StatusSeeOther)
return
}
data["bytecode_r2cmd"] = radareCommand
data["bytecode"] = bytecode
radareCommand = fmt.Sprintf("rasm2 -a %s -b %s -D %+v", bot.Archs[0].Name, bot.Bits[0].Name, bytecode)
disasm, err := r2cmd(r2p1, radareCommand)
if err != nil {
data["err"] = "Error disassembling the bot"
http.Redirect(w, r, fmt.Sprintf("/bot/%d", botid), http.StatusSeeOther)
return
}
data["err"] = "Could not get the id four your username... Please contact an admin"
data["disasm_r2cmd"] = radareCommand
data["disasm"] = disasm
// define the breadcrumbs
data["pagelink2"] = Link{bot.Name, fmt.Sprintf("/%d", bot.ID)}
allBotNames, err := BotGetAll()
var opts []Link
for _, bot := range allBotNames {
// don't add the current bot to the list, we're already on that page!
if bot.ID != botid {
opts = append(opts, Link{Name: bot.Name, Target: fmt.Sprintf("/%d", bot.ID)})
}
}
data["pagelink2options"] = opts
editable := false
for _, user := range bot.Users {
if user.ID == viewer.ID {
editable = true
}
}
if editable == true {
data["editable"] = true
}
// 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"
} else {
data["archs"] = archs
}
for i, a := range archs {
for _, b := range bot.Archs {
if a.ID == b.ID {
archs[i].Enabled = true
}
}
}
// 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"
} else {
data["bits"] = bits
}
for i, a := range bits {
for _, b := range bot.Bits {
if a.ID == b.ID {
bits[i].Enabled = true
}
}
}
// 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)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error reading template file"))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// exec!
t.ExecuteTemplate(w, "botSingle", data)
case "POST":
// checking if the user submitting the bot information is allowed to do so
session, _ := globalState.sessions.Get(r, "session")
username := session.Values["username"].(string)
// get the user submitting
log.Println("Getting the user submitting the change request...")
requesting_user, err := UserGetUserFromUsername(username)
if err != nil {
log.Println("err: ", err)
http.Redirect(w, r, fmt.Sprintf("/bot/%d", botid), http.StatusSeeOther)
return
}
// get the users the bot belongs
log.Println("Getting the user the bot belongs to...")
orig_bot, err := BotGetById(int(botid))
if err != nil {
log.Println("err: ", err)
http.Redirect(w, r, fmt.Sprintf("/bot/%d", botid), http.StatusSeeOther)
return
}
// check if the user submitting the change request is within the users the bot belongs to
log.Println("Checking if edit is allowed...")
allowed_to_edit := false
for _, user := range orig_bot.Users {
if user.ID == requesting_user.ID {
allowed_to_edit = true
}
}
if allowed_to_edit == false {
http.Redirect(w, r, fmt.Sprintf("/bot/%d", botid), http.StatusSeeOther)
return
}
// at this point, we're sure the user is allowed to edit the bot
r.ParseForm()
name := r.Form.Get("name")
source := r.Form.Get("source")
var archIDs []int
var bitIDs []int
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("/bot/%d?res=%s", botid, msg), http.StatusSeeOther)
return
}
archIDs = append(archIDs, id)
}
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("/bot/%d?res=%s", botid, msg), http.StatusSeeOther)
return
}
bitIDs = append(bitIDs, id)
}
}
if len(archIDs) == 0 {
msg := "ERROR: Please select an architecture"
http.Redirect(w, r, fmt.Sprintf("/bot/%d?res=%s", botid, msg), http.StatusSeeOther)
return
}
if len(archIDs) >= 2 {
msg := "ERROR: Please select ONE architecture"
http.Redirect(w, r, fmt.Sprintf("/bot/%d?res=%s", botid, msg), http.StatusSeeOther)
return
}
if len(bitIDs) == 0 {
msg := "ERROR: Please select one of the bits"
http.Redirect(w, r, fmt.Sprintf("/bot/%d?res=%s", botid, msg), http.StatusSeeOther)
return
}
if len(bitIDs) >= 2 {
msg := "ERROR: Please select ONE of the bits"
http.Redirect(w, r, fmt.Sprintf("/bot/%d?res=%s", botid, msg), http.StatusSeeOther)
return
}
// link archs to battle
err = BotLinkArchIDs(botid, 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("/bot/%d?res=%s", botid, msg), http.StatusSeeOther)
return
}
// link bits to battle
err = BotLinkBitIDs(botid, 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("/bot/%d?res=%s", botid, msg), http.StatusSeeOther)
return
}
if name != "" {
if source != "" {
log.Println("Updating bot...")
err := BotUpdate(botid, name, source)
if err != nil {
log.Println("err: ", err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error inserting bot into db"))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
http.Redirect(w, r, fmt.Sprintf("/bot/%d", botid), http.StatusSeeOther)
default:
http.Redirect(w, r, "/", http.StatusMethodNotAllowed)
}
}
func botNewHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
// define data
data := map[string]interface{}{}
data["version"] = os.Getenv("VERSION")
session, _ := globalState.sessions.Get(r, "session")
username := session.Values["username"].(string)
data["pagelink1"] = Link{Name: "bot", Target: "/bot"}
data["pagelink1options"] = []Link{
{Name: "user", Target: "/user"},
{Name: "battle", Target: "/battle"},
}
data["pagelink2"] = Link{Name: "new", Target: "/new"}
data["pagelink2options"] = []Link{
{Name: "list", Target: ""},
}
// display errors passed via query parameters
log.Println("[d] Getting previous results")
queryres := r.URL.Query().Get("res")
if queryres != "" {
data["res"] = queryres
}
user, err := UserGetUserFromUsername(username)
if err != nil {
data["err"] = "Could not fetch the user"
} else {
data["user"] = user
}
archs, err := ArchGetAll()
if err != nil {
data["err"] = "Could not fetch the archs"
} else {
data["archs"] = archs
}
bits, err := BitGetAll()
if err != nil {
data["err"] = "Could not fetch the bits"
} else {
data["bits"] = bits
}
// 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)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error reading template file"))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// exec!
t.ExecuteTemplate(w, "botNew", data)
case "POST":
session, _ := globalState.sessions.Get(r, "session")
username := session.Values["username"].(string)
// parse the post parameters
r.ParseForm()
log.Println("---")
log.Println(r.Form)
log.Println("---")
name := r.Form.Get("name")
source := r.Form.Get("source")
if name == "" {
msg := "ERROR: Please provide a name"
http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther)
return
}
if source == "" {
msg := "ERROR: Please provide some source"
http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther)
return
}
var archIDs []int
var bitIDs []int
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("/bot/new?res=%s", msg), http.StatusSeeOther)
return
}
archIDs = append(archIDs, id)
}
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("/bot/new?res=%s", msg), http.StatusSeeOther)
return
}
bitIDs = append(bitIDs, id)
}
}
if len(archIDs) == 0 {
msg := "ERROR: Please select an architecture"
http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther)
return
}
if len(archIDs) >= 2 {
msg := "ERROR: Please select ONE architecture"
http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther)
return
}
if len(bitIDs) == 0 {
msg := "ERROR: Please select one of the bits"
http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther)
return
}
if len(bitIDs) >= 2 {
msg := "ERROR: Please select ONE of the bits"
http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther)
return
}
botid, err := BotCreate(name, source)
if err != nil {
log.Println("Error creating the bot: ", err)
msg := "ERROR: Could not create bot"
http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther)
return
}
err = UserLinkBot(username, botid)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Error adding the bot to the user"))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if len(archIDs) == 0 {
msg := "ERROR: Please select an architecture"
http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther)
return
}
// link archs to battle
err = BotLinkArchIDs(botid, archIDs)
if err != nil {
log.Println("Error linking the arch ids to the bot: ", err)
msg := "ERROR: Could not create due to internal reasons"
http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther)
return
}
if len(bitIDs) == 0 {
msg := "ERROR: Please select an bits"
http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther)
return
}
// link bits to battle
err = BotLinkBitIDs(botid, bitIDs)
if err != nil {
log.Println("Error linking the bit ids to the bot: ", err)
msg := "ERROR: Could not create due to internal reasons"
http.Redirect(w, r, fmt.Sprintf("/bot/new?res=%s", msg), http.StatusSeeOther)
return
}
http.Redirect(w, r, "/bot", http.StatusSeeOther)
return
default:
http.Redirect(w, r, "/", http.StatusMethodNotAllowed)
}
}