/*
* 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
* 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
*/
/*
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <resolv.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <syslog.h>
#include <kerberosv5/krb5.h>
#include "res_update.h"
#include <smbns_dyndns.h>
#include <smbns_krb.h>
/*
* The following can be removed once head/arpa/nameser_compat.h
* defines BADSIG, BADKEY and BADTIME.
*/
#ifndef BADSIG
#endif /* BADSIG */
#ifndef BADKEY
#endif /* BADKEY */
#ifndef BADTIME
#endif /* BADTIME */
/*
* DYNDNS_TTL is the time to live in DNS caches. Note that this
* does not affect the entry in the authoritative DNS database.
*/
/* Microsoft AD interoperability */
/* lint-free alternatives to NS_PUT16, NS_PUT32 */
/* add a host integer to a network short */
/*
* Dynamic DNS update API for kclient.
*
* Returns 0 upon success. Otherwise, returns -1.
*/
int
{
int rc;
if (smb_nic_init() != SMB_NIC_SUCCESS)
return (-1);
(void) smb_strlwr(fqdn);
smb_nic_fini();
return (rc);
}
/*
* Convert IPv4 or IPv6 sockaddr to hostname.
* The value is lowercase per RFC 4120 section 6.2.1.
*/
static int
{
int rc;
socklen = sizeof (struct sockaddr_in);
socklen = sizeof (struct sockaddr_in6);
else
socklen = 0;
(void) smb_strlwr(hostname);
return (rc);
}
/*
* Log a DNS error message
*/
static void
{
struct {
int errnum;
char *errmsg;
} errtab[] = {
{ FORMERR, "message format error" },
{ SERVFAIL, "server internal error" },
{ NXDOMAIN, "entry should exist but does not exist" },
{ NOTIMP, "not supported" },
{ REFUSED, "operation refused" },
{ YXDOMAIN, "entry should not exist but does exist" },
{ YXRRSET, "RRSet should not exist but does exist" },
{ NXRRSET, "RRSet should exist but does not exist" },
{ NOTAUTH, "server is not authoritative for specified zone" },
{ NOTZONE, "name not within specified zone" },
{ BADSIG, "bad transaction signature (TSIG)" },
{ BADKEY, "bad transaction key (TKEY)" },
{ BADTIME, "time not synchronized" },
};
int i;
return;
break;
}
}
}
/*
* Log a DNS update description message
*/
static void
{
const char *zone_str;
const char *af_str;
return;
}
switch (update_op) {
case DYNDNS_UPDATE_ADD:
"add hostname %s %s address %s in %s zone",
break;
case DYNDNS_UPDATE_DEL_ALL:
if (update_zone == DYNDNS_ZONE_FWD) {
"delete all %s addresses for hostname %s "
} else if (update_zone == DYNDNS_ZONE_REV) {
"delete all hostnames for %s address %s "
}
break;
case DYNDNS_UPDATE_DEL_CLEAR:
if (update_zone == DYNDNS_ZONE_FWD) {
"delete hostname %s in forward zone", hostname);
} else if (update_zone == DYNDNS_ZONE_REV) {
"delete %s address %s in reverse zone",
}
break;
case DYNDNS_UPDATE_DEL_ONE:
"delete hostname %s %s address %s in %s zone",
break;
}
}
/*
* 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
{
return;
return;
return;
return;
}
/*
* dyndns_addr_to_ptrname
*
* Convert a network address structure into a domain name for reverse lookup.
* IPv4 addresses are converted to in-addr.arpa names (RFC 1035 section 3.5).
* IPv6 addresses are converted to ip6.arpa names (RFC 3596 section 2.5).
*
* Parameters:
* af: address family of *addr, AF_INET or AF_INET6
* addr: pointer to in_addr or in6_addr address structure
* buf: buffer to hold reverse domain name
* buf_sz: length of buffer
* Returns:
* 0 on error, or length of domain name written to buf
*/
static size_t
{
union {
} u;
uint8_t a, b, c, d;
int i;
switch (af) {
case AF_INET:
d, c, b, a);
return (0);
d, c, b, a);
break;
case AF_INET6:
/*
* The IPv6 reverse lookup domain name format has
* 4 chars for each address byte, followed by "ip6.arpa".
*/
return (0);
for (i = (IPV6_ADDR_LEN - 1); i >= 0; i--) {
*buf++ = '.';
*buf++ = '.';
}
break;
default:
return (0);
}
return (len);
}
/*
* dyndns_get_update_info
*
* Given a valid resolver context, update direction, and a host name and
* address pair, return the information needed to perform a DNS update.
* DNS zone and SOA nameservers are found by searching current DNS data.
*
* Example:
* dyndns_get_update_info(statp, DYNDNS_ZONE_FWD, type,
* "host.org.example.com", "10.0.0.1", ...) might return:
* type: ns_t_a
* zone_buf: "example.com"
* name_buf: "host.org.example.com"
* data_buf: "10.0.0.1"
* dyndns_get_update_info(statp, DYNDNS_ZONE_REV, type,
* "host.org.example.com", "10.0.0.1", ...) might return:
* type: ns_t_ptr
* zone_buf: "10.in-addr.arpa"
* name_buf: "1.0.0.10.in-addr.arpa"
* data_buf: "host.org.example.com"
*
* Parameters:
* statp: resolver state
* update_zone: one of DYNDNS_ZONE_FWD or DYNDNS_ZONE_REV
* hostname: host name, as a string
* af: address family of *addr, AF_INET or AF_INET6
* addr: pointer to in_addr or in6_addr address structure
* type: type of DNS record to update
* zone_buf: buffer to receive zone owning name
* zone_sz: size of zone_buf buffer
* name_buf: buffer to receive name to update
* name_sz: size of name_buf buffer
* data_buf: buffer to receive data to update for name
* data_sz: size of data_buf buffer
* ns: buffer to receive nameservers found for zone
* ns_cnt: number of nameservers that fit in ns buffer
* Returns:
* -1 on error, or number of nameservers returned in ns
*/
static int
{
switch (update_zone) {
case DYNDNS_ZONE_FWD:
switch (af) {
case AF_INET:
break;
case AF_INET6:
break;
default:
return (-1);
}
return (-1);
return (-1);
break;
case DYNDNS_ZONE_REV:
return (-1);
return (-1);
return (-1);
break;
default:
return (-1);
}
}
/*
* dyndns_build_tkey
*
* Build a TKEY RDATA section according to RFC 2930 section 2.
*
* May be called with buf == NULL and buf_sz == 0, in which case the
* function does not actually build the TKEY RDATA, but only returns the
* space needed for TKEY RDATA as specified by the tkey parameter.
*
* Parameters:
* buf: buffer to store TKEY RDATA
* buf_sz: buffer length
* tkey: pointer to DNS TKEY RDATA structure
* Returns:
* -1 on error, or length of data written (or that would be written) to buf
*/
static ssize_t
{
return (-1);
}
return (-1);
}
return (-1);
}
return (tkey_len);
return (-1);
}
"tk_key_size > 0 && tk_key_data == NULL");
return (-1);
}
"tk_other_size > 0 && tk_other_data == NULL");
return (-1);
}
if (tkey->tk_key_size > 0) {
}
if (tkey->tk_other_size > 0)
return (tkey_len);
}
/*
* dyndns_build_tsig
*
* Build a complete TSIG RR section according to RFC 2845 section 2.3,
* including either all fields, or only those fields used in computation
* of the message signature data.
*
* Because the TSIG RR is added to an existing DNS message for message
* digest computation, this function takes as parameters a buffer
* containing an existing message and its length. The TSIG RR is
* appended to the existing message in the same buffer.
*
* May be called with buf == NULL and buf_sz == 0, in which case the
* function does not actually build the TSIG RR, but only returns the
* space needed for a TSIG RR as specified by the other parameters.
*
* Parameters:
* buf: buffer to store TSIG RR
* buf_sz: buffer length
* msg_len: length of existing message in buffer
* key_name: key name, in C string format
* tsig: pointer to DNS TSIG RDATA structure
* digest_data: one of DYNDNS_DIGEST_SIGNED or DYNDNS_DIGEST_UNSIGNED
* Returns:
* -1 on error, or length of data written (or that would be written) to buf
*/
static ssize_t
{
return (-1);
}
return (-1);
}
return (-1);
}
return (-1);
}
switch (digest_data) {
case DYNDNS_DIGEST_SIGNED:
break;
case DYNDNS_DIGEST_UNSIGNED:
break;
default:
return (-1);
}
return (tsig_rrlen + tsig_rdlen);
return (-1);
}
"ts_mac_size > 0 && ts_mac_data == NULL");
return (-1);
}
"ts_other_size > 0 && ts_other_data == NULL");
return (-1);
}
/*
* TSIG RR fields:
* name [type] class ttl [rdlen]
*/
if (digest_data == DYNDNS_DIGEST_SIGNED)
DYNDNS_PUT32(0, buf);
if (digest_data == DYNDNS_DIGEST_SIGNED)
/*
* TSIG RDATA fields:
* alg_name sign_time fudge_time [mac_size] [mac_data] [orig_id] error
* other_size other_data
*/
if (digest_data == DYNDNS_DIGEST_SIGNED) {
if (tsig->ts_mac_size > 0) {
tsig->ts_mac_size);
}
}
if (tsig->ts_other_size > 0)
return (tsig_rrlen + tsig_rdlen);
}
/*
* 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.
*
* Parameters:
* statp: resolver state
* buf: buffer to store TKEY message
* buf_sz: size of buffer
* key_name: TKEY key name
* (this same key name must be also be used in the TSIG message)
* tkey: pointer to DNS TKEY RDATA structure
* Returns:
* -1 on error, or length of message written to buf
*/
static ssize_t
{
return (-1);
}
return (-1);
}
return (-1);
}
return (-1);
}
return (-1);
}
return (-1);
}
return (buf_len);
}
/*
* Construct a service name for the target system (i.e. the DNS server) as
* follows:
*
* DNS/<fqhostname>@<realm>
*
* and convert it to GSS-API internal format.
*/
static int
{
int num_bytes;
return (-1);
return (-1);
}
return (0);
}
/*
* Construct a service name for the GSS initiator (i.e. our dyndns client)
* as follows:
*
* <sam_account>@<realm>
*
* and convert it to GSS-API internal format.
*/
static int
{
int num_bytes;
return (-1);
return (-1);
return (-1);
}
return (0);
}
/*
* 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.
*
* See RFC 3645 section 3 for standards information.
*
* Parameters:
* statp: : resolver state
* gss_context : handle to security context
* cred_handle : handle to credential
* target : server principal
* key_name : TKEY key name
* (this same key name must be also be used in the TSIG message)
* Returns:
* gss_context : handle to security context
* -1 on failure, 0 on success
*/
static int
const char *key_name)
{
int rcode;
int gss_flags;
int tkey_alg_len;
return (-1);
}
return (-1);
}
tkey.tk_other_size = 0;
do {
(void) gettimeofday(&tv, 0);
return (-1);
}
if ((maj == GSS_S_COMPLETE) &&
!(ret_flags & GSS_C_REPLAY_FLAG)) {
"no replay attack detection available");
return (-1);
}
if ((maj == GSS_S_COMPLETE) &&
!(ret_flags & GSS_C_MUTUAL_FLAG)) {
"remote peer did not authenticate itself");
return (-1);
}
"could not build key exchange message");
return (-1);
}
"could not send key exchange message");
return (-1);
}
"could not parse key exchange message");
return (-1);
}
"secure update error: key exchange error");
return (-1);
}
"could not parse peer key data");
return (-1);
}
/* Extract token length and value from TKEY RDATA */
}
} while (maj != GSS_S_COMPLETE);
return (0);
}
/*
* dyndns_get_sec_context
*
* Get security context for secure dynamic DNS update. This routine opens
* a socket to the DNS server and establishes a security context with
* the DNS server using host principal to perform secure dynamic DNS update.
*
* Parameters:
* statp: resolver state
* hostname: fully qualified hostname
* dns_srv_ip: ip address of hostname in network byte order
* Returns:
* 0 on failure, or gss credential context
*/
static gss_ctx_id_t
{
sizeof (dns_srv_hostname), 0))
return (NULL);
return (NULL);
goto cleanup;
&target) != 0)
goto cleanup;
goto cleanup;
goto cleanup;
goto cleanup;
}
key_name) != 0) {
if (gss_context != GSS_C_NO_CONTEXT)
NULL);
gss_context = NULL;
}
return (gss_context);
}
/*
* dyndns_build_update_msg
*
* This routine builds the update request message for adding and removing DNS
* entries which is used for non-secure and secure DNS update.
*
* Parameters:
* statp: resolver state
* buf: buffer to build message
* buf_sz: buffer size
* zone: zone owning name
* name: name to update
* type: type of data to update for name
* data: value of data to update for name
* ttl: time to live of new DNS RR
* (if update_op == DYNDNS_UPDATE_ADD)
* update_op: one of:
* DYNDNS_UPDATE_ADD adds entries
* DYNDNS_UPDATE_DEL_ALL deletes entries of one name & type.
* DYNDNS_UPDATE_DEL_CLEAR deletes entries of one name.
* DYNDNS_UPDATE_DEL_ONE deletes one entry.
*
* Returns:
* -1 on error, or length of message written to buf
*/
static ssize_t
{
return (-1);
return (-1);
switch (update_op) {
case DYNDNS_UPDATE_ADD:
/* Add to an RRset */
break;
case DYNDNS_UPDATE_DEL_ALL:
/* Delete an RRset */
break;
case DYNDNS_UPDATE_DEL_CLEAR:
/* Delete all RRsets from a name */
break;
case DYNDNS_UPDATE_DEL_ONE:
/* Delete an RR from an RRset */
break;
default:
return (-1);
}
/*LINTED*/
/*LINTED*/
/*LINTED*/
return (-1);
return (msg_len);
}
/*
* dyndns_search_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: one of DYNDNS_ZONE_FWD or DYNDNS_ZONE_REV
* hostname : fully qualified hostname
* family : address family of *addr, AF_INET or AF_INET6
* addr : address of hostname in network format
* Returns:
* is_match: is 1 for found matching entry, otherwise 0
* 1 : an entry exist but not necessarily match
* 0 : an entry does not exist
* -1 : an error occurred
*/
static int
{
int salen;
*is_match = 0;
switch (family) {
case AF_INET:
break;
case AF_INET6:
break;
default:
return (-1);
}
if (update_zone == DYNDNS_ZONE_FWD) {
return (NULL);
}
if (res) {
/*
* if both ips aren't the same family skip to
* the next record
*/
do {
*is_match = 1;
break;
}
/* need compare macro here */
*is_match = 1;
break;
}
}
return (1);
}
} else {
0))
return (NULL);
*is_match = 1;
}
return (1);
}
/* entry does not exist */
return (0);
}
/*
* dyndns_update_nameaddr
*
* Perform non-secure dynamic DNS update. On error, perform secure update
* on behalf of the localhost's domain machine account if the localhost is a
* domain member.
*
* Secure dynamic DNS update using host credentials requires the system to
* be configured as a Kerberos client. That configuration is done as part of the
* AD domain join.
*
* This routine creates a new resolver context, builds 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_op: one of:
* DYNDNS_UPDATE_ADD adds entries
* DYNDNS_UPDATE_DEL_ALL deletes entries of one name & type.
* DYNDNS_UPDATE_DEL_CLEAR deletes entries of one name.
* DYNDNS_UPDATE_DEL_ONE deletes one entry.
* update_zone: one of DYNDNS_ZONE_FWD or DYNDNS_ZONE_REV
* hostname : fully qualified hostname
* af : address family of *addr, AF_INET or AF_INET6
* addr : address of hostname in network format
* ttl : cached time of this entry by others and not within DNS
* database
* do_check : one of:
* DYNDNS_CHECK_NONE for no DNS checking before update
* DYNDNS_CHECK_EXIST to check first in DNS
* fqhn : one of:
* fully qualified host name; required for secure update
* may be NULL if secure update not required
* Returns:
* -1: error
* 0: success
*/
int
{
char *ad_domainp;
union {
} mu;
int msg_len;
int ret;
"requested forward update but no hostname provided");
return (-1);
}
"requested reverse update but no address provided");
return (-1);
}
if (update_op == DYNDNS_UPDATE_ADD ||
"requested update requires hostname and address");
return (-1);
}
/* don't check af if it is not used by the update */
if (!(update_op == DYNDNS_UPDATE_DEL_CLEAR &&
update_zone == DYNDNS_ZONE_FWD) &&
"address family %d not supported", af);
return (-1);
}
if (do_check == DYNDNS_CHECK_EXIST &&
"dyndns: error while checking current DNS entry");
return (-1);
}
"nothing to add");
return (0);
"nothing to delete");
return (0);
}
}
/*
* TBD: read a property to set which update mode(s) to attempt
*/
if (dyndns_security_modes & DYNDNS_SECURITY_GSS) {
return (-1);
}
++ad_domainp;
} else {
}
smb_krb5_kt_getpath())) {
"cannot find host principal \"%s\" "
"for realm \"%s\" in local keytab file.",
fqhn, ad_domainp);
}
}
return (-1);
}
return (-1);
}
return (-1);
}
"could not initialize resolver");
return (-1);
}
/*
* WORKAROUND:
* 7013458 libresolv2 res_nsend is sometimes inconsistent
* for UDP vs. TCP queries
*
* Remove the next statement when 7013458 is fixed.
*/
"could not setup update information");
return (-1);
}
"could not build update message");
return (-1);
}
/*
* Updates are attempted using the servers listed in resolv.conf.
*/
/*
* TBD: revise this algorithm to use the primary master nameserver
* (returned by dyndns_get_update_info above) and the authoritative
* nameservers found by searching the zone's NS RRset.
*/
*ns_addr = '\0';
}
"dyndns: trying update on server at %s", ns_addr);
/* attempt non-secure update on this server */
int rcode;
/* set new message ID if needed */
if (ns_cur > 0)
ret = -1;
continue;
}
ret = -1;
continue;
}
"dyndns: non-secure update completed");
ret = 0;
break;
}
/*
* DNS servers typically return REFUSED error code when
* they allow secure updates only. If secure update
* will be attempted next, suppress the expected
* REFUSED error messages when performing
* non-secure updates. Otherwise, "REFUSED" error
* messages should be logged.
*/
(!secure_update)) {
"non-secure update response code");
ret = -1;
continue;
}
}
/* attempt secure update on this server */
if (secure_update) {
const char *key_name;
int rcode;
tsig.ts_mac_size = 0;
tsig.ts_other_size = 0;
/*
* TBD: per RFC 3645 section 3.1.2,
* the key name SHOULD be made globally unique
*/
ret = -1;
continue;
}
/* set new message ID if needed */
if (ns_cur > 0 ||
(void) gettimeofday(&tv, 0);
if (gss_context != GSS_C_NO_CONTEXT)
(void) gss_delete_sec_context(&min,
&gss_context, NULL);
ret = -1;
continue;
}
/* sign update message */
(void) gettimeofday(&tv, 0);
if (gss_context != GSS_C_NO_CONTEXT)
(void) gss_delete_sec_context(&min,
&gss_context, NULL);
ret = -1;
continue;
}
if (gss_context != GSS_C_NO_CONTEXT)
(void) gss_delete_sec_context(&min,
&gss_context, NULL);
ret = -1;
continue;
}
if (gss_context != GSS_C_NO_CONTEXT)
(void) gss_delete_sec_context(&min,
&gss_context, NULL);
ret = -1;
continue;
}
if (gss_context != GSS_C_NO_CONTEXT)
(void) gss_delete_sec_context(&min,
&gss_context, NULL);
ret = -1;
continue;
}
if (gss_context != GSS_C_NO_CONTEXT)
(void) gss_delete_sec_context(&min,
&gss_context, NULL);
/* check here for update request is successful */
"dyndns: secure update completed");
ret = 0;
break;
}
"secure update response code");
ret = -1;
}
}
if (ret != 0) {
"servers", (secure_update) ?
"both non-secure and secure updates" :
"non-secure update");
}
return (ret);
}
/*
* dyndns_zone_update
* Perform dynamic update on both forward and reverse lookup zone using
* the specified 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:
* fqdn - fully-qualified domain name (in lower case)
*
* Returns:
* -1: some dynamic DNS updates errors
* 0: successful or DDNS disabled.
*/
int
{
int rc;
return (0);
return (0);
return (-1);
/*
* To comply with RFC 4120 section 6.2.1, the fully-qualified hostname
* must be set to lower case.
*/
error = 0;
forw_update_ok = 0;
/*
* NULL IP is okay since we are removing all using the hostname.
*/
DYNDNS_CHECK_NONE, fqhn) == 0) {
forw_update_ok = 1;
} else {
error++;
}
if (smb_config_getbool(SMB_CI_IPV6_ENABLE)) {
forw_update_ok = 0;
DYNDNS_CHECK_NONE, fqhn) == 0) {
forw_update_ok = 1;
} else {
error++;
}
}
return (-1);
do {
continue;
if (forw_update_ok) {
if (rc == -1)
error++;
}
/*
* NULL hostname is okay since we are removing all using the IP.
*/
if (rc == 0)
if (rc == -1)
error++;
return ((error == 0) ? 0 : -1);
}
/*
* dyndns_zone_clear
* 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:
* fqdn - fully-qualified domain name (in lower case)
* Returns:
* -1: some dynamic DNS updates errors
* 0: successful or DDNS disabled.
*/
int
{
int error;
int rc;
return (0);
return (0);
return (-1);
/*
* To comply with RFC 4120 section 6.2.1, the fully-qualified hostname
* must be set to lower case.
*/
error = 0;
return (-1);
do {
continue;
/*
* NULL hostname is okay since we are removing all using the IP.
*/
if (rc != 0)
error++;
return ((error == 0) ? 0 : -1);
}