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