summaryrefslogtreecommitdiff
path: root/usr/src/lib/smbsrv/libsmbns/common/smbns_dyndns.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/smbsrv/libsmbns/common/smbns_dyndns.c')
-rw-r--r--usr/src/lib/smbsrv/libsmbns/common/smbns_dyndns.c1894
1 files changed, 1894 insertions, 0 deletions
diff --git a/usr/src/lib/smbsrv/libsmbns/common/smbns_dyndns.c b/usr/src/lib/smbsrv/libsmbns/common/smbns_dyndns.c
new file mode 100644
index 0000000000..474adca09b
--- /dev/null
+++ b/usr/src/lib/smbsrv/libsmbns/common/smbns_dyndns.c
@@ -0,0 +1,1894 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <string.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <netdb.h>
+#include <rpc/rpc.h>
+#include <syslog.h>
+#include <gssapi/gssapi.h>
+#include <kerberosv5/krb5.h>
+#include <net/if.h>
+
+#include <smbns_dyndns.h>
+#include <smbns_krb.h>
+
+/* internal use, in dyndns_add_entry */
+#define DEL_NONE 2
+/* Maximum retires if not authoritative */
+#define MAX_AUTH_RETRIES 3
+
+
+static int
+dyndns_enabled(void)
+{
+ int enabled;
+
+ smb_config_rdlock();
+ enabled = smb_config_getyorn(SMB_CI_DYNDNS_ENABLE);
+ smb_config_unlock();
+
+ return ((enabled) ? 1 : 0);
+}
+
+/*
+ * XXX The following should be removed once head/arpa/nameser_compat.h
+ * defines BADSIG, BADKEY, BADTIME macros
+ */
+#ifndef BADSIG
+#define BADSIG ns_r_badsig
+#endif /* BADSIG */
+
+#ifndef BADKEY
+#define BADKEY ns_r_badkey
+#endif /* BADKEY */
+
+#ifndef BADTIME
+#define BADTIME ns_r_badtime
+#endif /* BADTIME */
+
+/*
+ * dyndns_msg_err
+ * Display error message for DNS error code found in the DNS header in
+ * reply message.
+ * Parameters:
+ * err: DNS errer code
+ * Returns:
+ * None
+ */
+void
+dyndns_msg_err(int err)
+{
+ switch (err) {
+ case NOERROR:
+ break;
+ case FORMERR:
+ syslog(LOG_ERR, "DNS message format error\n");
+ break;
+ case SERVFAIL:
+ syslog(LOG_ERR, "DNS server internal error\n");
+ break;
+ case NXDOMAIN:
+ syslog(LOG_ERR, "DNS entry should exist but does not exist\n");
+ break;
+ case NOTIMP:
+ syslog(LOG_ERR, "DNS opcode not supported\n");
+ break;
+ case REFUSED:
+ syslog(LOG_ERR, "DNS operation refused\n");
+ break;
+ case YXDOMAIN:
+ syslog(LOG_ERR, "DNS entry shouldn't exist but does exist\n");
+ break;
+ case YXRRSET:
+ syslog(LOG_ERR, "DNS RRSet shouldn't exist but does exist\n");
+ break;
+ case NXRRSET:
+ syslog(LOG_ERR, "DNS RRSet should exist but does not exist\n");
+ break;
+ case NOTAUTH:
+ syslog(LOG_ERR, "DNS server is not authoritative "
+ "for specified zone\n");
+ break;
+ case NOTZONE:
+ syslog(LOG_ERR, "Name in Prereq or Update section not "
+ "within specified zone\n");
+ break;
+ case BADSIG:
+ syslog(LOG_ERR, "Bad transaction signature (TSIG)");
+ break;
+ case BADKEY:
+ syslog(LOG_ERR, "Bad transaction key (TKEY)");
+ break;
+ case BADTIME:
+ syslog(LOG_ERR, "Time not synchronized");
+ break;
+
+ default:
+ syslog(LOG_ERR, "Unknown DNS error\n");
+ }
+}
+
+/*
+ * display_stat
+ * Display GSS error message from error code. This routine is used to display
+ * the mechanism independent and mechanism specific error messages for GSS
+ * routines. The major status error code is the mechanism independent error
+ * code and the minor status error code is the mechanism specific error code.
+ * Parameters:
+ * maj: GSS major status
+ * min: GSS minor status
+ * Returns:
+ * None
+ */
+static void
+display_stat(OM_uint32 maj, OM_uint32 min)
+{
+ gss_buffer_desc msg;
+ OM_uint32 msg_ctx = 0;
+ OM_uint32 min2;
+ (void) gss_display_status(&min2, maj, GSS_C_GSS_CODE, GSS_C_NULL_OID,
+ &msg_ctx, &msg);
+ syslog(LOG_ERR, "dyndns: GSS major status error: %s\n",
+ (char *)msg.value);
+ (void) gss_display_status(&min2, min, GSS_C_MECH_CODE, GSS_C_NULL_OID,
+ &msg_ctx, &msg);
+ syslog(LOG_ERR, "dyndns: GSS minor status error: %s\n",
+ (char *)msg.value);
+}
+
+static char *
+dyndns_put_nshort(char *buf, uint16_t val)
+{
+ uint16_t nval;
+
+ nval = htons(val);
+ (void) memcpy(buf, &nval, sizeof (uint16_t));
+ buf += sizeof (uint16_t);
+ return (buf);
+}
+
+char *
+dyndns_get_nshort(char *buf, uint16_t *val)
+{
+ uint16_t nval;
+
+ (void) memcpy(&nval, buf, sizeof (uint16_t));
+ *val = ntohs(nval);
+ buf += sizeof (uint16_t);
+ return (buf);
+}
+
+static char *
+dyndns_put_nlong(char *buf, uint32_t val)
+{
+ uint32_t lval;
+
+ lval = htonl(val);
+ (void) memcpy(buf, &lval, sizeof (uint32_t));
+ buf += sizeof (uint32_t);
+ return (buf);
+}
+
+static char *
+dyndns_put_byte(char *buf, char val)
+{
+ *buf = val;
+ buf++;
+ return (buf);
+}
+
+static char *
+dyndns_put_int(char *buf, int val)
+{
+ (void) memcpy(buf, &val, sizeof (int));
+ buf += sizeof (int);
+ return (buf);
+}
+
+char *
+dyndns_get_int(char *buf, int *val)
+{
+ (void) memcpy(val, buf, sizeof (int));
+ buf += sizeof (int);
+ return (buf);
+}
+
+
+/*
+ * dyndns_stuff_str
+ * Converts a domain string by removing periods and replacing with a byte value
+ * of how many characters following period. A byte value is placed in front
+ * to indicate how many characters before first period. A NULL character is
+ * placed at the end. i.e. host.procom.com -> 4host5procom3com0
+ * Buffer space checking is done by caller.
+ * Parameters:
+ * ptr : address of pointer to buffer to store converted string
+ * zone: domain name string
+ * Returns:
+ * ptr: address of pointer to next available buffer space
+ * -1 : error
+ * 0 : success
+ */
+static int
+dyndns_stuff_str(char **ptr, char *zone)
+{
+ int len;
+ char *lenPtr, *zonePtr;
+
+ for (zonePtr = zone; *zonePtr; ) {
+ lenPtr = *ptr;
+ *ptr = *ptr + 1;
+ len = 0;
+ while (*zonePtr != '.' && *zonePtr != 0) {
+ *ptr = dyndns_put_byte(*ptr, *zonePtr);
+ zonePtr++;
+ len++;
+ }
+ *lenPtr = len;
+ if (*zonePtr == '.')
+ zonePtr++;
+ }
+ *ptr = dyndns_put_byte(*ptr, 0);
+ return (0);
+}
+
+/*
+ * dyndns_build_header
+ * Build the header for DNS query and DNS update request message.
+ * Parameters:
+ * ptr : address of pointer to buffer to store header
+ * buf_len : buffer length
+ * msg_id : message id
+ * query_req : use REQ_QUERY for query message or REQ_UPDATE for
+ * update message
+ * quest_zone_cnt : number of question record for query message or
+ * number of zone record for update message
+ * ans_prereq_cnt : number of answer record for query message or
+ * number of prerequisite record for update message
+ * nameser_update_cnt: number of name server for query message or
+ * number of update record for update message
+ * addit_cnt : number of additional record
+ * flags : query flags word
+ * Returns:
+ * ptr: address of pointer to next available buffer space
+ * -1 : error
+ * 0 : success
+ */
+int
+dyndns_build_header(char **ptr, int buf_len, uint16_t msg_id, int query_req,
+ uint16_t quest_zone_cnt, uint16_t ans_prereq_cnt,
+ uint16_t nameser_update_cnt, uint16_t addit_cnt, int flags)
+{
+ uint16_t opcode;
+
+ if (buf_len < 12) {
+ syslog(LOG_ERR, "dyndns: no more buf for header section\n");
+ return (-1);
+ }
+
+ *ptr = dyndns_put_nshort(*ptr, msg_id); /* mesg ID */
+ if (query_req == REQ_QUERY)
+ opcode = ns_o_query; /* query msg */
+ else
+ opcode = ns_o_update << 11; /* update msg */
+ opcode |= flags;
+ /* mesg opcode */
+ *ptr = dyndns_put_nshort(*ptr, opcode);
+ /* zone record count */
+ *ptr = dyndns_put_nshort(*ptr, quest_zone_cnt);
+ /* prerequiste record count */
+ *ptr = dyndns_put_nshort(*ptr, ans_prereq_cnt);
+ /* update record count */
+ *ptr = dyndns_put_nshort(*ptr, nameser_update_cnt);
+ /* additional record count */
+ *ptr = dyndns_put_nshort(*ptr, addit_cnt);
+
+ return (0);
+}
+
+/*
+ * dyndns_build_quest_zone
+ * Build the question section for query message or zone section for
+ * update message.
+ * Parameters:
+ * ptr : address of pointer to buffer to store question or zone section
+ * buf_len: buffer length
+ * name : question or zone name
+ * type : type of question or zone
+ * class : class of question or zone
+ * Returns:
+ * ptr: address of pointer to next available buffer space
+ * -1 : error
+ * 0 : success
+ */
+int
+dyndns_build_quest_zone(char **ptr, int buf_len, char *name, int type,
+ int class)
+{
+ char *zonePtr;
+
+ if ((strlen(name) + 6) > buf_len) {
+ syslog(LOG_ERR, "dyndns: no more buf "
+ "for question/zone section\n");
+ return (-1);
+ }
+
+ zonePtr = *ptr;
+ (void) dyndns_stuff_str(&zonePtr, name);
+ *ptr = zonePtr;
+ *ptr = dyndns_put_nshort(*ptr, type);
+ *ptr = dyndns_put_nshort(*ptr, class);
+ return (0);
+}
+
+/*
+ * dyndns_build_update
+ * Build update section of update message for adding and removing a record.
+ * If the ttl value is 0 then this message is for record deletion.
+ *
+ * Parameters:
+ * ptr : address of pointer to buffer to store update section
+ * buf_len : buffer length
+ * name : resource name of this record
+ * type : type of this record
+ * class : class of this record
+ * ttl : time-to-live, cached time of this entry by others and not
+ * within DNS database, a zero value for record(s) deletion
+ * data : data of this resource record
+ * forw_rev: UPDATE_FORW for forward zone, UPDATE_REV for reverse zone
+ * add_del : UPDATE_ADD for adding entry, UPDATE_DEL for removing zone
+ * del_type: DEL_ONE for deleting one entry, DEL_ALL for deleting all
+ * entries of the same resource name. Only valid for UPDATE_DEL.
+ * Returns:
+ * ptr: address of pointer to next available buffer space
+ * -1 : error
+ * 0 : success
+ */
+static int
+dyndns_build_update(char **ptr, int buf_len, char *name, int type, int class,
+ uint32_t ttl, char *data, int forw_rev, int add_del, int del_type)
+{
+ char *namePtr;
+ int rec_len, data_len;
+
+ rec_len = strlen(name) + 10;
+ if (add_del == UPDATE_ADD) {
+ if (forw_rev == UPDATE_FORW)
+ data_len = 4;
+ else
+ data_len = strlen(data) + 2;
+ } else {
+ if (del_type == DEL_ALL)
+ data_len = 0;
+ else if (forw_rev == UPDATE_FORW)
+ data_len = 4;
+ else
+ data_len = strlen(data) + 2;
+ }
+
+ if (rec_len + data_len > buf_len) {
+ syslog(LOG_ERR, "dyndns: no more buf for update section\n");
+ return (-1);
+ }
+
+ namePtr = *ptr;
+ (void) dyndns_stuff_str(&namePtr, name);
+ *ptr = namePtr;
+ *ptr = dyndns_put_nshort(*ptr, type);
+ *ptr = dyndns_put_nshort(*ptr, class);
+ *ptr = dyndns_put_nlong(*ptr, ttl);
+
+ if (add_del == UPDATE_DEL && del_type == DEL_ALL) {
+ *ptr = dyndns_put_nshort(*ptr, 0);
+ return (0);
+ }
+
+ if (forw_rev == UPDATE_FORW) {
+ *ptr = dyndns_put_nshort(*ptr, 4);
+ *ptr = dyndns_put_int(*ptr, inet_addr(data)); /* ip address */
+ } else {
+ *ptr = dyndns_put_nshort(*ptr, strlen(data)+2);
+ namePtr = *ptr;
+ (void) dyndns_stuff_str(&namePtr, data); /* hostname */
+ *ptr = namePtr;
+ }
+ return (0);
+}
+
+/*
+ * dyndns_build_tkey
+ * Build TKEY section to establish security context for secure dynamic DNS
+ * update. DNS header and question sections need to be build before this
+ * section. The TKEY data are the tokens generated during security context
+ * establishment and the TKEY message is used to transmit those tokens, one
+ * at a time, to the DNS server.
+ * Parameters:
+ * ptr : address of pointer to buffer to store TKEY
+ * buf_len : buffer length
+ * name : key name, must be unique and same as for TSIG record
+ * key_expire: expiration time of this key in second
+ * data : TKEY data
+ * data_size : data size
+ * Returns:
+ * ptr: address of the pointer to the next available buffer space
+ * -1 : error
+ * 0 : success
+ */
+static int
+dyndns_build_tkey(char **ptr, int buf_len, char *name, int key_expire,
+ char *data, int data_size)
+{
+ char *namePtr;
+ struct timeval tp;
+
+ if (strlen(name)+2 + 45 + data_size > buf_len) {
+ syslog(LOG_ERR, "dyndns: no more buf for TKEY record\n");
+ return (-1);
+ }
+
+ namePtr = *ptr;
+ (void) dyndns_stuff_str(&namePtr, name); /* unique global name */
+ *ptr = namePtr;
+ *ptr = dyndns_put_nshort(*ptr, ns_t_tkey);
+ *ptr = dyndns_put_nshort(*ptr, ns_c_any);
+ *ptr = dyndns_put_nlong(*ptr, 0);
+ /* 19 + 14 + data_size + 2 */
+ *ptr = dyndns_put_nshort(*ptr, 35 + data_size);
+ namePtr = *ptr;
+ (void) dyndns_stuff_str(&namePtr, "gss.microsoft.com");
+ *ptr = namePtr;
+ (void) gettimeofday(&tp, 0);
+ *ptr = dyndns_put_nlong(*ptr, tp.tv_sec); /* inception */
+ /* expiration, 86400 */
+ *ptr = dyndns_put_nlong(*ptr, tp.tv_sec + key_expire);
+ *ptr = dyndns_put_nshort(*ptr, MODE_GSS_API); /* mode: gss-api */
+ *ptr = dyndns_put_nshort(*ptr, 0); /* error */
+ *ptr = dyndns_put_nshort(*ptr, data_size); /* key size */
+ (void) memcpy(*ptr, data, data_size); /* key data */
+ *ptr += data_size;
+ *ptr = dyndns_put_nshort(*ptr, 0); /* other */
+ return (0);
+}
+
+/*
+ * dyndns_build_tsig
+ * Build TSIG section for secure dynamic DNS update. This routine will be
+ * called twice. First called with TSIG_UNSIGNED, and second with TSIG_SIGNED.
+ * The TSIG data is NULL and ignored for TSIG_UNSIGNED and is the update request
+ * message encrypted for TSIG_SIGNED. The message id must be the same id as the
+ * one in the update request before it is encrypted.
+ * Parameters:
+ * ptr : address of pointer to buffer to store TSIG
+ * buf_len : buffer length
+ * msg_id : message id
+ * name : key name, must be the same as in TKEY record
+ * fudge_time : amount of error time allow in seconds
+ * data : TSIG data if TSIG_SIGNED, otherwise NULL
+ * data_size : size of data, otherwise 0 if data is NULL
+ * data_signed: TSIG_SIGNED to indicate data is signed and encrypted,
+ * otherwise TSIG_UNSIGNED
+ * Returns:
+ * ptr: address of pointer to next available buffer space
+ * -1 : error
+ * 0 : success
+ */
+static int
+dyndns_build_tsig(char **ptr, int buf_len, int msg_id, char *name,
+ int fudge_time, char *data, int data_size, int data_signed)
+{
+ char *namePtr;
+ struct timeval tp;
+ int signtime, fudge, rec_len;
+
+ if (data_signed == TSIG_UNSIGNED)
+ rec_len = strlen(name)+2 + 37;
+ else
+ rec_len = strlen(name)+2 + 45 + data_size;
+
+ if (rec_len > buf_len) {
+ syslog(LOG_ERR, "dyndns: no more buf for TSIG record\n");
+ return (-1);
+ }
+
+ namePtr = *ptr;
+ (void) dyndns_stuff_str(&namePtr, name); /* unique global name */
+ *ptr = namePtr;
+ if (data_signed == TSIG_SIGNED)
+ *ptr = dyndns_put_nshort(*ptr, ns_t_tsig);
+ *ptr = dyndns_put_nshort(*ptr, ns_c_any);
+ *ptr = dyndns_put_nlong(*ptr, 0);
+ if (data_signed == TSIG_SIGNED) {
+ /* 19 + 10 + data_size + 6 */
+ *ptr = dyndns_put_nshort(*ptr, 35 + data_size);
+ }
+ namePtr = *ptr;
+ (void) dyndns_stuff_str(&namePtr, "gss.microsoft.com");
+ *ptr = namePtr;
+ (void) gettimeofday(&tp, 0);
+ signtime = tp.tv_sec >> 16;
+ *ptr = dyndns_put_nlong(*ptr, signtime); /* sign time */
+ fudge = tp.tv_sec << 16;
+ fudge |= fudge_time;
+ *ptr = dyndns_put_nlong(*ptr, fudge); /* fudge time */
+ if (data_signed == TSIG_SIGNED) {
+ /* signed data size */
+ *ptr = dyndns_put_nshort(*ptr, data_size);
+ (void) memcpy(*ptr, data, data_size); /* signed data */
+ *ptr += data_size;
+ *ptr = dyndns_put_nshort(*ptr, msg_id); /* original id */
+ }
+ *ptr = dyndns_put_nshort(*ptr, 0); /* error */
+ *ptr = dyndns_put_nshort(*ptr, 0); /* other */
+ return (0);
+}
+
+/*
+ * dyndns_open_init_socket
+ * This routine creates a SOCK_STREAM or SOCK_DGRAM socket and initializes it
+ * by doing bind() and setting linger option to off.
+ *
+ * Parameters:
+ * sock_type: SOCK_STREAM for TCP or SOCK_DGRAM for UDP
+ * dest_addr: destination address in network byte order
+ * port : destination port number
+ * Returns:
+ * descriptor: descriptor referencing the created socket
+ * -1 : error
+ */
+int
+dyndns_open_init_socket(int sock_type, unsigned long dest_addr, int port)
+{
+ int s;
+ struct sockaddr_in my_addr;
+ struct linger l;
+ struct sockaddr_in serv_addr;
+
+ if ((s = socket(AF_INET, sock_type, 0)) == -1) {
+ syslog(LOG_ERR, "dyndns: socket err\n");
+ return (-1);
+ }
+
+ l.l_onoff = 0;
+ if (setsockopt(s, SOL_SOCKET, SO_LINGER,
+ (char *)&l, sizeof (l)) == -1) {
+ syslog(LOG_ERR, "dyndns: setsocket err\n");
+ (void) close(s);
+ return (-1);
+ }
+
+ bzero(&my_addr, sizeof (my_addr));
+ my_addr.sin_family = AF_INET;
+ my_addr.sin_port = htons(0);
+ my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ if (bind(s, (struct sockaddr *)&my_addr, sizeof (my_addr)) < 0) {
+ syslog(LOG_ERR, "dyndns: client bind err\n");
+ (void) close(s);
+ return (-1);
+ }
+
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_port = htons(port);
+ serv_addr.sin_addr.s_addr = dest_addr;
+
+ if (connect(s, (struct sockaddr *)&serv_addr,
+ sizeof (struct sockaddr_in)) < 0) {
+ syslog(LOG_ERR, "dyndns: client connect err (%s)\n",
+ strerror(errno));
+ (void) close(s);
+ return (-1);
+ }
+
+ return (s);
+}
+
+/*
+ * dyndns_acquire_cred
+ * This routine is used to acquire a GSS credential handle to a user's Kerberos
+ * ticket-granting ticket (TGT) stored locally on the system. If getting a
+ * handle fails, then a new TGT will be obtained again before trying to get a
+ * handle once more.
+ * The user's password is taken from the environment variable
+ * lookup.dns.dynamic.passwd and is encrypted.
+ * Paramaters:
+ * kinit_retry: if 0 then a new TGT can be obtained before second attempt to
+ * get a handle to TGT if first attempt fails
+ * Returns:
+ * user_name : name of user to get credential handle from
+ * credHandle : handle to user's credential (TGT)
+ * oid : contains Kerberos 5 object identifier
+ * kinit_retry: 1 if a new TGT has been acquired in this routine, otherwise 0
+ * -1 : error
+ */
+static int
+dyndns_acquire_cred(gss_cred_id_t *credHandle, char *user_name,
+ gss_OID *oid, int *kinit_retry)
+{
+ char *p, pwd[100];
+
+ smb_config_rdlock();
+ p = smb_config_getstr(SMB_CI_ADS_USER);
+ if (p == NULL || *p == 0) {
+ syslog(LOG_ERR, "No user configured for "
+ "secure dynamic DNS update.\n");
+ smb_config_unlock();
+ return (-1);
+ }
+ (void) strcpy(user_name, p);
+
+ p = smb_config_getstr(SMB_CI_ADS_PASSWD);
+ if (p == NULL || *p == 0) {
+ syslog(LOG_ERR, "No password configured for "
+ "secure dynamic DNS update.\n");
+ smb_config_unlock();
+ return (-1);
+ }
+ smb_config_unlock();
+
+ (void) strcpy(pwd, p);
+
+ return krb5_acquire_cred_kinit(user_name, pwd, credHandle,
+ oid, kinit_retry, "dyndns");
+}
+
+/*
+ * dyndns_build_tkey_msg
+ * This routine is used to build the TKEY message to transmit GSS tokens
+ * during GSS security context establishment for secure DNS update. The
+ * TKEY message format uses the DNS query message format. The TKEY section
+ * is the answer section of the query message format.
+ * Microsoft uses a value of 86400 seconds (24 hours) for key expiration time.
+ * Parameters:
+ * buf : buffer to build and store TKEY message
+ * key_name: a unique key name, this same key name must be also be used in
+ * the TSIG message
+ * out_tok : TKEY message data (GSS tokens)
+ * Returns:
+ * id : message id of this TKEY message
+ * message size: the size of the TKEY message
+ * -1 : error
+ */
+static int
+dyndns_build_tkey_msg(char *buf, char *key_name, uint16_t *id,
+ gss_buffer_desc *out_tok)
+{
+ int queryReq, zoneCount, preqCount, updateCount, additionalCount;
+ int zoneType, zoneClass;
+ char *bufptr;
+
+ queryReq = REQ_QUERY;
+ /* query section of query request */
+ zoneCount = 1;
+ /* answer section of query request */
+ preqCount = 1;
+ updateCount = 0;
+ additionalCount = 0;
+
+ (void) memset(buf, 0, MAX_TCP_SIZE);
+ bufptr = buf;
+ *id = smb_get_next_resid();
+
+ /* add TCP length info that follows this field */
+ bufptr = dyndns_put_nshort(bufptr,
+ 26 + (strlen(key_name)+2)*2 + 35 + out_tok->length);
+
+ if (dyndns_build_header(&bufptr, BUFLEN_TCP(bufptr, buf), *id, queryReq,
+ zoneCount, preqCount, updateCount, additionalCount, 0) == -1) {
+ return (-1);
+ }
+
+ zoneType = ns_t_tkey;
+ zoneClass = ns_c_in;
+ if (dyndns_build_quest_zone(&bufptr, BUFLEN_TCP(bufptr, buf), key_name,
+ zoneType, zoneClass) == -1) {
+ return (-1);
+ }
+
+ if (dyndns_build_tkey(&bufptr, BUFLEN_TCP(bufptr, buf), key_name,
+ 86400, out_tok->value, out_tok->length) == -1) {
+ return (-1);
+ }
+
+ return (bufptr - buf);
+}
+
+/*
+ * dyndns_establish_sec_ctx
+ * This routine is used to establish a security context with the DNS server
+ * by building TKEY messages and sending them to the DNS server. TKEY messages
+ * are also received from the DNS server for processing. The security context
+ * establishment is done with the GSS client on the system producing a token
+ * and sending the token within the TKEY message to the GSS server on the DNS
+ * server. The GSS server then processes the token and then send a TKEY reply
+ * message with a new token to be processed by the GSS client. The GSS client
+ * processes the new token and then generates a new token to be sent to the
+ * GSS server. This cycle is continued until the security establishment is
+ * done. TCP is used to send and receive TKEY messages.
+ * If gss_init_sec_context fails then a new TGT will be acquired so that
+ * security establishment can be retry once more by the caller after getting
+ * a handle to the new TGT (credential).
+ * Parameters:
+ * credHandle : handle to credential
+ * s : socket descriptor to DNS server
+ * key_name : TKEY key name
+ * dns_hostname: fully qualified DNS hostname
+ * oid : contains Kerberos 5 object identifier
+ * user_name : name of user to perform DNS update
+ * kinit_retry : if 0 and gss_init_sec_context fails then get new TGT so
+ * the caller can restart doing security context establishment
+ * Returns:
+ * gss_context : handle to security context
+ * kinit_retry : 1 if a new TGT has been acquired in this routine,
+ * otherwise 0
+ * do_acquire_cred: if 1 then caller will restart security context
+ * establishment
+ * -1 : error
+ */
+static int
+dyndns_establish_sec_ctx(gss_ctx_id_t *gss_context, gss_cred_id_t credHandle,
+ int s, char *key_name, char *dns_hostname, gss_OID oid, char *user_name,
+ int *kinit_retry, int *do_acquire_cred)
+{
+ uint16_t id, rid, rsz;
+ char buf[MAX_TCP_SIZE], buf2[MAX_TCP_SIZE];
+ int ret;
+ char *service_name, *tmpptr;
+ int service_sz;
+ OM_uint32 min, maj, time_rec;
+ gss_buffer_desc service_buf, in_tok, out_tok;
+ gss_name_t target_name;
+ gss_buffer_desc *inputptr;
+ int gss_flags;
+ OM_uint32 ret_flags;
+ int buf_sz;
+ char *p, pwd[100];
+
+ smb_config_rdlock();
+ p = smb_config_getstr(SMB_CI_ADS_PASSWD);
+ if (p == NULL || *p == 0) {
+ syslog(LOG_ERR, "No password configured for "
+ "secure dynamic DNS update.\n");
+ smb_config_unlock();
+ return (-1);
+ }
+ smb_config_unlock();
+ (void) strcpy(pwd, p);
+
+ service_sz = strlen(dns_hostname) + 5;
+ service_name = (char *)malloc(sizeof (char) * service_sz);
+ if (service_name == NULL) {
+ syslog(LOG_ERR, "Malloc failed for %d bytes ", service_sz);
+ smb_config_unlock();
+ return (-1);
+ }
+ (void) snprintf(service_name, service_sz, "DNS@%s", dns_hostname);
+ service_buf.value = service_name;
+ service_buf.length = strlen(service_name)+1;
+ if ((maj = gss_import_name(&min, &service_buf,
+ (gss_OID) gss_nt_service_name,
+ &target_name)) != GSS_S_COMPLETE) {
+ display_stat(maj, min);
+ (void) gss_release_oid(&min, &oid);
+ (void) free(service_name);
+ return (-1);
+ }
+ (void) free(service_name);
+
+ inputptr = GSS_C_NO_BUFFER;
+ *gss_context = GSS_C_NO_CONTEXT;
+ gss_flags = GSS_C_MUTUAL_FLAG | GSS_C_DELEG_FLAG | GSS_C_REPLAY_FLAG |
+ GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG;
+ do {
+ if (krb5_establish_sec_ctx_kinit(user_name, pwd, credHandle,
+ gss_context, target_name, oid, gss_flags, inputptr,
+ &out_tok, &ret_flags, &time_rec, kinit_retry,
+ do_acquire_cred, &maj, "dyndns") == -1) {
+ (void) gss_release_oid(&min, &oid);
+ (void) gss_release_name(&min, &target_name);
+ return (-1);
+ }
+
+ if ((maj == GSS_S_COMPLETE) &&
+ !(ret_flags & GSS_C_REPLAY_FLAG)) {
+ syslog(LOG_ERR, "dyndns: No GSS_C_REPLAY_FLAG\n");
+ if (out_tok.length > 0)
+ (void) gss_release_buffer(&min, &out_tok);
+ (void) gss_release_oid(&min, &oid);
+ (void) gss_release_name(&min, &target_name);
+ return (-1);
+ }
+
+ if ((maj == GSS_S_COMPLETE) &&
+ !(ret_flags & GSS_C_MUTUAL_FLAG)) {
+ syslog(LOG_ERR, "dyndns: No GSS_C_MUTUAL_FLAG\n");
+ if (out_tok.length > 0)
+ (void) gss_release_buffer(&min, &out_tok);
+ (void) gss_release_oid(&min, &oid);
+ (void) gss_release_name(&min, &target_name);
+ return (-1);
+ }
+
+ if (out_tok.length > 0) {
+ if ((buf_sz = dyndns_build_tkey_msg(buf, key_name,
+ &id, &out_tok)) <= 0) {
+ (void) gss_release_buffer(&min, &out_tok);
+ (void) gss_release_oid(&min, &oid);
+ (void) gss_release_name(&min, &target_name);
+ return (-1);
+ }
+
+ (void) gss_release_buffer(&min, &out_tok);
+
+ if (send(s, buf, buf_sz, 0) == -1) {
+ syslog(LOG_ERR, "dyndns: TKEY send error\n");
+ (void) gss_release_oid(&min, &oid);
+ (void) gss_release_name(&min, &target_name);
+ return (-1);
+ }
+
+ bzero(buf2, MAX_TCP_SIZE);
+ if (recv(s, buf2, MAX_TCP_SIZE, 0) == -1) {
+ syslog(LOG_ERR, "dyndns: TKEY "
+ "reply recv error\n");
+ (void) gss_release_oid(&min, &oid);
+ (void) gss_release_name(&min, &target_name);
+ return (-1);
+ }
+
+ ret = buf2[5] & 0xf; /* error field in TCP */
+ if (ret != NOERROR) {
+ syslog(LOG_ERR, "dyndns: Error in "
+ "TKEY reply: %d: ", ret);
+ dyndns_msg_err(ret);
+ (void) gss_release_oid(&min, &oid);
+ (void) gss_release_name(&min, &target_name);
+ return (-1);
+ }
+
+ tmpptr = &buf2[2];
+ (void) dyndns_get_nshort(tmpptr, &rid);
+ if (id != rid) {
+ (void) gss_release_oid(&min, &oid);
+ (void) gss_release_name(&min, &target_name);
+ return (-1);
+ }
+
+ tmpptr = &buf2[59+(strlen(key_name)+2)*2];
+ (void) dyndns_get_nshort(tmpptr, &rsz);
+ in_tok.length = rsz;
+
+ /* bsd38 -> 2*7=14 */
+ in_tok.value = &buf2[61+(strlen(key_name)+2)*2];
+ inputptr = &in_tok;
+ }
+
+ } while (maj != GSS_S_COMPLETE);
+
+ (void) gss_release_oid(&min, &oid);
+ (void) gss_release_name(&min, &target_name);
+
+ return (0);
+}
+
+/*
+ * dyndns_get_sec_context
+ * Get security context for secure dynamic DNS update. This routine opens
+ * a TCP socket to the DNS server and calls routines to get a handle to a
+ * locally cached user's credential and establish a security context with
+ * the DNS server to perform secure dynamic DNS update. If getting security
+ * context fails then a retry may be done after reobtaining new credential and
+ * getting a new credential handle. If obtaining new credential has been
+ * done earlier during getting a handle to credential then there is no need to
+ * do a retry for security context.
+ * Parameters:
+ * hostname: fully qualified hostname
+ * dns_ip : ip address of hostname in network byte order
+ * Returns:
+ * gss_handle: gss credential handle
+ * gss_context: gss security context
+ * -1: error
+ * 0: success
+ */
+static gss_ctx_id_t
+dyndns_get_sec_context(const char *hostname, int dns_ip)
+{
+ int s;
+ gss_cred_id_t credHandle;
+ gss_ctx_id_t gss_context;
+ gss_OID oid;
+ OM_uint32 min;
+ struct hostent *hentry;
+ int kinit_retry, do_acquire_cred;
+ char *key_name, dns_hostname[255], user_name[50];
+
+ key_name = (char *)hostname;
+
+ hentry = gethostbyaddr((char *)&dns_ip, 4, AF_INET);
+ if (hentry == NULL) {
+ syslog(LOG_ERR, "dyndns: Can't get DNS "
+ "hostname from DNS ip.\n");
+ return (NULL);
+ }
+ (void) strcpy(dns_hostname, hentry->h_name);
+
+ if ((s = dyndns_open_init_socket(SOCK_STREAM, dns_ip, 53)) < 0) {
+ return (NULL);
+ }
+
+ kinit_retry = 0;
+ do_acquire_cred = 0;
+ acquire_cred:
+
+ if (dyndns_acquire_cred(&credHandle, user_name, &oid, &kinit_retry)) {
+ (void) close(s);
+ return (NULL);
+ }
+
+ if (dyndns_establish_sec_ctx(&gss_context, credHandle, s, key_name,
+ dns_hostname, oid, user_name, &kinit_retry, &do_acquire_cred)) {
+ (void) gss_release_cred(&min, &credHandle);
+ if (do_acquire_cred) {
+ do_acquire_cred = 0;
+ goto acquire_cred;
+ }
+ (void) close(s);
+ return (NULL);
+ }
+
+ (void) close(s);
+
+ (void) gss_release_cred(&min, &credHandle);
+ return (gss_context);
+}
+
+/*
+ * dyndns_build_add_remove_msg
+ * This routine builds the update request message for adding and removing DNS
+ * entries which is used for non-secure and secure DNS update.
+ * This routine builds an UDP message.
+ * Parameters:
+ * buf : buffer to build message
+ * update_zone: the type of zone to update, use UPDATE_FORW for forward
+ * lookup zone, use UPDATE_REV for reverse lookup zone
+ * hostname : fully qualified hostname to update DNS with
+ * ip_addr : IP address of hostname
+ * life_time : cached time of this entry by others and not within DNS
+ * database
+ * update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry
+ * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all
+ * entries of the same resource name. Only valid for UPDATE_DEL.
+ * addit_cnt : Indicate how many record is in the additional section of
+ * the DNS message. A value of zero is always used with
+ * non-secure update message. For secure update message,
+ * the value will be one because the signed TSIG message
+ * is added as the additional record of the DNS update message.
+ * id : DNS message ID. If a positive value then this ID value is
+ * used, otherwise the next incremented value is used
+ * level : This is the domain level which we send the request to, level
+ * zero is the default level, it can go upto 2 in reverse zone
+ * and virtually to any level in forward zone.
+ * Returns:
+ * buf : buffer containing update message
+ * id : DNS message ID
+ * int : size of update message
+ * -1 : error
+ *
+ * This function is changed to handle dynamic DNS update retires to higher
+ * authoritative domains.
+ */
+static int
+dyndns_build_add_remove_msg(char *buf, int update_zone, const char *hostname,
+ const char *ip_addr, int life_time, int update_type, int del_type,
+ int addit_cnt, uint16_t *id, int level)
+{
+ int a, b, c, d;
+ char *bufptr;
+ int queryReq, zoneCount, preqCount, updateCount, additionalCount;
+ char *zone, *resource, *data, zone_buf[100], resrc_buf[100];
+ int zoneType, zoneClass, type, class, ttl;
+ char *p;
+
+ queryReq = REQ_UPDATE;
+ zoneCount = 1;
+ preqCount = 0;
+ updateCount = 1;
+ additionalCount = addit_cnt;
+
+ (void) memset(buf, 0, NS_PACKETSZ);
+ bufptr = buf;
+
+ if (*id == 0)
+ *id = smb_get_next_resid();
+
+ if (dyndns_build_header(&bufptr, BUFLEN_UDP(bufptr, buf), *id, queryReq,
+ zoneCount, preqCount, updateCount, additionalCount, 0) == -1) {
+ return (-1);
+ }
+
+ zoneType = ns_t_soa;
+ zoneClass = ns_c_in;
+
+ if (update_zone == UPDATE_FORW) {
+ p = (char *)hostname;
+
+ /* Try higher domains according to the level requested */
+ do {
+ /* domain */
+ if ((zone = (char *)strchr(p, '.')) == NULL)
+ return (-1);
+ zone += 1;
+ p = zone;
+ } while (--level >= 0);
+ resource = (char *)hostname;
+ data = (char *)ip_addr;
+ } else {
+ (void) sscanf(ip_addr, "%d.%d.%d.%d", &a, &b, &c, &d);
+ (void) sprintf(zone_buf, "%d.%d.%d.in-addr.arpa", c, b, a);
+ zone = p = zone_buf;
+
+ /* Try higher domains according to the level requested */
+ while (--level >= 0) {
+ /* domain */
+ if ((zone = (char *)strchr(p, '.')) == NULL) {
+ return (-1);
+ }
+ zone += 1;
+ p = zone;
+ }
+
+ (void) sprintf(resrc_buf, "%d.%d.%d.%d.in-addr.arpa",
+ d, c, b, a);
+ resource = resrc_buf; /* ip info */
+ data = (char *)hostname;
+ }
+
+ if (dyndns_build_quest_zone(&bufptr, BUFLEN_UDP(bufptr, buf), zone,
+ zoneType, zoneClass) == -1) {
+ return (-1);
+ }
+
+ if (update_zone == UPDATE_FORW)
+ type = ns_t_a;
+ else
+ type = ns_t_ptr;
+
+ if (update_type == UPDATE_ADD) {
+ class = ns_c_in;
+ ttl = life_time;
+ } else {
+ if (del_type == DEL_ONE)
+ class = ns_c_none; /* remove one */
+ else
+ class = ns_c_any; /* remove all */
+ ttl = 0;
+ }
+ if (dyndns_build_update(&bufptr, BUFLEN_UDP(bufptr, buf),
+ resource, type, class, ttl, data, update_zone,
+ update_type, del_type) == -1) {
+ return (-1);
+ }
+
+ return (bufptr - buf);
+}
+
+/*
+ * dyndns_build_unsigned_tsig_msg
+ * This routine is used to build the unsigned TSIG message for signing. The
+ * unsigned TSIG message contains the update request message with certain TSIG
+ * fields included. An error time of 300 seconds is used for fudge time. This
+ * is the number used by Microsoft clients.
+ * This routine builds a UDP message.
+ * Parameters:
+ * buf : buffer to build message
+ * update_zone: the type of zone to update, use UPDATE_FORW for forward
+ * lookup zone, use UPDATE_REV for reverse lookup zone
+ * hostname : fully qualified hostname to update DNS with
+ * ip_addr : IP address of hostname
+ * life_time : cached time of this entry by others and not within DNS
+ * database
+ * update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry
+ * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all
+ * entries of the same resource name. Only valid for UPDATE_DEL.
+ * key_name : same key name used in TKEY message
+ * id : DNS message ID. If a positive value then this ID value is
+ * used, otherwise the next incremented value is used
+ * level : This is the domain level which we send the request to, level
+ * zero is the default level, it can go upto 2 in reverse zone
+ * and virtually to any level in forward zone.
+ * Returns:
+ * buf : buffer containing update message
+ * id : DNS message ID
+ * int : size of update message
+ * -1 : error
+ */
+static int
+dyndns_build_unsigned_tsig_msg(char *buf, int update_zone, const char *hostname,
+ const char *ip_addr, int life_time, int update_type, int del_type,
+ char *key_name, uint16_t *id, int level)
+{
+ char *bufptr;
+ int buf_sz;
+
+ if ((buf_sz = dyndns_build_add_remove_msg(buf, update_zone, hostname,
+ ip_addr, life_time, update_type, del_type, 0, id, level)) <= 0) {
+ return (-1);
+ }
+
+ bufptr = buf + buf_sz;
+
+ if (dyndns_build_tsig(&bufptr, BUFLEN_UDP(bufptr, buf), 0,
+ key_name, 300, NULL, 0, TSIG_UNSIGNED) == -1) {
+ return (-1);
+ }
+
+ return (bufptr - buf);
+}
+
+/*
+ * dyndns_build_signed_tsig_msg
+ * This routine build the signed TSIG message which contains the update
+ * request message encrypted. An error time of 300 seconds is used for fudge
+ * time. This is the number used by Microsoft clients.
+ * This routine builds a UDP message.
+ * Parameters:
+ * buf : buffer to build message
+ * update_zone: the type of zone to update, use UPDATE_FORW for forward
+ * lookup zone, use UPDATE_REV for reverse lookup zone
+ * hostname : fully qualified hostname to update DNS with
+ * ip_addr : IP address of hostname
+ * life_time : cached time of this entry by others and not within DNS
+ * database
+ * update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry
+ * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all
+ * entries of the same resource name. Only valid for UPDATE_DEL.
+ * key_name : same key name used in TKEY message
+ * id : DNS message ID. If a positive value then this ID value is
+ * used, otherwise the next incremented value is used
+ * in_mic : the update request message encrypted
+ * level : This is the domain level which we send the request to, level
+ * zero is the default level, it can go upto 2 in reverse zone
+ * and virtually to any level in forward zone.
+ *
+ * Returns:
+ * buf : buffer containing update message
+ * id : DNS message ID
+ * int : size of update message
+ * -1 : error
+ */
+static int
+dyndns_build_signed_tsig_msg(char *buf, int update_zone, const char *hostname,
+ const char *ip_addr, int life_time, int update_type, int del_type,
+ char *key_name, uint16_t *id, gss_buffer_desc *in_mic, int level)
+{
+ char *bufptr;
+ int buf_sz;
+
+ if ((buf_sz = dyndns_build_add_remove_msg(buf, update_zone, hostname,
+ ip_addr, life_time, update_type, del_type, 1, id, level)) <= 0) {
+ return (-1);
+ }
+
+ bufptr = buf + buf_sz;
+
+ if (dyndns_build_tsig(&bufptr, BUFLEN_UDP(bufptr, buf),
+ *id, key_name, 300, in_mic->value,
+ in_mic->length, TSIG_SIGNED) == -1) {
+ return (-1);
+ }
+
+ return (bufptr - buf);
+}
+
+/*
+ * dyndns_udp_send_recv
+ * This routine sends and receives UDP DNS request and reply messages. Time
+ * out value and retry count is indicated by two environment variables:
+ * lookup_dns_retry_cnt
+ * lookup_dns_retry_sec
+ * If either of these two variables are undefined or their value exceed the
+ * value of 10 then a default value of 3 retry and/or a default value of 3
+ * secs are used.
+ *
+ * Pre-condition: Caller must call dyndns_open_init_socket() before calling
+ * this function.
+ *
+ * Parameters:
+ * s : socket descriptor
+ * buf : buffer containing data to send
+ * buf_sz : size of data to send
+ * Returns:
+ * -1 : error
+ * rec_buf: reply dat
+ * 0 : success
+ */
+int
+dyndns_udp_send_recv(int s, char *buf, int buf_sz, char *rec_buf)
+{
+ int i, retval, addr_len, max_retries;
+ struct timeval tv, timeout;
+ fd_set rfds;
+ struct sockaddr_in from_addr;
+ char *p;
+
+ smb_config_rdlock();
+ p = smb_config_getstr(SMB_CI_DYNDNS_RETRY_COUNT);
+ if (p == NULL || *p == 0) {
+ max_retries = 3;
+ } else {
+ max_retries = atoi(p);
+ if (max_retries < 1 || max_retries > 10)
+ max_retries = 3;
+ }
+
+ p = smb_config_getstr(SMB_CI_DYNDNS_RETRY_SEC);
+ timeout.tv_usec = 0;
+ if (p == NULL || *p == 0) {
+ timeout.tv_sec = 3;
+ } else {
+ timeout.tv_sec = atoi(p);
+ if (timeout.tv_sec < 1 || timeout.tv_sec > 10)
+ timeout.tv_sec = 3;
+ }
+ smb_config_unlock();
+
+ for (i = 0; i < max_retries + 1; i++) {
+ if (send(s, buf, buf_sz, 0) == -1) {
+ syslog(LOG_ERR, "dyndns: UDP send error (%s)\n",
+ strerror(errno));
+ return (-1);
+ }
+
+ FD_ZERO(&rfds);
+ FD_SET(s, &rfds);
+
+ tv = timeout;
+
+ retval = select(s+1, &rfds, NULL, NULL, &tv);
+
+ if (retval == -1) {
+ return (-1);
+ } else if (retval > 0) {
+ bzero(rec_buf, NS_PACKETSZ);
+ /* required by recvfrom */
+ addr_len = sizeof (struct sockaddr_in);
+ if (recvfrom(s, rec_buf, NS_PACKETSZ, 0,
+ (struct sockaddr *)&from_addr, &addr_len) == -1) {
+ syslog(LOG_ERR, "dyndns: UDP recv err\n");
+ return (-1);
+ }
+ break;
+ }
+ }
+
+ if (i == (max_retries + 1)) { /* did not receive anything */
+ syslog(LOG_ERR, "dyndns: max retries for UDP recv reached\n");
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * dyndns_sec_add_remove_entry
+ * Perform secure dynamic DNS update after getting security context.
+ * This routine opens a UDP socket to the DNS sever, gets the security context,
+ * builds the unsigned TSIG message and signed TSIG message. The signed TSIG
+ * message containing the encrypted update request message is sent to the DNS
+ * server. The response is received and check for error. If there is no
+ * error then credential handle and security context are released and the local
+ * NSS cached is purged.
+ * Parameters:
+ * update_zone : UPDATE_FORW for forward zone, UPDATE_REV for reverse zone
+ * hostname : fully qualified hostname
+ * ip_addr : ip address of hostname in string format
+ * life_time : cached time of this entry by others and not within DNS
+ * database
+ * max_retries : maximum retries for sending DNS update request
+ * recv_timeout: receive timeout
+ * update_type : UPDATE_ADD for adding entry, UPDATE_DEL for removing entry
+ * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all
+ * entries of the same resource name. Only valid for UPDATE_DEL
+ * dns_str : DNS IP address in string format
+ * Returns:
+ * -1: error
+ * 0: success
+ *
+ * This function is enhanced to handle the case of NOTAUTH error when DNS server
+ * is not authoritative for specified zone. In this case we need to resend the
+ * same request to the higher authoritative domains.
+ * This is true for both secure and unsecure dynamic DNS updates.
+ */
+static int
+dyndns_sec_add_remove_entry(int update_zone, const char *hostname,
+ const char *ip_addr, int life_time, int update_type, int del_type,
+ char *dns_str)
+{
+ int s2;
+ uint16_t id, rid;
+ char buf[NS_PACKETSZ], buf2[NS_PACKETSZ];
+ int ret;
+ OM_uint32 min, maj;
+ gss_buffer_desc in_mic, out_mic;
+ gss_ctx_id_t gss_context;
+ int dns_ip;
+ char *key_name;
+ int buf_sz;
+ int level = 0;
+
+ assert(dns_str);
+ assert(*dns_str);
+
+ dns_ip = inet_addr(dns_str);
+
+sec_retry_higher:
+
+ if ((gss_context = dyndns_get_sec_context(hostname,
+ dns_ip)) == NULL) {
+ return (-1);
+ }
+
+ key_name = (char *)hostname;
+
+ if ((s2 = dyndns_open_init_socket(SOCK_DGRAM, dns_ip, 53)) < 0) {
+ (void) gss_delete_sec_context(&min, &gss_context, NULL);
+ return (-1);
+ }
+
+ id = 0;
+ if ((buf_sz = dyndns_build_unsigned_tsig_msg(buf, update_zone, hostname,
+ ip_addr, life_time, update_type, del_type,
+ key_name, &id, level)) <= 0) {
+ (void) close(s2);
+ (void) gss_delete_sec_context(&min, &gss_context, NULL);
+ return (-1);
+ }
+
+ in_mic.length = buf_sz;
+ in_mic.value = buf;
+
+ /* sign update message */
+ if ((maj = gss_get_mic(&min, gss_context, 0, &in_mic, &out_mic)) !=
+ GSS_S_COMPLETE) {
+ display_stat(maj, min);
+ (void) close(s2);
+ (void) gss_delete_sec_context(&min, &gss_context, NULL);
+ return (-1);
+ }
+
+ if ((buf_sz = dyndns_build_signed_tsig_msg(buf, update_zone, hostname,
+ ip_addr, life_time, update_type, del_type, key_name, &id,
+ &out_mic, level)) <= 0) {
+ (void) close(s2);
+ (void) gss_release_buffer(&min, &out_mic);
+ (void) gss_delete_sec_context(&min, &gss_context, NULL);
+ return (-1);
+ }
+
+ (void) gss_release_buffer(&min, &out_mic);
+
+ if (dyndns_udp_send_recv(s2, buf, buf_sz, buf2)) {
+ (void) close(s2);
+ (void) gss_delete_sec_context(&min, &gss_context, NULL);
+ return (-1);
+ }
+
+ (void) close(s2);
+
+ (void) gss_delete_sec_context(&min, &gss_context, NULL);
+
+ ret = buf2[3] & 0xf; /* error field in UDP */
+
+ /*
+ * If it is a NOTAUTH error we should retry with higher domains
+ * until we get a successful reply or the maximum retries is met.
+ */
+ if (ret == NOTAUTH && level++ < MAX_AUTH_RETRIES)
+ goto sec_retry_higher;
+
+ /* check here for update request is successful */
+ if (ret != NOERROR) {
+ syslog(LOG_ERR, "dyndns: Error in TSIG reply: %d: ", ret);
+ dyndns_msg_err(ret);
+ return (-1);
+ }
+
+ (void) dyndns_get_nshort(buf2, &rid);
+ if (id != rid)
+ return (-1);
+
+ return (0);
+}
+
+/*
+ * dyndns_seach_entry
+ * Query DNS server for entry. This routine can indicate if an entry exist
+ * or not during forward or reverse lookup. Also can indicate if the data
+ * of the entry matched. For example, for forward lookup, the entry is
+ * searched using the hostname and the data is the IP address. For reverse
+ * lookup, the entry is searched using the IP address and the data is the
+ * hostname.
+ * Parameters:
+ * update_zone: UPDATE_FORW for forward zone, UPDATE_REV for reverse zone
+ * hostname : fully qualified hostname
+ * ip_addr : ip address of hostname in string format
+ * update_type: UPDATE_ADD for adding entry, UPDATE_DEL for removing entry
+ * Returns:
+ * time_out: no use
+ * is_match: is 1 for found matching entry, otherwise 0
+ * 1 : an entry exist but not necessarily match
+ * 0 : an entry does not exist
+ */
+/*ARGSUSED*/
+static int
+dyndns_search_entry(int update_zone, const char *hostname, const char *ip_addr,
+ int update_type, struct timeval *time_out, int *is_match)
+{
+ struct hostent *hentry;
+ struct in_addr in;
+ in_addr_t ip;
+ int i;
+
+ *is_match = 0;
+ if (update_zone == UPDATE_FORW) {
+ hentry = gethostbyname(hostname);
+ if (hentry) {
+ ip = inet_addr(ip_addr);
+ for (i = 0; hentry->h_addr_list[i]; i++) {
+ (void) memcpy(&in.s_addr,
+ hentry->h_addr_list[i], sizeof (in.s_addr));
+ if (ip == in.s_addr) {
+ *is_match = 1;
+ break;
+ }
+ }
+ return (1);
+ }
+ } else {
+ int dns_ip = inet_addr(ip_addr);
+ hentry = gethostbyaddr((char *)&dns_ip, 4, AF_INET);
+ if (hentry) {
+ if (strncasecmp(hentry->h_name, hostname,
+ strlen(hostname)) == 0) {
+ *is_match = 1;
+ }
+ return (1);
+ }
+ }
+
+ /* entry does not exist */
+ return (0);
+}
+
+/*
+ * dyndns_add_remove_entry
+ * Perform non-secure dynamic DNS update. If fail then tries secure update.
+ * This routine opens a UDP socket to the DNS sever, build the update request
+ * message, and sends the message to the DNS server. The response is received
+ * and check for error. If there is no error then the local NSS cached is
+ * purged. DNS may be used to check to see if an entry already exist before
+ * adding or to see if an entry does exist before removing it. Adding
+ * duplicate entries or removing non-existing entries does not cause any
+ * problems. DNS is not check when doing a delete all.
+ * Parameters:
+ * update_zone: UPDATE_FORW for forward zone, UPDATE_REV for reverse zone
+ * hostname : fully qualified hostname
+ * ip_addr : ip address of hostname in string format
+ * life_time : cached time of this entry by others and not within DNS
+ * database
+ * update_type: UPDATE_ADD to add entry, UPDATE_DEL to remove entry
+ * do_check : DNS_CHECK to check first in DNS, DNS_NOCHECK for no DNS
+ * checking before update
+ * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all
+ * entries of the same resource name. Only valid for UPDATE_DEL.
+ * dns_str : DNS IP address in string format
+ * Returns:
+ * -1: error
+ * 0: success
+ *
+ * This function is enhanced to handle the case of NOTAUTH error when DNS server
+ * is not authoritative for specified zone. In this case we need to resend the
+ * same request to the higher authoritative domains.
+ * This is true for both secure and unsecure dynamic DNS updates.
+ */
+static int
+dyndns_add_remove_entry(int update_zone, const char *hostname,
+ const char *ip_addr, int life_time, int update_type,
+ int do_check, int del_type, char *dns_str)
+{
+ int s;
+ uint16_t id, rid;
+ char buf[NS_PACKETSZ], buf2[NS_PACKETSZ];
+ int ret, dns_ip;
+ int is_exist, is_match;
+ struct timeval timeout;
+ int buf_sz;
+ int level = 0;
+
+ assert(dns_str);
+ assert(*dns_str);
+
+ dns_ip = inet_addr(dns_str);
+
+ if (do_check == DNS_CHECK && del_type != DEL_ALL) {
+ is_exist = dyndns_search_entry(update_zone, hostname, ip_addr,
+ update_type, &timeout, &is_match);
+
+ if (update_type == UPDATE_ADD && is_exist && is_match) {
+ return (0);
+ } else if (update_type == UPDATE_DEL && !is_exist) {
+ return (0);
+ }
+ }
+
+retry_higher:
+ if ((s = dyndns_open_init_socket(SOCK_DGRAM, dns_ip, 53)) < 0) {
+ return (-1);
+ }
+
+ id = 0;
+ if ((buf_sz = dyndns_build_add_remove_msg(buf, update_zone, hostname,
+ ip_addr, life_time, update_type, del_type, 0, &id, level)) <= 0) {
+ (void) close(s);
+ return (-1);
+ }
+
+ if (dyndns_udp_send_recv(s, buf, buf_sz, buf2)) {
+ (void) close(s);
+ return (-1);
+ }
+
+ (void) close(s);
+
+ ret = buf2[3] & 0xf; /* error field in UDP */
+
+ /*
+ * If it is a NOTAUTH error we should retry with higher domains
+ * until we get a successful reply
+ */
+ if (ret == NOTAUTH && level++ < MAX_AUTH_RETRIES)
+ goto retry_higher;
+
+ /* check here for update request is successful */
+ if (ret == NOERROR) {
+ (void) dyndns_get_nshort(buf2, &rid);
+ if (id != rid)
+ return (-1);
+ return (0);
+ }
+
+ if (ret == NOTIMP) {
+ syslog(LOG_ERR, "dyndns: DNS does not "
+ "support dynamic update\n");
+ return (-1);
+ } else if (ret == NOTAUTH) {
+ syslog(LOG_ERR, "dyndns: DNS is not authoritative for "
+ "zone name in zone section\n");
+ return (-1);
+ }
+
+ ret = dyndns_sec_add_remove_entry(update_zone, hostname,
+ ip_addr, life_time, update_type, del_type, dns_str);
+
+ return (ret);
+}
+
+/*
+ * dyndns_add_entry
+ * Main routine to add an entry into DNS. The attempt will be made on the
+ * the servers returned by smb_get_nameserver(). Upon a successful
+ * attempt on any one of the server, the function will exit with 0.
+ * Otherwise, -1 is retuned to indicate the update attempt on all the
+ * nameservers has failed.
+ *
+ * Parameters:
+ * update_zone: the type of zone to update, use UPDATE_FORW for forward
+ * lookup zone, use UPDATE_REV for reverse lookup zone
+ * hostname : fully qualified hostname
+ * ip_addr : ip address of hostname in string format
+ * life_time : cached time of this entry by others and not within DNS
+ * database
+ * Returns:
+ * -1: error
+ * 0: success
+ */
+static int
+dyndns_add_entry(int update_zone, const char *hostname, const char *ip_addr,
+ int life_time)
+{
+ char *dns_str;
+ struct in_addr ns_list[MAXNS];
+ int i, cnt;
+ int addr, rc = 0;
+
+ if (hostname == NULL || ip_addr == NULL) {
+ return (-1);
+ }
+
+ addr = (int)inet_addr(ip_addr);
+ if ((addr == -1) || (addr == 0)) {
+ return (-1);
+ }
+
+ cnt = smb_get_nameservers(ns_list, MAXNS);
+
+ for (i = 0; i < cnt; i++) {
+ dns_str = inet_ntoa(ns_list[i]);
+ if ((dns_str == NULL) ||
+ (strcmp(dns_str, "0.0.0.0") == 0)) {
+ continue;
+ }
+
+ if (update_zone == UPDATE_FORW) {
+ syslog(LOG_DEBUG, "Dynamic update on forward lookup "
+ "zone for %s (%s)...\n", hostname, ip_addr);
+ } else {
+ syslog(LOG_DEBUG, "Dynamic update on reverse lookup "
+ "zone for %s (%s)...\n", hostname, ip_addr);
+ }
+ if (dyndns_add_remove_entry(update_zone, hostname,
+ ip_addr, life_time,
+ UPDATE_ADD, DNS_NOCHECK, DEL_NONE, dns_str) != -1) {
+ rc = 1;
+ break;
+ }
+ }
+
+ return (rc ? 0 : -1);
+}
+
+/*
+ * dyndns_remove_entry
+ * Main routine to remove an entry or all entries of the same resource name
+ * from DNS. The update attempt will be made on the primary DNS server. If
+ * there is a failure then another attempt will be made on the secondary DNS
+ * server.
+ * Parameters:
+ * update_zone: the type of zone to update, use UPDATE_FORW for forward
+ * lookup zone, use UPDATE_REV for reverse lookup zone
+ * hostname : fully qualified hostname
+ * ip_addr : ip address of hostname in string format
+ * del_type : DEL_ONE for deleting one entry, DEL_ALL for deleting all
+ * entries of the same resource name. Only valid for UPDATE_DEL
+ * Returns:
+ * -1: error
+ * 0: success
+ */
+static int
+dyndns_remove_entry(int update_zone, const char *hostname, const char *ip_addr,
+ int del_type)
+{
+ char *dns_str;
+ struct in_addr ns_list[MAXNS];
+ int i, cnt, scnt;
+ int addr;
+
+ if ((hostname == NULL || ip_addr == NULL)) {
+ return (-1);
+ }
+
+ addr = (int)inet_addr(ip_addr);
+ if ((addr == -1) || (addr == 0)) {
+ return (-1);
+ }
+
+ cnt = smb_get_nameservers(ns_list, MAXNS);
+ scnt = 0;
+
+ for (i = 0; i < cnt; i++) {
+ dns_str = inet_ntoa(ns_list[i]);
+ if ((dns_str == NULL) ||
+ (strcmp(dns_str, "0.0.0.0") == 0)) {
+ continue;
+ }
+
+ if (update_zone == UPDATE_FORW) {
+ if (del_type == DEL_ONE) {
+ syslog(LOG_DEBUG, "Dynamic update "
+ "on forward lookup "
+ "zone for %s (%s)...\n", hostname, ip_addr);
+ } else {
+ syslog(LOG_DEBUG, "Removing all "
+ "entries of %s "
+ "in forward lookup zone...\n", hostname);
+ }
+ } else {
+ if (del_type == DEL_ONE) {
+ syslog(LOG_DEBUG, "Dynamic update "
+ "on reverse lookup "
+ "zone for %s (%s)...\n", hostname, ip_addr);
+ } else {
+ syslog(LOG_DEBUG, "Removing all "
+ "entries of %s "
+ "in reverse lookup zone...\n", ip_addr);
+ }
+ }
+ if (dyndns_add_remove_entry(update_zone, hostname, ip_addr, 0,
+ UPDATE_DEL, DNS_NOCHECK, del_type, dns_str) != -1) {
+ scnt++;
+ break;
+ }
+ }
+ if (scnt)
+ return (0);
+ return (-1);
+}
+
+/*
+ * dyndns_update
+ * Perform dynamic update on both forward and reverse lookup zone using
+ * current hostname and IP addresses. Before updating DNS, existing host
+ * entries with the same hostname in the forward lookup zone are removed
+ * and existing pointer entries with the same IP addresses in the reverse
+ * lookup zone are removed. After DNS update, host entries for current
+ * hostname will show current IP addresses and pointer entries for current
+ * IP addresses will show current hostname.
+ * Parameters:
+ * None
+ * Returns:
+ * -1: some dynamic DNS updates errors
+ * 0: successful
+ */
+int
+dyndns_update(void)
+{
+ int i, forw_update_ok, error;
+ char fqdn[MAXHOSTNAMELEN];
+ char *my_ip;
+ int nc_cnt;
+ struct in_addr addr;
+ int rc;
+
+ if (!dyndns_enabled())
+ return (-1);
+
+ if (smb_getfqhostname(fqdn, MAXHOSTNAMELEN) != 0)
+ return (-1);
+
+ nc_cnt = smb_nic_get_num();
+
+ error = 0;
+ forw_update_ok = 0;
+
+ /*
+ * Dummy IP is okay since we are removing all using the hostname.
+ */
+ if (dyndns_remove_entry(UPDATE_FORW, fqdn, "1.1.1.1", DEL_ALL) == 0) {
+ forw_update_ok = 1;
+ } else {
+ error++;
+ }
+
+ for (i = 0; i < nc_cnt; i++) {
+ net_cfg_t cfg;
+ if (smb_nic_get_byind(i, &cfg) == NULL)
+ break;
+ addr.s_addr = cfg.ip;
+ if (addr.s_addr == 0)
+ continue;
+ if (smb_nic_status(cfg.ifname, IFF_STANDBY) ||
+ smb_nic_status(cfg.ifname, IFF_PRIVATE))
+ continue;
+
+ my_ip = (char *)strdup(inet_ntoa(addr));
+ if (my_ip == NULL) {
+ error++;
+ continue;
+ }
+
+ if (forw_update_ok) {
+ rc = dyndns_add_entry(UPDATE_FORW, fqdn, my_ip,
+ DDNS_TTL);
+
+ if (rc == -1)
+ error++;
+ }
+
+ rc = dyndns_remove_entry(UPDATE_REV, fqdn, my_ip, DEL_ALL);
+ if (rc == 0) {
+ rc = dyndns_add_entry(UPDATE_REV, fqdn, my_ip,
+ DDNS_TTL);
+ }
+
+ if (rc == -1)
+ error++;
+
+ (void) free(my_ip);
+ }
+
+ return ((error == 0) ? 0 : -1);
+}
+
+/*
+ * dyndns_clear_rev_zone
+ * Clear the rev zone records. Must be called to clear the OLD if list
+ * of down records prior to updating the list with new information.
+ *
+ * Parameters:
+ * None
+ * Returns:
+ * -1: some dynamic DNS updates errors
+ * 0: successful
+ */
+int
+dyndns_clear_rev_zone(void)
+{
+ int i, error;
+ char fqdn[MAXHOSTNAMELEN];
+ char *my_ip;
+ int nc_cnt;
+ struct in_addr addr;
+ int rc;
+
+ if (!dyndns_enabled())
+ return (-1);
+
+ if (smb_getfqhostname(fqdn, MAXHOSTNAMELEN) != 0)
+ return (-1);
+
+ nc_cnt = smb_nic_get_num();
+
+ error = 0;
+
+ for (i = 0; i < nc_cnt; i++) {
+ net_cfg_t cfg;
+ if (smb_nic_get_byind(i, &cfg) == NULL)
+ break;
+ addr.s_addr = cfg.ip;
+ if (addr.s_addr == 0)
+ continue;
+ if (smb_nic_status(cfg.ifname, IFF_STANDBY) ||
+ smb_nic_status(cfg.ifname, IFF_PRIVATE))
+ continue;
+
+ my_ip = (char *)strdup(inet_ntoa(addr));
+ if (my_ip == NULL) {
+ error++;
+ continue;
+ }
+
+ rc = dyndns_remove_entry(UPDATE_REV, fqdn, my_ip, DEL_ALL);
+ if (rc != 0)
+ error++;
+
+ (void) free(my_ip);
+ }
+
+ return ((error == 0) ? 0 : -1);
+}