xref: /titanic_52/usr/src/lib/smbsrv/libsmbns/common/smbns_dyndns.c (revision c1ecd8b9404ee0d96d93f02e82c441b9bb149a3d)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <assert.h>
29 #include <errno.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <strings.h>
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <netinet/in.h>
36 #include <arpa/inet.h>
37 #include <sys/time.h>
38 #include <unistd.h>
39 #include <string.h>
40 #include <arpa/nameser.h>
41 #include <resolv.h>
42 #include <netdb.h>
43 #include <rpc/rpc.h>
44 #include <syslog.h>
45 #include <gssapi/gssapi.h>
46 #include <kerberosv5/krb5.h>
47 #include <net/if.h>
48 
49 #include <smbns_dyndns.h>
50 #include <smbns_krb.h>
51 
52 /* internal use, in dyndns_add_entry */
53 #define	DEL_NONE		2
54 /* Maximum retires if not authoritative */
55 #define	MAX_AUTH_RETRIES 3
56 /* Number of times to retry a DNS query */
57 #define	DYNDNS_MAX_QUERY_RETRIES 3
58 /* Timeout value, in seconds, for DNS query responses */
59 #define	DYNDNS_QUERY_TIMEOUT 2
60 
61 static uint16_t dns_msgid;
62 mutex_t dns_msgid_mtx;
63 
64 int
65 dns_msgid_init(void)
66 {
67 	struct __res_state res;
68 
69 	bzero(&res, sizeof (struct __res_state));
70 	if (res_ninit(&res) < 0)
71 		return (-1);
72 
73 	(void) mutex_lock(&dns_msgid_mtx);
74 	dns_msgid = res.id;
75 	(void) mutex_unlock(&dns_msgid_mtx);
76 	res_ndestroy(&res);
77 	return (0);
78 }
79 
80 static int
81 dns_get_msgid(void)
82 {
83 	uint16_t id;
84 
85 	(void) mutex_lock(&dns_msgid_mtx);
86 	id = ++dns_msgid;
87 	(void) mutex_unlock(&dns_msgid_mtx);
88 	return (id);
89 }
90 
91 /*
92  * XXX The following should be removed once head/arpa/nameser_compat.h
93  * defines BADSIG, BADKEY, BADTIME macros
94  */
95 #ifndef	BADSIG
96 #define	BADSIG ns_r_badsig
97 #endif /* BADSIG */
98 
99 #ifndef	BADKEY
100 #define	BADKEY ns_r_badkey
101 #endif /* BADKEY */
102 
103 #ifndef	BADTIME
104 #define	BADTIME ns_r_badtime
105 #endif /* BADTIME */
106 
107 /*
108  * dyndns_msg_err
109  * Display error message for DNS error code found in the DNS header in
110  * reply message.
111  * Parameters:
112  *   err: DNS errer code
113  * Returns:
114  *   None
115  */
116 static void
117 dyndns_msg_err(int err)
118 {
119 	switch (err) {
120 	case NOERROR:
121 		break;
122 	case FORMERR:
123 		syslog(LOG_ERR, "DNS message format error\n");
124 		break;
125 	case SERVFAIL:
126 		syslog(LOG_ERR, "DNS server internal error\n");
127 		break;
128 	case NXDOMAIN:
129 		syslog(LOG_ERR, "DNS entry should exist but does not exist\n");
130 		break;
131 	case NOTIMP:
132 		syslog(LOG_ERR, "DNS opcode not supported\n");
133 		break;
134 	case REFUSED:
135 		syslog(LOG_ERR, "DNS operation refused\n");
136 		break;
137 	case YXDOMAIN:
138 		syslog(LOG_ERR, "DNS entry shouldn't exist but does exist\n");
139 		break;
140 	case YXRRSET:
141 		syslog(LOG_ERR, "DNS RRSet shouldn't exist but does exist\n");
142 		break;
143 	case NXRRSET:
144 		syslog(LOG_ERR, "DNS RRSet should exist but does not exist\n");
145 		break;
146 	case NOTAUTH:
147 		syslog(LOG_ERR, "DNS server is not authoritative "
148 		    "for specified zone\n");
149 		break;
150 	case NOTZONE:
151 		syslog(LOG_ERR, "Name in Prereq or Update section not "
152 		    "within specified zone\n");
153 		break;
154 	case BADSIG:
155 		syslog(LOG_ERR, "Bad transaction signature (TSIG)");
156 		break;
157 	case BADKEY:
158 		syslog(LOG_ERR, "Bad transaction key (TKEY)");
159 		break;
160 	case BADTIME:
161 		syslog(LOG_ERR, "Time not synchronized");
162 		break;
163 
164 	default:
165 		syslog(LOG_ERR, "Unknown DNS error\n");
166 	}
167 }
168 
169 /*
170  * display_stat
171  * Display GSS error message from error code.  This routine is used to display
172  * the mechanism independent and mechanism specific error messages for GSS
173  * routines.  The major status error code is the mechanism independent error
174  * code and the minor status error code is the mechanism specific error code.
175  * Parameters:
176  *   maj: GSS major status
177  *   min: GSS minor status
178  * Returns:
179  *   None
180  */
181 static void
182 display_stat(OM_uint32 maj, OM_uint32 min)
183 {
184 	gss_buffer_desc msg;
185 	OM_uint32 msg_ctx = 0;
186 	OM_uint32 min2;
187 	(void) gss_display_status(&min2, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID,
188 	    &msg_ctx, &msg);
189 	syslog(LOG_ERR, "dyndns: GSS major status error: %s\n",
190 	    (char *)msg.value);
191 	(void) gss_display_status(&min2, min, GSS_C_MECH_CODE, GSS_C_NULL_OID,
192 	    &msg_ctx, &msg);
193 	syslog(LOG_ERR, "dyndns: GSS minor status error: %s\n",
194 	    (char *)msg.value);
195 }
196 
197 static char *
198 dyndns_put_nshort(char *buf, uint16_t val)
199 {
200 	uint16_t nval;
201 
202 	nval = htons(val);
203 	(void) memcpy(buf, &nval, sizeof (uint16_t));
204 	buf += sizeof (uint16_t);
205 	return (buf);
206 }
207 
208 static char *
209 dyndns_get_nshort(char *buf, uint16_t *val)
210 {
211 	uint16_t nval;
212 
213 	(void) memcpy(&nval, buf, sizeof (uint16_t));
214 	*val = ntohs(nval);
215 	buf += sizeof (uint16_t);
216 	return (buf);
217 }
218 
219 static char *
220 dyndns_put_nlong(char *buf, uint32_t val)
221 {
222 	uint32_t lval;
223 
224 	lval = htonl(val);
225 	(void) memcpy(buf, &lval, sizeof (uint32_t));
226 	buf += sizeof (uint32_t);
227 	return (buf);
228 }
229 
230 static char *
231 dyndns_put_byte(char *buf, char val)
232 {
233 	*buf = val;
234 	buf++;
235 	return (buf);
236 }
237 
238 static char *
239 dyndns_put_int(char *buf, int val)
240 {
241 	(void) memcpy(buf, &val, sizeof (int));
242 	buf += sizeof (int);
243 	return (buf);
244 }
245 
246 /*
247  * dyndns_stuff_str
248  * Converts a domain string by removing periods and replacing with a byte value
249  * of how many characters following period.  A byte value is placed in front
250  * to indicate how many characters before first period.  A NULL character is
251  * placed at the end. i.e. host.procom.com -> 4host5procom3com0
252  * Buffer space checking is done by caller.
253  * Parameters:
254  *   ptr : address of pointer to buffer to store converted string
255  *   zone: domain name string
256  * Returns:
257  *   ptr: address of pointer to next available buffer space
258  *   -1 : error
259  *    0 : success
260  */
261 static int
262 dyndns_stuff_str(char **ptr, char *zone)
263 {
264 	int len;
265 	char *lenPtr, *zonePtr;
266 
267 	for (zonePtr = zone; *zonePtr; ) {
268 		lenPtr = *ptr;
269 		*ptr = *ptr + 1;
270 		len = 0;
271 		while (*zonePtr != '.' && *zonePtr != 0) {
272 			*ptr = dyndns_put_byte(*ptr, *zonePtr);
273 			zonePtr++;
274 			len++;
275 		}
276 		*lenPtr = len;
277 		if (*zonePtr == '.')
278 			zonePtr++;
279 	}
280 	*ptr = dyndns_put_byte(*ptr, 0);
281 	return (0);
282 }
283 
284 /*
285  * dyndns_build_header
286  * Build the header for DNS query and DNS update request message.
287  * Parameters:
288  *   ptr               : address of pointer to buffer to store header
289  *   buf_len           : buffer length
290  *   msg_id            : message id
291  *   query_req         : use REQ_QUERY for query message or REQ_UPDATE for
292  *                       update message
293  *   quest_zone_cnt    : number of question record for query message or
294  *                       number of zone record for update message
295  *   ans_prereq_cnt    : number of answer record for query message or
296  *                       number of prerequisite record for update message
297  *   nameser_update_cnt: number of name server for query message or
298  *                       number of update record for update message
299  *   addit_cnt         : number of additional record
300  *   flags             : query flags word
301  * Returns:
302  *   ptr: address of pointer to next available buffer space
303  *   -1 : error
304  *    0 : success
305  */
306 static int
307 dyndns_build_header(char **ptr, int buf_len, uint16_t msg_id, int query_req,
308     uint16_t quest_zone_cnt, uint16_t ans_prereq_cnt,
309     uint16_t nameser_update_cnt, uint16_t addit_cnt, int flags)
310 {
311 	uint16_t opcode;
312 
313 	if (buf_len < 12) {
314 		syslog(LOG_ERR, "dyndns: no more buf for header section\n");
315 		return (-1);
316 	}
317 
318 	*ptr = dyndns_put_nshort(*ptr, msg_id);	/* mesg ID */
319 	if (query_req == REQ_QUERY)
320 		opcode = ns_o_query;	/* query msg */
321 	else
322 		opcode = ns_o_update << 11;	/* update msg */
323 	opcode |= flags;
324 	/* mesg opcode */
325 	*ptr = dyndns_put_nshort(*ptr, opcode);
326 	/* zone record count */
327 	*ptr = dyndns_put_nshort(*ptr, quest_zone_cnt);
328 	/* prerequiste record count */
329 	*ptr = dyndns_put_nshort(*ptr, ans_prereq_cnt);
330 	/* update record count */
331 	*ptr = dyndns_put_nshort(*ptr, nameser_update_cnt);
332 	/* additional record count */
333 	*ptr = dyndns_put_nshort(*ptr, addit_cnt);
334 
335 	return (0);
336 }
337 
338 /*
339  * dyndns_build_quest_zone
340  * Build the question section for query message or zone section for
341  * update message.
342  * Parameters:
343  *   ptr    : address of pointer to buffer to store question or zone section
344  *   buf_len: buffer length
345  *   name   : question or zone name
346  *   type   : type of question or zone
347  *   class  : class of question or zone
348  * Returns:
349  *   ptr: address of pointer to next available buffer space
350  *   -1 : error
351  *    0 : success
352  */
353 static int
354 dyndns_build_quest_zone(char **ptr, int buf_len, char *name, int type,
355 	int class)
356 {
357 	char *zonePtr;
358 
359 	if ((strlen(name) + 6) > buf_len) {
360 		syslog(LOG_ERR, "dyndns: no more buf "
361 		    "for question/zone section\n");
362 		return (-1);
363 	}
364 
365 	zonePtr = *ptr;
366 	(void) dyndns_stuff_str(&zonePtr, name);
367 	*ptr = zonePtr;
368 	*ptr = dyndns_put_nshort(*ptr, type);
369 	*ptr = dyndns_put_nshort(*ptr, class);
370 	return (0);
371 }
372 
373 /*
374  * dyndns_build_update
375  * Build update section of update message for adding and removing a record.
376  * If the ttl value is 0 then this message is for record deletion.
377  *
378  * Parameters:
379  *   ptr     : address of pointer to buffer to store update section
380  *   buf_len : buffer length
381  *   name    : resource name of this record
382  *   type    : type of this record
383  *   class   : class of this record
384  *   ttl     : time-to-live, cached time of this entry by others and not
385  *             within DNS database, a zero value for record(s) deletion
386  *   data    : data of this resource record
387  *   forw_rev: UPDATE_FORW for forward zone, UPDATE_REV for reverse zone
388  *   add_del : UPDATE_ADD for adding entry, UPDATE_DEL for removing zone
389  *   del_type: DEL_ONE for deleting one entry, DEL_ALL for deleting all
390  *             entries of the same resource name.  Only valid for UPDATE_DEL.
391  * Returns:
392  *   ptr: address of pointer to next available buffer space
393  *   -1 : error
394  *    0 : success
395  */
396 static int
397 dyndns_build_update(char **ptr, int buf_len, char *name, int type, int class,
398 	uint32_t ttl, char *data, int forw_rev, int add_del, int del_type)
399 {
400 	char *namePtr;
401 	int rec_len, data_len;
402 
403 	rec_len = strlen(name) + 10;
404 	if (add_del == UPDATE_ADD) {
405 		if (forw_rev == UPDATE_FORW)
406 			data_len = 4;
407 		else
408 			data_len = strlen(data) + 2;
409 	} else {
410 		if (del_type == DEL_ALL)
411 			data_len = 0;
412 		else if (forw_rev == UPDATE_FORW)
413 			data_len = 4;
414 		else
415 			data_len = strlen(data) + 2;
416 	}
417 
418 	if (rec_len + data_len > buf_len) {
419 		syslog(LOG_ERR, "dyndns: no more buf for update section\n");
420 		return (-1);
421 	}
422 
423 	namePtr = *ptr;
424 	(void) dyndns_stuff_str(&namePtr, name);
425 	*ptr = namePtr;
426 	*ptr = dyndns_put_nshort(*ptr, type);
427 	*ptr = dyndns_put_nshort(*ptr, class);
428 	*ptr = dyndns_put_nlong(*ptr, ttl);
429 
430 	if (add_del == UPDATE_DEL && del_type == DEL_ALL) {
431 		*ptr = dyndns_put_nshort(*ptr, 0);
432 		return (0);
433 	}
434 
435 	if (forw_rev == UPDATE_FORW) {
436 		*ptr = dyndns_put_nshort(*ptr, 4);
437 		*ptr = dyndns_put_int(*ptr, inet_addr(data));	/* ip address */
438 	} else {
439 		*ptr = dyndns_put_nshort(*ptr, strlen(data)+2);
440 		namePtr = *ptr;
441 		(void) dyndns_stuff_str(&namePtr, data);	/* hostname */
442 		*ptr = namePtr;
443 	}
444 	return (0);
445 }
446 
447 /*
448  * dyndns_build_tkey
449  * Build TKEY section to establish security context for secure dynamic DNS
450  * update.  DNS header and question sections need to be build before this
451  * section.  The TKEY data are the tokens generated during security context
452  * establishment and the TKEY message is used to transmit those tokens, one
453  * at a time, to the DNS server.
454  * Parameters:
455  *   ptr       : address of pointer to buffer to store TKEY
456  *   buf_len   : buffer length
457  *   name      : key name, must be unique and same as for TSIG record
458  *   key_expire: expiration time of this key in second
459  *   data      : TKEY data
460  *   data_size : data size
461  * Returns:
462  *   ptr: address of the pointer to the next available buffer space
463  *   -1 : error
464  *    0 : success
465  */
466 static int
467 dyndns_build_tkey(char **ptr, int buf_len, char *name, int key_expire,
468 	char *data, int data_size)
469 {
470 	char *namePtr;
471 	struct timeval tp;
472 
473 	if (strlen(name)+2 + 45 + data_size > buf_len) {
474 		syslog(LOG_ERR, "dyndns: no more buf for TKEY record\n");
475 		return (-1);
476 	}
477 
478 	namePtr = *ptr;
479 	(void) dyndns_stuff_str(&namePtr, name);	/* unique global name */
480 	*ptr = namePtr;
481 	*ptr = dyndns_put_nshort(*ptr, ns_t_tkey);
482 	*ptr = dyndns_put_nshort(*ptr, ns_c_any);
483 	*ptr = dyndns_put_nlong(*ptr, 0);
484 	/* 19 + 14 + data_size + 2 */
485 	*ptr = dyndns_put_nshort(*ptr, 35 + data_size);
486 	namePtr = *ptr;
487 	(void) dyndns_stuff_str(&namePtr, "gss.microsoft.com");
488 	*ptr = namePtr;
489 	(void) gettimeofday(&tp, 0);
490 	*ptr = dyndns_put_nlong(*ptr, tp.tv_sec);	/* inception */
491 	/* expiration, 86400 */
492 	*ptr = dyndns_put_nlong(*ptr, tp.tv_sec + key_expire);
493 	*ptr = dyndns_put_nshort(*ptr, MODE_GSS_API);	/* mode: gss-api */
494 	*ptr = dyndns_put_nshort(*ptr, 0);		/* error */
495 	*ptr = dyndns_put_nshort(*ptr, data_size);	/* key size */
496 	(void) memcpy(*ptr, data, data_size);	/* key data */
497 	*ptr += data_size;
498 	*ptr = dyndns_put_nshort(*ptr, 0);	/* other */
499 	return (0);
500 }
501 
502 /*
503  * dyndns_build_tsig
504  * Build TSIG section for secure dynamic DNS update.  This routine will be
505  * called twice.  First called with TSIG_UNSIGNED, and second with TSIG_SIGNED.
506  * The TSIG data is NULL and ignored for TSIG_UNSIGNED and is the update request
507  * message encrypted for TSIG_SIGNED.  The message id must be the same id as the
508  * one in the update request before it is encrypted.
509  * Parameters:
510  *   ptr        : address of pointer to buffer to store TSIG
511  *   buf_len    : buffer length
512  *   msg_id     : message id
513  *   name       : key name, must be the same as in TKEY record
514  *   fudge_time : amount of error time allow in seconds
515  *   data       : TSIG data if TSIG_SIGNED, otherwise NULL
516  *   data_size  : size of data, otherwise 0 if data is NULL
517  *   data_signed: TSIG_SIGNED to indicate data is signed and encrypted,
518  *                otherwise TSIG_UNSIGNED
519  * Returns:
520  *   ptr: address of pointer to next available buffer space
521  *   -1 : error
522  *    0 : success
523  */
524 static int
525 dyndns_build_tsig(char **ptr, int buf_len, int msg_id, char *name,
526 	int fudge_time, char *data, int data_size, int data_signed)
527 {
528 	char *namePtr;
529 	struct timeval tp;
530 	int signtime, fudge, rec_len;
531 
532 	if (data_signed == TSIG_UNSIGNED)
533 		rec_len = strlen(name)+2 + 37;
534 	else
535 		rec_len = strlen(name)+2 + 45 + data_size;
536 
537 	if (rec_len > buf_len) {
538 		syslog(LOG_ERR, "dyndns: no more buf for TSIG record\n");
539 		return (-1);
540 	}
541 
542 	namePtr = *ptr;
543 	(void) dyndns_stuff_str(&namePtr, name);	/* unique global name */
544 	*ptr = namePtr;
545 	if (data_signed == TSIG_SIGNED)
546 		*ptr = dyndns_put_nshort(*ptr, ns_t_tsig);
547 	*ptr = dyndns_put_nshort(*ptr, ns_c_any);
548 	*ptr = dyndns_put_nlong(*ptr, 0);
549 	if (data_signed == TSIG_SIGNED) {
550 		/* 19 + 10 + data_size + 6 */
551 		*ptr = dyndns_put_nshort(*ptr, 35 + data_size);
552 	}
553 	namePtr = *ptr;
554 	(void) dyndns_stuff_str(&namePtr, "gss.microsoft.com");
555 	*ptr = namePtr;
556 	(void) gettimeofday(&tp, 0);
557 	signtime = tp.tv_sec >> 16;
558 	*ptr = dyndns_put_nlong(*ptr, signtime);	/* sign time */
559 	fudge = tp.tv_sec << 16;
560 	fudge |= fudge_time;
561 	*ptr = dyndns_put_nlong(*ptr, fudge);	/* fudge time */
562 	if (data_signed == TSIG_SIGNED) {
563 		/* signed data size */
564 		*ptr = dyndns_put_nshort(*ptr, data_size);
565 		(void) memcpy(*ptr, data, data_size);	/* signed data */
566 		*ptr += data_size;
567 		*ptr = dyndns_put_nshort(*ptr, msg_id);	/* original id */
568 	}
569 	*ptr = dyndns_put_nshort(*ptr, 0);	/* error */
570 	*ptr = dyndns_put_nshort(*ptr, 0);	/* other */
571 	return (0);
572 }
573 
574 /*
575  * dyndns_open_init_socket
576  * This routine creates a SOCK_STREAM or SOCK_DGRAM socket and initializes it
577  * by doing bind() and setting linger option to off.
578  *
579  * Parameters:
580  *   sock_type: SOCK_STREAM for TCP or SOCK_DGRAM for UDP
581  *   dest_addr: destination address in network byte order
582  *   port     : destination port number
583  * Returns:
584  *   descriptor: descriptor referencing the created socket
585  *   -1        : error
586  */
587 static int
588 dyndns_open_init_socket(int sock_type, unsigned long dest_addr, int port)
589 {
590 	int s;
591 	struct sockaddr_in my_addr;
592 	struct linger l;
593 	struct sockaddr_in serv_addr;
594 
595 	if ((s = socket(AF_INET, sock_type, 0)) == -1) {
596 		syslog(LOG_ERR, "dyndns: socket err\n");
597 		return (-1);
598 	}
599 
600 	l.l_onoff = 0;
601 	if (setsockopt(s, SOL_SOCKET, SO_LINGER,
602 	    (char *)&l, sizeof (l)) == -1) {
603 		syslog(LOG_ERR, "dyndns: setsocket err\n");
604 		(void) close(s);
605 		return (-1);
606 	}
607 
608 	bzero(&my_addr, sizeof (my_addr));
609 	my_addr.sin_family = AF_INET;
610 	my_addr.sin_port = htons(0);
611 	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
612 
613 	if (bind(s, (struct sockaddr *)&my_addr, sizeof (my_addr)) < 0) {
614 		syslog(LOG_ERR, "dyndns: client bind err\n");
615 		(void) close(s);
616 		return (-1);
617 	}
618 
619 	serv_addr.sin_family = AF_INET;
620 	serv_addr.sin_port = htons(port);
621 	serv_addr.sin_addr.s_addr = dest_addr;
622 
623 	if (connect(s, (struct sockaddr *)&serv_addr,
624 	    sizeof (struct sockaddr_in)) < 0) {
625 		syslog(LOG_ERR, "dyndns: client connect err (%s)\n",
626 		    strerror(errno));
627 		(void) close(s);
628 		return (-1);
629 	}
630 
631 	return (s);
632 }
633 
634 /*
635  * dyndns_build_tkey_msg
636  * This routine is used to build the TKEY message to transmit GSS tokens
637  * during GSS security context establishment for secure DNS update.  The
638  * TKEY message format uses the DNS query message format.  The TKEY section
639  * is the answer section of the query message format.
640  * Microsoft uses a value of 86400 seconds (24 hours) for key expiration time.
641  * Parameters:
642  *   buf     : buffer to build and store TKEY message
643  *   key_name: a unique key name, this same key name must be also be used in
644  *             the TSIG message
645  *   out_tok : TKEY message data (GSS tokens)
646  * Returns:
647  *   id          : message id of this TKEY message
648  *   message size: the size of the TKEY message
649  *   -1          : error
650  */
651 static int
652 dyndns_build_tkey_msg(char *buf, char *key_name, uint16_t *id,
653 	gss_buffer_desc *out_tok)
654 {
655 	int queryReq, zoneCount, preqCount, updateCount, additionalCount;
656 	int zoneType, zoneClass;
657 	char *bufptr;
658 
659 	queryReq = REQ_QUERY;
660 	/* query section of query request */
661 	zoneCount = 1;
662 	/* answer section of query request */
663 	preqCount = 1;
664 	updateCount = 0;
665 	additionalCount = 0;
666 
667 	(void) memset(buf, 0, MAX_TCP_SIZE);
668 	bufptr = buf;
669 	*id = dns_get_msgid();
670 
671 	/* add TCP length info that follows this field */
672 	bufptr = dyndns_put_nshort(bufptr,
673 	    26 + (strlen(key_name)+2)*2 + 35 + out_tok->length);
674 
675 	if (dyndns_build_header(&bufptr, BUFLEN_TCP(bufptr, buf), *id, queryReq,
676 	    zoneCount, preqCount, updateCount, additionalCount, 0) == -1) {
677 		return (-1);
678 	}
679 
680 	zoneType = ns_t_tkey;
681 	zoneClass = ns_c_in;
682 	if (dyndns_build_quest_zone(&bufptr, BUFLEN_TCP(bufptr, buf), key_name,
683 	    zoneType, zoneClass) == -1) {
684 		return (-1);
685 	}
686 
687 	if (dyndns_build_tkey(&bufptr, BUFLEN_TCP(bufptr, buf), key_name,
688 	    86400, out_tok->value, out_tok->length) == -1) {
689 		return (-1);
690 	}
691 
692 	return (bufptr - buf);
693 }
694 
695 /*
696  * dyndns_establish_sec_ctx
697  * This routine is used to establish a security context with the DNS server
698  * by building TKEY messages and sending them to the DNS server.  TKEY messages
699  * are also received from the DNS server for processing.   The security context
700  * establishment is done with the GSS client on the system producing a token
701  * and sending the token within the TKEY message to the GSS server on the DNS
702  * server.  The GSS server then processes the token and then send a TKEY reply
703  * message with a new token to be processed by the GSS client.  The GSS client
704  * processes the new token and then generates a new token to be sent to the
705  * GSS server.  This cycle is continued until the security establishment is
706  * done.  TCP is used to send and receive TKEY messages.
707  * Parameters:
708  *   cred_handle  : handle to credential
709  *   s           : socket descriptor to DNS server
710  *   key_name    : TKEY key name
711  *   dns_hostname: fully qualified DNS hostname
712  *   oid         : contains Kerberos 5 object identifier
713  * Returns:
714  *   gss_context    : handle to security context
715  */
716 static int
717 dyndns_establish_sec_ctx(gss_ctx_id_t *gss_context, gss_cred_id_t cred_handle,
718     int s, char *key_name, char *dns_hostname, gss_OID oid)
719 {
720 	uint16_t id, rid, rsz;
721 	char buf[MAX_TCP_SIZE], buf2[MAX_TCP_SIZE];
722 	int ret;
723 	char *service_name, *tmpptr;
724 	int service_sz;
725 	OM_uint32 min, maj, time_rec;
726 	gss_buffer_desc service_buf, in_tok, out_tok;
727 	gss_name_t target_name;
728 	gss_buffer_desc *inputptr;
729 	int gss_flags;
730 	OM_uint32 ret_flags;
731 	int buf_sz;
732 
733 	service_sz = strlen(dns_hostname) + 5;
734 	service_name = (char *)malloc(sizeof (char) * service_sz);
735 	if (service_name == NULL) {
736 		syslog(LOG_ERR, "Malloc failed for %d bytes ", service_sz);
737 		return (-1);
738 	}
739 	(void) snprintf(service_name, service_sz, "DNS@%s", dns_hostname);
740 	service_buf.value = service_name;
741 	service_buf.length = strlen(service_name)+1;
742 	if ((maj = gss_import_name(&min, &service_buf,
743 	    GSS_C_NT_HOSTBASED_SERVICE, &target_name)) != GSS_S_COMPLETE) {
744 		display_stat(maj, min);
745 		(void) free(service_name);
746 		return (-1);
747 	}
748 	(void) free(service_name);
749 
750 	inputptr = GSS_C_NO_BUFFER;
751 	*gss_context = GSS_C_NO_CONTEXT;
752 	gss_flags = GSS_C_MUTUAL_FLAG | GSS_C_DELEG_FLAG | GSS_C_REPLAY_FLAG |
753 	    GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG;
754 	do {
755 		maj = gss_init_sec_context(&min, cred_handle, gss_context,
756 		    target_name, oid, gss_flags, 0, NULL, inputptr, NULL,
757 		    &out_tok, &ret_flags, &time_rec);
758 
759 		if (maj != GSS_S_COMPLETE && maj != GSS_S_CONTINUE_NEEDED) {
760 			assert(gss_context);
761 			if (*gss_context != GSS_C_NO_CONTEXT)
762 				(void) gss_delete_sec_context(&min,
763 				    gss_context, NULL);
764 
765 			display_stat(maj, min);
766 			(void) gss_release_name(&min, &target_name);
767 			return (-1);
768 		}
769 
770 		if ((maj == GSS_S_COMPLETE) &&
771 		    !(ret_flags & GSS_C_REPLAY_FLAG)) {
772 			syslog(LOG_ERR, "dyndns: No GSS_C_REPLAY_FLAG\n");
773 			if (out_tok.length > 0)
774 				(void) gss_release_buffer(&min, &out_tok);
775 			(void) gss_release_name(&min, &target_name);
776 			return (-1);
777 		}
778 
779 		if ((maj == GSS_S_COMPLETE) &&
780 		    !(ret_flags & GSS_C_MUTUAL_FLAG)) {
781 			syslog(LOG_ERR, "dyndns: No GSS_C_MUTUAL_FLAG\n");
782 			if (out_tok.length > 0)
783 				(void) gss_release_buffer(&min, &out_tok);
784 			(void) gss_release_name(&min, &target_name);
785 			return (-1);
786 		}
787 
788 		if (out_tok.length > 0) {
789 			if ((buf_sz = dyndns_build_tkey_msg(buf, key_name,
790 			    &id, &out_tok)) <= 0) {
791 				(void) gss_release_buffer(&min, &out_tok);
792 				(void) gss_release_name(&min, &target_name);
793 				return (-1);
794 			}
795 
796 			(void) gss_release_buffer(&min, &out_tok);
797 
798 			if (send(s, buf, buf_sz, 0) == -1) {
799 				syslog(LOG_ERR, "dyndns: TKEY send error\n");
800 				(void) gss_release_name(&min, &target_name);
801 				return (-1);
802 			}
803 
804 			bzero(buf2, MAX_TCP_SIZE);
805 			if (recv(s, buf2, MAX_TCP_SIZE, 0) == -1) {
806 				syslog(LOG_ERR, "dyndns: TKEY "
807 				    "reply recv error\n");
808 				(void) gss_release_name(&min, &target_name);
809 				return (-1);
810 			}
811 
812 			ret = buf2[5] & 0xf;	/* error field in TCP */
813 			if (ret != NOERROR) {
814 				syslog(LOG_ERR, "dyndns: Error in "
815 				    "TKEY reply: %d: ", ret);
816 				dyndns_msg_err(ret);
817 				(void) gss_release_name(&min, &target_name);
818 				return (-1);
819 			}
820 
821 			tmpptr = &buf2[2];
822 			(void) dyndns_get_nshort(tmpptr, &rid);
823 			if (id != rid) {
824 				(void) gss_release_name(&min, &target_name);
825 				return (-1);
826 			}
827 
828 			tmpptr = &buf2[59+(strlen(key_name)+2)*2];
829 			(void) dyndns_get_nshort(tmpptr, &rsz);
830 			in_tok.length = rsz;
831 
832 			/* bsd38 -> 2*7=14 */
833 			in_tok.value = &buf2[61+(strlen(key_name)+2)*2];
834 			inputptr = &in_tok;
835 		}
836 
837 	} while (maj != GSS_S_COMPLETE);
838 
839 	(void) gss_release_name(&min, &target_name);
840 
841 	return (0);
842 }
843 
844 /*
845  * dyndns_get_sec_context
846  * Get security context for secure dynamic DNS update.  This routine opens
847  * a TCP socket to the DNS server and establishes a security context with
848  * the DNS server using host principal to perform secure dynamic DNS update.
849  * Parameters:
850  *   hostname: fully qualified hostname
851  *   dns_ip  : ip address of hostname in network byte order
852  * Returns:
853  *   gss_handle: gss credential handle
854  *   gss_context: gss security context
855  *   -1: error
856  *    0: success
857  */
858 static gss_ctx_id_t
859 dyndns_get_sec_context(const char *hostname, int dns_ip)
860 {
861 	int s;
862 	gss_cred_id_t cred_handle;
863 	gss_ctx_id_t gss_context;
864 	gss_OID oid;
865 	struct hostent *hentry;
866 	char *key_name, dns_hostname[MAXHOSTNAMELEN];
867 
868 	cred_handle = GSS_C_NO_CREDENTIAL;
869 	oid = GSS_C_NO_OID;
870 	key_name = (char *)hostname;
871 
872 	hentry = gethostbyaddr((char *)&dns_ip, 4, AF_INET);
873 	if (hentry == NULL) {
874 		syslog(LOG_ERR, "dyndns: Can't get DNS "
875 		    "hostname from DNS ip.\n");
876 		return (NULL);
877 	}
878 	(void) strcpy(dns_hostname, hentry->h_name);
879 
880 	if ((s = dyndns_open_init_socket(SOCK_STREAM, dns_ip, 53)) < 0) {
881 		return (NULL);
882 	}
883 
884 	if (dyndns_establish_sec_ctx(&gss_context, cred_handle, s, key_name,
885 	    dns_hostname, oid))
886 		gss_context = NULL;
887 
888 	(void) close(s);
889 	return (gss_context);
890 }
891 
892 /*
893  * dyndns_build_add_remove_msg
894  * This routine builds the update request message for adding and removing DNS
895  * entries which is used for non-secure and secure DNS update.
896  * This routine builds an UDP message.
897  * Parameters:
898  *   buf        : buffer to build message
899  *   update_zone: the type of zone to update, use UPDATE_FORW for forward
900  *                lookup zone, use UPDATE_REV for reverse lookup zone
901  *   hostname   : fully qualified hostname to update DNS with
902  *   ip_addr    : IP address of hostname
903  *   life_time  : cached time of this entry by others and not within DNS
904  *                database
905  *   update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry
906  *   del_type   : DEL_ONE for deleting one entry, DEL_ALL for deleting all
907  *                entries of the same resource name.  Only valid for UPDATE_DEL.
908  *   addit_cnt  : Indicate how many record is in the additional section of
909  *                the DNS message.  A value of zero is always used with
910  *                non-secure update message. For secure update message,
911  *                the value will be one because the signed TSIG message
912  *                is added as the additional record of the DNS update message.
913  *   id         : DNS message ID.  If a positive value then this ID value is
914  *                used, otherwise the next incremented value is used
915  *   level      : This is the domain level which we send the request to, level
916  *                zero is the default level, it can go upto 2 in reverse zone
917  *                and virtually to any level in forward zone.
918  * Returns:
919  *   buf      : buffer containing update message
920  *   id       : DNS message ID
921  *   int      : size of update message
922  *   -1       : error
923  *
924  * This function is changed to handle dynamic DNS update retires to higher
925  * authoritative domains.
926  */
927 static int
928 dyndns_build_add_remove_msg(char *buf, int update_zone, const char *hostname,
929 	const char *ip_addr, int life_time, int update_type, int del_type,
930 	int addit_cnt, uint16_t *id, int level)
931 {
932 	int a, b, c, d;
933 	char *bufptr;
934 	int queryReq, zoneCount, preqCount, updateCount, additionalCount;
935 	char *zone, *resource, *data, zone_buf[100], resrc_buf[100];
936 	int zoneType, zoneClass, type, class, ttl;
937 	char *p;
938 
939 	queryReq = REQ_UPDATE;
940 	zoneCount = 1;
941 	preqCount = 0;
942 	updateCount = 1;
943 	additionalCount = addit_cnt;
944 
945 	(void) memset(buf, 0, NS_PACKETSZ);
946 	bufptr = buf;
947 
948 	if (*id == 0)
949 		*id = dns_get_msgid();
950 
951 	if (dyndns_build_header(&bufptr, BUFLEN_UDP(bufptr, buf), *id, queryReq,
952 	    zoneCount, preqCount, updateCount, additionalCount, 0) == -1) {
953 		return (-1);
954 	}
955 
956 	zoneType = ns_t_soa;
957 	zoneClass = ns_c_in;
958 
959 	if (update_zone == UPDATE_FORW) {
960 		p = (char *)hostname;
961 
962 		/* Try higher domains according to the level requested */
963 		do {
964 			/* domain */
965 			if ((zone = (char *)strchr(p, '.')) == NULL)
966 				return (-1);
967 			zone += 1;
968 			p = zone;
969 		} while (--level >= 0);
970 		resource = (char *)hostname;
971 		data = (char *)ip_addr;
972 	} else {
973 		(void) sscanf(ip_addr, "%d.%d.%d.%d", &a, &b, &c, &d);
974 		(void) sprintf(zone_buf, "%d.%d.%d.in-addr.arpa", c, b, a);
975 		zone = p = zone_buf;
976 
977 		/* Try higher domains according to the level requested */
978 		while (--level >= 0) {
979 			/* domain */
980 			if ((zone = (char *)strchr(p, '.')) == NULL) {
981 				return (-1);
982 			}
983 			zone += 1;
984 			p = zone;
985 		}
986 
987 		(void) sprintf(resrc_buf, "%d.%d.%d.%d.in-addr.arpa",
988 		    d, c, b, a);
989 		resource = resrc_buf;	/* ip info */
990 		data = (char *)hostname;
991 	}
992 
993 	if (dyndns_build_quest_zone(&bufptr, BUFLEN_UDP(bufptr, buf), zone,
994 	    zoneType, zoneClass) == -1) {
995 		return (-1);
996 	}
997 
998 	if (update_zone == UPDATE_FORW)
999 		type = ns_t_a;
1000 	else
1001 		type = ns_t_ptr;
1002 
1003 	if (update_type == UPDATE_ADD) {
1004 		class = ns_c_in;
1005 		ttl = life_time;
1006 	} else {
1007 		if (del_type == DEL_ONE)
1008 			class = ns_c_none;	/* remove one */
1009 		else
1010 			class = ns_c_any;	/* remove all */
1011 		ttl = 0;
1012 	}
1013 	if (dyndns_build_update(&bufptr, BUFLEN_UDP(bufptr, buf),
1014 	    resource, type, class, ttl, data, update_zone,
1015 	    update_type, del_type) == -1) {
1016 		return (-1);
1017 	}
1018 
1019 	return (bufptr - buf);
1020 }
1021 
1022 /*
1023  * dyndns_build_unsigned_tsig_msg
1024  * This routine is used to build the unsigned TSIG message for signing.  The
1025  * unsigned TSIG message contains the update request message with certain TSIG
1026  * fields included.  An error time of 300 seconds is used for fudge time.  This
1027  * is the number used by Microsoft clients.
1028  * This routine builds a UDP message.
1029  * Parameters:
1030  *   buf        : buffer to build message
1031  *   update_zone: the type of zone to update, use UPDATE_FORW for forward
1032  *                lookup zone, use UPDATE_REV for reverse lookup zone
1033  *   hostname   : fully qualified hostname to update DNS with
1034  *   ip_addr    : IP address of hostname
1035  *   life_time  : cached time of this entry by others and not within DNS
1036  *                database
1037  *   update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry
1038  *   del_type   : DEL_ONE for deleting one entry, DEL_ALL for deleting all
1039  *                entries of the same resource name.  Only valid for UPDATE_DEL.
1040  *   key_name   : same key name used in TKEY message
1041  *   id         : DNS message ID.  If a positive value then this ID value is
1042  *                used, otherwise the next incremented value is used
1043  *   level      : This is the domain level which we send the request to, level
1044  *                zero is the default level, it can go upto 2 in reverse zone
1045  *                and virtually to any level in forward zone.
1046  * Returns:
1047  *   buf      : buffer containing update message
1048  *   id       : DNS message ID
1049  *   int      : size of update message
1050  *   -1       : error
1051  */
1052 static int
1053 dyndns_build_unsigned_tsig_msg(char *buf, int update_zone, const char *hostname,
1054 	const char *ip_addr, int life_time, int update_type, int del_type,
1055 	char *key_name, uint16_t *id, int level)
1056 {
1057 	char *bufptr;
1058 	int buf_sz;
1059 
1060 	if ((buf_sz = dyndns_build_add_remove_msg(buf, update_zone, hostname,
1061 	    ip_addr, life_time, update_type, del_type, 0, id, level)) <= 0) {
1062 		return (-1);
1063 	}
1064 
1065 	bufptr = buf + buf_sz;
1066 
1067 	if (dyndns_build_tsig(&bufptr, BUFLEN_UDP(bufptr, buf), 0,
1068 	    key_name, 300, NULL, 0, TSIG_UNSIGNED) == -1) {
1069 		return (-1);
1070 	}
1071 
1072 	return (bufptr - buf);
1073 }
1074 
1075 /*
1076  * dyndns_build_signed_tsig_msg
1077  * This routine build the signed TSIG message which contains the update
1078  * request message encrypted.  An error time of 300 seconds is used for fudge
1079  * time.  This is the number used by Microsoft clients.
1080  * This routine builds a UDP message.
1081  * Parameters:
1082  *   buf        : buffer to build message
1083  *   update_zone: the type of zone to update, use UPDATE_FORW for forward
1084  *                lookup zone, use UPDATE_REV for reverse lookup zone
1085  *   hostname   : fully qualified hostname to update DNS with
1086  *   ip_addr    : IP address of hostname
1087  *   life_time  : cached time of this entry by others and not within DNS
1088  *                database
1089  *   update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry
1090  *   del_type   : DEL_ONE for deleting one entry, DEL_ALL for deleting all
1091  *                entries of the same resource name.  Only valid for UPDATE_DEL.
1092  *   key_name   : same key name used in TKEY message
1093  *   id         : DNS message ID.  If a positive value then this ID value is
1094  *                used, otherwise the next incremented value is used
1095  *   in_mic     : the update request message encrypted
1096  *   level      : This is the domain level which we send the request to, level
1097  *                zero is the default level, it can go upto 2 in reverse zone
1098  *                and virtually to any level in forward zone.
1099  *
1100  * Returns:
1101  *   buf      : buffer containing update message
1102  *   id       : DNS message ID
1103  *   int      : size of update message
1104  *   -1       : error
1105  */
1106 static int
1107 dyndns_build_signed_tsig_msg(char *buf, int update_zone, const char *hostname,
1108 	const char *ip_addr, int life_time, int update_type, int del_type,
1109 	char *key_name, uint16_t *id, gss_buffer_desc *in_mic, int level)
1110 {
1111 	char *bufptr;
1112 	int buf_sz;
1113 
1114 	if ((buf_sz = dyndns_build_add_remove_msg(buf, update_zone, hostname,
1115 	    ip_addr, life_time, update_type, del_type, 1, id, level)) <= 0) {
1116 		return (-1);
1117 	}
1118 
1119 	bufptr = buf + buf_sz;
1120 
1121 	if (dyndns_build_tsig(&bufptr, BUFLEN_UDP(bufptr, buf),
1122 	    *id, key_name, 300, in_mic->value,
1123 	    in_mic->length, TSIG_SIGNED) == -1) {
1124 		return (-1);
1125 	}
1126 
1127 	return (bufptr - buf);
1128 }
1129 
1130 /*
1131  * dyndns_udp_send_recv
1132  * This routine sends and receives UDP DNS request and reply messages.
1133  *
1134  * Pre-condition: Caller must call dyndns_open_init_socket() before calling
1135  * this function.
1136  *
1137  * Parameters:
1138  *   s        : socket descriptor
1139  *   buf      : buffer containing data to send
1140  *   buf_sz   : size of data to send
1141  * Returns:
1142  *   -1     : error
1143  *   rec_buf: reply dat
1144  *    0     : success
1145  */
1146 static int
1147 dyndns_udp_send_recv(int s, char *buf, int buf_sz, char *rec_buf)
1148 {
1149 	int i, retval, addr_len;
1150 	struct timeval tv, timeout;
1151 	fd_set rfds;
1152 	struct sockaddr_in from_addr;
1153 
1154 	timeout.tv_usec = 0;
1155 	timeout.tv_sec = DYNDNS_QUERY_TIMEOUT;
1156 
1157 	for (i = 0; i <= DYNDNS_MAX_QUERY_RETRIES; i++) {
1158 		if (send(s, buf, buf_sz, 0) == -1) {
1159 			syslog(LOG_ERR, "dyndns: UDP send error (%s)\n",
1160 			    strerror(errno));
1161 			return (-1);
1162 		}
1163 
1164 		FD_ZERO(&rfds);
1165 		FD_SET(s, &rfds);
1166 
1167 		tv = timeout;
1168 
1169 		retval = select(s+1, &rfds, NULL, NULL, &tv);
1170 
1171 		if (retval == -1) {
1172 			return (-1);
1173 		} else if (retval > 0) {
1174 			bzero(rec_buf, NS_PACKETSZ);
1175 			/* required by recvfrom */
1176 			addr_len = sizeof (struct sockaddr_in);
1177 			if (recvfrom(s, rec_buf, NS_PACKETSZ, 0,
1178 			    (struct sockaddr *)&from_addr, &addr_len) == -1) {
1179 				syslog(LOG_ERR, "dyndns: UDP recv err\n");
1180 				return (-1);
1181 			}
1182 			break;
1183 		}
1184 	}
1185 
1186 	/* did not receive anything */
1187 	if (i == (DYNDNS_MAX_QUERY_RETRIES + 1)) {
1188 		syslog(LOG_ERR, "dyndns: max retries for UDP recv reached\n");
1189 		return (-1);
1190 	}
1191 
1192 	return (0);
1193 }
1194 
1195 /*
1196  * dyndns_sec_add_remove_entry
1197  * Perform secure dynamic DNS update after getting security context.
1198  * This routine opens a UDP socket to the DNS sever, gets the security context,
1199  * builds the unsigned TSIG message and signed TSIG message.  The signed TSIG
1200  * message containing the encrypted update request message is sent to the DNS
1201  * server.  The response is received and check for error.  If there is no
1202  * error then credential handle and security context are released and the local
1203  * NSS cached is purged.
1204  * Parameters:
1205  *   update_zone : UPDATE_FORW for forward zone, UPDATE_REV for reverse zone
1206  *   hostname    : fully qualified hostname
1207  *   ip_addr     : ip address of hostname in string format
1208  *   life_time   : cached time of this entry by others and not within DNS
1209  *                 database
1210  *   max_retries : maximum retries for sending DNS update request
1211  *   recv_timeout: receive timeout
1212  *   update_type : UPDATE_ADD for adding entry, UPDATE_DEL for removing entry
1213  *   del_type    : DEL_ONE for deleting one entry, DEL_ALL for deleting all
1214  *                 entries of the same resource name.  Only valid for UPDATE_DEL
1215  *   dns_str     : DNS IP address in string format
1216  * Returns:
1217  *   -1: error
1218  *    0: success
1219  *
1220  * This function is enhanced to handle the case of NOTAUTH error when DNS server
1221  * is not authoritative for specified zone. In this case we need to resend the
1222  * same request to the higher authoritative domains.
1223  * This is true for both secure and unsecure dynamic DNS updates.
1224  */
1225 static int
1226 dyndns_sec_add_remove_entry(int update_zone, const char *hostname,
1227     const char *ip_addr, int life_time, int update_type, int del_type,
1228     char *dns_str)
1229 {
1230 	int s2;
1231 	uint16_t id, rid;
1232 	char buf[NS_PACKETSZ], buf2[NS_PACKETSZ];
1233 	int ret;
1234 	OM_uint32 min, maj;
1235 	gss_buffer_desc in_mic, out_mic;
1236 	gss_ctx_id_t gss_context;
1237 	int dns_ip;
1238 	char *key_name;
1239 	int buf_sz;
1240 	int level = 0;
1241 
1242 	assert(dns_str);
1243 	assert(*dns_str);
1244 
1245 	dns_ip = inet_addr(dns_str);
1246 
1247 sec_retry_higher:
1248 
1249 	if ((gss_context = dyndns_get_sec_context(hostname,
1250 	    dns_ip)) == NULL) {
1251 		return (-1);
1252 	}
1253 
1254 	key_name = (char *)hostname;
1255 
1256 	if ((s2 = dyndns_open_init_socket(SOCK_DGRAM, dns_ip, 53)) < 0) {
1257 		if (gss_context != GSS_C_NO_CONTEXT)
1258 			(void) gss_delete_sec_context(&min, &gss_context, NULL);
1259 		return (-1);
1260 	}
1261 
1262 	id = 0;
1263 	if ((buf_sz = dyndns_build_unsigned_tsig_msg(buf, update_zone, hostname,
1264 	    ip_addr, life_time, update_type, del_type,
1265 	    key_name, &id, level)) <= 0) {
1266 		(void) close(s2);
1267 		if (gss_context != GSS_C_NO_CONTEXT)
1268 			(void) gss_delete_sec_context(&min, &gss_context, NULL);
1269 		return (-1);
1270 	}
1271 
1272 	in_mic.length = buf_sz;
1273 	in_mic.value = buf;
1274 
1275 	/* sign update message */
1276 	if ((maj = gss_get_mic(&min, gss_context, 0, &in_mic, &out_mic)) !=
1277 	    GSS_S_COMPLETE) {
1278 		display_stat(maj, min);
1279 		(void) close(s2);
1280 		if (gss_context != GSS_C_NO_CONTEXT)
1281 			(void) gss_delete_sec_context(&min, &gss_context, NULL);
1282 		return (-1);
1283 	}
1284 
1285 	if ((buf_sz = dyndns_build_signed_tsig_msg(buf, update_zone, hostname,
1286 	    ip_addr, life_time, update_type, del_type, key_name, &id,
1287 	    &out_mic, level)) <= 0) {
1288 		(void) close(s2);
1289 		(void) gss_release_buffer(&min, &out_mic);
1290 		if (gss_context != GSS_C_NO_CONTEXT)
1291 			(void) gss_delete_sec_context(&min, &gss_context, NULL);
1292 		return (-1);
1293 	}
1294 
1295 	(void) gss_release_buffer(&min, &out_mic);
1296 
1297 	if (dyndns_udp_send_recv(s2, buf, buf_sz, buf2)) {
1298 		(void) close(s2);
1299 		if (gss_context != GSS_C_NO_CONTEXT)
1300 			(void) gss_delete_sec_context(&min, &gss_context, NULL);
1301 		return (-1);
1302 	}
1303 
1304 	(void) close(s2);
1305 
1306 	if (gss_context != GSS_C_NO_CONTEXT)
1307 		(void) gss_delete_sec_context(&min, &gss_context, NULL);
1308 
1309 	ret = buf2[3] & 0xf;	/* error field in UDP */
1310 
1311 	/*
1312 	 * If it is a NOTAUTH error we should retry with higher domains
1313 	 * until we get a successful reply or the maximum retries is met.
1314 	 */
1315 	if (ret == NOTAUTH && level++ < MAX_AUTH_RETRIES)
1316 		goto sec_retry_higher;
1317 
1318 	/* check here for update request is successful */
1319 	if (ret != NOERROR) {
1320 		syslog(LOG_ERR, "dyndns: Error in TSIG reply: %d: ", ret);
1321 		dyndns_msg_err(ret);
1322 		return (-1);
1323 	}
1324 
1325 	(void) dyndns_get_nshort(buf2, &rid);
1326 	if (id != rid)
1327 		return (-1);
1328 
1329 	return (0);
1330 }
1331 
1332 /*
1333  * dyndns_seach_entry
1334  * Query DNS server for entry.  This routine can indicate if an entry exist
1335  * or not during forward or reverse lookup.  Also can indicate if the data
1336  * of the entry matched.  For example, for forward lookup, the entry is
1337  * searched using the hostname and the data is the IP address.  For reverse
1338  * lookup, the entry is searched using the IP address and the data is the
1339  * hostname.
1340  * Parameters:
1341  *   update_zone: UPDATE_FORW for forward zone, UPDATE_REV for reverse zone
1342  *   hostname   : fully qualified hostname
1343  *   ip_addr    : ip address of hostname in string format
1344  *   update_type: UPDATE_ADD for adding entry, UPDATE_DEL for removing entry
1345  * Returns:
1346  *   time_out: no use
1347  *   is_match: is 1 for found matching entry, otherwise 0
1348  *   1       : an entry exist but not necessarily match
1349  *   0       : an entry does not exist
1350  */
1351 /*ARGSUSED*/
1352 static int
1353 dyndns_search_entry(int update_zone, const char *hostname, const char *ip_addr,
1354     int update_type, struct timeval *time_out, int *is_match)
1355 {
1356 	struct hostent *hentry;
1357 	struct in_addr in;
1358 	in_addr_t ip;
1359 	int i;
1360 
1361 	*is_match = 0;
1362 	if (update_zone == UPDATE_FORW) {
1363 		hentry = gethostbyname(hostname);
1364 		if (hentry) {
1365 			ip = inet_addr(ip_addr);
1366 			for (i = 0; hentry->h_addr_list[i]; i++) {
1367 				(void) memcpy(&in.s_addr,
1368 				    hentry->h_addr_list[i], sizeof (in.s_addr));
1369 				if (ip == in.s_addr) {
1370 					*is_match = 1;
1371 					break;
1372 				}
1373 			}
1374 			return (1);
1375 		}
1376 	} else {
1377 		int dns_ip = inet_addr(ip_addr);
1378 		hentry = gethostbyaddr((char *)&dns_ip, 4, AF_INET);
1379 		if (hentry) {
1380 			if (strncasecmp(hentry->h_name, hostname,
1381 			    strlen(hostname)) == 0) {
1382 				*is_match = 1;
1383 			}
1384 			return (1);
1385 		}
1386 	}
1387 
1388 	/* entry does not exist */
1389 	return (0);
1390 }
1391 
1392 /*
1393  * dyndns_add_remove_entry
1394  * Perform non-secure dynamic DNS update.  If it fails and host principal
1395  * keys can be found in the local keytab file, secure update will be performed.
1396  *
1397  * This routine opens a UDP socket to the DNS sever, build the update request
1398  * message, and sends the message to the DNS server.  The response is received
1399  * and check for error.  If there is no error then the local NSS cached is
1400  * purged.  DNS may be used to check to see if an entry already exist before
1401  * adding or to see if an entry does exist before removing it.  Adding
1402  * duplicate entries or removing non-existing entries does not cause any
1403  * problems.  DNS is not check when doing a delete all.
1404  * Parameters:
1405  *   update_zone: UPDATE_FORW for forward zone, UPDATE_REV for reverse zone
1406  *   hostname   : fully qualified hostname
1407  *   ip_addr    : ip address of hostname in string format
1408  *   life_time  : cached time of this entry by others and not within DNS
1409  *                database
1410  *   update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry
1411  *   do_check   : DNS_CHECK to check first in DNS, DNS_NOCHECK for no DNS
1412  *                checking before update
1413  *   del_type   : DEL_ONE for deleting one entry, DEL_ALL for deleting all
1414  *                entries of the same resource name.  Only valid for UPDATE_DEL.
1415  *   dns_str    : DNS IP address in string format
1416  * Returns:
1417  *   -1: error
1418  *    0: success
1419  *
1420  * This function is enhanced to handle the case of NOTAUTH error when DNS server
1421  * is not authoritative for specified zone. In this case we need to resend the
1422  * same request to the higher authoritative domains.
1423  * This is true for both secure and unsecure dynamic DNS updates.
1424  */
1425 static int
1426 dyndns_add_remove_entry(int update_zone, const char *hostname,
1427     const char *ip_addr, int life_time, int update_type,
1428     int do_check, int del_type, char *dns_str)
1429 {
1430 	int s;
1431 	uint16_t id, rid;
1432 	char buf[NS_PACKETSZ], buf2[NS_PACKETSZ];
1433 	int ret, dns_ip;
1434 	int is_exist, is_match;
1435 	struct timeval timeout;
1436 	int buf_sz;
1437 	int level = 0;
1438 
1439 	assert(dns_str);
1440 	assert(*dns_str);
1441 
1442 	dns_ip = inet_addr(dns_str);
1443 
1444 	if (do_check == DNS_CHECK && del_type != DEL_ALL) {
1445 		is_exist = dyndns_search_entry(update_zone, hostname, ip_addr,
1446 		    update_type, &timeout, &is_match);
1447 
1448 		if (update_type == UPDATE_ADD && is_exist && is_match) {
1449 			return (0);
1450 		} else if (update_type == UPDATE_DEL && !is_exist) {
1451 			return (0);
1452 		}
1453 	}
1454 
1455 retry_higher:
1456 	if ((s = dyndns_open_init_socket(SOCK_DGRAM, dns_ip, 53)) < 0) {
1457 		return (-1);
1458 	}
1459 
1460 	id = 0;
1461 	if ((buf_sz = dyndns_build_add_remove_msg(buf, update_zone, hostname,
1462 	    ip_addr, life_time, update_type, del_type, 0, &id, level)) <= 0) {
1463 		(void) close(s);
1464 		return (-1);
1465 	}
1466 
1467 	if (dyndns_udp_send_recv(s, buf, buf_sz, buf2)) {
1468 		(void) close(s);
1469 		return (-1);
1470 	}
1471 
1472 	(void) close(s);
1473 
1474 	ret = buf2[3] & 0xf;	/* error field in UDP */
1475 
1476 	/*
1477 	 * If it is a NOTAUTH error we should retry with higher domains
1478 	 * until we get a successful reply
1479 	 */
1480 	if (ret == NOTAUTH && level++ < MAX_AUTH_RETRIES)
1481 		goto retry_higher;
1482 
1483 	/* check here for update request is successful */
1484 	if (ret == NOERROR) {
1485 		(void) dyndns_get_nshort(buf2, &rid);
1486 		if (id != rid)
1487 			return (-1);
1488 		return (0);
1489 	}
1490 
1491 	if (ret == NOTIMP) {
1492 		syslog(LOG_ERR, "dyndns: DNS does not "
1493 		    "support dynamic update\n");
1494 		return (-1);
1495 	} else if (ret == NOTAUTH) {
1496 		syslog(LOG_ERR, "dyndns: DNS is not authoritative for "
1497 		    "zone name in zone section\n");
1498 		return (-1);
1499 	}
1500 
1501 	if (smb_krb5_find_keytab_entries(hostname, SMBNS_KRB5_KEYTAB))
1502 		ret = dyndns_sec_add_remove_entry(update_zone, hostname,
1503 		    ip_addr, life_time, update_type, del_type, dns_str);
1504 
1505 	return (ret);
1506 }
1507 
1508 /*
1509  * dyndns_add_entry
1510  * Main routine to add an entry into DNS.  The attempt will be made on the
1511  * the servers returned by smb_get_nameserver().  Upon a successful
1512  * attempt on any one of the server, the function will exit with 0.
1513  * Otherwise, -1 is retuned to indicate the update attempt on all the
1514  * nameservers has failed.
1515  *
1516  * Parameters:
1517  *   update_zone: the type of zone to update, use UPDATE_FORW for forward
1518  *                lookup zone, use UPDATE_REV for reverse lookup zone
1519  *   hostname   : fully qualified hostname
1520  *   ip_addr    : ip address of hostname in string format
1521  *   life_time  : cached time of this entry by others and not within DNS
1522  *                database
1523  * Returns:
1524  *   -1: error
1525  *    0: success
1526  */
1527 static int
1528 dyndns_add_entry(int update_zone, const char *hostname, const char *ip_addr,
1529     int life_time)
1530 {
1531 	char *dns_str;
1532 	struct in_addr ns_list[MAXNS];
1533 	int i, cnt;
1534 	int addr, rc = 0;
1535 
1536 	if (hostname == NULL || ip_addr == NULL) {
1537 		return (-1);
1538 	}
1539 
1540 	addr = (int)inet_addr(ip_addr);
1541 	if ((addr == -1) || (addr == 0)) {
1542 		return (-1);
1543 	}
1544 
1545 	cnt = smb_get_nameservers(ns_list, MAXNS);
1546 
1547 	for (i = 0; i < cnt; i++) {
1548 		dns_str = inet_ntoa(ns_list[i]);
1549 		if ((dns_str == NULL) ||
1550 		    (strcmp(dns_str, "0.0.0.0") == 0)) {
1551 			continue;
1552 		}
1553 
1554 		if (update_zone == UPDATE_FORW) {
1555 			syslog(LOG_DEBUG, "Dynamic update on forward lookup "
1556 			    "zone for %s (%s)...\n", hostname, ip_addr);
1557 		} else {
1558 			syslog(LOG_DEBUG, "Dynamic update on reverse lookup "
1559 			    "zone for %s (%s)...\n", hostname, ip_addr);
1560 		}
1561 		if (dyndns_add_remove_entry(update_zone, hostname,
1562 		    ip_addr, life_time,
1563 		    UPDATE_ADD, DNS_NOCHECK, DEL_NONE, dns_str) != -1) {
1564 			rc = 1;
1565 			break;
1566 		}
1567 	}
1568 
1569 	return (rc ? 0 : -1);
1570 }
1571 
1572 /*
1573  * dyndns_remove_entry
1574  * Main routine to remove an entry or all entries of the same resource name
1575  * from DNS.  The update attempt will be made on the primary DNS server.  If
1576  * there is a failure then another attempt will be made on the secondary DNS
1577  * server.
1578  * Parameters:
1579  *   update_zone: the type of zone to update, use UPDATE_FORW for forward
1580  *                lookup zone, use UPDATE_REV for reverse lookup zone
1581  *   hostname   : fully qualified hostname
1582  *   ip_addr    : ip address of hostname in string format
1583  *   del_type   : DEL_ONE for deleting one entry, DEL_ALL for deleting all
1584  *                entries of the same resource name.  Only valid for UPDATE_DEL
1585  * Returns:
1586  *   -1: error
1587  *    0: success
1588  */
1589 static int
1590 dyndns_remove_entry(int update_zone, const char *hostname, const char *ip_addr,
1591 	int del_type)
1592 {
1593 	char *dns_str;
1594 	struct in_addr ns_list[MAXNS];
1595 	int i, cnt, scnt;
1596 	int addr;
1597 
1598 	if ((hostname == NULL || ip_addr == NULL)) {
1599 		return (-1);
1600 	}
1601 
1602 	addr = (int)inet_addr(ip_addr);
1603 	if ((addr == -1) || (addr == 0)) {
1604 		return (-1);
1605 	}
1606 
1607 	cnt = smb_get_nameservers(ns_list, MAXNS);
1608 	scnt = 0;
1609 
1610 	for (i = 0; i < cnt; i++) {
1611 		dns_str = inet_ntoa(ns_list[i]);
1612 		if ((dns_str == NULL) ||
1613 		    (strcmp(dns_str, "0.0.0.0") == 0)) {
1614 			continue;
1615 		}
1616 
1617 		if (update_zone == UPDATE_FORW) {
1618 			if (del_type == DEL_ONE) {
1619 				syslog(LOG_DEBUG, "Dynamic update "
1620 				    "on forward lookup "
1621 				    "zone for %s (%s)...\n", hostname, ip_addr);
1622 			} else {
1623 				syslog(LOG_DEBUG, "Removing all "
1624 				    "entries of %s "
1625 				    "in forward lookup zone...\n", hostname);
1626 			}
1627 		} else {
1628 			if (del_type == DEL_ONE) {
1629 				syslog(LOG_DEBUG, "Dynamic update "
1630 				    "on reverse lookup "
1631 				    "zone for %s (%s)...\n", hostname, ip_addr);
1632 			} else {
1633 				syslog(LOG_DEBUG, "Removing all "
1634 				    "entries of %s "
1635 				    "in reverse lookup zone...\n", ip_addr);
1636 			}
1637 		}
1638 		if (dyndns_add_remove_entry(update_zone, hostname, ip_addr, 0,
1639 		    UPDATE_DEL, DNS_NOCHECK, del_type, dns_str) != -1) {
1640 			scnt++;
1641 			break;
1642 		}
1643 	}
1644 	if (scnt)
1645 		return (0);
1646 	return (-1);
1647 }
1648 
1649 /*
1650  * dyndns_update
1651  *
1652  * Dynamic DNS update API for kclient.
1653  *
1654  * Returns:
1655  *   0: successful
1656  *  -1: dynamic update failure.
1657  *  -2: unable to obtain NIC info.
1658  *  -3: init failure
1659  */
1660 int
1661 dyndns_update(char *fqdn)
1662 {
1663 	int rc;
1664 
1665 	if (smb_nic_init() != 0)
1666 		return (-2);
1667 
1668 	if (dns_msgid_init() != 0)
1669 		return (-3);
1670 
1671 	rc = dyndns_update_core(fqdn);
1672 	smb_nic_fini();
1673 	return (rc);
1674 }
1675 
1676 /*
1677  * dyndns_update_core
1678  * Perform dynamic update on both forward and reverse lookup zone using
1679  * the specified hostname and IP addresses.  Before updating DNS, existing
1680  * host entries with the same hostname in the forward lookup zone are removed
1681  * and existing pointer entries with the same IP addresses in the reverse
1682  * lookup zone are removed.  After DNS update, host entries for current
1683  * hostname will show current IP addresses and pointer entries for current
1684  * IP addresses will show current hostname.
1685  * Parameters:
1686  *  fqhn - fully-qualified hostname
1687  *
1688  * Returns:
1689  *   -1: some dynamic DNS updates errors
1690  *    0: successful or DDNS disabled.
1691  */
1692 int
1693 dyndns_update_core(char *fqdn)
1694 {
1695 	int forw_update_ok, error;
1696 	char *my_ip;
1697 	struct in_addr addr;
1698 	smb_niciter_t ni;
1699 	int rc;
1700 	char fqhn[MAXHOSTNAMELEN];
1701 
1702 	if (!smb_config_getbool(SMB_CI_DYNDNS_ENABLE))
1703 		return (0);
1704 
1705 	if (smb_gethostname(fqhn, MAXHOSTNAMELEN, 0) != 0)
1706 		return (-1);
1707 
1708 	(void) snprintf(fqhn, MAXHOSTNAMELEN, "%s.%s", fqhn, fqdn);
1709 	error = 0;
1710 	forw_update_ok = 0;
1711 
1712 	/*
1713 	 * Dummy IP is okay since we are removing all using the hostname.
1714 	 */
1715 	if (dyndns_remove_entry(UPDATE_FORW, fqhn, "1.1.1.1", DEL_ALL) == 0) {
1716 		forw_update_ok = 1;
1717 	} else {
1718 		error++;
1719 	}
1720 
1721 	if (smb_nic_getfirst(&ni) != 0)
1722 		return (-1);
1723 
1724 	do {
1725 		if (ni.ni_nic.nic_sysflags & (IFF_STANDBY | IFF_PRIVATE))
1726 			continue;
1727 
1728 		addr.s_addr = ni.ni_nic.nic_ip;
1729 		my_ip = (char *)strdup(inet_ntoa(addr));
1730 		if (my_ip == NULL) {
1731 			error++;
1732 			continue;
1733 		}
1734 
1735 		if (forw_update_ok) {
1736 			rc = dyndns_add_entry(UPDATE_FORW, fqhn, my_ip,
1737 			    DDNS_TTL);
1738 
1739 			if (rc == -1)
1740 				error++;
1741 		}
1742 
1743 		rc = dyndns_remove_entry(UPDATE_REV, fqhn, my_ip, DEL_ALL);
1744 		if (rc == 0) {
1745 			rc = dyndns_add_entry(UPDATE_REV, fqhn, my_ip,
1746 			    DDNS_TTL);
1747 		}
1748 
1749 		if (rc == -1)
1750 			error++;
1751 
1752 		(void) free(my_ip);
1753 	} while (smb_nic_getnext(&ni) == 0);
1754 
1755 	return ((error == 0) ? 0 : -1);
1756 }
1757 
1758 /*
1759  * dyndns_clear_rev_zone
1760  * Clear the rev zone records. Must be called to clear the OLD if list
1761  * of down records prior to updating the list with new information.
1762  *
1763  * Parameters:
1764  *   fqhn - fully-qualified hostname
1765  * Returns:
1766  *   -1: some dynamic DNS updates errors
1767  *    0: successful or DDNS disabled.
1768  */
1769 int
1770 dyndns_clear_rev_zone(char *fqdn)
1771 {
1772 	int error;
1773 	char *my_ip;
1774 	struct in_addr addr;
1775 	smb_niciter_t ni;
1776 	int rc;
1777 	char fqhn[MAXHOSTNAMELEN];
1778 
1779 	if (!smb_config_getbool(SMB_CI_DYNDNS_ENABLE))
1780 		return (0);
1781 
1782 	if (smb_gethostname(fqhn, MAXHOSTNAMELEN, 0) != 0)
1783 		return (-1);
1784 
1785 	(void) snprintf(fqhn, MAXHOSTNAMELEN, "%s.%s", fqhn, fqdn);
1786 	error = 0;
1787 
1788 	if (smb_nic_getfirst(&ni) != 0)
1789 		return (-1);
1790 
1791 	do {
1792 		if (ni.ni_nic.nic_sysflags & (IFF_STANDBY | IFF_PRIVATE))
1793 			continue;
1794 
1795 		addr.s_addr = ni.ni_nic.nic_ip;
1796 		my_ip = (char *)strdup(inet_ntoa(addr));
1797 		if (my_ip == NULL) {
1798 			error++;
1799 			continue;
1800 		}
1801 
1802 		rc = dyndns_remove_entry(UPDATE_REV, fqhn, my_ip, DEL_ALL);
1803 		if (rc != 0)
1804 			error++;
1805 
1806 		(void) free(my_ip);
1807 	} while (smb_nic_getnext(&ni) == 0);
1808 
1809 	return ((error == 0) ? 0 : -1);
1810 }
1811