/* * read a zone from disk and split it up: * * zone: SOA a b c d e f g h i j k l * becomes: * zone1: SOA a b c d e f * zone2: SOA f g h i k l * * ldns-catzone removes the last name and put * the zone back together. * * This way you can incremental sign a zone * * See the file LICENSE for the license */ #include "config.h" #include #include #define DEFAULT_SPLIT 1000 #define FILE_SIZE 255 #define SPLIT_MAX 999 #define NO_SPLIT 0 #define INTENT_TO_SPLIT 1 #define SPLIT_NOW 2 static void usage(FILE *f, char *progname) { fprintf(f, "Usage: %s [OPTIONS] [keys]\n", progname); fprintf(f, " Cut a zone file into pieces, each part is put in a file\n"); fprintf(f, " named: '.NNN'. Where NNN is a integer ranging 000 to 999.\n"); fprintf(f, " If key files are given they are inserted in each part.\n"); fprintf(f, " The original SOA is also included in each part, making them correct DNS\n"); fprintf(f, " (mini) zones.\n"); fprintf(f, " This utility can be used to parallel sign a large zone.\n"); fprintf(f, " To make it work the original zone needs to be canonical ordered.\n"); fprintf(f, "\nOPTIONS:\n"); fprintf(f, " -n NUMBER\tsplit after this many RRs\n"); fprintf(f, " -o ORIGIN\tuse this as initial origin, for zones starting with @\n"); fprintf(f, " -z\t\tsort the zone prior to splitting. The current ldns zone\n"); fprintf(f, " \t\timplementation makes this unuseable for large zones.\n"); fprintf(f, " -v\t\tshow version number and exit\n"); } /* key the keys from the cmd line */ static ldns_rr_list * open_keyfiles(char **files, uint16_t filec) { uint16_t i; ldns_rr_list *pubkeys; ldns_rr *k; FILE *kfp; pubkeys = ldns_rr_list_new(); for (i = 0; i < filec; i++) { if (!(kfp = fopen(files[i], "r"))) { fprintf(stderr, "Error opening key file %s: %s\n", files[i], strerror(errno)); return NULL; } if (ldns_rr_new_frm_fp(&k, kfp, NULL, NULL, NULL) != LDNS_STATUS_OK) { fprintf(stderr, "Error parsing the key file %s: %s\n", files[i], strerror(errno)); return NULL; } fclose(kfp); ldns_rr_list_push_rr(pubkeys, k); } return pubkeys; } /* open a new zone file with the correct suffix */ static FILE * open_newfile(char *basename, ldns_zone *z, size_t counter, ldns_rr_list *keys) { char filename[FILE_SIZE]; FILE *fp; if (counter > SPLIT_MAX) { fprintf(stderr, "Maximum split count reached %u\n", (unsigned int) counter); return NULL; } snprintf(filename, FILE_SIZE, "%s.%03u", basename, (unsigned int) counter); if (!(fp = fopen(filename, "w"))) { fprintf(stderr, "Cannot open zone %s: %s\n", filename, strerror(errno)); return NULL; } else { fprintf(stderr, "%s\n", filename); } ldns_rr_print(fp, ldns_zone_soa(z)); if (keys) { ldns_rr_list_print(fp, keys); } return fp; } int main(int argc, char **argv) { char *progname; FILE *fp; ldns_zone *z; ldns_rr_list *zrrs; ldns_rdf *lastname; int c; int line_nr; size_t split; size_t i; int splitting; int compare; size_t file_counter; ldns_rdf *origin; ldns_rdf *current_rdf; ldns_rr *current_rr; ldns_rr_list *last_rrset; ldns_rr_list *pubkeys; bool sort; ldns_status s; progname = strdup(argv[0]); split = 0; splitting = NO_SPLIT; file_counter = 0; lastname = NULL; origin = NULL; last_rrset = ldns_rr_list_new(); sort = false; while ((c = getopt(argc, argv, "n:o:zv")) != -1) { switch(c) { case 'n': split = (size_t)atoi(optarg); if (split == 0) { fprintf(stderr, "-n want a integer\n"); exit(EXIT_FAILURE); } break; case 'o': origin = ldns_dname_new_frm_str(strdup(optarg)); if (!origin) { fprintf(stderr, "Cannot convert the origin %s to a domainname\n", optarg); exit(EXIT_FAILURE); } break; case 'v': printf("zone file splitter version %s (ldns version %s)\n", LDNS_VERSION, ldns_version()); exit(EXIT_SUCCESS); break; case 'z': sort = true; break; default: fprintf(stderr, "Unrecognized option\n"); usage(stdout, progname); exit(EXIT_FAILURE); } } if (split == 0) { split = DEFAULT_SPLIT; } argc -= optind; argv += optind; if (argc < 1) { usage(stdout, progname); exit(EXIT_FAILURE); } if (!(fp = fopen(argv[0], "r"))) { fprintf(stderr, "Unable to open %s: %s\n", argv[0], strerror(errno)); exit(EXIT_FAILURE); } /* get the keys */ pubkeys = open_keyfiles(argv + 1, (uint16_t) argc - 1); /* suck in the entire zone ... */ if (!origin) { origin = ldns_dname_new_frm_str("."); } s = ldns_zone_new_frm_fp_l(&z, fp, origin, 0, LDNS_RR_CLASS_IN, &line_nr); fclose(fp); if (s != LDNS_STATUS_OK) { fprintf(stderr, "Zone file %s could not be parsed correctly: %s at line %d\n", argv[0], ldns_get_errorstr_by_id(s), line_nr); exit(EXIT_FAILURE); } /* these kind of things can kill you... */ if (sort) { ldns_zone_sort(z); } zrrs = ldns_zone_rrs(z); if (ldns_rr_list_rr_count(zrrs) / split > SPLIT_MAX) { fprintf(stderr, "The zone is too large for the used -n value: %u\n", (unsigned int) split); exit(EXIT_FAILURE); } /* Setup */ if (!(fp = open_newfile(argv[0], z, file_counter, pubkeys))) { exit(EXIT_FAILURE); } for(i = 0; i < ldns_rr_list_rr_count(zrrs); i++) { current_rr = ldns_rr_list_rr(zrrs, i); current_rdf = ldns_rr_owner(current_rr); compare = ldns_dname_compare(current_rdf, lastname); if (compare == 0) { ldns_rr_list_push_rr(last_rrset, current_rr); } if (i > 0 && (i % split) == 0) { splitting = INTENT_TO_SPLIT; } if (splitting == INTENT_TO_SPLIT) { if (compare != 0) { splitting = SPLIT_NOW; } } if (splitting == SPLIT_NOW) { fclose(fp); lastname = NULL; splitting = NO_SPLIT; file_counter++; if (!(fp = open_newfile(argv[0], z, file_counter, pubkeys))) { exit(EXIT_FAILURE); } /* insert the last RRset in the new file */ ldns_rr_list_print(fp, last_rrset); /* print the current rr */ ldns_rr_print(fp, current_rr); /* remove them */ ldns_rr_list_free(last_rrset); last_rrset = ldns_rr_list_new(); /* add the current RR */ ldns_rr_list_push_rr(last_rrset, current_rr); continue; } if (splitting == NO_SPLIT || splitting == INTENT_TO_SPLIT) { ldns_rr_print(fp, current_rr); } if (compare != 0) { /* remove them and then add the current one */ ldns_rr_list_free(last_rrset); last_rrset = ldns_rr_list_new(); ldns_rr_list_push_rr(last_rrset, current_rr); } lastname = current_rdf; } fclose(fp); exit(EXIT_SUCCESS); }