/* Original at http://www.BeidelmanSoftware.com/utils/ptrgen.c * ptrgen.c V1.0 Keith Beidelman 21-April-2008 * * This tool is used to scan zone files, and generate the reverse zones * (for inverse DNS). * * There is also a companion program, namedgen.c, which sets up the named * configuration script for the required master and slave zones. * * Building and using the utility: * 1. Get Source: wget http://www.BeidelmanSoftware.com/misc/ptrgen.c * 2. Compile: gcc -o ptrgen ptrgen.c * 3. Help: ./ptrgen * * This is not release to the public quality code. In particular, the * buffers in main can probably be overflowed. However, it was only * intended to be a local tool when written. Never run this code as root, * if it accepts data from the public, as it would be exploitable. However, * it is OK to run it as root with private data sets. The buffers are plenty * large, and won't overflow unless you try hard to do so. * * I root named data at /var/named, place master zones in /var/named/master as * a file name which is the exact DNS name (no extension). I place reverse * zones in /var/named/rev. The file names for the reverse zones are the IP * addresses (in forward order) up to the scope of the zone (1-3 doted numbers). * I run this utility from /var/named/namedgen. You don't have to use this * structure, as you con override the zone directory with the -m command line * option. You can also change the NAMED and MASTER macros. However, if the * zone file name is not the same as the domain name of the zone, you will have * to edit the open code in Function RevGen. * * If you happen to take this code somewhere, please remove the defaults * from the soa record for dns="ns1.kgbsw.com" and email="keith.kgbsw.com". * ALso, remove or change this paragraph out of the comments, when doing so. * * This source file is organized so that everything is defined before it is * refrenced. Therefore, no prototype functions, or custom (non-system) * headers are required. The complete program is in a single source file. * */ #include #include #include #include #include #include #include /* default directory structure */ #define NAMED "/var/named" #define MASTER NAMED "/master" /* dynamic memory macros */ #define Allocate(s) (s *) Alloc (sizeof (s)) #define AllocateN(s,n) (s *) Alloc (((n)*sizeof(s))) #define ExpandN(p,s,n) (s *) Expand (p, ((n) * sizeof (s))) /* data structure to represent a PTR record. We hold it in memory so we * can sort it. */ typedef struct { int n; int quad[4]; char *name; char *rip; } PTR; /* Data structure to hold a list of secondary name servers */ typedef struct _NSLIST { char *name; struct _NSLIST *next; } NSLIST; /* data structure to hold a list of private subnets. */ typedef struct _SUBNET { int n; int quad[4]; struct _SUBNET *next; } SUBNET; /* data structure for table-driven command line parser */ typedef struct _OPTION { char *flag; int (*parser) (struct _OPTION *opt, int argc, char *argv[]); void *value; char *desc; } OPTION; /* command line options parse into this data area. The initializers represent * the default values */ static NSLIST *sns; /* list of secondary name servers */ static SUBNET *subnet; /* subnets in scope */ static PTR **pv; /* resulting PTR table */ static int npa, np; /* used and allocated PTR entries */ static int unique; /* unique flag */ static int go; /* null option supresses help */ static char *master = MASTER; /* master zone dir path */ static char *pns = "ns1.kgbsw.com"; /* authoritative name server */ static char *email = "keith.kgbsw.com"; /* SOA email address */ static char *soasn; /* SOA serial number */ static int ttl = 3600; /* default time to live */ static int min = 3600; /* minimum honored time to live */ static int refresh = 24 * 3600; /* zone refresh interval (secs) */ static int retry = 6 * 3600; /* refresh retry interval (secs) */ static int expire = 2 * 28 * 3600; /* zone refresh expire secs */ /* Allocate a block of memory. We abort if it fails, so the caller need not * check the return value * * Inputs: size : the number of bytes to allocate * * Returns: A pointer to the block of allocated memory * */ void *Alloc (int size) { char *p; p = malloc (size); if (p == NULL) { fprintf (stderr, "Memory allocation error!\n"); exit (1); } return p; } /* reallocate a block of memory. We abort if it fails, so the caller need not * check the return value. * * Inputs: p : a pointer to the previous block of memory * * n : the number of bytes to allocate * * Returns: a pointer to the possibly moved block of memory * */ void *Expand (void *p, int n) { if (p == NULL) p = malloc (n); else p = realloc (p, n); if (p == NULL) { fprintf (stderr, "Memory expansion error!\n"); exit (1); } return p; } /* Allocate a block of memory for a string, and copy the string into it. * We abort if this function fails, so the caller need not check a return + value. * * Inputs: s : the string to be allocated * * Returns: a pointer to the new string, in dynamic memory * */ char *Allocs (char *s) { int n; char *p; n = strlen (s) + 1; p = AllocateN(char, n); strcpy (p, s); return p; } /* parse a doted quad in domain, and place the parsed components in quad. * The return value indicates how many dots were parsed, or -1 if there is * a parse error. So the results will be stored in quad, up to the subscript * of the return value. (I.E. 3 dots, return 4 values 0 .. 3) * * Inputs: domain : a string containing a dotted quad, or part of * a dotted quad. (1 to 3 digits seperated by dots) * * Outputs: quad : a 4 element integer array which contains the * ip address components * * Returns: the number of dots in the dotted quad (1-3) * Parse error indicator : -1 * */ int DotQuad (char *domain, int *quad) { int i, ndots, number; ndots = 0; for (i = 0; i < 3; i++) quad[i] = 0; while (*domain) { if (!isdigit (*domain)) return -1; number = 0; while (isdigit (*domain)) { number = number * 10 + (*domain - '0'); if (number < 0 || number > 255) return -1; domain++; } quad[ndots] = number; if (*domain == '\0') break; if (*domain == '.' && ndots < 3) { ndots++; domain++; } else return -1; } return ndots; } /* Parse a flag, turning it on * * Inputs: opt : the option table entry being parsed * * argc : the number of command line arguments remaining * * argv : a vector to the remaining command line strings * * Returns: the number of additional arguments consumed after the option. * always 0. */ int ParFlag (OPTION *opt, int argc, char *argv[]) { *(int *) opt->value = 1; return 0; } /* parse a string from a command line option * * Inputs: opt : the option table entry being parsed * * argc : the number of command line arguments remaining * * argv : a vector to the remaining command line strings * * Returns: the number of additional arguments consumed after the option. * always 1. */ int ParString (OPTION *opt, int argc, char *argv[]) { if (argc < 1) { fprintf (stderr, "Parse Option: %s: Missing value\n", opt->flag); exit (1); } *(char **) opt->value = *argv; return 1; } /* parse an integer from a command line option * * Inputs: opt : the option table entry being parsed * * argc : the number of command line arguments remaining * * argv : a vector to the remaining command line strings * * Returns: the number of additional arguments consumed after the option. * always 1. */ int ParInt (OPTION *opt, int argc, char *argv[]) { char *value; int v; if (argc < 1) { fprintf (stderr, "Parse Option: %s: Missing value\n", opt->flag); exit (1); } value = *argv; v = 0; while (isdigit (*value)) v = v * 10 + (*value++ - '0'); if (*value != '\0') { fprintf (stderr, "Option Error: %s: Illegal `%s`\n", opt->flag, value); exit (1); } *(int *) opt->value = v; return 1; } /* parse a secondary name server from a command line option. Automaticly * update the secondary nameserver list * * Inputs: opt : the option table entry being parsed * * argc : the number of command line arguments remaining * * argv : a vector to the remaining command line strings * * Returns: the number of additional arguments consumed after the option. * always 1. */ int ParNs (OPTION *opt, int argc, char*argv[]) { NSLIST *nsl, *cns; char *ns; if (argc < 1) { fprintf (stderr, "Parse Option: %s: Missing value\n", opt->flag); exit (1); } ns = *argv; nsl = Allocate (NSLIST); nsl->name = Allocs (ns); nsl->next = NULL; if (sns == NULL) sns = nsl; else { for (cns = sns; cns->next; cns = cns->next); cns->next = nsl; } return 1; } /* Parse a local subnet ip address prefix. Automaticly update it's * data structure list * * Inputs: opt : the option table entry being parsed * * argc : the number of command line arguments remaining * * argv : a vector to the remaining command line strings * * Returns: the number of additional arguments consumed after the option. * always 1. */ int ParSubnet (OPTION *opt, int argc, char *argv[]) { SUBNET *p, *c; int n; if (argc < 1) { fprintf (stderr, "Parse Option: %s: Missing value\n", opt->flag); exit (1); } p = Allocate (SUBNET); n = DotQuad (*argv, p->quad); if (n < 0) { fprintf (stderr, "Parse: Not a doted quad prefix: %s\n", *argv); exit (1); } p->n = n + 1; p->next = NULL; if (subnet == NULL) subnet = p; else { for (c = subnet; c->next; c = c->next); c->next = p; } return 1; } /* table of command line options, parsers, resulting variables, and * help descriptions */ static OPTION options[] = { { "-u", ParFlag, &unique, " keep First Unique IP" }, { "-i", ParSubnet, &subnet, "dotip Include IPs with Prefix" }, { "-m", ParString, &master, "path Master Path to Zone Files" }, { "-e", ParString, &email, "email SOA Email Address" }, { "-p", ParString, &pns, "ns Primary Authoritative Nameserver" }, { "-s", ParNs, &sns, "ns Secondary slave Nameserver" }, { "-n", ParString, &soasn, "serial SOA Serial Number" }, { "-ttl", ParInt, &ttl, "value Time to Live" }, { "-min", ParInt, &min, "value Minimum Honored TTL" }, { "-refresh", ParInt, &refresh, "value Zone Refresh Seconds" }, { "-retry", ParInt, &retry, "value Zone Refresh Retry Seconds" }, { "-expire", ParInt, &expire, "value Zone Refresh Expire Seconds" }, { "-go", ParFlag, &go, " Run Without Options (Supress Help)" }, { NULL } }; /* SOA record format string */ char *soa = "$TTL %-10d\n" "@ IN SOA %s. %s. (\n" " %-10s ; Serial yyyymmddnn\n" " %-10d ; Refresh seconds\n" " %-10d ; Retry seconds\n" " %-10d ; Expire seconds\n" " %-10d ) ; Minimum ttl\n"; /* NS record format string */ char *ns = " NS %s.\n"; /* Generate a default serial number using today's date. The N suffix is * always zero, so be careful. * * Returns: a pointer to a static string containing YYYYMMDD00 * * Note: when this function is called again, any previously returned * static string is changed. */ char *CurrentSerial () { static char cts[11]; struct tm *tm; long t; time (&t); tm = localtime (&t); sprintf (cts, "%04d%02d%02d%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, 0); return cts; } /* Compare 2 dotted quads * * Inputs: a : node a, a PTR object * * b : note b, a PTR object * * n : the number of components to compare (1-4) * * Returns: The result of the compatison * -1 : a < b * 0 : a == b * 1 : a > b */ int QuadCompare (const PTR *a, const PTR *b, int n) { int i; for (i = 0; i < n; i++) { if (a->quad[i] < b->quad[i]) return -1; if (a->quad[i] > b->quad[i]) return 1; } return 0; } /* compare 2 doted quads. Note that we also use a serial number to * make qsort stable * * Inputs: a : node a, a PTR object * * b : note b, a PTR object * * n : the number of components to compare (1-4) * * Returns: The result of the compatison * -1 : a < b * 0 : a == b * 1 : a > b */ int Compare (const void *pa, const void *pb) { PTR *a, *b; int s; a = *(PTR **) pa; b = *(PTR **) pb; s = QuadCompare (a, b, 4); if (s != 0) return s; if (a->n < b->n) return -1; if (a->n > b->n) return 1; return 0; } /* Parse an A record, and return it's components * * Inpouts: buf : the line to be parsed * * Outputs: name : the symbolic name on the A record * * ip : the IP address assigned by the A record * * Returns: A record found and parsed flag * False : not a A record, or parse error * True : A record was parsed * */ int A (char *buf, char *name, char *ip) { while (*buf && !isspace (*buf)) *name++ = *buf++; *name++ = '\0'; *ip = '\0'; while (*buf && isspace (*buf)) buf++; if (*buf != 'A' || !isspace (buf[1])) return 0; buf++; while (*buf && isspace (*buf)) buf++; if (!isdigit (*buf)) return 0; while (*buf && !isspace (*buf)) *ip++ = *buf++; *ip++ = '\0'; return 1; } /* Read a line from a zone file * * Inputs: f : the file stream to read from * * limit : size of the input buffer * * Outputs: buf : the string which was read * * Returns: TRUE : a line was read * FALSE : no line read. EOF or error indicator. * */ int GetLine (FILE *f, char *buf, int limit) { int ch, i; ch = fgetc (f); if (ch < 0) return 0; for (i = 0; ch > 0 && ch != '\n' && i < limit - 1; i++) { buf [i] = ch; ch = fgetc (f); } buf[i] = '\0'; return 1; } /* Find a subnet on the subnet list wich contains the IP address passed * (as dotted-quad components). * * Inputs: quad : ip address to search for * * Returns: a pointer to the parsed subnet which the address is on * NULL : address is outside all interesting subnets * */ SUBNET *FindSubnet (int *quad) { int i, match; SUBNET *c; for (c = subnet; c; c = c->next) { match = 1; for (i = 0; match && i < c->n; i++) match = (quad[i] == c->quad[i]); if (match) return c; } return NULL; } /* Given a full IP address, make the left hand side of the PTR record. * This is the reverse-flipped quad, with components thrown out which * do not match our subnet scope * * Inputs: ip : the ip address as a dotted quad string * * Outputs: rip : the reversed quad for the left of the PTR record * * Returns: success flag * TRUE : address converted * FALSE : error - subnet not found * * Note: the number of dotted components varies, depending on the * size of the subnet (1-4) * */ int MkRip (char *ip, char *rip) { int n, i, ipq[4]; SUBNET *sub; if (DotQuad (ip, ipq) != 3) fprintf (stderr, "Warning: Bad or partial dot-quad: %s\n", ip); if (subnet) { sub = FindSubnet (ipq); if (sub == NULL) return 0; n = sub->n; } else n = 0; *rip = '\0'; for (i = 3; i >= n; i--) { rip += sprintf (rip, "%d", ipq[i]); if (i > n) *rip++ = '.'; } return 1; } /* Parse a zone file, and create PTR data structure nodes for each A record * which is in a subnet of interest. * * Inputs: pprefix : path prefix (directory componment) * * file : zone file name (also a domain name) * * Globals: pv : dynamic memory vector expanded to contain * every PTR record * np : number of full pv entries * * npa : number of currently allocated pv entries * * Note: Opens the given file, and reads it from end to end. * * As it reads, it creates a PTR data structure. * */ void RevGen (char *pprefix, char *file) { char buf[200], name[200], ip[200], rip[20], ptr[300]; int i; FILE *f; PTR *p; sprintf (buf, "%s/%s", pprefix, file); f = fopen (buf, "r"); if (f == NULL) { perror (buf); exit (1); } while (GetLine (f, buf, sizeof (buf))) if (A (buf, name, ip) && MkRip (ip, rip)) { if (strlen (name)) { strncpy (ptr, name, sizeof (ptr)); strncat (ptr, ".", sizeof (ptr)); strncat (ptr, file, sizeof (ptr)); } else strncpy (ptr, file, sizeof (ptr)); ptr[sizeof(ptr)-1] = '\0'; p = Allocate (PTR); p->n = np; if (DotQuad (ip, p->quad) != 3) fprintf (stderr, "Warning: bad ip %s\n", ip); p->name = Allocs (ptr); p->rip = Allocs (rip); if (npa <= np + 1) { npa += 1000; pv = ExpandN (pv, PTR *, npa); for (i = np; i < npa; i++) pv[i] = NULL; } pv [np++] = p; } fclose (f); } /* Modify the email address to SOAize it. Replace any '@' with a dot. * * Inputs: s : the email string, to be modified * * Outputs: s : the modified string * */ void FixEmail (char *e) { int i; for (i = 0; e[i]; i++) if (e[i] == '@') e[i] = '.'; } /* search the command line option table for an entry which matches the passed * name. * * Inputs: name : the command line parameter to be found in the parse * table * * Outputs: a pointer to the found entry in the parse table * NULL : option not found * */ OPTION *FindOption (char *name) { OPTION *o; for (o = options; o->flag; o++) if (strcmp (o->flag, name) == 0) return o; fprintf (stderr, "Parse: Unknown Flag: %s\n", name); exit (1); return NULL; } /* Parse command line options * * Inputs: argc : the number of command line arguments * * argv : a pointer to each command line string * * Returns: the numer of entries in argv parsed * */ int ParseOptions (int argc, char *argv[]) { OPTION *opt; int i; char **argv0; argv0 = argv; argv++; argc--; unique = 0; soasn = CurrentSerial (); while (argc > 0 && **argv == '-') { opt = FindOption (*argv); argc--; argv++; i = (*opt->parser) (opt, argc, argv); argc -= i; argv += i; } FixEmail (email); return argv - argv0; } /* output help text for options */ void HelpOptions () { OPTION *o; for (o = options; o->flag; o++) fprintf (stderr, "%8s %s\n", o->flag, o->desc); } /* Output help usage information */ void Help () { fprintf (stderr, " Purpose: Scan Forward Zone Files to Make Reverse Zones\n" " Example: ptrgen -i 192.168.1 > ../rev/192.168.1\n" " Usage: ptrgen [] [[Zone-file1]...] [> ]\n" " Options: Value Meaning\n"); HelpOptions (); exit (1); } /* create a SOA record */ void GenerateSoa () { printf (soa, ttl, pns, email, soasn, refresh, retry, expire, min); } /* Create all required NS records */ void GenerateNs () { NSLIST *cns; printf (ns, pns); for (cns = sns; cns; cns = cns->next) printf (ns, cns->name); } /* Create all required PTR Records */ void GeneratePtr () { int i; PTR *p; for (i = 0; i < np; i++) { p = pv[i]; if (unique && i > 0 && QuadCompare (pv[i], pv[i-1], 4) == 0) continue; printf ("%-16.16s PTR %s.\n", p->rip, p->name); } } /* main program. Parse zones, finding all A records. Of these A records, * create a PTR record whenever the IP is in a subnet of interest (as * determined by the -i option, Everything is interesting if no -i options * are given. * * While multiple subnets may be specified with -i, for generating a reverse * zone, this does not make sense. The zone must be relative to a single * subnet. However for other purposes, multiple -i's might make sense (such * as making a list of all local addresses). * * The resulting PTR records are sorted, and a proper reverse DNS zone file * is generated. * * Inputs: argc : the number of command line arguments * * argv : a vector of command line strings * * Returns: process exit code * 0 : good exit * != 0 : error exit */ int main (int argc, char *argv[]) { int i; DIR *dir; struct dirent *de; if (argc < 2) Help (); i = ParseOptions (argc, argv); argc -= i; argv += i; if (argc > 0) for (i = 0; i < argc; i++) RevGen (master, argv[i]); else if ((dir = opendir (master)) != NULL) { while (de = readdir (dir)) if (de->d_name[0] != '.') RevGen (master, de->d_name); closedir (dir); } if (np <= 0) { fprintf (stderr, "Resulting reverse zone is empty.\n"); return 1; } else { qsort (pv, np, sizeof (PTR *), Compare); GenerateSoa (); GenerateNs (); GeneratePtr (); return 0; } }