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