/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2000-2002 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#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, "");
	(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)
			(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)
{
	struct server_filter *sfp;
	const uchar_t *sndp;
	const uchar_t *macp;
	const uchar_t *maskp;
	int i;
	int passmatched;
	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 || ttyp == POETT_SERVICE)
			break;
		tagp = POET_NEXT(tagp);
	}
	if (ttyp != POETT_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)
		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)) < 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);
}