#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 <arpa/nameser.h>
#include <net/if.h>
#include <resolv.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <netdb.h>
#include <rpc/rpc.h>
#include <syslog.h>
#include <gssapi/gssapi.h>
#include <kerberosv5/krb5.h>
#include <smbns_dyndns.h>
#include <smbns_krb.h>
#ifndef BADSIG
#define BADSIG ns_r_badsig
#endif
#ifndef BADKEY
#define BADKEY ns_r_badkey
#endif
#ifndef BADTIME
#define BADTIME ns_r_badtime
#endif
#define DEL_NONE 2
#define MAX_AUTH_RETRIES 3
#define DYNDNS_MAX_QUERY_RETRIES 3
#define DYNDNS_QUERY_TIMEOUT 2
static uint16_t dns_msgid;
mutex_t dns_msgid_mtx;
#define DYNDNS_OP_CLEAR 1
#define DYNDNS_OP_UPDATE 2
#define DYNDNS_STATE_INIT 0
#define DYNDNS_STATE_READY 1
#define DYNDNS_STATE_PUBLISHING 2
#define DYNDNS_STATE_STOPPING 3
typedef struct dyndns_qentry {
list_node_t dqe_lnd;
int dqe_op;
char dqe_fqdn[MAXHOSTNAMELEN];
} dyndns_qentry_t;
typedef struct dyndns_queue {
list_t ddq_list;
mutex_t ddq_mtx;
cond_t ddq_cv;
uint32_t ddq_state;
} dyndns_queue_t;
static dyndns_queue_t dyndns_queue;
static void dyndns_queue_request(int, const char *);
static void dyndns_queue_flush(list_t *);
static void dyndns_process(list_t *);
static int dyndns_update_core(char *);
static int dyndns_clear_rev_zone(char *);
static void dyndns_msgid_init(void);
static int dyndns_get_msgid(void);
static void dyndns_syslog(int, int, const char *);
void
dyndns_start(void)
{
(void) mutex_lock(&dyndns_queue.ddq_mtx);
if (dyndns_queue.ddq_state != DYNDNS_STATE_INIT) {
(void) mutex_unlock(&dyndns_queue.ddq_mtx);
return;
}
dyndns_msgid_init();
list_create(&dyndns_queue.ddq_list, sizeof (dyndns_qentry_t),
offsetof(dyndns_qentry_t, dqe_lnd));
dyndns_queue.ddq_state = DYNDNS_STATE_READY;
(void) mutex_unlock(&dyndns_queue.ddq_mtx);
}
void
dyndns_stop(void)
{
(void) mutex_lock(&dyndns_queue.ddq_mtx);
switch (dyndns_queue.ddq_state) {
case DYNDNS_STATE_READY:
case DYNDNS_STATE_PUBLISHING:
dyndns_queue.ddq_state = DYNDNS_STATE_STOPPING;
(void) cond_signal(&dyndns_queue.ddq_cv);
break;
default:
break;
}
(void) mutex_unlock(&dyndns_queue.ddq_mtx);
}
void
dyndns_clear_zones(void)
{
char fqdn[MAXHOSTNAMELEN];
if (smb_getfqdomainname(fqdn, MAXHOSTNAMELEN) != 0) {
syslog(LOG_ERR, "dyndns: failed to get domainname");
return;
}
dyndns_queue_request(DYNDNS_OP_CLEAR, fqdn);
}
void
dyndns_update_zones(void)
{
char fqdn[MAXHOSTNAMELEN];
if (smb_getfqdomainname(fqdn, MAXHOSTNAMELEN) != 0) {
syslog(LOG_ERR, "dyndns: failed to get domainname");
return;
}
dyndns_queue_request(DYNDNS_OP_UPDATE, fqdn);
}
static void
dyndns_queue_request(int op, const char *fqdn)
{
dyndns_qentry_t *entry;
if (!smb_config_getbool(SMB_CI_DYNDNS_ENABLE))
return;
if ((entry = malloc(sizeof (dyndns_qentry_t))) == NULL)
return;
bzero(entry, sizeof (dyndns_qentry_t));
entry->dqe_op = op;
(void) strlcpy(entry->dqe_fqdn, fqdn, MAXNAMELEN);
(void) smb_strlwr(entry->dqe_fqdn);
(void) mutex_lock(&dyndns_queue.ddq_mtx);
switch (dyndns_queue.ddq_state) {
case DYNDNS_STATE_READY:
case DYNDNS_STATE_PUBLISHING:
list_insert_tail(&dyndns_queue.ddq_list, entry);
(void) cond_signal(&dyndns_queue.ddq_cv);
break;
default:
free(entry);
break;
}
(void) mutex_unlock(&dyndns_queue.ddq_mtx);
}
static void
dyndns_queue_flush(list_t *lst)
{
dyndns_qentry_t *entry;
while ((entry = list_head(lst)) != NULL) {
list_remove(lst, entry);
free(entry);
}
}
void *
dyndns_publisher(void *arg)
{
dyndns_qentry_t *entry;
list_t publist;
(void) mutex_lock(&dyndns_queue.ddq_mtx);
if (dyndns_queue.ddq_state != DYNDNS_STATE_READY) {
(void) mutex_unlock(&dyndns_queue.ddq_mtx);
return (NULL);
}
dyndns_queue.ddq_state = DYNDNS_STATE_PUBLISHING;
(void) mutex_unlock(&dyndns_queue.ddq_mtx);
list_create(&publist, sizeof (dyndns_qentry_t),
offsetof(dyndns_qentry_t, dqe_lnd));
for (;;) {
(void) mutex_lock(&dyndns_queue.ddq_mtx);
while (list_is_empty(&dyndns_queue.ddq_list) &&
(dyndns_queue.ddq_state == DYNDNS_STATE_PUBLISHING)) {
(void) cond_wait(&dyndns_queue.ddq_cv,
&dyndns_queue.ddq_mtx);
}
if (dyndns_queue.ddq_state != DYNDNS_STATE_PUBLISHING) {
(void) mutex_unlock(&dyndns_queue.ddq_mtx);
break;
}
while ((entry = list_head(&dyndns_queue.ddq_list)) != NULL) {
list_remove(&dyndns_queue.ddq_list, entry);
list_insert_tail(&publist, entry);
}
(void) mutex_unlock(&dyndns_queue.ddq_mtx);
dyndns_process(&publist);
}
(void) mutex_lock(&dyndns_queue.ddq_mtx);
dyndns_queue_flush(&dyndns_queue.ddq_list);
list_destroy(&dyndns_queue.ddq_list);
dyndns_queue.ddq_state = DYNDNS_STATE_INIT;
(void) mutex_unlock(&dyndns_queue.ddq_mtx);
dyndns_queue_flush(&publist);
list_destroy(&publist);
return (NULL);
}
static void
dyndns_process(list_t *publist)
{
dyndns_qentry_t *entry;
while ((entry = list_head(publist)) != NULL) {
(void) mutex_lock(&dyndns_queue.ddq_mtx);
if (dyndns_queue.ddq_state != DYNDNS_STATE_PUBLISHING) {
(void) mutex_unlock(&dyndns_queue.ddq_mtx);
dyndns_queue_flush(publist);
return;
}
(void) mutex_unlock(&dyndns_queue.ddq_mtx);
list_remove(publist, entry);
switch (entry->dqe_op) {
case DYNDNS_OP_CLEAR:
(void) dyndns_clear_rev_zone(entry->dqe_fqdn);
break;
case DYNDNS_OP_UPDATE:
(void) dyndns_update_core(entry->dqe_fqdn);
break;
default:
break;
}
free(entry);
}
}
int
dyndns_update(char *fqdn)
{
int rc;
if (smb_nic_init() != SMB_NIC_SUCCESS)
return (-1);
dyndns_msgid_init();
(void) smb_strlwr(fqdn);
rc = dyndns_update_core(fqdn);
smb_nic_fini();
return (rc);
}
static void
dyndns_msgid_init(void)
{
struct timeval now;
(void) gettimeofday(&now, NULL);
(void) mutex_lock(&dns_msgid_mtx);
dns_msgid = (0xffff & (now.tv_sec ^ now.tv_usec ^ getpid()));
(void) mutex_unlock(&dns_msgid_mtx);
}
static int
dyndns_get_msgid(void)
{
uint16_t id;
(void) mutex_lock(&dns_msgid_mtx);
id = ++dns_msgid;
(void) mutex_unlock(&dns_msgid_mtx);
return (id);
}
static void
dyndns_syslog(int severity, int errnum, const char *text)
{
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" },
};
char *errmsg = "unknown error";
int i;
if (errnum == NOERROR)
return;
for (i = 0; i < (sizeof (errtab) / sizeof (errtab[0])); ++i) {
if (errtab[i].errnum == errnum) {
errmsg = errtab[i].errmsg;
break;
}
}
syslog(severity, "dyndns: %s: %s: %d", text, errmsg, errnum);
}
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",
(char *)msg.value);
(void) gss_release_buffer(&min2, &msg);
(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",
(char *)msg.value);
(void) gss_release_buffer(&min2, &msg);
}
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);
}
static 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);
}
static char *
dyndns_put_v6addr(char *buf, smb_inaddr_t *val)
{
val->a_family = AF_INET6;
(void) memcpy(buf, &val->a_ipv6, IN6ADDRSZ);
buf += IN6ADDRSZ;
return (buf);
}
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);
}
static 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 header section: buffer too small");
return (-1);
}
*ptr = dyndns_put_nshort(*ptr, msg_id);
if (query_req == REQ_QUERY)
opcode = ns_o_query;
else
opcode = ns_o_update << 11;
opcode |= flags;
*ptr = dyndns_put_nshort(*ptr, opcode);
*ptr = dyndns_put_nshort(*ptr, quest_zone_cnt);
*ptr = dyndns_put_nshort(*ptr, ans_prereq_cnt);
*ptr = dyndns_put_nshort(*ptr, nameser_update_cnt);
*ptr = dyndns_put_nshort(*ptr, addit_cnt);
return (0);
}
static 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 question section: buffer too small");
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);
}
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;
smb_inaddr_t ipaddr;
int isv4 = 1;
rec_len = strlen(name) + 10;
if (inet_pton(AF_INET, data, &ipaddr) == 1)
isv4 = 1;
else if (inet_pton(AF_INET6, data, &ipaddr) == 1)
isv4 = 0;
if (add_del == UPDATE_ADD) {
if (forw_rev == UPDATE_FORW)
data_len = isv4 ? 4 : 16;
else
data_len = strlen(data) + 2;
} else {
if (del_type == DEL_ALL)
data_len = 0;
else if (forw_rev == UPDATE_FORW)
data_len = isv4 ? 4 : 16;
else
data_len = strlen(data) + 2;
}
if (rec_len + data_len > buf_len) {
syslog(LOG_ERR, "dyndns update section: buffer too small");
return (-1);
}
namePtr = *ptr;
(void) dyndns_stuff_str(&namePtr, name);
*ptr = namePtr;
if (isv4)
*ptr = dyndns_put_nshort(*ptr, type);
else
*ptr = dyndns_put_nshort(*ptr, ns_t_aaaa);
*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) {
if (isv4) {
*ptr = dyndns_put_nshort(*ptr, 4);
*ptr = dyndns_put_int(*ptr, ipaddr.a_ipv4);
} else {
*ptr = dyndns_put_nshort(*ptr, 16);
*ptr = dyndns_put_v6addr(*ptr, &ipaddr);
}
} else {
*ptr = dyndns_put_nshort(*ptr, strlen(data)+2);
namePtr = *ptr;
(void) dyndns_stuff_str(&namePtr, data);
*ptr = namePtr;
}
return (0);
}
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 TKEY: buffer too small");
return (-1);
}
namePtr = *ptr;
(void) dyndns_stuff_str(&namePtr, 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);
*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);
*ptr = dyndns_put_nlong(*ptr, tp.tv_sec + key_expire);
*ptr = dyndns_put_nshort(*ptr, MODE_GSS_API);
*ptr = dyndns_put_nshort(*ptr, 0);
*ptr = dyndns_put_nshort(*ptr, data_size);
(void) memcpy(*ptr, data, data_size);
*ptr += data_size;
*ptr = dyndns_put_nshort(*ptr, 0);
return (0);
}
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 TSIG: buffer too small");
return (-1);
}
namePtr = *ptr;
(void) dyndns_stuff_str(&namePtr, 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) {
*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);
fudge = tp.tv_sec << 16;
fudge |= fudge_time;
*ptr = dyndns_put_nlong(*ptr, fudge);
if (data_signed == TSIG_SIGNED) {
*ptr = dyndns_put_nshort(*ptr, data_size);
(void) memcpy(*ptr, data, data_size);
*ptr += data_size;
*ptr = dyndns_put_nshort(*ptr, msg_id);
}
*ptr = dyndns_put_nshort(*ptr, 0);
*ptr = dyndns_put_nshort(*ptr, 0);
return (0);
}
static int
dyndns_open_init_socket(int sock_type, smb_inaddr_t *dest_addr, int port)
{
int s;
struct sockaddr_in my_addr;
struct sockaddr_in6 my6_addr;
struct sockaddr_in serv_addr;
struct sockaddr_in6 serv6_addr;
int family;
family = dest_addr->a_family;
if ((s = socket(family, sock_type, 0)) == -1) {
syslog(LOG_ERR, "dyndns: socket error\n");
return (-1);
}
if (family == AF_INET) {
bzero(&my_addr, sizeof (my_addr));
my_addr.sin_family = family;
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 = family;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = dest_addr->a_ipv4;
if (connect(s, (struct sockaddr *)&serv_addr,
sizeof (struct sockaddr_in)) < 0) {
syslog(LOG_ERR, "dyndns: client connect (%s)\n",
strerror(errno));
(void) close(s);
return (-1);
}
} else {
bzero(&my6_addr, sizeof (my6_addr));
my6_addr.sin6_family = family;
my6_addr.sin6_port = htons(0);
bzero(&my6_addr.sin6_addr.s6_addr, IN6ADDRSZ);
if (bind(s, (struct sockaddr *)&my6_addr,
sizeof (my6_addr)) < 0) {
syslog(LOG_ERR, "dyndns: client bind err\n");
(void) close(s);
return (-1);
}
serv6_addr.sin6_family = family;
serv6_addr.sin6_port = htons(port);
bcopy(&serv6_addr.sin6_addr.s6_addr, &dest_addr->a_ipv6,
IN6ADDRSZ);
if (connect(s, (struct sockaddr *)&serv6_addr,
sizeof (struct sockaddr_in6)) < 0) {
syslog(LOG_ERR, "dyndns: client connect err (%s)\n",
strerror(errno));
(void) close(s);
return (-1);
}
}
return (s);
}
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;
zoneCount = 1;
preqCount = 1;
updateCount = 0;
additionalCount = 0;
(void) memset(buf, 0, MAX_TCP_SIZE);
bufptr = buf;
*id = dyndns_get_msgid();
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);
}
static int
dyndns_establish_sec_ctx(gss_ctx_id_t *gss_context, gss_cred_id_t cred_handle,
int s, char *key_name, char *dns_hostname, gss_OID oid)
{
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;
service_sz = strlen(dns_hostname) + 5;
service_name = (char *)malloc(sizeof (char) * service_sz);
if (service_name == NULL)
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_C_NT_HOSTBASED_SERVICE, &target_name)) != GSS_S_COMPLETE) {
display_stat(maj, min);
(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 {
maj = gss_init_sec_context(&min, cred_handle, gss_context,
target_name, oid, gss_flags, 0, NULL, inputptr, NULL,
&out_tok, &ret_flags, &time_rec);
if (maj != GSS_S_COMPLETE && maj != GSS_S_CONTINUE_NEEDED) {
assert(gss_context);
if (*gss_context != GSS_C_NO_CONTEXT)
(void) gss_delete_sec_context(&min,
gss_context, NULL);
display_stat(maj, min);
(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");
if (out_tok.length > 0)
(void) gss_release_buffer(&min, &out_tok);
(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");
if (out_tok.length > 0)
(void) gss_release_buffer(&min, &out_tok);
(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_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");
(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 recv error");
(void) gss_release_name(&min, &target_name);
return (-1);
}
ret = buf2[5] & 0xf;
if (ret != NOERROR) {
dyndns_syslog(LOG_ERR, ret, "TKEY reply");
(void) gss_release_name(&min, &target_name);
return (-1);
}
tmpptr = &buf2[2];
(void) dyndns_get_nshort(tmpptr, &rid);
if (id != rid) {
(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;
in_tok.value = &buf2[61+(strlen(key_name)+2)*2];
inputptr = &in_tok;
}
} while (maj != GSS_S_COMPLETE);
(void) gss_release_name(&min, &target_name);
return (0);
}
static gss_ctx_id_t
dyndns_get_sec_context(const char *hostname, smb_inaddr_t *dns_ip)
{
int s;
gss_cred_id_t cred_handle;
gss_ctx_id_t gss_context;
gss_OID oid;
char *key_name, dns_hostname[MAXHOSTNAMELEN];
cred_handle = GSS_C_NO_CREDENTIAL;
oid = GSS_C_NO_OID;
key_name = (char *)hostname;
if (smb_getnameinfo(dns_ip, dns_hostname,
sizeof (dns_hostname), 0)) {
return (NULL);
}
if ((s = dyndns_open_init_socket(SOCK_STREAM, dns_ip, 53)) < 0) {
return (NULL);
}
if (dyndns_establish_sec_ctx(&gss_context, cred_handle, s, key_name,
dns_hostname, oid))
gss_context = NULL;
(void) close(s);
return (gss_context);
}
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;
smb_inaddr_t tmp_addr;
int i, j, k;
int fourcnt;
queryReq = REQ_UPDATE;
zoneCount = 1;
preqCount = 0;
updateCount = 1;
additionalCount = addit_cnt;
(void) memset(buf, 0, NS_PACKETSZ);
bufptr = buf;
if (*id == 0)
*id = dyndns_get_msgid();
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;
do {
if ((zone = (char *)strchr(p, '.')) == NULL)
return (-1);
zone += 1;
p = zone;
} while (--level >= 0);
resource = (char *)hostname;
data = (char *)ip_addr;
} else {
if (inet_pton(AF_INET, ip_addr, &tmp_addr) == 1) {
(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;
while (--level >= 0) {
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);
} else {
bzero(resrc_buf, 100);
i = 0;
j = 0;
while (ip_addr[i] != 0)
i++;
i--;
while (i >= 0) {
fourcnt = 3;
while ((i >= 0) && (ip_addr[i] != ':')) {
resrc_buf[j++] = ip_addr[i];
(void) strcat(&resrc_buf[j++], ".");
fourcnt --;
i--;
}
for (k = 0; k <= fourcnt; k++) {
resrc_buf[j++] = '0';
(void) strcat(&resrc_buf[j++], ".");
}
i--;
}
(void) strcat(resrc_buf, "ip6.arpa");
(void) strcpy(zone_buf, &resrc_buf[32]);
zone = zone_buf;
}
resource = resrc_buf;
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;
else
class = ns_c_any;
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);
}
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);
}
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);
}
static int
dyndns_udp_send_recv(int s, char *buf, int buf_sz, char *rec_buf)
{
int i, retval, addr_len;
struct timeval tv, timeout;
fd_set rfds;
struct sockaddr_in6 from_addr;
timeout.tv_usec = 0;
timeout.tv_sec = DYNDNS_QUERY_TIMEOUT;
for (i = 0; i <= DYNDNS_MAX_QUERY_RETRIES; i++) {
if (send(s, buf, buf_sz, 0) == -1) {
syslog(LOG_ERR, "dyndns: UDP send error (%s)",
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);
addr_len = sizeof (struct sockaddr_in6);
if (recvfrom(s, rec_buf, NS_PACKETSZ, 0,
(struct sockaddr *)&from_addr, &addr_len) == -1) {
syslog(LOG_ERR, "dyndns: UDP recv error ");
return (-1);
}
break;
}
}
if (i == (DYNDNS_MAX_QUERY_RETRIES + 1)) {
syslog(LOG_ERR, "dyndns: max retries for UDP recv reached");
return (-1);
}
return (0);
}
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;
smb_inaddr_t dns_ip;
char *key_name;
int buf_sz;
int level = 0;
assert(dns_str);
assert(*dns_str);
if (inet_pton(AF_INET, dns_str, &dns_ip) == 1)
dns_ip.a_family = AF_INET;
else if (inet_pton(AF_INET6, dns_str, &dns_ip) == 1)
dns_ip.a_family = AF_INET6;
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) {
if (gss_context != GSS_C_NO_CONTEXT)
(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);
if (gss_context != GSS_C_NO_CONTEXT)
(void) gss_delete_sec_context(&min, &gss_context, NULL);
return (-1);
}
in_mic.length = buf_sz;
in_mic.value = buf;
if ((maj = gss_get_mic(&min, gss_context, 0, &in_mic, &out_mic)) !=
GSS_S_COMPLETE) {
display_stat(maj, min);
(void) close(s2);
if (gss_context != GSS_C_NO_CONTEXT)
(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);
if (gss_context != GSS_C_NO_CONTEXT)
(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);
if (gss_context != GSS_C_NO_CONTEXT)
(void) gss_delete_sec_context(&min, &gss_context, NULL);
return (-1);
}
(void) close(s2);
if (gss_context != GSS_C_NO_CONTEXT)
(void) gss_delete_sec_context(&min, &gss_context, NULL);
ret = buf2[3] & 0xf;
if (ret == NOTAUTH && level++ < MAX_AUTH_RETRIES)
goto sec_retry_higher;
if (ret != NOERROR) {
dyndns_syslog(LOG_ERR, ret, "TSIG reply");
return (-1);
}
(void) dyndns_get_nshort(buf2, &rid);
if (id != rid)
return (-1);
return (0);
}
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)
{
smb_inaddr_t ipaddr, dnsip;
char dns_hostname[NI_MAXHOST];
struct addrinfo hints, *res = NULL;
int salen;
int family;
*is_match = 0;
if (inet_pton(AF_INET, ip_addr, &ipaddr) == 1) {
salen = sizeof (ipaddr.a_ipv4);
family = AF_INET;
} else if (inet_pton(AF_INET6, ip_addr, &ipaddr) == 1) {
salen = sizeof (ipaddr.a_ipv6);
family = AF_INET6;
}
if (update_zone == UPDATE_FORW) {
bzero((char *)&hints, sizeof (hints));
hints.ai_family = family;
hints.ai_flags = AI_NUMERICHOST;
if (getaddrinfo(hostname, NULL, &hints, &res)) {
return (0);
}
if (res) {
do {
if ((res->ai_family == AF_INET) &&
(family == AF_INET)) {
(void) memcpy(&dnsip, &res->ai_addr[0],
salen);
if (ipaddr.a_ipv4 ==
dnsip.a_ipv4) {
*is_match = 1;
break;
}
} else if ((res->ai_family == AF_INET6) &&
(family == AF_INET6)) {
(void) memcpy(&dnsip, &res->ai_addr[0],
salen);
if (!memcmp(&ipaddr, &dnsip,
IN6ADDRSZ)) {
*is_match = 1;
break;
}
}
} while (res->ai_next);
freeaddrinfo(res);
return (1);
}
} else {
if (smb_getnameinfo(&ipaddr, dns_hostname, NI_MAXHOST, 0))
return (0);
if (strncasecmp(dns_hostname, hostname,
strlen(hostname)) == 0) {
*is_match = 1;
}
return (1);
}
return (0);
}
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;
int is_exist, is_match;
struct timeval timeout;
int buf_sz;
int level = 0;
smb_inaddr_t dns_ip;
char *fqdn;
char *p;
assert(dns_str);
assert(*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);
}
}
if (inet_pton(AF_INET, dns_str, &dns_ip) == 1)
dns_ip.a_family = AF_INET;
else if (inet_pton(AF_INET6, dns_str, &dns_ip) == 1)
dns_ip.a_family = AF_INET6;
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;
if (ret == NOTAUTH && level++ < MAX_AUTH_RETRIES)
goto retry_higher;
if (ret == NOERROR) {
(void) dyndns_get_nshort(buf2, &rid);
if (id != rid)
return (-1);
return (0);
}
if (ret == NOTIMP) {
dyndns_syslog(LOG_NOTICE, NOTIMP, "dynamic updates");
return (-1);
} else if (ret == NOTAUTH) {
dyndns_syslog(LOG_NOTICE, NOTAUTH, "DNS");
return (-1);
}
if ((p = strchr(hostname, '.')) == NULL)
return (-1);
fqdn = ++p;
if (smb_krb5_kt_find(SMB_KRB5_PN_ID_HOST_FQHN, fqdn,
SMBNS_KRB5_KEYTAB)) {
ret = dyndns_sec_add_remove_entry(update_zone, hostname,
ip_addr, life_time, update_type, del_type, dns_str);
} else {
syslog(LOG_NOTICE, "dyndns: secure update failed: cannot find "
"host principal \"%s\" in local keytab file.", hostname);
}
return (ret);
}
static int
dyndns_add_entry(int update_zone, const char *hostname, const char *ip_addr,
int life_time)
{
const char *dns_str;
char *which_zone;
smb_inaddr_t ns_list[MAXNS];
char dns_buf[INET6_ADDRSTRLEN];
int i, cnt;
int rc = 0;
if (hostname == NULL || ip_addr == NULL) {
return (-1);
}
cnt = smb_get_nameservers(&ns_list[0], MAXNS);
for (i = 0; i < cnt; i++) {
dns_str = smb_inet_ntop(&ns_list[i], dns_buf,
SMB_IPSTRLEN(ns_list[i].a_family));
if (dns_str == NULL)
continue;
which_zone = (update_zone == UPDATE_FORW) ?
"forward" : "reverse";
syslog(LOG_DEBUG, "dyndns %s lookup zone update %s (%s)",
which_zone, hostname, ip_addr);
if (dyndns_add_remove_entry(update_zone, hostname,
ip_addr, life_time,
UPDATE_ADD, DNS_NOCHECK, DEL_NONE, dns_buf) != -1) {
rc = 1;
break;
}
}
return (rc ? 0 : -1);
}
static int
dyndns_remove_entry(int update_zone, const char *hostname, const char *ip_addr,
int del_type)
{
const char *dns_str;
smb_inaddr_t ns_list[MAXNS];
char dns_buf[INET6_ADDRSTRLEN];
int i, cnt, scnt;
if ((hostname == NULL || ip_addr == NULL)) {
return (-1);
}
cnt = smb_get_nameservers(ns_list, MAXNS);
scnt = 0;
for (i = 0; i < cnt; i++) {
dns_str = smb_inet_ntop(&ns_list[i], dns_buf,
SMB_IPSTRLEN(ns_list[i].a_family));
if (dns_str == NULL)
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_buf) != -1) {
scnt++;
break;
}
}
if (scnt)
return (0);
return (-1);
}
int
dyndns_update_core(char *fqdn)
{
int forw_update_ok, error;
char my_ip[INET6_ADDRSTRLEN];
const char *my_str;
smb_niciter_t ni;
int rc;
char fqhn[MAXHOSTNAMELEN];
if (fqdn == NULL || *fqdn == '\0')
return (0);
if (!smb_config_getbool(SMB_CI_DYNDNS_ENABLE))
return (0);
if (smb_gethostname(fqhn, MAXHOSTNAMELEN, SMB_CASE_LOWER) != 0)
return (-1);
(void) strlcat(fqhn, ".", MAXHOSTNAMELEN);
(void) strlcat(fqhn, fqdn, MAXHOSTNAMELEN);
error = 0;
forw_update_ok = 0;
if (dyndns_remove_entry(UPDATE_FORW, fqhn, "1.1.1.1", DEL_ALL) == 0) {
forw_update_ok = 1;
} else {
error++;
}
if (smb_nic_getfirst(&ni) != SMB_NIC_SUCCESS)
return (-1);
do {
if (ni.ni_nic.nic_sysflags & IFF_PRIVATE)
continue;
my_str = smb_inet_ntop(&ni.ni_nic.nic_ip, my_ip,
SMB_IPSTRLEN(ni.ni_nic.nic_ip.a_family));
if (my_str == NULL) {
error++;
continue;
}
if (forw_update_ok) {
rc = dyndns_add_entry(UPDATE_FORW, fqhn, my_str,
DDNS_TTL);
if (rc == -1)
error++;
}
rc = dyndns_remove_entry(UPDATE_REV, fqhn, my_str, DEL_ALL);
if (rc == 0) {
rc = dyndns_add_entry(UPDATE_REV, fqhn, my_str,
DDNS_TTL);
}
if (rc == -1)
error++;
} while (smb_nic_getnext(&ni) == SMB_NIC_SUCCESS);
return ((error == 0) ? 0 : -1);
}
int
dyndns_clear_rev_zone(char *fqdn)
{
int error;
char my_ip[INET6_ADDRSTRLEN];
smb_niciter_t ni;
int rc;
char fqhn[MAXHOSTNAMELEN];
const char *my_str;
if (!smb_config_getbool(SMB_CI_DYNDNS_ENABLE))
return (0);
if (smb_gethostname(fqhn, MAXHOSTNAMELEN, SMB_CASE_LOWER) != 0)
return (-1);
(void) strlcat(fqhn, ".", MAXHOSTNAMELEN);
(void) strlcat(fqhn, fqdn, MAXHOSTNAMELEN);
error = 0;
if (smb_nic_getfirst(&ni) != SMB_NIC_SUCCESS)
return (-1);
do {
if (ni.ni_nic.nic_sysflags & IFF_PRIVATE)
continue;
my_str = smb_inet_ntop(&ni.ni_nic.nic_ip, my_ip,
SMB_IPSTRLEN(ni.ni_nic.nic_ip.a_family));
if (my_str == NULL) {
error++;
continue;
}
rc = dyndns_remove_entry(UPDATE_REV, fqhn, my_ip, DEL_ALL);
if (rc != 0)
error++;
} while (smb_nic_getnext(&ni) == SMB_NIC_SUCCESS);
return ((error == 0) ? 0 : -1);
}