Rearrange the logic of handling requests without changing behaviour.
The new order handles certificate zones and redirects defined in the system-wide config file as well as SCGI paths as early as possible without doing any unecessary filesystem operations and especially without the potentially expensive search for .molly files.
This commit is contained in:
parent
eb85a6e94c
commit
f9585ff2b7
1 changed files with 77 additions and 55 deletions
112
handler.go
112
handler.go
|
@ -88,43 +88,6 @@ func handleGeminiRequest(conn net.Conn, sysConfig SysConfig, config UserConfig,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve URI path to actual filesystem path, including following symlinks
|
|
||||||
raw_path := resolvePath(URL.Path, sysConfig)
|
|
||||||
path, err := filepath.EvalSymlinks(raw_path)
|
|
||||||
if err!= nil {
|
|
||||||
log.Println("Error evaluating path " + raw_path + " for symlinks: " + err.Error())
|
|
||||||
conn.Write([]byte("51 Not found!\r\n"))
|
|
||||||
logEntry.Status = 51
|
|
||||||
}
|
|
||||||
|
|
||||||
// If symbolic links have been used to escape the intended document directory,
|
|
||||||
// deny all knowledge
|
|
||||||
isSub, err := isSubdir(path, sysConfig.DocBase)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error testing whether path " + path + " is below DocBase: " + err.Error())
|
|
||||||
}
|
|
||||||
if !isSub {
|
|
||||||
log.Println("Refusing to follow simlink from " + raw_path + " outside of DocBase!")
|
|
||||||
}
|
|
||||||
if err != nil || !isSub {
|
|
||||||
conn.Write([]byte("51 Not found!\r\n"))
|
|
||||||
logEntry.Status = 51
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paranoid security measures:
|
|
||||||
// Fail ASAP if the URL has mapped to a sensitive file
|
|
||||||
if path == sysConfig.CertPath || path == sysConfig.KeyPath || path == sysConfig.AccessLog || path == sysConfig.ErrorLog || filepath.Base(path) == ".molly" {
|
|
||||||
conn.Write([]byte("51 Not found!\r\n"))
|
|
||||||
logEntry.Status = 51
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read Molly files
|
|
||||||
if sysConfig.ReadMollyFiles {
|
|
||||||
config = parseMollyFiles(path, sysConfig.DocBase, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether this URL is in a certificate zone
|
// Check whether this URL is in a certificate zone
|
||||||
handleCertificateZones(URL, clientCerts, config, conn, &logEntry)
|
handleCertificateZones(URL, clientCerts, config, conn, &logEntry)
|
||||||
if logEntry.Status != 0 {
|
if logEntry.Status != 0 {
|
||||||
|
@ -145,18 +108,33 @@ func handleGeminiRequest(conn net.Conn, sysConfig SysConfig, config UserConfig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether this URL is in a configured CGI path
|
// Resolve URI path to actual filesystem path
|
||||||
for _, cgiPath := range sysConfig.CGIPaths {
|
path := resolvePath(URL.Path, sysConfig)
|
||||||
if strings.HasPrefix(path, cgiPath) {
|
|
||||||
handleCGI(sysConfig, path, cgiPath, URL, &logEntry, conn)
|
// Read Molly files. Yes, even before checking if `path` exists!
|
||||||
|
// /foo/bar/baz.gmi may not exist on the disk but /foo/.molly may and it
|
||||||
|
// may inform us that /foo/bar/baz.gmi ought to redirect to somewhere which
|
||||||
|
// *does* exist on disk!
|
||||||
|
if sysConfig.ReadMollyFiles {
|
||||||
|
config = parseMollyFiles(path, sysConfig.DocBase, config)
|
||||||
|
// We may have picked up new cert zones and/or redirects above, so:
|
||||||
|
handleCertificateZones(URL, clientCerts, config, conn, &logEntry)
|
||||||
|
if logEntry.Status != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleRedirects(URL, config, conn, &logEntry)
|
||||||
if logEntry.Status != 0 {
|
if logEntry.Status != 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Fail if file does not exist or perms aren't right
|
// Okay, at this point we really are committed to looking on disk for `path`.
|
||||||
info, err := os.Stat(path)
|
// Make sure it exists, and is world readable, and if it's a symbolic link,
|
||||||
|
// follow it and check these things again!
|
||||||
|
rawPath := path
|
||||||
|
var info os.FileInfo
|
||||||
|
for {
|
||||||
|
info, err = os.Stat(path)
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if os.IsNotExist(err) || os.IsPermission(err) {
|
||||||
conn.Write([]byte("51 Not found!\r\n"))
|
conn.Write([]byte("51 Not found!\r\n"))
|
||||||
logEntry.Status = 51
|
logEntry.Status = 51
|
||||||
|
@ -171,8 +149,52 @@ func handleGeminiRequest(conn net.Conn, sysConfig SysConfig, config UserConfig,
|
||||||
logEntry.Status = 51
|
logEntry.Status = 51
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
newPath, err := filepath.EvalSymlinks(path)
|
||||||
|
if err!= nil {
|
||||||
|
log.Println("Error evaluating path " + path + " for symlinks: " + err.Error())
|
||||||
|
conn.Write([]byte("51 Not found!\r\n"))
|
||||||
|
logEntry.Status = 51
|
||||||
|
}
|
||||||
|
if newPath == path {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
path = newPath
|
||||||
|
}
|
||||||
|
|
||||||
// Finally, serve the file or directory
|
// If symbolic links have been used to escape the intended document directory,
|
||||||
|
// deny all knowledge
|
||||||
|
isSub, err := isSubdir(path, sysConfig.DocBase)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error testing whether path " + path + " is below DocBase: " + err.Error())
|
||||||
|
}
|
||||||
|
if !isSub {
|
||||||
|
log.Println("Refusing to follow simlink from " + rawPath + " outside of DocBase!")
|
||||||
|
}
|
||||||
|
if err != nil || !isSub {
|
||||||
|
conn.Write([]byte("51 Not found!\r\n"))
|
||||||
|
logEntry.Status = 51
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refuse to serve sensitive files even if they are inside DocBase and
|
||||||
|
// world-readable because if they are it's likely a mistake
|
||||||
|
if path == sysConfig.KeyPath || path == sysConfig.AccessLog || path == sysConfig.ErrorLog || filepath.Base(path) == ".molly" {
|
||||||
|
conn.Write([]byte("51 Not found!\r\n"))
|
||||||
|
logEntry.Status = 51
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether this URL is in a configured CGI path
|
||||||
|
for _, cgiPath := range sysConfig.CGIPaths {
|
||||||
|
if strings.HasPrefix(path, cgiPath) {
|
||||||
|
handleCGI(sysConfig, path, cgiPath, URL, &logEntry, conn)
|
||||||
|
if logEntry.Status != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, serve a simple static file or directory
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
serveDirectory(URL, path, &logEntry, conn, config)
|
serveDirectory(URL, path, &logEntry, conn, config)
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue