Feat: Per-user vhosts (user.sometilde.org)
Limitations: - Doesn't care about `extra.stuff.here.user.host.name` - No custom hostname for user subdomains - Returns not-found for `user.host.name` where `/home/user/userdir/` doesn't exist
This commit is contained in:
parent
efbf17cfd6
commit
2dfaed4fa3
11
README.md
11
README.md
|
@ -86,6 +86,7 @@ Here are the config options and their default values
|
||||||
|
|
||||||
* `userdirEnable=true`: enable serving `/~user/*` requests
|
* `userdirEnable=true`: enable serving `/~user/*` requests
|
||||||
* `userdir="public_spartan"`: root directory for users. This should not have trailing slashes, and it is relative to `/home/user/`
|
* `userdir="public_spartan"`: root directory for users. This should not have trailing slashes, and it is relative to `/home/user/`
|
||||||
|
* `userSubdomains=false`: User vhosts. Whether to allow `user.host.name/foo.txt` being the same as `host.name/~user/foo.txt` (When `hostname="host.name"`). **NOTE**: This only works when `hostname` option is set.
|
||||||
|
|
||||||
**CGI**
|
**CGI**
|
||||||
|
|
||||||
|
@ -143,11 +144,19 @@ Keep in mind that CGI scripts (as of now) are run by the same user as the server
|
||||||
- [x] user homedir
|
- [x] user homedir
|
||||||
- [x] hostname, port
|
- [x] hostname, port
|
||||||
- [x] public dir
|
- [x] public dir
|
||||||
- [ ] dirlist title
|
- [x] dirlist title
|
||||||
|
- [x] user vhost
|
||||||
- [ ] userdir slug
|
- [ ] userdir slug
|
||||||
- [ ] redirects
|
- [ ] redirects
|
||||||
- [x] CGI
|
- [x] CGI
|
||||||
- [x] pipe data block
|
- [x] pipe data block
|
||||||
- [ ] user cgi config and change uid to user
|
- [ ] user cgi config and change uid to user
|
||||||
- [ ] regex in cgi paths
|
- [ ] regex in cgi paths
|
||||||
|
- [ ] SCGI
|
||||||
|
|
||||||
|
- [ ] Multiple servers with each of their own confs
|
||||||
|
|
||||||
|
README:
|
||||||
|
- [ ] Add example confs
|
||||||
|
- [ ] Add example .service files
|
||||||
```
|
```
|
||||||
|
|
|
@ -15,6 +15,7 @@ type Config struct {
|
||||||
RootDir string
|
RootDir string
|
||||||
UserDirEnable bool
|
UserDirEnable bool
|
||||||
UserDir string
|
UserDir string
|
||||||
|
UserSubdomains bool
|
||||||
DirlistEnable bool
|
DirlistEnable bool
|
||||||
DirlistReverse bool
|
DirlistReverse bool
|
||||||
DirlistSort string
|
DirlistSort string
|
||||||
|
@ -33,6 +34,7 @@ var defaultConf = &Config{
|
||||||
DirlistTitles: true,
|
DirlistTitles: true,
|
||||||
UserDirEnable: true,
|
UserDirEnable: true,
|
||||||
UserDir: "public_spartan",
|
UserDir: "public_spartan",
|
||||||
|
UserSubdomains: false,
|
||||||
CGIPaths: []string{"cgi/"},
|
CGIPaths: []string{"cgi/"},
|
||||||
UserCGIEnable: false, // Turned off by default because scripts are run by server user as of now
|
UserCGIEnable: false, // Turned off by default because scripts are run by server user as of now
|
||||||
}
|
}
|
||||||
|
|
47
spsrv.go
47
spsrv.go
|
@ -21,6 +21,7 @@ import (
|
||||||
type Request struct {
|
type Request struct {
|
||||||
conn io.ReadWriteCloser
|
conn io.ReadWriteCloser
|
||||||
netConn *net.Conn
|
netConn *net.Conn
|
||||||
|
vhost string
|
||||||
user string
|
user string
|
||||||
path string // Requested path
|
path string // Requested path
|
||||||
filePath string // Actual file path that does not include the content dir name
|
filePath string // Actual file path that does not include the content dir name
|
||||||
|
@ -155,11 +156,17 @@ func handleConnection(netConn net.Conn, conf *Config) {
|
||||||
sendResponseHeader(conn, statusClientError, "Bad request")
|
sendResponseHeader(conn, statusClientError, "Bad request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
userSubdomainReq := false
|
||||||
if conf.Hostname != "" {
|
if conf.Hostname != "" {
|
||||||
if conf.Hostname != host {
|
if conf.Hostname != host {
|
||||||
log.Println("Request host does not match config value Hostname, returning client error.")
|
if conf.UserDirEnable && conf.UserSubdomains && strings.HasSuffix(host, conf.Hostname) {
|
||||||
sendResponseHeader(conn, statusClientError, "No proxying to other hosts!")
|
userSubdomainReq = true
|
||||||
return
|
}
|
||||||
|
if !userSubdomainReq {
|
||||||
|
log.Println("Request host does not match config value Hostname, returning client error.")
|
||||||
|
sendResponseHeader(conn, statusClientError, "No proxying to other hosts!")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.Contains(reqPath, "..") {
|
if strings.Contains(reqPath, "..") {
|
||||||
|
@ -185,7 +192,13 @@ func handleConnection(netConn net.Conn, conf *Config) {
|
||||||
data += newData
|
data += newData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req := &Request{path: reqPath, netConn: &netConn, conn: conn, data: data, dataLen: dataLen}
|
|
||||||
|
var vhost string
|
||||||
|
if userSubdomainReq {
|
||||||
|
// TODO: Handle extra dots like a.b.host.name?
|
||||||
|
vhost = strings.TrimSuffix(host, "."+conf.Hostname)
|
||||||
|
}
|
||||||
|
req := &Request{vhost: vhost, path: reqPath, netConn: &netConn, conn: conn, data: data, dataLen: dataLen}
|
||||||
|
|
||||||
// Time to fetch the files!
|
// Time to fetch the files!
|
||||||
path := resolvePath(reqPath, conf, req)
|
path := resolvePath(reqPath, conf, req)
|
||||||
|
@ -219,25 +232,35 @@ func handleConnection(netConn net.Conn, conf *Config) {
|
||||||
// resolvePath takes in teh request path and returns the cleaned filepath that needs to be fetched.
|
// resolvePath takes in teh request path and returns the cleaned filepath that needs to be fetched.
|
||||||
// It also handles user directories paths /~user/ and /~user if user directories is enabled in the config.
|
// It also handles user directories paths /~user/ and /~user if user directories is enabled in the config.
|
||||||
func resolvePath(reqPath string, conf *Config, req *Request) (path string) {
|
func resolvePath(reqPath string, conf *Config, req *Request) (path string) {
|
||||||
// Handle tildes
|
var user string
|
||||||
if conf.UserDirEnable && strings.HasPrefix(reqPath, "/~") {
|
// Handle user subdomains
|
||||||
|
if req.vhost != "" {
|
||||||
|
user = req.vhost
|
||||||
|
path = reqPath
|
||||||
|
} else if conf.UserDirEnable && strings.HasPrefix(reqPath, "/~") {
|
||||||
|
// Handle tildes
|
||||||
|
// Note that user.host.name/~user/ would treat it as a literal folder named /~user/
|
||||||
|
// (hence using `else if`)
|
||||||
bits := strings.Split(reqPath, "/")
|
bits := strings.Split(reqPath, "/")
|
||||||
username := bits[1][1:]
|
user = bits[1][1:]
|
||||||
|
|
||||||
// /~user to /~user/ is somehow able to be handled together with any other /folder to /foler/ redirects
|
// /~user to /~user/ is somehow able to be handled together with any other /folder to /folder/ redirects
|
||||||
// So I won't worry about that nor handle it specifically
|
// So I won't worry about that nor handle it specifically
|
||||||
|
|
||||||
req.filePath = strings.TrimPrefix(filepath.Clean(strings.TrimPrefix(reqPath, "/~"+username)), "/")
|
req.filePath = strings.TrimPrefix(filepath.Clean(strings.TrimPrefix(reqPath, "/~"+user)), "/")
|
||||||
|
path = req.filePath
|
||||||
|
}
|
||||||
|
|
||||||
new_prefix := filepath.Join("/home/", username, conf.UserDir)
|
if user != "" {
|
||||||
req.user = username
|
path = filepath.Join("/home/", user, conf.UserDir, path)
|
||||||
path = filepath.Clean(strings.Replace(reqPath, bits[1], new_prefix, 1))
|
req.user = user
|
||||||
|
|
||||||
if strings.HasSuffix(reqPath, "/") {
|
if strings.HasSuffix(reqPath, "/") {
|
||||||
path = filepath.Join(path, "index.gmi")
|
path = filepath.Join(path, "index.gmi")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
path = reqPath
|
path = reqPath
|
||||||
// TODO: [config] default index file for a directory is index.gmi
|
// TODO: [config] default index file for a directory is index.gmi
|
||||||
if strings.HasSuffix(reqPath, "/") || reqPath == "" {
|
if strings.HasSuffix(reqPath, "/") || reqPath == "" {
|
||||||
|
|
Loading…
Reference in New Issue