2 # update a win2000 DNS server using gss-tsig
3 # tridge@samba.org, October 2002
8 # tridge@samba.org, September 2009
9 # added --verbose, --noverify, --ntype and --nameserver
11 # See draft-ietf-dnsext-gss-tsig-02, RFC2845 and RFC2930
30 'h|help|?' => \
$opt_help,
32 'realm=s' => \
$opt_realm,
33 'nameserver=s' => \
$opt_nameserver,
34 'ntype=s' => \
$opt_ntype,
36 'noverify' => \
$opt_noverify,
37 'verbose' => \
$opt_verbose
40 #########################################
46 Copyright (C) tridge\@samba.org
48 Usage: nsupdate-gss [options] HOST DOMAIN TARGET TTL
51 --wipe wipe all records for this name
52 --add add to any existing records
53 --ntype=TYPE specify name type (default A)
54 --nameserver=server specify a specific nameserver
55 --noverify don't verify the MIC of the reply
56 --verbose show detailed steps
72 my $domain = $ARGV[1];
73 my $target = $ARGV[2];
75 my $alg = "gss.microsoft.com";
79 #######################################################################
80 # signing callback function for TSIG module
86 $key->get_mic(0, $data, $sig);
92 #####################################################################
93 # write a string into a file
96 my($filename) = shift;
99 open(FILE
, ">$filename") || die "can't open $filename";
105 #######################################################################
106 # verify a TSIG signature from a DNS server reply
113 my $tsig = ($packet->additional)[0];
114 $opt_verbose && print "calling sig_data\n";
115 my $sigdata = $tsig->sig_data($packet);
117 $opt_verbose && print "sig_data_done\n";
119 return $context->verify_mic($sigdata, $tsig->{"mac"}, 0);
123 #######################################################################
124 # find the nameserver for the domain
126 sub find_nameserver
($)
128 my $server_name = shift;
129 return Net
::DNS
::Resolver
->new(
130 nameservers
=> [$server_name],
136 #######################################################################
137 # find a server name for a domain - currently uses the NS record
138 sub find_server_name
($)
141 my $res = Net
::DNS
::Resolver
->new;
142 my $srv_query = $res->query("$domain.", "NS");
143 if (!defined($srv_query)) {
147 foreach my $rr (grep { $_->type eq 'NS' } $srv_query->answer) {
148 $server_name = $rr->nsdname;
153 #######################################################################
156 sub negotiate_tkey
($$$$)
159 my $nameserver = shift;
161 my $server_name = shift;
162 my $key_name = shift;
166 my $context = GSSAPI
::Context
->new;
167 my $name = GSSAPI
::Name
->new;
169 # use a principal name of dns/server@REALM
171 print "Using principal dns/" . $server_name . "@" . uc($opt_realm) . "\n";
172 $status = $name->import($name, "dns/" . $server_name . "@" . uc($opt_realm));
174 print "import name: $status\n";
179 GSS_C_REPLAY_FLAG
| GSS_C_MUTUAL_FLAG
|
180 GSS_C_SEQUENCE_FLAG
| GSS_C_CONF_FLAG
|
181 GSS_C_INTEG_FLAG
| GSS_C_DELEG_FLAG
;
184 $status = GSSAPI
::Cred
::acquire_cred
(undef, 120, undef, GSS_C_INITIATE
,
185 my $cred, my $oidset, my $time);
188 print "acquire_cred: $status\n";
192 $opt_verbose && print "creds acquired\n";
194 # call gss_init_sec_context()
195 $status = $context->init($cred, $name, undef, $flags,
196 0, undef, "", undef, my $tok,
199 print "init_sec_context: $status\n";
203 $opt_verbose && print "init done\n";
205 my $gss_query = Net
::DNS
::Packet
->new("$key_name", "TKEY", "IN");
207 # note that Windows2000 uses a SPNEGO wrapping on GSSAPI data sent to the nameserver.
208 # I tested using the gen_negTokenTarg() call from Samba 3.0 and it does work, but
209 # for this utility it is better to use plain GSSAPI/krb5 data so as to reduce the
210 # dependence on external libraries. If we ever want to sign DNS packets using
211 # NTLMSSP instead of krb5 then the SPNEGO wrapper could be used
213 $opt_verbose && print "calling RR new\n";
215 $a = Net
::DNS
::RR
->new(
223 expiration
=> time + 24*60*60,
228 $gss_query->push("answer", $a);
230 my $reply = $nameserver->send($gss_query);
232 if (!defined($reply) || $reply->header->{'rcode'} ne 'NOERROR') {
233 print "failed to send TKEY\n";
237 my $key2 = ($reply->answer)[0]->{"key"};
239 # call gss_init_sec_context() again. Strictly speaking
240 # we should loop until this stops returning CONTINUE
241 # but I'm a lazy bastard
242 $status = $context->init($cred, $name, undef, $flags,
243 0, undef, $key2, undef, $tok,
246 print "init_sec_context step 2: $status\n";
250 if (!$opt_noverify) {
251 $opt_verbose && print "verifying\n";
253 # check the signature on the TKEY reply
254 my $rc = sig_verify
($context, $reply);
256 print "Failed to verify TKEY reply: $rc\n";
260 $opt_verbose && print "verifying done\n";
267 #######################################################################
269 #######################################################################
272 $opt_realm = $domain;
275 # find the name of the DNS server
276 if (!$opt_nameserver) {
277 $opt_nameserver = find_server_name
($domain);
278 if (!defined($opt_nameserver)) {
279 print "Failed to find a DNS server name for $domain\n";
283 $opt_verbose && print "Using DNS server name $opt_nameserver\n";
285 # connect to the nameserver
286 my $nameserver = find_nameserver
($opt_nameserver);
287 if (!defined($nameserver) || $nameserver->{'errorstring'} ne 'NOERROR') {
288 print "Failed to connect to nameserver for domain $domain\n";
293 # use a long random key name
294 my $key_name = int(rand 10000000000000);
296 # negotiate a TKEY key
297 my $gss_context = negotiate_tkey
($nameserver, $domain, $opt_nameserver, $key_name);
298 if (!defined($gss_context)) {
299 print "Failed to negotiate a TKEY\n";
302 $opt_verbose && print "Negotiated TKEY $key_name\n";
304 # construct a signed update
305 my $update = Net
::DNS
::Update
->new($domain);
307 $update->push("pre", yxdomain
("$domain"));
309 $update->push("update", rr_del
("$host.$domain. $opt_ntype"));
312 $update->push("update", rr_add
("$host.$domain. $ttl $opt_ntype $target"));
315 my $sig = Net
::DNS
::RR
->new(
326 Sign_Func
=> \
&gss_sign
,
333 $update->push("additional", $sig);
335 # send the dynamic update
336 my $update_reply = $nameserver->send($update);
338 if (! defined($update_reply)) {
339 print "No reply to dynamic update\n";
343 # make sure it worked
344 my $result = $update_reply->header->{"rcode"};
346 ($opt_verbose || $result ne 'NOERROR') && print "Update gave rcode $result\n";
348 if ($result ne 'NOERROR') {