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