mirror of
https://github.com/isc-projects/bind9.git
synced 2026-03-10 10:11:39 -04:00
ISCCC_R_SYNTAX, ISCCC_R_EXPIRED, and ISCCC_R_CLOCKSKEW have the same usage and text formats as DNS_R_SYNTAX, DNS_R_EXPIRED and DNS_R_CLOCKSCREW respectively. this was originally done because result codes were defined in separate libraries, and some tool might be linked with libisccc but not libdns. as the result codes are now defined in only one place, there's no need to retain the duplicates.
1058 lines
24 KiB
C
1058 lines
24 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* SPDX-License-Identifier: MPL-2.0 AND ISC
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2001 Nominum, Inc.
|
|
*
|
|
* Permission to use, copy, modify, and/or 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 ISC AND NOMINUM DISCLAIMS ALL
|
|
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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.
|
|
*/
|
|
|
|
/*! \file */
|
|
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <isc/assertions.h>
|
|
#include <isc/hmac.h>
|
|
#include <isc/result.h>
|
|
#include <isc/safe.h>
|
|
|
|
#include <isccc/alist.h>
|
|
#include <isccc/base64.h>
|
|
#include <isccc/cc.h>
|
|
#include <isccc/sexpr.h>
|
|
#include <isccc/symtab.h>
|
|
#include <isccc/symtype.h>
|
|
#include <isccc/util.h>
|
|
|
|
#define MAX_TAGS 256
|
|
#define DUP_LIFETIME 900
|
|
#ifndef ISCCC_MAXDEPTH
|
|
#define ISCCC_MAXDEPTH \
|
|
10 /* Big enough for rndc which just sends a string each way. */
|
|
#endif
|
|
|
|
typedef isccc_sexpr_t *sexpr_ptr;
|
|
|
|
static unsigned char auth_hmd5[] = {
|
|
0x05, 0x5f, 0x61, 0x75, 0x74, 0x68, /*%< len + _auth */
|
|
ISCCC_CCMSGTYPE_TABLE, /*%< message type */
|
|
0x00, 0x00, 0x00, 0x20, /*%< length == 32 */
|
|
0x04, 0x68, 0x6d, 0x64, 0x35, /*%< len + hmd5 */
|
|
ISCCC_CCMSGTYPE_BINARYDATA, /*%< message type */
|
|
0x00, 0x00, 0x00, 0x16, /*%< length == 22 */
|
|
/*
|
|
* The base64 encoding of one of our HMAC-MD5 signatures is
|
|
* 22 bytes.
|
|
*/
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
#define HMD5_OFFSET 21 /*%< 21 = 6 + 1 + 4 + 5 + 1 + 4 */
|
|
#define HMD5_LENGTH 22
|
|
|
|
static unsigned char auth_hsha[] = {
|
|
0x05, 0x5f, 0x61, 0x75, 0x74, 0x68, /*%< len + _auth */
|
|
ISCCC_CCMSGTYPE_TABLE, /*%< message type */
|
|
0x00, 0x00, 0x00, 0x63, /*%< length == 99 */
|
|
0x04, 0x68, 0x73, 0x68, 0x61, /*%< len + hsha */
|
|
ISCCC_CCMSGTYPE_BINARYDATA, /*%< message type */
|
|
0x00, 0x00, 0x00, 0x59, /*%< length == 89 */
|
|
0x00, /*%< algorithm */
|
|
/*
|
|
* The base64 encoding of one of our HMAC-SHA* signatures is
|
|
* 88 bytes.
|
|
*/
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
#define HSHA_OFFSET 22 /*%< 21 = 6 + 1 + 4 + 5 + 1 + 4 + 1 */
|
|
#define HSHA_LENGTH 88
|
|
|
|
static isc_result_t
|
|
table_towire(isccc_sexpr_t *alist, isc_buffer_t **buffer);
|
|
|
|
static isc_result_t
|
|
list_towire(isccc_sexpr_t *alist, isc_buffer_t **buffer);
|
|
|
|
static isc_result_t
|
|
value_towire(isccc_sexpr_t *elt, isc_buffer_t **buffer) {
|
|
unsigned int len;
|
|
isccc_region_t *vr;
|
|
isc_result_t result;
|
|
|
|
if (isccc_sexpr_binaryp(elt)) {
|
|
vr = isccc_sexpr_tobinary(elt);
|
|
len = REGION_SIZE(*vr);
|
|
result = isc_buffer_reserve(*buffer, 1 + 4);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return ISC_R_NOSPACE;
|
|
}
|
|
isc_buffer_putuint8(*buffer, ISCCC_CCMSGTYPE_BINARYDATA);
|
|
isc_buffer_putuint32(*buffer, len);
|
|
|
|
result = isc_buffer_reserve(*buffer, len);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return ISC_R_NOSPACE;
|
|
}
|
|
isc_buffer_putmem(*buffer, vr->rstart, len);
|
|
} else if (isccc_alist_alistp(elt)) {
|
|
unsigned int used;
|
|
isc_buffer_t b;
|
|
|
|
result = isc_buffer_reserve(*buffer, 1 + 4);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return ISC_R_NOSPACE;
|
|
}
|
|
isc_buffer_putuint8(*buffer, ISCCC_CCMSGTYPE_TABLE);
|
|
/*
|
|
* Emit a placeholder length.
|
|
*/
|
|
used = (*buffer)->used;
|
|
isc_buffer_putuint32(*buffer, 0);
|
|
|
|
/*
|
|
* Emit the table.
|
|
*/
|
|
result = table_towire(elt, buffer);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
len = (*buffer)->used - used;
|
|
/*
|
|
* 'len' is 4 bytes too big, since it counts
|
|
* the placeholder length too. Adjust and
|
|
* emit.
|
|
*/
|
|
INSIST(len >= 4U);
|
|
len -= 4;
|
|
|
|
isc_buffer_init(&b, (unsigned char *)(*buffer)->base + used, 4);
|
|
isc_buffer_putuint32(&b, len);
|
|
} else if (isccc_sexpr_listp(elt)) {
|
|
unsigned int used;
|
|
isc_buffer_t b;
|
|
|
|
result = isc_buffer_reserve(*buffer, 1 + 4);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return ISC_R_NOSPACE;
|
|
}
|
|
isc_buffer_putuint8(*buffer, ISCCC_CCMSGTYPE_LIST);
|
|
/*
|
|
* Emit a placeholder length.
|
|
*/
|
|
used = (*buffer)->used;
|
|
isc_buffer_putuint32(*buffer, 0);
|
|
|
|
/*
|
|
* Emit the list.
|
|
*/
|
|
result = list_towire(elt, buffer);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
len = (*buffer)->used - used;
|
|
/*
|
|
* 'len' is 4 bytes too big, since it counts
|
|
* the placeholder length too. Adjust and
|
|
* emit.
|
|
*/
|
|
INSIST(len >= 4U);
|
|
len -= 4;
|
|
|
|
isc_buffer_init(&b, (unsigned char *)(*buffer)->base + used, 4);
|
|
isc_buffer_putuint32(&b, len);
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
table_towire(isccc_sexpr_t *alist, isc_buffer_t **buffer) {
|
|
isccc_sexpr_t *kv, *elt, *k, *v;
|
|
char *ks;
|
|
isc_result_t result;
|
|
unsigned int len;
|
|
|
|
for (elt = isccc_alist_first(alist); elt != NULL;
|
|
elt = ISCCC_SEXPR_CDR(elt))
|
|
{
|
|
kv = ISCCC_SEXPR_CAR(elt);
|
|
k = ISCCC_SEXPR_CAR(kv);
|
|
ks = isccc_sexpr_tostring(k);
|
|
v = ISCCC_SEXPR_CDR(kv);
|
|
len = (unsigned int)strlen(ks);
|
|
INSIST(len <= 255U);
|
|
/*
|
|
* Emit the key name.
|
|
*/
|
|
result = isc_buffer_reserve(*buffer, 1 + len);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return ISC_R_NOSPACE;
|
|
}
|
|
isc_buffer_putuint8(*buffer, (uint8_t)len);
|
|
isc_buffer_putmem(*buffer, (const unsigned char *)ks, len);
|
|
/*
|
|
* Emit the value.
|
|
*/
|
|
result = value_towire(v, buffer);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
list_towire(isccc_sexpr_t *list, isc_buffer_t **buffer) {
|
|
isc_result_t result;
|
|
|
|
while (list != NULL) {
|
|
result = value_towire(ISCCC_SEXPR_CAR(list), buffer);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
list = ISCCC_SEXPR_CDR(list);
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
sign(unsigned char *data, unsigned int length, unsigned char *out,
|
|
uint32_t algorithm, isccc_region_t *secret) {
|
|
const isc_md_type_t *md_type;
|
|
isc_result_t result;
|
|
isccc_region_t source, target;
|
|
unsigned char digest[ISC_MAX_MD_SIZE];
|
|
unsigned int digestlen = sizeof(digest);
|
|
unsigned char digestb64[HSHA_LENGTH + 4];
|
|
|
|
source.rstart = digest;
|
|
|
|
switch (algorithm) {
|
|
case ISCCC_ALG_HMACMD5:
|
|
md_type = ISC_MD_MD5;
|
|
break;
|
|
case ISCCC_ALG_HMACSHA1:
|
|
md_type = ISC_MD_SHA1;
|
|
break;
|
|
case ISCCC_ALG_HMACSHA224:
|
|
md_type = ISC_MD_SHA224;
|
|
break;
|
|
case ISCCC_ALG_HMACSHA256:
|
|
md_type = ISC_MD_SHA256;
|
|
break;
|
|
case ISCCC_ALG_HMACSHA384:
|
|
md_type = ISC_MD_SHA384;
|
|
break;
|
|
case ISCCC_ALG_HMACSHA512:
|
|
md_type = ISC_MD_SHA512;
|
|
break;
|
|
default:
|
|
return ISC_R_NOTIMPLEMENTED;
|
|
}
|
|
|
|
result = isc_hmac(md_type, secret->rstart, REGION_SIZE(*secret), data,
|
|
length, digest, &digestlen);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
source.rend = digest + digestlen;
|
|
|
|
memset(digestb64, 0, sizeof(digestb64));
|
|
target.rstart = digestb64;
|
|
target.rend = digestb64 + sizeof(digestb64);
|
|
result = isccc_base64_encode(&source, 64, "", &target);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
if (algorithm == ISCCC_ALG_HMACMD5) {
|
|
PUT_MEM(digestb64, HMD5_LENGTH, out);
|
|
} else {
|
|
PUT_MEM(digestb64, HSHA_LENGTH, out);
|
|
}
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
isc_result_t
|
|
isccc_cc_towire(isccc_sexpr_t *alist, isc_buffer_t **buffer, uint32_t algorithm,
|
|
isccc_region_t *secret) {
|
|
unsigned int hmac_base, signed_base;
|
|
isc_result_t result;
|
|
|
|
result = isc_buffer_reserve(*buffer,
|
|
4 + ((algorithm == ISCCC_ALG_HMACMD5)
|
|
? sizeof(auth_hmd5)
|
|
: sizeof(auth_hsha)));
|
|
if (result != ISC_R_SUCCESS) {
|
|
return ISC_R_NOSPACE;
|
|
}
|
|
|
|
/*
|
|
* Emit protocol version.
|
|
*/
|
|
isc_buffer_putuint32(*buffer, 1);
|
|
|
|
if (secret != NULL) {
|
|
/*
|
|
* Emit _auth section with zeroed HMAC signature.
|
|
* We'll replace the zeros with the real signature once
|
|
* we know what it is.
|
|
*/
|
|
if (algorithm == ISCCC_ALG_HMACMD5) {
|
|
hmac_base = (*buffer)->used + HMD5_OFFSET;
|
|
isc_buffer_putmem(*buffer, auth_hmd5,
|
|
sizeof(auth_hmd5));
|
|
} else {
|
|
unsigned char *hmac_alg;
|
|
|
|
hmac_base = (*buffer)->used + HSHA_OFFSET;
|
|
hmac_alg = (unsigned char *)isc_buffer_used(*buffer) +
|
|
HSHA_OFFSET - 1;
|
|
isc_buffer_putmem(*buffer, auth_hsha,
|
|
sizeof(auth_hsha));
|
|
*hmac_alg = algorithm;
|
|
}
|
|
} else {
|
|
hmac_base = 0;
|
|
}
|
|
signed_base = (*buffer)->used;
|
|
/*
|
|
* Delete any existing _auth section so that we don't try
|
|
* to encode it.
|
|
*/
|
|
isccc_alist_delete(alist, "_auth");
|
|
/*
|
|
* Emit the message.
|
|
*/
|
|
result = table_towire(alist, buffer);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
if (secret != NULL) {
|
|
return sign((unsigned char *)(*buffer)->base + signed_base,
|
|
(*buffer)->used - signed_base,
|
|
(unsigned char *)(*buffer)->base + hmac_base,
|
|
algorithm, secret);
|
|
}
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
verify(isccc_sexpr_t *alist, unsigned char *data, unsigned int length,
|
|
uint32_t algorithm, isccc_region_t *secret) {
|
|
const isc_md_type_t *md_type;
|
|
isccc_region_t source;
|
|
isccc_region_t target;
|
|
isc_result_t result;
|
|
isccc_sexpr_t *_auth, *hmacvalue;
|
|
unsigned char digest[ISC_MAX_MD_SIZE];
|
|
unsigned int digestlen = sizeof(digest);
|
|
unsigned char digestb64[HSHA_LENGTH * 4];
|
|
|
|
/*
|
|
* Extract digest.
|
|
*/
|
|
_auth = isccc_alist_lookup(alist, "_auth");
|
|
if (!isccc_alist_alistp(_auth)) {
|
|
return ISC_R_FAILURE;
|
|
}
|
|
if (algorithm == ISCCC_ALG_HMACMD5) {
|
|
hmacvalue = isccc_alist_lookup(_auth, "hmd5");
|
|
} else {
|
|
hmacvalue = isccc_alist_lookup(_auth, "hsha");
|
|
}
|
|
if (!isccc_sexpr_binaryp(hmacvalue)) {
|
|
return ISC_R_FAILURE;
|
|
}
|
|
/*
|
|
* Compute digest.
|
|
*/
|
|
source.rstart = digest;
|
|
|
|
switch (algorithm) {
|
|
case ISCCC_ALG_HMACMD5:
|
|
md_type = ISC_MD_MD5;
|
|
break;
|
|
case ISCCC_ALG_HMACSHA1:
|
|
md_type = ISC_MD_SHA1;
|
|
break;
|
|
case ISCCC_ALG_HMACSHA224:
|
|
md_type = ISC_MD_SHA224;
|
|
break;
|
|
case ISCCC_ALG_HMACSHA256:
|
|
md_type = ISC_MD_SHA256;
|
|
break;
|
|
case ISCCC_ALG_HMACSHA384:
|
|
md_type = ISC_MD_SHA384;
|
|
break;
|
|
case ISCCC_ALG_HMACSHA512:
|
|
md_type = ISC_MD_SHA512;
|
|
break;
|
|
default:
|
|
return ISC_R_NOTIMPLEMENTED;
|
|
}
|
|
|
|
result = isc_hmac(md_type, secret->rstart, REGION_SIZE(*secret), data,
|
|
length, digest, &digestlen);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
source.rend = digest + digestlen;
|
|
|
|
target.rstart = digestb64;
|
|
target.rend = digestb64 + sizeof(digestb64);
|
|
memset(digestb64, 0, sizeof(digestb64));
|
|
result = isccc_base64_encode(&source, 64, "", &target);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Verify.
|
|
*/
|
|
if (algorithm == ISCCC_ALG_HMACMD5) {
|
|
isccc_region_t *region;
|
|
unsigned char *value;
|
|
|
|
region = isccc_sexpr_tobinary(hmacvalue);
|
|
if ((region->rend - region->rstart) != HMD5_LENGTH) {
|
|
return ISCCC_R_BADAUTH;
|
|
}
|
|
value = region->rstart;
|
|
if (!isc_safe_memequal(value, digestb64, HMD5_LENGTH)) {
|
|
return ISCCC_R_BADAUTH;
|
|
}
|
|
} else {
|
|
isccc_region_t *region;
|
|
unsigned char *value;
|
|
uint32_t valalg;
|
|
|
|
region = isccc_sexpr_tobinary(hmacvalue);
|
|
|
|
/*
|
|
* Note: with non-MD5 algorithms, there's an extra octet
|
|
* to identify which algorithm is in use.
|
|
*/
|
|
if ((region->rend - region->rstart) != HSHA_LENGTH + 1) {
|
|
return ISCCC_R_BADAUTH;
|
|
}
|
|
value = region->rstart;
|
|
GET8(valalg, value);
|
|
if ((valalg != algorithm) ||
|
|
!isc_safe_memequal(value, digestb64, HSHA_LENGTH))
|
|
{
|
|
return ISCCC_R_BADAUTH;
|
|
}
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
table_fromwire(isccc_region_t *source, isccc_region_t *secret,
|
|
uint32_t algorithm, unsigned int depth, isccc_sexpr_t **alistp);
|
|
|
|
static isc_result_t
|
|
list_fromwire(isccc_region_t *source, unsigned int depth,
|
|
isccc_sexpr_t **listp);
|
|
|
|
static isc_result_t
|
|
value_fromwire(isccc_region_t *source, unsigned int depth,
|
|
isccc_sexpr_t **valuep) {
|
|
unsigned int msgtype;
|
|
uint32_t len;
|
|
isccc_sexpr_t *value;
|
|
isccc_region_t active;
|
|
isc_result_t result;
|
|
|
|
if (depth > ISCCC_MAXDEPTH) {
|
|
return ISCCC_R_MAXDEPTH;
|
|
}
|
|
|
|
if (REGION_SIZE(*source) < 1 + 4) {
|
|
return ISC_R_UNEXPECTEDEND;
|
|
}
|
|
GET8(msgtype, source->rstart);
|
|
GET32(len, source->rstart);
|
|
if (REGION_SIZE(*source) < len) {
|
|
return ISC_R_UNEXPECTEDEND;
|
|
}
|
|
active.rstart = source->rstart;
|
|
active.rend = active.rstart + len;
|
|
source->rstart = active.rend;
|
|
if (msgtype == ISCCC_CCMSGTYPE_BINARYDATA) {
|
|
value = isccc_sexpr_frombinary(&active);
|
|
if (value != NULL) {
|
|
*valuep = value;
|
|
result = ISC_R_SUCCESS;
|
|
} else {
|
|
result = ISC_R_NOMEMORY;
|
|
}
|
|
} else if (msgtype == ISCCC_CCMSGTYPE_TABLE) {
|
|
result = table_fromwire(&active, NULL, 0, depth + 1, valuep);
|
|
} else if (msgtype == ISCCC_CCMSGTYPE_LIST) {
|
|
result = list_fromwire(&active, depth + 1, valuep);
|
|
} else {
|
|
result = DNS_R_SYNTAX;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
table_fromwire(isccc_region_t *source, isccc_region_t *secret,
|
|
uint32_t algorithm, unsigned int depth, isccc_sexpr_t **alistp) {
|
|
char key[256];
|
|
uint32_t len;
|
|
isc_result_t result;
|
|
isccc_sexpr_t *alist, *value;
|
|
bool first_tag;
|
|
unsigned char *checksum_rstart;
|
|
|
|
REQUIRE(alistp != NULL && *alistp == NULL);
|
|
|
|
if (depth > ISCCC_MAXDEPTH) {
|
|
return ISCCC_R_MAXDEPTH;
|
|
}
|
|
|
|
checksum_rstart = NULL;
|
|
first_tag = true;
|
|
alist = isccc_alist_create();
|
|
if (alist == NULL) {
|
|
return ISC_R_NOMEMORY;
|
|
}
|
|
|
|
while (!REGION_EMPTY(*source)) {
|
|
GET8(len, source->rstart);
|
|
if (REGION_SIZE(*source) < len) {
|
|
result = ISC_R_UNEXPECTEDEND;
|
|
goto bad;
|
|
}
|
|
GET_MEM(key, len, source->rstart);
|
|
key[len] = '\0'; /* Ensure NUL termination. */
|
|
value = NULL;
|
|
result = value_fromwire(source, depth + 1, &value);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto bad;
|
|
}
|
|
if (isccc_alist_define(alist, key, value) == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto bad;
|
|
}
|
|
if (first_tag && secret != NULL && strcmp(key, "_auth") == 0) {
|
|
checksum_rstart = source->rstart;
|
|
}
|
|
first_tag = false;
|
|
}
|
|
|
|
if (secret != NULL) {
|
|
if (checksum_rstart != NULL) {
|
|
result = verify(
|
|
alist, checksum_rstart,
|
|
(unsigned int)(source->rend - checksum_rstart),
|
|
algorithm, secret);
|
|
} else {
|
|
result = ISCCC_R_BADAUTH;
|
|
}
|
|
} else {
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
|
|
bad:
|
|
if (result == ISC_R_SUCCESS) {
|
|
*alistp = alist;
|
|
} else {
|
|
isccc_sexpr_free(&alist);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
list_fromwire(isccc_region_t *source, unsigned int depth,
|
|
isccc_sexpr_t **listp) {
|
|
isccc_sexpr_t *list, *value;
|
|
isc_result_t result;
|
|
|
|
if (depth > ISCCC_MAXDEPTH) {
|
|
return ISCCC_R_MAXDEPTH;
|
|
}
|
|
|
|
list = NULL;
|
|
while (!REGION_EMPTY(*source)) {
|
|
value = NULL;
|
|
result = value_fromwire(source, depth + 1, &value);
|
|
if (result != ISC_R_SUCCESS) {
|
|
isccc_sexpr_free(&list);
|
|
return result;
|
|
}
|
|
if (isccc_sexpr_addtolist(&list, value) == NULL) {
|
|
isccc_sexpr_free(&value);
|
|
isccc_sexpr_free(&list);
|
|
return ISC_R_NOMEMORY;
|
|
}
|
|
}
|
|
|
|
*listp = list;
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
isc_result_t
|
|
isccc_cc_fromwire(isccc_region_t *source, isccc_sexpr_t **alistp,
|
|
uint32_t algorithm, isccc_region_t *secret) {
|
|
unsigned int size;
|
|
uint32_t version;
|
|
|
|
size = REGION_SIZE(*source);
|
|
if (size < 4) {
|
|
return ISC_R_UNEXPECTEDEND;
|
|
}
|
|
GET32(version, source->rstart);
|
|
if (version != 1) {
|
|
return ISCCC_R_UNKNOWNVERSION;
|
|
}
|
|
|
|
return table_fromwire(source, secret, algorithm, 0, alistp);
|
|
}
|
|
|
|
static isc_result_t
|
|
createmessage(uint32_t version, const char *from, const char *to,
|
|
uint32_t serial, isccc_time_t now, isccc_time_t expires,
|
|
isccc_sexpr_t **alistp, bool want_expires) {
|
|
isccc_sexpr_t *alist, *_ctrl, *_data;
|
|
isc_result_t result;
|
|
|
|
REQUIRE(alistp != NULL && *alistp == NULL);
|
|
|
|
if (version != 1) {
|
|
return ISCCC_R_UNKNOWNVERSION;
|
|
}
|
|
|
|
alist = isccc_alist_create();
|
|
if (alist == NULL) {
|
|
return ISC_R_NOMEMORY;
|
|
}
|
|
|
|
result = ISC_R_NOMEMORY;
|
|
|
|
_ctrl = isccc_alist_create();
|
|
if (_ctrl == NULL) {
|
|
goto bad;
|
|
}
|
|
if (isccc_alist_define(alist, "_ctrl", _ctrl) == NULL) {
|
|
isccc_sexpr_free(&_ctrl);
|
|
goto bad;
|
|
}
|
|
|
|
_data = isccc_alist_create();
|
|
if (_data == NULL) {
|
|
goto bad;
|
|
}
|
|
if (isccc_alist_define(alist, "_data", _data) == NULL) {
|
|
isccc_sexpr_free(&_data);
|
|
goto bad;
|
|
}
|
|
|
|
if (isccc_cc_defineuint32(_ctrl, "_ser", serial) == NULL ||
|
|
isccc_cc_defineuint32(_ctrl, "_tim", now) == NULL ||
|
|
(want_expires &&
|
|
isccc_cc_defineuint32(_ctrl, "_exp", expires) == NULL))
|
|
{
|
|
goto bad;
|
|
}
|
|
if (from != NULL && isccc_cc_definestring(_ctrl, "_frm", from) == NULL)
|
|
{
|
|
goto bad;
|
|
}
|
|
if (to != NULL && isccc_cc_definestring(_ctrl, "_to", to) == NULL) {
|
|
goto bad;
|
|
}
|
|
|
|
*alistp = alist;
|
|
|
|
return ISC_R_SUCCESS;
|
|
|
|
bad:
|
|
isccc_sexpr_free(&alist);
|
|
|
|
return result;
|
|
}
|
|
|
|
isc_result_t
|
|
isccc_cc_createmessage(uint32_t version, const char *from, const char *to,
|
|
uint32_t serial, isccc_time_t now, isccc_time_t expires,
|
|
isccc_sexpr_t **alistp) {
|
|
return createmessage(version, from, to, serial, now, expires, alistp,
|
|
true);
|
|
}
|
|
|
|
isc_result_t
|
|
isccc_cc_createack(isccc_sexpr_t *message, bool ok, isccc_sexpr_t **ackp) {
|
|
char *_frm, *_to;
|
|
uint32_t serial;
|
|
isccc_sexpr_t *ack, *_ctrl;
|
|
isc_result_t result;
|
|
isccc_time_t t;
|
|
|
|
REQUIRE(ackp != NULL && *ackp == NULL);
|
|
|
|
_ctrl = isccc_alist_lookup(message, "_ctrl");
|
|
if (!isccc_alist_alistp(_ctrl) ||
|
|
isccc_cc_lookupuint32(_ctrl, "_ser", &serial) != ISC_R_SUCCESS ||
|
|
isccc_cc_lookupuint32(_ctrl, "_tim", &t) != ISC_R_SUCCESS)
|
|
{
|
|
return ISC_R_FAILURE;
|
|
}
|
|
/*
|
|
* _frm and _to are optional.
|
|
*/
|
|
_frm = NULL;
|
|
(void)isccc_cc_lookupstring(_ctrl, "_frm", &_frm);
|
|
_to = NULL;
|
|
(void)isccc_cc_lookupstring(_ctrl, "_to", &_to);
|
|
/*
|
|
* Create the ack.
|
|
*/
|
|
ack = NULL;
|
|
result = createmessage(1, _to, _frm, serial, t, 0, &ack, false);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
_ctrl = isccc_alist_lookup(ack, "_ctrl");
|
|
if (_ctrl == NULL) {
|
|
result = ISC_R_FAILURE;
|
|
goto bad;
|
|
}
|
|
if (isccc_cc_definestring(ack, "_ack", (ok) ? "1" : "0") == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto bad;
|
|
}
|
|
|
|
*ackp = ack;
|
|
|
|
return ISC_R_SUCCESS;
|
|
|
|
bad:
|
|
isccc_sexpr_free(&ack);
|
|
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
isccc_cc_isack(isccc_sexpr_t *message) {
|
|
isccc_sexpr_t *_ctrl;
|
|
|
|
_ctrl = isccc_alist_lookup(message, "_ctrl");
|
|
if (!isccc_alist_alistp(_ctrl)) {
|
|
return false;
|
|
}
|
|
if (isccc_cc_lookupstring(_ctrl, "_ack", NULL) == ISC_R_SUCCESS) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
isccc_cc_isreply(isccc_sexpr_t *message) {
|
|
isccc_sexpr_t *_ctrl;
|
|
|
|
_ctrl = isccc_alist_lookup(message, "_ctrl");
|
|
if (!isccc_alist_alistp(_ctrl)) {
|
|
return false;
|
|
}
|
|
if (isccc_cc_lookupstring(_ctrl, "_rpl", NULL) == ISC_R_SUCCESS) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
isc_result_t
|
|
isccc_cc_createresponse(isccc_sexpr_t *message, isccc_time_t now,
|
|
isccc_time_t expires, isccc_sexpr_t **alistp) {
|
|
char *_frm, *_to, *type = NULL;
|
|
uint32_t serial;
|
|
isccc_sexpr_t *alist, *_ctrl, *_data;
|
|
isc_result_t result;
|
|
|
|
REQUIRE(alistp != NULL && *alistp == NULL);
|
|
|
|
_ctrl = isccc_alist_lookup(message, "_ctrl");
|
|
_data = isccc_alist_lookup(message, "_data");
|
|
if (!isccc_alist_alistp(_ctrl) || !isccc_alist_alistp(_data) ||
|
|
isccc_cc_lookupuint32(_ctrl, "_ser", &serial) != ISC_R_SUCCESS ||
|
|
isccc_cc_lookupstring(_data, "type", &type) != ISC_R_SUCCESS)
|
|
{
|
|
return ISC_R_FAILURE;
|
|
}
|
|
/*
|
|
* _frm and _to are optional.
|
|
*/
|
|
_frm = NULL;
|
|
(void)isccc_cc_lookupstring(_ctrl, "_frm", &_frm);
|
|
_to = NULL;
|
|
(void)isccc_cc_lookupstring(_ctrl, "_to", &_to);
|
|
/*
|
|
* Create the response.
|
|
*/
|
|
alist = NULL;
|
|
result = isccc_cc_createmessage(1, _to, _frm, serial, now, expires,
|
|
&alist);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
_ctrl = isccc_alist_lookup(alist, "_ctrl");
|
|
if (_ctrl == NULL) {
|
|
result = ISC_R_FAILURE;
|
|
goto bad;
|
|
}
|
|
|
|
_data = isccc_alist_lookup(alist, "_data");
|
|
if (_data == NULL) {
|
|
result = ISC_R_FAILURE;
|
|
goto bad;
|
|
}
|
|
|
|
if (isccc_cc_definestring(_ctrl, "_rpl", "1") == NULL ||
|
|
isccc_cc_definestring(_data, "type", type) == NULL)
|
|
{
|
|
result = ISC_R_NOMEMORY;
|
|
goto bad;
|
|
}
|
|
|
|
*alistp = alist;
|
|
|
|
return ISC_R_SUCCESS;
|
|
|
|
bad:
|
|
isccc_sexpr_free(&alist);
|
|
return result;
|
|
}
|
|
|
|
isccc_sexpr_t *
|
|
isccc_cc_definestring(isccc_sexpr_t *alist, const char *key, const char *str) {
|
|
size_t len;
|
|
isccc_region_t r;
|
|
|
|
len = strlen(str);
|
|
r.rstart = UNCONST(str);
|
|
r.rend = r.rstart + len;
|
|
|
|
return isccc_alist_definebinary(alist, key, &r);
|
|
}
|
|
|
|
isccc_sexpr_t *
|
|
isccc_cc_defineuint32(isccc_sexpr_t *alist, const char *key, uint32_t i) {
|
|
char b[100];
|
|
size_t len;
|
|
isccc_region_t r;
|
|
|
|
snprintf(b, sizeof(b), "%u", i);
|
|
len = strlen(b);
|
|
r.rstart = (unsigned char *)b;
|
|
r.rend = (unsigned char *)b + len;
|
|
|
|
return isccc_alist_definebinary(alist, key, &r);
|
|
}
|
|
|
|
isc_result_t
|
|
isccc_cc_lookupstring(isccc_sexpr_t *alist, const char *key, char **strp) {
|
|
isccc_sexpr_t *kv, *v;
|
|
|
|
REQUIRE(strp == NULL || *strp == NULL);
|
|
|
|
kv = isccc_alist_assq(alist, key);
|
|
if (kv != NULL) {
|
|
v = ISCCC_SEXPR_CDR(kv);
|
|
if (isccc_sexpr_binaryp(v)) {
|
|
if (strp != NULL) {
|
|
*strp = isccc_sexpr_tostring(v);
|
|
}
|
|
return ISC_R_SUCCESS;
|
|
} else {
|
|
return ISC_R_EXISTS;
|
|
}
|
|
}
|
|
|
|
return ISC_R_NOTFOUND;
|
|
}
|
|
|
|
isc_result_t
|
|
isccc_cc_lookupuint32(isccc_sexpr_t *alist, const char *key, uint32_t *uintp) {
|
|
isccc_sexpr_t *kv, *v;
|
|
|
|
kv = isccc_alist_assq(alist, key);
|
|
if (kv != NULL) {
|
|
v = ISCCC_SEXPR_CDR(kv);
|
|
if (isccc_sexpr_binaryp(v)) {
|
|
if (uintp != NULL) {
|
|
*uintp = (uint32_t)strtoul(
|
|
isccc_sexpr_tostring(v), NULL, 10);
|
|
}
|
|
return ISC_R_SUCCESS;
|
|
} else {
|
|
return ISC_R_EXISTS;
|
|
}
|
|
}
|
|
|
|
return ISC_R_NOTFOUND;
|
|
}
|
|
|
|
static void
|
|
symtab_undefine(char *key, unsigned int type, isccc_symvalue_t value,
|
|
void *arg) {
|
|
UNUSED(type);
|
|
UNUSED(value);
|
|
UNUSED(arg);
|
|
|
|
free(key);
|
|
}
|
|
|
|
static bool
|
|
symtab_clean(char *key, unsigned int type, isccc_symvalue_t value, void *arg) {
|
|
isccc_time_t *now;
|
|
|
|
UNUSED(key);
|
|
UNUSED(type);
|
|
|
|
now = arg;
|
|
|
|
if (*now < value.as_uinteger) {
|
|
return false;
|
|
}
|
|
if ((*now - value.as_uinteger) < DUP_LIFETIME) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
isc_result_t
|
|
isccc_cc_createsymtab(isccc_symtab_t **symtabp) {
|
|
return isccc_symtab_create(11897, symtab_undefine, NULL, false,
|
|
symtabp);
|
|
}
|
|
|
|
void
|
|
isccc_cc_cleansymtab(isccc_symtab_t *symtab, isccc_time_t now) {
|
|
isccc_symtab_foreach(symtab, symtab_clean, &now);
|
|
}
|
|
|
|
static bool
|
|
has_whitespace(const char *str) {
|
|
char c;
|
|
|
|
if (str == NULL) {
|
|
return false;
|
|
}
|
|
while ((c = *str++) != '\0') {
|
|
if (c == ' ' || c == '\t' || c == '\n') {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
isc_result_t
|
|
isccc_cc_checkdup(isccc_symtab_t *symtab, isccc_sexpr_t *message,
|
|
isccc_time_t now) {
|
|
const char *_frm;
|
|
const char *_to;
|
|
char *_ser = NULL, *_tim = NULL, *tmp;
|
|
isc_result_t result;
|
|
char *key;
|
|
size_t len;
|
|
isccc_symvalue_t value;
|
|
isccc_sexpr_t *_ctrl;
|
|
|
|
_ctrl = isccc_alist_lookup(message, "_ctrl");
|
|
if (!isccc_alist_alistp(_ctrl) ||
|
|
isccc_cc_lookupstring(_ctrl, "_ser", &_ser) != ISC_R_SUCCESS ||
|
|
isccc_cc_lookupstring(_ctrl, "_tim", &_tim) != ISC_R_SUCCESS)
|
|
{
|
|
return ISC_R_FAILURE;
|
|
}
|
|
|
|
INSIST(_ser != NULL);
|
|
INSIST(_tim != NULL);
|
|
|
|
/*
|
|
* _frm and _to are optional.
|
|
*/
|
|
tmp = NULL;
|
|
if (isccc_cc_lookupstring(_ctrl, "_frm", &tmp) != ISC_R_SUCCESS) {
|
|
_frm = "";
|
|
} else {
|
|
_frm = tmp;
|
|
INSIST(_frm != NULL);
|
|
}
|
|
tmp = NULL;
|
|
if (isccc_cc_lookupstring(_ctrl, "_to", &tmp) != ISC_R_SUCCESS) {
|
|
_to = "";
|
|
} else {
|
|
_to = tmp;
|
|
INSIST(_to != NULL);
|
|
}
|
|
/*
|
|
* Ensure there is no newline in any of the strings. This is so
|
|
* we can write them to a file later.
|
|
*/
|
|
if (has_whitespace(_frm) || has_whitespace(_to) ||
|
|
has_whitespace(_ser) || has_whitespace(_tim))
|
|
{
|
|
return ISC_R_FAILURE;
|
|
}
|
|
len = strlen(_frm) + strlen(_to) + strlen(_ser) + strlen(_tim) + 4;
|
|
key = malloc(len);
|
|
if (key == NULL) {
|
|
return ISC_R_NOMEMORY;
|
|
}
|
|
snprintf(key, len, "%s;%s;%s;%s", _frm, _to, _ser, _tim);
|
|
value.as_uinteger = now;
|
|
result = isccc_symtab_define(symtab, key, ISCCC_SYMTYPE_CCDUP, value,
|
|
isccc_symexists_reject);
|
|
if (result != ISC_R_SUCCESS) {
|
|
free(key);
|
|
return result;
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|