commit 7bb32e7c170134869e848fabfd71be9c8edc0228 Author: delorean Date: Sat Sep 30 18:06:22 2023 -0500 initial diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..a19e287 --- /dev/null +++ b/config.toml @@ -0,0 +1,6 @@ +webroot = "www" +lport = "5000" +vhost = "hardfiles.org" +dbfile = "dbfile.db" +filelen = 6 +folder = "files" \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ebc1c5d --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module hardfiles + +go 1.21.0 + +require ( + github.com/boltdb/bolt v1.3.1 + github.com/gabriel-vasile/mimetype v1.4.2 + github.com/gorilla/mux v1.8.0 + github.com/landlock-lsm/go-landlock v0.0.0-20230607164353-b03374193cb2 + github.com/rs/zerolog v1.31.0 +) + +require ( + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.12.0 // indirect + kernel.org/pub/linux/libs/security/libcap/psx v1.2.66 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..dbebc3a --- /dev/null +++ b/go.sum @@ -0,0 +1,29 @@ +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/landlock-lsm/go-landlock v0.0.0-20230607164353-b03374193cb2 h1:urvYVFXXrgiZWCYCQ6LWEE98QdU4Mvd5zwXuTllWMTg= +github.com/landlock-lsm/go-landlock v0.0.0-20230607164353-b03374193cb2/go.mod h1:D25+lEYNcoxH7SgM/VOvWNrF3ZNAfRdydDuVbQBe6yE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.66 h1:ikIhPzfkSSAEwBOU+2DWhoF+xnGUhvlMTfQjBVhvzQY= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.66/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= diff --git a/main.go b/main.go new file mode 100644 index 0000000..5060b2f --- /dev/null +++ b/main.go @@ -0,0 +1,206 @@ +package main + +import ( + "io" + "math/rand" + "net/http" + "os" + "strconv" + "time" + + "github.com/BurntSushi/toml" + "github.com/boltdb/bolt" + "github.com/gabriel-vasile/mimetype" + "github.com/gorilla/mux" + "github.com/landlock-lsm/go-landlock/landlock" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +var ( + db *bolt.DB + conf Config +) + +type Config struct { + Webroot string `toml:"webroot"` + LPort string `toml:"lport"` + VHost string `toml:"vhost"` + DBFile string `toml:"dbfile"` + FileLen int `toml:"filelen"` + FileFolder string `toml:"folder"` +} + +func LoadConf() { + if _, err := toml.DecodeFile("config.toml", &conf); err != nil { + log.Fatal().Err(err).Msg("unable to parse config.toml") + } +} + +func NameGen() string { + const chars = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789" + ll := len(chars) + b := make([]byte, conf.FileLen) + rand.Read(b) // generates len(b) random bytes + for i := int64(0); i < int64(conf.FileLen); i++ { + b[i] = chars[int(b[i])%ll] + } + return string(b) +} + +func CheckFile(name string) bool { // false if doesn't exist, true if exists + tfd, err := os.Open(conf.FileFolder + "/" + name) + if err != nil { + return false + } + tfd.Close() + return true +} + +func UploadHandler(w http.ResponseWriter, r *http.Request) { + // expiry sanitize + twentyfour := int64(86400) + + file, _, err := r.FormFile("file") + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + defer file.Close() + + mtype, err := mimetype.DetectReader(file) + if err != nil { + w.Write([]byte("error detecting the mime type of your file\n")) + return + } + file.Seek(0, 0) + + // generate + check name + var name string + for { + id := NameGen() + name = id + mtype.Extension() + if !CheckFile(name) { + break + } + } + + err = db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("expiry")) + err := b.Put([]byte(name), []byte(strconv.FormatInt(time.Now().Unix()+twentyfour, 10))) + return err + }) + if err != nil { + log.Error().Err(err).Msg("Failed to put expiry") + } + + log.Info().Int64("expiry", twentyfour).Msg("Writing new file") + + f, err := os.OpenFile(conf.FileFolder+"/"+name, os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + log.Error().Err(err).Msg("Error opening a file for write") + w.WriteHeader(http.StatusInternalServerError) // change to json + return + } + defer f.Close() + + io.Copy(f, file) + + w.Write([]byte("https://" + conf.VHost + "/uploads/" + name)) +} + +func Cull() { + for { + removed := 0 + db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("expiry")) + c := b.Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + eol, err := strconv.ParseInt(string(v), 10, 64) + if err != nil { + log.Error().Err(err).Bytes("k", k).Bytes("v", v).Msg("expiration time could not be parsed") + continue + } + if time.Now().After(time.Unix(eol, 0)) { + os.Remove(conf.FileFolder + "/" + string(k)) + removed += 1 + c.Delete() + } + } + return nil + }) + if removed >= 1 { + log.Info().Int("amount", removed).Msg("expired") + } + time.Sleep(5 * time.Second) + } +} + +func main() { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + LoadConf() + + err := landlock.V2.BestEffort().RestrictPaths( + landlock.RWDirs("./"+conf.FileFolder), + landlock.RWFiles(conf.DBFile), + landlock.RWFiles(conf.Webroot+"/index.html"), + ) + + if err != nil { + log.Warn().Err(err).Msg("could not landlock") + } + + _, err = os.Open("/etc/passwd") + if err == nil { + log.Warn().Msg("landlock failed, could open /etc/passwd") + } else { + log.Info().Err(err).Msg("Landlocked") + } + + db, err = bolt.Open(conf.DBFile, 0600, nil) + if err != nil { + log.Fatal().Err(err).Msg("unable to open database file") + } + db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucketIfNotExists([]byte("expiry")) + if err != nil { + log.Fatal().Err(err).Msg("error creating expiry bucket") + return err + } + return nil + }) + + r := mux.NewRouter() + r.HandleFunc("/", UploadHandler).Methods("POST") + r.HandleFunc("/uploads/{name}", func(w http.ResponseWriter, r *http.Request) { // upload hits + vars := mux.Vars(r) + if !CheckFile(vars["name"]) { + w.WriteHeader(http.StatusNotFound) + } else { + http.ServeFile(w, r, conf.FileFolder+"/"+vars["name"]) + } + }).Methods("GET") + r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, conf.Webroot+"/index.html") + }).Methods("GET") + http.Handle("/", r) + + go Cull() + + serv := &http.Server{ + Addr: ":" + conf.LPort, + Handler: r, + ErrorLog: nil, + //ReadTimeout: 20 * time.Second, + //WriteTimeout: 20 * time.Second, + IdleTimeout: 20 * time.Second, + } + + log.Info().Err(err).Msg("listening...") + + if err := serv.ListenAndServe(); err != nil { + log.Fatal().Err(err).Msg("error starting server") + } + + db.Close() +} diff --git a/www/fist.ico b/www/fist.ico new file mode 100644 index 0000000..ad0892d Binary files /dev/null and b/www/fist.ico differ diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000..e720718 --- /dev/null +++ b/www/index.html @@ -0,0 +1,155 @@ + + + + + + + + + + HARDFILES + + + +
+ +

HARDFILES

+

curl -F file=@example.png https://hardfiles.org/

+
+
+
+
Browse
+
No file chosen...
+ +
+ +
+
+

⚠️ Uploads are erased after 24 hours

+
+ + + + +