mirror of
https://github.com/NLnetLabs/unbound.git
synced 2024-09-21 14:47:09 +00:00
VALIDATE state and positive response validation.
git-svn-id: file:///svn/unbound/trunk@532 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
parent
3f8b0b1cfe
commit
1b42a51048
@ -1,5 +1,6 @@
|
||||
18 August 2007: Wouter
|
||||
- process DNSKEY response in FINDKEY state.
|
||||
- validate and positive validation, positive wildcard NSEC validation.
|
||||
|
||||
17 August 2007: Wouter
|
||||
- work on DS2KE routine.
|
||||
|
@ -711,6 +711,27 @@ dname_test_canoncmp()
|
||||
) == 0);
|
||||
}
|
||||
|
||||
/** Test dname_get_shared_topdomain */
|
||||
static void
|
||||
dname_test_topdomain()
|
||||
{
|
||||
unit_assert( query_dname_compare(
|
||||
dname_get_shared_topdomain(
|
||||
(uint8_t*)"",
|
||||
(uint8_t*)""),
|
||||
(uint8_t*)"") == 0);
|
||||
unit_assert( query_dname_compare(
|
||||
dname_get_shared_topdomain(
|
||||
(uint8_t*)"\003www\007example\003com",
|
||||
(uint8_t*)"\003www\007example\003com"),
|
||||
(uint8_t*)"\003www\007example\003com") == 0);
|
||||
unit_assert( query_dname_compare(
|
||||
dname_get_shared_topdomain(
|
||||
(uint8_t*)"\003www\007example\003com",
|
||||
(uint8_t*)"\003bla\007example\003com"),
|
||||
(uint8_t*)"\007example\003com") == 0);
|
||||
}
|
||||
|
||||
void dname_test()
|
||||
{
|
||||
ldns_buffer* buff = ldns_buffer_new(65800);
|
||||
@ -729,5 +750,6 @@ void dname_test()
|
||||
dname_test_sigcount();
|
||||
dname_test_iswild();
|
||||
dname_test_canoncmp();
|
||||
dname_test_topdomain();
|
||||
ldns_buffer_free(buff);
|
||||
}
|
||||
|
@ -725,3 +725,14 @@ dname_canonical_compare(uint8_t* d1, uint8_t* d2)
|
||||
labs2 = dname_count_labels(d2);
|
||||
return dname_canon_lab_cmp(d1, labs1, d2, labs2, &m);
|
||||
}
|
||||
|
||||
uint8_t* dname_get_shared_topdomain(uint8_t* d1, uint8_t* d2)
|
||||
{
|
||||
int labs1, labs2, m;
|
||||
size_t len = LDNS_MAX_DOMAINLEN;
|
||||
labs1 = dname_count_labels(d1);
|
||||
labs2 = dname_count_labels(d2);
|
||||
(void)dname_lab_cmp(d1, labs1, d2, labs2, &m);
|
||||
dname_remove_labels(&d1, &len, labs1-m);
|
||||
return d1;
|
||||
}
|
||||
|
@ -280,4 +280,12 @@ int dname_canon_lab_cmp(uint8_t* d1, int labs1, uint8_t* d2, int labs2,
|
||||
*/
|
||||
int dname_canonical_compare(uint8_t* d1, uint8_t* d2);
|
||||
|
||||
/**
|
||||
* Get the shared topdomain between two names. Root "." or longer.
|
||||
* @param d1: first dname. pointer to uncompressed wireformat.
|
||||
* @param d2: second dname. pointer to uncompressed wireformat.
|
||||
* @return pointer to shared topdomain. Ptr to a part of d1.
|
||||
*/
|
||||
uint8_t* dname_get_shared_topdomain(uint8_t* d1, uint8_t* d2);
|
||||
|
||||
#endif /* UTIL_DATA_DNAME_H */
|
||||
|
@ -304,3 +304,90 @@ int nsec_proves_nodata(struct ub_packed_rrset_key* nsec,
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
val_nsec_proves_name_error(struct ub_packed_rrset_key* nsec, uint8_t* qname)
|
||||
{
|
||||
uint8_t* owner = nsec->rk.dname;
|
||||
uint8_t* next;
|
||||
size_t nlen;
|
||||
if(!nsec_get_next(nsec, &next, &nlen))
|
||||
return 0;
|
||||
|
||||
/* If NSEC owner == qname, then this NSEC proves that qname exists. */
|
||||
if(query_dname_compare(qname, owner) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If NSEC is a parent of qname, we need to check the type map
|
||||
* If the parent name has a DNAME or is a delegation point, then
|
||||
* this NSEC is being misused. */
|
||||
if(dname_subdomain_c(qname, owner) &&
|
||||
(nsec_has_type(nsec, LDNS_RR_TYPE_DNAME) ||
|
||||
(nsec_has_type(nsec, LDNS_RR_TYPE_NS)
|
||||
&& !nsec_has_type(nsec, LDNS_RR_TYPE_SOA))
|
||||
)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* see if this nsec is the only nsec */
|
||||
if(query_dname_compare(owner, next) == 0) {
|
||||
/* only zone.name NSEC zone.name, disproves everything else */
|
||||
return 1;
|
||||
}
|
||||
/* see if this nsec is the last nsec */
|
||||
if(dname_canonical_compare(owner, next) > 0) {
|
||||
/* this is the last nsec, ....(bigger) NSEC zonename(smaller) */
|
||||
/* the names after the last (owner) name do not exist */
|
||||
if(dname_canonical_compare(owner, qname) < 0)
|
||||
return 1;
|
||||
/* there are no names before the zone name in the zone */
|
||||
/* if(dname_canonical_compare(qname, next) < 0) return 1; */
|
||||
} else {
|
||||
/* regular NSEC, (smaller) NSEC (larger) */
|
||||
if(dname_canonical_compare(owner, qname) < 0 &&
|
||||
dname_canonical_compare(qname, next) < 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine closest encloser of a query name and the NSEC that covers it
|
||||
* (and thus disproved it).
|
||||
*/
|
||||
static uint8_t* nsec_closest_encloser(uint8_t* qname,
|
||||
struct ub_packed_rrset_key* nsec)
|
||||
{
|
||||
uint8_t* next;
|
||||
size_t nlen;
|
||||
uint8_t* common1, *common2;
|
||||
if(!nsec_get_next(nsec, &next, &nlen))
|
||||
return NULL;
|
||||
/* longest common with owner or next name */
|
||||
common1 = dname_get_shared_topdomain(nsec->rk.dname, qname);
|
||||
common2 = dname_get_shared_topdomain(next, qname);
|
||||
if(dname_count_labels(common1) > dname_count_labels(common2))
|
||||
return common1;
|
||||
return common2;
|
||||
}
|
||||
|
||||
int val_nsec_proves_positive_wildcard(struct ub_packed_rrset_key* nsec,
|
||||
struct query_info* qinf, uint8_t* wc)
|
||||
{
|
||||
uint8_t* ce;
|
||||
/* 1) prove that qname doesn't exist and
|
||||
* 2) that the correct wildcard was used
|
||||
* nsec has been verified already. */
|
||||
if(!val_nsec_proves_name_error(nsec, qinf->qname))
|
||||
return 0;
|
||||
/* check wildcard name */
|
||||
ce = nsec_closest_encloser(qinf->qname, nsec);
|
||||
if(!ce)
|
||||
return 0;
|
||||
if(query_dname_compare(wc, ce) != 0) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
@ -91,4 +91,25 @@ int unitest_nsec_has_type_rdata(char* bitmap, size_t len, uint16_t type);
|
||||
int nsec_proves_nodata(struct ub_packed_rrset_key* nsec,
|
||||
struct query_info* qinfo);
|
||||
|
||||
/**
|
||||
* Determine if the given NSEC proves a NameError (NXDOMAIN) for a given
|
||||
* qname.
|
||||
*
|
||||
* @param nsec: the nsec to check
|
||||
* @param qname: what was queried.
|
||||
* @return true if proven.
|
||||
*/
|
||||
int val_nsec_proves_name_error(struct ub_packed_rrset_key* nsec,
|
||||
uint8_t* qname);
|
||||
|
||||
/**
|
||||
* Determine if the given NSEC proves a positive wildcard response.
|
||||
* @param nsec: the nsec to check
|
||||
* @param qinf: what was queried.
|
||||
* @param wc: wildcard (without *. label)
|
||||
* @return true if proven.
|
||||
*/
|
||||
int val_nsec_proves_positive_wildcard(struct ub_packed_rrset_key* nsec,
|
||||
struct query_info* qinf, uint8_t* wc);
|
||||
|
||||
#endif /* VALIDATOR_VAL_NSEC_H */
|
||||
|
@ -343,3 +343,47 @@ val_dsset_isusable(struct ub_packed_rrset_key* ds_rrset)
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** get label count for a signature */
|
||||
static uint8_t
|
||||
rrsig_get_labcount(struct packed_rrset_data* d, size_t sig)
|
||||
{
|
||||
if(d->rr_len[sig] < 2+4)
|
||||
return 0; /* bad sig length */
|
||||
return d->rr_data[sig][2+3];
|
||||
}
|
||||
|
||||
int
|
||||
val_rrset_wildcard(struct ub_packed_rrset_key* rrset, uint8_t** wc)
|
||||
{
|
||||
struct packed_rrset_data* d = (struct packed_rrset_data*)rrset->
|
||||
entry.data;
|
||||
uint8_t labcount;
|
||||
int labdiff;
|
||||
size_t i;
|
||||
if(d->rrsig_count == 0) {
|
||||
*wc = NULL;
|
||||
return 0;
|
||||
}
|
||||
labcount = rrsig_get_labcount(d, d->count + 0);
|
||||
/* check rest of signatures identical */
|
||||
for(i=1; i<d->rrsig_count; i++) {
|
||||
if(labcount != rrsig_get_labcount(d, d->count + i)) {
|
||||
*wc = NULL;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/* OK the rrsigs check out */
|
||||
/* if the RRSIG label count is shorter than the number of actual
|
||||
* labels, then this rrset was synthesized from a wildcard.
|
||||
* Note that the RRSIG label count doesn't count the root label. */
|
||||
labdiff = (dname_count_labels(rrset->rk.dname) - 1) - (int)labcount;
|
||||
if(labdiff > 0) {
|
||||
size_t wl = rrset->rk.dname_len;
|
||||
*wc = rrset->rk.dname;
|
||||
dname_remove_labels(wc, &wl, labdiff);
|
||||
return 1;
|
||||
}
|
||||
*wc = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
@ -150,4 +150,23 @@ struct key_entry_key* val_verify_new_DNSKEYs(struct region* region,
|
||||
*/
|
||||
int val_dsset_isusable(struct ub_packed_rrset_key* ds_rrset);
|
||||
|
||||
/**
|
||||
* Determine by looking at a signed RRset whether or not the RRset name was
|
||||
* the result of a wildcard expansion. If so, return the name of the
|
||||
* generating wildcard.
|
||||
*
|
||||
* @param rrset The rrset to chedck.
|
||||
* @param wc: the wildcard name, if the rrset was synthesized from a wildcard.
|
||||
* null if not. The wildcard name, without "*." in front, is returned.
|
||||
* This is a pointer into the rrset owner name.
|
||||
* @return false if the signatures are inconsistent in indicating the
|
||||
* wildcard status; possible spoofing of wildcard response for other
|
||||
* responses is being tried. We lost the status which rrsig was verified
|
||||
* after the verification routine finished, so we simply check if
|
||||
* the signatures are consistent; inserting a fake signature is a denial
|
||||
* of service; but in that you could also have removed the real
|
||||
* signature anyway.
|
||||
*/
|
||||
int val_rrset_wildcard(struct ub_packed_rrset_key* rrset, uint8_t** wc);
|
||||
|
||||
#endif /* VALIDATOR_VAL_UTILS_H */
|
||||
|
@ -262,6 +262,157 @@ prime_trust_anchor(struct module_qstate* qstate, struct val_qstate* vq,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a "positive" response -- a response that contains an answer to the
|
||||
* question, and no CNAME chain, validate this response. This generally
|
||||
* consists of verifying the answer RRset and the authority RRsets.
|
||||
*
|
||||
* Note that by the time this method is called, the process of finding the
|
||||
* trusted DNSKEY rrset that signs this response must already have been
|
||||
* completed.
|
||||
*
|
||||
* @param env: module env for verify.
|
||||
* @param ve: validator env for verify.
|
||||
* @param qchase: query that was made.
|
||||
* @param chase_reply: answer to that query to validate.
|
||||
* @param key_entry: the key entry, which is trusted, and which matches
|
||||
* the signer of the answer. The key entry isgood().
|
||||
*/
|
||||
static void
|
||||
validate_positive_response(struct module_env* env, struct val_env* ve,
|
||||
struct query_info* qchase, struct reply_info* chase_reply,
|
||||
struct key_entry_key* key_entry)
|
||||
{
|
||||
uint8_t* wc = NULL;
|
||||
int wc_NSEC_ok = 0;
|
||||
int dname_seen = 0;
|
||||
int nsec3s_seen = 0;
|
||||
size_t i;
|
||||
struct ub_packed_rrset_key* s;
|
||||
enum sec_status sec;
|
||||
|
||||
/* validate the ANSWER section - this will be the answer itself */
|
||||
for(i=0; i<chase_reply->an_numrrsets; i++) {
|
||||
s = chase_reply->rrsets[i];
|
||||
/* Skip the CNAME following a (validated) DNAME.
|
||||
* Because of the normalization routines in the iterator,
|
||||
* there will always be an unsigned CNAME following a DNAME
|
||||
* (unless qtype=DNAME). */
|
||||
if(dname_seen && ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME) {
|
||||
dname_seen = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Verify the answer rrset */
|
||||
sec = val_verify_rrset_entry(env, ve, s, key_entry);
|
||||
/* If the (answer) rrset failed to validate, then this
|
||||
* message is BAD. */
|
||||
if(sec != sec_status_secure) {
|
||||
log_nametypeclass(VERB_ALGO, "Positive response has "
|
||||
"failed ANSWER rrset: ", s->rk.dname,
|
||||
ntohs(s->rk.type), ntohs(s->rk.rrset_class));
|
||||
chase_reply->security = sec_status_bogus;
|
||||
return;
|
||||
}
|
||||
/* Check to see if the rrset is the result of a wildcard
|
||||
* expansion. If so, an additional check will need to be
|
||||
* made in the authority section. */
|
||||
if(!val_rrset_wildcard(s, &wc)) {
|
||||
log_nametypeclass(VERB_ALGO, "Positive response has "
|
||||
"inconsistent wildcard sigs: ", s->rk.dname,
|
||||
ntohs(s->rk.type), ntohs(s->rk.rrset_class));
|
||||
chase_reply->security = sec_status_bogus;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Notice a DNAME that should be followed by an unsigned
|
||||
* CNAME. */
|
||||
if(qchase->qtype != LDNS_RR_TYPE_DNAME &&
|
||||
ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME) {
|
||||
dname_seen = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* validate the AUTHORITY section as well - this will generally be
|
||||
* the NS rrset (which could be missing, no problem) */
|
||||
for(i=chase_reply->an_numrrsets; i<chase_reply->an_numrrsets+
|
||||
chase_reply->ns_numrrsets; i++) {
|
||||
s = chase_reply->rrsets[i];
|
||||
sec = val_verify_rrset_entry(env, ve, s, key_entry);
|
||||
/* If anything in the authority section fails to be secure,
|
||||
* we have a bad message. */
|
||||
if(sec != sec_status_secure) {
|
||||
log_nametypeclass(VERB_ALGO, "Positive response has "
|
||||
"failed AUTHORITY rrset: ", s->rk.dname,
|
||||
ntohs(s->rk.type), ntohs(s->rk.rrset_class));
|
||||
chase_reply->security = sec_status_bogus;
|
||||
return;
|
||||
}
|
||||
|
||||
/* If this is a positive wildcard response, and we have a
|
||||
* (just verified) NSEC record, try to use it to 1) prove
|
||||
* that qname doesn't exist and 2) that the correct wildcard
|
||||
* was used. */
|
||||
if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) {
|
||||
if(val_nsec_proves_positive_wildcard(s, qchase, wc)) {
|
||||
wc_NSEC_ok = 1;
|
||||
}
|
||||
/* if not, continue looking for proof */
|
||||
}
|
||||
|
||||
/* Otherwise, if this is a positive wildcard response and
|
||||
* we have NSEC3 records */
|
||||
if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) {
|
||||
nsec3s_seen = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* If this was a positive wildcard response that we haven't already
|
||||
* proven, and we have NSEC3 records, try to prove it using the NSEC3
|
||||
* records. */
|
||||
if(wc != NULL && !wc_NSEC_ok && nsec3s_seen) {
|
||||
/* TODO NSEC3 positive wildcard proof */
|
||||
/* possibly: wc_NSEC_ok = 1; */
|
||||
}
|
||||
|
||||
/* If after all this, we still haven't proven the positive wildcard
|
||||
* response, fail. */
|
||||
if(wc != NULL && !wc_NSEC_ok) {
|
||||
verbose(VERB_ALGO, "positive response was wildcard "
|
||||
"expansion and did not prove original data "
|
||||
"did not exist");
|
||||
chase_reply->security = sec_status_bogus;
|
||||
return;
|
||||
}
|
||||
|
||||
verbose(VERB_ALGO, "Successfully validated postive response");
|
||||
chase_reply->security = sec_status_secure;
|
||||
}
|
||||
|
||||
/** validate NODATA */
|
||||
static void
|
||||
validate_nodata_response(struct module_env* env, struct val_env* ve,
|
||||
struct query_info* qchase, struct reply_info* chase_reply,
|
||||
struct key_entry_key* key_entry)
|
||||
{
|
||||
}
|
||||
|
||||
/** validate NAME ERROR (nxdomain) response */
|
||||
static void
|
||||
validate_nameerror_response(struct module_env* env, struct val_env* ve,
|
||||
struct query_info* qchase, struct reply_info* chase_reply,
|
||||
struct key_entry_key* key_entry)
|
||||
{
|
||||
}
|
||||
|
||||
/** validate positive ANY response */
|
||||
static void
|
||||
validate_any_response(struct module_env* env, struct val_env* ve,
|
||||
struct query_info* qchase, struct reply_info* chase_reply,
|
||||
struct key_entry_key* key_entry)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Process init state for validator.
|
||||
* Process the INIT state. First tier responses start in the INIT state.
|
||||
@ -426,6 +577,112 @@ processFindKey(struct module_qstate* qstate, struct val_qstate* vq, int id)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the VALIDATE stage, the init and findkey stages are finished,
|
||||
* and the right keys are available to validate the response.
|
||||
* Or, there are no keys available, in order to invalidate the response.
|
||||
*
|
||||
* After validation, the status is recorded in the message and rrsets,
|
||||
* and finished state is started.
|
||||
*
|
||||
* @param qstate: query state.
|
||||
* @param vq: validator query state.
|
||||
* @param ve: validator shared global environment.
|
||||
* @param id: module id.
|
||||
* @return true if the event should be processed further on return, false if
|
||||
* not.
|
||||
*/
|
||||
static int
|
||||
processValidate(struct module_qstate* qstate, struct val_qstate* vq,
|
||||
struct val_env* ve, int id)
|
||||
{
|
||||
enum val_classification subtype;
|
||||
|
||||
if(!vq->key_entry) {
|
||||
verbose(VERB_ALGO, "validate: no key entry, failed");
|
||||
qstate->ext_state[id] = module_error;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This is the default next state. */
|
||||
vq->state = VAL_FINISHED_STATE;
|
||||
|
||||
/* signerName being null is the indicator that this response was
|
||||
* unsigned */
|
||||
if(vq->signer_name == NULL) {
|
||||
log_query_info(VERB_ALGO, "processValidate: state has no "
|
||||
"signer name", &vq->qchase);
|
||||
/* Unsigned responses must be underneath a "null" key entry.*/
|
||||
if(key_entry_isnull(vq->key_entry)) {
|
||||
verbose(VERB_ALGO, "Unsigned response was proven to "
|
||||
"be validly INSECURE");
|
||||
vq->chase_reply->security = sec_status_insecure;
|
||||
return 1;
|
||||
}
|
||||
verbose(VERB_ALGO, "Could not establish validation of "
|
||||
"INSECURE status of unsigned response.");
|
||||
vq->chase_reply->security = sec_status_bogus;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(key_entry_isbad(vq->key_entry)) {
|
||||
log_nametypeclass(VERB_ALGO, "Could not establish a chain "
|
||||
"of trust to keys for", vq->key_entry->name,
|
||||
LDNS_RR_TYPE_DNSKEY, vq->key_entry->key_class);
|
||||
vq->chase_reply->security = sec_status_bogus;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(key_entry_isnull(vq->key_entry)) {
|
||||
verbose(VERB_ALGO, "Verified that response is INSECURE");
|
||||
vq->chase_reply->security = sec_status_insecure;
|
||||
return 1;
|
||||
}
|
||||
|
||||
subtype = val_classify_response(&vq->qchase, vq->chase_reply);
|
||||
switch(subtype) {
|
||||
case VAL_CLASS_POSITIVE:
|
||||
verbose(VERB_ALGO, "Validating a positive response");
|
||||
validate_positive_response(qstate->env, ve,
|
||||
&vq->qchase, vq->chase_reply, vq->key_entry);
|
||||
break;
|
||||
|
||||
case VAL_CLASS_NODATA:
|
||||
verbose(VERB_ALGO, "Validating a nodata response");
|
||||
validate_nodata_response(qstate->env, ve,
|
||||
&vq->qchase, vq->chase_reply, vq->key_entry);
|
||||
break;
|
||||
|
||||
case VAL_CLASS_NAMEERROR:
|
||||
verbose(VERB_ALGO, "Validating a nxdomain response");
|
||||
validate_nameerror_response(qstate->env, ve,
|
||||
&vq->qchase, vq->chase_reply, vq->key_entry);
|
||||
break;
|
||||
|
||||
case VAL_CLASS_CNAME:
|
||||
verbose(VERB_ALGO, "Validating a cname response");
|
||||
/*
|
||||
* TODO special CNAME state or routines
|
||||
validate_cname_response(vq->qchase, vq->chase_reply,
|
||||
vq->key_entry);
|
||||
*/
|
||||
break;
|
||||
|
||||
case VAL_CLASS_ANY:
|
||||
verbose(VERB_ALGO, "Validating a positive ANY "
|
||||
"response");
|
||||
validate_any_response(qstate->env, ve,
|
||||
&vq->qchase, vq->chase_reply, vq->key_entry);
|
||||
break;
|
||||
|
||||
default:
|
||||
log_err("validate: unhandled response subtype: %d",
|
||||
subtype);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle validator state.
|
||||
* If a method returns true, the next state is started. If false, then
|
||||
@ -451,6 +708,8 @@ val_handle(struct module_qstate* qstate, struct val_qstate* vq,
|
||||
cont = processFindKey(qstate, vq, id);
|
||||
break;
|
||||
case VAL_VALIDATE_STATE:
|
||||
cont = processValidate(qstate, vq, ve, id);
|
||||
break;
|
||||
case VAL_FINISHED_STATE:
|
||||
default:
|
||||
log_warn("validator: invalid state %d",
|
||||
|
Loading…
Reference in New Issue
Block a user