nginx_log.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. package nginx_log
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "regexp"
  7. "strings"
  8. "github.com/0xJacky/Nginx-UI/internal/cache"
  9. "github.com/0xJacky/Nginx-UI/internal/helper"
  10. "github.com/0xJacky/Nginx-UI/internal/nginx"
  11. "github.com/0xJacky/Nginx-UI/settings"
  12. "github.com/uozi-tech/cosy/logger"
  13. )
  14. // Regular expression for log directives - matches access_log or error_log
  15. var (
  16. logDirectiveRegex = regexp.MustCompile(`(?m)(access_log|error_log)\s+([^\s;]+)(?:\s+[^;]+)?;`)
  17. prefix = ""
  18. )
  19. // Use init function to automatically register callback
  20. func init() {
  21. prefix = nginx.GetPrefix()
  22. // Register the callback directly with the global registry
  23. cache.RegisterCallback(scanForLogDirectives)
  24. }
  25. // scanForLogDirectives scans and parses configuration files for log directives
  26. func scanForLogDirectives(configPath string, content []byte) error {
  27. // First, remove all log paths that came from this config file
  28. // This ensures that removed log directives are properly cleaned up
  29. RemoveLogPathsFromConfig(configPath)
  30. // Find log directives using regex
  31. matches := logDirectiveRegex.FindAllSubmatch(content, -1)
  32. // Parse log paths
  33. for _, match := range matches {
  34. if len(match) >= 3 {
  35. // Check if this match is from a commented line
  36. if isCommentedMatch(content, match) {
  37. continue // Skip commented directives
  38. }
  39. directiveType := string(match[1]) // "access_log" or "error_log"
  40. logPath := string(match[2]) // Path to log file
  41. // Handle relative paths by joining with nginx prefix
  42. if !filepath.IsAbs(logPath) {
  43. logPath = filepath.Join(prefix, logPath)
  44. }
  45. // Validate log path
  46. if isValidLogPath(logPath) {
  47. logType := "access"
  48. if directiveType == "error_log" {
  49. logType = "error"
  50. }
  51. // Add to cache with config file path
  52. AddLogPath(logPath, logType, filepath.Base(logPath), configPath)
  53. }
  54. }
  55. }
  56. return nil
  57. }
  58. // isCommentedMatch checks if a regex match is from a commented line
  59. func isCommentedMatch(content []byte, match [][]byte) bool {
  60. // Find the position of the match in the content
  61. matchStr := string(match[0])
  62. matchIndex := strings.Index(string(content), matchStr)
  63. if matchIndex == -1 {
  64. return false
  65. }
  66. // Find the start of the line containing this match
  67. lineStart := matchIndex
  68. for lineStart > 0 && content[lineStart-1] != '\n' {
  69. lineStart--
  70. }
  71. // Check if the line starts with # (possibly with leading whitespace)
  72. for i := lineStart; i < matchIndex; i++ {
  73. char := content[i]
  74. if char == '#' {
  75. return true // This is a commented line
  76. }
  77. if char != ' ' && char != '\t' {
  78. return false // Found non-whitespace before the directive, not a comment
  79. }
  80. }
  81. return false
  82. }
  83. // GetAllLogs returns all log paths
  84. func GetAllLogs(filters ...func(*NginxLogCache) bool) []*NginxLogCache {
  85. return GetAllLogPaths(filters...)
  86. }
  87. // isValidLogPath checks if a log path is valid:
  88. // 1. It must be a regular file or a symlink to a regular file
  89. // 2. It must not point to a console or special device
  90. // 3. It must be under the whitelist directories
  91. func isValidLogPath(logPath string) bool {
  92. // First check if the path is in the whitelist
  93. if !IsLogPathUnderWhiteList(logPath) {
  94. logger.Warn("Log path is not under whitelist:", logPath)
  95. return false
  96. }
  97. // Check if the path exists
  98. fileInfo, err := os.Lstat(logPath)
  99. if err != nil {
  100. // If the file doesn't exist, it might be created later
  101. // We'll assume it's valid for now
  102. return true
  103. }
  104. // If it's a symlink, follow it safely
  105. if fileInfo.Mode()&os.ModeSymlink != 0 {
  106. // Use EvalSymlinks to safely resolve the entire symlink chain
  107. // This function detects circular symlinks and returns an error
  108. resolvedPath, err := filepath.EvalSymlinks(logPath)
  109. if err != nil {
  110. logger.Warn("Failed to resolve symlink (possible circular reference):", logPath, "error:", err)
  111. return false
  112. }
  113. // Check the resolved target file
  114. targetInfo, err := os.Stat(resolvedPath)
  115. if err != nil {
  116. return false
  117. }
  118. // Only accept regular files as targets
  119. return targetInfo.Mode().IsRegular()
  120. }
  121. // For non-symlinks, just check if it's a regular file
  122. return fileInfo.Mode().IsRegular()
  123. }
  124. // IsLogPathUnderWhiteList checks if a log path is under one of the paths in LogDirWhiteList
  125. func IsLogPathUnderWhiteList(path string) bool {
  126. cacheKey := fmt.Sprintf("isLogPathUnderWhiteList:%s", path)
  127. res, ok := cache.Get(cacheKey)
  128. // If cached, return the result directly
  129. if ok {
  130. return res.(bool)
  131. }
  132. // Only build the whitelist when cache miss occurs
  133. logDirWhiteList := append([]string{}, settings.NginxSettings.LogDirWhiteList...)
  134. accessLogPath := nginx.GetAccessLogPath()
  135. errorLogPath := nginx.GetErrorLogPath()
  136. if accessLogPath != "" {
  137. logDirWhiteList = append(logDirWhiteList, filepath.Dir(accessLogPath))
  138. }
  139. if errorLogPath != "" {
  140. logDirWhiteList = append(logDirWhiteList, filepath.Dir(errorLogPath))
  141. }
  142. if nginx.GetPrefix() != "" {
  143. logDirWhiteList = append(logDirWhiteList, nginx.GetPrefix())
  144. }
  145. // Check if path is under any whitelist directory
  146. for _, whitePath := range logDirWhiteList {
  147. if helper.IsUnderDirectory(path, whitePath) {
  148. cache.Set(cacheKey, true, 0)
  149. return true
  150. }
  151. }
  152. // Cache negative result as well to avoid repeated checks
  153. cache.Set(cacheKey, false, 0)
  154. return false
  155. }