diff --git a/lib/dns/Makefile.in b/lib/dns/Makefile.in index 37182ec8a5..3eb23131f9 100644 --- a/lib/dns/Makefile.in +++ b/lib/dns/Makefile.in @@ -28,6 +28,7 @@ CINCLUDES = -I../isc/unix/include \ -I./include \ -I. \ -I${srcdir}/include \ + -I${srcdir}/sec/dst/include \ -I${srcdir} CDEFINES = @@ -111,7 +112,7 @@ OBJS = callbacks.@O@ compress.@O@ db.@O@ dbiterator.@O@ \ name.@O@ rbt.@O@ rbtdb.@O@ rbtdb64.@O@ rdata.@O@ \ rdatalist.@O@ rdataset.@O@ rdatasetiter.@O@ rdataslab.@O@ \ resolver.@O@ result.@O@ version.@O@ masterdump.@O@ time.@O@ \ - ttl.@O@ tcpmsg.@O@ view.@O@ journal.@O@ \ + ttl.@O@ tcpmsg.@O@ tsig.@O@ view.@O@ journal.@O@ \ ${DSTOBJS} ${OPENSSLOBJS} ${DNSSAFEOBJS} SRCS = callbacks.c compress.c db.c dbiterator.c \ @@ -119,7 +120,7 @@ SRCS = callbacks.c compress.c db.c dbiterator.c \ name.c rbt.c rbtdb.c rbtdb64.c rdata.c \ rdatalist.c rdataset.c rdatasetiter.c rdataslab.c \ resolver.c result.c version.c masterdump.c time.c \ - ttl.c tcpmsg.c view.c journal.c + ttl.c tcpmsg.c tsig.c view.c journal.c SUBDIRS = include sec TARGETS = include/dns/enumtype.h include/dns/enumclass.h \ diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h index 5654cc4d72..2a8faa70d5 100644 --- a/lib/dns/include/dns/message.h +++ b/lib/dns/include/dns/message.h @@ -29,6 +29,7 @@ #include #include #include +#include #include /* @@ -139,6 +140,13 @@ struct dns_message { ISC_LIST(dns_rdata_t) freerdata; ISC_LIST(dns_rdataset_t) freerdataset; ISC_LIST(dns_rdatalist_t) freerdatalist; + + dns_rcode_t tsigstatus; + dns_rcode_t querytsigstatus; + dns_rdata_any_tsig_t *tsig; + dns_rdata_any_tsig_t *querytsig; + dns_tsig_key_t *tsigkey; + int tsigstart; }; dns_result_t @@ -358,6 +366,21 @@ dns_message_rendersection(dns_message_t *msg, dns_section_t section, * are records remaining for this section. */ +void +dns_message_renderheader(dns_message_t *msg, isc_buffer_t *target); +/* + * Render the message header. This is implicitly called by + * dns_message_renderend(). + * + * Requires: + * + * 'msg' be a valid message. + * + * dns_message_renderbegin() was called. + * + * 'target' is a valid buffer with enough space to hold a message header + */ + dns_result_t dns_message_renderend(dns_message_t *msg); /* diff --git a/lib/dns/include/dns/tsig.h b/lib/dns/include/dns/tsig.h new file mode 100644 index 0000000000..141045e154 --- /dev/null +++ b/lib/dns/include/dns/tsig.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 1999 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. + */ + +#ifndef DNS_TSIG_H +#define DNS_TSIG_H 1 + +#include +#include + +#include + +#include + +ISC_LANG_BEGINDECLS + +/* Standard algorithm */ +#define DNS_TSIG_HMACMD5 "HMAC-MD5.SIG-ALG.REG.INT." +extern dns_name_t *dns_tsig_hmacmd5_name; +#define DNS_TSIG_HMACMD5_NAME dns_tsig_hmacmd5_name + +/* Default fudge value. */ +#define DNS_TSIG_FUDGE 300 + +struct dns_tsig_key { + unsigned int magic; /* Magic number. */ + isc_mem_t *mctx; + dst_key_t *key; /* Key */ + dns_name_t name; /* Key name */ + dns_name_t algorithm; /* Algorithm name */ + ISC_LINK(dns_tsig_key_t) link; +}; + +#define dns_tsig_emptykey(tsigkey) ((tsigkey)->key == NULL) + +isc_result_t +dns_tsig_key_create(dns_name_t *name, dns_name_t *algorithm, + unsigned char *secret, int length, isc_mem_t *mctx, + dns_tsig_key_t **key); +/* + * Creates a tsig key structure pointed to by 'key'. + * + * Requires: + * 'name' is a valid dns_name_t + * 'algorithm' is a valid dns_name_t + * 'secret' is a valid pointer + * 'length' is an integer greater than 0 + * 'mctx' is a valid memory context + * 'key' must not be NULL + * '*key' must be NULL + * + * Returns: + * ISC_R_SUCCESS + * DNS_R_NOTIMPLEMENTED - algorithm is not implemented + * ISC_R_NOMEMORY + */ + +void +dns_tsig_key_free(dns_tsig_key_t **key); +/* + * Frees the tsig key structure pointed to by 'key'. + * + * Requires: + * 'key' is a valid TSIG key + */ + +isc_result_t +dns_tsig_sign(dns_message_t *msg); +/* + * Generates a TSIG record for this message + * + * Requires: + * 'msg' is a valid message + * 'msg->tsigkey' is a valid TSIG key + * 'msg->tsig' is NULL + * 'msg->querytsig' is not NULL if this is a response + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOMEMORY + * ISC_R_NOSPACE + */ + +isc_result_t +dns_tsig_verify(isc_buffer_t *source, dns_message_t *msg); +/* + * Verified the TSIG record in this message + * + * Requires: + * 'source' is a valid buffer containing the unparsed message + * 'msg' is a valid message containing a TSIG record + * 'msg->tsigkey' is a valid TSIG key + * 'msg->tsig' is NULL + * 'msg->querytsig' is not NULL if this is a response + * + * Returns: + * DNS_R_SUCCESS + * ISC_R_NOMEMORY + * DNS_R_TSIGERRORSET - the TSIG verified but ->error was set + * and this is a query + * DNS_R_TSIGVERIFYFAILURE - the TSIG failed to verify + */ + +isc_result_t +dns_tsig_findkey(dns_tsig_key_t **tsigkey, dns_name_t *name, + dns_name_t *algorithm); +/* + * Returns the TSIG key corresponding to this name and algorithm + * + * Requires: + * 'tsigkey' is not NULL + * '*tsigkey' is NULL + * 'name' is a valid dns_name_t + * 'algorithm' is a valid dns_name_t + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOTFOUND + */ + + +isc_result_t +dns_tsig_init(isc_mem_t *mctx); +/* + * Initializes the TSIG subsystem + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOMEMORY + */ + + +void +dns_tsig_destroy(void); +/* + * Frees all data associated with the TSIG subsystem + */ + +ISC_LANG_ENDDECLS + +#endif /* DNS_TSIG_H */ diff --git a/lib/dns/message.c b/lib/dns/message.c index f048353ed4..5d8a9a06a8 100644 --- a/lib/dns/message.c +++ b/lib/dns/message.c @@ -36,6 +36,7 @@ #include #include #include +#include #define DNS_MESSAGE_OPCODE_MASK 0x7800U #define DNS_MESSAGE_OPCODE_SHIFT 11 @@ -345,6 +346,15 @@ msginitprivate(dns_message_t *m) m->need_cctx_cleanup = 0; } +static inline void +msginittsig(dns_message_t *m) +{ + m->tsigstatus = m->querytsigstatus = dns_rcode_noerror; + m->tsig = m->querytsig = NULL; + m->tsigkey = NULL; + m->tsigstart = -1; +} + /* * Init elements to default state. Used both when allocating a new element * and when resetting one. @@ -354,6 +364,7 @@ msginit(dns_message_t *m) { msginitheader(m); msginitprivate(m); + msginittsig(m); m->header_ok = 0; m->question_ok = 0; } @@ -503,6 +514,20 @@ msgreset(dns_message_t *msg, isc_boolean_t everything) if (msg->need_cctx_cleanup == 1) dns_compress_invalidate(&msg->cctx); + if (msg->tsig != NULL) { + dns_rdata_freestruct(msg->tsig); + isc_mem_put(msg->mctx, msg->tsig, sizeof(dns_rdata_any_tsig_t)); + } + + if (msg->querytsig != NULL) { + dns_rdata_freestruct(msg->querytsig); + isc_mem_put(msg->mctx, msg->querytsig, + sizeof(dns_rdata_any_tsig_t)); + } + + if (msg->tsigkey != NULL && dns_tsig_emptykey(msg->tsigkey)) + dns_tsig_key_free(&msg->tsigkey); + /* * Set other bits to normal default values. */ @@ -909,6 +934,7 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, dns_namelist_t *section; for (count = 0 ; count < msg->counts[sectionid] ; count++) { + int recstart = source->current; section = &msg->sections[sectionid]; name = newname(msg); @@ -965,6 +991,7 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, if (rdclass != dns_rdataclass_any) return (DNS_R_FORMERR); section = &msg->sections[DNS_SECTION_TSIG]; + msg->tsigstart = recstart; } /* @@ -1050,14 +1077,19 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, /* * Read the rdata from the wire format. Interpret the * rdata according to its actual class, even if it had a - * DynDNS meta-class in the packet. Then put the meta-class - * back into the finished rdata. + * DynDNS meta-class in the packet (unless this is a TSIG). + * Then put the meta-class back into the finished rdata. */ rdata = newrdata(msg); if (rdata == NULL) return (DNS_R_NOMEMORY); - result = getrdata(name, source, msg, dctx, - msg->rdclass, rdtype, rdatalen, rdata); + if (rdtype != dns_rdatatype_tsig) + result = getrdata(name, source, msg, dctx, + msg->rdclass, rdtype, + rdatalen, rdata); + else + result = getrdata(name, source, msg, dctx, + rdclass, rdtype, rdatalen, rdata); if (result != DNS_R_SUCCESS) return (result); rdata->rdclass = rdclass; @@ -1152,9 +1184,11 @@ dns_message_parse(dns_message_t *msg, isc_buffer_t *source, if (r.length != 0) return (DNS_R_FORMERR); - /* - * XXXMLG Need to check the tsig(s) here... - */ + if (!ISC_LIST_EMPTY(msg->sections[DNS_SECTION_TSIG])) { + ret = dns_tsig_verify(source, msg); + if (ret != DNS_R_SUCCESS) + return ret; + } return (DNS_R_SUCCESS); } @@ -1344,33 +1378,60 @@ dns_message_rendersection(dns_message_t *msg, dns_section_t sectionid, return (DNS_R_SUCCESS); } -dns_result_t -dns_message_renderend(dns_message_t *msg) +void +dns_message_renderheader(dns_message_t *msg, isc_buffer_t *target) { - isc_buffer_t tmpbuf; - isc_region_t r; isc_uint16_t tmp; + isc_region_t r; REQUIRE(DNS_MESSAGE_VALID(msg)); - REQUIRE(msg->buffer != NULL); + REQUIRE(target != NULL); - isc_buffer_used(msg->buffer, &r); - isc_buffer_init(&tmpbuf, r.base, r.length, ISC_BUFFERTYPE_BINARY); + isc_buffer_available(target, &r); + REQUIRE(r.length >= DNS_MESSAGE_HEADERLEN); - isc_buffer_putuint16(&tmpbuf, msg->id); + isc_buffer_putuint16(target, msg->id); tmp = ((msg->opcode << DNS_MESSAGE_OPCODE_SHIFT) & DNS_MESSAGE_OPCODE_MASK); tmp |= (msg->rcode & DNS_MESSAGE_RCODE_MASK); /* XXX edns? */ tmp |= (msg->flags & DNS_MESSAGE_FLAG_MASK); - isc_buffer_putuint16(&tmpbuf, tmp); - isc_buffer_putuint16(&tmpbuf, msg->counts[DNS_SECTION_QUESTION]); - isc_buffer_putuint16(&tmpbuf, msg->counts[DNS_SECTION_ANSWER]); - isc_buffer_putuint16(&tmpbuf, msg->counts[DNS_SECTION_AUTHORITY]); + isc_buffer_putuint16(target, tmp); + isc_buffer_putuint16(target, msg->counts[DNS_SECTION_QUESTION]); + isc_buffer_putuint16(target, msg->counts[DNS_SECTION_ANSWER]); + isc_buffer_putuint16(target, msg->counts[DNS_SECTION_AUTHORITY]); tmp = msg->counts[DNS_SECTION_ADDITIONAL] + msg->counts[DNS_SECTION_TSIG]; - isc_buffer_putuint16(&tmpbuf, tmp); + isc_buffer_putuint16(target, tmp); +} + +dns_result_t +dns_message_renderend(dns_message_t *msg) +{ + isc_buffer_t tmpbuf; + isc_region_t r; + int result; + + REQUIRE(DNS_MESSAGE_VALID(msg)); + REQUIRE(msg->buffer != NULL); + + if (msg->tsigkey != NULL || + ((msg->flags & DNS_MESSAGEFLAG_QR) != 0 && + msg->querytsigstatus != dns_rcode_noerror)) + { + result = dns_tsig_sign(msg); + if (result != DNS_R_SUCCESS) + return (result); + result = dns_message_rendersection(msg, DNS_SECTION_TSIG, 0, 0); + if (result != DNS_R_SUCCESS) + return (result); + } + + isc_buffer_used(msg->buffer, &r); + isc_buffer_init(&tmpbuf, r.base, r.length, ISC_BUFFERTYPE_BINARY); + + dns_message_renderheader(msg, &tmpbuf); msg->buffer = NULL; /* forget about this buffer only on success XXX */ @@ -1650,5 +1711,17 @@ dns_message_reply(dns_message_t *msg, isc_boolean_t want_question_section) { msg->flags &= DNS_MESSAGE_REPLYPRESERVE; msg->flags |= DNS_MESSAGEFLAG_QR; + /* + * This saves the query TSIG information for later use, if there is + * any. This only happens once - that is, if dns_message_reply + * has already moved the variables, this has no effect. + */ + if (msg->tsig != NULL) { + msg->querytsig = msg->tsig; + msg->tsig = NULL; + msg->querytsigstatus = msg->tsigstatus; + msg->tsigstatus = dns_rcode_noerror; + } + return (DNS_R_SUCCESS); } diff --git a/lib/dns/rdata/any_255/tsig_250.c b/lib/dns/rdata/any_255/tsig_250.c index 70baa77393..2397739389 100644 --- a/lib/dns/rdata/any_255/tsig_250.c +++ b/lib/dns/rdata/any_255/tsig_250.c @@ -15,7 +15,7 @@ * SOFTWARE. */ - /* $Id: tsig_250.c,v 1.14 1999/08/12 01:32:29 halley Exp $ */ + /* $Id: tsig_250.c,v 1.15 1999/08/20 18:56:24 bwelling Exp $ */ /* draft-ietf-dnsind-tsig-07.txt */ @@ -292,6 +292,9 @@ static inline dns_result_t fromstruct_any_tsig(dns_rdataclass_t rdclass, dns_rdatatype_t type, void *source, isc_buffer_t *target) { + isc_region_t tr; + dns_rdata_any_tsig_t *tsig; + dns_compress_t cctx; REQUIRE(type == 250); REQUIRE(rdclass == 255); @@ -299,11 +302,68 @@ fromstruct_any_tsig(dns_rdataclass_t rdclass, dns_rdatatype_t type, source = source; target = target; - return (DNS_R_NOTIMPLEMENTED); + tsig = (dns_rdata_any_tsig_t *) source; + REQUIRE(tsig->mctx != NULL); + + /* Algorithm Name */ + RETERR(dns_compress_init(&cctx, -1, tsig->mctx)); + dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE); + RETERR(dns_name_towire(tsig->algorithm, &cctx, target)); + dns_compress_invalidate(&cctx); + + isc_buffer_available(target, &tr); + if (tr.length < 6 + 2 + 2) + return (DNS_R_NOSPACE); + + /* Time Signed: 48 bits */ + RETERR(uint16_tobuffer(tsig->timesigned >> 32, target)); + RETERR(uint32_tobuffer(tsig->timesigned & 0xffffffff, target)); + + /* Fudge */ + RETERR(uint16_tobuffer(tsig->fudge, target)); + + /* Signature Size */ + RETERR(uint16_tobuffer(tsig->siglen, target)); + + /* Signature */ + if (tsig->siglen > 0) { + isc_buffer_available(target, &tr); + if (tr.length < tsig->siglen) + return (DNS_R_NOSPACE); + memcpy(tr.base, tsig->signature, tsig->siglen); + isc_buffer_add(target, tsig->siglen); + } + + isc_buffer_available(target, &tr); + if (tr.length < 2 + 2 + 2) + return (DNS_R_NOSPACE); + + /* Original ID */ + RETERR(uint16_tobuffer(tsig->originalid, target)); + + /* Error */ + RETERR(uint16_tobuffer(tsig->error, target)); + + /* Other Len */ + RETERR(uint16_tobuffer(tsig->otherlen, target)); + + /* Other Data */ + if (tsig->otherlen > 0) { + isc_buffer_available(target, &tr); + if (tr.length < tsig->otherlen) + return (DNS_R_NOSPACE); + memcpy(tr.base, tsig->other, tsig->otherlen); + isc_buffer_add(target, tsig->otherlen); + } + + return (DNS_R_SUCCESS); } static inline dns_result_t tostruct_any_tsig(dns_rdata_t *rdata, void *target, isc_mem_t *mctx) { + dns_rdata_any_tsig_t *tsig; + dns_name_t alg; + isc_region_t sr; REQUIRE(rdata->type == 250); REQUIRE(rdata->rdclass == 255); @@ -311,18 +371,90 @@ tostruct_any_tsig(dns_rdata_t *rdata, void *target, isc_mem_t *mctx) { target = target; mctx = mctx; - return (DNS_R_NOTIMPLEMENTED); + tsig = (dns_rdata_any_tsig_t *) target; + tsig->common.rdclass = rdata->rdclass; + tsig->common.rdtype = rdata->type; + ISC_LINK_INIT(&tsig->common, link); + tsig->mctx = mctx; + dns_rdata_toregion(rdata, &sr); + + /* Algorithm Name */ + dns_name_init(&alg, NULL); + dns_name_fromregion(&alg, &sr); + tsig->algorithm = (dns_name_t *) isc_mem_get(mctx, sizeof(dns_name_t)); + if (tsig->algorithm == NULL) + return (DNS_R_NOMEMORY); + dns_name_init(tsig->algorithm, NULL); + RETERR(dns_name_dup(&alg, mctx, tsig->algorithm)); + + isc_region_consume(&sr, name_length(tsig->algorithm)); + + /* Time Signed */ + tsig->timesigned = ((isc_uint64_t)sr.base[0] << 40) | + ((isc_uint64_t)sr.base[1] << 32) | + (sr.base[2] << 24) | (sr.base[3] << 16) | + (sr.base[4] << 8) | sr.base[5]; + isc_region_consume(&sr, 6); + + /* Fudge */ + tsig->fudge = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* Signature Size */ + tsig->siglen = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* Signature */ + if (tsig->siglen > 0) { + tsig->signature = isc_mem_get(mctx, tsig->siglen); + if (tsig->signature == NULL) + return (DNS_R_NOMEMORY); + memcpy(tsig->signature, sr.base, tsig->siglen); + isc_region_consume(&sr, tsig->siglen); + } + else + tsig->signature = NULL; + + /* Original ID */ + tsig->originalid = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* Error */ + tsig->error = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* Other Size */ + tsig->otherlen = uint16_fromregion(&sr); + isc_region_consume(&sr, 2); + + /* Other */ + if (tsig->otherlen > 0) { + tsig->other = isc_mem_get(mctx, tsig->otherlen); + if (tsig->other == NULL) + return (DNS_R_NOMEMORY); + memcpy(tsig->other, sr.base, tsig->otherlen); + isc_region_consume(&sr, tsig->otherlen); + } + else + tsig->other = NULL; + + return (DNS_R_SUCCESS); } static inline void freestruct_any_tsig(void *source) { - dns_rdata_any_tsig_t *tsig = source; + dns_rdata_any_tsig_t *tsig = (dns_rdata_any_tsig_t *) source; REQUIRE(source != NULL); REQUIRE(tsig->common.rdclass == 255); REQUIRE(tsig->common.rdtype == 250); - REQUIRE(ISC_FALSE); + dns_name_free(tsig->algorithm, tsig->mctx); + isc_mem_put(tsig->mctx, tsig->algorithm, sizeof(dns_name_t)); + if (tsig->siglen > 0) + isc_mem_put(tsig->mctx, tsig->signature, tsig->siglen); + if (tsig->other != NULL) + isc_mem_put(tsig->mctx, tsig->other, tsig->otherlen); } static inline dns_result_t diff --git a/lib/dns/rdata/any_255/tsig_250.h b/lib/dns/rdata/any_255/tsig_250.h index 4156fb5482..9d01613d56 100644 --- a/lib/dns/rdata/any_255/tsig_250.h +++ b/lib/dns/rdata/any_255/tsig_250.h @@ -15,11 +15,20 @@ * SOFTWARE. */ - /* $Id: tsig_250.h,v 1.9 1999/05/07 03:24:05 marka Exp $ */ + /* $Id: tsig_250.h,v 1.10 1999/08/20 18:56:24 bwelling Exp $ */ - /* draft-ietf-dnsind-tsig-07.txt */ + /* draft-ietf-dnsind-tsig-10.txt */ typedef struct dns_rdata_any_tsig { dns_rdatacommon_t common; - /*XXX*/ + isc_mem_t * mctx; + dns_name_t * algorithm; + isc_uint64_t timesigned; + isc_uint16_t fudge; + isc_uint16_t siglen; + unsigned char * signature; + isc_uint16_t originalid; + isc_uint16_t error; + isc_uint16_t otherlen; + unsigned char * other; } dns_rdata_any_tsig_t; diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 22e0ddd0c7..9a6fc7b3c6 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -16,7 +16,11 @@ #include #include #include +#include #include +#include + +#include #include "../isc/util.h" /* XXX */ @@ -49,6 +53,8 @@ typedef struct query { dns_dispentry_t * dispentry; /* XXX name */ ISC_LINK(struct query) link; isc_buffer_t buffer; + dns_rdata_any_tsig_t *tsig; + dns_tsig_key_t *tsigkey; unsigned char data[512]; } resquery_t; @@ -266,6 +272,8 @@ fctx_sendquery(fetchctx_t *fctx) { if (result != DNS_R_SUCCESS) goto cleanup_query; query->fctx = fctx; + query->tsig = NULL; + query->tsigkey = NULL; query->magic = QUERY_MAGIC; fctx->qmessage->opcode = dns_opcode_query; @@ -303,14 +311,16 @@ fctx_sendquery(fetchctx_t *fctx) { DNS_SECTION_ADDITIONAL, 0, 0); if (result != DNS_R_SUCCESS) goto cleanup_message; - result = dns_message_rendersection(fctx->qmessage, - DNS_SECTION_TSIG, 0, 0); - if (result != DNS_R_SUCCESS) - goto cleanup_message; result = dns_message_renderend(fctx->qmessage); if (result != DNS_R_SUCCESS) goto cleanup_message; + if (fctx->qmessage->tsigkey != NULL) { + query->tsigkey = fctx->qmessage->tsigkey; + query->tsig = fctx->qmessage->tsig; + fctx->qmessage->tsig = NULL; + } + /* * We're now done with the query message. * @@ -376,6 +386,8 @@ fctx_cancelquery(resquery_t *query, dns_dispatchevent_t **deventp) { deventp); ISC_LIST_UNLINK(fctx->queries, query, link); query->magic = 0; + if (query->tsig != NULL) + dns_rdata_freestruct(query->tsig); isc_mem_put(fctx->res->mctx, query, sizeof *query); } @@ -803,6 +815,8 @@ query_response(isc_task_t *task, isc_event_t *event) { INSIST(fctx->state == fetchstate_active); message = fctx->rmessage; + message->querytsig = query->tsig; + message->tsigkey = query->tsigkey; result = dns_message_parse(message, &devent->buffer, ISC_FALSE); if (result != DNS_R_SUCCESS) { switch (result) { @@ -859,6 +873,7 @@ query_response(isc_task_t *task, isc_event_t *event) { goto done; } + query->tsig = NULL; fctx_stoptimer(fctx); fctx_cancelquery(query, &devent); diff --git a/lib/dns/tsig.c b/lib/dns/tsig.c new file mode 100644 index 0000000000..b6e0f4cb22 --- /dev/null +++ b/lib/dns/tsig.c @@ -0,0 +1,721 @@ +/* + * Copyright (C) 1999 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. + */ + +/* + * $Id: tsig.c,v 1.1 1999/08/20 18:56:23 bwelling Exp $ + * Principal Author: Brian Wellington + */ + + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define TSIG_MAGIC 0x54534947 /* TSIG */ +#define VALID_TSIG_KEY(x) ((x) != NULL && (x)->magic == TSIG_MAGIC) + +/* XXXBEW If an unsorted list isn't good enough, this can be updated */ +static ISC_LIST(dns_tsig_key_t) tsigkeys; +static isc_rwlock_t tsiglock; +static isc_mem_t *tsig_mctx = NULL; + +dns_name_t *dns_tsig_hmacmd5_name = NULL; + +#define is_response(msg) (msg->flags & DNS_MESSAGEFLAG_QR) + +isc_result_t +dns_tsig_key_create(dns_name_t *name, dns_name_t *algorithm, + unsigned char *secret, int length, + isc_mem_t *mctx, dns_tsig_key_t **key) +{ + isc_buffer_t b, nameb; + char namestr[1024]; + isc_uint16_t alg; + dns_tsig_key_t *tkey; + isc_result_t ret; + + REQUIRE(key != NULL); + REQUIRE(*key == NULL); + REQUIRE(name != NULL); + REQUIRE(algorithm != NULL); + REQUIRE(length >= 0); + if (length > 0) + REQUIRE(secret != NULL); + REQUIRE(mctx != NULL); + + if (!dns_name_equal(algorithm, DNS_TSIG_HMACMD5_NAME)) + return (ISC_R_NOTFOUND); + else + alg = DST_ALG_HMAC_MD5; + + *key = (dns_tsig_key_t *) isc_mem_get(mctx, sizeof(dns_tsig_key_t)); + if (*key == NULL) + return (ISC_R_NOMEMORY); + tkey = *key; + + dns_name_init(&tkey->name, NULL); + ret = dns_name_dup(name, mctx, &tkey->name); + if (ret != ISC_R_SUCCESS) + goto cleanup_key; + dns_name_downcase(&tkey->name); + + dns_name_init(&tkey->algorithm, NULL); + ret = dns_name_dup(algorithm, mctx, &tkey->algorithm); + if (ret != ISC_R_SUCCESS) + goto cleanup_name; + dns_name_downcase(&tkey->algorithm); + + isc_buffer_init(&nameb, namestr, sizeof(namestr), ISC_BUFFERTYPE_TEXT); + ret = dns_name_totext(name, ISC_FALSE, &nameb); + if (ret != ISC_R_SUCCESS) + goto cleanup_algorithm; + + if (length > 0) { + isc_buffer_init(&b, secret, length, ISC_BUFFERTYPE_BINARY); + isc_buffer_add(&b, length); + ret = dst_key_frombuffer(namestr, alg, + NS_KEY_NAME_ENTITY, + NS_KEY_PROT_DNSSEC, + &b, mctx, &tkey->key); + if (ret != ISC_R_SUCCESS) + goto cleanup_algorithm; + } + else + tkey->key = NULL; + + ISC_LINK_INIT(tkey, link); + isc_rwlock_lock(&tsiglock, isc_rwlocktype_write); + ISC_LIST_APPEND(tsigkeys, tkey, link); + isc_rwlock_unlock(&tsiglock, isc_rwlocktype_write); + tkey->mctx = mctx; + tkey->magic = TSIG_MAGIC; + return (ISC_R_SUCCESS); + +cleanup_algorithm: + dns_name_free(&tkey->algorithm, mctx); +cleanup_name: + dns_name_free(&tkey->name, mctx); +cleanup_key: + isc_mem_put(mctx, *key, sizeof(dns_tsig_key_t)); + + return (ret); +} + +/* Caller must be sure that this key is not in use. */ +void +dns_tsig_key_free(dns_tsig_key_t **key) { + dns_tsig_key_t *tkey; + + REQUIRE(key != NULL); + REQUIRE(VALID_TSIG_KEY(*key)); + tkey = *key; + + tkey->magic = 0; + isc_rwlock_lock(&tsiglock, isc_rwlocktype_write); + ISC_LIST_UNLINK(tsigkeys, tkey, link); + isc_rwlock_unlock(&tsiglock, isc_rwlocktype_write); + dns_name_free(&tkey->name, tkey->mctx); + dns_name_free(&tkey->algorithm, tkey->mctx); + if (tkey->key != NULL) + dst_key_free(tkey->key); + isc_mem_put(tkey->mctx, tkey, sizeof(dns_tsig_key_t)); +} + +isc_result_t +dns_tsig_sign(dns_message_t *msg) { + dns_tsig_key_t *key; + dns_rdata_any_tsig_t *tsig; + unsigned char data[128]; + isc_buffer_t databuf, sigbuf, rdatabuf; + isc_dynbuffer_t *dynbuf; + dns_name_t *owner; + dns_rdata_t *rdata; + dns_rdatalist_t *datalist; + dns_rdataset_t *dataset; + isc_region_t r; + isc_time_t now; + dst_context_t ctx; + isc_mem_t *mctx; + int tries; + isc_result_t ret; + + REQUIRE(msg != NULL); + if (msg->tsigkey != NULL) + REQUIRE(VALID_TSIG_KEY(msg->tsigkey)); + REQUIRE(msg->tsig == NULL); + if (is_response(msg)) + REQUIRE(msg->querytsig != NULL); + + mctx = msg->mctx; + key = msg->tsigkey; + + tsig = (dns_rdata_any_tsig_t *) + isc_mem_get(mctx, sizeof(dns_rdata_any_tsig_t)); + if (tsig == NULL) + return (ISC_R_NOMEMORY); + tsig->mctx = mctx; + tsig->common.rdclass = dns_rdataclass_any; + tsig->common.rdtype = dns_rdatatype_tsig; + ISC_LINK_INIT(&tsig->common, link); + tsig->algorithm = (dns_name_t *) isc_mem_get(mctx, sizeof(dns_name_t)); + if (tsig->algorithm == NULL) { + ret = ISC_R_NOMEMORY; + goto cleanup_struct; + } + dns_name_init(tsig->algorithm, NULL); + ret = dns_name_dup(&key->algorithm, mctx, tsig->algorithm); + if (ret != ISC_R_SUCCESS) + goto cleanup_struct; + + ret = isc_time_now(&now); + if (ret != ISC_R_SUCCESS) + goto cleanup_algorithm; + tsig->timesigned = now.seconds; + tsig->fudge = DNS_TSIG_FUDGE; + + tsig->originalid = msg->id; + + isc_buffer_init(&databuf, data, sizeof(data), ISC_BUFFERTYPE_BINARY); + + if (!dns_tsig_emptykey(key)) { + ret = dst_sign(DST_SIG_MODE_INIT, key->key, &ctx, NULL, NULL); + if (ret != ISC_R_SUCCESS) + goto cleanup_algorithm; + } + + if (is_response(msg)) { + if (!dns_tsig_emptykey(key)) { + isc_buffer_putuint16(&databuf, msg->querytsig->siglen); + isc_buffer_available(&databuf, &r); + if (r.length < msg->querytsig->siglen) + return (ISC_R_NOSPACE); + memcpy(r.base, msg->querytsig->signature, + msg->querytsig->siglen); + isc_buffer_add(&databuf, msg->querytsig->siglen); + isc_buffer_used(&databuf, &r); + ret = dst_sign(DST_SIG_MODE_UPDATE, key->key, &ctx, &r, + NULL); + if (ret != ISC_R_SUCCESS) + goto cleanup_algorithm; + } + tsig->error = msg->querytsigstatus; + } + else + tsig->error = dns_rcode_noerror; + + if (tsig->error != dns_tsigerror_badtime) { + tsig->otherlen = 0; + tsig->other = NULL; + } + else { + isc_buffer_t otherbuf; + tsig->otherlen = 6; + tsig->other = (unsigned char *) isc_mem_get(mctx, 6); + if (tsig->other == NULL) { + ret = ISC_R_NOMEMORY; + goto cleanup_other; + } + isc_buffer_init(&otherbuf, tsig->other, tsig->otherlen = 6, + ISC_BUFFERTYPE_BINARY); + isc_buffer_putuint16(&otherbuf, tsig->timesigned >> 32); + isc_buffer_putuint32(&otherbuf, tsig->timesigned & 0xFFFFFFFF); + + } + if (!dns_tsig_emptykey(key)) { + unsigned char header[DNS_MESSAGE_HEADERLEN]; + isc_buffer_t headerbuf; + + isc_buffer_init(&headerbuf, header, sizeof header, + ISC_BUFFERTYPE_BINARY); + dns_message_renderheader(msg, &headerbuf); + isc_buffer_used(&headerbuf, &r); + ret = dst_sign(DST_SIG_MODE_UPDATE, key->key, &ctx, &r, NULL); + if (ret != ISC_R_SUCCESS) + goto cleanup_other; + isc_buffer_used(msg->buffer, &r); + isc_region_consume(&r, DNS_MESSAGE_HEADERLEN); + ret = dst_sign(DST_SIG_MODE_UPDATE, key->key, &ctx, &r, NULL); + if (ret != ISC_R_SUCCESS) + goto cleanup_other; + + /* Digest the name, class, ttl, alg */ + dns_name_toregion(&key->name, &r); + ret = dst_sign(DST_SIG_MODE_UPDATE, key->key, &ctx, &r, NULL); + if (ret != ISC_R_SUCCESS) + goto cleanup_other; + + isc_buffer_clear(&databuf); + isc_buffer_putuint16(&databuf, dns_rdataclass_any); + isc_buffer_putuint32(&databuf, 0); /* ttl */ + isc_buffer_used(&databuf, &r); + ret = dst_sign(DST_SIG_MODE_UPDATE, key->key, &ctx, &r, NULL); + if (ret != ISC_R_SUCCESS) + goto cleanup_other; + + dns_name_toregion(tsig->algorithm, &r); + ret = dst_sign(DST_SIG_MODE_UPDATE, key->key, &ctx, &r, NULL); + if (ret != ISC_R_SUCCESS) + goto cleanup_other; + + isc_buffer_clear(&databuf); + if (tsig->error != dns_tsigerror_badtime) { + isc_buffer_putuint16(&databuf, + tsig->timesigned >> 32); + isc_buffer_putuint32(&databuf, + tsig->timesigned & 0xFFFFFFFF); + } + else { + isc_uint64_t querysigned = msg->querytsig->timesigned; + isc_buffer_putuint16(&databuf, + querysigned >> 32); + isc_buffer_putuint32(&databuf, + querysigned & 0xFFFFFFFF); + } + isc_buffer_putuint16(&databuf, tsig->fudge); + isc_buffer_putuint16(&databuf, tsig->error); + isc_buffer_putuint16(&databuf, tsig->otherlen); + + isc_buffer_used(&databuf, &r); + ret = dst_sign(DST_SIG_MODE_UPDATE, key->key, &ctx, &r, NULL); + if (ret != ISC_R_SUCCESS) + goto cleanup_other; + + if (tsig->otherlen > 0) { + r.length = tsig->otherlen; + r.base = tsig->other; + ret = dst_sign(DST_SIG_MODE_UPDATE, key->key, &ctx, &r, + NULL); + if (ret != ISC_R_SUCCESS) + goto cleanup_other; + } + + tsig->siglen = dst_sig_size(key->key); + tsig->signature = (unsigned char *) + isc_mem_get(mctx, tsig->siglen); + if (tsig->signature == NULL) { + ret = ISC_R_NOMEMORY; + goto cleanup_other; + } + + isc_buffer_init(&sigbuf, tsig->signature, tsig->siglen, + ISC_BUFFERTYPE_BINARY); + ret = dst_sign(DST_SIG_MODE_FINAL, key->key, &ctx, NULL, + &sigbuf); + if (ret != ISC_R_SUCCESS) + goto cleanup_signature; + } + else { + tsig->siglen = 0; + tsig->signature = NULL; + } + + /* There should be a better way of accessing msg->scratchpad */ + rdata = NULL; + ret = dns_message_gettemprdata(msg, &rdata); + if (ret != ISC_R_SUCCESS) + goto cleanup_signature; + tries = 0; + dynbuf = ISC_LIST_TAIL(msg->scratchpad); + INSIST(dynbuf != NULL); + rdatabuf = dynbuf->buffer; + while (tries < 2) { + ret = dns_rdata_fromstruct(rdata, dns_rdataclass_any, + dns_rdatatype_tsig, tsig, &rdatabuf); + if (ret == ISC_R_SUCCESS) + break; + else if (ret == ISC_R_NOSPACE) { + if (++tries == 2) + return (ISC_R_NOMEMORY); + ret = isc_dynbuffer_allocate(msg->mctx, &dynbuf, 512, + ISC_BUFFERTYPE_BINARY); + if (ret != ISC_R_SUCCESS) + goto cleanup_signature; + ISC_LIST_APPEND(msg->scratchpad, dynbuf, link); + rdatabuf = dynbuf->buffer; + } + else + goto cleanup_signature; + } + + msg->tsig = tsig; + + owner = NULL; + ret = dns_message_gettempname(msg, &owner); + if (ret != ISC_R_SUCCESS) + goto cleanup_signature; + dns_name_init(owner, NULL); + dns_name_clone(&key->name, owner); + + datalist = NULL; + ret = dns_message_gettemprdatalist(msg, &datalist); + if (ret != ISC_R_SUCCESS) + goto cleanup_signature; + datalist->rdclass = dns_rdataclass_any; + datalist->type = dns_rdatatype_tsig; + datalist->ttl = 0; + ISC_LIST_INIT(datalist->rdata); + ISC_LIST_APPEND(datalist->rdata, rdata, link); + dataset = NULL; + ret = dns_message_gettemprdataset(msg, &dataset); + if (ret != ISC_R_SUCCESS) + goto cleanup_signature; + dns_rdataset_init(dataset); + dns_rdatalist_tordataset(datalist, dataset); + ISC_LIST_APPEND(owner->list, dataset, link); + dns_message_addname(msg, owner, DNS_SECTION_TSIG); + + return (ISC_R_SUCCESS); + +cleanup_signature: + if (tsig->signature != NULL) + isc_mem_put(mctx, tsig->signature, tsig->siglen); +cleanup_other: + if (tsig->other != NULL) + isc_mem_put(mctx, tsig->other, tsig->otherlen); +cleanup_algorithm: + dns_name_free(tsig->algorithm, mctx); +cleanup_struct: + msg->tsig = NULL; + isc_mem_put(mctx, tsig, sizeof(dns_rdata_any_tsig_t)); + + return (ret); +} + +isc_result_t +dns_tsig_verify(isc_buffer_t *source, dns_message_t *msg) { + dns_rdata_any_tsig_t *tsig; + isc_region_t r, source_r, header_r, sig_r; + isc_buffer_t databuf; + unsigned char data[32]; + dns_name_t *keyname; + dns_rdataset_t *dataset; + dns_rdata_t rdata; + isc_time_t now; + isc_result_t ret; + dns_tsig_key_t *tsigkey = NULL; + dst_key_t *key = NULL; + unsigned char header[DNS_MESSAGE_HEADERLEN]; + dst_context_t ctx; + isc_mem_t *mctx; + isc_uint16_t addcount, id; + + REQUIRE(source != NULL); + REQUIRE(msg != NULL); + REQUIRE(msg->tsigkey != NULL); + REQUIRE(msg->tsig == NULL); + REQUIRE(!(ISC_LIST_EMPTY(msg->sections[DNS_SECTION_TSIG]))); + if (is_response(msg)) + REQUIRE(msg->querytsig != NULL); + + mctx = msg->mctx; + + /* + * If we're here, we know the message is well formed and contains a + * TSIG record. + */ + + ret = dns_message_firstname(msg, DNS_SECTION_TSIG); + if (ret != ISC_R_SUCCESS) + return (ret); + keyname = NULL; + dns_message_currentname(msg, DNS_SECTION_TSIG, &keyname); + dataset = ISC_LIST_HEAD(keyname->list); + ret = dns_rdataset_first(dataset); + if (ret != ISC_R_SUCCESS) + return (ret); + dns_rdataset_current(dataset, &rdata); + tsig = (dns_rdata_any_tsig_t *) + isc_mem_get(mctx, sizeof(dns_rdata_any_tsig_t)); + if (tsig == NULL) + return (ISC_R_NOMEMORY); + msg->tsig = tsig; + ret = dns_rdata_tostruct(&rdata, tsig, mctx); + if (ret != ISC_R_SUCCESS) + goto cleanup_emptystruct; + + isc_buffer_used(source, &r); + memcpy(header, r.base, DNS_MESSAGE_HEADERLEN); + isc_region_consume(&r, DNS_MESSAGE_HEADERLEN); + + /* Do the key name and algorithm match that of the query? */ + if (is_response(msg) && + (!dns_name_equal(keyname, &msg->tsigkey->name) || + !dns_name_equal(tsig->algorithm, msg->querytsig->algorithm))) + { + msg->tsigstatus = dns_tsigerror_badkey; + return (DNS_R_TSIGVERIFYFAILURE); + } + + /* Find dns_tsig_key_t based on keyname */ + ret = dns_tsig_findkey(&tsigkey, keyname, tsig->algorithm); + if (ret != ISC_R_SUCCESS) { + msg->tsigstatus = dns_tsigerror_badkey; + msg->tsigkey = NULL; + /* + * this key must be deleted later - an empty key can be found + * by calling dns_tsig_emptykey() + */ + ret = dns_tsig_key_create(keyname, tsig->algorithm, NULL, 0, + mctx, &msg->tsigkey); + if (ret != ISC_R_SUCCESS) + goto cleanup_struct; + return (DNS_R_TSIGVERIFYFAILURE); + } + + msg->tsigkey = tsigkey; + key = tsigkey->key; + + /* Is the time ok? */ + ret = isc_time_now(&now); + if (ret != ISC_R_SUCCESS) + goto cleanup_key; + if (abs(now.seconds - tsig->timesigned) > tsig->fudge) { + msg->tsigstatus = dns_tsigerror_badtime; + return (DNS_R_TSIGVERIFYFAILURE); + } + + if (tsig->siglen > 0) { + sig_r.base = tsig->signature; + sig_r.length = tsig->siglen; + + ret = dst_verify(DST_SIG_MODE_INIT, key, &ctx, NULL, &sig_r); + if (ret != ISC_R_SUCCESS) + goto cleanup_key; + + if (is_response(msg)) { + isc_buffer_init(&databuf, data, sizeof(data), + ISC_BUFFERTYPE_BINARY); + isc_buffer_putuint16(&databuf, msg->querytsig->siglen); + isc_buffer_used(&databuf, &r); + ret = dst_verify(DST_SIG_MODE_UPDATE, key, &ctx, &r, + NULL); + if (ret != ISC_R_SUCCESS) + goto cleanup_key; + if (msg->querytsig->siglen > 0) { + r.length = msg->querytsig->siglen; + r.base = msg->querytsig->signature; + ret = dst_verify(DST_SIG_MODE_UPDATE, key, + &ctx, &r, NULL); + if (ret != ISC_R_SUCCESS) + goto cleanup_key; + } + } + + /* Decrement the additional field counter */ + memcpy(&addcount, &header[DNS_MESSAGE_HEADERLEN - 2], 2); + addcount = htons(ntohs(addcount) - 1); + memcpy(&header[DNS_MESSAGE_HEADERLEN - 2], &addcount, 2); + + /* Put in the original id */ + id = htons(tsig->originalid); + memcpy(&header[0], &id, 2); + + /* Digest the modified header */ + header_r.base = (unsigned char *) header; + header_r.length = DNS_MESSAGE_HEADERLEN; + ret = dst_verify(DST_SIG_MODE_UPDATE, key, &ctx, &header_r, + &sig_r); + if (ret != ISC_R_SUCCESS) + goto cleanup_key; + + /* Digest all non-TSIG records. */ + isc_buffer_used(source, &source_r); + r.base = source_r.base + DNS_MESSAGE_HEADERLEN; + r.length = msg->tsigstart - DNS_MESSAGE_HEADERLEN; + ret = dst_verify(DST_SIG_MODE_UPDATE, key, &ctx, &r, &sig_r); + if (ret != ISC_R_SUCCESS) + goto cleanup_key; + + /* Digest the key name */ + dns_name_toregion(&tsigkey->name, &r); + ret = dst_verify(DST_SIG_MODE_UPDATE, key, &ctx, &r, &sig_r); + if (ret != ISC_R_SUCCESS) + goto cleanup_key; + + isc_buffer_init(&databuf, data, sizeof(data), + ISC_BUFFERTYPE_BINARY); + isc_buffer_putuint16(&databuf, tsig->common.rdclass); + isc_buffer_putuint32(&databuf, dataset->ttl); + isc_buffer_used(&databuf, &r); + ret = dst_verify(DST_SIG_MODE_UPDATE, key, &ctx, &r, &sig_r); + if (ret != ISC_R_SUCCESS) + goto cleanup_key; + + /* Digest the key algorithm */ + dns_name_toregion(&tsigkey->algorithm, &r); + ret = dst_verify(DST_SIG_MODE_UPDATE, key, &ctx, &r, &sig_r); + if (ret != ISC_R_SUCCESS) + goto cleanup_key; + + isc_buffer_clear(&databuf); + isc_buffer_putuint16(&databuf, tsig->timesigned >> 32); + isc_buffer_putuint32(&databuf, tsig->timesigned & 0xFFFFFFFF); + isc_buffer_putuint16(&databuf, tsig->fudge); + isc_buffer_putuint16(&databuf, tsig->error); + isc_buffer_putuint16(&databuf, tsig->otherlen); + isc_buffer_used(&databuf, &r); + ret = dst_verify(DST_SIG_MODE_UPDATE, key, &ctx, &r, &sig_r); + + if (tsig->otherlen > 0) { + r.base = tsig->other; + r.length = tsig->otherlen; + ret = dst_verify(DST_SIG_MODE_UPDATE, key, &ctx, &r, + &sig_r); + if (ret != ISC_R_SUCCESS) + goto cleanup_key; + } + + ret = dst_verify(DST_SIG_MODE_FINAL, key, &ctx, NULL, &sig_r); + if (ret == DST_R_VERIFY_FINAL_FAILURE) { + msg->tsigstatus = dns_tsigerror_badsig; + return (DNS_R_TSIGVERIFYFAILURE); + } + else if (ret != ISC_R_SUCCESS) + goto cleanup_key; + } + else if (tsig->error != dns_tsigerror_badsig && + tsig->error != dns_tsigerror_badkey) + { + msg->tsigstatus = dns_tsigerror_badsig; + return (DNS_R_TSIGVERIFYFAILURE); + } + + msg->tsigstatus = dns_rcode_noerror; + + if (tsig->error != dns_rcode_noerror) { + if (is_response(msg)) { + /* XXXBEW Log a message */ + return (ISC_R_SUCCESS); + } + else + return (DNS_R_TSIGERRORSET); + } + + return (ISC_R_SUCCESS); + +cleanup_key: + if (dns_tsig_emptykey(msg->tsigkey)) { + dns_tsig_key_free(&msg->tsigkey); + msg->tsigkey = NULL; + } +cleanup_struct: + dns_rdata_freestruct(tsig); +cleanup_emptystruct: + msg->tsig = NULL; + isc_mem_put(mctx, tsig, sizeof(dns_rdata_any_tsig_t)); + return (ret); +} + +isc_result_t +dns_tsig_findkey(dns_tsig_key_t **tsigkey, dns_name_t *name, + dns_name_t *algorithm) +{ + dns_tsig_key_t *key; + + REQUIRE(tsigkey != NULL); + REQUIRE(name != NULL); + REQUIRE(algorithm != NULL); + + isc_rwlock_lock(&tsiglock, isc_rwlocktype_read); + key = ISC_LIST_HEAD(tsigkeys); + while (key != NULL) { + if (dns_name_equal(&key->name, name) && + dns_name_equal(&key->algorithm, algorithm)) + { + *tsigkey = key; + isc_rwlock_unlock(&tsiglock, isc_rwlocktype_read); + return (ISC_R_SUCCESS); + } + key = ISC_LIST_NEXT(key, link); + } + isc_rwlock_unlock(&tsiglock, isc_rwlocktype_read); + *tsigkey = NULL; + return (ISC_R_NOTFOUND); +} + +isc_result_t +dns_tsig_init(isc_mem_t *mctx) { + isc_buffer_t hmacsrc, namebuf; + isc_result_t ret; + dns_name_t hmac_name; + unsigned char data[32]; + + ret = isc_rwlock_init(&tsiglock, 0, 0); + if (ret != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_rwlock_init() failed: %s", + isc_result_totext(ret)); + return (DNS_R_UNEXPECTED); + } + + ISC_LIST_INIT(tsigkeys); + isc_buffer_init(&hmacsrc, DNS_TSIG_HMACMD5, + strlen(DNS_TSIG_HMACMD5), ISC_BUFFERTYPE_TEXT); + isc_buffer_add(&hmacsrc, strlen(DNS_TSIG_HMACMD5)); + isc_buffer_init(&namebuf, data, sizeof(data), ISC_BUFFERTYPE_BINARY); + + dns_name_init(&hmac_name, NULL); + ret = dns_name_fromtext(&hmac_name, &hmacsrc, NULL, ISC_TRUE, &namebuf); + if (ret != ISC_R_SUCCESS) + return (ret); + + dns_tsig_hmacmd5_name = isc_mem_get(mctx, sizeof(dns_name_t)); + if (dns_tsig_hmacmd5_name == NULL) + return (ISC_R_NOMEMORY); + dns_name_init(dns_tsig_hmacmd5_name, NULL); + ret = dns_name_dup(&hmac_name, mctx, dns_tsig_hmacmd5_name); + if (ret != ISC_R_SUCCESS) { + isc_mem_put(mctx, dns_tsig_hmacmd5_name, sizeof(dns_name_t)); + return (ret); + } + + tsig_mctx = mctx; + + return (ISC_R_SUCCESS); +} + +void +dns_tsig_destroy() { + while (!ISC_LIST_EMPTY(tsigkeys)) { + dns_tsig_key_t *key = ISC_LIST_HEAD(tsigkeys); + dns_tsig_key_free(&key); + } + dns_name_free(dns_tsig_hmacmd5_name, tsig_mctx); + isc_mem_put(tsig_mctx, dns_tsig_hmacmd5_name, sizeof(dns_name_t)); +}