1 /* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2
3 This program is free software: you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation, either version 3 of the License, or
6 (at your option) any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <https://www.gnu.org/licenses/>.
15 */
16
17 #include <assert.h>
18
19 #include "knot/catalog/generate.h"
20 #include "knot/common/log.h"
21 #include "knot/conf/conf.h"
22 #include "knot/dnssec/key-events.h"
23 #include "knot/dnssec/zone-events.h"
24 #include "knot/events/handlers.h"
25 #include "knot/events/replan.h"
26 #include "knot/zone/digest.h"
27 #include "knot/zone/serial.h"
28 #include "knot/zone/zone-diff.h"
29 #include "knot/zone/zone-load.h"
30 #include "knot/zone/zone.h"
31 #include "knot/zone/zonefile.h"
32 #include "knot/updates/acl.h"
33
dontcare_load_error(conf_t * conf,const zone_t * zone)34 static bool dontcare_load_error(conf_t *conf, const zone_t *zone)
35 {
36 return (zone->contents == NULL && zone_load_can_bootstrap(conf, zone->name));
37 }
38
allowed_xfr(conf_t * conf,const zone_t * zone)39 static bool allowed_xfr(conf_t *conf, const zone_t *zone)
40 {
41 conf_val_t acl = conf_zone_get(conf, C_ACL, zone->name);
42 while (acl.code == KNOT_EOK) {
43 conf_val_t action = conf_id_get(conf, C_ACL, C_ACTION, &acl);
44 while (action.code == KNOT_EOK) {
45 if (conf_opt(&action) == ACL_ACTION_TRANSFER) {
46 return true;
47 }
48 conf_val_next(&action);
49 }
50 conf_val_next(&acl);
51 }
52
53 return false;
54 }
55
event_load(conf_t * conf,zone_t * zone)56 int event_load(conf_t *conf, zone_t *zone)
57 {
58 zone_update_t up = { 0 };
59 zone_contents_t *journal_conts = NULL, *zf_conts = NULL;
60 bool old_contents_exist = (zone->contents != NULL), zone_in_journal_exists = false;
61
62 conf_val_t val = conf_zone_get(conf, C_JOURNAL_CONTENT, zone->name);
63 unsigned load_from = conf_opt(&val);
64
65 val = conf_zone_get(conf, C_ZONEFILE_LOAD, zone->name);
66 unsigned zf_from = conf_opt(&val);
67
68 int ret = KNOT_EOK;
69
70 // If configured, load journal contents.
71 if (!old_contents_exist &&
72 ((load_from == JOURNAL_CONTENT_ALL && zf_from != ZONEFILE_LOAD_WHOLE) ||
73 zone->cat_members != NULL)) {
74 ret = zone_load_from_journal(conf, zone, &journal_conts);
75 switch (ret) {
76 case KNOT_EOK:
77 zone_in_journal_exists = true;
78 break;
79 case KNOT_ENOENT:
80 zone_in_journal_exists = false;
81 break;
82 default:
83 goto cleanup;
84 }
85 } else {
86 zone_in_journal_exists = zone_journal_has_zij(zone);
87 }
88
89 // If configured, attempt to load zonefile.
90 if (zf_from != ZONEFILE_LOAD_NONE && zone->cat_members == NULL) {
91 struct timespec mtime;
92 char *filename = conf_zonefile(conf, zone->name);
93 ret = zonefile_exists(filename, &mtime);
94 bool zonefile_unchanged = (zone->zonefile.exists &&
95 zone->zonefile.mtime.tv_sec == mtime.tv_sec &&
96 zone->zonefile.mtime.tv_nsec == mtime.tv_nsec);
97 if (ret == KNOT_EOK) {
98 ret = zone_load_contents(conf, zone->name, &zf_conts, false);
99 }
100 if (ret != KNOT_EOK) {
101 zf_conts = NULL;
102 if (dontcare_load_error(conf, zone)) {
103 log_zone_info(zone->name, "failed to parse zone file '%s' (%s)",
104 filename, knot_strerror(ret));
105 } else {
106 log_zone_error(zone->name, "failed to parse zone file '%s' (%s)",
107 filename, knot_strerror(ret));
108 }
109 free(filename);
110 goto load_end;
111 }
112 free(filename);
113
114 // Save zonefile information.
115 zone->zonefile.serial = zone_contents_serial(zf_conts);
116 zone->zonefile.exists = (zf_conts != NULL);
117 zone->zonefile.mtime = mtime;
118
119 // If configured and possible, fix the SOA serial of zonefile.
120 zone_contents_t *relevant = (zone->contents != NULL ? zone->contents : journal_conts);
121 if (zf_conts != NULL && zf_from == ZONEFILE_LOAD_DIFSE && relevant != NULL) {
122 uint32_t serial = zone_contents_serial(relevant);
123 conf_val_t policy = conf_zone_get(conf, C_SERIAL_POLICY, zone->name);
124 uint32_t set = serial_next(serial, conf_opt(&policy), 1);
125 zone_contents_set_soa_serial(zf_conts, set);
126 log_zone_info(zone->name, "zone file parsed, serial corrected %u -> %u",
127 zone->zonefile.serial, set);
128 zone->zonefile.serial = set;
129 } else {
130 log_zone_info(zone->name, "zone file parsed, serial %u",
131 zone->zonefile.serial);
132 }
133
134 // If configured and appliable to zonefile, load journal changes.
135 bool journal_load_configured1 = (load_from == JOURNAL_CONTENT_CHANGES);
136 bool journal_load_configured2 = (load_from == JOURNAL_CONTENT_ALL);
137
138 if ((journal_load_configured1 || journal_load_configured2) &&
139 (!old_contents_exist || zonefile_unchanged)) {
140 ret = zone_load_journal(conf, zone, zf_conts);
141 if (ret != KNOT_EOK) {
142 zone_contents_deep_free(zf_conts);
143 zf_conts = NULL;
144 log_zone_warning(zone->name, "failed to load journal (%s)",
145 knot_strerror(ret));
146 }
147 }
148 }
149 if (zone->cat_members != NULL && !old_contents_exist) {
150 uint32_t serial = journal_conts == NULL ? 1 : zone_contents_serial(journal_conts);
151 serial = serial_next(serial, SERIAL_POLICY_UNIXTIME, 1); // unixtime hardcoded
152 zf_conts = catalog_update_to_zone(zone->cat_members, zone->name, serial);
153 if (zf_conts == NULL) {
154 ret = zone->cat_members->error == KNOT_EOK ? KNOT_ENOMEM : zone->cat_members->error;
155 goto cleanup;
156 }
157 }
158
159 // If configured contents=all, but not present, store zonefile.
160 if ((load_from == JOURNAL_CONTENT_ALL || zone->cat_members != NULL) &&
161 !zone_in_journal_exists && (zf_conts != NULL || old_contents_exist)) {
162 zone_contents_t *store_c = old_contents_exist ? zone->contents : zf_conts;
163 ret = zone_in_journal_store(conf, zone, store_c);
164 if (ret != KNOT_EOK) {
165 log_zone_warning(zone->name, "failed to write zone-in-journal (%s)",
166 knot_strerror(ret));
167 } else {
168 zone_in_journal_exists = true;
169 }
170 }
171
172 val = conf_zone_get(conf, C_DNSSEC_SIGNING, zone->name);
173 bool dnssec_enable = (conf_bool(&val) && zone->cat_members == NULL), zu_from_zf_conts = false;
174 bool do_diff = (zf_from == ZONEFILE_LOAD_DIFF || zf_from == ZONEFILE_LOAD_DIFSE || zone->cat_members != NULL);
175 bool ignore_dnssec = (do_diff && dnssec_enable);
176
177 // Create zone_update structure according to current state.
178 if (old_contents_exist) {
179 if (zone->cat_members != NULL) {
180 ret = zone_update_init(&up, zone, UPDATE_INCREMENTAL);
181 if (ret == KNOT_EOK) {
182 ret = catalog_update_to_update(zone->cat_members, &up);
183 }
184 if (ret == KNOT_EOK) {
185 ret = zone_update_increment_soa(&up, conf);
186 }
187 } else if (zf_conts == NULL) {
188 // nothing to be re-loaded
189 ret = KNOT_EOK;
190 goto cleanup;
191 } else if (zf_from == ZONEFILE_LOAD_WHOLE) {
192 // throw old zone contents and load new from ZF
193 ret = zone_update_from_contents(&up, zone, zf_conts,
194 (load_from == JOURNAL_CONTENT_NONE ?
195 UPDATE_FULL : UPDATE_HYBRID));
196 zu_from_zf_conts = true;
197 } else {
198 // compute ZF diff and if success, apply it
199 ret = zone_update_from_differences(&up, zone, NULL, zf_conts, UPDATE_INCREMENTAL, ignore_dnssec);
200 }
201 } else {
202 if (journal_conts != NULL && (zf_from != ZONEFILE_LOAD_WHOLE || zone->cat_members != NULL)) {
203 if (zf_conts == NULL) {
204 // load zone-in-journal
205 ret = zone_update_from_contents(&up, zone, journal_conts, UPDATE_HYBRID);
206 } else {
207 // load zone-in-journal, compute ZF diff and if success, apply it
208 ret = zone_update_from_differences(&up, zone, journal_conts, zf_conts,
209 UPDATE_HYBRID, ignore_dnssec);
210 if (ret == KNOT_ESEMCHECK || ret == KNOT_ERANGE) {
211 log_zone_warning(zone->name,
212 "zone file changed with SOA serial %s, "
213 "ignoring zone file and loading from journal",
214 (ret == KNOT_ESEMCHECK ? "unupdated" : "decreased"));
215 zone_contents_deep_free(zf_conts);
216 zf_conts = NULL;
217 ret = zone_update_from_contents(&up, zone, journal_conts, UPDATE_HYBRID);
218 }
219 }
220 } else {
221 if (zf_conts == NULL) {
222 // nothing to be loaded
223 ret = KNOT_ENOENT;
224 } else {
225 // load from ZF
226 ret = zone_update_from_contents(&up, zone, zf_conts,
227 (load_from == JOURNAL_CONTENT_NONE ?
228 UPDATE_FULL : UPDATE_HYBRID));
229 if (zf_from == ZONEFILE_LOAD_WHOLE) {
230 zu_from_zf_conts = true;
231 }
232 }
233 }
234 }
235
236 load_end:
237 if (ret != KNOT_EOK) {
238 switch (ret) {
239 case KNOT_ENOENT:
240 if (zone_load_can_bootstrap(conf, zone->name)) {
241 log_zone_info(zone->name, "zone will be bootstrapped");
242 } else {
243 log_zone_info(zone->name, "zone not found");
244 }
245 break;
246 case KNOT_ESEMCHECK:
247 log_zone_warning(zone->name, "zone file changed without SOA serial update");
248 break;
249 case KNOT_ERANGE:
250 log_zone_warning(zone->name, "zone file changed, but SOA serial decreased");
251 break;
252 }
253 goto cleanup;
254 }
255
256 bool zf_serial_updated = (zf_conts != NULL && zone_contents_serial(zf_conts) != zone_contents_serial(zone->contents));
257
258 // The contents are already part of zone_update.
259 zf_conts = NULL;
260 journal_conts = NULL;
261
262 ret = zone_update_verify_digest(conf, &up);
263 if (ret != KNOT_EOK) {
264 goto cleanup;
265 }
266
267 uint32_t middle_serial = zone_contents_serial(up.new_cont);
268
269 if (do_diff && old_contents_exist && dnssec_enable && zf_serial_updated &&
270 !zone_in_journal_exists) {
271 ret = zone_update_start_extra(&up, conf);
272 if (ret != KNOT_EOK) {
273 goto cleanup;
274 }
275 }
276
277 val = conf_zone_get(conf, C_ZONEMD_GENERATE, zone->name);
278 unsigned digest_alg = conf_opt(&val);
279
280 // Sign zone using DNSSEC if configured.
281 zone_sign_reschedule_t dnssec_refresh = { 0 };
282 if (dnssec_enable) {
283 ret = knot_dnssec_zone_sign(&up, conf, 0, KEY_ROLL_ALLOW_ALL, 0, &dnssec_refresh);
284 if (ret != KNOT_EOK) {
285 goto cleanup;
286 }
287 if (zu_from_zf_conts && (up.flags & UPDATE_HYBRID) && allowed_xfr(conf, zone)) {
288 log_zone_warning(zone->name,
289 "with automatic DNSSEC signing and outgoing transfers enabled, "
290 "'zonefile-load: difference' should be set to avoid malformed "
291 "IXFR after manual zone file update");
292 }
293 } else if (digest_alg != ZONE_DIGEST_NONE) {
294 if (zone_update_to(&up) == NULL || middle_serial == zone->zonefile.serial) {
295 ret = zone_update_increment_soa(&up, conf);
296 }
297 if (ret == KNOT_EOK) {
298 ret = zone_update_add_digest(&up, digest_alg, false);
299 }
300 if (ret != KNOT_EOK) {
301 goto cleanup;
302 }
303 }
304
305 // If the change is only automatically incremented SOA serial, make it no change.
306 if ((zf_from == ZONEFILE_LOAD_DIFSE || zone->cat_members != NULL) &&
307 (up.flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) &&
308 changeset_differs_just_serial(&up.change)) {
309 changeset_t *cpy = changeset_clone(&up.change);
310 if (cpy == NULL) {
311 ret = KNOT_ENOMEM;
312 goto cleanup;
313 }
314 ret = zone_update_apply_changeset_reverse(&up, cpy);
315 changeset_free(cpy);
316 if (ret != KNOT_EOK) {
317 goto cleanup;
318 }
319 }
320
321 uint32_t old_serial = 0, new_serial = zone_contents_serial(up.new_cont);
322 char old_serial_str[11] = "none", new_serial_str[15] = "";
323 if (old_contents_exist) {
324 old_serial = zone_contents_serial(zone->contents);
325 (void)snprintf(old_serial_str, sizeof(old_serial_str), "%u", old_serial);
326 }
327 if (new_serial != middle_serial) {
328 (void)snprintf(new_serial_str, sizeof(new_serial_str), " -> %u", new_serial);
329 }
330
331 // Commit zone_update back to zone (including journal update, rcu,...).
332 ret = zone_update_commit(conf, &up);
333 if (ret != KNOT_EOK) {
334 goto cleanup;
335 }
336
337 log_zone_info(zone->name, "loaded, serial %s -> %u%s, %zu bytes",
338 old_serial_str, middle_serial, new_serial_str, zone->contents->size);
339
340 if (zone->cat_members != NULL) {
341 catalog_update_clear(zone->cat_members);
342 }
343
344 // Schedule depedent events.
345 const knot_rdataset_t *soa = zone_soa(zone);
346 zone->timers.soa_expire = knot_soa_expire(soa->rdata);
347
348 if (dnssec_enable) {
349 event_dnssec_reschedule(conf, zone, &dnssec_refresh, false); // false since we handle NOTIFY below
350 }
351
352 replan_from_timers(conf, zone);
353
354 if (!zone_timers_serial_notified(&zone->timers, new_serial)) {
355 zone_events_schedule_now(zone, ZONE_EVENT_NOTIFY);
356 }
357
358 return KNOT_EOK;
359
360 cleanup:
361 // Try to bootstrap the zone if local error.
362 replan_from_timers(conf, zone);
363
364 zone_update_clear(&up);
365 zone_contents_deep_free(zf_conts);
366 zone_contents_deep_free(journal_conts);
367
368 return (dontcare_load_error(conf, zone) ? KNOT_EOK : ret);
369 }
370