package main import ( "concord.hectabit.org/Hectabit/burgerbackup/lib/common" "encoding/base64" "encoding/json" "errors" "github.com/gin-gonic/gin" "github.com/spf13/viper" "golang.org/x/crypto/argon2" "io" "log" "mime/multipart" "net/http" "os" "strconv" "strings" "time" ) func main() { var configPath = "./config.ini" if len(os.Args) > 1 { if os.Args[1] == "-h" || os.Args[1] == "--help" { log.Println("[INFO] Usage: burgerbackup-client ") os.Exit(0) } else { configPath = os.Args[1] } } if _, err := os.Stat(configPath); err == nil { log.Println("[INFO] Config loaded at", time.Now().Unix()) } else if os.IsNotExist(err) { originalConfigPath := configPath configPath = "/etc/burgerbackup/server.ini" if _, err := os.Stat(configPath); err == nil { log.Println("[INFO] Config loaded at", time.Now().Unix()) log.Println("[WARN] The config file was not found at", originalConfigPath, "so the default config path of", configPath, "was used.") } else if os.IsNotExist(err) { log.Fatalln("[FATAL]", originalConfigPath, "does not exist") } else { log.Fatalln("[FATAL] File is in quantum uncertainty:", err) } } else { log.Fatalln("[FATAL] File is in quantum uncertainty:", err) } viper.SetConfigType("ini") configPathSlice := strings.Split(configPath, "/") configPathNoIni := strings.Split(configPathSlice[len(configPathSlice)-1], ".")[0] viper.SetConfigName(configPathNoIni) configPathNoFile := strings.Join(configPathSlice[:len(configPathSlice)-1], "/") + "/" viper.AddConfigPath(configPathNoFile) viper.AutomaticEnv() err := viper.ReadInConfig() if err != nil { log.Fatalln("[FATAL] Error in config file at", strconv.FormatInt(time.Now().Unix(), 10)+":", err) } backupKey := viper.GetString("config.BACKUP_KEY") cryptoKey := viper.GetString("config.CRYPTO_KEY") port := viper.GetInt("config.PORT") host := viper.GetString("config.HOST") backupFolder := viper.GetString("config.BACKUP_FOLDER") if host == "" { log.Fatalln("[FATAL] HOST is not set") } if port == 0 { log.Fatalln("[FATAL] PORT is not set") } if backupKey == "" { log.Fatalln("[FATAL] BACKUP_KEY is not set") } else if backupKey == "supersecretkey" { log.Println("[WARN] SECRET_KEY is set to a default value. Please set it to another value.") } if cryptoKey == "" { log.Fatalln("[FATAL] CRYPTO_KEY is not set") } else if cryptoKey == "supersecretkey" { log.Println("[WARN] SECRET_KEY is set to a default value. If it is also set to this on the client, please set both to another value.") } if _, err := os.Stat(backupFolder); os.IsNotExist(err) { log.Println("[INFO] Backup folder does not exist, creating") if err := os.Mkdir(backupFolder, 0755); err != nil { log.Fatalln("[FATAL] Could not create backup folder:", err) } } gin.SetMode(gin.ReleaseMode) router := gin.Default() router.Use(func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Headers", "*, Authorization") c.Writer.Header().Set("Access-Control-Allow-Methods", "*") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(200) return } c.Next() }) router.POST("/api/backup", func(c *gin.Context) { clientBackupKey := c.GetHeader("Authorization") if clientBackupKey == "" { c.JSON(400, gin.H{"error": "BackupKey not found", "goErr": "backupKey not found"}) return } var authenticated bool authenticated, err = common.VerifyHash(clientBackupKey, backupKey) if err != nil { c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgerbackup and refer to the documentation for more info. Your error code is: UNKNOWN-API-BACKUP-VERIFYERR", "goErr": err.Error()}) log.Println("[ERROR] Error in /api/backup hash verify:", err) return } if authenticated { file, err := c.FormFile("file") if err != nil { if errors.Is(err, http.ErrMissingFile) { c.JSON(400, gin.H{"error": "No file uploaded", "goErr": err.Error()}) } else { c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgerbackup and refer to the documentation for more info. Your error code is: UNKNOWN-API-BACKUP-FILEERR", "goErr": err.Error()}) log.Println("[ERROR] Error in /api/backup file upload:", err) } return } encryptedFile, err := file.Open() if err != nil { c.JSON(422, gin.H{"error": "Could not open file", "goErr": err.Error()}) } defer func(encryptedFile multipart.File) { err := encryptedFile.Close() if err != nil { c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgerbackup and refer to the documentation for more info. Your error code is: UNKNOWN-API-BACKUP-FILEDEFERERR", "goErr": err.Error()}) log.Println("[ERROR] Error in /api/backup file defer:", err) return } }(encryptedFile) encryptedData, err := io.ReadAll(encryptedFile) if err != nil { c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgerbackup and refer to the documentation for more info. Your error code is: UNKNOWN-API-BACKUP-FILECONTENTERR", "goErr": err.Error()}) log.Println("[ERROR] Error in /api/backup file read:", err) return } cryptoKeyHashed := argon2.IDKey([]byte(cryptoKey), []byte("burgerbackup"), 1, 64*1024, 4, 32) decryptedData, err := common.DecryptAES(cryptoKeyHashed, encryptedData) if err != nil { c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgerbackup and refer to the documentation for more info. Your error code is: UNKNOWN-API-BACKUP-DECRYPTERR", "goErr": err.Error()}) log.Println("[ERROR] Error in /api/backup file decrypt:", err) return } decryptedDataMap := make(map[string]interface{}) err = json.Unmarshal(decryptedData, &decryptedDataMap) if err != nil { c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgerbackup and refer to the documentation for more info. Your error code is: UNKNOWN-API-BACKUP-JSONERR", "goErr": err.Error()}) log.Println("[ERROR] Error in /api/backup file json:", err) return } originalFileName, ok := decryptedDataMap["filename"].(string) if !ok { c.JSON(422, gin.H{"error": "Could not get filename", "goErr": "could not get filename"}) return } contentBase64, ok := decryptedDataMap["content"].(string) if !ok { c.JSON(422, gin.H{"error": "Could not get content", "goErr": "could not get content"}) return } content, err := base64.StdEncoding.DecodeString(contentBase64) if err != nil { c.JSON(422, gin.H{"error": "Could not decode base64 content", "goErr": err.Error()}) return } log.Println("[INFO] Received backup from", c.ClientIP(), "with filename", originalFileName) fileNameSlice := strings.Split(originalFileName, ".") var fileName string if len(fileNameSlice) >= 2 { fileName = fileNameSlice[0] + "_" + strconv.FormatInt(time.Now().Unix(), 10) + "." + fileNameSlice[1] } else if len(fileNameSlice) == 1 { fileName = fileNameSlice[0] + "_" + strconv.FormatInt(time.Now().Unix(), 10) } else { fileName = "unnamed-backup_" + strconv.FormatInt(time.Now().Unix(), 10) } filePath := backupFolder + "/" + fileName if err := os.WriteFile(filePath, content, 0644); err != nil { c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgerbackup and refer to the documentation for more info. Your error code is: UNKNOWN-API-BACKUP-WRITEERR", "goErr": err.Error()}) log.Println("[ERROR] Error in /api/backup file write:", err) return } log.Println("[INFO] Successfully saved backup from", c.ClientIP()) c.JSON(200, gin.H{"success": "true", "timeTaken": time.Now().Unix(), "goErr": ""}) } else { c.JSON(401, gin.H{"error": "Incorrect backup key", "goErr": "incorrect backup key"}) } }) log.Println("[INFO] Server started at", time.Now().Unix()) log.Println("[INFO] Welcome to Burgerbackup! Today we are running on IP " + host + " on port " + strconv.Itoa(port) + ".") err = router.Run(host + ":" + strconv.Itoa(port)) if err != nil { log.Fatalln("[FATAL] Server failed to begin operations at", time.Now().Unix(), err) } }