1100 lines
28 KiB
C
1100 lines
28 KiB
C
/* $NetBSD: db_sec.c,v 1.1.1.1 1999/11/20 18:53:59 veego Exp $ */
|
|
|
|
|
|
#if !defined(lint) && !defined(SABER)
|
|
static const char rcsid[] = "Id: db_sec.c,v 8.30 1999/10/15 21:06:49 vixie Exp";
|
|
#endif /* not lint */
|
|
|
|
/*
|
|
* Copyright (c) 1986, 1990
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the University of
|
|
* California, Berkeley and its contributors.
|
|
* 4. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* Portions Copyright (c) 1993 by Digital Equipment Corporation.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies, and that
|
|
* the name of Digital Equipment Corporation not be used in advertising or
|
|
* publicity pertaining to distribution of the document or software without
|
|
* specific, written prior permission.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
|
|
* WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT
|
|
* CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
|
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
|
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
|
* ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
/*
|
|
* Portions Copyright (c) 1996-1999 by Internet Software Consortium.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
|
|
* ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
|
|
* CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
|
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
|
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
|
* ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#include "port_before.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <arpa/nameser.h>
|
|
|
|
#include <ctype.h>
|
|
#include <resolv.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <time.h>
|
|
|
|
#include <isc/eventlib.h>
|
|
#include <isc/logging.h>
|
|
#include <isc/memcluster.h>
|
|
#include <isc/tree.h>
|
|
|
|
#include <isc/dst.h>
|
|
|
|
#include "port_after.h"
|
|
|
|
#include "named.h"
|
|
|
|
struct zpubkey {
|
|
struct dst_key *zpk_key; /* Should be DST_KEY */
|
|
char *zpk_name;
|
|
struct zpubkey *zpk_next;
|
|
};
|
|
|
|
typedef struct zpubkey *zpubkey_list;
|
|
|
|
static int nxt_match_rrset(struct databuf *dp, struct db_rrset *rrset);
|
|
|
|
/*
|
|
* A converted databuf is a stripped down databuf after converting the
|
|
* data to wire format.
|
|
*/
|
|
struct converted_databuf {
|
|
struct converted_databuf *cd_next;
|
|
u_char *cd_data;
|
|
int cd_size, cd_alloc;
|
|
};
|
|
|
|
/* All of the trusted keys and zone keys */
|
|
static tree *trusted_keys = NULL;
|
|
|
|
static int
|
|
compare_pubkey (struct zpubkey *zpk1, struct zpubkey *zpk2) {
|
|
char ta[NS_MAXDNAME], tb[NS_MAXDNAME];
|
|
|
|
if (ns_makecanon(zpk1->zpk_name, ta, sizeof ta) < 0 ||
|
|
ns_makecanon(zpk2->zpk_name, tb, sizeof tb) < 0)
|
|
return (-1);
|
|
return (strcasecmp(ta, tb));
|
|
}
|
|
|
|
static struct zpubkey *
|
|
tree_srch_pubkey (const char *name) {
|
|
struct zpubkey tkey, *key;
|
|
|
|
tkey.zpk_name = (char *) name;
|
|
if (trusted_keys == NULL) {
|
|
tree_init(&trusted_keys);
|
|
return (NULL);
|
|
}
|
|
key = (struct zpubkey *)tree_srch(&trusted_keys, compare_pubkey,
|
|
&tkey);
|
|
return (key);
|
|
}
|
|
|
|
static DST_KEY *
|
|
find_public_key (const char *name, u_int16_t key_id) {
|
|
struct namebuf *knp;
|
|
struct hashbuf *htp;
|
|
struct databuf *dp;
|
|
const char *fname;
|
|
DST_KEY *key;
|
|
|
|
ns_debug(ns_log_default, 5, "find_public_key(%s, %d)", name, key_id);
|
|
|
|
htp = hashtab;
|
|
knp = nlookup (name, &htp, &fname, 0);
|
|
if (fname != name)
|
|
/* The name doesn't exist, so there's no key */
|
|
return (NULL);
|
|
|
|
for (dp = knp->n_data; dp != NULL; dp = dp->d_next) {
|
|
if (dp->d_type != ns_t_key || dp->d_secure < DB_S_SECURE)
|
|
continue;
|
|
key = dst_dnskey_to_key(name, dp->d_data, dp->d_size);
|
|
/* XXX what about multiple keys with same footprint? */
|
|
if (key) {
|
|
if (key->dk_id == ntohs(key_id))
|
|
return (key);
|
|
else
|
|
dst_free_key(key);
|
|
}
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
|
|
static DST_KEY *
|
|
find_trusted_key (const char *name, u_int16_t key_id) {
|
|
struct zpubkey *zpk;
|
|
zpubkey_list keylist = tree_srch_pubkey (name);
|
|
|
|
ns_debug(ns_log_default, 5, "find_trusted_key(%s, %d)", name, key_id);
|
|
|
|
for (zpk = keylist; zpk; zpk = zpk->zpk_next)
|
|
if (zpk->zpk_key->dk_id == ntohs(key_id))
|
|
return (zpk->zpk_key);
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
int
|
|
add_trusted_key (const char *name, const int flags, const int proto,
|
|
const int alg, const char *str)
|
|
{
|
|
zpubkey_list keylist;
|
|
struct zpubkey *zpk;
|
|
u_char buf[1024];
|
|
int n;
|
|
|
|
keylist = tree_srch_pubkey (name);
|
|
|
|
zpk = (struct zpubkey *) memget (sizeof (struct zpubkey));
|
|
if (zpk == NULL)
|
|
ns_panic(ns_log_default, 1,
|
|
"add_trusted_key: memget failed(%s)", name);
|
|
n = b64_pton(str, buf, sizeof(buf));
|
|
if (n < 0)
|
|
goto failure;
|
|
zpk->zpk_key = dst_buffer_to_key(name, alg, flags, proto, buf, n);
|
|
if (zpk->zpk_key == NULL) {
|
|
ns_warning(ns_log_default,
|
|
"add_trusted_key: dst_buffer_to_key(%s) failed",
|
|
name);
|
|
goto failure;
|
|
}
|
|
zpk->zpk_name = zpk->zpk_key->dk_key_name;
|
|
zpk->zpk_next = NULL;
|
|
|
|
if (keylist == NULL) {
|
|
if (tree_add (&trusted_keys, compare_pubkey, zpk, NULL) == NULL)
|
|
goto failure;
|
|
}
|
|
else {
|
|
struct zpubkey *tkey = keylist;
|
|
while (tkey->zpk_next)
|
|
tkey = tkey->zpk_next;
|
|
tkey->zpk_next = zpk;
|
|
}
|
|
|
|
return (1);
|
|
failure:
|
|
memput(zpk, sizeof (struct zpubkey));
|
|
return (0);
|
|
}
|
|
|
|
/* Can the signer sign records for this name? This is a heuristic. */
|
|
static int
|
|
can_sign(const char *name, const char *signer) {
|
|
return (ns_samedomain(name, signer) &&
|
|
dn_count_labels(name) - dn_count_labels(signer) <= 2);
|
|
}
|
|
|
|
static int
|
|
rrset_set_security(struct db_rrset *rrset, int slev) {
|
|
struct dnode *dnp;
|
|
|
|
for (dnp = rrset->rr_list; dnp != NULL; dnp = dnp->dn_next)
|
|
dnp->dp->d_secure = slev;
|
|
for (dnp = rrset->rr_sigs; dnp != NULL; dnp = dnp->dn_next)
|
|
dnp->dp->d_secure = slev;
|
|
return (slev);
|
|
}
|
|
|
|
static int
|
|
convert_databuf(struct databuf *dp, struct converted_databuf *cdp) {
|
|
u_char *bp = cdp->cd_data;
|
|
u_char *cp = dp->d_data;
|
|
u_char *eob = cdp->cd_data + cdp->cd_alloc;
|
|
int len;
|
|
u_char buf[MAXDNAME];
|
|
|
|
switch (dp->d_type) {
|
|
case ns_t_soa:
|
|
case ns_t_minfo:
|
|
case ns_t_rp:
|
|
if (eob - bp < strlen((char *)cp) + 1)
|
|
return (-1);
|
|
if (ns_name_pton((char *)cp, buf, sizeof buf) < 0)
|
|
return (-1);
|
|
len = ns_name_ntol(buf, bp, eob - bp);
|
|
if (len < 0)
|
|
return (-1);
|
|
bp += len;
|
|
cp += strlen((char *)cp) + 1;
|
|
|
|
if (eob - bp < strlen((char *)cp) + 1)
|
|
return (-1);
|
|
if (ns_name_pton((char *)cp, buf, sizeof buf) < 0)
|
|
return (-1);
|
|
len = ns_name_ntol(buf, bp, eob - bp);
|
|
if (len < 0)
|
|
return (-1);
|
|
bp += len;
|
|
cp += strlen((char *)cp) + 1;
|
|
|
|
if (dp->d_type == ns_t_soa) {
|
|
if (eob - bp < 5 * INT32SZ)
|
|
return (-1);
|
|
memcpy(bp, cp, 5 * INT32SZ);
|
|
bp += (5 * INT32SZ);
|
|
cp += (5 * INT32SZ);
|
|
}
|
|
|
|
break;
|
|
|
|
case ns_t_ns:
|
|
case ns_t_cname:
|
|
case ns_t_mb:
|
|
case ns_t_mg:
|
|
case ns_t_mr:
|
|
case ns_t_ptr:
|
|
case ns_t_nxt:
|
|
if (eob - bp < strlen((char *)cp) + 1)
|
|
return (-1);
|
|
if (ns_name_pton((char *)cp, buf, sizeof buf) < 0)
|
|
return (-1);
|
|
len = ns_name_ntol(buf, bp, eob - bp);
|
|
if (len < 0)
|
|
return (-1);
|
|
bp += len;
|
|
cp += (len = strlen((char *)cp) + 1);
|
|
|
|
if (dp->d_type == ns_t_nxt) {
|
|
if (eob - bp < dp->d_size - len)
|
|
return (-1);
|
|
memcpy(bp, cp, dp->d_size - len);
|
|
bp += (dp->d_size - len);
|
|
cp += (dp->d_size - len);
|
|
}
|
|
break;
|
|
|
|
case ns_t_srv:
|
|
if (eob - bp < 2 * INT16SZ)
|
|
return (-1);
|
|
memcpy(bp, cp, 2 * INT16SZ);
|
|
bp += (2 * INT16SZ);
|
|
cp += (2 * INT16SZ);
|
|
/* no break */
|
|
case ns_t_rt:
|
|
case ns_t_mx:
|
|
case ns_t_afsdb:
|
|
case ns_t_px:
|
|
if (eob - bp < INT16SZ)
|
|
return (-1);
|
|
memcpy (bp, cp, INT16SZ);
|
|
bp += INT16SZ;
|
|
cp += INT16SZ;
|
|
|
|
if (eob - bp < strlen((char *)cp) + 1)
|
|
return (-1);
|
|
if (ns_name_pton((char *)cp, buf, sizeof buf) < 0)
|
|
return (-1);
|
|
len = ns_name_ntol(buf, bp, eob - bp);
|
|
if (len < 0)
|
|
return (-1);
|
|
bp += len;
|
|
cp += strlen((char *)cp) + 1;
|
|
|
|
if (dp->d_type == ns_t_px) {
|
|
if (eob - bp < strlen((char *)cp) + 1)
|
|
return (-1);
|
|
if (ns_name_pton((char *)cp, buf, sizeof buf) < 0)
|
|
return (-1);
|
|
len = ns_name_ntol(buf, bp, eob - bp);
|
|
if (len < 0)
|
|
return (-1);
|
|
bp += len;
|
|
cp += strlen((char *)cp) + 1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (eob - bp < dp->d_size)
|
|
return (-1);
|
|
memcpy(bp, cp, dp->d_size);
|
|
bp += dp->d_size;
|
|
}
|
|
cdp->cd_size = bp - cdp->cd_data;
|
|
return (cdp->cd_size);
|
|
}
|
|
|
|
static int
|
|
digest_rr(char *envelope, int elen, struct converted_databuf *cdp,
|
|
char *buffer, int blen)
|
|
{
|
|
char *bp = buffer, *eob = buffer + blen;
|
|
|
|
if (eob - bp < elen)
|
|
return (-1);
|
|
memcpy (bp, envelope, elen);
|
|
bp += elen;
|
|
|
|
if (eob - bp < INT16SZ)
|
|
return (-1);
|
|
PUTSHORT(cdp->cd_size, bp);
|
|
|
|
if (eob - bp < cdp->cd_size)
|
|
return (-1);
|
|
memcpy (bp, cdp->cd_data, cdp->cd_size);
|
|
bp += cdp->cd_size;
|
|
|
|
return (bp - buffer);
|
|
}
|
|
|
|
/* Sorts the converted databuf in the list */
|
|
static void
|
|
insert_converted_databuf(struct converted_databuf *cdp,
|
|
struct converted_databuf **clist)
|
|
{
|
|
struct converted_databuf *tcdp, *next;
|
|
int t;
|
|
|
|
#define compare_cdatabuf(c1, c2, t) \
|
|
(t = memcmp(c1->cd_data, c2->cd_data, MIN(c1->cd_size, c2->cd_size)), \
|
|
t == 0 ? c1->cd_size - c2->cd_size : t)
|
|
|
|
if (*clist == NULL) {
|
|
*clist = cdp;
|
|
return;
|
|
}
|
|
|
|
tcdp = *clist;
|
|
if (compare_cdatabuf(cdp, tcdp, t) < 0) {
|
|
cdp->cd_next = tcdp;
|
|
*clist = cdp;
|
|
return;
|
|
}
|
|
|
|
next = tcdp->cd_next;
|
|
while (next) {
|
|
if (compare_cdatabuf(cdp, next, t) < 0) {
|
|
cdp->cd_next = next;
|
|
tcdp->cd_next = cdp;
|
|
return;
|
|
}
|
|
tcdp = next;
|
|
next = next->cd_next;
|
|
}
|
|
tcdp->cd_next = cdp;
|
|
#undef compare_cdatabuf
|
|
}
|
|
|
|
static void
|
|
free_clist(struct converted_databuf *clist) {
|
|
struct converted_databuf *cdp;
|
|
|
|
while (clist != NULL) {
|
|
cdp = clist;
|
|
clist = clist->cd_next;
|
|
memput(cdp->cd_data, cdp->cd_alloc);
|
|
memput(cdp, sizeof(struct converted_databuf));
|
|
}
|
|
}
|
|
|
|
/* Removes all empty nodes from an rrset's SIG list. */
|
|
static void
|
|
rrset_trim_sigs(struct db_rrset *rrset) {
|
|
struct dnode *dnp, *odnp, *ndnp;
|
|
|
|
odnp = NULL;
|
|
dnp = rrset->rr_sigs;
|
|
while (dnp != NULL) {
|
|
if (dnp->dp != NULL) {
|
|
odnp = dnp;
|
|
dnp = dnp->dn_next;
|
|
}
|
|
else {
|
|
if (odnp != NULL)
|
|
odnp->dn_next = dnp->dn_next;
|
|
else
|
|
rrset->rr_sigs = dnp->dn_next;
|
|
ndnp = dnp->dn_next;
|
|
memput(dnp, sizeof(struct dnode));
|
|
dnp = ndnp;
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
verify_set(struct db_rrset *rrset) {
|
|
DST_KEY *key = NULL;
|
|
struct sig_record *sigdata;
|
|
struct dnode *sigdn;
|
|
struct databuf *sigdp;
|
|
time_t now;
|
|
char *signer;
|
|
u_char name_n[MAXDNAME];
|
|
u_char *sig, *eom;
|
|
int trustedkey = 0, siglen, labels, len = 0, ret;
|
|
u_char *buffer = NULL, *bp;
|
|
u_char envelope[MAXDNAME+32], *ep;
|
|
struct dnode *dnp;
|
|
int bufsize = 2048; /* Large enough for MAXDNAME + SIG_HDR_SIZE */
|
|
struct converted_databuf *clist = NULL, *cdp;
|
|
int dnssec_failed = 0, dnssec_succeeded = 0;
|
|
int return_value;
|
|
int i;
|
|
|
|
if (rrset == NULL || rrset->rr_name == NULL) {
|
|
ns_warning (ns_log_default, "verify_set: missing rrset/name");
|
|
return (rrset_set_security(rrset, DB_S_FAILED));
|
|
}
|
|
|
|
if (rrset->rr_sigs == NULL)
|
|
return (rrset_set_security(rrset, DB_S_INSECURE));
|
|
|
|
ns_debug(ns_log_default, 5, "verify_set(%s, %s, %s)", rrset->rr_name,
|
|
p_type(rrset->rr_type), p_class(rrset->rr_class));
|
|
|
|
now = time(NULL);
|
|
|
|
for (sigdn = rrset->rr_sigs; sigdn != NULL; sigdn = sigdn->dn_next) {
|
|
u_int32_t namefield;
|
|
struct sig_record sigrec;
|
|
|
|
sigdp = sigdn->dp;
|
|
|
|
eom = sigdp->d_data + sigdp->d_size;
|
|
if (sigdp->d_size < SIG_HDR_SIZE) {
|
|
return_value = DB_S_FAILED;
|
|
goto end;
|
|
}
|
|
memcpy(&sigrec, sigdp->d_data, SIG_HDR_SIZE);
|
|
sigdata = &sigrec;
|
|
signer = (char *)sigdp->d_data + SIG_HDR_SIZE;
|
|
sig = (u_char *)signer + strlen(signer) + 1;
|
|
siglen = eom - sig;
|
|
|
|
/*
|
|
* Don't verify a set if the SIG inception time is in
|
|
* the future. This should be fixed before 2038 (BEW)
|
|
*/
|
|
if (ntohl(sigdata->sig_time_n) > now)
|
|
continue;
|
|
|
|
/* An expired set is dropped, but the data is not. */
|
|
if (ntohl(sigdata->sig_exp_n) < now) {
|
|
db_freedata(sigdp);
|
|
sigdn->dp = NULL;
|
|
continue;
|
|
}
|
|
|
|
/* Cleanup from the last iteration if we continue'd */
|
|
if (trustedkey == 0 && key != NULL)
|
|
dst_free_key(key);
|
|
|
|
key = find_trusted_key(signer, sigdata->sig_keyid_n);
|
|
|
|
if (key == NULL) {
|
|
trustedkey = 0;
|
|
key = find_public_key(signer, sigdata->sig_keyid_n);
|
|
}
|
|
else
|
|
trustedkey = 1;
|
|
|
|
/* if we don't have the key, either
|
|
* - the data should be considered insecure
|
|
* - the sig is not a dnssec signature
|
|
*/
|
|
if (key == NULL)
|
|
continue;
|
|
|
|
/* Can a key with this name sign the data? */
|
|
if (!can_sign(rrset->rr_name, signer))
|
|
continue;
|
|
|
|
/* Check the protocol and flags of the key */
|
|
if (key->dk_proto != NS_KEY_PROT_DNSSEC &&
|
|
key->dk_proto != NS_KEY_PROT_ANY)
|
|
continue;
|
|
if (key->dk_flags & NS_KEY_NO_AUTH)
|
|
continue;
|
|
namefield = key->dk_flags & NS_KEY_NAME_TYPE;
|
|
if (namefield == NS_KEY_NAME_USER ||
|
|
namefield == NS_KEY_NAME_RESERVED)
|
|
continue;
|
|
if (namefield == NS_KEY_NAME_ENTITY &&
|
|
(key->dk_flags & NS_KEY_SIGNATORYMASK == 0))
|
|
continue;
|
|
|
|
/*
|
|
* If we're still here, we have a non-null key that's either
|
|
* a zone key or an entity key with signing authority.
|
|
*/
|
|
|
|
if (buffer == NULL) {
|
|
bp = buffer = memget(bufsize);
|
|
if (bp == NULL) {
|
|
return_value = DB_S_FAILED;
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
bp = buffer;
|
|
|
|
|
|
/* Digest the fixed portion of the SIG record */
|
|
memcpy(bp, (char *) sigdata, SIG_HDR_SIZE);
|
|
bp += SIG_HDR_SIZE;
|
|
|
|
/* Digest the signer's name, canonicalized */
|
|
if (ns_name_pton(signer, name_n, sizeof name_n) < 0) {
|
|
return_value = DB_S_FAILED;
|
|
goto end;
|
|
}
|
|
i = ns_name_ntol(name_n, (u_char *)bp, bufsize - SIG_HDR_SIZE);
|
|
if (i < 0) {
|
|
return_value = DB_S_FAILED;
|
|
goto end;
|
|
}
|
|
bp += i;
|
|
|
|
/* create the dns record envelope:
|
|
* <name><type><class><Original TTL>
|
|
*/
|
|
if (ns_name_pton(rrset->rr_name, name_n, sizeof name_n) < 0 ||
|
|
ns_name_ntol(name_n, (u_char *)envelope, sizeof envelope) < 0) {
|
|
return_value = DB_S_FAILED;
|
|
goto end;
|
|
}
|
|
|
|
labels = dn_count_labels(rrset->rr_name);
|
|
if (labels > sigdata->sig_labels_n) {
|
|
ep = envelope;
|
|
for (i=0; i < (labels - 1 - sigdata->sig_labels_n); i++)
|
|
ep += (*ep+1);
|
|
i = dn_skipname(ep, envelope + sizeof envelope);
|
|
if (i < 0) {
|
|
return_value = DB_S_FAILED;
|
|
goto end;
|
|
}
|
|
envelope[0] = '\001';
|
|
envelope[1] = '*';
|
|
memmove(envelope + 2, ep, i);
|
|
}
|
|
i = dn_skipname(envelope, envelope + sizeof envelope);
|
|
if (i < 0) {
|
|
return_value = DB_S_FAILED;
|
|
goto end;
|
|
}
|
|
ep = envelope + i;
|
|
PUTSHORT (rrset->rr_type, ep);
|
|
PUTSHORT (rrset->rr_class, ep);
|
|
if (envelope + sizeof(envelope) - ep < INT32SZ) {
|
|
return_value = DB_S_FAILED;
|
|
goto end;
|
|
}
|
|
memcpy (ep, &sigdata->sig_ottl_n, INT32SZ);
|
|
ep += INT32SZ;
|
|
|
|
if (clist == NULL) {
|
|
for (dnp = rrset->rr_list;
|
|
dnp != NULL;
|
|
dnp = dnp->dn_next)
|
|
{
|
|
struct databuf *dp = dnp->dp;
|
|
|
|
cdp = memget(sizeof(struct converted_databuf));
|
|
if (cdp == NULL) {
|
|
return_value = DB_S_FAILED;
|
|
goto end;
|
|
}
|
|
memset(cdp, 0, sizeof(*cdp));
|
|
/* Should be large enough... */
|
|
cdp->cd_alloc = dp->d_size + 8;
|
|
cdp->cd_data = memget(cdp->cd_alloc);
|
|
if (cdp->cd_data == NULL) {
|
|
memput(cdp, sizeof(*cdp));
|
|
return_value = DB_S_FAILED;
|
|
goto end;
|
|
}
|
|
while (convert_databuf(dp, cdp) < 0) {
|
|
memput(cdp->cd_data, cdp->cd_alloc);
|
|
cdp->cd_alloc *= 2;
|
|
cdp->cd_data = memget(cdp->cd_alloc);
|
|
if (cdp->cd_data == NULL) {
|
|
memput(cdp, sizeof(*cdp));
|
|
return_value = DB_S_FAILED;
|
|
goto end;
|
|
}
|
|
}
|
|
insert_converted_databuf(cdp, &clist);
|
|
}
|
|
}
|
|
|
|
for (cdp = clist; cdp != NULL; cdp = cdp->cd_next) {
|
|
len = digest_rr((char *)envelope, ep-envelope, cdp,
|
|
(char *)bp, bufsize - (bp - buffer));
|
|
while (len < 0) {
|
|
u_char *newbuf;
|
|
|
|
/* Double the buffer size */
|
|
newbuf = memget(bufsize*2);
|
|
if (newbuf == NULL) {
|
|
return_value = DB_S_FAILED;
|
|
goto end;
|
|
}
|
|
memcpy(newbuf, buffer, bp - buffer);
|
|
bp = (bp - buffer) + newbuf;
|
|
memput(buffer, bufsize);
|
|
buffer = newbuf;
|
|
bufsize *= 2;
|
|
|
|
len = digest_rr((char *)envelope, ep-envelope,
|
|
cdp, (char *)bp,
|
|
bufsize - (bp - buffer));
|
|
}
|
|
bp += len;
|
|
}
|
|
|
|
if (len < 0) {
|
|
return_value = DB_S_FAILED;
|
|
goto end;
|
|
}
|
|
|
|
ret = dst_verify_data(SIG_MODE_ALL, key, NULL, buffer,
|
|
bp - buffer, sig, siglen);
|
|
|
|
if (ret < 0) {
|
|
dnssec_failed++;
|
|
db_freedata(sigdp);
|
|
sigdn->dp = NULL;
|
|
}
|
|
else
|
|
dnssec_succeeded++;
|
|
}
|
|
|
|
end:
|
|
if (dnssec_failed > 0)
|
|
rrset_trim_sigs(rrset);
|
|
if (trustedkey == 0 && key != NULL)
|
|
dst_free_key(key);
|
|
|
|
if (dnssec_failed > 0 && dnssec_succeeded == 0) {
|
|
ns_warning (ns_log_default,
|
|
"verify_set(%s, %s, %s) failed",
|
|
rrset->rr_name, p_type(rrset->rr_type),
|
|
p_class(rrset->rr_class));
|
|
return_value = DB_S_FAILED;
|
|
}
|
|
else if (dnssec_succeeded > 0)
|
|
return_value = DB_S_SECURE;
|
|
else
|
|
return_value = DB_S_INSECURE;
|
|
free_clist(clist);
|
|
if (buffer != NULL)
|
|
memput(buffer, bufsize);
|
|
return (rrset_set_security(rrset, return_value));
|
|
}
|
|
|
|
static void
|
|
rrset_free_partial(struct db_rrset *rrset, int free_data, struct dnode *start) {
|
|
struct dnode *dnp;
|
|
int found_start = 0;
|
|
|
|
ns_debug(ns_log_default, 5, "rrset_free(%s)", rrset->rr_name);
|
|
|
|
if (start == NULL)
|
|
found_start = 1;
|
|
|
|
while (rrset->rr_list) {
|
|
dnp = rrset->rr_list;
|
|
if (dnp == start)
|
|
found_start = 1;
|
|
rrset->rr_list = rrset->rr_list->dn_next;
|
|
if (dnp->dp != NULL && free_data == 1 && found_start == 1)
|
|
db_freedata(dnp->dp);
|
|
memput(dnp, sizeof(struct dnode));
|
|
}
|
|
while (rrset->rr_sigs) {
|
|
dnp = rrset->rr_sigs;
|
|
if (dnp == start)
|
|
found_start = 1;
|
|
rrset->rr_sigs = rrset->rr_sigs->dn_next;
|
|
if (dnp->dp != NULL && free_data == 1 && found_start == 1)
|
|
db_freedata(dnp->dp);
|
|
memput(dnp, sizeof(struct dnode));
|
|
}
|
|
}
|
|
|
|
static void
|
|
rrset_free(struct db_rrset *rrset, int free_data) {
|
|
rrset_free_partial(rrset, free_data, NULL);
|
|
}
|
|
|
|
/*
|
|
* This is called when we have an rrset with SIGs and no other data.
|
|
* Returns 1 if we either found the necessary data or if the SIG can be added
|
|
* with no other data. 0 indicates that the SIG cannot be added.
|
|
*/
|
|
static int
|
|
attach_data(struct db_rrset *rrset) {
|
|
int type, class;
|
|
struct databuf *dp, *newdp, *sigdp;
|
|
struct dnode *dnp;
|
|
struct namebuf *np;
|
|
struct hashbuf *htp;
|
|
char *signer;
|
|
const char *fname;
|
|
char *name = rrset->rr_name;
|
|
|
|
sigdp = rrset->rr_sigs->dp;
|
|
|
|
type = SIG_COVERS(sigdp);
|
|
class = sigdp->d_class;
|
|
signer = (char *)(sigdp + SIG_HDR_SIZE);
|
|
|
|
/* First, see if the signer can sign data for the name. If not,
|
|
* it's not a DNSSEC signature, so we can insert it with no
|
|
* corresponding data.
|
|
*/
|
|
if (!can_sign(name, signer))
|
|
return (1);
|
|
|
|
htp = hashtab;
|
|
np = nlookup (name, &htp, &fname, 0);
|
|
if (fname != name)
|
|
return (0);
|
|
|
|
for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
|
|
if (dp->d_type == type && dp->d_class == class) {
|
|
newdp = savedata(class, type, dp->d_ttl, dp->d_data,
|
|
dp->d_size);
|
|
dnp = (struct dnode *) memget (sizeof (struct dnode));
|
|
if (dnp == NULL)
|
|
ns_panic(ns_log_default, 1,
|
|
"attach_data: memget failed");
|
|
dnp->dp = newdp;
|
|
dnp->dn_next = rrset->rr_list;
|
|
rrset->rr_list = dnp;
|
|
}
|
|
}
|
|
if (rrset->rr_list != NULL)
|
|
return (1);
|
|
else
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
rrset_db_update(struct db_rrset *rrset, int flags, struct hashbuf **htpp,
|
|
struct sockaddr_in from, int *rrcount)
|
|
{
|
|
struct dnode *dnp;
|
|
struct databuf *dp;
|
|
int ret;
|
|
|
|
/* If we have any unattached SIG records that are DNSSEC signatures,
|
|
* don't cache them unless we already have the corresponding data.
|
|
* If we do cache unattached SIGs, we run into problems later if we
|
|
* have a SIG X and get a query for type X.
|
|
*/
|
|
if (rrset->rr_list == NULL) {
|
|
if (attach_data(rrset) == 0) {
|
|
rrset_free(rrset, 1);
|
|
return (OK);
|
|
}
|
|
|
|
if (rrset->rr_list != NULL &&
|
|
verify_set(rrset) == DB_S_FAILED)
|
|
{
|
|
rrset_free(rrset, 1);
|
|
return (OK);
|
|
}
|
|
}
|
|
|
|
for (dnp = rrset->rr_list; dnp != NULL; dnp = dnp->dn_next) {
|
|
dp = dnp->dp;
|
|
ret = db_update(rrset->rr_name, dp, dp, NULL,
|
|
flags, (*htpp), from);
|
|
if (ret != OK) {
|
|
/* XXX Probably should do rollback. */
|
|
db_err(ret, rrset->rr_name, dp->d_type,
|
|
dnp->file, dnp->line);
|
|
if (ret != DATAEXISTS) {
|
|
rrset_free_partial(rrset, 1, dnp);
|
|
return (ret);
|
|
}
|
|
db_freedata(dp);
|
|
}
|
|
if (rrcount != NULL)
|
|
(*rrcount)++;
|
|
dnp->dp = NULL;
|
|
}
|
|
for (dnp = rrset->rr_sigs; dnp != NULL; dnp = dnp->dn_next) {
|
|
dp = dnp->dp;
|
|
if (dp == NULL) /* verifyset() can remove sigs */
|
|
continue;
|
|
ret = db_update(rrset->rr_name, dp, dp, NULL,
|
|
flags, (*htpp), from);
|
|
if (ret != OK) {
|
|
/* XXX Probably should do rollback. */
|
|
db_err(ret, rrset->rr_name, dp->d_type,
|
|
dnp->file, dnp->line);
|
|
if (ret != DATAEXISTS) {
|
|
rrset_free_partial(rrset, 1, dnp);
|
|
return (ret);
|
|
}
|
|
db_freedata(dp);
|
|
}
|
|
if (rrcount != NULL)
|
|
(*rrcount)++;
|
|
dnp->dp = NULL;
|
|
}
|
|
rrset_free(rrset, 0);
|
|
return (OK);
|
|
}
|
|
|
|
static int
|
|
rr_in_set(struct databuf *rr, struct dnode *set) {
|
|
struct dnode *dnp;
|
|
|
|
if (set == NULL)
|
|
return (0);
|
|
|
|
for(dnp = set; dnp != NULL; dnp = dnp->dn_next) {
|
|
if (dnp->dp->d_size == rr->d_size &&
|
|
memcmp(dnp->dp->d_data, rr->d_data, dnp->dp->d_size) == 0)
|
|
return (1);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
add_to_rrset_list(struct db_rrset **rrsets, char *name, struct databuf *dp,
|
|
int line, const char *file)
|
|
{
|
|
struct db_rrset *rrset = *rrsets;
|
|
struct dnode *dnp;
|
|
|
|
while (rrset != NULL) {
|
|
if (rrset->rr_type != ns_t_nxt || dp->d_type != ns_t_nxt) {
|
|
if (dp->d_type == ns_t_sig) {
|
|
if (SIG_COVERS(dp) == rrset->rr_type)
|
|
break;
|
|
} else {
|
|
if (dp->d_type == rrset->rr_type)
|
|
break;
|
|
}
|
|
}
|
|
else if (nxt_match_rrset(dp, rrset))
|
|
break;
|
|
rrset = rrset->rr_next;
|
|
}
|
|
|
|
if (rrset != NULL) {
|
|
if ((dp->d_type == ns_t_sig && rr_in_set(dp, rrset->rr_sigs)) ||
|
|
(dp->d_type != ns_t_sig && rr_in_set(dp, rrset->rr_list)))
|
|
{
|
|
db_freedata(dp);
|
|
return (DATAEXISTS);
|
|
}
|
|
} else {
|
|
rrset = (struct db_rrset *) memget(sizeof(struct db_rrset));
|
|
if (rrset == NULL)
|
|
ns_panic(ns_log_default, 1,
|
|
"add_to_rrset_list: memget failed(%s)", name);
|
|
memset(rrset, 0, sizeof(struct db_rrset));
|
|
rrset->rr_name = savestr(name, 1);
|
|
rrset->rr_class = dp->d_class;
|
|
if (dp->d_type == ns_t_sig)
|
|
rrset->rr_type = SIG_COVERS(dp);
|
|
else
|
|
rrset->rr_type = dp->d_type;
|
|
rrset->rr_next = *rrsets;
|
|
*rrsets = rrset;
|
|
}
|
|
|
|
dnp = (struct dnode *) memget(sizeof(struct dnode));
|
|
if (dnp == NULL)
|
|
ns_panic(ns_log_default, 1,
|
|
"add_to_rrset_list: memget failed(%s)", name);
|
|
memset(dnp, 0, sizeof(struct dnode));
|
|
dnp->dp = dp;
|
|
if (dp->d_type == ns_t_sig) {
|
|
if (rrset->rr_sigs != NULL) {
|
|
struct dnode *fdnp;
|
|
|
|
/* Preserve the order of the RRs */
|
|
/* Add this one to the end of the list */
|
|
for (fdnp = rrset->rr_sigs;
|
|
fdnp->dn_next != NULL;
|
|
fdnp = fdnp->dn_next)
|
|
/* NULL */ ;
|
|
fdnp->dn_next = dnp;
|
|
} else
|
|
rrset->rr_sigs = dnp;
|
|
} else {
|
|
if (rrset->rr_list != NULL) {
|
|
struct dnode *fdnp;
|
|
|
|
/* Preserve the order of the RRs */
|
|
/* Add this one to the end of the list */
|
|
for (fdnp = rrset->rr_list;
|
|
fdnp->dn_next != NULL;
|
|
fdnp = fdnp->dn_next)
|
|
/* NULL */ ;
|
|
fdnp->dn_next = dnp;
|
|
} else
|
|
rrset->rr_list = dnp;
|
|
}
|
|
dnp->file = (char *) file;
|
|
dnp->line = line;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
update_rrset_list(struct db_rrset **rrsets, int flags, struct hashbuf **htpp,
|
|
struct sockaddr_in from, int *rrcount)
|
|
{
|
|
struct db_rrset *rrset = *rrsets, *next = NULL, *last = NULL;
|
|
int result = 0, tresult, cnameandother = 0;
|
|
|
|
while (rrset != NULL) {
|
|
if (rrset->rr_type == ns_t_key)
|
|
break;
|
|
last = rrset;
|
|
rrset = rrset->rr_next;
|
|
}
|
|
|
|
if (rrset != NULL && last != NULL) {
|
|
last->rr_next = rrset->rr_next;
|
|
rrset->rr_next = *rrsets;
|
|
*rrsets = rrset;
|
|
}
|
|
|
|
rrset = *rrsets;
|
|
|
|
while (rrset != NULL) {
|
|
if (verify_set(rrset) > DB_S_FAILED) {
|
|
ns_debug(ns_log_default, 10,
|
|
"update_rrset_list(%s, %s): set verified",
|
|
rrset->rr_name, p_type(rrset->rr_type));
|
|
tresult = rrset_db_update(rrset, flags, htpp,
|
|
from, rrcount);
|
|
if (tresult == CNAMEANDOTHER)
|
|
cnameandother++;
|
|
if (tresult != OK)
|
|
result = tresult;
|
|
}
|
|
else {
|
|
rrset_free(rrset, 1);
|
|
result = DNSSECFAIL;
|
|
}
|
|
freestr(rrset->rr_name);
|
|
next = rrset->rr_next;
|
|
memput(rrset, sizeof(struct db_rrset));
|
|
rrset = next;
|
|
}
|
|
*rrsets = NULL;
|
|
if (cnameandother != 0)
|
|
return (CNAMEANDOTHER);
|
|
return (result);
|
|
}
|
|
|
|
int
|
|
db_set_update(char *name, struct databuf *dp, void **state,
|
|
int flags, struct hashbuf **htpp, struct sockaddr_in from,
|
|
int *rrcount, int line, const char *file)
|
|
{
|
|
struct db_rrset **rrsets;
|
|
struct db_rrset *rrset;
|
|
int result = 0;
|
|
|
|
ns_debug(ns_log_default, 5, "db_set_update(%s)",
|
|
(name == NULL) ? "<NULL>" : (*name == 0) ? "." : name);
|
|
|
|
if (state == NULL)
|
|
ns_panic(ns_log_default, 1,
|
|
"Called db_set_update with state == NULL");
|
|
|
|
rrsets = (struct db_rrset **) state;
|
|
|
|
if (*rrsets != NULL) {
|
|
rrset = *rrsets;
|
|
if (rrset->rr_name != NULL && dp != NULL &&
|
|
name != NULL && ns_samename(name, rrset->rr_name) == 1 &&
|
|
dp->d_class == rrset->rr_class)
|
|
return (add_to_rrset_list(rrsets, name, dp,
|
|
line, file));
|
|
}
|
|
|
|
if (*rrsets != NULL)
|
|
result = update_rrset_list(rrsets, flags, htpp, from, rrcount);
|
|
|
|
if (dp != NULL) {
|
|
ns_debug(ns_log_default, 10,
|
|
"db_set_update(%s), creating new list", name);
|
|
|
|
(void) add_to_rrset_list(rrsets, name, dp, line, file);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
static int
|
|
nxt_match_rrset(struct databuf *dp, struct db_rrset *rrset) {
|
|
if (rrset->rr_list != NULL)
|
|
return (nxtmatch(rrset->rr_name, dp, rrset->rr_list->dp));
|
|
else
|
|
return (nxtmatch(rrset->rr_name, dp, rrset->rr_sigs->dp));
|
|
}
|