root/usr/src/cmd/cmd-inet/usr.lib/pppoe/pppoec.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * PPPoE Client-mode "chat" utility for use with Solaris PPP 4.0.
 *
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <stropts.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>

#include <net/sppptun.h>
#include <net/pppoe.h>

#include "common.h"
#include "logging.h"

/*
 * This value, currently set to the characters "POE1," is used to
 * distinguish among control messages from multiple lower streams
 * under /dev/sppp.  This feature is needed to support PPP translation
 * (LAC-like behavior), but isn't currently used.
 */
#define PPPOE_DISCRIM   0x504F4531

/* milliseconds between retries */
#define PADI_RESTART_TIME       500
#define PADR_RESTART_TIME       2000

/* default inquiry mode timer in milliseconds. */
#define PADI_INQUIRY_DWELL      3000

/* maximum timer value in milliseconds */
#define RESTART_LIMIT   5000

char *myname;           /* copy of argv[0] for error messages */
static int verbose;     /* -v flag given */
static int onlyflag;    /* keyword "only" at end of command line */
static char *service = "";      /* saved service name from command line */

static int pado_wait_time = 0;  /* see main() */
static int pads_wait_time = PADR_RESTART_TIME;

static int tunfd;       /* open connection to sppptun driver */

static struct timeval tvstart;  /* time of last PADI/PADR transmission */

struct server_filter {
        struct server_filter *sf_next;  /* Next filter in list */
        struct ether_addr sf_mac;       /* Ethernet address */
        struct ether_addr sf_mask;      /* Mask (0 or 0xFF in each byte) */
        const char *sf_name;            /* String for AC-Name compare */
        boolean_t sf_hasmac;            /* Set if string could be MAC */
        boolean_t sf_isexcept;          /* Ignore server if matching */
};

/* List of filters defined on command line. */
static struct server_filter *sfhead, *sftail;

/*
 * PPPoE Client State Machine
 */

/* Client events */
#define PCSME_CLOSE     0       /* User close */
#define PCSME_OPEN      1       /* User open */
#define PCSME_TOP       2       /* Timeout+ (counter non-zero) */
#define PCSME_TOM       3       /* Timeout- (counter zero) */
#define PCSME_RPADT     4       /* Receive PADT (unexpected here) */
#define PCSME_RPADOP    5       /* Receive desired PADO */
#define PCSME_RPADO     6       /* Receive ordinary PADO */
#define PCSME_RPADS     7       /* Receive PADS */
#define PCSME_RPADSN    8       /* Receive bad (errored) PADS */
#define PCSME__MAX      9

/* Client states */
#define PCSMS_DEAD      0       /* Initial state */
#define PCSMS_INITSENT  1       /* PADI sent */
#define PCSMS_OFFRRCVD  2       /* PADO received */
#define PCSMS_REQSENT   3       /* PADR sent */
#define PCSMS_CONVERS   4       /* Conversational */
#define PCSMS__MAX      5

/* Client actions */
#define PCSMA_NONE      0       /* Do nothing */
#define PCSMA_FAIL      1       /* Unrecoverable error */
#define PCSMA_SPADI     2       /* Send PADI */
#define PCSMA_ADD       3       /* Add ordinary server to list */
#define PCSMA_SPADR     4       /* Send PADR to top server */
#define PCSMA_SPADRP    5       /* Send PADR to this server (make top) */
#define PCSMA_SPADRN    6       /* Send PADR to next (or terminate) */
#define PCSMA_OPEN      7       /* Start PPP */
#define PCSMA__MAX      8

static uint8_t client_next_state[PCSMS__MAX][PCSME__MAX] = {
/* 0 PCSMS_DEAD Initial state */
        {
                PCSMS_DEAD,     /* PCSME_CLOSE  User close */
                PCSMS_INITSENT, /* PCSME_OPEN   User open */
                PCSMS_DEAD,     /* PCSME_TOP    Timeout+ */
                PCSMS_DEAD,     /* PCSME_TOM    Timeout- */
                PCSMS_DEAD,     /* PCSME_RPADT  Receive PADT */
                PCSMS_DEAD,     /* PCSME_RPADOP Receive desired PADO */
                PCSMS_DEAD,     /* PCSME_RPADO  Receive ordinary PADO */
                PCSMS_DEAD,     /* PCSME_RPADS  Receive PADS */
                PCSMS_DEAD,     /* PCSME_RPADSN Receive bad PADS */
        },
/* 1 PCSMS_INITSENT PADI sent */
        {
                PCSMS_DEAD,     /* PCSME_CLOSE  User close */
                PCSMS_INITSENT, /* PCSME_OPEN   User open */
                PCSMS_INITSENT, /* PCSME_TOP    Timeout+ */
                PCSMS_DEAD,     /* PCSME_TOM    Timeout- */
                PCSMS_DEAD,     /* PCSME_RPADT  Receive PADT */
                PCSMS_REQSENT,  /* PCSME_RPADOP Receive desired PADO */
                PCSMS_OFFRRCVD, /* PCSME_RPADO  Receive ordinary PADO */
                PCSMS_INITSENT, /* PCSME_RPADS  Receive PADS */
                PCSMS_INITSENT, /* PCSME_RPADSN Receive bad PADS */
        },
/* 2 PCSMS_OFFRRCVD PADO received */
        {
                PCSMS_DEAD,     /* PCSME_CLOSE  User close */
                PCSMS_INITSENT, /* PCSME_OPEN   User open */
                PCSMS_REQSENT,  /* PCSME_TOP    Timeout+ */
                PCSMS_REQSENT,  /* PCSME_TOM    Timeout- */
                PCSMS_DEAD,     /* PCSME_RPADT  Receive PADT */
                PCSMS_REQSENT,  /* PCSME_RPADOP Receive desired PADO */
                PCSMS_OFFRRCVD, /* PCSME_RPADO  Receive ordinary PADO */
                PCSMS_OFFRRCVD, /* PCSME_RPADS  Receive PADS */
                PCSMS_OFFRRCVD, /* PCSME_RPADSN Receive bad PADS */
        },
/* 3 PCSMS_REQSENT  PADR sent */
        {
                PCSMS_DEAD,     /* PCSME_CLOSE  User close */
                PCSMS_INITSENT, /* PCSME_OPEN   User open */
                PCSMS_REQSENT,  /* PCSME_TOP    Timeout+ */
                PCSMS_REQSENT,  /* PCSME_TOM    Timeout- */
                PCSMS_DEAD,     /* PCSME_RPADT  Receive PADT */
                PCSMS_REQSENT,  /* PCSME_RPADOP Receive desired PADO */
                PCSMS_REQSENT,  /* PCSME_RPADO  Receive ordinary PADO */
                PCSMS_CONVERS,  /* PCSME_RPADS  Receive PADS */
                PCSMS_REQSENT,  /* PCSME_RPADSN Receive bad PADS */
        },
/* 4 PCSMS_CONVERS  Conversational */
        {
                PCSMS_DEAD,     /* PCSME_CLOSE  User close */
                PCSMS_INITSENT, /* PCSME_OPEN   User open */
                PCSMS_CONVERS,  /* PCSME_TOP    Timeout+ */
                PCSMS_CONVERS,  /* PCSME_TOM    Timeout- */
                PCSMS_DEAD,     /* PCSME_RPADT  Receive PADT */
                PCSMS_CONVERS,  /* PCSME_RPADOP Receive desired PADO */
                PCSMS_CONVERS,  /* PCSME_RPADO  Receive ordinary PADO */
                PCSMS_CONVERS,  /* PCSME_RPADS  Receive PADS */
                PCSMS_CONVERS,  /* PCSME_RPADSN Receive bad PADS */
        },
};

static uint8_t client_action[PCSMS__MAX][PCSME__MAX] = {
/* 0 PCSMS_DEAD Initial state */
        {
                PCSMA_NONE,     /* PCSME_CLOSE  User close */
                PCSMA_SPADI,    /* PCSME_OPEN   User open */
                PCSMA_NONE,     /* PCSME_TOP    Timeout+ */
                PCSMA_NONE,     /* PCSME_TOM    Timeout- */
                PCSMA_NONE,     /* PCSME_RPADT  Receive PADT */
                PCSMA_NONE,     /* PCSME_RPADOP Receive desired PADO */
                PCSMA_NONE,     /* PCSME_RPADO  Receive ordinary PADO */
                PCSMA_NONE,     /* PCSME_RPADS  Receive PADS */
                PCSMA_NONE,     /* PCSME_RPADSN Receive bad PADS */
        },
/* 1 PCSMS_INITSENT PADI sent */
        {
                PCSMA_FAIL,     /* PCSME_CLOSE  User close */
                PCSMA_SPADI,    /* PCSME_OPEN   User open */
                PCSMA_SPADI,    /* PCSME_TOP    Timeout+ */
                PCSMA_FAIL,     /* PCSME_TOM    Timeout- */
                PCSMA_FAIL,     /* PCSME_RPADT  Receive PADT */
                PCSMA_SPADRP,   /* PCSME_RPADOP Receive desired PADO */
                PCSMA_ADD,      /* PCSME_RPADO  Receive ordinary PADO */
                PCSMA_NONE,     /* PCSME_RPADS  Receive PADS */
                PCSMA_NONE,     /* PCSME_RPADSN Receive bad PADS */
        },
/* 2 PCSMS_OFFRRCVD PADO received */
        {
                PCSMA_FAIL,     /* PCSME_CLOSE  User close */
                PCSMA_SPADI,    /* PCSME_OPEN   User open */
                PCSMA_SPADR,    /* PCSME_TOP    Timeout+ */
                PCSMA_SPADR,    /* PCSME_TOM    Timeout- */
                PCSMA_FAIL,     /* PCSME_RPADT  Receive PADT */
                PCSMA_SPADRP,   /* PCSME_RPADOP Receive desired PADO */
                PCSMA_ADD,      /* PCSME_RPADO  Receive ordinary PADO */
                PCSMA_NONE,     /* PCSME_RPADS  Receive PADS */
                PCSMA_NONE,     /* PCSME_RPADSN Receive bad PADS */
        },
/* 3 PCSMS_REQSENT  PADR sent */
        {
                PCSMA_FAIL,     /* PCSME_CLOSE  User close */
                PCSMA_SPADI,    /* PCSME_OPEN   User open */
                PCSMA_SPADR,    /* PCSME_TOP    Timeout+ */
                PCSMA_SPADRN,   /* PCSME_TOM    Timeout- */
                PCSMA_FAIL,     /* PCSME_RPADT  Receive PADT */
                PCSMA_ADD,      /* PCSME_RPADOP Receive desired PADO */
                PCSMA_ADD,      /* PCSME_RPADO  Receive ordinary PADO */
                PCSMA_OPEN,     /* PCSME_RPADS  Receive PADS */
                PCSMA_SPADRN,   /* PCSME_RPADSN Receive bad PADS */
        },
/* 4 PCSMS_CONVERS  Conversational */
        {
                PCSMA_FAIL,     /* PCSME_CLOSE  User close */
                PCSMA_SPADI,    /* PCSME_OPEN   User open */
                PCSMA_FAIL,     /* PCSME_TOP    Timeout+ */
                PCSMA_FAIL,     /* PCSME_TOM    Timeout- */
                PCSMA_FAIL,     /* PCSME_RPADT  Receive PADT */
                PCSMA_NONE,     /* PCSME_RPADOP Receive desired PADO */
                PCSMA_NONE,     /* PCSME_RPADO  Receive ordinary PADO */
                PCSMA_NONE,     /* PCSME_RPADS  Receive PADS */
                PCSMA_NONE,     /* PCSME_RPADSN Receive bad PADS */
        },
};

/*
 * PPPoE Message structure -- holds data from a received PPPoE
 * message.  These are copied and saved when queuing offers from
 * possible servers.
 */
typedef struct poesm_s {
        struct poesm_s  *poemsg_next;   /* Next message in list */
        const poep_t    *poemsg_data;   /* Pointer to PPPoE packet */
        int             poemsg_len;     /* Length of packet */
        ppptun_atype    poemsg_sender;  /* Address of sender */
        const char      *poemsg_iname;  /* Name of input interface */
} poemsg_t;

/*
 * PPPoE State Machine structure -- holds state of PPPoE negotiation;
 * currently, there's exactly one of these per pppoec instance.
 */
typedef struct {
        int             poesm_state;            /* PCSMS_* */
        int             poesm_timer;            /* Milliseconds to next TO */
        int             poesm_count;            /* Retry countdown */
        int             poesm_interval;         /* Reload value */
        uint32_t        poesm_sequence;         /* Sequence for PADR */

        poemsg_t        *poesm_firstoff;        /* Queue of valid offers; */
        poemsg_t        *poesm_lastoff;         /* first is best offer */
        poemsg_t        *poesm_tried;           /* Tried and failed offers */

        int             poesm_localid;          /* Local session ID (driver) */
} poesm_t;

/*
 * Convert an internal PPPoE event code number into a printable
 * string.
 */
static const char *
poe_event(int event)
{
        static const char *poeevent[PCSME__MAX] = {
                "Close", "Open", "TO+", "TO-", "rPADT",
                "rPADO+", "rPADO", "rPADS", "rPADS-"
        };

        if (event < 0 || event >= PCSME__MAX) {
                return ("?");
        }
        return (poeevent[event]);
}

/*
 * Convert an internal PPPoE state number into a printable string.
 */
static const char *
poe_state(int state)
{
        static const char *poestate[PCSMS__MAX] = {
                "Dead", "InitSent", "OffrRcvd", "ReqSent", "Convers",
        };

        if (state < 0 || state >= PCSMS__MAX) {
                return ("?");
        }
        return (poestate[state]);
}

/*
 * Convert an internal PPPoE action number into a printable string.
 */
static const char *
poe_action(int act)
{
        static const char *poeaction[PCSMA__MAX] = {
                "None", "Fail", "SendPADI", "Add", "SendPADR",
                "SendPADR+", "SendPADR-", "Open"
        };

        if (act < 0 || act >= PCSMA__MAX) {
                return ("?");
        }
        return (poeaction[act]);
}

/*
 * This calls mygetmsg (which discards partial messages as needed) and
 * logs errors as appropriate.
 */
static int
pppoec_getmsg(int fd, struct strbuf *ctrl, struct strbuf *data, int *flags)
{
        int retv;

        for (;;) {
                retv = mygetmsg(fd, ctrl, data, flags);
                if (retv == 0)
                        break;
                if (retv < 0) {
                        if (errno == EINTR)
                                continue;
                        logstrerror("getmsg");
                        break;
                }
                if (verbose) {
                        if (!(retv & (MORECTL | MOREDATA)))
                                logerr("%s: discard: "
                                    "unexpected status %d\n", myname, retv);
                        else
                                logerr("%s: discard: "
                                    "truncated %s%smessage\n", myname,
                                    retv & MORECTL ? "control " : "",
                                    retv & MOREDATA ? "data " : "");
                }
        }
        return (retv);
}

/*
 * Connect the control path to the lower stream of interest.  This
 * must be called after opening the tunnel driver in order to
 * establish the interface to be used for signaling.  Returns local
 * session ID number.
 */
static int
set_control(const char *dname)
{
        struct ppptun_peer ptp;
        union ppptun_name ptn;

        /* Fetch the local session ID first. */
        (void) memset(&ptp, '\0', sizeof (ptp));
        ptp.ptp_style = PTS_PPPOE;
        if (strioctl(tunfd, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) <
            0) {
                logstrerror("PPPTUN_SPEER");
                exit(1);
        }

        /* Connect to lower stream. */
        (void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%s:pppoed",
            dname);
        if (strioctl(tunfd, PPPTUN_SCTL, &ptn, sizeof (ptn), 0) < 0) {
                logerr("%s: PPPTUN_SCTL %s: %s\n", myname,
                    ptn.ptn_name, mystrerror(errno));
                exit(1);
        }
        return (ptp.ptp_lsessid);
}

/*
 * Check if standard input is actually a viable connection to the
 * tunnel driver.  This is the normal mode of operation with pppd; the
 * tunnel driver is opened by pppd as the tty and pppoec is exec'd as
 * the connect script.
 */
static void
check_stdin(void)
{
        struct ppptun_info pti;
        union ppptun_name ptn;

        if (strioctl(0, PPPTUN_GDATA, &ptn, 0, sizeof (ptn)) < 0) {
                if (errno == EINVAL)
                        logerr("%s: PPPoE operation requires "
                            "the use of a tunneling device\n", myname);
                else
                        logstrerror("PPPTUN_GDATA");
                exit(1);
        }
        if (ptn.ptn_name[0] != '\0') {
                if (strioctl(0, PPPTUN_GINFO, &pti, 0, sizeof (pti)) < 0) {
                        logstrerror("PPPTUN_GINFO");
                        exit(1);
                }
                if (pti.pti_style != PTS_PPPOE) {
                        logerr("%s: Cannot connect to server "
                            "using PPPoE; stream already set to style %d\n",
                            myname, pti.pti_style);
                        exit(1);
                }
                if (verbose)
                        logerr("%s: Warning:  PPPoE data link "
                            "already connected\n", myname);
                exit(0);
        }
        /* Standard input is the tunnel driver; use it. */
        tunfd = 0;
}

/*
 * Write a summary of a PPPoE message to the given file.  This is used
 * for logging and to display received offers in the inquiry (-i) mode.
 */
static void
display_pppoe(FILE *out, const poep_t *poep, int plen, const ppptun_atype *pap)
{
        int ttyp;
        int tlen;
        const uint8_t *tagp;
        const uint8_t *dp;
        const char *str;
        poer_t poer;
        uint32_t mask;

        if (out == stderr)
                logerr(" ");    /* Give us a timestamp */
        /* Print name of sender. */
        (void) fprintf(out, "%-16s ", ehost(pap));

        /* Loop through tags and print each. */
        tagp = (const uint8_t *)(poep + 1);
        while (poe_tagcheck(poep, plen, tagp)) {
                ttyp = POET_GET_TYPE(tagp);
                if (ttyp == POETT_END)
                        break;
                tlen = POET_GET_LENG(tagp);
                dp = POET_DATA(tagp);
                str = NULL;
                switch (ttyp) {
                case POETT_SERVICE:     /* Service-Name */
                        str = "Svc";
                        break;
                case POETT_ACCESS:      /* AC-Name */
                        str = "Name";
                        break;
                case POETT_UNIQ:        /* Host-Uniq */
                        str = "Uniq";
                        break;
                case POETT_COOKIE:      /* AC-Cookie */
                        str = "Cookie";
                        break;
                case POETT_VENDOR:      /* Vendor-Specific */
                        break;
                case POETT_RELAY:       /* Relay-Session-Id */
                        str = "Relay";
                        break;
                case POETT_NAMERR:      /* Service-Name-Error */
                        str = "SvcNameErr";
                        break;
                case POETT_SYSERR:      /* AC-System-Error */
                        str = "SysErr";
                        break;
                case POETT_GENERR:      /* Generic-Error */
                        str = "GenErr";
                        break;
                case POETT_MULTI:       /* Multicast-Capable */
                        break;
                case POETT_HURL:        /* Host-URL */
                        str = "URL";
                        break;
                case POETT_MOTM:        /* Message-Of-The-Minute */
                        str = "Mesg";
                        break;
                case POETT_RTEADD:      /* IP-Route-Add */
                        break;
                }
                switch (ttyp) {
                case POETT_NAMERR:      /* Service-Name-Error */
                case POETT_SYSERR:      /* AC-System-Error */
                        if (tlen > 0 && *dp == '\0')
                                tlen = 0;
                        /* FALLTHROUGH */
                case POETT_SERVICE:     /* Service-Name */
                case POETT_ACCESS:      /* AC-Name */
                case POETT_GENERR:      /* Generic-Error */
                case POETT_MOTM:        /* Message-Of-The-Minute */
                case POETT_HURL:        /* Host-URL */
                        (void) fprintf(out, "%s:\"%.*s\" ", str, tlen, dp);
                        break;
                case POETT_UNIQ:        /* Host-Uniq */
                case POETT_COOKIE:      /* AC-Cookie */
                case POETT_RELAY:       /* Relay-Session-Id */
                        (void) fprintf(out, "%s:", str);
                        while (--tlen >= 0)
                                (void) fprintf(out, "%02X", *dp++);
                        (void) putc(' ', out);
                        break;
                case POETT_VENDOR:      /* Vendor-Specific */
                        (void) fputs("Vendor:", out);
                        if (tlen >= 4) {
                                if (*dp++ != 0) {
                                        (void) fprintf(out, "(%02X?)", dp[-1]);
                                }
                                (void) fprintf(out, "%x-%x-%x:", dp[0], dp[1],
                                    dp[2]);
                                tlen -= 4;
                                dp += 3;
                        }
                        while (--tlen >= 0)
                                (void) fprintf(out, "%02X", *dp++);
                        (void) putc(' ', out);
                        break;
                case POETT_MULTI:       /* Multicast-Capable */
                        (void) fprintf(out, "Multi:%d ", *dp);
                        break;
                case POETT_RTEADD:      /* IP-Route-Add */
                        if (tlen != sizeof (poer)) {
                                (void) fprintf(out, "RTE%d? ", tlen);
                                break;
                        }
                        (void) memcpy(&poer, dp, sizeof (poer));
                        (void) fputs("RTE:", out);
                        if (poer.poer_dest_network == 0)
                                (void) fputs("default", out);
                        else
                                (void) fputs(ihost(poer.poer_dest_network),
                                    out);
                        mask = ntohl(poer.poer_subnet_mask);
                        if (mask != 0 && mask != (uint32_t)~0) {
                                if ((~mask & (~mask + 1)) == 0)
                                        (void) fprintf(out, "/%d",
                                            sizeof (struct in_addr) * NBBY +
                                            1 - ffs(mask));
                                else
                                        (void) fprintf(out, "/%s",
                                            ihost(poer.poer_subnet_mask));
                        }
                        (void) fprintf(out, ",%s,%u ",
                            ihost(poer.poer_gateway), ntohl(poer.poer_metric));
                        break;
                default:
                        (void) fprintf(out, "%s:%d ", poe_tagname(ttyp), tlen);
                        break;
                }
                tagp = POET_NEXT(tagp);
        }
        (void) putc('\n', out);
}

/*
 * Transmit a PPPoE message to the indicated destination.  Used for
 * PADI and PADR messages.
 */
static int
send_pppoe(const poep_t *poep, const char *msgname,
    const ppptun_atype *destaddr)
{
        struct strbuf ctrl;
        struct strbuf data;
        struct ppptun_control *ptc;

        /* Set up the control data expected by the driver. */
        ptc = (struct ppptun_control *)pkt_octl;
        (void) memset(ptc, '\0', sizeof (*ptc));
        ptc->ptc_discrim = PPPOE_DISCRIM;
        ptc->ptc_action = PTCA_CONTROL;
        ptc->ptc_address = *destaddr;
        ctrl.len = sizeof (*ptc);
        ctrl.buf = (caddr_t)ptc;
        data.len = poe_length(poep) + sizeof (*poep);
        data.buf = (caddr_t)poep;
        if (verbose)
                logerr("%s: Sending %s to %s: %d bytes\n",
                    myname, msgname, ehost(destaddr), data.len);
        if (putmsg(tunfd, &ctrl, &data, 0) < 0) {
                logstrerror("putmsg");
                return (-1);
        }
        return (0);
}

/*
 * Create and transmit a PPPoE Active Discovery Initiation packet.
 * This is broadcasted to all hosts on the LAN.
 */
static int
send_padi(int localid)
{
        poep_t *poep;
        ppptun_atype destaddr;

        poep = poe_mkheader(pkt_output, POECODE_PADI, 0);
        (void) poe_add_str(poep, POETT_SERVICE, service);
        (void) poe_add_long(poep, POETT_UNIQ, localid);
        (void) memset(&destaddr, '\0', sizeof (destaddr));
        (void) memcpy(destaddr.pta_pppoe.ptma_mac, ether_bcast,
            sizeof (destaddr.pta_pppoe.ptma_mac));
        return (send_pppoe(poep, "PADI", &destaddr));
}

/*
 * This is used by the procedure below -- when the alarm goes off,
 * just exit.  (This was once a dummy procedure and used the EINTR
 * side-effect to terminate the loop, but that's not reliable, since
 * the EINTR could be caught and ignored by the calls to standard
 * output.)
 */
/* ARGSUSED */
static void
alarm_hand(int dummy)
{
        exit(0);
}

/*
 * Send out a single PADI and listen for servers.  This implements the
 * "inquiry" (-i) mode.
 */
static void
find_all_servers(int localid)
{
        struct strbuf ctrl;
        struct strbuf data;
        poep_t *poep;
        int flags;
        struct sigaction act;
        struct ppptun_control *ptc;

        /* Set a default 3-second timer */
        (void) memset(&act, '\0', sizeof (act));
        act.sa_handler = alarm_hand;
        (void) sigaction(SIGALRM, &act, NULL);
        (void) alarm((pado_wait_time + 999) / 1000);

        /* Broadcast a single request. */
        if (send_padi(localid) != 0)
                return;

        /* Loop over responses and print them. */
        for (;;) {
                ctrl.maxlen = PKT_OCTL_LEN;
                ctrl.buf = (caddr_t)pkt_octl;
                data.maxlen = PKT_INPUT_LEN;
                data.buf = (caddr_t)pkt_input;
                flags = 0;

                if (pppoec_getmsg(tunfd, &ctrl, &data, &flags) < 0)
                        break;

                /* Ignore unwanted responses from the driver. */
                if (ctrl.len != sizeof (*ptc)) {
                        if (verbose)
                                logerr("%s: unexpected %d byte"
                                    " control message from driver.\n", myname,
                                    ctrl.len);
                        continue;
                }
                ptc = (struct ppptun_control *)pkt_octl;
                poep = (poep_t *)pkt_input;

                /* If it's an offer, then print it out. */
                if (poe_code(poep) == POECODE_PADO) {
                        display_pppoe(stdout, poep, data.len,
                            &ptc->ptc_address);
                }
        }
}

/*
 * Parse a server filter from the command line.  The passed-in string
 * must be allocated and unchanged, since a pointer to it is saved in
 * the filter data structure.  The string is also parsed for a MAC
 * address, if possible.
 */
static void
parse_filter(const char *str, int exceptflag)
{
        struct server_filter *sfnew;
        const char *cp;
        const char *wordstart;
        const char *wordend;
        int len;
        char hbuf[MAXHOSTNAMELEN];
        uchar_t *ucp;
        uchar_t *mcp;

        /* Allocate the new filter structure. */
        sfnew = (struct server_filter *)calloc(1, sizeof (*sfnew));
        if (sfnew == NULL) {
                logstrerror("filter allocation");
                exit(1);
        }

        /* Save the string for AC-Name comparison. */
        sfnew->sf_name = str;

        sfnew->sf_isexcept = exceptflag == 0 ? 0 : 1;

        /* Extract just one word. */
        cp = str;
        while (isspace(*cp))
                cp++;
        wordstart = cp;
        while (*cp != '\0' && !isspace(*cp))
                cp++;
        wordend = cp;
        if ((len = wordend - wordstart) >= sizeof (hbuf))
                len = sizeof (hbuf) - 1;
        (void) strlcpy(hbuf, wordstart, len);
        hbuf[len] = '\0';

        /* Try to translate this as an Ethernet host or address. */
        mcp = sfnew->sf_mask.ether_addr_octet;
        if (ether_hostton(hbuf, &sfnew->sf_mac) == 0) {
                mcp[0] = mcp[1] = mcp[2] = mcp[3] = mcp[4] = mcp[5] = 0xFF;
                sfnew->sf_hasmac = 1;
        } else {
                ucp = sfnew->sf_mac.ether_addr_octet;
                len = wordend - wordstart;
                cp = wordstart;
                while (cp < wordend) {
                        if (ucp >= sfnew->sf_mac.ether_addr_octet +
                            sizeof (sfnew->sf_mac))
                                break;
                        if (*cp == '*') {
                                *mcp++ = *ucp++ = 0;
                                cp++;
                        } else {
                                if (!isxdigit(*cp))
                                        break;
                                *ucp = hexdecode(*cp++);
                                if (cp < wordend && isxdigit(*cp)) {
                                        *ucp = (*ucp << 4) |
                                            hexdecode(*cp++);
                                }
                                ucp++;
                                *mcp++ = 0xFF;
                        }
                        if (cp < wordend) {
                                if (*cp != ':' || cp + 1 == wordend)
                                        break;
                                cp++;
                        }
                }
                if (cp >= wordend)
                        sfnew->sf_hasmac = 1;
                else if (verbose)
                        logerr("%s: treating '%.*s' as server "
                            "name only, not MAC address\n", myname, len,
                            wordstart);
        }

        /* Add to end of list. */
        if (sftail == NULL)
                sfhead = sfnew;
        else
                sftail->sf_next = sfnew;
        sftail = sfnew;
}

/*
 * Create a copy of a given PPPoE message.  This is used for enqueuing
 * received PADO (offers) from possible servers.
 */
static poemsg_t *
save_message(const poemsg_t *pmsg)
{
        poemsg_t *newmsg;
        char *cp;

        newmsg = (poemsg_t *)malloc(sizeof (*pmsg) + pmsg->poemsg_len +
            strlen(pmsg->poemsg_iname) + 1);
        if (newmsg != NULL) {
                newmsg->poemsg_next = NULL;
                newmsg->poemsg_data = (const poep_t *)(newmsg + 1);
                (void) memcpy(newmsg + 1, pmsg->poemsg_data, pmsg->poemsg_len);
                newmsg->poemsg_len = pmsg->poemsg_len;
                cp = (char *)newmsg->poemsg_data + pmsg->poemsg_len;
                newmsg->poemsg_iname = (const char *)cp;
                (void) strcpy(cp, pmsg->poemsg_iname);
                (void) memcpy(&newmsg->poemsg_sender, &pmsg->poemsg_sender,
                    sizeof (newmsg->poemsg_sender));
        }
        return (newmsg);
}

/*
 * Create and send a PPPoE Active Discovery Request (PADR) message to
 * the sender of the given PADO.  Some tags -- Service-Name,
 * AC-Cookie, and Relay-Session-Id -- must be copied from PADO to
 * PADR.  Others are not.  The Service-Name must be selected from the
 * offered services in the PADO based on the user's requested service
 * name.  If the server offered "wildcard" service, then we ask for
 * this only if we can't find the user's requested service.
 *
 * Returns 1 if we can't send a valid PADR in response to the given
 * PADO.  The offer should be ignored and the next one tried.
 */
static int
send_padr(poesm_t *psm, const poemsg_t *pado)
{
        poep_t *poep;
        boolean_t haswild;
        boolean_t hassvc;
        const uint8_t *tagp;
        int ttyp;
        int tlen;

        /*
         * Increment sequence number for PADR so that we don't mistake
         * old replies for valid ones if the server is very slow.
         */
        psm->poesm_sequence++;

        poep = poe_mkheader(pkt_output, POECODE_PADR, 0);
        (void) poe_two_longs(poep, POETT_UNIQ, psm->poesm_localid,
            psm->poesm_sequence);

        haswild = B_FALSE;
        hassvc = B_FALSE;
        tagp = (const uint8_t *)(pado->poemsg_data + 1);
        while (poe_tagcheck(pado->poemsg_data, pado->poemsg_len, tagp)) {
                ttyp = POET_GET_TYPE(tagp);
                if (ttyp == POETT_END)
                        break;
                tlen = POET_GET_LENG(tagp);
                switch (ttyp) {
                case POETT_SERVICE:     /* Service-Name */
                        /* Allow only one */
                        if (hassvc)
                                break;
                        if (tlen == 0) {
                                haswild = B_TRUE;
                                break;
                        }
                        if (service[0] == '\0' ||
                            (tlen == strlen(service) &&
                            memcmp(service, POET_DATA(tagp), tlen) == 0)) {
                                (void) poe_tag_copy(poep, tagp);
                                hassvc = B_TRUE;
                        }
                        break;
                /* Ones we should discard */
                case POETT_ACCESS:      /* AC-Name */
                case POETT_UNIQ:        /* Host-Uniq */
                case POETT_NAMERR:      /* Service-Name-Error */
                case POETT_SYSERR:      /* AC-System-Error */
                case POETT_GENERR:      /* Generic-Error */
                case POETT_HURL:        /* Host-URL */
                case POETT_MOTM:        /* Message-Of-The-Minute */
                case POETT_RTEADD:      /* IP-Route-Add */
                case POETT_VENDOR:      /* Vendor-Specific */
                case POETT_MULTI:       /* Multicast-Capable */
                default:                /* Anything else we don't understand */
                        break;
                /* Ones we should copy */
                case POETT_COOKIE:      /* AC-Cookie */
                case POETT_RELAY:       /* Relay-Session-Id */
                        (void) poe_tag_copy(poep, tagp);
                        break;
                }
                tagp = POET_NEXT(tagp);
        }
        if (!hassvc) {
                if (haswild && service[0] == '\0')
                        (void) poe_add_str(poep, POETT_SERVICE, "");
                else
                        return (1);
        }

        return (send_pppoe(poep, "PADR", &pado->poemsg_sender));
}

/*
 * ********************************************************************
 * act_* functions implement the actions driven by the state machine
 * tables.  See "action_table" below.
 *
 * All action routines must return the next state value.
 * ********************************************************************
 */

/* ARGSUSED */
static int
act_none(poesm_t *psm, poemsg_t *pmsg, int event, int nextst)
{
        return (nextst);
}

/* ARGSUSED */
static int
act_fail(poesm_t *psm, poemsg_t *pmsg, int event, int nextst)
{
        if (verbose)
                logerr("%s: unrecoverable error\n", myname);
        return (PCSMS_DEAD);
}

/* ARGSUSED */
static int
act_spadi(poesm_t *psm, poemsg_t *pmsg, int event, int nextst)
{
        if (send_padi(psm->poesm_localid) != 0)
                return (PCSMS_DEAD);
        /*
         * If this is the first time, then initialize the retry count
         * and interval.
         */
        if (psm->poesm_state == PCSMS_DEAD) {
                psm->poesm_count = 3;
                psm->poesm_interval = pado_wait_time;
        } else {
                if ((psm->poesm_interval <<= 1) > RESTART_LIMIT)
                        psm->poesm_interval = RESTART_LIMIT;
        }
        psm->poesm_timer = psm->poesm_interval;
        (void) gettimeofday(&tvstart, NULL);
        return (nextst);
}

/* ARGSUSED */
static int
act_add(poesm_t *psm, poemsg_t *pmsg, int event, int nextst)
{
        pmsg = save_message(pmsg);
        if (pmsg != NULL) {
                if (psm->poesm_lastoff == NULL)
                        psm->poesm_firstoff = pmsg;
                else
                        psm->poesm_lastoff->poemsg_next = pmsg;
                psm->poesm_lastoff = pmsg;
        }
        return (nextst);
}

/* ARGSUSED */
static int
act_spadr(poesm_t *psm, poemsg_t *pmsg, int event, int nextst)
{
        poemsg_t *msgp;
        int retv;

        for (;;) {
                if ((msgp = psm->poesm_firstoff) == NULL)
                        return (PCSMS_DEAD);
                retv = send_padr(psm, msgp);
                if (retv < 0)
                        return (PCSMS_DEAD);
                if (retv == 0)
                        break;
                /* Can't send this request; try looking at next offer. */
                psm->poesm_firstoff = msgp->poemsg_next;
                msgp->poemsg_next = psm->poesm_tried;
                psm->poesm_tried = msgp;
        }
        if (psm->poesm_state != PCSMS_REQSENT) {
                psm->poesm_count = 3;
                psm->poesm_interval = pads_wait_time;
        } else {
                if ((psm->poesm_interval <<= 1) > RESTART_LIMIT)
                        psm->poesm_interval = RESTART_LIMIT;
        }
        psm->poesm_timer = psm->poesm_interval;
        (void) gettimeofday(&tvstart, NULL);
        return (nextst);
}

/* ARGSUSED */
static int
act_spadrp(poesm_t *psm, poemsg_t *pmsg, int event, int nextst)
{
        int retv;

        retv = send_padr(psm, pmsg);
        if (retv < 0)
                return (PCSMS_DEAD);
        pmsg = save_message(pmsg);
        if (retv > 0) {
                /*
                 * Cannot use this one; mark as tried and continue as
                 * if we never saw it.
                 */
                pmsg->poemsg_next = psm->poesm_tried;
                psm->poesm_tried = pmsg;
                return (psm->poesm_state);
        }
        pmsg->poemsg_next = psm->poesm_firstoff;
        psm->poesm_firstoff = pmsg;
        if (psm->poesm_lastoff == NULL)
                psm->poesm_lastoff = pmsg;
        psm->poesm_count = 3;
        psm->poesm_timer = psm->poesm_interval = pads_wait_time;
        (void) gettimeofday(&tvstart, NULL);
        return (nextst);
}

/* ARGSUSED */
static int
act_spadrn(poesm_t *psm, poemsg_t *pmsg, int event, int nextst)
{
        poemsg_t *msgp;
        int retv;

        if ((msgp = psm->poesm_firstoff) == NULL)
                return (PCSMS_DEAD);
        do {
                psm->poesm_firstoff = msgp->poemsg_next;
                msgp->poemsg_next = psm->poesm_tried;
                psm->poesm_tried = msgp;
                if ((msgp = psm->poesm_firstoff) == NULL)
                        return (PCSMS_DEAD);
                retv = send_padr(psm, msgp);
                if (retv < 0)
                        return (PCSMS_DEAD);
        } while (retv != 0);
        psm->poesm_count = 3;
        psm->poesm_timer = psm->poesm_interval = pads_wait_time;
        (void) gettimeofday(&tvstart, NULL);
        return (nextst);
}

/*
 * For safety -- remove end of line from strings passed back to pppd.
 */
static void
remove_eol(char *str, size_t len)
{
        while (len > 0) {
                if (*str == '\n')
                        *str = '$';
                str++;
                len--;
        }
}

/* ARGSUSED */
static int
act_open(poesm_t *psm, poemsg_t *pmsg, int event, int nextst)
{
        struct ppptun_peer ptp;
        union ppptun_name ptn;
        const char *cp;
        FILE *fp;
        const uint8_t *tagp, *vp;
        int tlen, ttyp;
        char *access;
        uint32_t val;
        size_t acc_len, serv_len;

        /*
         * The server has now assigned its session ID for the data
         * (PPP) portion of this tunnel.  Send that ID down to the
         * driver.
         */
        (void) memset(&ptp, '\0', sizeof (ptp));
        ptp.ptp_lsessid = psm->poesm_localid;
        ptp.ptp_rsessid = poe_session_id(pmsg->poemsg_data);
        (void) memcpy(&ptp.ptp_address, &pmsg->poemsg_sender,
            sizeof (ptp.ptp_address));
        ptp.ptp_style = PTS_PPPOE;
        if (strioctl(tunfd, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) <
            0) {
                logstrerror("PPPTUN_SPEER");
                return (PCSMS_DEAD);
        }

        /*
         * Data communication is now possible on this session.
         * Connect the data portion to the correct lower stream.
         */
        if ((cp = strchr(pmsg->poemsg_iname, ':')) == NULL)
                cp = pmsg->poemsg_iname + strlen(pmsg->poemsg_iname);
        (void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%.*s:pppoe",
            cp - pmsg->poemsg_iname, pmsg->poemsg_iname);
        if (strioctl(tunfd, PPPTUN_SDATA, &ptn, sizeof (ptn), 0) < 0) {
                logerr("%s: PPPTUN_SDATA %s: %s\n",
                    myname, ptn.ptn_name, mystrerror(errno));
                return (PCSMS_DEAD);
        }
        if (verbose)
                logerr("%s: Connection open; session %04X on "
                    "%s\n", myname, ptp.ptp_rsessid, ptn.ptn_name);

        /*
         * Walk through the PADS message to get the access server name
         * and the service.  If there are multiple instances of either
         * tag, then take the last access server and the first
         * non-null service.
         */
        access = "";
        acc_len = 0;
        serv_len = strlen(service);
        tagp = (const uint8_t *)(pmsg->poemsg_data + 1);
        while (poe_tagcheck(pmsg->poemsg_data, pmsg->poemsg_len, tagp)) {
                ttyp = POET_GET_TYPE(tagp);
                if (ttyp == POETT_END)
                        break;
                tlen = POET_GET_LENG(tagp);
                if (ttyp == POETT_ACCESS) {
                        access = (char *)POET_DATA(tagp);
                        acc_len = tlen;
                }
                if (serv_len == 0 && ttyp == POETT_SERVICE && tlen != 0) {
                        service = (char *)POET_DATA(tagp);
                        serv_len = tlen;
                }
                tagp = POET_NEXT(tagp);
        }

        /*
         * Remove end of line to make sure that integrity of values
         * passed back to pppd can't be compromised by the PPPoE
         * server.
         */
        remove_eol(service, serv_len);
        remove_eol(access, acc_len);

        /*
         * pppd has given us a pipe as fd 3, and we're expected to
         * write out the values of the following environment
         * variables:
         *      IF_AND_SERVICE
         *      SERVICE_NAME
         *      AC_NAME
         *      AC_MAC
         *      SESSION_ID
         *      VENDOR_SPECIFIC_1 ... N
         * See usr.bin/pppd/plugins/pppoe.c for more information.
         */
        if ((fp = fdopen(3, "w")) != NULL) {
                (void) fprintf(fp, "%.*s:%.*s\n",
                    cp - pmsg->poemsg_iname, pmsg->poemsg_iname, serv_len,
                    service);
                (void) fprintf(fp, "%.*s\n", serv_len, service);
                (void) fprintf(fp, "%.*s\n", acc_len, access);
                (void) fprintf(fp, "%s\n", ehost(&pmsg->poemsg_sender));
                (void) fprintf(fp, "%d\n", poe_session_id(pmsg->poemsg_data));
                tagp = (const uint8_t *)(pmsg->poemsg_data + 1);
                while (poe_tagcheck(pmsg->poemsg_data, pmsg->poemsg_len,
                    tagp)) {
                        ttyp = POET_GET_TYPE(tagp);
                        if (ttyp == POETT_END)
                                break;
                        tlen = POET_GET_LENG(tagp);
                        if (ttyp == POETT_VENDOR && tlen >= 4) {
                                (void) memcpy(&val, POET_DATA(tagp), 4);
                                (void) fprintf(fp, "%08lX:",
                                    (unsigned long)ntohl(val));
                                tlen -= 4;
                                vp = POET_DATA(tagp) + 4;
                                while (--tlen >= 0)
                                        (void) fprintf(fp, "%02X", *vp++);
                                (void) putc('\n', fp);
                        }
                        tagp = POET_NEXT(tagp);
                }
                (void) fclose(fp);
        }

        return (nextst);
}

static int (* const action_table[PCSMA__MAX])(poesm_t *psm, poemsg_t *pmsg,
    int event, int nextst) = {
            act_none, act_fail, act_spadi, act_add, act_spadr, act_spadrp,
            act_spadrn, act_open
};

/*
 * Dispatch an event and a corresponding message on a given state
 * machine.
 */
static void
handle_event(poesm_t *psm, int event, poemsg_t *pmsg)
{
        int nextst;

        if (verbose)
                logerr("%s: PPPoE Event %s (%d) in state %s "
                    "(%d): action %s (%d)\n", myname, poe_event(event), event,
                    poe_state(psm->poesm_state), psm->poesm_state,
                    poe_action(client_action[psm->poesm_state][event]),
                    client_action[psm->poesm_state][event]);

        nextst = (*action_table[client_action[psm->poesm_state][event]])(psm,
            pmsg, event, client_next_state[psm->poesm_state][event]);

        if (verbose)
                logerr("%s: PPPoE State change %s (%d) -> %s (%d)\n", myname,
                    poe_state(psm->poesm_state), psm->poesm_state,
                    poe_state(nextst), nextst);

        psm->poesm_state = nextst;

        /*
         * Life-altering states are handled here.  If we reach dead
         * state again after starting, then we failed.  If we reach
         * conversational state, then we're open.
         */
        if (nextst == PCSMS_DEAD) {
                if (verbose)
                        logerr("%s: action failed\n", myname);
                exit(1);
        }
        if (nextst == PCSMS_CONVERS) {
                if (verbose)
                        logerr("%s: connected\n", myname);
                exit(0);
        }
}

/*
 * Check for error message tags in the PPPoE packet.  We must ignore
 * offers that merely report errors, and need to log errors in any
 * case.
 */
static int
error_check(poemsg_t *pmsg)
{
        const uint8_t *tagp;
        int ttyp;

        tagp = (const uint8_t *)(pmsg->poemsg_data + 1);
        while (poe_tagcheck(pmsg->poemsg_data, pmsg->poemsg_len, tagp)) {
                ttyp = POET_GET_TYPE(tagp);
                if (ttyp == POETT_END)
                        break;
                if (ttyp == POETT_NAMERR || ttyp == POETT_SYSERR ||
                    ttyp == POETT_GENERR) {
                        if (verbose)
                                display_pppoe(stderr, pmsg->poemsg_data,
                                    pmsg->poemsg_len, &pmsg->poemsg_sender);
                        return (-1);
                }
                tagp = POET_NEXT(tagp);
        }
        return (0);
}

/*
 * Extract sequence number, if any, from PADS message, so that we can
 * relate it to the PADR that we sent.
 */
static uint32_t
get_sequence(const poemsg_t *pmsg)
{
        const uint8_t *tagp;
        int ttyp;
        uint32_t vals[2];

        tagp = (const uint8_t *)(pmsg->poemsg_data + 1);
        while (poe_tagcheck(pmsg->poemsg_data, pmsg->poemsg_len, tagp)) {
                ttyp = POET_GET_TYPE(tagp);
                if (ttyp == POETT_END)
                        break;
                if (ttyp == POETT_UNIQ) {
                        if (POET_GET_LENG(tagp) < sizeof (vals))
                                break;
                        (void) memcpy(vals, POET_DATA(tagp), sizeof (vals));
                        return (ntohl(vals[1]));
                }
                tagp = POET_NEXT(tagp);
        }
        return (0);
}

/*
 * Server filter cases:
 *
 *      No filters -- all servers generate RPADO+ event; select the
 *      first responding server.
 *
 *      Only "except" filters -- matching servers are RPADO, others
 *      are RPADO+.
 *
 *      Mix of filters -- those matching "pass" are RPADO+, those
 *      matching "except" are RPADO, and all others are also RPADO.
 *
 * If the "only" keyword was given, then RPADO becomes -1; only RPADO+
 * events occur.
 */
static int
use_server(poemsg_t *pado, const ppptun_atype *pap)
{
        struct server_filter *sfp;
        const uchar_t *sndp;
        const uchar_t *macp;
        const uchar_t *maskp;
        int i;
        int passmatched;
        int tlen;
        const uint8_t *tagp;
        int ttyp;

        /*
         * If no service mentioned in offer, then we can't use it.
         */
        tagp = (const uint8_t *)(pado->poemsg_data + 1);
        ttyp = POETT_END;
        while (poe_tagcheck(pado->poemsg_data, pado->poemsg_len, tagp)) {
                ttyp = POET_GET_TYPE(tagp);
                if (ttyp == POETT_END)
                        break;
                if (ttyp == POETT_SERVICE) {
                        /*
                         * If the user has requested a specific service, then
                         * this selection is exclusive.  We never use the
                         * wildcard for this.
                         */
                        tlen = POET_GET_LENG(tagp);
                        if (service[0] == '\0' || (strlen(service) == tlen &&
                            memcmp(service, POET_DATA(tagp), tlen) == 0))
                                break;
                        /* just in case we run off the end */
                        ttyp = POETT_END;
                }
                tagp = POET_NEXT(tagp);
        }
        if (ttyp != POETT_SERVICE) {
                if (verbose)
                        logerr("%s: Discard unusable offer from %s; service "
                            "'%s' not seen\n", myname, ehost(pap), service);
                return (-1);
        }

        passmatched = 0;
        for (sfp = sfhead; sfp != NULL; sfp = sfp->sf_next) {
                passmatched |= !sfp->sf_isexcept;
                if (sfp->sf_hasmac) {
                        sndp = pado->poemsg_sender.pta_pppoe.ptma_mac;
                        macp = sfp->sf_mac.ether_addr_octet;
                        maskp = sfp->sf_mask.ether_addr_octet;
                        i = sizeof (pado->poemsg_sender.pta_pppoe.ptma_mac);
                        for (; i > 0; i--)
                                if (((*macp++ ^ *sndp++) & *maskp++) != 0)
                                        break;
                        if (i <= 0)
                                break;
                }
        }

        if (sfp == NULL) {
                /*
                 * No match encountered; if only exclude rules have
                 * been seen, then accept this offer.
                 */
                if (!passmatched)
                        return (PCSME_RPADOP);
        } else {
                if (!sfp->sf_isexcept)
                        return (PCSME_RPADOP);
        }
        if (onlyflag) {
                if (verbose)
                        logerr("%s: Discard unusable offer from %s; server not "
                            "matched\n", myname, ehost(pap));
                return (-1);
        }
        return (PCSME_RPADO);
}

/*
 * This is the normal event loop.  It initializes the state machine
 * and sends in an Open event to kick things off.  Then it drops into
 * a loop to dispatch events for the state machine.
 */
static void
find_server(int localid)
{
        poesm_t psm;
        struct pollfd pfd[1];
        struct timeval tv, tvnow;
        int retv;
        poemsg_t pmsg;
        struct strbuf ctrl;
        struct strbuf data;
        poep_t *poep;
        int flags;
        uint32_t seqval;
        struct ppptun_control *ptc;

        (void) memset(&psm, '\0', sizeof (psm));

        /*
         * Initialize the sequence number with something handy.  It
         * doesn't need to be absolutely unique, since the localid
         * value actually demultiplexes everything.  This just makes
         * the operation a little safer.
         */
        psm.poesm_sequence = getpid() << 16;
        psm.poesm_localid = localid;

        /* Start the state machine */
        handle_event(&psm, PCSME_OPEN, NULL);

        /* Enter event polling loop. */
        pfd[0].fd = tunfd;
        pfd[0].events = POLLIN;
        for (;;) {
                /* Wait for timeout or message */
                retv = poll(pfd, 1, psm.poesm_timer > 0 ? psm.poesm_timer :
                    INFTIM);
                if (retv < 0) {
                        logstrerror("poll");
                        break;
                }

                /* Handle a timeout */
                if (retv == 0) {
                        psm.poesm_timer = 0;
                        handle_event(&psm, --psm.poesm_count > 0 ? PCSME_TOP :
                            PCSME_TOM, NULL);
                        continue;
                }

                /* Adjust the timer for the time we slept. */
                if (psm.poesm_timer > 0) {
                        (void) gettimeofday(&tvnow, NULL);
                        tv = tvnow;
                        if ((tv.tv_sec -= tvstart.tv_sec) < 0) {
                                /* Darn */
                                tv.tv_sec = 1;
                                tv.tv_usec = 0;
                        } else if ((tv.tv_usec -= tvstart.tv_usec) < 0) {
                                tv.tv_usec += 1000000;
                                if (--tv.tv_sec < 0)
                                        tv.tv_sec = 0;
                        }
                        psm.poesm_timer -= tv.tv_sec*1000 + tv.tv_usec/1000;
                        tvstart = tvnow;
                }

                /* Read in the message from the server. */
                ctrl.maxlen = PKT_OCTL_LEN;
                ctrl.buf = (caddr_t)pkt_octl;
                data.maxlen = PKT_INPUT_LEN;
                data.buf = (caddr_t)pkt_input;
                flags = 0;

                if (pppoec_getmsg(tunfd, &ctrl, &data, &flags) < 0)
                        break;

                if (ctrl.len != sizeof (*ptc)) {
                        if (verbose)
                                logerr("%s: discard: ctrl len %d\n", myname,
                                    ctrl.len);
                        continue;
                }
                poep = (poep_t *)pkt_input;
                (void) memset(&pmsg, '\0', sizeof (pmsg));
                pmsg.poemsg_next = NULL;
                pmsg.poemsg_data = poep;
                pmsg.poemsg_len = data.len;
                ptc = (struct ppptun_control *)pkt_octl;
                if (ptc->ptc_action != PTCA_CONTROL) {
                        if (verbose)
                                logerr("%s: discard: unexpected action %d\n",
                                    myname, ptc->ptc_action);
                        continue;
                }
                pmsg.poemsg_iname = ptc->ptc_name;
                if (verbose)
                        logerr("%s: Received %s from %s/%s\n",
                            myname, poe_codename(poep->poep_code),
                            ehost(&ptc->ptc_address), pmsg.poemsg_iname);
                pmsg.poemsg_sender = ptc->ptc_address;

                /* Check for messages from unexpected peers. */
                if ((poep->poep_code == POECODE_PADT ||
                    poep->poep_code == POECODE_PADS) &&
                    (psm.poesm_firstoff == NULL ||
                    memcmp(&psm.poesm_firstoff->poemsg_sender,
                    &pmsg.poemsg_sender, sizeof (pmsg.poemsg_sender)) != 0)) {
                        if (verbose) {
                                logerr("%s: Unexpected peer %s", myname,
                                    ehost(&ptc->ptc_address));
                                logerr(" != %s\n",
                                    ehost(&psm.poesm_firstoff->poemsg_sender));
                        }
                        continue;
                }

                /* Eliminate stale PADS responses. */
                if (poep->poep_code == POECODE_PADS) {
                        seqval = get_sequence(&pmsg);
                        if (seqval != psm.poesm_sequence) {
                                if (verbose) {
                                        if (seqval == 0)
                                                logerr(
                                                    "%s: PADS has no sequence "
                                                    "number.\n", myname);
                                        else
                                                logerr(
                                                    "%s: PADS has sequence "
                                                    "%08X instead of %08X.\n",
                                                    myname, seqval,
                                                    psm.poesm_sequence);
                                }
                                continue;
                        }
                }

                /* Dispatch message event. */
                retv = error_check(&pmsg);
                switch (poep->poep_code) {
                case POECODE_PADT:
                        handle_event(&psm, PCSME_RPADT, &pmsg);
                        break;
                case POECODE_PADS:
                        /*
                         * Got a PPPoE Active Discovery Session-
                         * confirmation message.  It's a PADS event if
                         * everything's in order.  It's a PADS- event
                         * if the message is merely reporting an
                         * error.
                         */
                        handle_event(&psm, retv != 0 ? PCSME_RPADSN :
                            PCSME_RPADS, &pmsg);
                        break;
                case POECODE_PADO:
                        /* Ignore offers that merely report errors. */
                        if (retv != 0)
                                break;
                        /* Ignore offers from servers we don't want. */
                        if ((retv = use_server(&pmsg, &ptc->ptc_address)) < 0)
                                break;
                        /* Dispatch either RPADO or RAPDO+ event. */
                        handle_event(&psm, retv, &pmsg);
                        break;

                default:
                        if (verbose)
                                logerr("%s: Unexpected code %s (%d)\n", myname,
                                    poe_codename(poep->poep_code),
                                    poep->poep_code);
                        break;
                }
        }
        exit(1);
}

static void
usage(void)
{
        logerr("Usage:\n"
            "\t%s [-os#] [-v] <dev> [<service> [<server> [only]]]\n\n"
            "    or\n\n"
            "\t%s [-o#] [-v] -i <dev>\n", myname, myname);
        exit(1);
}

/*
 * In addition to the usual 0-2 file descriptors, pppd will leave fd 3
 * open on a pipe to receive the environment variables.  See
 * pppoe_device_pipe() in pppd/plugins/pppoe.c and device_pipe_hook in
 * pppd/main.c.
 */
int
main(int argc, char **argv)
{
        int inquiry_mode, exceptflag, arg, localid;
        char *cp;

        log_to_stderr(LOGLVL_DBG);

        if ((myname = *argv) == NULL)
                myname = "pppoec";

        inquiry_mode = 0;
        while ((arg = getopt(argc, argv, "io:s:v")) != EOF)
                switch (arg) {
                case 'i':
                        inquiry_mode++;
                        break;
                case 'v':
                        verbose++;
                        break;
                case 'o':
                        pado_wait_time = strtol(optarg, &cp, 0);
                        if (pado_wait_time <= 0 || *cp != '\0' ||
                            cp == optarg) {
                                logerr("%s: illegal PADO wait time: %s\n",
                                    myname, optarg);
                                exit(1);
                        }
                        break;
                case 's':
                        pads_wait_time = strtol(optarg, &cp, 0);
                        if (pads_wait_time <= 0 || *cp != '\0' ||
                            cp == optarg) {
                                logerr("%s: illegal PADS wait time: %s\n",
                                    myname, optarg);
                                exit(1);
                        }
                        break;
                case '?':
                        usage();
                }

        /* Handle inquiry mode. */
        if (inquiry_mode) {
                if (optind != argc-1)
                        usage();
                if (pado_wait_time == 0)
                        pado_wait_time = PADI_INQUIRY_DWELL;

                /* Invoked by user; open the tunnel driver myself. */
                tunfd = open(tunnam, O_RDWR | O_NOCTTY);
                if (tunfd == -1) {
                        logstrerror(tunnam);
                        exit(1);
                }

                /*
                 * Set up the control stream for PPPoE negotiation
                 * (set_control), then broadcast a query for all servers
                 * and listen for replies (find_all_servers).
                 */
                find_all_servers(set_control(argv[optind]));
                return (0);
        }

        if (pado_wait_time == 0)
                pado_wait_time = PADI_RESTART_TIME;

        if (optind >= argc)
                usage();

        /* Make sure we've got a usable tunnel driver on stdin. */
        check_stdin();

        /* Set up the control stream for PPPoE negotiation. */
        localid = set_control(argv[optind++]);

        /* Pick the service, if any. */
        if (optind < argc)
                service = argv[optind++];

        /* Parse out the filters. */
        if (optind < argc) {
                if (strcasecmp(argv[argc - 1], "only") == 0) {
                        argc--;
                        onlyflag = 1;
                }
                exceptflag = 0;
                for (; optind < argc; optind++) {
                        if (!exceptflag &&
                            strcasecmp(argv[optind], "except") == 0) {
                                exceptflag = 1;
                        } else {
                                parse_filter(argv[optind], exceptflag);
                                exceptflag = 0;
                        }
                }
        }

        /* Enter the main loop. */
        find_server(localid);

        return (0);
}