Skip to content

Commit a20e57e

Browse files
committed
Add support for Unicode domain names (IDNA2008/TR46)
1 parent 6b51ad5 commit a20e57e

File tree

2 files changed

+67
-14
lines changed

2 files changed

+67
-14
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ For OPENPGPKEY usage, `danectl` also requires `perl` and `gpg`.
142142

143143
For SMIMEA usage, `danectl` also requires `perl` and `openssl`.
144144

145+
For non-ASCII domain names, `danectl` also requires *GNU* `idn2`.
146+
145147
The `danectl-zonefile` output adapter requires `perl`.
146148

147149
The `danectl-nsupdate` output adapter requires `perl`.

danectl

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -639,23 +639,32 @@ into input for the nsupdate(1) dynamic DNS update utility.
639639
640640
REQUIREMENTS
641641
642+
Danectl is written in Bourne shell, and should work on any platform
643+
that has the following prerequisites.
644+
642645
In all cases, danectl requires /bin/sh and host (or drill).
643646
647+
On systems like Solaris, /usr/xpg4/bin/sh is used instead of /bin/sh.
648+
644649
For TLSA usage, danectl also requires ls, sed, grep, readlink, certbot,
645650
openssl, sha256sum, and root privileges (for certbot).
646651
647-
For SSHFP usage, danectl also requires sed and ssh-keygen.
652+
For SSHFP usage, danectl also requires sed, perl and ssh-keygen.
648653
649654
For OPENPGPKEY usage, danectl also requires perl and gpg.
650655
651656
For SMIMEA usage, danectl also requires perl and openssl.
652657
653-
On systems like Solaris, /usr/xpg4/bin/sh is used instead of /bin/sh.
658+
For non-ASCII domain names, danectl also requires GNU idn2.
654659
655660
The danectl-zonefile output adapter requires perl.
656661
657662
The danectl-nsupdate output adapter requires perl.
658663
664+
For reloading affected services on key rollover, any system with
665+
systemctl, service, rcctl, or service scripts in
666+
/etc/init.d, /etc/rc.d, or /usr/local/etc/rc.d should work.
667+
659668
LICENSE
660669
661670
Danectl is released under the terms of the GPLv2+
@@ -742,7 +751,7 @@ default_certbot_command()
742751
fetch_dns()
743752
{
744753
dns_type="$1"
745-
dns_name="$2"
754+
dns_name="`idna $2`"
746755

747756
if [ $HAVE_HOST = 1 ]
748757
then
@@ -834,7 +843,7 @@ tlsa_role()
834843
for t in $tlsa
835844
do
836845
domain="`case \"$t\" in *.) echo $t;; *) echo $t.$certname.;; esac`"
837-
printf "$prefix$domain\tIN\tTLSA\t3 1 1 $hash\n"
846+
printf "$prefix`idna $domain`\tIN\tTLSA\t3 1 1 $hash\n"
838847
done
839848
}
840849

@@ -877,7 +886,7 @@ tlsa_check()
877886
else
878887
[ "$missing" = 0 ] && echo "; Missing $certname current (must be published)"
879888
missing=1
880-
printf "$domain\tIN\tTLSA\t3 1 1 $hash_current\n"
889+
printf "`idna $domain`\tIN\tTLSA\t3 1 1 $hash_current\n"
881890
fi
882891
done
883892
missing=0
@@ -890,7 +899,7 @@ tlsa_check()
890899
else
891900
[ "$missing" = 0 ] && echo "; Missing $certname next (must be published)"
892901
missing=1
893-
printf "$domain\tIN\tTLSA\t3 1 1 $hash_next\n"
902+
printf "`idna $domain`\tIN\tTLSA\t3 1 1 $hash_next\n"
894903
fi
895904
done
896905
superfluous=0
@@ -1128,12 +1137,26 @@ remove_item()
11281137
sed 's/ /\n/g' | grep -v "$item"
11291138
}
11301139

1140+
# Output the ASCII version (IDNA2008/TR46) of a Unicode domain name
1141+
1142+
idna()
1143+
{
1144+
domainname="$1"
1145+
if echo "$domainname" | LANG=C grep -q '^[a-zA-Z0-9-][a-zA-Z0-9.-]*$'
1146+
then
1147+
echo $domainname
1148+
else
1149+
case "`which idn2 2>/dev/null`" in /*) ;; *) die "Failed to find idn2";; esac
1150+
idn2 -l -- "$domainname"
1151+
fi
1152+
}
1153+
11311154
# Output a form of certname that is suitable for use in a shell variable identifier
11321155

11331156
shcertname()
11341157
{
11351158
certname="$1"
1136-
echo "$certname" | LANG=C sed 's/[^a-zA-Z0-9]/_/g'
1159+
idna "$certname" | LANG=C sed 's/[^a-zA-Z0-9]/_/g'
11371160
}
11381161

11391162
# Check for host or drill
@@ -1237,15 +1260,15 @@ check_sshfp_prerequisites()
12371260

12381261
sshfp()
12391262
{
1240-
hostname="$1"
1263+
hostname="`idna $1`"
12411264
ssh-keygen -r "$hostname" | sed -e 's/ /. /' -e 's/ / /' -e 's/ / /'
12421265
}
12431266

12441267
# Check that SSHFP RRs are published
12451268

12461269
sshfp_check()
12471270
{
1248-
hostname="$1"
1271+
hostname="`idna $1`"
12491272
perl -e '
12501273
use strict;
12511274
use warnings;
@@ -1299,13 +1322,20 @@ openpgpkey()
12991322
$encoded =~ s/^ // if '$oneline' == 1 && '$spaces' == 1;
13001323
return $encoded;
13011324
}
1325+
sub idna
1326+
{
1327+
my $domain = shift;
1328+
return $domain if $domain =~ /^[a-zA-Z0-9.-]+$/;
1329+
chop($domain = `idn2 -l -- $domain`);
1330+
return $domain;
1331+
}
13021332
my $origin;
13031333
my $prefix;
13041334
my $comment = "";
13051335
my $key_hex = "";
13061336
while (<>)
13071337
{
1308-
$origin = $1, next if /^\$ORIGIN (\S+)/;
1338+
$origin = idna($1), next if /^\$ORIGIN (\S+)/;
13091339
$comment .= $_, next if /^;/;
13101340
$prefix = $1, next if /^([0-9a-f]+) TYPE61 \\# \d+ \($/;
13111341
$key_hex .= $1, next if /^\s+([0-9a-f]+)$/;
@@ -1343,13 +1373,20 @@ openpgpkey_check()
13431373
$encoded =~ s/^ // if '$oneline' == 1 && '$spaces' == 1;
13441374
return $encoded;
13451375
}
1376+
sub idna
1377+
{
1378+
my $domain = shift;
1379+
return $domain if $domain =~ /^[a-zA-Z0-9.-]+$/;
1380+
chop($domain = `idn2 -l -- $domain`);
1381+
return $domain;
1382+
}
13461383
my $origin;
13471384
my $prefix;
13481385
my $comment = "";
13491386
my $key_hex = "";
13501387
while (<>)
13511388
{
1352-
$origin = $1, next if /^\$ORIGIN (\S+)/;
1389+
$origin = idna($1), next if /^\$ORIGIN (\S+)/;
13531390
$comment .= $_, next if /^;/;
13541391
$prefix = $1, next if /^([0-9a-f]+) TYPE61 \\# \d+ \($/;
13551392
$key_hex .= $1, next if /^\s+([0-9a-f]+)$/;
@@ -1366,7 +1403,7 @@ openpgpkey_check()
13661403
my $hash = Digest->new("SHA-256");
13671404
$hash->add($localpart);
13681405
$prefix = substr($hash->hexdigest(), 0, 28 * 2);
1369-
$origin = "_openpgpkey.$domain";
1406+
$origin = "_openpgpkey." . idna($domain);
13701407
}
13711408
# Is it published? Are any other keys published?
13721409
my $missing = $key_exists;
@@ -1426,6 +1463,13 @@ smimea()
14261463
use Digest;
14271464
binmode(STDIN);
14281465
$/ = undef;
1466+
sub idna
1467+
{
1468+
my $domain = shift;
1469+
return $domain if $domain =~ /^[a-zA-Z0-9.-]+$/;
1470+
chop($domain = `idn2 -l -- $domain`);
1471+
return $domain;
1472+
}
14291473
my $cert = unpack("H*", <STDIN>);
14301474
$cert =~ s/(.{1,56})/\t$1\n/g if '$oneline' == 0;
14311475
$cert =~ s/^\t// if '$oneline' == 0;
@@ -1437,7 +1481,7 @@ smimea()
14371481
my $hash = Digest->new("SHA-256");
14381482
$hash->add($localpart);
14391483
my $prefix = substr($hash->hexdigest(), 0, 28 * 2);
1440-
my $origin = "_smimecert.$domain.";
1484+
my $origin = "_smimecert." . idna($domain) . ".";
14411485
print("; $email\n");
14421486
my $start = ('$oneline' == 0) ? "(\n\t" : "";
14431487
my $middle = ('$oneline' == 0) ? "\n\t" : " ";
@@ -1468,6 +1512,13 @@ smimea_check()
14681512
use Digest;
14691513
binmode(STDIN);
14701514
$/ = undef;
1515+
sub idna
1516+
{
1517+
my $domain = shift;
1518+
return $domain if $domain =~ /^[a-zA-Z0-9.-]+$/;
1519+
chop($domain = `idn2 -l -- $domain`);
1520+
return $domain;
1521+
}
14711522
my $cert = unpack("H*", <STDIN>);
14721523
$cert =~ s/(.{1,56})/\t$1\n/g if '$oneline' == 0;
14731524
$cert =~ s/^\t// if '$oneline' == 0;
@@ -1480,7 +1531,7 @@ smimea_check()
14801531
$hash->add($localpart);
14811532
my $comment = "; $email\n";
14821533
my $prefix = substr($hash->hexdigest(), 0, 28 * 2);
1483-
my $origin = "_smimecert.$domain.";
1534+
my $origin = "_smimecert." . idna($domain) . ".";
14841535
my $missing = 1;
14851536
my $superfluous = "";
14861537
my $start = ('$oneline' == 0) ? "(\n\t" : "";

0 commit comments

Comments
 (0)