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:
hedy 2022-03-12 15:45:18 +08:00
parent efbf17cfd6
commit 2dfaed4fa3
Signed by: hedy
GPG Key ID: B51B5A8D1B176372
3 changed files with 47 additions and 13 deletions

View File

@ -86,6 +86,7 @@ Here are the config options and their default values
* `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/`
* `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**
@ -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] hostname, port
- [x] public dir
- [ ] dirlist title
- [x] dirlist title
- [x] user vhost
- [ ] userdir slug
- [ ] redirects
- [x] CGI
- [x] pipe data block
- [ ] user cgi config and change uid to user
- [ ] regex in cgi paths
- [ ] SCGI
- [ ] Multiple servers with each of their own confs
README:
- [ ] Add example confs
- [ ] Add example .service files
```

View File

@ -15,6 +15,7 @@ type Config struct {
RootDir string
UserDirEnable bool
UserDir string
UserSubdomains bool
DirlistEnable bool
DirlistReverse bool
DirlistSort string
@ -33,6 +34,7 @@ var defaultConf = &Config{
DirlistTitles: true,
UserDirEnable: true,
UserDir: "public_spartan",
UserSubdomains: false,
CGIPaths: []string{"cgi/"},
UserCGIEnable: false, // Turned off by default because scripts are run by server user as of now
}

View File

@ -21,6 +21,7 @@ import (
type Request struct {
conn io.ReadWriteCloser
netConn *net.Conn
vhost string
user string
path string // Requested path
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")
return
}
userSubdomainReq := false
if conf.Hostname != "" {
if conf.Hostname != host {
log.Println("Request host does not match config value Hostname, returning client error.")
sendResponseHeader(conn, statusClientError, "No proxying to other hosts!")
return
if conf.UserDirEnable && conf.UserSubdomains && strings.HasSuffix(host, conf.Hostname) {
userSubdomainReq = true
}
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, "..") {
@ -185,7 +192,13 @@ func handleConnection(netConn net.Conn, conf *Config) {
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!
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.
// 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) {
// Handle tildes
if conf.UserDirEnable && strings.HasPrefix(reqPath, "/~") {
var user string
// 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, "/")
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
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)
req.user = username
path = filepath.Clean(strings.Replace(reqPath, bits[1], new_prefix, 1))
if user != "" {
path = filepath.Join("/home/", user, conf.UserDir, path)
req.user = user
if strings.HasSuffix(reqPath, "/") {
path = filepath.Join(path, "index.gmi")
}
return
}
path = reqPath
// TODO: [config] default index file for a directory is index.gmi
if strings.HasSuffix(reqPath, "/") || reqPath == "" {