Selaa lähdekoodia

Merge pull request #4302 from acmesh-official/dev

sync
neil 3 vuotta sitten
vanhempi
sitoutus
8ded524236

+ 25 - 26
.github/workflows/DNS.yml

@@ -81,9 +81,7 @@ jobs:
         if [ "${{ secrets.TokenName5}}" ] ; then
           echo "${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}" >> docker.env
         fi
-        echo "TEST_DNS_NO_WILDCARD" >> docker.env
-        echo "http_proxy" >> docker.env
-        echo "https_proxy" >> docker.env
+
     - name: Run acmetest
       run: cd ../acmetest && ./rundocker.sh  testall
 
@@ -236,7 +234,7 @@ jobs:
 
 
 
-  Solaris:
+  OpenBSD:
     runs-on: macos-12
     needs: FreeBSD
     env:
@@ -254,14 +252,13 @@ jobs:
     - uses: actions/checkout@v2
     - name: Clone acmetest
       run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
-    - uses: vmactions/solaris-vm@v0
+    - uses: vmactions/openbsd-vm@v0
       with:
         envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
+        prepare: pkg_add socat curl
+        usesh: true
         copyback: false
-        prepare: pkgutil -y -i socat
         run: |
-          pkg set-mediator -v -I [email protected] openssl
-          export PATH=/usr/gnu/bin:$PATH
           if [ "${{ secrets.TokenName1}}" ] ; then
             export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
           fi
@@ -283,9 +280,9 @@ jobs:
 
 
 
-  OpenBSD:
+  NetBSD:
     runs-on: macos-12
-    needs: Solaris
+    needs: OpenBSD
     env:
       TEST_DNS : ${{ secrets.TEST_DNS }}
       TestingDomain: ${{ secrets.TestingDomain }}
@@ -301,10 +298,11 @@ jobs:
     - uses: actions/checkout@v2
     - name: Clone acmetest
       run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
-    - uses: vmactions/openbsd-vm@v0
+    - uses: vmactions/netbsd-vm@v0
       with:
         envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
-        prepare: pkg_add socat curl
+        prepare: |
+          pkg_add curl socat
         usesh: true
         copyback: false
         run: |
@@ -329,9 +327,9 @@ jobs:
 
 
 
-  NetBSD:
+  DragonFlyBSD:
     runs-on: macos-12
-    needs: OpenBSD
+    needs: NetBSD
     env:
       TEST_DNS : ${{ secrets.TEST_DNS }}
       TestingDomain: ${{ secrets.TestingDomain }}
@@ -347,11 +345,11 @@ jobs:
     - uses: actions/checkout@v2
     - name: Clone acmetest
       run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
-    - uses: vmactions/netbsd-vm@v0
+    - uses: vmactions/dragonflybsd-vm@v0
       with:
         envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
         prepare: |
-          pkg_add curl socat
+          pkg install -y curl socat
         usesh: true
         copyback: false
         run: |
@@ -376,9 +374,12 @@ jobs:
 
 
 
-  DragonFlyBSD:
+
+
+
+  Solaris:
     runs-on: macos-12
-    needs: NetBSD
+    needs: DragonFlyBSD
     env:
       TEST_DNS : ${{ secrets.TEST_DNS }}
       TestingDomain: ${{ secrets.TestingDomain }}
@@ -390,18 +391,19 @@ jobs:
       DEBUG: ${{ secrets.DEBUG }}
       http_proxy: ${{ secrets.http_proxy }}
       https_proxy: ${{ secrets.https_proxy }}
+      HTTPS_INSECURE: 1 # always set to 1 to ignore https error, sincc Solaris doesn't accept the expired ISRG X1 root
     steps:
     - uses: actions/checkout@v2
     - name: Clone acmetest
       run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
-    - uses: vmactions/dragonflybsd-vm@v0
+    - uses: vmactions/solaris-vm@v0
       with:
-        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
-        prepare: |
-          pkg install -y curl socat
-        usesh: true
+        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
         copyback: false
+        prepare: pkgutil -y -i socat
         run: |
+          pkg set-mediator -v -I [email protected] openssl
+          export PATH=/usr/gnu/bin:$PATH
           if [ "${{ secrets.TokenName1}}" ] ; then
             export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
           fi
@@ -421,6 +423,3 @@ jobs:
           ./letest.sh
 
 
-
-
-

+ 5 - 7
acme.sh

@@ -2561,7 +2561,7 @@ __initHome() {
       _script_home="$(dirname "$_script")"
       _debug "_script_home" "$_script_home"
       if [ -d "$_script_home" ]; then
-        _SCRIPT_HOME="$_script_home"
+        export _SCRIPT_HOME="$_script_home"
       else
         _err "It seems the script home is not correct:$_script_home"
       fi
@@ -6557,7 +6557,7 @@ install() {
   if [ "$_accountemail" ]; then
     _saveaccountconf "ACCOUNT_EMAIL" "$_accountemail"
   fi
-
+  _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)"
   _info OK
 }
 
@@ -6921,8 +6921,6 @@ installOnline() {
     chmod +x $PROJECT_ENTRY
     if ./$PROJECT_ENTRY --install "$@"; then
       _info "Install success!"
-      _initpath
-      _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)"
     fi
 
     cd ..
@@ -7442,17 +7440,17 @@ _process() {
       shift
       ;;
     --home)
-      LE_WORKING_DIR="$2"
+      export LE_WORKING_DIR="$2"
       shift
       ;;
     --cert-home | --certhome)
       _certhome="$2"
-      CERT_HOME="$_certhome"
+      export CERT_HOME="$_certhome"
       shift
       ;;
     --config-home)
       _confighome="$2"
-      LE_CONFIG_HOME="$_confighome"
+      export LE_CONFIG_HOME="$_confighome"
       shift
       ;;
     --useragent)

+ 248 - 0
dnsapi/dns_bunny.sh

@@ -0,0 +1,248 @@
+#!/usr/bin/env sh
+
+## Will be called by acme.sh to add the TXT record via the Bunny DNS API.
+## returns 0 means success, otherwise error.
+
+## Author: nosilver4u <nosilver4u at ewww.io>
+## GitHub: https://github.com/nosilver4u/acme.sh
+
+##
+## Environment Variables Required:
+##
+## BUNNY_API_KEY="75310dc4-ca77-9ac3-9a19-f6355db573b49ce92ae1-2655-3ebd-61ac-3a3ae34834cc"
+##
+
+#####################  Public functions  #####################
+
+## Create the text record for validation.
+## Usage: fulldomain txtvalue
+## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs"
+dns_bunny_add() {
+  fulldomain="$(echo "$1" | _lower_case)"
+  txtvalue=$2
+
+  BUNNY_API_KEY="${BUNNY_API_KEY:-$(_readaccountconf_mutable BUNNY_API_KEY)}"
+  # Check if API Key is set
+  if [ -z "$BUNNY_API_KEY" ]; then
+    BUNNY_API_KEY=""
+    _err "You did not specify Bunny.net API key."
+    _err "Please export BUNNY_API_KEY and try again."
+    return 1
+  fi
+
+  _info "Using Bunny.net dns validation - add record"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  ## save the env vars (key and domain split location) for later automated use
+  _saveaccountconf_mutable BUNNY_API_KEY "$BUNNY_API_KEY"
+
+  ## split the domain for Bunny API
+  if ! _get_base_domain "$fulldomain"; then
+    _err "domain not found in your account for addition"
+    return 1
+  fi
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+  _debug _domain_id "$_domain_id"
+
+  ## Set the header with our post type and auth key
+  export _H1="Accept: application/json"
+  export _H2="AccessKey: $BUNNY_API_KEY"
+  export _H3="Content-Type: application/json"
+  PURL="https://api.bunny.net/dnszone/$_domain_id/records"
+  PBODY='{"Id":'$_domain_id',"Type":3,"Name":"'$_sub_domain'","Value":"'$txtvalue'","ttl":120}'
+
+  _debug PURL "$PURL"
+  _debug PBODY "$PBODY"
+
+  ## the create request - POST
+  ## args: BODY, URL, [need64, httpmethod]
+  response="$(_post "$PBODY" "$PURL" "" "PUT")"
+
+  ## check response
+  if [ "$?" != "0" ]; then
+    _err "error in response: $response"
+    return 1
+  fi
+  _debug2 response "$response"
+
+  ## finished correctly
+  return 0
+}
+
+## Remove the txt record after validation.
+## Usage: fulldomain txtvalue
+## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs"
+dns_bunny_rm() {
+  fulldomain="$(echo "$1" | _lower_case)"
+  txtvalue=$2
+
+  BUNNY_API_KEY="${BUNNY_API_KEY:-$(_readaccountconf_mutable BUNNY_API_KEY)}"
+  # Check if API Key Exists
+  if [ -z "$BUNNY_API_KEY" ]; then
+    BUNNY_API_KEY=""
+    _err "You did not specify Bunny.net API key."
+    _err "Please export BUNNY_API_KEY and try again."
+    return 1
+  fi
+
+  _info "Using Bunny.net dns validation - remove record"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  ## split the domain for Bunny API
+  if ! _get_base_domain "$fulldomain"; then
+    _err "Domain not found in your account for TXT record removal"
+    return 1
+  fi
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+  _debug _domain_id "$_domain_id"
+
+  ## Set the header with our post type and key auth key
+  export _H1="Accept: application/json"
+  export _H2="AccessKey: $BUNNY_API_KEY"
+  ## get URL for the list of DNS records
+  GURL="https://api.bunny.net/dnszone/$_domain_id"
+
+  ## 1) Get the domain/zone records
+  ## the fetch request - GET
+  ## args: URL, [onlyheader, timeout]
+  domain_list="$(_get "$GURL")"
+
+  ## check response
+  if [ "$?" != "0" ]; then
+    _err "error in domain_list response: $domain_list"
+    return 1
+  fi
+  _debug2 domain_list "$domain_list"
+
+  ## 2) search through records
+  ## check for what we are looking for: "Type":3,"Value":"$txtvalue","Name":"$_sub_domain"
+  record="$(echo "$domain_list" | _egrep_o "\"Id\"\s*\:\s*\"*[0-9]+\"*,\s*\"Type\"[^}]*\"Value\"\s*\:\s*\"$txtvalue\"[^}]*\"Name\"\s*\:\s*\"$_sub_domain\"")"
+
+  if [ -n "$record" ]; then
+
+    ## We found records
+    rec_ids="$(echo "$record" | _egrep_o "Id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")"
+    _debug rec_ids "$rec_ids"
+    if [ -n "$rec_ids" ]; then
+      echo "$rec_ids" | while IFS= read -r rec_id; do
+        ## delete the record
+        ## delete URL for removing the one we dont want
+        DURL="https://api.bunny.net/dnszone/$_domain_id/records/$rec_id"
+
+        ## the removal request - DELETE
+        ## args: BODY, URL, [need64, httpmethod]
+        response="$(_post "" "$DURL" "" "DELETE")"
+
+        ## check response (sort of)
+        if [ "$?" != "0" ]; then
+          _err "error in remove response: $response"
+          return 1
+        fi
+        _debug2 response "$response"
+
+      done
+    fi
+  fi
+
+  ## finished correctly
+  return 0
+}
+
+#####################  Private functions below  #####################
+
+## Split the domain provided into the "base domain" and the "start prefix".
+## This function searches for the longest subdomain in your account
+## for the full domain given and splits it into the base domain (zone)
+## and the prefix/record to be added/removed
+## USAGE: fulldomain
+## EG: "_acme-challenge.two.three.four.domain.com"
+## returns
+## _sub_domain="_acme-challenge.two"
+## _domain="three.four.domain.com" *IF* zone "three.four.domain.com" exists
+## _domain_id=234
+## if only "domain.com" exists it will return
+## _sub_domain="_acme-challenge.two.three.four"
+## _domain="domain.com"
+## _domain_id=234
+_get_base_domain() {
+  # args
+  fulldomain="$(echo "$1" | _lower_case)"
+  _debug fulldomain "$fulldomain"
+
+  # domain max legal length = 253
+  MAX_DOM=255
+  page=1
+
+  ## get a list of domains for the account to check thru
+  ## Set the headers
+  export _H1="Accept: application/json"
+  export _H2="AccessKey: $BUNNY_API_KEY"
+  _debug BUNNY_API_KEY "$BUNNY_API_KEY"
+  ## get URL for the list of domains
+  ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}}
+  DOMURL="https://api.bunny.net/dnszone"
+
+  ## while we dont have a matching domain we keep going
+  while [ -z "$found" ]; do
+    ## get the domain list (current page)
+    domain_list="$(_get "$DOMURL")"
+
+    ## check response
+    if [ "$?" != "0" ]; then
+      _err "error in domain_list response: $domain_list"
+      return 1
+    fi
+    _debug2 domain_list "$domain_list"
+
+    i=1
+    while [ $i -gt 0 ]; do
+      ## get next longest domain
+      _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM")
+      ## check we got something back from our cut (or are we at the end)
+      if [ -z "$_domain" ]; then
+        break
+      fi
+      ## we got part of a domain back - grep it out
+      found="$(echo "$domain_list" | _egrep_o "\"Id\"\s*:\s*\"*[0-9]+\"*,\s*\"Domain\"\s*\:\s*\"$_domain\"")"
+      ## check if it exists
+      if [ -n "$found" ]; then
+        ## exists - exit loop returning the parts
+        sub_point=$(_math $i - 1)
+        _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point")
+        _domain_id="$(echo "$found" | _egrep_o "Id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")"
+        _debug _domain_id "$_domain_id"
+        _debug _domain "$_domain"
+        _debug _sub_domain "$_sub_domain"
+        found=""
+        return 0
+      fi
+      ## increment cut point $i
+      i=$(_math $i + 1)
+    done
+
+    if [ -z "$found" ]; then
+      page=$(_math $page + 1)
+      nextpage="https://api.bunny.net/dnszone?page=$page"
+      ## Find the next page if we don't have a match.
+      hasnextpage="$(echo "$domain_list" | _egrep_o "\"HasMoreItems\"\s*:\s*true")"
+      if [ -z "$hasnextpage" ]; then
+        _err "No record and no nextpage in Bunny.net domain search."
+        found=""
+        return 1
+      fi
+      _debug2 nextpage "$nextpage"
+      DOMURL="$nextpage"
+    fi
+
+  done
+
+  ## We went through the entire domain zone list and didn't find one that matched.
+  ## If we ever get here, something is broken in the code...
+  _err "Domain not found in Bunny.net account, but we should never get here!"
+  found=""
+  return 1
+}

+ 3 - 2
dnsapi/dns_cpanel.sh

@@ -13,6 +13,7 @@
 # cPanel_Hostname=hostname
 #
 # Usage: add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+
 # Used to add txt record
 dns_cpanel_add() {
   fulldomain=$1
@@ -120,7 +121,7 @@ _myget() {
 
 _get_root() {
   _myget 'json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzones'
-  _domains=$(echo "$_result" | sed 's/.*\(zones.*\[\).*/\1/' | cut -d':' -f2 | sed 's/"//g' | sed 's/{//g')
+  _domains=$(echo "$_result" | _egrep_o '"[a-z0-9\.\-]*":\["; cPanel first' | cut -d':' -f1 | sed 's/"//g' | sed 's/{//g')
   _debug "_result is: $_result"
   _debug "_domains is: $_domains"
   if [ -z "$_domains" ]; then
@@ -146,7 +147,7 @@ _findentry() {
   _debug "In _findentry"
   #returns id of dns entry, if it exists
   _myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzone_records&domain=$_domain"
-  _id=$(echo "$_result" | sed "s/.*\(line.*$fulldomain.*$txtvalue\).*/\1/" | cut -d ':' -f 2 | cut -d ',' -f 1)
+  _id=$(echo "$_result" | sed -e "s/},{/},\n{/g" | grep "$fulldomain" | grep "$txtvalue" | _egrep_o 'line":[0-9]+' | cut -d ':' -f 2)
   _debug "_result is: $_result"
   _debug "fulldomain. is $fulldomain."
   _debug "txtvalue is $txtvalue"

+ 2 - 3
dnsapi/dns_dgon.sh

@@ -192,6 +192,7 @@ _get_base_domain() {
   ## get URL for the list of domains
   ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}}
   DOMURL="https://api.digitalocean.com/v2/domains"
+  found=""
 
   ## while we dont have a matching domain we keep going
   while [ -z "$found" ]; do
@@ -205,9 +206,7 @@ _get_base_domain() {
     fi
     _debug2 domain_list "$domain_list"
 
-    ## for each shortening of our $fulldomain, check if it exists in the $domain_list
-    ## can never start on 1 (aka whole $fulldomain) as $fulldomain starts with "_acme-challenge"
-    i=2
+    i=1
     while [ $i -gt 0 ]; do
       ## get next longest domain
       _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM")

+ 40 - 26
dnsapi/dns_dnsservices.sh

@@ -13,8 +13,8 @@ DNSServices_API=https://dns.services/api
 
 #Usage: dns_dnsservices_add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 dns_dnsservices_add() {
-  fulldomain=$1
-  txtvalue=$2
+  fulldomain="$1"
+  txtvalue="$2"
 
   _info "Using dns.services to create ACME DNS challenge"
   _debug2 add_fulldomain "$fulldomain"
@@ -61,8 +61,8 @@ dns_dnsservices_add() {
 #Usage: fulldomain txtvalue
 #Description: Remove the txt record after validation.
 dns_dnsservices_rm() {
-  fulldomain=$1
-  txtvalue=$2
+  fulldomain="$1"
+  txtvalue="$2"
 
   _info "Using dns.services to remove DNS record $fulldomain TXT $txtvalue"
   _debug rm_fulldomain "$fulldomain"
@@ -117,36 +117,40 @@ _setup_headers() {
 }
 
 _get_root() {
-  domain=$1
+  domain="$1"
   _debug2 _get_root "Get the root domain of ${domain} for DNS API"
 
   # Setup _get() and _post() headers
   #_setup_headers
 
   result=$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/dns")
+  result2="$(printf "%s\n" "$result" | tr '[' '\n' | grep '"name"')"
+  result3="$(printf "%s\n" "$result2" | tr '}' '\n' | grep '"name"' | sed "s,^\,,,g" | sed "s,$,},g")"
+  useResult=""
   _debug2 _get_root "Got the following root domain(s) $result"
   _debug2 _get_root "- JSON: $result"
 
-  if [ "$(echo "$result" | grep -c '"name"')" -gt "1" ]; then
+  if [ "$(printf "%s\n" "$result" | tr '}' '\n' | grep -c '"name"')" -gt "1" ]; then
     checkMultiZones="true"
     _debug2 _get_root "- multiple zones found"
   else
     checkMultiZones="false"
-
+    _debug2 _get_root "- single zone found"
   fi
 
   # Find/isolate the root zone to work with in createRecord() and deleteRecord()
   rootZone=""
   if [ "$checkMultiZones" = "true" ]; then
-    rootZone=$(for zone in $(echo "$result" | tr -d '\n' ' '); do
-      if [ "$(echo "$domain" | grep "$zone")" != "" ]; then
-        _debug2 _get_root "- trying to figure out if $zone is in $domain"
-        echo "$zone"
-        break
-      fi
-    done)
+    #rootZone=$(for x in $(printf "%s" "${result3}" | tr ',' '\n' | sed -n 's/.*"name":"\(.*\)",.*/\1/p'); do if [ "$(echo "$domain" | grep "$x")" != "" ]; then echo "$x"; fi; done)
+    rootZone=$(for x in $(printf "%s\n" "${result3}" | tr ',' '\n' | grep name | cut -d'"' -f4); do if [ "$(echo "$domain" | grep "$x")" != "" ]; then echo "$x"; fi; done)
+    if [ "$rootZone" != "" ]; then
+      _debug2 _rootZone "- root zone for $domain is $rootZone"
+    else
+      _err "Could not find root zone for $domain, is it correctly typed?"
+      return 1
+    fi
   else
-    rootZone=$(echo "$result" | _egrep_o '"name":"[^"]*' | cut -d'"' -f4)
+    rootZone=$(echo "$result" | tr '}' '\n' | _egrep_o '"name":"[^"]*' | cut -d'"' -f4)
     _debug2 _get_root "- only found 1 domain in API: $rootZone"
   fi
 
@@ -155,14 +159,18 @@ _get_root() {
     return 1
   fi
 
+  # Make sure we use the correct API zone data
+  useResult="$(printf "%s\n" "${result3}" tr ',' '\n' | grep "$rootZone")"
+  _debug2 _useResult "useResult=$useResult"
+
   # Setup variables used by other functions to communicate with DNS.Services API
-  #zoneInfo=$(echo "$result" | sed "s,\"zones,\n&,g" | grep zones | cut -d'[' -f2 | cut -d']' -f1 | tr '}' '\n' | grep "\"$rootZone\"")
-  zoneInfo=$(echo "$result" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"name":")([^"]*)"(.*)$,\2,g' | grep "\"$rootZone\"")
+  #zoneInfo=$(printf "%s\n" "$useResult" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"name":")([^"]*)"(.*)$,\2,g')
+  zoneInfo=$(printf "%s\n" "$useResult" | tr ',' '\n' | grep '"name"' | cut -d'"' -f4)
   rootZoneName="$rootZone"
-  subDomainName="$(echo "$domain" | sed "s,\.$rootZone,,g")"
-  subDomainNameClean="$(echo "$domain" | sed "s,_acme-challenge.,,g")"
-  rootZoneDomainID=$(echo "$result" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"domain_id":")([^"]*)"(.*)$,\2,g')
-  rootZoneServiceID=$(echo "$result" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"service_id":")([^"]*)"(.*)$,\2,g')
+  subDomainName="$(printf "%s\n" "$domain" | sed "s,\.$rootZone,,g")"
+  subDomainNameClean="$(printf "%s\n" "$domain" | sed "s,_acme-challenge.,,g")"
+  rootZoneDomainID=$(printf "%s\n" "$useResult" | tr ',' '\n' | grep domain_id | cut -d'"' -f4)
+  rootZoneServiceID=$(printf "%s\n" "$useResult" | tr ',' '\n' | grep service_id | cut -d'"' -f4)
 
   _debug2 _zoneInfo "Zone info from API  : $zoneInfo"
   _debug2 _get_root "Root zone name      : $rootZoneName"
@@ -175,13 +183,17 @@ _get_root() {
 }
 
 createRecord() {
-  fulldomain=$1
+  fulldomain="$1"
   txtvalue="$2"
 
   # Get root domain information - needed for DNS.Services API communication
   if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then
     _get_root "$fulldomain"
   fi
+  if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then
+    _err "Something happend - could not get the API zone information"
+    return 1
+  fi
 
   _debug2 createRecord "CNAME TXT value is: $txtvalue"
 
@@ -203,8 +215,8 @@ createRecord() {
 }
 
 deleteRecord() {
-  fulldomain=$1
-  txtvalue=$2
+  fulldomain="$1"
+  txtvalue="$2"
 
   _log deleteRecord "Deleting $fulldomain TXT $txtvalue record"
 
@@ -213,8 +225,10 @@ deleteRecord() {
   fi
 
   result="$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID")"
-  recordInfo="$(echo "$result" | sed -e 's/:{/:{\n/g' -e 's/},/\n},\n/g' | grep "${txtvalue}")"
-  recordID="$(echo "$recordInfo" | sed -e 's/:{/:{\n/g' -e 's/},/\n},\n/g' | grep "${txtvalue}" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"id":")([^"]*)"(.*)$,\2,g')"
+  #recordInfo="$(echo "$result" | sed -e 's/:{/:{\n/g' -e 's/},/\n},\n/g' | grep "${txtvalue}")"
+  #recordID="$(echo "$recordInfo" | sed -e 's/:{/:{\n/g' -e 's/},/\n},\n/g' | grep "${txtvalue}" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"id":")([^"]*)"(.*)$,\2,g')"
+  recordID="$(printf "%s\n" "$result" | tr '}' '\n' | grep -- "$txtvalue" | tr ',' '\n' | grep '"id"' | cut -d'"' -f4)"
+  _debug2 _recordID "recordID used for deletion of record: $recordID"
 
   if [ -z "$recordID" ]; then
     _info "Record $fulldomain TXT $txtvalue not found or already deleted"

+ 2 - 2
dnsapi/dns_opnsense.sh

@@ -137,7 +137,7 @@ _get_root() {
   domain=$1
   i=2
   p=1
-  if _opns_rest "GET" "/domain/searchDomain"; then
+  if _opns_rest "GET" "/domain/searchMasterDomain"; then
     _domain_response="$response"
   else
     return 1
@@ -150,7 +150,7 @@ _get_root() {
       return 1
     fi
     _debug h "$h"
-    id=$(echo "$_domain_response" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"1\",\"type\":\"master\",[^.]*,\"domainname\":\"${h}\"" | cut -d ':' -f 2 | cut -d '"' -f 2)
+    id=$(echo "$_domain_response" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"1\",\"type\":\"master\",\"domainname\":\"${h}\"" | cut -d ':' -f 2 | cut -d '"' -f 2)
     if [ -n "$id" ]; then
       _debug id "$id"
       _host=$(printf "%s" "$domain" | cut -d . -f 1-$p)

+ 94 - 0
dnsapi/dns_selfhost.sh

@@ -0,0 +1,94 @@
+#!/usr/bin/env sh
+#
+#       Author: Marvin Edeler
+#       Report Bugs here: https://github.com/Marvo2011/acme.sh/issues/1
+#	Last Edit: 17.02.2022
+
+dns_selfhost_add() {
+  fulldomain=$1
+  txt=$2
+  _info "Calling acme-dns on selfhost"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txt"
+
+  SELFHOSTDNS_UPDATE_URL="https://selfhost.de/cgi-bin/api.pl"
+
+  # Get values, but don't save until we successfully validated
+  SELFHOSTDNS_USERNAME="${SELFHOSTDNS_USERNAME:-$(_readaccountconf_mutable SELFHOSTDNS_USERNAME)}"
+  SELFHOSTDNS_PASSWORD="${SELFHOSTDNS_PASSWORD:-$(_readaccountconf_mutable SELFHOSTDNS_PASSWORD)}"
+  # These values are domain dependent, so read them from there
+  SELFHOSTDNS_MAP="${SELFHOSTDNS_MAP:-$(_readdomainconf SELFHOSTDNS_MAP)}"
+  # Selfhost api can't dynamically add TXT record,
+  # so we have to store the last used RID of the domain to support a second RID for wildcard domains
+  # (format: 'fulldomainA:lastRid fulldomainB:lastRid ...')
+  SELFHOSTDNS_MAP_LAST_USED_INTERNAL=$(_readdomainconf SELFHOSTDNS_MAP_LAST_USED_INTERNAL)
+
+  if [ -z "${SELFHOSTDNS_USERNAME:-}" ] || [ -z "${SELFHOSTDNS_PASSWORD:-}" ]; then
+    _err "SELFHOSTDNS_USERNAME and SELFHOSTDNS_PASSWORD must be set"
+    return 1
+  fi
+
+  # get the domain entry from SELFHOSTDNS_MAP
+  # only match full domains (at the beginning of the string or with a leading whitespace),
+  # e.g. don't match mytest.example.com or sub.test.example.com for test.example.com
+  # if the domain is defined multiple times only the last occurance will be matched
+  mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain)(:[[:digit:]]+)([:]?[[:digit:]]*)(.*)/\2\3\4/p")
+  _debug2 mapEntry "$mapEntry"
+  if test -z "$mapEntry"; then
+    _err "SELFHOSTDNS_MAP must contain the fulldomain incl. prefix and at least one RID"
+    return 1
+  fi
+
+  # get the RIDs from the map entry
+  rid1=$(echo "$mapEntry" | cut -d: -f2)
+  rid2=$(echo "$mapEntry" | cut -d: -f3)
+
+  # read last used rid domain
+  lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain:[[:digit:]]+)(.*)/\2/p")
+  _debug2 lastUsedRidForDomainEntry "$lastUsedRidForDomainEntry"
+  lastUsedRidForDomain=$(echo "$lastUsedRidForDomainEntry" | cut -d: -f2)
+
+  rid="$rid1"
+  if [ "$lastUsedRidForDomain" = "$rid" ] && ! test -z "$rid2"; then
+    rid="$rid2"
+  fi
+
+  _info "Trying to add $txt on selfhost for rid: $rid"
+
+  data="?username=$SELFHOSTDNS_USERNAME&password=$SELFHOSTDNS_PASSWORD&rid=$rid&content=$txt"
+  response="$(_get "$SELFHOSTDNS_UPDATE_URL$data")"
+
+  if ! echo "$response" | grep "200 OK" >/dev/null; then
+    _err "Invalid response of acme-dns for selfhost"
+    return 1
+  fi
+
+  # write last used rid domain
+  newLastUsedRidForDomainEntry="$fulldomain:$rid"
+  if ! test -z "$lastUsedRidForDomainEntry"; then
+    # replace last used rid entry for domain
+    SELFHOSTDNS_MAP_LAST_USED_INTERNAL=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/$lastUsedRidForDomainEntry/$newLastUsedRidForDomainEntry/p")
+  else
+    # add last used rid entry for domain
+    if test -z "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL"; then
+      SELFHOSTDNS_MAP_LAST_USED_INTERNAL="$newLastUsedRidForDomainEntry"
+    else
+      SELFHOSTDNS_MAP_LAST_USED_INTERNAL="$SELFHOSTDNS_MAP_LAST_USED_INTERNAL $newLastUsedRidForDomainEntry"
+    fi
+  fi
+
+  # Now that we know the values are good, save them
+  _saveaccountconf_mutable SELFHOSTDNS_USERNAME "$SELFHOSTDNS_USERNAME"
+  _saveaccountconf_mutable SELFHOSTDNS_PASSWORD "$SELFHOSTDNS_PASSWORD"
+  # These values are domain dependent, so store them there
+  _savedomainconf SELFHOSTDNS_MAP "$SELFHOSTDNS_MAP"
+  _savedomainconf SELFHOSTDNS_MAP_LAST_USED_INTERNAL "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL"
+}
+
+dns_selfhost_rm() {
+  fulldomain=$1
+  txt=$2
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txt"
+  _info "Creating and removing of records is not supported by selfhost API, will not delete anything."
+}

+ 2 - 2
dnsapi/dns_world4you.sh

@@ -195,7 +195,7 @@ _get_paketnr() {
   fqdn="$1"
   form="$2"
 
-  domains=$(echo "$form" | grep 'header-paket-domain' | sed 's/<[^>]*>//g' | sed 's/^.*>\([^>]*\)$/\1/')
+  domains=$(echo "$form" | grep '<ul class="nav header-paket-list">' | sed 's/<li/\n<li/g' | sed 's/<[^>]*>/ /g' | sed 's/^.*>\([^>]*\)$/\1/')
   domain=''
   for domain in $domains; do
     if _contains "$fqdn" "$domain\$"; then
@@ -210,7 +210,7 @@ _get_paketnr() {
   TLD="$domain"
   _debug domain "$domain"
   RECORD=$(echo "$fqdn" | cut -c"1-$((${#fqdn} - ${#TLD} - 1))")
-  PAKETNR=$(echo "$form" | grep "data-textfilter=\".* $domain " | _tail_n 1 | sed "s|.*$WORLD4YOU_API/\\([0-9]*\\)/.*|\\1|")
+  PAKETNR=$(echo "$domains" | grep "$domain" | sed 's/^[^,]*, *\([0-9]*\).*$/\1/')
   return 0
 }
 

+ 264 - 0
dnsapi/dns_yc.sh

@@ -0,0 +1,264 @@
+#!/usr/bin/env sh
+
+#YC_Zone_ID="" # DNS Zone ID
+#YC_Folder_ID="" # YC Folder ID
+#YC_SA_ID="" # Service Account ID
+#YC_SA_Key_ID="" # Service Account IAM Key ID
+#YC_SA_Key_File_Path="/path/to/private.key" # Path to private.key use instead of YC_SA_Key_File_PEM_b64
+#YC_SA_Key_File_PEM_b64="" # Base64 content of private.key use instead of YC_SA_Key_File_Path
+YC_Api="https://dns.api.cloud.yandex.net/dns/v1"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_yc_add() {
+  fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
+  txtvalue=$2
+
+  YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readaccountconf_mutable YC_SA_Key_File_PEM_b64)}"
+  YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readaccountconf_mutable YC_SA_Key_File_Path)}"
+
+  if [ "$YC_SA_Key_File_PEM_b64" ]; then
+    echo "$YC_SA_Key_File_PEM_b64" | _dbase64 >private.key
+    YC_SA_Key_File="private.key"
+    _savedomainconf YC_SA_Key_File_PEM_b64 "$YC_SA_Key_File_PEM_b64"
+  else
+    YC_SA_Key_File="$YC_SA_Key_File_Path"
+    _savedomainconf YC_SA_Key_File_Path "$YC_SA_Key_File_Path"
+  fi
+
+  YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
+  YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
+  YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
+  YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
+
+  if [ "$YC_SA_ID" ] && [ "$YC_SA_Key_ID" ] && [ "$YC_SA_Key_File" ]; then
+    if [ -f "$YC_SA_Key_File" ]; then
+      if _isRSA "$YC_SA_Key_File" >/dev/null 2>&1; then
+        if [ "$YC_Zone_ID" ]; then
+          _savedomainconf YC_Zone_ID "$YC_Zone_ID"
+          _savedomainconf YC_SA_ID "$YC_SA_ID"
+          _savedomainconf YC_SA_Key_ID "$YC_SA_Key_ID"
+        elif [ "$YC_Folder_ID" ]; then
+          _savedomainconf YC_Folder_ID "$YC_Folder_ID"
+          _saveaccountconf_mutable YC_SA_ID "$YC_SA_ID"
+          _saveaccountconf_mutable YC_SA_Key_ID "$YC_SA_Key_ID"
+          _clearaccountconf_mutable YC_Zone_ID
+          _clearaccountconf YC_Zone_ID
+        else
+          _err "You didn't specify a Yandex Cloud Zone ID or Folder ID yet."
+          return 1
+        fi
+      else
+        _err "YC_SA_Key_File not a RSA file(_isRSA function return false)."
+        return 1
+      fi
+    else
+      _err "YC_SA_Key_File not found in path $YC_SA_Key_File."
+      return 1
+    fi
+  else
+    _clearaccountconf YC_Zone_ID
+    _clearaccountconf YC_Folder_ID
+    _clearaccountconf YC_SA_ID
+    _clearaccountconf YC_SA_Key_ID
+    _clearaccountconf YC_SA_Key_File_PEM_b64
+    _clearaccountconf YC_SA_Key_File_Path
+    _err "You didn't specify a YC_SA_ID or YC_SA_Key_ID or YC_SA_Key_File."
+    return 1
+  fi
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _debug "Getting txt records"
+  if ! _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then
+    _err "Error: $response"
+    return 1
+  fi
+
+  _info "Adding record"
+  if _yc_rest POST "zones/$_domain_id:upsertRecordSets" "{\"merges\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":[\"$txtvalue\"]}]}"; then
+    if _contains "$response" "\"done\": true"; then
+      _info "Added, OK"
+      return 0
+    else
+      _err "Add txt record error."
+      return 1
+    fi
+  fi
+  _err "Add txt record error."
+  return 1
+
+}
+
+#fulldomain txtvalue
+dns_yc_rm() {
+  fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
+  txtvalue=$2
+
+  YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
+  YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
+  YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
+  YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _debug "Getting txt records"
+  if _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then
+    exists_txtvalue=$(echo "$response" | _normalizeJson | _egrep_o "\"data\".*\][^,]*" | _egrep_o "[^:]*$")
+    _debug exists_txtvalue "$exists_txtvalue"
+  else
+    _err "Error: $response"
+    return 1
+  fi
+
+  if _yc_rest POST "zones/$_domain_id:updateRecordSets" "{\"deletions\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":$exists_txtvalue}]}"; then
+    if _contains "$response" "\"done\": true"; then
+      _info "Delete, OK"
+      return 0
+    else
+      _err "Delete record error."
+      return 1
+    fi
+  fi
+  _err "Delete record error."
+  return 1
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+  domain=$1
+  i=1
+  p=1
+
+  # Use Zone ID directly if provided
+  if [ "$YC_Zone_ID" ]; then
+    if ! _yc_rest GET "zones/$YC_Zone_ID"; then
+      return 1
+    else
+      if echo "$response" | tr -d " " | _egrep_o "\"id\":\"$YC_Zone_ID\"" >/dev/null; then
+        _domain=$(echo "$response" | _egrep_o "\"zone\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
+        if [ "$_domain" ]; then
+          _cutlength=$((${#domain} - ${#_domain}))
+          _sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cutlength")
+          _domain_id=$YC_Zone_ID
+          return 0
+        else
+          return 1
+        fi
+      else
+        return 1
+      fi
+    fi
+  fi
+
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug h "$h"
+    if [ -z "$h" ]; then
+      #not valid
+      return 1
+    fi
+    if [ "$YC_Folder_ID" ]; then
+      if ! _yc_rest GET "zones?folderId=$YC_Folder_ID"; then
+        return 1
+      fi
+    else
+      echo "You didn't specify a Yandex Cloud Folder ID."
+      return 1
+    fi
+    if _contains "$response" "\"zone\": \"$h\""; then
+      _domain_id=$(echo "$response" | _normalizeJson | _egrep_o "[^{]*\"zone\":\"$h\"[^}]*" | _egrep_o "\"id\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')
+      _debug _domain_id "$_domain_id"
+      if [ "$_domain_id" ]; then
+        _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+        _domain=$h
+        return 0
+      fi
+      return 1
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+_yc_rest() {
+  m=$1
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+
+  if [ ! "$YC_Token" ]; then
+    _debug "Login"
+    _yc_login
+  else
+    _debug "Token already exists. Skip Login."
+  fi
+
+  token_trimmed=$(echo "$YC_Token" | tr -d '"')
+
+  export _H1="Content-Type: application/json"
+  export _H2="Authorization: Bearer $token_trimmed"
+
+  if [ "$m" != "GET" ]; then
+    _debug data "$data"
+    response="$(_post "$data" "$YC_Api/$ep" "" "$m")"
+  else
+    response="$(_get "$YC_Api/$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}
+
+_yc_login() {
+  header=$(echo "{\"typ\":\"JWT\",\"alg\":\"PS256\",\"kid\":\"$YC_SA_Key_ID\"}" | _normalizeJson | _base64 | _url_replace)
+  _debug header "$header"
+
+  _current_timestamp=$(_time)
+  _expire_timestamp=$(_math "$_current_timestamp" + 1200) # 20 minutes
+  payload=$(echo "{\"iss\":\"$YC_SA_ID\",\"aud\":\"https://iam.api.cloud.yandex.net/iam/v1/tokens\",\"iat\":$_current_timestamp,\"exp\":$_expire_timestamp}" | _normalizeJson | _base64 | _url_replace)
+  _debug payload "$payload"
+
+  #signature=$(printf "%s.%s" "$header" "$payload" | ${ACME_OPENSSL_BIN:-openssl} dgst -sign "$YC_SA_Key_File -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1" | _base64 | _url_replace )
+  _signature=$(printf "%s.%s" "$header" "$payload" | _sign "$YC_SA_Key_File" "sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1" | _url_replace)
+  _debug2 _signature "$_signature"
+
+  rm -rf "$YC_SA_Key_File"
+
+  _jwt=$(printf "{\"jwt\": \"%s.%s.%s\"}" "$header" "$payload" "$_signature")
+  _debug2 _jwt "$_jwt"
+
+  export _H1="Content-Type: application/json"
+  _iam_response="$(_post "$_jwt" "https://iam.api.cloud.yandex.net/iam/v1/tokens" "" "POST")"
+  _debug3 _iam_response "$(echo "$_iam_response" | _normalizeJson)"
+
+  YC_Token="$(echo "$_iam_response" | _normalizeJson | _egrep_o "\"iamToken\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')"
+  _debug3 YC_Token
+
+  return 0
+}