#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"
#define PPPOE_DISCRIM 0x504F4531
#define PADI_RESTART_TIME 500
#define PADR_RESTART_TIME 2000
#define PADI_INQUIRY_DWELL 3000
#define RESTART_LIMIT 5000
char *myname;
static int verbose;
static int onlyflag;
static char *service = "";
static int pado_wait_time = 0;
static int pads_wait_time = PADR_RESTART_TIME;
static int tunfd;
static struct timeval tvstart;
struct server_filter {
struct server_filter *sf_next;
struct ether_addr sf_mac;
struct ether_addr sf_mask;
const char *sf_name;
boolean_t sf_hasmac;
boolean_t sf_isexcept;
};
static struct server_filter *sfhead, *sftail;
#define PCSME_CLOSE 0
#define PCSME_OPEN 1
#define PCSME_TOP 2
#define PCSME_TOM 3
#define PCSME_RPADT 4
#define PCSME_RPADOP 5
#define PCSME_RPADO 6
#define PCSME_RPADS 7
#define PCSME_RPADSN 8
#define PCSME__MAX 9
#define PCSMS_DEAD 0
#define PCSMS_INITSENT 1
#define PCSMS_OFFRRCVD 2
#define PCSMS_REQSENT 3
#define PCSMS_CONVERS 4
#define PCSMS__MAX 5
#define PCSMA_NONE 0
#define PCSMA_FAIL 1
#define PCSMA_SPADI 2
#define PCSMA_ADD 3
#define PCSMA_SPADR 4
#define PCSMA_SPADRP 5
#define PCSMA_SPADRN 6
#define PCSMA_OPEN 7
#define PCSMA__MAX 8
static uint8_t client_next_state[PCSMS__MAX][PCSME__MAX] = {
{
PCSMS_DEAD,
PCSMS_INITSENT,
PCSMS_DEAD,
PCSMS_DEAD,
PCSMS_DEAD,
PCSMS_DEAD,
PCSMS_DEAD,
PCSMS_DEAD,
PCSMS_DEAD,
},
{
PCSMS_DEAD,
PCSMS_INITSENT,
PCSMS_INITSENT,
PCSMS_DEAD,
PCSMS_DEAD,
PCSMS_REQSENT,
PCSMS_OFFRRCVD,
PCSMS_INITSENT,
PCSMS_INITSENT,
},
{
PCSMS_DEAD,
PCSMS_INITSENT,
PCSMS_REQSENT,
PCSMS_REQSENT,
PCSMS_DEAD,
PCSMS_REQSENT,
PCSMS_OFFRRCVD,
PCSMS_OFFRRCVD,
PCSMS_OFFRRCVD,
},
{
PCSMS_DEAD,
PCSMS_INITSENT,
PCSMS_REQSENT,
PCSMS_REQSENT,
PCSMS_DEAD,
PCSMS_REQSENT,
PCSMS_REQSENT,
PCSMS_CONVERS,
PCSMS_REQSENT,
},
{
PCSMS_DEAD,
PCSMS_INITSENT,
PCSMS_CONVERS,
PCSMS_CONVERS,
PCSMS_DEAD,
PCSMS_CONVERS,
PCSMS_CONVERS,
PCSMS_CONVERS,
PCSMS_CONVERS,
},
};
static uint8_t client_action[PCSMS__MAX][PCSME__MAX] = {
{
PCSMA_NONE,
PCSMA_SPADI,
PCSMA_NONE,
PCSMA_NONE,
PCSMA_NONE,
PCSMA_NONE,
PCSMA_NONE,
PCSMA_NONE,
PCSMA_NONE,
},
{
PCSMA_FAIL,
PCSMA_SPADI,
PCSMA_SPADI,
PCSMA_FAIL,
PCSMA_FAIL,
PCSMA_SPADRP,
PCSMA_ADD,
PCSMA_NONE,
PCSMA_NONE,
},
{
PCSMA_FAIL,
PCSMA_SPADI,
PCSMA_SPADR,
PCSMA_SPADR,
PCSMA_FAIL,
PCSMA_SPADRP,
PCSMA_ADD,
PCSMA_NONE,
PCSMA_NONE,
},
{
PCSMA_FAIL,
PCSMA_SPADI,
PCSMA_SPADR,
PCSMA_SPADRN,
PCSMA_FAIL,
PCSMA_ADD,
PCSMA_ADD,
PCSMA_OPEN,
PCSMA_SPADRN,
},
{
PCSMA_FAIL,
PCSMA_SPADI,
PCSMA_FAIL,
PCSMA_FAIL,
PCSMA_FAIL,
PCSMA_NONE,
PCSMA_NONE,
PCSMA_NONE,
PCSMA_NONE,
},
};
typedef struct poesm_s {
struct poesm_s *poemsg_next;
const poep_t *poemsg_data;
int poemsg_len;
ppptun_atype poemsg_sender;
const char *poemsg_iname;
} poemsg_t;
typedef struct {
int poesm_state;
int poesm_timer;
int poesm_count;
int poesm_interval;
uint32_t poesm_sequence;
poemsg_t *poesm_firstoff;
poemsg_t *poesm_lastoff;
poemsg_t *poesm_tried;
int poesm_localid;
} poesm_t;
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]);
}
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]);
}
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]);
}
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);
}
static int
set_control(const char *dname)
{
struct ppptun_peer ptp;
union ppptun_name ptn;
(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);
}
(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);
}
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);
}
tunfd = 0;
}
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(" ");
(void) fprintf(out, "%-16s ", ehost(pap));
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:
str = "Svc";
break;
case POETT_ACCESS:
str = "Name";
break;
case POETT_UNIQ:
str = "Uniq";
break;
case POETT_COOKIE:
str = "Cookie";
break;
case POETT_VENDOR:
break;
case POETT_RELAY:
str = "Relay";
break;
case POETT_NAMERR:
str = "SvcNameErr";
break;
case POETT_SYSERR:
str = "SysErr";
break;
case POETT_GENERR:
str = "GenErr";
break;
case POETT_MULTI:
break;
case POETT_HURL:
str = "URL";
break;
case POETT_MOTM:
str = "Mesg";
break;
case POETT_RTEADD:
break;
}
switch (ttyp) {
case POETT_NAMERR:
case POETT_SYSERR:
if (tlen > 0 && *dp == '\0')
tlen = 0;
case POETT_SERVICE:
case POETT_ACCESS:
case POETT_GENERR:
case POETT_MOTM:
case POETT_HURL:
(void) fprintf(out, "%s:\"%.*s\" ", str, tlen, dp);
break;
case POETT_UNIQ:
case POETT_COOKIE:
case POETT_RELAY:
(void) fprintf(out, "%s:", str);
while (--tlen >= 0)
(void) fprintf(out, "%02X", *dp++);
(void) putc(' ', out);
break;
case POETT_VENDOR:
(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:
(void) fprintf(out, "Multi:%d ", *dp);
break;
case POETT_RTEADD:
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);
}
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;
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);
}
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));
}
static void
alarm_hand(int dummy)
{
exit(0);
}
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;
(void) memset(&act, '\0', sizeof (act));
act.sa_handler = alarm_hand;
(void) sigaction(SIGALRM, &act, NULL);
(void) alarm((pado_wait_time + 999) / 1000);
if (send_padi(localid) != 0)
return;
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;
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 (poe_code(poep) == POECODE_PADO) {
display_pppoe(stdout, poep, data.len,
&ptc->ptc_address);
}
}
}
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;
sfnew = (struct server_filter *)calloc(1, sizeof (*sfnew));
if (sfnew == NULL) {
logstrerror("filter allocation");
exit(1);
}
sfnew->sf_name = str;
sfnew->sf_isexcept = exceptflag == 0 ? 0 : 1;
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';
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);
}
if (sftail == NULL)
sfhead = sfnew;
else
sftail->sf_next = sfnew;
sftail = sfnew;
}
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);
}
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;
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:
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;
case POETT_ACCESS:
case POETT_UNIQ:
case POETT_NAMERR:
case POETT_SYSERR:
case POETT_GENERR:
case POETT_HURL:
case POETT_MOTM:
case POETT_RTEADD:
case POETT_VENDOR:
case POETT_MULTI:
default:
break;
case POETT_COOKIE:
case POETT_RELAY:
(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));
}
static int
act_none(poesm_t *psm, poemsg_t *pmsg, int event, int nextst)
{
return (nextst);
}
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);
}
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 (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);
}
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);
}
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;
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);
}
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) {
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);
}
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);
}
static void
remove_eol(char *str, size_t len)
{
while (len > 0) {
if (*str == '\n')
*str = '$';
str++;
len--;
}
}
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;
(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);
}
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);
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_eol(service, serv_len);
remove_eol(access, acc_len);
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
};
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;
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);
}
}
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);
}
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);
}
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;
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) {
tlen = POET_GET_LENG(tagp);
if (service[0] == '\0' || (strlen(service) == tlen &&
memcmp(service, POET_DATA(tagp), tlen) == 0))
break;
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) {
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);
}
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));
psm.poesm_sequence = getpid() << 16;
psm.poesm_localid = localid;
handle_event(&psm, PCSME_OPEN, NULL);
pfd[0].fd = tunfd;
pfd[0].events = POLLIN;
for (;;) {
retv = poll(pfd, 1, psm.poesm_timer > 0 ? psm.poesm_timer :
INFTIM);
if (retv < 0) {
logstrerror("poll");
break;
}
if (retv == 0) {
psm.poesm_timer = 0;
handle_event(&psm, --psm.poesm_count > 0 ? PCSME_TOP :
PCSME_TOM, NULL);
continue;
}
if (psm.poesm_timer > 0) {
(void) gettimeofday(&tvnow, NULL);
tv = tvnow;
if ((tv.tv_sec -= tvstart.tv_sec) < 0) {
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;
}
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;
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;
}
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;
}
}
retv = error_check(&pmsg);
switch (poep->poep_code) {
case POECODE_PADT:
handle_event(&psm, PCSME_RPADT, &pmsg);
break;
case POECODE_PADS:
handle_event(&psm, retv != 0 ? PCSME_RPADSN :
PCSME_RPADS, &pmsg);
break;
case POECODE_PADO:
if (retv != 0)
break;
if ((retv = use_server(&pmsg, &ptc->ptc_address)) < 0)
break;
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);
}
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();
}
if (inquiry_mode) {
if (optind != argc-1)
usage();
if (pado_wait_time == 0)
pado_wait_time = PADI_INQUIRY_DWELL;
tunfd = open(tunnam, O_RDWR | O_NOCTTY);
if (tunfd == -1) {
logstrerror(tunnam);
exit(1);
}
find_all_servers(set_control(argv[optind]));
return (0);
}
if (pado_wait_time == 0)
pado_wait_time = PADI_RESTART_TIME;
if (optind >= argc)
usage();
check_stdin();
localid = set_control(argv[optind++]);
if (optind < argc)
service = argv[optind++];
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;
}
}
}
find_server(localid);
return (0);
}