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