Branch data Line data Source code
1 : : // Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
2 : : //
3 : : // Permission to use, copy, modify, and/or distribute this software for any
4 : : // purpose with or without fee is hereby granted, provided that the above
5 : : // copyright notice and this permission notice appear in all copies.
6 : : //
7 : : // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
8 : : // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9 : : // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
10 : : // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11 : : // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
12 : : // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13 : : // PERFORMANCE OF THIS SOFTWARE.
14 : :
15 : : #include <istream>
16 : : #include <fstream>
17 : : #include <sstream>
18 : : #include <string>
19 : : #include <cctype>
20 : :
21 : : #include <boost/scoped_ptr.hpp>
22 : :
23 : : #include <exceptions/exceptions.h>
24 : :
25 : : #include <dns/masterload.h>
26 : : #include <dns/name.h>
27 : : #include <dns/rdata.h>
28 : : #include <dns/rdataclass.h>
29 : : #include <dns/rrclass.h>
30 : : #include <dns/rrset.h>
31 : : #include <dns/rrttl.h>
32 : : #include <dns/rrtype.h>
33 : :
34 : : using namespace std;
35 : : using namespace boost;
36 : : using namespace isc::dns::rdata;
37 : :
38 : : namespace isc {
39 : : namespace dns {
40 : : namespace {
41 : : // A helper function that strips off any comment or whitespace at the end of
42 : : // an RR.
43 : : // This is an incomplete implementation, and cannot handle all such comments;
44 : : // it's considered a short term workaround to deal with some real world
45 : : // cases.
46 : : string
47 : 36 : stripLine(string& s, const Exception& ex) {
48 : : // Find any ';' in the text data, and locate the position of the last
49 : : // occurrence. Note that unless/until we support empty RDATA it
50 : : // shouldn't be placed at the beginning of the data.
51 : 36 : const size_t pos_semicolon = s.rfind(';');
52 [ + + ]: 36 : if (pos_semicolon == 0) {
53 : 2 : throw ex;
54 [ + + ]: 35 : } else if (pos_semicolon != string::npos) {
55 : 32 : s.resize(pos_semicolon);
56 : : }
57 : : // Remove any trailing whitespace return the resulting text.
58 : 35 : s.resize(s.find_last_not_of(" \t") + 1);
59 : 35 : return (s);
60 : : }
61 : : }
62 : :
63 : : void
64 : 61 : masterLoad(const char* const filename, const Name& origin,
65 : : const RRClass& zone_class, MasterLoadCallback callback)
66 : : {
67 [ + + ][ - + ]: 61 : if ((filename == NULL) || (*filename == '\0')) {
68 [ + - ][ + - ]: 2 : isc_throw(MasterLoadError, "Name of master file must not be null");
69 : : }
70 : :
71 : 120 : ifstream ifs;
72 [ + - ]: 60 : ifs.open(filename, ios_base::in);
73 [ + + ]: 60 : if (ifs.fail()) {
74 [ + - ][ + - ]: 6 : isc_throw(MasterLoadError, "Failed to open master file: " << filename);
[ + - ][ + - ]
75 : : }
76 [ + + ]: 57 : masterLoad(ifs, origin, zone_class, callback);
77 [ + - ]: 52 : ifs.close();
78 : 52 : }
79 : :
80 : : void
81 : 590 : masterLoad(istream& input, const Name& origin, const RRClass& zone_class,
82 : : MasterLoadCallback callback)
83 : : {
84 : : RRsetPtr rrset;
85 : : ConstRdataPtr prev_rdata; // placeholder for special case of RRSIGs
86 : 590 : string line;
87 : : unsigned int line_count = 1;
88 : :
89 [ + + ]: 11781 : do {
90 [ + - ]: 11799 : getline(input, line);
91 [ + + ][ + + ]: 11799 : if (input.bad() || (input.fail() && !input.eof())) {
[ + - ][ + + ]
92 [ + - ][ + - ]: 2 : isc_throw(MasterLoadError, "Unexpectedly failed to read a line");
[ + - ]
93 : : }
94 : :
95 : : // blank/comment lines should be simply skipped.
96 [ + + ][ + + ]: 22771 : if (line.empty() || line[0] == ';') {
[ + + ]
97 : 1370 : continue;
98 : : }
99 : :
100 : : // The line shouldn't have leading space (which means omitting the
101 : : // owner name).
102 [ + + ]: 10428 : if (isspace(line[0])) {
103 [ + - ][ + - ]: 6 : isc_throw(MasterLoadError, "Leading space at line " << line_count);
[ + - ]
104 : : }
105 : :
106 : : // Parse a single RR
107 [ + - + - ]: 20852 : istringstream iss(line);
108 : 10426 : string owner_txt, ttl_txt, rrclass_txt, rrtype_txt;
109 : 20852 : stringbuf rdatabuf;
110 [ + - ][ + - ]: 10426 : iss >> owner_txt >> ttl_txt >> rrclass_txt >> rrtype_txt >> &rdatabuf;
[ + - ][ + - ]
[ + - ]
111 [ + - ][ + + ]: 10426 : if (iss.bad() || iss.fail()) {
[ + + ]
112 [ + - ][ + - ]: 3 : isc_throw(MasterLoadError, "Parse failure for a valid RR at line "
[ + - ]
113 : : << line_count);
114 : : }
115 : :
116 : : // This simple version doesn't support relative owner names with a
117 : : // separate origin.
118 [ + - ][ + - ]: 10425 : if (owner_txt.empty() || *(owner_txt.end() - 1) != '.') {
[ + + ][ + + ]
119 [ + - ][ + - ]: 3 : isc_throw(MasterLoadError, "Owner name is not absolute at line "
[ + - ]
120 : : << line_count);
121 : : }
122 : :
123 : : // XXX: this part is a bit tricky (and less efficient). We are going
124 : : // to validate the text for the RR parameters, and throw an exception
125 : : // if any of them is invalid by converting an underlying exception
126 : : // to MasterLoadError. To do that, we need to define the corresponding
127 : : // variables used for RRset construction outside the try-catch block,
128 : : // but we don't like to use a temporary variable with a meaningless
129 : : // initial value. So we define pointers outside the try block
130 : : // and allocate/initialize the actual objects within the block.
131 : : // To make it exception safe we use Boost.scoped_ptr.
132 : 10424 : scoped_ptr<const Name> owner;
133 : : scoped_ptr<const RRTTL> ttl;
134 : : scoped_ptr<const RRClass> rrclass;
135 : : scoped_ptr<const RRType> rrtype;
136 : : ConstRdataPtr rdata;
137 : : try {
138 [ + - ][ + + ]: 10424 : owner.reset(new Name(owner_txt));
139 [ + - ][ + + ]: 10423 : ttl.reset(new RRTTL(ttl_txt));
140 [ + - ][ + + ]: 10422 : rrclass.reset(new RRClass(rrclass_txt));
141 [ + - ][ + + ]: 10421 : rrtype.reset(new RRType(rrtype_txt));
142 [ + - ]: 20840 : string rdtext = rdatabuf.str();
143 : : try {
144 [ + + ][ + - ]: 10420 : rdata = createRdata(*rrtype, *rrclass, rdtext);
145 [ - + ]: 72 : } catch (const Exception& ex) {
146 : : // If the parse for the RDATA fails, check if it has comments
147 : : // or whitespace at the end, and if so, retry the conversion
148 : : // after stripping off the comment or whitespace
149 [ + + ][ + + ]: 70 : rdata = createRdata(*rrtype, *rrclass, stripLine(rdtext, ex));
[ - + ]
150 : : }
151 [ - + ]: 12 : } catch (const Exception& ex) {
152 [ - + ][ - + ]: 18 : isc_throw(MasterLoadError, "Invalid RR text at line " << line_count
[ - + ][ - + ]
[ - + ]
153 : : << ": " << ex.what());
154 : : }
155 : :
156 : : // Origin related validation:
157 : : // - reject out-of-zone data
158 : : // - reject SOA whose owner is not at the top of zone
159 [ + - ]: 10418 : const NameComparisonResult cmp_result = owner->compare(origin);
160 [ + + ][ + + ]: 10418 : if (cmp_result.getRelation() != NameComparisonResult::EQUAL &&
[ + + ]
161 : 9454 : cmp_result.getRelation() != NameComparisonResult::SUBDOMAIN) {
162 [ + - ][ + - ]: 12 : isc_throw(MasterLoadError, "Out-of-zone data at line "
[ + - ]
163 : : << line_count);
164 : : }
165 [ + + ][ + + ]: 10414 : if (*rrtype == RRType::SOA() &&
[ + + ]
166 : 194 : cmp_result.getRelation() != NameComparisonResult::EQUAL) {
167 [ + - ][ + - ]: 6 : isc_throw(MasterLoadError, "SOA not at top of zone at line "
[ + - ]
168 : : << line_count);
169 : : }
170 : :
171 : : // Reject RR class mismatching
172 [ + + ]: 10412 : if (*rrclass != zone_class) {
173 [ + - ][ + - ]: 3 : isc_throw(MasterLoadError, "RR class (" << rrclass_txt
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ]
174 : : << ") does not match the zone class (" << zone_class
175 : : << ") at line " << line_count);
176 : : }
177 : :
178 : : // Everything is okay. Now create/update RRset with the new RR.
179 : : // If this is the first RR or the RR type/name is new, we are seeing
180 : : // a new RRset.
181 : 10411 : bool new_rrset = false;
182 [ + + ][ + - ]: 13481 : if (!rrset || rrset->getType() != *rrtype ||
[ + + ][ + + ]
[ + + ]
183 [ + - ]: 3070 : rrset->getName() != *owner) {
184 : : new_rrset = true;
185 [ + - ][ + + ]: 1067 : } else if (rrset->getType() == RRType::RRSIG()) {
186 : : // We are seeing two consecutive RRSIGs of the same name.
187 : : // They can be combined iff they have the same type covered.
188 [ + - ][ + - ]: 8 : if (dynamic_cast<const generic::RRSIG&>(*rdata).typeCovered() !=
[ + + ]
189 [ + - ][ + - ]: 8 : dynamic_cast<const generic::RRSIG&>(*prev_rdata).typeCovered())
190 : : {
191 : 6 : new_rrset = true;
192 : : }
193 : : }
194 [ + + ]: 10411 : if (new_rrset) {
195 : : // Commit the previous RRset, if any.
196 [ + + ]: 9350 : if (rrset) {
197 [ + - ]: 8777 : callback(rrset);
198 : : }
199 [ + - ][ + - ]: 18700 : rrset = RRsetPtr(new RRset(*owner, *rrclass, *rrtype, *ttl));
[ + - ][ + - ]
200 : : }
201 [ + - ]: 20822 : rrset->addRdata(rdata);
202 : : prev_rdata = rdata;
203 : 11781 : } while (++line_count, !input.eof());
204 : :
205 : : // Commit the last RRset, if any.
206 [ + + ]: 572 : if (rrset) {
207 [ + + ]: 571 : callback(rrset);
208 : : }
209 : 571 : }
210 : : } // namespace dns
211 : 137 : } // namespace isc
|