# # 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 # # # Copyright 2000-2002 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. include ../../../Makefile.cmd PROG= pppoec pppoed CLIENT_OBJS= pppoec.o common.o logging.o DAEMON_OBJS= pppoed.o options.o logging.o common.o CPPFLAGS += -I$(SRC)/uts/common # not linted SMATCH=off .KEEP_STATE: all: $(PROG) OBJS= $(CLIENT_OBJS) $(DAEMON_OBJS) LDLIBS += -lsocket -lnsl .PARALLEL: $(OBJS) pppoec: $(CLIENT_OBJS) $(LINK.c) $(CLIENT_OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) pppoed: $(DAEMON_OBJS) $(LINK.c) $(DAEMON_OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) include ../Makefile.lib install: all .WAIT $(PROG) $(ROOTLIBINETPROG) clean: $(RM) $(OBJS) lint: $(LINT.c) $(CLIENT_OBJS:%.o=%.c) $(LDLIBS) $(LINT.c) $(DAEMON_OBJS:%.o=%.c) $(LDLIBS) include ../../../Makefile.targ /* * 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 common utilities and data. * * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" /* Not all functions are used by all applications. Let lint know this. */ /*LINTLIBRARY*/ /* Common I/O buffers */ uint32_t pkt_input[PKT_INPUT_LEN / sizeof (uint32_t)]; uint32_t pkt_octl[PKT_OCTL_LEN / sizeof (uint32_t)]; uint32_t pkt_output[PKT_OUTPUT_LEN / sizeof (uint32_t)]; const char tunnam[] = "/dev/" PPP_TUN_NAME; const ether_addr_t ether_bcast = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; /* * Wrapper for standard strerror() function -- the standard allows * that routine to return NULL, and that's inconvenient to handle. * This function never returns NULL. */ const char * mystrerror(int err) { const char *estr; static char ebuf[64]; if ((estr = strerror(err)) != NULL) return (estr); (void) snprintf(ebuf, sizeof (ebuf), "Error:%d", err); return (ebuf); } /* * Wrapper for standard perror() function -- the standard definition * of perror doesn't include the program name in the output and is * thus inconvenient to use. */ void myperror(const char *emsg) { (void) fprintf(stderr, "%s: %s: %s\n", myname, emsg, mystrerror(errno)); } /* * Wrapper for standard getmsg() function. Completely discards any * fragmented messages because we don't expect ever to see these from * a properly functioning tunnel driver. Returns flags * (MORECTL|MOREDATA) as seen by interface. */ int mygetmsg(int fd, struct strbuf *ctrl, struct strbuf *data, int *flags) { int retv; int hadflags; hadflags = getmsg(fd, ctrl, data, flags); if (hadflags <= 0 || !(hadflags & (MORECTL | MOREDATA))) return (hadflags); do { if (flags != NULL) *flags = 0; retv = getmsg(fd, ctrl, data, flags); } while (retv > 0 || (retv < 0 && errno == EINTR)); /* * What remains at this point is the tail end of the * truncated message. Toss it. */ return (retv < 0 ? retv : hadflags); } /* * Common wrapper function for STREAMS I_STR ioctl. Returns -1 on * failure, 0 for success. */ int strioctl(int fd, int cmd, void *ptr, int ilen, int olen) { struct strioctl str; str.ic_cmd = cmd; str.ic_timout = 0; /* Default timeout; 15 seconds */ str.ic_len = ilen; str.ic_dp = ptr; if (ioctl(fd, I_STR, &str) == -1) { return (-1); } if (str.ic_len != olen) { errno = EINVAL; return (-1); } return (0); } /* * Format a PPPoE header in the user's buffer. The returned pointer * is either identical to the first argument, or is NULL if it's not * usable. On entry, dptr should point to the first byte after the * Ethertype field, codeval should be one of the POECODE_* values, and * sessionid should be the assigned session ID number or one of the * special POESESS_* values. */ poep_t * poe_mkheader(void *dptr, uint8_t codeval, int sessionid) { poep_t *poep; /* Discard obvious junk. */ assert(dptr != NULL && IS_P2ALIGNED(dptr, sizeof (poep_t *))); /* Initialize the header */ poep = (poep_t *)dptr; poep->poep_version_type = POE_VERSION; poep->poep_code = codeval; poep->poep_session_id = htons(sessionid); poep->poep_length = htons(0); return (poep); } /* * Validate that a given tag is intact. This is intended to be used * in tag-parsing loops before attempting to access the tag data. */ boolean_t poe_tagcheck(const poep_t *poep, int length, const uint8_t *tptr) { int plen; const uint8_t *tstart, *tend; if (poep == NULL || !IS_P2ALIGNED(poep, sizeof (uint16_t)) || tptr == NULL || length < sizeof (*poep)) return (B_FALSE); plen = poe_length(poep); if (plen + sizeof (*poep) > length) return (B_FALSE); tstart = (const uint8_t *)(poep+1); tend = tstart + plen; /* * Note careful dereference of tptr; it might be near the end * already, so we have to range check it before dereferencing * to get the actual tag length. Yes, it looks like we have * duplicate array end checks. No, they're not duplicates. */ if (tptr < tstart || tptr+POET_HDRLEN > tend || tptr+POET_HDRLEN+POET_GET_LENG(tptr) > tend) return (B_FALSE); return (B_TRUE); } static int poe_tag_insert(poep_t *poep, uint16_t ttype, const void *data, size_t dlen) { int plen; uint8_t *dp; plen = poe_length(poep); if (data == NULL) dlen = 0; if (sizeof (*poep) + plen + POET_HDRLEN + dlen > PPPOE_MSGMAX) return (-1); dp = (uint8_t *)(poep + 1) + plen; POET_SET_TYPE(dp, ttype); POET_SET_LENG(dp, dlen); if (dlen > 0) (void) memcpy(POET_DATA(dp), data, dlen); poep->poep_length = htons(plen + POET_HDRLEN + dlen); return (0); } /* * Add a tag with text string data to a PPPoE packet being * constructed. Returns -1 if it doesn't fit, or 0 for success. */ int poe_add_str(poep_t *poep, uint16_t ttype, const char *str) { return (poe_tag_insert(poep, ttype, str, strlen(str))); } /* * Add a tag with 32-bit integer data to a PPPoE packet being * constructed. Returns -1 if it doesn't fit, or 0 for success. */ int poe_add_long(poep_t *poep, uint16_t ttype, uint32_t val) { val = htonl(val); return (poe_tag_insert(poep, ttype, &val, sizeof (val))); } /* * Add a tag with two 32-bit integers to a PPPoE packet being * constructed. Returns -1 if it doesn't fit, or 0 for success. */ int poe_two_longs(poep_t *poep, uint16_t ttype, uint32_t val1, uint32_t val2) { uint32_t vals[2]; vals[0] = htonl(val1); vals[1] = htonl(val2); return (poe_tag_insert(poep, ttype, vals, sizeof (vals))); } /* * Copy a single tag and its data from one PPPoE packet to a PPPoE * packet being constructed. Returns -1 if it doesn't fit, or 0 for * success. */ int poe_tag_copy(poep_t *poep, const uint8_t *tagp) { int tlen; int plen; tlen = POET_GET_LENG(tagp) + POET_HDRLEN; plen = poe_length(poep); if (sizeof (*poep) + plen + tlen > PPPOE_MSGMAX) return (-1); (void) memcpy((uint8_t *)(poep + 1) + plen, tagp, tlen); poep->poep_length = htons(tlen + plen); return (0); } struct tag_list { int tl_type; const char *tl_name; }; /* List of PPPoE data tag types. */ static const struct tag_list tag_list[] = { { POETT_END, "End-Of-List" }, { POETT_SERVICE, "Service-Name" }, { POETT_ACCESS, "AC-Name" }, { POETT_UNIQ, "Host-Uniq" }, { POETT_COOKIE, "AC-Cookie" }, { POETT_VENDOR, "Vendor-Specific" }, { POETT_RELAY, "Relay-Session-Id" }, { POETT_NAMERR, "Service-Name-Error" }, { POETT_SYSERR, "AC-System-Error" }, { POETT_GENERR, "Generic-Error" }, { POETT_MULTI, "Multicast-Capable" }, { POETT_HURL, "Host-URL" }, { POETT_MOTM, "Message-Of-The-Minute" }, { POETT_RTEADD, "IP-Route-Add" }, { 0, NULL } }; /* List of PPPoE message code numbers. */ static const struct tag_list code_list[] = { { POECODE_DATA, "Data" }, { POECODE_PADO, "Active Discovery Offer" }, { POECODE_PADI, "Active Discovery Initiation" }, { POECODE_PADR, "Active Discovery Request" }, { POECODE_PADS, "Active Discovery Session-confirmation" }, { POECODE_PADT, "Active Discovery Terminate" }, { POECODE_PADM, "Active Discovery Message" }, { POECODE_PADN, "Active Discovery Network" }, { 0, NULL } }; /* * Given a tag type number, return a pointer to a string describing * the tag. */ const char * poe_tagname(uint16_t tagtype) { const struct tag_list *tlp; static char tname[32]; for (tlp = tag_list; tlp->tl_name != NULL; tlp++) if (tagtype == tlp->tl_type) return (tlp->tl_name); (void) sprintf(tname, "Tag%d", tagtype); return (tname); } /* * Given a PPPoE message code number, return a pointer to a string * describing the message. */ const char * poe_codename(uint8_t codetype) { const struct tag_list *tlp; static char tname[32]; for (tlp = code_list; tlp->tl_name != NULL; tlp++) if (codetype == tlp->tl_type) return (tlp->tl_name); (void) sprintf(tname, "Code%d", codetype); return (tname); } /* * Given a tunnel driver address structure, return a pointer to a * string naming that Ethernet host. */ const char * ehost2(const struct ether_addr *ea) { static char hbuf[MAXHOSTNAMELEN+1]; if (ea == NULL) return ("NULL"); if (ether_ntohost(hbuf, ea) == 0) return (hbuf); return (ether_ntoa(ea)); } const char * ehost(const ppptun_atype *pap) { return (ehost2((const struct ether_addr *)pap)); } /* * Given an Internet address (in network byte order), return a pointer * to a string naming the host. */ const char * ihost(uint32_t haddr) { struct hostent *hp; struct sockaddr_in sin; (void) memset(&sin, '\0', sizeof (sin)); sin.sin_addr.s_addr = haddr; hp = gethostbyaddr((const char *)&sin, sizeof (sin), AF_INET); if (hp != NULL) return (hp->h_name); return (inet_ntoa(sin.sin_addr)); } int hexdecode(char chr) { if (chr >= '0' && chr <= '9') return ((int)(chr - '0')); if (chr >= 'a' && chr <= 'f') return ((int)(chr - 'a' + 10)); return ((int)(chr - 'A' + 10)); } /* * 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 common utilities and data. * * Copyright (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef PPPOE_COMMON_H #define PPPOE_COMMON_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #define PKT_INPUT_LEN PPPOE_MSGMAX #define PKT_OCTL_LEN (sizeof (struct ppptun_control) + 1) #define PKT_OUTPUT_LEN PPPOE_MSGMAX /* Common buffers */ extern uint32_t pkt_input[]; extern uint32_t pkt_octl[]; extern uint32_t pkt_output[]; /* Name of PPPoE tunnel driver */ extern const char tunnam[]; /* Name of application (from argv[0]) */ extern char *myname; /* Ethernet broadcast address */ extern const ether_addr_t ether_bcast; /* General purpose utility functions. */ struct strbuf; extern int strioctl(int fd, int cmd, void *ptr, int ilen, int olen); extern const char *ehost(const ppptun_atype *pap); extern const char *ehost2(const struct ether_addr *ea); extern const char *ihost(uint32_t haddr); extern int hexdecode(char chr); extern const char *mystrerror(int err); extern void myperror(const char *emsg); extern int mygetmsg(int fd, struct strbuf *ctrl, struct strbuf *data, int *flags); /* PPPoE-specific functions. */ extern poep_t *poe_mkheader(void *dptr, uint8_t codeval, int sessionid); extern boolean_t poe_tagcheck(const poep_t *poep, int length, const uint8_t *tptr); extern int poe_add_str(poep_t *poep, uint16_t ttype, const char *str); extern int poe_add_long(poep_t *poep, uint16_t ttype, uint32_t val); extern int poe_two_longs(poep_t *poep, uint16_t ttype, uint32_t val1, uint32_t val2); extern int poe_tag_copy(poep_t *poep, const uint8_t *tagp); extern const char *poe_tagname(uint16_t tagtype); extern const char *poe_codename(uint8_t codetype); /* These are here in case access wrappers are desired. */ #define poe_version_type(p) ((p)->poep_version_type) #define poe_code(p) ((p)->poep_code) #define poe_session_id(p) ntohs((p)->poep_session_id) #define poe_length(p) ntohs((p)->poep_length) #ifdef __cplusplus } #endif #endif /* PPPOE_COMMON_H */ /* * 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 Server-mode daemon log file support. * * Copyright (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "logging.h" /* Not all functions are used by all applications. Let lint know this. */ /*LINTLIBRARY*/ const char *prog_name = "none"; /* Subsystem name for syslog */ int log_level; /* Higher number for more detail. */ static int curlogfd = -1; /* Current log file */ static const char *curfname; /* Name of current log file */ static const char *stderr_name = "stderr"; #define SMALLSTR 254 /* Don't allocate for most strings. */ /* * Returns -1 on error (with errno set), 0 on blocked write (file * system full), or N (buffer length) on success. */ static int dowrite(int fd, const void *buf, int len) { int retv; const uint8_t *bp = (uint8_t *)buf; while (len > 0) { retv = write(fd, bp, len); if (retv == 0) { break; } if (retv == -1) { if (errno != EINTR) break; } else { bp += retv; len -= retv; } } if (len <= 0) return (bp - (uint8_t *)buf); return (retv); } /* A close that avoids closing stderr */ static int doclose(void) { int retval = 0; if (curlogfd == -1) return (0); if ((curlogfd != STDERR_FILENO) || (curfname != stderr_name)) retval = close(curlogfd); curlogfd = -1; return (retval); } /* * Log levels are 0 for no messages, 1 for errors, 2 for warnings, 3 * for informational messages, and 4 for debugging messages. */ static void vlogat(int loglev, const char *fmt, va_list args) { char timbuf[64]; char regbuf[SMALLSTR+2]; char *ostr; int timlen; int slen; char *nstr; int err1, err2; int sloglev; int retv; va_list args2; static int xlate_loglev[] = { LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG }; if (loglev >= log_level) return; timbuf[0] = '\0'; timlen = 0; if (curlogfd >= 0) { time_t now = time(NULL); /* * Form a time/date string for file (non-syslog) logging. * Caution: string broken in two so that SCCS doesn't mangle * the %-T-% sequence. */ timlen = strftime(timbuf, sizeof (timbuf), "%Y/%m/%d %T" "%Z: ", localtime(&now)); } /* Try formatting once into the small buffer. */ va_copy(args2, args); slen = vsnprintf(regbuf, SMALLSTR, fmt, args); if (slen < SMALLSTR) { ostr = regbuf; } else { /* * Length returned by vsnprintf doesn't include null, * and may also be missing a terminating \n. */ ostr = alloca(slen + 2); slen = vsnprintf(ostr, slen + 1, fmt, args2); } /* Don't bother logging empty lines. */ if (slen <= 0) return; /* Tack on a \n if needed. */ if (ostr[slen - 1] != '\n') { ostr[slen++] = '\n'; ostr[slen] = '\0'; } /* Translate our log levels into syslog standard values */ assert(loglev >= 0 && loglev < Dim(xlate_loglev)); sloglev = xlate_loglev[loglev]; /* Log each line separately */ for (; *ostr != '\0'; ostr = nstr + 1) { nstr = strchr(ostr, '\n'); /* Ignore zero-length lines. */ if (nstr == ostr) continue; slen = nstr - ostr + 1; /* * If we're supposed to be logging to a file, then try * that first. Ditch the file and revert to syslog if * any errors occur. */ if (curlogfd >= 0) { if ((retv = dowrite(curlogfd, timbuf, timlen)) > 0) retv = dowrite(curlogfd, ostr, slen); /* * If we've successfully logged this line, * then go do the next one. */ if (retv > 0) continue; /* Save errno (if any) and close log file */ err1 = errno; if (doclose() == -1) err2 = errno; else err2 = 0; /* * Recursion is safe here because we cleared * out curlogfd above. */ if (retv == -1) logerr("write log %s: %s", curfname, mystrerror(err1)); else logerr("cannot write %s", curfname); if (err2 == 0) logdbg("closed log %s", curfname); else logerr("closing log %s: %s", curfname, mystrerror(err2)); } syslog(sloglev, "%.*s", slen, ostr); } } /* Log at debug level */ void logdbg(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogat(LOGLVL_DBG, fmt, args); va_end(args); } /* Log informational messages */ void loginfo(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogat(LOGLVL_INFO, fmt, args); va_end(args); } /* Log warning messages */ void logwarn(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogat(LOGLVL_WARN, fmt, args); va_end(args); } /* Log error messages */ void logerr(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogat(LOGLVL_ERR, fmt, args); va_end(args); } /* Log a strerror message */ void logstrerror(const char *emsg) { logerr("%s: %s\n", emsg, mystrerror(errno)); } void log_to_stderr(int dbglvl) { log_level = dbglvl; if (curlogfd >= 0) close_log_files(); curlogfd = STDERR_FILENO; curfname = stderr_name; } /* * Set indicated log file and debug level. */ void log_for_service(const char *fname, int dbglvl) { int err1, err2; boolean_t closed; log_level = dbglvl; if (fname != NULL && (*fname == '\0' || strcasecmp(fname, "syslog") == 0)) fname = NULL; if (fname == NULL && curfname == NULL) return; err1 = err2 = 0; closed = B_FALSE; if (curlogfd >= 0) { if (fname == curfname || (fname != NULL && strcmp(fname, curfname) == 0)) { curfname = fname; return; } if (doclose() == -1) err1 = errno; closed = B_TRUE; } if (fname != NULL) { curlogfd = open(fname, O_WRONLY|O_APPEND|O_CREAT, 0600); if (curlogfd == -1) err2 = errno; } if (closed) { if (err1 == 0) logdbg("closed log %s", curfname); else logerr("closing log %s: %s", curfname, mystrerror(err1)); } if (fname != NULL) { if (err2 == 0) logdbg("opened log %s", fname); else logerr("opening log %s: %s", fname, mystrerror(err2)); } curfname = fname; } /* * Close any open log file. This is used for SIGHUP (to support log * file rotation) and when execing. */ void close_log_files(void) { int err = 0; if (curlogfd >= 0) { if (doclose() == -1) err = errno; if (err == 0) logdbg("closed log %s", curfname); else logerr("closing log %s: %s", curfname, mystrerror(err)); } } /* * Reopen syslog connection; in case it was closed. */ void reopen_log(void) { openlog(prog_name, LOG_PID | LOG_NDELAY | LOG_NOWAIT, LOG_DAEMON); /* I control the log level */ (void) setlogmask(LOG_UPTO(LOG_DEBUG)); } /* * 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 Server-mode daemon logging functions. * * Copyright (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef PPPOE_LOGGING_H #define PPPOE_LOGGING_H #ifdef __cplusplus extern "C" { #endif #define LOGLVL_DBG 3 #define LOGLVL_INFO 2 #define LOGLVL_WARN 1 #define LOGLVL_ERR 0 /* Functions in logging.c */ extern void logdbg(const char *fmt, ...); extern void loginfo(const char *fmt, ...); extern void logwarn(const char *fmt, ...); extern void logerr(const char *fmt, ...); extern void logstrerror(const char *emsg); extern void log_for_service(const char *fname, int dbglvl); extern void log_to_stderr(int dbglvl); extern void close_log_files(void); extern void reopen_log(void); /* Data in logging.c */ extern const char *prog_name; extern int log_level; /* Functions in options.c */ extern void global_logging(void); /* A handy macro. */ #ifndef Dim #define Dim(x) (sizeof (x) / sizeof (*(x))) #endif #ifdef __cplusplus } #endif #endif /* PPPOE_LOGGING_H */ /* * 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 Server-mode daemon option parsing. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "logging.h" #define MAX_KEYWORD 4096 /* Maximum token length */ #define MAX_NEST 32 /* Maximum ${$sub} nesting */ #define MAXARGS 256 /* Maximum number of pppd arguments */ /* * Client filter entry. These are linked in *reverse* order so that * the DAG created by file inclusion nesting works as expected. Since * the administrator who wrote the configuration expects "first * match," this means that tests against the filter list must actually * use "last match." */ struct filter_entry { struct filter_entry *fe_prev; /* Previous filter in list */ struct ether_addr fe_mac; /* MAC address */ struct ether_addr fe_mask; /* Mask for above address test */ uchar_t fe_isexcept; /* invert sense; exclude matching clients */ uchar_t fe_prevcopy; /* fe_prev points to copied list */ uchar_t fe_unused[2]; /* padding */ }; /* * Note: I would like to make the strings and filters here const, but * I can't because they have to be passed to free() during parsing. I * could work around this with offsetof() or data copies, but it's not * worth the effort. */ struct service_entry { const char *se_name; /* Name of service */ struct filter_entry *se_flist; /* Pointer to list of client filters */ uint_t se_flags; /* SEF_* flags (below) */ int se_debug; /* Debug level (0=nodebug) */ char *se_server; /* Server (AC) name */ char *se_pppd; /* Options for pppd */ char *se_path; /* Path to pppd executable */ char *se_extra; /* Extra options */ char *se_log; /* Log file */ uid_t se_uid; /* User ID */ gid_t se_gid; /* Group ID */ }; #define SEF_WILD 0x00000001 /* Offer in wildcard reply */ #define SEF_NOWILD 0x00000002 /* Don't offer in wildcard */ #define SEF_CFLIST 0x00000004 /* se_flist copied from global */ #define SEF_CSERVER 0x00000008 /* se_server copied from global */ #define SEF_CPPPD 0x00000010 /* se_pppd copied from global */ #define SEF_CPATH 0x00000020 /* se_path copied from global */ #define SEF_CEXTRA 0x00000040 /* se_extra copied from global */ #define SEF_CLOG 0x00000080 /* se_log copied from global */ #define SEF_UIDSET 0x00000100 /* se_uid has been set */ #define SEF_GIDSET 0x00000200 /* se_gid has been set */ #define SEF_DEBUGCLR 0x00000400 /* do not add se_debug from global */ #define SEF_CDEV 0x00000800 /* copied devs (parse only) */ /* * One of these is allocated per lower-level stream (device) that is * referenced by the configuration files. The queries are received * per device, and this structure allows us to find all of the * services that correspond to that device. */ struct device_entry { const char *de_name; const struct service_entry **de_services; int de_nservices; }; /* * This is the parsed configuration. While a new configuration is * being read, this is kept around until the new configuration is * ready, and then it is discarded in one operation. It has an array * of device entries (as above) -- one per referenced lower stream -- * and a pointer to the allocated parser information. The latter is * kept around because we reuse pointers rather than reallocating and * copying the data. There are thus multiple aliases to the dynamic * data, and the "owner" (for purposes of freeing the storage) is * considered to be this 'junk' list. */ struct option_state { const struct device_entry *os_devices; int os_ndevices; struct per_file *os_pfjunk; /* Kept for deallocation */ char **os_evjunk; /* ditto */ }; /* * This is the root pointer to the current parsed options. * This cannot be const because it's passed to free() when reparsing * options. */ static struct option_state *cur_options; /* Global settings for module-wide options. */ static struct service_entry glob_svc; /* * ******************************************************************* * Data structures generated during parsing. */ /* List of device names attached to one service */ struct device_list { struct device_list *dl_next; const char *dl_name; /* Name of one device */ }; /* Entry for a single defined service. */ struct service_list { struct service_entry sl_entry; /* Parsed service data */ struct service_list *sl_next; /* Next service entry */ struct parse_state *sl_parse; /* Back pointer to state */ struct device_list *sl_dev; /* List of devices */ int sl_serial; /* Serial number (conflict resolve) */ }; #define SESERIAL(x) ((struct service_list *)&(x))->sl_serial #define ISGLOBAL(x) ((x) == &(x)->sl_parse->ps_cfile->pf_global) /* * Structure allocated for each file opened. File nesting is chained * in reverse order so that global option scoping works as expected. */ struct per_file { struct per_file *pf_prev; /* Back chain */ struct service_list pf_global; /* Global (default) service context */ struct service_list *pf_svc; /* List of services */ struct service_list *pf_svc_last; FILE *pf_input; /* File for input */ const char *pf_name; /* File name */ int pf_nsvc; /* Count of services */ }; /* State of parser */ enum key_state { ksDefault, ksService, ksDevice, ksClient, ksClientE, ksServer, ksPppd, ksFile, ksPath, ksExtra, ksLog, ksUser, ksGroup }; /* * Global parser state. There is one of these structures, and it * exists only while actively parsing configuration files. */ struct parse_state { enum key_state ps_state; /* Parser state */ int ps_serial; /* Service serial number */ struct per_file *ps_files; /* Parsed files */ struct per_file *ps_cfile; /* Current file */ struct service_list *ps_csvc; /* Current service */ struct device_list *ps_star; /* Wildcard device */ int ps_flags; /* PSF_* below */ char **ps_evlist; /* allocated environment variables */ int ps_evsize; /* max length; for realloc */ }; #define PSF_PERDEV 0x0001 /* In a per-device file */ #define PSF_SETLEVEL 0x0002 /* Set log level along the way */ /* Should be in a library somewhere. */ static char * strsave(const char *str) { char *newstr; if (str == NULL) return (NULL); newstr = (char *)malloc(strlen(str) + 1); if (newstr != NULL) (void) strcpy(newstr, str); return (newstr); } /* * Stop defining current service and revert to global definition. * This resolves any implicit references to global options by copying * ("inheriting") from the current global state. */ static void close_service(struct service_list *slp) { struct parse_state *psp; struct per_file *cfile; struct service_entry *sep; struct service_entry *sedefp; struct filter_entry *fep; assert(slp != NULL); psp = slp->sl_parse; cfile = psp->ps_cfile; /* If no current file, then nothing to close. */ if (cfile == NULL) return; sep = &slp->sl_entry; /* * Fix up filter pointers to make DAG. First, locate * the end of the filter list. */ if (sep->se_flags & SEF_CFLIST) { sep->se_flist = fep = NULL; } else { for (fep = sep->se_flist; fep != NULL; fep = fep->fe_prev) if (fep->fe_prev == NULL || fep->fe_prevcopy) { fep->fe_prev = NULL; break; } } if (slp == &cfile->pf_global) { /* * If we're in a global context, then we're about to * open a new service, so it's time to fix up the * filter list so that it's usable as a reference. * Loop through files from which we were included, and * link up filters. Note: closure may occur more than * once here. */ /* We don't inherit from ourselves. */ cfile = cfile->pf_prev; while (cfile != NULL) { if (fep == NULL) { sep->se_flist = fep = cfile->pf_global.sl_entry.se_flist; sep->se_flags |= SEF_CFLIST; } else if (fep->fe_prev == NULL) { fep->fe_prev = cfile->pf_global.sl_entry.se_flist; fep->fe_prevcopy = 1; } cfile = cfile->pf_prev; } } else { /* * Loop through default options in current and all * enclosing include files. Inherit options. */ logdbg("service %s ends", slp->sl_entry.se_name); while (cfile != NULL) { /* Inherit from global service options. */ if (slp->sl_dev == NULL) { slp->sl_dev = cfile->pf_global.sl_dev; sep->se_flags |= SEF_CDEV; } sedefp = &cfile->pf_global.sl_entry; if (fep == NULL) { sep->se_flist = fep = sedefp->se_flist; sep->se_flags |= SEF_CFLIST; } else if (fep->fe_prev == NULL) { fep->fe_prev = sedefp->se_flist; fep->fe_prevcopy = 1; } if (sep->se_server == NULL) { sep->se_server = sedefp->se_server; sep->se_flags |= SEF_CSERVER; } if (sep->se_pppd == NULL) { sep->se_pppd = sedefp->se_pppd; sep->se_flags |= SEF_CPPPD; } if (sep->se_path == NULL) { sep->se_path = sedefp->se_path; sep->se_flags |= SEF_CPATH; } if (sep->se_extra == NULL) { sep->se_extra = sedefp->se_extra; sep->se_flags |= SEF_CEXTRA; } if (sep->se_log == NULL) { sep->se_log = sedefp->se_log; sep->se_flags |= SEF_CLOG; } if (!(sep->se_flags & SEF_UIDSET) && (sedefp->se_flags & SEF_UIDSET)) { sep->se_uid = sedefp->se_uid; sep->se_flags |= SEF_UIDSET; } if (!(sep->se_flags & SEF_GIDSET) && (sedefp->se_flags & SEF_GIDSET)) { sep->se_gid = sedefp->se_gid; sep->se_flags |= SEF_GIDSET; } if (!(sep->se_flags & (SEF_WILD|SEF_NOWILD))) sep->se_flags |= sedefp->se_flags & (SEF_WILD|SEF_NOWILD); if (!(sep->se_flags & SEF_DEBUGCLR)) { sep->se_debug += sedefp->se_debug; sep->se_flags |= sedefp->se_flags & SEF_DEBUGCLR; } cfile = cfile->pf_prev; } } /* Revert to global definitions. */ psp->ps_csvc = &psp->ps_cfile->pf_global; } /* Discard a dynamic device list */ static void free_device_list(struct device_list *dlp) { struct device_list *dln; while (dlp != NULL) { dln = dlp->dl_next; free(dlp); dlp = dln; } } /* * Handle "service " -- finish up previous service definition * (if any) by copying from global state where necessary, and start * defining new service. */ static int set_service(struct service_list *slp, const char *str) { struct parse_state *psp; struct per_file *cfile; /* Finish current service */ close_service(slp); /* Start new service */ psp = slp->sl_parse; slp = (struct service_list *)calloc(sizeof (*slp) + strlen(str) + 1, 1); if (slp == NULL) { logerr("no memory for service \"%s\"", str); return (-1); } /* Add to end of list */ cfile = psp->ps_cfile; if (cfile->pf_svc_last == NULL) cfile->pf_svc = slp; else cfile->pf_svc_last->sl_next = slp; cfile->pf_svc_last = slp; cfile->pf_nsvc++; /* Fill in initial service entry */ slp->sl_entry.se_name = (const char *)(slp+1); (void) strcpy((char *)(slp+1), str); logdbg("service %s begins", slp->sl_entry.se_name); slp->sl_serial = psp->ps_serial++; slp->sl_parse = psp; /* This is now the current service that we're defining. */ psp->ps_csvc = slp; return (0); } /* * Handle both "wildcard" and "nowildcard" options. */ static int set_wildcard(struct service_list *slp, const char *str) { /* Allow global context to switch back and forth without error. */ if (!ISGLOBAL(slp) && (slp->sl_entry.se_flags & (SEF_WILD|SEF_NOWILD))) { logdbg("%s: extra \"%s\" ignored", slp->sl_parse->ps_cfile->pf_name, str); return (0); } slp->sl_entry.se_flags = (slp->sl_entry.se_flags & ~(SEF_WILD|SEF_NOWILD)) | (*str == 'n' ? SEF_NOWILD : SEF_WILD); return (0); } /* * Handle "debug" option. */ /*ARGSUSED*/ static int set_debug(struct service_list *slp, const char *str) { slp->sl_entry.se_debug++; if (ISGLOBAL(slp) && (slp->sl_parse->ps_flags & PSF_SETLEVEL)) { log_level = slp->sl_entry.se_debug; } return (0); } /* * Handle "nodebug" option. */ /*ARGSUSED*/ static int set_nodebug(struct service_list *slp, const char *str) { slp->sl_entry.se_flags |= SEF_DEBUGCLR; slp->sl_entry.se_debug = 0; if (ISGLOBAL(slp) && (slp->sl_parse->ps_flags & PSF_SETLEVEL)) { log_level = slp->sl_entry.se_debug; } return (0); } /* * Handle all plain string options; "server", "pppd", "path", "extra", * and "log". */ static int set_string(struct service_list *slp, const char *str) { char **cpp; assert(!(slp->sl_entry.se_flags & (SEF_CSERVER|SEF_CPPPD|SEF_CPATH|SEF_CEXTRA|SEF_CLOG))); switch (slp->sl_parse->ps_state) { case ksServer: cpp = &slp->sl_entry.se_server; break; case ksPppd: cpp = &slp->sl_entry.se_pppd; break; case ksPath: cpp = &slp->sl_entry.se_path; break; case ksExtra: cpp = &slp->sl_entry.se_extra; break; case ksLog: cpp = &slp->sl_entry.se_log; break; default: assert(0); return (-1); } if (*cpp != NULL) free(*cpp); *cpp = strsave(str); return (0); } /* * Handle "file " option. Close out current service (if any) * and begin parsing from new file. */ static int set_file(struct service_list *slp, const char *str) { FILE *fp; struct per_file *pfp; struct parse_state *psp; close_service(slp); if ((fp = fopen(str, "r")) == NULL) { logwarn("%s: %s: %s", slp->sl_parse->ps_cfile->pf_name, str, mystrerror(errno)); return (-1); } pfp = (struct per_file *)calloc(sizeof (*pfp) + strlen(str) + 1, 1); if (pfp == NULL) { logerr("no memory for parsing file %s", str); (void) fclose(fp); return (-1); } logdbg("config file %s open", str); /* Fill in new file structure. */ pfp->pf_name = (const char *)(pfp+1); (void) strcpy((char *)(pfp+1), str); pfp->pf_input = fp; psp = slp->sl_parse; pfp->pf_prev = psp->ps_cfile; psp->ps_cfile = pfp; /* Start off in global context for this file. */ psp->ps_csvc = &pfp->pf_global; pfp->pf_global.sl_parse = psp; pfp->pf_global.sl_entry.se_name = ""; return (0); } /* * Handle "device " option. */ static int set_device(struct service_list *slp, const char *str) { struct parse_state *psp = slp->sl_parse; struct device_list *dlp; struct device_list *dln; struct device_list **dlpp; const char *cp; int len; /* Can't use this option in the per-device files. */ if (psp->ps_flags & PSF_PERDEV) { logerr("\"device %s\" ignored in %s", str, psp->ps_cfile->pf_name); return (0); } if (strcmp(str, "*") == 0 || strcmp(str, "all") == 0) { if (!(slp->sl_entry.se_flags & SEF_CDEV)) free_device_list(slp->sl_dev); slp->sl_dev = psp->ps_star; slp->sl_entry.se_flags |= SEF_CDEV; } else { dlpp = &dlp; for (;;) { while (isspace(*str) || *str == ',') str++; if (*str == '\0') break; cp = str; while (*str != '\0' && !isspace(*str) && *str != ',') str++; len = str - cp; if ((len == 1 && *cp == '*') || (len == 3 && strncmp(cp, "all", 3) == 0)) { logerr("%s: cannot use %.*s in device list", psp->ps_cfile->pf_name, len, cp); continue; } dln = (struct device_list *)malloc(sizeof (*dln) + len + 1); if (dln == NULL) { logerr("no memory for device name"); break; } dln->dl_name = (const char *)(dln + 1); /* Cannot use strcpy because cp isn't terminated. */ (void) memcpy(dln + 1, cp, len); ((char *)(dln + 1))[len] = '\0'; logdbg("%s: device %s", psp->ps_cfile->pf_name, dln->dl_name); *dlpp = dln; dlpp = &dln->dl_next; } *dlpp = NULL; dlpp = &slp->sl_dev; if (!(slp->sl_entry.se_flags & SEF_CDEV)) while (*dlpp != NULL) dlpp = &(*dlpp)->dl_next; *dlpp = dlp; slp->sl_entry.se_flags &= ~SEF_CDEV; } return (0); } /* * Handle portion of "client [except] " option. Attach * to list of filters in reverse order. */ static int set_client(struct service_list *slp, const char *str) { struct parse_state *psp = slp->sl_parse; struct filter_entry *fep; struct filter_entry *fen; const char *cp; int len; char hbuf[MAXHOSTNAMELEN]; struct ether_addr ea; struct ether_addr mask; uchar_t *ucp; uchar_t *mcp; /* Head of list. */ fep = slp->sl_entry.se_flist; for (;;) { while (isspace(*str) || *str == ',') str++; if (*str == '\0') break; cp = str; while (*str != '\0' && !isspace(*str) && *str != ',') str++; len = str - cp; (void) memcpy(hbuf, cp, len); hbuf[len] = '\0'; mcp = mask.ether_addr_octet; mcp[0] = mcp[1] = mcp[2] = mcp[3] = mcp[4] = mcp[5] = 0xFF; if (ether_hostton(hbuf, &ea) != 0) { ucp = ea.ether_addr_octet; while (cp < str) { if (ucp >= ea.ether_addr_octet + sizeof (ea)) break; if (*cp == '*') { *mcp++ = *ucp++ = 0; cp++; } else { if (!isxdigit(*cp)) break; *ucp = hexdecode(*cp++); if (cp < str && isxdigit(*cp)) { *ucp = (*ucp << 4) | hexdecode(*cp++); } ucp++; *mcp++ = 0xFF; } if (cp < str) { if (*cp != ':' || cp + 1 == str) break; cp++; } } if (cp < str) { logerr("%s: illegal Ethernet address %.*s", psp->ps_cfile->pf_name, len, cp); continue; } } fen = (struct filter_entry *)malloc(sizeof (*fen)); if (fen == NULL) { logerr("unable to allocate memory for filter"); break; } fen->fe_isexcept = psp->ps_state == ksClientE; fen->fe_prevcopy = 0; (void) memcpy(&fen->fe_mac, &ea, sizeof (fen->fe_mac)); (void) memcpy(&fen->fe_mask, &mask, sizeof (fen->fe_mask)); fen->fe_prev = fep; fep = fen; } slp->sl_entry.se_flist = fep; return (0); } /* * Handle "user " option. */ static int set_user(struct service_list *slp, const char *str) { struct passwd *pw; char *cp; uid_t myuid, uid; if ((pw = getpwnam(str)) == NULL) { uid = (uid_t)strtol(str, &cp, 0); if (str == cp || *cp != '\0') { logerr("%s: bad user name \"%s\"", slp->sl_parse->ps_cfile->pf_name, str); return (0); } } else { uid = pw->pw_uid; } slp->sl_entry.se_uid = uid; myuid = getuid(); if (myuid != 0) { if (myuid == uid) return (0); logdbg("%s: not root; ignoring attempt to set UID %d (%s)", slp->sl_parse->ps_cfile->pf_name, uid, str); return (0); } slp->sl_entry.se_flags |= SEF_UIDSET; return (0); } /* * Handle "group " option. */ static int set_group(struct service_list *slp, const char *str) { struct group *gr; char *cp; gid_t gid; if ((gr = getgrnam(str)) == NULL) { gid = (gid_t)strtol(str, &cp, 0); if (str == cp || *cp != '\0') { logerr("%s: bad group name \"%s\"", slp->sl_parse->ps_cfile->pf_name, str); return (0); } } else { gid = gr->gr_gid; } slp->sl_entry.se_gid = gid; if (getuid() != 0) { logdbg("%s: not root; ignoring attempt to set GID %d (%s)", slp->sl_parse->ps_cfile->pf_name, gid, str); return (0); } slp->sl_entry.se_flags |= SEF_GIDSET; return (0); } /* * This state machine is used to parse the configuration files. The * "kwe_in" is the state in which the keyword is recognized. The * "kwe_out" is the state that the keyword produces. */ struct kw_entry { const char *kwe_word; enum key_state kwe_in; enum key_state kwe_out; int (*kwe_func)(struct service_list *slp, const char *str); }; static const struct kw_entry key_list[] = { { "service", ksDefault, ksService, NULL }, { "device", ksDefault, ksDevice, NULL }, { "client", ksDefault, ksClient, NULL }, { "except", ksClient, ksClientE, NULL }, { "wildcard", ksDefault, ksDefault, set_wildcard }, { "nowildcard", ksDefault, ksDefault, set_wildcard }, { "server", ksDefault, ksServer, NULL }, { "pppd", ksDefault, ksPppd, NULL }, { "debug", ksDefault, ksDefault, set_debug }, { "nodebug", ksDefault, ksDefault, set_nodebug }, { "file", ksDefault, ksFile, NULL }, { "path", ksDefault, ksPath, NULL }, { "extra", ksDefault, ksExtra, NULL }, { "log", ksDefault, ksLog, NULL }, { "user", ksDefault, ksUser, NULL }, { "group", ksDefault, ksGroup, NULL }, /* Wildcards only past this point. */ { "", ksService, ksDefault, set_service }, { "", ksDevice, ksDefault, set_device }, { "", ksClient, ksDefault, set_client }, { "", ksClientE, ksDefault, set_client }, { "", ksServer, ksDefault, set_string }, { "", ksPppd, ksDefault, set_string }, { "", ksFile, ksDefault, set_file }, { "", ksPath, ksDefault, set_string }, { "", ksExtra, ksDefault, set_string }, { "", ksLog, ksDefault, set_string }, { "", ksUser, ksDefault, set_user }, { "", ksGroup, ksDefault, set_group }, { NULL, ksDefault, ksDefault, NULL } }; /* * Produce a string for the keyword that would have gotten us into the * current state. */ static const char * after_key(enum key_state kstate) { const struct kw_entry *kep; for (kep = key_list; kep->kwe_word != NULL; kep++) if (kep->kwe_out == kstate) return (kep->kwe_word); return ("nothing"); } /* * Handle end-of-file processing -- close service, close file, revert * to global context in previous include file nest level. */ static void file_end(struct parse_state *psp) { struct per_file *pfp; /* Must not be in the middle of parsing a multi-word sequence now. */ if (psp->ps_state != ksDefault) { logerr("%s ends with \"%s\"", psp->ps_cfile->pf_name, after_key(psp->ps_state)); psp->ps_state = ksDefault; } close_service(psp->ps_csvc); if ((pfp = psp->ps_cfile) != NULL) { /* Put this file on the list of finished files. */ psp->ps_cfile = pfp->pf_prev; pfp->pf_prev = psp->ps_files; psp->ps_files = pfp; if (pfp->pf_input != NULL) { logdbg("file %s closed", pfp->pf_name); (void) fclose(pfp->pf_input); pfp->pf_input = NULL; } /* Back up to previous file, if any, and set global context. */ if ((pfp = psp->ps_cfile) != NULL) psp->ps_csvc = &pfp->pf_global; } } /* * Dispatch a single keyword against the parser state machine or * handle an environment variable assignment. The input is a string * containing the single word to be dispatched. */ static int dispatch_keyword(struct parse_state *psp, const char *keybuf) { const struct kw_entry *kep; int retv; char *cp; char *env; char **evlist; int len; retv = 0; for (kep = key_list; kep->kwe_word != NULL; kep++) { if (kep->kwe_in == psp->ps_state && (*kep->kwe_word == '\0' || strcasecmp(kep->kwe_word, keybuf) == 0)) { if (kep->kwe_func != NULL) retv = (*kep->kwe_func)(psp->ps_csvc, keybuf); psp->ps_state = kep->kwe_out; return (retv); } } if (strchr(keybuf, '=') != NULL) { if ((cp = strsave(keybuf)) == NULL) { logerr("no memory to save %s", keybuf); return (0); } len = (strchr(cp, '=') - cp) + 1; if ((evlist = psp->ps_evlist) == NULL) { psp->ps_evlist = evlist = (char **)malloc(8 * sizeof (*evlist)); if (evlist == NULL) { logerr("no memory for evlist"); free(cp); return (0); } psp->ps_evsize = 8; evlist[0] = evlist[1] = NULL; } else { while ((env = *evlist) != NULL) { if (strncmp(cp, env, len) == 0) break; evlist++; } if (env == NULL && evlist-psp->ps_evlist >= psp->ps_evsize-1) { evlist = (char **)realloc(psp->ps_evlist, (psp->ps_evsize + 8) * sizeof (*evlist)); if (evlist == NULL) { logerr("cannot realloc evlist to %d", psp->ps_evsize + 8); free(cp); return (0); } psp->ps_evlist = evlist; evlist += psp->ps_evsize - 1; psp->ps_evsize += 8; evlist[1] = NULL; } } logdbg("setenv \"%s\"", cp); if (*evlist != NULL) free(*evlist); *evlist = cp; return (0); } logerr("%s: unknown keyword '%s'", psp->ps_cfile->pf_name, keybuf); return (-1); } /* * Modified version of standard getenv; looks in locally-stored * environment first. This function exists because we need to be able * to revert to the original environment during a reread (SIGHUP), and * the putenv() function overwrites that environment. */ static char * my_getenv(struct parse_state *psp, char *estr) { char **evlist, *ent; int elen; if (psp != NULL && (evlist = psp->ps_evlist) != NULL) { elen = strlen(estr); while ((ent = *evlist++) != NULL) { if (strncmp(ent, estr, elen) == 0 && ent[elen] == '=') return (ent + elen + 1); } } return (getenv(estr)); } /* * Expand an environment variable at the end of current buffer and * return pointer to next spot in buffer for character append. psp * context may be null. */ static char * env_replace(struct parse_state *psp, char *keybuf, char kwstate) { char *cpe; char *cp; if ((cp = strrchr(keybuf, kwstate)) != NULL) { if ((cpe = my_getenv(psp, cp + 1)) != NULL) { *cp = '\0'; (void) strncat(cp, cpe, MAX_KEYWORD - (cp - keybuf) - 1); keybuf[MAX_KEYWORD - 1] = '\0'; cp += strlen(cp); } else { logerr("unknown variable \"%s\"", cp + 1); } } else { /* Should not occur. */ cp = keybuf + strlen(keybuf); } return (cp); } /* * Given a character-at-a-time input function, get a delimited keyword * from the input. This function handles the usual escape sequences, * quoting, commenting, and environment variable expansion. * * The standard wordexp(3C) function isn't used here because the POSIX * definition is hard to use, and the Solaris implementation is * resource-intensive and insecure. The "hard-to-use" part is that * wordexp expands only variables from the environment, and can't * handle an environment overlay. Instead, the caller must use the * feeble putenv/getenv interface, and rewinding to the initial * environment without leaking storage is hard. The Solaris * implementation invokes an undocumented extensions via * fork/exec("/bin/ksh -\005 %s") for every invocation, and gathers * the expanded result with pipe. This makes it slow to execute and * exposes the string being expanded to users with access to "ps -f." * * psp may be null; it's used only for environment variable expansion. * Input "flag" is 1 to ignore EOL, '#', and '$'; 0 for normal file parsing. * * Returns: * 0 - keyword parsed. * 1 - end of file; no keyword. * 2 - end of file after this keyword. */ static int getkeyword(struct parse_state *psp, char *keybuf, int keymax, int (*nextchr)(void *), void *arg, int flag) { char varnest[MAX_NEST]; char *kbp; char *vnp; char chr; int ichr; char kwstate; static const char escstr[] = "a\ab\bf\fn\nr\r"; const char *cp; keymax--; /* Account for trailing NUL byte */ kwstate = '\0'; kbp = keybuf; vnp = varnest; for (;;) { ichr = (*nextchr)(arg); chr = (char)ichr; tryagain: switch (kwstate) { case '\\': /* Start of unquoted escape sequence */ case '|': /* Start of escape sequence in double quotes */ case '~': /* Start of escape sequence in single quotes */ /* Convert the character if we can. */ if (chr == '\n') chr = '\0'; else if (isalpha(chr) && (cp = strchr(escstr, chr)) != NULL) chr = cp[1]; /* Revert to previous state */ switch (kwstate) { case '\\': kwstate = 'A'; break; case '|': kwstate = '"'; break; case '~': kwstate = '\''; break; } break; case '"': /* In double-quote string */ if (!flag && chr == '$') { /* Handle variable expansion. */ kwstate = '%'; chr = '\0'; break; } /* FALLTHROUGH */ case '\'': /* In single-quote string */ if (chr == '\\') { /* Handle start of escape sequence */ kwstate = kwstate == '"' ? '|' : '~'; chr = '\0'; break; } if (chr == kwstate) { /* End of quoted string; revert to normal */ kwstate = 'A'; chr = '\0'; } break; case '$': /* Start of unquoted variable name */ case '%': /* Start of variable name in quoted string */ if (chr == '{') { /* Variable name is bracketed. */ kwstate = chr = kwstate == '$' ? '{' : '['; break; } *kbp++ = kwstate = kwstate == '$' ? '+' : '*'; /* FALLTHROUGH */ case '+': /* Gathering unquoted variable name */ case '*': /* Gathering variable name in quoted string */ if (chr == '$' && vnp < varnest + sizeof (varnest)) { *vnp++ = kwstate; kwstate = '$'; chr = '\0'; break; } if (!isalnum(chr) && chr != '_' && chr != '.' && chr != '-') { *kbp = '\0'; kbp = env_replace(psp, keybuf, kwstate); if (vnp > varnest) kwstate = *--vnp; else kwstate = kwstate == '+' ? 'A' : '"'; /* Go reinterpret in new context */ goto tryagain; } break; case '{': /* Gathering bracketed, unquoted var name */ case '[': /* Gathering bracketed, quoted var name */ if (chr == '}') { *kbp = '\0'; kbp = env_replace(psp, keybuf, kwstate); kwstate = kwstate == '{' ? 'A' : '"'; chr = '\0'; } break; case '#': /* Comment before word state */ case '@': /* Comment after word state */ if (chr == '\n' || chr == '\r' || ichr == EOF) { /* At end of line, revert to previous state */ kwstate = kwstate == '#' ? '\0' : ' '; chr = '\0'; break; } chr = '\0'; break; case '\0': /* Initial state; no word seen yet. */ if (ichr == EOF || isspace(chr)) { chr = '\0'; /* Skip over leading spaces */ break; } if (chr == '#') { kwstate = '#'; chr = '\0'; /* Skip over comments */ break; } /* Start of keyword seen. */ kwstate = 'A'; /* FALLTHROUGH */ default: /* Middle of keyword parsing. */ if (ichr == EOF) break; if (isspace(chr)) { /* Space terminates word */ kwstate = ' '; break; } if (chr == '"' || chr == '\'' || chr == '\\') { kwstate = chr; /* Begin quote or escape */ chr = '\0'; break; } if (flag) /* Allow ignore; for string reparse */ break; if (chr == '#') { /* Comment terminates word */ kwstate = '@'; /* Must consume comment also */ chr = '\0'; break; } if (chr == '$') { kwstate = '$'; /* Begin variable expansion */ chr = '\0'; } break; } /* * If we've reached a space at the end of the word, * then we're done. */ if (ichr == EOF || kwstate == ' ') break; /* * If there's a character to store and space * available, then add it to the string */ if (chr != '\0' && kbp < keybuf + keymax) *kbp++ = (char)chr; } *kbp = '\0'; if (ichr == EOF) { return (kwstate == '\0' ? 1 : 2); } return (0); } /* * Fetch words from current file until all files are closed. Handles * include files. */ static void parse_from_file(struct parse_state *psp) { char keybuf[MAX_KEYWORD]; int retv; while (psp->ps_cfile != NULL && psp->ps_cfile->pf_input != NULL) { retv = getkeyword(psp, keybuf, sizeof (keybuf), (int (*)(void *))fgetc, (void *)psp->ps_cfile->pf_input, 0); if (retv != 1) (void) dispatch_keyword(psp, keybuf); if (retv != 0) file_end(psp); } } /* * Open and parse named file. This is for the predefined * configuration files in /etc/ppp -- it's not an error if any of * these are missing. */ static void parse_file(struct parse_state *psp, const char *fname) { struct stat sb; /* It's ok if any of these files are missing. */ if (stat(fname, &sb) == -1 && errno == ENOENT) return; if (set_file(psp->ps_csvc, fname) == 0) parse_from_file(psp); } /* * Dispatch keywords from command line. Handles any files included * from there. */ static void parse_arg_list(struct parse_state *psp, int argc, char **argv) { /* The first argument (program name) can be null. */ if (--argc <= 0) return; while (--argc >= 0) { (void) dispatch_keyword(psp, *++argv); if (psp->ps_cfile->pf_input != NULL) parse_from_file(psp); } } /* Count length of dynamic device list */ static int count_devs(struct device_list *dlp) { int ndevs; ndevs = 0; for (; dlp != NULL; dlp = dlp->dl_next) ndevs++; return (ndevs); } /* Count number of devices named in entire file. */ static int count_per_file(struct per_file *pfp) { struct service_list *slp; int ndevs = 0; for (; pfp != NULL; pfp = pfp->pf_prev) { ndevs += count_devs(pfp->pf_global.sl_dev); for (slp = pfp->pf_svc; slp != NULL; slp = slp->sl_next) if (!(slp->sl_entry.se_flags & SEF_CDEV)) ndevs += count_devs(slp->sl_dev); } return (ndevs); } /* Write device names into linear array. */ static const char ** devs_to_list(struct device_list *dlp, const char **dnames) { for (; dlp != NULL; dlp = dlp->dl_next) *dnames++ = dlp->dl_name; return (dnames); } /* Write all device names from file into a linear array. */ static const char ** per_file_to_list(struct per_file *pfp, const char **dnames) { struct service_list *slp; for (; pfp != NULL; pfp = pfp->pf_prev) { dnames = devs_to_list(pfp->pf_global.sl_dev, dnames); for (slp = pfp->pf_svc; slp != NULL; slp = slp->sl_next) if (!(slp->sl_entry.se_flags & SEF_CDEV)) dnames = devs_to_list(slp->sl_dev, dnames); } return (dnames); } /* Compare device names; used with qsort */ static int devcmp(const void *d1, const void *d2) { return (strcmp(*(const char **)d1, *(const char **)d2)); } /* * Get sorted list of unique device names among all defined and * partially defined services in all files. */ static const char ** get_unique_devs(struct parse_state *psp) { int ndevs; const char **dnames; const char **dnp; const char **dnf; /* * Count number of explicitly referenced devices among all * services (including duplicates). */ ndevs = count_per_file(psp->ps_files); ndevs += count_per_file(psp->ps_cfile); if (ndevs <= 0) { return (NULL); } /* Sort and trim out duplicate devices. */ dnames = (const char **)malloc((ndevs+1) * sizeof (const char *)); if (dnames == NULL) { logerr("unable to allocate space for %d devices", ndevs + 1); return (NULL); } dnp = per_file_to_list(psp->ps_files, dnames); (void) per_file_to_list(psp->ps_cfile, dnp); qsort(dnames, ndevs, sizeof (const char *), devcmp); for (dnf = (dnp = dnames) + 1; dnf < dnames+ndevs; dnf++) if (strcmp(*dnf, *dnp) != 0) *++dnp = *dnf; *++dnp = NULL; /* Return array of pointers to names. */ return (dnames); } /* * Convert data structures created by parsing process into data * structures used by service dispatch. This gathers the unique * device (lower stream) names and attaches the services available on * each device to a list while triming duplicate services. */ static struct option_state * organize_state(struct parse_state *psp) { struct per_file *pfp; struct per_file *pftopp; struct service_list *slp; struct device_list *dlp; int ndevs; int nsvcs; const char **dnames; const char **dnp; struct device_entry *dep; struct option_state *osp; struct service_entry **sepp; struct service_entry **sebpp; struct service_entry **se2pp; /* * Parsing is now done. */ close_service(psp->ps_csvc); psp->ps_csvc = NULL; if ((pfp = psp->ps_cfile) != NULL) { pfp->pf_prev = psp->ps_files; psp->ps_files = pfp; psp->ps_cfile = NULL; } /* Link the services from all files together for easy referencing. */ pftopp = psp->ps_files; for (pfp = pftopp->pf_prev; pfp != NULL; pfp = pfp->pf_prev) if (pfp->pf_svc != NULL) { if (pftopp->pf_svc_last == NULL) pftopp->pf_svc = pfp->pf_svc; else pftopp->pf_svc_last->sl_next = pfp->pf_svc; pftopp->pf_svc_last = pfp->pf_svc_last; pfp->pf_svc = pfp->pf_svc_last = NULL; } /* * Count up number of services per device, including * duplicates but not including defaults. */ nsvcs = 0; for (slp = psp->ps_files->pf_svc; slp != NULL; slp = slp->sl_next) for (dlp = slp->sl_dev; dlp != NULL; dlp = dlp->dl_next) nsvcs++; /* * Get the unique devices referenced by all services. */ dnames = get_unique_devs(psp); if (dnames == NULL) { logdbg("no devices referenced by any service"); return (NULL); } ndevs = 0; for (dnp = dnames; *dnp != NULL; dnp++) ndevs++; /* * Allocate room for main structure, device records, and * per-device lists. Worst case is all devices having all * services; that's why we allocate for nsvcs * ndevs. */ osp = (struct option_state *)malloc(sizeof (*osp) + ndevs * sizeof (*dep) + nsvcs * ndevs * sizeof (*sepp)); if (osp == NULL) { logerr("unable to allocate option state structure"); free(dnames); return (NULL); } /* We're going to succeed now, so steal these over. */ osp->os_devices = dep = (struct device_entry *)(osp+1); osp->os_pfjunk = psp->ps_files; psp->ps_files = NULL; osp->os_evjunk = psp->ps_evlist; psp->ps_evlist = NULL; /* Loop over devices, install services, remove duplicates. */ sepp = (struct service_entry **)(dep + ndevs); for (dnp = dnames; *dnp != NULL; dnp++) { dep->de_name = *dnp; dep->de_services = (const struct service_entry **)sepp; sebpp = sepp; for (slp = osp->os_pfjunk->pf_svc; slp != NULL; slp = slp->sl_next) for (dlp = slp->sl_dev; dlp != NULL; dlp = dlp->dl_next) { if (dlp->dl_name == *dnp || strcmp(dlp->dl_name, *dnp) == 0) { for (se2pp = sebpp; se2pp < sepp; se2pp++) if ((*se2pp)->se_name == slp->sl_entry.se_name || strcmp((*se2pp)-> se_name, slp->sl_entry. se_name) == 0) break; /* * We retain a service if it's * unique or if its serial * number (position in the * file) is greater than than * any other. */ if (se2pp >= sepp) *sepp++ = &slp->sl_entry; else if (SESERIAL(**se2pp) < SESERIAL(slp->sl_entry)) *se2pp = &slp->sl_entry; } } /* Count up the services on this device. */ dep->de_nservices = (const struct service_entry **)sepp - dep->de_services; /* Ignore devices having no services at all. */ if (dep->de_nservices > 0) dep++; } /* Count up the devices. */ osp->os_ndevices = dep - osp->os_devices; /* Free the list of device names */ free(dnames); return (osp); } /* * Free storage unique to a given service. Pointers copied from other * services are ignored. */ static void free_service(struct service_list *slp) { struct filter_entry *fep; struct filter_entry *fen; if (!(slp->sl_entry.se_flags & SEF_CDEV)) free_device_list(slp->sl_dev); if (!(slp->sl_entry.se_flags & SEF_CFLIST)) { fep = slp->sl_entry.se_flist; while (fep != NULL) { fen = fep->fe_prevcopy ? NULL : fep->fe_prev; free(fep); fep = fen; } } if (!(slp->sl_entry.se_flags & SEF_CPPPD) && slp->sl_entry.se_pppd != NULL) free(slp->sl_entry.se_pppd); if (!(slp->sl_entry.se_flags & SEF_CSERVER) && slp->sl_entry.se_server != NULL) free(slp->sl_entry.se_server); if (!(slp->sl_entry.se_flags & SEF_CPATH) && slp->sl_entry.se_path != NULL) free(slp->sl_entry.se_path); if (!(slp->sl_entry.se_flags & SEF_CEXTRA) && slp->sl_entry.se_extra != NULL) free(slp->sl_entry.se_extra); if (!(slp->sl_entry.se_flags & SEF_CLOG) && slp->sl_entry.se_log != NULL) free(slp->sl_entry.se_log); } /* * Free a linked list of services. */ static void free_service_list(struct service_list *slp) { struct service_list *sln; while (slp != NULL) { free_service(slp); sln = slp->sl_next; free(slp); slp = sln; } } /* * Free a linked list of files and all services in those files. */ static void free_file_list(struct per_file *pfp) { struct per_file *pfn; while (pfp != NULL) { free_service(&pfp->pf_global); free_service_list(pfp->pf_svc); pfn = pfp->pf_prev; free(pfp); pfp = pfn; } } /* * Free an array of local environment variables. */ static void free_env_list(char **evlist) { char **evp; char *env; if ((evp = evlist) != NULL) { while ((env = *evp++) != NULL) free(env); free(evlist); } } /* * Add a new device (lower stream) to the list for which we're the * PPPoE server. */ static void add_new_dev(int tunfd, const char *dname) { union ppptun_name ptn; (void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%s:pppoed", dname); if (strioctl(tunfd, PPPTUN_SCTL, &ptn, sizeof (ptn), 0) < 0) { logerr("PPPTUN_SCTL %s: %s", ptn.ptn_name, mystrerror(errno)); } else { logdbg("added %s", ptn.ptn_name); } } /* * Remove an existing device (lower stream) from the list for which we * were the PPPoE server. */ static void rem_old_dev(int tunfd, const char *dname) { union ppptun_name ptn; (void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%s:pppoed", dname); if (strioctl(tunfd, PPPTUN_DCTL, &ptn, sizeof (ptn), 0) < 0) { logerr("PPPTUN_DCTL %s: %s", ptn.ptn_name, mystrerror(errno)); } else { logdbg("removed %s", ptn.ptn_name); } } /* * Get a list of all of the devices currently plumbed for PPPoE. This * is used for supporting the "*" and "all" device aliases. */ static void get_device_list(struct parse_state *psp, int tunfd) { struct device_list *dlp; struct device_list **dlpp; struct device_list *dlalt; struct device_list **dl2pp; struct device_list *dla; int i; union ppptun_name ptn; char *cp; /* First pass; just allocate space for all *:pppoe* devices */ dlpp = &psp->ps_star; dl2pp = &dlalt; for (i = 0; ; i++) { ptn.ptn_index = i; if (strioctl(tunfd, PPPTUN_GNNAME, &ptn, sizeof (ptn), sizeof (ptn)) < 0) { logerr("PPPTUN_GNNAME %d: %s", i, mystrerror(errno)); break; } if (ptn.ptn_name[0] == '\0') break; if ((cp = strchr(ptn.ptn_name, ':')) == NULL || strncmp(cp, ":pppoe", 6) != 0 || (cp[6] != '\0' && strcmp(cp+6, "d") != 0)) continue; *cp = '\0'; dlp = (struct device_list *)malloc(sizeof (*dlp) + strlen(ptn.ptn_name) + 1); if (dlp == NULL) break; dlp->dl_name = (const char *)(dlp + 1); (void) strcpy((char *)(dlp + 1), ptn.ptn_name); if (cp[6] == '\0') { *dlpp = dlp; dlpp = &dlp->dl_next; } else { *dl2pp = dlp; dl2pp = &dlp->dl_next; } } *dlpp = NULL; *dl2pp = NULL; /* Second pass; eliminate improperly plumbed devices */ for (dlpp = &psp->ps_star; (dlp = *dlpp) != NULL; ) { for (dla = dlalt; dla != NULL; dla = dla->dl_next) if (strcmp(dla->dl_name, dlp->dl_name) == 0) break; if (dla == NULL) { *dlpp = dlp->dl_next; free(dlp); } else { dlpp = &dlp->dl_next; } } free_device_list(dlalt); /* Add in "*" so we can always handle dynamic plumbing. */ dlp = (struct device_list *)malloc(sizeof (*dlp) + 2); if (dlp != NULL) { dlp->dl_name = (const char *)(dlp + 1); (void) strcpy((char *)(dlp + 1), "*"); dlp->dl_next = psp->ps_star; psp->ps_star = dlp; } } /* * Set logging subsystem back to configured global default values. */ void global_logging(void) { log_for_service(glob_svc.se_log, glob_svc.se_debug); } /* * Handle SIGHUP -- reparse command line and all configuration files. * When reparsing is complete, free old parsed data and replace with * new. */ void parse_options(int tunfd, int argc, char **argv) { struct parse_state pstate; struct per_file *argpf; struct option_state *newopt; const char **dnames; const char **dnp; const struct device_entry *newdep, *newmax; const struct device_entry *olddep, *oldmax; int cmpval; struct service_entry newglobsvc, *mainsvc; /* Note that all per_file structures must be freeable */ argpf = (struct per_file *)calloc(sizeof (*argpf), 1); if (argpf == NULL) { return; } (void) memset(&pstate, '\0', sizeof (pstate)); pstate.ps_state = ksDefault; pstate.ps_cfile = argpf; pstate.ps_csvc = &argpf->pf_global; argpf->pf_global.sl_parse = &pstate; argpf->pf_name = "command line"; /* Default is 1 -- errors only */ argpf->pf_global.sl_entry.se_debug++; argpf->pf_global.sl_entry.se_name = ""; /* Get list of all devices */ get_device_list(&pstate, tunfd); /* Parse options from command line and main configuration file. */ pstate.ps_flags |= PSF_SETLEVEL; parse_arg_list(&pstate, argc, argv); parse_file(&pstate, "/etc/ppp/pppoe"); pstate.ps_flags &= ~PSF_SETLEVEL; /* * At this point, global options from the main configuration * file are pointed to by ps_files, and options from command * line are in argpf. We need to pull three special options * from these -- wildcard, debug, and log. Note that the main * options file overrides the command line. This is * intentional. The semantics are such that the system * behaves as though the main configuration file were * "included" from the command line, and thus options there * override the command line. This may seem odd, but at least * it's self-consistent. */ newglobsvc = argpf->pf_global.sl_entry; if (pstate.ps_files != NULL) { mainsvc = &pstate.ps_files->pf_global.sl_entry; if (mainsvc->se_log != NULL) newglobsvc.se_log = mainsvc->se_log; if (mainsvc->se_flags & (SEF_WILD|SEF_NOWILD)) newglobsvc.se_flags = (newglobsvc.se_flags & ~(SEF_WILD|SEF_NOWILD)) | (mainsvc->se_flags & (SEF_WILD|SEF_NOWILD)); if (mainsvc->se_flags & SEF_DEBUGCLR) newglobsvc.se_debug = 0; newglobsvc.se_debug += mainsvc->se_debug; } glob_svc = newglobsvc; global_logging(); /* Get the list of devices referenced by configuration above. */ dnames = get_unique_devs(&pstate); if (dnames != NULL) { /* Read per-device configuration files. */ pstate.ps_flags |= PSF_PERDEV; for (dnp = dnames; *dnp != NULL; dnp++) parse_file(&pstate, *dnp); pstate.ps_flags &= ~PSF_PERDEV; free(dnames); } file_end(&pstate); /* * Convert parsed data structures into per-device structures. * (Invert the table.) */ newopt = organize_state(&pstate); /* If we're going to free the file name, then stop logging there. */ if (newopt == NULL && glob_svc.se_log != NULL) { glob_svc.se_log = NULL; global_logging(); } /* * Unless an error has occurred, these pointers are normally * all NULL. Nothing is freed until the file is re-read. */ free_file_list(pstate.ps_files); free_file_list(pstate.ps_cfile); free_device_list(pstate.ps_star); free_env_list(pstate.ps_evlist); /* * Match up entries on device list. Detach devices no longer * referenced. Attach ones now referenced. (The use of null * pointers here may look fishy, but it actually works. * NULL>=NULL is always true.) */ if (newopt != NULL) { newdep = newopt->os_devices; newmax = newdep + newopt->os_ndevices; } else { newdep = newmax = NULL; } if (cur_options != NULL) { olddep = cur_options->os_devices; oldmax = olddep + cur_options->os_ndevices; } else { olddep = oldmax = NULL; } while ((newdep != NULL && newdep < newmax) || (olddep != NULL && olddep < oldmax)) { if (newdep < newmax) { if (olddep >= oldmax) { add_new_dev(tunfd, newdep->de_name); newdep++; } else { cmpval = strcmp(newdep->de_name, olddep->de_name); if (cmpval < 0) { /* Brand new device seen. */ add_new_dev(tunfd, newdep->de_name); newdep++; } else if (cmpval == 0) { /* Existing device; skip it. */ newdep++; olddep++; } /* No else clause -- removal is below */ } } if (olddep < oldmax) { if (newdep >= newmax) { rem_old_dev(tunfd, olddep->de_name); olddep++; } else { cmpval = strcmp(newdep->de_name, olddep->de_name); if (cmpval > 0) { /* Old device is gone */ rem_old_dev(tunfd, olddep->de_name); olddep++; } else if (cmpval == 0) { /* Existing device; skip it. */ newdep++; olddep++; } /* No else clause -- insert handled above */ } } } /* Discard existing parsed data storage. */ if (cur_options != NULL) { free_file_list(cur_options->os_pfjunk); free_env_list(cur_options->os_evjunk); free(cur_options); } /* Install new. */ cur_options = newopt; } /* * Check if configured filters permit requesting client to use a given * service. Note -- filters are stored in reverse order in order to * make file-inclusion work as expected. Thus, the "first match" * filter rule becomes "last match" here. */ static boolean_t allow_service(const struct service_entry *sep, const ppptun_atype *pap) { const struct filter_entry *fep; const struct filter_entry *lmatch; boolean_t anynonexcept = B_FALSE; const uchar_t *upt; const uchar_t *macp; const uchar_t *maskp; int i; lmatch = NULL; for (fep = sep->se_flist; fep != NULL; fep = fep->fe_prev) { anynonexcept |= !fep->fe_isexcept; upt = pap->pta_pppoe.ptma_mac; macp = fep->fe_mac.ether_addr_octet; maskp = fep->fe_mask.ether_addr_octet; for (i = sizeof (pap->pta_pppoe.ptma_mac); i > 0; i--) if (((*macp++ ^ *upt++) & *maskp++) != 0) break; if (i <= 0) lmatch = fep; } if (lmatch == NULL) { /* * Assume reject by default if any positive-match * (non-except) filters are given. Otherwise, if * there are no positive-match filters, then * non-matching means accept by default. */ return (!anynonexcept); } return (!lmatch->fe_isexcept); } /* * Locate available service(s) based on client request. Assumes that * outp points to a buffer of at least size PPPOE_MSGMAX. Creates a * PPPoE response message in outp. Returns count of matched services * and (through *srvp) a pointer to the last (or only) service. If * some error is found in the request, an error string is added and -1 * is returned; the caller should just send the message without * alteration. */ int locate_service(poep_t *poep, int plen, const char *iname, ppptun_atype *pap, uint32_t *outp, void **srvp) { poep_t *opoe; const uint8_t *tagp; const char *cp; int ttyp; int tlen; int nsvcs; const struct device_entry *dep, *depe; const struct device_entry *wdep; const struct service_entry **sepp, **seppe; const struct service_entry *sep; char *str; boolean_t ispadi; ispadi = poep->poep_code == POECODE_PADI; opoe = poe_mkheader(outp, ispadi ? POECODE_PADO : POECODE_PADS, 0); *srvp = NULL; if (cur_options == NULL) return (0); /* Search for named device (lower stream) in tables. */ dep = cur_options->os_devices; depe = dep + cur_options->os_ndevices; wdep = NULL; if ((cp = strchr(iname, ':')) != NULL) tlen = cp - iname; else tlen = strlen(iname); for (; dep < depe; dep++) if (strncmp(iname, dep->de_name, tlen) == 0 && dep->de_name[tlen] == '\0') break; else if (dep->de_name[0] == '*' && dep->de_name[1] == '\0') wdep = dep; if (dep >= depe) dep = wdep; /* * Return if interface not found. Zero-service case can't * occur, since devices with no services aren't included in * the list, but the code is just being safe here. */ if (dep == NULL || dep->de_services == NULL || dep->de_nservices <= 0) return (0); /* * Loop over tags in client message and process them. * Services must be matched against our list. Host-Uniq and * Relay-Session-Id must be copied to the reply. All others * must be discarded. */ nsvcs = 0; sepp = dep->de_services; 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); switch (ttyp) { case POETT_SERVICE: /* Service-Name */ /* * Allow only one. (Note that this test works * because there's always at least one service * per device; otherwise, the device is * removed from the list.) */ if (sepp != dep->de_services) { if (nsvcs != -1) (void) poe_add_str(opoe, POETT_NAMERR, "Too many Service-Name tags"); nsvcs = -1; break; } seppe = sepp + dep->de_nservices; if (tlen == 0) { /* * If config specifies "nowild" in a * global context, then we don't * respond to wildcard PADRs. The * client must know the exact service * name to get access. */ if (!ispadi && (glob_svc.se_flags & SEF_NOWILD)) sepp = seppe; while (sepp < seppe) { sep = *sepp++; if (sep->se_name[0] == '\0' || (sep->se_flags & SEF_NOWILD) || !allow_service(sep, pap)) continue; *srvp = (void *)sep; /* * RFC requires that PADO includes the * wildcard service request in response * to PADI. */ if (ispadi && nsvcs == 0 && !(glob_svc.se_flags & SEF_NOWILD)) (void) poe_tag_copy(opoe, tagp); nsvcs++; (void) poe_add_str(opoe, POETT_SERVICE, sep->se_name); /* If PADR, then one is enough */ if (!ispadi) break; } /* Just for generating error messages */ if (nsvcs == 0) (void) poe_tag_copy(opoe, tagp); } else { /* * Clients's requested service must appear in * reply. */ (void) poe_tag_copy(opoe, tagp); /* Requested specific service; find it. */ cp = (char *)POET_DATA(tagp); while (sepp < seppe) { sep = *sepp++; if (strlen(sep->se_name) == tlen && strncasecmp(sep->se_name, cp, tlen) == 0) { if (allow_service(sep, pap)) { nsvcs++; *srvp = (void *)sep; } break; } } } /* * Allow service definition to override * AC-Name (concentrator [server] name) field. */ if (*srvp != NULL) { sep = (const struct service_entry *)*srvp; log_for_service(sep->se_log, sep->se_debug); str = "Solaris PPPoE"; if (sep->se_server != NULL) str = sep->se_server; (void) poe_add_str(opoe, POETT_ACCESS, str); } break; /* Ones we should discard */ case POETT_ACCESS: /* AC-Name */ case POETT_COOKIE: /* AC-Cookie */ 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: break; /* Ones we should copy */ case POETT_UNIQ: /* Host-Uniq */ case POETT_RELAY: /* Relay-Session-Id */ (void) poe_tag_copy(opoe, tagp); break; } tagp = POET_NEXT(tagp); } return (nsvcs); } /* * Like fgetc, but reads from a string. */ static int sgetc(void *arg) { char **cpp = (char **)arg; if (**cpp == '\0') return (EOF); return (*(*cpp)++); } /* * Given a service structure, launch pppd. Called by handle_input() * in pppoed.c if locate_service() [above] finds exactly one service * matching a PADR. */ int launch_service(int tunfd, poep_t *poep, void *srvp, struct ppptun_control *ptc) { const struct service_entry *sep = (const struct service_entry *)srvp; const char *path; const char *extra; const char *pppd; const char *cp; pid_t pidv; int newtun; struct ppptun_peer ptp; union ppptun_name ptn; const char *args[MAXARGS]; struct strbuf ctrl; struct strbuf data; const char **cpp; char *sptr; char *spv; int slen; int retv; char keybuf[MAX_KEYWORD]; assert(sep != NULL); /* Get tunnel driver connection for new PPP session. */ newtun = open(tunnam, O_RDWR); if (newtun == -1) goto syserr; /* Set this session up for standard PPP and client's address. */ (void) memset(&ptp, '\0', sizeof (ptp)); ptp.ptp_style = PTS_PPPOE; ptp.ptp_address = ptc->ptc_address; if (strioctl(newtun, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) < 0) goto syserr; ptp.ptp_rsessid = ptp.ptp_lsessid; if (strioctl(newtun, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) < 0) goto syserr; /* Attach the requested lower stream. */ cp = strchr(ptc->ptc_name, ':'); if (cp == NULL) cp = ptc->ptc_name + strlen(ptc->ptc_name); (void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%.*s:pppoe", cp-ptc->ptc_name, ptc->ptc_name); if (strioctl(newtun, PPPTUN_SDATA, &ptn, sizeof (ptn), 0) < 0) goto syserr; (void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%.*s:pppoed", cp-ptc->ptc_name, ptc->ptc_name); if (strioctl(newtun, PPPTUN_SCTL, &ptn, sizeof (ptn), 0) < 0) goto syserr; pidv = fork(); if (pidv == (pid_t)-1) goto syserr; if (pidv == (pid_t)0) { /* * Use syslog only in order to avoid mixing log messages * in regular files. */ close_log_files(); if ((path = sep->se_path) == NULL) path = "/usr/bin/pppd"; if ((extra = sep->se_extra) == NULL) extra = "plugin pppoe.so directtty"; if ((pppd = sep->se_pppd) == NULL) pppd = ""; /* Concatenate these. */ slen = strlen(path) + strlen(extra) + strlen(pppd) + 3; if ((sptr = (char *)malloc(slen)) == NULL) goto bail_out; (void) strcpy(sptr, path); (void) strcat(sptr, " "); (void) strcat(sptr, extra); (void) strcat(sptr, " "); (void) strcat(sptr, pppd); /* Parse out into arguments */ cpp = args; spv = sptr; while (cpp < args + MAXARGS - 1) { retv = getkeyword(NULL, keybuf, sizeof (keybuf), sgetc, (void *)&spv, 1); if (retv != 1) *cpp++ = strsave(keybuf); if (retv != 0) break; } *cpp = NULL; if (cpp == args) goto bail_out; /* * Fix tunnel device on stdin/stdout and error file on * stderr. */ if (newtun != 0 && dup2(newtun, 0) < 0) goto bail_out; if (newtun != 1 && dup2(newtun, 1) < 0) goto bail_out; if (newtun > 1) (void) close(newtun); if (tunfd > 1) (void) close(tunfd); (void) close(2); (void) open("/etc/ppp/pppoe-errors", O_WRONLY | O_APPEND | O_CREAT, 0600); /* * Change GID first, for obvious reasons. Note that * we log any problems to syslog, not the errors file. * The errors file is intended for problems in the * exec'd program. */ if ((sep->se_flags & SEF_GIDSET) && setgid(sep->se_gid) == -1) { cp = mystrerror(errno); reopen_log(); logerr("setgid(%d): %s", sep->se_gid, cp); goto logged; } if ((sep->se_flags & SEF_UIDSET) && setuid(sep->se_uid) == -1) { cp = mystrerror(errno); reopen_log(); logerr("setuid(%d): %s", sep->se_uid, cp); goto logged; } /* Run pppd */ path = args[0]; cp = strrchr(args[0], '/'); if (cp != NULL && cp[1] != '\0') args[0] = cp+1; errno = 0; (void) execv(path, (char * const *)args); newtun = 0; /* * Exec failure; attempt to log the problem and send a * PADT to the client so that it knows the session * went south. */ bail_out: cp = mystrerror(errno); reopen_log(); logerr("\"%s\": %s", (sptr == NULL ? path : sptr), cp); logged: poep = poe_mkheader(pkt_output, POECODE_PADT, ptp.ptp_lsessid); poep->poep_session_id = htons(ptp.ptp_lsessid); (void) poe_add_str(poep, POETT_SYSERR, cp); (void) sleep(1); ctrl.len = sizeof (*ptc); ctrl.buf = (caddr_t)ptc; data.len = poe_length(poep) + sizeof (*poep); data.buf = (caddr_t)poep; if (putmsg(newtun, &ctrl, &data, 0) < 0) { logerr("putmsg %s: %s", ptc->ptc_name, mystrerror(errno)); } exit(1); } (void) close(newtun); /* Give session ID to client in reply. */ poep->poep_session_id = htons(ptp.ptp_lsessid); return (1); syserr: /* Peer doesn't know session ID yet; hope for the best. */ retv = errno; if (newtun >= 0) (void) close(newtun); (void) poe_add_str(poep, POETT_SYSERR, mystrerror(retv)); return (0); } /* * This is pretty awful -- it uses recursion to print a simple list. * It's just for debug, though, and does a reasonable job of printing * the filters in the right order. */ static void print_filter_list(FILE *fp, struct filter_entry *fep) { if (fep->fe_prev != NULL) print_filter_list(fp, fep->fe_prev); (void) fprintf(fp, "\t\t MAC %s", ehost2(&fep->fe_mac)); (void) fprintf(fp, ", mask %s%s\n", ehost2(&fep->fe_mask), (fep->fe_isexcept ? ", except" : "")); } /* * Write summary of parsed configuration data to given file. */ void dump_configuration(FILE *fp) { const struct device_entry *dep; const struct service_entry *sep, **sepp; struct per_file *pfp; int i, j; (void) fprintf(fp, "Will%s respond to wildcard queries.\n", (glob_svc.se_flags & SEF_NOWILD) ? " not" : ""); (void) fprintf(fp, "Global debug level %d, log to %s; current level %d\n", glob_svc.se_debug, ((glob_svc.se_log == NULL || *glob_svc.se_log == '\0') ? "syslog" : glob_svc.se_log), log_level); if (cur_options == NULL) { (void) fprintf(fp, "No current configuration.\n"); return; } (void) fprintf(fp, "Current configuration:\n"); (void) fprintf(fp, " %d device(s):\n", cur_options->os_ndevices); dep = cur_options->os_devices; for (i = 0; i < cur_options->os_ndevices; i++, dep++) { (void) fprintf(fp, "\t%s: %d service(s):\n", dep->de_name, dep->de_nservices); sepp = dep->de_services; for (j = 0; j < dep->de_nservices; j++, sepp++) { sep = *sepp; (void) fprintf(fp, "\t %s: debug level %d", sep->se_name, sep->se_debug); if (sep->se_flags & SEF_UIDSET) (void) fprintf(fp, ", UID %u", sep->se_uid); if (sep->se_flags & SEF_GIDSET) (void) fprintf(fp, ", GID %u", sep->se_gid); if (sep->se_flags & SEF_WILD) (void) fprintf(fp, ", wildcard"); else if (sep->se_flags & SEF_NOWILD) (void) fprintf(fp, ", nowildcard"); else (void) fprintf(fp, ", wildcard (default)"); (void) putc('\n', fp); if (sep->se_server != NULL) (void) fprintf(fp, "\t\tserver \"%s\"\n", sep->se_server); if (sep->se_pppd != NULL) (void) fprintf(fp, "\t\tpppd \"%s\"\n", sep->se_pppd); if (sep->se_path != NULL) (void) fprintf(fp, "\t\tpath \"%s\"\n", sep->se_path); if (sep->se_extra != NULL) (void) fprintf(fp, "\t\textra \"%s\"\n", sep->se_extra); if (sep->se_log != NULL) (void) fprintf(fp, "\t\tlog \"%s\"\n", sep->se_log); if (sep->se_flist != NULL) { (void) fprintf(fp, "\t\tfilter list:\n"); print_filter_list(fp, sep->se_flist); } } } (void) fprintf(fp, "\nConfiguration read from:\n"); for (pfp = cur_options->os_pfjunk; pfp != NULL; pfp = pfp->pf_prev) { (void) fprintf(fp, " %s: %d service(s)\n", pfp->pf_name, pfp->pf_nsvc); } } /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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] [ [ [only]]]\n\n" " or\n\n" "\t%s [-o#] [-v] -i \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); } /* * 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 Server-mode daemon for use with Solaris PPP 4.0. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "pppoed.h" #include "logging.h" static int tunfd; /* Global connection to tunnel device */ char *myname; /* Copied from argv[0] for logging */ static int main_argc; /* Saved for reparse on SIGHUP */ static char **main_argv; /* Saved for reparse on SIGHUP */ static time_t time_started; /* Time daemon was started; for debug */ static time_t last_reread; /* Last time configuration was read. */ /* Various operational statistics. */ static unsigned long input_packets, padi_packets, padr_packets; static unsigned long output_packets; static unsigned long sessions_started; static sigset_t sigmask; /* Global signal mask */ /* * Used for handling errors that occur before we daemonize. */ static void early_error(const char *str) { const char *cp; cp = mystrerror(errno); if (isatty(2)) { (void) fprintf(stderr, "%s: %s: %s\n", myname, str, cp); } else { reopen_log(); logerr("%s: %s", str, cp); } exit(1); } /* * Open the sppptun driver. */ static void open_tunnel_dev(void) { struct ppptun_peer ptp; tunfd = open(tunnam, O_RDWR); if (tunfd == -1) { early_error(tunnam); } /* * Tell the device driver that I'm a daemon handling inbound * connections, not a PPP session. */ (void) memset(&ptp, '\0', sizeof (ptp)); ptp.ptp_style = PTS_PPPOE; ptp.ptp_flags = PTPF_DAEMON; (void) memcpy(ptp.ptp_address.pta_pppoe.ptma_mac, ether_bcast, sizeof (ptp.ptp_address.pta_pppoe.ptma_mac)); if (strioctl(tunfd, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) < 0) { myperror("PPPTUN_SPEER"); exit(1); } } /* * Callback function for fdwalk. Closes everything but the tunnel * file descriptor when becoming daemon. (Log file must be reopened * manually, since syslog file descriptor, if any, is unknown.) */ /*ARGSUSED*/ static int fdcloser(void *arg, int fd) { if (fd != tunfd) (void) close(fd); return (0); } /* * Become a daemon. */ static void daemonize(void) { pid_t cpid; /* * A little bit of magic here. By the first fork+setsid, we * disconnect from our current controlling terminal and become * a session group leader. By forking again without setsid, * we make certain that we're not the session group leader and * can never reacquire a controlling terminal. */ if ((cpid = fork()) == (pid_t)-1) { early_error("fork 1"); } if (cpid != 0) { (void) wait(NULL); _exit(0); } if (setsid() == (pid_t)-1) { early_error("setsid"); } if ((cpid = fork()) == (pid_t)-1) { early_error("fork 2"); } if (cpid != 0) { /* Parent just exits */ (void) printf("%d\n", (int)cpid); (void) fflush(stdout); _exit(0); } (void) chdir("/"); (void) umask(0); (void) fdwalk(fdcloser, NULL); reopen_log(); } /* * Handle SIGHUP -- close and reopen non-syslog log files and reparse * options. */ /*ARGSUSED*/ static void handle_hup(int sig) { close_log_files(); global_logging(); last_reread = time(NULL); parse_options(tunfd, main_argc, main_argv); } /* * Handle SIGINT -- write current daemon status to /tmp. */ /*ARGSUSED*/ static void handle_int(int sig) { FILE *fp; char dumpname[MAXPATHLEN]; time_t now; struct rusage rusage; (void) snprintf(dumpname, sizeof (dumpname), "/tmp/pppoed.%ld", getpid()); if ((fp = fopen(dumpname, "w+")) == NULL) { logerr("%s: %s", dumpname, mystrerror(errno)); return; } now = time(NULL); (void) fprintf(fp, "pppoed running %s", ctime(&now)); (void) fprintf(fp, "Started on %s", ctime(&time_started)); if (last_reread != 0) (void) fprintf(fp, "Last reconfig %s", ctime(&last_reread)); (void) putc('\n', fp); if (getrusage(RUSAGE_SELF, &rusage) == 0) { (void) fprintf(fp, "CPU usage: user %ld.%06ld, system %ld.%06ld\n", rusage.ru_utime.tv_sec, rusage.ru_utime.tv_usec, rusage.ru_stime.tv_sec, rusage.ru_stime.tv_usec); } (void) fprintf(fp, "Packets: %lu received (%lu PADI, %lu PADR), ", input_packets, padi_packets, padr_packets); (void) fprintf(fp, "%lu transmitted\n", output_packets); (void) fprintf(fp, "Sessions started: %lu\n\n", sessions_started); dump_configuration(fp); (void) fclose(fp); } static void add_signal_handlers(void) { struct sigaction sa; (void) sigemptyset(&sigmask); (void) sigaddset(&sigmask, SIGHUP); (void) sigaddset(&sigmask, SIGCHLD); (void) sigaddset(&sigmask, SIGINT); (void) sigprocmask(SIG_BLOCK, &sigmask, NULL); sa.sa_mask = sigmask; sa.sa_flags = 0; /* Signals to handle */ sa.sa_handler = handle_hup; if (sigaction(SIGHUP, &sa, NULL) < 0) early_error("sigaction HUP"); sa.sa_handler = handle_int; if (sigaction(SIGINT, &sa, NULL) < 0) early_error("sigaction INT"); /* * Signals to ignore. Ignoring SIGCHLD in this way makes the * children exit without ever creating zombies. (No wait(2) * call required.) */ sa.sa_handler = SIG_IGN; if (sigaction(SIGPIPE, &sa, NULL) < 0) early_error("sigaction PIPE"); sa.sa_flags = SA_NOCLDWAIT; if (sigaction(SIGCHLD, &sa, NULL) < 0) early_error("sigaction CHLD"); } /* * Dispatch a message from the tunnel driver. It could be an actual * PPPoE message or just an event notification. */ static void handle_input(uint32_t *ctrlbuf, int ctrllen, uint32_t *databuf, int datalen) { poep_t *poep = (poep_t *)databuf; union ppptun_name ptn; int retv; struct strbuf ctrl; struct strbuf data; void *srvp; boolean_t launch; struct ppptun_control *ptc; if (ctrllen != sizeof (*ptc)) { logdbg("bogus %d byte control message from driver", ctrllen); return; } ptc = (struct ppptun_control *)ctrlbuf; /* Switch out on event notifications. */ switch (ptc->ptc_action) { case PTCA_TEST: logdbg("test reply for discriminator %X", ptc->ptc_discrim); return; case PTCA_CONTROL: break; case PTCA_DISCONNECT: logdbg("session %d disconnected on %s; send PADT", ptc->ptc_rsessid, ptc->ptc_name); poep = poe_mkheader(pkt_output, POECODE_PADT, ptc->ptc_rsessid); ptc->ptc_action = PTCA_CONTROL; ctrl.len = sizeof (*ptc); ctrl.buf = (caddr_t)ptc; data.len = poe_length(poep) + sizeof (*poep); data.buf = (caddr_t)poep; if (putmsg(tunfd, &ctrl, &data, 0) < 0) { logerr("putmsg PADT: %s", mystrerror(errno)); } else { output_packets++; } return; case PTCA_UNPLUMB: logdbg("%s unplumbed", ptc->ptc_name); return; case PTCA_BADCTRL: logwarn("bad control data on %s for session %u", ptc->ptc_name, ptc->ptc_rsessid); return; default: logdbg("unexpected code %d from driver", ptc->ptc_action); return; } /* Only PPPoE control messages get here. */ input_packets++; if (datalen < sizeof (*poep)) { logdbg("incomplete PPPoE message from %s/%s", ehost(&ptc->ptc_address), ptc->ptc_name); return; } /* Server handles only PADI and PADR; all others are ignored. */ if (poep->poep_code == POECODE_PADI) { padi_packets++; } else if (poep->poep_code == POECODE_PADR) { padr_packets++; } else { loginfo("unexpected %s from %s", poe_codename(poep->poep_code), ehost(&ptc->ptc_address)); return; } logdbg("Recv from %s/%s: %s", ehost(&ptc->ptc_address), ptc->ptc_name, poe_codename(poep->poep_code)); /* Parse out service and formulate template reply. */ retv = locate_service(poep, datalen, ptc->ptc_name, &ptc->ptc_address, pkt_output, &srvp); /* Continue formulating reply */ launch = B_FALSE; if (retv != 1) { /* Ignore initiation if we don't offer a service. */ if (retv <= 0 && poep->poep_code == POECODE_PADI) { logdbg("no services; no reply"); return; } if (retv == 0) (void) poe_add_str((poep_t *)pkt_output, POETT_NAMERR, "No such service."); } else { /* Exactly one service chosen; if it's PADR, then we start. */ if (poep->poep_code == POECODE_PADR) { launch = B_TRUE; } } poep = (poep_t *)pkt_output; /* Select control interface for output. */ (void) strncpy(ptn.ptn_name, ptc->ptc_name, sizeof (ptn.ptn_name)); if (strioctl(tunfd, PPPTUN_SCTL, &ptn, sizeof (ptn), 0) < 0) { logerr("PPPTUN_SCTL %s: %s", ptn.ptn_name, mystrerror(errno)); return; } /* Launch the PPP service */ if (launch && launch_service(tunfd, poep, srvp, ptc)) sessions_started++; /* Send the reply. */ ctrl.len = sizeof (*ptc); ctrl.buf = (caddr_t)ptc; data.len = poe_length(poep) + sizeof (*poep); data.buf = (caddr_t)poep; if (putmsg(tunfd, &ctrl, &data, 0) < 0) { logerr("putmsg %s: %s", ptc->ptc_name, mystrerror(errno)); } else { output_packets++; logdbg("Send to %s/%s: %s", ehost(&ptc->ptc_address), ptc->ptc_name, poe_codename(poep->poep_code)); } } static void main_loop(void) { struct strbuf ctrl; struct strbuf data; int flags; int rc; int err; for (;;) { ctrl.maxlen = PKT_OCTL_LEN; ctrl.buf = (caddr_t)pkt_octl; data.maxlen = PKT_INPUT_LEN; data.buf = (caddr_t)pkt_input; /* Allow signals only while idle */ (void) sigprocmask(SIG_UNBLOCK, &sigmask, NULL); errno = 0; flags = 0; rc = mygetmsg(tunfd, &ctrl, &data, &flags); err = errno; /* * Block signals -- data structures must not change * while we're busy dispatching the client's request */ (void) sigprocmask(SIG_BLOCK, &sigmask, NULL); if (rc == -1) { if (err == EAGAIN || err == EINTR) continue; logerr("%s getmsg: %s", tunnam, mystrerror(err)); exit(1); } if (rc > 0) logwarn("%s returned truncated data", tunnam); else handle_input(pkt_octl, ctrl.len, pkt_input, data.len); } } int main(int argc, char **argv) { prog_name = "pppoed"; log_level = 1; /* Default to error messages only at first */ time_started = time(NULL); if ((myname = argv[0]) == NULL) myname = "pppoed"; main_argc = argc; main_argv = argv; open_tunnel_dev(); add_signal_handlers(); daemonize(); parse_options(tunfd, argc, argv); main_loop(); return (0); } /* * 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 Server-mode daemon option parsing. * * Copyright (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef PPPOED_H #define PPPOED_H #include #include "common.h" #ifdef __cplusplus extern "C" { #endif /* Functions in options.c */ extern void parse_options(int tunfd, int argc, char **argv); extern int locate_service(poep_t *poep, int plen, const char *iname, ppptun_atype *pap, uint32_t *outp, void **srvp); extern int launch_service(int tunfd, poep_t *poep, void *srvp, struct ppptun_control *ptc); extern void dump_configuration(FILE *fp); #ifdef __cplusplus } #endif #endif /* PPPOED_H */