1 /*
2  * Reads a zone file from disk and prints it to stdout, one RR per line.
3  * Adds artificial DS records and RRs.
4  * For the purpose of generating a test zone file
5  *
6  * (c) SIDN 2010/2011 - Marco Davids/Miek Gieben
7  *
8  * See the LICENSE file for the license
9  */
10 
11 #include "config.h"
12 #include <unistd.h>
13 #include <stdlib.h>
14 #include <ldns/ldns.h>
15 #include <errno.h>
16 
17 #define NUM_DS 4                /* maximum of 4 DS records per delegation */
18 #define ALGO 8			/* Algorithm to use for fake DS records - RSASHA256 - RFC5702  */
19 #define DIGESTTYPE 2		/* Digest type to use for fake DS records - SHA-256 - RFC 4509 */
20 
21 
22 /**
23  * Usage function.
24  *
25  */
26 static void
usage(FILE * fp,char * prog)27 usage(FILE *fp, char *prog) {
28         fprintf(fp, "\n\nUsage: %s [-hsv] [-ap NUM] [-o ORIGIN] [<zonefile>]\n", prog);
29         fprintf(fp, "\tReads a zonefile and add some artificial NS RRsets and DS records.\n");
30         fprintf(fp, "\tIf no zonefile is given, the zone is read from stdin.\n");
31         fprintf(fp, "\t-a <NUM> add NUM artificial delegations (NS RRSets) to output.\n");
32         fprintf(fp, "\t-p <NUM> add NUM percent of DS RRset's to the NS RRsets (1-%d RR's per DS RRset).\n", NUM_DS);
33         fprintf(fp, "\t-o ORIGIN sets an $ORIGIN, which can be handy if the one in the zonefile is set to @.\n");
34         fprintf(fp, "\t-s if input zone file is already sorted and canonicalized (ie all lowercase),\n\t   use this option to speed things up while inserting DS records.\n");
35         fprintf(fp, "\t-h show this text.\n");
36         fprintf(fp, "\t-v shows the version and exits.\n");
37         fprintf(fp, "\nif no file is given standard input is read.\n\n");
38 }
39 
40 /**
41  * Insert the DS records, return the amount added.
42  *
43  */
44 static int
insert_ds(ldns_rdf * dsowner,uint32_t ttl)45 insert_ds(ldns_rdf *dsowner, uint32_t ttl)
46 {
47         int d, dsrand;
48         int keytag = 0;
49         char *dsownerstr;
50         char digeststr[70];
51 
52         /**
53          * Average the amount of DS records per delegation a little.
54          */
55         dsrand = 1+rand() % NUM_DS;
56         for(d = 0; d < dsrand; d++) {
57                 keytag = 1+rand() % 65535;
58                 /**
59                  * Dynamic hashes method below is still too slow... 20% slower than a fixed string...
60                  *
61                  * We assume RAND_MAX is 32 bit, http://www.gnu.org/s/libc/manual/html_node/ISO-Random.html
62                  * 2147483647 or 0x7FFFFFFF
63                  */
64                 snprintf(digeststr, 65,
65                         "%08x%08x%08x%08x%08x%08x%08x%08x",
66                         (unsigned) rand()%RAND_MAX, (unsigned) rand()%RAND_MAX, (unsigned) rand()%RAND_MAX,
67                         (unsigned) rand()%RAND_MAX, (unsigned) rand()%RAND_MAX, (unsigned) rand()%RAND_MAX,
68                         (unsigned) rand()%RAND_MAX, (unsigned) rand()%RAND_MAX);
69                 dsownerstr = ldns_rdf2str(dsowner);
70                 fprintf(stdout, "%s\t%u\tIN\tDS\t%d %d %d %s\n", dsownerstr, (unsigned) ttl, keytag, ALGO, DIGESTTYPE, digeststr);
71         }
72         return dsrand;
73 }
74 
75 int
main(int argc,char ** argv)76 main(int argc, char **argv) {
77         char *filename, *rrstr, *ownerstr;
78         const char *classtypestr1 = "IN NS ns1.example.com.";
79         const char *classtypestr2 = "IN NS ns2.example.com.";
80         const size_t classtypelen = strlen(classtypestr1);
81         /* Simply because this was developed by SIDN and we don't use xn-- for .nl :-) */
82         const char *punystr = "xn--fake-rr";
83         const size_t punylen = strlen(punystr);
84         size_t rrstrlen, ownerlen;
85         FILE *fp;
86         int c, nsrand;
87         uint32_t ttl;
88         int counta,countd,countr;
89         ldns_zone *z;
90         ldns_rdf *origin = NULL;
91         int line_nr = 0;
92         int addrrs = 0;
93         int dsperc = 0;
94         bool canonicalize = true;
95         bool sort = true;
96         bool do_ds = false;
97         ldns_status s;
98         size_t i;
99         ldns_rr_list *rrset_list;
100         ldns_rdf *owner;
101         ldns_rr_type cur_rr_type;
102         ldns_rr *cur_rr;
103         ldns_status status;
104 
105         counta = countd = countr = 0;
106 
107         /**
108          * Set some random seed.
109          */
110         srand((unsigned int)time(NULL));
111 
112         /**
113          * Commandline options.
114          */
115         while ((c = getopt(argc, argv, "a:p:shvo:")) != -1) {
116                 switch (c) {
117         	    case 'a':
118                         addrrs = atoi(optarg);
119                         if (addrrs <= 0) {
120                                 fprintf(stderr, "error\n");
121                                 exit(EXIT_FAILURE);
122                         }
123                         break;
124                 case 'o':
125                         origin = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_DNAME, optarg);
126                         if (!origin) {
127                                 fprintf(stderr, "error: creating origin from -o %s failed.\n", optarg);
128                                 exit(EXIT_FAILURE);
129                         }
130                         break;
131                 case 'p':
132                         dsperc = atoi(optarg);
133                         if (dsperc < 0 || dsperc > 100) {
134                                 fprintf(stderr, "error: percentage of signed delegations must be between [0-100].\n");
135                                 exit(EXIT_FAILURE);
136                         }
137                         do_ds = true;
138                         break;
139                 case 's':
140                         sort = false;
141                         canonicalize = false;
142                         break;
143                 case 'h':
144                         usage(stdout, argv[0]);
145                         exit(EXIT_SUCCESS);
146                 case 'v':
147                         fprintf(stdout, "ldns-gen-zone version %s (ldns version %s)\n", LDNS_VERSION, ldns_version());
148                         exit(EXIT_SUCCESS);
149                 default:
150                         fprintf(stderr, "\nTry -h for more information.\n\n");
151                         exit(EXIT_FAILURE);
152 	        }
153         }
154         argc -= optind;
155         argv += optind;
156 
157         /**
158          * Read zone.
159          */
160         if (argc == 0) {
161                 fp = stdin;
162         } else {
163                 filename = argv[0];
164                 fp = fopen(filename, "r");
165                 if (!fp) {
166 	                fprintf(stderr, "Unable to open %s: %s\n", filename, strerror (errno));
167                 	exit(EXIT_FAILURE);
168 	        }
169         }
170         s = ldns_zone_new_frm_fp_l(&z, fp, origin, 0, LDNS_RR_CLASS_IN, &line_nr);
171         if (s != LDNS_STATUS_OK) {
172                 fprintf(stderr, "%s at line %d\n", ldns_get_errorstr_by_id(s), line_nr);
173                 exit(EXIT_FAILURE);
174         }
175         if (!ldns_zone_soa(z)) {
176                 fprintf(stderr, "No zone data seen\n");
177                 exit(EXIT_FAILURE);
178         }
179 
180         ttl = ldns_rr_ttl(ldns_zone_soa(z));
181         if (!origin) {
182                 origin = ldns_rr_owner(ldns_zone_soa(z));
183                 // Check for root (.) origin here TODO(MG)
184         }
185         ownerstr = ldns_rdf2str(origin);
186         if (!ownerstr) {
187                 fprintf(stderr, "ldns_rdf2str(origin) failed\n");
188                 exit(EXIT_FAILURE);
189         }
190         ownerlen = strlen(ownerstr);
191 
192         ldns_rr_print(stdout, ldns_zone_soa(z));
193 
194         if (addrrs > 0) {
195                 while (addrrs > counta) {
196                         counta++;
197                         rrstrlen = punylen + ownerlen + classtypelen + 4;
198                         rrstrlen *= 2; /* estimate */
199                         rrstr = (char*)malloc(rrstrlen);
200                         if (!rrstr) {
201                                 fprintf(stderr, "malloc() failed: Out of memory\n");
202                                 exit(EXIT_FAILURE);
203                         }
204                         (void)snprintf(rrstr, rrstrlen, "%s%d.%s %u %s", punystr, counta,
205                                 ownerstr, (unsigned) ttl, classtypestr1);
206                         status = ldns_rr_new_frm_str(&cur_rr, rrstr, 0, NULL, NULL);
207                         if (status == LDNS_STATUS_OK) {
208                                 ldns_rr_print(stdout, cur_rr);
209                                 ldns_rr_free(cur_rr);
210                         } else {
211                                 fprintf(stderr, "ldns_rr_new_frm_str() failed\n");
212                                 exit(EXIT_FAILURE);
213                         }
214 
215                         (void)snprintf(rrstr, rrstrlen, "%s%d.%s %u %s", punystr, counta,
216                                 ownerstr, (unsigned) ttl, classtypestr2);
217                         status = ldns_rr_new_frm_str(&cur_rr, rrstr, 0, NULL, NULL);
218                         if (status == LDNS_STATUS_OK) {
219                                 ldns_rr_print(stdout, cur_rr);
220                         } else {
221                                 fprintf(stderr, "ldns_rr_new_frm_str() failed\n");
222                                 exit(EXIT_FAILURE);
223                         }
224 
225                         free(rrstr);
226 
227                         /* may we add a DS record as well? */
228                         if (do_ds) {
229                                 /*
230                                  * Per definition this may not be the same as the origin, so no
231                                  * check required same for NS check - so the only thing left is some
232                                  * randomization.
233                                  */
234                                 nsrand = rand() % 100;
235                                 if (nsrand < dsperc) {
236                                         owner = ldns_rr_owner(cur_rr);
237                                         ttl = ldns_rr_ttl(cur_rr);
238                                         countd += insert_ds(owner, ttl);
239                                 }
240                         }
241                         ldns_rr_free(cur_rr);
242                 }
243         }
244 
245         if (!do_ds) {
246                 ldns_rr_list_print(stdout, ldns_zone_rrs(z));
247         } else {
248                 /*
249                 * We use dns_rr_list_pop_rrset and that requires a sorted list weird things may happen
250                 * if the -s option was used on unsorted, non-canonicalized input
251                 */
252                 if (canonicalize) {
253                         ldns_rr2canonical(ldns_zone_soa(z));
254                         for (i = 0; i < ldns_rr_list_rr_count(ldns_zone_rrs(z)); i++) {
255                                 ldns_rr2canonical(ldns_rr_list_rr(ldns_zone_rrs(z), i));
256                         }
257                 }
258 
259                 if (sort) {
260                         ldns_zone_sort(z);
261                 }
262 
263                 /* Work on a per RRset basis for DS records - weird things will happen if the -s option
264                  * was used in combination with an unsorted zone file
265                  */
266                 while((rrset_list = ldns_rr_list_pop_rrset(ldns_zone_rrs(z)))) {
267                         owner = ldns_rr_list_owner(rrset_list);
268                         cur_rr_type = ldns_rr_list_type(rrset_list);
269                         /**
270                          * Print them...
271                          */
272                         cur_rr = ldns_rr_list_pop_rr(rrset_list);
273                         while (cur_rr) {
274                                 ttl = ldns_rr_ttl(cur_rr);
275                                 fprintf(stdout, "%s", ldns_rr2str(cur_rr));
276                                 cur_rr = ldns_rr_list_pop_rr(rrset_list);
277                         }
278                         /*
279                          * And all the way at the end a DS record if
280                          * we are dealing with an NS rrset
281                          */
282                         nsrand = rand() % 100;
283                         if (nsrand == 0) {
284                                 nsrand = 100;
285                         }
286 
287                         if ((cur_rr_type == LDNS_RR_TYPE_NS) &&
288                                 (ldns_rdf_compare(owner, origin) != 0) && (nsrand < dsperc)) {
289                                 /**
290                                  * No DS records for the $ORIGIN, only for delegations, obey dsperc.
291                                  */
292                                 countr++;
293                                 countd += insert_ds(owner, ttl);
294                         }
295                         ldns_rr_list_free(rrset_list);
296                         ldns_rdf_free(owner);
297                 }
298         }
299 
300         /**
301          * And done...
302          */
303         fclose(fp);
304         fprintf(stdout, ";; Added %d DS records (percentage was %d) to %d NS RRset's (from input-zone: %d, from added: %d)\n;; lines in original input-zone: %d\n",
305                 countd, dsperc, counta + countr, countr, counta, line_nr);
306         exit(EXIT_SUCCESS);
307 }
308