package main import ( "crypto/tls" "fmt" "io" "math/rand" "net/http" "os" "strconv" "strings" "time" "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 // config var ( index = "index.html" folder = "data" dbfile = "dbfile.db" filelen = 6 lport = "443" vhost = "filetray.co" key = "privkey.pem" cert = "cert.pem" ) // end config func redir(w http.ResponseWriter, r *http.Request) { target := "https://" + r.Host + r.URL.Path if len(r.URL.RawQuery) > 0 { target += "?" + r.URL.RawQuery } http.Redirect(w, r, target, http.StatusTemporaryRedirect) } func NameGen() string { const chars = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789" ll := len(chars) b := make([]byte, filelen) rand.Read(b) // generates len(b) random bytes for i := int64(0); i < int64(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(folder + "/" + name) if err != nil { return false } tfd.Close() return true } func UploadHandler(w http.ResponseWriter, r *http.Request) { // expiry sanitize sanExpiry := 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()+sanExpiry, 10))) return err }) if err != nil { log.Error().Err(err).Msg("Failed to put expiry") } log.Info().Int64("expiry", sanExpiry).Msg("Writing new file") f, err := os.OpenFile(folder+"/"+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://" + vhost + "/uploads/" + name)) } func Expiry() { 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() { expiryTime, err := strconv.ParseInt(string(v), 10, 64) if err != nil { log.Error().Err(err).Bytes("k", k).Bytes("v", v).Msg("Expiry time could not be parsed") continue } if time.Now().After(time.Unix(expiryTime, 0)) { os.Remove(folder + "/" + string(k)) removed += 1 c.Delete() } } return nil }) if removed >= 1 { log.Info().Int("amount", removed).Msg("Purged based on expiry") } time.Sleep(5 * time.Second) } } func main() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) err := landlock.V2.BestEffort().RestrictPaths( landlock.RWDirs("./"+folder), landlock.RWFiles(dbfile), landlock.RWFiles(index), ) 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(dbfile, 0600, nil) if err != nil { log.Fatal().Err(err).Msg("dangerous database activity") } 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 { t := time.Now() deets := fmt.Sprintf("HIT -> %s visited by %s at %d:%d %s %d\n `--- User Agent: %s", r.RequestURI, strings.Split(r.RemoteAddr, ":")[0], t.Hour(), t.Minute(), t.Month(), t.Day(), r.UserAgent()) log.Info().Err(err).Msg(deets) http.ServeFile(w, r, folder+"/"+vars["name"]) } }).Methods("GET") r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, index) }).Methods("GET") http.Handle("/", r) go Expiry() serv := &http.Server{ Addr: ":" + lport, Handler: r, ErrorLog: nil, //ReadTimeout: 20 * time.Second, //WriteTimeout: 20 * time.Second, IdleTimeout: 20 * time.Second, TLSConfig: &tls.Config{ServerName: "heehee"}, } log.Info().Err(err).Msg("Listening...") go http.ListenAndServe(":80", http.HandlerFunc(redir)) if err := serv.ListenAndServeTLS(cert, key); err != nil { log.Fatal().Err(err).Msg("Error starting TLS server") } db.Close() }