From 2dfaed4fa3c0833a3088259546c51e186d5445f6 Mon Sep 17 00:00:00 2001 From: hedy Date: Sat, 12 Mar 2022 15:45:18 +0800 Subject: [PATCH] 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 --- README.md | 11 ++++++++++- config.go | 2 ++ spsrv.go | 47 +++++++++++++++++++++++++++++++++++------------ 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 2e43110..afbaa1e 100644 --- a/README.md +++ b/README.md @@ -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 ``` diff --git a/config.go b/config.go index 34db231..be9663e 100644 --- a/config.go +++ b/config.go @@ -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 } diff --git a/spsrv.go b/spsrv.go index f544287..b1ce2cc 100644 --- a/spsrv.go +++ b/spsrv.go @@ -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 == "" {