From 77691d698376fda405a822b9af9ce367ccf5159f Mon Sep 17 00:00:00 2001 From: Solderpunk Date: Sun, 28 Jun 2020 18:34:50 +0200 Subject: [PATCH] Introduce error log. --- README.md | 5 ++++- config.go | 6 ++++-- dynamic.go | 6 ++++-- example.conf | 3 ++- handler.go | 25 ++++++++++++++----------- main.go | 29 +++++++++++++++++++++-------- 6 files changed, 49 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index dac5489..68ab53b 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,10 @@ The following options can be set in `/etc/molly.conf`: actual home directories like you may expect based on experience with other server software. Of course, you can symlink `/var/gemini/users/gus/` to `/home/gus/public_gemini/` if you want. -* `LogPath`: Path to log file (default value `molly.log`). Note that +* `AccessLog`: Path to access log file (default value `access.log`, i.e. in the current wrorking directory). Note that + all intermediate directories must exist, Molly Brown won't create + them for you. +* `ErrorLog`: Path to error log file (default value `error.log`, i.e. in the current wrorking directory). Note that all intermediate directories must exist, Molly Brown won't create them for you. * `MimeOverrides`: A map from path regexs to MIME types. If the path of a file which is about to be served matches the regex, the specified MIME type will be used instead of one inferred from the filename extension. diff --git a/config.go b/config.go index d089ffc..c44fe35 100644 --- a/config.go +++ b/config.go @@ -14,7 +14,8 @@ type Config struct { HomeDocBase string GeminiExt string DefaultLang string - LogPath string + AccessLog string + ErrorLog string TempRedirects map[string]string PermRedirects map[string]string MimeOverrides map[string]string @@ -47,7 +48,8 @@ func getConfig(filename string) (Config, error) { config.HomeDocBase = "users" config.GeminiExt = "gmi" config.DefaultLang = "" - config.LogPath = "molly.log" + config.AccessLog = "access.log" + config.ErrorLog = "error.log" config.TempRedirects = make(map[string]string) config.PermRedirects = make(map[string]string) config.CGIPaths = make([]string, 0) diff --git a/dynamic.go b/dynamic.go index 1627147..d451173 100644 --- a/dynamic.go +++ b/dynamic.go @@ -16,7 +16,7 @@ import ( "time" ) -func handleCGI(config Config, path string, URL *url.URL, log *LogEntry, conn net.Conn) { +func handleCGI(config Config, path string, URL *url.URL, log *LogEntry, errorLog chan string, conn net.Conn) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() cmd := exec.CommandContext(ctx, path) @@ -27,14 +27,15 @@ func handleCGI(config Config, path string, URL *url.URL, log *LogEntry, conn net for key, value := range vars { cmd.Env = append(cmd.Env, key+"="+value) } - response, err := cmd.Output() if ctx.Err() == context.DeadlineExceeded { + errorLog <- "Terminating CGI process " + path + " due to exceeding 10 second runtime limit." conn.Write([]byte("42 CGI process timed out!\r\n")) log.Status = 42 return } if err != nil { + errorLog <- "Error starting CGI executable " + path + ": " + err.Error() conn.Write([]byte("42 CGI error!\r\n")) log.Status = 42 return @@ -43,6 +44,7 @@ func handleCGI(config Config, path string, URL *url.URL, log *LogEntry, conn net header, _, err := bufio.NewReader(strings.NewReader(string(response))).ReadLine() status, err2 := strconv.Atoi(strings.Fields(string(header))[0]) if err != nil || err2 != nil { + errorLog <- "Unable to parse first line of output from CGI process " + path + " as valid Gemini response header." conn.Write([]byte("42 CGI error!\r\n")) log.Status = 42 return diff --git a/example.conf b/example.conf index 61772e7..80d32e1 100644 --- a/example.conf +++ b/example.conf @@ -5,7 +5,8 @@ #DocBase = "/var/gemini/" #HomeDocBase = "users" #GeminiExt = "gmi" -#LogPath = "molly.log" +#AccessLog = "/var/log/molly/access.log" +#ErrorLog = "/var/log/molly/error.log" #CGIPaths = [ "^/var/gemini/cgi-bin/" ] #[SCGIPaths] #"/scgi/" = "/var/run/scgi.sock" diff --git a/handler.go b/handler.go index c4cc0b8..a71dc35 100644 --- a/handler.go +++ b/handler.go @@ -19,7 +19,7 @@ import ( "time" ) -func handleGeminiRequest(conn net.Conn, config Config, logEntries chan LogEntry) { +func handleGeminiRequest(conn net.Conn, config Config, accessLogEntries chan LogEntry, errorLogEntries chan string) { defer conn.Close() var tlsConn (*tls.Conn) = conn.(*tls.Conn) var log LogEntry @@ -27,10 +27,10 @@ func handleGeminiRequest(conn net.Conn, config Config, logEntries chan LogEntry) log.RemoteAddr = conn.RemoteAddr() log.RequestURL = "-" log.Status = 0 - defer func() { logEntries <- log }() + defer func() { accessLogEntries <- log }() // Read request - URL, err := readRequest(conn, &log) + URL, err := readRequest(conn, &log, errorLogEntries) if err != nil { return } @@ -149,7 +149,7 @@ func handleGeminiRequest(conn net.Conn, config Config, logEntries chan LogEntry) // Paranoid security measure: // Fail if the URL has mapped to our TLS files or the log - if path == config.CertPath || path == config.KeyPath || path == config.LogPath { + if path == config.CertPath || path == config.KeyPath || path == config.AccessLog || path == config.ErrorLog { conn.Write([]byte("51 Not found!\r\n")) log.Status = 51 return @@ -163,7 +163,7 @@ func handleGeminiRequest(conn net.Conn, config Config, logEntries chan LogEntry) } // Read Molly files - parseMollyFiles(path, info, &config) + parseMollyFiles(path, info, &config, errorLogEntries) // Handle directories if info.IsDir() { @@ -178,7 +178,7 @@ func handleGeminiRequest(conn net.Conn, config Config, logEntries chan LogEntry) index_path := filepath.Join(path, "index."+config.GeminiExt) index_info, err := os.Stat(index_path) if err == nil && uint64(index_info.Mode().Perm())&0444 == 0444 { - serveFile(index_path, &log, conn, config) + serveFile(index_path, &log, conn, config, errorLogEntries) // Serve a generated listing } else { conn.Write([]byte("20 text/gemini\r\n")) @@ -193,19 +193,19 @@ func handleGeminiRequest(conn net.Conn, config Config, logEntries chan LogEntry) for _, cgiPath := range config.CGIPaths { inCGIPath, err := regexp.Match(cgiPath, []byte(path)) if err == nil && inCGIPath { - handleCGI(config, path, URL, &log, conn) + handleCGI(config, path, URL, &log, errorLogEntries, conn) return } } } // Otherwise, serve the file contents - serveFile(path, &log, conn, config) + serveFile(path, &log, conn, config, errorLogEntries) return } -func readRequest(conn net.Conn, log *LogEntry) (*url.URL, error) { +func readRequest(conn net.Conn, log *LogEntry, errorLog chan string) (*url.URL, error) { reader := bufio.NewReaderSize(conn, 1024) request, overflow, err := reader.ReadLine() if overflow { @@ -213,6 +213,7 @@ func readRequest(conn net.Conn, log *LogEntry) (*url.URL, error) { log.Status = 59 return nil, errors.New("Request too long") } else if err != nil { + errorLog <- "Error reading request: " + err.Error() conn.Write([]byte("40 Unknown error reading request!\r\n")) log.Status = 40 return nil, errors.New("Error reading request") @@ -221,6 +222,7 @@ func readRequest(conn net.Conn, log *LogEntry) (*url.URL, error) { // Parse request as URL URL, err := url.Parse(string(request)) if err != nil { + errorLog <- "Error parsing request URL " + string(request) + ": " + err.Error() conn.Write([]byte("59 Error parsing URL!\r\n")) log.Status = 59 return nil, errors.New("Bad URL in request") @@ -254,7 +256,7 @@ func resolvePath(path string, config Config) (string, os.FileInfo, error) { return path, info, nil } -func parseMollyFiles(path string, info os.FileInfo, config *Config) { +func parseMollyFiles(path string, info os.FileInfo, config *Config, errorLogEntries chan string) { // Build list of directories to check dirs := make([]string, 16) if !info.IsDir() { @@ -286,6 +288,7 @@ func parseMollyFiles(path string, info os.FileInfo, config *Config) { } _, err = toml.DecodeFile(mollyPath, &mollyFile) if err != nil { + errorLogEntries <- "Error parsing .molly file " + mollyPath + ": " + err.Error() continue } // Overwrite main Config using MollyFile @@ -399,7 +402,7 @@ func readHeading(path string, info os.FileInfo) string { return info.Name() } -func serveFile(path string, log *LogEntry, conn net.Conn, config Config) { +func serveFile(path string, log *LogEntry, conn net.Conn, config Config, errorLog chan string) { // Get MIME type of files ext := filepath.Ext(path) var mimeType string diff --git a/main.go b/main.go index ab43243..2aae31a 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "log" "os" "strconv" + "time" ) func main() { @@ -25,12 +26,17 @@ func main() { log.Fatal(err) } - // Open logfile - logfile, err := os.OpenFile(config.LogPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + // Open log files + errorLogFile, err := os.OpenFile(config.ErrorLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Fatal(err) } - defer logfile.Close() + defer errorLogFile.Close() + accessLogFile, err := os.OpenFile(config.AccessLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + log.Fatal(err) + } + defer accessLogFile.Close() // Read TLS files, create TLS config cert, err := tls.LoadX509KeyPair(config.CertPath, config.KeyPath) @@ -50,12 +56,19 @@ func main() { } defer listener.Close() - // Start log handling routine - logEntries := make(chan LogEntry, 10) + // Start log handling routines + accessLogEntries := make(chan LogEntry, 10) go func() { for { - entry := <-logEntries - writeLogEntry(logfile, entry) + entry := <- accessLogEntries + writeLogEntry(accessLogFile, entry) + } + }() + errorLogEntries := make(chan string, 10) + go func() { + for { + message := <- errorLogEntries + errorLogFile.WriteString( time.Now().Format(time.RFC3339) + " " + message + "\n") } }() @@ -65,7 +78,7 @@ func main() { if err != nil { log.Fatal(err) } - go handleGeminiRequest(conn, config, logEntries) + go handleGeminiRequest(conn, config, accessLogEntries, errorLogEntries) } }