mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-21 11:13:30 +00:00
1906 lines
44 KiB
C
1906 lines
44 KiB
C
/*
|
|
* Copyright (C) 2004-2012 Internet Systems Consortium, Inc. ("ISC")
|
|
* Copyright (C) 1998-2003 Internet Software Consortium.
|
|
*
|
|
* 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 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.
|
|
*/
|
|
|
|
/* $Id$ */
|
|
|
|
/*! \file */
|
|
|
|
#include <config.h>
|
|
#include <ctype.h>
|
|
|
|
#include <isc/base64.h>
|
|
#include <isc/hex.h>
|
|
#include <isc/lex.h>
|
|
#include <isc/mem.h>
|
|
#include <isc/parseint.h>
|
|
#include <isc/print.h>
|
|
#include <isc/string.h>
|
|
#include <isc/stdlib.h>
|
|
#include <isc/util.h>
|
|
|
|
#include <dns/callbacks.h>
|
|
#include <dns/cert.h>
|
|
#include <dns/compress.h>
|
|
#include <dns/enumtype.h>
|
|
#include <dns/keyflags.h>
|
|
#include <dns/keyvalues.h>
|
|
#include <dns/message.h>
|
|
#include <dns/rcode.h>
|
|
#include <dns/rdata.h>
|
|
#include <dns/rdataclass.h>
|
|
#include <dns/rdatastruct.h>
|
|
#include <dns/rdatatype.h>
|
|
#include <dns/result.h>
|
|
#include <dns/secalg.h>
|
|
#include <dns/secproto.h>
|
|
#include <dns/time.h>
|
|
#include <dns/ttl.h>
|
|
|
|
#define RETERR(x) \
|
|
do { \
|
|
isc_result_t _r = (x); \
|
|
if (_r != ISC_R_SUCCESS) \
|
|
return (_r); \
|
|
} while (0)
|
|
|
|
#define RETTOK(x) \
|
|
do { \
|
|
isc_result_t _r = (x); \
|
|
if (_r != ISC_R_SUCCESS) { \
|
|
isc_lex_ungettoken(lexer, &token); \
|
|
return (_r); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define DNS_AS_STR(t) ((t).value.as_textregion.base)
|
|
|
|
#define ARGS_FROMTEXT int rdclass, dns_rdatatype_t type, \
|
|
isc_lex_t *lexer, dns_name_t *origin, \
|
|
unsigned int options, isc_buffer_t *target, \
|
|
dns_rdatacallbacks_t *callbacks
|
|
|
|
#define ARGS_TOTEXT dns_rdata_t *rdata, dns_rdata_textctx_t *tctx, \
|
|
isc_buffer_t *target
|
|
|
|
#define ARGS_FROMWIRE int rdclass, dns_rdatatype_t type, \
|
|
isc_buffer_t *source, dns_decompress_t *dctx, \
|
|
unsigned int options, isc_buffer_t *target
|
|
|
|
#define ARGS_TOWIRE dns_rdata_t *rdata, dns_compress_t *cctx, \
|
|
isc_buffer_t *target
|
|
|
|
#define ARGS_COMPARE const dns_rdata_t *rdata1, const dns_rdata_t *rdata2
|
|
|
|
#define ARGS_FROMSTRUCT int rdclass, dns_rdatatype_t type, \
|
|
void *source, isc_buffer_t *target
|
|
|
|
#define ARGS_TOSTRUCT dns_rdata_t *rdata, void *target, isc_mem_t *mctx
|
|
|
|
#define ARGS_FREESTRUCT void *source
|
|
|
|
#define ARGS_ADDLDATA dns_rdata_t *rdata, dns_additionaldatafunc_t add, \
|
|
void *arg
|
|
|
|
#define ARGS_DIGEST dns_rdata_t *rdata, dns_digestfunc_t digest, void *arg
|
|
|
|
#define ARGS_CHECKOWNER dns_name_t *name, dns_rdataclass_t rdclass, \
|
|
dns_rdatatype_t type, isc_boolean_t wildcard
|
|
|
|
#define ARGS_CHECKNAMES dns_rdata_t *rdata, dns_name_t *owner, dns_name_t *bad
|
|
|
|
|
|
/*%
|
|
* Context structure for the totext_ functions.
|
|
* Contains formatting options for rdata-to-text
|
|
* conversion.
|
|
*/
|
|
typedef struct dns_rdata_textctx {
|
|
dns_name_t *origin; /*%< Current origin, or NULL. */
|
|
unsigned int flags; /*%< DNS_STYLEFLAG_* */
|
|
unsigned int width; /*%< Width of rdata column. */
|
|
const char *linebreak; /*%< Line break string. */
|
|
} dns_rdata_textctx_t;
|
|
|
|
static isc_result_t
|
|
txt_totext(isc_region_t *source, isc_buffer_t *target);
|
|
|
|
static isc_result_t
|
|
txt_fromtext(isc_textregion_t *source, isc_buffer_t *target);
|
|
|
|
static isc_result_t
|
|
txt_fromwire(isc_buffer_t *source, isc_buffer_t *target);
|
|
|
|
static isc_boolean_t
|
|
name_prefix(dns_name_t *name, dns_name_t *origin, dns_name_t *target);
|
|
|
|
static unsigned int
|
|
name_length(dns_name_t *name);
|
|
|
|
static isc_result_t
|
|
str_totext(const char *source, isc_buffer_t *target);
|
|
|
|
static isc_result_t
|
|
inet_totext(int af, isc_region_t *src, isc_buffer_t *target);
|
|
|
|
static isc_boolean_t
|
|
buffer_empty(isc_buffer_t *source);
|
|
|
|
static void
|
|
buffer_fromregion(isc_buffer_t *buffer, isc_region_t *region);
|
|
|
|
static isc_result_t
|
|
uint32_tobuffer(isc_uint32_t, isc_buffer_t *target);
|
|
|
|
static isc_result_t
|
|
uint16_tobuffer(isc_uint32_t, isc_buffer_t *target);
|
|
|
|
static isc_result_t
|
|
uint8_tobuffer(isc_uint32_t, isc_buffer_t *target);
|
|
|
|
static isc_result_t
|
|
name_tobuffer(dns_name_t *name, isc_buffer_t *target);
|
|
|
|
static isc_uint32_t
|
|
uint32_fromregion(isc_region_t *region);
|
|
|
|
static isc_uint16_t
|
|
uint16_fromregion(isc_region_t *region);
|
|
|
|
static isc_uint8_t
|
|
uint8_fromregion(isc_region_t *region);
|
|
|
|
static isc_uint8_t
|
|
uint8_consume_fromregion(isc_region_t *region);
|
|
|
|
static isc_result_t
|
|
mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length);
|
|
|
|
static int
|
|
hexvalue(char value);
|
|
|
|
static int
|
|
decvalue(char value);
|
|
|
|
static isc_result_t
|
|
btoa_totext(unsigned char *inbuf, int inbuflen, isc_buffer_t *target);
|
|
|
|
static isc_result_t
|
|
atob_tobuffer(isc_lex_t *lexer, isc_buffer_t *target);
|
|
|
|
static void
|
|
default_fromtext_callback(dns_rdatacallbacks_t *callbacks, const char *, ...)
|
|
ISC_FORMAT_PRINTF(2, 3);
|
|
|
|
static void
|
|
fromtext_error(void (*callback)(dns_rdatacallbacks_t *, const char *, ...),
|
|
dns_rdatacallbacks_t *callbacks, const char *name,
|
|
unsigned long line, isc_token_t *token, isc_result_t result);
|
|
|
|
static void
|
|
fromtext_warneof(isc_lex_t *lexer, dns_rdatacallbacks_t *callbacks);
|
|
|
|
static isc_result_t
|
|
rdata_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx,
|
|
isc_buffer_t *target);
|
|
|
|
static void
|
|
warn_badname(dns_name_t *name, isc_lex_t *lexer,
|
|
dns_rdatacallbacks_t *callbacks);
|
|
|
|
static void
|
|
warn_badmx(isc_token_t *token, isc_lex_t *lexer,
|
|
dns_rdatacallbacks_t *callbacks);
|
|
|
|
static isc_uint16_t
|
|
uint16_consume_fromregion(isc_region_t *region);
|
|
|
|
static isc_result_t
|
|
unknown_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx,
|
|
isc_buffer_t *target);
|
|
|
|
static inline int
|
|
getquad(const void *src, struct in_addr *dst,
|
|
isc_lex_t *lexer, dns_rdatacallbacks_t *callbacks)
|
|
{
|
|
int result;
|
|
struct in_addr *tmp;
|
|
|
|
result = inet_aton(src, dst);
|
|
if (result == 1 && callbacks != NULL &&
|
|
inet_pton(AF_INET, src, &tmp) != 1) {
|
|
const char *name = isc_lex_getsourcename(lexer);
|
|
if (name == NULL)
|
|
name = "UNKNOWN";
|
|
(*callbacks->warn)(callbacks, "%s:%lu: \"%s\" "
|
|
"is not a decimal dotted quad", name,
|
|
isc_lex_getsourceline(lexer), src);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
static inline isc_result_t
|
|
name_duporclone(dns_name_t *source, isc_mem_t *mctx, dns_name_t *target) {
|
|
|
|
if (mctx != NULL)
|
|
return (dns_name_dup(source, mctx, target));
|
|
dns_name_clone(source, target);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static inline void *
|
|
mem_maybedup(isc_mem_t *mctx, void *source, size_t length) {
|
|
void *new;
|
|
|
|
if (mctx == NULL)
|
|
return (source);
|
|
new = isc_mem_allocate(mctx, length);
|
|
if (new != NULL)
|
|
memcpy(new, source, length);
|
|
|
|
return (new);
|
|
}
|
|
|
|
static const char hexdigits[] = "0123456789abcdef";
|
|
static const char decdigits[] = "0123456789";
|
|
|
|
#include "code.h"
|
|
|
|
#define META 0x0001
|
|
#define RESERVED 0x0002
|
|
|
|
/***
|
|
*** Initialization
|
|
***/
|
|
|
|
void
|
|
dns_rdata_init(dns_rdata_t *rdata) {
|
|
|
|
REQUIRE(rdata != NULL);
|
|
|
|
rdata->data = NULL;
|
|
rdata->length = 0;
|
|
rdata->rdclass = 0;
|
|
rdata->type = 0;
|
|
rdata->flags = 0;
|
|
ISC_LINK_INIT(rdata, link);
|
|
/* ISC_LIST_INIT(rdata->list); */
|
|
}
|
|
|
|
void
|
|
dns_rdata_reset(dns_rdata_t *rdata) {
|
|
|
|
REQUIRE(rdata != NULL);
|
|
|
|
REQUIRE(!ISC_LINK_LINKED(rdata, link));
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
|
|
|
|
rdata->data = NULL;
|
|
rdata->length = 0;
|
|
rdata->rdclass = 0;
|
|
rdata->type = 0;
|
|
rdata->flags = 0;
|
|
}
|
|
|
|
/***
|
|
***
|
|
***/
|
|
|
|
void
|
|
dns_rdata_clone(const dns_rdata_t *src, dns_rdata_t *target) {
|
|
|
|
REQUIRE(src != NULL);
|
|
REQUIRE(target != NULL);
|
|
|
|
REQUIRE(DNS_RDATA_INITIALIZED(target));
|
|
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(src));
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(target));
|
|
|
|
target->data = src->data;
|
|
target->length = src->length;
|
|
target->rdclass = src->rdclass;
|
|
target->type = src->type;
|
|
target->flags = src->flags;
|
|
}
|
|
|
|
|
|
/***
|
|
*** Comparisons
|
|
***/
|
|
|
|
int
|
|
dns_rdata_compare(const dns_rdata_t *rdata1, const dns_rdata_t *rdata2) {
|
|
int result = 0;
|
|
isc_boolean_t use_default = ISC_FALSE;
|
|
|
|
REQUIRE(rdata1 != NULL);
|
|
REQUIRE(rdata2 != NULL);
|
|
REQUIRE(rdata1->data != NULL);
|
|
REQUIRE(rdata2->data != NULL);
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata1));
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata2));
|
|
|
|
if (rdata1->rdclass != rdata2->rdclass)
|
|
return (rdata1->rdclass < rdata2->rdclass ? -1 : 1);
|
|
|
|
if (rdata1->type != rdata2->type)
|
|
return (rdata1->type < rdata2->type ? -1 : 1);
|
|
|
|
COMPARESWITCH
|
|
|
|
if (use_default) {
|
|
isc_region_t r1;
|
|
isc_region_t r2;
|
|
|
|
dns_rdata_toregion(rdata1, &r1);
|
|
dns_rdata_toregion(rdata2, &r2);
|
|
result = isc_region_compare(&r1, &r2);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
int
|
|
dns_rdata_casecompare(const dns_rdata_t *rdata1, const dns_rdata_t *rdata2) {
|
|
int result = 0;
|
|
isc_boolean_t use_default = ISC_FALSE;
|
|
|
|
REQUIRE(rdata1 != NULL);
|
|
REQUIRE(rdata2 != NULL);
|
|
REQUIRE(rdata1->data != NULL);
|
|
REQUIRE(rdata2->data != NULL);
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata1));
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata2));
|
|
|
|
if (rdata1->rdclass != rdata2->rdclass)
|
|
return (rdata1->rdclass < rdata2->rdclass ? -1 : 1);
|
|
|
|
if (rdata1->type != rdata2->type)
|
|
return (rdata1->type < rdata2->type ? -1 : 1);
|
|
|
|
CASECOMPARESWITCH
|
|
|
|
if (use_default) {
|
|
isc_region_t r1;
|
|
isc_region_t r2;
|
|
|
|
dns_rdata_toregion(rdata1, &r1);
|
|
dns_rdata_toregion(rdata2, &r2);
|
|
result = isc_region_compare(&r1, &r2);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/***
|
|
*** Conversions
|
|
***/
|
|
|
|
void
|
|
dns_rdata_fromregion(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
|
|
dns_rdatatype_t type, isc_region_t *r)
|
|
{
|
|
|
|
REQUIRE(rdata != NULL);
|
|
REQUIRE(DNS_RDATA_INITIALIZED(rdata));
|
|
REQUIRE(r != NULL);
|
|
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
|
|
|
|
rdata->data = r->base;
|
|
rdata->length = r->length;
|
|
rdata->rdclass = rdclass;
|
|
rdata->type = type;
|
|
rdata->flags = 0;
|
|
}
|
|
|
|
void
|
|
dns_rdata_toregion(const dns_rdata_t *rdata, isc_region_t *r) {
|
|
|
|
REQUIRE(rdata != NULL);
|
|
REQUIRE(r != NULL);
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
|
|
|
|
r->base = rdata->data;
|
|
r->length = rdata->length;
|
|
}
|
|
|
|
isc_result_t
|
|
dns_rdata_fromwire(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
|
|
dns_rdatatype_t type, isc_buffer_t *source,
|
|
dns_decompress_t *dctx, unsigned int options,
|
|
isc_buffer_t *target)
|
|
{
|
|
isc_result_t result = ISC_R_NOTIMPLEMENTED;
|
|
isc_region_t region;
|
|
isc_buffer_t ss;
|
|
isc_buffer_t st;
|
|
isc_boolean_t use_default = ISC_FALSE;
|
|
isc_uint32_t activelength;
|
|
|
|
REQUIRE(dctx != NULL);
|
|
if (rdata != NULL) {
|
|
REQUIRE(DNS_RDATA_INITIALIZED(rdata));
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
|
|
}
|
|
|
|
if (type == 0)
|
|
return (DNS_R_FORMERR);
|
|
|
|
ss = *source;
|
|
st = *target;
|
|
|
|
activelength = isc_buffer_activelength(source);
|
|
INSIST(activelength < 65536);
|
|
|
|
FROMWIRESWITCH
|
|
|
|
if (use_default) {
|
|
if (activelength > isc_buffer_availablelength(target))
|
|
result = ISC_R_NOSPACE;
|
|
else {
|
|
isc_buffer_putmem(target, isc_buffer_current(source),
|
|
activelength);
|
|
isc_buffer_forward(source, activelength);
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We should have consumed all of our buffer.
|
|
*/
|
|
if (result == ISC_R_SUCCESS && !buffer_empty(source))
|
|
result = DNS_R_EXTRADATA;
|
|
|
|
if (rdata != NULL && result == ISC_R_SUCCESS) {
|
|
region.base = isc_buffer_used(&st);
|
|
region.length = isc_buffer_usedlength(target) -
|
|
isc_buffer_usedlength(&st);
|
|
dns_rdata_fromregion(rdata, rdclass, type, ®ion);
|
|
}
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
*source = ss;
|
|
*target = st;
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_rdata_towire(dns_rdata_t *rdata, dns_compress_t *cctx,
|
|
isc_buffer_t *target)
|
|
{
|
|
isc_result_t result = ISC_R_NOTIMPLEMENTED;
|
|
isc_boolean_t use_default = ISC_FALSE;
|
|
isc_region_t tr;
|
|
isc_buffer_t st;
|
|
|
|
REQUIRE(rdata != NULL);
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
|
|
|
|
/*
|
|
* Some DynDNS meta-RRs have empty rdata.
|
|
*/
|
|
if ((rdata->flags & DNS_RDATA_UPDATE) != 0) {
|
|
INSIST(rdata->length == 0);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
st = *target;
|
|
|
|
TOWIRESWITCH
|
|
|
|
if (use_default) {
|
|
isc_buffer_availableregion(target, &tr);
|
|
if (tr.length < rdata->length)
|
|
return (ISC_R_NOSPACE);
|
|
memcpy(tr.base, rdata->data, rdata->length);
|
|
isc_buffer_add(target, rdata->length);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
*target = st;
|
|
INSIST(target->used < 65536);
|
|
dns_compress_rollback(cctx, (isc_uint16_t)target->used);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* If the binary data in 'src' is valid uncompressed wire format
|
|
* rdata of class 'rdclass' and type 'type', return ISC_R_SUCCESS
|
|
* and copy the validated rdata to 'dest'. Otherwise return an error.
|
|
*/
|
|
static isc_result_t
|
|
rdata_validate(isc_buffer_t *src, isc_buffer_t *dest, dns_rdataclass_t rdclass,
|
|
dns_rdatatype_t type)
|
|
{
|
|
dns_decompress_t dctx;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
isc_result_t result;
|
|
|
|
dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE);
|
|
isc_buffer_setactive(src, isc_buffer_usedlength(src));
|
|
result = dns_rdata_fromwire(&rdata, rdclass, type, src,
|
|
&dctx, 0, dest);
|
|
dns_decompress_invalidate(&dctx);
|
|
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
unknown_fromtext(dns_rdataclass_t rdclass, dns_rdatatype_t type,
|
|
isc_lex_t *lexer, isc_mem_t *mctx, isc_buffer_t *target)
|
|
{
|
|
isc_result_t result;
|
|
isc_buffer_t *buf = NULL;
|
|
isc_token_t token;
|
|
|
|
if (type == 0 || dns_rdatatype_ismeta(type))
|
|
return (DNS_R_METATYPE);
|
|
|
|
result = isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
|
|
ISC_FALSE);
|
|
if (result == ISC_R_SUCCESS && token.value.as_ulong > 65535U)
|
|
return (ISC_R_RANGE);
|
|
result = isc_buffer_allocate(mctx, &buf, token.value.as_ulong);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
|
|
result = isc_hex_tobuffer(lexer, buf,
|
|
(unsigned int)token.value.as_ulong);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto failure;
|
|
if (isc_buffer_usedlength(buf) != token.value.as_ulong) {
|
|
result = ISC_R_UNEXPECTEDEND;
|
|
goto failure;
|
|
}
|
|
|
|
if (dns_rdatatype_isknown(type)) {
|
|
result = rdata_validate(buf, target, rdclass, type);
|
|
} else {
|
|
isc_region_t r;
|
|
isc_buffer_usedregion(buf, &r);
|
|
result = isc_buffer_copyregion(target, &r);
|
|
}
|
|
if (result != ISC_R_SUCCESS)
|
|
goto failure;
|
|
|
|
isc_buffer_free(&buf);
|
|
return (ISC_R_SUCCESS);
|
|
|
|
failure:
|
|
isc_buffer_free(&buf);
|
|
return (result);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_rdata_fromtext(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
|
|
dns_rdatatype_t type, isc_lex_t *lexer,
|
|
dns_name_t *origin, unsigned int options, isc_mem_t *mctx,
|
|
isc_buffer_t *target, dns_rdatacallbacks_t *callbacks)
|
|
{
|
|
isc_result_t result = ISC_R_NOTIMPLEMENTED;
|
|
isc_region_t region;
|
|
isc_buffer_t st;
|
|
isc_token_t token;
|
|
unsigned int lexoptions = ISC_LEXOPT_EOL | ISC_LEXOPT_EOF |
|
|
ISC_LEXOPT_DNSMULTILINE | ISC_LEXOPT_ESCAPE;
|
|
char *name;
|
|
unsigned long line;
|
|
void (*callback)(dns_rdatacallbacks_t *, const char *, ...);
|
|
isc_result_t tresult;
|
|
|
|
REQUIRE(origin == NULL || dns_name_isabsolute(origin) == ISC_TRUE);
|
|
if (rdata != NULL) {
|
|
REQUIRE(DNS_RDATA_INITIALIZED(rdata));
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
|
|
}
|
|
if (callbacks != NULL) {
|
|
REQUIRE(callbacks->warn != NULL);
|
|
REQUIRE(callbacks->error != NULL);
|
|
}
|
|
|
|
st = *target;
|
|
|
|
if (callbacks != NULL)
|
|
callback = callbacks->error;
|
|
else
|
|
callback = default_fromtext_callback;
|
|
|
|
result = isc_lex_getmastertoken(lexer, &token, isc_tokentype_qstring,
|
|
ISC_FALSE);
|
|
if (result != ISC_R_SUCCESS) {
|
|
name = isc_lex_getsourcename(lexer);
|
|
line = isc_lex_getsourceline(lexer);
|
|
fromtext_error(callback, callbacks, name, line, NULL, result);
|
|
return (result);
|
|
}
|
|
|
|
if (strcmp(DNS_AS_STR(token), "\\#") == 0)
|
|
result = unknown_fromtext(rdclass, type, lexer, mctx, target);
|
|
else {
|
|
isc_lex_ungettoken(lexer, &token);
|
|
|
|
FROMTEXTSWITCH
|
|
}
|
|
|
|
/*
|
|
* Consume to end of line / file.
|
|
* If not at end of line initially set error code.
|
|
* Call callback via fromtext_error once if there was an error.
|
|
*/
|
|
do {
|
|
name = isc_lex_getsourcename(lexer);
|
|
line = isc_lex_getsourceline(lexer);
|
|
tresult = isc_lex_gettoken(lexer, lexoptions, &token);
|
|
if (tresult != ISC_R_SUCCESS) {
|
|
if (result == ISC_R_SUCCESS)
|
|
result = tresult;
|
|
if (callback != NULL)
|
|
fromtext_error(callback, callbacks, name,
|
|
line, NULL, result);
|
|
break;
|
|
} else if (token.type != isc_tokentype_eol &&
|
|
token.type != isc_tokentype_eof) {
|
|
if (result == ISC_R_SUCCESS)
|
|
result = DNS_R_EXTRATOKEN;
|
|
if (callback != NULL) {
|
|
fromtext_error(callback, callbacks, name,
|
|
line, &token, result);
|
|
callback = NULL;
|
|
}
|
|
} else if (result != ISC_R_SUCCESS && callback != NULL) {
|
|
fromtext_error(callback, callbacks, name, line,
|
|
&token, result);
|
|
break;
|
|
} else {
|
|
if (token.type == isc_tokentype_eof)
|
|
fromtext_warneof(lexer, callbacks);
|
|
break;
|
|
}
|
|
} while (1);
|
|
|
|
if (rdata != NULL && result == ISC_R_SUCCESS) {
|
|
region.base = isc_buffer_used(&st);
|
|
region.length = isc_buffer_usedlength(target) -
|
|
isc_buffer_usedlength(&st);
|
|
dns_rdata_fromregion(rdata, rdclass, type, ®ion);
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
*target = st;
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
unknown_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx,
|
|
isc_buffer_t *target)
|
|
{
|
|
isc_result_t result;
|
|
char buf[sizeof("65535")];
|
|
isc_region_t sr;
|
|
|
|
strlcpy(buf, "\\# ", sizeof(buf));
|
|
result = str_totext(buf, target);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
|
|
dns_rdata_toregion(rdata, &sr);
|
|
INSIST(sr.length < 65536);
|
|
snprintf(buf, sizeof(buf), "%u", sr.length);
|
|
result = str_totext(buf, target);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
|
|
if (sr.length != 0U) {
|
|
if ((tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0)
|
|
result = str_totext(" ( ", target);
|
|
else
|
|
result = str_totext(" ", target);
|
|
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
|
|
if (tctx->width == 0) /* No splitting */
|
|
result = isc_hex_totext(&sr, 0, "", target);
|
|
else
|
|
result = isc_hex_totext(&sr, tctx->width - 2,
|
|
tctx->linebreak,
|
|
target);
|
|
if (result == ISC_R_SUCCESS &&
|
|
(tctx->flags & DNS_STYLEFLAG_MULTILINE) != 0)
|
|
result = str_totext(" )", target);
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
rdata_totext(dns_rdata_t *rdata, dns_rdata_textctx_t *tctx,
|
|
isc_buffer_t *target)
|
|
{
|
|
isc_result_t result = ISC_R_NOTIMPLEMENTED;
|
|
isc_boolean_t use_default = ISC_FALSE;
|
|
|
|
REQUIRE(rdata != NULL);
|
|
REQUIRE(tctx->origin == NULL ||
|
|
dns_name_isabsolute(tctx->origin) == ISC_TRUE);
|
|
|
|
/*
|
|
* Some DynDNS meta-RRs have empty rdata.
|
|
*/
|
|
if ((rdata->flags & DNS_RDATA_UPDATE) != 0) {
|
|
INSIST(rdata->length == 0);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
TOTEXTSWITCH
|
|
|
|
if (use_default)
|
|
result = unknown_totext(rdata, tctx, target);
|
|
|
|
return (result);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_rdata_totext(dns_rdata_t *rdata, dns_name_t *origin, isc_buffer_t *target)
|
|
{
|
|
dns_rdata_textctx_t tctx;
|
|
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
|
|
|
|
/*
|
|
* Set up formatting options for single-line output.
|
|
*/
|
|
tctx.origin = origin;
|
|
tctx.flags = 0;
|
|
tctx.width = 60;
|
|
tctx.linebreak = " ";
|
|
return (rdata_totext(rdata, &tctx, target));
|
|
}
|
|
|
|
isc_result_t
|
|
dns_rdata_tofmttext(dns_rdata_t *rdata, dns_name_t *origin,
|
|
unsigned int flags, unsigned int width,
|
|
const char *linebreak, isc_buffer_t *target)
|
|
{
|
|
dns_rdata_textctx_t tctx;
|
|
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
|
|
|
|
/*
|
|
* Set up formatting options for formatted output.
|
|
*/
|
|
tctx.origin = origin;
|
|
tctx.flags = flags;
|
|
if ((flags & DNS_STYLEFLAG_MULTILINE) != 0) {
|
|
tctx.width = width;
|
|
tctx.linebreak = linebreak;
|
|
} else {
|
|
tctx.width = 60; /* Used for hex word length only. */
|
|
tctx.linebreak = " ";
|
|
}
|
|
return (rdata_totext(rdata, &tctx, target));
|
|
}
|
|
|
|
isc_result_t
|
|
dns_rdata_fromstruct(dns_rdata_t *rdata, dns_rdataclass_t rdclass,
|
|
dns_rdatatype_t type, void *source,
|
|
isc_buffer_t *target)
|
|
{
|
|
isc_result_t result = ISC_R_NOTIMPLEMENTED;
|
|
isc_buffer_t st;
|
|
isc_region_t region;
|
|
isc_boolean_t use_default = ISC_FALSE;
|
|
|
|
REQUIRE(source != NULL);
|
|
if (rdata != NULL) {
|
|
REQUIRE(DNS_RDATA_INITIALIZED(rdata));
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
|
|
}
|
|
|
|
st = *target;
|
|
|
|
FROMSTRUCTSWITCH
|
|
|
|
if (use_default)
|
|
(void)NULL;
|
|
|
|
if (rdata != NULL && result == ISC_R_SUCCESS) {
|
|
region.base = isc_buffer_used(&st);
|
|
region.length = isc_buffer_usedlength(target) -
|
|
isc_buffer_usedlength(&st);
|
|
dns_rdata_fromregion(rdata, rdclass, type, ®ion);
|
|
}
|
|
if (result != ISC_R_SUCCESS)
|
|
*target = st;
|
|
return (result);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_rdata_tostruct(dns_rdata_t *rdata, void *target, isc_mem_t *mctx) {
|
|
isc_result_t result = ISC_R_NOTIMPLEMENTED;
|
|
isc_boolean_t use_default = ISC_FALSE;
|
|
|
|
REQUIRE(rdata != NULL);
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
|
|
|
|
TOSTRUCTSWITCH
|
|
|
|
if (use_default)
|
|
(void)NULL;
|
|
|
|
return (result);
|
|
}
|
|
|
|
void
|
|
dns_rdata_freestruct(void *source) {
|
|
dns_rdatacommon_t *common = source;
|
|
REQUIRE(source != NULL);
|
|
|
|
FREESTRUCTSWITCH
|
|
}
|
|
|
|
isc_result_t
|
|
dns_rdata_additionaldata(dns_rdata_t *rdata, dns_additionaldatafunc_t add,
|
|
void *arg)
|
|
{
|
|
isc_result_t result = ISC_R_NOTIMPLEMENTED;
|
|
isc_boolean_t use_default = ISC_FALSE;
|
|
|
|
/*
|
|
* Call 'add' for each name and type from 'rdata' which is subject to
|
|
* additional section processing.
|
|
*/
|
|
|
|
REQUIRE(rdata != NULL);
|
|
REQUIRE(add != NULL);
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
|
|
|
|
ADDITIONALDATASWITCH
|
|
|
|
/* No additional processing for unknown types */
|
|
if (use_default)
|
|
result = ISC_R_SUCCESS;
|
|
|
|
return (result);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_rdata_digest(dns_rdata_t *rdata, dns_digestfunc_t digest, void *arg) {
|
|
isc_result_t result = ISC_R_NOTIMPLEMENTED;
|
|
isc_boolean_t use_default = ISC_FALSE;
|
|
isc_region_t r;
|
|
|
|
/*
|
|
* Send 'rdata' in DNSSEC canonical form to 'digest'.
|
|
*/
|
|
|
|
REQUIRE(rdata != NULL);
|
|
REQUIRE(digest != NULL);
|
|
REQUIRE(DNS_RDATA_VALIDFLAGS(rdata));
|
|
|
|
DIGESTSWITCH
|
|
|
|
if (use_default) {
|
|
dns_rdata_toregion(rdata, &r);
|
|
result = (digest)(arg, &r);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_rdata_checkowner(dns_name_t *name, dns_rdataclass_t rdclass,
|
|
dns_rdatatype_t type, isc_boolean_t wildcard)
|
|
{
|
|
isc_boolean_t result;
|
|
|
|
CHECKOWNERSWITCH
|
|
return (result);
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_rdata_checknames(dns_rdata_t *rdata, dns_name_t *owner, dns_name_t *bad)
|
|
{
|
|
isc_boolean_t result;
|
|
|
|
CHECKNAMESSWITCH
|
|
return (result);
|
|
}
|
|
|
|
unsigned int
|
|
dns_rdatatype_attributes(dns_rdatatype_t type)
|
|
{
|
|
RDATATYPE_ATTRIBUTE_SW
|
|
if (type >= (dns_rdatatype_t)128 && type < (dns_rdatatype_t)255)
|
|
return (DNS_RDATATYPEATTR_UNKNOWN | DNS_RDATATYPEATTR_META);
|
|
return (DNS_RDATATYPEATTR_UNKNOWN);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_rdatatype_fromtext(dns_rdatatype_t *typep, isc_textregion_t *source) {
|
|
unsigned int hash;
|
|
unsigned int n;
|
|
unsigned char a, b;
|
|
|
|
n = source->length;
|
|
|
|
if (n == 0)
|
|
return (DNS_R_UNKNOWN);
|
|
|
|
a = tolower((unsigned char)source->base[0]);
|
|
b = tolower((unsigned char)source->base[n - 1]);
|
|
|
|
hash = ((a + n) * b) % 256;
|
|
|
|
/*
|
|
* This switch block is inlined via \#define, and will use "return"
|
|
* to return a result to the caller if it is a valid (known)
|
|
* rdatatype name.
|
|
*/
|
|
RDATATYPE_FROMTEXT_SW(hash, source->base, n, typep);
|
|
|
|
if (source->length > 4 && source->length < (4 + sizeof("65000")) &&
|
|
strncasecmp("type", source->base, 4) == 0) {
|
|
char buf[sizeof("65000")];
|
|
char *endp;
|
|
unsigned int val;
|
|
|
|
strncpy(buf, source->base + 4, source->length - 4);
|
|
buf[source->length - 4] = '\0';
|
|
val = strtoul(buf, &endp, 10);
|
|
if (*endp == '\0' && val <= 0xffff) {
|
|
*typep = (dns_rdatatype_t)val;
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
}
|
|
|
|
return (DNS_R_UNKNOWN);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_rdatatype_totext(dns_rdatatype_t type, isc_buffer_t *target) {
|
|
char buf[sizeof("TYPE65535")];
|
|
|
|
RDATATYPE_TOTEXT_SW
|
|
snprintf(buf, sizeof(buf), "TYPE%u", type);
|
|
return (str_totext(buf, target));
|
|
}
|
|
|
|
void
|
|
dns_rdatatype_format(dns_rdatatype_t rdtype,
|
|
char *array, unsigned int size)
|
|
{
|
|
isc_result_t result;
|
|
isc_buffer_t buf;
|
|
|
|
if (size == 0U)
|
|
return;
|
|
|
|
isc_buffer_init(&buf, array, size);
|
|
result = dns_rdatatype_totext(rdtype, &buf);
|
|
/*
|
|
* Null terminate.
|
|
*/
|
|
if (result == ISC_R_SUCCESS) {
|
|
if (isc_buffer_availablelength(&buf) >= 1)
|
|
isc_buffer_putuint8(&buf, 0);
|
|
else
|
|
result = ISC_R_NOSPACE;
|
|
}
|
|
if (result != ISC_R_SUCCESS)
|
|
strlcpy(array, "<unknown>", size);
|
|
}
|
|
|
|
/*
|
|
* Private function.
|
|
*/
|
|
|
|
static unsigned int
|
|
name_length(dns_name_t *name) {
|
|
return (name->length);
|
|
}
|
|
|
|
static isc_result_t
|
|
txt_totext(isc_region_t *source, isc_buffer_t *target) {
|
|
unsigned int tl;
|
|
unsigned int n;
|
|
unsigned char *sp;
|
|
char *tp;
|
|
isc_region_t region;
|
|
|
|
isc_buffer_availableregion(target, ®ion);
|
|
sp = source->base;
|
|
tp = (char *)region.base;
|
|
tl = region.length;
|
|
|
|
n = *sp++;
|
|
|
|
REQUIRE(n + 1 <= source->length);
|
|
|
|
if (tl < 1)
|
|
return (ISC_R_NOSPACE);
|
|
*tp++ = '"';
|
|
tl--;
|
|
while (n--) {
|
|
if (*sp < 0x20 || *sp >= 0x7f) {
|
|
if (tl < 4)
|
|
return (ISC_R_NOSPACE);
|
|
*tp++ = 0x5c;
|
|
*tp++ = 0x30 + ((*sp / 100) % 10);
|
|
*tp++ = 0x30 + ((*sp / 10) % 10);
|
|
*tp++ = 0x30 + (*sp % 10);
|
|
sp++;
|
|
tl -= 4;
|
|
continue;
|
|
}
|
|
/* double quote, semi-colon, backslash */
|
|
if (*sp == 0x22 || *sp == 0x3b || *sp == 0x5c) {
|
|
if (tl < 2)
|
|
return (ISC_R_NOSPACE);
|
|
*tp++ = '\\';
|
|
tl--;
|
|
}
|
|
if (tl < 1)
|
|
return (ISC_R_NOSPACE);
|
|
*tp++ = *sp++;
|
|
tl--;
|
|
}
|
|
if (tl < 1)
|
|
return (ISC_R_NOSPACE);
|
|
*tp++ = '"';
|
|
tl--;
|
|
isc_buffer_add(target, tp - (char *)region.base);
|
|
isc_region_consume(source, *source->base + 1);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static isc_result_t
|
|
txt_fromtext(isc_textregion_t *source, isc_buffer_t *target) {
|
|
isc_region_t tregion;
|
|
isc_boolean_t escape;
|
|
unsigned int n, nrem;
|
|
char *s;
|
|
unsigned char *t;
|
|
int d;
|
|
int c;
|
|
|
|
isc_buffer_availableregion(target, &tregion);
|
|
s = source->base;
|
|
n = source->length;
|
|
t = tregion.base;
|
|
nrem = tregion.length;
|
|
escape = ISC_FALSE;
|
|
if (nrem < 1)
|
|
return (ISC_R_NOSPACE);
|
|
/*
|
|
* Length byte.
|
|
*/
|
|
nrem--;
|
|
t++;
|
|
/*
|
|
* Maximum text string length.
|
|
*/
|
|
if (nrem > 255)
|
|
nrem = 255;
|
|
while (n-- != 0) {
|
|
c = (*s++) & 0xff;
|
|
if (escape && (d = decvalue((char)c)) != -1) {
|
|
c = d;
|
|
if (n == 0)
|
|
return (DNS_R_SYNTAX);
|
|
n--;
|
|
if ((d = decvalue(*s++)) != -1)
|
|
c = c * 10 + d;
|
|
else
|
|
return (DNS_R_SYNTAX);
|
|
if (n == 0)
|
|
return (DNS_R_SYNTAX);
|
|
n--;
|
|
if ((d = decvalue(*s++)) != -1)
|
|
c = c * 10 + d;
|
|
else
|
|
return (DNS_R_SYNTAX);
|
|
if (c > 255)
|
|
return (DNS_R_SYNTAX);
|
|
} else if (!escape && c == '\\') {
|
|
escape = ISC_TRUE;
|
|
continue;
|
|
}
|
|
escape = ISC_FALSE;
|
|
if (nrem == 0)
|
|
return ((tregion.length <= 256U) ?
|
|
ISC_R_NOSPACE : DNS_R_SYNTAX);
|
|
*t++ = c;
|
|
nrem--;
|
|
}
|
|
if (escape)
|
|
return (DNS_R_SYNTAX);
|
|
*tregion.base = t - tregion.base - 1;
|
|
isc_buffer_add(target, *tregion.base + 1);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static isc_result_t
|
|
txt_fromwire(isc_buffer_t *source, isc_buffer_t *target) {
|
|
unsigned int n;
|
|
isc_region_t sregion;
|
|
isc_region_t tregion;
|
|
|
|
isc_buffer_activeregion(source, &sregion);
|
|
if (sregion.length == 0)
|
|
return(ISC_R_UNEXPECTEDEND);
|
|
n = *sregion.base + 1;
|
|
if (n > sregion.length)
|
|
return (ISC_R_UNEXPECTEDEND);
|
|
|
|
isc_buffer_availableregion(target, &tregion);
|
|
if (n > tregion.length)
|
|
return (ISC_R_NOSPACE);
|
|
|
|
memcpy(tregion.base, sregion.base, n);
|
|
isc_buffer_forward(source, n);
|
|
isc_buffer_add(target, n);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static isc_boolean_t
|
|
name_prefix(dns_name_t *name, dns_name_t *origin, dns_name_t *target) {
|
|
int l1, l2;
|
|
|
|
if (origin == NULL)
|
|
goto return_false;
|
|
|
|
if (dns_name_compare(origin, dns_rootname) == 0)
|
|
goto return_false;
|
|
|
|
if (!dns_name_issubdomain(name, origin))
|
|
goto return_false;
|
|
|
|
l1 = dns_name_countlabels(name);
|
|
l2 = dns_name_countlabels(origin);
|
|
|
|
if (l1 == l2)
|
|
goto return_false;
|
|
|
|
/* Master files should be case preserving. */
|
|
dns_name_getlabelsequence(name, l1 - l2, l2, target);
|
|
if (!dns_name_caseequal(origin, target))
|
|
goto return_false;
|
|
|
|
dns_name_getlabelsequence(name, 0, l1 - l2, target);
|
|
return (ISC_TRUE);
|
|
|
|
return_false:
|
|
*target = *name;
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
static isc_result_t
|
|
str_totext(const char *source, isc_buffer_t *target) {
|
|
unsigned int l;
|
|
isc_region_t region;
|
|
|
|
isc_buffer_availableregion(target, ®ion);
|
|
l = strlen(source);
|
|
|
|
if (l > region.length)
|
|
return (ISC_R_NOSPACE);
|
|
|
|
memcpy(region.base, source, l);
|
|
isc_buffer_add(target, l);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static isc_result_t
|
|
inet_totext(int af, isc_region_t *src, isc_buffer_t *target) {
|
|
char tmpbuf[64];
|
|
|
|
/* Note - inet_ntop doesn't do size checking on its input. */
|
|
if (inet_ntop(af, src->base, tmpbuf, sizeof(tmpbuf)) == NULL)
|
|
return (ISC_R_NOSPACE);
|
|
if (strlen(tmpbuf) > isc_buffer_availablelength(target))
|
|
return (ISC_R_NOSPACE);
|
|
isc_buffer_putstr(target, tmpbuf);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static isc_boolean_t
|
|
buffer_empty(isc_buffer_t *source) {
|
|
return((source->current == source->active) ? ISC_TRUE : ISC_FALSE);
|
|
}
|
|
|
|
static void
|
|
buffer_fromregion(isc_buffer_t *buffer, isc_region_t *region) {
|
|
isc_buffer_init(buffer, region->base, region->length);
|
|
isc_buffer_add(buffer, region->length);
|
|
isc_buffer_setactive(buffer, region->length);
|
|
}
|
|
|
|
static isc_result_t
|
|
uint32_tobuffer(isc_uint32_t value, isc_buffer_t *target) {
|
|
isc_region_t region;
|
|
|
|
isc_buffer_availableregion(target, ®ion);
|
|
if (region.length < 4)
|
|
return (ISC_R_NOSPACE);
|
|
isc_buffer_putuint32(target, value);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static isc_result_t
|
|
uint16_tobuffer(isc_uint32_t value, isc_buffer_t *target) {
|
|
isc_region_t region;
|
|
|
|
if (value > 0xffff)
|
|
return (ISC_R_RANGE);
|
|
isc_buffer_availableregion(target, ®ion);
|
|
if (region.length < 2)
|
|
return (ISC_R_NOSPACE);
|
|
isc_buffer_putuint16(target, (isc_uint16_t)value);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static isc_result_t
|
|
uint8_tobuffer(isc_uint32_t value, isc_buffer_t *target) {
|
|
isc_region_t region;
|
|
|
|
if (value > 0xff)
|
|
return (ISC_R_RANGE);
|
|
isc_buffer_availableregion(target, ®ion);
|
|
if (region.length < 1)
|
|
return (ISC_R_NOSPACE);
|
|
isc_buffer_putuint8(target, (isc_uint8_t)value);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static isc_result_t
|
|
name_tobuffer(dns_name_t *name, isc_buffer_t *target) {
|
|
isc_region_t r;
|
|
dns_name_toregion(name, &r);
|
|
return (isc_buffer_copyregion(target, &r));
|
|
}
|
|
|
|
static isc_uint32_t
|
|
uint32_fromregion(isc_region_t *region) {
|
|
isc_uint32_t value;
|
|
|
|
REQUIRE(region->length >= 4);
|
|
value = region->base[0] << 24;
|
|
value |= region->base[1] << 16;
|
|
value |= region->base[2] << 8;
|
|
value |= region->base[3];
|
|
return(value);
|
|
}
|
|
|
|
static isc_uint16_t
|
|
uint16_consume_fromregion(isc_region_t *region) {
|
|
isc_uint16_t r = uint16_fromregion(region);
|
|
|
|
isc_region_consume(region, 2);
|
|
return r;
|
|
}
|
|
|
|
static isc_uint16_t
|
|
uint16_fromregion(isc_region_t *region) {
|
|
|
|
REQUIRE(region->length >= 2);
|
|
|
|
return ((region->base[0] << 8) | region->base[1]);
|
|
}
|
|
|
|
static isc_uint8_t
|
|
uint8_fromregion(isc_region_t *region) {
|
|
|
|
REQUIRE(region->length >= 1);
|
|
|
|
return (region->base[0]);
|
|
}
|
|
|
|
static isc_uint8_t
|
|
uint8_consume_fromregion(isc_region_t *region) {
|
|
isc_uint8_t r = uint8_fromregion(region);
|
|
|
|
isc_region_consume(region, 1);
|
|
return r;
|
|
}
|
|
|
|
static isc_result_t
|
|
mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length) {
|
|
isc_region_t tr;
|
|
|
|
isc_buffer_availableregion(target, &tr);
|
|
if (length > tr.length)
|
|
return (ISC_R_NOSPACE);
|
|
memcpy(tr.base, base, length);
|
|
isc_buffer_add(target, length);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static int
|
|
hexvalue(char value) {
|
|
char *s;
|
|
unsigned char c;
|
|
|
|
c = (unsigned char)value;
|
|
|
|
if (!isascii(c))
|
|
return (-1);
|
|
if (isupper(c))
|
|
c = tolower(c);
|
|
if ((s = strchr(hexdigits, c)) == NULL)
|
|
return (-1);
|
|
return (s - hexdigits);
|
|
}
|
|
|
|
static int
|
|
decvalue(char value) {
|
|
char *s;
|
|
|
|
/*
|
|
* isascii() is valid for full range of int values, no need to
|
|
* mask or cast.
|
|
*/
|
|
if (!isascii(value))
|
|
return (-1);
|
|
if ((s = strchr(decdigits, value)) == NULL)
|
|
return (-1);
|
|
return (s - decdigits);
|
|
}
|
|
|
|
static const char atob_digits[86] =
|
|
"!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" \
|
|
"abcdefghijklmnopqrstu";
|
|
/*
|
|
* Subroutines to convert between 8 bit binary bytes and printable ASCII.
|
|
* Computes the number of bytes, and three kinds of simple checksums.
|
|
* Incoming bytes are collected into 32-bit words, then printed in base 85:
|
|
* exp(85,5) > exp(2,32)
|
|
* The ASCII characters used are between '!' and 'u';
|
|
* 'z' encodes 32-bit zero; 'x' is used to mark the end of encoded data.
|
|
*
|
|
* Originally by Paul Rutter (philabs!per) and Joe Orost (petsd!joe) for
|
|
* the atob/btoa programs, released with the compress program, in mod.sources.
|
|
* Modified by Mike Schwartz 8/19/86 for use in BIND.
|
|
* Modified to be re-entrant 3/2/99.
|
|
*/
|
|
|
|
|
|
struct state {
|
|
isc_int32_t Ceor;
|
|
isc_int32_t Csum;
|
|
isc_int32_t Crot;
|
|
isc_int32_t word;
|
|
isc_int32_t bcount;
|
|
};
|
|
|
|
#define Ceor state->Ceor
|
|
#define Csum state->Csum
|
|
#define Crot state->Crot
|
|
#define word state->word
|
|
#define bcount state->bcount
|
|
|
|
#define times85(x) ((((((x<<2)+x)<<2)+x)<<2)+x)
|
|
|
|
static isc_result_t byte_atob(int c, isc_buffer_t *target,
|
|
struct state *state);
|
|
static isc_result_t putbyte(int c, isc_buffer_t *, struct state *state);
|
|
static isc_result_t byte_btoa(int c, isc_buffer_t *, struct state *state);
|
|
|
|
/*
|
|
* Decode ASCII-encoded byte c into binary representation and
|
|
* place into *bufp, advancing bufp.
|
|
*/
|
|
static isc_result_t
|
|
byte_atob(int c, isc_buffer_t *target, struct state *state) {
|
|
char *s;
|
|
if (c == 'z') {
|
|
if (bcount != 0)
|
|
return(DNS_R_SYNTAX);
|
|
else {
|
|
RETERR(putbyte(0, target, state));
|
|
RETERR(putbyte(0, target, state));
|
|
RETERR(putbyte(0, target, state));
|
|
RETERR(putbyte(0, target, state));
|
|
}
|
|
} else if ((s = strchr(atob_digits, c)) != NULL) {
|
|
if (bcount == 0) {
|
|
word = s - atob_digits;
|
|
++bcount;
|
|
} else if (bcount < 4) {
|
|
word = times85(word);
|
|
word += s - atob_digits;
|
|
++bcount;
|
|
} else {
|
|
word = times85(word);
|
|
word += s - atob_digits;
|
|
RETERR(putbyte((word >> 24) & 0xff, target, state));
|
|
RETERR(putbyte((word >> 16) & 0xff, target, state));
|
|
RETERR(putbyte((word >> 8) & 0xff, target, state));
|
|
RETERR(putbyte(word & 0xff, target, state));
|
|
word = 0;
|
|
bcount = 0;
|
|
}
|
|
} else
|
|
return(DNS_R_SYNTAX);
|
|
return(ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* Compute checksum info and place c into target.
|
|
*/
|
|
static isc_result_t
|
|
putbyte(int c, isc_buffer_t *target, struct state *state) {
|
|
isc_region_t tr;
|
|
|
|
Ceor ^= c;
|
|
Csum += c;
|
|
Csum += 1;
|
|
if ((Crot & 0x80000000)) {
|
|
Crot <<= 1;
|
|
Crot += 1;
|
|
} else {
|
|
Crot <<= 1;
|
|
}
|
|
Crot += c;
|
|
isc_buffer_availableregion(target, &tr);
|
|
if (tr.length < 1)
|
|
return (ISC_R_NOSPACE);
|
|
tr.base[0] = c;
|
|
isc_buffer_add(target, 1);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* Read the ASCII-encoded data from inbuf, of length inbuflen, and convert
|
|
* it into T_UNSPEC (binary data) in outbuf, not to exceed outbuflen bytes;
|
|
* outbuflen must be divisible by 4. (Note: this is because outbuf is filled
|
|
* in 4 bytes at a time. If the actual data doesn't end on an even 4-byte
|
|
* boundary, there will be no problem...it will be padded with 0 bytes, and
|
|
* numbytes will indicate the correct number of bytes. The main point is
|
|
* that since the buffer is filled in 4 bytes at a time, even if there is
|
|
* not a full 4 bytes of data at the end, there has to be room to 0-pad the
|
|
* data, so the buffer must be of size divisible by 4). Place the number of
|
|
* output bytes in numbytes, and return a failure/success status.
|
|
*/
|
|
|
|
static isc_result_t
|
|
atob_tobuffer(isc_lex_t *lexer, isc_buffer_t *target) {
|
|
long oeor, osum, orot;
|
|
struct state statebuf, *state= &statebuf;
|
|
isc_token_t token;
|
|
char c;
|
|
char *e;
|
|
|
|
Ceor = Csum = Crot = word = bcount = 0;
|
|
|
|
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
|
|
ISC_FALSE));
|
|
while (token.value.as_textregion.length != 0) {
|
|
if ((c = token.value.as_textregion.base[0]) == 'x') {
|
|
break;
|
|
} else
|
|
RETERR(byte_atob(c, target, state));
|
|
isc_textregion_consume(&token.value.as_textregion, 1);
|
|
}
|
|
|
|
/*
|
|
* Number of bytes.
|
|
*/
|
|
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_number,
|
|
ISC_FALSE));
|
|
if ((token.value.as_ulong % 4) != 0U)
|
|
isc_buffer_subtract(target, 4 - (token.value.as_ulong % 4));
|
|
|
|
/*
|
|
* Checksum.
|
|
*/
|
|
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
|
|
ISC_FALSE));
|
|
oeor = strtol(DNS_AS_STR(token), &e, 16);
|
|
if (*e != 0)
|
|
return (DNS_R_SYNTAX);
|
|
|
|
/*
|
|
* Checksum.
|
|
*/
|
|
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
|
|
ISC_FALSE));
|
|
osum = strtol(DNS_AS_STR(token), &e, 16);
|
|
if (*e != 0)
|
|
return (DNS_R_SYNTAX);
|
|
|
|
/*
|
|
* Checksum.
|
|
*/
|
|
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
|
|
ISC_FALSE));
|
|
orot = strtol(DNS_AS_STR(token), &e, 16);
|
|
if (*e != 0)
|
|
return (DNS_R_SYNTAX);
|
|
|
|
if ((oeor != Ceor) || (osum != Csum) || (orot != Crot))
|
|
return(DNS_R_BADCKSUM);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* Encode binary byte c into ASCII representation and place into *bufp,
|
|
* advancing bufp.
|
|
*/
|
|
static isc_result_t
|
|
byte_btoa(int c, isc_buffer_t *target, struct state *state) {
|
|
isc_region_t tr;
|
|
|
|
isc_buffer_availableregion(target, &tr);
|
|
Ceor ^= c;
|
|
Csum += c;
|
|
Csum += 1;
|
|
if ((Crot & 0x80000000)) {
|
|
Crot <<= 1;
|
|
Crot += 1;
|
|
} else {
|
|
Crot <<= 1;
|
|
}
|
|
Crot += c;
|
|
|
|
word <<= 8;
|
|
word |= c;
|
|
if (bcount == 3) {
|
|
if (word == 0) {
|
|
if (tr.length < 1)
|
|
return (ISC_R_NOSPACE);
|
|
tr.base[0] = 'z';
|
|
isc_buffer_add(target, 1);
|
|
} else {
|
|
register int tmp = 0;
|
|
register isc_int32_t tmpword = word;
|
|
|
|
if (tmpword < 0) {
|
|
/*
|
|
* Because some don't support u_long.
|
|
*/
|
|
tmp = 32;
|
|
tmpword -= (isc_int32_t)(85 * 85 * 85 * 85 * 32);
|
|
}
|
|
if (tmpword < 0) {
|
|
tmp = 64;
|
|
tmpword -= (isc_int32_t)(85 * 85 * 85 * 85 * 32);
|
|
}
|
|
if (tr.length < 5)
|
|
return (ISC_R_NOSPACE);
|
|
tr.base[0] = atob_digits[(tmpword /
|
|
(isc_int32_t)(85 * 85 * 85 * 85))
|
|
+ tmp];
|
|
tmpword %= (isc_int32_t)(85 * 85 * 85 * 85);
|
|
tr.base[1] = atob_digits[tmpword / (85 * 85 * 85)];
|
|
tmpword %= (85 * 85 * 85);
|
|
tr.base[2] = atob_digits[tmpword / (85 * 85)];
|
|
tmpword %= (85 * 85);
|
|
tr.base[3] = atob_digits[tmpword / 85];
|
|
tmpword %= 85;
|
|
tr.base[4] = atob_digits[tmpword];
|
|
isc_buffer_add(target, 5);
|
|
}
|
|
bcount = 0;
|
|
} else {
|
|
bcount += 1;
|
|
}
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
|
|
/*
|
|
* Encode the binary data from inbuf, of length inbuflen, into a
|
|
* target. Return success/failure status
|
|
*/
|
|
static isc_result_t
|
|
btoa_totext(unsigned char *inbuf, int inbuflen, isc_buffer_t *target) {
|
|
int inc;
|
|
struct state statebuf, *state = &statebuf;
|
|
char buf[sizeof("x 2000000000 ffffffff ffffffff ffffffff")];
|
|
|
|
Ceor = Csum = Crot = word = bcount = 0;
|
|
for (inc = 0; inc < inbuflen; inbuf++, inc++)
|
|
RETERR(byte_btoa(*inbuf, target, state));
|
|
|
|
while (bcount != 0)
|
|
RETERR(byte_btoa(0, target, state));
|
|
|
|
/*
|
|
* Put byte count and checksum information at end of buffer,
|
|
* delimited by 'x'
|
|
*/
|
|
snprintf(buf, sizeof(buf), "x %d %x %x %x", inbuflen, Ceor, Csum, Crot);
|
|
return (str_totext(buf, target));
|
|
}
|
|
|
|
|
|
static void
|
|
default_fromtext_callback(dns_rdatacallbacks_t *callbacks, const char *fmt,
|
|
...)
|
|
{
|
|
va_list ap;
|
|
|
|
UNUSED(callbacks);
|
|
|
|
va_start(ap, fmt);
|
|
vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
static void
|
|
fromtext_warneof(isc_lex_t *lexer, dns_rdatacallbacks_t *callbacks) {
|
|
if (isc_lex_isfile(lexer) && callbacks != NULL) {
|
|
const char *name = isc_lex_getsourcename(lexer);
|
|
if (name == NULL)
|
|
name = "UNKNOWN";
|
|
(*callbacks->warn)(callbacks,
|
|
"%s:%lu: file does not end with newline",
|
|
name, isc_lex_getsourceline(lexer));
|
|
}
|
|
}
|
|
|
|
static void
|
|
warn_badmx(isc_token_t *token, isc_lex_t *lexer,
|
|
dns_rdatacallbacks_t *callbacks)
|
|
{
|
|
const char *file;
|
|
unsigned long line;
|
|
|
|
if (lexer != NULL) {
|
|
file = isc_lex_getsourcename(lexer);
|
|
line = isc_lex_getsourceline(lexer);
|
|
(*callbacks->warn)(callbacks, "%s:%u: warning: '%s': %s",
|
|
file, line, DNS_AS_STR(*token),
|
|
dns_result_totext(DNS_R_MXISADDRESS));
|
|
}
|
|
}
|
|
|
|
static void
|
|
warn_badname(dns_name_t *name, isc_lex_t *lexer,
|
|
dns_rdatacallbacks_t *callbacks)
|
|
{
|
|
const char *file;
|
|
unsigned long line;
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
|
|
if (lexer != NULL) {
|
|
file = isc_lex_getsourcename(lexer);
|
|
line = isc_lex_getsourceline(lexer);
|
|
dns_name_format(name, namebuf, sizeof(namebuf));
|
|
(*callbacks->warn)(callbacks, "%s:%u: warning: %s: %s",
|
|
file, line, namebuf,
|
|
dns_result_totext(DNS_R_BADNAME));
|
|
}
|
|
}
|
|
|
|
static void
|
|
fromtext_error(void (*callback)(dns_rdatacallbacks_t *, const char *, ...),
|
|
dns_rdatacallbacks_t *callbacks, const char *name,
|
|
unsigned long line, isc_token_t *token, isc_result_t result)
|
|
{
|
|
if (name == NULL)
|
|
name = "UNKNOWN";
|
|
|
|
if (token != NULL) {
|
|
switch (token->type) {
|
|
case isc_tokentype_eol:
|
|
(*callback)(callbacks, "%s: %s:%lu: near eol: %s",
|
|
"dns_rdata_fromtext", name, line,
|
|
dns_result_totext(result));
|
|
break;
|
|
case isc_tokentype_eof:
|
|
(*callback)(callbacks, "%s: %s:%lu: near eof: %s",
|
|
"dns_rdata_fromtext", name, line,
|
|
dns_result_totext(result));
|
|
break;
|
|
case isc_tokentype_number:
|
|
(*callback)(callbacks, "%s: %s:%lu: near %lu: %s",
|
|
"dns_rdata_fromtext", name, line,
|
|
token->value.as_ulong,
|
|
dns_result_totext(result));
|
|
break;
|
|
case isc_tokentype_string:
|
|
case isc_tokentype_qstring:
|
|
(*callback)(callbacks, "%s: %s:%lu: near '%s': %s",
|
|
"dns_rdata_fromtext", name, line,
|
|
DNS_AS_STR(*token),
|
|
dns_result_totext(result));
|
|
break;
|
|
default:
|
|
(*callback)(callbacks, "%s: %s:%lu: %s",
|
|
"dns_rdata_fromtext", name, line,
|
|
dns_result_totext(result));
|
|
break;
|
|
}
|
|
} else {
|
|
(*callback)(callbacks, "dns_rdata_fromtext: %s:%lu: %s",
|
|
name, line, dns_result_totext(result));
|
|
}
|
|
}
|
|
|
|
dns_rdatatype_t
|
|
dns_rdata_covers(dns_rdata_t *rdata) {
|
|
if (rdata->type == 46)
|
|
return (covers_rrsig(rdata));
|
|
return (covers_sig(rdata));
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_rdatatype_ismeta(dns_rdatatype_t type) {
|
|
if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_META) != 0)
|
|
return (ISC_TRUE);
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_rdatatype_issingleton(dns_rdatatype_t type) {
|
|
if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_SINGLETON)
|
|
!= 0)
|
|
return (ISC_TRUE);
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_rdatatype_notquestion(dns_rdatatype_t type) {
|
|
if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_NOTQUESTION)
|
|
!= 0)
|
|
return (ISC_TRUE);
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_rdatatype_questiononly(dns_rdatatype_t type) {
|
|
if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_QUESTIONONLY)
|
|
!= 0)
|
|
return (ISC_TRUE);
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_rdatatype_atparent(dns_rdatatype_t type) {
|
|
if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ATPARENT) != 0)
|
|
return (ISC_TRUE);
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_rdataclass_ismeta(dns_rdataclass_t rdclass) {
|
|
|
|
if (rdclass == dns_rdataclass_reserved0
|
|
|| rdclass == dns_rdataclass_none
|
|
|| rdclass == dns_rdataclass_any)
|
|
return (ISC_TRUE);
|
|
|
|
return (ISC_FALSE); /* Assume it is not a meta class. */
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_rdatatype_isdnssec(dns_rdatatype_t type) {
|
|
if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_DNSSEC) != 0)
|
|
return (ISC_TRUE);
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_rdatatype_iszonecutauth(dns_rdatatype_t type) {
|
|
if ((dns_rdatatype_attributes(type)
|
|
& (DNS_RDATATYPEATTR_DNSSEC | DNS_RDATATYPEATTR_ZONECUTAUTH))
|
|
!= 0)
|
|
return (ISC_TRUE);
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
isc_boolean_t
|
|
dns_rdatatype_isknown(dns_rdatatype_t type) {
|
|
if ((dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_UNKNOWN)
|
|
== 0)
|
|
return (ISC_TRUE);
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
void
|
|
dns_rdata_exists(dns_rdata_t *rdata, dns_rdatatype_t type) {
|
|
|
|
REQUIRE(rdata != NULL);
|
|
REQUIRE(DNS_RDATA_INITIALIZED(rdata));
|
|
|
|
rdata->data = NULL;
|
|
rdata->length = 0;
|
|
rdata->flags = DNS_RDATA_UPDATE;
|
|
rdata->type = type;
|
|
rdata->rdclass = dns_rdataclass_any;
|
|
}
|
|
|
|
void
|
|
dns_rdata_notexist(dns_rdata_t *rdata, dns_rdatatype_t type) {
|
|
|
|
REQUIRE(rdata != NULL);
|
|
REQUIRE(DNS_RDATA_INITIALIZED(rdata));
|
|
|
|
rdata->data = NULL;
|
|
rdata->length = 0;
|
|
rdata->flags = DNS_RDATA_UPDATE;
|
|
rdata->type = type;
|
|
rdata->rdclass = dns_rdataclass_none;
|
|
}
|
|
|
|
void
|
|
dns_rdata_deleterrset(dns_rdata_t *rdata, dns_rdatatype_t type) {
|
|
|
|
REQUIRE(rdata != NULL);
|
|
REQUIRE(DNS_RDATA_INITIALIZED(rdata));
|
|
|
|
rdata->data = NULL;
|
|
rdata->length = 0;
|
|
rdata->flags = DNS_RDATA_UPDATE;
|
|
rdata->type = type;
|
|
rdata->rdclass = dns_rdataclass_any;
|
|
}
|
|
|
|
void
|
|
dns_rdata_makedelete(dns_rdata_t *rdata) {
|
|
REQUIRE(rdata != NULL);
|
|
|
|
rdata->rdclass = dns_rdataclass_none;
|
|
}
|
|
|
|
const char *
|
|
dns_rdata_updateop(dns_rdata_t *rdata, dns_section_t section) {
|
|
|
|
REQUIRE(rdata != NULL);
|
|
REQUIRE(DNS_RDATA_INITIALIZED(rdata));
|
|
|
|
switch (section) {
|
|
case DNS_SECTION_PREREQUISITE:
|
|
switch (rdata->rdclass) {
|
|
case dns_rdataclass_none:
|
|
switch (rdata->type) {
|
|
case dns_rdatatype_any:
|
|
return ("domain doesn't exist");
|
|
default:
|
|
return ("rrset doesn't exist");
|
|
}
|
|
case dns_rdataclass_any:
|
|
switch (rdata->type) {
|
|
case dns_rdatatype_any:
|
|
return ("domain exists");
|
|
default:
|
|
return ("rrset exists (value independent)");
|
|
}
|
|
default:
|
|
return ("rrset exists (value dependent)");
|
|
}
|
|
case DNS_SECTION_UPDATE:
|
|
switch (rdata->rdclass) {
|
|
case dns_rdataclass_none:
|
|
return ("delete");
|
|
case dns_rdataclass_any:
|
|
switch (rdata->type) {
|
|
case dns_rdatatype_any:
|
|
return ("delete all rrsets");
|
|
default:
|
|
return ("delete rrset");
|
|
}
|
|
default:
|
|
return ("add");
|
|
}
|
|
}
|
|
return ("invalid");
|
|
}
|