/** @file
  Copyright (c) 1999 - 2014, Intel Corporation. All rights reserved.<BR>
  This program and the accompanying materials are licensed and made available
  under the terms and conditions of the BSD License which accompanies this
  distribution.  The full text of the license may be found at
  http://opensource.org/licenses/bsd-license.php.

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
/*
 * Copyright (c) 1996 by Internet Software Consortium.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */

/*
 * Portions copyright (c) 1999, 2000
 * Intel Corporation.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *
 *    This product includes software developed by Intel Corporation and
 *    its contributors.
 *
 * 4. Neither the name of Intel Corporation or its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY INTEL CORPORATION AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL INTEL CORPORATION OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

/*
 * Based on the Dynamic DNS reference implementation by Viraj Bais
 * <viraj_bais@ccm.fm.intel.com>
 */

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <errno.h>
#include <limits.h>
#include <netdb.h>
#include <resolv.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
 * Separate a linked list of records into groups so that all records
 * in a group will belong to a single zone on the nameserver.
 * Create a dynamic update packet for each zone and send it to the
 * nameservers for that zone, and await answer.
 * Abort if error occurs in updating any zone.
 * Return the number of zones updated on success, < 0 on error.
 *
 * On error, caller must deal with the unsynchronized zones
 * eg. an A record might have been successfully added to the forward
 * zone but the corresponding PTR record would be missing if error
 * was encountered while updating the reverse zone.
 */

#define NSMAX 16

struct ns1 {
    char nsname[MAXDNAME];
    struct in_addr nsaddr1;
};

struct zonegrp {
    char        z_origin[MAXDNAME];
    int16_t     z_class;
    char        z_soardata[MAXDNAME + 5 * INT32SZ];
    struct ns1  z_ns[NSMAX];
    int     z_nscount;
    ns_updrec * z_rr;
    struct zonegrp *z_next;
};


int
res_update(ns_updrec *rrecp_in) {
    ns_updrec *rrecp, *tmprrecp;
    u_char buf[PACKETSZ], answer[PACKETSZ], packet[2*PACKETSZ];
    char name[MAXDNAME], zname[MAXDNAME], primary[MAXDNAME],
         mailaddr[MAXDNAME];
    u_char soardata[2*MAXCDNAME+5*INT32SZ];
    char *dname, *svdname, *cp1, *target;
    u_char *cp, *eom;
    HEADER *hp = (HEADER *) answer;
    struct zonegrp *zptr = NULL, *tmpzptr, *prevzptr, *zgrp_start = NULL;
    int i, j, k = 0, n, ancount, nscount, arcount, rcode, rdatasize,
        newgroup, done, myzone, seen_before, numzones = 0;
    u_int16_t dlen, class, qclass, type, qtype;
    u_int32_t ttl;

    if ((_res.options & RES_INIT) == 0 && res_init() == -1) {
        h_errno = NETDB_INTERNAL;
        return (-1);
    }

    for (rrecp = rrecp_in; rrecp; rrecp = rrecp->r_next) {
        dname = rrecp->r_dname;
        n = (int)strlen(dname);
        if (dname[n-1] == '.')
            dname[n-1] = '\0';
        qtype = T_SOA;
        qclass = rrecp->r_class;
        done = 0;
        seen_before = 0;

        while (!done && dname) {
            if (qtype == T_SOA) {
            for (tmpzptr = zgrp_start;
                 tmpzptr && !seen_before;
                 tmpzptr = tmpzptr->z_next) {
                if (strcasecmp(dname,
                           tmpzptr->z_origin) == 0 &&
                    tmpzptr->z_class == qclass)
                    seen_before++;
                for (tmprrecp = tmpzptr->z_rr;
                     tmprrecp && !seen_before;
                     tmprrecp = tmprrecp->r_grpnext)
                if (strcasecmp(dname, tmprrecp->r_dname) == 0
                    && tmprrecp->r_class == qclass) {
                    seen_before++;
                    break;
                }
                if (seen_before) {
                    /*
                     * Append to the end of
                     * current group.
                     */
                    for (tmprrecp = tmpzptr->z_rr;
                         tmprrecp->r_grpnext;
                         tmprrecp = tmprrecp->r_grpnext)
                        (void)NULL;
                    tmprrecp->r_grpnext = rrecp;
                    rrecp->r_grpnext = NULL;
                    done = 1;
                    break;
                }
            }
        } else if (qtype == T_A) {
            for (tmpzptr = zgrp_start;
             tmpzptr && !done;
             tmpzptr = tmpzptr->z_next)
                for (i = 0; i < tmpzptr->z_nscount; i++)
                if (tmpzptr->z_class == qclass &&
                    strcasecmp(tmpzptr->z_ns[i].nsname,
                           dname) == 0 &&
                    tmpzptr->z_ns[i].nsaddr1.s_addr != 0) {
                    zptr->z_ns[k].nsaddr1.s_addr =
                     tmpzptr->z_ns[i].nsaddr1.s_addr;
                    done = 1;
                    break;
                }
        }
        if (done)
            break;
        n = res_mkquery(QUERY, dname, qclass, qtype, NULL,
                0, NULL, buf, sizeof buf);
        if (n <= 0) {
            fprintf(stderr, "res_update: mkquery failed\n");
            return (n);
        }
        n = res_send(buf, n, answer, sizeof answer);
        if (n < 0) {
            fprintf(stderr, "res_update: send error for %s\n",
                rrecp->r_dname);
            return (n);
        }
        if (n < HFIXEDSZ)
            return (-1);
        ancount = ntohs(hp->ancount);
        nscount = ntohs(hp->nscount);
        arcount = ntohs(hp->arcount);
        rcode = hp->rcode;
        cp = answer + HFIXEDSZ;
        eom = answer + n;
        /* skip the question section */
        n = dn_skipname(cp, eom);
        if (n < 0 || cp + n + 2 * INT16SZ > eom)
            return (-1);
        cp += n + 2 * INT16SZ;

        if (qtype == T_SOA) {
            if (ancount == 0 && nscount == 0 && arcount == 0) {
            /*
             * if (rcode == NOERROR) then the dname exists but
             * has no soa record associated with it.
             * if (rcode == NXDOMAIN) then the dname does not
             * exist and the server is replying out of NCACHE.
             * in either case, proceed with the next try
             */
            dname = strchr(dname, '.');
            if (dname != NULL)
                dname++;
            continue;
            } else if ((rcode == NOERROR || rcode == NXDOMAIN) &&
                   ancount == 0 &&
                   nscount == 1 && arcount == 0) {
            /*
             * name/data does not exist, soa record supplied in the
             * authority section
             */
            /* authority section must contain the soa record */
            if ((n = dn_expand(answer, eom, cp, zname,
                    sizeof zname)) < 0)
                return (n);
            cp += n;
            if (cp + 2 * INT16SZ > eom)
                return (-1);
            GETSHORT(type, cp);
            GETSHORT(class, cp);
            if (type != T_SOA || class != qclass) {
                fprintf(stderr, "unknown answer\n");
                return (-1);
            }
            myzone = 0;
            svdname = dname;
            while (dname)
                if (strcasecmp(dname, zname) == 0) {
                myzone = 1;
                break;
                } else if ((dname = strchr(dname, '.')) != NULL)
                dname++;
            if (!myzone) {
                dname = strchr(svdname, '.');
                if (dname != NULL)
                dname++;
                continue;
            }
            nscount = 0;
            /* fallthrough */
            } else if (rcode == NOERROR && ancount == 1) {
            /*
             * found the zone name
             * new servers will supply NS records for the zone
             * in authority section and A records for those
             * nameservers in the additional section
             * older servers have to be explicitly queried for
             * NS records for the zone
             */
            /* answer section must contain the soa record */
            if ((n = dn_expand(answer, eom, cp, zname,
                           sizeof zname)) < 0)
                return (n);
            else
                cp += n;
            if (cp + 2 * INT16SZ > eom)
                return (-1);
            GETSHORT(type, cp);
            GETSHORT(class, cp);
            if (type == T_CNAME) {
                dname = strchr(dname, '.');
                if (dname != NULL)
                    dname++;
                continue;
            }
            if (strcasecmp(dname, zname) != 0 ||
                type != T_SOA ||
                class != rrecp->r_class) {
                fprintf(stderr, "unknown answer\n");
                return (-1);
            }
            /* FALLTHROUGH */
            } else {
            fprintf(stderr,
        "unknown response: ans=%d, auth=%d, add=%d, rcode=%d\n",
                ancount, nscount, arcount, hp->rcode);
            return (-1);
            }
            if (cp + INT32SZ + INT16SZ > eom)
                return (-1);
            /* continue processing the soa record */
            GETLONG(ttl, cp);
            GETSHORT(dlen, cp);
            if (cp + dlen > eom)
                return (-1);
            newgroup = 1;
            zptr = zgrp_start;
            prevzptr = NULL;
            while (zptr) {
            if (strcasecmp(zname, zptr->z_origin) == 0 &&
                type == T_SOA && class == qclass) {
                newgroup = 0;
                break;
            }
            prevzptr = zptr;
            zptr = zptr->z_next;
            }
            if (!newgroup) {
            for (tmprrecp = zptr->z_rr;
                 tmprrecp->r_grpnext;
                 tmprrecp = tmprrecp->r_grpnext)
                    ;
            tmprrecp->r_grpnext = rrecp;
            rrecp->r_grpnext = NULL;
            done = 1;
            cp += dlen;
            break;
            } else {
            if ((n = dn_expand(answer, eom, cp, primary,
                           sizeof primary)) < 0)
                return (n);
            cp += n;
            /*
             * We don't have to bounds check here because the
             * next use of 'cp' is in dn_expand().
             */
            cp1 = (char *)soardata;
            strcpy(cp1, primary);
            cp1 += strlen(cp1) + 1;
            if ((n = dn_expand(answer, eom, cp, mailaddr,
                           sizeof mailaddr)) < 0)
                return (n);
            cp += n;
            strcpy(cp1, mailaddr);
            cp1 += strlen(cp1) + 1;
            if (cp + 5*INT32SZ > eom)
                return (-1);
            memcpy(cp1, cp, 5*INT32SZ);
            cp += 5*INT32SZ;
            cp1 += 5*INT32SZ;
            rdatasize = (int)((u_char *)cp1 - soardata);
            zptr = calloc(1, sizeof(struct zonegrp));
            if (zptr == NULL)
                        return (-1);
            if (zgrp_start == NULL)
                zgrp_start = zptr;
            else
                prevzptr->z_next = zptr;
            zptr->z_rr = rrecp;
            rrecp->r_grpnext = NULL;
            strcpy(zptr->z_origin, zname);
            zptr->z_class = class;
            memcpy(zptr->z_soardata, soardata, rdatasize);
            /* fallthrough to process NS and A records */
            }
        } else if (qtype == T_NS) {
            if (rcode == NOERROR && ancount > 0) {
            strcpy(zname, dname);
            for (zptr = zgrp_start; zptr; zptr = zptr->z_next) {
                if (strcasecmp(zname, zptr->z_origin) == 0)
                break;
            }
            if (zptr == NULL)
                /* should not happen */
                return (-1);
            if (nscount > 0) {
                /*
                 * answer and authority sections contain
                 * the same information, skip answer section
                 */
                for (j = 0; j < ancount; j++) {
                n = dn_skipname(cp, eom);
                if (n < 0)
                    return (-1);
                n += 2*INT16SZ + INT32SZ;
                if (cp + n + INT16SZ > eom)
                    return (-1);
                cp += n;
                GETSHORT(dlen, cp);
                cp += dlen;
                }
            } else
                nscount = ancount;
            /* fallthrough to process NS and A records */
            } else {
            fprintf(stderr, "cannot determine nameservers for %s:\
ans=%d, auth=%d, add=%d, rcode=%d\n",
                dname, ancount, nscount, arcount, hp->rcode);
            return (-1);
            }
        } else if (qtype == T_A) {
            if (rcode == NOERROR && ancount > 0) {
            arcount = ancount;
            ancount = nscount = 0;
            /* fallthrough to process A records */
            } else {
            fprintf(stderr, "cannot determine address for %s:\
ans=%d, auth=%d, add=%d, rcode=%d\n",
                dname, ancount, nscount, arcount, hp->rcode);
            return (-1);
            }
        }
        /* process NS records for the zone */
        j = 0;
        for (i = 0; i < nscount; i++) {
            if ((n = dn_expand(answer, eom, cp, name,
                    sizeof name)) < 0)
            return (n);
            cp += n;
            if (cp + 3 * INT16SZ + INT32SZ > eom)
                return (-1);
            GETSHORT(type, cp);
            GETSHORT(class, cp);
            GETLONG(ttl, cp);
            GETSHORT(dlen, cp);
            if (cp + dlen > eom)
            return (-1);
            if (strcasecmp(name, zname) == 0 &&
            type == T_NS && class == qclass) {
                if ((n = dn_expand(answer, eom, cp,
                           name, sizeof name)) < 0)
                    return (n);
                target = zptr->z_ns[j++].nsname;
                strcpy(target, name);
            }
            cp += dlen;
        }
        if (zptr->z_nscount == 0)
            zptr->z_nscount = j;
        /* get addresses for the nameservers */
        for (i = 0; i < arcount; i++) {
            if ((n = dn_expand(answer, eom, cp, name,
                    sizeof name)) < 0)
            return (n);
            cp += n;
            if (cp + 3 * INT16SZ + INT32SZ > eom)
            return (-1);
            GETSHORT(type, cp);
            GETSHORT(class, cp);
            GETLONG(ttl, cp);
            GETSHORT(dlen, cp);
            if (cp + dlen > eom)
                return (-1);
            if (type == T_A && dlen == INT32SZ && class == qclass) {
            for (j = 0; j < zptr->z_nscount; j++)
                if (strcasecmp(name, zptr->z_ns[j].nsname) == 0) {
                memcpy(&zptr->z_ns[j].nsaddr1.s_addr, cp,
                       INT32SZ);
                break;
                }
            }
            cp += dlen;
        }
        if (zptr->z_nscount == 0) {
            dname = zname;
            qtype = T_NS;
            continue;
        }
        done = 1;
        for (k = 0; k < zptr->z_nscount; k++)
            if (zptr->z_ns[k].nsaddr1.s_addr == 0) {
            done = 0;
            dname = zptr->z_ns[k].nsname;
            qtype = T_A;
            }
        } /* while */
    }
    --ttl;  // Suppress the "Set but not used" warning/error for ttl.

    _res.options |= RES_DEBUG;
    for (zptr = zgrp_start; zptr; zptr = zptr->z_next) {

        /* append zone section */
        rrecp = res_mkupdrec(ns_s_zn, zptr->z_origin,
                     zptr->z_class, ns_t_soa, 0);
        if (rrecp == NULL) {
            fprintf(stderr, "saverrec error\n");
            fflush(stderr);
            return (-1);
        }
        rrecp->r_grpnext = zptr->z_rr;
        zptr->z_rr = rrecp;

        n = res_mkupdate(zptr->z_rr, packet, sizeof packet);
        if (n < 0) {
            fprintf(stderr, "res_mkupdate error\n");
            fflush(stderr);
            return (-1);
        } else
            fprintf(stdout, "res_mkupdate: packet size = %d\n", n);

        /* Override the list of NS records from res_init() with
         * the authoritative nameservers for the zone being updated.
         * Sort primary to be the first in the list of nameservers.
         */
        for (i = 0; i < zptr->z_nscount; i++) {
            if (strcasecmp(zptr->z_ns[i].nsname,
                       zptr->z_soardata) == 0) {
                struct in_addr tmpaddr;

                if (i != 0) {
                    strcpy(zptr->z_ns[i].nsname,
                           zptr->z_ns[0].nsname);
                    strcpy(zptr->z_ns[0].nsname,
                           zptr->z_soardata);
                    tmpaddr = zptr->z_ns[i].nsaddr1;
                    zptr->z_ns[i].nsaddr1 =
                        zptr->z_ns[0].nsaddr1;
                    zptr->z_ns[0].nsaddr1 = tmpaddr;
                }
                break;
            }
        }
        for (i = 0; i < MAXNS; i++) {
            _res.nsaddr_list[i].sin_addr = zptr->z_ns[i].nsaddr1;
            _res.nsaddr_list[i].sin_family = AF_INET;
            _res.nsaddr_list[i].sin_port = htons(NAMESERVER_PORT);
        }
        _res.nscount = (zptr->z_nscount < MAXNS) ?
                    zptr->z_nscount : MAXNS;
        n = res_send(packet, n, answer, sizeof(answer));
        if (n < 0) {
            fprintf(stderr, "res_send: send error, n=%d\n", n);
            break;
        } else
            numzones++;
    }

    /* free malloc'ed memory */
    while(zgrp_start) {
        zptr = zgrp_start;
        zgrp_start = zgrp_start->z_next;
        res_freeupdrec(zptr->z_rr);  /* Zone section we allocated. */
        free((char *)zptr);
    }

    return (numzones);
}
