global-functions 6.9 KB
Newer Older
Christian Hesse's avatar
Christian Hesse committed
1 2
#!rsc
# RouterOS script: global-functions
Christian Hesse's avatar
Christian Hesse committed
3
# Copyright (c) 2013-2019 Christian Hesse <mail@eworm.de>
Christian Hesse's avatar
Christian Hesse committed
4 5 6
#
# global functions

7
# expected configuration version
Christian Hesse's avatar
Christian Hesse committed
8
:global ExpectedConfigVersion 6;
9 10

# global variables not to be changed by user
11
:global SentConfigChangesNotification "-";
12 13 14
:global SentRouterosUpdateNotification "-";
:global SentLteFirmwareUpgradeNotification "-";
:global Identity [ / system identity get name ];
15

16 17
# url encoding
:global UrlEncode do={
18 19
  :local Input [ :tostr $1 ];
  :local Return "";
20

21
  :if ([ :len $Input ] > 0) do={
22 23 24 25 26
    :local Chars " !\"#\$%&'()*+,-./:;<=>\?@[\\]^_`{|}~";
    :local Subs { "%20"; "%21"; "%22"; "%23"; "%24"; "%25"; "%26"; "%27"; "%28"; "%29";
                  "%2A"; "%2B"; "%2C"; "%2D"; "%2E"; "%2F"; "%3A"; "%3B"; "%3C"; "%3D";
                  "%3E"; "%3F"; "%40"; "%5B"; "%5C"; "%5D"; "%5E"; "%5F"; "%60"; "%7B";
                  "%7C"; "%7D"; "%7E" };
27

28 29 30
    :for I from=0 to=([ :len $Input ] - 1) do={
      :local Char [ :pick $Input $I ];
      :local Replace [ :find $Chars $Char ];
31

32 33
      :if ([ :len $Replace ] > 0) do={
        :set Char ($Subs->$Replace);
34
      }
35
      :set Return ($Return . $Char);
36 37 38
    }
  }

39
  :return $Return;
40 41
}

42 43 44 45 46 47
# character replace
:global CharacterReplace do={
  :local String [ :tostr $1 ];
  :local ReplaceFrom [ :tostr $2 ];
  :local ReplaceWith [ :tostr $3 ];
  :local Len [ :len $ReplaceFrom ];
48
  :local Return "";
49

50 51 52 53
  :if ($ReplaceFrom = "") do={
    :return $String;
  }

54
  :while ([ :typeof [ :find $String $ReplaceFrom ] ] != "nil") do={
55
    :local Pos [ :find $String $ReplaceFrom ];
56 57
    :set Return ($Return . [ :pick $String 0 $Pos ] . $ReplaceWith);
    :set String [ :pick $String ($Pos + $Len) 999 ];
58 59
  }

60
  :return ($Return . $String);
61 62
}

63 64
# check and import required certificates
:global CertificateAvailable do={
65
  :local CommonName [ :tostr $1 ];
66

67 68
  :global ScriptUpdatesBaseUrl;
  :global ScriptUpdatesUrlSuffix;
69

70
  :global UrlEncode;
71 72
  :global WaitForFile;

73 74
  :if ([ / certificate print count-only where common-name=$CommonName ] = 0) do={
    :log info ("Certificate with CommonName " . $CommonName . \
75 76
      " not available, downloading and importing.");
    :do {
77 78
      :local LocalFileName ($CommonName . ".pem");
      :local UrlFileName ([ $UrlEncode $CommonName ] . ".pem");
79
      / tool fetch check-certificate=yes-without-crl \
80
        ($ScriptUpdatesBaseUrl . "certs/" . \
81 82 83 84 85
        $UrlFileName . $ScriptUpdatesUrlSuffix) \
        dst-path=$LocalFileName;
      $WaitForFile $LocalFileName;
      / certificate import file-name=$LocalFileName passphrase="";
      / file remove $LocalFileName;
86 87 88 89 90 91
    } on-error={
      :log warning "Failed imprting certificate!";
    }
  }
}

92
# send notification via e-mail and telegram
93
# Note that attachment is ignored for telegram!
94
:global SendNotification do={
95 96 97
  :local Subject [ :tostr $1 ];
  :local Message [ :tostr $2 ];
  :local Attach [ :tostr $3 ];
98

99 100 101 102 103
  :global Identity;
  :global EmailGeneralTo;
  :global EmailGeneralCc;
  :global TelegramTokenId;
  :global TelegramChatId;
104

105
  :global UrlEncode;
106
  :global CertificateAvailable;
107

108
  :if ([ :len $EmailGeneralTo ] > 0) do={
109
    :do {
110 111 112 113
      :local Signature [ / system note get note ];
      :if ([ :len $Signature ] > 0) do={
        :set Signature ("\n-- \n" . $Signature);
      }
114
      / tool e-mail send to=$EmailGeneralTo cc=$EmailGeneralCc \
115 116
        subject=("[" . $Identity . "] " . $Subject) \
        body=($Message . $Signature) file=$Attach;
117 118 119 120 121
    } on-error={
      :log warning "Failed sending notification mail!";
    }
  }

122
  :if ([ :len $TelegramTokenId ] > 0 && [ :len $TelegramChatId ] > 0) do={
123
    $CertificateAvailable "Go Daddy Secure Certificate Authority - G2";
124 125
    :do {
      / tool fetch check-certificate=yes-without-crl keep-result=no http-method=post \
126 127 128
        ("https://api.telegram.org/bot" . $TelegramTokenId . "/sendMessage") \
        http-data=("chat_id=" . $TelegramChatId . "&text=" . \
        [ $UrlEncode ("[" . $Identity . "] " . $Subject . "\n\n" . $Message) ]);
129 130 131 132 133
    } on-error={
      :log warning "Failed sending telegram notification!";
    }
  }
}
134 135 136

# get MAC vendor
:global GetMacVendor do={
137
  :local Mac [ :tostr $1 ];
138

139 140
  :global CertificateAvailable;

141
  :do {
142
    :local Vendor;
143
    $CertificateAvailable "Let's Encrypt Authority X3";
144
    :set Vendor ([ / tool fetch check-certificate=yes-without-crl \
145 146
        ("https://api.macvendors.com/" . [ :pick $Mac 0 8 ]) output=user as-value ]->"data");
    :return $Vendor;
147 148 149 150
  } on-error={
    :return "unknown vendor";
  }
}
151

152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
# clean file path
:global 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;
}

171 172
# download package from upgrade server
:global DownloadPackage do={
173 174 175
  :local PkgName [ :tostr $1 ];
  :local PkgVer  [ :tostr $2 ];
  :local PkgArch [ :tostr $3 ];
176
  :local PkgDir  [ :tostr $4 ];
177 178

  :global CertificateAvailable;
179
  :global CleanFilePath;
180
  :global WaitForFile;
181

182 183 184
  :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 ]; }
185

186
  :local PkgFile ($PkgName . "-" . $PkgVer . "-" . $PkgArch . ".npk");
187 188 189
  :if ($PkgArch = "x86_64") do={
    :set PkgFile ($PkgName . "-" . $PkgVer . ".npk");
  }
190
  :local PkgDest [ $CleanFilePath ($PkgDir . "/" . $PkgFile) ];
191

192
  $CertificateAvailable "Let's Encrypt Authority X3";
193
  :do {
194
    / tool fetch check-certificate=yes-without-crl \
195
      ("https://upgrade.mikrotik.com/routeros/" . $PkgVer . "/" . $PkgFile) \
196
      dst-path=$PkgDest;
197
    $WaitForFile $PkgDest;
198
  } on-error={
199
    / file remove [ find where name=$PkgDest ];
200
    :return false;
201
  }
202 203

  :return true;
204
}
205 206 207 208 209 210 211 212 213 214

# lock script against multiple invocation
:global ScriptLock do={
  :local Script [ :tostr $1 ];

  :if ([ / system script job print count-only where script=$Script ] > 1) do={
    :log debug ("Script " . $Script . " started more than once... Aborting.");
    :error "Locked."
  }
}
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231

# wait for file to be available
:global WaitForFile do={
  :global CleanFilePath;

  :local FileName [ $CleanFilePath [ :tostr $1 ] ];
  :local I 0;

  :while ([ file print count-only where name=$FileName ] = 0) do={
    :if ($I > 20) do={
      :return false;
    }
    :delay 100ms;
    :set I ($I + 1);
  }
  :return true;
}
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247

# parse key value store
:global ParseKeyValueStore do={
  :global CharacterReplace;

  :local Source $1;
  :if ([ :typeof $Source ] != "array") do={
    :set Source [ :tostr $1 ];
  }
  :local Result [ :toarray "" ];
  :foreach KeyValue in=[ :toarray $Source ] do={
    :set KeyValue [ :toarray [ $CharacterReplace $KeyValue "=" "," ] ];
    :set ($Result->($KeyValue->0)) ($KeyValue->1);
  }
  :return $Result;
}