/*
 *      dnsutl - utilities to make DNS easier to configure
 *      Copyright (C) 1996, 1999, 2000, 2006, 2007 Peter Miller
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 3 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program. If not, see
 *      <http://www.gnu.org/licenses/>.
 */

#include <ac/ctype.h>
#include <ac/stdlib.h>
#include <ac/string.h>
#include <ac/time.h>

#include <error.h>
#include <mem.h>
#include <regu_expr.h>
#include <srrf.h>
#include <srrf/address.h>
#include <srrf/private.h>
#include <srrf/origin.h>


static int      auto_time_stamp;


void
srrf_automatic_time_stamp(void)
{
    auto_time_stamp = 1;
}


/* ---------- a ------------------------------------------------------------ */


static void
a_check(srrf_t *rp)
{
    string_ty       *s;

    if (rp->arg.nstrings < 1)
        return;
    s = srrf_address_cannonicalize(rp->arg.string[0]);
    if (!s)
    {
        srrf_lex_error
        (
            "the string \"%s\" is not a legal IP address",
            rp->arg.string[0]->str_text
        );
    }
    else
    {
        str_free(rp->arg.string[0]);
        rp->arg.string[0] = s;
    }
}


/* ---------- cname -------------------------------------------------------- */


static void
cname_check(srrf_t *rp)
{
    string_ty       *tmp;
    string_ty       *o;

    if (rp->arg.nstrings != 1)
        srrf_lex_error("exactly one argument required");
    if (rp->arg.nstrings < 1)
        return;
    tmp = rp->arg.string[0];
    rp->arg.string[0] = srrf_relative_to_absolute(tmp);
    o = srrf_origin_get();
    if (o && str_equal(rp->name, o))
        srrf_lex_error("cname for $origin not allowed");
    str_free(tmp);
}


static int
cname_local(srrf_t *rp)
{
    return srrf_private_domain_member(rp->arg.string[0]);
}


static void
cname_a2r(srrf_t *rp)
{
    string_ty       *s;

    s = rp->arg.string[0];
    rp->arg.string[0] = srrf_absolute_to_relative(s);
    str_free(s);
}


/* ---------- hinfo -------------------------------------------------------- */


static char    *
first_word(const char *s)
{
    const char      *ep;
    char            *result;
    char            *op;

    while (*s && isspace((unsigned char)*s))
        ++s;
    ep = s;
    while (*ep && !isspace(*ep))
        ++ep;
    result = mem_alloc(ep - s + 1);
    op = result;
    while (s < ep)
    {
        if (isupper((unsigned char)*s))
            *op++ = tolower((unsigned char)*s);
        else
            *op++ = *s;
        ++s;
    }
    *op = 0;
    return result;
}


static const char *
contains_any(const char *s1, const char *s2)
{
    while (*s1)
    {
        if (strchr(s2, *s1))
            return s1;
        ++s1;
    }
    return 0;
}


static int
find_word(const char *w, const char *const *list, size_t length)
{
    size_t          j;
    const char      *pattern;
    const char      *yuck;

    for (j = 0; j < length; ++j)
    {
        pattern = list[j];

        /*
         * see if there are no magic characters
         * do a literal compare if none
         */
        yuck = contains_any(pattern, ".[]*(){}^$\\|");
        if (!yuck)
        {
            if (!strcmp(w, pattern))
                return 1;
            continue;
        }

        /*
         * If there were some regex characters, compare the
         * leading literal characters to try and generate a
         * miss before the expensive regex calls.
         */
        if (yuck > pattern)
        {
            if (memcmp(w, pattern, yuck - pattern) != 0)
                continue;
        }

        if (regular_expression_match(pattern, w))
            return 1;
    }
    return 0;
}


static char *
list_to_string(const char *const *list, size_t length)
{
    size_t          nbytes;
    size_t          j;
    char            *result;
    char            *cp;

    nbytes = 0;
    for (j = 0; j < length; ++j)
        nbytes += 4 + strlen(list[j]);
    result = mem_alloc(nbytes);
    cp = result;
    for (j = 0; j < length; ++j)
    {
        if (j)
        {
            *cp++ = ',';
            *cp++ = ' ';
        }
        *cp++ = '"';
        nbytes = strlen(list[j]);
        memcpy(cp, list[j], nbytes);
        cp += nbytes;
        *cp++ = '"';
    }
    *cp = 0;
    return result;
}


static void
hinfo_check(srrf_t *rp)
{
    /*
     * This is the hardware list from RFC-1010
     *
     * These are the Official Machine Names as they appear in the
     * NIC Host Table.  Their use is described in RFC 810 [39].
     *
     * A machine name or CPU type may be up to 40 characters taken
     * from the set of uppercase letters, digits, and the two
     * punctuation characters hyphen and slash.  It must start with
     * a letter, and end with a letter or digit.
     *
     * NOTE: each of these is treated as a regular expression,
     * to facilitate multiple entries, e.g. SUN-.*
     */
    static const char *const hardware[] =
    {
        "alto",
        "amdahl-v7",
        "apollo",
        "att-3b20",
        "bbn-c/60",
        "burroughs-b/29",
        "burroughs-b/4800",
        "butterfly",
        "c/30",
        "c/70",
        "cadlinc",
        "cadr",
        "cdc-170",
        "cdc-170/750",
        "cdc-173",
        "celerity-1200",
        "comten-3690",
        "cp8040",
        "cray-1",
        "cray-x/mp",
        "cray-2",
        "ctiws-117",
        "dandelion",
        "dec-10",
        "dec-1050",
        "dec-1077",
        "dec-1080",
        "dec-1090",
        "dec-1090b",
        "dec-1090t",
        "dec-2020t",
        "dec-2040",
        "dec-2040t",
        "dec-2050t",
        "dec-2060",
        "dec-2060t",
        "dec-2065",
        "dec-falcon",
        "dec-ks10",
        "dec-.*",
        "dorado",
        "dps8/70m",
        "elxsi-6400",
        "foonly-f2",
        "foonly-f3",
        "foonly-f4",
        "gould",
        "gould-6050",
        "gould-6080",
        "gould-9050",
        "gould-9080",
        "h-316",
        "h-60/68",
        "h-68",
        "h-68/80",
        "h-89",
        "honeywell-dps-6",
        "honeywell-dps-8/70",
        "hp3000",
        "hp3000/64",
        "ibm-158",
        "ibm-360/67",
        "ibm-370/3033",
        "ibm-3081",
        "ibm-3084qx",
        "ibm-3101",
        "ibm-4331",
        "ibm-4341",
        "ibm-4361",
        "ibm-4381",
        "ibm-4956",
        "ibm-pc",
        "ibm-pc/at",
        "ibm-pc/xt",
        "ibm-series/1",
        "ibm-[1-9][0-9]*",
        "ibm-[1-9][0-9]*/[1-9][0-9]*",
        "imagen",
        "imagen-8/300",
        "imsai",
        "integrated-solutions",
        "integrated-solutions-68k",
        "integrated-solutions-creator",
        "integrated-solutions-creator-8",
        "intel-ipsc",
        "is-1",
        "is-68010",
        "lmi",
        "lsi-11",
        "lsi-11/2",
        "lsi-11/23",
        "lsi-11/73",
        "m68000",
        "masscomp",
        "mc500",
        "mc68000",
        "microvax",
        "microvax-i",
        "mv/8000",
        "nas3-5",
        "ncr-comten-3690",
        "now",
        "onyx-z8000",
        "pdp-11",
        "pdp-11/3",
        "pdp-11/23",
        "pdp-11/24",
        "pdp-11/34",
        "pdp-11/40",
        "pdp-11/44",
        "pdp-11/45",
        "pdp-11/50",
        "pdp-11/70",
        "pdp-11/73",
        "pe-7/32",
        "pe-3205",
        "perq",
        "plexus-p/60",
        "pli",
        "pluribus",
        "prime-2350",
        "prime-2450",
        "prime-2755",
        "prime-9655",
        "prime-9755",
        "prime-9955ii",
        "prime-2250",
        "prime-2655",
        "prime-9955",
        "prime-9950",
        "prime-9650",
        "prime-9750",
        "prime-2250",
        "prime-750",
        "prime-850",
        "prime-550ii",
        "pyramid-90",
        "pyramid-90mx",
        "pyramid-90x",
        "pyramid-.*",
        "ridge",
        "ridge-32",
        "ridge-32c",
        "rolm-1666",
        "s1-mkiia",
        "smi",
        "sequent-balance-8000",
        "siemens",
        "silicon-graphics",
        "silicon-graphics-iris",
        "silicon-graphics-.*",
        "sperry-dcp/10",
        "sun",
        "sun-2",
        "sun-2/50",
        "sun-2/100",
        "sun-2/120",
        "sun-2/140",
        "sun-2/150",
        "sun-2/160",
        "sun-2/170",
        "sun-3/160",
        "sun-3/50",
        "sun-3/75",
        "sun-3/110",
        "sun-50",
        "sun-100",
        "sun-120",
        "sun-130",
        "sun-150",
        "sun-170",
        "sun-68000",
        "sun-[0-9][0-9]*/[0-9][0-9]*",
        "symbolics-3600",
        "symbolics-3670",
        "tandem-txp",
        "tek-6130",
        "ti-explorer",
        "tp-4000",
        "trs-80",
        "univac-1100",
        "univac-1100/60",
        "univac-1100/62",
        "univac-1100/63",
        "univac-1100/64",
        "univac-1100/70",
        "univac-1160",
        "vax-11/725",
        "vax-11/730",
        "vax-11/750",
        "vax-11/780",
        "vax-11/785",
        "vax-11/790",
        "vax-11/8600",
        "vax-8600",
        "vax-.*",
        "wang-pc002",
        "wang-vs100",
        "wang-vs400",
        "xerox-1108",
        "xerox-8010",

        /*
         * This entry is not part of the official set,
         * but often very necessary in practice.
         */
        "data-general-.*",
        "dg-.*",
        "hewlett-packard-.*",
        "hp-.*",
        "apple-macintosh",
        "apple-2",
        "other:",
        /* x-term, term serv, bridges, etc. */
        "unknown"
    };

    /*
     * This is the Operating System list from RFC-1010
     *
     * These are the Official System Names as they appear in the
     * NIC Host Table.  Their use is described in RFC 810 [39].
     *
     * A system name may be up to 40 characters taken from the set
     * of uppercase letters, digits, and the two punctuation
     * characters hyphen and slash.  It must start with a letter,
     * and end with a letter or digit.
     *
     * NOTE: each of these is treated as a regular expression,
     * to facilitate multiple entries.
     */
    static const char *const system_name[] =
    {
        "aegis",
        "apollo",
        "bs-2000",
        "cedar",
        "cgw",
        "chrysalis",
        "cmos",
        "cms",
        "cos",
        "cpix",
        "ctos",
        "ctss",
        "dcn",
        "ddnos",
        "domain",
        "edx",
        "elf",
        "embos",
        "emmos",
        "epos",
        "foonex",
        "fuzz",
        "gcos",
        "gpos",
        "hdos",
        "imagen",
        "intercom",
        "impress",
        "interlisp",
        "ios",
        "its",
        "lisp",
        "lispm",
        "locus",
        "minos",
        "mos",
        "mpe5",
        "msdos",
        "multics",
        "mvs",
        "mvs/sp",
        "nexus",
        "nms",
        "nonstop",
        "nos-2",
        "os/ddp",
        "os4",
        "os86",
        "osx",
        "pcdos",
        "perq/os",
        "pli",
        "psdos/mit",
        "primos",
        "rmx/rdos",
        "ros",
        "rsx11m",
        "satops",
        "scs",
        "simp",
        "swift",
        "tac",
        "tandem",
        "tenex",
        "tops10",
        "tops20",
        "tp3010",
        "trsdos",
        "ultrix",
        "unix",
        "ut2d",
        "v",
        "vm",
        "vm/370",
        "vm/cms",
        "vm/sp",
        "vms",
        "vms/eunice",
        "vrtx",
        "waits",
        "wang",
        "xde",
        "xenix",

        /*
         * These entries are not part of the official set,
         * but often very necessary in practice.
         */
        "macos",                /* MacOS, Apple Macintosh operating system */
        "other:",
        "unknown",              /* don't know what the operating system is */
        "none"                  /* device does not have an operating system */
    };

    char            *w;

    /*
     * We are extemely lenient, we only check the first
     * space-separated word for compliance.  This allows us to
     * piggy-back extra stuff into these fields.
     */
    if (rp->arg.nstrings < 1)
        return;
    w = first_word(rp->arg.string[0]->str_text);
    if (strlen(w) > 40)
        srrf_lex_error("harware name too long (by %d)", strlen(w) - 40);
    if (!find_word(w, hardware, SIZEOF(hardware)))
    {
        static int      listed;
        char            *s;

        srrf_lex_error("harware name \"%s\" unknown", w);
        if (!listed)
        {
            listed = 1;
            s = list_to_string(hardware, SIZEOF(hardware));
            error("the harware list from rfc1010 defines %s", s);
            mem_free(s);
        }
    }
    mem_free(w);

    if (rp->arg.nstrings < 2)
        return;
    w = first_word(rp->arg.string[1]->str_text);
    if (strlen(w) > 40)
        srrf_lex_error("system name too long (by %d)", strlen(w) - 40);
    if (!find_word(w, system_name, SIZEOF(system_name)))
    {
        static int      listed;
        char            *s;

        srrf_lex_error("system name \"%s\" unknown", w);
        if (!listed)
        {
            listed = 1;
            s = list_to_string(system_name, SIZEOF(system_name));
            error("the system list from rfc1010 defines %s", s);
            mem_free(s);
        }
    }
    mem_free(w);
}


/* ---------- loc ---------------------------------------------------------- */

enum loc_token_t
{
    loc_token_end,
    loc_token_integer,
    loc_token_real,
    loc_token_metres,
    loc_token_ns,
    loc_token_ew,
    loc_token_junk
};
typedef enum loc_token_t loc_token_t;

typedef struct loc_lex_t loc_lex_t;
struct loc_lex_t
{
    strlist_ty      *line;
    size_t          pos;
    loc_token_t     curtok;
    long            value_long;
    double          value_double;
};


static          loc_token_t
loc_lex(loc_lex_t *ctx)
{
    string_ty       *s;
    const char      *cp;
    char            *ep;
    long            n;
    double          d;

    /*
     * Once we get to the end, keep saying its the end.
     */
    if (ctx->pos >= ctx->line->nstrings)
    {
        ctx->curtok = loc_token_end;
        return ctx->curtok;
    }

    /*
     * Get the next string for the argument list
     */
    s = ctx->line->string[ctx->pos++];
    cp = s->str_text;

    /*
     * Check for the easy stuff first.
     */
    if (strcasecmp(cp, "n") == 0)
    {
        ctx->value_long = 1;
        ctx->curtok = loc_token_ns;
        return ctx->curtok;
    }
    if (strcasecmp(cp, "s") == 0)
    {
        ctx->value_long = -1;
        ctx->curtok = loc_token_ns;
        return ctx->curtok;
    }
    if (strcasecmp(cp, "e") == 0)
    {
        ctx->value_long = 1;
        ctx->curtok = loc_token_ew;
        return ctx->curtok;
    }
    if (strcasecmp(cp, "w") == 0)
    {
        ctx->value_long = -1;
        ctx->curtok = loc_token_ew;
        return ctx->curtok;
    }

    /*
     * See if we can make it into an integer.
     */
    n = strtol(cp, &ep, 10);
    if (ep != cp && 0 == strcasecmp(ep, "m"))
    {
        ctx->value_double = n;
        ctx->curtok = loc_token_metres;
        return ctx->curtok;
    }
    if (ep != cp && *ep == '\0')
    {
        ctx->value_long = n;
        ctx->curtok = loc_token_integer;
        return ctx->curtok;
    }

    /*
     * See if we can make it into an real.
     */
    d = strtod(cp, &ep);
    if (ep != cp && 0 == strcasecmp(ep, "m"))
    {
        ctx->value_double = d;
        ctx->curtok = loc_token_metres;
        return ctx->curtok;
    }
    if (ep != cp && *ep == '\0')
    {
        ctx->value_double = d;
        ctx->curtok = loc_token_real;
        return ctx->curtok;
    }

    /*
     * Nope.  Can't figure it out.
     */
    ctx->curtok = loc_token_junk;
    return ctx->curtok;
}


static string_ty *
three_sig_dig(double n)
{
    double          nn;

    nn = (n < 0 ? -n : n);
    if (nn < 10)
        return str_format("%4.2fm", n);
    if (nn < 100)
        return str_format("%3.1fm", n);
    return str_format("%.0fm", n);
}

static void
loc_check(srrf_t *rp)
{
    /*
     * Quoting RFC1876
     *
     * "The LOC record is expressed in a master file in the following
     * format:
     *
     * <owner> <TTL> <class> LOC ( d1 [m1 [s1]] {"N"|"S"}
     *                             d2 [m2 [s2]] {"E"|"W"}
     *                             alt["m"]
     *                             [ siz["m"] [ hp["m"] [ vp["m"] ] ] ] )
     *
     * (The parentheses are used for multi-line data as specified in
     * [RFC 1035] section 5.1.)
     *
     * where:
     *      d1:     [0 .. 90]            (degrees latitude)
     *      d2:     [0 .. 180]           (degrees longitude)
     *      m1, m2: [0 .. 59]            (minutes latitude/longitude)
     *      s1, s2: [0 .. 59.999]        (seconds latitude/longitude)
     *      alt:    [-100000.00 .. 42849672.95] BY .01 (altitude in meters)
     *      siz, hp, vp: [0 .. 90000000.00] (size/precision in meters)
     *
     * If omitted, minutes and seconds default to zero, size defaults to 1m,
     * horizontal precision defaults to 10000m, and vertical precision
     * defaults to 10m.  These defaults are chosen to represent typical
     * ZIP/postal code area sizes, since it is often easy to find
     * approximate geographical location by ZIP/postal code."
     */
    loc_lex_t       ctx;
    strlist_ty      canonical;
    string_ty       *s;

    ctx.line = &rp->arg;
    ctx.pos = 0;
    strlist_zero(&canonical);

    /*
     * latitude
     */
    loc_lex(&ctx);
    if (ctx.curtok != loc_token_integer)
    {
        bad_lat:
        srrf_lex_error
        (
            "LOC: arg %d: latitude must be defined as "
                "\"deg [ min [ sec[.nnn] ]] { N | S }\"",
            ctx.pos
        );
        return;
    }
    if (ctx.value_long < 0 || ctx.value_long > 90)
        goto bad_lat;
    s = str_format("%ld", ctx.value_long);
    strlist_append(&canonical, s);
    str_free(s);

    loc_lex(&ctx);
    if (ctx.curtok == loc_token_integer)
    {
        if (ctx.value_long < 0 || ctx.value_long >= 60)
            goto bad_lat;
        s = str_format("%ld", ctx.value_long);
        strlist_append(&canonical, s);
        str_free(s);

        loc_lex(&ctx);
        if (ctx.curtok == loc_token_integer)
        {
            if (ctx.value_long < 0 || ctx.value_long >= 60)
                goto bad_lat;
            s = str_format("%ld", ctx.value_long);
            strlist_append(&canonical, s);
            str_free(s);

            loc_lex(&ctx);
        }
        else if (ctx.curtok == loc_token_real)
        {
            if (ctx.value_double < 0 || ctx.value_double >= 60)
                goto bad_lat;
            s = str_format("%g", ctx.value_double);
            strlist_append(&canonical, s);
            str_free(s);

            loc_lex(&ctx);
        }
    }
    else
    {
        s = str_from_c("0");
        strlist_append(&canonical, s);
        strlist_append(&canonical, s);
        str_free(s);
    }

    if (ctx.curtok != loc_token_ns)
        goto bad_lat;
    s = str_from_c(ctx.value_long >= 0 ? "N" : "S");
    strlist_append(&canonical, s);
    str_free(s);

    /*
     * longitude
     */
    loc_lex(&ctx);
    if (ctx.curtok != loc_token_integer)
    {
        bad_lon:
        srrf_lex_error
        (
            "LOC: arg %d: longitude must be defined as "
                "\"deg [ min [ sec[.nnn] ]] { E | W }\"",
            ctx.pos
        );
        return;
    }
    if (ctx.value_long < 0 || ctx.value_long > 180)
        goto bad_lon;
    s = str_format("%ld", ctx.value_long);
    strlist_append(&canonical, s);
    str_free(s);

    loc_lex(&ctx);
    if (ctx.curtok == loc_token_integer)
    {
        if (ctx.value_long < 0 || ctx.value_long >= 60)
            goto bad_lon;
        s = str_format("%ld", ctx.value_long);
        strlist_append(&canonical, s);
        str_free(s);

        loc_lex(&ctx);
        if (ctx.curtok == loc_token_integer)
        {
            if (ctx.value_long < 0 || ctx.value_long >= 60)
                goto bad_lon;
            s = str_format("%ld", ctx.value_long);
            strlist_append(&canonical, s);
            str_free(s);

            loc_lex(&ctx);
        }
        else if (ctx.curtok == loc_token_real)
        {
            if (ctx.value_double < 0 || ctx.value_double >= 60)
                goto bad_lon;
            s = str_format("%g", ctx.value_double);
            strlist_append(&canonical, s);
            str_free(s);

            loc_lex(&ctx);
        }
    }
    else
    {
        s = str_from_c("0");
        strlist_append(&canonical, s);
        strlist_append(&canonical, s);
        str_free(s);
    }

    if (ctx.curtok != loc_token_ew)
        goto bad_lon;
    s = str_from_c(ctx.value_long >= 0 ? "E" : "W");
    strlist_append(&canonical, s);
    str_free(s);

    /*
     * altitude
     */
    loc_lex(&ctx);
    if (ctx.curtok == loc_token_integer)
    {
        ctx.value_double = ctx.value_long;
        ctx.curtok = loc_token_metres;
    }
    if (ctx.curtok == loc_token_real)
    {
        ctx.curtok = loc_token_metres;
    }
    if (ctx.curtok != loc_token_metres)
    {
        bad_alt:
        srrf_lex_error
        (
            "LOC: arg %d: altitude must be defined as "
                "\"[+|-]num[.nnn]['m']\"",
            ctx.pos
        );
        return;
    }
    if (ctx.value_double < -100000.00 || ctx.value_double > 42849672.95)
        goto bad_alt;
    s = three_sig_dig(ctx.value_double);
    strlist_append(&canonical, s);
    str_free(s);

    /*
     * size
     */
    loc_lex(&ctx);
    if (ctx.curtok == loc_token_integer)
    {
        ctx.value_double = ctx.value_long;
        ctx.curtok = loc_token_metres;
    }
    if (ctx.curtok == loc_token_real)
    {
        ctx.curtok = loc_token_metres;
    }
    if (ctx.curtok == loc_token_metres)
    {
        /* Optional size is present. */
        if (ctx.value_double < 0 || ctx.value_double > 9e7)
        {
            srrf_lex_error
            (
                "LOC: arg %d: size must be defined as "
                    "\"num[.nnn]['m']\"",
                ctx.pos
            );
            return;
        }
        s = three_sig_dig(ctx.value_double);
        strlist_append(&canonical, s);
        str_free(s);
        loc_lex(&ctx);
    }
    else
    {
        s = three_sig_dig(1.);
        strlist_append(&canonical, s);
        str_free(s);
    }

    /*
     * horizontal precision
     */
    if (ctx.curtok == loc_token_integer)
    {
        ctx.value_double = ctx.value_long;
        ctx.curtok = loc_token_metres;
    }
    if (ctx.curtok == loc_token_real)
    {
        ctx.curtok = loc_token_metres;
    }
    if (ctx.curtok == loc_token_metres)
    {
        /* Optional hp is present. */
        if (ctx.value_double < 0 || ctx.value_double > 9e7)
        {
            srrf_lex_error
            (
                "LOC: arg %d: horizontal precision must "
                    "be defined as \"num[.nnn]['m']\"",
                ctx.pos
            );
            return;
        }
        s = three_sig_dig(ctx.value_double);
        strlist_append(&canonical, s);
        str_free(s);
        loc_lex(&ctx);
    }
    else
    {
        s = three_sig_dig(10000.);
        strlist_append(&canonical, s);
        str_free(s);
    }

    /*
     * vertical precision
     */
    if (ctx.curtok == loc_token_integer)
    {
        ctx.value_double = ctx.value_long;
        ctx.curtok = loc_token_metres;
    }
    if (ctx.curtok == loc_token_real)
    {
        ctx.curtok = loc_token_metres;
    }
    if (ctx.curtok == loc_token_metres)
    {
        /* Optional hp is present. */
        if (ctx.value_double < 0 || ctx.value_double > 9e7)
        {
            srrf_lex_error
            (
                "LOC: arg %d: vertical precision must "
                    "be defined as \"num[.nnn]['m']\"",
                ctx.pos
            );
            return;
        }
        s = three_sig_dig(ctx.value_double);
        strlist_append(&canonical, s);
        str_free(s);
        loc_lex(&ctx);
    }
    else
    {
        s = three_sig_dig(10.);
        strlist_append(&canonical, s);
        str_free(s);
    }

    /*
     * Make sure we are done.
     */
    if (ctx.curtok != loc_token_end)
    {
        srrf_lex_error("LOC: arg %d: junk on end of line", ctx.pos);
    }

    /*
     * Replace the argument list we were given with the canonical
     * one we constructed.
     */
    strlist_free(&rp->arg);
    rp->arg = canonical;
}


static void
indent_to(int n, int *col, FILE *fp)
{
    if (*col >= n)
    {
        putc(' ', fp);
        ++*col;
    }
    else
    {
        while (*col < n)
        {
            putc(' ', fp);
            ++*col;
        }
    }
}


static int
loc_print(srrf_t *rp, FILE *fp)
{
    int             col;
    const char     *tmp;

    /*
     * print the name
     */
    col = 0;
    if (rp->name)
    {
        fprintf(fp, "%s", rp->name->str_text);
        col += rp->name->str_length;
    }

    /*
     * print the time to live
     */
    if (rp->ttl)
    {
        char            ttl[30];

        indent_to(8, &col, fp);
        sprintf(ttl, "%ld", rp->ttl);
        fprintf(fp, "%s", ttl);
        col += strlen(ttl);
    }

    /*
     * print the class
     */
    indent_to(16, &col, fp);
    tmp = rp->class->name;
    fprintf(fp, "%s", tmp);
    col += strlen(tmp);

    /*
     * print the type
     */
    indent_to(24, &col, fp);
    tmp = rp->type->name;
    fprintf(fp, "%s", tmp);
    col += strlen(tmp);

    /*
     * print the other stuff
     */
    indent_to(32, &col, fp);
    fprintf
    (
        fp,
        "( %s %s %s %s ; latitude\n",
        rp->arg.string[0]->str_text,
        rp->arg.string[1]->str_text,
        rp->arg.string[2]->str_text,
        rp->arg.string[3]->str_text
    );
    col = 0;
    indent_to(32, &col, fp);
    fprintf
    (
        fp,
        "%s %s %s %s ; longitude\n",
        rp->arg.string[4]->str_text,
        rp->arg.string[5]->str_text,
        rp->arg.string[6]->str_text,
        rp->arg.string[7]->str_text
    );
    col = 0;
    indent_to(32, &col, fp);
    fprintf(fp, "%s ; altitude\n", rp->arg.string[8]->str_text);
    col = 0;
    indent_to(32, &col, fp);
    fprintf(fp, "%s ; error size\n", rp->arg.string[9]->str_text);
    col = 0;
    indent_to(32, &col, fp);
    fprintf
    (
        fp,
        "%s ; horizontal precision\n",
        rp->arg.string[10]->str_text
    );
    col = 0;
    indent_to(32, &col, fp);
    fprintf
    (
        fp,
        "%s ) ; vertical precision\n",
        rp->arg.string[11]->str_text
    );
    col = 0;

    /*
     * done
     */
    return 6;
}


/* ---------- mx ----------------------------------------------------------- */


static int
mx_local(srrf_t *rp)
{
    return srrf_private_domain_member(rp->arg.string[1]);
}


/* ---------- ns ----------------------------------------------------------- */


static void
ns_check(srrf_t *rp)
{
    string_ty      *tmp;

    tmp = rp->arg.string[0];
    rp->arg.string[0] = srrf_relative_to_absolute(tmp);
    str_free(tmp);
}


static int
ns_local(srrf_t *rp)
{
    return srrf_private_domain_member(rp->arg.string[0]);
}


/* ---------- ptr ---------------------------------------------------------- */


static int
ptr_local(srrf_t *rp)
{
    return srrf_private_domain_member(rp->arg.string[0]);
}


/* ---------- soa ---------------------------------------------------------- */


static int
is_pos_int(const char *s)
{
    if (!*s || *s == '0')
        return 0;
    while (*s)
    {
        if (!isdigit(*s))
            return 0;
        ++s;
    }
    return 1;
}


static void
soa_check(srrf_t *rp)
{
    size_t          j;

    if (auto_time_stamp && rp->arg.nstrings >= 3)
    {
        time_t          now;
        struct tm       *tm;

        /*
         * Create a time stamp of the form YYMMDDnnn.
         * The result must always be less than 2^31,
         * which a 9-digit number will always be.  This
         * form has an 86 second granularity.
         *
         * It was possible to use the time_t value
         * directly, and have a 1 second granularity,
         * but this form is human readable.
         */
        time(&now);
        tm = localtime(&now);
        str_free(rp->arg.string[2]);
        rp->arg.string[2] =
            str_format
            (
                "%2.2d%2.2d%2.2d%3.3d",
                tm->tm_year % 100,
                tm->tm_mon + 1,
                tm->tm_mday,
                (
                    (((tm->tm_hour * 60 + tm->tm_min) * 60 + tm->tm_sec) * 1000)
                /
                    (24 * 60 * 60)
                )
            );
    }
    if (rp->arg.nstrings != 7)
    {
        srrf_lex_error("SOA requires 7 arguments");
    }
    for (j = 3; j < rp->arg.nstrings && j < 7; ++j)
    {
        if (!is_pos_int(rp->arg.string[j]->str_text))
        {
            srrf_lex_error
            (
                "SOA argument %ld (\"%s\") must be a positive integer",
                (long)(j + 1),
                rp->arg.string[j]->str_text
            );
        }
    }
    srrf_private_domain_set(rp->name);
}


static char *
time_string(const char *s)
{
    long            nsec;
    static char     buffer[100];
    char            *bp;
    long            n;

    nsec = atol(s);
    bp = buffer;
    if (nsec >= 7L * 24L * 60L * 60L)
    {
        n = nsec / (7L * 24L * 60L * 60L);
        sprintf(bp, "%ld week%s", n, (n == 1 ? "" : "s"));
        bp += strlen(bp);
        nsec %= (7L * 24L * 60L * 60L);
    }
    if (nsec >= 24L * 60L * 60L)
    {
        if (bp != buffer)
        {
            *bp++ = ',';
            *bp++ = ' ';
        }
        n = nsec / (24L * 60L * 60L);
        sprintf(bp, "%ld day%s", n, (n == 1 ? "" : "s"));
        bp += strlen(bp);
        nsec %= (24L * 60L * 60L);
    }
    if (nsec >= 60L * 60L)
    {
        if (bp != buffer)
        {
            *bp++ = ',';
            *bp++ = ' ';
        }
        n = nsec / (60L * 60L);
        sprintf(bp, "%ld hour%s", n, (n == 1 ? "" : "s"));
        bp += strlen(bp);
        nsec %= (60L * 60L);
    }
    if (nsec >= 60L)
    {
        if (bp != buffer)
        {
            *bp++ = ',';
            *bp++ = ' ';
        }
        n = nsec / 60;
        sprintf(bp, "%ld minute%s", n, (n == 1 ? "" : "s"));
        bp += strlen(bp);
        nsec %= 60;
    }
    if (nsec || bp == buffer)
    {
        if (bp != buffer)
        {
            *bp++ = ',';
            *bp++ = ' ';
        }
        n = nsec;
        sprintf(bp, "%ld second%s", n, (n == 1 ? "" : "s"));
    }
    return buffer;
}


static int
soa_print(srrf_t *rp, FILE *fp)
{
    int             col;
    const char      *tmp;

    /*
     * print the name
     */
    col = 0;
    if (rp->name)
    {
        fprintf(fp, "%s", rp->name->str_text);
        col += rp->name->str_length;
    }

    /*
     * print the time to live
     */
    if (rp->ttl)
    {
        char            ttl[30];

        indent_to(8, &col, fp);
        sprintf(ttl, "%ld", rp->ttl);
        fprintf(fp, "%s", ttl);
        col += strlen(ttl);
    }

    /*
     * print the class
     */
    indent_to(16, &col, fp);
    tmp = rp->class->name;
    fprintf(fp, "%s", tmp);
    col += strlen(tmp);

    /*
     * print the type
     */
    indent_to(24, &col, fp);
    tmp = rp->type->name;
    fprintf(fp, "%s", tmp);
    col += strlen(tmp);

    /*
     * print the other stuff
     */
    indent_to(32, &col, fp);
    fprintf
    (
        fp,
        "%s %s (\n", rp->arg.string[0]->str_text,
        rp->arg.string[1]->str_text
    );
    col = 0;
    indent_to(32, &col, fp);
    fprintf(fp, "%s ; serial\n", rp->arg.string[2]->str_text);
    col = 0;
    indent_to(32, &col, fp);
    fprintf
    (
        fp,
        "%s ; refresh: %s\n",
        rp->arg.string[3]->str_text,
        time_string(rp->arg.string[3]->str_text)
    );
    col = 0;
    indent_to(32, &col, fp);
    fprintf
    (
        fp,
        "%s ; retry: %s\n",
        rp->arg.string[4]->str_text,
        time_string(rp->arg.string[4]->str_text)
    );
    col = 0;
    indent_to(32, &col, fp);
    fprintf
    (
        fp,
        "%s ; expire: %s\n",
        rp->arg.string[5]->str_text,
        time_string(rp->arg.string[5]->str_text)
    );
    col = 0;
    indent_to(32, &col, fp);
    fprintf
    (
        fp,
        "%s ) ; minimum: %s\n",
        rp->arg.string[6]->str_text,
        time_string(rp->arg.string[6]->str_text)
    );
    col = 0;

    /*
     * done
     */
    return 6;
}


/* ------------------------------------------------------------------------- */


static srrf_type_ty type[] =
{
    {
        "a",
        1,  /* number_of_arguments */
        a_check,    /* check_arguments */
        0,  /* local_test */
        0,  /* print */
        0,  /* abs_to_rel */
        0,
        0,
    },
    {
        "cname",
        1,  /* number of arguments */
        cname_check,        /* check_arguments */
        cname_local,        /* local_test */
        0,  /* print */
        cname_a2r,  /* abs_to_rel */
        0,
        0,
    },
    {
        "hinfo",
        2,  /* number of arguments */
        hinfo_check,        /* check_arguments */
        0,  /* local_test */
        0,  /* print */
        0,  /* abs_to_rel */
        0,
        0,
    },
    {
        "loc",
        0,  /* number of arguments */
        loc_check,  /* check arguments */
        0,  /* local_test */
        loc_print,  /* print */
        0,  /* abs_to_rel */
        0,
        0,
    },
    {
        "mx",
        2,  /* number of arguments */
        0,  /* check argukents */
        mx_local,   /* local_test */
        0,  /* print */
        0,  /* abs_to_rel */
        0,
        0,
    },
    {
        "ns",
        1,  /* number of arguments */
        ns_check,   /* check arguments */
        ns_local,   /* local_test */
        0,  /* print */
        0,  /* abs_to_rel */
        0,
        0,
    },
    {
        "ptr",
        1,  /* number of arguments */
        0,  /* check arguments */
        ptr_local,  /* local test */
        0,  /* print */
        0,  /* abs_to_rel */
        0,
        0,
    },
    {
        "soa",
        7,  /* number of arguments */
        soa_check,  /* check arguments */
        0,  /* local test */
        soa_print,  /* print */
        0,  /* abs_to_rel */
        0,
        0,
    },
    {
        "txt",
        0,  /* number of arguments */
        0,  /* check arguments */
        0,  /* local test */
        0,  /* print */
        0,  /* abs_to_rel */
        0,
        0,
    },
};


/*
 * This symbol describes the class.
 * It should be the only symbol exported from this file.
 */
srrf_class_ty   srrf_class_in =
{
    "in",
    type,
    SIZEOF(type)
};


syntax highlighted by Code2HTML, v. 0.9.1