private static void RenameOldCert(MikroTikConnection con, CertificateDto[] cert, LetsEncryptCert newCert) { // Может быть несколько сертификатор с одинаковым common-name. var newCertes = con.Command("/certificate print") .Query("fingerprint", newCert.Thumbprint) .Proplist(".id,name,invalid-after") .ToArray <CertificateDto>(); foreach (var mtNewCert in newCertes) { string newName = "new_" + mtNewCert.Name; while (CertExists(con, newName)) { newName = "new_" + newName; } con.Command("/certificate set") .Attribute("numbers", mtNewCert.Id) .Attribute("name", newName) .Send(); } RenameOldMtCerts(con, cert); }
private static void PingHostAsync(MikroTikConnection con, string host, int intervalSec, CancellationToken cancellationToken) { var listener = con.Command("/ping") .Attribute("address", "SERV.LAN") .Attribute("interval", intervalSec.ToString()) .Proplist("time") .Listen(); while (!listener.IsComplete) { MikroTikResponseFrameDictionary result; try { result = listener.ListenNext(); if (cancellationToken.IsCancellationRequested) { listener.Cancel(); } } catch (MikroApiCommandInterruptedException) { // Операция прервана по запросу Cancel return; } catch (Exception) { // Обрыв соединения. return; } Console.WriteLine(result); } }
private static void RemoveFile(MikroTikConnection con, string fileName) { Log.Information($"Удаляем файл '{fileName}' из микротика."); var res = con.Command("/file remove") .Attribute("numbers", fileName) .Send(); }
private static void AddWarning(MikroTikConnection con, string message) { Log.Information("Оставляем сообщение в логах микротика."); con.Command("/log warning") .Attribute("message", message) .Send(); }
private static bool CertExists(MikroTikConnection con, string mtName) { var newCertes = con.Command("/certificate print") .Query("name", mtName) .Proplist(".id,name") .ToArray <CertificateDto>(); return(newCertes.Length > 0); }
private static bool TryImport(MikroTikConnection con, string fileName) { int filesImported = con.Command("/certificate import") .Attribute("file-name", fileName) .Attribute("passphrase", "") .Scalar <int>("files-imported"); return(filesImported > 0); }
public void TestQuit() { using (var con = new MikroTikConnection()) { con.Connect(Login, Password, Address, Port); bool success = con.Quit(2000); Assert.True(success); } }
private static string MtGetFileId(MikroTikConnection con, string fileName) { string fileId = con.Command("/file print") .Query("name", fileName) .Proplist(".id") .ScalarOrDefault(); return(fileId); }
public async Task TestQuitAsync() { using (var con = new MikroTikConnection()) { await con.ConnectAsync(Login, Password, Address, Port); bool success = await con.QuitAsync(2000); Assert.True(success); } }
public async Task TestCancelListenersAsync() { using (var con = new MikroTikConnection()) { await con.ConnectAsync(Login, Password, Address, Port); var task = con.CancelListenersAsync(); bool success = task.Wait(3000); Assert.True(success); } }
///// <summary> ///// Находит адрес в тойже подсети. ///// </summary> //private IPAddress GetLocalIPAddress() //{ // var mask24 = IPAddress.Parse("255.255.255.0"); // var host = Dns.GetHostEntry(Dns.GetHostName()); // foreach (IPAddress ip in host.AddressList) // { // if (ip.AddressFamily == AddressFamily.InterNetwork) // { // if (ip.IsInSameSubnet(Config.MikroTikAddress, mask24)) // return ip; // } // } // Log.Error($"Не найден сетевой интерфейс в подсети {Config.MikroTikAddress.GetNetworkAddress(mask24)}/24."); // throw new LetsEncryptMikroTikException($"Не найден сетевой интерфейс в подсети {Config.MikroTikAddress.GetNetworkAddress(mask24)}/24."); //} /// <summary> /// /// </summary> /// <exception cref="LetsEncryptMikroTikException"/> private async Task MainAsync() { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); /* Русская кодировка */ var encoding = Encoding.GetEncoding("windows-1251"); Log.Information("Подключение к микротику."); using (var con = new MikroTikConnection(encoding)) { try { //if (Config.UseSsl) { //con.ConnectSsl(Config.MikroTikAddress, Config.MikroTikPort, Config.MikroTikLogin, Config.MikroTikPassword); } //else { con.Connect(Config.MikroTikAddress, Config.MikroTikPort, Config.MikroTikLogin, Config.MikroTikPassword); } } catch (MikroTikTrapException ex) when(ex.Message == "std failure: not allowed (9)") { string message = $"Не удалось авторизоваться пользователю '{Config.MikroTikLogin}'. Проверьте права пользователя на доступ к api."; Log.Error(message); throw new LetsEncryptMikroTikException(message, ex); } catch (Exception ex) { throw new LetsEncryptMikroTikException("Не удалось подключиться к микротику", ex); } try { await UpdateCertificateAsync(con).ConfigureAwait(false); } catch (MikroTikTrapException ex) when(ex.Message == "not enough permissions (9)") { string message = $"У пользователя '{Config.MikroTikLogin}' недостаточно прав в микротике. Требуются права: api, read, write, ftp."; Log.Error(message); throw new LetsEncryptMikroTikException(message, ex); } finally { Log.Information("Отключение от микротика."); con.Quit(2000); } } }
private void CheckWanInterface(MikroTikConnection con) { Log.Information($"Проверяем что в микротике есть интерфейс '{Config.WanIface}'."); string ifaceId = con.Command("/interface print") .Query("name", Config.WanIface) .Proplist(".id") .ScalarOrDefault <string>(); if (ifaceId == null) { throw new LetsEncryptMikroTikException($"В Микротике не найден интерфейс '{Config.WanIface}'"); } }
public Acme(MikroTikConnection connection, ConfigClass config) { _mtCon = connection; if (string.IsNullOrEmpty(config.DomainName)) { throw new ArgumentOutOfRangeException(nameof(config), "Имя домена не может быть пустым"); } _domainName = config.DomainName; _thisMachineIp = config.ThisMachineIp ?? throw new ArgumentOutOfRangeException(nameof(config), "ThisMachineIp не может быть пустым"); _letsEncryptAddress = config.LetsEncryptAddress ?? throw new ArgumentOutOfRangeException(nameof(config), "LetsEncryptAddress не может быть пустым"); _config = config; }
public async Task TestCancelListenerAsync() { using (var con = new MikroTikConnection()) { await con.ConnectAsync(Login, Password, Address, Port); var listener = con.Command("/ping") .Attribute("address", "SERV.LAN") .Proplist("time") .Listen(); var task = listener.CancelAsync(); bool success = task.Wait(3000); Assert.True(success); } }
private async Task UploadFtpAsync(MikroTikConnection con, string certFileName, string certPrivKeyFileName, LetsEncryptCert cert) { // Включить FTP в микротике. EnableFtp(con, out int ftpPort, out bool enabledChanged, out bool allowedChanged, out string allowedAddresses); try { // Загружаем сертификат в микротик по FTP. await UploadFileAsync(con, ftpPort, cert.CertPem, certFileName).ConfigureAwait(false); // Загружаем закрытый ключ сертификата в микротик по FTP. await UploadFileAsync(con, ftpPort, cert.KeyPem, certPrivKeyFileName).ConfigureAwait(false); } finally { RestoreFtp(con, enabledChanged, allowedChanged, allowedAddresses); } }
private static void RenameOldMtCerts(MikroTikConnection con, CertificateDto[] cert) { foreach (CertificateDto c in cert) { if (!c.Name.StartsWith("old_", StringComparison.Ordinal)) { string newOldName = "old_" + c.Name; while (CertExists(con, newOldName)) { newOldName = "old_" + newOldName; } con.Command("/certificate set") .Attribute("numbers", c.Id) .Attribute("name", newOldName) .Send(); } } }
private void RestoreFtp(MikroTikConnection con, bool enabledChanged, bool allowedChanged, string allowedAddresses) { if (enabledChanged || allowedChanged) { var com = con.Command("/ip service set") .Attribute("numbers", "ftp"); if (enabledChanged) { Log.Information("Выключаем FTP в микротике."); com.Attribute("disabled", "true"); } if (allowedChanged) { Log.Information($"Убираем IP {Config.ThisMachineIp} из разрешённых для FTP в микротике."); com.Attribute("address", allowedAddresses); } com.Send(); } }
private static bool TryGetExpiredAfter(MikroTikConnection con, string commonName, out TimeSpan expires, out CertificateDto[] certes) { Log.Information($"Запрашиваем у микротика информацию о сертификате с Common-Name: '{commonName}'."); // Может быть несколько сертификатор с одинаковым common-name. certes = con.Command("/certificate print") .Query("common-name", commonName) .Proplist(".id,name,invalid-after") .ToArray <CertificateDto>(); DateTime?invalidAfter = certes.Select(x => new { Cert = x, InvalidAfter = (DateTime?)DateTime.Parse(x.InvalidAfter, CultureInfo.InvariantCulture) }) .DefaultIfEmpty() .Min(x => x?.InvalidAfter); if (invalidAfter != null) { expires = invalidAfter.Value - DateTime.Now; return(true); } expires = default; return(false); }
private static async Task Main() { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // Support localized comments. MikroTikConnection.DefaultEncoding = Encoding.GetEncoding("windows-1251"); // RUS using (var con = new MikroTikConnection(Encoding.GetEncoding("windows-1251"))) { con.Connect("api_dbg", "debug_password", "10.0.0.1"); var leases = con.Command("/ip dhcp-server lease print") .Query("disabled", "false") // filter .Proplist("address", "mac-address", "host-name", "status") .Send(); con.Quit(1000); } //// Отправляет запрос без получения результата. //var listener = con.Command("/ping") // .Attribute("address", "SERV.LAN") // .Attribute("interval", "1") // .Attribute("count", "4") // .Proplist("time") // .Listen(); //// сервер будет присылать результат каждую секунду. //while (!listener.IsComplete) //{ // MikroTikResponseFrame result; // try // { // result = listener.ListenNext(); // } // catch (MikroTikDoneException) // { // break; // } // Console.WriteLine(result); // listener.Cancel(true); //} //var logListener = con.Command("/log listen") // .Listen(); //while (!logListener.IsComplete) //{ // try // { // logListener.ListenNext(); // } // catch (TimeoutException) // { // } // var entry = logListener.ListenNext(); // Console.WriteLine(entry); //} //// Вытащить активные сессии юзера. //var activeSessionsResult = con.Command("/ip hotspot active print") // .Proplist(".id") // .Query("user", "2515") // .Send(); //string[] activeSessions = activeSessionsResult.ScalarArray(".id"); //Thread.Sleep(-1); //resultPrint.Scalar(""); //resultPrint.ScalarList(); //resultPrint.ScalarOrDefault(); //var sw = Stopwatch.StartNew(); //for (int i = 0; i < 500_000; i++) //{ // resultPrint.ToArray(new { }); // resultPrint.ScalarList<int>(); // var row = resultPrint.ToList<InterfaceDto>(); //} //sw.Stop(); //Trace.WriteLine(sw.Elapsed); //Console.WriteLine("OK"); //Thread.Sleep(-1); // Команда выполняется 20 сек. //var task = con.Command("/delay") // .Attribute("delay-time", "20") // .Send(); // Tell router we are done. //con.Quit(1000); }
//private void RemoveExisted(MikroTikConnection con, string commonName) //{ // Log.Information($"Запрашиваем у микротика сертификат с Common-Name: '{commonName}'."); // // Может быть несколько сертификатор с одинаковым common-name. // string[] invalidAfterRaw = con.Command("/certificate print") // .Query("common-name", commonName) // .Proplist(".id") // .ScalarArray<string>(); // if (invalidAfterRaw.Length > 0) // { // Log.Information($"Удаляем существующий сертификат из микротика."); // foreach (string id in invalidAfterRaw) // { // con.Command("/certificate remove") // .Attribute(".id", id) // .Send(); // } // } //} private void EnableFtp(MikroTikConnection con, out int ftpPort, out bool enabledChanged, out bool allowedChanged, out string allowedAddresses) { var ftpService = con.Command("/ip service print") .Query("name", "ftp") .Proplist("disabled,port,address") .Single <IpService>(); ftpPort = ftpService.Port; allowedAddresses = ftpService.Address; bool connectionAllowed = true; if (ftpService.Addresses != null) { connectionAllowed = ftpService.Addresses.Any(x => { if (string.IsNullOrEmpty(x)) { return(true); } string[] parts = x.Split('/'); var ipAddress = IPAddress.Parse(parts[0]); int netMask = int.Parse(parts[1], CultureInfo.InvariantCulture); return(IsInRange(Config.ThisMachineIp, ipAddress, netMask)); }); } if (ftpService.Disabled || !connectionAllowed) { if (ftpService.Disabled) { enabledChanged = true; Log.Warning("В микротике выключен FTP. Временно включаем его."); } else { enabledChanged = false; } string address = ftpService.Address; if (!connectionAllowed) { allowedChanged = true; Log.Warning($"В микротике доступ к FTP с адреса {Config.ThisMachineIp} не разрешён. Временно разрешаем."); address += $",{Config.ThisMachineIp}/32"; } else { allowedChanged = true; } con.Command("/ip service set") .Attribute("numbers", "ftp") .Attribute("disabled", "false") .Attribute("address", address) .Send(); } else { allowedChanged = false; enabledChanged = false; } }
private async Task UploadFileAsync(MikroTikConnection con, int ftpPort, string fileContent, string fileName) { var request = (FtpWebRequest)WebRequest.Create(new Uri($"ftp://{Config.MikroTikAddress}:{ftpPort}/{fileName}")); request.Method = WebRequestMethods.Ftp.UploadFile; // Перезапишет существующий. request.Proxy = null; request.UseBinary = true; request.EnableSsl = false; request.UsePassive = true; request.Credentials = new NetworkCredential(Config.FtpLogin, Config.FtpPassword); Log.Information($"Отправляем файл '{fileName}' в микротик по FTP с заменой файла если такой существует."); using (var fileStream = new MemoryStream(Encoding.ASCII.GetBytes(fileContent))) { Stream stream; try { stream = request.GetRequestStream(); } catch (WebException ex) { if (ex.Response is FtpWebResponse response) { if (response.StatusCode == FtpStatusCode.NotLoggedIn) { throw new WebException("Не удалось авторизоваться на FTP (не верный пароль?).", ex); } } throw new WebException("Ошибка доступа к FTP микротика.", ex); } using (stream) { fileStream.CopyTo(stream); fileStream.Flush(); } } // Файл в микротике будет доступен через небольшой интервал. await Task.Delay(200).ConfigureAwait(false); Log.Information("Проверяем что файл появился в микротике."); string fileId = MtGetFileId(con, fileName); // Файл может появиться не сразу. if (fileId == null) { Log.Information("Файл в микротике ещё не появился. Делаем паузу на 1 сек."); await Task.Delay(1000).ConfigureAwait(false); Log.Information("Ещё раз проверяем файл в микротике."); fileId = MtGetFileId(con, fileName); if (fileId == null) { Log.Error("Файл в микротике не появился. Прекращаем попытки."); throw new LetsEncryptMikroTikException("Не удалось загрузить файл через FTP."); } } }
private async Task UpdateCertificateAsync(MikroTikConnection con) { // Валидация имени WAN-интерфейса. CheckWanInterface(con); bool mtHasOldCert = TryGetExpiredAfter(con, Config.DomainName, out TimeSpan expires, out CertificateDto[] existedCertes); if (mtHasOldCert) { int daysLeft = (int)expires.TotalDays; if (daysLeft > Config.ReplaceCertOnDaysLessThan && !Config.Force) { Log.Information($"В микротике есть ещё актуальный сертификат который истекает через {daysLeft} {Days(daysLeft)}. Завершаем работу."); return; } else { Log.Information($"В микротике уже есть сертификат с таким Common-Name который истекает через {daysLeft} {Days(daysLeft)}."); } } LetsEncryptCert newCert; string certFileName = $"{Config.DomainName}-cert.pem"; string certPrivKeyFileName = $"{Config.DomainName}-key.pem"; if (Config.SaveFile) { string certFilePath = GetFilePath(certFileName); string keyFilePath = GetFilePath(certPrivKeyFileName); CreateDirectory(certFilePath); CreateDirectory(keyFilePath); try { // Перед загрузкой сертификата убедимся что мы сможем его сохранить. Log.Information($"Создаём файлы в папке '{FolderName}'."); using (StreamWriter? certStream = File.CreateText(certFilePath)) { using (StreamWriter? keyStream = File.CreateText(keyFilePath)) { var acme = new Acme(con, Config); // Загрузить сертификат от Let's Encrypt. newCert = await acme.GetCertAsync().ConfigureAwait(false); certStream.Write(newCert.CertPem); keyStream.Write(newCert.KeyPem); } } } catch { Log.Information("Удаляем файлы в следствии ошибки."); if (File.Exists(certFilePath)) { File.Delete(certFilePath); } if (File.Exists(keyFilePath)) { File.Delete(keyFilePath); } throw; } } else { var acme = new Acme(con, Config); // Загрузить сертификат от Let's Encrypt. newCert = await acme.GetCertAsync().ConfigureAwait(false); } // Загружаем сертификат и приватный ключ в микротик по FTP. await UploadFtpAsync(con, certFileName, certPrivKeyFileName, newCert).ConfigureAwait(false); Log.Information($"Импортируем сертификат в микротик из файла '{certFileName}'"); if (!TryImport(con, certFileName)) { Log.Error($"Не удалось импортировать сертификат в микротик."); throw new LetsEncryptMikroTikException("Не удалось импортировать сертификат в микротик."); } Log.Information($"Импортируем закрытый ключ в микротик из файла '{certPrivKeyFileName}'"); if (!TryImport(con, certPrivKeyFileName)) { Log.Error($"Не удалось импортировать закрытый ключ в микротик."); throw new LetsEncryptMikroTikException("Не удалось импортировать закрытый ключ в микротик."); } if (mtHasOldCert && expires.TotalDays > 1) { int daysValid = (int)expires.TotalDays; // Оставляем сообщение в логах микротика. AddWarning(con, $"Был добавлен новый сертификат '{Config.DomainName}'. Старый сертификат будет актуален ещё {daysValid} {Days(daysValid)}"); } else { // Оставляем сообщение в логах микротика. AddWarning(con, $"Был добавлен новый сертификат '{Config.DomainName}'"); } // Удаляем файлы из микротика. RemoveFile(con, certFileName); RemoveFile(con, certPrivKeyFileName); // Переименовать старый сертификат. RenameOldCert(con, existedCertes, newCert); Log.Information("Сертификат успешно установлен."); }