Exemplo n.º 1
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="dir"></param>
        public void CleanUpDir(string dir)
        {
            if (!Directory.Exists(dir))
            {
                return;
            }

            foreach (var difo in new DirectoryInfo(dir).EnumerateDirectories())
            {
                foreach (var appDifo in difo.EnumerateDirectories())
                {
                    try
                    {
                        // Delete folders older than 1 month
                        if ((DateTime.Now - appDifo.LastWriteTime).TotalDays > 30)
                        {
                            UtilsSystem.DeleteDirectory(appDifo.FullName, this.Logger);
                            this.Logger.LogInfo(true, "Deleted temp dir: {0}", appDifo.FullName);
                        }
                    }
                    catch (Exception e)
                    {
                        this.Logger.LogException(e);
                    }
                }
            }
        }
Exemplo n.º 2
0
        public void undeploy(bool isUninstall = false)
        {
            // Not that we want to accidentally delete contents and files
            // for an application...
            if (!isUninstall)
            {
                return;
            }

            var diskSettings = this.DeployerSettings.castTo <DiskServiceSettings>();
            var mounts       = this.Deployment.GetSettingCollection <DiskStore>($"service.{diskSettings.id}");

            foreach (var m in mounts.Values)
            {
                // Most of the time this directory will not exist, as the base storage deployer will already have
                // deleted the application folder. But for "local" installed applications, this removes
                // the symlinks.
                if (!string.IsNullOrWhiteSpace(m.junctionRealPath) && Directory.Exists(m.junctionRealPath))
                {
                    UtilsJunction.RemoveJunction(m.junctionRealPath);
                }

                if (Directory.Exists(m.path))
                {
                    UtilsSystem.DeleteDirectory(m.path, this.Logger);
                }
            }

            var baseStoragePath = this.GetStoragePath(diskSettings);

            if (!string.IsNullOrWhiteSpace(baseStoragePath) && Directory.Exists(baseStoragePath))
            {
                UtilsSystem.DeleteDirectory(baseStoragePath, this.Logger);
            }
        }
Exemplo n.º 3
0
        /// <summary>
        /// Delete the artifact's source if it is remote
        /// </summary>
        /// <param name="artifact"></param>
        /// <param name="logger"></param>
        public static void DeleteIfRemote(this Artifact artifact, ILoggerInterface logger)
        {
            if (!artifact.isRemote)
            {
                return;
            }

            UtilsSystem.DeleteDirectory(artifact.localPath, logger);
        }
Exemplo n.º 4
0
        protected override void ProcessRecord()
        {
            ConsoleUtils.RunCode(() =>
            {
                var logger = new logger.ConsoleLogger();
                logger.SetVerbose(true);

                UtilsSystem.DeleteDirectory(this.Directory, logger, 30);
            });
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="settings"></param>
        protected void DeployFonts(AppBaseStorageDeployerSettings settings)
        {
            // Nada que desplegar
            if (settings.fonts?.Any() != true)
            {
                return;
            }

            // TODO: Newer windows versions allow for per-user font registration, we should implement that
            // because with this approach fonts are installed first come first-served, and cannot be updated
            // if necessary. Plus we don't know who owns a font, se we can't delete them on cleanup

            var utilsFont = new UtilsFont();

            foreach (var font in settings.fonts.AsIterable())
            {
                // Check that the provided path is a zip with the font
                var fontSourceZip = Path.Combine(this.Deployment.appPath, font.Path);

                if (Path.GetExtension(fontSourceZip) != ".zip")
                {
                    throw new Exception($"Unable to install the font '{font.Path}' because fonts have to be in a .zip file.");
                }

                if (!File.Exists(fontSourceZip))
                {
                    throw new Exception($"Font file not found '{font.Path}'.");
                }

                // We use a know directory
                var fontTempPath          = UtilsSystem.GetTempPath("font-" + Guid.NewGuid());
                var fontPersitentTempPath = UtilsSystem.GetTempPath("font-chef");

                try
                {
                    ZipFile.ExtractToDirectory(fontSourceZip, fontTempPath);
                    UtilsSystem.CopyFilesRecursively(new DirectoryInfo(fontTempPath), new DirectoryInfo(fontPersitentTempPath), true, true);
                    utilsFont.InstallFont(fontPersitentTempPath);
                }
                finally
                {
                    UtilsSystem.DeleteDirectory(fontTempPath, this.Logger, 8);

                    // This will some fonts get locked by the native API without explanation
                    try
                    {
                        UtilsSystem.DeleteDirectory(fontPersitentTempPath, this.Logger, 4);
                    }
                    catch
                    {
                        // ignored
                    }
                }
            }
        }
        public void TestSymlinksAreNotRemoved()
        {
            var logger = new TestLogsLogger(this, nameof(this.TestSymlinksAreNotRemoved));

            string testPath = UtilsSystem.GetTempPath("symlink_test" + Guid.NewGuid());

            var pathWebsite            = Path.Combine(testPath, "website");
            var pathContentsPersistent = Path.Combine(testPath, "content_store_persistent");

            Directory.CreateDirectory(pathWebsite);
            Directory.CreateDirectory(pathContentsPersistent);
            Directory.CreateDirectory(Path.Combine(pathContentsPersistent, "empty_directory"));

            string linkedDir  = Path.Combine(pathWebsite, "contents");
            string linkedDir2 = Path.Combine(pathWebsite, "contents2", "contents");

            UtilsSystem.EnsureDirectoryExists(linkedDir);
            UtilsSystem.EnsureDirectoryExists(linkedDir2);

            UtilsJunction.EnsureLink(linkedDir, pathContentsPersistent, logger, true, linkType: UtilsJunction.LinkTypeRequest.Junction);
            UtilsJunction.EnsureLink(linkedDir2, pathContentsPersistent, logger, true, linkType: UtilsJunction.LinkTypeRequest.Symlink);

            Assert.True(UtilsJunction.IsJunctionOrSymlink(linkedDir));
            Assert.True(UtilsJunction.IsJunctionOrSymlink(linkedDir2));

            string fileInContentsPeristent = Path.Combine(pathContentsPersistent, "test.txt");
            string fileInSymlinkDir        = Path.Combine(linkedDir, "test.txt");

            Assert.Equal(fileInContentsPeristent, UtilsJunction.ResolvePath(fileInSymlinkDir));

            string fileInContentsPeristent2 = Path.Combine(pathContentsPersistent, "test2.txt");
            string fileInSymlinkDir2        = Path.Combine(linkedDir2, "test2.txt");

            Assert.Equal(fileInContentsPeristent2, UtilsJunction.ResolvePath(fileInSymlinkDir2));

            File.WriteAllText(fileInSymlinkDir, "testfile");
            File.WriteAllText(fileInSymlinkDir2, "testfile");

            Assert.True(File.Exists(fileInSymlinkDir), $"File exists {fileInSymlinkDir}");
            Assert.True(File.Exists(fileInSymlinkDir2), $"File exists {fileInSymlinkDir2}");
            Assert.True(File.Exists(fileInContentsPeristent), $"File exists {fileInContentsPeristent}");
            Assert.True(File.Exists(fileInContentsPeristent2), $"File exists {fileInContentsPeristent2}");

            // If we delete the directory containing the symlink, the file still exists
            UtilsSystem.DeleteDirectory(pathWebsite, logger);
            Assert.False(Directory.Exists(pathWebsite), "Directory exists " + pathWebsite);

            Assert.False(File.Exists(fileInSymlinkDir), $"File exists {fileInSymlinkDir}");
            Assert.True(File.Exists(fileInContentsPeristent), $"File exists {fileInContentsPeristent}");
            Assert.False(File.Exists(fileInSymlinkDir2), $"File exists {fileInSymlinkDir2}");
            Assert.True(File.Exists(fileInContentsPeristent2), $"File exists {fileInContentsPeristent2}");

            Directory.Delete(testPath, true);
        }
        public void TestResolveJunctionPath()
        {
            var logger = new TestLogsLogger(this, nameof(this.TestResolveJunctionPath));

            string testPath = UtilsSystem.GetTempPath("symlink_test" + Guid.NewGuid());

            // Probar resolución de nivel 1

            string test1OriginalPath = UtilsSystem.EnsureDirectoryExists(Path.Combine(testPath, "test1"), true);
            string test1LinkPath     = Path.Combine(testPath, "test1_link");
            string test1JunctionPath = Path.Combine(testPath, "test1_junction");

            UtilsJunction.EnsureLink(test1LinkPath, test1OriginalPath, logger, true, linkType: UtilsJunction.LinkTypeRequest.Symlink);
            UtilsJunction.EnsureLink(test1JunctionPath, test1OriginalPath, logger, true, linkType: UtilsJunction.LinkTypeRequest.Junction);

            Assert.Equal(test1OriginalPath, UtilsJunction.ResolvePath(test1LinkPath));
            Assert.Equal(test1OriginalPath, UtilsJunction.ResolvePath(test1JunctionPath));

            // Probar resolución de subdirectorio existente y no existente

            string test2OriginalPath = UtilsSystem.EnsureDirectoryExists(Path.Combine(testPath, "test2"), true);
            string test2LinkPath     = Path.Combine(testPath, "test2_link");
            string test2JunctionPath = Path.Combine(testPath, "test2_junction");

            UtilsJunction.EnsureLink(test2LinkPath, test2OriginalPath, logger, true, linkType: UtilsJunction.LinkTypeRequest.Symlink);
            UtilsJunction.EnsureLink(test2JunctionPath, test2OriginalPath, logger, true, linkType: UtilsJunction.LinkTypeRequest.Junction);

            string test2LinkSubDir     = UtilsSystem.EnsureDirectoryExists(Path.Combine(test2LinkPath, "sub1", "sub2"), true);
            string test2JunctionSubDir = UtilsSystem.EnsureDirectoryExists(Path.Combine(test2JunctionPath, "sub3", "sub4"), true);

            Assert.Equal(Path.Combine(test2OriginalPath, "sub1", "sub2"), UtilsJunction.ResolvePath(test2LinkSubDir));
            Assert.Equal(Path.Combine(test2OriginalPath, "sub3", "sub4"), UtilsJunction.ResolvePath(test2JunctionSubDir));

            // Ahora subdirectorios que no existen
            Assert.Equal(Path.Combine(test2OriginalPath, "sub4", "sub5"), UtilsJunction.ResolvePath(Path.Combine(test2LinkPath, "sub4", "sub5")));
            Assert.Equal(Path.Combine(test2OriginalPath, "sub6", "sub7"), UtilsJunction.ResolvePath(Path.Combine(test2JunctionPath, "sub6", "sub7")));

            // Ahora una cadena de enlaces dentro de otro enlace...
            string test3LinkSubDir = Path.Combine(test2LinkPath, "sub8");

            UtilsSystem.EnsureDirectoryExists(Path.Combine(test2LinkPath, "test3"), true);
            UtilsJunction.EnsureLink(test3LinkSubDir, Path.Combine(test2LinkPath, "test3"), logger, true, linkType: UtilsJunction.LinkTypeRequest.Symlink);

            Assert.Equal(Path.Combine(test2OriginalPath, "test3"), UtilsJunction.ResolvePath(test3LinkSubDir));

            UtilsSystem.DeleteDirectory(testPath, logger, 2);

            // Non existent and malformed network uri get reconstructed as-is
            string testNetworkUri = "\\\\147.83.73.25\\a\\b\\c\\\\d";

            Assert.Equal(testNetworkUri, UtilsJunction.ResolvePath(testNetworkUri));
        }
Exemplo n.º 8
0
        /// <inheritdoc cref="IDownloaderInterface"/>
        public Artifact PullFromId(string version, string preferredLocalArtifactPath)
        {
            if (string.IsNullOrWhiteSpace(version))
            {
                version = this.Settings.path;
            }

            Artifact artifact = new Artifact
            {
                id        = version,
                localPath = preferredLocalArtifactPath,
                isRemote  = true
            };

            // Use artifact temp path, or local system temporary directory.
            if (Directory.Exists(artifact.localPath))
            {
                UtilsSystem.DeleteDirectory(artifact.localPath, this.Logger);
            }

            // The ID is the PATH to the local zip, so just unzip
            this.Logger.LogInfo(true, "Unzipping file....");
            ZipFile.ExtractToDirectory(version, artifact.localPath);
            this.Logger.LogInfo(true, "Unzipping finished....");

            artifact.obtainedAt = DateTime.UtcNow;

            artifact.artifactSettings = new ArtifactSettings();

            // We will merge data from both git and settings file, local settigns file
            // will override anything from GIT (if available).
            artifact.artifactSettings.PopulateFromGit(artifact.localPath);
            artifact.artifactSettings.PopulateFromSettingsFile(artifact.localPath, this.Logger);
            artifact.artifactSettings.PopulateFromEnvironment();

            // Branch name is critical to some deployment... populate with a no-branch-found....
            if (string.IsNullOrEmpty(artifact.artifactSettings.branch))
            {
                artifact.artifactSettings.branch = "no-branch-found";
                this.Logger.LogInfo(true, "Could not identify git branch for artifact. Using default: 'no-branch-found'");
            }

            return(artifact);
        }
Exemplo n.º 9
0
        /// <inheritdoc cref="IDownloaderInterface"/>
        public Artifact PullFromId(string version, string preferredLocalArtifactPath)
        {
            Artifact artifact = new Artifact
            {
                id        = version,
                localPath = preferredLocalArtifactPath,
                isRemote  = true
            };

            // Use artifact temp path, or local system temporary directory.
            if (Directory.Exists(artifact.localPath))
            {
                UtilsSystem.DeleteDirectory(artifact.localPath, this.Logger);
            }

            // Use the build version to pull the build information.
            Build build = this.Client.GetBuildFromVersion(version, this.Settings.username, this.Settings.project);

            // Make sure that the builds matches the current active branch, otherwise throw an exception
            if (!build.branch.Equals(this.Settings.branch, StringComparison.CurrentCultureIgnoreCase))
            {
                throw new Exception($"Requested version '{version}' with branch '{build.branch}' does not belong to active settings branch '{this.Settings.branch}'");
            }

            this.Client.DownloadSingleArtifactFromBuild(this.ApplicationId, build, this.Settings.artifact_regex, artifact.localPath, this.Logger);

            artifact.artifactSettings = new ArtifactSettings();
            artifact.artifactSettings.PopulateFromSettingsFile(artifact.localPath, this.Logger);

            if (string.IsNullOrWhiteSpace(artifact.artifactSettings.branch))
            {
                artifact.artifactSettings.branch = Convert.ToString(build.branch);
            }

            if (string.IsNullOrWhiteSpace(artifact.artifactSettings.commit_sha))
            {
                artifact.artifactSettings.commit_sha = Convert.ToString(build.commitId);
            }

            return(artifact);
        }
Exemplo n.º 10
0
        public void TestCannotDeleteDirectoriesWithSmallDepth()
        {
            var directory = Directory.CreateDirectory("c:\\testdirectory");

            var logger = new TestLogsLogger(this, nameof(this.TestCannotDeleteDirectoriesWithSmallDepth));

            Assert.Throws <InvalidOperationException>(() =>
            {
                UtilsSystem.DeleteDirectory(directory.FullName, logger);
            });

            Assert.Throws <InvalidOperationException>(() =>
            {
                UtilsSystem.DeleteDirectory(directory.FullName, logger, 5);
            });

            Assert.Throws <InvalidOperationException>(() =>
            {
                UtilsSystem.DeleteDirectory(directory.FullName, logger);
            });

            directory.Delete();
        }
Exemplo n.º 11
0
        /// <summary>
        /// Provisions a certificate in the central store
        /// </summary>
        /// <param name="hostName">Domain to register</param>
        /// <param name="email">Registration e-mail</param>
        /// <param name="bindingInfo">IIS binding info</param>
        /// <param name="ownerSiteName">The site that owns the binding, used to assign identity and application pool permissions.</param>
        /// <param name="forceSelfSigned">Force a self-signed certificate</param>
        /// <param name="forceRenewal">Force renewal, even if renewal conditions are not met</param>
        /// <returns>The certificate's friendly name, ready to be bound in IIS</returns>
        public void ProvisionCertificateInIis(
            string hostName,
            string email,
            string bindingInfo,
            string ownerSiteName,
            bool forceSelfSigned = false,
            bool forceRenewal    = false)
        {
            if (hostName.Contains("*"))
            {
                throw new Exception($"Provisioning certificates for wildcard host name '{hostName}' is not supported.");
            }

            var    currentCertificate       = UtilsIis.FindCertificateInCentralCertificateStore(hostName, this.Logger, out _);
            double remainingCertificateDays = (currentCertificate?.NotAfter - DateTime.Now)?.TotalDays ?? 0;

            this.Logger.LogInfo(true, "Total days remaining for certificate expiration: '{0}'", (int)Math.Floor(remainingCertificateDays));

            // Trigger renovation. Do this differently on mock/prod environment.
            // Next renewal attempt is calculated based on previous renewal attempt
            var renewalState = this.GetCertificateRenewalState(hostName);

            // Legacy apps don't have this set, or when a certificate has been manually placed
            if (renewalState.NextRenewal == null && remainingCertificateDays > 1)
            {
                renewalState.NextRenewal = this.CalculateNextRenewalAttempt(hostName, (int)remainingCertificateDays);
                this.StoreCertificateRenewalState(renewalState);
            }

            int remainingDaysForNextRenewal = renewalState.NextRenewal == null ? 0 : (int)(renewalState.NextRenewal - DateTime.UtcNow).Value.TotalDays;
            int certificateTotalDuration    = currentCertificate == null ? 0 : (int)(currentCertificate.NotAfter - currentCertificate.NotBefore).TotalDays;

            this.Logger.LogInfo(true, "Next renewal attempt for this site SSL targeted in '{0}' days.", remainingDaysForNextRenewal);

            // Check that the validationfailed request rate is not exceeded for this domain
            if (!forceRenewal && renewalState.FailedValidations.AsIterable().Count(i => (DateTime.UtcNow - i).TotalHours < 48) >= 2)
            {
                // Make this message verbos so that it will not flood the logs, the failed validation message will get logged
                // anyways and is sufficient.
                this.Logger.LogWarning(true, "The hostname '{0}' has reached the limit of two failed validations in the last 48 hours.", hostName);
                return;
            }

            if (!forceRenewal && !forceSelfSigned && remainingDaysForNextRenewal > 0 && remainingCertificateDays > 0)
            {
                this.Logger.LogWarning(true, "Next renewal attempt date not reached, skipping SSL provisioning.");
                return;
            }

            if (!forceRenewal && remainingDaysForNextRenewal > 0 && (remainingDaysForNextRenewal > certificateTotalDuration * 0.5) && certificateTotalDuration > 0)
            {
                this.Logger.LogWarning(false, "Certificate has not yet been through at least 50% of it's lifetime so it will not be renewed.'");
                renewalState.NextRenewal = this.CalculateNextRenewalAttempt(hostName, (int)remainingCertificateDays);
                this.StoreCertificateRenewalState(renewalState);
                return;
            }

            // Check the general too many requests rate exceeded
            if (!forceRenewal && this.SimpleStoreRenewalStatus.Get <bool>("ssl-certificate-provider-too-many-requests", out var tooManyRequests))
            {
                this.Logger.LogWarning(false, "Certificate provisioning temporarily disabled due to a Too Many Requests ACME error. Flag stored in {0}", tooManyRequests.StorePath);
                return;
            }

            this.Logger.LogInfo(false, "Attempting SSL certificate renewal for site '{0}' and host '{1}'", ownerSiteName, hostName);

            // Clear old validation failed requests
            if (renewalState.FailedValidations?.Any() == true)
            {
                // Only keep failed validations that happen in the last 5 days
                renewalState.FailedValidations = renewalState.FailedValidations
                                                 .Where((i) => (DateTime.UtcNow - i).TotalDays < 5).ToList();
            }

            // This is a little bit inconvenient but... the most reliable and compatible
            // way to do this is to setup a custom IIS website that uses the binding during
            // provisioning.

            long        tempSiteId;
            List <Site> haltedSites = new List <Site>();

            var    tempSiteName  = "cert-" + this.AppId;
            var    tempSiteAppId = "cert-" + this.AppId;
            string tempHostName  = "localcert-" + hostName;

            this.Logger.LogInfo(true, "Preparing temp site: " + tempSiteName);

            List <Site> conflictingSites;

            // Prepare the site
            using (ServerManager sm = new ServerManager())
            {
                // Query the sites in a resilient way...
                conflictingSites = UtilsSystem.QueryEnumerable(
                    sm.Sites,
                    (s) => s.State == ObjectState.Started && s.Bindings.Any((i) => i.Host.Equals(hostName)),
                    (s) => s,
                    (s) => s.Name,
                    this.Logger);
            }

            // Stop the sites that might prevent this one from starting
            foreach (var s in conflictingSites)
            {
                this.Logger.LogInfo(true, "Stopping site {0} to avoid binding collision.", s.Name);
                this.AppPoolUtils.WebsiteAction(s.Name, AppPoolActionType.Stop, skipApplicationPools: true);
                haltedSites.Add(s);
            }

            using (ServerManager sm = new ServerManager())
            {
                // Make sure there is no other site (might be stuck?)
                var existingSite = (from p in sm.Sites
                                    where p.Name == tempSiteName
                                    select p).FirstOrDefault();

                var tempSite = existingSite ?? sm.Sites.Add(tempSiteName, this.GetAcmeTemporarySiteRootForApplication(), 80);

                // Propagate application pool usage so that permissions are properly handled.
                var ownerSite = sm.Sites.First((i) => i.Name == ownerSiteName);
                tempSite.Applications.First().ApplicationPoolName = ownerSite.Applications.First().ApplicationPoolName;

                // Delete all bindings
                tempSite.Bindings.Clear();

                tempSite.Bindings.Add(bindingInfo, "http");
                tempSite.Bindings.Add($"{UtilsIis.LOCALHOST_ADDRESS}:80:" + tempHostName, "http");
                tempSiteId = tempSite.Id;

                this.UtilsHosts.AddHostsMapping(UtilsIis.LOCALHOST_ADDRESS, tempHostName, tempSiteAppId);

                // Prepare the website contents
                var sourceDir = UtilsSystem.FindResourcePhysicalPath(typeof(IISDeployer), ".well-known");
                UtilsSystem.CopyFilesRecursively(new DirectoryInfo(sourceDir), new DirectoryInfo(this.GetWellKnownSharedPathForApplication()), true);

                UtilsIis.CommitChanges(sm);
            }

            UtilsIis.WaitForSiteToBeAvailable(tempSiteName, this.Logger);
            UtilsIis.ConfigureAnonymousAuthForIisApplication(tempSiteName, this.Deployment.WindowsUsernameFqdn(), this.Deployment.GetWindowsPassword());

            IAcmeSharpProvider provider = null;

            try
            {
                this.AppPoolUtils.WebsiteAction(tempSiteName, AppPoolActionType.Start);

                // Check that the site does work using the local binding
                var testDataUrl = $"http://{tempHostName}/.well-known/acme-challenge/test.html";
                this.Logger.LogInfo(true, "Validating local challenge setup at: {0}", testDataUrl);

                if (!string.Equals(UtilsSystem.DownloadUriAsText(testDataUrl), "test data"))
                {
                    throw new Exception($"Could not locally validate acme challenge site setup at {testDataUrl}");
                }

                // Ssl registration configuration only depends on the e-mail and is signed as such
                string sslSignerAndRegistrationStoragePath = UtilsSystem.EnsureDirectoryExists(
                    UtilsSystem.CombinePaths(this.StoragePath, "_ssl_config", StringFormating.Instance.ExtremeClean(email)), true);

                // Initialize the provider
                bool useMockProvider = this.MockEnvironment || forceSelfSigned;

                provider = useMockProvider
                    ? (IAcmeSharpProvider) new AcmeSharpProviderMock(this.Logger, tempHostName)
                    : this.GetAcmeProvider(this.Logger, hostName);

                var signerPath       = Path.Combine(this.StoragePath, "_signer.xml");
                var registrationPath = Path.Combine(sslSignerAndRegistrationStoragePath, "registration.json");

                provider.InitRegistration(signerPath, registrationPath, email);

                string challengeUrl;
                string challengeContent;
                string challengeFilePath;

                try
                {
                    provider.GenerateHttpChallenge(
                        out challengeUrl,
                        out challengeContent,
                        out challengeFilePath);
                }
                catch (AcmeClient.AcmeWebException acmeException)
                {
                    if (acmeException.Message.Contains("429"))
                    {
                        int waitHours = 1;
                        this.SimpleStoreRenewalStatus.Set("ssl-certificate-provider-too-many-requests", true, 60 * waitHours);
                        this.Logger.LogError("Let's encrypt too many requests issue. Certificate provisioning disabled for the next {0} hours.", waitHours);
                        this.Logger.LogException(acmeException, EventLogEntryType.Warning);
                        return;
                    }

                    throw;
                }

                // Write the challanege contents
                string challengeFullPath =
                    Path.Combine(this.GetAcmeTemporarySiteRootForApplication(), challengeFilePath);

                File.WriteAllText(
                    challengeFullPath,
                    challengeContent);

                this.Logger.LogInfo(false, $"Veryfing challenge at '{challengeUrl}'");

                try
                {
                    // Validate that we can actually access the challenge ourselves!
                    string contents = UtilsSystem.DownloadUriAsText(challengeUrl, false);

                    if (!string.Equals(contents, challengeContent))
                    {
                        throw new Exception(
                                  $"Could not validate ACME challenge, retrieved challenge '{contents}' does not match '{challengeContent}'");
                    }
                }
                catch (Exception e)
                {
                    this.Logger.LogWarning(true, "Cannot self-verify auth challenge, this can sometimes happeen under some DNS setups. {0}", e.Message + Environment.NewLine + e.InnerException?.Message);
                }

                var challengeValidated = false;

                try
                {
                    challengeValidated = provider.ValidateChallenge();
                }
                catch (Exception e)
                {
                    this.Logger.LogException(e, EventLogEntryType.Warning);
                }

                this.Logger.LogWarning(true, "Remote challenge validation success: " + (challengeValidated ? "Yes" : "No"));

                // Download the certificates to this temp location
                string           temporaryCertificatePath = UtilsSystem.EnsureDirectoryExists(UtilsSystem.CombinePaths(this.StoragePath, this.AppId, "ssl_certificates", hostName), true);
                CertificatePaths certificatepaths         = null;

                // This is here for testing purposes
                if (Environment.GetEnvironmentVariable("TEST_FAIL_CHALLENGE_VALIDATION") == true.ToString())
                {
                    challengeValidated = false;
                }

                if (!challengeValidated)
                {
                    // There is a Failed Validation limit of 5 failures per account, per hostname, per hour.
                    renewalState.FailedValidations = renewalState.FailedValidations ?? new List <DateTime>();
                    renewalState.FailedValidations.Add(DateTime.UtcNow);

                    this.Logger.LogError(
                        "Challenge could not be validated at '{0}'. If behind a load balancer, make sure that the site is deployed in ALL nodes, remove the self-signed certificate from the store and redeploy the application.",
                        challengeUrl);

                    this.StoreCertificateRenewalState(renewalState);
                }
                else
                {
                    try
                    {
                        certificatepaths = provider.DownloadCertificate(
                            UtilsEncryption.GetMD5(hostName),
                            hostName,
                            temporaryCertificatePath);
                    }
                    catch (AcmeClient.AcmeWebException acmeException)
                    {
                        this.Logger.LogException(acmeException, EventLogEntryType.Warning);
                    }
                    catch (WebException webException)
                    {
                        this.Logger.LogException(webException, EventLogEntryType.Warning);
                    }
                    catch (Exception e)
                    {
                        this.Logger.LogException(e, EventLogEntryType.Warning);
                    }
                }

                if (certificatepaths == null && currentCertificate == null)
                {
                    this.Logger.LogWarning(false, "Unable to acquire certificate and site does not have a valid existing one, using self-signed fallback.");

                    provider = new AcmeSharpProviderMock(this.Logger, hostName);

                    certificatepaths = provider.DownloadCertificate(
                        UtilsEncryption.GetMD5(hostName),
                        hostName,
                        temporaryCertificatePath);
                }

                // Save this, use a fixed name certificate file
                if (certificatepaths != null)
                {
                    string certificateFilePath = Path.Combine(UtilsIis.CentralStorePath(this.Logger), hostName + ".pfx");

                    UtilsSystem.RetryWhile(
                        () => { File.Copy(certificatepaths.pfxPemFile, certificateFilePath, true); },
                        (e) => true,
                        2500,
                        this.Logger);

                    this.Logger.LogInfo(false, "Certificate file writen to '{0}'", certificateFilePath);

                    // TODO: Activate this refreshing when it's prooved to work
                    // UtilsIis.EnsureCertificateInCentralCertificateStoreIsRebound(hostName, this.Logger);
                }

                // Remove temporary certificates
                UtilsSystem.DeleteDirectory(temporaryCertificatePath, this.Logger);

                // Remove the already used challenge if it was validated. Otherwise keep it
                // for debugging purposes.
                if (challengeValidated && File.Exists(challengeFullPath))
                {
                    File.Delete(challengeFullPath);
                }

                // In the end, we always have a certificate. Program a renewal date according to the remaining expiration.
                currentCertificate       = UtilsIis.FindCertificateInCentralCertificateStore(hostName, this.Logger, out _);
                remainingCertificateDays = (currentCertificate?.NotAfter - DateTime.Now)?.TotalDays ?? 0;

                // Add some randomness in renewal dates to avoid all certificates being renewed at once and reaching api limits
                renewalState.LastRenewal = DateTime.UtcNow;
                renewalState.NextRenewal = this.CalculateNextRenewalAttempt(hostName, (int)remainingCertificateDays);
                this.StoreCertificateRenewalState(renewalState);
            }
            finally
            {
                this.Logger.LogInfo(true, "Disposing temporary verification setup");

                provider?.Dispose();

                this.UtilsHosts.RemoveHostsMapping(tempSiteAppId);

                // Restore the original state of IIS!!!
                using (ServerManager sm = new ServerManager())
                {
                    var site = sm.Sites.Single(i => i.Id == tempSiteId);
                    UtilsIis.RemoveSite(site, sm, this.Logger);
                    UtilsIis.CommitChanges(sm);
                }

                // Give IIS some time to reconfigure itself and free resources.
                Thread.Sleep(1000);

                // Start the sites
                foreach (var site in haltedSites)
                {
                    // Add some retry logic here because bringing the original sites online is critical
                    UtilsSystem.RetryWhile(() => { this.AppPoolUtils.WebsiteAction(site.Name, AppPoolActionType.Start); }, (e) => true, 5000, this.Logger);
                }
            }
        }