diff options
Diffstat (limited to 'global-functions')
-rw-r--r-- | global-functions | 1153 |
1 files changed, 0 insertions, 1153 deletions
diff --git a/global-functions b/global-functions deleted file mode 100644 index 7de3d72..0000000 --- a/global-functions +++ /dev/null @@ -1,1153 +0,0 @@ -#!rsc by RouterOS -# RouterOS script: global-functions -# Copyright (c) 2013-2021 Christian Hesse <mail@eworm.de> -# Michael Gisbers <michael@gisbers.de> -# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md -# -# global functions -# https://git.eworm.de/cgit/routeros-scripts/about/ - -# expected configuration version -:global ExpectedConfigVersion 47; - -# global variables not to be changed by user -:global GlobalFunctionsReady false; -:global Identity [ / system identity get name ]; - -# global functions -:global CertificateAvailable; -:global CertificateDownload; -:global CertificateNameByCN; -:global CharacterReplace; -:global CleanFilePath; -:global DefaultRouteIsReachable; -:global DeviceInfo; -:global DNSIsResolving; -:global DownloadPackage; -:global FlushEmailQueue; -:global FlushTelegramQueue; -:global GetMacVendor; -:global GetRandom20CharHex; -:global GetRandomNumber; -:global IfThenElse; -:global IPCalc; -:global LogPrintExit; -:global LogPrintExit2; -:global MkDir; -:global ParseKeyValueStore; -:global RandomDelay; -:global RequiredRouterOS; -:global ScriptFromTerminal; -:global ScriptInstallUpdate; -:global ScriptLock; -:global SendEMail; -:global SendNotification; -:global SendTelegram; -:global SymbolByUnicodeName; -:global SymbolForNotification; -:global TimeIsSync; -:global UrlEncode; -:global ValidateSyntax; -:global VersionToNum; -:global WaitDefaultRouteReachable; -:global WaitDNSResolving; -:global WaitForFile; -:global WaitFullyConnected; -:global WaitTimeSync; - -# check and download required certificate -:set CertificateAvailable do={ - :local CommonName [ :tostr $1 ]; - - :global CertificateDownload; - :global LogPrintExit2; - :global ParseKeyValueStore; - :global RequiredRouterOS; - - :if ([ / system resource get free-hdd-space ] < 8388608 && \ - [ / certificate settings get crl-download ] = true && \ - [ / certificate settings get crl-store ] = "system") do={ - $LogPrintExit2 warning $0 ("This system has low free flash space but " . \ - "is configured to download certificate CRLs to system!") false; - } - - :if ([ :len [ / certificate find where common-name=$CommonName ] ] = 0) do={ - $LogPrintExit2 info $0 ("Certificate with CommonName \"" . $CommonName . "\" not available.") false; - :if ([ $CertificateDownload $CommonName ] = false) do={ - :return false; - } - } - - :if ([ $RequiredRouterOS $0 "6.47" ] = false) do={ - :return true; - } - - :local CertVal [ / certificate get [ find where common-name=$CommonName ] ]; - :do { - :if ([ :len [ / certificate find where skid=($CertVal->"akid") ] ] = 0) do={ - $LogPrintExit2 info $0 ("Certificate chain for \"" . $CommonName . \ - "\" is incomplete, missing \"" . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") . "\".") false; - :if ([ $CertificateDownload $CommonName ] = false) do={ - :return false; - } - } - :set CertVal [ / certificate get [ find where skid=($CertVal->"akid") ] ]; - } while=(($CertVal->"akid") != "" && ($CertVal->"akid") != ($CertVal->"skid")); - :return true; -} - -# download and import certificate -:set CertificateDownload do={ - :local CommonName [ :tostr $1 ]; - - :global ScriptUpdatesBaseUrl; - :global ScriptUpdatesUrlSuffix; - - :global CertificateNameByCN; - :global LogPrintExit2; - :global UrlEncode; - :global WaitForFile; - - $LogPrintExit2 info $0 ("Downloading and importing certificate with " . \ - "CommonName \"" . $CommonName . "\".") false; - :do { - :local LocalFileName ($CommonName . ".pem"); - :local UrlFileName ([ $UrlEncode $CommonName ] . ".pem"); - / tool fetch check-certificate=yes-without-crl \ - ($ScriptUpdatesBaseUrl . "certs/" . \ - $UrlFileName . $ScriptUpdatesUrlSuffix) \ - dst-path=$LocalFileName as-value; - $WaitForFile $LocalFileName; - / certificate import file-name=$LocalFileName passphrase=""; - / file remove $LocalFileName; - - :foreach Cert in=[ / certificate find where name~("^" . $LocalFileName . "_[0-9]+\$") ] do={ - $CertificateNameByCN [ / certificate get $Cert common-name ]; - } - } on-error={ - $LogPrintExit2 warning $0 ("Failed importing certificate with " . \ - "CommonName \"" . $CommonName . "\"!") false; - :return false; - } - :return true; -} - -# name a certificate by its common-name -:set CertificateNameByCN do={ - :local CommonName [ :tostr $1 ]; - - :global CharacterReplace; - - :local Cert [ / certificate find where common-name=$CommonName ]; - / certificate set $Cert \ - name=[ $CharacterReplace [ $CharacterReplace [ $CharacterReplace $CommonName "'" "-" ] " " "-" ] "---" "-" ]; -} - -# character replace -:set CharacterReplace do={ - :local String [ :tostr $1 ]; - :local ReplaceFrom [ :tostr $2 ]; - :local ReplaceWith [ :tostr $3 ]; - :local Return ""; - - :if ($ReplaceFrom = "") do={ - :return $String; - } - - :while ([ :typeof [ :find $String $ReplaceFrom ] ] != "nil") do={ - :local Pos [ :find $String $ReplaceFrom ]; - :set Return ($Return . [ :pick $String 0 $Pos ] . $ReplaceWith); - :set String [ :pick $String ($Pos + [ :len $ReplaceFrom ]) [ :len $String ] ]; - } - - :return ($Return . $String); -} - -# clean file path -:set CleanFilePath do={ - :local Path [ :tostr $1 ]; - - :global CharacterReplace; - - :while ($Path ~ "//") do={ - :set $Path [ $CharacterReplace $Path "//" "/" ]; - } - :if ([ :pick $Path 0 ] = "/") do={ - :set Path [ :pick $Path 1 [ :len $Path ] ]; - } - :if ([ :pick $Path ([ :len $Path ] - 1) ] = "/") do={ - :set Path [ :pick $Path 0 ([ :len $Path ] - 1) ]; - } - - :return $Path; -} - -# default route is reachable -:set DefaultRouteIsReachable do={ - :if ([ :len [ / ip route find where dst-address=0.0.0.0/0 active !blackhole !routing-mark !unreachable ] ] > 0) do={ - :return true; - } - :return false; -} - -# get readable device info -:set DeviceInfo do={ - :global ExpectedConfigVersion; - :global GlobalConfigVersion; - :global Identity; - - :global IfThenElse; - - :local Resource [ / system resource get ]; - :local RouterBoard [ / system routerboard get ]; - :local Update [ / system package update get ]; - - :return ( \ - "Hostname: " . $Identity . \ - "\nBoard name: " . $Resource->"board-name" . \ - "\nArchitecture: " . $Resource->"architecture-name" . \ - [ $IfThenElse ($RouterBoard->"routerboard" = true) \ - ("\nModel: " . $RouterBoard->"model" . \ - [ $IfThenElse ([ :len ($RouterBoard->"revision") ] > 0) \ - (" " . $RouterBoard->"revision") ] . \ - "\nSerial number: " . $RouterBoard->"serial-number") ] . \ - "\nRouterOS:" . \ - "\n Channel: " . $Update->"channel" . \ - "\n Installed: " . $Update->"installed-version" . \ - [ $IfThenElse ([ :typeof ($Update->"latest-version") ] != "nothing" && \ - $Update->"installed-version" != $Update->"latest-version") \ - ("\n Available: " . $Update->"latest-version") ] . \ - "\nRouterOS-Scripts:" . \ - "\n Current: " . $GlobalConfigVersion . \ - [ $IfThenElse ($GlobalConfigVersion != $ExpectedConfigVersion) \ - ("\n Expected: " . $ExpectedConfigVersion) ]); -} - -# check if DNS is resolving -:set DNSIsResolving do={ - :global CharacterReplace; - - :do { - :resolve "low-ttl.eworm.de"; - :return true; - } on-error={ - :return false; - } -} - -# download package from upgrade server -:set DownloadPackage do={ - :local PkgName [ :tostr $1 ]; - :local PkgVer [ :tostr $2 ]; - :local PkgArch [ :tostr $3 ]; - :local PkgDir [ :tostr $4 ]; - - :global CertificateAvailable; - :global CleanFilePath; - :global LogPrintExit2; - :global WaitForFile; - - :if ([ :len $PkgName ] = 0) do={ :return false; } - :if ([ :len $PkgVer ] = 0) do={ :set PkgVer [ / system package update get installed-version ]; } - :if ([ :len $PkgArch ] = 0) do={ :set PkgArch [ / system resource get architecture-name ]; } - - :local PkgFile ($PkgName . "-" . $PkgVer . "-" . $PkgArch . ".npk"); - :if ($PkgArch = "x86_64" || $PkgName ~ "^routeros-") do={ - :set PkgFile ($PkgName . "-" . $PkgVer . ".npk"); - } - :local PkgDest [ $CleanFilePath ($PkgDir . "/" . $PkgFile) ]; - - :if ([ :len [ / file find where name=$PkgDest type="package" ] ] > 0) do={ - $LogPrintExit2 info $0 ("Package file alreasy exists.") false; - :return true; - } - - :if ([ $CertificateAvailable "R3" ] = false) do={ - $LogPrintExit2 error $0 ("Downloading required certificate failed.") true; - } - - :local Retry 3; - :while ($Retry > 0) do={ - :do { - / tool fetch check-certificate=yes-without-crl \ - ("https://upgrade.mikrotik.com/routeros/" . $PkgVer . "/" . $PkgFile) \ - dst-path=$PkgDest; - $WaitForFile $PkgDest; - - :if ([ / file get [ find where name=$PkgDest ] type ] = "package") do={ - :return true; - } - } on-error={ - $LogPrintExit2 debug $0 ("Downloading package failed.") false; - } - - / file remove [ find where name=$PkgDest ]; - :set Retry ($Retry - 1); - } - - :return false; -} - -# flush e-mail queue -:set FlushEmailQueue do={ - :global EmailQueue; - - :global LogPrintExit2; - - :local AllDone true; - :local QueueLen [ :len $EmailQueue ]; - - :if ([ :len [ / system scheduler find where name="FlushEmailQueue" ] ] > 0 && $QueueLen = 0) do={ - $LogPrintExit2 warning $0 ("Flushing E-Mail messages from scheduler, but queue is empty.") false; - } - - / system scheduler set interval=1m [ find where name="FlushEmailQueue" interval=1s ]; - - :foreach Id,Message in=$EmailQueue do={ - :if ([ :typeof $Message ] = "array" ) do={ - / tool e-mail send to=($Message->"to") cc=($Message->"cc") \ - subject=($Message->"subject") body=($Message->"body"); - :local Wait true; - :do { - :delay 1s; - :local Status [ / tool e-mail get last-status ]; - :if ($Status = "succeeded") do={ - :set ($EmailQueue->$Id); - :set Wait false; - } - :if ($Status = "failed") do={ - :set AllDone false; - :set Wait false; - } - } while=($Wait = true); - } - } - - :if ($AllDone = true && $QueueLen = [ :len $EmailQueue ]) do={ - / system scheduler remove [ find where name="FlushEmailQueue" ]; - :set EmailQueue; - } -} - -# flush telegram queue -:set FlushTelegramQueue do={ - :global TelegramQueue; - :global TelegramTokenId; - - :global LogPrintExit2; - - :local AllDone true; - :local QueueLen [ :len $TelegramQueue ]; - - :if ([ :len [ / system scheduler find where name="FlushTelegramQueue" ] ] > 0 && $QueueLen = 0) do={ - $LogPrintExit2 warning $0 ("Flushing Telegram messages from scheduler, but queue is empty.") false; - } - - :foreach Id,Message in=$TelegramQueue do={ - :if ([ :typeof $Message ] = "array" ) do={ - :do { - / tool fetch check-certificate=yes-without-crl output=none http-method=post \ - ("https://api.telegram.org/bot" . $TelegramTokenId . "/sendMessage") \ - http-data=("chat_id=" . ($Message->"chatid") . \ - "&disable_notification=" . ($Message->"silent") . \ - "&disable_web_page_preview=true&parse_mode=" . ($Message->"parsemode") . \ - "&text=" . ($Message->"text")) as-value; - :set ($TelegramQueue->$Id); - } on-error={ - $LogPrintExit2 debug $0 ("Sending queued Telegram message failed.") false; - :set AllDone false; - } - } - } - - :if ($AllDone = true && $QueueLen = [ :len $TelegramQueue ]) do={ - / system scheduler remove [ find where name="FlushTelegramQueue" ]; - :set TelegramQueue; - } -} - -# get MAC vendor -:set GetMacVendor do={ - :local Mac [ :tostr $1 ]; - - :global CertificateAvailable; - :global LogPrintExit2; - - :do { - :if ([ $CertificateAvailable "Cloudflare Inc ECC CA-3" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true; - } - :local Vendor ([ / tool fetch check-certificate=yes-without-crl \ - ("https://api.macvendors.com/" . [ :pick $Mac 0 8 ]) output=user as-value ]->"data"); - :return $Vendor; - } on-error={ - :do { - / tool fetch check-certificate=yes-without-crl ("https://api.macvendors.com/") \ - output=none as-value; - $LogPrintExit2 debug $0 ("The mac vendor is not known in database.") false; - } on-error={ - $LogPrintExit2 warning $0 ("Failed getting mac vendor.") false; - } - :return "unknown vendor"; - } -} - -# generate random 20 chars hex (0-9 and a-f) -:set GetRandom20CharHex do={ - :local Random ([ / certificate scep-server otp generate minutes-valid=0 as-value ]->"password"); - / certificate scep-server otp remove [ find where password=$Random ]; - :return $Random; -} - -# generate random number -:set GetRandomNumber do={ - :local Max 4294967295; - :if ([ :typeof $1 ] != "nothing" ) do={ - :set Max ([ :tonum $1 ] + 1); - } - - :global GetRandom20CharHex; - - :local Num; - :local 40CharHex ([ $GetRandom20CharHex ] . [ $GetRandom20CharHex ]); - - :for I from=0 to=39 do={ - :local Char [ :pick $40CharHex $I ]; - :if ($Char~"[0-9]") do={ - :set Num ($Num . $Char); - } - } - - :return ([ :tonum [ :pick $Num 0 18 ] ] % $Max); -} - -# mimic conditional/ternary operator (condition ? consequent : alternative) -:set IfThenElse do={ - :if ([ :tostr $1 ] = "true" || [ :tobool $1 ] = true) do={ - :return $2; - } - :return $3; -} - -# calculate and print netmask, network, min host, max host and broadcast -:set IPCalc do={ - :local Input [ :tostr $1 ]; - :local Address [ :toip [ :pick $Input 0 [ :find $Input "/" ] ] ]; - :local Bits [ :tonum [ :pick $Input ([ :find $Input "/" ] + 1) [ :len $Input ] ] ]; - :local Mask ((255.255.255.255 << (32 - $Bits)) & 255.255.255.255); - - :local Return { - "address"=$Address; - "netmask"=$Mask; - "networkaddress"=($Address & $Mask); - "networkbits"=$Bits; - "network"=(($Address & $Mask) . "/" . $Bits); - "hostmin"=(($Address & $Mask) | 0.0.0.1); - "hostmax"=(($Address | ~$Mask) ^ 0.0.0.1); - "broadcast"=($Address | ~$Mask); - } - - :put ( \ - "Address: " . $Return->"address" . "\n\r" . \ - "Netmask: " . $Return->"netmask" . "\n\r" . \ - "Network: " . $Return->"network" . "\n\r" . \ - "HostMin: " . $Return->"hostmin" . "\n\r" . \ - "HostMax: " . $Return->"hostmax" . "\n\r" . \ - "Broadcast: " . $Return->"broadcast"); - - :return $Return; -} - -# deprecated compatibility wrapper -:set LogPrintExit do={ - :global LogPrintExit2; - - $LogPrintExit2 $1 "unknown" $2 $3; -} - -# log and print with same text, optionally exit -:set LogPrintExit2 do={ - :local Severity [ :tostr $1 ]; - :local Name [ :tostr $2 ]; - :local Message [ :tostr $3 ]; - :local Exit [ :tostr $4 ]; - - :global PrintDebug; - - :local PrintSeverity do={ - :global TerminalColorOutput; - - :if ($TerminalColorOutput != true) do={ - :return $1; - } - - :local Color { debug=96; info=97; warning=93; error=91 }; - :return ("\1B[" . $Color->$1 . "m" . $1 . "\1B[0m"); - } - - :local Log ($Name . ": " . $Message); - :if ($Severity ~ "^(debug|error|info)\$") do={ - :if ($Severity = "debug") do={ :log debug $Log; } - :if ($Severity = "error") do={ :log error $Log; } - :if ($Severity = "info" ) do={ :log info $Log; } - } else={ - :log warning $Log; - :set Severity "warning"; - } - - :if ($Severity != "debug" || $PrintDebug = true) do={ - :if ($Exit = "true") do={ - :error ([ $PrintSeverity $Severity ] . ": " . $Message); - } else={ - :put ([ $PrintSeverity $Severity ] . ": " . $Message); - } - } -} - -# create directory -:set MkDir do={ - :local Dir [ :tostr $1 ]; - - :global CleanFilePath; - :global WaitForFile; - - :set Dir [ $CleanFilePath $Dir ]; - - :if ([ :len [ / file find where name=$Dir type="directory" ] ] = 1) do={ - :return true; - } - - :local Return true; - :local WwwVal [ / ip service get www ]; - / ip service set www address=127.0.0.1/32 disabled=no port=80; - :do { - / tool fetch http://127.0.0.1/ dst-path=($Dir . "/tmp") as-value; - $WaitForFile ($Dir . "/tmp"); - / file remove ($Dir . "/tmp"); - } on-error={ - :set Return false; - } - / ip service set www address=($WwwVal->"address") \ - disabled=($WwwVal->"disabled") port=($WwwVal->"port"); - :return $Return; -} - -# parse key value store -:set ParseKeyValueStore do={ - :local Source $1; - :if ([ :typeof $Source ] != "array") do={ - :set Source [ :tostr $1 ]; - } - :local Result [ :toarray "" ]; - :foreach KeyValue in=[ :toarray $Source ] do={ - :if ([ :find $KeyValue "=" ]) do={ - :set ($Result->[ :pick $KeyValue 0 [ :find $KeyValue "=" ] ]) \ - [ :pick $KeyValue ([ :find $KeyValue "=" ] + 1) [ :len $KeyValue ] ]; - } else={ - :set ($Result->$KeyValue) true; - } - } - :return $Result; -} - -# delay a random amount of seconds -:set RandomDelay do={ - :global GetRandomNumber; - - :delay ([ $GetRandomNumber $1 ] . "s"); -} - -# check for required RouterOS version -:set RequiredRouterOS do={ - :local Caller [ :tostr $1 ]; - :local Required [ :tostr $2 ]; - - :global IfThenElse; - :global LogPrintExit2; - :global VersionToNum; - - :if ([ $VersionToNum $Required ] > [ $VersionToNum [ / system package update get installed-version ] ]) do={ - $LogPrintExit2 warning $0 ("This " . [ $IfThenElse ([ :pick $Caller 0 ] = "\$") "function" "script" ] . \ - " '" . $Caller . "' (at least specific functionality) requires RouterOS " . $Required . ". Please update!") false; - :return false; - } - :return true; -} - -# check if script is run from terminal -:set ScriptFromTerminal do={ - :local Script [ :tostr $1 ]; - - :global LogPrintExit2; - - :foreach Job in=[ / system script job find where script=$Script ] do={ - :set Job [ / system script job get $Job ]; - :while ([ :typeof ($Job->"parent") ] = "id") do={ - :set Job [ / system script job get [ find where .id=($Job->"parent") ] ]; - } - :if (($Job->"type") = "login") do={ - $LogPrintExit2 debug $0 ("Script " . $Script . " started from terminal.") false; - :return true; - } - } - $LogPrintExit2 debug $0 ("Script " . $Script . " NOT started from terminal.") false; - - :return false; -} - -# install new scripts, update existing scripts -:set ScriptInstallUpdate do={ - :local Scripts [ :toarray $1 ]; - - :global ExpectedConfigVersion; - :global GlobalConfigVersion; - :global Identity; - :global IDonate; - :global NotificationsWithSymbols; - :global ScriptUpdatesBaseUrl; - :global ScriptUpdatesFetch; - :global ScriptUpdatesUrlSuffix; - :global SentConfigChangesNotification; - - :global CertificateAvailable; - :global IfThenElse; - :global LogPrintExit2; - :global ParseKeyValueStore; - :global ScriptInstallUpdate; - :global SendNotification; - :global SymbolForNotification; - :global ValidateSyntax; - - :if ([ $CertificateAvailable "R3" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading certificate failed, trying without.") false; - } - - :foreach Script in=$Scripts do={ - :if ([ :len [ / system script find where name=$Script ] ] = 0) do={ - $LogPrintExit2 info $0 ("Adding new script: " . $Script) false; - / system script add name=$Script source="#!rsc by RouterOS\n"; - } - } - - :local ScriptInstallUpdateBefore [ :tostr $ScriptInstallUpdate ]; - - :foreach Script in=[ / system script find where source~"^#!rsc( by RouterOS)\?\n" ] do={ - :local ScriptVal [ / system script get $Script ]; - :local ScriptFile [ / file find where name=("script-updates/" . $ScriptVal->"name") ]; - :local SourceNew; - :if ([ :len $ScriptFile ] > 0) do={ - :set SourceNew [ / file get $ScriptFile content ]; - / file remove $ScriptFile; - } - - :foreach Scheduler in=[ / system scheduler find where on-event~("\\b" . $ScriptVal->"name" . "\\b") ] do={ - :local SchedulerVal [ / system scheduler get $Scheduler ]; - :if ($ScriptVal->"policy" != $SchedulerVal->"policy") do={ - $LogPrintExit2 warning $0 ("Policies differ for script " . $ScriptVal->"name" . \ - " and its scheduler " . $SchedulerVal->"name" . "!") false; - } - } - - :if ([ :len $SourceNew ] = 0 && $ScriptUpdatesFetch = true) do={ - :local Comment [ $ParseKeyValueStore ($ScriptVal->"comment") ]; - :if (!($Comment->"ignore" = true)) do={ - $LogPrintExit2 debug $0 ("Fetching script from url: " . $ScriptVal->"name") false; - :do { - :local BaseUrl $ScriptUpdatesBaseUrl; - :local UrlSuffix $ScriptUpdatesUrlSuffix; - :if ([ :typeof ($Comment->"base-url") ] = "str") do={ :set BaseUrl ($Comment->"base-url"); } - :if ([ :typeof ($Comment->"url-suffix") ] = "str") do={ :set UrlSuffix ($Comment->"url-suffix"); } - - :local Result [ / tool fetch check-certificate=yes-without-crl \ - ($BaseUrl . $ScriptVal->"name" . $UrlSuffix) output=user as-value ]; - :if ($Result->"status" = "finished") do={ - :set SourceNew ($Result->"data"); - } - } on-error={ - $LogPrintExit2 warning $0 ("Failed fetching " . $ScriptVal->"name") false; - } - } - } - - :if ([ :len $SourceNew ] > 0) do={ - :if ($SourceNew != $ScriptVal->"source") do={ - :if ([ :pick $SourceNew 0 18 ] = "#!rsc by RouterOS\n") do={ - :if ([ $ValidateSyntax $SourceNew ] = true) do={ - :local DontRequirePermissions \ - ($SourceNew~"\n# requires: dont-require-permissions=yes\n"); - $LogPrintExit2 info $0 ("Updating script: " . $ScriptVal->"name") false; - / system script set owner=($ScriptVal->"name") source=$SourceNew \ - dont-require-permissions=$DontRequirePermissions $Script; - :if ($ScriptVal->"name" = "global-config") do={ - $LogPrintExit2 info $0 ("Reloading global configuration and overlay.") false; - :do { - / system script { run global-config; run global-config-overlay; } - } on-error={ - $LogPrintExit2 error $0 ("Reloading global configuration and overlay failed!" . \ - " Syntax error or missing overlay\?") false; - } - } - :if ($ScriptVal->"name" = "global-functions") do={ - $LogPrintExit2 info $0 ("Reloading global functions.") false; - :do { - / system script run global-functions; - } on-error={ - $LogPrintExit2 error $0 ("Reloading global functions failed!") false; - } - } - } else={ - $LogPrintExit2 warning $0 ("Syntax validation for script " . $ScriptVal->"name" . \ - " failed! Ignoring!") false; - } - } else={ - $LogPrintExit2 warning $0 ("Looks like new script " . $ScriptVal->"name" . \ - " is not valid (missing shebang). Ignoring!") false; - } - } else={ - $LogPrintExit2 debug $0 ("Script " . $ScriptVal->"name" . " did not change.") false; - } - } else={ - $LogPrintExit2 debug $0 ("No update for script " . $ScriptVal->"name" . ".") false; - } - } - - :if ($SentConfigChangesNotification!=$ExpectedConfigVersion && \ - $GlobalConfigVersion < $ExpectedConfigVersion) do={ - :global GlobalConfigChanges; - :global GlobalConfigMigration; - :local ChangeLogCode; - :local NotificationMessage ("Current configuration on " . $Identity . \ - " is out of date. Please update global-config-overlay, then increase " . \ - "\$GlobalConfigVersion (currently " . $GlobalConfigVersion . \ - ") to " . $ExpectedConfigVersion . " and re-run global-config-overlay."); - $LogPrintExit2 info $0 ($NotificationMessage) false; - - $LogPrintExit2 debug $0 ("Fetching changelog.") false; - :do { - :local Result [ / tool fetch check-certificate=yes-without-crl \ - ($ScriptUpdatesBaseUrl . "global-config.changes" . $ScriptUpdatesUrlSuffix) \ - output=user as-value ]; - :if ($Result->"status" = "finished") do={ - :set ChangeLogCode ($Result->"data"); - } - } on-error={ - $LogPrintExit2 warning $0 ("Failed fetching changes!") false; - :set NotificationMessage ($NotificationMessage . \ - "\n\nChanges are not available."); - } - - :if ([ :len $ChangeLogCode ] > 0) do={ - :if ([ $ValidateSyntax $ChangeLogCode ] = true) do={ - :set NotificationMessage ($NotificationMessage . "\n\nChanges:"); - [ :parse $ChangeLogCode ]; - :for I from=($GlobalConfigVersion + 1) to=$ExpectedConfigVersion do={ - :local Migration ($GlobalConfigMigration->[ :tostr $I ]); - :if ([ :typeof $Migration ] = "str") do={ - :if ([ $ValidateSyntax $Migration ] = true) do={ - $LogPrintExit2 info $0 ("Applying migration: " . $Migration) false; - [ :parse $Migration ]; - } else={ - $LogPrintExit2 warning $0 ("Migration code for change " . $I . " failed syntax validation!") false; - } - } - :set NotificationMessage ($NotificationMessage . \ - "\n " . [ $IfThenElse ($NotificationsWithSymbols = true) ("\E2\97\8F") "*" ] . " " . \ - $GlobalConfigChanges->[ :tostr $I ]); - $LogPrintExit2 info $0 ("Change: " . $GlobalConfigChanges->[ :tostr $I ]) false; - } - :set GlobalConfigChanges; - :set GlobalConfigMigration; - } else={ - $LogPrintExit2 warning $0 ("The changelog failed syntax validation!") false; - :set NotificationMessage ($NotificationMessage . \ - "\n\nChanges are not available."); - } - } - - :local Link; - :if ($IDonate != true) do={ - :set NotificationMessage ($NotificationMessage . \ - "\n\n==== donation hint ====\n" . \ - "This project is developed in private spare time and usage is " . \ - "free of charge for you. If you like the scripts and think this is " . \ - "of value for you or your business please consider a donation."); - :set Link "https://git.eworm.de/cgit/routeros-scripts/about/#donate"; - } - - $SendNotification ([ $SymbolForNotification "pushpin" ] . "News and configuration changes") \ - $NotificationMessage $Link; - :set SentConfigChangesNotification $ExpectedConfigVersion; - } - - :if ($ScriptInstallUpdateBefore != [ :tostr $ScriptInstallUpdate ]) do={ - $LogPrintExit2 info $0 ("This function changed, you may want to re-run.") false; - } -} - -# lock script against multiple invocation -:set ScriptLock do={ - :global LogPrintExit2; - - :local Script [ :tostr $1 ]; - - :if ([ :len [ / system script job find where script=$Script ] ] > 1) do={ - $LogPrintExit2 info $0 ("Script " . $Script . " started more than once... Aborting.") true; - } -} - -# send notification via e-mail -:set SendEMail do={ - :local Subject [ :tostr $1 ]; - :local Message [ :tostr $2 ]; - :local Link [ :tostr $3 ]; - - :global Identity; - :global EmailGeneralTo; - :global EmailGeneralCc; - :global EmailQueue; - - :global LogPrintExit2; - :global IfThenElse; - - :if ([ :len $EmailGeneralTo ] = 0) do={ - :return false; - } - - :if ([ :typeof $EmailQueue ] = "nothing") do={ - :set EmailQueue [ :toarray "" ]; - } - :local Signature [ / system note get note ]; - :set ($EmailQueue->[ :len $EmailQueue ]) { - to=$EmailGeneralTo; cc=$EmailGeneralCc; subject=("[" . $Identity . "] " . $Subject); - body=($Message . \ - [ $IfThenElse ([ :len $Link ] > 0) ("\n\n" . $Link) "" ] . \ - [ $IfThenElse ([ :len $Signature ] > 0) ("\n-- \n" . $Signature) "" ]) }; - :if ([ :len [ / system scheduler find where name="FlushEmailQueue" ] ] = 0) do={ - / system scheduler add name=FlushEmailQueue interval=1s start-time=startup \ - on-event=":global FlushEmailQueue; \$FlushEmailQueue;"; - } -} - -# send notification via e-mail and telegram -# Note that attachment is ignored for telegram, silent is ignored for e-mail! -:set SendNotification do={ - :local Subject [ :tostr $1 ]; - :local Message [ :tostr $2 ]; - :local Link [ :tostr $3 ]; - :local Silent [ :tostr $4 ]; - - :global SendEMail; - :global SendTelegram; - - $SendEMail $Subject $Message $Link; - $SendTelegram $Subject $Message $Link $Silent; -} - -# send notification via telegram -:set SendTelegram do={ - :local Subject [ :tostr $1 ]; - :local Message [ :tostr $2 ]; - :local Link [ :tostr $3 ]; - :local Silent [ :tostr $4 ]; - - :global Identity; - :global TelegramChatId; - :global TelegramChatIdOverride; - :global TelegramFixedWidthFont; - :global TelegramQueue; - :global TelegramTokenId; - - :global CertificateAvailable; - :global CharacterReplace; - :global IfThenElse; - :global LogPrintExit2; - :global SymbolForNotification; - :global UrlEncode; - - :local EscapeMD do={ - :global TelegramFixedWidthFont; - - :global CharacterReplace; - :global IfThenElse; - - :if ($TelegramFixedWidthFont != true) do={ - :return ($1 . [ $IfThenElse ($2 = "body") "\n" "" ]); - } - - :local Return $1; - :local Chars { - "body"={ "\\"; "`" }; - "hint"={ "_"; "*"; "["; "]"; "("; ")"; "~"; "`"; ">"; - "#"; "+"; "-"; "="; "|"; "{"; "}"; "."; "!" }; - } - :foreach Char in=($Chars->$2) do={ - :set Return [ $CharacterReplace $Return $Char ("\\" . $Char) ]; - } - - :if ($2 = "body") do={ - :return ("```\n" . $Return . "\n```"); - } - - :return $Return; - } - - :local ChatId $TelegramChatId; - :if ([ :len $TelegramChatIdOverride ] > 0) do={ - :set ChatId $TelegramChatIdOverride; - } - - :if ([ :len $TelegramTokenId ] = 0 || [ :len $ChatId ] = 0) do={ - :return false; - } - - :local Truncated false; - :local LenLink [ :len $Link ]; - :local Text ("[" . $Identity . "] " . $Subject . "\n\n" . $Message); - :local LenText [ :len $Text ]; - :if ($LenText > (3968 - $LenLink)) do={ - :set Text [ $EscapeMD ([ :pick $Text 0 (3840 - $LenLink) ] . "...") "body" ]; - :set Truncated true; - } else={ - :set Text [ $EscapeMD $Text "body" ]; - } - :if ($LenLink > 0) do={ - :set Text ($Text . "\n" . [ $SymbolForNotification "link" ] . [ $EscapeMD $Link "hint" ]); - } - :if ($Truncated = true) do={ - :set Text ($Text . "\n" . [ $SymbolForNotification "scissors" ] . \ - [ $EscapeMD ("The Telegram message was too long and has been truncated, cut off " . \ - (($LenText - [ :len $Text ]) * 100 / $LenText) . "%!") "hint" ]); - } - :set Text [ $UrlEncode $Text ]; - :local ParseMode [ $IfThenElse ($TelegramFixedWidthFont = true) "MarkdownV2" "" ]; - - :do { - :if ([ $CertificateAvailable "Go Daddy Secure Certificate Authority - G2" ] = false) do={ - $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true; - } - / tool fetch check-certificate=yes-without-crl output=none http-method=post \ - ("https://api.telegram.org/bot" . $TelegramTokenId . "/sendMessage") \ - http-data=("chat_id=" . $ChatId . "&disable_notification=" . $Silent . \ - "&disable_web_page_preview=true&parse_mode=" . $ParseMode . "&text=" . $Text) as-value; - } on-error={ - $LogPrintExit2 info $0 ("Failed sending telegram notification! Queuing...") false; - - :if ([ :typeof $TelegramQueue ] = "nothing") do={ - :set TelegramQueue [ :toarray "" ]; - } - :set Text ($Text . [ $UrlEncode ("\n" . [ $SymbolForNotification "alarm-clock" ] . \ - [ $EscapeMD ("This message was queued since " . [ / system clock get date ] . \ - " " . [ / system clock get time ] . " and may be obsolete.") "hint" ]) ]); - :set ($TelegramQueue->[ :len $TelegramQueue ]) { - chatid=$ChatId; parsemode=$ParseMode; text=$Text; silent=$Silent }; - :if ([ :len [ / system scheduler find where name="FlushTelegramQueue" ] ] = 0) do={ - / system scheduler add name=FlushTelegramQueue interval=1m start-time=startup \ - on-event=":global FlushTelegramQueue; \$FlushTelegramQueue;"; - } - } -} - -# return UTF-8 symbol for unicode name -:set SymbolByUnicodeName do={ - :local Symbols { - "alarm-clock"="\E2\8F\B0"; - "calendar"="\F0\9F\93\85"; - "cross-mark"="\E2\9D\8C"; - "fire"="\F0\9F\94\A5"; - "floppy-disk"="\F0\9F\92\BE"; - "high-voltage-sign"="\E2\9A\A1"; - "incoming-envelope"="\F0\9F\93\A8"; - "link"="\F0\9F\94\97"; - "lock-with-ink-pen"="\F0\9F\94\8F"; - "mobile-phone"="\F0\9F\93\B1"; - "pushpin"="\F0\9F\93\8C"; - "scissors"="\E2\9C\82"; - "sparkles"="\E2\9C\A8"; - "warning-sign"="\E2\9A\A0"; - "white-heavy-check-mark"="\E2\9C\85" - } - - :return ($Symbols->$1); -} - -# return symbol for notification -:set SymbolForNotification do={ - :global NotificationsWithSymbols; - :global SymbolByUnicodeName; - - :if ($NotificationsWithSymbols != true) do={ - :return ""; - } - :local Return ""; - :foreach Symbol in=[ :toarray $1 ] do={ - :set Return ($Return . [ $SymbolByUnicodeName $Symbol ]); - } - :return ($Return . " "); -} - -# check if system time is sync -:set TimeIsSync do={ - :global LogPrintExit2; - - :if ([ / system ntp client get enabled ] = true) do={ - :do { - :if ([ / system ntp client get status ] = "synchronized") do={ - :return true; - } - } on-error={ - :if ([ :typeof [ / system ntp client get last-adjustment ] ] = "time") do={ - :return true; - } - } - :return false; - } - - :if ([ / ip cloud get ddns-enabled ] = true && [ / ip cloud get update-time ] = true) do={ - :if ([ :typeof [ / ip cloud get public-address ] ] = "ip") do={ - :return true; - } - :return false; - } - - $LogPrintExit2 debug $0 ("No time source configured! Returning gracefully...") false; - :return true; -} - -# url encoding -:set UrlEncode do={ - :local Input [ :tostr $1 ]; - :local Return ""; - - :if ([ :len $Input ] > 0) do={ - :local Chars "\n\r !\"#\$%&'()*+,:;<=>\?@[\\]^`{|}~"; - :local Subs { "%0A"; "%0D"; "%20"; "%21"; "%22"; "%23"; "%24"; "%25"; "%26"; "%27"; - "%28"; "%29"; "%2A"; "%2B"; "%2C"; "%3A"; "%3B"; "%3C"; "%3D"; "%3E"; - "%3F"; "%40"; "%5B"; "%5C"; "%5D"; "%5E"; "%60"; "%7B"; "%7C"; "%7D"; - "%7E" }; - - :for I from=0 to=([ :len $Input ] - 1) do={ - :local Char [ :pick $Input $I ]; - :local Replace [ :find $Chars $Char ]; - - :if ([ :len $Replace ] > 0) do={ - :set Char ($Subs->$Replace); - } - :set Return ($Return . $Char); - } - } - - :return $Return; -} - -# basic syntax validation -:set ValidateSyntax do={ - :local Code [ :tostr $1 ]; - - :do { - [ :parse (":local Validate do={ " . $Code . " }") ]; - } on-error={ - :return false; - } - :return true; -} - -# convert version string to numeric value -:set VersionToNum do={ - :local Input [ :tostr $1 ]; - :local Multi 0x1000000; - :local Return 0; - - :global CharacterReplace; - - :set Input [ $CharacterReplace [ $CharacterReplace [ $CharacterReplace $Input \ - "." "," ] "beta" ",beta," ] "rc" ",rc," ]; - - :foreach Value in=([ :toarray $Input ], 0) do={ - :local Num [ :tonum $Value ]; - :if ($Multi = 0x100) do={ - :if ([ :typeof $Num ] = "num") do={ - :set Return ($Return + 0xff00); - :set Multi ($Multi / 0x100); - } else={ - :if ($Value = "beta") do={ :set Return ($Return + 0x3f00); } - :if ($Value = "rc") do={ :set Return ($Return + 0x7f00); } - } - } - :if ([ :typeof $Num ] = "num") do={ :set Return ($Return + ($Value * $Multi)); } - :set Multi ($Multi / 0x100); - } - - :return $Return; -} - -# wait for default route to be reachable -:set WaitDefaultRouteReachable do={ - :global DefaultRouteIsReachable; - - :while ([ $DefaultRouteIsReachable ] = false) do={ - :delay 1s; - } -} - -# wait for DNS to resolve -:set WaitDNSResolving do={ - :global DNSIsResolving; - - :while ([ $DNSIsResolving ] = false) do={ - :delay 1s; - } -} - -# wait for file to be available -:set WaitForFile do={ - :local FileName [ :tostr $1 ]; - - :global CleanFilePath; - - :set FileName [ $CleanFilePath $FileName ]; - :local I 0; - - :while ([ :len [ / file find where name=$FileName ] ] = 0) do={ - :if ($I > 20) do={ - :return false; - } - :delay 100ms; - :set I ($I + 1); - } - :return true; -} - -# wait to be fully connected (default route is reachable, time is sync, DNS resolves) -:set WaitFullyConnected do={ - :global WaitDefaultRouteReachable; - :global WaitDNSResolving; - :global WaitTimeSync; - - $WaitDefaultRouteReachable; - $WaitTimeSync; - $WaitDNSResolving; -} - -# wait for time to become synced -:set WaitTimeSync do={ - :global LogPrintExit2; - :global TimeIsSync; - - :while ([ $TimeIsSync ] = false) do={ - :if ([ :len [ / system script find where name="rotate-ntp" ] ] > 0 && \ - ([ / system resource get uptime ] % (180 * 1000000000)) = 0s) do={ - :do { - / system script run rotate-ntp; - } on-error={ - $LogPrintExit2 debug $0 ("Running rotate-ntp failed.") false; - } - } - :delay 1s; - } -} - -# check for required RouterOS version -$RequiredRouterOS "global-functions" "6.47"; - -# signal we are ready -:set GlobalFunctionsReady true; |