package main import ( "bufio" "fmt" "io/ioutil" "log" "net/http" "os" "path/filepath" "strings" "net/smtp" "encoding/json" "crypto/sha256" "encoding/hex" "sort" // _ "expvar" // _ "net/http/pprof" ) // private recaptcha v3 const pivcap string = "PUT_YOUR_KEY" // score recaptcha v3 const scorecap float64 = 0.6 // Configuration structure type Config struct { LogLevel string LogFile string } // recaptcha v3 structure type jDaten struct { Success bool ChallengeTS string Hostname string Score float64 Action string } var ( configFile = "config.json" config Config filterFile = "filter.cfg" logFile = "requests.log" blockFile = "blocklist.cfg" ) func init() { // Load configuration loadConfig() } func main() { // Open the log file for writing, create it if it doesn't exist logFile, err := os.OpenFile(config.LogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Fatal(err) } defer logFile.Close() // Create a new logger with the log file logger := log.New(logFile, "", log.Ldate|log.Ltime|log.Lmicroseconds) // Create a new ServeMux mux := http.NewServeMux() // Handle all requests with the logger http.Handle("/", RequestLogger(mux, logger, config.LogLevel)) // Define your existing handlers mux.HandleFunc("/", handleRequest) mux.HandleFunc("/subscribe", handleSubscriptionForm) // Set up and start the server port := 8080 fmt.Printf("Server listening on :%d...\n", port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil)) } func RequestLogger(targetMux http.Handler, logger *log.Logger, logLevel string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Wrap the original response writer with the buffered one secheader(w) // Check if the IP is in the blocklist if _, err := os.Stat(blockFile); err == nil { // Check if the IP is in the blocklist if isBlocked(getClientIP(r)) { // Return a 403 Forbidden response http.Error(w, "Access denied", http.StatusForbidden) return } } // Create a buffer for the response writer buffer := &responseBuffer{ResponseWriter: w} // Log request based on the specified log level switch logLevel { case "info": logger.Printf("%s\t\t%s\t\t%s\t\t%s\n", getClientIP(r), r.Method, r.Referer(), r.RequestURI) case "debug": logger.Printf( "%s\t\t%s\t\t%s\t\t%s\t\t%s\n", getClientIP(r), r.Method, r.Referer(), r.RequestURI, r.Form.Encode(), r.Header, ) default: logger.Printf("Unknown log level: %s", logLevel) } // Serve the request using the original response writer targetMux.ServeHTTP(buffer, r) }) } // Custom response buffer to capture the status code type responseBuffer struct { http.ResponseWriter statusCode int } func (b *responseBuffer) WriteHeader(code int) { b.statusCode = code b.ResponseWriter.WriteHeader(code) } func loadConfig() { config = Config{ LogLevel: "info", // Standardwert für debug_level //LogFile: "requests.log", // Standardwert für log_file } file, err := os.Open("./server.cfg") if err != nil { log.Printf("Config file not found, using default settings.") return } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { key := strings.TrimSpace(parts[0]) value := strings.TrimSpace(parts[1]) switch key { case "log_level": config.LogLevel = value case "log_file": config.LogFile = value } } } if err := scanner.Err(); err != nil { log.Fatalf("Error reading config file: %v", err) } } func handleRequest(w http.ResponseWriter, r *http.Request) { // Get the requested path from the URL requestedPath := filepath.Clean(r.URL.Path) // Create the absolute path to the requested resource in the ./www directory absolutePath := filepath.Join(".", "www", requestedPath) // Check if the requested path is a directory fileInfo, err := os.Stat(absolutePath) if err != nil { if os.IsNotExist(err) { log.Printf("%v\t\t\t\t404 Page not found: %s", getClientIP(r), requestedPath) http.NotFound(w, r) return } log.Printf("\t\t\t\tError checking if path is a directory: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } if fileInfo.IsDir() { var indexFileName string /* if proto := r.Header.Get("X-Forwarded-Proto"); proto == "https" { indexFileName = "index.html" } else { indexFileName = "index_http.html" } */ indexFileName = "index.html" // If the requested path is a directory, check for index.html indexFilePath := filepath.Join(absolutePath, indexFileName) _, err := os.Stat(indexFilePath) if err == nil { // If index.html exists, serve its content serveFile(w, r, indexFilePath) } else { // If index.html doesn't exist, list the directory listDirectory(w, r, absolutePath, requestedPath, indexFilePath) } } else { // If the requested path is a file, serve its contents serveFile(w, r, absolutePath) } } func handleSubscriptionForm(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) return } err := r.ParseForm() if err != nil { http.Error(w, "Bad Request", http.StatusBadRequest) return } email := r.FormValue("email") if email == "" { http.Error(w, "Bad Request: Email is required", http.StatusBadRequest) return } token := r.FormValue("recaptcha_token") if token == "" { http.Error(w, "Bad Token", http.StatusBadRequest) return } // Get the real IP from X-Real-IP header, fallback to RemoteAddr requesterIP := getClientIP(r) if (OnPage(email, token, requesterIP , "subscript") == "1") { // Send the subscription email to root@localhost log.Printf("%v\t\t\t\tSubscription: - %s", requesterIP, email) // Send email err = sendSubscriptionEmail(email) if err != nil { log.Printf("\t\t\t\tError sending subscription email: %v", err) http.Error(w, fmt.Sprintf("Internal Server Error: %v", err), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html") fmt.Fprintln(w, "") fmt.Fprintln(w, "Subscription email sent successfully") } else { w.Header().Set("Content-Type", "text/html") fmt.Fprintln(w, "") fmt.Fprintln(w, "Google Token failed") } } func sendSubscriptionEmail(email string) error { to := "root@localhost" subject := "New Subscription" body := fmt.Sprintf("Email: %s has subscribed.", email) // Update the "From" address to use the email parameter msg := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s", email, to, subject, body) err := smtp.SendMail("localhost:25", nil, email, []string{to}, []byte(msg)) if err != nil { log.Printf("Error sending subscription email: %v", err) return err } return nil } func getVisitorFingerprint(r *http.Request) (string, error) { // ip := strings.Split(r.RemoteAddr, ":")[0] ip := getClientIP(r) userAgent := r.UserAgent() // Concatenate IP address and user agent fingerprint := ip + userAgent // Hash the fingerprint using SHA-256 to create a fixed-size hash hash := sha256.New() hash.Write([]byte(fingerprint)) hashBytes := hash.Sum(nil) return hex.EncodeToString(hashBytes), nil } func serveFile(w http.ResponseWriter, r *http.Request, path string) { // Open the file with proper error handling file, err := os.Open(path) if err != nil { log.Printf("\t\t\t\tError opening file: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } defer file.Close() // Get the visitor's fingerprint fingerprint, err := getVisitorFingerprint(r) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // Extract the filename and extension filename := filepath.Base(path) // Check if the fingerprint is already in the file fingerprintPath := filepath.Join(filepath.Dir(path), "."+filename) if fingerprintExists(fingerprintPath, fingerprint) { // Fingerprint is already present, serve the file content and return fileInfo, err := file.Stat() if err != nil { log.Printf("\t\t\t\tError getting file info: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } http.ServeContent(w, r, path, fileInfo.ModTime(), file) return } // Append the fingerprint as a new line to the file fingerprintFile, err := os.OpenFile(fingerprintPath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) if err != nil { log.Printf("\t\t\t\tError opening or creating fingerprint file: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } defer fingerprintFile.Close() // Write the fingerprint as a new line to the file _, err = fmt.Fprintln(fingerprintFile, fingerprint) if err != nil { log.Printf("\t\t\t\tError writing fingerprint to file: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // Use http.ServeContent for efficient file serving fileInfo, err := file.Stat() if err != nil { log.Printf("\t\t\t\tError getting file info: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } http.ServeContent(w, r, path, fileInfo.ModTime(), file) } // Function to check if a fingerprint already exists in the file func fingerprintExists(fingerprintPath, fingerprint string) bool { existingFingerprints, err := ioutil.ReadFile(fingerprintPath) if err != nil { return false // Assume the file does not exist } fingerprints := strings.Split(string(existingFingerprints), "\n") for _, fp := range fingerprints { if fp == fingerprint { return true } } return false } func listDirectory(w http.ResponseWriter, r *http.Request, basePath, relativePath , indexFilePath string) { // Open the directory with proper error handling dir, err := os.Open(basePath) if err != nil { log.Printf("\t\t\t\tError opening directory: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } defer dir.Close() // Read the directory entries files, err := dir.Readdir(-1) if err != nil { log.Printf("\t\t\t\tError reading directory: %v", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } // Filter out files/directories starting with a dot var visibleFiles []os.FileInfo for _, file := range files { if !strings.HasPrefix(file.Name(), ".") { visibleFiles = append(visibleFiles, file) } } // Sortiere alphabetisch sort.Slice(visibleFiles, func(i, j int) bool { return visibleFiles[i].Name() < visibleFiles[j].Name() }) // Create a styled HTML page to list the directory contents w.Header().Set("Content-Type", "text/html") fmt.Fprintln(w, ` `) fmt.Fprintf(w, " Index of %s", relativePath) fmt.Fprintln(w, ` `) fmt.Fprintln(w, ` `) fmt.Fprintf(w, "

Index of %s

", relativePath) fmt.Fprintln(w, "") fmt.Fprintln(w, "") fmt.Fprintln(w, "") fmt.Fprintln(w, "") fmt.Fprintln(w, "") fmt.Fprintln(w, "") fmt.Fprintln(w, "") fmt.Fprintf(w, "", filepath.Join("/", relativePath, "../")) for _, file := range visibleFiles { linkPath := filepath.Join(relativePath, file.Name()) fingerprintPath := filepath.Join(filepath.Dir(indexFilePath), "."+file.Name()) fileInfo, err := os.Stat(filepath.Join(basePath, file.Name())) var visitorCount interface{} // Use interface to handle both string and int if err == nil { // Check if fingerprintPath exists if _, err := os.Stat(fingerprintPath); err == nil { if fileInfo.IsDir() { // If it's a directory, set visitorCount to an empty string visitorCount = "---" } else { // If it's a file and fingerprintPath exists, display visitor count visitorCount = countVisitors(fingerprintPath) } } else { // If fingerprintPath doesn't exist, set visitorCount to an empty string visitorCount = "" } } else { // If there's an error (file doesn't exist), set visitorCount to an empty string visitorCount = "" } fmt.Fprintln(w, "") fmt.Fprintf(w, "", linkPath, file.Name()) if fileInfo.IsDir() { // If it's a directory, set file size to an empty string fmt.Fprintf(w, "") } else { // If it's a file, display file date fmt.Fprintf(w, "", file.ModTime().Format("2006-01-02 15:04:05")) } if fileInfo.IsDir() { // If it's a directory, set file size to an empty string fmt.Fprintf(w, "") } else { // If it's a file, display file size fmt.Fprintf(w, "", file.Size()) } fmt.Fprintf(w, "", visitorCount) fmt.Fprintln(w, "") } fmt.Fprintln(w, "
NameLast ModifiedSizeVisitors
Parent Directory
%s---%s---%d bytes%v
") fmt.Fprintln(w, "") } // Function to count the number of visitors in the fingerprint file func countVisitors(fingerprintPath string) int { file, err := os.Open(fingerprintPath) if err != nil { // Return 0 when the file doesn't exist log.Printf("Error opening fingerprint file: %v", err) return 0 } defer file.Close() var visitorCount int scanner := bufio.NewScanner(file) for scanner.Scan() { // Increment the count for each line in the file visitorCount++ } if err := scanner.Err(); err != nil { // Log the error if needed log.Printf("Error scanning fingerprint file: %v", err) } return visitorCount } func secheader(w http.ResponseWriter) { w.Header().Set("X-XSS-Protection", "1; mode=block") w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0") w.Header().Set("Pragma", "no-cache") w.Header().Set("Expires", "0") w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=(), payment=()") w.Header().Set("Feature-Policy", "none") w.Header().Set("Server", "gws") w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Frame-Options", "DENY") w.Header().Set("X-Permitted-Cross-Domain-Policies", "none") w.Header().Set("Content-Encoding", "identity") w.Header().Set("Content-Security-Policy", "default-src 'self' www.google.com www.gstatic.com 'unsafe-inline' 'unsafe-eval' data:;") //w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload") } /* func getClientIP(r *http.Request) string { // Get the real IP from X-Real-IP header, fallback to RemoteAddr requesterIP := r.Header.Get("X-Real-IP") if requesterIP == "" { requesterIP = r.RemoteAddr } return requesterIP } */ func getClientIP(r *http.Request) string { // Check for the X-Forwarded-For header ip := r.Header.Get("X-Forwarded-For") if ip == "" { // Fallback to X-Real-IP ip = r.Header.Get("X-Real-IP") } if ip == "" { // Finally fallback to RemoteAddr ip = r.RemoteAddr } return ip } func isBlocked(ip string) bool { file, err := os.Open(blockFile) if err != nil { log.Printf("Error opening blocklist file: %v", err) return false } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { blockedIP := strings.TrimSpace(scanner.Text()) if ip == blockedIP { return true } } if err := scanner.Err(); err != nil { log.Printf("Error reading blocklist file: %v", err) } return false } func OnPage(email string, id string, ip string, aktion string)(string) { var link string = `https://www.google.com/recaptcha/api/siteverify?secret=`+pivcap+`&response=`+id+`&remoteip=`+ip+`` res, err := http.Get(link) if err != nil { panic(err.Error()) } content, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { panic(err.Error()) } var daten jDaten Data := []byte(string(content)) errb := json.Unmarshal(Data, &daten) if errb != nil { panic(err.Error()) } var output string = "0" if (daten.Action == aktion && daten.Success == true && daten.Score > scorecap) { output = "1" } log.Printf("%v\t\t\t\t%s --> Action: %s, Success: %t, Score: %f, Email: %s", ip, link, daten.Action, daten.Success, daten.Score, email) //log.Printf("%v", daten) //fmt.Println(link) //fmt.Println(daten) //fmt.Println(output) return output }