/*
 * Copyright (c) 1990,1993 Regents of The University of Michigan.
 * All Rights Reserved. See COPYRIGHT.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/types.h>
#include <atalk/logger.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <net/if.h>
#include <net/route.h>
#include <netatalk/at.h>

#ifdef __svr4__
#include <sys/sockio.h>
#endif /* __svr4__ */

#include <atalk/ddp.h>
#include <atalk/zip.h>
#include <atalk/atp.h>
#include <atalk/util.h>

#include "atserv.h"
#include "interface.h"
#include "gate.h"
#include "zip.h"
#include "rtmp.h"
#include "list.h"
#include "multicast.h"
#include "main.h"

struct ziptab	*ziptab = NULL, *ziplast = NULL;

static int zonecheck(struct rtmptab *rtmp, struct interface *iface)
{
    struct list		*l;
    struct ziptab	*czt, *zt;
    int			cztcnt, ztcnt;

    if ((iface->i_flags & IFACE_SEED) == 0) {
        return 0;
    }

    for (cztcnt = 0, czt = iface->i_czt; czt; czt = czt->zt_next, cztcnt++) {
        for (l = rtmp->rt_zt; l; l = l->l_next) {
            zt = (struct ziptab *)l->l_data;

            if (czt->zt_len == zt->zt_len &&
                    !strndiacasecmp(czt->zt_name, zt->zt_name, czt->zt_len)) {
                break;
            }
        }

        if (l == NULL) {
            LOG(log_error, logtype_atalkd, "zonecheck: %.*s not in zone list", czt->zt_len,
                czt->zt_name);
            /* configured zone not found in net zones */
            return -1;
        }
    }

    for (ztcnt = 0, l = rtmp->rt_zt; l; l = l->l_next, ztcnt++)
        ;

    if (cztcnt != ztcnt) {
        LOG(log_error, logtype_atalkd, "zonecheck: %d configured zones, %d zones found",
            cztcnt, ztcnt);
        /* more net zones than configured zones */
        return -1;
    }

    return 0;
}


int zip_packet(struct atport *ap, struct sockaddr_at *from, char *data, int len)
{
    struct ziphdr	zh;
    struct atphdr	ah;
    struct interface	*iface;
    struct gate		*gate;
    struct rtmptab	*rtmp = NULL;
    struct list		*l;
    struct ziptab	*zt;
    unsigned short		firstnet, lastnet, index, nz;
    char		*end, zname[32], packet[ATP_BUFSIZ], *nzones, *lastflag;
    char		*reply, *rend, *ziphdr;
    int			zlen, n, zipop, rcnt, qcnt, zcnt, zsz;
    extern int		stabletimer;
    end = data + len;

    if (data >= end) {
        LOG(log_info, logtype_atalkd, "zip_packet malformed packet");
        return 1;
    }

    /* get interface */
    iface = ap->ap_iface;

    switch (*data++) {
    case DDPTYPE_ZIP :
        if (data + sizeof(struct ziphdr) > end) {
            LOG(log_info, logtype_atalkd, "zip_packet malformed packet");
            return 1;
        }

        memcpy(&zh, data, sizeof(struct ziphdr));
        data += sizeof(struct ziphdr);

        switch (zh.zh_op) {
        case ZIPOP_QUERY :
            /* set up reply */
            reply = packet;
            rend = packet + sizeof(packet);
            *reply++ = DDPTYPE_ZIP;
            ziphdr = reply;
            reply += 2;
            rcnt = 0;
            qcnt = zh.zh_count;

            while (data + sizeof(unsigned short) <= end && qcnt-- > 0) {
                memcpy(&firstnet, data, sizeof(unsigned short));
                data += sizeof(unsigned short);

                /*
                 * Look for the given network number (firstnet).
                 * Perhaps we could do better than brute force?
                 */
                for (iface = interfaces; iface; iface = iface->i_next) {
                    for (rtmp = iface->i_rt; rtmp; rtmp = rtmp->rt_inext) {
                        if (firstnet == rtmp->rt_firstnet) {
                            break;
                        }
                    }

                    if (rtmp) {
                        break;
                    }
                }

                if (rtmp == NULL) {
                    continue;
                }

                /*
                 * Count the number of zones in this list, and the
                 * number of byte it will consume in a reply.
                 */
                for (zsz = 0, zcnt = 0, l = rtmp->rt_zt; l; l = l->l_next) {
                    zcnt++;
                    zt = (struct ziptab *)l->l_data;
                    zsz += sizeof(unsigned short) + 1 + zt->zt_len;
                }

                /*
                 * We might send this list in the current reply, as the
                 * first thing in the next reply, or as an extended packet.
                 */
                if (reply + zsz > rend) {
                    if (rcnt > 0) {
                        zh.zh_op = ZIPOP_REPLY;
                        zh.zh_cnt = rcnt;
                        memcpy(ziphdr, &zh, sizeof(struct ziphdr));

                        if (sendto(ap->ap_fd, packet, reply - packet, 0,
                                   (struct sockaddr *)from,
                                   sizeof(struct sockaddr_at)) < 0) {
                            LOG(log_error, logtype_atalkd, "zip reply sendto: %s",
                                strerror(errno));
                        }

                        reply = packet + 3;
                        rcnt = 0;
                    }

                    if (reply + zsz > rend) {
                        /* ereply */
                        for (l = rtmp->rt_zt; l; l = l->l_next, rcnt++) {
                            zt = (struct ziptab *)l->l_data;

                            if (reply + sizeof(unsigned short) + 1 + zt->zt_len >
                                    rend) {
                                zh.zh_op = ZIPOP_EREPLY;
                                zh.zh_cnt = zcnt;
                                memcpy(ziphdr, &zh, sizeof(struct ziphdr));

                                if (sendto(ap->ap_fd, packet, reply - packet,
                                           0, (struct sockaddr *)from,
                                           sizeof(struct sockaddr_at)) < 0) {
                                    LOG(log_error, logtype_atalkd, "zip reply sendto: %s",
                                        strerror(errno));
                                }

                                reply = packet + 3;
                                rcnt = 0;
                            }

                            memcpy(reply, &firstnet, sizeof(unsigned short));
                            reply += sizeof(unsigned short);
                            *reply++ = zt->zt_len;
                            memcpy(reply, zt->zt_name, zt->zt_len);
                            reply += zt->zt_len;
                        }

                        if (rcnt > 0) {
                            zh.zh_op = ZIPOP_EREPLY;
                            zh.zh_cnt = zcnt;
                            memcpy(ziphdr, &zh, sizeof(struct ziphdr));

                            if (sendto(ap->ap_fd, packet, reply - packet, 0,
                                       (struct sockaddr *)from,
                                       sizeof(struct sockaddr_at)) < 0) {
                                LOG(log_error, logtype_atalkd, "zip reply sendto: %s",
                                    strerror(errno));
                            }

                            reply = packet + 3;
                            rcnt = 0;
                        }

                        continue;
                    }
                }

                for (l = rtmp->rt_zt; l; l = l->l_next, rcnt++) {
                    zt = (struct ziptab *)l->l_data;
                    memcpy(reply, &firstnet, sizeof(unsigned short));
                    reply += sizeof(unsigned short);
                    *reply++ = zt->zt_len;
                    memcpy(reply, zt->zt_name, zt->zt_len);
                    reply += zt->zt_len;
                }
            }

            if (rcnt > 0) {
                zh.zh_op = ZIPOP_REPLY;
                zh.zh_cnt = rcnt;
                memcpy(ziphdr, &zh, sizeof(struct ziphdr));

                if (sendto(ap->ap_fd, packet, reply - packet, 0,
                           (struct sockaddr *)from,
                           sizeof(struct sockaddr_at)) < 0) {
                    LOG(log_error, logtype_atalkd, "zip reply sendto: %s",
                        strerror(errno));
                }
            }

            break;

        case ZIPOP_REPLY :
            for (gate = iface->i_gate; gate; gate = gate->g_next) {
                if ((from->sat_addr.s_net == 0 ||
                        gate->g_sat.sat_addr.s_net == from->sat_addr.s_net) &&
                        gate->g_sat.sat_addr.s_node == from->sat_addr.s_node) {
                    break;
                }
            }

            if (gate == NULL) {
                LOG(log_info, logtype_atalkd, "zip reply from non-gateway %u.%u",
                    ntohs(from->sat_addr.s_net), from->sat_addr.s_node);
                return 1;
            }

            rtmp = NULL;

            do {
                if (data + sizeof(unsigned short) + 1 > end) {	/* + strlen */
                    LOG(log_info, logtype_atalkd, "zip reply short (%d)", len);
                    return 1;
                }

                memcpy(&firstnet, data, sizeof(unsigned short));
                data += sizeof(unsigned short);

                if (rtmp && rtmp->rt_firstnet != firstnet) {
                    /* XXX */
                    if (rtmp->rt_gate == NULL &&
                            zonecheck(rtmp, gate->g_iface) != 0) {
                        LOG(log_error, logtype_atalkd, "zip_packet seed zonelist mismatch");
                        return -1;
                    }

                    rtmp->rt_flags &= ~RTMPTAB_ZIPQUERY;
                }

                /* Check if this is the interface's route. */
                if (firstnet == gate->g_iface->i_rt->rt_firstnet) {
                    rtmp = gate->g_iface->i_rt;
                } else {
                    for (rtmp = gate->g_rt; rtmp; rtmp = rtmp->rt_next) {
                        if (rtmp->rt_firstnet == firstnet) {
                            break;
                        }
                    }

                    /*
                     * Update head to this rtmp entry.
                     */
                    if (rtmp != NULL && gate->g_rt != rtmp) {
                        gate->g_rt->rt_prev->rt_next = gate->g_rt;
                        gate->g_rt = rtmp;
                        rtmp->rt_prev->rt_next = NULL;
                    }
                }

                zlen = *data++;

                if (zlen > 32 || zlen <= 0) {
                    LOG(log_info, logtype_atalkd, "zip reply bad packet");
                    return 1;
                }

                if (data + zlen > end) {
                    LOG(log_info, logtype_atalkd, "zip reply short (%d)", len);
                    return 1;
                }

                memcpy(zname, data, zlen);
                data += zlen;

                /*
                 * We won't find any rtmp entry if the gateway is no longer
                 * telling us about the entry.
                 */
                if (rtmp == NULL) {
                    LOG(log_info, logtype_atalkd, "zip skip reply %u from %u.%u (no rtmp)",
                        ntohs(firstnet), ntohs(from->sat_addr.s_net),
                        from->sat_addr.s_node);
                    /*
                     * Check if the route is still in use (the iprev check is
                     * no good if rtmp is the interface's route).
                     */
                } else if (rtmp->rt_iprev == NULL && rtmp->rt_prev != NULL) {
                    LOG(log_info, logtype_atalkd,
                        "zip skip reply %u-%u from %u.%u (rtmp not in use)",
                        ntohs(rtmp->rt_firstnet),
                        ntohs(rtmp->rt_lastnet),
                        ntohs(from->sat_addr.s_net),
                        from->sat_addr.s_node);
                    /*
                     * Check if we've got an outstanding query for this route.
                     * We will often get this, since we ask every router on a
                     * net to verify our interface's zone(s).
                     */
                } else if ((rtmp->rt_flags & RTMPTAB_ZIPQUERY) == 0) {
                    LOG(log_info, logtype_atalkd,
                        "zip skip reply %u-%u from %u.%u (no query)",
                        ntohs(rtmp->rt_firstnet),
                        ntohs(rtmp->rt_lastnet),
                        ntohs(from->sat_addr.s_net),
                        from->sat_addr.s_node);
                } else {
                    if (addzone(rtmp, zlen, zname) < 0) {
                        LOG(log_error, logtype_atalkd, "zip_packet: addzone");
                        return -1;
                    }

                    rtmp->rt_flags |= RTMPTAB_HASZONES;
                }
            } while (data < end);

            if (rtmp && rtmp->rt_flags & RTMPTAB_HASZONES) {
                /* XXX */
                if (rtmp->rt_gate == NULL &&
                        zonecheck(rtmp, gate->g_iface) != 0) {
                    LOG(log_error, logtype_atalkd, "zip_packet seed zonelist mismatch");
                    return -1;
                }

                rtmp->rt_flags &= ~RTMPTAB_ZIPQUERY;
            }

            break;

        case ZIPOP_EREPLY :
            for (gate = iface->i_gate; gate; gate = gate->g_next) {
                if ((from->sat_addr.s_net == 0 ||
                        gate->g_sat.sat_addr.s_net == from->sat_addr.s_net) &&
                        gate->g_sat.sat_addr.s_node == from->sat_addr.s_node) {
                    break;
                }
            }

            if (gate == NULL) {
                LOG(log_info, logtype_atalkd, "zip ereply from non-gateway %u.%u",
                    ntohs(from->sat_addr.s_net), from->sat_addr.s_node);
                return 1;
            }

            /*
             * Note that we're not advancing "data" here.  We do that
             * at the top of the do-while loop, below.
             */
            if (data + sizeof(unsigned short) + 1 > end) {	/* + strlen */
                LOG(log_info, logtype_atalkd, "zip ereply short (%d)", len);
                return 1;
            }

            memcpy(&firstnet, data, sizeof(unsigned short));

            /* Check if this is the interface's route. */
            if (firstnet == gate->g_iface->i_rt->rt_firstnet) {
                rtmp = gate->g_iface->i_rt;
            } else {
                for (rtmp = gate->g_rt; rtmp; rtmp = rtmp->rt_next) {
                    if (rtmp->rt_firstnet == firstnet) {
                        break;
                    }
                }

                if (rtmp == NULL) {
                    LOG(log_info, logtype_atalkd, "zip ereply %u from %u.%u (no rtmp)",
                        ntohs(firstnet), ntohs(from->sat_addr.s_net),
                        from->sat_addr.s_node);
                    return 1;
                }

                if (rtmp->rt_iprev == NULL) {
                    LOG(log_info, logtype_atalkd,
                        "zip ereply %u-%u from %u.%u (rtmp not in use)",
                        ntohs(rtmp->rt_firstnet),
                        ntohs(rtmp->rt_lastnet),
                        ntohs(from->sat_addr.s_net),
                        from->sat_addr.s_node);
                }

                /* update head to *next* rtmp entry */
                if (rtmp->rt_next != NULL) {
                    gate->g_rt->rt_prev->rt_next = gate->g_rt;
                    gate->g_rt = rtmp->rt_next;
                    rtmp->rt_next = NULL;
                }
            }

            if ((rtmp->rt_flags & RTMPTAB_ZIPQUERY) == 0) {
                LOG(log_info, logtype_atalkd, "zip ereply %u-%u from %u.%u (no query)",
                    ntohs(rtmp->rt_firstnet),
                    ntohs(rtmp->rt_lastnet),
                    ntohs(from->sat_addr.s_net),
                    from->sat_addr.s_node);
                return 0;
            }

            do {
                /*
                 * We copy out firstnet, twice (see above).  Not
                 * a big deal, and it makes the end condition cleaner.
                 */
                if (data + sizeof(unsigned short) + 1 > end) {	/* + strlen */
                    LOG(log_info, logtype_atalkd, "zip ereply short (%d)", len);
                    return 1;
                }

                memcpy(&firstnet, data, sizeof(unsigned short));
                data += sizeof(unsigned short);

                /* check route */
                if (firstnet != rtmp->rt_firstnet) {
                    LOG(log_info, logtype_atalkd, "zip ereply with multiple nets");
                    return 1;
                }

                zlen = *data++;

                if (zlen > 32 || zlen <= 0) {
                    LOG(log_info, logtype_atalkd, "zip ereply bad zone length (%d)", zlen);
                    return 1;
                }

                if (data + zlen > end) {
                    LOG(log_info, logtype_atalkd, "zip ereply short (%d)", len);
                    return 1;
                }

                memcpy(zname, data, zlen);
                data += zlen;

                if (addzone(rtmp, zlen, zname) < 0) {
                    LOG(log_error, logtype_atalkd, "zip_packet: addzone");
                    return -1;
                }
            } while (data < end);

            if (rtmp) {
                /*
                 * Count zones for rtmptab entry.
                 */
                for (n = 0, l = rtmp->rt_zt; l; l = l->l_next, n++)
                    ;

                if (n == zh.zh_count) {
                    rtmp->rt_flags |= RTMPTAB_HASZONES;

                    /* XXX */
                    if (rtmp->rt_gate == NULL &&
                            zonecheck(rtmp, gate->g_iface) != 0) {
                        LOG(log_error, logtype_atalkd, "zip_packet seed zonelist mismatch");
                        return -1;
                    }

                    rtmp->rt_flags &= ~RTMPTAB_ZIPQUERY;
                }
            }

            break;

        case ZIPOP_GNI :

            /*
             * Don't answer with bogus information.
             */
            if (((iface->i_flags & IFACE_ISROUTER) == 0) ||
                    iface->i_rt->rt_zt == NULL ||
                    (iface->i_flags & IFACE_CONFIG) == 0) {
                return 0;
            }

            if (zh.zh_zero != 0 || data + 2 * sizeof(unsigned short) > end) {
                LOG(log_info, logtype_atalkd, "zip_packet malformed packet");
                return 1;
            }

            memcpy(&firstnet, data, sizeof(unsigned short));
            data += sizeof(unsigned short);
            memcpy(&lastnet, data, sizeof(unsigned short));
            data += sizeof(unsigned short);

            if (firstnet != 0 || lastnet != 0 || data >= end) {
                LOG(log_info, logtype_atalkd, "zip_packet malformed packet");
                return 1;
            }

            zlen = *data++;

            if (zlen < 0 || zlen > 32) {
                LOG(log_info, logtype_atalkd, "zip_packet malformed packet");
                return 1;
            }

            memcpy(zname, data, zlen);
            data = packet;
            end = data + sizeof(packet);
            zh.zh_op = ZIPOP_GNIREPLY;
            zh.zh_flags = 0;
            /*
             * Skip to the nets.  Fill in header when we're done.
             */
            data += 1 + sizeof(struct ziphdr);
            memcpy(data, &iface->i_rt->rt_firstnet, sizeof(unsigned short));
            data += sizeof(unsigned short);
            memcpy(data, &iface->i_rt->rt_lastnet, sizeof(unsigned short));
            data += sizeof(unsigned short);
            *data++ = zlen;
            memcpy(data, zname, zlen);
            data += zlen;

            /*
             * Check if the given zone is valid.  If it's valid, just fill in
             * the multicast address.  If it's not, fill the multicast address
             * in with the default zone and return the default zone.
             */
            for (l = iface->i_rt->rt_zt; l; l = l->l_next) {
                zt = (struct ziptab *)l->l_data;

                if (zt->zt_len == zlen &&
                        strndiacasecmp(zname, zt->zt_name, zlen) == 0) {
                    break;
                }
            }

            if (l == NULL) {
                zt = (struct ziptab *)iface->i_rt->rt_zt->l_data;
                zh.zh_flags |= ZIPGNI_INVALID;
            }

            for (n = 0, l = iface->i_rt->rt_zt; l; l = l->l_next, n++)
                ;

            if (n == 1) {
                zh.zh_flags |= ZIPGNI_ONEZONE;
            }

            /* multicast */
            *data++ = 6;	/* sizeof ??? */

            if (zone_bcast(zt) < 0) {
                LOG(log_error, logtype_atalkd, "zip_packet: zone_bcast");
                return -1;
            }

            memcpy(data, zt->zt_bcast, 6);
            data += 6;

            /*
             * Add default zone.
             */
            if (zh.zh_flags & ZIPGNI_INVALID) {
                *data++ = zt->zt_len;
                memcpy(data, zt->zt_name, zt->zt_len);
                data += zt->zt_len;
            }

            /* fill in header */
            *packet = DDPTYPE_ZIP;
            memcpy(packet + 1, &zh, sizeof(struct ziphdr));

            /*
             * If the address we received this request from isn't correct
             * for the net we received it on, send a broadcast.
             */
            if (ntohs(from->sat_addr.s_net) <
                    ntohs(iface->i_rt->rt_firstnet) ||
                    ntohs(from->sat_addr.s_net) >
                    ntohs(iface->i_rt->rt_lastnet)) {
                from->sat_addr.s_net = 0;
                from->sat_addr.s_node = ATADDR_BCAST;
            }

            if (sendto(ap->ap_fd, packet, data - packet, 0,
                       (struct sockaddr *)from,
                       sizeof(struct sockaddr_at)) < 0) {
                LOG(log_error, logtype_atalkd, "zip gni sendto %u.%u: %s",
                    ntohs(from->sat_addr.s_net), from->sat_addr.s_node,
                    strerror(errno));
                return 1;
            }

            break;

        case ZIPOP_GNIREPLY :
            /*
             * Ignore ZIP GNIReplys which are either late or unsolicited.
             */
            LOG(log_debug, logtype_atalkd, "zip gnireply from %u.%u (%s %x)",
                ntohs(from->sat_addr.s_net), from->sat_addr.s_node,
                iface->i_name, iface->i_flags);

            if ((iface->i_flags & (IFACE_CONFIG | IFACE_PHASE1)) ||
                    (iface->i_flags & IFACE_ADDR) == 0) {
                LOG(log_debug, logtype_atalkd, "zip ignoring gnireply");
                return 1;
            }

            if (data + 2 * sizeof(unsigned short) > end) {
                LOG(log_info, logtype_atalkd, "zip_packet malformed packet");
                return 1;
            }

            memcpy(&firstnet, data, sizeof(unsigned short));
            data += sizeof(unsigned short);
            memcpy(&lastnet, data, sizeof(unsigned short));
            data += sizeof(unsigned short);

            /*
             * We never ask for a zone, so we can get back what the
             * default zone is.
             */
            if (data >= end || data + *data > end) {
                LOG(log_info, logtype_atalkd, "zip_packet malformed packet");
                return 1;
            }

            if (*data++ != 0) {
                LOG(log_info, logtype_atalkd, "zip_packet unsolicited zone");
                return 1;
            }

            /* skip multicast (should really check it) */
            if (data >= end || data + *data > end) {
                LOG(log_info, logtype_atalkd, "zip_packet malformed packet");
                return 1;
            }

            data += *data + 1;

            if (data >= end || data + *data > end) {
                LOG(log_info, logtype_atalkd, "zip_packet malformed packet");
                return 1;
            }

            /*
             * First, if we're not seed, we always get our zone information
             * from the net -- we don't even save what was in the file.
             * Second, if we are seed, we keep our zone list in the
             * interface structure, not in the zone table.  This allows us
             * to check that the net is giving us good zones.
             */
            if ((iface->i_flags & IFACE_SEED) && iface->i_czt) {
                if (iface->i_czt->zt_len != *data ||
                        strndiacasecmp(iface->i_czt->zt_name,
                                       data + 1, *data) != 0) {
                    LOG(log_error, logtype_atalkd, "default zone mismatch on %s",
                        iface->i_name);
                    LOG(log_error, logtype_atalkd, "%.*s != %.*s",
                        iface->i_czt->zt_len, iface->i_czt->zt_name,
                        *data, data + 1);
                    LOG(log_error, logtype_atalkd, "Seed error! Exiting!");
                    return -1;
                }
            }

            if (addzone(iface->i_rt, *data, data + 1) < 0) {
                LOG(log_error, logtype_atalkd, "zip_packet: addzone");
                return -1;
            }

            /*
             * The netrange we received from the router doesn't match the
             * range we have locally. This is not a problem, unless we
             * have seed information.
             */
            if (firstnet != iface->i_rt->rt_firstnet ||
                    lastnet != iface->i_rt->rt_lastnet) {
                if (iface->i_flags & IFACE_SEED) {
                    LOG(log_error, logtype_atalkd, "netrange mismatch on %s",
                        iface->i_name);
                    LOG(log_error, logtype_atalkd, "%u-%u != %u-%u",
                        ntohs(firstnet), ntohs(lastnet),
                        ntohs(iface->i_rt->rt_firstnet),
                        ntohs(iface->i_rt->rt_lastnet));
                    LOG(log_error, logtype_atalkd, "Seed error! Exiting!");
                    return -1;
                }

                /*
                 * It is possible that we will corrupt our route database
                 * by just forcing this change.  A better solution would
                 * be to search all of our current routes, looking for
                 * this new route, and delete any old versions.  Also, we
                 * would call rtmp_delete() on the old net range, in case
                 * there is some other net which actually had that range.  XXX
                 */
                iface->i_rt->rt_firstnet = firstnet;
                iface->i_rt->rt_lastnet = lastnet;

                if (ntohs(iface->i_addr.sat_addr.s_net) <
                        ntohs(firstnet) ||
                        ntohs(iface->i_addr.sat_addr.s_net) >
                        ntohs(lastnet)) {
                    iface->i_addr.sat_addr.s_net = 0;	/* ATADDR_ANYNET? */
                }

                setaddr(iface, IFACE_PHASE2, iface->i_addr.sat_addr.s_net,
                        iface->i_addr.sat_addr.s_node, firstnet, lastnet);
                stabletimer = UNSTABLE;
            }

            /* add addr to loopback route */
            if (looproute(iface, RTMP_ADD)) {    /* -1 or 1 */
                LOG(log_error, logtype_atalkd,
                    "zip_packet: can't route %u.%u to loopback: %s",
                    ntohs(iface->i_addr.sat_addr.s_net),
                    iface->i_addr.sat_addr.s_node,
                    strerror(errno));
                return -1;
            }

            LOG(log_info, logtype_atalkd, "zip_packet configured %s from %u.%u",
                iface->i_name, ntohs(from->sat_addr.s_net),
                from->sat_addr.s_node);
            iface->i_flags |= IFACE_CONFIG;

            if (iface == ciface) {
                ciface = ciface->i_next;
                bootaddr(ciface);
            }

            break;

        case ZIPOP_NOTIFY :
            LOG(log_debug, logtype_atalkd, "zip notify from %u.%u\n",
                ntohs(from->sat_addr.s_net),
                from->sat_addr.s_node);
            break;

        default :
            LOG(log_info, logtype_atalkd, "zip_packet bad zip op from %u.%u",
                ntohs(from->sat_addr.s_net), from->sat_addr.s_node);
        }

        break;

    case DDPTYPE_ATP :
        if (data + sizeof(struct atphdr) > end) {
            LOG(log_info, logtype_atalkd, "zip atp malformed packet");
            return 1;
        }

        memcpy(&ah, data, sizeof(struct atphdr));
        data += sizeof(struct atphdr);

        if (ah.atphd_ctrlinfo != ATP_TREQ) {
            LOG(log_info, logtype_atalkd, "zip atp bad control");
            return 1;
        }

        ah.atphd_ctrlinfo = ATP_TRESP | ATP_EOM;

        if (ah.atphd_bitmap != 1) {
            LOG(log_error, logtype_atalkd, "zip atp bad bitmap");
            return 1;
        }

        ah.atphd_bitmap = 0;
        zipop = *data++;
        data++;
        memcpy(&index, data, sizeof(unsigned short));
        data += sizeof(unsigned short);
        index = ntohs(index);

        if (data != end) {
            LOG(log_info, logtype_atalkd, "zip atp malformed packet");
            return 1;
        }

        data = packet;
        end = data + sizeof(packet);
        *data++ = DDPTYPE_ATP;
        memcpy(data, &ah, sizeof(struct atphdr));
        data += sizeof(struct atphdr);
        /* mark and space for last flag */
        lastflag = data++;
        *data++ = 0;
        /* mark and space for zone count */
        nzones = data;
        data += sizeof(unsigned short);

        switch (zipop) {
        case ZIPOP_GETMYZONE :
            if (index != 0) {
                LOG(log_info, logtype_atalkd, "zip atp gmz bad index");
                return 1;
            }

            if (iface->i_flags & IFACE_LOOPBACK) {
                /* first interface */
                iface = interfaces->i_next;
            } else if (ntohs(iface->i_rt->rt_firstnet) >
                       ntohs(from->sat_addr.s_net) ||
                       ntohs(iface->i_rt->rt_lastnet) <
                       ntohs(from->sat_addr.s_net)) {
                return 0;
            }

            if (iface->i_rt->rt_zt == NULL) {
                return 0;
            }

            zt = (struct ziptab *)iface->i_rt->rt_zt->l_data;

            if (data + 1 + zt->zt_len > end) {
                LOG(log_info, logtype_atalkd, "zip atp gmz reply too long");
                return 1;
            }

            *data++ = zt->zt_len;
            memcpy(data, zt->zt_name, zt->zt_len);
            data += zt->zt_len;
            *lastflag = 0;
            nz = 1;
            break;

        case ZIPOP_GETZONELIST :
            for (zt = ziptab; zt && (index > 1); zt = zt->zt_next, index--)
                ;

            for (nz = 0; zt; zt = zt->zt_next, nz++) {
                if (data + 1 + zt->zt_len > end) {
                    break;
                }

                *data++ = zt->zt_len;
                memcpy(data, zt->zt_name, zt->zt_len);
                data += zt->zt_len;
            }

            /* Too clever? */
            *lastflag = (zt == NULL);
            break;

        case ZIPOP_GETLOCALZONES :
            if (iface->i_flags & IFACE_LOOPBACK) {
                /* first interface */
                iface = interfaces->i_next;
            } else if (ntohs(iface->i_rt->rt_firstnet) >
                       ntohs(from->sat_addr.s_net) ||
                       ntohs(iface->i_rt->rt_lastnet) <
                       ntohs(from->sat_addr.s_net)) {
                return 0;
            }

            for (l = iface->i_rt->rt_zt; l && (index > 1);
                    l = l->l_next, index--)
                ;

            for (nz = 0; l; l = l->l_next, nz++) {
                zt = (struct ziptab *)l->l_data;

                if (data + 1 + zt->zt_len > end) {
                    break;
                }

                *data++ = zt->zt_len;
                memcpy(data, zt->zt_name, zt->zt_len);
                data += zt->zt_len;
            }

            *lastflag = (l == NULL);
            break;

        default :
            LOG(log_info, logtype_atalkd, "zip atp bad option");
            return 1;
        }

        /* send reply */
        if (nz > 0) {
            nz = htons(nz);
            memcpy(nzones, &nz, sizeof(unsigned short));

            if (sendto(ap->ap_fd, packet, data - packet, 0,
                       (struct sockaddr *)from,
                       sizeof(struct sockaddr_at)) < 0) {
                LOG(log_error, logtype_atalkd, "zip atp sendto %u.%u: %s",
                    ntohs(from->sat_addr.s_net), from->sat_addr.s_node,
                    strerror(errno));
                return 1;
            }
        }

        break;

    default :
        LOG(log_info, logtype_atalkd, "zip_packet bad ddp type from %u.%u",
            ntohs(from->sat_addr.s_net), from->sat_addr.s_node);
        return 1;
    }

    return 0;
}

int zip_getnetinfo(struct interface *iface)
{
    struct atport	*ap;
    struct ziphdr	zh;
    struct sockaddr_at	sat;
    char		*data, packet[40];
    unsigned short		net;
    LOG(log_info, logtype_atalkd, "zip_getnetinfo for %s", iface->i_name);

    for (ap = iface->i_ports; ap; ap = ap->ap_next) {
        if (ap->ap_packet == zip_packet) {
            break;
        }
    }

    if (ap == NULL) {
        LOG(log_error, logtype_atalkd, "zip_getnetinfo can't find zip socket!");
        return -1;
    }

    data = packet;
    *data++ = DDPTYPE_ZIP;
    zh.zh_op = ZIPOP_GNI;
    zh.zh_zero = 0;
    memcpy(data, &zh, sizeof(struct ziphdr));
    data += sizeof(struct ziphdr);
    net = 0;
    memcpy(data, &net, sizeof(unsigned short));
    data += sizeof(unsigned short);
    memcpy(data, &net, sizeof(unsigned short));
    data += sizeof(unsigned short);
    /*
     * Set our requesting zone to NULL, so the response will contain
     * the default zone.
     */
    *data++ = 0;
    memset(&sat, 0, sizeof(struct sockaddr_at));
#ifdef BSD4_4
    sat.sat_len = sizeof(struct sockaddr_at);
#endif /* BSD4_4 */
    sat.sat_family = AF_APPLETALK;
    sat.sat_addr.s_net = 0;
    sat.sat_addr.s_node = ATADDR_BCAST;
    sat.sat_port = ap->ap_port;

    if (sendto(ap->ap_fd, packet, data - packet, 0, (struct sockaddr *)&sat,
               sizeof(struct sockaddr_at)) < 0) {
        LOG(log_error, logtype_atalkd, "zip_getnetinfo sendto: %s", strerror(errno));
        return -1;
    }

    return 0;
}

struct ziptab *newzt(const int len, const char *name)
{
    struct ziptab	*zt;

    if ((zt = (struct ziptab *)calloc(1, sizeof(struct ziptab))) == NULL) {
        return NULL;
    }

    zt->zt_len = len;

    if ((zt->zt_name = (char *)malloc(len)) == NULL) {
        free(zt);
        return NULL;
    }

    memcpy(zt->zt_name, name, len);
    return zt;
}


/*
 * Insert at the end.  Return 1 if a mapping already exists, 0 otherwise.
 * -1 on error.
 */
static int add_list(struct list **head, void *data)
{
    struct list	*l, *l2;

    for (l = *head; l; l = l->l_next) {
        if (l->l_data == data) {
            return 1;
        }
    }

    if ((l = (struct list *)malloc(sizeof(struct list))) == NULL) {
        LOG(log_error, logtype_atalkd, "add_list malloc: %s", strerror(errno));
        return -1;
    }

    l->l_data = data;
    l->l_next = NULL;

    if (*head == NULL) {
        l->l_prev = NULL;
        *head = l;
    } else {
        /* find end of list */
        for (l2 = *head; l2->l_next; l2 = l2->l_next)
            ;

        l->l_prev = l2;
        l2->l_next = l;
    }

    return 0;
}

int addzone(struct rtmptab *rt, int len, char *zone)
{
    struct ziptab	*zt;
    int			cc, exists = 0;

    for (zt = ziptab; zt; zt = zt->zt_next) {
        if (zt->zt_len == len &&
                strndiacasecmp(zt->zt_name, zone, len) == 0) {
            break;
        }
    }

    if (zt == NULL) {
        if ((zt = newzt(len, zone)) == NULL) {
            LOG(log_error, logtype_atalkd, "addzone newzt: %s", strerror(errno));
            return -1;
        }

        if (ziptab == NULL) {
            zt->zt_prev = NULL;
            ziptab = zt;
        } else {
            zt->zt_prev = ziplast;
            ziplast->zt_next = zt;
        }

        ziplast = zt;
    }

    if ((cc = add_list(&zt->zt_rt, rt)) < 0) {
        return -1;
    }

    if (cc) {
        exists++;
    }

    if ((cc = add_list(&rt->rt_zt, zt)) < 0) {
        return -1;
    }

    if (cc) {
        if (!exists) {
            LOG(log_error, logtype_atalkd, "addzone corrupted route/zone mapping");
            return -1;
        }

        /*
         * We get the repeat for local nets which have zone information
         * already: we ask anyway, just to make sure.
         */
        return 0;
    }

    if (exists) {
        LOG(log_error, logtype_atalkd, "addzone corrupted zone/route mapping");
        return -1;
    }

    return 0;
}
