From 580dec5b89215310ce34341e11ff17fe38bdb63a Mon Sep 17 00:00:00 2001 From: FChannel <> Date: Sun, 8 May 2022 14:57:40 -0700 Subject: more cleanup, logging and error logging everywhere things are mostly in place can work on "features" and polish --- util/blacklist.go | 33 ++-- util/key.go | 30 ++-- util/proxy.go | 40 +++-- util/util.go | 79 +++------ util/verification.go | 482 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 559 insertions(+), 105 deletions(-) create mode 100644 util/verification.go (limited to 'util') diff --git a/util/blacklist.go b/util/blacklist.go index 5368037..acb0b21 100644 --- a/util/blacklist.go +++ b/util/blacklist.go @@ -11,28 +11,28 @@ type PostBlacklist struct { Regex string } -func DeleteRegexBlacklistDB(id int) error { +func DeleteRegexBlacklist(id int) error { query := `delete from postblacklist where id=$1` - _, err := config.DB.Exec(query, id) - return err + + return MakeError(err, "DeleteRegexBlacklist") } -func GetRegexBlacklistDB() ([]PostBlacklist, error) { +func GetRegexBlacklist() ([]PostBlacklist, error) { var list []PostBlacklist query := `select id, regex from postblacklist` - rows, err := config.DB.Query(query) + if err != nil { - return list, err + return list, MakeError(err, "GetRegexBlacklist") } defer rows.Close() for rows.Next() { var temp PostBlacklist - rows.Scan(&temp.Id, &temp.Regex) + rows.Scan(&temp.Id, &temp.Regex) list = append(list, temp) } @@ -40,10 +40,10 @@ func GetRegexBlacklistDB() ([]PostBlacklist, error) { } func IsPostBlacklist(comment string) (bool, error) { - postblacklist, err := GetRegexBlacklistDB() + postblacklist, err := GetRegexBlacklist() if err != nil { - return false, err + return false, MakeError(err, "IsPostBlacklist") } for _, e := range postblacklist { @@ -57,20 +57,15 @@ func IsPostBlacklist(comment string) (bool, error) { return false, nil } -func WriteRegexBlacklistDB(regex string) error { +func WriteRegexBlacklist(regex string) error { var re string query := `select from postblacklist where regex=$1` if err := config.DB.QueryRow(query, regex).Scan(&re); err != nil { - return err - } - - if re != "" { - return nil + query = `insert into postblacklist (regex) values ($1)` + _, err := config.DB.Exec(query, regex) + return MakeError(err, "WriteRegexBlacklist") } - query = `insert into postblacklist (regex) values ($1)` - - _, err := config.DB.Exec(query, regex) - return err + return nil } diff --git a/util/key.go b/util/key.go index cd8662a..60eeb43 100644 --- a/util/key.go +++ b/util/key.go @@ -3,6 +3,7 @@ package util import ( "crypto/sha512" "encoding/hex" + "errors" "math/rand" "os" "strings" @@ -13,14 +14,14 @@ import ( const domain = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" -func CreateKey(len int) string { +func CreateKey(len int) (string, error) { // TODO: provided that CreateTripCode still uses sha512, the max len can be 128 at most. if len > 128 { - panic("len is greater than 128") // awful way to do it + return "", MakeError(errors.New("len is greater than 128"), "CreateKey") } str := CreateTripCode(RandomID(len)) - return str[:len] + return str[:len], nil } func CreateTripCode(input string) string { @@ -29,23 +30,13 @@ func CreateTripCode(input string) string { return hex.EncodeToString(out[:]) } -func RandomID(size int) string { - rng := size - newID := strings.Builder{} - for i := 0; i < rng; i++ { - newID.WriteByte(domain[rand.Intn(len(domain))]) - } - - return newID.String() -} - func GetCookieKey() (string, error) { if config.CookieKey == "" { var file *os.File var err error if file, err = os.OpenFile("config/config-init", os.O_APPEND|os.O_WRONLY, 0644); err != nil { - return "", err + return "", MakeError(err, "GetCookieKey") } defer file.Close() @@ -56,3 +47,14 @@ func GetCookieKey() (string, error) { return config.CookieKey, nil } + +func RandomID(size int) string { + rng := size + newID := strings.Builder{} + + for i := 0; i < rng; i++ { + newID.WriteByte(domain[rand.Intn(len(domain))]) + } + + return newID.String() +} diff --git a/util/proxy.go b/util/proxy.go index 0f4a648..daa90b5 100644 --- a/util/proxy.go +++ b/util/proxy.go @@ -9,27 +9,11 @@ import ( "github.com/FChannel0/FChannel-Server/config" ) -func RouteProxy(req *http.Request) (*http.Response, error) { - var proxyType = GetPathProxyType(req.URL.Host) - - if proxyType == "tor" { - proxyUrl, err := url.Parse("socks5://" + config.TorProxy) - if err != nil { - return nil, err - } - - proxyTransport := &http.Transport{Proxy: http.ProxyURL(proxyUrl)} - client := &http.Client{Transport: proxyTransport, Timeout: time.Second * 15} - return client.Do(req) - } - - return http.DefaultClient.Do(req) -} - func GetPathProxyType(path string) string { if config.TorProxy != "" { re := regexp.MustCompile(`(http://|http://)?(www.)?\w+\.onion`) onion := re.MatchString(path) + if onion { return "tor" } @@ -40,17 +24,35 @@ func GetPathProxyType(path string) string { func MediaProxy(url string) string { re := regexp.MustCompile("(.+)?" + config.Domain + "(.+)?") - if re.MatchString(url) { return url } re = regexp.MustCompile("(.+)?\\.onion(.+)?") - if re.MatchString(url) { return url } config.MediaHashs[HashMedia(url)] = url + return "/api/media?hash=" + HashMedia(url) } + +func RouteProxy(req *http.Request) (*http.Response, error) { + var proxyType = GetPathProxyType(req.URL.Host) + + if proxyType == "tor" { + proxyUrl, err := url.Parse("socks5://" + config.TorProxy) + + if err != nil { + return nil, MakeError(err, "RouteProxy") + } + + proxyTransport := &http.Transport{Proxy: http.ProxyURL(proxyUrl)} + client := &http.Client{Transport: proxyTransport, Timeout: time.Second * 15} + + return client.Do(req) + } + + return http.DefaultClient.Do(req) +} diff --git a/util/util.go b/util/util.go index 9c1ba97..6d45442 100644 --- a/util/util.go +++ b/util/util.go @@ -12,6 +12,7 @@ import ( "os" "path" "regexp" + "runtime" "strings" "github.com/FChannel0/FChannel-Server/config" @@ -28,34 +29,19 @@ func IsOnion(url string) bool { func StripTransferProtocol(value string) string { re := regexp.MustCompile("(http://|https://)?(www.)?") - value = re.ReplaceAllString(value, "") return value } -func GetCaptchaCode(captcha string) string { - re := regexp.MustCompile("\\w+\\.\\w+$") - code := re.FindString(captcha) - - re = regexp.MustCompile("\\w+") - code = re.FindString(code) - - return code -} - func ShortURL(actorName string, url string) string { + var reply string re := regexp.MustCompile(`.+\/`) - actor := re.FindString(actorName) - urlParts := strings.Split(url, "|") - op := urlParts[0] - var reply string - if len(urlParts) > 1 { reply = urlParts[1] } @@ -99,17 +85,11 @@ func LocalShort(url string) string { func RemoteShort(url string) string { re := regexp.MustCompile(`\w+$`) - id := re.FindString(StripTransferProtocol(url)) - re = regexp.MustCompile(`.+/.+/`) - actorurl := re.FindString(StripTransferProtocol(url)) - re = regexp.MustCompile(`/.+/`) - actorname := re.FindString(actorurl) - actorname = strings.Replace(actorname, "/", "", -1) return "f" + actorname + "-" + id @@ -117,9 +97,7 @@ func RemoteShort(url string) string { func ShortImg(url string) string { nURL := url - re := regexp.MustCompile(`(\.\w+$)`) - fileName := re.ReplaceAllString(url, "") if len(fileName) > 26 { @@ -199,32 +177,19 @@ func HashBytes(media []byte) string { func EscapeString(text string) string { // TODO: not enough - text = strings.Replace(text, "<", "<", -1) return text } func CreateUniqueID(actor string) (string, error) { var newID string - isUnique := false - for !isUnique { - newID = RandomID(8) + for true { + newID = RandomID(8) query := "select id from activitystream where id=$1" args := fmt.Sprintf("%s/%s/%s", config.Domain, actor, newID) - rows, err := config.DB.Query(query, args) - if err != nil { - return "", MakeError(err, "CreateUniqueID") - } - - defer rows.Close() - // reusing a variable here - // if we encounter a match, it'll get set to false causing the outer for loop to loop and to go through this all over again - // however if nothing is there, it'll remain true and exit the loop - isUnique = true - for rows.Next() { - isUnique = false + if err := config.DB.QueryRow(query, args); err != nil { break } } @@ -234,14 +199,13 @@ func CreateUniqueID(actor string) (string, error) { func GetFileContentType(out multipart.File) (string, error) { buffer := make([]byte, 512) - _, err := out.Read(buffer) + if err != nil { return "", MakeError(err, "GetFileContentType") } out.Seek(0, 0) - contentType := http.DetectContentType(buffer) return contentType, nil @@ -249,26 +213,33 @@ func GetFileContentType(out multipart.File) (string, error) { func GetContentType(location string) string { elements := strings.Split(location, ";") + if len(elements) > 0 { return elements[0] - } else { - return location } + + return location } -func CreatedNeededDirectories() { +func CreatedNeededDirectories() error { if _, err := os.Stat("./public"); os.IsNotExist(err) { - os.Mkdir("./public", 0755) + if err = os.Mkdir("./public", 0755); err != nil { + return MakeError(err, "CreatedNeededDirectories") + } } if _, err := os.Stat("./pem/board"); os.IsNotExist(err) { - os.MkdirAll("./pem/board", 0700) + if err = os.MkdirAll("./pem/board", 0700); err != nil { + return MakeError(err, "CreatedNeededDirectories") + } } + + return nil } -func LoadThemes() { - // get list of themes +func LoadThemes() error { themes, err := ioutil.ReadDir("./static/css/themes") + if err != nil { MakeError(err, "LoadThemes") } @@ -278,15 +249,16 @@ func LoadThemes() { config.Themes = append(config.Themes, strings.TrimSuffix(f.Name(), e)) } } + + return nil } func GetBoardAuth(board string) ([]string, error) { var auth []string - - query := `select type from actorauth where board=$1` - var rows *sql.Rows var err error + + query := `select type from actorauth where board=$1` if rows, err = config.DB.Query(query, board); err != nil { return auth, MakeError(err, "GetBoardAuth") } @@ -306,7 +278,8 @@ func GetBoardAuth(board string) ([]string, error) { func MakeError(err error, msg string) error { if err != nil { - s := fmt.Sprintf("%s: %s", msg, err.Error()) + _, _, line, _ := runtime.Caller(1) + s := fmt.Sprintf("%s:%d : %s", msg, line, err.Error()) return errors.New(s) } diff --git a/util/verification.go b/util/verification.go new file mode 100644 index 0000000..c64b54d --- /dev/null +++ b/util/verification.go @@ -0,0 +1,482 @@ +package util + +import ( + "fmt" + "math/rand" + "net/smtp" + "os" + "os/exec" + "strings" + "time" + + "github.com/FChannel0/FChannel-Server/config" + "github.com/gofiber/fiber/v2" + _ "github.com/lib/pq" +) + +type Verify struct { + Type string + Identifier string + Code string + Created string + Board string +} + +type VerifyCooldown struct { + Identifier string + Code string + Time int +} + +type Signature struct { + KeyId string + Headers []string + Signature string + Algorithm string +} + +func (verify Verify) Create() error { + query := `insert into verification (type, identifier, code, created) values ($1, $2, $3, $4)` + _, err := config.DB.Exec(query, verify.Type, verify.Identifier, verify.Code, time.Now().UTC().Format(time.RFC3339)) + + return MakeError(err, "Create") +} + +func (verify Verify) CreateBoardAccess() error { + hasAccess, err := verify.HasBoardAccess() + + if err != nil { + return MakeError(err, "CreateBoardAccess") + } + + if !hasAccess { + query := `insert into boardaccess (identifier, board) values($1, $2)` + _, err := config.DB.Exec(query, verify.Identifier, verify.Board) + + return MakeError(err, "CreateBoardAccess") + } + + return nil +} + +func (verify Verify) CreateBoardMod() error { + var pass string + var err error + + if pass, err = CreateKey(50); err != nil { + return MakeError(err, "CreateBoardMod") + } + + var code string + + query := `select code from verification where identifier=$1 and type=$2` + if err := config.DB.QueryRow(query, verify.Board, verify.Type).Scan(&code); err != nil { + return nil + } + + var ident string + + query = `select identifier from boardaccess where identifier=$1 and board=$2` + if err := config.DB.QueryRow(query, verify.Identifier, verify.Board).Scan(&ident); err != nil { + return nil + } + + if ident != verify.Identifier { + query := `insert into crossverification (verificationcode, code) values ($1, $2)` + if _, err := config.DB.Exec(query, code, pass); err != nil { + return MakeError(err, "CreateBoardMod") + } + + query = `insert into boardaccess (identifier, code, board, type) values ($1, $2, $3, $4)` + if _, err = config.DB.Exec(query, verify.Identifier, pass, verify.Board, verify.Type); err != nil { + return MakeError(err, "CreateBoardMod") + } + + config.Log.Printf("Board access - Board: %s, Identifier: %s, Code: %s\n", verify.Board, verify.Identifier, pass) + } + + return nil +} + +func (verify Verify) DeleteBoardMod() error { + var code string + + query := `select code from boardaccess where identifier=$1 and board=$1` + if err := config.DB.QueryRow(query, verify.Identifier, verify.Board).Scan(&code); err != nil { + return nil + } + + query = `delete from crossverification where code=$1` + if _, err := config.DB.Exec(query, code); err != nil { + return MakeError(err, "DeleteBoardMod") + } + + query = `delete from boardaccess where identifier=$1 and board=$2` + if _, err := config.DB.Exec(query, verify.Identifier, verify.Board); err != nil { + return MakeError(err, "DeleteBoardMod") + } + + return nil +} + +func (verify Verify) GetBoardMod() (Verify, error) { + var nVerify Verify + + query := `select code, board, type, identifier from boardaccess where identifier=$1` + if err := config.DB.QueryRow(query, verify.Identifier).Scan(&nVerify.Code, &nVerify.Board, &nVerify.Type, &nVerify.Identifier); err != nil { + return nVerify, MakeError(err, "GetBoardMod") + } + + return nVerify, nil +} + +func (verify Verify) GetCode() (Verify, error) { + var nVerify Verify + + query := `select type, identifier, code, board from boardaccess where identifier=$1 and board=$2` + if err := config.DB.QueryRow(query, verify.Identifier, verify.Board).Scan(&nVerify.Type, &nVerify.Identifier, &nVerify.Code, &nVerify.Board); err != nil { + return verify, nil + } + + return nVerify, nil +} + +func (verify Verify) HasBoardAccess() (bool, error) { + var count int + + query := `select count(*) from boardaccess where identifier=$1 and board=$2` + if err := config.DB.QueryRow(query, verify.Identifier, verify.Board).Scan(&count); err != nil { + return false, nil + } + + return true, nil +} + +func (verify Verify) SendVerification() error { + config.Log.Println("sending email") + + from := config.SiteEmail + pass := config.SiteEmailPassword + to := verify.Identifier + body := fmt.Sprintf("You can use either\r\nEmail: %s \r\n Verfication Code: %s\r\n for the board %s", verify.Identifier, verify.Code, verify.Board) + + msg := "From: " + from + "\n" + + "To: " + to + "\n" + + "Subject: Image Board Verification\n\n" + + body + + err := smtp.SendMail(config.SiteEmailServer+":"+config.SiteEmailPort, + smtp.PlainAuth("", from, pass, config.SiteEmailServer), + from, []string{to}, []byte(msg)) + + return MakeError(err, "SendVerification") +} + +func (verify Verify) VerifyCooldownAdd() error { + query := `insert into verficationcooldown (identifier, code) values ($1, $2)` + _, err := config.DB.Exec(query, verify.Identifier, verify.Code) + + return MakeError(err, "VerifyCooldownAdd") +} + +func BoardHasAuthType(board string, auth string) (bool, error) { + authTypes, err := GetBoardAuth(board) + + if err != nil { + return false, MakeError(err, "BoardHasAuthType") + } + + for _, e := range authTypes { + if e == auth { + return true, nil + } + } + + return false, nil +} + +func Captcha() string { + rand.Seed(time.Now().UTC().UnixNano()) + domain := "ABEFHKMNPQRSUVWXYZ#$&" + rng := 4 + newID := "" + + for i := 0; i < rng; i++ { + newID += string(domain[rand.Intn(len(domain))]) + } + + return newID +} + +func CreateNewCaptcha() error { + id := RandomID(8) + file := "public/" + id + ".png" + + for true { + if _, err := os.Stat("./" + file); err == nil { + id = RandomID(8) + file = "public/" + id + ".png" + } else { + break + } + } + + var pattern string + + captcha := Captcha() + rnd := fmt.Sprintf("%d", rand.Intn(3)) + srnd := string(rnd) + + switch srnd { + case "0": + pattern = "pattern:verticalbricks" + break + + case "1": + pattern = "pattern:verticalsaw" + break + + case "2": + pattern = "pattern:hs_cross" + break + + } + + cmd := exec.Command("convert", "-size", "200x98", pattern, "-transparent", "white", file) + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return MakeError(err, "CreateNewCaptcha") + } + + cmd = exec.Command("convert", file, "-fill", "blue", "-pointsize", "62", "-annotate", "+0+70", captcha, "-tile", "pattern:left30", "-gravity", "center", "-transparent", "white", file) + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return MakeError(err, "CreateNewCaptcha") + } + + rnd = fmt.Sprintf("%d", rand.Intn(24)-12) + cmd = exec.Command("convert", file, "-rotate", rnd, "-wave", "5x35", "-distort", "Arc", "20", "-wave", "2x35", "-transparent", "white", file) + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return MakeError(err, "CreateNewCaptcha") + } + + var verification Verify + + verification.Type = "captcha" + verification.Code = captcha + verification.Identifier = file + + return verification.Create() +} + +func GetRandomCaptcha() (string, error) { + var verify string + + query := `select identifier from verification where type='captcha' order by random() limit 1` + if err := config.DB.QueryRow(query).Scan(&verify); err != nil { + return verify, MakeError(err, "GetRandomCaptcha") + } + + return verify, nil +} + +func GetCaptchaTotal() (int, error) { + var count int + + query := `select count(*) from verification where type='captcha'` + if err := config.DB.QueryRow(query).Scan(&count); err != nil { + return count, MakeError(err, "GetCaptchaTotal") + } + + return count, nil +} + +func GetCaptchaCode(verify string) (string, error) { + var code string + + query := `select code from verification where identifier=$1 limit 1` + if err := config.DB.QueryRow(query, verify).Scan(&code); err != nil { + return code, MakeError(err, "GetCaptchaCodeDB") + } + + return code, nil +} + +func DeleteCaptchaCode(verify string) error { + query := `delete from verification where identifier=$1` + _, err := config.DB.Exec(query, verify) + + if err != nil { + return MakeError(err, "DeleteCaptchaCode") + } + + err = os.Remove("./" + verify) + return MakeError(err, "DeleteCaptchaCode") +} + +func GetVerificationByCode(code string) (Verify, error) { + // TODO: this only needs to select one row. + + var verify Verify + + query := `select type, identifier, code, board from boardaccess where code=$1` + + rows, err := config.DB.Query(query, code) + if err != nil { + return verify, MakeError(err, "GetVerificationByCode") + } + + defer rows.Close() + + for rows.Next() { + if err := rows.Scan(&verify.Type, &verify.Identifier, &verify.Code, &verify.Board); err != nil { + return verify, MakeError(err, "GetVerificationByCode") + } + } + + return verify, nil +} + +func GetVerificationByEmail(email string) (Verify, error) { + var verify Verify + + query := `select type, identifier, code, board from boardaccess where identifier=$1` + if err := config.DB.QueryRow(query, email).Scan(&verify.Type, &verify.Identifier, &verify.Code, &verify.Board); err != nil { + return verify, nil + } + + return verify, nil +} + +func GetVerify(access string) (Verify, error) { + verify, err := GetVerificationByCode(access) + + if err != nil { + return verify, MakeError(err, "GetVerify") + } + + if verify.Identifier == "" { + verify, err = GetVerificationByEmail(access) + } + + return verify, MakeError(err, "GetVerify") +} + +func HasAuthCooldown(auth string) (bool, error) { + var current VerifyCooldown + var err error + + if current, err = VerifyCooldownCurrent(auth); err != nil { + return false, MakeError(err, "HasAuthCooldown") + } + + if current.Time > 0 { + return true, nil + } + + return false, nil +} + +func HasAuth(code string, board string) (bool, error) { + verify, err := GetVerificationByCode(code) + if err != nil { + return false, MakeError(err, "HasAuth") + } + + if res, err := verify.HasBoardAccess(); err == nil && (verify.Board == config.Domain || (res && verify.Board == board)) { + return true, nil + } else { + return false, MakeError(err, "HasAuth") + } + + return false, nil +} + +func IsEmailSetup() bool { + return config.SiteEmail != "" || config.SiteEmailPassword != "" || config.SiteEmailServer != "" || config.SiteEmailPort != "" +} + +func VerficationCooldown() error { + query := `select identifier, code, time from verificationcooldown` + rows, err := config.DB.Query(query) + + if err != nil { + return MakeError(err, "VerficationCooldown") + } + + defer rows.Close() + for rows.Next() { + var verify VerifyCooldown + + if err := rows.Scan(&verify.Identifier, &verify.Code, &verify.Time); err != nil { + return MakeError(err, "VerficationCooldown") + } + + nTime := verify.Time - 1 + query = `update set time=$1 where identifier=$2` + + if _, err := config.DB.Exec(query, nTime, verify.Identifier); err != nil { + return MakeError(err, "VerficationCooldown") + } + + VerficationCooldownRemove() + } + + return nil +} + +func VerficationCooldownRemove() error { + query := `delete from verificationcooldown where time < 1` + _, err := config.DB.Exec(query) + + return MakeError(err, "VerficationCooldownRemove") +} + +func VerifyCooldownCurrent(auth string) (VerifyCooldown, error) { + var current VerifyCooldown + + query := `select identifier, code, time from verificationcooldown where code=$1` + if err := config.DB.QueryRow(query, auth).Scan(¤t.Identifier, ¤t.Code, ¤t.Time); err != nil { + query := `select identifier, code, time from verificationcooldown where identifier=$1` + if err := config.DB.QueryRow(query, auth).Scan(¤t.Identifier, ¤t.Code, ¤t.Time); err != nil { + return current, nil + } + + return current, nil + } + + return current, nil +} + +func GetPasswordFromSession(ctx *fiber.Ctx) (string, string) { + cookie := ctx.Cookies("session_token") + parts := strings.Split(cookie, "|") + + if len(parts) > 1 { + return parts[0], parts[1] + } + + return "", "" +} + +func MakeCaptchas(total int) error { + dbtotal, err := GetCaptchaTotal() + + if err != nil { + return MakeError(err, "MakeCaptchas") + } + + difference := total - dbtotal + + for i := 0; i < difference; i++ { + if err := CreateNewCaptcha(); err != nil { + return MakeError(err, "MakeCaptchas") + } + } + + return nil +} -- cgit v1.2.3