public override async Task FlushAsync(bool halfFlush = false, CancellationToken cancel = default) { List <string> linesToSend; List <string> lines2ToSend; try { if (MyLocalIp == null) { MyLocalIp = await GetMyPrivateIpNativeUtil.GetMyPrivateIpAsync(IPVersion.IPv4); } } catch (Exception ex) { Console.WriteLine(ex.ToString()); } if (MyIpInfo == null) { try { await using GetMyIpClient c = new GetMyIpClient(); MyIpInfo = await c.GetMyIpInfoAsync(IPVersion.IPv4, cancel); } catch { } } string globalInfo = "(Unknown)"; if (MyIpInfo != null) { if (MyIpInfo.GlobalFqdn._IsSamei(MyIpInfo.GlobalIpAddress.ToString())) { globalInfo = MyIpInfo.GlobalFqdn; } else { globalInfo = $"{MyIpInfo.GlobalFqdn} - {MyIpInfo.GlobalIpAddress}"; } } lock (this.Lock) { if (this.Lines.Count == 0 && this.Lines2.Count == 0) { return; } linesToSend = this.Lines; lines2ToSend = this.Lines2; this.Lines = new List <string>(); this.Lines2 = new List <string>(); } string cmdName = CoresLib.Report_CommandName; if (cmdName._IsEmpty()) { cmdName = "Unknown"; } string resultStr = CoresLib.Report_SimpleResult._OneLine(); if (resultStr._IsEmpty()) { resultStr = "Ok"; } StringWriter w = new StringWriter(); w.WriteLine($"Reported: {DtOffsetNow._ToDtStr()}"); w.WriteLine($"Program: {CoresLib.AppName} Built: {Env.BuildTimeStamp._ToDtStr()}"); w.WriteLine($"Hostname: {Env.DnsFqdnHostName}"); w.WriteLine($"Global: {globalInfo}, Local: {MyLocalIp}"); w.WriteLine($"Command: {cmdName}, Result: {(CoresLib.Report_HasError ? "*Error* - " : "OK - ")}{resultStr}"); if (lines2ToSend.Count >= 1) { w.WriteLine("====================="); w.WriteLine(); lines2ToSend.ForEach(x => w.WriteLine(x)); w.WriteLine(); } if (linesToSend.Count >= 1) { w.WriteLine("--------------------"); w.WriteLine(); linesToSend.ForEach(x => w.WriteLine(x)); w.WriteLine(); } w.WriteLine("--------------------"); EnvInfoSnapshot snapshot = new EnvInfoSnapshot(); snapshot.CommandLine = ""; w.WriteLine($"Program Details: {snapshot._GetObjectDump()}"); w.WriteLine(); string subject = $"Report - {cmdName}{(CoresLib.Report_HasError ? " *Error*" : "")} - {linesToSend.Count} lines - {Env.DnsHostName} - {MyLocalIp} ({globalInfo}): {resultStr._NormalizeSoftEther(true)._TruncStrEx(60)}"; var hostAndPort = this.Settings.SmtpServer._ParseHostnaneAndPort(Consts.Ports.Smtp); SmtpConfig cfg = new SmtpConfig(hostAndPort.Item1, hostAndPort.Item2, this.Settings.SmtpUseSsl, this.Settings.SmtpUsername, this.Settings.SmtpPassword); Console.WriteLine("Report Subject: " + subject); try { Console.WriteLine($"SMTP Log Sending to '{this.Settings.MailTo}' ..."); await TaskUtil.RetryAsync(async() => { await SmtpUtil.SendAsync(cfg, this.Settings.MailFrom, this.Settings.MailTo, subject, w.ToString(), false, cancel); return(0); }, 1000, 3, cancel : cancel, randomInterval : true); Console.WriteLine("SMTP Log Sent."); } catch (Exception ex) { Console.WriteLine("SMTP Log Send Error: " + ex.ToString()); } }
// 商用サービス開発者向け: サーバー情報の取得、アクティベーション、アクティベーション解除処理 public async Task <ThinControllerRpcServerObjectInfo?> Paid_GetOrSetServerObjectInfoAsync(string hostKey, bool?newState = null, string tag = "", CancellationToken cancel = default) { hostKey = hostKey._NonNullTrim()._NormalizeHexString(); // データベースエラー時は処理禁止 if (IsDatabaseConnected == false) { throw new VpnException(VpnError.ERR_TEMP_ERROR); } await using var db = await OpenDatabaseForWriteAsync(cancel); // この関数は同時に 1 ユーザーからしか実行されないようにする (高負荷防止のため) using var asyncLock = await RenamePcidAsyncLock.LockWithAwait(cancel); ThinControllerRpcServerObjectInfo?ret = null; await db.TranAsync(async() => { var machine = await db.EasySelectSingleAsync <ThinDbMachine>("select * from MACHINE where CERT_HASH = @CERT_HASH", new { CERT_HASH = hostKey, }, false, true, cancel); if (machine == null) { return(false); } var oldStatus = this.Controller.Paid_CalcServerLicenseStatus(machine.PCID, machine.JSON_ATTRIBUTES, machine.FIRST_CLIENT_DATE._AsDateTimeOffset(true, true)); if (newState.HasValue == false || newState.Value == oldStatus.IsCurrentActivated) { // 状態の変更なし ret = oldStatus; return(false); } bool activate = newState.Value; EasyJsonStrAttributes json = new EasyJsonStrAttributes(machine.JSON_ATTRIBUTES); json["Paid_IsCurrentActivated"] = activate._ToBoolStrLower(); if (activate) { json["Paid_ActivatedOnceInPast"] = true._ToBoolStrLower(); json["Paid_ActivatedDateTime"] = DtOffsetNow._ToDtStr(true); } else { json["Paid_DeactivatedDateTime"] = DtOffsetNow._ToDtStr(true); } json["Paid_Tag"] = tag; string newJsonStr = json.ToString(); var newStatus = this.Controller.Paid_CalcServerLicenseStatus(machine.PCID, newJsonStr, machine.FIRST_CLIENT_DATE._AsDateTimeOffset(true, true)); int i = await db.EasyExecuteAsync("update MACHINE set JSON_ATTRIBUTES = @JSON_ATTRIBUTES where CERT_HASH = @CERT_HASH", new { JSON_ATTRIBUTES = newJsonStr, CERT_HASH = machine.CERT_HASH, }); if (i != 1) { throw new CoresLibException("Database update error. i != 1."); } await this.AddApiLogAsync(db, activate ? "Activate" : "Deactivate", new { UniqueId = machine.CERT_HASH, Pcid = machine.PCID, OldStatus = oldStatus, NewStatus = newStatus, }); ret = newStatus; return(true); }); return(ret); }
// GitLab のユーザーメンテナンス async Task Loop1_MainteUsersAsync(CancellationToken cancel = default) { long lastHookTick = -1; List <GitLabMainteClient.User> lastPendingUsers = new List <GitLabMainteClient.User>(); while (cancel.IsCancellationRequested == false) { // 新規申請中のユーザーが増えたらメールで知らせる try { // ユーザーの列挙 var users = await this.GitLabClient.EnumUsersAsync(cancel); var pendingUsers = users.Where(x => x.IsSystemUser() == false && x.state == "blocked_pending_approval").OrderBy(x => x.id); var newPendingUsers = pendingUsers.Where(u => lastPendingUsers.Where(a => a.id == u.id).Any() == false); StringWriter w = new StringWriter(); string url = this.Settings.GitLabClientSettings.GitLabBaseUrl._CombineUrl("/admin/users?filter=blocked_pending_approval").ToString(); string subject = $"{url._ParseUrl().Host} にユーザー {newPendingUsers.Select(x => ("[" + x.commit_email._NonNullTrim() + " " + x.name + " " + x.username + "]"))._Combine(" ,")} の参加申請がありました"; w.WriteLine(subject + "。"); w.WriteLine(); w.WriteLine($"GitLab のアドレス: {url}"); w.WriteLine(); w.WriteLine($"現在時刻: {DtOffsetNow._ToDtStr()}"); w.WriteLine(); w.WriteLine($"新しい申請中のユーザー ({newPendingUsers.Count()}):"); int num = 0; foreach (var user in newPendingUsers) { lastPendingUsers.Add(user._CloneDeep()); w.WriteLine("- " + user.name + " " + user.username + " " + user.commit_email); num++; } w.WriteLine(); w.WriteLine($"現在申請中のユーザー一覧 ({pendingUsers.Count()})"); foreach (var user in pendingUsers) { w.WriteLine("- " + user.name + " " + user.username + " " + user.commit_email); } w.WriteLine(); w.WriteLine($"GitLab のアドレス: {url}"); w.WriteLine(); w.WriteLine(); //Dbg.Where(); if (num >= 1) { await this.SendMailAsync(subject, w.ToString(), cancel); } } catch (Exception ex) { ex._Error(); } // すべてのユーザーをデフォルトグループに自動追加する try { await this.JoinAllUsersToSpecificGroupAsync(this.Settings.DefaultGroupsAllUsersWillJoin, cancel); } catch (Exception ex) { ex._Error(); } await TaskUtil.AwaitWithPollAsync(this.Settings.UsersListMainteIntervalMsecs, 500, () => { long currentHookTick = this.HookFiredTick; if (lastHookTick != currentHookTick) { lastHookTick = currentHookTick; return(true); } return(false); }, cancel, true); } }
public void UpdateCerts(IEnumerable <CertificateStore> certsList, bool updateSameCert, string emptySiteAs) { int numWarningTotal = 0; int warningCerts = 0; // サーバーに存在するすべての証明書リストを取得 var currentCertDict = GetCurrentMachineCertificateList(); // 取得した証明書が古くなっていれば警告を出す DateTimeOffset threshold2 = DtOffsetNow.AddDays(50); foreach (var cert in certsList.Select(x => x.PrimaryCertificate).OrderBy(x => x.CommonNameOrFirstDnsName, StrComparer.FqdnReverseStrComparer)) { if (cert.ExpireSpan < Consts.Numbers.MaxCertExpireSpanTargetForUpdate) { if (cert.NotAfter < threshold2) { Con.WriteLine($"Warning: A supplied certificate is expiring or expired. Please check! Cert: '{cert.ToString()}'", flags: LogFlags.Heading); warningCerts++; numWarningTotal++; } } } // IIS のバインディング情報を取得 List <BindItem> bindItems = GetIisCertBindings(currentCertDict); Con.WriteLine(); Con.WriteLine($"The IIS server has {bindItems.Count} SSL bindings."); int index = 0; foreach (var bind in bindItems.OrderBy(x => x.SiteName, StrComparer.IgnoreCaseComparer).ThenBy(x => x.BindingInfo, StrComparer.IgnoreCaseComparer)) { index++; Con.WriteLine($"Binding #{index}/{bindItems.Count}: '{bind.SiteName}' - '{bind.BindingInfo}' (Hostname: '{bind.HostName._NonNull()}'): '{bind.Cert}' (hasPrivateKey: {bind.HasPrivateKey})"); } Con.WriteLine(); // サーバーに存在するすべての証明書バインディングについて検討し、更新すべき証明書をマーク // ただしもともと有効期限が 約 3 年間よりも長い証明書が登録されている場合は、意図的に登録されているオレオレ証明書であるので、更新対象としてマークしない foreach (var bind in bindItems.Where(x => x.Cert.ExpireSpan < Consts.Numbers.MaxCertExpireSpanTargetForUpdate)) { var cert = bind.Cert; bool forceUpdate = false; if (bind.HasPrivateKey == false) { // Current binded certificate has no private key. It should be error. Therefore update this certificate forcefully. forceUpdate = true; numWarningTotal++; Con.WriteLine($"Warning: the current binding '{bind.SiteName}' - '{bind.BindingInfo}': '{bind.Cert}' has no private key. Thus we update this binding forcefully.", flags: LogFlags.Heading); } List <CertificateStore> newCandidateCerts = new List <CertificateStore>(); if (cert.IsMultipleSanCertificate() == false && cert.HostNameList.Any(x => x.HostName._InStri("iis-default-short-ssl-cert")) == false) { // すでに登録されている証明書がシングル証明書の場合、その証明書の DNS 名から、この binding が現在どのホスト名での使用を意図しているものであるのか判別する foreach (var certDns in cert.HostNameList) { if (certDns.Type == CertificateHostnameType.SingleHost) { // aaa.example.org のような普通のホスト名 var candidates = certsList.GetHostnameMatchedCertificatesList(certDns.HostName); candidates._DoForEach(x => newCandidateCerts.Add(x.Item1)); } else if (certDns.Type == CertificateHostnameType.Wildcard) { // *.example.org のようなワイルドカードホスト名 var candidates = certsList.Where(x => x.PrimaryCertificate.HostNameList.Any(x => x.Type == CertificateHostnameType.Wildcard && x.HostName._IsSamei(certDns.HostName))); candidates._DoForEach(x => newCandidateCerts.Add(x)); } } } else { if (bind.BindingInfo._IsSamei("ftp")) { // FTP の場合で、すでに登録されている証明書が SAN 複数 DNS 名保持証明書の場合、判断ができないので更新はしない if (emptySiteAs._IsFilled()) { // EMPTYSITEAS が指定されているので、その指定されている文字列がホスト名であると仮定して処理をしてみます。 var candidates = certsList.GetHostnameMatchedCertificatesList(emptySiteAs); candidates._DoForEach(x => newCandidateCerts.Add(x.Item1)); } } else { // すでに登録されている証明書が SAN 複数 DNS 名保持証明書の場合、証明書から意図している DNS ホスト名は分からない。 // そこで、binding のホスト名を参考にして判断する。 if (bind.HostName._IsFilled()) { var candidates = certsList.GetHostnameMatchedCertificatesList(bind.HostName); candidates._DoForEach(x => newCandidateCerts.Add(x.Item1)); } else { // ホスト名が空の場合はどうすれば良いかわからないので 警告を 出します!! if (emptySiteAs._IsFilled()) { // ホスト名は空ですが、EMPTYSITEAS が指定されているので、その指定されている文字列がホスト名であると仮定して処理をしてみます。 var candidates = certsList.GetHostnameMatchedCertificatesList(emptySiteAs); candidates._DoForEach(x => newCandidateCerts.Add(x.Item1)); } } } } if (newCandidateCerts.Any() == false) { // 警告さん numWarningTotal++; Con.WriteLine($"Warning: We could not determine the best certificate for the binding '{bind.SiteName}' - '{bind.BindingInfo}': '{bind.Cert}'", flags: LogFlags.Heading); } // 更新候補に挙げられた証明書リストの中で最も有効期限が長いものを選択 var bestCert = newCandidateCerts.OrderByDescending(x => x.PrimaryCertificate.NotAfter).ThenBy(x => x.DigestSHA1Str).FirstOrDefault(); if (bestCert != null) { // この最も有効期限が長い候補の証明書と、現在登録されている証明書との有効期限を比較し、候補証明書のほうが発行日が新しいか、または期限が長ければ、更新する if (forceUpdate || bestCert.NotBefore > cert.NotBefore || bestCert.NotAfter > cert.NotAfter || (updateSameCert && bestCert.DigestSHA1Str._IsSameHex(cert.DigestSHA1Str))) { // 更新対象としてマーク bind.NewCert = bestCert; } } } // 更新対象としてマークされた証明書を証明書ストアに書き込む HashSet <string> newCertsHashList = new HashSet <string>(StrComparer.IgnoreCaseComparer); bindItems.Where(x => x.NewCert != null).Select(x => x.NewCert !.DigestSHA1Str !)._DoForEach(x => newCertsHashList.Add(x)); foreach (var hash in newCertsHashList.OrderBy(x => x)) { if (currentCertDict.ContainsKey(hash) == false || currentCertDict[hash].Item2 == false) { var certToWrite = certsList.Where(x => x.DigestSHA1Str._IsSamei(hash)).First(); X509Certificate2 certObj = certToWrite.GetX509Certificate2ForAddToWindowsCertStore(); #pragma warning disable CA1416 // プラットフォームの互換性を検証 certObj.FriendlyName = certToWrite.GenerateFriendlyName(); #pragma warning restore CA1416 // プラットフォームの互換性を検証 this.CertStore.Add(certObj); } } // 念のため証明書ストアにすべての証明書の書き込みが成功したかどうか検査する var certDict2 = GetCurrentMachineCertificateList(); foreach (var hash in newCertsHashList.OrderBy(x => x)) { var certTuple = certDict2[hash]; var cert = certTuple.Item1; if (cert.DigestSHA1Str._IsSameHex(hash) == false) { throw new CoresLibException("Invalid certificate status! hash: " + hash); } if (certTuple.Item2 == false) { throw new CoresLibException("Added certificate has no private key! hash: " + hash); } } int numUpdates = 0; // IIS のバインディング設定を更新する foreach (var site in Svr.Sites.Where(x => x.Name._IsFilled()).OrderBy(x => x.Name)) { foreach (var bind in site.Bindings.Where(x => x.BindingInformation._IsFilled()).OrderBy(x => x.BindingInformation)) { if (bind.Protocol._IsSamei("https")) { var item = bindItems.Where(x => x.NewCert != null && x.SiteName == site.Name && x.BindingInfo == bind.BindingInformation).SingleOrDefault(); if (item != null) { bind.CertificateHash = item.NewCert !.DigestSHA1Str._GetHexBytes(); Con.WriteLine($"UpdatedHttpsCert: Site = '{item.SiteName}', Binding = '{item.BindingInfo}', newCert = '{item.NewCert}', oldCert = '{item.Cert}'"); numUpdates++; } } else if (bind.Protocol._IsSamei("ftp")) { var ftpServer = site.GetChildElement("ftpServer"); if (ftpServer != null) { var security = ftpServer.GetChildElement("security"); if (security != null) { var ssl = security.GetChildElement("ssl"); if (ssl != null) { string hash = (string)ssl.Attributes["serverCertHash"].Value; if (hash._IsFilled()) { hash = hash._NormalizeHexString(); var item = bindItems.Where(x => x.NewCert != null && x.SiteName == site.Name && x.BindingInfo == "ftp").SingleOrDefault(); if (item != null) { ssl["serverCertHash"] = item.NewCert !.DigestSHA1Str; Con.WriteLine($"UpdatedFtpCert: Site = '{item.SiteName}', Binding = '{item.BindingInfo}', newCert = '{item.NewCert}', oldCert = '{item.Cert}'"); numUpdates++; } } } } } } } } if (numUpdates >= 1) { Con.WriteLine($"Total {numUpdates} certs updated.", flags: LogFlags.Heading); CoresLib.Report_SimpleResult = $"Updated {numUpdates} certs"; Svr.CommitChanges(); } else { Con.WriteLine($"No certs updated.", flags: LogFlags.Heading); CoresLib.Report_SimpleResult = $"Updated No certs"; } // 再度 IIS のバインディング情報を取得して証明書の有効期限が怪しいものがあれば表示する // 28 日以内に有効期限が切れる証明書を検出して警告を出す bindItems = GetIisCertBindings(currentCertDict); DateTimeOffset threshold = DtOffsetNow.AddDays(28); foreach (var item in bindItems) { if (item.Cert.ExpireSpan < Consts.Numbers.MaxCertExpireSpanTargetForUpdate) { if (item.Cert.NotAfter < threshold) { Con.WriteLine($"Warning: IIS bound certificate is expiring or expired. Site: '{item.SiteName}', Binding: '{item.BindingInfo}', Cert: '{item.Cert.ToString()}'", flags: LogFlags.Heading); warningCerts++; numWarningTotal++; } } } if (warningCerts >= 1) { Con.WriteLine($"Warning! There are {warningCerts} certificates with warning! Please consider to check if it is Ok!!", flags: LogFlags.Heading); } if (numWarningTotal >= 1) { CoresLib.Report_SimpleResult += $" (Warnings: {numWarningTotal})"; } }