/***************************************************************** ** ** @(#) gen6dns.c (c) Feb 2009 - Nov 2015 by hznet H.Zuleger ** *****************************************************************/ #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H # include "config.h" #endif #if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG # include #endif #include "debug.h" #include "misc.h" #include "ip6.h" #include "hostid.h" #include "subnet.h" #include "scope.h" #include "filep.h" #include "parse.h" #include "squeezev6.h" #include "hostptr.h" #define extern #include "gen6dns.h" #undef extern extern int optopt; extern int opterr; extern int optind; extern char *optarg; /* option vars */ const char *version = VERSION; #if MAXFILEP <= MAXSNET # error "The maximum number of file pointers must be larger than the # of subnets\n" # error "(please change MAXFILEP and/or MAXSNET)\n" #endif #define BINDCONFIG 0 /* for later version */ static int revzone = 0; #if defined(BINDCONFIG) && BINDCONFIG int bindconfig; const char *multiline; #endif #if defined(DYNUPDATE) && DYNUPDATE # if defined(BINDCONFIG) && BINDCONFIG /* don't remove leading ':' (see man getopt_long) */ # define short_options ":6:aBdl:C::D::frb:hmo:p:P:RsSt:vVw" # else # define short_options ":6:adl:C::D::frb:ho:p:P:RsSt:vVw" #endif #else # if defined(BINDCONFIG) && BINDCONFIG # define short_options ":6:aBC::D::frb:hmo:p:RsSt:vVw" # else # define short_options ":6:aC::D::frb:ho:p:RsSt:vVw" # endif #endif #if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG static struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "revzone", no_argument, NULL, 'R' }, { "verbose", no_argument, NULL, 'v' }, { "comment", optional_argument, NULL, 'C' }, { "write", no_argument, NULL, 'w' }, { "append", no_argument, NULL, 'a' }, #if defined(BINDCONFIG) && BINDCONFIG { "bind-config", no_argument, NULL, 'B' }, { "multiline", no_argument, NULL, 'm' }, #endif { "forward", no_argument, NULL, 'f' }, { "reverse", no_argument, NULL, 'r' }, { "bits", required_argument, NULL, 'b' }, { "ttl", required_argument, NULL, 't' }, { "6to4", required_argument, NULL, '6' }, { "origin", required_argument, NULL, 'o' }, { "delim", optional_argument, NULL, 'D' }, { "prefix", required_argument, NULL, 'p' }, /* lower p */ { "prefix-add", required_argument, NULL, 'p' }, /* lower p */ { "add-prefix", required_argument, NULL, 'p' }, /* lower p */ #if defined(DYNUPDATE) && DYNUPDATE { "prefix-remove", required_argument, NULL, 'P' }, /* upper P */ { "prefix-del", required_argument, NULL, 'P' }, /* upper P */ { "del-prefix", required_argument, NULL, 'P' }, /* upper P */ { "ddns-update", no_argument, NULL, 'd' }, { "lookup", required_argument, NULL, 'l' }, #endif { "squeeze", no_argument, NULL, 's' }, { "full-squeeze", no_argument, NULL, 'S' }, }; #endif /* static function declaration */ static void usage (const char *mesg); static int gen_dns (int type, const ip6_t *prfx, const scope_t *scope, const host_t *host, const ip6_t *hid, ip6_t *sid, const char *extra_rr); static int gen_forw_upd (int del, const ip6_t *prfx, const scope_t *scope, const host_t *host, const ip6_t *hid, ip6_t *sid, const char *extra_rr); static int gen_forw_dns (const ip6_t *prfx, const scope_t *scope, const host_t *host, const ip6_t *hid, ip6_t *sid, const char *extra_rr); static int gen_rev_dns (const ip6_t *prfx, const scope_t *scope, const host_t *host, const ip6_t *hid, ip6_t *sid); static int print_forw_upd (FILE *fp, int del, const char *host, const ip6_t *ipv6, long ttl); static int print_forw_dns (FILE *fp, const char *host, const ip6_t *ipv6, long ttl); static int print_rev_dns (FILE *fp, const char *host, const char *origin, const ip6_t *ip, int nibbles, long ttl, unsigned long subnetid); #if defined(BINDCONFIG) && BINDCONFIG static void print_zoneconfig (const char *zone, const char *fname, const char *multiline); #endif int main (int argc, char*argv[]) { FILE *outfp; int opt_index; int c; char tmpstr[127+1]; if ( (progname = strrchr (*argv, '/')) == NULL ) progname = *argv; else progname++; opterr = 0; /* prevent the printing of the getopt() build-in error messages */ parm.forw = parm.rev = 0; parm.revbits = 0; parm.filemode = 0; #if defined(BINDCONFIG) && BINDCONFIG bindconfig = 0; multiline = ""; #endif #if defined(HAVE_GETOPT_LONG) && HAVE_GETOPT_LONG while ( (c = getopt_long (argc, argv, short_options, long_options, &opt_index)) != -1 ) #else while ( (c = getopt (argc, argv, short_options)) != -1 ) #endif { switch ( c ) { case 'V': fprintf (stderr, "%s version %s\n", progname, version); fprintf (stderr, "based on but not compatible with gen6dns v0.3 (c) Feb 2009 - Apr 2010\n"); fprintf (stderr, "compiled for a maximum of \n"); fprintf (stderr, "\t%5d different prefixes\n", MAXPREFIXES); fprintf (stderr, "\t%5d different scopes\n", MAXSCOPES); fprintf (stderr, "\t%5d subnets\n", MAXSNET); fprintf (stderr, "\t%5d file handles\n", MAXFILEP); exit (0); break; case 'v': parm.verbose++; break; case 'w': parm.filemode = 1; break; case 'a': parm.filemode = 2; break; #if defined(BINDCONFIG) && BINDCONFIG case 'B': bindconfig = 1; break; case 'm': multiline = "\n"; break; #endif case 'f': parm.forw = 1; break; case 'r': parm.rev = 1; break; case 'b': parm.revbits = atoi (optarg); if ( parm.revbits < 0 || parm.revbits > 128 ) usage ("illegal reverse mask"); if ( parm.revbits % 4 != 0 ) usage ("Option -b: reverse mask must be on nibble boundary"); break; case 'R': revzone = 1; break; case 's': parm.squeeze++; break; case 'S': parm.squeeze = 2; break; case 'h': usage (NULL); break; case 't': ttlfromstr (optarg, &parm.ttl); break; case 'C': if ( optarg ) { if ( *optarg == parm.delim || isspace (*optarg)) { error ("Option -C \'%c\' would change the comment char to the delimiter char. Option skipped\n", *optarg); } else parm.comment_char = *optarg; } break; case 'D': if ( optarg ) parm.delim = *optarg; else parm.delim = ';'; break; case 'o': { int len; len = strlen (optarg); parm.origin = malloc (len + 1 + 1); if ( !parm.origin ) usage ("out of memory"); if ( optarg[len-1] != '.' ) snprintf (parm.origin, len+2, "%s.", optarg); else snprintf (parm.origin, len+2, "%s", optarg); } break; case 'p': { /* a prefix to add */ ip6_t prfx; if ( ip6fromstr (&prfx, optarg) < 0 ) usage ("illegal prefix"); prfxput (&prfx, 0); } break; case 'P': { /* a prefix to remove */ ip6_t prfx; if ( *optarg ) { if ( ip6fromstr (&prfx, optarg) < 0 ) usage ("illegal prefix"); prfxput (&prfx, 1); } else prfxput (NULL, 1); } break; case 'd': parm.ddns = 1; /* dynamic update mode ... */ parm.forw = 1; /* ... is only defined in forward mode ... */ if ( !parm.ttl ) /* ... and needs a ttl */ parm.ttl = 86400; break; case 'l': parm.lookup = optarg; break; case '6': { ip6_t prfx; int ip[4]; if ( sscanf (optarg, "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3]) != 4 ) { snprintf (tmpstr, sizeof (tmpstr), "Illegal ipv4 address %s\n", optarg); usage (tmpstr); } snprintf (tmpstr, sizeof (tmpstr), "2002:%02x%02x:%02x%02x::/48\n", ip[0], ip[1], ip[2], ip[3]); if ( ip6fromstr (&prfx, tmpstr) < 0 ) usage ("illegal 6to4 prefix"); prfxput (&prfx, 0); } break; case ':': snprintf (tmpstr, sizeof (tmpstr), "option \"-%c\" requires an argument\n", optopt); usage (tmpstr); break; case '?': if ( isprint (optopt) ) snprintf (tmpstr, sizeof (tmpstr), "%s: unknown option \"-%c\"\n", progname, optopt); else snprintf (tmpstr, sizeof (tmpstr), "%s: unknown option char \\x%x\n", progname, optopt); usage (tmpstr); break; default: abort(); } } argc -= optind; argv += optind; if ( revzone ) /* Option -R given ? */ { while ( argc-- > 0 ) hostptr (parm.revbits, *argv++); exit (0); } if ( parm.forw == 0 && parm.rev == 0 ) parm.forw = parm.rev = 1; /* default is to generate forward and reverse */ /* some error checking */ #if defined(BINDCONFIG) && BINDCONFIG if ( bindconfig && !parm.filemode ) usage ("-B is only supported in filemode (-w or -a)"); #endif if ( parm.rev ) /* check reverse bits for reasonable value */ { prfx_t *prf; int prefmask; prf = prfxstart (); if ( prf == NULL ) /* no prefix list available */ usage ("option -p needed in reverse mode"); prefmask = 128; while ( prf ) { int pmask; pmask = ip6getmask (prf->prfx); prefmask = min (prefmask, pmask); prf = prfxnext (); } dbg_val2 ("revbits = %d; min of prefixmasks %d\n", parm.revbits, prefmask); if ( parm.revbits < prefmask ) #if 1 parm.revbits = prefmask; /* set it to a reasonable value ... */ #else /* ...instead of printing an error message */ usage ("reverse mask must be greater or equal than the network prefix"); #endif } /* open outputfiles if we are in file mode */ if ( parm.filemode ) { int n; n = filep_init (NULL, parm.filemode == 1 ? "w": "a"); if ( parm.verbose ) fprintf (stderr, "No of concurrent open files is %d while %d files in total can be handled\n", n, MAXFILEP); outfp = NULL; } else outfp = stdout; /* loop through file arguments */ if ( argc <= 0 ) parsefile ("stdin", outfp, gen_dns); else while ( argc-- > 0 ) { parsefile (*argv++, outfp, gen_dns); } if ( parm.ddns ) { if ( parm.verbose ) fprintf (outfp, "debug\n"); fprintf (outfp, "send\n"); } if ( parm.filemode ) { #if defined(BINDCONFIG) && BINDCONFIG if ( bindconfig ) { int i; int eol; long id; char fname[20+1]; id = -1L; eol = filep_first (&i); while ( !eol ) { //snprintf (fname, sizeof (fname), "%0*lx", (parm.revbits - prefmask) / 4, id); print_zoneconfig (filep_getzone (&i), filep_getname (&i), multiline); eol = filep_next (&i); } } #endif filep_close (); } return 0; } /***************************************************************** ** gendns() generic rr output function *****************************************************************/ static int gen_dns (int type, const ip6_t *prfx, const scope_t *scope, const host_t *host, const ip6_t *hid, ip6_t *sid, const char *extra_rr) { switch ( type ) { case 'D': /* dynamic update del */ case 'd': /* dynamic update add */ return gen_forw_upd (type == 'D', prfx, scope, host, hid, sid, extra_rr); case 'f': return gen_forw_dns (prfx, scope, host, hid, sid, extra_rr); case 'r': return gen_rev_dns (prfx, scope, host, hid, sid); default: fatal ("fatal error: illegal gen_dns type %d\n", type); } /* NOTREACHED */ return -1; } /***************************************************************** ** ** gen_forw_upd () ** ** Generate a forward DNS update request ** address build from "prfx", sid, and hid. ** The scope is used to build the filename to print the AAAA ** record. ** *****************************************************************/ static int gen_forw_upd (int del, const ip6_t *prfx, const scope_t *scope, const host_t *host, const ip6_t *hid, ip6_t *sid, const char *extra_rr) { const scope_t *scp; FILE *outfp; ip6_t ipv6; int ret; char fqdn[255+1]; ip6copy (&ipv6, prfx); ip6setsbit (sid, ip6getebit (&ipv6)); ip6append (&ipv6, sid); ip6append (&ipv6, hid); if ( scope ) scp = scope; else scp = &parm.defscope; if ( parm.filemode ) { #if 0 char fname[MAXFNAME+1]; /* do we have a view field, and it's not empty or "none" */ if ( scp->view && scp->view[0] && strcmp (scp->view, "none") != 0 ) snprintf (fname, sizeof (fname), FORW_FILE_VIEW_TMPL, scp->view, scp->domain ? scp->domain: parm.origin); else snprintf (fname, sizeof (fname), FORW_FILE_TMPL, scp->domain ? scp->domain: parm.origin); outfp = filep_open (fname); #else fatal ("Views are actually not supported in dynamic update mode\n"); #endif } else outfp = stdout; if ( parm.lookup == NULL || strcmp (parm.lookup, host->name) == 0 ) { snprintf (fqdn, sizeof (fqdn), "%s.%s", host->name, scp->domain ? scp->domain: parm.origin); ret = print_forw_upd (outfp, del, fqdn, &ipv6, host->ttl ? host->ttl: parm.ttl); if ( extra_rr && *extra_rr ) #if 0 fprintf (outfp, "%s", extra_rr); #else fatal ("The extra RR records are actually not supported in dynamic update mode\n"); #endif } else ret = 1; return ret; } static int print_forw_upd (FILE *fp, int del, const char *host, const ip6_t *ipv6, long ttl) { char str[127+1]; ip6_t ip; fprintf (fp, "update %s ", del ? "del": "add"); fprintf (fp, "%s \t", host); /* the label */ if ( del ) fprintf (fp, "\t"); else fprintf (fp, "%lu\t", ttl); /* optional ttl */ ip6copy (&ip, ipv6); ip6tostr (str, sizeof (str), &ip); /* address to string */ squeezev6 (str, str, 1); fprintf (fp, "IN AAAA %s", str); /* the AAAA record */ fprintf (fp, "\n"); return 1; } /***************************************************************** ** ** gen_forw_dns () ** ** Generate a forward DNS record for "host" with an IPv6 ** address build from "prfx", sid, and hid. ** The scope is used to build the filename to print the AAAA ** record. ** *****************************************************************/ static int gen_forw_dns (const ip6_t *prfx, const scope_t *scope, const host_t *host, const ip6_t *hid, ip6_t *sid, const char *extra_rr) { const scope_t *scp; FILE *outfp; ip6_t ipv6; int ret; char fname[MAXFNAME+1]; ip6copy (&ipv6, prfx); ip6setsbit (sid, ip6getebit (&ipv6)); ip6append (&ipv6, sid); ip6append (&ipv6, hid); if ( scope ) scp = scope; else scp = &parm.defscope; if ( parm.filemode ) { /* do we have a view field, and it's not empty or "none" */ if ( scp->view && scp->view[0] && strcmp (scp->view, "none") != 0 ) snprintf (fname, sizeof (fname), FORW_FILE_VIEW_TMPL, scp->view, scp->domain ? scp->domain: parm.origin); else snprintf (fname, sizeof (fname), FORW_FILE_TMPL, scp->domain ? scp->domain: parm.origin); outfp = filep_open (fname); } else outfp = stdout; ret = print_forw_dns (outfp, host->name, &ipv6, host->ttl ? host->ttl: parm.ttl); if ( extra_rr && *extra_rr ) fprintf (outfp, "%s", extra_rr); return ret; } static int print_forw_dns (FILE *fp, const char *host, const ip6_t *ipv6, long ttl) { char str[127+1]; ip6_t ip; fprintf (fp, "%-21s\t", host); /* the label */ if ( ttl ) fprintf (fp, "%7lu\t", ttl); /* optional ttl */ else fprintf (fp, "\t"); ip6copy (&ip, ipv6); ip6tostr (str, sizeof (str), &ip); /* address to string */ if ( parm.squeeze ) squeezev6 (str, str, parm.squeeze >= 2); fprintf (fp, "IN AAAA\t%s ", str); /* the AAAA record */ fprintf (fp, "\n"); return 1; } /***************************************************************** ** ** gen_rev_dns () ** ** Generate a reverse DNS record for the ipv6 address build ** out of the "prfx", sid and hid. ** The target of the ptr record is "host" plus the origin ** from the scope or from the command line parameter. ** The scope is used to build the filename to print the PTR ** record to. ** *****************************************************************/ static int gen_rev_dns (const ip6_t *prfx, const scope_t *scope, const host_t *host, const ip6_t *hid, ip6_t *sid) { int nibbles; int mask_nibbles; int size; int i; FILE *outfp; ip6_t ipv6; unsigned long subnetid; const scope_t *scp; char fname[MAXFNAME+1]; ip6copy (&ipv6, prfx); ip6setsbit (sid, ip6getebit (&ipv6)); ip6append (&ipv6, sid); ip6append (&ipv6, hid); if ( scope ) scp = scope; else scp = &parm.defscope; /* we are looking only for nibbles */ nibbles = parm.revbits / 4; mask_nibbles = ip6getmask (prfx); assert ( mask_nibbles > 0 ); mask_nibbles /= 4; size = nibbles - mask_nibbles; /* size of subnetid in nibbles */ if ( size < 0 ) size = 0; subnetid = 0L; /* which subnet is this ? */ i = mask_nibbles; /* start at prefmask nibbles! */ while ( i < nibbles ) { subnetid += ip6getnibble (&ipv6, i++); if ( i < nibbles ) subnetid <<= 4; } if ( parm.filemode ) /* in filemode lookup the fp for the reverse file */ { /* do we have a view field, and it's not empty or "none" */ if ( scp->view && scp->view[0] && strcmp (scp->view, "none") != 0 ) snprintf (fname, sizeof (fname), REV_FILE_VIEW_TMPL, scp->view, size, subnetid); else snprintf (fname, sizeof (fname), REV_FILE_TMPL, size, subnetid); if ( (outfp = filep_open (fname)) == NULL ) fatal ("error in opening reverse outputfile for subnet %0*lx\n", size, subnetid); } else outfp = stdout; return print_rev_dns (outfp, host->name, scp->domain ? scp->domain: parm.origin, &ipv6, nibbles, host->ttl ? host->ttl : parm.ttl, subnetid); } static int print_rev_dns (FILE *fp, const char *host, const char *origin, const ip6_t *ip, int nibbles, long ttl, unsigned long subnetid) { int i; assert ( fp != NULL ); assert ( host != NULL ); assert ( origin != NULL ); assert ( ip != NULL && ip6isset (ip) ); /* for a PTR record the label is a subdomain for each nibble. */ /* print out nibbles starting with the right most one */ i = 32; while ( i-- > nibbles ) fprintf (fp, "%01x%c", ip6getnibble (ip, i), i > nibbles ? '.': ' '); fprintf (fp, " "); if ( ttl ) fprintf (fp, "%7lu ", ttl); /* optional the ttl */ else fprintf (fp, "%7s ", ""); fprintf (fp, "IN PTR\t%s.%s ", host, origin); /* the PTR record */ if ( subnetid ) fprintf (fp, "; subnetid: %0*lx", 4, subnetid); /* comment indicating which subnet this PTR is in */ fprintf (fp, "\n"); return 1; } #if defined(BINDCONFIG) && BINDCONFIG void print_zoneconfig (const char *zone, const char *fname, const char *multiline) { printf ("zone \"%s\" IN {%s", zone, multiline); printf ("\ttype master;%s", multiline); printf ("\tfile \"%s\";\t%s", fname, multiline); printf ("};\n"); } #endif #if defined(BINDCONFIG) && BINDCONFIG # define bconfigstr " [-B [-m]]" #else # define bconfigstr "" #endif #define pdconfigstr "{-p prefix} {-P prefix}" #define pconfigstr "{-p prefix |-6 ipv4addr }" static void usage (const char *mesg) { char str[127+1]; if ( mesg && *mesg ) { fprintf (stderr, "%s error: ", progname); fprintf (stderr, "%s\n", mesg); fprintf (stderr, "\n"); } fprintf (stderr, "usage: %s -h|-V\n", progname); fprintf (stderr, "usage: %s -R [-b mask] | [...]\n", progname); fprintf (stderr, "usage: %s [-f] [-s|-S] [-w|-a" bconfigstr "] [-t ttl] [-D] [-C] " pconfigstr " [file ...]\n", progname); fprintf (stderr, "usage: %s [-r] [-b mask] [-w|-a" bconfigstr "] [-t ttl] [-D] [-C] [-o origin] " pconfigstr " [file ...]\n", progname); #if defined(DYNUPDATE) && DYNUPDATE fprintf (stderr, "usage: %s -d [-l label] [-t ttl] [-D] [-C] [-o origin] " pdconfigstr " [file ...]\n", progname); #endif fprintf (stderr, "\t-h, --help\t\t print out this help message \n"); fprintf (stderr, "\t-V, --version\t\t print out version and exit \n"); fprintf (stderr, "\t-R, --revzone\t\t print the ip6.arpa zone of the given prefix \n"); fprintf (stderr, "\t-v, --verbose\t\t give some hints about what's going on in the background \n"); fprintf (stderr, "\t\t\t\t use -v more often to increase the verbosity (up to level 2)\n"); fprintf (stderr, "\t-D, --delim[=char]\t use additionally to white space as delimiter (default is ';')\n"); fprintf (stderr, "\t-C, --comment[=char]\t use as comment character (default is '#')\n"); fprintf (stderr, "\t-s, --squeeze\t\t print ipv6 addresses w/o leading zeros (like 2001:db8:0:0:0:0:0:1/128)\n"); fprintf (stderr, "\t-S, --full-squeeze\t compress ipv6 address slightly more (like 2001:db8::1/128)\n"); fprintf (stderr, "\t-t, --ttl \t specify ttl of RR (default is none or the host specific one)\n"); fprintf (stderr, "\t\t\t\t known ttl units are s(ecs), m(ins), h(ours), d(ays) or w(eeks) \n"); #if defined(DYNUPDATE) && DYNUPDATE fprintf (stderr, "\t-d, --ddns-update\t print dynamic update messages to stdout (this implies -f) \n"); fprintf (stderr, "\t-l, --lookup=label\t The update add/del is done only for the host matching \"label\" \n"); #endif fprintf (stderr, "\t-f, --forward\t\t generate AAAA records for forward zone only \n"); fprintf (stderr, "\t-r, --reverse\t\t generate PTR records for reverse zone only\n"); fprintf (stderr, "\t\t\t\t The default is to generate forward and reverse zone entries\n"); fprintf (stderr, "\t\t\t\t (The use of the -w switch is highly recommended then)\n"); fprintf (stderr, "\t-b, --bits[=mask]\t split zone files on boundary (default is prefix size)\n"); //fprintf (stderr, "\n sizeof str = %lu\n", sizeof (str)); fprintf (stderr, "\t-w, --write\t\t write output to file instead of stdout\n"); snprintf (str, sizeof (str), FORW_FILE_TMPL, ""); fprintf (stderr, "\t\t\t\t filename is %s for the forward or ", str); snprintf (str, sizeof (str), REV_FILE_TMPL, 4, 0L); fprintf (stderr, "%s for the reverse zone\n", str); //fprintf (stderr, "\t\t\t\t %s for the reverse zones\n", str); fprintf (stderr, "\t\t\t\t This option potentially generates a lot of files (up to the\n"); fprintf (stderr, "\t\t\t\t compiled in # of %d)\n", MAXFILEP); fprintf (stderr, "\t-a, --append\t\t same as -w but append to file instead of overwriting \n"); fprintf (stderr, "\t-o, --origin=zone\t specify forward domain (default is %s)\n", parm.origin); fprintf (stderr, "\t-p, --add-prefix=prefix\t network prefix to add (default is %s)\n", DEF_PREFIX); #if defined(DYNUPDATE) && DYNUPDATE fprintf (stderr, "\t-P, --del-prefix=prefix\t network prefix to delete (default is none)\n"); #endif fprintf (stderr, "\t-6, --6to4=ipv4\t\t same as -p but argument is an ipv4 address\n"); fprintf (stderr, "\t\t\t\t resulting prefix is a 6to4 prefix (2002:ipv4:addr::/48)\n"); exit (0); }