/*
* dnssecjava - a DNSSEC validating stub resolver for Java
* Copyright (c) 2013-2015 Ingo Bauersachs
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* This file is based on work under the following copyright and permission
* notice:
*
* Copyright (c) 2005 VeriSign. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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 DAMAGE.
*/
package org.jitsi.dnssec;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xbill.DNS.Flags;
import org.xbill.DNS.Header;
import org.xbill.DNS.Message;
import org.xbill.DNS.Name;
import org.xbill.DNS.OPTRecord;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Record;
import org.xbill.DNS.Section;
import org.xbill.DNS.Type;
/**
* This class represents a DNS message with validator state and some utility
* methods.
*
* @author davidb
*/
public class SMessage {
private static final Logger logger = LoggerFactory.getLogger(SMessage.class);
private static final SRRset[] EMPTY_SRRSET_ARRAY = new SRRset[0];
private static final int NUM_SECTIONS = 3;
private static final int MAX_FLAGS = 16;
private static final int EXTENDED_FLAGS_BIT_OFFSET = 4;
private Header header;
private Record question;
private OPTRecord oPTRecord;
private List<SRRset>[] sections;
private SecurityStatus securityStatus;
private String bogusReason;
/**
* Creates a instance of this class.
*
* @param h The header of the original message.
*/
@SuppressWarnings("unchecked")
public SMessage(Header h) {
this.sections = new List[NUM_SECTIONS];
this.header = h;
this.securityStatus = SecurityStatus.UNCHECKED;
}
/**
* Creates a new instance of this class.
*
* @param id The ID of the DNS query or response message.
* @param question The question section of the query or response.
*/
public SMessage(int id, Record question) {
this(new Header(id));
this.question = question;
}
/**
* Creates a new instance of this class.
*
* @param m The DNS message to wrap.
*/
public SMessage(Message m) {
this(m.getHeader());
this.question = m.getQuestion();
this.oPTRecord = m.getOPT();
for (int i = Section.ANSWER; i <= Section.ADDITIONAL; i++) {
RRset[] rrsets = m.getSectionRRsets(i);
for (int j = 0; j < rrsets.length; j++) {
this.addRRset(new SRRset(rrsets[j]), i);
}
}
}
/**
* Gets the header of this message.
*
* @return The header of this message.
*/
public Header getHeader() {
return this.header;
}
/**
* Gets the question section of this message.
*
* @return The question section of this message.
*/
public Record getQuestion() {
return this.question;
}
/**
* Gets signed RRsets for the queried section.
*
* @param section The section whose RRsets are demanded.
* @return Signed RRsets for the queried section.
*/
public List<SRRset> getSectionRRsets(int section) {
this.checkSectionValidity(section);
if (this.sections[section - 1] == null) {
this.sections[section - 1] = new LinkedList<SRRset>();
}
return this.sections[section - 1];
}
private void addRRset(SRRset srrset, int section) {
this.checkSectionValidity(section);
if (srrset.getType() == Type.OPT) {
this.oPTRecord = (OPTRecord)srrset.first();
return;
}
List<SRRset> sectionList = this.getSectionRRsets(section);
sectionList.add(srrset);
}
private void checkSectionValidity(int section) {
if (section <= Section.QUESTION || section > Section.ADDITIONAL) {
throw new IllegalArgumentException("Invalid section");
}
}
/**
* Gets signed RRsets for the queried section.
*
* @param section The section whose RRsets are demanded.
* @param qtype Filter the results for these record types.
* @return Signed RRsets for the queried section.
*/
public SRRset[] getSectionRRsets(int section, int qtype) {
List<SRRset> slist = this.getSectionRRsets(section);
if (slist.size() == 0) {
return EMPTY_SRRSET_ARRAY;
}
List<SRRset> result = new ArrayList<SRRset>(slist.size());
for (SRRset rrset : slist) {
if (rrset.getType() == qtype) {
result.add(rrset);
}
}
return result.toArray(EMPTY_SRRSET_ARRAY);
}
/**
* Gets the result code of the response message.
*
* @return The result code of the response message.
*/
public int getRcode() {
int rcode = this.header.getRcode();
if (this.oPTRecord != null) {
rcode += this.oPTRecord.getExtendedRcode() << EXTENDED_FLAGS_BIT_OFFSET;
}
return rcode;
}
/**
* Gets the security status of this message.
*
* @return The security status of this message.
*/
public SecurityStatus getStatus() {
return this.securityStatus;
}
/**
* Sets the security status for this message.
*
* @param status the new security status for this message.
*/
public void setStatus(SecurityStatus status) {
this.securityStatus = status;
}
/**
* Sets the security status for this message.
*
* @param status the new security status for this message.
* @param reason Why this message's status is set as indicated.
*/
public void setStatus(SecurityStatus status, String reason) {
this.securityStatus = status;
this.bogusReason = reason;
logger.debug(this.bogusReason);
}
/**
* Gets the reason why this messages' status is bogus.
*
* @return The reason why this messages' status is bogus.
*/
public String getBogusReason() {
return this.bogusReason;
}
/**
* Sets the security status of this message to bogus and sets the reason.
*
* @param reason Why this message's status is bogus.
*/
public void setBogus(String reason) {
this.setStatus(SecurityStatus.BOGUS);
this.bogusReason = reason;
logger.debug(this.bogusReason);
}
/**
* Gets this message as a standard DNSJAVA message.
*
* @return This message as a standard DNSJAVA message.
*/
public Message getMessage() {
// Generate our new message.
Message m = new Message(this.header.getID());
// Convert the header
// We do this for two reasons:
// 1) setCount() is package scope, so we can't do that, and
// 2) setting the header on a message after creating the
// message frequently gets stuff out of sync, leading to malformed wire
// format messages.
Header h = m.getHeader();
h.setOpcode(this.header.getOpcode());
h.setRcode(this.header.getRcode());
for (int i = 0; i < MAX_FLAGS; i++) {
if (Flags.isFlag(i) && this.header.getFlag(i)) {
h.setFlag(i);
}
}
// Add all the records. -- this will set the counts correctly in the
// message header.
if (this.question != null) {
m.addRecord(this.question, Section.QUESTION);
}
for (int sec = Section.ANSWER; sec <= Section.ADDITIONAL; sec++) {
List<SRRset> slist = this.getSectionRRsets(sec);
for (SRRset rrset : slist) {
for (Iterator<?> j = rrset.rrs(); j.hasNext();) {
m.addRecord((Record)j.next(), sec);
}
for (Iterator<?> j = rrset.sigs(); j.hasNext();) {
m.addRecord((Record)j.next(), sec);
}
}
}
if (this.oPTRecord != null) {
m.addRecord(this.oPTRecord, Section.ADDITIONAL);
}
return m;
}
/**
* Gets the number of records.
*
* @param section The section for which the records are counted.
* @return The number of records for the queried section.
*/
public int getCount(int section) {
if (section == Section.QUESTION) {
return 1;
}
List<SRRset> sectionList = this.getSectionRRsets(section);
if (sectionList.size() == 0) {
return 0;
}
int count = 0;
for (SRRset sr : sectionList) {
count += sr.size();
}
return count;
}
/**
* Find a specific (S)RRset in a given section.
*
* @param name the name of the RRset.
* @param type the type of the RRset.
* @param dclass the class of the RRset.
* @param section the section to look in (ANSWER to ADDITIONAL)
*
* @return The SRRset if found, null otherwise.
*/
public SRRset findRRset(Name name, int type, int dclass, int section) {
this.checkSectionValidity(section);
for (SRRset set : this.getSectionRRsets(section)) {
if (set.getName().equals(name) && set.getType() == type && set.getDClass() == dclass) {
return set;
}
}
return null;
}
/**
* Find an "answer" RRset. This will look for RRsets in the ANSWER section
* that match the <qname,qtype,qclass>, without considering CNAMEs.
*
* @param qname The starting search name.
* @param qtype The search type.
* @param qclass The search class.
*
* @return a SRRset matching the query.
*/
public SRRset findAnswerRRset(Name qname, int qtype, int qclass) {
return this.findRRset(qname, qtype, qclass, Section.ANSWER);
}
}