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

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

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/user.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/mbuf.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <net/if.h>
#include <net/af.h>
#include <net/route.h>
#include <netinet/in.h>
#undef s_net
#include <netinet/if_ether.h>

#include "at.h"
#include "at_var.h"
#include "aarp.h"
#include "phase2.h"

#ifdef BSD4_4
# define sateqaddr(a,b)	((a)->sat_len == (b)->sat_len && \
		    (a)->sat_family == (b)->sat_family && \
		    (a)->sat_addr.s_net == (b)->sat_addr.s_net && \
		    (a)->sat_addr.s_node == (b)->sat_addr.s_node )
#else /* BSD4_4 */
void atalk_hash(struct sockaddr_at *sat, struct afhash *hp)
{
    hp->afh_nethash = sat->sat_addr.s_net;
    hp->afh_hosthash = (sat->sat_addr.s_net << 8) +
                       sat->sat_addr.s_node;
}

/*
 * Note the magic to get ifa_ifwithnet() to work without adding an
 * ifaddr entry for each net in our local range.
 */
int atalk_netmatch(struct sockaddr_at *sat1, struct sockaddr_at *sat2)
{
    struct at_ifaddr	*aa;

    for (aa = at_ifaddr; aa; aa = aa->aa_next) {
        if (AA_SAT(aa) == sat1) {
            break;
        }
    }

    if (aa) {
        return ntohs(aa->aa_firstnet) <= ntohs(sat2->sat_addr.s_net) &&
               ntohs(aa->aa_lastnet) >= ntohs(sat2->sat_addr.s_net);
    }

    return sat1->sat_addr.s_net == sat2->sat_addr.s_net;
}
#endif /* BSD4_4 */

at_control(cmd, data, ifp)
int			cmd;
caddr_t		data;
struct ifnet	*ifp;
{
    struct ifreq	*ifr = (struct ifreq *)data;
    struct sockaddr_at	*sat;
    struct netrange	*nr;
#ifdef BSD4_4
    struct at_aliasreq	*ifra = (struct at_aliasreq *)data;
    struct at_ifaddr	*aa0;
#endif /* BSD4_4 */
    struct at_ifaddr	*aa = 0;
    struct mbuf		*m;
    struct ifaddr	*ifa;

    if (ifp) {
        for (aa = at_ifaddr; aa; aa = aa->aa_next) {
            if (aa->aa_ifp == ifp) {
                break;
            }
        }
    }

    switch (cmd) {
#ifdef BSD4_4

    case SIOCAIFADDR:
    case SIOCDIFADDR:
        if (ifra->ifra_addr.sat_family == AF_APPLETALK) {
            for (; aa; aa = aa->aa_next) {
                if (aa->aa_ifp == ifp &&
                        sateqaddr(&aa->aa_addr, &ifra->ifra_addr)) {
                    break;
                }
            }
        }

        if (cmd == SIOCDIFADDR && aa == 0) {
            return EADDRNOTAVAIL;
        }

        /*FALLTHROUGH*/
#endif /* BSD4_4 */

    case SIOCSIFADDR:
#ifdef BSD4_4

        /*
         * What a great idea this is: Let's reverse the meaning of
         * the return...
         */
        if (suser(u.u_cred, &u.u_acflag)) {
            return EPERM;
        }

#else /* BSD4_4 */

        if (!suser()) {
            return EPERM;
        }

#endif /* BSD4_4 */
        sat = satosat(&ifr->ifr_addr);
        nr = (struct netrange *)sat->sat_zero;

        if (nr->nr_phase == 1) {
            for (; aa; aa = aa->aa_next) {
                if (aa->aa_ifp == ifp &&
                        (aa->aa_flags & AFA_PHASE2) == 0) {
                    break;
                }
            }
        } else {
            /* default to phase 2 */
            for (; aa; aa = aa->aa_next) {
                if (aa->aa_ifp == ifp && (aa->aa_flags & AFA_PHASE2)) {
                    break;
                }
            }
        }

        if (ifp == 0) {
            panic("at_control");
        }

        if (aa == (struct at_ifaddr *) 0) {
            m = m_getclr(M_WAIT, MT_IFADDR);

            if (m == (struct mbuf *)NULL) {
                return ENOBUFS;
            }

            if ((aa = at_ifaddr) != NULL) {
                /*
                 * Don't let the loopback be first, since the first
                 * address is the machine's default address for
                 * binding.
                 */
                if (at_ifaddr->aa_ifp->if_flags & IFF_LOOPBACK) {
                    aa = mtod(m, struct at_ifaddr *);
                    aa->aa_next = at_ifaddr;
                    at_ifaddr = aa;
                } else {
                    for (; aa->aa_next; aa = aa->aa_next)
                        ;

                    aa->aa_next = mtod(m, struct at_ifaddr *);
                }
            } else {
                at_ifaddr = mtod(m, struct at_ifaddr *);
            }

            aa = mtod(m, struct at_ifaddr *);

            if ((ifa = ifp->if_addrlist) != NULL) {
                for (; ifa->ifa_next; ifa = ifa->ifa_next)
                    ;

                ifa->ifa_next = (struct ifaddr *)aa;
            } else {
                ifp->if_addrlist = (struct ifaddr *)aa;
            }

#ifdef BSD4_4
            aa->aa_ifa.ifa_addr = (struct sockaddr *)&aa->aa_addr;
            aa->aa_ifa.ifa_dstaddr = (struct sockaddr *)&aa->aa_addr;
            aa->aa_ifa.ifa_netmask = (struct sockaddr *)&aa->aa_netmask;
#endif /* BSD4_4 */

            /*
             * Set/clear the phase 2 bit.
             */
            if (nr->nr_phase == 1) {
                aa->aa_flags &= ~AFA_PHASE2;
            } else {
                aa->aa_flags |= AFA_PHASE2;
            }

            aa->aa_ifp = ifp;
        } else {
            at_scrub(ifp, aa);
        }

        break;

    case SIOCGIFADDR :
        sat = satosat(&ifr->ifr_addr);
        nr = (struct netrange *)sat->sat_zero;

        if (nr->nr_phase == 1) {
            for (; aa; aa = aa->aa_next) {
                if (aa->aa_ifp == ifp &&
                        (aa->aa_flags & AFA_PHASE2) == 0) {
                    break;
                }
            }
        } else {
            /* default to phase 2 */
            for (; aa; aa = aa->aa_next) {
                if (aa->aa_ifp == ifp && (aa->aa_flags & AFA_PHASE2)) {
                    break;
                }
            }
        }

        if (aa == (struct at_ifaddr *) 0) {
            return EADDRNOTAVAIL;
        }

        break;
    }

    switch (cmd) {
    case SIOCGIFADDR:
#ifdef BSD4_4
        *(struct sockaddr_at *)&ifr->ifr_addr = aa->aa_addr;
#else /* BSD4_4 */
        ifr->ifr_addr = aa->aa_addr;
#endif /* BSD4_4 */
        break;

    case SIOCSIFADDR:
        return at_ifinit(ifp, aa, (struct sockaddr_at *)&ifr->ifr_addr);
#ifdef BSD4_4

    case SIOCAIFADDR:
        if (sateqaddr(&ifra->ifra_addr, &aa->aa_addr)) {
            return 0;
        }

        return at_ifinit(ifp, aa, (struct sockaddr_at *)&ifr->ifr_addr);

    case SIOCDIFADDR:
        at_scrub(ifp, aa);

        if ((ifa = ifp->if_addrlist) == (struct ifaddr *)aa) {
            ifp->if_addrlist = ifa->ifa_next;
        } else {
            while (ifa->ifa_next && (ifa->ifa_next != (struct ifaddr *)aa)) {
                ifa = ifa->ifa_next;
            }

            if (ifa->ifa_next) {
                ifa->ifa_next = ((struct ifaddr *)aa)->ifa_next;
            } else {
                panic("at_control");
            }
        }

        aa0 = aa;

        if (aa0 == (aa = at_ifaddr)) {
            at_ifaddr = aa->aa_next;
        } else {
            while (aa->aa_next && (aa->aa_next != aa0)) {
                aa = aa->aa_next;
            }

            if (aa->aa_next) {
                aa->aa_next = aa0->aa_next;
            } else {
                panic("at_control");
            }
        }

        m_free(dtom(aa0));
        break;
#endif /* BSD4_4 */

    default:
        if (ifp == 0 || ifp->if_ioctl == 0) {
            return EOPNOTSUPP;
        }

        return (*ifp->if_ioctl)(ifp, cmd, data);
    }

    return 0;
}

at_scrub(ifp, aa)
struct ifnet	*ifp;
struct at_ifaddr	*aa;
{
#ifndef BSD4_4
    struct sockaddr_at	netsat;
    int			error;
    unsigned short		net;
#endif /* ! BSD4_4 */

    if (aa->aa_flags & AFA_ROUTE) {
#ifdef BSD4_4

        if ((error = rtinit(&(aa->aa_ifa), RTM_DELETE,
                            (ifp->if_flags & IFF_LOOPBACK) ? RTF_HOST : 0)) != 0) {
            return error;
        }

        aa->aa_ifa.ifa_flags &= ~IFA_ROUTE;
#else /* BSD4_4 */

        if (ifp->if_flags & IFF_LOOPBACK) {
            rtinit(&aa->aa_addr, &aa->aa_addr, SIOCDELRT, RTF_HOST);
        } else {
            bzero(&netsat, sizeof(struct sockaddr_at));
            netsat.sat_family = AF_APPLETALK;
            netsat.sat_addr.s_node = ATADDR_ANYNODE;

            /*
             * If the range is the full 0-fffe range, just use
             * the default route.
             */
            if (aa->aa_firstnet == htons(0x0000) &&
                    aa->aa_lastnet == htons(0xfffe)) {
                netsat.sat_addr.s_net = 0;
                rtinit((struct sockaddr *)&netsat, &aa->aa_addr,
                       (int)SIOCDELRT, 0);
            } else {
                for (net = ntohs(aa->aa_firstnet);
                        net <= ntohs(aa->aa_lastnet); net++) {
                    netsat.sat_addr.s_net = htons(net);
                    rtinit((struct sockaddr *)&netsat, &aa->aa_addr,
                           (int)SIOCDELRT, 0);
                }
            }
        }

#endif /* BSD4_4 */
        aa->aa_flags &= ~AFA_ROUTE;
    }

    return 0;
}

extern struct timeval	time;

at_ifinit(ifp, aa, sat)
struct ifnet	*ifp;
struct at_ifaddr	*aa;
struct sockaddr_at	*sat;
{
    struct netrange	nr, onr;
#ifdef BSD4_4
    struct sockaddr_at	oldaddr;
#else /* BSD4_4 */
    struct sockaddr	oldaddr;
#endif /* BSD4_4 */
    struct sockaddr_at	netaddr;
    int			s = splimp(), error = 0, i, j, netinc, nodeinc, nnets;
    unsigned short		net;
    oldaddr = aa->aa_addr;
    bzero(AA_SAT(aa), sizeof(struct sockaddr_at));
    bcopy(sat->sat_zero, &nr, sizeof(struct netrange));
    nnets = ntohs(nr.nr_lastnet) - ntohs(nr.nr_firstnet) + 1;
    onr.nr_firstnet = aa->aa_firstnet;
    onr.nr_lastnet = aa->aa_lastnet;
    aa->aa_firstnet = nr.nr_firstnet;
    aa->aa_lastnet = nr.nr_lastnet;

    /*
     * We could eliminate the need for a second phase 1 probe (post
     * autoconf) if we check whether we're resetting the node. Note
     * that phase 1 probes use only nodes, not net.node pairs.  Under
     * phase 2, both the net and node must be the same.
     */
    if (ifp->if_flags & IFF_LOOPBACK) {
#ifdef BSD4_4
        AA_SAT(aa)->sat_len = sat->sat_len;
#endif /* BSD4_4 */
        AA_SAT(aa)->sat_family = AF_APPLETALK;
        AA_SAT(aa)->sat_addr.s_net = sat->sat_addr.s_net;
        AA_SAT(aa)->sat_addr.s_node = sat->sat_addr.s_node;
    } else {
        aa->aa_flags |= AFA_PROBING;
        AA_SAT(aa)->sat_family = AF_APPLETALK;

        if (aa->aa_flags & AFA_PHASE2) {
            if (sat->sat_addr.s_net == ATADDR_ANYNET) {
                if (nnets != 1) {
                    net = ntohs(nr.nr_firstnet) + time.tv_sec % (nnets - 1);
                } else {
                    net = ntohs(nr.nr_firstnet);
                }
            } else {
                if (ntohs(sat->sat_addr.s_net) < ntohs(nr.nr_firstnet) ||
                        ntohs(sat->sat_addr.s_net) > ntohs(nr.nr_lastnet)) {
                    aa->aa_addr = oldaddr;
                    aa->aa_firstnet = onr.nr_firstnet;
                    aa->aa_lastnet = onr.nr_lastnet;
                    return EINVAL;
                }

                net = ntohs(sat->sat_addr.s_net);
            }
        } else {
            net = ntohs(sat->sat_addr.s_net);
        }

        if (sat->sat_addr.s_node == ATADDR_ANYNODE) {
            AA_SAT(aa)->sat_addr.s_node = time.tv_sec;
        } else {
            AA_SAT(aa)->sat_addr.s_node = sat->sat_addr.s_node;
        }

        for (i = nnets, netinc = 1; i > 0; net = ntohs(nr.nr_firstnet) +
                ((net - ntohs(nr.nr_firstnet) + netinc) % nnets), i--) {
            AA_SAT(aa)->sat_addr.s_net = htons(net);

            for (j = 0, nodeinc = time.tv_sec | 1; j < 256;
                    j++, AA_SAT(aa)->sat_addr.s_node += nodeinc) {
                if (AA_SAT(aa)->sat_addr.s_node > 253 ||
                        AA_SAT(aa)->sat_addr.s_node < 1) {
                    continue;
                }

                aa->aa_probcnt = 10;
                timeout(aarpprobe, (caddr_t)ifp, hz / 5);
                splx(s);

                if (sleep(aa, PSLEP | PCATCH)) {
                    printf("at_ifinit why did this happen?!\n");
                    aa->aa_addr = oldaddr;
                    aa->aa_firstnet = onr.nr_firstnet;
                    aa->aa_lastnet = onr.nr_lastnet;
                    return EINTR;
                }

                s = splimp();

                if ((aa->aa_flags & AFA_PROBING) == 0) {
                    break;
                }
            }

            if ((aa->aa_flags & AFA_PROBING) == 0) {
                break;
            }

            /* reset node for next network */
            AA_SAT(aa)->sat_addr.s_node = time.tv_sec;
        }

        if (aa->aa_flags & AFA_PROBING) {
            aa->aa_addr = oldaddr;
            aa->aa_firstnet = onr.nr_firstnet;
            aa->aa_lastnet = onr.nr_lastnet;
            splx(s);
            return EADDRINUSE;
        }
    }

    if (ifp->if_ioctl &&
            (error = (*ifp->if_ioctl)(ifp, SIOCSIFADDR, aa))) {
        splx(s);
        aa->aa_addr = oldaddr;
        aa->aa_firstnet = onr.nr_firstnet;
        aa->aa_lastnet = onr.nr_lastnet;
        return error;
    }

#ifdef BSD4_4
    aa->aa_netmask.sat_len = 6;
    aa->aa_netmask.sat_family = AF_APPLETALK;
    aa->aa_netmask.sat_addr.s_net = 0xffff;
    aa->aa_netmask.sat_addr.s_node = 0;
#endif /* BSD4_4 */

    if (ifp->if_flags & IFF_LOOPBACK) {
#ifndef BSD4_4
        rtinit(&aa->aa_addr, &aa->aa_addr, (int)SIOCADDRT,
               RTF_HOST | RTF_UP);
#else /* ! BSD4_4 */
        error = rtinit(&(aa->aa_ifa), (int)RTM_ADD, RTF_HOST | RTF_UP);
#endif /* ! BSD4_4 */
    } else {
#ifndef BSD4_4
        /*
         * rtrequest looks for point-to-point links first. The
         * broadaddr is in the same spot as the destaddr. So, if
         * ATADDR_ANYNET is 0, and we don't fill in the broadaddr, we
         * get 0.0 routed out the ether interface.  So, initialize the
         * broadaddr, even tho we don't use it.
         *
         * We *could* use the broadaddr field to reduce some of the
         * sockaddr_at overloading that we've done.  E.g. Just send
         * to INTERFACE-NET.255, and have the kernel reroute that
         * to broadaddr, which would be 0.255 for phase 2 interfaces,
         * and IFACE-NET.255 for phase 1 interfaces.
         */
        ((struct sockaddr_at *)&aa->aa_broadaddr)->sat_addr.s_net =
            sat->sat_addr.s_net;
        ((struct sockaddr_at *)&aa->aa_broadaddr)->sat_addr.s_node =
            ATADDR_BCAST;
        bzero(&netaddr, sizeof(struct sockaddr_at));
        netaddr.sat_family = AF_APPLETALK;
        netaddr.sat_addr.s_node = ATADDR_ANYNODE;

        if ((aa->aa_flags & AFA_PHASE2) == 0) {
            netaddr.sat_addr.s_net = AA_SAT(aa)->sat_addr.s_net;
            rtinit((struct sockaddr *)&netaddr, &aa->aa_addr,
                   (int)SIOCADDRT, RTF_UP);
        } else {
            /*
             * If the range is the full 0-fffe range, just use
             * the default route.
             */
            if (aa->aa_firstnet == htons(0x0000) &&
                    aa->aa_lastnet == htons(0xfffe)) {
                netaddr.sat_addr.s_net = 0;
                rtinit((struct sockaddr *)&netaddr, &aa->aa_addr,
                       (int)SIOCADDRT, RTF_UP);
            } else {
                for (net = ntohs(aa->aa_firstnet);
                        net <= ntohs(aa->aa_lastnet); net++) {
                    netaddr.sat_addr.s_net = htons(net);
                    rtinit((struct sockaddr *)&netaddr, &aa->aa_addr,
                           (int)SIOCADDRT, RTF_UP);
                }
            }
        }

#else /* ! BSD4_4 */
        error = rtinit(&(aa->aa_ifa), (int)RTM_ADD, RTF_UP);
#endif /* ! BSD4_4 */
    }

    if (error) {
        aa->aa_addr = oldaddr;
        aa->aa_firstnet = onr.nr_firstnet;
        aa->aa_lastnet = onr.nr_lastnet;
        splx(s);
        return error;
    }

#ifdef BSD4_4
    aa->aa_ifa.ifa_flags |= IFA_ROUTE;
#endif /* BSD4_4 */
    aa->aa_flags |= AFA_ROUTE;
    splx(s);
    return 0;
}

at_broadcast(sat)
struct sockaddr_at	*sat;
{
    struct at_ifaddr	*aa;

    if (sat->sat_addr.s_node != ATADDR_BCAST) {
        return 0;
    }

    if (sat->sat_addr.s_net == 0) {
        return 1;
    } else {
        for (aa = at_ifaddr; aa; aa = aa->aa_next) {
            if ((aa->aa_ifp->if_flags & IFF_BROADCAST) &&
                    (ntohs(sat->sat_addr.s_net) >= ntohs(aa->aa_firstnet) &&
                     ntohs(sat->sat_addr.s_net) <= ntohs(aa->aa_lastnet))) {
                return 1;
            }
        }
    }

    return 0;
}

aa_clean()
{
    struct at_ifaddr	*aa;
    struct ifaddr	*ifa;
    struct ifnet	*ifp;

    while (aa = at_ifaddr) {
        ifp = aa->aa_ifp;
        at_scrub(ifp, aa);
        at_ifaddr = aa->aa_next;

        if ((ifa = ifp->if_addrlist) == (struct ifaddr *)aa) {
            ifp->if_addrlist = ifa->ifa_next;
        } else {
            while (ifa->ifa_next &&
                    (ifa->ifa_next != (struct ifaddr *)aa)) {
                ifa = ifa->ifa_next;
            }

            if (ifa->ifa_next) {
                ifa->ifa_next = ((struct ifaddr *)aa)->ifa_next;
            } else {
                panic("at_entry");
            }
        }
    }
}
