This commit is contained in:
Simon Kelley
2013-12-31 13:50:39 +00:00
parent 963c380d13
commit c3e0b9b6e7
7 changed files with 413 additions and 34 deletions

View File

@@ -26,7 +26,7 @@ static union bigname *big_free = NULL;
static int bignames_left, hash_size; static int bignames_left, hash_size;
static int uid = 1; static int uid = 1;
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
static struct keydata *keyblock_free = NULL; static struct blockdata *keyblock_free = NULL;
#endif #endif
/* type->string mapping: this is also used by the name-hash function as a mixing table. */ /* type->string mapping: this is also used by the name-hash function as a mixing table. */
@@ -198,7 +198,7 @@ static void cache_free(struct crec *crecp)
} }
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
else if (crecp->flags & (F_DNSKEY | F_DS)) else if (crecp->flags & (F_DNSKEY | F_DS))
keydata_free(crecp->addr.key.keydata); blockdata_free(crecp->addr.key.keydata);
#endif #endif
} }
@@ -1361,10 +1361,10 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
} }
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
struct keydata *keydata_alloc(char *data, size_t len) struct blockdata *blockdata_alloc(char *data, size_t len)
{ {
struct keydata *block, *ret = NULL; struct blockdata *block, *ret = NULL;
struct keydata **prev = &ret; struct blockdata **prev = &ret;
size_t blen; size_t blen;
while (len > 0) while (len > 0)
@@ -1375,12 +1375,12 @@ struct keydata *keydata_alloc(char *data, size_t len)
keyblock_free = block->next; keyblock_free = block->next;
} }
else else
block = whine_malloc(sizeof(struct keydata)); block = whine_malloc(sizeof(struct blockdata));
if (!block) if (!block)
{ {
/* failed to alloc, free partial chain */ /* failed to alloc, free partial chain */
keydata_free(ret); blockdata_free(ret);
return NULL; return NULL;
} }
@@ -1396,7 +1396,7 @@ struct keydata *keydata_alloc(char *data, size_t len)
return ret; return ret;
} }
size_t keydata_walk(struct keydata **key, unsigned char **p, size_t cnt) size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt)
{ {
if (*p == NULL) if (*p == NULL)
*p = (*key)->key; *p = (*key)->key;
@@ -1411,9 +1411,9 @@ size_t keydata_walk(struct keydata **key, unsigned char **p, size_t cnt)
return MIN(cnt, (*key)->key + KEYBLOCK_LEN - (*p)); return MIN(cnt, (*key)->key + KEYBLOCK_LEN - (*p));
} }
void keydata_free(struct keydata *blocks) void blockdata_free(struct blockdata *blocks)
{ {
struct keydata *tmp; struct blockdata *tmp;
if (blocks) if (blocks)
{ {

View File

@@ -140,3 +140,9 @@ struct dns_header {
GETLONG(var, ptr); \ GETLONG(var, ptr); \
(len) -= 4; \ (len) -= 4; \
} while (0) } while (0)
#define CHECK_LEN(header, pp, plen, len) \
((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen))
#define ADD_RDLEN(header, pp, plen, len) \
(!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1))

View File

@@ -81,8 +81,14 @@ int main (int argc, char **argv)
umask(022); /* known umask, create leases and pid files as 0644 */ umask(022); /* known umask, create leases and pid files as 0644 */
read_opts(argc, argv, compile_opts); read_opts(argc, argv, compile_opts);
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID)) if (option_bool(OPT_DNSSEC_VALID))
if (daemon->doctors) exit(1); /* TODO */ if (daemon->doctors) exit(1); /* TODO */
daemon->keyname = safe_malloc(MAXDNAME);
#endif
if (daemon->edns_pktsz < PACKETSZ) if (daemon->edns_pktsz < PACKETSZ)
daemon->edns_pktsz = option_bool(OPT_DNSSEC_VALID) ? EDNS_PKTSZ : PACKETSZ; daemon->edns_pktsz = option_bool(OPT_DNSSEC_VALID) ? EDNS_PKTSZ : PACKETSZ;
daemon->packet_buff_sz = daemon->edns_pktsz > DNSMASQ_PACKETSZ ? daemon->packet_buff_sz = daemon->edns_pktsz > DNSMASQ_PACKETSZ ?

View File

@@ -335,8 +335,8 @@ union bigname {
union bigname *next; /* freelist */ union bigname *next; /* freelist */
}; };
struct keydata { struct blockdata {
struct keydata *next; struct blockdata *next;
unsigned char key[KEYBLOCK_LEN]; unsigned char key[KEYBLOCK_LEN];
}; };
@@ -528,6 +528,7 @@ struct frec {
unsigned int crc; unsigned int crc;
time_t time; time_t time;
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
int class;
struct blockdata *stash; /* Saved reply, whilst we validate */ struct blockdata *stash; /* Saved reply, whilst we validate */
size_t stash_len; size_t stash_len;
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
@@ -900,6 +901,9 @@ extern struct daemon {
char *packet; /* packet buffer */ char *packet; /* packet buffer */
int packet_buff_sz; /* size of above */ int packet_buff_sz; /* size of above */
char *namebuff; /* MAXDNAME size buffer */ char *namebuff; /* MAXDNAME size buffer */
#ifdef HAVE_DNSSEC
char *keyname; /* MAXDNAME size buffer */
#endif
unsigned int local_answer, queries_forwarded, auth_answer; unsigned int local_answer, queries_forwarded, auth_answer;
struct frec *frec_list; struct frec *frec_list;
struct serverfd *sfds; struct serverfd *sfds;
@@ -1030,7 +1034,11 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
#endif #endif
/* dnssec.c */ /* dnssec.c */
int dnssec_validate(int flags, struct dns_header *header, size_t plen); size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type);
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, char *name, char *keyname);
int dnssec_validate_reply(struct dns_header *header, size_t plen, char *name, char *keyname, int *class);
/* util.c */ /* util.c */
void rand_init(void); void rand_init(void);

View File

@@ -21,11 +21,15 @@
/* Maximum length in octects of a domain name, in wire format */ /* Maximum length in octects of a domain name, in wire format */
#define MAXCDNAME 256 #define MAXCDNAME 256
#define MAXRRSET 16
#define SERIAL_UNDEF -100 #define SERIAL_UNDEF -100
#define SERIAL_EQ 0 #define SERIAL_EQ 0
#define SERIAL_LT -1 #define SERIAL_LT -1
#define SERIAL_GT 1 #define SERIAL_GT 1
static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen);
/* Implement RFC1982 wrapped compare for 32-bit numbers */ /* Implement RFC1982 wrapped compare for 32-bit numbers */
static int serial_compare_32(unsigned long s1, unsigned long s2) static int serial_compare_32(unsigned long s1, unsigned long s2)
{ {
@@ -494,7 +498,359 @@ static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pk
return 1; return 1;
} }
size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type)
{
unsigned char *p;
header->qdcount = htons(1);
header->ancount = htons(0);
header->nscount = htons(0);
header->arcount = htons(0);
header->hb3 = HB3_RD;
SET_OPCODE(header, QUERY);
header->hb4 = 0;
/* ID filled in later */
p = (unsigned char *)(header+1);
p = do_rfc1035_name(p, name);
PUTSHORT(type, p);
PUTSHORT(class, p);
return add_do_bit(header, p - (unsigned char *)header, ((char *) header) + PACKETSZ);
}
/* The DNS packet is expected to contain the answer to a DNSKEY query
Put all DNSKEYs in the answer which are valid into the cache.
return codes:
STAT_INSECURE bad packet, no DNSKEYs in reply.
STAT_SECURE At least one valid DNSKEY found and in cache.
STAT_BOGUS At least one DNSKEY found, which fails validation.
STAT_NEED_DS DS records to validate a key not found, name in namebuff
*/
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
{
unsigned char *p;
struct crec *crecp, *recp1;
int j, qtype, qclass, ttl, rdlen, flags, protocol, algo, gotone;
struct blockdata *key;
if (ntohs(header->qdcount) != 1)
return STAT_INSECURE;
if (!extract_name(header, plen, &p, name, 1, 4))
return STAT_INSECURE;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qtype != T_DNSKEY || qclass != class)
return STAT_INSECURE;
cache_start_insert();
for (gotone = 0, j = ntohs(header->ancount); j != 0; j--)
{
/* Ensure we have type, class TTL and length */
if (!extract_name(header, plen, &p, name, 1, 10))
return STAT_INSECURE; /* bad packet */
GETSHORT(qtype, p);
GETSHORT(qclass, p);
GETLONG(ttl, p);
GETSHORT(rdlen, p);
if (qclass != class || qtype != T_DNSKEY || rdlen < 4)
{
/* skip all records other than DNSKEY */
p += rdlen;
continue;
}
crecp = cache_find_by_name(NULL, name, now, F_DS);
/* length at least covers flags, protocol and algo now. */
GETSHORT(flags, p);
protocol = *p++;
algo = *p++;
/* See if we have cached a DS record which validates this key */
for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
if (recp1->addr.key.algo == algo && is_supported_digest(recp1->addr.key.digest))
break;
/* DS record needed to validate key is missing, return name of DS in namebuff */
if (!recp1)
return STAT_NEED_DS;
else
{
int valid = 1;
/* calculate digest of canonicalised DNSKEY data using digest in (recp1->addr.key.digest)
and see if it equals digest stored in recp1
*/
if (!valid)
return STAT_BOGUS;
}
if ((key = blockdata_alloc((char*)p, rdlen)))
{
/* We've proved that the KEY is OK, store it in the cache */
if ((crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY)))
{
crecp->uid = rdlen;
crecp->addr.key.keydata = key;
crecp->addr.key.algo = algo;
crecp->addr.key.keytag = dnskey_keytag(algo, (char*)p, rdlen);
gotone = 1;
}
}
}
cache_end_insert();
return gotone ? STAT_SECURE : STAT_INSECURE;
}
/* The DNS packet is expected to contain the answer to a DS query
Put all DSs in the answer which are valid into the cache.
return codes:
STAT_INSECURE bad packet, no DNSKEYs in reply.
STAT_SECURE At least one valid DS found and in cache.
STAT_BOGUS At least one DS found, which fails validation.
STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname
*/
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
{
unsigned char *p = (unsigned char *)(header+1);
struct crec *crecp, *recp1;
int qtype, qclass, val, j, gotone;
struct blockdata *key;
if (ntohs(header->qdcount) != 1)
return STAT_INSECURE;
if (!extract_name(header, plen, &p, name, 1, 4))
return STAT_INSECURE;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qtype != T_DS || qclass != class)
return STAT_INSECURE;
val = validate_rrset(header, plen, class, T_DS, name, keyname);
/* failed to validate or missing key. */
if (val != STAT_SECURE)
return val;
cache_start_insert();
for (gotone = 0, j = ntohs(header->ancount); j != 0; j--)
{
int ttl, rdlen, rc, algo;
/* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
return STAT_INSECURE; /* bad packet */
GETSHORT(qtype, p);
GETSHORT(qclass, p);
GETLONG(ttl, p);
GETSHORT(rdlen, p);
/* check type, class and name, skip if not in DS rrset */
if (qclass != class || qtype != T_DS || rc == 2)
{
p += rdlen;
continue;
}
if ((key = blockdata_alloc((char*)p, rdlen)))
{
/* We've proved that the DS is OK, store it in the cache */
if ((crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DS)))
{
crecp->uid = rdlen;
crecp->addr.key.keydata = key;
crecp->addr.key.algo = algo;
crecp->addr.key.keytag = dnskey_keytag(algo, (char*)p, rdlen);
gotone = 1;
}
}
}
cache_end_insert();
return gotone ? STAT_SECURE : STAT_INSECURE;
}
/* Validate a single RRset (class, type, name) in the supplied DNS reply
Return code:
STAT_SECURE if it validates.
STAT_INSECURE can't validate (no RRSIG, bad packet).
STAT_BOGUS signature is wrong.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
*/
int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, char *name, char *keyname)
{
unsigned char *p, *psav, *sig;
int rrsetidx, res, sigttl, sig_data_len, j;
struct crec *crecp;
void *rrset[MAXRRSET]; /* TODO: max RRset size? */
int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag;
if (!(p = skip_questions(header, plen)))
return STAT_INSECURE;
/* look for an RRSIG record for this RRset and get pointers to each record */
for (rrsetidx = 0, sig = NULL, j = ntohs(header->ancount) + ntohs(header->nscount);
j != 0; j--)
{
unsigned char *pstart = p;
int stype, sclass, sttl, rdlen;
if (!(res = extract_name(header, plen, &p, name, 0, 10)))
return STAT_INSECURE; /* bad packet */
GETSHORT(stype, p);
GETSHORT(sclass, p);
GETLONG(sttl, p);
GETSHORT(rdlen, p);
if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_INSECURE; /* bad packet */
if (res == 2 || htons(stype) != T_RRSIG || htons(sclass) != class)
continue;
if (htons(stype) == type)
{
rrset[rrsetidx++] = pstart;
if (rrsetidx == MAXRRSET)
return STAT_INSECURE; /* RRSET too big TODO */
}
if (htons(stype) == T_RRSIG)
{
/* name matches, RRSIG for correct class */
/* enough data? */
if (rdlen < 18)
return STAT_INSECURE;
GETSHORT(type_covered, p);
algo = *p++;
labels = *p++;
GETLONG(orig_ttl, p);
GETLONG(sig_expiration, p);
GETLONG(sig_inception, p);
GETSHORT(key_tag, p);
if (type_covered != type ||
!check_date_range(sig_inception, sig_expiration))
{
/* covers wrong type or out of date - skip */
p = psav;
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE;
continue;
}
if (!extract_name(header, plen, &p, keyname, 1, 0))
return STAT_INSECURE;
/* OK, we have the signature record, see if the
relevant DNSKEY is in the cache. */
for (crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY);
crecp;
crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag)
break;
/* No, abort for now whilst we get it */
if (!crecp)
return STAT_NEED_KEY;
/* Save point to signature data */
sig = p;
sig_data_len = rdlen - (p - psav);
sigttl = sttl;
/* next record */
p = psav;
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE;
}
}
/* Didn't find RRSIG or RRset is empty */
if (!sig || rrsetidx == 0)
return STAT_INSECURE;
/* OK, we have an RRSIG and an RRset and we have a the DNSKEY that validates them. */
/* Sort RRset records in canonical order. */
rrset_canonical_order_ctx.header = header;
rrset_canonical_order_ctx.pktlen = plen;
qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
/* Now initialize the signature verification algorithm and process the whole
RRset */
VerifyAlgCtx *alg = verifyalg_alloc(algo);
if (!alg)
return STAT_INSECURE;
alg->sig = sig;
alg->siglen = sig_data_len;
u16 ntype = htons(type);
u16 nclass = htons(class);
u32 nsigttl = htonl(sigttl);
/* TODO: we shouldn't need to convert this to wire here. Best solution would be:
- Use process_name() instead of extract_name() everywhere in dnssec code
- Convert from wire format to representation format only for querying/storing cache
*/
unsigned char owner_wire[MAXCDNAME];
int owner_wire_len = convert_domain_to_wire(name, owner_wire);
digestalg_begin(alg->vtbl->digest_algo);
digestalg_add_data(sigrdata, 18+signer_name_rdlen);
for (i = 0; i < rrsetidx; ++i)
{
p = (unsigned char*)(rrset[i]);
digestalg_add_data(owner_wire, owner_wire_len);
digestalg_add_data(&ntype, 2);
digestalg_add_data(&nclass, 2);
digestalg_add_data(&nsigttl, 4);
p += 8;
if (!digestalg_add_rdata(ntohs(sigtype), header, pktlen, p))
return 0;
}
int digest_len = digestalg_len();
memcpy(alg->digest, digestalg_final(), digest_len);
if (alg->vtbl->verify(alg, crecp->addr.key.keydata, crecp_uid))
return STAT_SECURE;
return STAT_INSECURE;
}
#if 0
static int begin_rrsig_validation(struct dns_header *header, size_t pktlen, static int begin_rrsig_validation(struct dns_header *header, size_t pktlen,
unsigned char *reply, int count, char *owner, unsigned char *reply, int count, char *owner,
int sigclass, int sigrdlen, unsigned char *sig, int sigclass, int sigrdlen, unsigned char *sig,
@@ -624,6 +980,7 @@ static int end_rrsig_validation(PendingRRSIGValidation *val, struct crec *crec_d
return val->alg->vtbl->verify(val->alg, crec_dnskey->addr.key.keydata, crec_dnskey->uid); return val->alg->vtbl->verify(val->alg, crec_dnskey->addr.key.keydata, crec_dnskey->uid);
} }
static void dnssec_parserrsig(struct dns_header *header, size_t pktlen, static void dnssec_parserrsig(struct dns_header *header, size_t pktlen,
unsigned char *reply, int count, char *owner, unsigned char *reply, int count, char *owner,
int sigclass, int sigrdlen, unsigned char *sig) int sigclass, int sigrdlen, unsigned char *sig)
@@ -663,6 +1020,8 @@ static void dnssec_parserrsig(struct dns_header *header, size_t pktlen,
} }
} }
#endif /* comment out */
/* Compute keytag (checksum to quickly index a key). See RFC4034 */ /* Compute keytag (checksum to quickly index a key). See RFC4034 */
static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen) static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen)
{ {

View File

@@ -678,15 +678,14 @@ void reply_query(int fd, int family, time_t now)
if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED))
{ {
int status; int status;
char rrbitmap[256/8];
int class; int class;
if (forward->flags && FREC_DNSSKEY_QUERY) if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(header, n, daemon->namebuff, &class); status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else if (forward->flags && FREC_DS_QUERY) else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_dnskey(header, n, daemon->namebuff, &class); status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else else
status = dnssec_validate_reply(&rrbitmap, header, n, daemon->namebuff, &class); status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class);
/* Can't validate, as we're missing key data. Put this /* Can't validate, as we're missing key data. Put this
answer aside, whilst we get that. */ answer aside, whilst we get that. */
@@ -717,7 +716,7 @@ void reply_query(int fd, int family, time_t now)
} }
new->crc = questions_crc(header, nn, daemon->namebuff); new->crc = questions_crc(header, nn, daemon->namebuff);
new->new_id = get_id(new->crc); new->new_id = get_id(new->crc);
header->id = htons(new->id); header->id = htons(new->new_id);
/* Don't resend this. */ /* Don't resend this. */
daemon->srv_save = NULL; daemon->srv_save = NULL;
@@ -751,22 +750,30 @@ void reply_query(int fd, int family, time_t now)
and validate them with the new data. Failure to find needed data here is an internal error. and validate them with the new data. Failure to find needed data here is an internal error.
Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates, Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates,
return it to the original requestor. */ return it to the original requestor. */
while (forward->flags & FREC_DNSSEC_QUERY) while (forward->dependent)
{ {
if (status == STAT_SECURE) struct frec *prev = forward->dependent;
extract_dnssec_replies();
free_frec(forward); free_frec(forward);
forward = forward->dependent; forward = prev;
blockdata_retrieve_and_free(forward->stash, forward->stash_len, (void *)header); blockdata_retrieve_and_free(forward->stash, forward->stash_len, (void *)header);
n = forward->stash_len; n = forward->stash_len;
if (status == STAT_SECURE) if (status == STAT_SECURE)
{ {
status = dnssec_validate(forward->flags, header, n); if (forward->flags & FREC_DNSKEY_QUERY)
if (status == STAT_NEED_DS || status == STAT_NEED_KEY) status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation")); else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_dnskey(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation"));
} }
} }
/* All DNSKEY and DS records done and in cache, now finally validate original
answer, provided last DNSKEY is OK. */
if (status == STAT_SECURE)
status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class);
if (status == STAT_SECURE) if (status == STAT_SECURE)
cache_secure = 1; cache_secure = 1;
/* TODO return SERVFAIL here */ /* TODO return SERVFAIL here */

View File

@@ -16,13 +16,6 @@
#include "dnsmasq.h" #include "dnsmasq.h"
#define CHECK_LEN(header, pp, plen, len) \
((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen))
#define ADD_RDLEN(header, pp, plen, len) \
(!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1))
int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
char *name, int isExtract, int extrabytes) char *name, int isExtract, int extrabytes)
{ {