/** * Process TSIG RR. * */ static ldns_pkt_rcode query_process_tsig(query_type* q) { if (!q || !q->tsig_rr) { return LDNS_RCODE_SERVFAIL; } if (q->tsig_rr->status == TSIG_ERROR) { return LDNS_RCODE_FORMERR; } if (q->tsig_rr->status == TSIG_OK) { if (!tsig_rr_lookup(q->tsig_rr)) { ods_log_debug("[%s] tsig unknown key/algorithm", query_str); return LDNS_RCODE_REFUSED; } buffer_set_limit(q->buffer, q->tsig_rr->position); buffer_pkt_set_arcount(q->buffer, buffer_pkt_arcount(q->buffer)-1); tsig_rr_prepare(q->tsig_rr); tsig_rr_update(q->tsig_rr, q->buffer, buffer_limit(q->buffer)); if (!tsig_rr_verify(q->tsig_rr)) { ods_log_debug("[%s] bad tsig signature", query_str); return LDNS_RCODE_NOTAUTH; } } return LDNS_RCODE_NOERROR; }
static int xfrd_xfr_process_tsig(xfrd_zone_t* zone, buffer_type* packet) { int have_tsig = 0; assert(zone && zone->master && zone->master->key_options && zone->master->key_options->tsig_key && packet); if(!tsig_find_rr(&zone->tsig, packet)) { log_msg(LOG_ERR, "xfrd: zone %s, from %s: malformed tsig RR", zone->apex_str, zone->master->ip_address_spec); return 0; } if(zone->tsig.status == TSIG_OK) { have_tsig = 1; } if(have_tsig) { /* strip the TSIG resource record off... */ buffer_set_limit(packet, zone->tsig.position); ARCOUNT_SET(packet, ARCOUNT(packet) - 1); } /* keep running the TSIG hash */ tsig_update(&zone->tsig, packet, buffer_limit(packet)); if(have_tsig) { if (!tsig_verify(&zone->tsig)) { log_msg(LOG_ERR, "xfrd: zone %s, from %s: bad tsig signature", zone->apex_str, zone->master->ip_address_spec); return 0; } DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s, from %s: good tsig signature", zone->apex_str, zone->master->ip_address_spec)); /* prepare for next tsigs */ tsig_prepare(&zone->tsig); } else if(zone->tsig.updates_since_last_prepare > XFRD_TSIG_MAX_UNSIGNED) { /* we allow a number of non-tsig signed packets */ log_msg(LOG_INFO, "xfrd: zone %s, from %s: too many consecutive " "packets without TSIG", zone->apex_str, zone->master->ip_address_spec); return 0; } if(!have_tsig && zone->msg_seq_nr == 0) { log_msg(LOG_ERR, "xfrd: zone %s, from %s: no tsig in first packet of reply", zone->apex_str, zone->master->ip_address_spec); return 0; } return 1; }
/** * Error. * */ static query_state query_error(query_type* q, ldns_pkt_rcode rcode) { size_t limit = 0; if (!q) { return QUERY_DISCARDED; } limit = buffer_limit(q->buffer); buffer_clear(q->buffer); buffer_pkt_set_qr(q->buffer); buffer_pkt_set_rcode(q->buffer, rcode); buffer_pkt_set_ancount(q->buffer, 0); buffer_pkt_set_nscount(q->buffer, 0); buffer_pkt_set_arcount(q->buffer, 0); buffer_set_position(q->buffer, limit); return QUERY_PROCESSED; }
/** * Prepare response. * */ void query_prepare(query_type* q) { uint16_t limit = 0; uint16_t flags = 0; ods_log_assert(q); ods_log_assert(q->buffer); limit = buffer_limit(q->buffer); flags = buffer_pkt_flags(q->buffer); flags &= 0x0100U; /* preserve the rd flag */ flags |= 0x8000U; /* set the qr flag */ buffer_pkt_set_flags(q->buffer, flags); buffer_clear(q->buffer); buffer_set_position(q->buffer, limit); buffer_set_limit(q->buffer, buffer_capacity(q->buffer)); q->reserved_space = edns_rr_reserved_space(q->edns_rr); q->reserved_space += tsig_rr_reserved_space(q->tsig_rr); return; }
int dname_make_wire_from_packet(uint8_t *buf, buffer_type *packet, int allow_pointers) { int done = 0; uint8_t visited[(MAX_PACKET_SIZE+7)/8]; size_t dname_length = 0; const uint8_t *label; ssize_t mark = -1; memset(visited, 0, (buffer_limit(packet)+7)/8); while (!done) { if (!buffer_available(packet, 1)) { /* error("dname out of bounds"); */ return 0; } if (get_bit(visited, buffer_position(packet))) { /* error("dname loops"); */ return 0; } set_bit(visited, buffer_position(packet)); label = buffer_current(packet); if (label_is_pointer(label)) { size_t pointer; if (!allow_pointers) { return 0; } if (!buffer_available(packet, 2)) { /* error("dname pointer out of bounds"); */ return 0; } pointer = label_pointer_location(label); if (pointer >= buffer_limit(packet)) { /* error("dname pointer points outside packet"); */ return 0; } buffer_skip(packet, 2); if (mark == -1) { mark = buffer_position(packet); } buffer_set_position(packet, pointer); } else if (label_is_normal(label)) { size_t length = label_length(label) + 1; done = label_is_root(label); if (!buffer_available(packet, length)) { /* error("dname label out of bounds"); */ return 0; } if (dname_length + length >= MAXDOMAINLEN+1) { /* error("dname too large"); */ return 0; } buffer_read(packet, buf + dname_length, length); dname_length += length; } else { /* error("bad label type"); */ return 0; } } if (mark != -1) { buffer_set_position(packet, mark); } return dname_length; }
enum xfrd_packet_result xfrd_handle_received_xfr_packet(xfrd_zone_t* zone, buffer_type* packet) { xfrd_soa_t soa; enum xfrd_packet_result res; /* parse and check the packet - see if it ends the xfr */ switch((res=xfrd_parse_received_xfr_packet(zone, packet, &soa))) { case xfrd_packet_more: case xfrd_packet_transfer: /* continue with commit */ break; case xfrd_packet_newlease: return xfrd_packet_newlease; case xfrd_packet_tcp: return xfrd_packet_tcp; case xfrd_packet_notimpl: case xfrd_packet_bad: default: { /* rollback */ if(zone->msg_seq_nr > 0) { /* do not process xfr - if only one part simply ignore it. */ /* rollback previous parts of commit */ buffer_clear(packet); buffer_printf(packet, "xfrd: zone %s xfr " "rollback serial %u at " "time %u from %s of %u " "parts", zone->apex_str, (int)zone->msg_new_serial, (int)xfrd_time(), zone->master->ip_address_spec, zone->msg_seq_nr); buffer_flip(packet); diff_write_commit(zone->apex_str, zone->msg_old_serial, zone->msg_new_serial, zone->query_id, zone->msg_seq_nr, 0, (char*)buffer_begin(packet), xfrd->nsd->options); DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s " "xfr reverted " "\"%s\"", zone->apex_str, (char*)buffer_begin(packet))); } if (res == xfrd_packet_notimpl) return res; else return xfrd_packet_bad; } } /* dump reply on disk to diff file */ diff_write_packet(zone->apex_str, zone->msg_new_serial, zone->query_id, zone->msg_seq_nr, buffer_begin(packet), buffer_limit(packet), xfrd->nsd->options); VERBOSITY(1, (LOG_INFO, "xfrd: zone %s written received XFR from %s with serial %u to " "disk", zone->apex_str, zone->master->ip_address_spec, (int)zone->msg_new_serial)); zone->msg_seq_nr++; if(res == xfrd_packet_more) { /* wait for more */ return xfrd_packet_more; } /* done. we are completely sure of this */ buffer_clear(packet); buffer_printf(packet, "xfrd: zone %s received update to serial %u at " "time %u from %s in %u parts", zone->apex_str, (int)zone->msg_new_serial, (int)xfrd_time(), zone->master->ip_address_spec, zone->msg_seq_nr); if(zone->master->key_options) { buffer_printf(packet, " TSIG verified with key %s", zone->master->key_options->name); } buffer_flip(packet); diff_write_commit(zone->apex_str, zone->msg_old_serial, zone->msg_new_serial, zone->query_id, zone->msg_seq_nr, 1, (char*)buffer_begin(packet), xfrd->nsd->options); VERBOSITY(1, (LOG_INFO, "xfrd: zone %s committed \"%s\"", zone->apex_str, (char*)buffer_begin(packet))); /* update the disk serial no. */ zone->soa_disk_acquired = xfrd_time(); zone->soa_disk = soa; if(zone->soa_notified_acquired && ( zone->soa_notified.serial == 0 || compare_serial(htonl(zone->soa_disk.serial), htonl(zone->soa_notified.serial)) >= 0)) { zone->soa_notified_acquired = 0; } if(!zone->soa_notified_acquired) { /* do not set expired zone to ok: * it would cause nsd to start answering * bad data, since the zone is not loaded yet. * if nsd does not reload < retry time, more * queries (for even newer versions) are made. * For expired zone after reload it is set ok (SOAINFO ipc). */ if(zone->state != xfrd_zone_expired) xfrd_set_zone_state(zone, xfrd_zone_ok); DEBUG(DEBUG_XFRD,1, (LOG_INFO, "xfrd: zone %s is waiting for reload", zone->apex_str)); zone->round_num = -1; /* next try start anew */ xfrd_set_timer_refresh(zone); xfrd_set_reload_timeout(); return xfrd_packet_transfer; } else { /* try to get an even newer serial */ /* pretend it was bad to continue queries */ xfrd_set_reload_timeout(); return xfrd_packet_bad; } }
/** * Process query. * */ query_state query_process(query_type* q, void* engine) { ldns_status status = LDNS_STATUS_OK; ldns_pkt* pkt = NULL; ldns_rr* rr = NULL; ldns_pkt_rcode rcode = LDNS_RCODE_NOERROR; ldns_pkt_opcode opcode = LDNS_PACKET_QUERY; ldns_rr_type qtype = LDNS_RR_TYPE_SOA; engine_type* e = (engine_type*) engine; ods_log_assert(e); ods_log_assert(q); ods_log_assert(q->buffer); if (!e || !q || !q->buffer) { ods_log_error("[%s] drop query: assertion error", query_str); return QUERY_DISCARDED; /* should not happen */ } if (buffer_limit(q->buffer) < BUFFER_PKT_HEADER_SIZE) { ods_log_debug("[%s] drop query: packet too small", query_str); return QUERY_DISCARDED; /* too small */ } if (buffer_pkt_qr(q->buffer)) { ods_log_debug("[%s] drop query: qr bit set", query_str); return QUERY_DISCARDED; /* not a query */ } /* parse packet */ status = ldns_wire2pkt(&pkt, buffer_current(q->buffer), buffer_remaining(q->buffer)); if (status != LDNS_STATUS_OK) { ods_log_debug("[%s] got bad packet: %s", query_str, ldns_get_errorstr_by_id(status)); return query_formerr(q); } rr = ldns_rr_list_rr(ldns_pkt_question(pkt), 0); lock_basic_lock(&e->zonelist->zl_lock); /* we can just lookup the zone, because we will only handle SOA queries, zone transfers, updates and notifies */ q->zone = zonelist_lookup_zone_by_dname(e->zonelist, ldns_rr_owner(rr), ldns_rr_get_class(rr)); /* don't answer for zones that are just added */ if (q->zone && q->zone->zl_status == ZONE_ZL_ADDED) { ods_log_warning("[%s] zone %s just added, don't answer for now", query_str, q->zone->name); q->zone = NULL; } lock_basic_unlock(&e->zonelist->zl_lock); if (!q->zone) { ods_log_debug("[%s] zone not found", query_str); return query_servfail(q); } /* see if it is tsig signed */ if (!query_find_tsig(q)) { return query_formerr(q); } /* else: valid tsig, or no tsig present */ ods_log_debug("[%s] tsig %s", query_str, tsig_status2str(q->tsig_rr->status)); rcode = query_process_tsig(q); if (rcode != LDNS_RCODE_NOERROR) { return query_error(q, rcode); } /* process edns */ rcode = query_process_edns(q); if (rcode != LDNS_RCODE_NOERROR) { /* We should not return FORMERR, but BADVERS (=16). * BADVERS is created with Ext. RCODE, followed by RCODE. * Ext. RCODE is set to 1, RCODE must be 0 (getting 0x10 = 16). * Thus RCODE = NOERROR = NSD_RC_OK. */ return query_error(q, LDNS_RCODE_NOERROR); } /* handle incoming request */ opcode = ldns_pkt_get_opcode(pkt); qtype = ldns_rr_get_type(rr); ldns_pkt_free(pkt); switch (opcode) { case LDNS_PACKET_NOTIFY: return query_process_notify(q, qtype, engine); case LDNS_PACKET_QUERY: return query_process_query(q, qtype, engine); case LDNS_PACKET_UPDATE: return query_process_update(q); default: break; } return query_notimpl(q); }
static int dns_name_from_wire(buffer_type* data, struct decompress_ctx *decompress, char* text_name) { char *q; u_int8_t *p, label_len; int length, off, saved = -1, i; if (data == NULL || text_name == NULL || decompress == NULL) return -1; p = buffer_current(data); q = text_name; length = 0; label_len = buffer_read_u8(data); if (label_len == 0) { *q = '.'; q++; } while (label_len != 0 && length < NAME_MAX_LENGTH) { if ((label_len & 0xc0) == 0xc0) { buffer_set_position(data, buffer_position(data) - 1); label_len = buffer_read_u16(data); off = label_len & ~0xc0; if (off >= buffer_limit(data) /*|| decompress->pos[off] != off*/) return -1; if (saved == -1) saved = buffer_position(data); buffer_set_position(data, off); label_len = buffer_read_u8(data); } else { if (!buffer_available(data, label_len)) return -1; for (i = 0; i < label_len; i++) { if (decompress->pos[i] == -1) decompress->pos[i] = buffer_position(data); } length += label_len; p = buffer_current(data); memcpy(q, p, label_len); buffer_skip(data, label_len); q += label_len; *q = '.'; q++; label_len = buffer_read_u8(data); } } if (label_len == 0) { *q = '\0'; if (saved > -1) buffer_set_position(data, saved); } else { log_msg("dns:domain not ends with \\0\n"); return -1; } return 0; }