1
0
Fork 0

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:
Solderpunk 2023-02-25 12:06:34 +01:00
parent eb85a6e94c
commit f9585ff2b7

View file

@ -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 {