#!rsc by RouterOS # DNSDrone for Mikrotik - DNS Record Updater for FreeIPA # Configuration Variables :local CONFIG { "wanInterface"="VLAN666_WAN_Telenor"; "IpaServer"="ipa.demo1.freeipa.org"; "dnsZone"="demo1.freeipa.org"; "IpaUser"="admin"; "IpaPassword"="Secret123"; "apiVersion"="2.253"; "recordTTL"="300"; # Curlproxy "curlproxyserver"="pubdns.dmz.skyfritt.net"; "curlproxyuser"="curluser"; "cookiefile"="cookie_output.txt"; # Record Names (without zone) "dualStackName"="tik-test"; "ipv4OnlyName"="tik-test-v4"; "ipv6OnlyName"="tik-test-v6"; "prefixTxtName"="tik-test-prefix"; # Resolver Config "DNSserver"="ipa.demo1.freeipa.org"; "DoHserver"="one.one.one.one"; # Script options (true/false) "Debug"="true" } # Functions: :local GetIPACookie do={ # Import config :local CONFIG $1; :if ( "$($CONFIG->"Debug")" = "true") do={ :put ""; :put "* RouterOS-multiline authentication-command (what we want):"; :put ""; :put "/tool fetch url=\"https://$($CONFIG->"IpaServer")/ipa/session/login_password\" \\"; :put " http-method=post \\"; :put " http-header-field=\"Accept: text/plain, \\"; :put " Content-Type: application/x-www-form-urlencoded, \\"; :put " Referer: https://$($CONFIG->"IpaServer")/ipa,\\"; :put " X-IPA-API-Version: $($CONFIG->"apiVersion")\" \\"; :put " http-data=\"user=$($CONFIG->"IpaUser")&password=$($CONFIG->"IpaPassword")\" \\"; :put " output=user-with-headers"; :put ""; :put "* Reverting to ugly curl+ssh workaround instead via the following command:"; :put ""; :put "/system ssh-exec \"$($CONFIG->"curlproxyserver")\" user=\"$($CONFIG->"curlproxyuser")\" \\"; :put " command=\"curl -s -k -i -H 'Accept: text/plain' \\"; :put " -H 'Content-Type: application/x-www-form-urlencoded' \\"; :put " -H 'Referer: https://$($CONFIG->"IpaServer")/ipa' \\"; :put " -H 'X-IPA-API-Version: $($CONFIG->"apiVersion")' \\"; :put " --data-urlencode 'user=$($CONFIG->"IpaUser")' \\"; :put " --data-urlencode 'password=$($CONFIG->"IpaPassword")' \\"; :put " 'https://$($CONFIG->"IpaServer")/ipa/session/login_password'\" \\"; :put " output-to-file=\"$($CONFIG->"cookiefile")\""; :put ""; } # Save Cookie-information in tempoary local file via ugly ssh hack /system ssh-exec "$($CONFIG->"curlproxyserver")" user="$($CONFIG->"curlproxyuser")" \ command="curl -s -k -i -H 'Accept: text/plain' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -H 'Referer: https://$($CONFIG->"IpaServer")/ipa' \ -H 'X-IPA-API-Version: $($CONFIG->"apiVersion")' \ --data-urlencode 'user=$($CONFIG->"IpaUser")' \ --data-urlencode 'password=$($CONFIG->"IpaPassword")' \ 'https://$($CONFIG->"IpaServer")/ipa/session/login_password'" \ output-to-file="$($CONFIG->"cookiefile")" # Needs time to save the cookiefile /delay 2s # Extract cookie-information from temporary filr :local cookie; :local fileContents [/file get "$($CONFIG->"cookiefile")" contents] :local startIndex [:find $fileContents "Set-Cookie: ipa_session="] :if ($startIndex != -1) do={ :set startIndex ($startIndex + 12) :local endIndex [:find $fileContents "\r\n" $startIndex] :if ($endIndex = -1) do={ :set endIndex [:len $fileContents] } :set cookie [:pick $fileContents $startIndex $endIndex] } # Cleaning up the cookiefile /file remove "$($CONFIG->"cookiefile")" # :put "DEBUG (function: GetIPACookie):; # :put Ipakjeks: $cookie"; # Output :return "$cookie"; } :local GetLocalDynamicConfig do={ # Import config :local CONFIG $1; # Valid options: ipv4address, ipv6address, ipv6prefix :local ConfigType $2; # result variable :local resultRAW; :local resultClean; :if ($ConfigType = "ipv4address" ) do={ # Retrieve information with error handling :local dhcpError; :onerror dhcpError in={ :set resultRAW [/ip dhcp-client get [/ip dhcp-client find interface="$($CONFIG->"wanInterface")"] address]; } do={ :put "* Warning: Failed to get DHCPv4 address for interface $($CONFIG->"wanInterface"): $dhcpError"; # Set default value on error :set resultRAW ""; } # Clean information only if resultRAW was set successfully :if ([:len "$resultRAW"] != 0) do={ :set resultClean [:pick "$resultRAW" 0 [:find "$resultRAW" "/"]]; } else={ :set resultClean "NA"; } } :if ($ConfigType = "ipv6address" ) do={ # Retrieve information with error handling :local dhcpError; :onerror dhcpError in={ :set resultRAW [/ipv6 dhcp-client get [/ipv6 dhcp-client find interface="$($CONFIG->"wanInterface")"] address]; } do={ :put "* Warning: Failed to get DHCPv6 address for interface $($CONFIG->"wanInterface"): $dhcpError"; # Set default value on error :set resultRAW ""; } # Clean information only if resultRAW was set successfully :if ([:len "$resultRAW"] != 0) do={ :set resultClean [:pick "$resultRAW" 0 [:find "$resultRAW" "/"]]; } else={ :set resultClean "NA"; } } :if ($ConfigType = "ipv6prefix" ) do={ # Retrieve information with error handling :local dhcpError; :onerror dhcpError in={ :set resultRAW [/ipv6 dhcp-client get [/ipv6 dhcp-client find interface="$($CONFIG->"wanInterface")"] prefix]; } do={ :put "* Warning: Failed to get DHCPv6 prefix for interface $($CONFIG->"wanInterface"): $dhcpError"; # Set default value on error :set resultRAW ""; } # Clean information only if resultRAW was set successfully :if ([:len "$resultRAW"] != 0) do={ :set resultClean [:pick "$resultRAW" 0 [:find "$resultRAW" ","]]; } else={ :set resultClean "NA"; } } # Output :return "$resultClean"; } :local GetDynamicDNSRecords do={ # Import config :local CONFIG $1; # Valid options: ipv4address, ipv6address, ipv6prefix :local ConfigType $2; # Record name :local RecordName $3; # result variable :local resultRAW; :local resultClean; :if ($ConfigType = "ipv4address" ) do={ # Retrive information with error handling :local resolveError; :onerror resolveError in={ :set resultRAW [/resolve "$RecordName" server="$($CONFIG->"DNSserver")" type=ipv4]; } do={ :put "* Warning: Failed to resolve IPv4 address for $RecordName: $resolveError"; # You can set a default value or take alternative action :set resultRAW ""; } # Clean information (only if resultRAW was set successfully) :if ([:len "$resultRAW"] != 0) do={ :set resultClean "$resultRAW"; } else={ :set resultClean "NA"; } } :if ($ConfigType = "ipv6address" ) do={ # Retrive information with error handling :local resolveError; :onerror resolveError in={ :set resultRAW [/resolve "$RecordName" server="$($CONFIG->"DNSserver")" type=ipv6]; } do={ :put "* Warning: Failed to resolve IPv6 address for $RecordName: $resolveError"; # You can set a default value or take alternative action :set resultRAW ""; } # Clean information (only if resultRAW was set successfully) :if ([:len "$resultRAW"] != 0) do={ :set resultClean "$resultRAW"; } else={ :set resultClean "NA"; } } :if ($ConfigType = "ipv6prefix") do={ # Fetch prefix TXT-record via DoH server: :set resultRAW [/tool fetch \ url="https://$($CONFIG->"DoHserver")/dns-query?name=$RecordName&type=TXT" \ http-method=get http-header-field="accept: application/dns-json" \ output=user as-value]; # :put "DEBUG: resultRAW: $resultRAW"; # Get just the data content :local resultJSON ($resultRAW -> "data"); # :put "DEBUG: resultJSON: $resultJSON"; # Extracting TXT-record-data :local startIndex [:find $resultJSON "data\":\"" 0]; :local startPos ($startIndex + 7); :local endIndex [:find $resultJSON "\"}" $startPos]; :local resultTXT [:pick $resultJSON $startPos $endIndex]; # Cleaning the prefix-variable (remove escaped double quotes) :set resultClean [:pick $resultTXT 2 ([:len $resultTXT] - 2)]; # :put "DEBUG: resultClean: $resultClean"; } # Output :return "$resultClean"; } :local UpdateDynamicDNSRecord do={ # Import config :local CONFIG $1; # Import IPACookie :local IPACookie $2; # Valid options: ipv4address, ipv6address, ipv6prefix :local ConfigType $3; # Record name :local RecordName $4; # Record content :local RecordContent $5; # result variable :local resultRAW; :local resultClean; :if ($ConfigType = "ipv4address" ) do={ :local IPACookieData "$IPACookie"; :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* RouterOS-multiline update-command:"; :put ""; :put "/tool fetch url=\"https://$($CONFIG->"IpaServer")/ipa/session/json\" \\"; :put " http-method=post \\"; :put " http-header-field=\"Accept: application/json, \\"; :put " Content-Type: application/json,\\"; :put " Referer: https://$($CONFIG->"IpaServer")/ipa,\\"; :put " X-IPA-API-Version: $($CONFIG->"apiVersion"),\\"; :put " Cookie: $IPACookieData\" \\"; :put " http-data=\"{\\\"method\\\":\\\"dnsrecord_mod\\\",\\\"params\\\":[[\\\"$($CONFIG->"dnsZone")\\\",\\\"$RecordName.\\\"],{\\\"arecord\\\":\\\"$RecordContent\\\",\\\"dnsttl\\\":$($CONFIG->"recordTTL")}]}\" \\"; :put " keep-result=no"; :put ""; } /tool fetch url="https://$($CONFIG->"IpaServer")/ipa/session/json" \ http-method=post \ http-header-field="Accept: application/json, \ Content-Type: application/json,\ Referer: https://$($CONFIG->"IpaServer")/ipa,\ X-IPA-API-Version: $($CONFIG->"apiVersion"),\ Cookie: $IPACookieData" \ http-data="{\"method\":\"dnsrecord_mod\",\"params\":[[\"$($CONFIG->"dnsZone")\",\"$RecordName.\"],{\"arecord\":\"$RecordContent\",\"dnsttl\":$($CONFIG->"recordTTL")}]}" \ keep-result=no } :if ($ConfigType = "ipv6address" ) do={ :local IPACookieData "$IPACookie"; :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* RouterOS-multiline update-command:"; :put ""; :put "/tool fetch url=\"https://$($CONFIG->"IpaServer")/ipa/session/json\" \\"; :put " http-method=post \\"; :put " http-header-field=\"Accept: application/json, \\"; :put " Content-Type: application/json,\\"; :put " Referer: https://$($CONFIG->"IpaServer")/ipa,\\"; :put " X-IPA-API-Version: $($CONFIG->"apiVersion"),\\"; :put " Cookie: $IPACookieData\" \\"; :put " http-data=\"{\\\"method\\\":\\\"dnsrecord_mod\\\",\\\"params\\\":[[\\\"$($CONFIG->"dnsZone")\\\",\\\"$RecordName.\\\"],{\\\"aaaarecord\\\":\\\"$RecordContent\\\",\\\"dnsttl\\\":$($CONFIG->"recordTTL")}]}\" \\"; :put " keep-result=no"; :put ""; } /tool fetch url="https://$($CONFIG->"IpaServer")/ipa/session/json" \ http-method=post \ http-header-field="Accept: application/json, \ Content-Type: application/json,\ Referer: https://$($CONFIG->"IpaServer")/ipa,\ X-IPA-API-Version: $($CONFIG->"apiVersion"),\ Cookie: $IPACookieData" \ http-data="{\"method\":\"dnsrecord_mod\",\"params\":[[\"$($CONFIG->"dnsZone")\",\"$RecordName.\"],{\"aaaarecord\":\"$RecordContent\",\"dnsttl\":$($CONFIG->"recordTTL")}]}" \ keep-result=no } :if ($ConfigType = "ipv6prefix" ) do={ :local IPACookieData "$IPACookie"; :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* RouterOS-multiline update-command:"; :put ""; :put "/tool fetch url=\"https://$($CONFIG->"IpaServer")/ipa/session/json\" \\"; :put " http-method=post \\"; :put " http-header-field=\"Accept: application/json, \\"; :put " Content-Type: application/json,\\"; :put " Referer: https://$($CONFIG->"IpaServer")/ipa,\\"; :put " X-IPA-API-Version: $($CONFIG->"apiVersion"),\\"; :put " Cookie: $IPACookieData\" \\"; :put " http-data=\"{\\\"method\\\":\\\"dnsrecord_mod\\\",\\\"params\\\":[[\\\"$($CONFIG->"dnsZone")\\\",\\\"$RecordName.\\\"],{\\\"txtrecord\\\":\\\"\\\\\\\"$RecordContent\\\\\\\"\\\",\\\"dnsttl\\\":$($CONFIG->"recordTTL")}]}\" \\"; :put " keep-result=no"; :put ""; } /tool fetch url="https://$($CONFIG->"IpaServer")/ipa/session/json" \ http-method=post \ http-header-field="Accept: application/json, \ Content-Type: application/json,\ Referer: https://$($CONFIG->"IpaServer")/ipa,\ X-IPA-API-Version: $($CONFIG->"apiVersion"),\ Cookie: $IPACookieData" \ http-data="{\"method\":\"dnsrecord_mod\",\"params\":[[\"$($CONFIG->"dnsZone")\",\"$RecordName.\"],{\"txtrecord\":\"\\\"$RecordContent\\\"\",\"dnsttl\":$($CONFIG->"recordTTL")}]}" \ keep-result=no } :if ( "$($CONFIG->"Debug")" = "true") do={ :put "" } # Output :return "$resultClean"; } # Main Runtime :local UpdateDNS; :local UpdateCurrentDualStackRecordv4; :local UpdateCurrentDualStackRecordv6; :local UpdateCurrentSingleStackRecordv4; :local UpdateCurrentSingleStackRecordv6; :local UpdateCurrentTXTRecordIPv6Prefix; :if ( "$($CONFIG->"Debug")" = "true") do={ :put ""; :put "Debug: Collecting information"; :put "#############################"; :put ""; } :local CurrentIPv4address [$GetLocalDynamicConfig $CONFIG ipv4address]; :local CurrentIPv6address [$GetLocalDynamicConfig $CONFIG ipv6address]; :local CurrentIPv6prefix [$GetLocalDynamicConfig $CONFIG ipv6prefix]; :local CurrentDualStackRecordName "$($CONFIG->"dualStackName").$($CONFIG->"dnsZone")"; :local CurrentSingleStackIPv4RecordName "$($CONFIG->"ipv4OnlyName").$($CONFIG->"dnsZone")"; :local CurrentSingleStackIPv6RecordName "$($CONFIG->"ipv6OnlyName").$($CONFIG->"dnsZone")"; :local CurrentTXTRecordIPv6PrefixName "$($CONFIG->"prefixTxtName").$($CONFIG->"dnsZone")"; :local CurrentDualStackRecordv4 [$GetDynamicDNSRecords $CONFIG ipv4address $CurrentDualStackRecordName ]; :local CurrentDualStackRecordv6 [$GetDynamicDNSRecords $CONFIG ipv6address $CurrentDualStackRecordName ]; :local CurrentSingleStackRecordv4 [$GetDynamicDNSRecords $CONFIG ipv4address $CurrentSingleStackIPv4RecordName ]; :local CurrentSingleStackRecordv6 [$GetDynamicDNSRecords $CONFIG ipv6address $CurrentSingleStackIPv6RecordName ]; :local CurrentTXTRecordIPv6Prefix [$GetDynamicDNSRecords $CONFIG ipv6prefix $CurrentTXTRecordIPv6PrefixName ]; :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* CurrentIPv4address via DHCPv4 on '$($CONFIG->"wanInterface")' is '$CurrentIPv4address'"; :put "* CurrentIPv6address via DHCPv6 on '$($CONFIG->"wanInterface")' is '$CurrentIPv6address'"; :put "* CurrentIPv6prefix via DHCPv6 on '$($CONFIG->"wanInterface")' is '$CurrentIPv6prefix'"; :put ""; :put "* '$CurrentDualStackRecordName' has the following IPv4-(A)-record '$CurrentDualStackRecordv4'"; :put "* '$CurrentDualStackRecordName' has the following IPv6-(AAAA)-record '$CurrentDualStackRecordv6';" :put "* '$CurrentSingleStackIPv4RecordName' has the following IPv4-(A)-record '$CurrentSingleStackRecordv4'"; :put "* '$CurrentSingleStackIPv6RecordName' has the following IPv6-(AAAA)-record '$CurrentSingleStackRecordv6';" :put "* '$CurrentTXTRecordIPv6PrefixName' has the following prefix-(TXT)-record '$CurrentTXTRecordIPv6Prefix'"; } :if ( "$($CONFIG->"Debug")" = "true") do={ :put ""; :put "Debug: Evaluating information"; :put "#############################"; :put ""; } if (("$CurrentDualStackRecordv4" != "$CurrentIPv4address" ) and \ ([:typeof [:toip "$CurrentIPv4address"]] = "ip")) do { :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* '$CurrentDualStackRecordName' needs to update the IPv4-(A)-record"; } :set $UpdateDNS "true"; :set $UpdateCurrentDualStackRecordv4 "true"; } if (("$CurrentDualStackRecordv6" != "$CurrentIPv6address" ) && \ ([:typeof [:toip6 "$CurrentIPv6address"]] = "ip6")) do { :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* '$CurrentDualStackRecordName' needs to update the IPv6-(AAAA)-record"; } :set $UpdateDNS "true"; :set $UpdateCurrentDualStackRecordv6 "true"; } if (("$CurrentSingleStackRecordv4" != "$CurrentIPv4address" ) && \ ([:typeof [:toip "$CurrentIPv4address"]] = "ip")) do { :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* '$CurrentSingleStackIPv4RecordName' needs to update the IPv4-(A)-record"; } :set $UpdateDNS "true"; :set $UpdateCurrentSingleStackRecordv4 "true"; } if (("$CurrentSingleStackRecordv6" != "$CurrentIPv6address" ) && \ ([:typeof [:toip6 "$CurrentIPv6address"]] = "ip6")) do { :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* '$CurrentSingleStackIPv6RecordName' needs to update the IPv6-(AAAA)-record"; } :set $UpdateDNS "true"; :set $UpdateCurrentSingleStackRecordv6 "true"; } if (("$CurrentTXTRecordIPv6Prefix" != "$CurrentIPv6prefix" ) && \ # The following trick/workaround for the check was recomended by forum-sources: ([:typeof ([[:parse "return $CurrentIPv6prefix"]])] = "ip6-prefix")) do { :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* '$CurrentTXTRecordIPv6PrefixName' needs to update the prefix-(TXT)-record"; } :set $UpdateDNS "true"; :set $UpdateCurrentTXTRecordIPv6Prefix "true"; } :put ""; # UPDATE INFORMATION if ("$UpdateDNS" != "true" ) do { :if ( "$($CONFIG->"Debug")" = "true") do={ :put "No DNS-updates-nescessary... everything is aye-ok!" :put ""; } } else { :if ( "$($CONFIG->"Debug")" = "true") do={ :put ""; :put "Debug: Updating DNS-information"; :put "###############################"; :put ""; } :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* Attempting to authenticate against '$($CONFIG->"IpaServer")' as user '$($CONFIG->"IpaUser")'"; } :local IPACookie [$GetIPACookie $CONFIG]; :if ( "$($CONFIG->"Debug")" = "true") do={ :put ""; :put "* The following cookie was recived:"; :put "- '$IPACookie'"; :put ""; } if ("$UpdateCurrentDualStackRecordv4" = "true" ) do { :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* Attempting to update '$CurrentDualStackRecordName' IPv4-(A)-record with '$CurrentIPv4address' "; :put "" } $UpdateDynamicDNSRecord $CONFIG $IPACookie ipv4address $CurrentDualStackRecordName $CurrentIPv4address; } if ("$UpdateCurrentDualStackRecordv6" = "true" ) do { :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* Attempting to update '$CurrentDualStackRecordName' IPv6-(AAAA)-record with '$CurrentIPv6address' "; :put "" } $UpdateDynamicDNSRecord $CONFIG $IPACookie ipv6address $CurrentDualStackRecordName $CurrentIPv6address; } if ("$UpdateCurrentSingleStackRecordv4" = "true" ) do { :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* Attempting to update '$CurrentSingleStackIPv4RecordName' IPv4-(A)-record with '$CurrentIPv4address' "; :put "" } $UpdateDynamicDNSRecord $CONFIG $IPACookie ipv4address $CurrentSingleStackIPv4RecordName $CurrentIPv4address; } if ("$UpdateCurrentSingleStackRecordv6" = "true" ) do { :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* Attempting to update '$CurrentSingleStackIPv6RecordName' IPv4-(A)-record with '$CurrentIPv6address' "; :put "" } $UpdateDynamicDNSRecord $CONFIG $IPACookie ipv6address $CurrentSingleStackIPv6RecordName $CurrentIPv6address; } if ("$UpdateCurrentTXTRecordIPv6Prefix" = "true" ) do { :if ( "$($CONFIG->"Debug")" = "true") do={ :put "* Attempting to update '$CurrentTXTRecordIPv6PrefixName' prefix-(TXT)-record with '$CurrentIPv6prefix' "; :put "" } $UpdateDynamicDNSRecord $CONFIG $IPACookie ipv6prefix $CurrentTXTRecordIPv6PrefixName $CurrentIPv6prefix; } }