Compare commits

...

22 Commits
v1.1.1 ... main

Author SHA1 Message Date
Aaron Bieber e3b3dad3ee clean our passPath for good measure 2021-12-10 15:50:28 -07:00
Aaron Bieber 14bdf17e49 Add version flag 2021-12-10 15:50:07 -07:00
Aaron Bieber 0ea90c5ddd switch to term package 2021-12-10 15:49:52 -07:00
Aaron Bieber a86c07b5e8 add goreleaser config 2021-12-10 15:49:29 -07:00
Aaron Bieber d11f802f05
Merge pull request #5
Adds header authentication
2021-12-10 06:52:44 -07:00
Aaron Bieber e0ee714d3e
Merge pull request #8 from qbit/TW_5.2.1
update TW to 5.2.1
2021-12-10 06:35:16 -07:00
Aaron Bieber db7f06dc5b
Merge pull request #7 from qbit/muxd-handlers
Store our handlers in a slice and lock them when they are being used.
2021-12-10 06:34:42 -07:00
Aaron Bieber 68c586f805 use RWLock on handlers, fix close bug 2021-12-10 06:27:27 -07:00
Aaron Bieber 8c113d95c1 tidy 2021-12-10 06:26:31 -07:00
Aaron Bieber 1112c9874e update TW to 5.2.1 2021-12-09 06:28:37 -07:00
Aaron Bieber 18a4ed5026 remove extra pledges var 2021-12-09 05:59:56 -07:00
Aaron Bieber f9de42e801 Don't assign var 2021-12-09 05:48:34 -07:00
Aaron Bieber 4fdac0116f Store our handlers in a slice and lock them when they are being used.
Hopefully this helps with #3 (at least the HTTP 423 errors).
2021-12-09 05:16:00 -07:00
Aaron Bieber 0205c58868
Merge pull request #6 from bentsai/patch-1
Fix typo
2021-12-08 19:12:38 -07:00
Ben Tsai 4e224c9c08
Fix typo 2021-12-08 21:11:22 -05:00
Ysbrand van Eijck fc9da8e1f3
Adds missing break in header authentication 2021-12-08 13:48:13 +01:00
Ysbrand van Eijck 3ddc711cc1
Adds header authentication 2021-12-08 13:42:48 +01:00
Aaron Bieber 6e2f8a7495 Add new examples and expand installation info 2021-12-06 06:13:24 -07:00
Aaron Bieber 2551d2d9a0 remove useless []byte() 2021-12-06 06:07:35 -07:00
Aaron Bieber 3fb82cec02 update deps 2021-12-06 06:00:04 -07:00
Aaron Bieber 574110c52f update to TW 5.2.0 2021-12-06 05:45:41 -07:00
Aaron Bieber 3849f2ba51 switch to using ReducePledges 2021-07-08 19:05:28 -06:00
8 changed files with 3194 additions and 12946 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
widdler
.htpasswd
dist/

55
.goreleaser.yml Normal file
View File

@ -0,0 +1,55 @@
before:
hooks:
- go mod tidy
- go vet
- staticcheck
- gosec .
builds:
- env:
- CGO_ENABLED=0
targets:
- openbsd_amd64
- openbsd_arm64
- openbsd_386
- openbsd_arm
- freebsd_amd64
- freebsd_arm64
- freebsd_386
- darwin_amd64
- darwin_arm64
- windows_amd64
- netbsd_amd64
- netbsd_arm64
- netbsd_386
- dragonfly_amd64
goos:
- openbsd
- freebsd
- netbsd
- dragonfly
- linux
- windows
- darwin
ldflags:
- -s -w -X main.build={{.Version}}
archives:
- replacements:
openbsd: OpenBSD
freebsd: FreeBSD
netbsd: NetBSD
dragonfly: DragonflyBSD
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'

View File

@ -4,7 +4,7 @@ widdler
widdler is a single binary that serves up
[TiddlyWiki](https://tiddlywiki.com)s.
It can be used to server existing wikis, or to create new ones.
It can be used to serve existing wikis, or to create new ones.
# Features
@ -18,10 +18,16 @@ It can be used to server existing wikis, or to create new ones.
# Installation
For Go 1.16:
```
go get -u suah.dev/widdler
```
For Go 1.17 and up:
```
go install suah.dev/widdler@latest
```
# Running
```
@ -46,3 +52,16 @@ create the wiki file based off the current `empty.html` TiddlyWiki version.
Simply hit the save button!
# Updating widdler
```
go install suah.dev/widdler@latest
```
# Running without .htpasswd
You can disable auth all together by setting the `-auth` flag to false:
```
widdler -auth=false -wikis ~/wiki
```

File diff suppressed because one or more lines are too long

3007
empty-5.2.1.html Normal file

File diff suppressed because one or more lines are too long

8
go.mod
View File

@ -3,7 +3,9 @@ module suah.dev/widdler
go 1.16
require (
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
golang.org/x/net v0.0.0-20210510120150-4163338589ed
suah.dev/protect v1.0.0
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e
golang.org/x/net v0.0.0-20211205041911-012df41ee64c
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
suah.dev/protect v1.2.0
)

24
go.sum
View File

@ -1,16 +1,18 @@
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e h1:MUP6MR3rJ7Gk9LEia0LP2ytiH6MuCfs7qYz+47jGdD8=
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211205041911-012df41ee64c h1:7SfqwP5fxEtl/P02w5IhKc86ziJ+A25yFrkVgoy2FT8=
golang.org/x/net v0.0.0-20211205041911-012df41ee64c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
suah.dev/protect v1.0.0 h1:X8pzDvDIZIiugmkmr6DES6JFO1XUdJWi34Ffmk6CMZY=
suah.dev/protect v1.0.0/go.mod h1:ZSgyBM30JUwhVPWJzVHh0jlu5W6Qz1VR6tIhAzqJZ9Y=
suah.dev/protect v1.2.0 h1:4G4V43yVYXCjLFzaE9QJR0fLo3rf5vNBS9YxyoI19DU=
suah.dev/protect v1.2.0/go.mod h1:Ocn1yqUskqe/is6N2bxQxtT+fegbvQsOFyHbJAQu9XE=

124
main.go
View File

@ -15,12 +15,13 @@ import (
"path/filepath"
"regexp"
"strings"
"sync"
"text/template"
"time"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/net/webdav"
"golang.org/x/term"
"suah.dev/protect"
)
@ -45,34 +46,53 @@ const landingPage = `
`
var (
twFile = "empty-5.1.23.html"
twFile = "empty-5.2.1.html"
//go:embed empty-5.1.23.html
//go:embed empty-5.2.1.html
tiddly embed.FS
templ *template.Template
)
type userHandler struct {
mu sync.Mutex
dav *webdav.Handler
fs http.Handler
name string
}
type userHandlers struct {
dav *webdav.Handler
fs http.Handler
list []userHandler
mu sync.RWMutex
}
func (u *userHandlers) find(name string) *userHandler {
for i := range u.list {
if u.list[i].name == name {
return &u.list[i]
}
}
return nil
}
var (
auth bool
auth string
davDir string
fullListen string
genHtpass bool
handlers map[string]userHandlers
handlers userHandlers
listen string
passPath string
tlsCert string
tlsKey string
users map[string]string
version bool
build string
)
var pledges = "stdio wpath rpath cpath tty inet dns unveil"
func init() {
users = make(map[string]string)
handlers = make(map[string]userHandlers)
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
log.Fatalln(err)
@ -83,8 +103,9 @@ func init() {
flag.StringVar(&tlsCert, "tlscert", "", "TLS certificate.")
flag.StringVar(&tlsKey, "tlskey", "", "TLS key.")
flag.StringVar(&passPath, "htpass", fmt.Sprintf("%s/.htpasswd", dir), "Path to .htpasswd file..")
flag.BoolVar(&auth, "auth", true, "Enable HTTP Basic Authentication.")
flag.StringVar(&auth, "auth", "basic", "Enable HTTP Basic Authentication.")
flag.BoolVar(&genHtpass, "gen", false, "Generate a .htpasswd file or add a new entry to an existing file.")
flag.BoolVar(&version, "v", false, "Show version and exit.")
flag.Parse()
// These are OpenBSD specific protections used to prevent unnecessary file access.
@ -92,13 +113,12 @@ func init() {
_ = protect.Unveil(davDir, "rwc")
_ = protect.Unveil("/etc/ssl/cert.pem", "r")
_ = protect.Unveil("/etc/resolv.conf", "r")
_ = protect.Pledge("stdio wpath rpath cpath tty inet dns unveil")
_ = protect.Pledge(pledges)
templ, err = template.New("landing").Parse(landingPage)
if err != nil {
log.Fatalln(err)
}
}
func authenticate(user string, pass string) bool {
@ -132,7 +152,7 @@ func createEmpty(path string) error {
if os.IsNotExist(fErr) {
log.Printf("creating %q\n", path)
twData, _ := tiddly.ReadFile(twFile)
wErr := ioutil.WriteFile(path, []byte(twData), 0600)
wErr := ioutil.WriteFile(path, twData, 0600)
if wErr != nil {
return wErr
}
@ -145,20 +165,25 @@ func prompt(prompt string, secure bool) (string, error) {
fmt.Print(prompt)
if secure {
b, err := terminal.ReadPassword(int(os.Stdin.Fd()))
b, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", err
}
input = string(b)
} else {
fmt.Scanln(&input)
_, err := fmt.Scanln(&input)
if err != nil {
return "", err
}
}
return input, nil
}
func main() {
_ = protect.Pledge("stdio wpath rpath cpath inet dns unveil")
if version {
fmt.Println(build)
os.Exit(0)
}
if genHtpass {
user, err := prompt("Username: ", false)
if err != nil {
@ -175,38 +200,41 @@ func main() {
log.Fatalln(err)
}
f, err := os.OpenFile(passPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
f, err := os.OpenFile(filepath.Clean(passPath), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
log.Fatalln(err)
}
defer f.Close()
if _, err := f.WriteString(fmt.Sprintf("%s:%s\n", user, hash)); err != nil {
log.Fatalln(err)
}
err = f.Close()
if err != nil {
log.Fatalln(err)
}
fmt.Printf("Added %q to %q\n", user, passPath)
os.Exit(0)
}
pledges, _ = protect.ReducePledges(pledges, "tty")
// drop to only read on passPath
_ = protect.Unveil(passPath, "r")
_ = protect.Pledge("stdio wpath rpath cpath inet dns")
pledges, _ = protect.ReducePledges(pledges, "unveil")
_, fErr := os.Stat(passPath)
if os.IsNotExist(fErr) {
if auth {
if auth == "basic" || auth == "header" {
fmt.Println("No .htpasswd file found!")
os.Exit(1)
}
} else {
p, err := os.Open(passPath)
p, err := os.Open(filepath.Clean(passPath))
if err != nil {
log.Fatal(err)
}
defer p.Close()
ht := csv.NewReader(p)
ht.Comma = ':'
@ -218,30 +246,37 @@ func main() {
log.Fatal(err)
}
err = p.Close()
if err != nil {
log.Fatal(err)
}
for _, parts := range entries {
users[parts[0]] = parts[1]
}
}
if auth {
if auth == "basic" || auth == "header" {
for u := range users {
uPath := path.Join(davDir, u)
handlers[u] = userHandlers{
handlers.list = append(handlers.list, userHandler{
name: u,
dav: &webdav.Handler{
LockSystem: webdav.NewMemLS(),
FileSystem: webdav.Dir(uPath),
},
fs: http.FileServer(http.Dir(uPath)),
}
})
}
} else {
handlers[""] = userHandlers{
handlers.list = append(handlers.list, userHandler{
name: "",
dav: &webdav.Handler{
LockSystem: webdav.NewMemLS(),
FileSystem: webdav.Dir(davDir),
},
fs: http.FileServer(http.Dir(davDir)),
}
})
}
mux := http.NewServeMux()
@ -260,8 +295,24 @@ func main() {
return
}
if auth {
if auth == "basic" {
user, pass, ok = r.BasicAuth()
if !(ok && authenticate(user, pass)) {
w.Header().Set("WWW-Authenticate", `Basic realm="widdler"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
} else if auth == "header" {
var prefix = "Auth"
for name, values := range r.Header {
if strings.HasPrefix(name, prefix) {
user = strings.TrimLeft(name, prefix)
pass = values[0]
ok = true
break
}
}
if !(ok && authenticate(user, pass)) {
w.Header().Set("WWW-Authenticate", `Basic realm="widdler"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
@ -269,7 +320,19 @@ func main() {
}
}
handler := handlers[user]
handlers.mu.RLock()
handler := handlers.find(user)
handlers.mu.RUnlock()
if handler == nil {
http.NotFound(w, r)
return
}
handler.mu.Lock()
defer handler.mu.Unlock()
userPath := path.Join(davDir, user)
fullPath := path.Join(davDir, user, r.URL.Path)
@ -350,5 +413,4 @@ func main() {
log.Printf("Listening for HTTP on 'http://%s'", listen)
log.Fatalln(s.Serve(lis))
}
}