added Public IP monitoring and fixing issue where the app would stop recording session event until user logout.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# User Session Monitor Agent
|
||||
|
||||
- For Windows
|
||||
- created with AI
|
||||
## Overview
|
||||
|
||||
This application monitors Windows session events (logon, logoff, lock, unlock) and sends them to a specified API endpoint. It's designed to run as a Windows service on any version of Windows, collecting user session activity and securely transmitting it with an API key.
|
||||
|
||||
@@ -14,7 +14,8 @@ type EventData struct {
|
||||
EventType string `json:"EventType"`
|
||||
UserName string `json:"UserName"`
|
||||
ComputerName string `json:"ComputerName"`
|
||||
IPAddress string `json:"IPAddress"`
|
||||
LocalIP string `json:"LocalIP"`
|
||||
PublicIP string `json:"PublicIP"`
|
||||
Timestamp string `json:"Timestamp"`
|
||||
Retry int `json:"retry,omitempty"`
|
||||
}
|
||||
@@ -48,13 +49,14 @@ func NewClient(apiKey, serverURL string) *Client {
|
||||
}
|
||||
|
||||
// SendEvent sends an event to the API
|
||||
func (c *Client) SendEvent(eventType, username, computerName, ipAddress, timestamp string, retry int) (bool, error) {
|
||||
func (c *Client) SendEvent(eventType, username, computerName, localIP, publicIP, timestamp string, retry int) (bool, error) {
|
||||
// Create event data
|
||||
eventData := EventData{
|
||||
EventType: eventType,
|
||||
UserName: username,
|
||||
ComputerName: computerName,
|
||||
IPAddress: ipAddress,
|
||||
LocalIP: localIP,
|
||||
PublicIP: publicIP,
|
||||
Timestamp: timestamp,
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,6 @@ func (hc *HealthChecker) retryFailedEvents() {
|
||||
// Create API client for retry
|
||||
cfg := config.GetConfig()
|
||||
apiClient := NewClient(cfg.APIKey, cfg.ServerURL)
|
||||
|
||||
// Try to send each failed event
|
||||
successCount := 0
|
||||
var successfulEvents []map[string]string
|
||||
@@ -148,7 +147,8 @@ func (hc *HealthChecker) retryFailedEvents() {
|
||||
event["eventtype"],
|
||||
event["username"],
|
||||
event["hostname"],
|
||||
event["ipaddress"],
|
||||
event["localip"],
|
||||
event["publicip"],
|
||||
event["timestamp"],
|
||||
1, // Retry flag
|
||||
)
|
||||
@@ -166,7 +166,8 @@ func (hc *HealthChecker) retryFailedEvents() {
|
||||
event["eventtype"],
|
||||
event["username"],
|
||||
event["hostname"],
|
||||
event["ipaddress"],
|
||||
event["localip"],
|
||||
event["publicip"],
|
||||
event["timestamp"],
|
||||
logging.SendStatusRetrySuccess,
|
||||
); err != nil {
|
||||
|
||||
Binary file not shown.
@@ -39,6 +39,12 @@ const (
|
||||
DefaultLogRotationSizeMB = 5
|
||||
)
|
||||
|
||||
// DefaultPublicIPHTTPURLs are the default URLs for HTTP-based public IP detection
|
||||
var DefaultPublicIPHTTPURLs = []string{
|
||||
"https://ifconfig.me/ip",
|
||||
"https://ipv4.icanhazip.com",
|
||||
}
|
||||
|
||||
// Config represents the application configuration
|
||||
type Config struct {
|
||||
APIKey string
|
||||
@@ -46,11 +52,12 @@ type Config struct {
|
||||
DebugLogs bool
|
||||
Timezone string
|
||||
InstallDir string
|
||||
HealthCheckInterval int // Health check interval in minutes (0 = disabled)
|
||||
HealthCheckPath string // Health check endpoint path
|
||||
SessionLogRotationSizeMB int // Session monitor log rotation size in MB (0 = no rotation, max 20MB)
|
||||
ErrorLogRotationSizeMB int // Error log rotation size in MB (0 = no rotation, max 20MB)
|
||||
EventLogRotationSizeMB int // Event CSV log rotation size in MB (0 = no rotation, max 20MB)
|
||||
HealthCheckInterval int // Health check interval in minutes (0 = disabled)
|
||||
HealthCheckPath string // Health check endpoint path
|
||||
PublicIPHTTPURLs []string // Custom HTTP URLs for public IP detection
|
||||
SessionLogRotationSizeMB int // Session monitor log rotation size in MB (0 = no rotation, max 20MB)
|
||||
ErrorLogRotationSizeMB int // Error log rotation size in MB (0 = no rotation, max 20MB)
|
||||
EventLogRotationSizeMB int // Event CSV log rotation size in MB (0 = no rotation, max 20MB)
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
@@ -71,10 +78,12 @@ func GetConfig() *Config {
|
||||
InstallDir: DefaultInstallDir,
|
||||
HealthCheckInterval: DefaultHealthCheckInterval,
|
||||
HealthCheckPath: DefaultHealthCheckPath,
|
||||
PublicIPHTTPURLs: make([]string, len(DefaultPublicIPHTTPURLs)),
|
||||
SessionLogRotationSizeMB: DefaultLogRotationSizeMB,
|
||||
ErrorLogRotationSizeMB: DefaultLogRotationSizeMB,
|
||||
EventLogRotationSizeMB: DefaultLogRotationSizeMB,
|
||||
}
|
||||
copy(instance.PublicIPHTTPURLs, DefaultPublicIPHTTPURLs)
|
||||
})
|
||||
return instance
|
||||
}
|
||||
@@ -137,6 +146,19 @@ func (c *Config) Load(configFile string) error {
|
||||
c.HealthCheckInterval = apiSection.Key("health_check_interval").MustInt(DefaultHealthCheckInterval)
|
||||
c.HealthCheckPath = apiSection.Key("health_check_path").MustString(DefaultHealthCheckPath)
|
||||
|
||||
// Load custom public IP HTTP URLs
|
||||
publicIPURLsStr := apiSection.Key("public_ip_http_urls").MustString("")
|
||||
if publicIPURLsStr != "" {
|
||||
c.PublicIPHTTPURLs = strings.Split(publicIPURLsStr, ",")
|
||||
// Trim whitespace from URLs
|
||||
for i := range c.PublicIPHTTPURLs {
|
||||
c.PublicIPHTTPURLs[i] = strings.TrimSpace(c.PublicIPHTTPURLs[i])
|
||||
}
|
||||
} else {
|
||||
c.PublicIPHTTPURLs = make([]string, len(DefaultPublicIPHTTPURLs))
|
||||
copy(c.PublicIPHTTPURLs, DefaultPublicIPHTTPURLs)
|
||||
}
|
||||
|
||||
// Read Logging section
|
||||
loggingSection := cfg.Section("Logging")
|
||||
c.SessionLogRotationSizeMB = loggingSection.Key("session_log_rotation_size_mb").MustInt(DefaultLogRotationSizeMB)
|
||||
@@ -195,6 +217,19 @@ func (c *Config) LoadFromSource(configFile string) error {
|
||||
c.HealthCheckInterval = apiSection.Key("health_check_interval").MustInt(DefaultHealthCheckInterval)
|
||||
c.HealthCheckPath = apiSection.Key("health_check_path").MustString(DefaultHealthCheckPath)
|
||||
|
||||
// Load custom public IP HTTP URLs
|
||||
publicIPURLsStr := apiSection.Key("public_ip_http_urls").MustString("")
|
||||
if publicIPURLsStr != "" {
|
||||
c.PublicIPHTTPURLs = strings.Split(publicIPURLsStr, ",")
|
||||
// Trim whitespace from URLs
|
||||
for i := range c.PublicIPHTTPURLs {
|
||||
c.PublicIPHTTPURLs[i] = strings.TrimSpace(c.PublicIPHTTPURLs[i])
|
||||
}
|
||||
} else {
|
||||
c.PublicIPHTTPURLs = make([]string, len(DefaultPublicIPHTTPURLs))
|
||||
copy(c.PublicIPHTTPURLs, DefaultPublicIPHTTPURLs)
|
||||
}
|
||||
|
||||
// Read Logging section
|
||||
loggingSection := cfg.Section("Logging")
|
||||
c.SessionLogRotationSizeMB = loggingSection.Key("session_log_rotation_size_mb").MustInt(DefaultLogRotationSizeMB)
|
||||
@@ -241,6 +276,7 @@ func (c *Config) Save(configFile string) error {
|
||||
apiSection.Key("install_dir").SetValue(c.InstallDir)
|
||||
apiSection.Key("health_check_interval").SetValue(fmt.Sprintf("%d", c.HealthCheckInterval))
|
||||
apiSection.Key("health_check_path").SetValue(c.HealthCheckPath)
|
||||
apiSection.Key("public_ip_http_urls").SetValue(strings.Join(c.PublicIPHTTPURLs, ","))
|
||||
|
||||
// Create Logging section
|
||||
loggingSection, err := cfg.NewSection("Logging")
|
||||
@@ -348,6 +384,7 @@ func (c *Config) createDefaultConfig(configFile string) error {
|
||||
apiSection.Key("install_dir").SetValue(c.InstallDir)
|
||||
apiSection.Key("health_check_interval").SetValue(fmt.Sprintf("%d", c.HealthCheckInterval))
|
||||
apiSection.Key("health_check_path").SetValue(c.HealthCheckPath)
|
||||
apiSection.Key("public_ip_http_urls").SetValue(strings.Join(c.PublicIPHTTPURLs, ","))
|
||||
|
||||
// Create Logging section
|
||||
loggingSection, err := cfg.NewSection("Logging")
|
||||
|
||||
@@ -26,9 +26,10 @@ const (
|
||||
EventTypeCol = 0
|
||||
UsernameCol = 1
|
||||
HostnameCol = 2
|
||||
IPAddressCol = 3
|
||||
TimestampCol = 4
|
||||
SendStatusCol = 5
|
||||
LocalIPCol = 3
|
||||
PublicIPCol = 4
|
||||
TimestampCol = 5
|
||||
SendStatusCol = 6
|
||||
|
||||
// Send status values
|
||||
SendStatusFailed = 0
|
||||
@@ -41,8 +42,8 @@ const (
|
||||
|
||||
// Headers for CSV files
|
||||
var (
|
||||
MainCSVHeaders = []string{"eventtype", "username", "hostname", "ipaddress", "timestamp", "send_status"}
|
||||
RetryCSVHeaders = []string{"eventtype", "username", "hostname", "ipaddress", "timestamp"}
|
||||
MainCSVHeaders = []string{"eventtype", "username", "hostname", "localip", "publicip", "timestamp", "send_status"}
|
||||
RetryCSVHeaders = []string{"eventtype", "username", "hostname", "localip", "publicip", "timestamp"}
|
||||
)
|
||||
|
||||
// NewCSVLogger creates a new CSV logger
|
||||
@@ -109,7 +110,7 @@ func (l *CSVLogger) initCSVFile(filePath string, headers []string) error {
|
||||
}
|
||||
|
||||
// LogEvent logs an event to the CSV file
|
||||
func (l *CSVLogger) LogEvent(eventType, username, hostname, ipAddress, timestamp string, sendStatus int) error {
|
||||
func (l *CSVLogger) LogEvent(eventType, username, hostname, localIP, publicIP, timestamp string, sendStatus int) error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
@@ -125,11 +126,11 @@ func (l *CSVLogger) LogEvent(eventType, username, hostname, ipAddress, timestamp
|
||||
if sendStatus == SendStatusFailed {
|
||||
// For failed events, use the retry file
|
||||
filePath = l.retryFile
|
||||
record = []string{eventType, username, hostname, ipAddress, timestamp}
|
||||
record = []string{eventType, username, hostname, localIP, publicIP, timestamp}
|
||||
} else {
|
||||
// For successful events, use the main file
|
||||
filePath = l.csvFile
|
||||
record = []string{eventType, username, hostname, ipAddress, timestamp, fmt.Sprintf("%d", sendStatus)}
|
||||
record = []string{eventType, username, hostname, localIP, publicIP, timestamp, fmt.Sprintf("%d", sendStatus)}
|
||||
}
|
||||
|
||||
// Open the file in append mode
|
||||
@@ -236,15 +237,15 @@ func (l *CSVLogger) RemoveSuccessfulRetries(successfulEvents []map[string]string
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get failed events: %v", err)
|
||||
}
|
||||
|
||||
// Create a map of successful events for quick lookup
|
||||
successfulMap := make(map[string]bool)
|
||||
for _, event := range successfulEvents {
|
||||
key := fmt.Sprintf("%s|%s|%s|%s|%s",
|
||||
key := fmt.Sprintf("%s|%s|%s|%s|%s|%s",
|
||||
event["eventtype"],
|
||||
event["username"],
|
||||
event["hostname"],
|
||||
event["ipaddress"],
|
||||
event["localip"],
|
||||
event["publicip"],
|
||||
event["timestamp"])
|
||||
successfulMap[key] = true
|
||||
}
|
||||
@@ -252,11 +253,12 @@ func (l *CSVLogger) RemoveSuccessfulRetries(successfulEvents []map[string]string
|
||||
// Filter out successful events from the failed events list
|
||||
var remainingEvents []map[string]string
|
||||
for _, event := range allFailedEvents {
|
||||
key := fmt.Sprintf("%s|%s|%s|%s|%s",
|
||||
key := fmt.Sprintf("%s|%s|%s|%s|%s|%s",
|
||||
event["eventtype"],
|
||||
event["username"],
|
||||
event["hostname"],
|
||||
event["ipaddress"],
|
||||
event["localip"],
|
||||
event["publicip"],
|
||||
event["timestamp"])
|
||||
if !successfulMap[key] {
|
||||
remainingEvents = append(remainingEvents, event)
|
||||
@@ -280,14 +282,14 @@ func (l *CSVLogger) RemoveSuccessfulRetries(successfulEvents []map[string]string
|
||||
return fmt.Errorf("failed to open retry file for writing: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
writer := csv.NewWriter(file)
|
||||
for _, event := range remainingEvents {
|
||||
record := []string{
|
||||
event["eventtype"],
|
||||
event["username"],
|
||||
event["hostname"],
|
||||
event["ipaddress"],
|
||||
event["localip"],
|
||||
event["publicip"],
|
||||
event["timestamp"],
|
||||
}
|
||||
if err := writer.Write(record); err != nil {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -11,6 +15,7 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"monitoring-agent-win/api"
|
||||
"monitoring-agent-win/config"
|
||||
"monitoring-agent-win/logging"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
@@ -95,6 +100,10 @@ type Monitor struct {
|
||||
mu sync.RWMutex
|
||||
stopChan chan struct{}
|
||||
hwnd HWND
|
||||
// Add session notification health monitoring
|
||||
lastNotificationTime time.Time
|
||||
healthCheckTicker *time.Ticker
|
||||
notificationHealthy bool
|
||||
}
|
||||
|
||||
// NewMonitor creates a new session monitor
|
||||
@@ -187,16 +196,23 @@ func (m *Monitor) Start() error {
|
||||
}
|
||||
|
||||
m.hwnd = HWND(hwnd)
|
||||
|
||||
// Register for session notifications
|
||||
if err := wtsRegisterSessionNotification(m.hwnd, NOTIFY_FOR_ALL_SESSIONS); err != nil {
|
||||
destroyWindow(m.hwnd)
|
||||
return fmt.Errorf("failed to register for session notifications: %v", err)
|
||||
}
|
||||
|
||||
// Initialize health monitoring
|
||||
m.lastNotificationTime = time.Now()
|
||||
m.notificationHealthy = true
|
||||
m.healthCheckTicker = time.NewTicker(5 * time.Minute) // Check every 5 minutes
|
||||
|
||||
// Start message loop in a goroutine
|
||||
go m.messageLoop()
|
||||
|
||||
// Start health monitoring goroutine
|
||||
go m.healthMonitor()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -204,6 +220,11 @@ func (m *Monitor) Start() error {
|
||||
func (m *Monitor) Stop() {
|
||||
close(m.stopChan)
|
||||
|
||||
// Stop health monitoring
|
||||
if m.healthCheckTicker != nil {
|
||||
m.healthCheckTicker.Stop()
|
||||
}
|
||||
|
||||
// Unregister session notifications and destroy window
|
||||
if m.hwnd != 0 {
|
||||
wtsUnRegisterSessionNotification(m.hwnd)
|
||||
@@ -269,24 +290,29 @@ func (m *Monitor) messageLoop() {
|
||||
func (m *Monitor) wndProc(hwnd HWND, msg uint32, wParam, lParam uintptr) uintptr {
|
||||
switch msg {
|
||||
case WM_WTSSESSION_CHANGE:
|
||||
// Capture timestamp immediately when event occurs for accuracy
|
||||
eventTime := time.Now()
|
||||
|
||||
// Update last notification time for health monitoring
|
||||
m.lastNotificationTime = eventTime
|
||||
|
||||
sessionID := uint32(lParam)
|
||||
eventType := uint32(wParam)
|
||||
|
||||
// Get session info
|
||||
username := m.getSessionUsername(sessionID)
|
||||
computerName := getComputerName()
|
||||
ipAddress := getDefaultIPv4()
|
||||
localIP := getDefaultIPv4()
|
||||
|
||||
// Log the event
|
||||
// Log the event with the captured timestamp
|
||||
switch eventType {
|
||||
case WTS_SESSION_LOGON:
|
||||
m.logEvent(EventLogon, username, computerName, ipAddress)
|
||||
m.logEventWithTime(EventLogon, username, computerName, localIP, eventTime)
|
||||
case WTS_SESSION_LOGOFF:
|
||||
m.logEvent(EventLogoff, username, computerName, ipAddress)
|
||||
m.logEventWithTime(EventLogoff, username, computerName, localIP, eventTime)
|
||||
case WTS_SESSION_LOCK:
|
||||
m.logEvent(EventLock, username, computerName, ipAddress)
|
||||
m.logEventWithTime(EventLock, username, computerName, localIP, eventTime)
|
||||
case WTS_SESSION_UNLOCK:
|
||||
m.logEvent(EventUnlock, username, computerName, ipAddress)
|
||||
m.logEventWithTime(EventUnlock, username, computerName, localIP, eventTime)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,31 +358,105 @@ func destroyWindow(hwnd HWND) {
|
||||
proc.Call(uintptr(hwnd))
|
||||
}
|
||||
|
||||
// logEvent logs an event to the CSV file and sends it to the API
|
||||
func (m *Monitor) logEvent(eventType, username, computerName, ipAddress string) {
|
||||
// Get current timestamp in the configured timezone
|
||||
// getPublicIP retrieves public IP with HTTP first, then DNS fallback
|
||||
func getPublicIP() (string, error) {
|
||||
// Get config to check for custom HTTP URLs
|
||||
cfg := config.GetConfig()
|
||||
|
||||
// Use custom URLs if configured, otherwise use defaults
|
||||
urls := []string{
|
||||
"https://ipv4.icanhazip.com",
|
||||
"https://ifconfig.me/ip",
|
||||
}
|
||||
if len(cfg.PublicIPHTTPURLs) > 0 {
|
||||
urls = cfg.PublicIPHTTPURLs
|
||||
}
|
||||
|
||||
// Try HTTP services
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
for _, url := range urls {
|
||||
if resp, err := client.Get(url); err == nil && resp.StatusCode == 200 {
|
||||
defer resp.Body.Close()
|
||||
if body, err := io.ReadAll(resp.Body); err == nil {
|
||||
if ip := strings.TrimSpace(string(body)); net.ParseIP(ip) != nil && net.ParseIP(ip).To4() != nil {
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DNS fallback using Google's nameserver (equivalent to: dig TXT +short o-o.myaddr.l.google.com @ns1.google.com)
|
||||
// First resolve ns1.google.com to get its IP dynamically
|
||||
nsIPs, err := net.LookupHost("ns1.google.com")
|
||||
if err != nil || len(nsIPs) == 0 {
|
||||
return "", fmt.Errorf("failed to resolve ns1.google.com")
|
||||
}
|
||||
|
||||
// Use the first IPv4 address from ns1.google.com
|
||||
var nsIP string
|
||||
for _, ip := range nsIPs {
|
||||
if net.ParseIP(ip).To4() != nil {
|
||||
nsIP = ip
|
||||
break
|
||||
}
|
||||
}
|
||||
if nsIP == "" {
|
||||
return "", fmt.Errorf("no IPv4 address found for ns1.google.com")
|
||||
}
|
||||
|
||||
resolver := &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
d := net.Dialer{Timeout: 2 * time.Second}
|
||||
return d.DialContext(ctx, network, nsIP+":53")
|
||||
},
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if txts, err := resolver.LookupTXT(ctx, "o-o.myaddr.l.google.com"); err == nil {
|
||||
for _, txt := range txts {
|
||||
if parsedIP := net.ParseIP(txt); parsedIP != nil && parsedIP.To4() != nil {
|
||||
return txt, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("failed to retrieve public IP")
|
||||
}
|
||||
|
||||
// logEventWithTime logs an event with a pre-captured timestamp
|
||||
func (m *Monitor) logEventWithTime(eventType, username, computerName, localIP string, eventTime time.Time) {
|
||||
// Get public IP (this can be done in background as it's less time-sensitive)
|
||||
publicIP, err := getPublicIP()
|
||||
if err != nil {
|
||||
m.logger.Warning("Failed to get public IP: %v - using 'Unknown'", err)
|
||||
publicIP = "Unknown"
|
||||
}
|
||||
|
||||
// Format the timestamp using the pre-captured event time
|
||||
var timestamp string
|
||||
if m.timezone != "" {
|
||||
// Load the configured timezone
|
||||
loc, err := time.LoadLocation(m.timezone)
|
||||
// Try to load the configured timezone with Windows-specific handling
|
||||
loc, err := loadTimezoneWithFallback(m.timezone)
|
||||
if err != nil {
|
||||
// If timezone loading fails, fall back to local time but log the error
|
||||
m.logger.Warning("Failed to load timezone '%s': %v - using local time", m.timezone, err)
|
||||
timestamp = time.Now().Format("2006-01-02T15:04:05-07:00")
|
||||
timestamp = eventTime.Format("2006-01-02T15:04:05-07:00")
|
||||
} else {
|
||||
// Use the configured timezone
|
||||
timestamp = time.Now().In(loc).Format("2006-01-02T15:04:05-07:00")
|
||||
timestamp = eventTime.In(loc).Format("2006-01-02T15:04:05-07:00")
|
||||
}
|
||||
} else {
|
||||
// If no timezone configured, use local time
|
||||
timestamp = time.Now().Format("2006-01-02T15:04:05-07:00")
|
||||
timestamp = eventTime.Format("2006-01-02T15:04:05-07:00")
|
||||
}
|
||||
|
||||
// Log the event
|
||||
m.logger.Info("User: %s - Event: %s - Computer: %s - IP: %s", username, eventType, computerName, ipAddress)
|
||||
m.logger.Info("User: %s - Event: %s - Computer: %s - LocalIP: %s - PublicIP: %s", username, eventType, computerName, localIP, publicIP)
|
||||
|
||||
// Send to API
|
||||
success, err := m.apiClient.SendEvent(eventType, username, computerName, ipAddress, timestamp, 0)
|
||||
success, err := m.apiClient.SendEvent(eventType, username, computerName, localIP, publicIP, timestamp, 0)
|
||||
if err != nil {
|
||||
m.logger.Error("Failed to send event: %v", err)
|
||||
}
|
||||
@@ -367,7 +467,7 @@ func (m *Monitor) logEvent(eventType, username, computerName, ipAddress string)
|
||||
sendStatus = logging.SendStatusFailed
|
||||
}
|
||||
|
||||
if err := m.csvLogger.LogEvent(eventType, username, computerName, ipAddress, timestamp, sendStatus); err != nil {
|
||||
if err := m.csvLogger.LogEvent(eventType, username, computerName, localIP, publicIP, timestamp, sendStatus); err != nil {
|
||||
m.logger.Error("Failed to log event to CSV: %v", err)
|
||||
}
|
||||
|
||||
@@ -399,7 +499,8 @@ func (m *Monitor) retryFailedEvents() {
|
||||
event["eventtype"],
|
||||
event["username"],
|
||||
event["hostname"],
|
||||
event["ipaddress"],
|
||||
event["localip"],
|
||||
event["publicip"],
|
||||
event["timestamp"],
|
||||
1, // Retry flag
|
||||
)
|
||||
@@ -415,7 +516,8 @@ func (m *Monitor) retryFailedEvents() {
|
||||
event["eventtype"],
|
||||
event["username"],
|
||||
event["hostname"],
|
||||
event["ipaddress"],
|
||||
event["localip"],
|
||||
event["publicip"],
|
||||
event["timestamp"],
|
||||
logging.SendStatusRetrySuccess,
|
||||
); err != nil {
|
||||
@@ -617,3 +719,98 @@ func utf16PtrToString(p *uint16) string {
|
||||
|
||||
return string(utf16.Decode(s))
|
||||
}
|
||||
|
||||
// loadTimezoneWithFallback loads timezone with Windows-specific handling
|
||||
func loadTimezoneWithFallback(timezone string) (*time.Location, error) {
|
||||
// First try the standard IANA timezone
|
||||
loc, err := time.LoadLocation(timezone)
|
||||
if err == nil {
|
||||
return loc, nil
|
||||
}
|
||||
|
||||
// Map common IANA timezones to Windows timezones
|
||||
windowsTimezoneMap := map[string]string{
|
||||
"Europe/London": "GMT Standard Time",
|
||||
"Europe/Paris": "Romance Standard Time",
|
||||
"Europe/Berlin": "W. Europe Standard Time",
|
||||
"Europe/Madrid": "Romance Standard Time",
|
||||
"Europe/Rome": "W. Europe Standard Time",
|
||||
"Europe/Amsterdam": "W. Europe Standard Time",
|
||||
"America/New_York": "Eastern Standard Time",
|
||||
"America/Chicago": "Central Standard Time",
|
||||
"America/Denver": "Mountain Standard Time",
|
||||
"America/Los_Angeles": "Pacific Standard Time",
|
||||
"Asia/Tokyo": "Tokyo Standard Time",
|
||||
"Asia/Shanghai": "China Standard Time",
|
||||
"Asia/Kolkata": "India Standard Time",
|
||||
"Australia/Sydney": "AUS Eastern Standard Time",
|
||||
"UTC": "UTC",
|
||||
}
|
||||
|
||||
// Try Windows timezone name
|
||||
if windowsName, exists := windowsTimezoneMap[timezone]; exists {
|
||||
loc, err = time.LoadLocation(windowsName)
|
||||
if err == nil {
|
||||
return loc, nil
|
||||
}
|
||||
}
|
||||
|
||||
// If both fail, return the original error
|
||||
return nil, fmt.Errorf("timezone '%s' not found (tried IANA and Windows formats)", timezone)
|
||||
}
|
||||
|
||||
// reregisterSessionNotifications re-registers for session notifications
|
||||
// This helps fix the issue where session notifications stop working
|
||||
func (m *Monitor) reregisterSessionNotifications() error {
|
||||
if m.hwnd == 0 {
|
||||
return fmt.Errorf("no window handle available")
|
||||
}
|
||||
|
||||
// Unregister first
|
||||
wtsUnRegisterSessionNotification(m.hwnd)
|
||||
|
||||
// Small delay before re-registering
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Re-register
|
||||
err := wtsRegisterSessionNotification(m.hwnd, NOTIFY_FOR_ALL_SESSIONS)
|
||||
if err != nil {
|
||||
m.logger.Error("Failed to re-register session notifications: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
m.logger.Info("Successfully re-registered session notifications")
|
||||
return nil
|
||||
}
|
||||
|
||||
// healthMonitor monitors session notification health and re-registers if needed
|
||||
func (m *Monitor) healthMonitor() {
|
||||
for {
|
||||
select {
|
||||
case <-m.stopChan:
|
||||
if m.healthCheckTicker != nil {
|
||||
m.healthCheckTicker.Stop()
|
||||
}
|
||||
return
|
||||
case <-m.healthCheckTicker.C:
|
||||
// Check if we've received any notifications in the last 10 minutes
|
||||
// Note: This assumes some activity on the system, adjust threshold as needed
|
||||
timeSinceLastNotification := time.Since(m.lastNotificationTime)
|
||||
|
||||
// If no notifications for more than 15 minutes and system isn't locked,
|
||||
// consider re-registering (aggressive approach for testing)
|
||||
if timeSinceLastNotification > 15*time.Minute {
|
||||
m.logger.Warning("No session notifications received for %v, attempting re-registration", timeSinceLastNotification)
|
||||
|
||||
if err := m.reregisterSessionNotifications(); err != nil {
|
||||
m.logger.Error("Failed to re-register session notifications: %v", err)
|
||||
m.notificationHealthy = false
|
||||
} else {
|
||||
m.notificationHealthy = true
|
||||
// Reset the timer to give the re-registration a chance
|
||||
m.lastNotificationTime = time.Now()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user