burgerbackup/bin/server/main.go

224 lines
8.5 KiB
Go

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 </path/to/config/file>")
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)
}
}