// Notify is responsable for selecting the domains that should be notified in the system. // It will send alert e-mails for each owner of a domain func Notify() { defer func() { // Something went really wrong while notifying the owners. Log the error stacktrace // and move out if r := recover(); r != nil { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] log.Printf("Panic detected while notifying the owners. Details: %v\n%s", r, buf) } }() log.Info("Start notification job") defer func() { log.Info("End notification job") }() log.Debugf("Initializing database with the parameters: URIS - %v | Name - %s | Auth - %t | Username - %s", config.ShelterConfig.Database.URIs, config.ShelterConfig.Database.Name, config.ShelterConfig.Database.Auth.Enabled, config.ShelterConfig.Database.Auth.Username, ) database, databaseSession, err := mongodb.Open( config.ShelterConfig.Database.URIs, config.ShelterConfig.Database.Name, config.ShelterConfig.Database.Auth.Enabled, config.ShelterConfig.Database.Auth.Username, config.ShelterConfig.Database.Auth.Password, ) if err != nil { log.Println("Error while initializing database. Details:", err) return } defer databaseSession.Close() domainDAO := dao.DomainDAO{ Database: database, } domainChannel, err := domainDAO.FindAllAsyncToBeNotified( config.ShelterConfig.Notification.NameserverErrorAlertDays, config.ShelterConfig.Notification.NameserverTimeoutAlertDays, config.ShelterConfig.Notification.DSErrorAlertDays, config.ShelterConfig.Notification.DSTimeoutAlertDays, // TODO: Should we move this configuration parameter to a place were both modules can // access it. This sounds better for configuration deployment config.ShelterConfig.Scan.VerificationIntervals.MaxExpirationAlertDays, ) if err != nil { log.Println("Error retrieving domains to notify. Details:", err) return } // Dispatch the asynchronous part of the method for { // Get domain from the database (one-by-one) domainResult := <-domainChannel // Detect errors while retrieving a specific domain. We are not going to stop all the // process when only one domain got an error if domainResult.Error != nil { log.Println("Error retrieving domain to notify. Details:", domainResult.Error) continue } // Problem detected while retrieving a domain or we don't have domains anymore if domainResult.Error != nil || domainResult.Domain == nil { break } if err := notifyDomain(domainResult.Domain); err != nil { log.Println("Error notifying a domain. Details:", err) } } }
// The main function of the system is responsable for deploying all system components that // are enabled. For now we have the REST server and the scan system func main() { runtime.GOMAXPROCS(runtime.NumCPU()) logPath := filepath.Join( config.ShelterConfig.BasePath, config.ShelterConfig.LogFilename, ) if err := log.SetOutput(logPath); err != nil { log.Println(err) return } defer log.Close() if config.ShelterConfig.RESTServer.Enabled { var err error restListeners, err = rest.Listen() if err != nil { log.Println("Error while aquiring interfaces for REST server. Details:", err) os.Exit(ErrListeningRESTInterfaces) } if err := rest.Start(restListeners); err != nil { log.Println("Error starting the REST server. Details:", err) os.Exit(ErrStartingRESTServer) } log.Info("REST server started") } if config.ShelterConfig.WebClient.Enabled { var err error clientListeners, err = client.Listen() if err != nil { log.Println("Error while aquiring interfaces for Client server. Details:", err) os.Exit(ErrListeningClientInterfaces) } if err := client.Start(clientListeners); err != nil { log.Println("Error starting the Client server. Details:", err) os.Exit(ErrStartingWebClient) } log.Info("Web client started") } if config.ShelterConfig.Scan.Enabled { // Attention: Cannot use timezone abbreviations // http://stackoverflow.com/questions/25368415/golang-timezone-parsing scanTime, err := time.Parse("15:04:05 -0700", config.ShelterConfig.Scan.Time) if err != nil { log.Println("Scan time not in a valid format. Details:", err) os.Exit(ErrScanTimeFormat) } scanTime = scanTime.UTC() now := time.Now().UTC() nextExecution := time.Date( now.Year(), now.Month(), now.Day(), scanTime.Hour(), scanTime.Minute(), scanTime.Second(), scanTime.Nanosecond(), scanTime.Location(), ) scheduler.Register(scheduler.Job{ Type: scheduler.JobTypeScan, NextExecution: nextExecution, Interval: time.Duration(config.ShelterConfig.Scan.IntervalHours) * time.Hour, Task: scan.ScanDomains, }) // Must be called after registering in scheduler, because we retrieve the next execution time // from it if err := model.InitializeCurrentScan(); err != nil { log.Println("Current scan information got an error while initializing. Details:", err) os.Exit(ErrCurrentScanInitialize) } } if config.ShelterConfig.Notification.Enabled { if err := notification.LoadTemplates(); err != nil { log.Println("Error loading notification templates. Details:", err) os.Exit(ErrNotificationTemplates) } // Attention: Cannot use timezone abbreviations // http://stackoverflow.com/questions/25368415/golang-timezone-parsing notificationTime, err := time.Parse("15:04:05 -0700", config.ShelterConfig.Scan.Time) if err != nil { log.Println("Scan time not in a valid format. Details:", err) os.Exit(ErrScanTimeFormat) } notificationTime = notificationTime.UTC() now := time.Now().UTC() nextExecution := time.Date( now.Year(), now.Month(), now.Day(), notificationTime.Hour(), notificationTime.Minute(), notificationTime.Second(), notificationTime.Nanosecond(), notificationTime.Location(), ) scheduler.Register(scheduler.Job{ Type: scheduler.JobTypeNotification, NextExecution: nextExecution, Interval: time.Duration(config.ShelterConfig.Notification.IntervalHours) * time.Hour, Task: notification.Notify, }) } scheduler.Start() select {} }
// Function responsible for running the domain scan system, checking the configuration of each // domain in the database according to an algorithm. This method is synchronous and will return only // after the scan proccess is done func ScanDomains() { defer func() { // Something went really wrong while scanning the domains. Log the error stacktrace // and move out if r := recover(); r != nil { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] log.Printf("Panic detected while scanning domains. Details: %v\n%s", r, buf) } }() log.Info("Start scan job") defer func() { log.Info("End scan job") }() log.Debugf("Initializing database with the parameters: URIS - %v | Name - %s | Auth - %t | Username - %s", config.ShelterConfig.Database.URIs, config.ShelterConfig.Database.Name, config.ShelterConfig.Database.Auth.Enabled, config.ShelterConfig.Database.Auth.Username, ) database, databaseSession, err := mongodb.Open( config.ShelterConfig.Database.URIs, config.ShelterConfig.Database.Name, config.ShelterConfig.Database.Auth.Enabled, config.ShelterConfig.Database.Auth.Username, config.ShelterConfig.Database.Auth.Password, ) if err != nil { log.Println("Error while initializing database. Details:", err) return } defer databaseSession.Close() injector := NewInjector( database, config.ShelterConfig.Scan.DomainsBufferSize, config.ShelterConfig.Scan.VerificationIntervals.MaxOKDays, config.ShelterConfig.Scan.VerificationIntervals.MaxErrorDays, config.ShelterConfig.Scan.VerificationIntervals.MaxExpirationAlertDays, ) querierDispatcher := NewQuerierDispatcher( config.ShelterConfig.Scan.NumberOfQueriers, config.ShelterConfig.Scan.DomainsBufferSize, config.ShelterConfig.Scan.UDPMaxSize, time.Duration(config.ShelterConfig.Scan.Timeouts.DialSeconds)*time.Second, time.Duration(config.ShelterConfig.Scan.Timeouts.ReadSeconds)*time.Second, time.Duration(config.ShelterConfig.Scan.Timeouts.WriteSeconds)*time.Second, config.ShelterConfig.Scan.ConnectionRetries, ) collector := NewCollector( database, config.ShelterConfig.Scan.SaveAtOnce, ) // Create a new scan information model.StartNewScan() var scanGroup sync.WaitGroup errorsChannel := make(chan error, config.ShelterConfig.Scan.ErrorsBufferSize) domainsToQueryChannel := injector.Start(&scanGroup, errorsChannel) domainsToSaveChannel := querierDispatcher.Start(&scanGroup, domainsToQueryChannel) collector.Start(&scanGroup, domainsToSaveChannel, errorsChannel) // Keep track of errors for the scan information structure errorDetected := false go func() { for { select { case err := <-errorsChannel: // Detect the poison pill to finish the error listener go routine. This poison // pill should be sent after all parts of the scan are done and we are sure that // we don't have any error to log anymore if err == nil { return } else { errorDetected = true log.Println("Error detected while executing the scan. Details:", err) } } } }() // Wait for all parts of the scan to finish their job scanGroup.Wait() // Finish the error listener sending a poison pill errorsChannel <- nil scanDAO := dao.ScanDAO{ Database: database, } // Save the scan information for future reports if err := model.FinishAndSaveScan(errorDetected, scanDAO.Save); err != nil { log.Println("Error while saving scan information. Details:", err) } }