1#!/bin/sh
2# Copyright (C) 2020 Michel Stam <michel@reverze.net>
3#
4# This file is part of uacme.
5#
6# uacme is free software: you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# uacme is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14# General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
19# Commands
20DIG=dig
21NSUPDATE=nsupdate
22
23# Files
24# RNDC_KEY_{NSUPDATE,DIG}
25#   if you wish to specify an RDC key for TSIG transactions, do so
26#   here. If you do, also make sure /etc/named.conf specifies the
27#   key "KEYNAME"; in the zone that must be updated (and disallow
28#   all others for safety)
29RNDC_KEY_NSUPDATE=
30RNDC_KEY_DIG=
31
32# Arguments
33METHOD=$1
34TYPE=$2
35IDENT=$3
36TOKEN=$4
37AUTH=$5
38
39ns_getdomain()
40{
41    local domain=$1
42
43    [ -n "$domain" ] || return
44    set -- $($DIG ${RNDC_KEY_DIG:+-k ${RNDC_KEY_DIG}} +noall +authority "$domain" SOA 2>/dev/null)
45
46    echo $1
47}
48
49ns_getprimary()
50{
51    local domain=$1
52
53    [ -n "$domain" ] || return
54    set -- $($DIG ${RNDC_KEY_DIG:+-k ${RNDC_KEY_DIG}} +short "$domain" SOA 2>/dev/null)
55
56    echo $1
57}
58
59ns_getall()
60{
61    local domain=$1
62
63    [ -n "$domain" ] || return 1
64
65    $DIG ${RNDC_KEY_DIG:+-k ${RNDC_KEY_DIG}} +short "$domain" NS 2>/dev/null
66}
67
68ns_ispresent()
69{
70    local fqhn="$1"
71    local expect="$2"
72    local domain=$(ns_getdomain "$fqhn")
73    local nameservers=$(ns_getall "$domain")
74    local res
75    local ret
76
77    for NS in $nameservers; do
78        OLDIFS="${IFS}"
79        IFS='.'
80        set -- $($DIG ${RNDC_KEY_DIG:+-k ${RNDC_KEY_DIG}} +short "@$NS" "$fqhn" TXT 2>/dev/null)
81        IFS="${OLDIFS}"
82        { [ "$*" = "$expect" ] || [ "$*" = "\"$expect\"" ] ; } || return 1
83    done
84
85    return 0
86}
87
88ns_doupdate()
89{
90    local fqhn="$1"
91    local challenge="$2"
92    local ttl=600
93    local domain=$(ns_getdomain "$fqhn")
94    local nameserver=$(ns_getprimary "$domain")
95    local action=
96
97    [ -n "$nameserver" ] || return
98
99    if [ -n "${challenge}" ]; then
100            action="update add ${fqhn}. ${ttl} IN TXT ${challenge}"
101    else
102            action="update del ${fqhn}."
103    fi
104
105    $NSUPDATE ${RNDC_KEY_NSUPDATE:+-k ${RNDC_KEY_NSUPDATE}} -v <<-EOF
106            server ${nameserver}
107            ${action}
108            send
109EOF
110
111    return $?
112}
113
114ns_update()
115{
116    local fqhn="$1"
117    local challenge="$2"
118    local count=0
119    local res
120
121    res=1
122    while [ $res -ne 0 ]; do
123        if [ $count -eq 0 ]; then
124            ns_doupdate "$fqhn" "$challenge"
125            res=$?
126            [ $res -eq 0 ] || break
127        else
128            sleep 1
129        fi
130
131        count=$(((count + 1) % 5))
132        ns_ispresent "$fqhn" "$challenge"
133        res=$?
134    done
135
136    return $?
137}
138
139ARGS=5
140E_BADARGS=85
141
142if [ $# -ne "$ARGS" ]; then
143    echo "Usage: $(basename "$0") method type ident token auth" 1>&2
144    exit $E_BADARGS
145fi
146
147case "$METHOD" in
148    "begin")
149        case "$TYPE" in
150            dns-01)
151                ns_update "_acme-challenge.$IDENT" "$AUTH"
152                exit $?
153                ;;
154            *)
155                exit 1
156                ;;
157        esac
158        ;;
159
160    "done"|"failed")
161        case "$TYPE" in
162            dns-01)
163                ns_update "_acme-challenge.$IDENT"
164                exit $?
165                ;;
166            *)
167                exit 1
168                ;;
169        esac
170        ;;
171
172    *)
173        echo "$0: invalid method" 1>&2
174        exit 1
175esac
176