package fic import ( "bytes" "errors" "fmt" "regexp" "time" "golang.org/x/crypto/blake2b" ) // FlagKey represents a flag's challenge, stored as hash. type FlagKey struct { Id int `json:"id"` // IdExercice is the identifier of the underlying challenge IdExercice int64 `json:"idExercice"` // Order is used to sort the flag between them Order int8 `json:"order"` // Label is the title of the flag as displayed to players Label string `json:"label"` // Type is the kind of flag Type string `json:"type,omitempty"` // Placeholder is a small piece of text that aims to add useful information like flag format, ... Placeholder string `json:"placeholder"` // Help is a description of the flag Help string `json:"help"` // IgnoreCase indicates if the case is sensitive to case or not IgnoreCase bool `json:"ignorecase"` // Multiline indicates if the flag is stored on multiple lines Multiline bool `json:"multiline"` // ValidatorRegexp extracts a subset of the player's answer, that will be checked. ValidatorRegexp *string `json:"validator_regexp"` // Checksum is the expected hashed flag Checksum []byte `json:"value"` // ChoicesCost is the number of points lost to display choices. ChoicesCost int64 `json:"choices_cost"` } // GetFlagKeys returns a list of key's flags comming with the challenge. func (e Exercice) GetFlagKeys() ([]FlagKey, error) { if rows, err := DBQuery("SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, ignorecase, multiline, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_exercice = ?", e.Id); err != nil { return nil, err } else { defer rows.Close() var flags = make([]FlagKey, 0) for rows.Next() { var k FlagKey k.IdExercice = e.Id if err := rows.Scan(&k.Id, &k.IdExercice, &k.Order, &k.Label, &k.Type, &k.Placeholder, &k.Help, &k.IgnoreCase, &k.Multiline, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost); err != nil { return nil, err } flags = append(flags, k) } if err := rows.Err(); err != nil { return nil, err } return flags, nil } } // GetFlagKey returns a list of flags comming with the challenge. func GetFlagKey(id int) (k FlagKey, err error) { err = DBQueryRow("SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, ignorecase, multiline, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_flag = ?", id).Scan(&k.Id, &k.IdExercice, &k.Order, &k.Label, &k.Type, &k.Placeholder, &k.Help, &k.IgnoreCase, &k.Multiline, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost) return } // GetFlagKeyByLabel returns a flag matching the given label. func (e Exercice) GetFlagKeyByLabel(label string) (k FlagKey, err error) { err = DBQueryRow("SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, ignorecase, multiline, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE type LIKE ? AND id_exercice = ?", label, e.Id).Scan(&k.Id, &k.IdExercice, &k.Order, &k.Label, &k.Type, &k.Placeholder, &k.Help, &k.IgnoreCase, &k.Multiline, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost) return } // ComputeHashedFlag calculates the expected checksum for the given raw_value. func ComputeHashedFlag(raw_value []byte, ignorecase bool, validator_regexp *string) (hash [blake2b.Size]byte, err error) { if ignorecase { raw_value = bytes.ToLower(raw_value) } // Check that raw value passes through the regexp if validator_regexp != nil { if raw_value, err = ExecValidatorRegexp(*validator_regexp, raw_value, ignorecase); err != nil { return } } // Check that the value is not empty if len(raw_value) == 0 { err = errors.New("Empty flag after applying filters") } hash = blake2b.Sum512(raw_value) return } func ExecValidatorRegexp(vre string, val []byte, ignorecase bool) ([]byte, error) { if ignorecase { vre = "(?i)" + vre } if re, err := regexp.Compile(vre); err != nil { return val, err } else if res := re.FindSubmatch(val); res == nil { return val, errors.New("Expected flag doesn't pass through the validator_regexp") } else { return bytes.Join(res[1:], []byte("+")), nil } } // AddRawFlagKey creates and fills a new struct FlagKey, from a non-hashed flag, and registers it into the database. func (e Exercice) AddRawFlagKey(name string, placeholder string, ignorecase bool, multiline bool, validator_regexp *string, raw_value []byte, choicescost int64) (f FlagKey, err error) { hash, errr := ComputeHashedFlag(raw_value, ignorecase, validator_regexp) if errr != nil { return f, err } f = FlagKey{ Label: name, Placeholder: placeholder, IgnoreCase: ignorecase, Multiline: multiline, ValidatorRegexp: validator_regexp, Checksum: hash[:], ChoicesCost: choicescost, } _, err = f.Create(e) return } // GetId returns the Flag identifier. func (k FlagKey) GetId() int { return k.Id } // RecoverId returns the Flag identifier as register in DB. func (k FlagKey) RecoverId() (Flag, error) { if err := DBQueryRow("SELECT id_flag FROM exercice_flags WHERE label LIKE ? AND id_exercice = ?", k.Label, k.IdExercice).Scan(&k.Id); err != nil { return FlagKey{}, err } else { return k, err } } // AddFlagKey creates and fills a new struct Flag, from a hashed flag, and registers it into the database. func (k FlagKey) Create(e Exercice) (Flag, error) { // Check the regexp compile if k.ValidatorRegexp != nil { if _, err := regexp.Compile(*k.ValidatorRegexp); err != nil { return k, err } } if res, err := DBExec("INSERT INTO exercice_flags (id_exercice, ordre, label, type, placeholder, help, ignorecase, multiline, validator_regexp, cksum, choices_cost) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", e.Id, k.Order, k.Label, k.Type, k.Placeholder, k.Help, k.IgnoreCase, k.Multiline, k.ValidatorRegexp, k.Checksum, k.ChoicesCost); err != nil { return k, err } else if kid, err := res.LastInsertId(); err != nil { return k, err } else { k.Id = int(kid) k.IdExercice = e.Id return k, nil } } // ComputeChecksum calculates the checksum for a given value. func (k FlagKey) ComputeChecksum(val []byte) ([]byte, error) { cksum, err := ComputeHashedFlag(val, k.IgnoreCase, k.ValidatorRegexp) return cksum[:], err } // Update applies modifications back to the database. func (k FlagKey) Update() (int64, error) { if k.ValidatorRegexp != nil { if _, err := regexp.Compile(*k.ValidatorRegexp); err != nil { return 0, err } } if res, err := DBExec("UPDATE exercice_flags SET id_exercice = ?, ordre = ?, label = ?, type = ?, placeholder = ?, help = ?, ignorecase = ?, multiline = ?, validator_regexp = ?, cksum = ?, choices_cost = ? WHERE id_flag = ?", k.IdExercice, k.Order, k.Label, k.Type, k.Placeholder, k.Help, k.IgnoreCase, k.Multiline, k.ValidatorRegexp, k.Checksum, k.ChoicesCost, k.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err } else { return nb, err } } // Delete the flag from the database. func (k FlagKey) Delete() (int64, error) { if _, err := DBExec("DELETE FROM exercice_files_okey_deps WHERE id_flag = ?", k.Id); err != nil { return 0, err } else if _, err := DBExec("DELETE FROM exercice_mcq_okey_deps WHERE id_flag_dep = ?", k.Id); err != nil { return 0, err } else if _, err := DBExec("DELETE FROM exercice_flags_omcq_deps WHERE id_flag = ?", k.Id); err != nil { return 0, err } else if _, err := DBExec("DELETE FROM exercice_flags_deps WHERE id_flag = ? OR id_flag_dep = ?", k.Id, k.Id); err != nil { return 0, err } else if _, err := DBExec("DELETE FROM flag_choices WHERE id_flag = ?", k.Id); err != nil { return 0, err } else if res, err := DBExec("DELETE FROM exercice_flags WHERE id_flag = ?", k.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err } else { return nb, err } } func (k FlagKey) GetOrder() int8 { return k.Order } // AddDepend insert a new dependency to a given flag. func (k FlagKey) AddDepend(j Flag) (err error) { if d, ok := j.(FlagKey); ok { _, err = DBExec("INSERT INTO exercice_flags_deps (id_flag, id_flag_dep) VALUES (?, ?)", k.Id, d.Id) } else if d, ok := j.(MCQ); ok { _, err = DBExec("INSERT INTO exercice_flags_omcq_deps (id_flag, id_mcq_dep) VALUES (?, ?)", k.Id, d.Id) } else { err = fmt.Errorf("Dependancy type for key (%T) not implemented for this flag.", j) } return } // GetDepends retrieve the flag's dependency list. func (k FlagKey) GetDepends() ([]Flag, error) { var deps = make([]Flag, 0) if rows, err := DBQuery("SELECT id_flag_dep FROM exercice_flags_deps WHERE id_flag = ?", k.Id); err != nil { return nil, err } else { defer rows.Close() for rows.Next() { var d int if err := rows.Scan(&d); err != nil { return nil, err } deps = append(deps, FlagKey{Id: d, IdExercice: k.IdExercice}) } if err := rows.Err(); err != nil { return nil, err } } if rows, err := DBQuery("SELECT id_mcq_dep FROM exercice_flags_omcq_deps WHERE id_flag = ?", k.Id); err != nil { return nil, err } else { defer rows.Close() for rows.Next() { var d int if err := rows.Scan(&d); err != nil { return nil, err } deps = append(deps, MCQ{Id: d, IdExercice: k.IdExercice}) } if err := rows.Err(); err != nil { return nil, err } } return deps, nil } // Check if the given val is the expected one for this flag. func (k FlagKey) Check(v interface{}) int { var val []byte if va, ok := v.([]byte); !ok { return -1 } else { val = va } hash, err := k.ComputeChecksum(val) if err != nil { return 1 } if len(k.Checksum) != len(hash) { return 1 } for i := range hash { if k.Checksum[i] != hash[i] { return 1 } } return 0 } // FoundBy registers in the database that the given Team solved the flag. func (k FlagKey) FoundBy(t Team) { DBExec("INSERT INTO flag_found (id_flag, id_team, time) VALUES (?, ?, ?)", k.Id, t.Id, time.Now()) } // GetExercice returns the parent Exercice where this flag can be found. func (k FlagKey) GetExercice() (Exercice, error) { var eid int64 if err := DBQueryRow("SELECT id_exercice FROM exercice_flags WHERE id_flag = ?", k.Id).Scan(&eid); err != nil { return Exercice{}, err } return GetExercice(eid) }