220 lines
5.0 KiB
Go
220 lines
5.0 KiB
Go
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()
|
|
}
|
|
|