606 lines
16 KiB
C
606 lines
16 KiB
C
#ifndef lint
|
|
static char *rcsid = "$Id: msgtrans.c,v 1.24 2001/02/21 05:54:17 m-kasahr Exp $";
|
|
#endif
|
|
|
|
/*
|
|
* Copyright (c) 2000,2001 Japan Network Information Center.
|
|
* All rights reserved.
|
|
*
|
|
* By using this file, you agree to the terms and conditions set forth bellow.
|
|
*
|
|
* LICENSE TERMS AND CONDITIONS
|
|
*
|
|
* The following License Terms and Conditions apply, unless a different
|
|
* license is obtained from Japan Network Information Center ("JPNIC"),
|
|
* a Japanese association, Fuundo Bldg., 1-2 Kanda Ogawamachi, Chiyoda-ku,
|
|
* Tokyo, Japan.
|
|
*
|
|
* 1. Use, Modification and Redistribution (including distribution of any
|
|
* modified or derived work) in source and/or binary forms is permitted
|
|
* under this License Terms and Conditions.
|
|
*
|
|
* 2. Redistribution of source code must retain the copyright notices as they
|
|
* appear in each source code file, this License Terms and Conditions.
|
|
*
|
|
* 3. Redistribution in binary form must reproduce the Copyright Notice,
|
|
* this License Terms and Conditions, in the documentation and/or other
|
|
* materials provided with the distribution. For the purposes of binary
|
|
* distribution the "Copyright Notice" refers to the following language:
|
|
* "Copyright (c) Japan Network Information Center. All rights reserved."
|
|
*
|
|
* 4. Neither the name of JPNIC may be used to endorse or promote products
|
|
* derived from this Software without specific prior written approval of
|
|
* JPNIC.
|
|
*
|
|
* 5. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY JPNIC
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JPNIC BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
|
*
|
|
* 6. Indemnification by Licensee
|
|
* Any person or entities using and/or redistributing this Software under
|
|
* this License Terms and Conditions shall defend indemnify and hold
|
|
* harmless JPNIC from and against any and all judgements damages,
|
|
* expenses, settlement liabilities, cost and other liabilities of any
|
|
* kind as a result of use and redistribution of this Software or any
|
|
* claim, suite, action, litigation or proceeding by any third party
|
|
* arising out of or relates to this License Terms and Conditions.
|
|
*
|
|
* 7. Governing Law, Jurisdiction and Venue
|
|
* This License Terms and Conditions shall be governed by and and
|
|
* construed in accordance with the law of Japan. Any person or entities
|
|
* using and/or redistributing this Software under this License Terms and
|
|
* Conditions hereby agrees and consent to the personal and exclusive
|
|
* jurisdiction and venue of Tokyo District Court of Japan.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#ifdef WIN32
|
|
#include <windows.h>
|
|
#include <winsock.h>
|
|
#else /* for normal systems */
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <netinet/in.h>
|
|
#ifdef HAVE_ARPA_NAMESER_H
|
|
#include <arpa/nameser.h>
|
|
#endif
|
|
#ifdef HAVE_RESOLV_H
|
|
#include <resolv.h>
|
|
#endif
|
|
#endif
|
|
|
|
#include <mdn/result.h>
|
|
#include <mdn/assert.h>
|
|
#include <mdn/logmacro.h>
|
|
#include <mdn/converter.h>
|
|
#include <mdn/normalizer.h>
|
|
#include <mdn/res.h>
|
|
#include <mdn/msgheader.h>
|
|
#include <mdn/msgtrans.h>
|
|
#include <mdn/dn.h>
|
|
#include <mdn/debug.h>
|
|
|
|
/*
|
|
* Name translation instructions.
|
|
*
|
|
* For query, perform
|
|
* 1. local encoding to UTF-8 conversion
|
|
* 2. delimiter mapping
|
|
* 3. local mapping
|
|
* 4. nameprep
|
|
* 5. UTF-8 to IDN encoding conversion
|
|
*
|
|
* For reply,
|
|
* 1. IDN encoding to UTF-8 conversion
|
|
* 2. UTF-8 to local encoding conversion
|
|
*
|
|
* See mdn/res.h for the mnemonic.
|
|
*/
|
|
#define INSN_QUERY "ldMNI"
|
|
#define INSN_REPLY "iL"
|
|
|
|
#define DNS_HEADER_SIZE 12
|
|
#define DNAME_SIZE 512
|
|
#define RRFORMAT_HASH_SIZE 47
|
|
|
|
/*
|
|
* DNS opcodes.
|
|
*/
|
|
enum {
|
|
opcode_query = 0,
|
|
opcode_iquery = 1,
|
|
opcode_status = 2,
|
|
opcode_notify = 4,
|
|
opcode_update = 5
|
|
};
|
|
|
|
/*
|
|
* Resource record types.
|
|
*/
|
|
enum {
|
|
rrtype_A = 1,
|
|
rrtype_NS = 2,
|
|
rrtype_MD = 3,
|
|
rrtype_MF = 4,
|
|
rrtype_CNAME = 5,
|
|
rrtype_SOA = 6,
|
|
rrtype_MB = 7,
|
|
rrtype_MG = 8,
|
|
rrtype_MR = 9,
|
|
rrtype_NULL = 10,
|
|
rrtype_WKS = 11,
|
|
rrtype_PTR = 12,
|
|
rrtype_HINFO = 13,
|
|
rrtype_MINFO = 14,
|
|
rrtype_MX = 15,
|
|
rrtype_TXT = 16,
|
|
rrtype_RP = 17,
|
|
rrtype_AFSDB = 18,
|
|
rrtype_X25 = 19,
|
|
rrtype_ISDN = 20,
|
|
rrtype_RT = 21,
|
|
rrtype_AAAA = 28
|
|
};
|
|
|
|
/*
|
|
* Resource record classes.
|
|
*/
|
|
enum {
|
|
rrclass_IN = 1,
|
|
rrclass_CS = 2,
|
|
rrclass_CH = 3,
|
|
rrclass_ANY = 255
|
|
};
|
|
|
|
typedef struct msgtrans_ctx {
|
|
char *insn; /* name translation instruction */
|
|
const char *in; /* input message */
|
|
size_t in_len; /* length of it */
|
|
const char *in_ptr; /* current pointer */
|
|
size_t in_remain; /* # of remaining octets */
|
|
char *out; /* output (translated) message */
|
|
char *out_ptr; /* current pointer */
|
|
size_t out_remain; /* # of remaining (available) octets */
|
|
mdn__dn_t dn_ctx; /* for compression */
|
|
mdn_resconf_t conf; /* translation parameters */
|
|
} msgtrans_ctx_t;
|
|
|
|
static struct rrformat {
|
|
unsigned int type; /* RR type */
|
|
unsigned int class; /* RR class */
|
|
const char *format; /* RDATA format */
|
|
struct rrformat *next; /* hash chain */
|
|
} rrformats[] = {
|
|
{ rrtype_CNAME, rrclass_ANY, "D" },
|
|
{ rrtype_HINFO, rrclass_ANY, "TT" },
|
|
{ rrtype_MB, rrclass_ANY, "D" },
|
|
{ rrtype_MD, rrclass_ANY, "D" },
|
|
{ rrtype_MF, rrclass_ANY, "D" },
|
|
{ rrtype_MG, rrclass_ANY, "D" },
|
|
{ rrtype_MINFO, rrclass_ANY, "DD" },
|
|
{ rrtype_MR, rrclass_ANY, "D" },
|
|
{ rrtype_MX, rrclass_ANY, "SD" },
|
|
{ rrtype_NULL, rrclass_ANY, "R" },
|
|
{ rrtype_NS, rrclass_ANY, "D" },
|
|
{ rrtype_PTR, rrclass_ANY, "D" },
|
|
{ rrtype_SOA, rrclass_ANY, "DDLLLLL" },
|
|
{ rrtype_TXT, rrclass_ANY, "T" },
|
|
{ rrtype_A, rrclass_IN, "L" },
|
|
{ rrtype_WKS, rrclass_IN, "LCR" },
|
|
{ rrtype_RP, rrclass_ANY, "DD" },
|
|
{ rrtype_AFSDB, rrclass_ANY, "SD" },
|
|
{ rrtype_X25, rrclass_ANY, "T" },
|
|
{ rrtype_ISDN, rrclass_ANY, "TT" },
|
|
{ rrtype_RT, rrclass_ANY, "SD" },
|
|
{ rrtype_AAAA, rrclass_IN, "H" },
|
|
{ 0, 0, NULL },
|
|
};
|
|
static struct rrformat *rrformathash[RRFORMAT_HASH_SIZE];
|
|
|
|
static mdn_result_t copy_header(msgtrans_ctx_t *ctx);
|
|
static mdn_result_t translate_question(msgtrans_ctx_t *ctx);
|
|
static mdn_result_t translate_rr(msgtrans_ctx_t *ctx);
|
|
static mdn_result_t translate_rdata(msgtrans_ctx_t *ctx,
|
|
unsigned int rr_type,
|
|
unsigned int rr_class,
|
|
unsigned int rr_length);
|
|
static const char *rdata_format(unsigned int rr_type,
|
|
unsigned int rr_class);
|
|
static mdn_result_t translate_domain(msgtrans_ctx_t *ctx);
|
|
static mdn_result_t get_domainname(msgtrans_ctx_t *ctx, char *buf,
|
|
size_t bufsize);
|
|
static mdn_result_t put_domainname(msgtrans_ctx_t *ctx, char *name);
|
|
static void ctx_init(msgtrans_ctx_t *ctx,
|
|
mdn_resconf_t conf, mdn_msgheader_t *header,
|
|
const char *msg, size_t msglen,
|
|
char *outbuf, size_t outbufsize);
|
|
static mdn_result_t copy_rest(msgtrans_ctx_t *ctx);
|
|
static mdn_result_t copy_message(msgtrans_ctx_t *ctx, size_t len);
|
|
static size_t output_length(msgtrans_ctx_t *ctx);
|
|
static void dump_message(const char *title, const char *p,
|
|
size_t length);
|
|
|
|
|
|
mdn_result_t
|
|
mdn_msgtrans_translate(mdn_resconf_t conf,
|
|
const char *msg, size_t msglen,
|
|
char *outbuf, size_t outbufsize, size_t *outmsglenp)
|
|
{
|
|
mdn_result_t r;
|
|
msgtrans_ctx_t ctx;
|
|
mdn_msgheader_t header;
|
|
int i, n;
|
|
|
|
assert(conf != NULL && msg != NULL &&
|
|
outbuf != NULL && outbufsize > 0 && outmsglenp != NULL);
|
|
|
|
TRACE(("mdn_msgtrans_translate(msg=<%s>,msglen=%d)\n",
|
|
mdn_debug_hexdata(msg, msglen, 64), msglen));
|
|
|
|
if (LOGLEVEL >= mdn_log_level_dump)
|
|
dump_message("before translation", msg, msglen);
|
|
|
|
/*
|
|
* Check message length.
|
|
*/
|
|
if (msglen < DNS_HEADER_SIZE) {
|
|
INFO(("mdn_msgtrans_translate: incoming packet too short "
|
|
"(%d octets)\n", msglen));
|
|
return (mdn_invalid_message);
|
|
}
|
|
|
|
/*
|
|
* Parse message header.
|
|
*/
|
|
if ((r = mdn_msgheader_parse(msg, msglen, &header)) != mdn_success) {
|
|
WARNING(("mdn_msgtrans_translate: message header "
|
|
"parsing failed: %s\n",
|
|
mdn_result_tostring(r)));
|
|
return (r);
|
|
}
|
|
|
|
/*
|
|
* Create translation context.
|
|
*/
|
|
ctx_init(&ctx, conf, &header, msg, msglen, outbuf, outbufsize);
|
|
|
|
/*
|
|
* We handle only query, notify and update messages.
|
|
* Do not process others.
|
|
*/
|
|
switch (header.opcode) {
|
|
case opcode_query:
|
|
case opcode_notify:
|
|
case opcode_update:
|
|
break;
|
|
default:
|
|
INFO(("mdn_msgtrans_translate: pass through message "
|
|
"whose opcode is %d", header.opcode));
|
|
if ((r = copy_rest(&ctx)) == mdn_success)
|
|
*outmsglenp = output_length(&ctx);
|
|
return (mdn_success);
|
|
}
|
|
|
|
/*
|
|
* Copy header part verbatim.
|
|
*/
|
|
(void)copy_header(&ctx);
|
|
|
|
/*
|
|
* Parse question/zone section.
|
|
*/
|
|
n = header.qdcount;
|
|
for (i = 0; i < n; i++) {
|
|
if ((r = translate_question(&ctx)) != mdn_success)
|
|
return (r);
|
|
}
|
|
|
|
/*
|
|
* Translate other sections.
|
|
*/
|
|
n = header.ancount + header.nscount + header.arcount;
|
|
for (i = 0; i < n; i++) {
|
|
if ((r = translate_rr(&ctx)) != mdn_success)
|
|
return (r);
|
|
}
|
|
|
|
if (LOGLEVEL >= mdn_log_level_dump)
|
|
dump_message("after translation",
|
|
ctx.out, output_length(&ctx));
|
|
|
|
/*
|
|
* Is there anything left out?
|
|
*/
|
|
if (ctx.in_remain != 0) {
|
|
WARNING(("mdn_msgtrans_translate: garbage at the end "
|
|
"(%d octets)\n", ctx.in_remain));
|
|
/* don't consider this as an error. */
|
|
/* return (mdn_invalid_message); */
|
|
}
|
|
|
|
*outmsglenp = output_length(&ctx);
|
|
return (mdn_success);
|
|
}
|
|
|
|
static mdn_result_t
|
|
copy_header(msgtrans_ctx_t *ctx) {
|
|
return (copy_message(ctx, DNS_HEADER_SIZE));
|
|
}
|
|
|
|
static mdn_result_t
|
|
translate_question(msgtrans_ctx_t *ctx) {
|
|
mdn_result_t r;
|
|
char qname[DNAME_SIZE], qname_translated[DNAME_SIZE];
|
|
|
|
/* Get QNAME. */
|
|
if ((r = get_domainname(ctx, qname, sizeof(qname))) != mdn_success)
|
|
return (r);
|
|
|
|
/* Translate QNAME. */
|
|
r = mdn_res_nameconv(ctx->conf, ctx->insn, qname,
|
|
qname_translated, sizeof(qname_translated));
|
|
if (r != mdn_success)
|
|
return (r);
|
|
|
|
if ((r = put_domainname(ctx, qname_translated)) != mdn_success)
|
|
return (r);
|
|
|
|
/* Copy QTYPE and QCLASS */
|
|
return (copy_message(ctx, 4));
|
|
}
|
|
|
|
static mdn_result_t
|
|
translate_rr(msgtrans_ctx_t *ctx) {
|
|
mdn_result_t r;
|
|
unsigned char *p;
|
|
unsigned int rr_type, rr_class, rr_length;
|
|
char dname[DNAME_SIZE], dname_translated[DNAME_SIZE];
|
|
size_t length_before;
|
|
|
|
/* Get NAME. */
|
|
if ((r = get_domainname(ctx, dname, sizeof(dname))) != mdn_success)
|
|
return (r);
|
|
|
|
/* Translate NAME. */
|
|
r = mdn_res_nameconv(ctx->conf, ctx->insn, dname,
|
|
dname_translated, sizeof(dname_translated));
|
|
if (r != mdn_success)
|
|
return (r);
|
|
|
|
if ((r = put_domainname(ctx, dname_translated)) != mdn_success)
|
|
return (r);
|
|
|
|
/* Get TYPE and CLASS */
|
|
if (ctx->in_remain < 10)
|
|
return (mdn_invalid_message);
|
|
p = (unsigned char *)ctx->in_ptr;
|
|
#define GET16(off) ((p[off]<<8)+p[(off)+1])
|
|
rr_type = GET16(0);
|
|
rr_class = GET16(2);
|
|
rr_length = GET16(8);
|
|
#undef GET16
|
|
|
|
/* Copy TYPE, CLASS, TTL and RDLENGTH. */
|
|
if ((r = copy_message(ctx, 10)) != mdn_success)
|
|
return (r);
|
|
|
|
/* Remember the current output length. */
|
|
length_before = output_length(ctx);
|
|
|
|
/* Translate RDATA. */
|
|
r = translate_rdata(ctx, rr_type, rr_class, rr_length);
|
|
|
|
if (r == mdn_success) {
|
|
/* Reset RDLENGTH */
|
|
rr_length = output_length(ctx) - length_before;
|
|
ctx->out[length_before - 2] = (rr_length >> 8) & 0xff;
|
|
ctx->out[length_before - 1] = rr_length & 0xff;
|
|
}
|
|
|
|
return (r);
|
|
}
|
|
|
|
static mdn_result_t
|
|
translate_rdata(msgtrans_ctx_t *ctx, unsigned int rr_type,
|
|
unsigned int rr_class, unsigned int rr_length)
|
|
{
|
|
const char *format;
|
|
int c;
|
|
|
|
if ((format = rdata_format(rr_type, rr_class)) == NULL) {
|
|
INFO(("mdn_msgtrans: unknown resource record type %d "
|
|
"pass through\n", rr_type));
|
|
return (copy_message(ctx, rr_length));
|
|
}
|
|
|
|
while ((c = *format++) != '\0') {
|
|
int copy_len;
|
|
mdn_result_t r;
|
|
|
|
switch (c) {
|
|
case 'D': /* domain name */
|
|
{
|
|
int remain_org = ctx->in_remain;
|
|
|
|
if ((r = translate_domain(ctx)) != mdn_success)
|
|
return (r);
|
|
rr_length -= remain_org - ctx->in_remain;
|
|
continue;
|
|
}
|
|
case 'T': /* character string */
|
|
copy_len = *((unsigned char *)ctx->in_ptr) + 1;
|
|
break;
|
|
case 'C': /* 1-octet value */
|
|
copy_len = 1;
|
|
break;
|
|
case 'S': /* 2-octet value */
|
|
copy_len = 2;
|
|
break;
|
|
case 'L': /* 4-octet value */
|
|
copy_len = 4;
|
|
break;
|
|
case 'H': /* 16-octet value (AAAA) */
|
|
copy_len = 16;
|
|
break;
|
|
case 'R': /* the rest */
|
|
copy_len = rr_length;
|
|
break;
|
|
default:
|
|
copy_len = 0; /* for gcc -Wall */
|
|
FATAL(("mdn_msgtrans: internal error -- "
|
|
"unknown format character %c", c));
|
|
/* NOTREACHED */
|
|
break;
|
|
}
|
|
if ((r = copy_message(ctx, copy_len)) != mdn_success)
|
|
return (r);
|
|
rr_length -= copy_len;
|
|
}
|
|
return (mdn_success);
|
|
}
|
|
|
|
static const char *
|
|
rdata_format(unsigned int rr_type, unsigned int rr_class) {
|
|
static int initialized;
|
|
struct rrformat *rp;
|
|
int h;
|
|
|
|
if (!initialized) {
|
|
/*
|
|
* Build hash table.
|
|
*/
|
|
for (rp = rrformats; rp->format != NULL; rp++) {
|
|
h = rp->type % RRFORMAT_HASH_SIZE;
|
|
rp->next = rrformathash[h];
|
|
rrformathash[h] = rp;
|
|
}
|
|
initialized = 1;
|
|
}
|
|
|
|
/*
|
|
* Find the element with the specified type and class.
|
|
*/
|
|
h = rr_type % RRFORMAT_HASH_SIZE;
|
|
for (rp = rrformathash[h]; rp != NULL; rp = rp->next) {
|
|
if (rp->type == rr_type &&
|
|
(rp->class == rr_class || rp->class == rrclass_ANY))
|
|
return (rp->format);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
static mdn_result_t
|
|
translate_domain(msgtrans_ctx_t *ctx) {
|
|
mdn_result_t r;
|
|
char dname[DNAME_SIZE], dname_translated[DNAME_SIZE];
|
|
|
|
/* Get NAME. */
|
|
if ((r = get_domainname(ctx, dname, sizeof(dname))) != mdn_success)
|
|
return (r);
|
|
|
|
/* Translate NAME. */
|
|
r = mdn_res_nameconv(ctx->conf, ctx->insn, dname,
|
|
dname_translated, sizeof(dname_translated));
|
|
if (r != mdn_success)
|
|
return (r);
|
|
|
|
if ((r = put_domainname(ctx, dname_translated)) != mdn_success)
|
|
return (r);
|
|
|
|
return (mdn_success);
|
|
}
|
|
|
|
static mdn_result_t
|
|
get_domainname(msgtrans_ctx_t *ctx, char *buf, size_t bufsize) {
|
|
mdn_result_t r;
|
|
size_t n;
|
|
|
|
r = mdn__dn_expand(ctx->in, ctx->in_len, ctx->in_ptr,
|
|
buf, bufsize, &n);
|
|
if (r == mdn_success) {
|
|
ctx->in_ptr += n;
|
|
ctx->in_remain -= n;
|
|
}
|
|
return (r);
|
|
}
|
|
|
|
static mdn_result_t
|
|
put_domainname(msgtrans_ctx_t *ctx, char *name) {
|
|
mdn_result_t r;
|
|
size_t n;
|
|
|
|
r = mdn__dn_compress(name, ctx->out_ptr, ctx->out_remain,
|
|
&ctx->dn_ctx, &n);
|
|
if (r == mdn_success) {
|
|
ctx->out_ptr += n;
|
|
ctx->out_remain -= n;
|
|
}
|
|
return (r);
|
|
}
|
|
|
|
static void
|
|
ctx_init(msgtrans_ctx_t *ctx, mdn_resconf_t conf, mdn_msgheader_t *header,
|
|
const char *msg, size_t msglen, char *outbuf, size_t outbufsize)
|
|
{
|
|
ctx->insn = (header->qr == 0) ? INSN_QUERY : INSN_REPLY;
|
|
ctx->in = ctx->in_ptr = msg;
|
|
ctx->in_len = ctx->in_remain = msglen;
|
|
ctx->out = ctx->out_ptr = outbuf;
|
|
ctx->out_remain = outbufsize;
|
|
mdn__dn_initcompress(&ctx->dn_ctx, outbuf);
|
|
ctx->conf = conf;
|
|
}
|
|
|
|
static mdn_result_t
|
|
copy_rest(msgtrans_ctx_t *ctx) {
|
|
return (copy_message(ctx, ctx->in_remain));
|
|
}
|
|
|
|
static mdn_result_t
|
|
copy_message(msgtrans_ctx_t *ctx, size_t len) {
|
|
assert(ctx != NULL);
|
|
|
|
if (ctx->in_remain < len)
|
|
return (mdn_invalid_message);
|
|
|
|
if (ctx->out_remain < len)
|
|
return (mdn_buffer_overflow);
|
|
|
|
(void)memcpy(ctx->out_ptr, ctx->in_ptr, len);
|
|
|
|
ctx->in_ptr += len;
|
|
ctx->in_remain -= len;
|
|
ctx->out_ptr += len;
|
|
ctx->out_remain -= len;
|
|
|
|
return (mdn_success);
|
|
}
|
|
|
|
static size_t
|
|
output_length(msgtrans_ctx_t *ctx) {
|
|
return (ctx->out_ptr - ctx->out);
|
|
}
|
|
|
|
static void
|
|
dump_message(const char *title, const char *p, size_t length) {
|
|
DUMP(("message (%s): length %d\n", title, length));
|
|
while (length > 0) {
|
|
int len = length < 16 ? length : 16;
|
|
DUMP((" %s\n", mdn_debug_hexdata(p, len, 16)));
|
|
p += len;
|
|
length -= len;
|
|
}
|
|
}
|