Пример #1
0
        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);
        }
Пример #2
0
        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);
            }
        }
Пример #3
0
        private static void RemoveFile(MikroTikConnection con, string fileName)
        {
            Log.Information($"Удаляем файл '{fileName}' из микротика.");

            var res = con.Command("/file remove")
                      .Attribute("numbers", fileName)
                      .Send();
        }
Пример #4
0
        private static void AddWarning(MikroTikConnection con, string message)
        {
            Log.Information("Оставляем сообщение в логах микротика.");

            con.Command("/log warning")
            .Attribute("message", message)
            .Send();
        }
Пример #5
0
        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);
        }
Пример #6
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);
        }
Пример #7
0
 public void TestQuit()
 {
     using (var con = new MikroTikConnection())
     {
         con.Connect(Login, Password, Address, Port);
         bool success = con.Quit(2000);
         Assert.True(success);
     }
 }
Пример #8
0
        private static string MtGetFileId(MikroTikConnection con, string fileName)
        {
            string fileId = con.Command("/file print")
                            .Query("name", fileName)
                            .Proplist(".id")
                            .ScalarOrDefault();

            return(fileId);
        }
Пример #9
0
        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);
            }
        }
Пример #10
0
        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);
            }
        }
Пример #11
0
        ///// <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);
                }
            }
        }
Пример #12
0
        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}'");
            }
        }
Пример #13
0
        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;
        }
Пример #14
0
        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);
            }
        }
Пример #15
0
        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);
            }
        }
Пример #16
0
        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();
                }
            }
        }
Пример #17
0
        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();
            }
        }
Пример #18
0
        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);
        }
Пример #19
0
        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);
        }
Пример #20
0
        //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;
            }
        }
Пример #21
0
        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.");
                }
            }
        }
Пример #22
0
        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("Сертификат успешно установлен.");
        }