libppp: import user space PPP implementation from FreeBSD 7.4-RELEASE.

Change-Id: I78d2eb0fa010078b4cd131cadc39bf32cbc93986
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..68a882f
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,129 @@
+# $FreeBSD: src/usr.sbin/ppp/Makefile,v 1.110.12.1 2010/12/21 17:10:29 kensmith Exp $
+
+.include <bsd.own.mk>
+
+PROG=	ppp
+MAN=	ppp.8
+SRCS=	acf.c arp.c async.c auth.c bundle.c cbcp.c ccp.c chap.c chat.c \
+	command.c datalink.c deflate.c defs.c exec.c filter.c fsm.c hdlc.c \
+	iface.c ip.c ipcp.c ipv6cp.c iplist.c lcp.c link.c log.c lqr.c main.c \
+	mbuf.c mp.c ncp.c ncpaddr.c pap.c physical.c pred.c probe.c prompt.c \
+	proto.c route.c server.c sig.c slcompress.c sync.c systems.c tcp.c \
+	tcpmss.c throughput.c timer.c tty.c tun.c udp.c vjcomp.c
+WARNS?=	3
+.if defined(RELEASE_CRUNCH)
+CFLAGS+=-DRELEASE_CRUNCH
+PPP_NO_ATM=
+PPP_NO_DES=
+PPP_NO_I4B=
+PPP_NO_KLDLOAD=
+PPP_NO_NAT=
+PPP_NO_PAM=
+PPP_NO_RADIUS=
+PPP_NO_SUID=
+.endif
+
+.if ${MK_ATM} == "no"
+PPP_NO_ATM=
+.endif
+.if ${MK_I4B} == "no"
+PPP_NO_I4B=
+.endif
+.if ${MK_PAM_SUPPORT} == "no"
+PPP_NO_PAM=
+.endif
+
+.if defined(PPP_NO_SUID)
+BINMODE=550
+.else
+BINMODE=4550
+BINOWN=	root
+.endif
+BINGRP=	network
+M4FLAGS=
+
+LDADD= -lcrypt -lmd -lutil -lz
+DPADD=	${LIBCRYPT} ${LIBMD} ${LIBUTIL} ${LIBZ}
+
+.SUFFIXES: .8 .8.m4
+
+.8.m4.8:
+	m4 ${M4FLAGS} ${.IMPSRC} >${.TARGET}
+
+CLEANFILES= ppp.8
+
+.if defined(PPP_CONFDIR) && !empty(PPP_CONFDIR)
+CFLAGS+=-DPPP_CONFDIR=\"${PPP_CONFDIR}\"
+.endif
+
+.if defined(PPP_NO_KLDLOAD)
+CFLAGS+=-DNOKLDLOAD
+.endif
+
+.if ${MK_INET6_SUPPORT} == "no"
+CFLAGS+=-DNOINET6
+.endif
+
+.if defined(PPP_NO_NAT)
+CFLAGS+=-DNONAT
+.else
+SRCS+=	nat_cmd.c
+LDADD+=	-lalias
+DPADD+= ${LIBALIAS}
+.endif
+
+.if defined(PPP_NO_ATM)
+CFLAGS+=-DNOATM
+.else
+SRCS+=	atm.c
+.endif
+
+.if defined(PPP_NO_SUID)
+CFLAGS+=-DNOSUID
+.else
+SRCS+=	id.c
+.endif
+
+.if defined(RELEASE_CRUNCH) || ${MK_OPENSSL} == "no" || \
+    defined(PPP_NO_DES)
+CFLAGS+=-DNODES
+.else
+SRCS+=	chap_ms.c mppe.c
+LDADD+= -lcrypto
+DPADD+= ${LIBCRYPTO}
+.endif
+
+.if defined(PPP_NO_RADIUS)
+CFLAGS+=-DNORADIUS
+.else
+SRCS+=	radius.c
+LDADD+=	-lradius
+DPADD+= ${LIBRADIUS}
+.endif
+
+.if defined(PPP_NO_I4B) || ${MACHINE_ARCH} != "i386"
+CFLAGS+=-DNOI4B
+.else
+SRCS+=	i4b.c
+.endif
+
+.if defined(PPP_NO_NETGRAPH)
+CFLAGS+=-DNONETGRAPH
+.else
+SRCS+=	ether.c
+LDADD+=	-lnetgraph
+DPADD+= ${LIBNETGRAPH}
+.if defined(EXPERIMENTAL_NETGRAPH)
+CFLAGS+=-DEXPERIMENTAL_NETGRAPH
+SRCS+=	netgraph.c
+.endif
+.endif
+
+.if defined(PPP_NO_PAM)
+CFLAGS+=-DNOPAM
+.else
+LDADD+=	${MINUSLPAM}
+DPADD+=	${LIBPAM}
+.endif
+
+.include <bsd.prog.mk>
diff --git a/src/README.changes b/src/README.changes
new file mode 100644
index 0000000..49acd04
--- /dev/null
+++ b/src/README.changes
@@ -0,0 +1,140 @@
+Copyright (c) 2001 Brian Somers <brian@Awfulhak.org>
+              based on work by Eivind Eklund <perhaps@yes.no>,
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+$FreeBSD: src/usr.sbin/ppp/README.changes,v 1.24.26.1 2010/12/21 17:10:29 kensmith Exp $
+
+This file summarises changes made to ppp that effect
+its configuration.
+
+It does not describe new features, rather it attempts
+to answer any `this used to work, why doesn't it now?'
+questions.
+
+o The `set debug' command was replaced with `set log'.
+o The `set log LCP' command was split into LCP, IPCP and CCP logs.
+o Syslogd is used for logging.  /etc/syslog.conf must be updated.
+o LQR is disabled by default.
+o Openmode is active by default.
+o Users must be a member of group `network' for ppp access.  Furthermore,
+  they must be `allow'ed to run ppp via the `allow' command in the
+  configuration file.
+  For a brief period, ppp could only be run as root.
+o No diagnostic socket is created by default.  The `set server' command
+  must be used.
+o The diagnostic socket password must be specified *only* on the `set
+  server' command line.
+o When `set server' is used to re-select a diagnostic port, all existing
+  diagnostic connections are dropped.
+o pppd-deflate is now called deflate24.
+o Filter IPs of 0.0.0.0 have a default width of 0, not 32.
+o Errors in `add' and `delete' are logged as warnings rather than being
+  written to the TCP/IP log.
+o Any number of diagnostic prompts are allowed, and they are allowed in
+  interactive mode.
+o The default `device' is cuad1, then cuad0
+o A password of "*" in ppp.secret causes a passwd database lookup in
+  pap mode.
+o The value of the CONNECT environment variable is logged in the
+  utmp host field in -direct mode.
+o Out-of-sequence FSM packets (IPCP/LCP/CCP) are dropped by default.
+o Reconnect values are used after an LQR timeout.
+o ^C works on the parent in -background mode.
+o The dial/call/open command works asynchronously.  As a result, prompts
+  do not lose control while dialing.
+o The `display' command has been removed.  All information is available
+  with the appropriate `show' command.
+o Msext does not need to be enabled/disabled.  Setting the NBNS (set nbns)
+  will auto enable it.  The DNS side may be enabled/disabled, and if
+  enabled without a `set dns' (was `set ns') will use values from
+  /etc/resolv.conf.
+o Filters are now called `allow', `dial', `in' and `out'.  `set
+  ifilter ...' becomes `set filter in ...' etc.
+o Authname and Authkey may only be `set' in phase DEAD.
+o Set encrypt is no longer necessary.  Ppp will respond to M$CHAP
+  servers correctly if it's built with DES.
+o Throughput statistics are enabled by default.
+o `Set stopped' only has two parameters.  It's no longer possible to
+  have an IPCP stopped timer.
+o `Set timeout' only has one or two parameters.  Use `set lqrperiod' and
+  `set {lcp,ccp,ipcp,chap,pap}retry' for the other timers.  These timeout
+  values can be seen using the relevant show commands.
+o `set loopback' is now `enable/disable loopback'.
+o `show auto', `show loopback' and `show mtu' are all part of `show bundle'.
+o `show mru' is part of `show lcp'
+o `show msext' and `show vj' are part of `show ipcp'
+o `show reconnect' and `show redial' are part of `show link'
+o A signal 15 (TERM) will now shut down the link gracefully.
+o A signal 2 (HUP) will drop all links immediately.
+o Signal 30 (USR1) is now ignored.
+o Add & delete commands are not necessary in ppp.linkup if they are
+  `sticky routes' (ie, contain MYADDR or HISADDR).
+o LINK and CARRIER logging are no longer available.
+o Timer based DEBUG messages are now logged in the new TIMER log.
+o Ppp can use tun devices > tun255.
+o Protocol-compressed packets are accepted even if they were denied
+  at LCP negotiation time.
+o Passwords aren't logged when logging the ``set server'' line.
+o Command line options only need enough characters to uniquely identify
+  them.  -a == -auto, -dd == -ddial etc.  -interactive is also allowed.
+o If you don't like seeing additional interface aliases when running in
+  -auto -alias mode, add ``iface clear'' to your ppp.linkdown file -
+  check the sample file.
+o Ppp waits for 1 second before checking whether the device supports
+  carrier.  This is controllable with ``set cd''.
+o Random dial timeouts are now between 1 and 30 seconds inclusive rather
+  than between 0 and 29.
+o Ppp now accepts M$CHAP (as well as normal CHAP) by default.  If this
+  is not required, you must ``deny chap05 chap80''.
+o The ``set device'' command now expects each device to be specified as an
+  argument rather than concatentating all arguments and splitting based
+  on commas and spaces.
+o The ``show modem'' command is deprecated and has been changed to
+  ``show physical''.
+o The words ``host'' and ``port'' are no longer accepted by the ``set filter''
+  command.  Removing them should yield the same results as before.
+o The ``set weight'' command has been deprecated.  The ``set bandwidth''
+  command should now be used instead.
+o The ``set autoload'' command syntax and implementation have changed as the
+  old implementation was mis-designed and dysfunctional.
+o Ppp now waits either the full ``set cd'' time or until carrier is detected
+  before running the login script (whichever comes first).
+o The -alias flag has been deprecated.  The -nat flag should be used instead.
+o Unbalanced quotes in commands are now warned about and the entire command
+  is ignored.
+o It is now only necessary to escape the `-' character in chat scripts twice.
+  See the example files for details.
+o Environment variables and ~ are expanded on in commands
+o ``nat pptp'' is no longer necessary as this is now done transparently
+o The ``!'' at the start of chat scripts and authkey can be made literal
+  (rather than meaning execute) by doubling it to ``!!''.
+o MP autoload throughput measurements are now based on the maximum of input
+  and output averages rather than on the total.
+o When only one link is open in MP mode, MP link level compression is not
+  open and the peer MRU >= the peer MRRU, ppp sends outbound traffic as
+  PROTO_IP traffic rather than PROTO_MP.
+o MSCHAPv2 is now accepted by default.  If you don't wish to negotiate
+  this, you must explicitly deny it.
+o MPPE is enabled and accepted by default (although deflate and predictor1
+  are preferred.
diff --git a/src/README.nat b/src/README.nat
new file mode 100644
index 0000000..01b572f
--- /dev/null
+++ b/src/README.nat
@@ -0,0 +1,378 @@
+Copyright (c) 2001 Charles Mott <cm@linktel.net>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+$FreeBSD: src/usr.sbin/ppp/README.nat,v 1.7.40.1 2010/12/21 17:10:29 kensmith Exp $
+
+User PPP NAT (Packet Aliasing)
+
+
+
+0. Contents
+    1. Background
+    2. Setup
+    3. New commands in ppp
+    4. Future Work
+    5. Authors / Acknowledgements
+    6. Revision History for Aliasing Code
+
+
+
+1. Background
+
+User mode ppp has embedded NAT (Network Address Translation) code.
+Enabling this, either by the "-nat" command line option or the
+"nat enable yes" command in a ppp.conf file, makes the ppp host
+automatically NAT IP packets forwarded from a local network, making
+them appear to come from the ppp host machine.  Incoming packets
+from the outside world are then appropriately de-NAT'd.
+
+The process of NAT'ing involves both the IP address and the TCP or UDP
+port numbers. ICMP echo and timestamp packets are natted by their id
+numbers.  ICMP error messages can be properly directed by examining the
+fragment of the offending packet which is contained in the body of the
+message.
+
+This software was specifically meant to support users who have
+unregistered, private address IP networks (e.g. 192.168.0.x or 10.0.0.x
+addresses).  The ppp host can act as a gateway for these networks, and
+computers on the local area net will have some degree of Internet access
+without the need for a registered IP address.  Additionally, there will
+be no need for an Internet service provider to maintain routing tables
+for the local area network.
+
+A disadvantage of NAT is that machines on the local network,
+behind the ppp host, are not visible from the outside world.  They can
+establish TCP connections and make UDP inquiries (such as domain name
+service requests) but the connections seem to come from the ppp host
+itself.  There is, in effect, a partial firewall.  Of course, if this is
+what you want, the disadvantage becomes an advantage.
+
+A second disadvantage is that "IP encoding" protocols, which send IP
+address or port information within the data stream, are not supported
+for the cases where exception code exists.  This implementation has
+workarounds for FTP and IRC DCC, the most well known of the IP encoding
+protocols.  This frees users from depending on using the ftp passive
+mode and avoiding IRC DCC sends, as is sometimes the case with other
+masquerading solutions.
+
+The implementation supports all standard, non-encoding TCP and UDP protocols.
+Examples of these protocols are http, gopher and telnet. The standard UDP
+mode of Real-Audio is not presently supported, but the TCP mode does work
+correctly.
+
+The NAT code also handles many ICMP messages.  In particular,
+ping and traceroute are supported.
+
+
+
+2. Packet Aliasing Setup
+
+It is recommended that users first verify correct ppp operation without
+NAT enabled.  This will confirm that the ppp.conf file is
+properly set up and that there are no ppp problems. Then start ppp with
+the "-nat" option on the command line.  The user should verify that
+the ppp host can correctly connect to the Internet in NAT
+mode.  Finally, check that machines on the private network can access
+the Internet.
+
+The NAT software handles all packets, whether they come from
+the host or another computer on the local area network.  Thus, a correctly
+operating ppp host indicates that the software should work properly for
+other computers on the private network.
+
+If the ppp host can access the Internet, but other computers on the local
+network cannot, check that IP forwarding is enabled on the ppp host. Also,
+verify that the other computers use this machine as a gateway.  Of course,
+you should also verify that machines within the local area network
+communicate properly.  A common error is inconsistent subnet addresses
+and masks.
+
+
+
+3. New commands in ppp
+
+In order to control NAT behaviour in a simple manner (no need for
+recompilation), a new command has been added to ppp: nat.  This
+is in addition to the -nat command line option.  System managers and
+more experienced users may prefer to use the ppp command syntax
+within the ppp.conf file.  The nat command also allows NAT
+behaviour to be more precisely specified.
+
+The decision to add a command instead of extending 'set' or 'option' was
+to make obvious that these options only work when NAT is enabled.
+
+The syntax for 'nat' is
+
+    ppp>  nat option [yes|no]
+
+where option is given by one of the following templates.
+
+
+ - nat enable [yes|no]  (default no)
+
+Enable NAT functionality.  If disabled, no other NAT
+options will have any effect.  You should usually enable NAT
+before routing any packets over the link; good points are in the
+initial script or right before adding a route.  If you do not always
+want NAT, consider using the -nat option to ppp instead of this
+command.
+
+
+ - nat deny_incoming [yes|no] (default yes)
+
+Set to "yes" to disable all incoming connections.  This just drops
+connections to, for example, ftp, telnet or web servers.  The NAT
+mechanism prevents these connections. Technically, this option denies
+all incoming TCP and UDP requests, making the NAT software a
+fairly efficient one-way firewall.  The default is no, which will allow
+all incoming connections to telnetd, ftpd, etc.
+
+
+ - nat log [yes|no]
+
+Controls logging of NAT link creation to "/var/log/alias.log" - this
+is usually only useful if debugging a setup, to see if the bug is in
+the PPP NATing.  The debugging information is fairly limited, listing
+the number of NAT links open for different protocols.
+
+
+ - nat same_ports [yes|no] (default yes)
+
+When a connection is being established going through the NAT
+routines, it will normally have its port number changed to allow the
+NAT code to track it.  If same_ports is enabled, the NAT
+software attempts to keep the connection's source port unchanged.
+This will allow rsh, RPC and other specialised protocols to work
+_most of the time_, at least on the host machine.  Please, do not
+report this being unstable as a bug - it is a result of the way
+NAT has to work. TCP/IP was intended to have one IP address
+per machine.
+
+
+ - nat use_sockets [yes|no] (default yes)
+
+This is a fairly obscure option.  For the most part, the NAT
+software does not have to allocate system sockets when it chooses a
+NAT port number.  Under very specific circumstances, FTP data
+connections (which don't know the remote port number, though it is
+usually 20) and IRC DCC send (which doesn't know either the address or
+the port from which the connection will come), there can potentially be
+some interference with an open server socket having the same port number
+on the ppp host machine.  This possibility for interference only exists
+until the TCP connection has been acknowledged on both sides.  The safe
+option is yes, though fewer system resources are consumed by specifying
+no.
+
+
+ - nat unregistered_only [yes|no] (default no)
+
+NAT normally remaps all packets coming from the local area
+network to the ppp host machine address.  Set this option to only map
+addresses from the following standard ranges for private, unregistered
+addresses:
+
+                10.0.0.0     ->   10.255.255.255
+                172.16.0.0   ->   172.31.255.255
+                192.168.0.0  ->   192.168.255.255  */
+
+In the instance that there is a subnet of public addresses and another
+subnet of private addresses being routed by the ppp host, then only the
+packets on the private subnet will be NAT'd.
+
+
+- nat port <proto> <local addr>:<port>  <nat port>
+
+This command allows incoming traffic to <nat port> on the host
+machine to be redirected to a specific machine and port on the
+local area network.  One example of this would be:
+
+    nat port tcp 192.168.0.4:telnet  8066
+
+All traffic to port 8066 of the ppp host would then be sent to
+the telnet port (23) of machine 192.168.0.4.  Port numbers
+can either be designated numerically or by symbolic names
+listed in /etc/services.  Similarly, addresses can be either
+in dotted quad notation or in /etc/hosts.
+
+
+- nat addr <local addr> <public addr>
+
+This command allows traffic for a public IP address to be
+redirected to a machine on the local network.  This function
+is known as "static NAT".  An address assignment of 0 refers
+to the default address of the ppp host.  Normally static
+NAT is useful if your ISP has allocated a small block of
+IP addresses to the user, but it can even be used in the
+case of a single, dynamically allocated IP address:
+
+    nat addr 10.0.0.8 0
+
+The above command would redirect all incoming traffic to
+machine 10.0.0.8.
+
+If several address NATs specify the same public address
+as follows
+
+    nat addr 192.168.0.2  public_addr
+    nat addr 192.168.0.3  public_addr
+    nat addr 192.168.0.4  public_addr
+
+then incoming traffic will be directed to the last
+translated local address (192.168.0.4), but outgoing
+traffic to the first two addresses will still be NAT'd
+to the specified public address.
+
+
+
+4. Future Work
+
+What is called NAT here has been variously called masquerading, packet
+aliasing and transparent proxying by others.  It is an extremely useful
+function to many users, but it is also necessarily imperfect.  The
+occasional IP-encoding protocols always need workarounds (hacks).
+Users who are interested in supporting new IP-encoding protocols
+can follow the examples of alias_ftp.c and alias_irc.c.
+
+ICMP error messages are currently handled only in the incoming direction.
+A handler needs to be added to correctly NAT outgoing error messages.
+
+IRC and FTP exception handling make reasonable, though not strictly correct
+assumptions, about how IP encoded messages will appear in the control
+stream.  Programmers may wish to consider how to make this process more
+robust.
+
+The NAT engine (alias.c, alias_db.c, alias_ftp.c, alias_irc.c
+and alias_util.c) runs in user space, and is intended to be both portable
+and reusable for interfaces other than ppp.  To access the basic engine
+only requires four simple function calls (initialisation, communication of
+host address, outgoing NAT and incoming de-NATing).
+
+
+
+5. Authors / Acknowledgements
+
+Charles Mott (cm@linktel.net)  <versions 1.0 - 1.8, 2.0, 2.1>
+Eivind Eklund (perhaps@yes.no) <versions 1.8b - 1.9, new ppp commands>
+
+Listed below, in chronological order, are individuals who have provided
+valuable comments and/or debugging assistance.
+
+    Gary Roberts
+    Tom Torrance
+    Reto Burkhalter
+    Martin Renters
+    Brian Somers
+    Paul Traina
+    Ari Suutari
+    J. Fortes
+    Andrzej Bialeki
+
+
+
+6. Revision History for Aliasing Code
+
+Version 1.0: August 11, 1996 (cjm)
+
+Version 1.1:  August 20, 1996  (cjm)
+    PPP host accepts incoming connections for ports 0 to 1023.
+
+Version 1.2:  September 7, 1996 (cjm)
+    Fragment handling error in alias_db.c corrected.
+
+Version 1.3: September 15, 1996 (cjm)
+    - Generalised mechanism for handling incoming connections
+      (no more 0 to 1023 restriction).
+    - Increased ICMP support (will handle traceroute now).
+    - Improved TCP close connection logic.
+
+Version 1.4: September 16, 1996
+    Can't remember (this version only lasted a day -- cjm).
+
+Version 1.5: September 17, 1996 (cjm)
+    Corrected error in handling incoming UDP packets
+    with zero checksum.
+
+Version 1.6: September 18, 1996
+    Simplified ICMP data storage.  Will now handle
+    tracert from Win95 as well as FreeBSD traceroute.
+
+Version 1.7: January 9, 1997 (cjm)
+    - Reduced malloc() activity for ICMP echo and
+      timestamp requests.
+    - Added handling for out-of-order IP fragments.
+    - Switched to differential checksum computation
+      for IP headers (TCP, UDP and ICMP checksums
+      were already differential).
+    - Accepts FTP data connections from other than
+      port 20.  This allows one ftp connections
+      from two hosts which are both running packet
+      aliasing.
+
+Version 1.8: January 14, 1997 (cjm)
+    - Fixed data type error in function StartPoint()
+      in alias_db.c (this bug did not exist before v1.7)
+
+Version 1.8b: January 16, 1997 (Eivind Eklund <perhaps@yes.no>)
+    - Upgraded base PPP version to be the source code from
+      FreeBSD 2.1.6, with additional security patches.  This
+      version should still be possible to run on 2.1.5, though -
+      I've run it with a 2.1.5 kernel without problems.
+      (Update done with the permission of cjm)
+
+Version 1.9: February 1, 1997 (Eivind Eklund <perhaps@yes.no>)
+    - Added support for IRC DCC (ee)
+    - Changed the aliasing routines to use ANSI style throughout -
+      minor API changes for integration with other programs than PPP (ee)
+    - Changed the build process, making all options switchable
+      from the Makefile (ee)
+    - Fixed minor security hole in alias_ftp.c for other applications
+      of the aliasing software.  Hole could _not_ manifest in
+      PPP+pktAlias, but could potentially manifest in other
+      applications of the aliasing. (ee)
+    - Connections initiated from packet aliasing host machine will
+      not have their port number aliased unless it conflicts with
+      an aliasing port already being used. (There is an option to
+      disable this for debugging) (cjm)
+    - Sockets will be allocated in cases where there might be
+      port interference with the host machine.  This can be disabled
+      in cases where the ppp host will be acting purely as a
+      masquerading router and not generate any traffic of its own.
+      (cjm)
+
+Version 2.0: March, 1997 (cjm)
+    - Incoming packets which are not recognised by the packet
+      aliasing engine are now completely dropped in ip.c.
+    - Aliasing links are cleared when a host interface address
+      changes (due to re-dial and dynamic address allocation).
+    - PacketAliasPermanentLink() API added.
+    - Option for only aliasing private, unregistered IP addresses
+      added.
+    - Substantial rework to the aliasing lookup engine.
+
+Version 2.1: May, 1997 (cjm)
+    - Continuing rework to the aliasing lookup engine to support
+      multiple incoming addresses and static NAT.
+    - Now supports outgoing as well as incoming ICMP error messages/
+    - PPP commands to support address and port redirection.
+
diff --git a/src/acf.c b/src/acf.c
new file mode 100644
index 0000000..6975f11
--- /dev/null
+++ b/src/acf.c
@@ -0,0 +1,116 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/acf.c,v 1.7.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "layer.h"
+#include "timer.h"
+#include "fsm.h"
+#include "log.h"
+#include "mbuf.h"
+#include "acf.h"
+#include "proto.h"
+#include "throughput.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "async.h"
+#include "physical.h"
+
+int
+acf_WrapperOctets(struct lcp *lcp, u_short proto)
+{
+  return (proto == PROTO_LCP || lcp->his_acfcomp == 0) ? 2 : 0;
+}
+
+static struct mbuf *
+acf_LayerPush(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+              int pri __unused, u_short *proto)
+{
+  const u_char cp[2] = { HDLC_ADDR, HDLC_UI };
+
+  if (*proto == PROTO_LCP || l->lcp.his_acfcomp == 0) {
+    bp = m_prepend(bp, cp, 2, 0);
+    m_settype(bp, MB_ACFOUT);
+  }
+
+  return bp;
+}
+
+static struct mbuf *
+acf_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+	      u_short *proto __unused)
+{
+  struct physical *p = link2physical(l);
+  u_char cp[2];
+
+  if (!p) {
+    log_Printf(LogERROR, "Can't Pull an acf packet from a logical link\n");
+    return bp;
+  }
+
+  if (mbuf_View(bp, cp, 2) == 2) {
+    if (!p->link.lcp.want_acfcomp) {
+      /* We expect the packet not to be compressed */
+      bp = mbuf_Read(bp, cp, 2);
+      if (cp[0] != HDLC_ADDR) {
+        p->hdlc.lqm.ifInErrors++;
+        p->hdlc.stats.badaddr++;
+        log_Printf(LogDEBUG, "acf_LayerPull: addr 0x%02x\n", cp[0]);
+        m_freem(bp);
+        return NULL;
+      }
+      if (cp[1] != HDLC_UI) {
+        p->hdlc.lqm.ifInErrors++;
+        p->hdlc.stats.badcommand++;
+        log_Printf(LogDEBUG, "acf_LayerPull: control 0x%02x\n", cp[1]);
+        m_freem(bp);
+        return NULL;
+      }
+      m_settype(bp, MB_ACFIN);
+    } else if (cp[0] == HDLC_ADDR && cp[1] == HDLC_UI) {
+      /*
+       * We can receive compressed packets, but the peer still sends
+       * uncompressed packets (or maybe this is a PROTO_LCP packet) !
+       */
+      bp = mbuf_Read(bp, cp, 2);
+      m_settype(bp, MB_ACFIN);
+    }
+  }
+
+  return bp;
+}
+
+struct layer acflayer = { LAYER_ACF, "acf", acf_LayerPush, acf_LayerPull };
diff --git a/src/acf.h b/src/acf.h
new file mode 100644
index 0000000..4a53500
--- /dev/null
+++ b/src/acf.h
@@ -0,0 +1,33 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/acf.h,v 1.2.62.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct lcp;
+
+extern int acf_WrapperOctets(struct lcp *, u_short);
+
+extern struct layer acflayer;
diff --git a/src/arp.c b/src/arp.c
new file mode 100644
index 0000000..6d7c312
--- /dev/null
+++ b/src/arp.c
@@ -0,0 +1,317 @@
+/*
+ * sys-bsd.c - System-dependent procedures for setting up
+ * PPP interfaces on bsd-4.4-ish systems (including 386BSD, NetBSD, etc.)
+ *
+ * Copyright (c) 1989 Carnegie Mellon University.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Carnegie Mellon University.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/arp.c,v 1.46.26.1 2010/12/21 17:10:29 kensmith Exp $
+ *
+ */
+
+/*
+ * TODO:
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <net/if_dl.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <arpa/inet.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/sysctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "id.h"
+#include "timer.h"
+#include "fsm.h"
+#include "defs.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "descriptor.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#include "ncp.h"
+#include "filter.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "bundle.h"
+#include "iface.h"
+#include "arp.h"
+
+/*
+ * SET_SA_FAMILY - set the sa_family field of a struct sockaddr,
+ * if it exists.
+ */
+#define SET_SA_FAMILY(addr, family)		\
+    memset((char *) &(addr), '\0', sizeof(addr));	\
+    addr.sa_family = (family); 			\
+    addr.sa_len = sizeof(addr);
+
+
+#if RTM_VERSION >= 3
+
+/*
+ * arp_SetProxy - Make a proxy ARP entry for the peer.
+ */
+static struct {
+  struct rt_msghdr hdr;
+  struct sockaddr_inarp dst;
+  struct sockaddr_dl hwa;
+  char extra[128];
+} arpmsg;
+
+static int
+arp_ProxySub(struct bundle *bundle, struct in_addr addr, int add)
+{
+  int routes;
+
+  /*
+   * Get the hardware address of an interface on the same subnet as our local
+   * address.
+   */
+
+  memset(&arpmsg, 0, sizeof arpmsg);
+  if (!arp_EtherAddr(addr, &arpmsg.hwa, 0)) {
+    log_Printf(LogWARN, "%s: Cannot determine ethernet address for proxy ARP\n",
+	       inet_ntoa(addr));
+    return 0;
+  }
+  routes = ID0socket(PF_ROUTE, SOCK_RAW, AF_INET);
+  if (routes < 0) {
+    log_Printf(LogERROR, "arp_SetProxy: opening routing socket: %s\n",
+	      strerror(errno));
+    return 0;
+  }
+  arpmsg.hdr.rtm_type = add ? RTM_ADD : RTM_DELETE;
+  arpmsg.hdr.rtm_flags = RTF_ANNOUNCE | RTF_HOST | RTF_STATIC;
+  arpmsg.hdr.rtm_version = RTM_VERSION;
+  arpmsg.hdr.rtm_seq = ++bundle->routing_seq;
+  arpmsg.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY;
+  arpmsg.hdr.rtm_inits = RTV_EXPIRE;
+  arpmsg.dst.sin_len = sizeof(struct sockaddr_inarp);
+  arpmsg.dst.sin_family = AF_INET;
+  arpmsg.dst.sin_addr.s_addr = addr.s_addr;
+  arpmsg.dst.sin_other = SIN_PROXY;
+
+  arpmsg.hdr.rtm_msglen = (char *) &arpmsg.hwa - (char *) &arpmsg
+    + arpmsg.hwa.sdl_len;
+
+
+  if (ID0write(routes, &arpmsg, arpmsg.hdr.rtm_msglen) < 0 &&
+      !(!add && errno == ESRCH)) {
+    log_Printf(LogERROR, "%s proxy arp entry %s: %s\n",
+	add ? "Add" : "Delete", inet_ntoa(addr), strerror(errno));
+    close(routes);
+    return 0;
+  }
+  close(routes);
+  return 1;
+}
+
+int
+arp_SetProxy(struct bundle *bundle, struct in_addr addr)
+{
+  return (arp_ProxySub(bundle, addr, 1));
+}
+
+/*
+ * arp_ClearProxy - Delete the proxy ARP entry for the peer.
+ */
+int
+arp_ClearProxy(struct bundle *bundle, struct in_addr addr)
+{
+  return (arp_ProxySub(bundle, addr, 0));
+}
+
+#else				/* RTM_VERSION */
+
+/*
+ * arp_SetProxy - Make a proxy ARP entry for the peer.
+ */
+int
+arp_SetProxy(struct bundle *bundle, struct in_addr addr, int s)
+{
+  struct arpreq arpreq;
+  struct {
+    struct sockaddr_dl sdl;
+    char space[128];
+  }      dls;
+
+  memset(&arpreq, '\0', sizeof arpreq);
+
+  /*
+   * Get the hardware address of an interface on the same subnet as our local
+   * address.
+   */
+  if (!arp_EtherAddr(addr, &dls.sdl, 1)) {
+    log_Printf(LOG_PHASE_BIT, "Cannot determine ethernet address for "
+               "proxy ARP\n");
+    return 0;
+  }
+  arpreq.arp_ha.sa_len = sizeof(struct sockaddr);
+  arpreq.arp_ha.sa_family = AF_UNSPEC;
+  memcpy(arpreq.arp_ha.sa_data, LLADDR(&dls.sdl), dls.sdl.sdl_alen);
+  SET_SA_FAMILY(arpreq.arp_pa, AF_INET);
+  ((struct sockaddr_in *)&arpreq.arp_pa)->sin_addr.s_addr = addr.s_addr;
+  arpreq.arp_flags = ATF_PERM | ATF_PUBL;
+  if (ID0ioctl(s, SIOCSARP, (caddr_t) & arpreq) < 0) {
+    log_Printf(LogERROR, "arp_SetProxy: ioctl(SIOCSARP): %s\n",
+               strerror(errno));
+    return 0;
+  }
+  return 1;
+}
+
+/*
+ * arp_ClearProxy - Delete the proxy ARP entry for the peer.
+ */
+int
+arp_ClearProxy(struct bundle *bundle, struct in_addr addr, int s)
+{
+  struct arpreq arpreq;
+
+  memset(&arpreq, '\0', sizeof arpreq);
+  SET_SA_FAMILY(arpreq.arp_pa, AF_INET);
+  ((struct sockaddr_in *)&arpreq.arp_pa)->sin_addr.s_addr = addr.s_addr;
+  if (ID0ioctl(s, SIOCDARP, (caddr_t) & arpreq) < 0) {
+    log_Printf(LogERROR, "arp_ClearProxy: ioctl(SIOCDARP): %s\n",
+               strerror(errno));
+    return 0;
+  }
+  return 1;
+}
+
+#endif				/* RTM_VERSION */
+
+
+/*
+ * arp_EtherAddr - get the hardware address of an interface on the
+ * the same subnet as ipaddr.
+ */
+
+int
+arp_EtherAddr(struct in_addr ipaddr, struct sockaddr_dl *hwaddr,
+              int verbose)
+{
+  int mib[6], skip;
+  size_t needed;
+  char *buf, *ptr, *end;
+  struct if_msghdr *ifm;
+  struct ifa_msghdr *ifam;
+  struct sockaddr_dl *dl;
+  struct sockaddr *sa[RTAX_MAX];
+
+  mib[0] = CTL_NET;
+  mib[1] = PF_ROUTE;
+  mib[2] = 0;
+  mib[3] = 0;
+  mib[4] = NET_RT_IFLIST;
+  mib[5] = 0;
+
+  if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+    log_Printf(LogERROR, "arp_EtherAddr: sysctl: estimate: %s\n",
+              strerror(errno));
+    return 0;
+  }
+
+  if ((buf = malloc(needed)) == NULL)
+    return 0;
+
+  if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
+    free(buf);
+    return 0;
+  }
+  end = buf + needed;
+
+  ptr = buf;
+  while (ptr < end) {
+    ifm = (struct if_msghdr *)ptr;		/* On if_msghdr */
+    if (ifm->ifm_type != RTM_IFINFO)
+      break;
+    dl = (struct sockaddr_dl *)(ifm + 1);	/* Single _dl at end */
+    skip = (ifm->ifm_flags & (IFF_UP | IFF_BROADCAST | IFF_POINTOPOINT |
+            IFF_NOARP | IFF_LOOPBACK)) != (IFF_UP | IFF_BROADCAST);
+    ptr += ifm->ifm_msglen;			/* First ifa_msghdr */
+    while (ptr < end) {
+      ifam = (struct ifa_msghdr *)ptr;	/* Next ifa_msghdr (alias) */
+      if (ifam->ifam_type != RTM_NEWADDR)	/* finished ? */
+        break;
+      ptr += ifam->ifam_msglen;
+      if (skip || (ifam->ifam_addrs & (RTA_NETMASK|RTA_IFA)) !=
+          (RTA_NETMASK|RTA_IFA))
+        continue;
+      /* Found a candidate.  Do the addresses match ? */
+      if (log_IsKept(LogDEBUG) &&
+          ptr == (char *)ifm + ifm->ifm_msglen + ifam->ifam_msglen)
+        log_Printf(LogDEBUG, "%.*s interface is a candidate for proxy\n",
+                  dl->sdl_nlen, dl->sdl_data);
+
+      iface_ParseHdr(ifam, sa);
+
+      if (sa[RTAX_IFA]->sa_family == AF_INET) {
+        struct sockaddr_in *ifa, *netmask;
+
+        ifa = (struct sockaddr_in *)sa[RTAX_IFA];
+        netmask = (struct sockaddr_in *)sa[RTAX_NETMASK];
+
+        if (log_IsKept(LogDEBUG)) {
+          char a[16];
+
+          strncpy(a, inet_ntoa(netmask->sin_addr), sizeof a - 1);
+          a[sizeof a - 1] = '\0';
+          log_Printf(LogDEBUG, "Check addr %s, mask %s\n",
+                     inet_ntoa(ifa->sin_addr), a);
+        }
+
+        if ((ifa->sin_addr.s_addr & netmask->sin_addr.s_addr) ==
+            (ipaddr.s_addr & netmask->sin_addr.s_addr)) {
+          log_Printf(verbose ? LogPHASE : LogDEBUG,
+                     "Found interface %.*s for %s\n", dl->sdl_nlen,
+                     dl->sdl_data, inet_ntoa(ipaddr));
+          memcpy(hwaddr, dl, dl->sdl_len);
+          free(buf);
+          return 1;
+        }
+      }
+    }
+  }
+  free(buf);
+
+  return 0;
+}
diff --git a/src/arp.h b/src/arp.h
new file mode 100644
index 0000000..f00620c
--- /dev/null
+++ b/src/arp.h
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/arp.h,v 1.12.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct sockaddr_dl;
+struct bundle;
+
+extern int arp_ClearProxy(struct bundle *, struct in_addr);
+extern int arp_SetProxy(struct bundle *, struct in_addr);
+extern int arp_EtherAddr(struct in_addr, struct sockaddr_dl *, int);
diff --git a/src/async.c b/src/async.c
new file mode 100644
index 0000000..745c52f
--- /dev/null
+++ b/src/async.c
@@ -0,0 +1,220 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.sbin/ppp/async.c,v 1.29.26.1 2010/12/21 17:10:29 kensmith Exp $");
+
+#include <sys/types.h>
+
+#include <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "proto.h"
+#include "async.h"
+#include "throughput.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "physical.h"
+
+#define MODE_HUNT 0x01
+#define MODE_ESC  0x02
+
+void
+async_Init(struct async *async)
+{
+  async_Setup(async);
+  memset(async->cfg.EscMap, '\0', sizeof async->cfg.EscMap);
+}
+
+void
+async_Setup(struct async *async)
+{
+  async->mode = MODE_HUNT;
+  async->length = 0;
+  async->my_accmap = async->his_accmap = 0xffffffff;
+}
+
+void
+async_SetLinkParams(struct async *async, u_int32_t mymap, u_int32_t hismap)
+{
+  async->my_accmap = mymap;
+  async->his_accmap = hismap | mymap;
+}
+
+/*
+ * Encode into async HDLC byte code
+ */
+static void
+async_Encode(struct async *async, u_char **cp, u_char c, int proto)
+{
+  u_char *wp;
+
+  wp = *cp;
+  if ((c < 0x20 && (proto == PROTO_LCP || (async->his_accmap & (1 << c))))
+      || (c == HDLC_ESC) || (c == HDLC_SYN)) {
+    *wp++ = HDLC_ESC;
+    c ^= HDLC_XOR;
+  }
+  if (async->cfg.EscMap[32] && async->cfg.EscMap[c >> 3] & (1 << (c & 7))) {
+    *wp++ = HDLC_ESC;
+    c ^= HDLC_XOR;
+  }
+  *wp++ = c;
+  *cp = wp;
+}
+
+static struct mbuf *
+async_LayerPush(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+                int pri __unused, u_short *proto)
+{
+  struct physical *p = link2physical(l);
+  u_char *cp, *sp, *ep;
+  struct mbuf *wp;
+  size_t oldcnt;
+  size_t cnt;
+
+  if (!p || m_length(bp) > HDLCSIZE) {
+    m_freem(bp);
+    return NULL;
+  }
+
+  oldcnt = m_length(bp);
+
+  cp = p->async.xbuff;
+  ep = cp + HDLCSIZE - 10;
+  wp = bp;
+  *cp++ = HDLC_SYN;
+  while (wp) {
+    sp = MBUF_CTOP(wp);
+    for (cnt = wp->m_len; cnt > 0; cnt--) {
+      async_Encode(&p->async, &cp, *sp++, *proto);
+      if (cp >= ep) {
+	m_freem(bp);
+	return NULL;
+      }
+    }
+    wp = wp->m_next;
+  }
+  *cp++ = HDLC_SYN;
+
+  cnt = cp - p->async.xbuff;
+  m_freem(bp);
+  bp = m_get(cnt, MB_ASYNCOUT);
+  memcpy(MBUF_CTOP(bp), p->async.xbuff, cnt);
+  bp->priv = cnt - oldcnt;
+  log_DumpBp(LogASYNC, "Write", bp);
+
+  return bp;
+}
+
+static struct mbuf *
+async_Decode(struct async *async, u_char c)
+{
+  struct mbuf *bp;
+
+  if ((async->mode & MODE_HUNT) && c != HDLC_SYN)
+    return NULL;
+
+  switch (c) {
+  case HDLC_SYN:
+    async->mode &= ~MODE_HUNT;
+    if (async->length) {		/* packet is ready. */
+      bp = m_get(async->length, MB_ASYNCIN);
+      mbuf_Write(bp, async->hbuff, async->length);
+      async->length = 0;
+      return bp;
+    }
+    break;
+  case HDLC_ESC:
+    if (!(async->mode & MODE_ESC)) {
+      async->mode |= MODE_ESC;
+      break;
+    }
+    /* FALLTHROUGH */
+  default:
+    if (async->length >= HDLCSIZE) {
+      /* packet is too large, discard it */
+      log_Printf(LogWARN, "Packet too large (%d), discarding.\n",
+                 async->length);
+      async->length = 0;
+      async->mode = MODE_HUNT;
+      break;
+    }
+    if (async->mode & MODE_ESC) {
+      c ^= HDLC_XOR;
+      async->mode &= ~MODE_ESC;
+    }
+    async->hbuff[async->length++] = c;
+    break;
+  }
+  return NULL;
+}
+
+static struct mbuf *
+async_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+                u_short *proto __unused)
+{
+  struct mbuf *nbp, **last;
+  struct physical *p = link2physical(l);
+  u_char *ch;
+  size_t cnt;
+
+  if (!p) {
+    log_Printf(LogERROR, "Can't Pull an async packet from a logical link\n");
+    return bp;
+  }
+
+  last = &nbp;
+
+  log_DumpBp(LogASYNC, "Read", bp);
+  while (bp) {
+    ch = MBUF_CTOP(bp);
+    for (cnt = bp->m_len; cnt; cnt--) {
+      *last = async_Decode(&p->async, *ch++);
+      if (*last != NULL)
+        last = &(*last)->m_nextpkt;
+    }
+    bp = m_free(bp);
+  }
+
+  return nbp;
+}
+
+struct layer asynclayer =
+  { LAYER_ASYNC, "async", async_LayerPush, async_LayerPull };
diff --git a/src/async.h b/src/async.h
new file mode 100644
index 0000000..c0f5d14
--- /dev/null
+++ b/src/async.h
@@ -0,0 +1,53 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/async.h,v 1.8.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define HDLCSIZE	(MAX_MRU*2+6)
+
+struct async {
+  int mode;
+  int length;
+  u_char hbuff[HDLCSIZE];	/* recv buffer */
+  u_char xbuff[HDLCSIZE];	/* xmit buffer */
+  u_int32_t my_accmap;
+  u_int32_t his_accmap;
+
+  struct {
+    u_char EscMap[33];
+  } cfg;
+};
+
+struct lcp;
+struct mbuf;
+struct physical;
+struct bundle;
+
+extern void async_Init(struct async *);
+extern void async_Setup(struct async *);
+extern void async_SetLinkParams(struct async *, u_int32_t, u_int32_t);
+
+extern struct layer asynclayer;
diff --git a/src/atm.c b/src/atm.c
new file mode 100644
index 0000000..6477bc1
--- /dev/null
+++ b/src/atm.c
@@ -0,0 +1,237 @@
+/*-
+ * Copyright (c) 2000 Jakob Stoklund Olesen <stoklund@taxidriver.dk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/atm.c,v 1.10.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netnatm/natm.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "main.h"
+#include "atm.h"
+
+/* String identifying PPPoA */
+#define PPPOA		"PPPoA"
+#define PPPOA_LEN	(sizeof(PPPOA) - 1)
+
+struct atmdevice {
+  struct device dev;		/* What struct physical knows about */
+};
+
+#define device2atm(d) ((d)->type == ATM_DEVICE ? (struct atmdevice *)d : NULL)
+
+unsigned
+atm_DeviceSize(void)
+{
+  return sizeof(struct atmdevice);
+}
+
+static ssize_t
+atm_Sendto(struct physical *p, const void *v, size_t n)
+{
+  ssize_t ret = write(p->fd, v, n);
+  if (ret < 0) {
+    log_Printf(LogDEBUG, "atm_Sendto(%ld): %s\n", (long)n, strerror(errno));
+    return ret;
+  }
+  return ret;
+}
+
+static ssize_t
+atm_Recvfrom(struct physical *p, void *v, size_t n)
+{
+    ssize_t ret = read(p->fd, (char*)v, n);
+    if (ret < 0) {
+      log_Printf(LogDEBUG, "atm_Recvfrom(%ld): %s\n", (long)n, strerror(errno));
+      return ret;
+    }
+    return ret;
+}
+
+static void
+atm_Free(struct physical *p)
+{
+  struct atmdevice *dev = device2atm(p->handler);
+
+  free(dev);
+}
+
+static void
+atm_device2iov(struct device *d, struct iovec *iov, int *niov,
+               int maxiov __unused, int *auxfd __unused, int *nauxfd __unused)
+{
+  int sz = physical_MaxDeviceSize();
+
+  iov[*niov].iov_base = realloc(d, sz);
+  if (iov[*niov].iov_base == NULL) {
+    log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
+    AbortProgram(EX_OSERR);
+  }
+  iov[*niov].iov_len = sz;
+  (*niov)++;
+}
+
+static const struct device baseatmdevice = {
+  ATM_DEVICE,
+  "atm",
+  0,
+  { CD_NOTREQUIRED, 0 },
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  atm_Free,
+  atm_Recvfrom,
+  atm_Sendto,
+  atm_device2iov,
+  NULL,
+  NULL,
+  NULL
+};
+
+struct device *
+atm_iov2device(int type, struct physical *p, struct iovec *iov, int *niov,
+               int maxiov __unused, int *auxfd __unused, int *nauxfd __unused)
+{
+  if (type == ATM_DEVICE) {
+    struct atmdevice *dev = (struct atmdevice *)iov[(*niov)++].iov_base;
+
+    dev = realloc(dev, sizeof *dev);	/* Reduce to the correct size */
+    if (dev == NULL) {
+      log_Printf(LogALERT, "Failed to allocate memory: %d\n",
+                 (int)(sizeof *dev));
+      AbortProgram(EX_OSERR);
+    }
+
+    /* Refresh function pointers etc */
+    memcpy(&dev->dev, &baseatmdevice, sizeof dev->dev);
+
+    physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF);
+    return &dev->dev;
+  }
+
+  return NULL;
+}
+
+static struct atmdevice *
+atm_CreateDevice(struct physical *p, const char *iface, unsigned vpi,
+                 unsigned vci)
+{
+  struct atmdevice *dev;
+  struct sockaddr_natm sock;
+
+  if ((dev = calloc(1, sizeof *dev)) == NULL) {
+    log_Printf(LogWARN, "%s: Cannot allocate an atm device: %s\n",
+               p->link.name, strerror(errno));
+    return NULL;
+  }
+
+  sock.snatm_len = sizeof sock;
+  sock.snatm_family = AF_NATM;
+  strncpy(sock.snatm_if, iface, IFNAMSIZ);
+  sock.snatm_vpi = vpi;
+  sock.snatm_vci = vci;
+
+  log_Printf(LogPHASE, "%s: Connecting to %s:%u.%u\n", p->link.name,
+             iface, vpi, vci);
+
+  p->fd = socket(PF_NATM, SOCK_DGRAM, PROTO_NATMAAL5);
+  if (p->fd >= 0) {
+    log_Printf(LogDEBUG, "%s: Opened atm socket %s\n", p->link.name,
+               p->name.full);
+    if (connect(p->fd, (struct sockaddr *)&sock, sizeof sock) == 0)
+      return dev;
+    else
+      log_Printf(LogWARN, "%s: connect: %s\n", p->name.full, strerror(errno));
+  } else
+    log_Printf(LogWARN, "%s: socket: %s\n", p->name.full, strerror(errno));
+
+  close(p->fd);
+  p->fd = -1;
+  free(dev);
+
+  return NULL;
+}
+
+struct device *
+atm_Create(struct physical *p)
+{
+  struct atmdevice *dev;
+
+  dev = NULL;
+  if (p->fd < 0 && !strncasecmp(p->name.full, PPPOA, PPPOA_LEN)
+      && p->name.full[PPPOA_LEN] == ':') {
+    char iface[25];
+    unsigned vci, vpi;
+
+    if (sscanf(p->name.full + PPPOA_LEN + 1, "%25[A-Za-z0-9]:%u.%u", iface,
+               &vpi, &vci) != 3) {
+      log_Printf(LogWARN, "Malformed ATM device name \'%s\', "
+                 "PPPoA:if:vpi.vci expected\n", p->name.full);
+      return NULL;
+    }
+
+    dev = atm_CreateDevice(p, iface, vpi, vci);
+  }
+
+  if (dev) {
+    memcpy(&dev->dev, &baseatmdevice, sizeof dev->dev);
+    physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF);
+    if (p->cfg.cd.necessity != CD_DEFAULT)
+      log_Printf(LogWARN, "Carrier settings ignored\n");
+    return &dev->dev;
+  }
+
+  return NULL;
+}
diff --git a/src/atm.h b/src/atm.h
new file mode 100644
index 0000000..f91e826
--- /dev/null
+++ b/src/atm.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 2000 Jakob Stoklund Olesen <stoklund@taxidriver.dk>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/atm.h,v 1.2.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct physical;
+struct device;
+
+extern struct device *atm_Create(struct physical *);
+extern struct device *atm_iov2device(int, struct physical *,
+                                     struct iovec *, int *, int, int *, int *);
+extern unsigned atm_DeviceSize(void);
diff --git a/src/auth.c b/src/auth.c
new file mode 100644
index 0000000..1b441bc
--- /dev/null
+++ b/src/auth.c
@@ -0,0 +1,479 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/auth.c,v 1.58.10.1.8.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#ifndef NOPAM
+#include <security/pam_appl.h>
+#ifdef OPENPAM
+#include <security/openpam.h>
+#endif
+#endif /* !NOPAM */
+
+#include "layer.h"
+#include "mbuf.h"
+#include "defs.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "auth.h"
+#include "systems.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "chat.h"
+#include "proto.h"
+#include "filter.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "cbcp.h"
+#include "chap.h"
+#include "async.h"
+#include "physical.h"
+#include "datalink.h"
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+const char *
+Auth2Nam(u_short auth, u_char type)
+{
+  static char chap[10];
+
+  switch (auth) {
+  case PROTO_PAP:
+    return "PAP";
+  case PROTO_CHAP:
+    snprintf(chap, sizeof chap, "CHAP 0x%02x", type);
+    return chap;
+  case 0:
+    return "none";
+  }
+  return "unknown";
+}
+
+#if !defined(NOPAM) && !defined(OPENPAM)
+static int
+pam_conv(int n, const struct pam_message **msg, struct pam_response **resp,
+  void *data)
+{
+
+  if (n != 1 || msg[0]->msg_style != PAM_PROMPT_ECHO_OFF)
+    return (PAM_CONV_ERR);
+  if ((*resp = malloc(sizeof(struct pam_response))) == NULL)
+    return (PAM_CONV_ERR);
+  (*resp)[0].resp = strdup((const char *)data);
+  (*resp)[0].resp_retcode = 0;
+
+  return ((*resp)[0].resp != NULL ? PAM_SUCCESS : PAM_CONV_ERR);
+}
+#endif /* !defined(NOPAM) && !defined(OPENPAM) */
+
+static int
+auth_CheckPasswd(const char *name, const char *data, const char *key)
+{
+  if (!strcmp(data, "*")) {
+#ifdef NOPAM
+    /* Then look up the real password database */
+    struct passwd *pw;
+    int result;
+
+    result = (pw = getpwnam(name)) &&
+             !strcmp(crypt(key, pw->pw_passwd), pw->pw_passwd);
+    endpwent();
+    return result;
+#else /* !NOPAM */
+    /* Then consult with PAM. */
+    pam_handle_t *pamh;
+    int status;
+
+    struct pam_conv pamc = {
+#ifdef OPENPAM
+      &openpam_nullconv, NULL
+#else
+      &pam_conv, key
+#endif
+    };
+
+    if (pam_start("ppp", name, &pamc, &pamh) != PAM_SUCCESS)
+      return (0);
+#ifdef OPENPAM
+    if ((status = pam_set_item(pamh, PAM_AUTHTOK, key)) == PAM_SUCCESS)
+#endif
+      status = pam_authenticate(pamh, 0);
+    pam_end(pamh, status);
+    return (status == PAM_SUCCESS);
+#endif /* !NOPAM */
+  }
+
+  return !strcmp(data, key);
+}
+
+int
+auth_SetPhoneList(const char *name, char *phone, int phonelen)
+{
+  FILE *fp;
+  int n, lineno;
+  char *vector[6], buff[LINE_LEN];
+  const char *slash;
+
+  fp = OpenSecret(SECRETFILE);
+  if (fp != NULL) {
+again:
+    lineno = 0;
+    while (fgets(buff, sizeof buff, fp)) {
+      lineno++;
+      if (buff[0] == '#')
+        continue;
+      buff[strlen(buff) - 1] = '\0';
+      memset(vector, '\0', sizeof vector);
+      if ((n = MakeArgs(buff, vector, VECSIZE(vector), PARSE_REDUCE)) < 0)
+        log_Printf(LogWARN, "%s: %d: Invalid line\n", SECRETFILE, lineno);
+      if (n < 5)
+        continue;
+      if (strcmp(vector[0], name) == 0) {
+        CloseSecret(fp);
+        if (*vector[4] == '\0')
+          return 0;
+        strncpy(phone, vector[4], phonelen - 1);
+        phone[phonelen - 1] = '\0';
+        return 1;		/* Valid */
+      }
+    }
+
+    if ((slash = strrchr(name, '\\')) != NULL && slash[1]) {
+      /* Look for the name without the leading domain */
+      name = slash + 1;
+      rewind(fp);
+      goto again;
+    }
+
+    CloseSecret(fp);
+  }
+  *phone = '\0';
+  return 0;
+}
+
+int
+auth_Select(struct bundle *bundle, const char *name)
+{
+  FILE *fp;
+  int n, lineno;
+  char *vector[5], buff[LINE_LEN];
+  const char *slash;
+
+  if (*name == '\0') {
+    ipcp_Setup(&bundle->ncp.ipcp, INADDR_NONE);
+    return 1;
+  }
+
+#ifndef NORADIUS
+  if (bundle->radius.valid && bundle->radius.ip.s_addr != INADDR_NONE &&
+	bundle->radius.ip.s_addr != RADIUS_INADDR_POOL) {
+    /* We've got a radius IP - it overrides everything */
+    if (!ipcp_UseHisIPaddr(bundle, bundle->radius.ip))
+      return 0;
+    ipcp_Setup(&bundle->ncp.ipcp, bundle->radius.mask.s_addr);
+    /* Continue with ppp.secret in case we've got a new label */
+  }
+#endif
+
+  fp = OpenSecret(SECRETFILE);
+  if (fp != NULL) {
+again:
+    lineno = 0;
+    while (fgets(buff, sizeof buff, fp)) {
+      lineno++;
+      if (buff[0] == '#')
+        continue;
+      buff[strlen(buff) - 1] = '\0';
+      memset(vector, '\0', sizeof vector);
+      if ((n = MakeArgs(buff, vector, VECSIZE(vector), PARSE_REDUCE)) < 0)
+        log_Printf(LogWARN, "%s: %d: Invalid line\n", SECRETFILE, lineno);
+      if (n < 2)
+        continue;
+      if (strcmp(vector[0], name) == 0) {
+        CloseSecret(fp);
+#ifndef NORADIUS
+        if (!bundle->radius.valid || bundle->radius.ip.s_addr == INADDR_NONE) {
+#endif
+          if (n > 2 && *vector[2] && strcmp(vector[2], "*") &&
+              !ipcp_UseHisaddr(bundle, vector[2], 1))
+            return 0;
+          ipcp_Setup(&bundle->ncp.ipcp, INADDR_NONE);
+#ifndef NORADIUS
+        }
+#endif
+        if (n > 3 && *vector[3] && strcmp(vector[3], "*"))
+          bundle_SetLabel(bundle, vector[3]);
+        return 1;		/* Valid */
+      }
+    }
+
+    if ((slash = strrchr(name, '\\')) != NULL && slash[1]) {
+      /* Look for the name without the leading domain */
+      name = slash + 1;
+      rewind(fp);
+      goto again;
+    }
+
+    CloseSecret(fp);
+  }
+
+#ifndef NOPASSWDAUTH
+  /* Let 'em in anyway - they must have been in the passwd file */
+  ipcp_Setup(&bundle->ncp.ipcp, INADDR_NONE);
+  return 1;
+#else
+#ifndef NORADIUS
+  if (bundle->radius.valid)
+    return 1;
+#endif
+
+  /* Disappeared from ppp.secret ??? */
+  return 0;
+#endif
+}
+
+int
+auth_Validate(struct bundle *bundle, const char *name, const char *key)
+{
+  /* Used by PAP routines */
+
+  FILE *fp;
+  int n, lineno;
+  char *vector[5], buff[LINE_LEN];
+  const char *slash;
+
+  fp = OpenSecret(SECRETFILE);
+again:
+  lineno = 0;
+  if (fp != NULL) {
+    while (fgets(buff, sizeof buff, fp)) {
+      lineno++;
+      if (buff[0] == '#')
+        continue;
+      buff[strlen(buff) - 1] = 0;
+      memset(vector, '\0', sizeof vector);
+      if ((n = MakeArgs(buff, vector, VECSIZE(vector), PARSE_REDUCE)) < 0)
+        log_Printf(LogWARN, "%s: %d: Invalid line\n", SECRETFILE, lineno);
+      if (n < 2)
+        continue;
+      if (strcmp(vector[0], name) == 0) {
+        CloseSecret(fp);
+        return auth_CheckPasswd(name, vector[1], key);
+      }
+    }
+  }
+
+  if ((slash = strrchr(name, '\\')) != NULL && slash[1]) {
+    /* Look for the name without the leading domain */
+    name = slash + 1;
+    if (fp != NULL) {
+      rewind(fp);
+      goto again;
+    }
+  }
+
+  if (fp != NULL)
+    CloseSecret(fp);
+
+#ifndef NOPASSWDAUTH
+  if (Enabled(bundle, OPT_PASSWDAUTH))
+    return auth_CheckPasswd(name, "*", key);
+#endif
+
+  return 0;			/* Invalid */
+}
+
+char *
+auth_GetSecret(const char *name, size_t len)
+{
+  /* Used by CHAP routines */
+
+  FILE *fp;
+  int n, lineno;
+  char *vector[5];
+  const char *slash;
+  static char buff[LINE_LEN];	/* vector[] will point here when returned */
+
+  fp = OpenSecret(SECRETFILE);
+  if (fp == NULL)
+    return (NULL);
+
+again:
+  lineno = 0;
+  while (fgets(buff, sizeof buff, fp)) {
+    lineno++;
+    if (buff[0] == '#')
+      continue;
+    n = strlen(buff) - 1;
+    if (buff[n] == '\n')
+      buff[n] = '\0';	/* Trim the '\n' */
+    memset(vector, '\0', sizeof vector);
+    if ((n = MakeArgs(buff, vector, VECSIZE(vector), PARSE_REDUCE)) < 0)
+      log_Printf(LogWARN, "%s: %d: Invalid line\n", SECRETFILE, lineno);
+    if (n < 2)
+      continue;
+    if (strlen(vector[0]) == len && strncmp(vector[0], name, len) == 0) {
+      CloseSecret(fp);
+      return vector[1];
+    }
+  }
+
+  if ((slash = strrchr(name, '\\')) != NULL && slash[1]) {
+    /* Go back and look for the name without the leading domain */
+    len -= slash - name + 1;
+    name = slash + 1;
+    rewind(fp);
+    goto again;
+  }
+
+  CloseSecret(fp);
+  return (NULL);		/* Invalid */
+}
+
+static void
+AuthTimeout(void *vauthp)
+{
+  struct authinfo *authp = (struct authinfo *)vauthp;
+
+  timer_Stop(&authp->authtimer);
+  if (--authp->retry > 0) {
+    authp->id++;
+    (*authp->fn.req)(authp);
+    timer_Start(&authp->authtimer);
+  } else {
+    log_Printf(LogPHASE, "Auth: No response from server\n");
+    datalink_AuthNotOk(authp->physical->dl);
+  }
+}
+
+void
+auth_Init(struct authinfo *authp, struct physical *p, auth_func req,
+          auth_func success, auth_func failure)
+{
+  memset(authp, '\0', sizeof(struct authinfo));
+  authp->cfg.fsm.timeout = DEF_FSMRETRY;
+  authp->cfg.fsm.maxreq = DEF_FSMAUTHTRIES;
+  authp->cfg.fsm.maxtrm = 0;	/* not used */
+  authp->fn.req = req;
+  authp->fn.success = success;
+  authp->fn.failure = failure;
+  authp->physical = p;
+}
+
+void
+auth_StartReq(struct authinfo *authp)
+{
+  timer_Stop(&authp->authtimer);
+  authp->authtimer.func = AuthTimeout;
+  authp->authtimer.name = "auth";
+  authp->authtimer.load = authp->cfg.fsm.timeout * SECTICKS;
+  authp->authtimer.arg = (void *)authp;
+  authp->retry = authp->cfg.fsm.maxreq;
+  authp->id = 1;
+  (*authp->fn.req)(authp);
+  timer_Start(&authp->authtimer);
+}
+
+void
+auth_StopTimer(struct authinfo *authp)
+{
+  timer_Stop(&authp->authtimer);
+}
+
+struct mbuf *
+auth_ReadHeader(struct authinfo *authp, struct mbuf *bp)
+{
+  size_t len;
+
+  len = m_length(bp);
+  if (len >= sizeof authp->in.hdr) {
+    bp = mbuf_Read(bp, (u_char *)&authp->in.hdr, sizeof authp->in.hdr);
+    if (len >= ntohs(authp->in.hdr.length))
+      return bp;
+    authp->in.hdr.length = htons(0);
+    log_Printf(LogWARN, "auth_ReadHeader: Short packet (%u > %zu) !\n",
+               ntohs(authp->in.hdr.length), len);
+  } else {
+    authp->in.hdr.length = htons(0);
+    log_Printf(LogWARN, "auth_ReadHeader: Short packet header (%u > %zu) !\n",
+               (int)(sizeof authp->in.hdr), len);
+  }
+
+  m_freem(bp);
+  return NULL;
+}
+
+struct mbuf *
+auth_ReadName(struct authinfo *authp, struct mbuf *bp, size_t len)
+{
+  if (len > sizeof authp->in.name - 1)
+    log_Printf(LogWARN, "auth_ReadName: Name too long (%zu) !\n", len);
+  else {
+    size_t mlen = m_length(bp);
+
+    if (len > mlen)
+      log_Printf(LogWARN, "auth_ReadName: Short packet (%zu > %zu) !\n",
+                 len, mlen);
+    else {
+      bp = mbuf_Read(bp, (u_char *)authp->in.name, len);
+      authp->in.name[len] = '\0';
+      return bp;
+    }
+  }
+
+  *authp->in.name = '\0';
+  m_freem(bp);
+  return NULL;
+}
diff --git a/src/auth.h b/src/auth.h
new file mode 100644
index 0000000..c08565b
--- /dev/null
+++ b/src/auth.h
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/auth.h,v 1.21.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct physical;
+struct bundle;
+struct authinfo;
+typedef void (*auth_func)(struct authinfo *);
+
+struct authinfo {
+  struct {
+    auth_func req;
+    auth_func success;
+    auth_func failure;
+  } fn;
+  struct {
+    struct fsmheader hdr;
+    char name[AUTHLEN];
+  } in;
+  struct pppTimer authtimer;
+  int retry;
+  int id;
+  struct physical *physical;
+  struct {
+    struct fsm_retry fsm;	/* How often/frequently to resend requests */
+  } cfg;
+};
+
+#define auth_Failure(a) (*(a)->fn.failure)(a)
+#define auth_Success(a) (*(a)->fn.success)(a)
+
+extern const char *Auth2Nam(u_short, u_char);
+extern void auth_Init(struct authinfo *, struct physical *,
+                      auth_func, auth_func, auth_func);
+extern void auth_StopTimer(struct authinfo *);
+extern void auth_StartReq(struct authinfo *);
+extern int auth_Validate(struct bundle *, const char *, const char *);
+extern char *auth_GetSecret(const char *, size_t);
+extern int auth_SetPhoneList(const char *, char *, int);
+extern int auth_Select(struct bundle *, const char *);
+extern struct mbuf *auth_ReadHeader(struct authinfo *, struct mbuf *);
+extern struct mbuf *auth_ReadName(struct authinfo *, struct mbuf *, size_t);
diff --git a/src/bundle.c b/src/bundle.c
new file mode 100644
index 0000000..d5fe108
--- /dev/null
+++ b/src/bundle.c
@@ -0,0 +1,2019 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/bundle.c,v 1.136.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <net/if_tun.h>		/* For TUNS* ioctls */
+#include <net/route.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#ifdef __OpenBSD__
+#include <util.h>
+#else
+#include <libutil.h>
+#endif
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "id.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "route.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "async.h"
+#include "physical.h"
+#include "auth.h"
+#include "proto.h"
+#include "chap.h"
+#include "tun.h"
+#include "prompt.h"
+#include "chat.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "iface.h"
+#include "server.h"
+#include "probe.h"
+#ifndef NODES
+#include "mppe.h"
+#endif
+
+#define SCATTER_SEGMENTS 7  /* version, datalink, name, physical,
+                               throughput, throughput, device       */
+
+#define SEND_MAXFD 3        /* Max file descriptors passed through
+                               the local domain socket              */
+
+static int bundle_RemainingIdleTime(struct bundle *);
+
+static const char * const PhaseNames[] = {
+  "Dead", "Establish", "Authenticate", "Network", "Terminate"
+};
+
+const char *
+bundle_PhaseName(struct bundle *bundle)
+{
+  return bundle->phase <= PHASE_TERMINATE ?
+    PhaseNames[bundle->phase] : "unknown";
+}
+
+void
+bundle_NewPhase(struct bundle *bundle, u_int new)
+{
+  if (new == bundle->phase)
+    return;
+
+  if (new <= PHASE_TERMINATE)
+    log_Printf(LogPHASE, "bundle: %s\n", PhaseNames[new]);
+
+  switch (new) {
+  case PHASE_DEAD:
+    bundle->phase = new;
+#ifndef NODES
+    MPPE_MasterKeyValid = 0;
+#endif
+    log_DisplayPrompts();
+    break;
+
+  case PHASE_ESTABLISH:
+    bundle->phase = new;
+    break;
+
+  case PHASE_AUTHENTICATE:
+    bundle->phase = new;
+    log_DisplayPrompts();
+    break;
+
+  case PHASE_NETWORK:
+    if (ncp_fsmStart(&bundle->ncp, bundle)) {
+      bundle->phase = new;
+      log_DisplayPrompts();
+    } else {
+      log_Printf(LogPHASE, "bundle: All NCPs are disabled\n");
+      bundle_Close(bundle, NULL, CLOSE_STAYDOWN);
+    }
+    break;
+
+  case PHASE_TERMINATE:
+    bundle->phase = new;
+    mp_Down(&bundle->ncp.mp);
+    log_DisplayPrompts();
+    break;
+  }
+}
+
+static void
+bundle_LayerStart(void *v __unused, struct fsm *fp __unused)
+{
+  /* The given FSM is about to start up ! */
+}
+
+
+void
+bundle_Notify(struct bundle *bundle, char c)
+{
+  if (bundle->notify.fd != -1) {
+    int ret;
+
+    ret = write(bundle->notify.fd, &c, 1);
+    if (c != EX_REDIAL && c != EX_RECONNECT) {
+      if (ret == 1)
+        log_Printf(LogCHAT, "Parent notified of %s\n",
+                   c == EX_NORMAL ? "success" : "failure");
+      else
+        log_Printf(LogERROR, "Failed to notify parent of success\n");
+      close(bundle->notify.fd);
+      bundle->notify.fd = -1;
+    } else if (ret == 1)
+      log_Printf(LogCHAT, "Parent notified of %s\n", ex_desc(c));
+    else
+      log_Printf(LogERROR, "Failed to notify parent of %s\n", ex_desc(c));
+  }
+}
+
+static void
+bundle_ClearQueues(void *v)
+{
+  struct bundle *bundle = (struct bundle *)v;
+  struct datalink *dl;
+
+  log_Printf(LogPHASE, "Clearing choked output queue\n");
+  timer_Stop(&bundle->choked.timer);
+
+  /*
+   * Emergency time:
+   *
+   * We've had a full queue for PACKET_DEL_SECS seconds without being
+   * able to get rid of any of the packets.  We've probably given up
+   * on the redials at this point, and the queued data has almost
+   * definitely been timed out by the layer above.  As this is preventing
+   * us from reading the TUN_NAME device (we don't want to buffer stuff
+   * indefinitely), we may as well nuke this data and start with a clean
+   * slate !
+   *
+   * Unfortunately, this has the side effect of shafting any compression
+   * dictionaries in use (causing the relevant RESET_REQ/RESET_ACK).
+   */
+
+  ncp_DeleteQueues(&bundle->ncp);
+  for (dl = bundle->links; dl; dl = dl->next)
+    physical_DeleteQueue(dl->physical);
+}
+
+static void
+bundle_LinkAdded(struct bundle *bundle, struct datalink *dl)
+{
+  bundle->phys_type.all |= dl->physical->type;
+  if (dl->state == DATALINK_OPEN)
+    bundle->phys_type.open |= dl->physical->type;
+
+#ifndef NORADIUS
+  if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL))
+      != bundle->phys_type.open && bundle->session.timer.state == TIMER_STOPPED)
+    if (bundle->radius.sessiontime)
+      bundle_StartSessionTimer(bundle, 0);
+#endif
+
+  if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL))
+      != bundle->phys_type.open && bundle->idle.timer.state == TIMER_STOPPED)
+    /* We may need to start our idle timer */
+    bundle_StartIdleTimer(bundle, 0);
+}
+
+void
+bundle_LinksRemoved(struct bundle *bundle)
+{
+  struct datalink *dl;
+
+  bundle->phys_type.all = bundle->phys_type.open = 0;
+  for (dl = bundle->links; dl; dl = dl->next)
+    bundle_LinkAdded(bundle, dl);
+
+  bundle_CalculateBandwidth(bundle);
+  mp_CheckAutoloadTimer(&bundle->ncp.mp);
+
+  if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL))
+      == bundle->phys_type.open) {
+#ifndef NORADIUS
+    if (bundle->radius.sessiontime)
+      bundle_StopSessionTimer(bundle);
+#endif
+    bundle_StopIdleTimer(bundle);
+   }
+}
+
+static void
+bundle_LayerUp(void *v, struct fsm *fp)
+{
+  /*
+   * The given fsm is now up
+   * If it's an LCP, adjust our phys_mode.open value and check the
+   * autoload timer.
+   * If it's the first NCP, calculate our bandwidth
+   * If it's the first NCP, set our ``upat'' time
+   * If it's the first NCP, start the idle timer.
+   * If it's an NCP, tell our -background parent to go away.
+   * If it's the first NCP, start the autoload timer
+   */
+  struct bundle *bundle = (struct bundle *)v;
+
+  if (fp->proto == PROTO_LCP) {
+    struct physical *p = link2physical(fp->link);
+
+    bundle_LinkAdded(bundle, p->dl);
+    mp_CheckAutoloadTimer(&bundle->ncp.mp);
+  } else if (isncp(fp->proto)) {
+    if (ncp_LayersOpen(&fp->bundle->ncp) == 1) {
+      bundle_CalculateBandwidth(fp->bundle);
+      time(&bundle->upat);
+#ifndef NORADIUS
+      if (bundle->radius.sessiontime)
+        bundle_StartSessionTimer(bundle, 0);
+#endif
+      bundle_StartIdleTimer(bundle, 0);
+      mp_CheckAutoloadTimer(&fp->bundle->ncp.mp);
+    }
+    bundle_Notify(bundle, EX_NORMAL);
+  } else if (fp->proto == PROTO_CCP)
+    bundle_CalculateBandwidth(fp->bundle);	/* Against ccp_MTUOverhead */
+}
+
+static void
+bundle_LayerDown(void *v, struct fsm *fp)
+{
+  /*
+   * The given FSM has been told to come down.
+   * If it's our last NCP, stop the idle timer.
+   * If it's our last NCP, clear our ``upat'' value.
+   * If it's our last NCP, stop the autoload timer
+   * If it's an LCP, adjust our phys_type.open value and any timers.
+   * If it's an LCP and we're in multilink mode, adjust our tun
+   * If it's the last LCP, down all NCPs
+   * speed and make sure our minimum sequence number is adjusted.
+   */
+
+  struct bundle *bundle = (struct bundle *)v;
+
+  if (isncp(fp->proto)) {
+    if (ncp_LayersOpen(&fp->bundle->ncp) == 0) {
+#ifndef NORADIUS
+      if (bundle->radius.sessiontime)
+        bundle_StopSessionTimer(bundle);
+#endif
+      bundle_StopIdleTimer(bundle);
+      bundle->upat = 0;
+      mp_StopAutoloadTimer(&bundle->ncp.mp);
+    }
+  } else if (fp->proto == PROTO_LCP) {
+    struct datalink *dl;
+    struct datalink *lost;
+    int others_active;
+
+    bundle_LinksRemoved(bundle);  /* adjust timers & phys_type values */
+
+    lost = NULL;
+    others_active = 0;
+    for (dl = bundle->links; dl; dl = dl->next) {
+      if (fp == &dl->physical->link.lcp.fsm)
+        lost = dl;
+      else if (dl->state != DATALINK_CLOSED && dl->state != DATALINK_HANGUP)
+        others_active++;
+    }
+
+    if (bundle->ncp.mp.active) {
+      bundle_CalculateBandwidth(bundle);
+
+      if (lost)
+        mp_LinkLost(&bundle->ncp.mp, lost);
+      else
+        log_Printf(LogALERT, "Oops, lost an unrecognised datalink (%s) !\n",
+                   fp->link->name);
+    }
+
+    if (!others_active) {
+      /* Down the NCPs.  We don't expect to get fsm_Close()d ourself ! */
+      ncp2initial(&bundle->ncp);
+      mp_Down(&bundle->ncp.mp);
+    }
+  }
+}
+
+static void
+bundle_LayerFinish(void *v, struct fsm *fp)
+{
+  /* The given fsm is now down (fp cannot be NULL)
+   *
+   * If it's the last NCP, fsm_Close all LCPs
+   * If it's the last NCP, bring any MP layer down
+   */
+
+  struct bundle *bundle = (struct bundle *)v;
+  struct datalink *dl;
+
+  if (isncp(fp->proto) && !ncp_LayersUnfinished(&bundle->ncp)) {
+    if (bundle_Phase(bundle) != PHASE_DEAD)
+      bundle_NewPhase(bundle, PHASE_TERMINATE);
+    for (dl = bundle->links; dl; dl = dl->next)
+      if (dl->state == DATALINK_OPEN)
+        datalink_Close(dl, CLOSE_STAYDOWN);
+    fsm2initial(fp);
+    mp_Down(&bundle->ncp.mp);
+  }
+}
+
+void
+bundle_Close(struct bundle *bundle, const char *name, int how)
+{
+  /*
+   * Please close the given datalink.
+   * If name == NULL or name is the last datalink, fsm_Close all NCPs
+   * (except our MP)
+   * If it isn't the last datalink, just Close that datalink.
+   */
+
+  struct datalink *dl, *this_dl;
+  int others_active;
+
+  others_active = 0;
+  this_dl = NULL;
+
+  for (dl = bundle->links; dl; dl = dl->next) {
+    if (name && !strcasecmp(name, dl->name))
+      this_dl = dl;
+    if (name == NULL || this_dl == dl) {
+      switch (how) {
+        case CLOSE_LCP:
+          datalink_DontHangup(dl);
+          break;
+        case CLOSE_STAYDOWN:
+          datalink_StayDown(dl);
+          break;
+      }
+    } else if (dl->state != DATALINK_CLOSED && dl->state != DATALINK_HANGUP)
+      others_active++;
+  }
+
+  if (name && this_dl == NULL) {
+    log_Printf(LogWARN, "%s: Invalid datalink name\n", name);
+    return;
+  }
+
+  if (!others_active) {
+#ifndef NORADIUS
+    if (bundle->radius.sessiontime)
+      bundle_StopSessionTimer(bundle);
+#endif
+    bundle_StopIdleTimer(bundle);
+    if (ncp_LayersUnfinished(&bundle->ncp))
+      ncp_Close(&bundle->ncp);
+    else {
+      ncp2initial(&bundle->ncp);
+      mp_Down(&bundle->ncp.mp);
+      for (dl = bundle->links; dl; dl = dl->next)
+        datalink_Close(dl, how);
+    }
+  } else if (this_dl && this_dl->state != DATALINK_CLOSED &&
+             this_dl->state != DATALINK_HANGUP)
+    datalink_Close(this_dl, how);
+}
+
+void
+bundle_Down(struct bundle *bundle, int how)
+{
+  struct datalink *dl;
+
+  for (dl = bundle->links; dl; dl = dl->next)
+    datalink_Down(dl, how);
+}
+
+static int
+bundle_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n)
+{
+  struct bundle *bundle = descriptor2bundle(d);
+  struct datalink *dl;
+  int result, nlinks;
+  u_short ifqueue;
+  size_t queued;
+
+  result = 0;
+
+  /* If there are aren't many packets queued, look for some more. */
+  for (nlinks = 0, dl = bundle->links; dl; dl = dl->next)
+    nlinks++;
+
+  if (nlinks) {
+    queued = r ? ncp_FillPhysicalQueues(&bundle->ncp, bundle) :
+                 ncp_QueueLen(&bundle->ncp);
+
+    if (r && (bundle->phase == PHASE_NETWORK ||
+              bundle->phys_type.all & PHYS_AUTO)) {
+      /* enough surplus so that we can tell if we're getting swamped */
+      ifqueue = nlinks > bundle->cfg.ifqueue ? nlinks : bundle->cfg.ifqueue;
+      if (queued < ifqueue) {
+        /* Not enough - select() for more */
+        if (bundle->choked.timer.state == TIMER_RUNNING)
+          timer_Stop(&bundle->choked.timer);	/* Not needed any more */
+        FD_SET(bundle->dev.fd, r);
+        if (*n < bundle->dev.fd + 1)
+          *n = bundle->dev.fd + 1;
+        log_Printf(LogTIMER, "%s: fdset(r) %d\n", TUN_NAME, bundle->dev.fd);
+        result++;
+      } else if (bundle->choked.timer.state == TIMER_STOPPED) {
+        bundle->choked.timer.func = bundle_ClearQueues;
+        bundle->choked.timer.name = "output choke";
+        bundle->choked.timer.load = bundle->cfg.choked.timeout * SECTICKS;
+        bundle->choked.timer.arg = bundle;
+        timer_Start(&bundle->choked.timer);
+      }
+    }
+  }
+
+#ifndef NORADIUS
+  result += descriptor_UpdateSet(&bundle->radius.desc, r, w, e, n);
+#endif
+
+  /* Which links need a select() ? */
+  for (dl = bundle->links; dl; dl = dl->next)
+    result += descriptor_UpdateSet(&dl->desc, r, w, e, n);
+
+  /*
+   * This *MUST* be called after the datalink UpdateSet()s as it
+   * might be ``holding'' one of the datalinks (death-row) and
+   * wants to be able to de-select() it from the descriptor set.
+   */
+  result += descriptor_UpdateSet(&bundle->ncp.mp.server.desc, r, w, e, n);
+
+  return result;
+}
+
+static int
+bundle_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+  struct bundle *bundle = descriptor2bundle(d);
+  struct datalink *dl;
+
+  for (dl = bundle->links; dl; dl = dl->next)
+    if (descriptor_IsSet(&dl->desc, fdset))
+      return 1;
+
+#ifndef NORADIUS
+  if (descriptor_IsSet(&bundle->radius.desc, fdset))
+    return 1;
+#endif
+
+  if (descriptor_IsSet(&bundle->ncp.mp.server.desc, fdset))
+    return 1;
+
+  return FD_ISSET(bundle->dev.fd, fdset);
+}
+
+static void
+bundle_DescriptorRead(struct fdescriptor *d __unused, struct bundle *bundle,
+                      const fd_set *fdset)
+{
+  struct datalink *dl;
+  unsigned secs;
+  u_int32_t af;
+
+  if (descriptor_IsSet(&bundle->ncp.mp.server.desc, fdset))
+    descriptor_Read(&bundle->ncp.mp.server.desc, bundle, fdset);
+
+  for (dl = bundle->links; dl; dl = dl->next)
+    if (descriptor_IsSet(&dl->desc, fdset))
+      descriptor_Read(&dl->desc, bundle, fdset);
+
+#ifndef NORADIUS
+  if (descriptor_IsSet(&bundle->radius.desc, fdset))
+    descriptor_Read(&bundle->radius.desc, bundle, fdset);
+#endif
+
+  if (FD_ISSET(bundle->dev.fd, fdset)) {
+    struct tun_data tun;
+    int n, pri;
+    u_char *data;
+    size_t sz;
+
+    if (bundle->dev.header) {
+      data = (u_char *)&tun;
+      sz = sizeof tun;
+    } else {
+      data = tun.data;
+      sz = sizeof tun.data;
+    }
+
+    /* something to read from tun */
+
+    n = read(bundle->dev.fd, data, sz);
+    if (n < 0) {
+      log_Printf(LogWARN, "%s: read: %s\n", bundle->dev.Name, strerror(errno));
+      return;
+    }
+
+    if (bundle->dev.header) {
+      n -= sz - sizeof tun.data;
+      if (n <= 0) {
+        log_Printf(LogERROR, "%s: read: Got only %d bytes of data !\n",
+                   bundle->dev.Name, n);
+        return;
+      }
+      af = ntohl(tun.header.family);
+#ifndef NOINET6
+      if (af != AF_INET && af != AF_INET6)
+#else
+      if (af != AF_INET)
+#endif
+        /* XXX: Should be maintaining drop/family counts ! */
+        return;
+    } else
+      af = AF_INET;
+
+    if (af == AF_INET && ((struct ip *)tun.data)->ip_dst.s_addr ==
+        bundle->ncp.ipcp.my_ip.s_addr) {
+      /* we've been asked to send something addressed *to* us :( */
+      if (Enabled(bundle, OPT_LOOPBACK)) {
+        pri = PacketCheck(bundle, af, tun.data, n, &bundle->filter.in,
+                          NULL, NULL);
+        if (pri >= 0) {
+          n += sz - sizeof tun.data;
+          write(bundle->dev.fd, data, n);
+          log_Printf(LogDEBUG, "Looped back packet addressed to myself\n");
+        }
+        return;
+      } else
+        log_Printf(LogDEBUG, "Oops - forwarding packet addressed to myself\n");
+    }
+
+    /*
+     * Process on-demand dialup. Output packets are queued within the tunnel
+     * device until the appropriate NCP is opened.
+     */
+
+    if (bundle_Phase(bundle) == PHASE_DEAD) {
+      /*
+       * Note, we must be in AUTO mode :-/ otherwise our interface should
+       * *not* be UP and we can't receive data
+       */
+      pri = PacketCheck(bundle, af, tun.data, n, &bundle->filter.dial,
+                        NULL, NULL);
+      if (pri >= 0)
+        bundle_Open(bundle, NULL, PHYS_AUTO, 0);
+      else
+        /*
+         * Drop the packet.  If we were to queue it, we'd just end up with
+         * a pile of timed-out data in our output queue by the time we get
+         * around to actually dialing.  We'd also prematurely reach the
+         * threshold at which we stop select()ing to read() the tun
+         * device - breaking auto-dial.
+         */
+        return;
+    }
+
+    secs = 0;
+    pri = PacketCheck(bundle, af, tun.data, n, &bundle->filter.out,
+                      NULL, &secs);
+    if (pri >= 0) {
+      /* Prepend the number of seconds timeout given in the filter */
+      tun.header.timeout = secs;
+      ncp_Enqueue(&bundle->ncp, af, pri, (char *)&tun, n + sizeof tun.header);
+    }
+  }
+}
+
+static int
+bundle_DescriptorWrite(struct fdescriptor *d __unused, struct bundle *bundle,
+                       const fd_set *fdset)
+{
+  struct datalink *dl;
+  int result = 0;
+
+  /* This is not actually necessary as struct mpserver doesn't Write() */
+  if (descriptor_IsSet(&bundle->ncp.mp.server.desc, fdset))
+    if (descriptor_Write(&bundle->ncp.mp.server.desc, bundle, fdset) == 1)
+      result++;
+
+  for (dl = bundle->links; dl; dl = dl->next)
+    if (descriptor_IsSet(&dl->desc, fdset))
+      switch (descriptor_Write(&dl->desc, bundle, fdset)) {
+      case -1:
+        datalink_ComeDown(dl, CLOSE_NORMAL);
+        break;
+      case 1:
+        result++;
+      }
+
+  return result;
+}
+
+void
+bundle_LockTun(struct bundle *bundle)
+{
+  FILE *lockfile;
+  char pidfile[PATH_MAX];
+
+  snprintf(pidfile, sizeof pidfile, "%stun%d.pid", _PATH_VARRUN, bundle->unit);
+  lockfile = ID0fopen(pidfile, "w");
+  if (lockfile != NULL) {
+    fprintf(lockfile, "%d\n", (int)getpid());
+    fclose(lockfile);
+  }
+#ifndef RELEASE_CRUNCH
+  else
+    log_Printf(LogERROR, "Warning: Can't create %s: %s\n",
+               pidfile, strerror(errno));
+#endif
+}
+
+static void
+bundle_UnlockTun(struct bundle *bundle)
+{
+  char pidfile[PATH_MAX];
+
+  snprintf(pidfile, sizeof pidfile, "%stun%d.pid", _PATH_VARRUN, bundle->unit);
+  ID0unlink(pidfile);
+}
+
+struct bundle *
+bundle_Create(const char *prefix, int type, int unit)
+{
+  static struct bundle bundle;		/* there can be only one */
+  int enoentcount, err, minunit, maxunit;
+  const char *ifname;
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+  int kldtried;
+#endif
+#if defined(TUNSIFMODE) || defined(TUNSLMODE) || defined(TUNSIFHEAD)
+  int iff;
+#endif
+
+  if (bundle.iface != NULL) {	/* Already allocated ! */
+    log_Printf(LogALERT, "bundle_Create:  There's only one BUNDLE !\n");
+    return NULL;
+  }
+
+  if (unit == -1) {
+    minunit = 0;
+    maxunit = -1;
+  } else {
+    minunit = unit;
+    maxunit = unit + 1;
+  }
+  err = ENOENT;
+  enoentcount = 0;
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+  kldtried = 0;
+#endif
+  for (bundle.unit = minunit; bundle.unit != maxunit; bundle.unit++) {
+    snprintf(bundle.dev.Name, sizeof bundle.dev.Name, "%s%d",
+             prefix, bundle.unit);
+    bundle.dev.fd = ID0open(bundle.dev.Name, O_RDWR);
+    if (bundle.dev.fd >= 0)
+      break;
+    else if (errno == ENXIO || errno == ENOENT) {
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+      if (bundle.unit == minunit && !kldtried++) {
+        /*
+         * Attempt to load the tunnel interface KLD if it isn't loaded
+         * already.
+         */
+        if (loadmodules(LOAD_VERBOSLY, "if_tun", NULL))
+          bundle.unit--;
+        continue;
+      }
+#endif
+      if (errno != ENOENT || ++enoentcount > 2) {
+        err = errno;
+	break;
+      }
+    } else
+      err = errno;
+  }
+
+  if (bundle.dev.fd < 0) {
+    if (unit == -1)
+      log_Printf(LogWARN, "No available tunnel devices found (%s)\n",
+                strerror(err));
+    else
+      log_Printf(LogWARN, "%s%d: %s\n", prefix, unit, strerror(err));
+    return NULL;
+  }
+
+  log_SetTun(bundle.unit);
+
+  ifname = strrchr(bundle.dev.Name, '/');
+  if (ifname == NULL)
+    ifname = bundle.dev.Name;
+  else
+    ifname++;
+
+  bundle.iface = iface_Create(ifname);
+  if (bundle.iface == NULL) {
+    close(bundle.dev.fd);
+    return NULL;
+  }
+
+#ifdef TUNSIFMODE
+  /* Make sure we're POINTOPOINT & IFF_MULTICAST */
+  iff = IFF_POINTOPOINT | IFF_MULTICAST;
+  if (ID0ioctl(bundle.dev.fd, TUNSIFMODE, &iff) < 0)
+    log_Printf(LogERROR, "bundle_Create: ioctl(TUNSIFMODE): %s\n",
+	       strerror(errno));
+#endif
+
+#ifdef TUNSLMODE
+  /* Make sure we're not prepending sockaddrs */
+  iff = 0;
+  if (ID0ioctl(bundle.dev.fd, TUNSLMODE, &iff) < 0)
+    log_Printf(LogERROR, "bundle_Create: ioctl(TUNSLMODE): %s\n",
+	       strerror(errno));
+#endif
+
+#ifdef TUNSIFHEAD
+  /* We want the address family please ! */
+  iff = 1;
+  if (ID0ioctl(bundle.dev.fd, TUNSIFHEAD, &iff) < 0) {
+    log_Printf(LogERROR, "bundle_Create: ioctl(TUNSIFHEAD): %s\n",
+	       strerror(errno));
+    bundle.dev.header = 0;
+  } else
+    bundle.dev.header = 1;
+#else
+#ifdef __OpenBSD__
+  /* Always present for OpenBSD */
+  bundle.dev.header = 1;
+#else
+  /*
+   * If TUNSIFHEAD isn't available and we're not OpenBSD, assume
+   * everything's AF_INET (hopefully the tun device won't pass us
+   * anything else !).
+   */
+  bundle.dev.header = 0;
+#endif
+#endif
+
+  log_Printf(LogPHASE, "Using interface: %s\n", ifname);
+
+  bundle.bandwidth = 0;
+  bundle.routing_seq = 0;
+  bundle.phase = PHASE_DEAD;
+  bundle.CleaningUp = 0;
+  bundle.NatEnabled = 0;
+
+  bundle.fsm.LayerStart = bundle_LayerStart;
+  bundle.fsm.LayerUp = bundle_LayerUp;
+  bundle.fsm.LayerDown = bundle_LayerDown;
+  bundle.fsm.LayerFinish = bundle_LayerFinish;
+  bundle.fsm.object = &bundle;
+
+  bundle.cfg.idle.timeout = NCP_IDLE_TIMEOUT;
+  bundle.cfg.idle.min_timeout = 0;
+  *bundle.cfg.auth.name = '\0';
+  *bundle.cfg.auth.key = '\0';
+  bundle.cfg.optmask = (1ull << OPT_IDCHECK) | (1ull << OPT_LOOPBACK) |
+                       (1ull << OPT_SROUTES) | (1ull << OPT_TCPMSSFIXUP) |
+                       (1ull << OPT_THROUGHPUT) | (1ull << OPT_UTMP) |
+                       (1ull << OPT_NAS_IP_ADDRESS) |
+                       (1ull << OPT_NAS_IDENTIFIER);
+#ifndef NOINET6
+  opt_enable(&bundle, OPT_IPCP);
+  if (probe.ipv6_available)
+    opt_enable(&bundle, OPT_IPV6CP);
+#endif
+  *bundle.cfg.label = '\0';
+  bundle.cfg.ifqueue = DEF_IFQUEUE;
+  bundle.cfg.choked.timeout = CHOKED_TIMEOUT;
+  bundle.phys_type.all = type;
+  bundle.phys_type.open = 0;
+  bundle.upat = 0;
+
+  bundle.links = datalink_Create("deflink", &bundle, type);
+  if (bundle.links == NULL) {
+    log_Printf(LogALERT, "Cannot create data link: %s\n", strerror(errno));
+    iface_Destroy(bundle.iface);
+    bundle.iface = NULL;
+    close(bundle.dev.fd);
+    return NULL;
+  }
+
+  bundle.desc.type = BUNDLE_DESCRIPTOR;
+  bundle.desc.UpdateSet = bundle_UpdateSet;
+  bundle.desc.IsSet = bundle_IsSet;
+  bundle.desc.Read = bundle_DescriptorRead;
+  bundle.desc.Write = bundle_DescriptorWrite;
+
+  ncp_Init(&bundle.ncp, &bundle);
+
+  memset(&bundle.filter, '\0', sizeof bundle.filter);
+  bundle.filter.in.fragok = bundle.filter.in.logok = 1;
+  bundle.filter.in.name = "IN";
+  bundle.filter.out.fragok = bundle.filter.out.logok = 1;
+  bundle.filter.out.name = "OUT";
+  bundle.filter.dial.name = "DIAL";
+  bundle.filter.dial.logok = 1;
+  bundle.filter.alive.name = "ALIVE";
+  bundle.filter.alive.logok = 1;
+  {
+    int i;
+    for (i = 0; i < MAXFILTERS; i++) {
+        bundle.filter.in.rule[i].f_action = A_NONE;
+        bundle.filter.out.rule[i].f_action = A_NONE;
+        bundle.filter.dial.rule[i].f_action = A_NONE;
+        bundle.filter.alive.rule[i].f_action = A_NONE;
+    }
+  }
+  memset(&bundle.idle.timer, '\0', sizeof bundle.idle.timer);
+  bundle.idle.done = 0;
+  bundle.notify.fd = -1;
+  memset(&bundle.choked.timer, '\0', sizeof bundle.choked.timer);
+#ifndef NORADIUS
+  radius_Init(&bundle.radius);
+#endif
+
+  /* Clean out any leftover crud */
+  iface_Clear(bundle.iface, &bundle.ncp, 0, IFACE_CLEAR_ALL);
+
+  bundle_LockTun(&bundle);
+
+  return &bundle;
+}
+
+static void
+bundle_DownInterface(struct bundle *bundle)
+{
+  route_IfDelete(bundle, 1);
+  iface_ClearFlags(bundle->iface->name, IFF_UP);
+}
+
+void
+bundle_Destroy(struct bundle *bundle)
+{
+  struct datalink *dl;
+
+  /*
+   * Clean up the interface.  We don't really need to do the timer_Stop()s,
+   * mp_Down(), iface_Clear() and bundle_DownInterface() unless we're getting
+   * out under exceptional conditions such as a descriptor exception.
+   */
+  timer_Stop(&bundle->idle.timer);
+  timer_Stop(&bundle->choked.timer);
+  mp_Down(&bundle->ncp.mp);
+  iface_Clear(bundle->iface, &bundle->ncp, 0, IFACE_CLEAR_ALL);
+  bundle_DownInterface(bundle);
+
+#ifndef NORADIUS
+  /* Tell the radius server the bad news */
+  radius_Destroy(&bundle->radius);
+#endif
+
+  /* Again, these are all DATALINK_CLOSED unless we're abending */
+  dl = bundle->links;
+  while (dl)
+    dl = datalink_Destroy(dl);
+
+  ncp_Destroy(&bundle->ncp);
+
+  close(bundle->dev.fd);
+  bundle_UnlockTun(bundle);
+
+  /* In case we never made PHASE_NETWORK */
+  bundle_Notify(bundle, EX_ERRDEAD);
+
+  iface_Destroy(bundle->iface);
+  bundle->iface = NULL;
+}
+
+void
+bundle_LinkClosed(struct bundle *bundle, struct datalink *dl)
+{
+  /*
+   * Our datalink has closed.
+   * CleanDatalinks() (called from DoLoop()) will remove closed
+   * BACKGROUND, FOREGROUND and DIRECT links.
+   * If it's the last data link, enter phase DEAD.
+   *
+   * NOTE: dl may not be in our list (bundle_SendDatalink()) !
+   */
+
+  struct datalink *odl;
+  int other_links;
+
+  log_SetTtyCommandMode(dl);
+
+  other_links = 0;
+  for (odl = bundle->links; odl; odl = odl->next)
+    if (odl != dl && odl->state != DATALINK_CLOSED)
+      other_links++;
+
+  if (!other_links) {
+    if (dl->physical->type != PHYS_AUTO)	/* Not in -auto mode */
+      bundle_DownInterface(bundle);
+    ncp2initial(&bundle->ncp);
+    mp_Down(&bundle->ncp.mp);
+    bundle_NewPhase(bundle, PHASE_DEAD);
+#ifndef NORADIUS
+    if (bundle->radius.sessiontime)
+      bundle_StopSessionTimer(bundle);
+#endif
+    bundle_StopIdleTimer(bundle);
+  }
+}
+
+void
+bundle_Open(struct bundle *bundle, const char *name, int mask, int force)
+{
+  /*
+   * Please open the given datalink, or all if name == NULL
+   */
+  struct datalink *dl;
+
+  for (dl = bundle->links; dl; dl = dl->next)
+    if (name == NULL || !strcasecmp(dl->name, name)) {
+      if ((mask & dl->physical->type) &&
+          (dl->state == DATALINK_CLOSED ||
+           (force && dl->state == DATALINK_OPENING &&
+            dl->dial.timer.state == TIMER_RUNNING) ||
+           dl->state == DATALINK_READY)) {
+        timer_Stop(&dl->dial.timer);	/* We're finished with this */
+        datalink_Up(dl, 1, 1);
+        if (mask & PHYS_AUTO)
+          break;			/* Only one AUTO link at a time */
+      }
+      if (name != NULL)
+        break;
+    }
+}
+
+struct datalink *
+bundle2datalink(struct bundle *bundle, const char *name)
+{
+  struct datalink *dl;
+
+  if (name != NULL) {
+    for (dl = bundle->links; dl; dl = dl->next)
+      if (!strcasecmp(dl->name, name))
+        return dl;
+  } else if (bundle->links && !bundle->links->next)
+    return bundle->links;
+
+  return NULL;
+}
+
+int
+bundle_ShowLinks(struct cmdargs const *arg)
+{
+  struct datalink *dl;
+  struct pppThroughput *t;
+  unsigned long long octets;
+  int secs;
+
+  for (dl = arg->bundle->links; dl; dl = dl->next) {
+    octets = MAX(dl->physical->link.stats.total.in.OctetsPerSecond,
+                 dl->physical->link.stats.total.out.OctetsPerSecond);
+
+    prompt_Printf(arg->prompt, "Name: %s [%s, %s]",
+                  dl->name, mode2Nam(dl->physical->type), datalink_State(dl));
+    if (dl->physical->link.stats.total.rolling && dl->state == DATALINK_OPEN)
+      prompt_Printf(arg->prompt, " bandwidth %d, %llu bps (%llu bytes/sec)",
+                    dl->mp.bandwidth ? dl->mp.bandwidth :
+                                       physical_GetSpeed(dl->physical),
+                    octets * 8, octets);
+    prompt_Printf(arg->prompt, "\n");
+  }
+
+  t = &arg->bundle->ncp.mp.link.stats.total;
+  octets = MAX(t->in.OctetsPerSecond, t->out.OctetsPerSecond);
+  secs = t->downtime ? 0 : throughput_uptime(t);
+  if (secs > t->SamplePeriod)
+    secs = t->SamplePeriod;
+  if (secs)
+    prompt_Printf(arg->prompt, "Currently averaging %llu bps (%llu bytes/sec)"
+                  " over the last %d secs\n", octets * 8, octets, secs);
+
+  return 0;
+}
+
+static const char *
+optval(struct bundle *bundle, int opt)
+{
+  return Enabled(bundle, opt) ? "enabled" : "disabled";
+}
+
+int
+bundle_ShowStatus(struct cmdargs const *arg)
+{
+  int remaining;
+
+  prompt_Printf(arg->prompt, "Phase %s\n", bundle_PhaseName(arg->bundle));
+  prompt_Printf(arg->prompt, " Device:        %s\n", arg->bundle->dev.Name);
+  prompt_Printf(arg->prompt, " Interface:     %s @ %lubps",
+                arg->bundle->iface->name, arg->bundle->bandwidth);
+
+  if (arg->bundle->upat) {
+    int secs = bundle_Uptime(arg->bundle);
+
+    prompt_Printf(arg->prompt, ", up time %d:%02d:%02d", secs / 3600,
+                  (secs / 60) % 60, secs % 60);
+  }
+  prompt_Printf(arg->prompt, "\n Queued:        %lu of %u\n",
+                (unsigned long)ncp_QueueLen(&arg->bundle->ncp),
+                arg->bundle->cfg.ifqueue);
+
+  prompt_Printf(arg->prompt, "\nDefaults:\n");
+  prompt_Printf(arg->prompt, " Label:             %s\n",
+                arg->bundle->cfg.label);
+  prompt_Printf(arg->prompt, " Auth name:         %s\n",
+                arg->bundle->cfg.auth.name);
+  prompt_Printf(arg->prompt, " Diagnostic socket: ");
+  if (*server.cfg.sockname != '\0') {
+    prompt_Printf(arg->prompt, "%s", server.cfg.sockname);
+    if (server.cfg.mask != (mode_t)-1)
+      prompt_Printf(arg->prompt, ", mask 0%03o", (int)server.cfg.mask);
+    prompt_Printf(arg->prompt, "%s\n", server.fd == -1 ? " (not open)" : "");
+  } else if (server.cfg.port != 0)
+    prompt_Printf(arg->prompt, "TCP port %d%s\n", server.cfg.port,
+                  server.fd == -1 ? " (not open)" : "");
+  else
+    prompt_Printf(arg->prompt, "none\n");
+
+  prompt_Printf(arg->prompt, " Choked Timer:      %us\n",
+                arg->bundle->cfg.choked.timeout);
+
+#ifndef NORADIUS
+  radius_Show(&arg->bundle->radius, arg->prompt);
+#endif
+
+  prompt_Printf(arg->prompt, " Idle Timer:        ");
+  if (arg->bundle->cfg.idle.timeout) {
+    prompt_Printf(arg->prompt, "%us", arg->bundle->cfg.idle.timeout);
+    if (arg->bundle->cfg.idle.min_timeout)
+      prompt_Printf(arg->prompt, ", min %us",
+                    arg->bundle->cfg.idle.min_timeout);
+    remaining = bundle_RemainingIdleTime(arg->bundle);
+    if (remaining != -1)
+      prompt_Printf(arg->prompt, " (%ds remaining)", remaining);
+    prompt_Printf(arg->prompt, "\n");
+  } else
+    prompt_Printf(arg->prompt, "disabled\n");
+
+  prompt_Printf(arg->prompt, " Filter Decap:      %-20.20s",
+                optval(arg->bundle, OPT_FILTERDECAP));
+  prompt_Printf(arg->prompt, " ID check:          %s\n",
+                optval(arg->bundle, OPT_IDCHECK));
+  prompt_Printf(arg->prompt, " Iface-Alias:       %-20.20s",
+                optval(arg->bundle, OPT_IFACEALIAS));
+#ifndef NOINET6
+  prompt_Printf(arg->prompt, " IPCP:              %s\n",
+                optval(arg->bundle, OPT_IPCP));
+  prompt_Printf(arg->prompt, " IPV6CP:            %-20.20s",
+                optval(arg->bundle, OPT_IPV6CP));
+#endif
+  prompt_Printf(arg->prompt, " Keep-Session:      %s\n",
+                optval(arg->bundle, OPT_KEEPSESSION));
+  prompt_Printf(arg->prompt, " Loopback:          %-20.20s",
+                optval(arg->bundle, OPT_LOOPBACK));
+  prompt_Printf(arg->prompt, " PasswdAuth:        %s\n",
+                optval(arg->bundle, OPT_PASSWDAUTH));
+  prompt_Printf(arg->prompt, " Proxy:             %-20.20s",
+                optval(arg->bundle, OPT_PROXY));
+  prompt_Printf(arg->prompt, " Proxyall:          %s\n",
+                optval(arg->bundle, OPT_PROXYALL));
+  prompt_Printf(arg->prompt, " Sticky Routes:     %-20.20s",
+                optval(arg->bundle, OPT_SROUTES));
+  prompt_Printf(arg->prompt, " TCPMSS Fixup:      %s\n",
+                optval(arg->bundle, OPT_TCPMSSFIXUP));
+  prompt_Printf(arg->prompt, " Throughput:        %-20.20s",
+                optval(arg->bundle, OPT_THROUGHPUT));
+  prompt_Printf(arg->prompt, " Utmp Logging:      %s\n",
+                optval(arg->bundle, OPT_UTMP));
+  prompt_Printf(arg->prompt, " NAS-IP-Address:    %-20.20s",
+                optval(arg->bundle, OPT_NAS_IP_ADDRESS));
+  prompt_Printf(arg->prompt, " NAS-Identifier:    %s\n",
+                optval(arg->bundle, OPT_NAS_IDENTIFIER));
+
+  return 0;
+}
+
+static void
+bundle_IdleTimeout(void *v)
+{
+  struct bundle *bundle = (struct bundle *)v;
+
+  log_Printf(LogPHASE, "Idle timer expired\n");
+  bundle_StopIdleTimer(bundle);
+  bundle_Close(bundle, NULL, CLOSE_STAYDOWN);
+}
+
+/*
+ *  Start Idle timer. If timeout is reached, we call bundle_Close() to
+ *  close LCP and link.
+ */
+void
+bundle_StartIdleTimer(struct bundle *bundle, unsigned secs)
+{
+  timer_Stop(&bundle->idle.timer);
+  if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL)) !=
+      bundle->phys_type.open && bundle->cfg.idle.timeout) {
+    time_t now = time(NULL);
+
+    if (secs == 0)
+      secs = bundle->cfg.idle.timeout;
+
+    /* We want at least `secs' */
+    if (bundle->cfg.idle.min_timeout > secs && bundle->upat) {
+      unsigned up = now - bundle->upat;
+
+      if (bundle->cfg.idle.min_timeout > up &&
+          bundle->cfg.idle.min_timeout - up > (long long)secs)
+        /* Only increase from the current `remaining' value */
+        secs = bundle->cfg.idle.min_timeout - up;
+    }
+    bundle->idle.timer.func = bundle_IdleTimeout;
+    bundle->idle.timer.name = "idle";
+    bundle->idle.timer.load = secs * SECTICKS;
+    bundle->idle.timer.arg = bundle;
+    timer_Start(&bundle->idle.timer);
+    bundle->idle.done = now + secs;
+  }
+}
+
+void
+bundle_SetIdleTimer(struct bundle *bundle, unsigned timeout,
+		    unsigned min_timeout)
+{
+  bundle->cfg.idle.timeout = timeout;
+  bundle->cfg.idle.min_timeout = min_timeout;
+  if (ncp_LayersOpen(&bundle->ncp))
+    bundle_StartIdleTimer(bundle, 0);
+}
+
+void
+bundle_StopIdleTimer(struct bundle *bundle)
+{
+  timer_Stop(&bundle->idle.timer);
+  bundle->idle.done = 0;
+}
+
+static int
+bundle_RemainingIdleTime(struct bundle *bundle)
+{
+  if (bundle->idle.done)
+    return bundle->idle.done - time(NULL);
+  return -1;
+}
+
+#ifndef NORADIUS
+
+static void
+bundle_SessionTimeout(void *v)
+{
+  struct bundle *bundle = (struct bundle *)v;
+
+  log_Printf(LogPHASE, "Session-Timeout timer expired\n");
+  bundle_StopSessionTimer(bundle);
+  bundle_Close(bundle, NULL, CLOSE_STAYDOWN);
+}
+
+void
+bundle_StartSessionTimer(struct bundle *bundle, unsigned secs)
+{
+  timer_Stop(&bundle->session.timer);
+  if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL)) !=
+      bundle->phys_type.open && bundle->radius.sessiontime) {
+    time_t now = time(NULL);
+
+    if (secs == 0)
+      secs = bundle->radius.sessiontime;
+
+    bundle->session.timer.func = bundle_SessionTimeout;
+    bundle->session.timer.name = "session";
+    bundle->session.timer.load = secs * SECTICKS;
+    bundle->session.timer.arg = bundle;
+    timer_Start(&bundle->session.timer);
+    bundle->session.done = now + secs;
+  }
+}
+
+void
+bundle_StopSessionTimer(struct bundle *bundle)
+{
+  timer_Stop(&bundle->session.timer);
+  bundle->session.done = 0;
+}
+
+#endif
+
+int
+bundle_IsDead(struct bundle *bundle)
+{
+  return !bundle->links || (bundle->phase == PHASE_DEAD && bundle->CleaningUp);
+}
+
+static struct datalink *
+bundle_DatalinkLinkout(struct bundle *bundle, struct datalink *dl)
+{
+  struct datalink **dlp;
+
+  for (dlp = &bundle->links; *dlp; dlp = &(*dlp)->next)
+    if (*dlp == dl) {
+      *dlp = dl->next;
+      dl->next = NULL;
+      bundle_LinksRemoved(bundle);
+      return dl;
+    }
+
+  return NULL;
+}
+
+static void
+bundle_DatalinkLinkin(struct bundle *bundle, struct datalink *dl)
+{
+  struct datalink **dlp = &bundle->links;
+
+  while (*dlp)
+    dlp = &(*dlp)->next;
+
+  *dlp = dl;
+  dl->next = NULL;
+
+  bundle_LinkAdded(bundle, dl);
+  mp_CheckAutoloadTimer(&bundle->ncp.mp);
+}
+
+void
+bundle_CleanDatalinks(struct bundle *bundle)
+{
+  struct datalink **dlp = &bundle->links;
+  int found = 0;
+
+  while (*dlp)
+    if ((*dlp)->state == DATALINK_CLOSED &&
+        (*dlp)->physical->type &
+        (PHYS_DIRECT|PHYS_BACKGROUND|PHYS_FOREGROUND)) {
+      *dlp = datalink_Destroy(*dlp);
+      found++;
+    } else
+      dlp = &(*dlp)->next;
+
+  if (found)
+    bundle_LinksRemoved(bundle);
+}
+
+int
+bundle_DatalinkClone(struct bundle *bundle, struct datalink *dl,
+                     const char *name)
+{
+  if (bundle2datalink(bundle, name)) {
+    log_Printf(LogWARN, "Clone: %s: name already exists\n", name);
+    return 0;
+  }
+
+  bundle_DatalinkLinkin(bundle, datalink_Clone(dl, name));
+  return 1;
+}
+
+void
+bundle_DatalinkRemove(struct bundle *bundle, struct datalink *dl)
+{
+  dl = bundle_DatalinkLinkout(bundle, dl);
+  if (dl)
+    datalink_Destroy(dl);
+}
+
+void
+bundle_SetLabel(struct bundle *bundle, const char *label)
+{
+  if (label)
+    strncpy(bundle->cfg.label, label, sizeof bundle->cfg.label - 1);
+  else
+    *bundle->cfg.label = '\0';
+}
+
+const char *
+bundle_GetLabel(struct bundle *bundle)
+{
+  return *bundle->cfg.label ? bundle->cfg.label : NULL;
+}
+
+int
+bundle_LinkSize()
+{
+  struct iovec iov[SCATTER_SEGMENTS];
+  int niov, expect, f;
+
+  iov[0].iov_len = strlen(Version) + 1;
+  iov[0].iov_base = NULL;
+  niov = 1;
+  if (datalink2iov(NULL, iov, &niov, SCATTER_SEGMENTS, NULL, NULL) == -1) {
+    log_Printf(LogERROR, "Cannot determine space required for link\n");
+    return 0;
+  }
+
+  for (f = expect = 0; f < niov; f++)
+    expect += iov[f].iov_len;
+
+  return expect;
+}
+
+void
+bundle_ReceiveDatalink(struct bundle *bundle, int s)
+{
+  char cmsgbuf[sizeof(struct cmsghdr) + sizeof(int) * SEND_MAXFD];
+  int niov, expect, f, *fd, nfd, onfd;
+  ssize_t got;
+  struct iovec iov[SCATTER_SEGMENTS];
+  struct cmsghdr *cmsg;
+  struct msghdr msg;
+  struct datalink *dl;
+  pid_t pid;
+
+  log_Printf(LogPHASE, "Receiving datalink\n");
+
+  /*
+   * Create our scatter/gather array - passing NULL gets the space
+   * allocation requirement rather than actually flattening the
+   * structures.
+   */
+  iov[0].iov_len = strlen(Version) + 1;
+  iov[0].iov_base = NULL;
+  niov = 1;
+  if (datalink2iov(NULL, iov, &niov, SCATTER_SEGMENTS, NULL, NULL) == -1) {
+    log_Printf(LogERROR, "Cannot determine space required for link\n");
+    return;
+  }
+
+  /* Allocate the scatter/gather array for recvmsg() */
+  for (f = expect = 0; f < niov; f++) {
+    if ((iov[f].iov_base = malloc(iov[f].iov_len)) == NULL) {
+      log_Printf(LogERROR, "Cannot allocate space to receive link\n");
+      return;
+    }
+    if (f)
+      expect += iov[f].iov_len;
+  }
+
+  /* Set up our message */
+  cmsg = (struct cmsghdr *)cmsgbuf;
+  cmsg->cmsg_len = sizeof cmsgbuf;
+  cmsg->cmsg_level = SOL_SOCKET;
+  cmsg->cmsg_type = 0;
+
+  memset(&msg, '\0', sizeof msg);
+  msg.msg_name = NULL;
+  msg.msg_namelen = 0;
+  msg.msg_iov = iov;
+  msg.msg_iovlen = 1;		/* Only send the version at the first pass */
+  msg.msg_control = cmsgbuf;
+  msg.msg_controllen = sizeof cmsgbuf;
+
+  log_Printf(LogDEBUG, "Expecting %u scatter/gather bytes\n",
+             (unsigned)iov[0].iov_len);
+
+  if ((got = recvmsg(s, &msg, MSG_WAITALL)) != (ssize_t)iov[0].iov_len) {
+    if (got == -1)
+      log_Printf(LogERROR, "Failed recvmsg: %s\n", strerror(errno));
+    else
+      log_Printf(LogERROR, "Failed recvmsg: Got %zd, not %u\n",
+                 got, (unsigned)iov[0].iov_len);
+    while (niov--)
+      free(iov[niov].iov_base);
+    return;
+  }
+
+  if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
+    log_Printf(LogERROR, "Recvmsg: no descriptors received !\n");
+    while (niov--)
+      free(iov[niov].iov_base);
+    return;
+  }
+
+  fd = (int *)CMSG_DATA(cmsg);
+  nfd = ((caddr_t)cmsg + cmsg->cmsg_len - (caddr_t)fd) / sizeof(int);
+
+  if (nfd < 2) {
+    log_Printf(LogERROR, "Recvmsg: %d descriptor%s received (too few) !\n",
+               nfd, nfd == 1 ? "" : "s");
+    while (nfd--)
+      close(fd[nfd]);
+    while (niov--)
+      free(iov[niov].iov_base);
+    return;
+  }
+
+  /*
+   * We've successfully received two or more open file descriptors
+   * through our socket, plus a version string.  Make sure it's the
+   * correct version, and drop the connection if it's not.
+   */
+  if (strncmp(Version, iov[0].iov_base, iov[0].iov_len)) {
+    log_Printf(LogWARN, "Cannot receive datalink, incorrect version"
+               " (\"%.*s\", not \"%s\")\n", (int)iov[0].iov_len,
+               (char *)iov[0].iov_base, Version);
+    while (nfd--)
+      close(fd[nfd]);
+    while (niov--)
+      free(iov[niov].iov_base);
+    return;
+  }
+
+  /*
+   * Everything looks good.  Send the other side our process id so that
+   * they can transfer lock ownership, and wait for them to send the
+   * actual link data.
+   */
+  pid = getpid();
+  if ((got = write(fd[1], &pid, sizeof pid)) != sizeof pid) {
+    if (got == -1)
+      log_Printf(LogERROR, "Failed write: %s\n", strerror(errno));
+    else
+      log_Printf(LogERROR, "Failed write: Got %zd, not %d\n", got,
+                 (int)(sizeof pid));
+    while (nfd--)
+      close(fd[nfd]);
+    while (niov--)
+      free(iov[niov].iov_base);
+    return;
+  }
+
+  if ((got = readv(fd[1], iov + 1, niov - 1)) != expect) {
+    if (got == -1)
+      log_Printf(LogERROR, "Failed write: %s\n", strerror(errno));
+    else
+      log_Printf(LogERROR, "Failed write: Got %zd, not %d\n", got, expect);
+    while (nfd--)
+      close(fd[nfd]);
+    while (niov--)
+      free(iov[niov].iov_base);
+    return;
+  }
+  close(fd[1]);
+
+  onfd = nfd;	/* We've got this many in our array */
+  nfd -= 2;	/* Don't include p->fd and our reply descriptor */
+  niov = 1;	/* Skip the version id */
+  dl = iov2datalink(bundle, iov, &niov, sizeof iov / sizeof *iov, fd[0],
+                    fd + 2, &nfd);
+  if (dl) {
+
+    if (nfd) {
+      log_Printf(LogERROR, "bundle_ReceiveDatalink: Failed to handle %d "
+                 "auxiliary file descriptors (%d remain)\n", onfd, nfd);
+      datalink_Destroy(dl);
+      while (nfd--)
+        close(fd[onfd--]);
+      close(fd[0]);
+    } else {
+      bundle_DatalinkLinkin(bundle, dl);
+      datalink_AuthOk(dl);
+      bundle_CalculateBandwidth(dl->bundle);
+    }
+  } else {
+    while (nfd--)
+      close(fd[onfd--]);
+    close(fd[0]);
+    close(fd[1]);
+  }
+
+  free(iov[0].iov_base);
+}
+
+void
+bundle_SendDatalink(struct datalink *dl, int s, struct sockaddr_un *sun)
+{
+  char cmsgbuf[CMSG_SPACE(sizeof(int) * SEND_MAXFD)];
+  const char *constlock;
+  char *lock;
+  struct cmsghdr *cmsg;
+  struct msghdr msg;
+  struct iovec iov[SCATTER_SEGMENTS];
+  int niov, f, expect, newsid, fd[SEND_MAXFD], nfd, reply[2];
+  ssize_t got;
+  pid_t newpid;
+
+  log_Printf(LogPHASE, "Transmitting datalink %s\n", dl->name);
+
+  /* Record the base device name for a lock transfer later */
+  constlock = physical_LockedDevice(dl->physical);
+  if (constlock) {
+    lock = alloca(strlen(constlock) + 1);
+    strcpy(lock, constlock);
+  } else
+    lock = NULL;
+
+  bundle_LinkClosed(dl->bundle, dl);
+  bundle_DatalinkLinkout(dl->bundle, dl);
+
+  /* Build our scatter/gather array */
+  iov[0].iov_len = strlen(Version) + 1;
+  iov[0].iov_base = strdup(Version);
+  niov = 1;
+  nfd = 0;
+
+  fd[0] = datalink2iov(dl, iov, &niov, SCATTER_SEGMENTS, fd + 2, &nfd);
+
+  if (fd[0] != -1 && socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, reply) != -1) {
+    /*
+     * fd[1] is used to get the peer process id back, then to confirm that
+     * we've transferred any device locks to that process id.
+     */
+    fd[1] = reply[1];
+
+    nfd += 2;			/* Include fd[0] and fd[1] */
+    memset(&msg, '\0', sizeof msg);
+
+    msg.msg_name = NULL;
+    msg.msg_namelen = 0;
+    /*
+     * Only send the version to start...  We used to send the whole lot, but
+     * this caused problems with our RECVBUF size as a single link is about
+     * 22k !  This way, we should bump into no limits.
+     */
+    msg.msg_iovlen = 1;
+    msg.msg_iov = iov;
+    msg.msg_control = cmsgbuf;
+    msg.msg_controllen = CMSG_SPACE(sizeof(int) * nfd);
+    msg.msg_flags = 0;
+
+    cmsg = (struct cmsghdr *)cmsgbuf;
+    cmsg->cmsg_len = msg.msg_controllen;
+    cmsg->cmsg_level = SOL_SOCKET;
+    cmsg->cmsg_type = SCM_RIGHTS;
+
+    for (f = 0; f < nfd; f++)
+      *((int *)CMSG_DATA(cmsg) + f) = fd[f];
+
+    for (f = 1, expect = 0; f < niov; f++)
+      expect += iov[f].iov_len;
+
+    if (setsockopt(reply[0], SOL_SOCKET, SO_SNDBUF, &expect, sizeof(int)) == -1)
+      log_Printf(LogERROR, "setsockopt(SO_RCVBUF, %d): %s\n", expect,
+                 strerror(errno));
+    if (setsockopt(reply[1], SOL_SOCKET, SO_RCVBUF, &expect, sizeof(int)) == -1)
+      log_Printf(LogERROR, "setsockopt(SO_RCVBUF, %d): %s\n", expect,
+                 strerror(errno));
+
+    log_Printf(LogDEBUG, "Sending %d descriptor%s and %u bytes in scatter"
+               "/gather array\n", nfd, nfd == 1 ? "" : "s",
+               (unsigned)iov[0].iov_len);
+
+    if ((got = sendmsg(s, &msg, 0)) == -1)
+      log_Printf(LogERROR, "Failed sendmsg: %s: %s\n",
+                 sun->sun_path, strerror(errno));
+    else if (got != (ssize_t)iov[0].iov_len)
+      log_Printf(LogERROR, "%s: Failed initial sendmsg: Only sent %zd of %u\n",
+                 sun->sun_path, got, (unsigned)iov[0].iov_len);
+    else {
+      /* We must get the ACK before closing the descriptor ! */
+      int res;
+
+      if ((got = read(reply[0], &newpid, sizeof newpid)) == sizeof newpid) {
+        log_Printf(LogDEBUG, "Received confirmation from pid %ld\n",
+                   (long)newpid);
+        if (lock && (res = ID0uu_lock_txfr(lock, newpid)) != UU_LOCK_OK)
+            log_Printf(LogERROR, "uu_lock_txfr: %s\n", uu_lockerr(res));
+
+        log_Printf(LogDEBUG, "Transmitting link (%d bytes)\n", expect);
+        if ((got = writev(reply[0], iov + 1, niov - 1)) != expect) {
+          if (got == -1)
+            log_Printf(LogERROR, "%s: Failed writev: %s\n",
+                       sun->sun_path, strerror(errno));
+          else
+            log_Printf(LogERROR, "%s: Failed writev: Wrote %zd of %d\n",
+                       sun->sun_path, got, expect);
+        }
+      } else if (got == -1)
+        log_Printf(LogERROR, "%s: Failed socketpair read: %s\n",
+                   sun->sun_path, strerror(errno));
+      else
+        log_Printf(LogERROR, "%s: Failed socketpair read: Got %zd of %d\n",
+                   sun->sun_path, got, (int)(sizeof newpid));
+    }
+
+    close(reply[0]);
+    close(reply[1]);
+
+    newsid = Enabled(dl->bundle, OPT_KEEPSESSION) ||
+             tcgetpgrp(fd[0]) == getpgrp();
+    while (nfd)
+      close(fd[--nfd]);
+    if (newsid)
+      bundle_setsid(dl->bundle, got != -1);
+  }
+  close(s);
+
+  while (niov--)
+    free(iov[niov].iov_base);
+}
+
+int
+bundle_RenameDatalink(struct bundle *bundle, struct datalink *ndl,
+                      const char *name)
+{
+  struct datalink *dl;
+
+  if (!strcasecmp(ndl->name, name))
+    return 1;
+
+  for (dl = bundle->links; dl; dl = dl->next)
+    if (!strcasecmp(dl->name, name))
+      return 0;
+
+  datalink_Rename(ndl, name);
+  return 1;
+}
+
+int
+bundle_SetMode(struct bundle *bundle, struct datalink *dl, int mode)
+{
+  int omode;
+
+  omode = dl->physical->type;
+  if (omode == mode)
+    return 1;
+
+  if (mode == PHYS_AUTO && !(bundle->phys_type.all & PHYS_AUTO))
+    /* First auto link */
+    if (bundle->ncp.ipcp.peer_ip.s_addr == INADDR_ANY) {
+      log_Printf(LogWARN, "You must `set ifaddr' or `open' before"
+                 " changing mode to %s\n", mode2Nam(mode));
+      return 0;
+    }
+
+  if (!datalink_SetMode(dl, mode))
+    return 0;
+
+  if (mode == PHYS_AUTO && !(bundle->phys_type.all & PHYS_AUTO) &&
+      bundle->phase != PHASE_NETWORK)
+    /* First auto link, we need an interface */
+    ipcp_InterfaceUp(&bundle->ncp.ipcp);
+
+  /* Regenerate phys_type and adjust idle timer */
+  bundle_LinksRemoved(bundle);
+
+  return 1;
+}
+
+void
+bundle_setsid(struct bundle *bundle, int holdsession)
+{
+  /*
+   * Lose the current session.  This means getting rid of our pid
+   * too so that the tty device will really go away, and any getty
+   * etc will be allowed to restart.
+   */
+  pid_t pid, orig;
+  int fds[2];
+  char done;
+  struct datalink *dl;
+
+  if (!holdsession && bundle_IsDead(bundle)) {
+    /*
+     * No need to lose our session after all... we're going away anyway
+     *
+     * We should really stop the timer and pause if holdsession is set and
+     * the bundle's dead, but that leaves other resources lying about :-(
+     */
+    return;
+  }
+
+  orig = getpid();
+  if (pipe(fds) == -1) {
+    log_Printf(LogERROR, "pipe: %s\n", strerror(errno));
+    return;
+  }
+  switch ((pid = fork())) {
+    case -1:
+      log_Printf(LogERROR, "fork: %s\n", strerror(errno));
+      close(fds[0]);
+      close(fds[1]);
+      return;
+    case 0:
+      close(fds[1]);
+      read(fds[0], &done, 1);		/* uu_locks are mine ! */
+      close(fds[0]);
+      if (pipe(fds) == -1) {
+        log_Printf(LogERROR, "pipe(2): %s\n", strerror(errno));
+        return;
+      }
+      switch ((pid = fork())) {
+        case -1:
+          log_Printf(LogERROR, "fork(2): %s\n", strerror(errno));
+          close(fds[0]);
+          close(fds[1]);
+          return;
+        case 0:
+          close(fds[1]);
+          bundle_LockTun(bundle);	/* update pid */
+          read(fds[0], &done, 1);	/* uu_locks are mine ! */
+          close(fds[0]);
+          setsid();
+          bundle_ChangedPID(bundle);
+          log_Printf(LogDEBUG, "%ld -> %ld: %s session control\n",
+                     (long)orig, (long)getpid(),
+                     holdsession ? "Passed" : "Dropped");
+          timer_InitService(0);		/* Start the Timer Service */
+          break;
+        default:
+          close(fds[0]);
+          /* Give away all our physical locks (to the final process) */
+          for (dl = bundle->links; dl; dl = dl->next)
+            if (dl->state != DATALINK_CLOSED)
+              physical_ChangedPid(dl->physical, pid);
+          write(fds[1], "!", 1);	/* done */
+          close(fds[1]);
+          _exit(0);
+          break;
+      }
+      break;
+    default:
+      close(fds[0]);
+      /* Give away all our physical locks (to the intermediate process) */
+      for (dl = bundle->links; dl; dl = dl->next)
+        if (dl->state != DATALINK_CLOSED)
+          physical_ChangedPid(dl->physical, pid);
+      write(fds[1], "!", 1);	/* done */
+      close(fds[1]);
+      if (holdsession) {
+        int fd, status;
+
+        timer_TermService();
+        signal(SIGPIPE, SIG_DFL);
+        signal(SIGALRM, SIG_DFL);
+        signal(SIGHUP, SIG_DFL);
+        signal(SIGTERM, SIG_DFL);
+        signal(SIGINT, SIG_DFL);
+        signal(SIGQUIT, SIG_DFL);
+        for (fd = getdtablesize(); fd >= 0; fd--)
+          close(fd);
+        /*
+         * Reap the intermediate process.  As we're not exiting but the
+         * intermediate is, we don't want it to become defunct.
+         */
+        waitpid(pid, &status, 0);
+        /* Tweak our process arguments.... */
+        SetTitle("session owner");
+#ifndef NOSUID
+        setuid(ID0realuid());
+#endif
+        /*
+         * Hang around for a HUP.  This should happen as soon as the
+         * ppp that we passed our ctty descriptor to closes it.
+         * NOTE: If this process dies, the passed descriptor becomes
+         *       invalid and will give a select() error by setting one
+         *       of the error fds, aborting the other ppp.  We don't
+         *       want that to happen !
+         */
+        pause();
+      }
+      _exit(0);
+      break;
+  }
+}
+
+unsigned
+bundle_HighestState(struct bundle *bundle)
+{
+  struct datalink *dl;
+  unsigned result = DATALINK_CLOSED;
+
+  for (dl = bundle->links; dl; dl = dl->next)
+    if (result < dl->state)
+      result = dl->state;
+
+  return result;
+}
+
+int
+bundle_Exception(struct bundle *bundle, int fd)
+{
+  struct datalink *dl;
+
+  for (dl = bundle->links; dl; dl = dl->next)
+    if (dl->physical->fd == fd) {
+      datalink_Down(dl, CLOSE_NORMAL);
+      return 1;
+    }
+
+  return 0;
+}
+
+void
+bundle_AdjustFilters(struct bundle *bundle, struct ncpaddr *local,
+                     struct ncpaddr *remote)
+{
+  filter_AdjustAddr(&bundle->filter.in, local, remote, NULL);
+  filter_AdjustAddr(&bundle->filter.out, local, remote, NULL);
+  filter_AdjustAddr(&bundle->filter.dial, local, remote, NULL);
+  filter_AdjustAddr(&bundle->filter.alive, local, remote, NULL);
+}
+
+void
+bundle_AdjustDNS(struct bundle *bundle)
+{
+  struct in_addr *dns = bundle->ncp.ipcp.ns.dns;
+
+  filter_AdjustAddr(&bundle->filter.in, NULL, NULL, dns);
+  filter_AdjustAddr(&bundle->filter.out, NULL, NULL, dns);
+  filter_AdjustAddr(&bundle->filter.dial, NULL, NULL, dns);
+  filter_AdjustAddr(&bundle->filter.alive, NULL, NULL, dns);
+}
+
+void
+bundle_CalculateBandwidth(struct bundle *bundle)
+{
+  struct datalink *dl;
+  int sp, overhead, maxoverhead;
+
+  bundle->bandwidth = 0;
+  bundle->iface->mtu = 0;
+  maxoverhead = 0;
+
+  for (dl = bundle->links; dl; dl = dl->next) {
+    overhead = ccp_MTUOverhead(&dl->physical->link.ccp);
+    if (maxoverhead < overhead)
+      maxoverhead = overhead;
+    if (dl->state == DATALINK_OPEN) {
+      if ((sp = dl->mp.bandwidth) == 0 &&
+          (sp = physical_GetSpeed(dl->physical)) == 0)
+        log_Printf(LogDEBUG, "%s: %s: Cannot determine bandwidth\n",
+                   dl->name, dl->physical->name.full);
+      else
+        bundle->bandwidth += sp;
+      if (!bundle->ncp.mp.active) {
+        bundle->iface->mtu = dl->physical->link.lcp.his_mru;
+        break;
+      }
+    }
+  }
+
+  if (bundle->bandwidth == 0)
+    bundle->bandwidth = 115200;		/* Shrug */
+
+  if (bundle->ncp.mp.active) {
+    bundle->iface->mtu = bundle->ncp.mp.peer_mrru;
+    overhead = ccp_MTUOverhead(&bundle->ncp.mp.link.ccp);
+    if (maxoverhead < overhead)
+      maxoverhead = overhead;
+  } else if (!bundle->iface->mtu)
+    bundle->iface->mtu = DEF_MRU;
+
+#ifndef NORADIUS
+  if (bundle->radius.valid && bundle->radius.mtu &&
+      bundle->radius.mtu < bundle->iface->mtu) {
+    log_Printf(LogLCP, "Reducing MTU to radius value %lu\n",
+               bundle->radius.mtu);
+    bundle->iface->mtu = bundle->radius.mtu;
+  }
+#endif
+
+  if (maxoverhead) {
+    log_Printf(LogLCP, "Reducing MTU from %lu to %lu (CCP requirement)\n",
+               bundle->iface->mtu, bundle->iface->mtu - maxoverhead);
+    bundle->iface->mtu -= maxoverhead;
+  }
+
+  tun_configure(bundle);
+
+  route_UpdateMTU(bundle);
+}
+
+void
+bundle_AutoAdjust(struct bundle *bundle, int percent, int what)
+{
+  struct datalink *dl, *choice, *otherlinkup;
+
+  choice = otherlinkup = NULL;
+  for (dl = bundle->links; dl; dl = dl->next)
+    if (dl->physical->type == PHYS_AUTO) {
+      if (dl->state == DATALINK_OPEN) {
+        if (what == AUTO_DOWN) {
+          if (choice)
+            otherlinkup = choice;
+          choice = dl;
+        }
+      } else if (dl->state == DATALINK_CLOSED) {
+        if (what == AUTO_UP) {
+          choice = dl;
+          break;
+        }
+      } else {
+        /* An auto link in an intermediate state - forget it for the moment */
+        choice = NULL;
+        break;
+      }
+    } else if (dl->state == DATALINK_OPEN && what == AUTO_DOWN)
+      otherlinkup = dl;
+
+  if (choice) {
+    if (what == AUTO_UP) {
+      log_Printf(LogPHASE, "%d%% saturation -> Opening link ``%s''\n",
+                 percent, choice->name);
+      datalink_Up(choice, 1, 1);
+      mp_CheckAutoloadTimer(&bundle->ncp.mp);
+    } else if (otherlinkup) {	/* Only bring the second-last link down */
+      log_Printf(LogPHASE, "%d%% saturation -> Closing link ``%s''\n",
+                 percent, choice->name);
+      datalink_Close(choice, CLOSE_STAYDOWN);
+      mp_CheckAutoloadTimer(&bundle->ncp.mp);
+    }
+  }
+}
+
+int
+bundle_WantAutoloadTimer(struct bundle *bundle)
+{
+  struct datalink *dl;
+  int autolink, opened;
+
+  if (bundle->phase == PHASE_NETWORK) {
+    for (autolink = opened = 0, dl = bundle->links; dl; dl = dl->next)
+      if (dl->physical->type == PHYS_AUTO) {
+        if (++autolink == 2 || (autolink == 1 && opened))
+          /* Two auto links or one auto and one open in NETWORK phase */
+          return 1;
+      } else if (dl->state == DATALINK_OPEN) {
+        opened++;
+        if (autolink)
+          /* One auto and one open link in NETWORK phase */
+          return 1;
+      }
+  }
+
+  return 0;
+}
+
+void
+bundle_ChangedPID(struct bundle *bundle)
+{
+#ifdef TUNSIFPID
+  ioctl(bundle->dev.fd, TUNSIFPID, 0);
+#endif
+}
+
+int
+bundle_Uptime(struct bundle *bundle)
+{
+  if (bundle->upat)
+    return time(NULL) - bundle->upat;
+
+  return 0;
+}
diff --git a/src/bundle.h b/src/bundle.h
new file mode 100644
index 0000000..67ad907
--- /dev/null
+++ b/src/bundle.h
@@ -0,0 +1,216 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/bundle.h,v 1.52.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define	PHASE_DEAD		0	/* Link is dead */
+#define	PHASE_ESTABLISH		1	/* Establishing link */
+#define	PHASE_AUTHENTICATE	2	/* Being authenticated */
+#define	PHASE_NETWORK		3	/* We're alive ! */
+#define	PHASE_TERMINATE		4	/* Terminating link */
+
+/* cfg.opt bit settings */
+#define OPT_FILTERDECAP		1
+#define OPT_FORCE_SCRIPTS	2 /* force chat scripts */
+#define OPT_IDCHECK		3
+#define OPT_IFACEALIAS		4
+#ifndef NOINET6
+#define OPT_IPCP		5
+#define OPT_IPV6CP		6
+#endif
+#define OPT_KEEPSESSION		7
+#define OPT_LOOPBACK		8
+#define OPT_NAS_IP_ADDRESS	9
+#define OPT_NAS_IDENTIFIER	10
+#define OPT_PASSWDAUTH		11
+#define OPT_PROXY		12
+#define OPT_PROXYALL		13
+#define OPT_SROUTES		14
+#define OPT_TCPMSSFIXUP		15
+#define OPT_THROUGHPUT		16
+#define OPT_UTMP		17
+#define OPT_MAX			17
+
+#define MAX_ENDDISC_CLASS 5
+
+#define Enabled(b, o)		((b)->cfg.optmask & (1ull << (o)))
+#define opt_enable(b, o)	((b)->cfg.optmask |= (1ull << (o)))
+#define opt_disable(b, o)	((b)->cfg.optmask &= ~(1ull << (o)))
+
+/* AutoAdjust() values */
+#define AUTO_UP		1
+#define AUTO_DOWN	2
+
+struct sockaddr_un;
+struct datalink;
+struct physical;
+struct link;
+struct server;
+struct prompt;
+struct iface;
+
+struct bundle {
+  struct fdescriptor desc;    /* really all our datalinks */
+  int unit;                   /* The device/interface unit number */
+
+  struct {
+    char Name[20];            /* The /dev/XXXX name */
+    int fd;                   /* The /dev/XXXX descriptor */
+    unsigned header : 1;      /* Family header sent & received ? */
+  } dev;
+
+  u_long bandwidth;           /* struct tuninfo speed */
+  struct iface *iface;        /* Interface information */
+
+  int routing_seq;            /* The current routing sequence number */
+  u_int phase;                /* Curent phase */
+
+  struct {
+    int all;                  /* Union of all physical::type's */
+    int open;                 /* Union of all open physical::type's */
+  } phys_type;
+
+  unsigned CleaningUp : 1;    /* Going to exit.... */
+  unsigned NatEnabled : 1;    /* Are we using libalias ? */
+
+  struct fsm_parent fsm;      /* Our callback functions */
+  struct datalink *links;     /* Our data links */
+
+  time_t upat;                /* When the link came up */
+
+  struct {
+    struct {
+      unsigned timeout;          /* NCP Idle timeout value */
+      unsigned min_timeout;      /* Don't idle out before this */
+    } idle;
+    struct {
+      char name[AUTHLEN];        /* PAP/CHAP system name */
+      char key[AUTHLEN];         /* PAP/CHAP key */
+    } auth;
+    unsigned long long optmask;  /* Uses OPT_ bits from above */
+    char label[50];              /* last thing `load'ed */
+    u_short ifqueue;             /* Interface queue size */
+
+    struct {
+      unsigned timeout;          /* How long to leave the output queue choked */
+    } choked;
+  } cfg;
+
+  struct ncp ncp;
+
+  struct {
+    struct filter in;         /* incoming packet filter */
+    struct filter out;        /* outgoing packet filter */
+    struct filter dial;       /* dial-out packet filter */
+    struct filter alive;      /* keep-alive packet filter */
+  } filter;
+
+  struct {
+    struct pppTimer timer;    /* timeout after cfg.idle_timeout */
+    time_t done;
+  } idle;
+
+#ifndef NORADIUS
+  struct {
+    struct pppTimer timer;
+    time_t done;
+  } session;
+#endif
+
+  struct {
+    int fd;                   /* write status here */
+  } notify;
+
+  struct {
+    struct pppTimer timer;    /* choked output queue timer */
+  } choked;
+
+#ifndef NORADIUS
+  struct radius radius;       /* Info retrieved from radius server */
+  struct radacct radacct;
+#ifndef NOINET6
+  struct radacct radacct6;
+#endif
+#endif
+};
+
+#define descriptor2bundle(d) \
+  ((d)->type == BUNDLE_DESCRIPTOR ? (struct bundle *)(d) : NULL)
+
+extern struct bundle *bundle_Create(const char *, int, int);
+extern void bundle_Destroy(struct bundle *);
+extern const char *bundle_PhaseName(struct bundle *);
+#define bundle_Phase(b) ((b)->phase)
+extern void bundle_NewPhase(struct bundle *, u_int);
+extern void bundle_LinksRemoved(struct bundle *);
+extern void bundle_Close(struct bundle *, const char *, int);
+extern void bundle_Down(struct bundle *, int);
+extern void bundle_Open(struct bundle *, const char *, int, int);
+extern void bundle_LinkClosed(struct bundle *, struct datalink *);
+
+extern int bundle_ShowLinks(struct cmdargs const *);
+extern int bundle_ShowStatus(struct cmdargs const *);
+extern void bundle_StartIdleTimer(struct bundle *, unsigned secs);
+extern void bundle_SetIdleTimer(struct bundle *, unsigned, unsigned);
+extern void bundle_StopIdleTimer(struct bundle *);
+extern int bundle_IsDead(struct bundle *);
+extern struct datalink *bundle2datalink(struct bundle *, const char *);
+
+#ifndef NORADIUS
+extern void bundle_StartSessionTimer(struct bundle *, unsigned secs);
+extern void bundle_StopSessionTimer(struct bundle *);
+#endif
+
+extern void bundle_RegisterDescriptor(struct bundle *, struct fdescriptor *);
+extern void bundle_UnRegisterDescriptor(struct bundle *, struct fdescriptor *);
+
+extern void bundle_SetTtyCommandMode(struct bundle *, struct datalink *);
+
+extern int bundle_DatalinkClone(struct bundle *, struct datalink *,
+                                const char *);
+extern void bundle_DatalinkRemove(struct bundle *, struct datalink *);
+extern void bundle_CleanDatalinks(struct bundle *);
+extern void bundle_SetLabel(struct bundle *, const char *);
+extern const char *bundle_GetLabel(struct bundle *);
+extern void bundle_SendDatalink(struct datalink *, int, struct sockaddr_un *);
+extern int bundle_LinkSize(void);
+extern void bundle_ReceiveDatalink(struct bundle *, int);
+extern int bundle_SetMode(struct bundle *, struct datalink *, int);
+extern int bundle_RenameDatalink(struct bundle *, struct datalink *,
+                                 const char *);
+extern void bundle_setsid(struct bundle *, int);
+extern void bundle_LockTun(struct bundle *);
+extern unsigned bundle_HighestState(struct bundle *);
+extern int bundle_Exception(struct bundle *, int);
+extern void bundle_AdjustFilters(struct bundle *, struct ncpaddr *,
+                                 struct ncpaddr *);
+extern void bundle_AdjustDNS(struct bundle *);
+extern void bundle_CalculateBandwidth(struct bundle *);
+extern void bundle_AutoAdjust(struct bundle *, int, int);
+extern int bundle_WantAutoloadTimer(struct bundle *);
+extern void bundle_ChangedPID(struct bundle *);
+extern void bundle_Notify(struct bundle *, char);
+extern int bundle_Uptime(struct bundle *);
diff --git a/src/cbcp.c b/src/cbcp.c
new file mode 100644
index 0000000..53cebc8
--- /dev/null
+++ b/src/cbcp.c
@@ -0,0 +1,763 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/cbcp.c,v 1.26.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+
+#ifdef __FreeBSD__
+#include <netinet/in.h>
+#endif
+#include <sys/un.h>
+
+#include <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "log.h"
+#include "timer.h"
+#include "descriptor.h"
+#include "lqr.h"
+#include "mbuf.h"
+#include "fsm.h"
+#include "throughput.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "physical.h"
+#include "proto.h"
+#include "cbcp.h"
+#include "mp.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "datalink.h"
+
+void
+cbcp_Init(struct cbcp *cbcp, struct physical *p)
+{
+  cbcp->required = 0;
+  cbcp->fsm.state = CBCP_CLOSED;
+  cbcp->fsm.id = 0;
+  cbcp->fsm.delay = 0;
+  *cbcp->fsm.phone = '\0';
+  memset(&cbcp->fsm.timer, '\0', sizeof cbcp->fsm.timer);
+  cbcp->p = p;
+}
+
+static void cbcp_SendReq(struct cbcp *);
+static void cbcp_SendResponse(struct cbcp *);
+static void cbcp_SendAck(struct cbcp *);
+
+static void
+cbcp_Timeout(void *v)
+{
+  struct cbcp *cbcp = (struct cbcp *)v;
+
+  timer_Stop(&cbcp->fsm.timer);
+  if (cbcp->fsm.restart) {
+    switch (cbcp->fsm.state) {
+      case CBCP_CLOSED:
+      case CBCP_STOPPED:
+        log_Printf(LogCBCP, "%s: Urk - unexpected CBCP timeout !\n",
+                   cbcp->p->dl->name);
+        break;
+
+      case CBCP_REQSENT:
+        cbcp_SendReq(cbcp);
+        break;
+      case CBCP_RESPSENT:
+        cbcp_SendResponse(cbcp);
+        break;
+      case CBCP_ACKSENT:
+        cbcp_SendAck(cbcp);
+        break;
+    }
+  } else {
+    const char *missed;
+
+    switch (cbcp->fsm.state) {
+      case CBCP_STOPPED:
+        missed = "REQ";
+        break;
+      case CBCP_REQSENT:
+        missed = "RESPONSE";
+        break;
+      case CBCP_RESPSENT:
+        missed = "ACK";
+        break;
+      case CBCP_ACKSENT:
+        missed = "Terminate REQ";
+        break;
+      default:
+        log_Printf(LogCBCP, "%s: Urk - unexpected CBCP timeout !\n",
+                   cbcp->p->dl->name);
+        missed = NULL;
+        break;
+    }
+    if (missed)
+      log_Printf(LogCBCP, "%s: Timeout waiting for peer %s\n",
+                 cbcp->p->dl->name, missed);
+    datalink_CBCPFailed(cbcp->p->dl);
+  }
+}
+
+static void
+cbcp_StartTimer(struct cbcp *cbcp, int timeout)
+{
+  timer_Stop(&cbcp->fsm.timer);
+  cbcp->fsm.timer.func = cbcp_Timeout;
+  cbcp->fsm.timer.name = "cbcp";
+  cbcp->fsm.timer.load = timeout * SECTICKS;
+  cbcp->fsm.timer.arg = cbcp;
+  timer_Start(&cbcp->fsm.timer);
+}
+
+#define CBCP_CLOSED	(0)	/* Not in use */
+#define CBCP_STOPPED	(1)	/* Waiting for a REQ */
+#define CBCP_REQSENT	(2)	/* Waiting for a RESP */
+#define CBCP_RESPSENT	(3)	/* Waiting for an ACK */
+#define CBCP_ACKSENT	(4)	/* Waiting for an LCP Term REQ */
+
+static const char * const cbcpname[] = {
+  "closed", "stopped", "req-sent", "resp-sent", "ack-sent"
+};
+
+static const char *
+cbcpstate(unsigned s)
+{
+  if (s < sizeof cbcpname / sizeof cbcpname[0])
+    return cbcpname[s];
+  return HexStr(s, NULL, 0);
+}
+
+static void
+cbcp_NewPhase(struct cbcp *cbcp, int new)
+{
+  if (cbcp->fsm.state != new) {
+    log_Printf(LogCBCP, "%s: State change %s --> %s\n", cbcp->p->dl->name,
+               cbcpstate(cbcp->fsm.state), cbcpstate(new));
+    cbcp->fsm.state = new;
+  }
+}
+
+struct cbcp_header {
+  u_char code;
+  u_char id;
+  u_int16_t length;	/* Network byte order */
+};
+
+
+/* cbcp_header::code values */
+#define CBCP_REQ	(1)
+#define CBCP_RESPONSE	(2)
+#define CBCP_ACK	(3)
+
+struct cbcp_data {
+  u_char type;
+  u_char length;
+  u_char delay;
+  char addr_start[253];	/* max cbcp_data length 255 + 1 for NULL */
+};
+
+/* cbcp_data::type values */
+#define CBCP_NONUM	(1)
+#define CBCP_CLIENTNUM	(2)
+#define CBCP_SERVERNUM	(3)
+#define CBCP_LISTNUM	(4)
+
+static void
+cbcp_Output(struct cbcp *cbcp, u_char code, struct cbcp_data *data)
+{
+  struct cbcp_header *head;
+  struct mbuf *bp;
+
+  bp = m_get(sizeof *head + data->length, MB_CBCPOUT);
+  head = (struct cbcp_header *)MBUF_CTOP(bp);
+  head->code = code;
+  head->id = cbcp->fsm.id;
+  head->length = htons(sizeof *head + data->length);
+  memcpy(MBUF_CTOP(bp) + sizeof *head, data, data->length);
+  log_DumpBp(LogDEBUG, "cbcp_Output", bp);
+  link_PushPacket(&cbcp->p->link, bp, cbcp->p->dl->bundle,
+                  LINK_QUEUES(&cbcp->p->link) - 1, PROTO_CBCP);
+}
+
+static const char *
+cbcp_data_Type(unsigned type)
+{
+  static const char * const types[] = {
+    "No callback", "User-spec", "Server-spec", "list"
+  };
+
+  if (type < 1 || type > sizeof types / sizeof types[0])
+    return HexStr(type, NULL, 0);
+  return types[type-1];
+}
+
+struct cbcp_addr {
+  u_char type;
+  char addr[sizeof ((struct cbcp_data *)0)->addr_start - 1];	/* ASCIIZ */
+};
+
+/* cbcp_data::type values */
+#define CBCP_ADDR_PSTN	(1)
+
+static void
+cbcp_data_Show(struct cbcp_data *data)
+{
+  struct cbcp_addr *addr;
+  char *end;
+
+  addr = (struct cbcp_addr *)data->addr_start;
+  end = (char *)data + data->length;
+  *end = '\0';
+
+  log_Printf(LogCBCP, " TYPE %s\n", cbcp_data_Type(data->type));
+  if ((char *)&data->delay < end) {
+    log_Printf(LogCBCP, " DELAY %d\n", data->delay);
+    while (addr->addr < end) {
+      if (addr->type == CBCP_ADDR_PSTN)
+        log_Printf(LogCBCP, " ADDR %s\n", addr->addr);
+      else
+        log_Printf(LogCBCP, " ADDR type %d ??\n", (int)addr->type);
+      addr = (struct cbcp_addr *)(addr->addr + strlen(addr->addr) + 1);
+    }
+  }
+}
+
+static void
+cbcp_SendReq(struct cbcp *cbcp)
+{
+  struct cbcp_data data;
+  struct cbcp_addr *addr;
+  char list[sizeof cbcp->fsm.phone], *next;
+  int len, max;
+
+  /* Only callees send REQs */
+
+  log_Printf(LogCBCP, "%s: SendReq(%d) state = %s\n", cbcp->p->dl->name,
+             cbcp->fsm.id, cbcpstate(cbcp->fsm.state));
+  data.type = cbcp->fsm.type;
+  data.delay = 0;
+  strncpy(list, cbcp->fsm.phone, sizeof list - 1);
+  list[sizeof list - 1] = '\0';
+
+  switch (data.type) {
+    case CBCP_CLIENTNUM:
+      addr = (struct cbcp_addr *)data.addr_start;
+      addr->type = CBCP_ADDR_PSTN;
+      *addr->addr = '\0';
+      data.length = addr->addr - (char *)&data;
+      break;
+
+    case CBCP_LISTNUM:
+      addr = (struct cbcp_addr *)data.addr_start;
+      for (next = strtok(list, ","); next; next = strtok(NULL, ",")) {
+        len = strlen(next);
+        max = data.addr_start + sizeof data.addr_start - addr->addr - 1;
+        if (len <= max) {
+          addr->type = CBCP_ADDR_PSTN;
+          strncpy(addr->addr, next, sizeof addr->addr - 1);
+          addr->addr[sizeof addr->addr - 1] = '\0';
+          addr = (struct cbcp_addr *)((char *)addr + len + 2);
+        } else
+          log_Printf(LogWARN, "CBCP ADDR \"%s\" skipped - packet too large\n",
+                     next);
+      }
+      data.length = (char *)addr - (char *)&data;
+      break;
+
+    case CBCP_SERVERNUM:
+      data.length = data.addr_start - (char *)&data;
+      break;
+
+    default:
+      data.length = (char *)&data.delay - (char *)&data;
+      break;
+  }
+
+  cbcp_data_Show(&data);
+  cbcp_Output(cbcp, CBCP_REQ, &data);
+  cbcp->fsm.restart--;
+  cbcp_StartTimer(cbcp, cbcp->fsm.delay);
+  cbcp_NewPhase(cbcp, CBCP_REQSENT);		/* Wait for a RESPONSE */
+}
+
+void
+cbcp_Up(struct cbcp *cbcp)
+{
+  struct lcp *lcp = &cbcp->p->link.lcp;
+
+  cbcp->fsm.delay = cbcp->p->dl->cfg.cbcp.delay;
+  if (*cbcp->p->dl->peer.authname == '\0' ||
+      !auth_SetPhoneList(cbcp->p->dl->peer.authname, cbcp->fsm.phone,
+                         sizeof cbcp->fsm.phone)) {
+    strncpy(cbcp->fsm.phone, cbcp->p->dl->cfg.cbcp.phone,
+            sizeof cbcp->fsm.phone - 1);
+    cbcp->fsm.phone[sizeof cbcp->fsm.phone - 1] = '\0';
+  }
+
+  if (lcp->want_callback.opmask) {
+    if (*cbcp->fsm.phone == '\0')
+      cbcp->fsm.type = CBCP_NONUM;
+    else if (!strcmp(cbcp->fsm.phone, "*")) {
+      cbcp->fsm.type = CBCP_SERVERNUM;
+      *cbcp->fsm.phone = '\0';
+    } else
+      cbcp->fsm.type = CBCP_CLIENTNUM;
+    cbcp_NewPhase(cbcp, CBCP_STOPPED);		/* Wait for a REQ */
+    cbcp_StartTimer(cbcp, cbcp->fsm.delay * DEF_FSMTRIES);
+  } else {
+    if (*cbcp->fsm.phone == '\0')
+      cbcp->fsm.type = CBCP_NONUM;
+    else if (!strcmp(cbcp->fsm.phone, "*")) {
+      cbcp->fsm.type = CBCP_CLIENTNUM;
+      *cbcp->fsm.phone = '\0';
+    } else if (strchr(cbcp->fsm.phone, ','))
+      cbcp->fsm.type = CBCP_LISTNUM;
+    else
+      cbcp->fsm.type = CBCP_SERVERNUM;
+    cbcp->fsm.restart = DEF_FSMTRIES;
+    cbcp_SendReq(cbcp);
+  }
+}
+
+static int
+cbcp_AdjustResponse(struct cbcp *cbcp, struct cbcp_data *data)
+{
+  /*
+   * We've received a REQ (data).  Adjust our reponse (cbcp->fsm.*)
+   * so that we (hopefully) agree with the peer
+   */
+  struct cbcp_addr *addr;
+
+  switch (data->type) {
+    case CBCP_NONUM:
+      if (cbcp->p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_NONE))
+        /*
+         * if ``none'' is a configured callback possibility
+         * (ie, ``set callback cbcp none''), go along with the callees
+         * request
+         */
+        cbcp->fsm.type = CBCP_NONUM;
+
+      /*
+       * Otherwise, we send our desired response anyway.  This seems to be
+       * what Win95 does - although I can't find this behaviour documented
+       * in the CBCP spec....
+       */
+
+      return 1;
+
+    case CBCP_CLIENTNUM:
+      if (cbcp->fsm.type == CBCP_CLIENTNUM) {
+        char *ptr;
+
+        if (data->length > data->addr_start - (char *)data) {
+          /*
+           * The peer has given us an address type spec - make sure we
+           * understand !
+           */
+          addr = (struct cbcp_addr *)data->addr_start;
+          if (addr->type != CBCP_ADDR_PSTN) {
+            log_Printf(LogPHASE, "CBCP: Unrecognised address type %d !\n",
+                       (int)addr->type);
+            return 0;
+          }
+        }
+        /* we accept the REQ even if the peer didn't specify an addr->type */
+        ptr = strchr(cbcp->fsm.phone, ',');
+        if (ptr)
+          *ptr = '\0';		/* Just use the first number in our list */
+        return 1;
+      }
+      log_Printf(LogPHASE, "CBCP: no number to pass to the peer !\n");
+      return 0;
+
+    case CBCP_SERVERNUM:
+      if (cbcp->fsm.type == CBCP_SERVERNUM) {
+        *cbcp->fsm.phone = '\0';
+        return 1;
+      }
+      if (data->length > data->addr_start - (char *)data) {
+        /*
+         * This violates the spec, but if the peer has told us the
+         * number it wants to call back, take advantage of this fact
+         * and allow things to proceed if we've specified the same
+         * number
+         */
+        addr = (struct cbcp_addr *)data->addr_start;
+        if (addr->type != CBCP_ADDR_PSTN) {
+          log_Printf(LogPHASE, "CBCP: Unrecognised address type %d !\n",
+                     (int)addr->type);
+          return 0;
+        } else if (cbcp->fsm.type == CBCP_CLIENTNUM) {
+          /*
+           * If the peer's insisting on deciding the number, make sure
+           * it's one of the ones in our list.  If it is, let the peer
+           * think it's in control :-)
+           */
+          char list[sizeof cbcp->fsm.phone], *next;
+
+          strncpy(list, cbcp->fsm.phone, sizeof list - 1);
+          list[sizeof list - 1] = '\0';
+          for (next = strtok(list, ","); next; next = strtok(NULL, ","))
+            if (!strcmp(next, addr->addr)) {
+              cbcp->fsm.type = CBCP_SERVERNUM;
+              strcpy(cbcp->fsm.phone, next);
+              return 1;
+            }
+        }
+      }
+      log_Printf(LogPHASE, "CBCP: Peer won't allow local decision !\n");
+      return 0;
+
+    case CBCP_LISTNUM:
+      if (cbcp->fsm.type == CBCP_CLIENTNUM || cbcp->fsm.type == CBCP_LISTNUM) {
+        /*
+         * Search through ``data''s addresses and see if cbcp->fsm.phone
+         * contains any of them
+         */
+        char list[sizeof cbcp->fsm.phone], *next, *end;
+
+        addr = (struct cbcp_addr *)data->addr_start;
+        end = (char *)data + data->length;
+
+        while (addr->addr < end) {
+          if (addr->type == CBCP_ADDR_PSTN) {
+            strncpy(list, cbcp->fsm.phone, sizeof list - 1);
+            list[sizeof list - 1] = '\0';
+            for (next = strtok(list, ","); next; next = strtok(NULL, ","))
+              if (!strcmp(next, addr->addr)) {
+                cbcp->fsm.type = CBCP_LISTNUM;
+                strcpy(cbcp->fsm.phone, next);
+                return 1;
+              }
+          } else
+            log_Printf(LogCBCP, "Warning: Unrecognised address type %d !\n",
+                       (int)addr->type);
+          addr = (struct cbcp_addr *)(addr->addr + strlen(addr->addr) + 1);
+        }
+      }
+      log_Printf(LogPHASE, "CBCP: no good number to pass to the peer !\n");
+      return 0;
+  }
+
+  log_Printf(LogCBCP, "Unrecognised REQ type %d !\n", (int)data->type);
+  return 0;
+}
+
+static void
+cbcp_SendResponse(struct cbcp *cbcp)
+{
+  struct cbcp_data data;
+  struct cbcp_addr *addr;
+
+  /* Only callers send RESPONSEs */
+
+  log_Printf(LogCBCP, "%s: SendResponse(%d) state = %s\n", cbcp->p->dl->name,
+             cbcp->fsm.id, cbcpstate(cbcp->fsm.state));
+
+  data.type = cbcp->fsm.type;
+  data.delay = cbcp->fsm.delay;
+  addr = (struct cbcp_addr *)data.addr_start;
+  if (data.type == CBCP_NONUM)
+    data.length = (char *)&data.delay - (char *)&data;
+  else if (*cbcp->fsm.phone) {
+    addr->type = CBCP_ADDR_PSTN;
+    strncpy(addr->addr, cbcp->fsm.phone, sizeof addr->addr - 1);
+    addr->addr[sizeof addr->addr - 1] = '\0';
+    data.length = (addr->addr + strlen(addr->addr) + 1) - (char *)&data;
+  } else
+    data.length = data.addr_start - (char *)&data;
+
+  cbcp_data_Show(&data);
+  cbcp_Output(cbcp, CBCP_RESPONSE, &data);
+  cbcp->fsm.restart--;
+  cbcp_StartTimer(cbcp, cbcp->fsm.delay);
+  cbcp_NewPhase(cbcp, CBCP_RESPSENT);	/* Wait for an ACK */
+}
+
+/* What to do after checking an incoming response */
+#define CBCP_ACTION_DOWN (0)
+#define CBCP_ACTION_REQ (1)
+#define CBCP_ACTION_ACK (2)
+
+static int
+cbcp_CheckResponse(struct cbcp *cbcp, struct cbcp_data *data)
+{
+  /*
+   * We've received a RESPONSE (data).  Check if it agrees with
+   * our REQ (cbcp->fsm)
+   */
+  struct cbcp_addr *addr;
+
+  addr = (struct cbcp_addr *)data->addr_start;
+
+  if (data->type == cbcp->fsm.type) {
+    switch (cbcp->fsm.type) {
+      case CBCP_NONUM:
+        return CBCP_ACTION_ACK;
+
+      case CBCP_CLIENTNUM:
+        if ((char *)data + data->length <= addr->addr)
+          log_Printf(LogPHASE, "CBCP: peer didn't respond with a number !\n");
+        else if (addr->type != CBCP_ADDR_PSTN)
+          log_Printf(LogPHASE, "CBCP: Unrecognised address type %d !\n",
+                     addr->type);
+        else {
+          strncpy(cbcp->fsm.phone, addr->addr, sizeof cbcp->fsm.phone - 1);
+          cbcp->fsm.phone[sizeof cbcp->fsm.phone - 1] = '\0';
+          cbcp->fsm.delay = data->delay;
+          return CBCP_ACTION_ACK;
+        }
+        return CBCP_ACTION_DOWN;
+
+      case CBCP_SERVERNUM:
+        cbcp->fsm.delay = data->delay;
+        return CBCP_ACTION_ACK;
+
+      case CBCP_LISTNUM:
+        if ((char *)data + data->length <= addr->addr)
+          log_Printf(LogPHASE, "CBCP: peer didn't respond with a number !\n");
+        else if (addr->type != CBCP_ADDR_PSTN)
+          log_Printf(LogPHASE, "CBCP: Unrecognised address type %d !\n",
+                     addr->type);
+        else {
+          char list[sizeof cbcp->fsm.phone], *next;
+
+          strncpy(list, cbcp->fsm.phone, sizeof list - 1);
+          list[sizeof list - 1] = '\0';
+          for (next = strtok(list, ","); next; next = strtok(NULL, ","))
+            if (!strcmp(addr->addr, next)) {
+              strcpy(cbcp->fsm.phone, next);
+              cbcp->fsm.delay = data->delay;
+              return CBCP_ACTION_ACK;
+            }
+          log_Printf(LogPHASE, "CBCP: peer didn't respond with a "
+                     "valid number !\n");
+        }
+        return CBCP_ACTION_DOWN;
+    }
+    log_Printf(LogPHASE, "Internal CBCP error - agreed on %d !\n",
+               (int)cbcp->fsm.type);
+    return CBCP_ACTION_DOWN;
+  } else if (data->type == CBCP_NONUM && cbcp->fsm.type == CBCP_CLIENTNUM) {
+    /*
+     * Client doesn't want CBCP after all....
+     * We only allow this when ``set cbcp *'' has been specified.
+     */
+    cbcp->fsm.type = CBCP_NONUM;
+    return CBCP_ACTION_ACK;
+  }
+  log_Printf(LogCBCP, "Invalid peer RESPONSE\n");
+  return CBCP_ACTION_REQ;
+}
+
+static void
+cbcp_SendAck(struct cbcp *cbcp)
+{
+  struct cbcp_data data;
+  struct cbcp_addr *addr;
+
+  /* Only callees send ACKs */
+
+  log_Printf(LogCBCP, "%s: SendAck(%d) state = %s\n", cbcp->p->dl->name,
+             cbcp->fsm.id, cbcpstate(cbcp->fsm.state));
+
+  data.type = cbcp->fsm.type;
+  switch (data.type) {
+    case CBCP_NONUM:
+      data.length = (char *)&data.delay - (char *)&data;
+      break;
+    case CBCP_CLIENTNUM:
+      addr = (struct cbcp_addr *)data.addr_start;
+      addr->type = CBCP_ADDR_PSTN;
+      strncpy(addr->addr, cbcp->fsm.phone, sizeof addr->addr - 1);
+      addr->addr[sizeof addr->addr - 1] = '\0';
+      data.delay = cbcp->fsm.delay;
+      data.length = addr->addr + strlen(addr->addr) + 1 - (char *)&data;
+      break;
+    default:
+      data.delay = cbcp->fsm.delay;
+      data.length = data.addr_start - (char *)&data;
+      break;
+  }
+
+  cbcp_data_Show(&data);
+  cbcp_Output(cbcp, CBCP_ACK, &data);
+  cbcp->fsm.restart--;
+  cbcp_StartTimer(cbcp, cbcp->fsm.delay);
+  cbcp_NewPhase(cbcp, CBCP_ACKSENT);	/* Wait for an ACK */
+}
+
+extern struct mbuf *
+cbcp_Input(struct bundle *bundle __unused, struct link *l, struct mbuf *bp)
+{
+  struct physical *p = link2physical(l);
+  struct cbcp_header *head;
+  struct cbcp_data *data;
+  struct cbcp *cbcp = &p->dl->cbcp;
+  size_t len;
+
+  if (p == NULL) {
+    log_Printf(LogERROR, "cbcp_Input: Not a physical link - dropped\n");
+    m_freem(bp);
+    return NULL;
+  }
+
+  bp = m_pullup(bp);
+  len = m_length(bp);
+  if (len < sizeof(struct cbcp_header)) {
+    m_freem(bp);
+    return NULL;
+  }
+  head = (struct cbcp_header *)MBUF_CTOP(bp);
+  if (ntohs(head->length) != len) {
+    log_Printf(LogWARN, "Corrupt CBCP packet (code %d, length %u not %zu)"
+               " - ignored\n", head->code, ntohs(head->length), len);
+    m_freem(bp);
+    return NULL;
+  }
+  m_settype(bp, MB_CBCPIN);
+
+  /* XXX check the id */
+
+  bp->m_offset += sizeof(struct cbcp_header);
+  bp->m_len -= sizeof(struct cbcp_header);
+  data = (struct cbcp_data *)MBUF_CTOP(bp);
+
+  switch (head->code) {
+    case CBCP_REQ:
+      log_Printf(LogCBCP, "%s: RecvReq(%d) state = %s\n",
+                 p->dl->name, head->id, cbcpstate(cbcp->fsm.state));
+      cbcp_data_Show(data);
+      if (cbcp->fsm.state == CBCP_STOPPED || cbcp->fsm.state == CBCP_RESPSENT) {
+        timer_Stop(&cbcp->fsm.timer);
+        if (cbcp_AdjustResponse(cbcp, data)) {
+          cbcp->fsm.restart = DEF_FSMTRIES;
+          cbcp->fsm.id = head->id;
+          cbcp_SendResponse(cbcp);
+        } else
+          datalink_CBCPFailed(cbcp->p->dl);
+      } else
+        log_Printf(LogCBCP, "%s: unexpected REQ dropped\n", p->dl->name);
+      break;
+
+    case CBCP_RESPONSE:
+      log_Printf(LogCBCP, "%s: RecvResponse(%d) state = %s\n",
+	         p->dl->name, head->id, cbcpstate(cbcp->fsm.state));
+      cbcp_data_Show(data);
+      if (cbcp->fsm.id != head->id) {
+        log_Printf(LogCBCP, "Warning: Expected id was %d, not %d\n",
+                   cbcp->fsm.id, head->id);
+        cbcp->fsm.id = head->id;
+      }
+      if (cbcp->fsm.state == CBCP_REQSENT || cbcp->fsm.state == CBCP_ACKSENT) {
+        timer_Stop(&cbcp->fsm.timer);
+        switch (cbcp_CheckResponse(cbcp, data)) {
+          case CBCP_ACTION_REQ:
+            cbcp_SendReq(cbcp);
+            break;
+
+          case CBCP_ACTION_ACK:
+            cbcp->fsm.restart = DEF_FSMTRIES;
+            cbcp_SendAck(cbcp);
+            if (cbcp->fsm.type == CBCP_NONUM) {
+              /*
+               * Don't change state in case the peer doesn't get our ACK,
+               * just bring the layer up.
+               */
+              timer_Stop(&cbcp->fsm.timer);
+              datalink_NCPUp(cbcp->p->dl);
+            }
+            break;
+
+          default:
+            datalink_CBCPFailed(cbcp->p->dl);
+            break;
+        }
+      } else
+        log_Printf(LogCBCP, "%s: unexpected RESPONSE dropped\n", p->dl->name);
+      break;
+
+    case CBCP_ACK:
+      log_Printf(LogCBCP, "%s: RecvAck(%d) state = %s\n",
+	         p->dl->name, head->id, cbcpstate(cbcp->fsm.state));
+      cbcp_data_Show(data);
+      if (cbcp->fsm.id != head->id) {
+        log_Printf(LogCBCP, "Warning: Expected id was %d, not %d\n",
+                   cbcp->fsm.id, head->id);
+        cbcp->fsm.id = head->id;
+      }
+      if (cbcp->fsm.type == CBCP_NONUM) {
+        /*
+         * Don't change state in case the peer doesn't get our ACK,
+         * just bring the layer up.
+         */
+        timer_Stop(&cbcp->fsm.timer);
+        datalink_NCPUp(cbcp->p->dl);
+      } else if (cbcp->fsm.state == CBCP_RESPSENT) {
+        timer_Stop(&cbcp->fsm.timer);
+        datalink_CBCPComplete(cbcp->p->dl);
+        log_Printf(LogPHASE, "%s: CBCP: Peer will dial back\n", p->dl->name);
+      } else
+        log_Printf(LogCBCP, "%s: unexpected ACK dropped\n", p->dl->name);
+      break;
+
+    default:
+      log_Printf(LogWARN, "Unrecognised CBCP packet (code %d, length %zd)\n",
+               head->code, len);
+      break;
+  }
+
+  m_freem(bp);
+  return NULL;
+}
+
+void
+cbcp_Down(struct cbcp *cbcp)
+{
+  timer_Stop(&cbcp->fsm.timer);
+  cbcp_NewPhase(cbcp, CBCP_CLOSED);
+  cbcp->required = 0;
+}
+
+void
+cbcp_ReceiveTerminateReq(struct physical *p)
+{
+  if (p->dl->cbcp.fsm.state == CBCP_ACKSENT) {
+    /* Don't change our state in case the peer doesn't get the ACK */
+    p->dl->cbcp.required = 1;
+    log_Printf(LogPHASE, "%s: CBCP: Will dial back on %s\n", p->dl->name,
+               p->dl->cbcp.fsm.phone);
+  } else
+    cbcp_NewPhase(&p->dl->cbcp, CBCP_CLOSED);
+}
diff --git a/src/cbcp.h b/src/cbcp.h
new file mode 100644
index 0000000..a18942a
--- /dev/null
+++ b/src/cbcp.h
@@ -0,0 +1,65 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/cbcp.h,v 1.3.62.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct mbuf;
+struct physical;
+struct datalink;
+
+/* fsm states */
+#define CBCP_CLOSED	(0)	/* Not in use */
+#define CBCP_STOPPED	(1)	/* Waiting for a REQ */
+#define CBCP_REQSENT	(2)	/* Waiting for a RESP */
+#define CBCP_RESPSENT	(3)	/* Waiting for an ACK */
+#define CBCP_ACKSENT	(4)	/* Waiting for an LCP Term REQ */
+
+struct cbcpcfg {
+  u_char delay;
+  char phone[SCRIPT_LEN];
+  long fsmretry;
+};
+
+struct cbcp {
+  unsigned required : 1;	/* Are we gonna call back ? */
+  struct physical *p;		/* On this physical link */
+  struct {
+    u_char type;		/* cbcp_data::type (none/me/him/list) */
+    u_char delay;		/* How long to delay */
+    char phone[SCRIPT_LEN];	/* What to dial */
+
+    int state;			/* Our FSM state */
+    u_char id;			/* Our FSM ID */
+    u_char restart;		/* FSM Send again ? */
+    struct pppTimer timer;	/* Resend last option */
+  } fsm;
+};
+
+extern void cbcp_Init(struct cbcp *, struct physical *);
+extern void cbcp_Up(struct cbcp *);
+extern struct mbuf *cbcp_Input(struct bundle *, struct link *, struct mbuf *);
+extern void cbcp_Down(struct cbcp *);
+extern void cbcp_ReceiveTerminateReq(struct physical *);
diff --git a/src/ccp.c b/src/ccp.c
new file mode 100644
index 0000000..c8c94e2
--- /dev/null
+++ b/src/ccp.c
@@ -0,0 +1,826 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ccp.c,v 1.78.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>	/* memcpy() on some archs */
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "proto.h"
+#include "pred.h"
+#include "deflate.h"
+#include "throughput.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "link.h"
+#include "mp.h"
+#include "async.h"
+#include "physical.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#ifndef NODES
+#include "mppe.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+static void CcpSendConfigReq(struct fsm *);
+static void CcpSentTerminateReq(struct fsm *);
+static void CcpSendTerminateAck(struct fsm *, u_char);
+static void CcpDecodeConfig(struct fsm *, u_char *, u_char *, int,
+                            struct fsm_decode *);
+static void CcpLayerStart(struct fsm *);
+static void CcpLayerFinish(struct fsm *);
+static int CcpLayerUp(struct fsm *);
+static void CcpLayerDown(struct fsm *);
+static void CcpInitRestartCounter(struct fsm *, int);
+static int CcpRecvResetReq(struct fsm *);
+static void CcpRecvResetAck(struct fsm *, u_char);
+
+static struct fsm_callbacks ccp_Callbacks = {
+  CcpLayerUp,
+  CcpLayerDown,
+  CcpLayerStart,
+  CcpLayerFinish,
+  CcpInitRestartCounter,
+  CcpSendConfigReq,
+  CcpSentTerminateReq,
+  CcpSendTerminateAck,
+  CcpDecodeConfig,
+  CcpRecvResetReq,
+  CcpRecvResetAck
+};
+
+static const char * const ccp_TimerNames[] =
+  {"CCP restart", "CCP openmode", "CCP stopped"};
+
+static const char *
+protoname(int proto)
+{
+  static char const * const cftypes[] = {
+    /* Check out the latest ``Compression Control Protocol'' rfc (1962) */
+    "OUI",		/* 0: OUI */
+    "PRED1",		/* 1: Predictor type 1 */
+    "PRED2",		/* 2: Predictor type 2 */
+    "PUDDLE",		/* 3: Puddle Jumber */
+    NULL, NULL, NULL, NULL, NULL, NULL,
+    NULL, NULL, NULL, NULL, NULL, NULL,
+    "HWPPC",		/* 16: Hewlett-Packard PPC */
+    "STAC",		/* 17: Stac Electronics LZS (rfc1974) */
+    "MPPE",		/* 18: Microsoft PPC (rfc2118) and */
+			/*     Microsoft PPE (draft-ietf-pppext-mppe) */
+    "GAND",		/* 19: Gandalf FZA (rfc1993) */
+    "V42BIS",		/* 20: ARG->DATA.42bis compression */
+    "BSD",		/* 21: BSD LZW Compress */
+    NULL,
+    "LZS-DCP",		/* 23: LZS-DCP Compression Protocol (rfc1967) */
+    "MAGNALINK/DEFLATE",/* 24: Magnalink Variable Resource (rfc1975) */
+			/* 24: Deflate (according to pppd-2.3.*) */
+    "DCE",		/* 25: Data Circuit-Terminating Equip (rfc1976) */
+    "DEFLATE",		/* 26: Deflate (rfc1979) */
+  };
+
+  if (proto < 0 || (unsigned)proto > sizeof cftypes / sizeof *cftypes ||
+      cftypes[proto] == NULL) {
+    if (proto == -1)
+      return "none";
+    return HexStr(proto, NULL, 0);
+  }
+
+  return cftypes[proto];
+}
+
+/* We support these algorithms, and Req them in the given order */
+static const struct ccp_algorithm * const algorithm[] = {
+  &DeflateAlgorithm,
+  &Pred1Algorithm,
+  &PppdDeflateAlgorithm
+#ifndef NODES
+  , &MPPEAlgorithm
+#endif
+};
+
+#define NALGORITHMS (sizeof algorithm/sizeof algorithm[0])
+
+int
+ccp_ReportStatus(struct cmdargs const *arg)
+{
+  struct ccp_opt **o;
+  struct link *l;
+  struct ccp *ccp;
+  int f;
+
+  l = command_ChooseLink(arg);
+  ccp = &l->ccp;
+
+  prompt_Printf(arg->prompt, "%s: %s [%s]\n", l->name, ccp->fsm.name,
+                State2Nam(ccp->fsm.state));
+  if (ccp->fsm.state == ST_OPENED) {
+    prompt_Printf(arg->prompt, " My protocol = %s, His protocol = %s\n",
+                  protoname(ccp->my_proto), protoname(ccp->his_proto));
+    prompt_Printf(arg->prompt, " Output: %ld --> %ld,  Input: %ld --> %ld\n",
+                  ccp->uncompout, ccp->compout,
+                  ccp->compin, ccp->uncompin);
+  }
+
+  if (ccp->in.algorithm != -1)
+    prompt_Printf(arg->prompt, "\n Input Options:  %s\n",
+                  (*algorithm[ccp->in.algorithm]->Disp)(&ccp->in.opt));
+
+  if (ccp->out.algorithm != -1) {
+    o = &ccp->out.opt;
+    for (f = 0; f < ccp->out.algorithm; f++)
+      if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]))
+        o = &(*o)->next;
+    prompt_Printf(arg->prompt, " Output Options: %s\n",
+                  (*algorithm[ccp->out.algorithm]->Disp)(&(*o)->val));
+  }
+
+  prompt_Printf(arg->prompt, "\n Defaults: ");
+  prompt_Printf(arg->prompt, "FSM retry = %us, max %u Config"
+                " REQ%s, %u Term REQ%s\n", ccp->cfg.fsm.timeout,
+                ccp->cfg.fsm.maxreq, ccp->cfg.fsm.maxreq == 1 ? "" : "s",
+                ccp->cfg.fsm.maxtrm, ccp->cfg.fsm.maxtrm == 1 ? "" : "s");
+  prompt_Printf(arg->prompt, "           deflate windows: ");
+  prompt_Printf(arg->prompt, "incoming = %d, ", ccp->cfg.deflate.in.winsize);
+  prompt_Printf(arg->prompt, "outgoing = %d\n", ccp->cfg.deflate.out.winsize);
+#ifndef NODES
+  prompt_Printf(arg->prompt, "           MPPE: ");
+  if (ccp->cfg.mppe.keybits)
+    prompt_Printf(arg->prompt, "%d bits, ", ccp->cfg.mppe.keybits);
+  else
+    prompt_Printf(arg->prompt, "any bits, ");
+  switch (ccp->cfg.mppe.state) {
+  case MPPE_STATEFUL:
+    prompt_Printf(arg->prompt, "stateful");
+    break;
+  case MPPE_STATELESS:
+    prompt_Printf(arg->prompt, "stateless");
+    break;
+  case MPPE_ANYSTATE:
+    prompt_Printf(arg->prompt, "any state");
+    break;
+  }
+  prompt_Printf(arg->prompt, "%s\n",
+                ccp->cfg.mppe.required ? ", required" : "");
+#endif
+
+  prompt_Printf(arg->prompt, "\n           DEFLATE:    %s\n",
+                command_ShowNegval(ccp->cfg.neg[CCP_NEG_DEFLATE]));
+  prompt_Printf(arg->prompt, "           PREDICTOR1: %s\n",
+                command_ShowNegval(ccp->cfg.neg[CCP_NEG_PRED1]));
+  prompt_Printf(arg->prompt, "           DEFLATE24:  %s\n",
+                command_ShowNegval(ccp->cfg.neg[CCP_NEG_DEFLATE24]));
+#ifndef NODES
+  prompt_Printf(arg->prompt, "           MPPE:       %s\n",
+                command_ShowNegval(ccp->cfg.neg[CCP_NEG_MPPE]));
+#endif
+  return 0;
+}
+
+void
+ccp_SetupCallbacks(struct ccp *ccp)
+{
+  ccp->fsm.fn = &ccp_Callbacks;
+  ccp->fsm.FsmTimer.name = ccp_TimerNames[0];
+  ccp->fsm.OpenTimer.name = ccp_TimerNames[1];
+  ccp->fsm.StoppedTimer.name = ccp_TimerNames[2];
+}
+
+void
+ccp_Init(struct ccp *ccp, struct bundle *bundle, struct link *l,
+         const struct fsm_parent *parent)
+{
+  /* Initialise ourselves */
+
+  fsm_Init(&ccp->fsm, "CCP", PROTO_CCP, 1, CCP_MAXCODE, LogCCP,
+           bundle, l, parent, &ccp_Callbacks, ccp_TimerNames);
+
+  ccp->cfg.deflate.in.winsize = 0;
+  ccp->cfg.deflate.out.winsize = 15;
+  ccp->cfg.fsm.timeout = DEF_FSMRETRY;
+  ccp->cfg.fsm.maxreq = DEF_FSMTRIES;
+  ccp->cfg.fsm.maxtrm = DEF_FSMTRIES;
+  ccp->cfg.neg[CCP_NEG_DEFLATE] = NEG_ENABLED|NEG_ACCEPTED;
+  ccp->cfg.neg[CCP_NEG_PRED1] = NEG_ENABLED|NEG_ACCEPTED;
+  ccp->cfg.neg[CCP_NEG_DEFLATE24] = 0;
+#ifndef NODES
+  ccp->cfg.mppe.keybits = 0;
+  ccp->cfg.mppe.state = MPPE_ANYSTATE;
+  ccp->cfg.mppe.required = 0;
+  ccp->cfg.neg[CCP_NEG_MPPE] = NEG_ENABLED|NEG_ACCEPTED;
+#endif
+
+  ccp_Setup(ccp);
+}
+
+void
+ccp_Setup(struct ccp *ccp)
+{
+  /* Set ourselves up for a startup */
+  ccp->fsm.open_mode = 0;
+  ccp->his_proto = ccp->my_proto = -1;
+  ccp->reset_sent = ccp->last_reset = -1;
+  ccp->in.algorithm = ccp->out.algorithm = -1;
+  ccp->in.state = ccp->out.state = NULL;
+  ccp->in.opt.hdr.id = -1;
+  ccp->out.opt = NULL;
+  ccp->his_reject = ccp->my_reject = 0;
+  ccp->uncompout = ccp->compout = 0;
+  ccp->uncompin = ccp->compin = 0;
+}
+
+/*
+ * Is ccp *REQUIRED* ?
+ * We ask each of the configured ccp protocols if they're required and
+ * return TRUE if they are.
+ *
+ * It's not possible for the peer to reject a required ccp protocol
+ * without our state machine bringing the supporting lcp layer down.
+ *
+ * If ccp is required but not open, the NCP layer should not push
+ * any data into the link.
+ */
+int
+ccp_Required(struct ccp *ccp)
+{
+  unsigned f;
+
+  for (f = 0; f < NALGORITHMS; f++)
+    if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]) &&
+        (*algorithm[f]->Required)(&ccp->fsm))
+      return 1;
+
+  return 0;
+}
+
+/*
+ * Report whether it's possible to increase a packet's size after
+ * compression (and by how much).
+ */
+int
+ccp_MTUOverhead(struct ccp *ccp)
+{
+  if (ccp->fsm.state == ST_OPENED && ccp->out.algorithm >= 0)
+    return algorithm[ccp->out.algorithm]->o.MTUOverhead;
+
+  return 0;
+}
+
+static void
+CcpInitRestartCounter(struct fsm *fp, int what)
+{
+  /* Set fsm timer load */
+  struct ccp *ccp = fsm2ccp(fp);
+
+  fp->FsmTimer.load = ccp->cfg.fsm.timeout * SECTICKS;
+  switch (what) {
+    case FSM_REQ_TIMER:
+      fp->restart = ccp->cfg.fsm.maxreq;
+      break;
+    case FSM_TRM_TIMER:
+      fp->restart = ccp->cfg.fsm.maxtrm;
+      break;
+    default:
+      fp->restart = 1;
+      break;
+  }
+}
+
+static void
+CcpSendConfigReq(struct fsm *fp)
+{
+  /* Send config REQ please */
+  struct ccp *ccp = fsm2ccp(fp);
+  struct ccp_opt **o;
+  u_char *cp, buff[100];
+  unsigned f;
+  int alloc;
+
+  cp = buff;
+  o = &ccp->out.opt;
+  alloc = ccp->his_reject == 0 && ccp->out.opt == NULL;
+  ccp->my_proto = -1;
+  ccp->out.algorithm = -1;
+  for (f = 0; f < NALGORITHMS; f++)
+    if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]) &&
+        !REJECTED(ccp, algorithm[f]->id) &&
+        (*algorithm[f]->Usable)(fp)) {
+
+      if (!alloc)
+        for (o = &ccp->out.opt; *o != NULL; o = &(*o)->next)
+          if ((*o)->val.hdr.id == algorithm[f]->id && (*o)->algorithm == (int)f)
+            break;
+
+      if (alloc || *o == NULL) {
+        if ((*o = (struct ccp_opt *)malloc(sizeof(struct ccp_opt))) == NULL) {
+	  log_Printf(LogERROR, "%s: Not enough memory for CCP REQ !\n",
+		     fp->link->name);
+	  break;
+	}
+        (*o)->val.hdr.id = algorithm[f]->id;
+        (*o)->val.hdr.len = 2;
+        (*o)->next = NULL;
+        (*o)->algorithm = f;
+        (*algorithm[f]->o.OptInit)(fp->bundle, &(*o)->val, &ccp->cfg);
+      }
+
+      if (cp + (*o)->val.hdr.len > buff + sizeof buff) {
+        log_Printf(LogERROR, "%s: CCP REQ buffer overrun !\n", fp->link->name);
+        break;
+      }
+      memcpy(cp, &(*o)->val, (*o)->val.hdr.len);
+      cp += (*o)->val.hdr.len;
+
+      ccp->my_proto = (*o)->val.hdr.id;
+      ccp->out.algorithm = f;
+
+      if (alloc)
+        o = &(*o)->next;
+    }
+
+  fsm_Output(fp, CODE_CONFIGREQ, fp->reqid, buff, cp - buff, MB_CCPOUT);
+}
+
+void
+ccp_SendResetReq(struct fsm *fp)
+{
+  /* We can't read our input - ask peer to reset */
+  struct ccp *ccp = fsm2ccp(fp);
+
+  ccp->reset_sent = fp->reqid;
+  ccp->last_reset = -1;
+  fsm_Output(fp, CODE_RESETREQ, fp->reqid, NULL, 0, MB_CCPOUT);
+}
+
+static void
+CcpSentTerminateReq(struct fsm *fp __unused)
+{
+  /* Term REQ just sent by FSM */
+}
+
+static void
+CcpSendTerminateAck(struct fsm *fp, u_char id)
+{
+  /* Send Term ACK please */
+  fsm_Output(fp, CODE_TERMACK, id, NULL, 0, MB_CCPOUT);
+}
+
+static int
+CcpRecvResetReq(struct fsm *fp)
+{
+  /* Got a reset REQ, reset outgoing dictionary */
+  struct ccp *ccp = fsm2ccp(fp);
+  if (ccp->out.state == NULL)
+    return 1;
+  return (*algorithm[ccp->out.algorithm]->o.Reset)(ccp->out.state);
+}
+
+static void
+CcpLayerStart(struct fsm *fp)
+{
+  /* We're about to start up ! */
+  struct ccp *ccp = fsm2ccp(fp);
+
+  log_Printf(LogCCP, "%s: LayerStart.\n", fp->link->name);
+  fp->more.reqs = fp->more.naks = fp->more.rejs = ccp->cfg.fsm.maxreq * 3;
+}
+
+static void
+CcpLayerDown(struct fsm *fp)
+{
+  /* About to come down */
+  struct ccp *ccp = fsm2ccp(fp);
+  struct ccp_opt *next;
+
+  log_Printf(LogCCP, "%s: LayerDown.\n", fp->link->name);
+  if (ccp->in.state != NULL) {
+    (*algorithm[ccp->in.algorithm]->i.Term)(ccp->in.state);
+    ccp->in.state = NULL;
+    ccp->in.algorithm = -1;
+  }
+  if (ccp->out.state != NULL) {
+    (*algorithm[ccp->out.algorithm]->o.Term)(ccp->out.state);
+    ccp->out.state = NULL;
+    ccp->out.algorithm = -1;
+  }
+  ccp->his_reject = ccp->my_reject = 0;
+
+  while (ccp->out.opt) {
+    next = ccp->out.opt->next;
+    free(ccp->out.opt);
+    ccp->out.opt = next;
+  }
+  ccp_Setup(ccp);
+}
+
+static void
+CcpLayerFinish(struct fsm *fp)
+{
+  /* We're now down */
+  struct ccp *ccp = fsm2ccp(fp);
+  struct ccp_opt *next;
+
+  log_Printf(LogCCP, "%s: LayerFinish.\n", fp->link->name);
+
+  /*
+   * Nuke options that may be left over from sending a REQ but never
+   * coming up.
+   */
+  while (ccp->out.opt) {
+    next = ccp->out.opt->next;
+    free(ccp->out.opt);
+    ccp->out.opt = next;
+  }
+
+  if (ccp_Required(ccp)) {
+    if (fp->link->lcp.fsm.state == ST_OPENED)
+      log_Printf(LogLCP, "%s: Closing due to CCP completion\n", fp->link->name);
+    fsm_Close(&fp->link->lcp.fsm);
+  }
+}
+
+/*  Called when CCP has reached the OPEN state */
+static int
+CcpLayerUp(struct fsm *fp)
+{
+  /* We're now up */
+  struct ccp *ccp = fsm2ccp(fp);
+  struct ccp_opt **o;
+  unsigned f, fail;
+
+  for (f = fail = 0; f < NALGORITHMS; f++)
+    if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]) &&
+        (*algorithm[f]->Required)(&ccp->fsm) &&
+        (ccp->in.algorithm != (int)f || ccp->out.algorithm != (int)f)) {
+      /* Blow it all away - we haven't negotiated a required algorithm */
+      log_Printf(LogWARN, "%s: Failed to negotiate (required) %s\n",
+                 fp->link->name, protoname(algorithm[f]->id));
+      fail = 1;
+    }
+
+  if (fail) {
+    ccp->his_proto = ccp->my_proto = -1;
+    fsm_Close(fp);
+    fsm_Close(&fp->link->lcp.fsm);
+    return 0;
+  }
+
+  log_Printf(LogCCP, "%s: LayerUp.\n", fp->link->name);
+
+  if (ccp->in.state == NULL && ccp->in.algorithm >= 0 &&
+      ccp->in.algorithm < (int)NALGORITHMS) {
+    ccp->in.state = (*algorithm[ccp->in.algorithm]->i.Init)
+      (fp->bundle, &ccp->in.opt);
+    if (ccp->in.state == NULL) {
+      log_Printf(LogERROR, "%s: %s (in) initialisation failure\n",
+                fp->link->name, protoname(ccp->his_proto));
+      ccp->his_proto = ccp->my_proto = -1;
+      fsm_Close(fp);
+      return 0;
+    }
+  }
+
+  o = &ccp->out.opt;
+  if (ccp->out.algorithm > 0)
+    for (f = 0; f < (unsigned)ccp->out.algorithm; f++)
+      if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]))
+	o = &(*o)->next;
+
+  if (ccp->out.state == NULL && ccp->out.algorithm >= 0 &&
+      ccp->out.algorithm < (int)NALGORITHMS) {
+    ccp->out.state = (*algorithm[ccp->out.algorithm]->o.Init)
+      (fp->bundle, &(*o)->val);
+    if (ccp->out.state == NULL) {
+      log_Printf(LogERROR, "%s: %s (out) initialisation failure\n",
+                fp->link->name, protoname(ccp->my_proto));
+      ccp->his_proto = ccp->my_proto = -1;
+      fsm_Close(fp);
+      return 0;
+    }
+  }
+
+  fp->more.reqs = fp->more.naks = fp->more.rejs = ccp->cfg.fsm.maxreq * 3;
+
+  log_Printf(LogCCP, "%s: Out = %s[%d], In = %s[%d]\n",
+            fp->link->name, protoname(ccp->my_proto), ccp->my_proto,
+            protoname(ccp->his_proto), ccp->his_proto);
+
+  return 1;
+}
+
+static void
+CcpDecodeConfig(struct fsm *fp, u_char *cp, u_char *end, int mode_type,
+                struct fsm_decode *dec)
+{
+  /* Deal with incoming data */
+  struct ccp *ccp = fsm2ccp(fp);
+  int f;
+  const char *disp;
+  struct fsm_opt *opt;
+
+  if (mode_type == MODE_REQ)
+    ccp->in.algorithm = -1;	/* In case we've received two REQs in a row */
+
+  while (end >= cp + sizeof(opt->hdr)) {
+    if ((opt = fsm_readopt(&cp)) == NULL)
+      break;
+
+    for (f = NALGORITHMS-1; f > -1; f--)
+      if (algorithm[f]->id == opt->hdr.id)
+        break;
+
+    disp = f == -1 ? "" : (*algorithm[f]->Disp)(opt);
+    if (disp == NULL)
+      disp = "";
+
+    log_Printf(LogCCP, " %s[%d] %s\n", protoname(opt->hdr.id),
+               opt->hdr.len, disp);
+
+    if (f == -1) {
+      /* Don't understand that :-( */
+      if (mode_type == MODE_REQ) {
+        ccp->my_reject |= (1 << opt->hdr.id);
+        fsm_rej(dec, opt);
+      }
+    } else {
+      struct ccp_opt *o;
+
+      switch (mode_type) {
+      case MODE_REQ:
+        if (IsAccepted(ccp->cfg.neg[algorithm[f]->Neg]) &&
+            (*algorithm[f]->Usable)(fp) &&
+            ccp->in.algorithm == -1) {
+          memcpy(&ccp->in.opt, opt, opt->hdr.len);
+          switch ((*algorithm[f]->i.Set)(fp->bundle, &ccp->in.opt, &ccp->cfg)) {
+          case MODE_REJ:
+            fsm_rej(dec, &ccp->in.opt);
+            break;
+          case MODE_NAK:
+            fsm_nak(dec, &ccp->in.opt);
+            break;
+          case MODE_ACK:
+            fsm_ack(dec, &ccp->in.opt);
+            ccp->his_proto = opt->hdr.id;
+            ccp->in.algorithm = (int)f;		/* This one'll do :-) */
+            break;
+          }
+        } else {
+          fsm_rej(dec, opt);
+        }
+        break;
+      case MODE_NAK:
+        for (o = ccp->out.opt; o != NULL; o = o->next)
+          if (o->val.hdr.id == opt->hdr.id)
+            break;
+        if (o == NULL)
+          log_Printf(LogCCP, "%s: Warning: Ignoring peer NAK of unsent"
+                     " option\n", fp->link->name);
+        else {
+          memcpy(&o->val, opt, opt->hdr.len);
+          if ((*algorithm[f]->o.Set)(fp->bundle, &o->val, &ccp->cfg) ==
+              MODE_ACK)
+            ccp->my_proto = algorithm[f]->id;
+          else {
+            ccp->his_reject |= (1 << opt->hdr.id);
+            ccp->my_proto = -1;
+            if (algorithm[f]->Required(fp)) {
+              log_Printf(LogWARN, "%s: Cannot understand peers (required)"
+                         " %s negotiation\n", fp->link->name,
+                         protoname(algorithm[f]->id));
+              fsm_Close(&fp->link->lcp.fsm);
+            }
+          }
+        }
+        break;
+      case MODE_REJ:
+        ccp->his_reject |= (1 << opt->hdr.id);
+        ccp->my_proto = -1;
+        if (algorithm[f]->Required(fp)) {
+          log_Printf(LogWARN, "%s: Peer rejected (required) %s negotiation\n",
+                     fp->link->name, protoname(algorithm[f]->id));
+          fsm_Close(&fp->link->lcp.fsm);
+        }
+        break;
+      }
+    }
+  }
+
+  if (mode_type != MODE_NOP) {
+    fsm_opt_normalise(dec);
+    if (dec->rejend != dec->rej || dec->nakend != dec->nak) {
+      if (ccp->in.state == NULL) {
+        ccp->his_proto = -1;
+        ccp->in.algorithm = -1;
+      }
+    }
+  }
+}
+
+extern struct mbuf *
+ccp_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+  /* Got PROTO_CCP from link */
+  m_settype(bp, MB_CCPIN);
+  if (bundle_Phase(bundle) == PHASE_NETWORK)
+    fsm_Input(&l->ccp.fsm, bp);
+  else {
+    if (bundle_Phase(bundle) < PHASE_NETWORK)
+      log_Printf(LogCCP, "%s: Error: Unexpected CCP in phase %s (ignored)\n",
+                 l->ccp.fsm.link->name, bundle_PhaseName(bundle));
+    m_freem(bp);
+  }
+  return NULL;
+}
+
+static void
+CcpRecvResetAck(struct fsm *fp, u_char id)
+{
+  /* Got a reset ACK, reset incoming dictionary */
+  struct ccp *ccp = fsm2ccp(fp);
+
+  if (ccp->reset_sent != -1) {
+    if (id != ccp->reset_sent) {
+      log_Printf(LogCCP, "%s: Incorrect ResetAck (id %d, not %d)"
+                " ignored\n", fp->link->name, id, ccp->reset_sent);
+      return;
+    }
+    /* Whaddaya know - a correct reset ack */
+  } else if (id == ccp->last_reset)
+    log_Printf(LogCCP, "%s: Duplicate ResetAck (resetting again)\n",
+               fp->link->name);
+  else {
+    log_Printf(LogCCP, "%s: Unexpected ResetAck (id %d) ignored\n",
+               fp->link->name, id);
+    return;
+  }
+
+  ccp->last_reset = ccp->reset_sent;
+  ccp->reset_sent = -1;
+  if (ccp->in.state != NULL)
+    (*algorithm[ccp->in.algorithm]->i.Reset)(ccp->in.state);
+}
+
+static struct mbuf *
+ccp_LayerPush(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+              int pri, u_short *proto)
+{
+  if (PROTO_COMPRESSIBLE(*proto)) {
+    if (l->ccp.fsm.state != ST_OPENED) {
+      if (ccp_Required(&l->ccp)) {
+        /* The NCP layer shouldn't have let this happen ! */
+        log_Printf(LogERROR, "%s: Unexpected attempt to use an unopened and"
+                   " required CCP layer\n", l->name);
+        m_freem(bp);
+        bp = NULL;
+      }
+    } else if (l->ccp.out.state != NULL) {
+      bp = (*algorithm[l->ccp.out.algorithm]->o.Write)
+             (l->ccp.out.state, &l->ccp, l, pri, proto, bp);
+      switch (*proto) {
+        case PROTO_ICOMPD:
+          m_settype(bp, MB_ICOMPDOUT);
+          break;
+        case PROTO_COMPD:
+          m_settype(bp, MB_COMPDOUT);
+          break;
+      }
+    }
+  }
+
+  return bp;
+}
+
+static struct mbuf *
+ccp_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+	      u_short *proto)
+{
+  /*
+   * If proto isn't PROTO_[I]COMPD, we still want to pass it to the
+   * decompression routines so that the dictionary's updated
+   */
+  if (l->ccp.fsm.state == ST_OPENED) {
+    if (*proto == PROTO_COMPD || *proto == PROTO_ICOMPD) {
+      /* Decompress incoming data */
+      if (l->ccp.reset_sent != -1)
+        /* Send another REQ and put the packet in the bit bucket */
+        fsm_Output(&l->ccp.fsm, CODE_RESETREQ, l->ccp.reset_sent, NULL, 0,
+                   MB_CCPOUT);
+      else if (l->ccp.in.state != NULL) {
+        bp = (*algorithm[l->ccp.in.algorithm]->i.Read)
+               (l->ccp.in.state, &l->ccp, proto, bp);
+        switch (*proto) {
+          case PROTO_ICOMPD:
+            m_settype(bp, MB_ICOMPDIN);
+            break;
+          case PROTO_COMPD:
+            m_settype(bp, MB_COMPDIN);
+            break;
+        }
+        return bp;
+      }
+      m_freem(bp);
+      bp = NULL;
+    } else if (PROTO_COMPRESSIBLE(*proto) && l->ccp.in.state != NULL) {
+      /* Add incoming Network Layer traffic to our dictionary */
+      (*algorithm[l->ccp.in.algorithm]->i.DictSetup)
+        (l->ccp.in.state, &l->ccp, *proto, bp);
+    }
+  }
+
+  return bp;
+}
+
+u_short
+ccp_Proto(struct ccp *ccp)
+{
+  return !link2physical(ccp->fsm.link) || !ccp->fsm.bundle->ncp.mp.active ?
+         PROTO_COMPD : PROTO_ICOMPD;
+}
+
+int
+ccp_SetOpenMode(struct ccp *ccp)
+{
+  int f;
+
+  for (f = 0; f < CCP_NEG_TOTAL; f++)
+    if (IsEnabled(ccp->cfg.neg[f])) {
+      ccp->fsm.open_mode = 0;
+      return 1;
+    }
+
+  ccp->fsm.open_mode = OPEN_PASSIVE;	/* Go straight to ST_STOPPED ? */
+
+  for (f = 0; f < CCP_NEG_TOTAL; f++)
+    if (IsAccepted(ccp->cfg.neg[f]))
+      return 1;
+
+  return 0;				/* No CCP at all */
+}
+
+int
+ccp_DefaultUsable(struct fsm *fp __unused)
+{
+  return 1;
+}
+
+int
+ccp_DefaultRequired(struct fsm *fp __unused)
+{
+  return 0;
+}
+
+struct layer ccplayer = { LAYER_CCP, "ccp", ccp_LayerPush, ccp_LayerPull };
diff --git a/src/ccp.h b/src/ccp.h
new file mode 100644
index 0000000..442936e
--- /dev/null
+++ b/src/ccp.h
@@ -0,0 +1,165 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ccp.h,v 1.31.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define	CCP_MAXCODE	CODE_RESETACK
+
+#define	TY_OUI		0	/* OUI */
+#define	TY_PRED1	1	/* Predictor type 1 */
+#define	TY_PRED2	2	/* Predictor type 2 */
+#define	TY_PUDDLE	3	/* Puddle Jumper */
+#define	TY_HWPPC	16	/* Hewlett-Packard PPC */
+#define	TY_STAC		17	/* Stac Electronics LZS */
+#define	TY_MSPPC	18	/* Microsoft PPC */
+#define	TY_MPPE		18	/* Microsoft PPE */
+#define	TY_GAND		19	/* Gandalf FZA */
+#define	TY_V42BIS	20	/* V.42bis compression */
+#define	TY_BSD		21	/* BSD LZW Compress */
+#define	TY_PPPD_DEFLATE	24	/* Deflate (gzip) - (mis) numbered by pppd */
+#define	TY_DEFLATE	26	/* Deflate (gzip) - rfc 1979 */
+
+#define CCP_NEG_DEFLATE		0
+#define CCP_NEG_PRED1		1
+#define CCP_NEG_DEFLATE24	2
+#ifndef NODES
+#define CCP_NEG_MPPE		3
+#define CCP_NEG_TOTAL		4
+#else
+#define CCP_NEG_TOTAL		3
+#endif
+
+#ifndef NODES
+enum mppe_negstate {
+  MPPE_ANYSTATE,
+  MPPE_STATELESS,
+  MPPE_STATEFUL
+};
+#endif
+
+struct mbuf;
+struct link;
+
+struct ccp_config {
+  struct {
+    struct {
+      int winsize;
+    } in, out;
+  } deflate;
+#ifndef NODES
+  struct {
+    int keybits;
+    enum mppe_negstate state;
+    unsigned required : 1;
+  } mppe;
+#endif
+  struct fsm_retry fsm;	/* How often/frequently to resend requests */
+  unsigned neg[CCP_NEG_TOTAL];
+};
+
+struct ccp_opt {
+  struct ccp_opt *next;
+  int algorithm;
+  struct fsm_opt val;
+};
+
+struct ccp {
+  struct fsm fsm;		/* The finite state machine */
+
+  int his_proto;		/* peer's compression protocol */
+  int my_proto;			/* our compression protocol */
+
+  int reset_sent;		/* If != -1, ignore compressed 'till ack */
+  int last_reset;		/* We can receive more (dups) w/ this id */
+
+  struct {
+    int algorithm;		/* Algorithm in use */
+    void *state;		/* Returned by implementations Init() */
+    struct fsm_opt opt;		/* Set by implementation's OptInit() */
+  } in;
+
+  struct {
+    int algorithm;		/* Algorithm in use */
+    void *state;		/* Returned by implementations Init() */
+    struct ccp_opt *opt;	/* Set by implementation's OptInit() */
+  } out;
+
+  u_int32_t his_reject;		/* Request codes rejected by peer */
+  u_int32_t my_reject;		/* Request codes I have rejected */
+
+  u_long uncompout, compout;	/* Outgoing bytes before/after compression */
+  u_long uncompin, compin;	/* Incoming bytes after/before decompression */
+
+  struct ccp_config cfg;
+};
+
+#define fsm2ccp(fp) (fp->proto == PROTO_CCP ? (struct ccp *)fp : NULL)
+
+struct ccp_algorithm {
+  int id;
+  int Neg;					/* ccp_config neg array item */
+  const char *(*Disp)(struct fsm_opt *);	/* Use result immediately !  */
+  int (*Usable)(struct fsm *);			/* Ok to negotiate ? */
+  int (*Required)(struct fsm *);		/* Must negotiate ? */
+  struct {
+    int (*Set)(struct bundle *, struct fsm_opt *, const struct ccp_config *);
+    void *(*Init)(struct bundle *, struct fsm_opt *);
+    void (*Term)(void *);
+    void (*Reset)(void *);
+    struct mbuf *(*Read)(void *, struct ccp *, u_short *, struct mbuf *);
+    void (*DictSetup)(void *, struct ccp *, u_short, struct mbuf *);
+  } i;
+  struct {
+    int MTUOverhead;
+    void (*OptInit)(struct bundle *, struct fsm_opt *,
+                    const struct ccp_config *);
+    int (*Set)(struct bundle *, struct fsm_opt *, const struct ccp_config *);
+    void *(*Init)(struct bundle *, struct fsm_opt *);
+    void (*Term)(void *);
+    int (*Reset)(void *);
+    struct mbuf *(*Write)(void *, struct ccp *, struct link *, int, u_short *,
+                          struct mbuf *);
+  } o;
+};
+
+extern void ccp_Init(struct ccp *, struct bundle *, struct link *,
+                     const struct fsm_parent *);
+extern void ccp_Setup(struct ccp *);
+extern int ccp_Required(struct ccp *);
+extern int ccp_MTUOverhead(struct ccp *);
+
+extern void ccp_SendResetReq(struct fsm *);
+extern struct mbuf *ccp_Input(struct bundle *, struct link *, struct mbuf *);
+extern int ccp_ReportStatus(struct cmdargs const *);
+extern u_short ccp_Proto(struct ccp *);
+extern void ccp_SetupCallbacks(struct ccp *);
+extern int ccp_SetOpenMode(struct ccp *);
+extern int ccp_DefaultUsable(struct fsm *);
+extern int ccp_DefaultRequired(struct fsm *);
+
+extern struct layer ccplayer;
diff --git a/src/chap.c b/src/chap.c
new file mode 100644
index 0000000..cf78dd0
--- /dev/null
+++ b/src/chap.c
@@ -0,0 +1,972 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/chap.c,v 1.86.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifndef NODES
+#include <md4.h>
+#endif
+#include <md5.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "proto.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "auth.h"
+#include "async.h"
+#include "throughput.h"
+#include "descriptor.h"
+#include "chap.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "ccp.h"
+#include "link.h"
+#include "physical.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "chat.h"
+#include "cbcp.h"
+#include "command.h"
+#include "datalink.h"
+#ifndef NODES
+#include "chap_ms.h"
+#include "mppe.h"
+#endif
+#include "id.h"
+
+static const char * const chapcodes[] = {
+  "???", "CHALLENGE", "RESPONSE", "SUCCESS", "FAILURE"
+};
+#define MAXCHAPCODE (sizeof chapcodes / sizeof chapcodes[0] - 1)
+
+static void
+ChapOutput(struct physical *physical, u_int code, u_int id,
+	   const u_char *ptr, int count, const char *text)
+{
+  int plen;
+  struct fsmheader lh;
+  struct mbuf *bp;
+
+  plen = sizeof(struct fsmheader) + count;
+  lh.code = code;
+  lh.id = id;
+  lh.length = htons(plen);
+  bp = m_get(plen, MB_CHAPOUT);
+  memcpy(MBUF_CTOP(bp), &lh, sizeof(struct fsmheader));
+  if (count)
+    memcpy(MBUF_CTOP(bp) + sizeof(struct fsmheader), ptr, count);
+  log_DumpBp(LogDEBUG, "ChapOutput", bp);
+  if (text == NULL)
+    log_Printf(LogPHASE, "Chap Output: %s\n", chapcodes[code]);
+  else
+    log_Printf(LogPHASE, "Chap Output: %s (%s)\n", chapcodes[code], text);
+  link_PushPacket(&physical->link, bp, physical->dl->bundle,
+                  LINK_QUEUES(&physical->link) - 1, PROTO_CHAP);
+}
+
+static char *
+chap_BuildAnswer(char *name, char *key, u_char id, char *challenge
+#ifndef NODES
+		 , u_char type, char *peerchallenge, char *authresponse,
+		 int lanman
+#endif
+                )
+{
+  char *result, *digest;
+  size_t nlen, klen;
+
+  nlen = strlen(name);
+  klen = strlen(key);
+
+#ifndef NODES
+  if (type == 0x80) {
+    char expkey[AUTHLEN << 2];
+    MD4_CTX MD4context;
+    size_t f;
+
+    if ((result = malloc(1 + nlen + MS_CHAP_RESPONSE_LEN)) == NULL)
+      return result;
+
+    digest = result;					/* the response */
+    *digest++ = MS_CHAP_RESPONSE_LEN;			/* 49 */
+    memcpy(digest + MS_CHAP_RESPONSE_LEN, name, nlen);
+    if (lanman) {
+      memset(digest + 24, '\0', 25);
+      mschap_LANMan(digest, challenge + 1, key);	/* LANMan response */
+    } else {
+      memset(digest, '\0', 25);
+      digest += 24;
+
+      for (f = 0; f < klen; f++) {
+        expkey[2*f] = key[f];
+        expkey[2*f+1] = '\0';
+      }
+      /*
+       *           -----------
+       * expkey = | k\0e\0y\0 |
+       *           -----------
+       */
+      MD4Init(&MD4context);
+      MD4Update(&MD4context, expkey, klen << 1);
+      MD4Final(digest, &MD4context);
+
+      /*
+       *           ---- -------- ---------------- ------- ------
+       * result = | 49 | LANMan | 16 byte digest | 9 * ? | name |
+       *           ---- -------- ---------------- ------- ------
+       */
+      mschap_NT(digest, challenge + 1);
+    }
+    /*
+     *           ---- -------- ------------- ----- ------
+     *          |    |  struct MS_ChapResponse24  |      |
+     * result = | 49 | LANMan  |  NT digest | 0/1 | name |
+     *           ---- -------- ------------- ----- ------
+     * where only one of LANMan & NT digest are set.
+     */
+  } else if (type == 0x81) {
+    char expkey[AUTHLEN << 2];
+    char pwdhash[CHAP81_HASH_LEN];
+    char pwdhashhash[CHAP81_HASH_LEN];
+    char *ntresponse;
+    size_t f;
+
+    if ((result = malloc(1 + nlen + CHAP81_RESPONSE_LEN)) == NULL)
+      return result;
+
+    memset(result, 0, 1 + nlen + CHAP81_RESPONSE_LEN);
+
+    digest = result;
+    *digest++ = CHAP81_RESPONSE_LEN;		/* value size */
+
+    /* Copy our challenge */
+    memcpy(digest, peerchallenge + 1, CHAP81_CHALLENGE_LEN);
+
+    /* Expand password to Unicode XXX */
+    for (f = 0; f < klen; f++) {
+      expkey[2*f] = key[f];
+      expkey[2*f+1] = '\0';
+    }
+
+    ntresponse = digest + CHAP81_NTRESPONSE_OFF;
+
+    /* Get some needed hashes */
+    NtPasswordHash(expkey, klen * 2, pwdhash);
+    HashNtPasswordHash(pwdhash, pwdhashhash);
+
+    /* Generate NTRESPONSE to respond on challenge call */
+    GenerateNTResponse(challenge + 1, peerchallenge + 1, name,
+                       expkey, klen * 2, ntresponse);
+
+    /* Generate MPPE MASTERKEY */
+    GetMasterKey(pwdhashhash, ntresponse, MPPE_MasterKey);    /* XXX Global ! */
+
+    /* Generate AUTHRESPONSE to verify on auth success */
+    GenerateAuthenticatorResponse(expkey, klen * 2, ntresponse,
+                                  peerchallenge + 1, challenge + 1, name,
+                                  authresponse);
+
+    authresponse[CHAP81_AUTHRESPONSE_LEN] = 0;
+
+    memcpy(digest + CHAP81_RESPONSE_LEN, name, nlen);
+  } else
+#endif
+  if ((result = malloc(nlen + 17)) != NULL) {
+    /* Normal MD5 stuff */
+    MD5_CTX MD5context;
+
+    digest = result;
+    *digest++ = 16;				/* value size */
+
+    MD5Init(&MD5context);
+    MD5Update(&MD5context, &id, 1);
+    MD5Update(&MD5context, key, klen);
+    MD5Update(&MD5context, challenge + 1, *challenge);
+    MD5Final(digest, &MD5context);
+
+    memcpy(digest + 16, name, nlen);
+    /*
+     *           ---- -------- ------
+     * result = | 16 | digest | name |
+     *           ---- -------- ------
+     */
+  }
+
+  return result;
+}
+
+static void
+chap_StartChild(struct chap *chap, char *prog, const char *name)
+{
+  char *argv[MAXARGS], *nargv[MAXARGS];
+  int argc, fd;
+  int in[2], out[2];
+  pid_t pid;
+
+  if (chap->child.fd != -1) {
+    log_Printf(LogWARN, "Chap: %s: Program already running\n", prog);
+    return;
+  }
+
+  if (pipe(in) == -1) {
+    log_Printf(LogERROR, "Chap: pipe: %s\n", strerror(errno));
+    return;
+  }
+
+  if (pipe(out) == -1) {
+    log_Printf(LogERROR, "Chap: pipe: %s\n", strerror(errno));
+    close(in[0]);
+    close(in[1]);
+    return;
+  }
+
+  pid = getpid();
+  switch ((chap->child.pid = fork())) {
+    case -1:
+      log_Printf(LogERROR, "Chap: fork: %s\n", strerror(errno));
+      close(in[0]);
+      close(in[1]);
+      close(out[0]);
+      close(out[1]);
+      chap->child.pid = 0;
+      return;
+
+    case 0:
+      timer_TermService();
+
+      if ((argc = command_Interpret(prog, strlen(prog), argv)) <= 0) {
+        if (argc < 0) {
+          log_Printf(LogWARN, "CHAP: Invalid command syntax\n");
+          _exit(255);
+        }
+        _exit(0);
+      }
+
+      close(in[1]);
+      close(out[0]);
+      if (out[1] == STDIN_FILENO)
+        out[1] = dup(out[1]);
+      dup2(in[0], STDIN_FILENO);
+      dup2(out[1], STDOUT_FILENO);
+      close(STDERR_FILENO);
+      if (open(_PATH_DEVNULL, O_RDWR) != STDERR_FILENO) {
+        log_Printf(LogALERT, "Chap: Failed to open %s: %s\n",
+                  _PATH_DEVNULL, strerror(errno));
+        exit(1);
+      }
+      for (fd = getdtablesize(); fd > STDERR_FILENO; fd--)
+        fcntl(fd, F_SETFD, 1);
+#ifndef NOSUID
+      setuid(ID0realuid());
+#endif
+      command_Expand(nargv, argc, (char const *const *)argv,
+                     chap->auth.physical->dl->bundle, 0, pid);
+      execvp(nargv[0], nargv);
+      printf("exec() of %s failed: %s\n", nargv[0], strerror(errno));
+      _exit(255);
+
+    default:
+      close(in[0]);
+      close(out[1]);
+      chap->child.fd = out[0];
+      chap->child.buf.len = 0;
+      write(in[1], chap->auth.in.name, strlen(chap->auth.in.name));
+      write(in[1], "\n", 1);
+      write(in[1], chap->challenge.peer + 1, *chap->challenge.peer);
+      write(in[1], "\n", 1);
+      write(in[1], name, strlen(name));
+      write(in[1], "\n", 1);
+      close(in[1]);
+      break;
+  }
+}
+
+static void
+chap_Cleanup(struct chap *chap, int sig)
+{
+  if (chap->child.pid) {
+    int status;
+
+    close(chap->child.fd);
+    chap->child.fd = -1;
+    if (sig)
+      kill(chap->child.pid, SIGTERM);
+    chap->child.pid = 0;
+    chap->child.buf.len = 0;
+
+    if (wait(&status) == -1)
+      log_Printf(LogERROR, "Chap: wait: %s\n", strerror(errno));
+    else if (WIFSIGNALED(status))
+      log_Printf(LogWARN, "Chap: Child received signal %d\n", WTERMSIG(status));
+    else if (WIFEXITED(status) && WEXITSTATUS(status))
+      log_Printf(LogERROR, "Chap: Child exited %d\n", WEXITSTATUS(status));
+  }
+  *chap->challenge.local = *chap->challenge.peer = '\0';
+#ifndef NODES
+  chap->peertries = 0;
+#endif
+}
+
+static void
+chap_Respond(struct chap *chap, char *name, char *key
+#ifndef NODES
+             , u_char type, int lm
+#endif
+            )
+{
+  u_char *ans;
+
+  ans = chap_BuildAnswer(name, key, chap->auth.id, chap->challenge.peer
+#ifndef NODES
+                         , type, chap->challenge.local, chap->authresponse, lm
+#endif
+                        );
+
+  if (ans) {
+    ChapOutput(chap->auth.physical, CHAP_RESPONSE, chap->auth.id,
+               ans, *ans + 1 + strlen(name), name);
+#ifndef NODES
+    chap->NTRespSent = !lm;
+    MPPE_IsServer = 0;		/* XXX Global ! */
+#endif
+    free(ans);
+  } else
+    ChapOutput(chap->auth.physical, CHAP_FAILURE, chap->auth.id,
+               "Out of memory!", 14, NULL);
+}
+
+static int
+chap_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w __unused,
+	       fd_set *e __unused, int *n)
+{
+  struct chap *chap = descriptor2chap(d);
+
+  if (r && chap && chap->child.fd != -1) {
+    FD_SET(chap->child.fd, r);
+    if (*n < chap->child.fd + 1)
+      *n = chap->child.fd + 1;
+    log_Printf(LogTIMER, "Chap: fdset(r) %d\n", chap->child.fd);
+    return 1;
+  }
+
+  return 0;
+}
+
+static int
+chap_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+  struct chap *chap = descriptor2chap(d);
+
+  return chap && chap->child.fd != -1 && FD_ISSET(chap->child.fd, fdset);
+}
+
+static void
+chap_Read(struct fdescriptor *d, struct bundle *bundle __unused,
+	  const fd_set *fdset __unused)
+{
+  struct chap *chap = descriptor2chap(d);
+  int got;
+
+  got = read(chap->child.fd, chap->child.buf.ptr + chap->child.buf.len,
+             sizeof chap->child.buf.ptr - chap->child.buf.len - 1);
+  if (got == -1) {
+    log_Printf(LogERROR, "Chap: Read: %s\n", strerror(errno));
+    chap_Cleanup(chap, SIGTERM);
+  } else if (got == 0) {
+    log_Printf(LogWARN, "Chap: Read: Child terminated connection\n");
+    chap_Cleanup(chap, SIGTERM);
+  } else {
+    char *name, *key, *end;
+
+    chap->child.buf.len += got;
+    chap->child.buf.ptr[chap->child.buf.len] = '\0';
+    name = chap->child.buf.ptr;
+    name += strspn(name, " \t");
+    if ((key = strchr(name, '\n')) == NULL)
+      end = NULL;
+    else
+      end = strchr(++key, '\n');
+
+    if (end == NULL) {
+      if (chap->child.buf.len == sizeof chap->child.buf.ptr - 1) {
+        log_Printf(LogWARN, "Chap: Read: Input buffer overflow\n");
+        chap_Cleanup(chap, SIGTERM);
+      }
+    } else {
+#ifndef NODES
+      int lanman = chap->auth.physical->link.lcp.his_authtype == 0x80 &&
+                   ((chap->NTRespSent &&
+                     IsAccepted(chap->auth.physical->link.lcp.cfg.chap80lm)) ||
+                    !IsAccepted(chap->auth.physical->link.lcp.cfg.chap80nt));
+#endif
+
+      while (end >= name && strchr(" \t\r\n", *end))
+        *end-- = '\0';
+      end = key - 1;
+      while (end >= name && strchr(" \t\r\n", *end))
+        *end-- = '\0';
+      key += strspn(key, " \t");
+
+      chap_Respond(chap, name, key
+#ifndef NODES
+                   , chap->auth.physical->link.lcp.his_authtype, lanman
+#endif
+                  );
+      chap_Cleanup(chap, 0);
+    }
+  }
+}
+
+static int
+chap_Write(struct fdescriptor *d __unused, struct bundle *bundle __unused,
+	   const fd_set *fdset __unused)
+{
+  /* We never want to write here ! */
+  log_Printf(LogALERT, "chap_Write: Internal error: Bad call !\n");
+  return 0;
+}
+
+static void
+chap_ChallengeInit(struct authinfo *authp)
+{
+  struct chap *chap = auth2chap(authp);
+  int len, i;
+  char *cp;
+
+  len = strlen(authp->physical->dl->bundle->cfg.auth.name);
+
+  if (!*chap->challenge.local) {
+    randinit();
+    cp = chap->challenge.local;
+
+#ifndef NORADIUS
+    if (*authp->physical->dl->bundle->radius.cfg.file) {
+      /* For radius, our challenge is 16 readable NUL terminated bytes :*/
+      *cp++ = 16;
+      for (i = 0; i < 16; i++)
+        *cp++ = (random() % 10) + '0';
+    } else
+#endif
+    {
+#ifndef NODES
+      if (authp->physical->link.lcp.want_authtype == 0x80)
+        *cp++ = 8;	/* MS does 8 byte callenges :-/ */
+      else if (authp->physical->link.lcp.want_authtype == 0x81)
+        *cp++ = 16;	/* MS-CHAP-V2 does 16 bytes challenges */
+      else
+#endif
+        *cp++ = random() % (CHAPCHALLENGELEN-16) + 16;
+      for (i = 0; i < *chap->challenge.local; i++)
+        *cp++ = random() & 0xff;
+    }
+    memcpy(cp, authp->physical->dl->bundle->cfg.auth.name, len);
+  }
+}
+
+static void
+chap_Challenge(struct authinfo *authp)
+{
+  struct chap *chap = auth2chap(authp);
+  int len;
+
+  log_Printf(LogDEBUG, "CHAP%02X: Challenge\n",
+             authp->physical->link.lcp.want_authtype);
+
+  len = strlen(authp->physical->dl->bundle->cfg.auth.name);
+
+  /* Generate new local challenge value */
+  if (!*chap->challenge.local)
+    chap_ChallengeInit(authp);
+
+#ifndef NODES
+  if (authp->physical->link.lcp.want_authtype == 0x81)
+    ChapOutput(authp->physical, CHAP_CHALLENGE, authp->id,
+             chap->challenge.local, 1 + *chap->challenge.local, NULL);
+  else
+#endif
+    ChapOutput(authp->physical, CHAP_CHALLENGE, authp->id,
+             chap->challenge.local, 1 + *chap->challenge.local + len, NULL);
+}
+
+static void
+chap_Success(struct authinfo *authp)
+{
+  struct bundle *bundle = authp->physical->dl->bundle;
+  const char *msg;
+
+  datalink_GotAuthname(authp->physical->dl, authp->in.name);
+#ifndef NODES
+  if (authp->physical->link.lcp.want_authtype == 0x81) {
+#ifndef NORADIUS
+    if (*bundle->radius.cfg.file && bundle->radius.msrepstr)
+      msg = bundle->radius.msrepstr;
+    else
+#endif
+      msg = auth2chap(authp)->authresponse;
+    MPPE_MasterKeyValid = 1;		/* XXX Global ! */
+  } else
+#endif
+#ifndef NORADIUS
+  if (*bundle->radius.cfg.file && bundle->radius.repstr)
+    msg = bundle->radius.repstr;
+  else
+#endif
+    msg = "Welcome!!";
+
+  ChapOutput(authp->physical, CHAP_SUCCESS, authp->id, msg, strlen(msg),
+             NULL);
+
+  authp->physical->link.lcp.auth_ineed = 0;
+  if (Enabled(bundle, OPT_UTMP))
+    physical_Login(authp->physical, authp->in.name);
+
+  if (authp->physical->link.lcp.auth_iwait == 0)
+    /*
+     * Either I didn't need to authenticate, or I've already been
+     * told that I got the answer right.
+     */
+    datalink_AuthOk(authp->physical->dl);
+}
+
+static void
+chap_Failure(struct authinfo *authp)
+{
+#ifndef NODES
+  char buf[1024], *ptr;
+#endif
+  const char *msg;
+
+#ifndef NORADIUS
+  struct bundle *bundle = authp->physical->link.lcp.fsm.bundle;
+  if (*bundle->radius.cfg.file && bundle->radius.errstr)
+    msg = bundle->radius.errstr;
+  else
+#endif
+#ifndef NODES
+  if (authp->physical->link.lcp.want_authtype == 0x80) {
+    sprintf(buf, "E=691 R=1 M=Invalid!");
+    msg = buf;
+  } else if (authp->physical->link.lcp.want_authtype == 0x81) {
+    int i;
+
+    ptr = buf;
+    ptr += sprintf(buf, "E=691 R=0 C=");
+    for (i=0; i<16; i++)
+      ptr += sprintf(ptr, "%02X", *(auth2chap(authp)->challenge.local+1+i));
+
+    sprintf(ptr, " V=3 M=Invalid!");
+    msg = buf;
+  } else
+#endif
+    msg = "Invalid!!";
+
+  ChapOutput(authp->physical, CHAP_FAILURE, authp->id, msg, strlen(msg) + 1,
+             NULL);
+  datalink_AuthNotOk(authp->physical->dl);
+}
+
+static int
+chap_Cmp(char *myans, int mylen, char *hisans, int hislen
+#ifndef NODES
+         , u_char type, int lm
+#endif
+        )
+{
+  int off;
+
+  if (mylen != hislen)
+    return 0;
+
+  off = 0;
+
+#ifndef NODES
+  if (type == 0x80) {
+    off = lm ? 0 : 24;
+    mylen = 24;
+  }
+#endif
+
+  for (; mylen; off++, mylen--)
+    if (toupper(myans[off]) != toupper(hisans[off]))
+      return 0;
+
+  return 1;
+}
+
+#ifndef NODES
+static int
+chap_HaveAnotherGo(struct chap *chap)
+{
+  if (++chap->peertries < 3) {
+    /* Give the peer another shot */
+    *chap->challenge.local = '\0';
+    chap_Challenge(&chap->auth);
+    return 1;
+  }
+
+  return 0;
+}
+#endif
+
+void
+chap_Init(struct chap *chap, struct physical *p)
+{
+  chap->desc.type = CHAP_DESCRIPTOR;
+  chap->desc.UpdateSet = chap_UpdateSet;
+  chap->desc.IsSet = chap_IsSet;
+  chap->desc.Read = chap_Read;
+  chap->desc.Write = chap_Write;
+  chap->child.pid = 0;
+  chap->child.fd = -1;
+  auth_Init(&chap->auth, p, chap_Challenge, chap_Success, chap_Failure);
+  *chap->challenge.local = *chap->challenge.peer = '\0';
+#ifndef NODES
+  chap->NTRespSent = 0;
+  chap->peertries = 0;
+#endif
+}
+
+void
+chap_ReInit(struct chap *chap)
+{
+  chap_Cleanup(chap, SIGTERM);
+}
+
+struct mbuf *
+chap_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+  struct physical *p = link2physical(l);
+  struct chap *chap = &p->dl->chap;
+  char *name, *key, *ans;
+  int len;
+  size_t nlen;
+  u_char alen;
+#ifndef NODES
+  int lanman;
+#endif
+
+  if (p == NULL) {
+    log_Printf(LogERROR, "chap_Input: Not a physical link - dropped\n");
+    m_freem(bp);
+    return NULL;
+  }
+
+  if (bundle_Phase(bundle) != PHASE_NETWORK &&
+      bundle_Phase(bundle) != PHASE_AUTHENTICATE) {
+    log_Printf(LogPHASE, "Unexpected chap input - dropped !\n");
+    m_freem(bp);
+    return NULL;
+  }
+
+  m_settype(bp, MB_CHAPIN);
+  if ((bp = auth_ReadHeader(&chap->auth, bp)) == NULL &&
+      ntohs(chap->auth.in.hdr.length) == 0)
+    log_Printf(LogWARN, "Chap Input: Truncated header !\n");
+  else if (chap->auth.in.hdr.code == 0 || chap->auth.in.hdr.code > MAXCHAPCODE)
+    log_Printf(LogPHASE, "Chap Input: %d: Bad CHAP code !\n",
+               chap->auth.in.hdr.code);
+  else {
+    len = m_length(bp);
+    ans = NULL;
+
+    if (chap->auth.in.hdr.code != CHAP_CHALLENGE &&
+        chap->auth.id != chap->auth.in.hdr.id &&
+        Enabled(bundle, OPT_IDCHECK)) {
+      /* Wrong conversation dude ! */
+      log_Printf(LogPHASE, "Chap Input: %s dropped (got id %d, not %d)\n",
+                 chapcodes[chap->auth.in.hdr.code], chap->auth.in.hdr.id,
+                 chap->auth.id);
+      m_freem(bp);
+      return NULL;
+    }
+    chap->auth.id = chap->auth.in.hdr.id;	/* We respond with this id */
+
+#ifndef NODES
+    lanman = 0;
+#endif
+    switch (chap->auth.in.hdr.code) {
+      case CHAP_CHALLENGE:
+        bp = mbuf_Read(bp, &alen, 1);
+        len -= alen + 1;
+        if (len < 0) {
+          log_Printf(LogERROR, "Chap Input: Truncated challenge !\n");
+          m_freem(bp);
+          return NULL;
+        }
+        *chap->challenge.peer = alen;
+        bp = mbuf_Read(bp, chap->challenge.peer + 1, alen);
+        bp = auth_ReadName(&chap->auth, bp, len);
+#ifndef NODES
+        lanman = p->link.lcp.his_authtype == 0x80 &&
+                 ((chap->NTRespSent && IsAccepted(p->link.lcp.cfg.chap80lm)) ||
+                  !IsAccepted(p->link.lcp.cfg.chap80nt));
+
+        /* Generate local challenge value */
+        chap_ChallengeInit(&chap->auth);
+#endif
+        break;
+
+      case CHAP_RESPONSE:
+        auth_StopTimer(&chap->auth);
+        bp = mbuf_Read(bp, &alen, 1);
+        len -= alen + 1;
+        if (len < 0) {
+          log_Printf(LogERROR, "Chap Input: Truncated response !\n");
+          m_freem(bp);
+          return NULL;
+        }
+        if ((ans = malloc(alen + 1)) == NULL) {
+          log_Printf(LogERROR, "Chap Input: Out of memory !\n");
+          m_freem(bp);
+          return NULL;
+        }
+        *ans = chap->auth.id;
+        bp = mbuf_Read(bp, ans + 1, alen);
+        bp = auth_ReadName(&chap->auth, bp, len);
+#ifndef NODES
+        lanman = p->link.lcp.want_authtype == 0x80 &&
+                 alen == 49 && ans[alen] == 0;
+#endif
+        break;
+
+      case CHAP_SUCCESS:
+      case CHAP_FAILURE:
+        /* chap->auth.in.name is already set up at CHALLENGE time */
+        if ((ans = malloc(len + 1)) == NULL) {
+          log_Printf(LogERROR, "Chap Input: Out of memory !\n");
+          m_freem(bp);
+          return NULL;
+        }
+        bp = mbuf_Read(bp, ans, len);
+        ans[len] = '\0';
+        break;
+    }
+
+    switch (chap->auth.in.hdr.code) {
+      case CHAP_CHALLENGE:
+      case CHAP_RESPONSE:
+        if (*chap->auth.in.name)
+          log_Printf(LogPHASE, "Chap Input: %s (%d bytes from %s%s)\n",
+                     chapcodes[chap->auth.in.hdr.code], alen,
+                     chap->auth.in.name,
+#ifndef NODES
+                     lanman && chap->auth.in.hdr.code == CHAP_RESPONSE ?
+                     " - lanman" :
+#endif
+                     "");
+        else
+          log_Printf(LogPHASE, "Chap Input: %s (%d bytes%s)\n",
+                     chapcodes[chap->auth.in.hdr.code], alen,
+#ifndef NODES
+                     lanman && chap->auth.in.hdr.code == CHAP_RESPONSE ?
+                     " - lanman" :
+#endif
+                     "");
+        break;
+
+      case CHAP_SUCCESS:
+      case CHAP_FAILURE:
+        if (*ans)
+          log_Printf(LogPHASE, "Chap Input: %s (%s)\n",
+                     chapcodes[chap->auth.in.hdr.code], ans);
+        else
+          log_Printf(LogPHASE, "Chap Input: %s\n",
+                     chapcodes[chap->auth.in.hdr.code]);
+        break;
+    }
+
+    switch (chap->auth.in.hdr.code) {
+      case CHAP_CHALLENGE:
+        if (*bundle->cfg.auth.key == '!' && bundle->cfg.auth.key[1] != '!')
+          chap_StartChild(chap, bundle->cfg.auth.key + 1,
+                          bundle->cfg.auth.name);
+        else
+          chap_Respond(chap, bundle->cfg.auth.name, bundle->cfg.auth.key +
+                       (*bundle->cfg.auth.key == '!' ? 1 : 0)
+                       
+#ifndef NODES
+                       , p->link.lcp.his_authtype, lanman
+#endif
+                      );
+        break;
+
+      case CHAP_RESPONSE:
+        name = chap->auth.in.name;
+        nlen = strlen(name);
+#ifndef NODES
+        if (p->link.lcp.want_authtype == 0x81) {
+          struct MSCHAPv2_resp *resp = (struct MSCHAPv2_resp *)(ans + 1);
+
+          chap->challenge.peer[0] = sizeof resp->PeerChallenge;
+          memcpy(chap->challenge.peer + 1, resp->PeerChallenge,
+                 sizeof resp->PeerChallenge);
+        }
+#endif
+
+#ifndef NORADIUS
+        if (*bundle->radius.cfg.file) {
+          if (!radius_Authenticate(&bundle->radius, &chap->auth,
+                                   chap->auth.in.name, ans, alen + 1,
+                                   chap->challenge.local + 1,
+                                   *chap->challenge.local))
+            chap_Failure(&chap->auth);
+        } else
+#endif
+        {
+          if (p->link.lcp.want_authtype == 0x81 && ans[alen] != '\0' &&
+              alen == sizeof(struct MSCHAPv2_resp)) {
+            struct MSCHAPv2_resp *resp = (struct MSCHAPv2_resp *)(ans + 1);
+
+            log_Printf(LogWARN, "%s: Compensating for corrupt (Win98/WinME?) "
+                       "CHAP81 RESPONSE\n", l->name);
+            resp->Flags = '\0';	/* rfc2759 says it *MUST* be zero */
+          }
+          key = auth_GetSecret(name, nlen);
+          if (key) {
+#ifndef NODES
+            if (p->link.lcp.want_authtype == 0x80 &&
+                lanman && !IsEnabled(p->link.lcp.cfg.chap80lm)) {
+              log_Printf(LogPHASE, "Auth failure: LANMan not enabled\n");
+              if (chap_HaveAnotherGo(chap))
+                break;
+              key = NULL;
+            } else if (p->link.lcp.want_authtype == 0x80 &&
+                !lanman && !IsEnabled(p->link.lcp.cfg.chap80nt)) {
+              log_Printf(LogPHASE, "Auth failure: mschap not enabled\n");
+              if (chap_HaveAnotherGo(chap))
+                break;
+              key = NULL;
+            } else if (p->link.lcp.want_authtype == 0x81 &&
+                !IsEnabled(p->link.lcp.cfg.chap81)) {
+              log_Printf(LogPHASE, "Auth failure: CHAP81 not enabled\n");
+              key = NULL;
+            } else
+#endif
+            {
+              char *myans = chap_BuildAnswer(name, key, chap->auth.id,
+                                             chap->challenge.local
+#ifndef NODES
+					     , p->link.lcp.want_authtype,
+					     chap->challenge.peer,
+					     chap->authresponse, lanman);
+              MPPE_IsServer = 1;		/* XXX Global ! */
+#else
+                                      );
+#endif
+              if (myans == NULL)
+                key = NULL;
+              else {
+                if (!chap_Cmp(myans + 1, *myans, ans + 1, alen
+#ifndef NODES
+                              , p->link.lcp.want_authtype, lanman
+#endif
+                             ))
+                  key = NULL;
+                free(myans);
+              }
+            }
+          }
+
+          if (key)
+            chap_Success(&chap->auth);
+          else
+            chap_Failure(&chap->auth);
+        }
+
+        break;
+
+      case CHAP_SUCCESS:
+        if (p->link.lcp.auth_iwait == PROTO_CHAP) {
+          p->link.lcp.auth_iwait = 0;
+          if (p->link.lcp.auth_ineed == 0) {
+#ifndef NODES
+            if (p->link.lcp.his_authtype == 0x81) {
+              if (strncasecmp(ans, chap->authresponse, 42)) {
+                datalink_AuthNotOk(p->dl);
+	        log_Printf(LogWARN, "CHAP81: AuthenticatorResponse: (%.42s)"
+                           " != ans: (%.42s)\n", chap->authresponse, ans);
+
+              } else {
+                /* Successful login */
+                MPPE_MasterKeyValid = 1;		/* XXX Global ! */
+                datalink_AuthOk(p->dl);
+              }
+            } else
+#endif
+            /*
+             * We've succeeded in our ``login''
+             * If we're not expecting  the peer to authenticate (or he already
+             * has), proceed to network phase.
+             */
+            datalink_AuthOk(p->dl);
+          }
+        }
+        break;
+
+      case CHAP_FAILURE:
+        datalink_AuthNotOk(p->dl);
+        break;
+    }
+    free(ans);
+  }
+
+  m_freem(bp);
+  return NULL;
+}
diff --git a/src/chap.h b/src/chap.h
new file mode 100644
index 0000000..e9936e4
--- /dev/null
+++ b/src/chap.h
@@ -0,0 +1,75 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/chap.h,v 1.22.38.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct mbuf;
+struct physical;
+
+#define	CHAP_CHALLENGE	1
+#define	CHAP_RESPONSE	2
+#define	CHAP_SUCCESS	3
+#define	CHAP_FAILURE	4
+
+struct chap {
+  struct fdescriptor desc;
+  struct {
+    pid_t pid;
+    int fd;
+    struct {
+      char ptr[AUTHLEN * 2 + 3];	/* Allow for \r\n at the end (- NUL) */
+      int len;
+    } buf;
+  } child;
+  struct authinfo auth;
+  struct {
+    u_char local[CHAPCHALLENGELEN + AUTHLEN];	/* I invented this one */
+    u_char peer[CHAPCHALLENGELEN + AUTHLEN];	/* Peer gave us this one */
+  } challenge;
+#ifndef NODES
+  unsigned NTRespSent : 1;		/* Our last response */
+  int peertries;
+  u_char authresponse[CHAPAUTHRESPONSELEN];	/* CHAP 81 response */
+#endif
+};
+
+#define descriptor2chap(d) \
+  ((d)->type == CHAP_DESCRIPTOR ? (struct chap *)(d) : NULL)
+#define auth2chap(a) \
+  ((struct chap *)((char *)a - (int)&((struct chap *)0)->auth))
+
+struct MSCHAPv2_resp {		/* rfc2759 */
+  char PeerChallenge[16];
+  char Reserved[8];
+  char NTResponse[24];
+  char Flags;
+};
+
+extern void chap_Init(struct chap *, struct physical *);
+extern void chap_ReInit(struct chap *);
+extern struct mbuf *chap_Input(struct bundle *, struct link *, struct mbuf *);
diff --git a/src/chap_ms.c b/src/chap_ms.c
new file mode 100644
index 0000000..a2a06a8
--- /dev/null
+++ b/src/chap_ms.c
@@ -0,0 +1,415 @@
+/*-
+ * Copyright (c) 1997        Gabor Kincses <gabor@acm.org>
+ *               1997 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Eric Rosenquist
+ *                           Strata Software Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/chap_ms.c,v 1.20.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <ctype.h>
+#ifdef __FreeBSD__
+#include <openssl/des.h>
+#include <sha.h>
+#else
+#include <sys/types.h>
+#include <stdlib.h>
+#ifdef __NetBSD__
+#include <openssl/des.h>
+#else
+#include <des.h>
+#endif
+#include <openssl/sha.h>
+#endif
+#include <md4.h>
+#include <string.h>
+
+#include "chap_ms.h"
+
+/*
+ * Documentation & specifications:
+ *
+ * MS-CHAP (CHAP80)	rfc2433
+ * MS-CHAP-V2 (CHAP81)	rfc2759
+ * MPPE key management	draft-ietf-pppext-mppe-keys-02.txt
+ */
+
+static char SHA1_Pad1[40] =
+  {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static char SHA1_Pad2[40] =
+  {0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2,
+   0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2,
+   0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2,
+   0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2};
+
+/* unused, for documentation only */
+/* only NTResp is filled in for FreeBSD */
+struct MS_ChapResponse {
+    u_char LANManResp[24];
+    u_char NTResp[24];
+    u_char UseNT;	/* If 1, ignore the LANMan response field */
+};
+
+static u_char
+Get7Bits(u_char *input, int startBit)
+{
+    register unsigned int	word;
+
+    word  = (unsigned)input[startBit / 8] << 8;
+    word |= (unsigned)input[startBit / 8 + 1];
+
+    word >>= 15 - (startBit % 8 + 7);
+
+    return word & 0xFE;
+}
+
+/* IN  56 bit DES key missing parity bits
+   OUT 64 bit DES key with parity bits added */
+static void
+MakeKey(u_char *key, u_char *des_key)
+{
+    des_key[0] = Get7Bits(key,  0);
+    des_key[1] = Get7Bits(key,  7);
+    des_key[2] = Get7Bits(key, 14);
+    des_key[3] = Get7Bits(key, 21);
+    des_key[4] = Get7Bits(key, 28);
+    des_key[5] = Get7Bits(key, 35);
+    des_key[6] = Get7Bits(key, 42);
+    des_key[7] = Get7Bits(key, 49);
+
+    des_set_odd_parity((des_cblock *)des_key);
+}
+
+static void /* IN 8 octets IN 7 octest OUT 8 octets */
+DesEncrypt(u_char *clear, u_char *key, u_char *cipher)
+{
+    des_cblock		des_key;
+    des_key_schedule	key_schedule;
+
+    MakeKey(key, des_key);
+    des_set_key(&des_key, key_schedule);
+    des_ecb_encrypt((des_cblock *)clear, (des_cblock *)cipher, key_schedule, 1);
+}
+
+static void      /* IN 8 octets      IN 16 octets     OUT 24 octets */
+ChallengeResponse(u_char *challenge, u_char *pwHash, u_char *response)
+{
+    char    ZPasswordHash[21];
+
+    memset(ZPasswordHash, '\0', sizeof ZPasswordHash);
+    memcpy(ZPasswordHash, pwHash, 16);
+
+    DesEncrypt(challenge, ZPasswordHash +  0, response + 0);
+    DesEncrypt(challenge, ZPasswordHash +  7, response + 8);
+    DesEncrypt(challenge, ZPasswordHash + 14, response + 16);
+}
+
+void
+NtPasswordHash(char *key, int keylen, char *hash)
+{
+  MD4_CTX MD4context;
+
+  MD4Init(&MD4context);
+  MD4Update(&MD4context, key, keylen);
+  MD4Final(hash, &MD4context);
+}
+
+void
+HashNtPasswordHash(char *hash, char *hashhash)
+{
+  MD4_CTX MD4context;
+
+  MD4Init(&MD4context);
+  MD4Update(&MD4context, hash, 16);
+  MD4Final(hashhash, &MD4context);
+}
+
+static void
+ChallengeHash(char *PeerChallenge, char *AuthenticatorChallenge,
+              char *UserName, char *Challenge)
+{
+  SHA_CTX Context;
+  char Digest[SHA_DIGEST_LENGTH];
+  char *Name;
+
+  Name = strrchr(UserName, '\\');
+  if(NULL == Name)
+    Name = UserName;
+  else
+    Name++;
+
+  SHA1_Init(&Context);
+
+  SHA1_Update(&Context, PeerChallenge, 16);
+  SHA1_Update(&Context, AuthenticatorChallenge, 16);
+  SHA1_Update(&Context, Name, strlen(Name));
+
+  SHA1_Final(Digest, &Context);
+  memcpy(Challenge, Digest, 8);
+}
+
+void
+GenerateNTResponse(char *AuthenticatorChallenge, char *PeerChallenge,
+                   char *UserName, char *Password,
+                   int PasswordLen, char *Response)
+{
+  char Challenge[8];
+  char PasswordHash[16];
+
+  ChallengeHash(PeerChallenge, AuthenticatorChallenge, UserName, Challenge);
+  NtPasswordHash(Password, PasswordLen, PasswordHash);
+  ChallengeResponse(Challenge, PasswordHash, Response);
+}
+
+#ifndef __FreeBSD__
+#define LENGTH 20
+static char *
+SHA1_End(SHA_CTX *ctx, char *buf)
+{
+    int i;
+    unsigned char digest[LENGTH];
+    static const char hex[]="0123456789abcdef";
+
+    if (!buf)
+        buf = malloc(2*LENGTH + 1);
+    if (!buf)
+        return 0;
+    SHA1_Final(digest, ctx);
+    for (i = 0; i < LENGTH; i++) {
+        buf[i+i] = hex[digest[i] >> 4];
+        buf[i+i+1] = hex[digest[i] & 0x0f];
+    }
+    buf[i+i] = '\0';
+    return buf;
+}
+#endif
+
+void
+GenerateAuthenticatorResponse(char *Password, int PasswordLen,
+                              char *NTResponse, char *PeerChallenge,
+                              char *AuthenticatorChallenge, char *UserName,
+                              char *AuthenticatorResponse)
+{
+  SHA_CTX Context;
+  char PasswordHash[16];
+  char PasswordHashHash[16];
+  char Challenge[8];
+  u_char Digest[SHA_DIGEST_LENGTH];
+  int i;
+
+      /*
+       * "Magic" constants used in response generation
+       */
+  char Magic1[39] =
+         {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
+          0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
+          0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
+          0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};
+
+
+  char Magic2[41] =
+         {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
+          0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
+          0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
+          0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
+          0x6E};
+      /*
+       * Hash the password with MD4
+       */
+  NtPasswordHash(Password, PasswordLen, PasswordHash);
+      /*
+       * Now hash the hash
+       */
+  HashNtPasswordHash(PasswordHash, PasswordHashHash);
+
+  SHA1_Init(&Context);
+  SHA1_Update(&Context, PasswordHashHash, 16);
+  SHA1_Update(&Context, NTResponse, 24);
+  SHA1_Update(&Context, Magic1, 39);
+  SHA1_Final(Digest, &Context);
+  ChallengeHash(PeerChallenge, AuthenticatorChallenge, UserName, Challenge);
+  SHA1_Init(&Context);
+  SHA1_Update(&Context, Digest, 20);
+  SHA1_Update(&Context, Challenge, 8);
+  SHA1_Update(&Context, Magic2, 41);
+
+      /*
+       * Encode the value of 'Digest' as "S=" followed by
+       * 40 ASCII hexadecimal digits and return it in
+       * AuthenticatorResponse.
+       * For example,
+       *   "S=0123456789ABCDEF0123456789ABCDEF01234567"
+       */
+  AuthenticatorResponse[0] = 'S';
+  AuthenticatorResponse[1] = '=';
+  SHA1_End(&Context, AuthenticatorResponse + 2);
+  for (i=2; i<42; i++)
+    AuthenticatorResponse[i] = toupper(AuthenticatorResponse[i]);
+
+}
+
+void
+GetMasterKey(char *PasswordHashHash, char *NTResponse, char *MasterKey)
+{
+  char Digest[SHA_DIGEST_LENGTH];
+  SHA_CTX Context;
+  static char Magic1[27] =
+      {0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
+       0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d,
+       0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79};
+
+  SHA1_Init(&Context);
+  SHA1_Update(&Context, PasswordHashHash, 16);
+  SHA1_Update(&Context, NTResponse, 24);
+  SHA1_Update(&Context, Magic1, 27);
+  SHA1_Final(Digest, &Context);
+  memcpy(MasterKey, Digest, 16);
+}
+
+void
+GetAsymetricStartKey(char *MasterKey, char *SessionKey, int SessionKeyLength,
+                     int IsSend, int IsServer)
+{
+  char Digest[SHA_DIGEST_LENGTH];
+  SHA_CTX Context;
+  char *s;
+
+  static char Magic2[84] =
+      {0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
+       0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
+       0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
+       0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79,
+       0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
+       0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
+       0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
+       0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
+       0x6b, 0x65, 0x79, 0x2e};
+
+  static char Magic3[84] =
+      {0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69,
+       0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20,
+       0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
+       0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
+       0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68,
+       0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
+       0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
+       0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20,
+       0x6b, 0x65, 0x79, 0x2e};
+
+  if (IsSend) {
+     if (IsServer) {
+        s = Magic3;
+     } else {
+        s = Magic2;
+     }
+  } else {
+     if (IsServer) {
+        s = Magic2;
+     } else {
+        s = Magic3;
+     }
+  }
+
+  SHA1_Init(&Context);
+  SHA1_Update(&Context, MasterKey, 16);
+  SHA1_Update(&Context, SHA1_Pad1, 40);
+  SHA1_Update(&Context, s, 84);
+  SHA1_Update(&Context, SHA1_Pad2, 40);
+  SHA1_Final(Digest, &Context);
+
+  memcpy(SessionKey, Digest, SessionKeyLength);
+}
+
+void
+GetNewKeyFromSHA(char *StartKey, char *SessionKey, long SessionKeyLength,
+                 char *InterimKey)
+{
+  SHA_CTX Context;
+  char Digest[SHA_DIGEST_LENGTH];
+
+  SHA1_Init(&Context);
+  SHA1_Update(&Context, StartKey, SessionKeyLength);
+  SHA1_Update(&Context, SHA1_Pad1, 40);
+  SHA1_Update(&Context, SessionKey, SessionKeyLength);
+  SHA1_Update(&Context, SHA1_Pad2, 40);
+  SHA1_Final(Digest, &Context);
+
+  memcpy(InterimKey, Digest, SessionKeyLength);
+}
+
+#if 0
+static void
+Get_Key(char *InitialSessionKey, char *CurrentSessionKey,
+        int LengthOfDesiredKey)
+{
+  SHA_CTX Context;
+  char Digest[SHA_DIGEST_LENGTH];
+
+  SHA1_Init(&Context);
+  SHA1_Update(&Context, InitialSessionKey, LengthOfDesiredKey);
+  SHA1_Update(&Context, SHA1_Pad1, 40);
+  SHA1_Update(&Context, CurrentSessionKey, LengthOfDesiredKey);
+  SHA1_Update(&Context, SHA1_Pad2, 40);
+  SHA1_Final(Digest, &Context);
+
+  memcpy(CurrentSessionKey, Digest, LengthOfDesiredKey);
+}
+#endif
+
+/* passwordHash 16-bytes MD4 hashed password
+   challenge    8-bytes peer CHAP challenge
+   since passwordHash is in a 24-byte buffer, response is written in there */
+void
+mschap_NT(char *passwordHash, char *challenge)
+{
+    u_char response[24];
+
+    ChallengeResponse(challenge, passwordHash, response);
+    memcpy(passwordHash, response, 24);
+    passwordHash[24] = 1;		/* NT-style response */
+}
+
+void
+mschap_LANMan(char *digest, char *challenge, char *secret)
+{
+  static u_char salt[] = "KGS!@#$%";	/* RASAPI32.dll */
+  char SECRET[14], *ptr, *end;
+  u_char hash[16];
+
+  end = SECRET + sizeof SECRET;
+  for (ptr = SECRET; *secret && ptr < end; ptr++, secret++)
+    *ptr = toupper(*secret);
+  if (ptr < end)
+    memset(ptr, '\0', end - ptr);
+
+  DesEncrypt(salt, SECRET, hash);
+  DesEncrypt(salt, SECRET + 7, hash + 8);
+
+  ChallengeResponse(challenge, hash, digest);
+}
diff --git a/src/chap_ms.h b/src/chap_ms.h
new file mode 100644
index 0000000..14793f0
--- /dev/null
+++ b/src/chap_ms.h
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 1997        Gabor Kincses <gabor@acm.org>
+ *               1997 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Eric Rosenquist
+ *                           Strata Software Limited.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/chap_ms.h,v 1.9.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+/* Max # of (Unicode) chars in an NT password */
+#define MAX_NT_PASSWORD	256
+
+/* Don't rely on sizeof(MS_ChapResponse) in case of struct padding */
+#define MS_CHAP_RESPONSE_LEN    49
+#define CHAP81_RESPONSE_LEN     49
+#define CHAP81_NTRESPONSE_LEN   24
+#define CHAP81_NTRESPONSE_OFF   24
+#define CHAP81_HASH_LEN         16
+#define CHAP81_AUTHRESPONSE_LEN	42
+#define CHAP81_CHALLENGE_LEN    16
+
+extern void mschap_NT(char *, char *);
+extern void mschap_LANMan(char *, char *, char *);
+extern void GenerateNTResponse(char *, char *, char *, char *, int , char *);
+extern void HashNtPasswordHash(char *, char *);
+extern void NtPasswordHash(char *, int, char *);
+extern void GenerateAuthenticatorResponse(char *, int, char *, char *, char *, char *, char *);
+extern void GetAsymetricStartKey(char *, char *, int, int, int);
+extern void GetMasterKey(char *, char *, char *);
+extern void GetNewKeyFromSHA(char *, char *, long, char *);
diff --git a/src/chat.c b/src/chat.c
new file mode 100644
index 0000000..bd635d5
--- /dev/null
+++ b/src/chat.c
@@ -0,0 +1,797 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/chat.c,v 1.80.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "chat.h"
+#include "mp.h"
+#include "auth.h"
+#include "chap.h"
+#include "slcompress.h"
+#include "iplist.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "cbcp.h"
+#include "command.h"
+#include "datalink.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "id.h"
+
+#define BUFLEFT(c) (sizeof (c)->buf - ((c)->bufend - (c)->buf))
+
+static void ExecStr(struct physical *, char *, char *, int);
+static char *ExpandString(struct chat *, const char *, char *, int, int);
+
+static void
+chat_PauseTimer(void *v)
+{
+  struct chat *c = (struct chat *)v;
+  timer_Stop(&c->pause);
+  c->pause.load = 0;
+}
+
+static void
+chat_Pause(struct chat *c, u_long load)
+{
+  timer_Stop(&c->pause);
+  c->pause.load += load;
+  c->pause.func = chat_PauseTimer;
+  c->pause.name = "chat pause";
+  c->pause.arg = c;
+  timer_Start(&c->pause);
+}
+
+static void
+chat_TimeoutTimer(void *v)
+{
+  struct chat *c = (struct chat *)v;
+  timer_Stop(&c->timeout);
+  c->TimedOut = 1;
+}
+
+static void
+chat_SetTimeout(struct chat *c)
+{
+  timer_Stop(&c->timeout);
+  if (c->TimeoutSec > 0) {
+    c->timeout.load = SECTICKS * c->TimeoutSec;
+    c->timeout.func = chat_TimeoutTimer;
+    c->timeout.name = "chat timeout";
+    c->timeout.arg = c;
+    timer_Start(&c->timeout);
+  }
+}
+
+static char *
+chat_NextChar(char *ptr, char ch)
+{
+  for (; *ptr; ptr++)
+    if (*ptr == ch)
+      return ptr;
+    else if (*ptr == '\\')
+      if (*++ptr == '\0')
+        return NULL;
+
+  return NULL;
+}
+
+static int
+chat_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n)
+{
+  struct chat *c = descriptor2chat(d);
+  int special, gotabort, gottimeout, needcr;
+  int TimedOut = c->TimedOut;
+  static char arg_term;		/* An empty string */
+
+  if (c->pause.state == TIMER_RUNNING)
+    return 0;
+
+  if (TimedOut) {
+    log_Printf(LogCHAT, "Expect timeout\n");
+    if (c->nargptr == NULL)
+      c->state = CHAT_FAILED;
+    else {
+      /* c->state = CHAT_EXPECT; */
+      c->argptr = &arg_term;
+    }
+    c->TimedOut = 0;
+  }
+
+  if (c->state != CHAT_EXPECT && c->state != CHAT_SEND)
+    return 0;
+
+  gottimeout = gotabort = 0;
+
+  if (c->arg < c->argc && (c->arg < 0 || *c->argptr == '\0')) {
+    /* Go get the next string */
+    if (c->arg < 0 || c->state == CHAT_SEND)
+      c->state = CHAT_EXPECT;
+    else
+      c->state = CHAT_SEND;
+
+    special = 1;
+    while (special && (c->nargptr || c->arg < c->argc - 1)) {
+      if (c->arg < 0 || (!TimedOut && c->state == CHAT_SEND))
+        c->nargptr = NULL;
+
+      if (c->nargptr != NULL) {
+        /* We're doing expect-send-expect.... */
+        c->argptr = c->nargptr;
+        /* Put the '-' back in case we ever want to rerun our script */
+        c->nargptr[-1] = '-';
+        c->nargptr = chat_NextChar(c->nargptr, '-');
+        if (c->nargptr != NULL)
+          *c->nargptr++ = '\0';
+      } else {
+        int minus;
+
+        if ((c->argptr = c->argv[++c->arg]) == NULL) {
+          /* End of script - all ok */
+          c->state = CHAT_DONE;
+          return 0;
+        }
+
+        if (c->state == CHAT_EXPECT) {
+          /* Look for expect-send-expect sequence */
+          c->nargptr = c->argptr;
+          minus = 0;
+          while ((c->nargptr = chat_NextChar(c->nargptr, '-'))) {
+            c->nargptr++;
+            minus++;
+          }
+
+          if (minus % 2)
+            log_Printf(LogWARN, "chat_UpdateSet: \"%s\": Uneven number of"
+                      " '-' chars, all ignored\n", c->argptr);
+          else if (minus) {
+            c->nargptr = chat_NextChar(c->argptr, '-');
+            *c->nargptr++ = '\0';
+          }
+        }
+      }
+
+      /*
+       * c->argptr now temporarily points into c->script (via c->argv)
+       * If it's an expect-send-expect sequence, we've just got the correct
+       * portion of that sequence.
+       */
+
+      needcr = c->state == CHAT_SEND &&
+               (*c->argptr != '!' || c->argptr[1] == '!');
+
+      /* We leave room for a potential HDLC header in the target string */
+      ExpandString(c, c->argptr, c->exp + 2, sizeof c->exp - 2, needcr);
+
+      /*
+       * Now read our string.  If it's not a special string, we unset
+       * ``special'' to break out of the loop.
+       */
+      if (gotabort) {
+        if (c->abort.num < MAXABORTS) {
+          int len, i;
+
+          len = strlen(c->exp+2);
+          for (i = 0; i < c->abort.num; i++)
+            if (len > c->abort.string[i].len) {
+              int last;
+
+              for (last = c->abort.num; last > i; last--) {
+                c->abort.string[last].data = c->abort.string[last-1].data;
+                c->abort.string[last].len = c->abort.string[last-1].len;
+              }
+              break;
+            }
+          c->abort.string[i].len = len;
+          if ((c->abort.string[i].data = (char *)malloc(len+1)) != NULL) {
+            memcpy(c->abort.string[i].data, c->exp+2, len+1);
+            c->abort.num++;
+	  }
+        } else
+          log_Printf(LogERROR, "chat_UpdateSet: too many abort strings\n");
+        gotabort = 0;
+      } else if (gottimeout) {
+        c->TimeoutSec = atoi(c->exp + 2);
+        if (c->TimeoutSec <= 0)
+          c->TimeoutSec = 30;
+        gottimeout = 0;
+      } else if (c->nargptr == NULL && !strcmp(c->exp+2, "ABORT"))
+        gotabort = 1;
+      else if (c->nargptr == NULL && !strcmp(c->exp+2, "TIMEOUT"))
+        gottimeout = 1;
+      else {
+        if (c->exp[2] == '!' && c->exp[3] != '!')
+          ExecStr(c->physical, c->exp + 3, c->exp + 3, sizeof c->exp - 3);
+
+        if (c->exp[2] == '\0') {
+          /* Empty string, reparse (this may be better as a `goto start') */
+          c->argptr = &arg_term;
+          return chat_UpdateSet(d, r, w, e, n);
+        }
+
+        special = 0;
+      }
+    }
+
+    if (special) {
+      if (gottimeout)
+        log_Printf(LogWARN, "chat_UpdateSet: TIMEOUT: Argument expected\n");
+      else if (gotabort)
+        log_Printf(LogWARN, "chat_UpdateSet: ABORT: Argument expected\n");
+
+      /* End of script - all ok */
+      c->state = CHAT_DONE;
+      return 0;
+    }
+
+    /* set c->argptr to point in the right place */
+    c->argptr = c->exp + (c->exp[2] == '!' ? 3 : 2);
+    c->arglen = strlen(c->argptr);
+
+    if (c->state == CHAT_EXPECT) {
+      /* We must check to see if the string's already been found ! */
+      char *begin, *end;
+
+      end = c->bufend - c->arglen + 1;
+      if (end < c->bufstart)
+        end = c->bufstart;
+      for (begin = c->bufstart; begin < end; begin++)
+        if (!strncmp(begin, c->argptr, c->arglen)) {
+          c->bufstart = begin + c->arglen;
+          c->argptr += c->arglen;
+          c->arglen = 0;
+          /* Continue - we've already read our expect string */
+          return chat_UpdateSet(d, r, w, e, n);
+        }
+
+      log_Printf(LogCHAT, "Expect(%d): %s\n", c->TimeoutSec, c->argptr);
+      chat_SetTimeout(c);
+    }
+  }
+
+  /*
+   * We now have c->argptr pointing at what we want to expect/send and
+   * c->state saying what we want to do... we now know what to put in
+   * the fd_set :-)
+   */
+
+  if (c->state == CHAT_EXPECT)
+    return physical_doUpdateSet(&c->physical->desc, r, NULL, e, n, 1);
+  else
+    return physical_doUpdateSet(&c->physical->desc, NULL, w, e, n, 1);
+}
+
+static int
+chat_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+  struct chat *c = descriptor2chat(d);
+  return c->argptr && physical_IsSet(&c->physical->desc, fdset);
+}
+
+static void
+chat_UpdateLog(struct chat *c, int in)
+{
+  if (log_IsKept(LogCHAT) || log_IsKept(LogCONNECT)) {
+    /*
+     * If a linefeed appears in the last `in' characters of `c's input
+     * buffer, output from there, all the way back to the last linefeed.
+     * This is called for every read of `in' bytes.
+     */
+    char *ptr, *end, *stop, ch;
+    int level;
+
+    level = log_IsKept(LogCHAT) ? LogCHAT : LogCONNECT;
+    if (in == -1)
+      end = ptr = c->bufend;
+    else {
+      ptr = c->bufend - in;
+      for (end = c->bufend - 1; end >= ptr; end--)
+        if (*end == '\n')
+          break;
+    }
+
+    if (end >= ptr) {
+      for (ptr = c->bufend - (in == -1 ? 1 : in + 1); ptr >= c->bufstart; ptr--)
+        if (*ptr == '\n')
+          break;
+      ptr++;
+      stop = NULL;
+      while (stop < end) {
+        if ((stop = memchr(ptr, '\n', end - ptr)) == NULL)
+          stop = end;
+        ch = *stop;
+        *stop = '\0';
+        if (level == LogCHAT || strstr(ptr, "CONNECT"))
+          log_Printf(level, "Received: %s\n", ptr);
+        *stop = ch;
+        ptr = stop + 1;
+      }
+    }
+  }
+}
+
+static void
+chat_Read(struct fdescriptor *d, struct bundle *bundle __unused,
+	  const fd_set *fdset __unused)
+{
+  struct chat *c = descriptor2chat(d);
+
+  if (c->state == CHAT_EXPECT) {
+    ssize_t in;
+    char *abegin, *ebegin, *begin, *aend, *eend, *end;
+    int n;
+
+    /*
+     * XXX - should this read only 1 byte to guarantee that we don't
+     * swallow any ppp talk from the peer ?
+     */
+    in = BUFLEFT(c);
+    if (in > (ssize_t)sizeof c->buf / 2)
+      in = sizeof c->buf / 2;
+
+    in = physical_Read(c->physical, c->bufend, in);
+    if (in <= 0)
+      return;
+
+    /* `begin' and `end' delimit where we're going to strncmp() from */
+    ebegin = c->bufend - c->arglen + 1;
+    eend = ebegin + in;
+    if (ebegin < c->bufstart)
+      ebegin = c->bufstart;
+
+    if (c->abort.num) {
+      abegin = c->bufend - c->abort.string[0].len + 1;
+      aend = c->bufend - c->abort.string[c->abort.num-1].len + in + 1;
+      if (abegin < c->bufstart)
+        abegin = c->bufstart;
+    } else {
+      abegin = ebegin;
+      aend = eend;
+    }
+    begin = abegin < ebegin ? abegin : ebegin;
+    end = aend < eend ? eend : aend;
+
+    c->bufend += in;
+
+    chat_UpdateLog(c, in);
+
+    if (c->bufend > c->buf + sizeof c->buf / 2) {
+      /* Shuffle our receive buffer back a bit */
+      int chop;
+
+      for (chop = begin - c->buf; chop; chop--)
+        if (c->buf[chop] == '\n')
+          /* found some already-logged garbage to remove :-) */
+          break;
+
+      if (!chop)
+        chop = begin - c->buf;
+
+      if (chop) {
+        char *from, *to;
+
+        to = c->buf;
+        from = to + chop;
+        while (from < c->bufend)
+          *to++ = *from++;
+        c->bufstart -= chop;
+        c->bufend -= chop;
+        begin -= chop;
+        end -= chop;
+        abegin -= chop;
+        aend -= chop;
+        ebegin -= chop;
+        eend -= chop;
+      }
+    }
+
+    for (; begin < end; begin++)
+      if (begin >= ebegin && begin < eend &&
+          !strncmp(begin, c->argptr, c->arglen)) {
+        /* Got it ! */
+        timer_Stop(&c->timeout);
+        if (memchr(begin + c->arglen - 1, '\n',
+            c->bufend - begin - c->arglen + 1) == NULL) {
+          /* force it into the log */
+          end = c->bufend;
+          c->bufend = begin + c->arglen;
+          chat_UpdateLog(c, -1);
+          c->bufend = end;
+        }
+        c->bufstart = begin + c->arglen;
+        c->argptr += c->arglen;
+        c->arglen = 0;
+        break;
+      } else if (begin >= abegin && begin < aend) {
+        for (n = c->abort.num - 1; n >= 0; n--) {
+          if (begin + c->abort.string[n].len > c->bufend)
+            break;
+          if (!strncmp(begin, c->abort.string[n].data,
+                       c->abort.string[n].len)) {
+            if (memchr(begin + c->abort.string[n].len - 1, '\n',
+                c->bufend - begin - c->abort.string[n].len + 1) == NULL) {
+              /* force it into the log */
+              end = c->bufend;
+              c->bufend = begin + c->abort.string[n].len;
+              chat_UpdateLog(c, -1);
+              c->bufend = end;
+            }
+            c->bufstart = begin + c->abort.string[n].len;
+            c->state = CHAT_FAILED;
+            return;
+          }
+        }
+      }
+  }
+}
+
+static int
+chat_Write(struct fdescriptor *d, struct bundle *bundle __unused,
+	   const fd_set *fdset __unused)
+{
+  struct chat *c = descriptor2chat(d);
+  int result = 0;
+
+  if (c->state == CHAT_SEND) {
+    int wrote;
+
+    if (strstr(c->argv[c->arg], "\\P"))            /* Don't log the password */
+      log_Printf(LogCHAT, "Send: %s\n", c->argv[c->arg]);
+    else {
+      int sz;
+
+      sz = c->arglen - 1;
+      while (sz >= 0 && c->argptr[sz] == '\n')
+        sz--;
+      log_Printf(LogCHAT, "Send: %.*s\n", sz + 1, c->argptr);
+    }
+
+    if (physical_IsSync(c->physical)) {
+      /*
+       * XXX: Fix me
+       * This data should be stuffed down through the link layers
+       */
+      /* There's always room for the HDLC header */
+      c->argptr -= 2;
+      c->arglen += 2;
+      memcpy(c->argptr, "\377\003", 2);	/* Prepend HDLC header */
+    }
+
+    wrote = physical_Write(c->physical, c->argptr, c->arglen);
+    result = wrote > 0 ? 1 : 0;
+    if (wrote == -1) {
+      if (errno != EINTR) {
+        log_Printf(LogWARN, "chat_Write: %s\n", strerror(errno));
+	result = -1;
+      }
+      if (physical_IsSync(c->physical)) {
+        c->argptr += 2;
+        c->arglen -= 2;
+      }
+    } else if (wrote < 2 && physical_IsSync(c->physical)) {
+      /* Oops - didn't even write our HDLC header ! */
+      c->argptr += 2;
+      c->arglen -= 2;
+    } else {
+      c->argptr += wrote;
+      c->arglen -= wrote;
+    }
+  }
+
+  return result;
+}
+
+void
+chat_Init(struct chat *c, struct physical *p)
+{
+  c->desc.type = CHAT_DESCRIPTOR;
+  c->desc.UpdateSet = chat_UpdateSet;
+  c->desc.IsSet = chat_IsSet;
+  c->desc.Read = chat_Read;
+  c->desc.Write = chat_Write;
+  c->physical = p;
+  *c->script = '\0';
+  c->argc = 0;
+  c->arg = -1;
+  c->argptr = NULL;
+  c->nargptr = NULL;
+  c->bufstart = c->bufend = c->buf;
+
+  memset(&c->pause, '\0', sizeof c->pause);
+  memset(&c->timeout, '\0', sizeof c->timeout);
+}
+
+int
+chat_Setup(struct chat *c, const char *data, const char *phone)
+{
+  c->state = CHAT_EXPECT;
+
+  if (data == NULL) {
+    *c->script = '\0';
+    c->argc = 0;
+  } else {
+    strncpy(c->script, data, sizeof c->script - 1);
+    c->script[sizeof c->script - 1] = '\0';
+    c->argc = MakeArgs(c->script, c->argv, VECSIZE(c->argv), PARSE_NOHASH);
+  }
+
+  c->arg = -1;
+  c->argptr = NULL;
+  c->nargptr = NULL;
+
+  c->TimeoutSec = 30;
+  c->TimedOut = 0;
+  c->phone = phone;
+  c->abort.num = 0;
+
+  timer_Stop(&c->pause);
+  timer_Stop(&c->timeout);
+
+  return c->argc >= 0;
+}
+
+void
+chat_Finish(struct chat *c)
+{
+  timer_Stop(&c->pause);
+  timer_Stop(&c->timeout);
+  while (c->abort.num)
+    free(c->abort.string[--c->abort.num].data);
+  c->abort.num = 0;
+}
+
+void
+chat_Destroy(struct chat *c)
+{
+  chat_Finish(c);
+}
+
+/*
+ *  \c	don't add a cr
+ *  \d  Sleep a little (delay 2 seconds
+ *  \n  Line feed character
+ *  \P  Auth Key password
+ *  \p  pause 0.25 sec
+ *  \r	Carrige return character
+ *  \s  Space character
+ *  \T  Telephone number(s) (defined via `set phone')
+ *  \t  Tab character
+ *  \U  Auth User
+ */
+static char *
+ExpandString(struct chat *c, const char *str, char *result, int reslen, int cr)
+{
+  int len;
+
+  result[--reslen] = '\0';
+  while (*str && reslen > 0) {
+    switch (*str) {
+    case '\\':
+      str++;
+      switch (*str) {
+      case 'c':
+	cr = 0;
+	break;
+      case 'd':		/* Delay 2 seconds */
+        chat_Pause(c, 2 * SECTICKS);
+	break;
+      case 'p':
+        chat_Pause(c, SECTICKS / 4);
+	break;		/* Delay 0.25 seconds */
+      case 'n':
+	*result++ = '\n';
+	reslen--;
+	break;
+      case 'r':
+	*result++ = '\r';
+	reslen--;
+	break;
+      case 's':
+	*result++ = ' ';
+	reslen--;
+	break;
+      case 't':
+	*result++ = '\t';
+	reslen--;
+	break;
+      case 'P':
+	strncpy(result, c->physical->dl->bundle->cfg.auth.key, reslen);
+        len = strlen(result);
+	reslen -= len;
+	result += len;
+	break;
+      case 'T':
+        if (c->phone) {
+          strncpy(result, c->phone, reslen);
+          len = strlen(result);
+          reslen -= len;
+          result += len;
+        }
+	break;
+      case 'U':
+	strncpy(result, c->physical->dl->bundle->cfg.auth.name, reslen);
+        len = strlen(result);
+	reslen -= len;
+	result += len;
+	break;
+      default:
+	reslen--;
+	*result++ = *str;
+	break;
+      }
+      if (*str)
+	str++;
+      break;
+    case '^':
+      str++;
+      if (*str) {
+	*result++ = *str++ & 0x1f;
+	reslen--;
+      }
+      break;
+    default:
+      *result++ = *str++;
+      reslen--;
+      break;
+    }
+  }
+  if (--reslen > 0) {
+    if (cr)
+      *result++ = '\r';
+  }
+  if (--reslen > 0)
+    *result++ = '\0';
+  return (result);
+}
+
+static void
+ExecStr(struct physical *physical, char *command, char *out, int olen)
+{
+  pid_t pid;
+  int fids[2];
+  char *argv[MAXARGS], *vector[MAXARGS], *startout, *endout;
+  int stat, nb, argc, i;
+
+  log_Printf(LogCHAT, "Exec: %s\n", command);
+  if ((argc = MakeArgs(command, vector, VECSIZE(vector),
+                       PARSE_REDUCE|PARSE_NOHASH)) <= 0) {
+    if (argc < 0)
+      log_Printf(LogWARN, "Syntax error in exec command\n");
+    *out = '\0';
+    return;
+  }
+
+  if (pipe(fids) < 0) {
+    log_Printf(LogCHAT, "Unable to create pipe in ExecStr: %s\n",
+	      strerror(errno));
+    *out = '\0';
+    return;
+  }
+  if ((pid = fork()) == 0) {
+    command_Expand(argv, argc, (char const *const *)vector,
+                   physical->dl->bundle, 0, getpid());
+    close(fids[0]);
+    timer_TermService();
+    if (fids[1] == STDIN_FILENO)
+      fids[1] = dup(fids[1]);
+    dup2(physical->fd, STDIN_FILENO);
+    dup2(fids[1], STDERR_FILENO);
+    dup2(STDIN_FILENO, STDOUT_FILENO);
+    close(3);
+    if (open(_PATH_TTY, O_RDWR) != 3)
+      open(_PATH_DEVNULL, O_RDWR);	/* Leave it closed if it fails... */
+    for (i = getdtablesize(); i > 3; i--)
+      fcntl(i, F_SETFD, 1);
+#ifndef NOSUID
+    setuid(ID0realuid());
+#endif
+    execvp(argv[0], argv);
+    fprintf(stderr, "execvp: %s: %s\n", argv[0], strerror(errno));
+    _exit(127);
+  } else {
+    char *name = strdup(vector[0]);
+
+    close(fids[1]);
+    endout = out + olen - 1;
+    startout = out;
+    while (out < endout) {
+      nb = read(fids[0], out, 1);
+      if (nb <= 0)
+	break;
+      out++;
+    }
+    *out = '\0';
+    close(fids[0]);
+    close(fids[1]);
+    waitpid(pid, &stat, WNOHANG);
+    if (WIFSIGNALED(stat)) {
+      log_Printf(LogWARN, "%s: signal %d\n", name, WTERMSIG(stat));
+      free(name);
+      *out = '\0';
+      return;
+    } else if (WIFEXITED(stat)) {
+      switch (WEXITSTATUS(stat)) {
+        case 0:
+          free(name);
+          break;
+        case 127:
+          log_Printf(LogWARN, "%s: %s\n", name, startout);
+          free(name);
+          *out = '\0';
+          return;
+          break;
+        default:
+          log_Printf(LogWARN, "%s: exit %d\n", name, WEXITSTATUS(stat));
+          free(name);
+          *out = '\0';
+          return;
+          break;
+      }
+    } else {
+      log_Printf(LogWARN, "%s: Unexpected exit result\n", name);
+      free(name);
+      *out = '\0';
+      return;
+    }
+  }
+}
diff --git a/src/chat.h b/src/chat.h
new file mode 100644
index 0000000..1afbabe
--- /dev/null
+++ b/src/chat.h
@@ -0,0 +1,82 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/chat.h,v 1.16.42.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define CHAT_EXPECT 0
+#define CHAT_SEND   1
+#define CHAT_DONE   2
+#define CHAT_FAILED 3
+
+#define MAXABORTS   50
+
+struct physical;
+
+struct chat {
+  struct fdescriptor desc;
+  struct physical *physical;
+
+  int state;				/* Our CHAT_* status */
+
+  char script[LINE_LEN];		/* Our arg buffer */
+  char *argv[MAXARGS];			/* Our arguments (pointing to script) */
+  int argc;				/* Number of argv's */
+
+  int arg;				/* Our current arg number */
+  char exp[LINE_LEN];			/* Our translated current argument */
+  char *argptr;				/* Our current arg pointer */
+  int arglen;				/* The length of argptr */
+  char *nargptr;			/* Our next for expect-send-expect */
+
+  char buf[LINE_LEN*2];			/* Our input */
+  char *bufstart;			/* start of relevent data */
+  char *bufend;				/* end of relevent data */
+
+  int TimeoutSec;			/* Expect timeout value */
+  int TimedOut;				/* We timed out */
+
+  const char *phone;			/* Our phone number */
+
+  struct {
+    struct {
+      char *data;			/* Abort the dial if we get one */
+      int len;
+    } string[MAXABORTS];
+    int num;				/* How many AbortStrings */
+  } abort;
+
+  struct pppTimer pause;		/* Inactivity timer */
+  struct pppTimer timeout;		/* TimeoutSec timer */
+};
+
+#define descriptor2chat(d) \
+  ((d)->type == CHAT_DESCRIPTOR ? (struct chat *)(d) : NULL)
+#define	VECSIZE(v)	(sizeof(v) / sizeof(v[0]))
+
+extern void chat_Init(struct chat *, struct physical *);
+extern int chat_Setup(struct chat *, const char *, const char *);
+extern void chat_Finish(struct chat *);
+extern void chat_Destroy(struct chat *);
diff --git a/src/command.c b/src/command.c
new file mode 100644
index 0000000..0d280da
--- /dev/null
+++ b/src/command.c
@@ -0,0 +1,3310 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/command.c,v 1.307.12.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <net/route.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+#ifndef NONAT
+#ifdef LOCALNAT
+#include "alias.h"
+#else
+#include <alias.h>
+#endif
+#endif
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#ifndef NONAT
+#include "nat_cmd.h"
+#endif
+#include "systems.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "main.h"
+#include "route.h"
+#include "ccp.h"
+#include "auth.h"
+#include "async.h"
+#include "link.h"
+#include "physical.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "server.h"
+#include "prompt.h"
+#include "chat.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "iface.h"
+#include "id.h"
+#include "probe.h"
+
+/* ``set'' values */
+#define	VAR_AUTHKEY	0
+#define	VAR_DIAL	1
+#define	VAR_LOGIN	2
+#define	VAR_AUTHNAME	3
+#define	VAR_AUTOLOAD	4
+#define	VAR_WINSIZE	5
+#define	VAR_DEVICE	6
+#define	VAR_ACCMAP	7
+#define	VAR_MRRU	8
+#define	VAR_MRU		9
+#define	VAR_MTU		10
+#define	VAR_OPENMODE	11
+#define	VAR_PHONE	12
+#define	VAR_HANGUP	13
+#define	VAR_IDLETIMEOUT	14
+#define	VAR_LQRPERIOD	15
+#define	VAR_LCPRETRY	16
+#define	VAR_CHAPRETRY	17
+#define	VAR_PAPRETRY	18
+#define	VAR_CCPRETRY	19
+#define	VAR_IPCPRETRY	20
+#define	VAR_DNS		21
+#define	VAR_NBNS	22
+#define	VAR_MODE	23
+#define	VAR_CALLBACK	24
+#define	VAR_CBCP	25
+#define	VAR_CHOKED	26
+#define	VAR_SENDPIPE	27
+#define	VAR_RECVPIPE	28
+#define	VAR_RADIUS	29
+#define	VAR_CD		30
+#define	VAR_PARITY	31
+#define VAR_CRTSCTS	32
+#define VAR_URGENTPORTS	33
+#define	VAR_LOGOUT	34
+#define	VAR_IFQUEUE	35
+#define	VAR_MPPE	36
+#define	VAR_IPV6CPRETRY	37
+#define	VAR_RAD_ALIVE	38
+#define	VAR_PPPOE	39
+#define	VAR_PORT_ID	40
+
+/* ``accept|deny|disable|enable'' masks */
+#define NEG_HISMASK (1)
+#define NEG_MYMASK (2)
+
+/* ``accept|deny|disable|enable'' values */
+#define NEG_ACFCOMP	40
+#define NEG_CHAP05	41
+#define NEG_CHAP80	42
+#define NEG_CHAP80LM	43
+#define NEG_DEFLATE	44
+#define NEG_DNS		45
+#define NEG_ECHO	46
+#define NEG_ENDDISC	47
+#define NEG_LQR		48
+#define NEG_PAP		49
+#define NEG_PPPDDEFLATE	50
+#define NEG_PRED1	51
+#define NEG_PROTOCOMP	52
+#define NEG_SHORTSEQ	53
+#define NEG_VJCOMP	54
+#define NEG_MPPE	55
+#define NEG_CHAP81	56
+
+const char Version[] = "3.4.2";
+
+static int ShowCommand(struct cmdargs const *);
+static int TerminalCommand(struct cmdargs const *);
+static int QuitCommand(struct cmdargs const *);
+static int OpenCommand(struct cmdargs const *);
+static int CloseCommand(struct cmdargs const *);
+static int DownCommand(struct cmdargs const *);
+static int SetCommand(struct cmdargs const *);
+static int LinkCommand(struct cmdargs const *);
+static int AddCommand(struct cmdargs const *);
+static int DeleteCommand(struct cmdargs const *);
+static int NegotiateCommand(struct cmdargs const *);
+static int ClearCommand(struct cmdargs const *);
+static int RunListCommand(struct cmdargs const *);
+static int IfaceAddCommand(struct cmdargs const *);
+static int IfaceDeleteCommand(struct cmdargs const *);
+static int IfaceClearCommand(struct cmdargs const *);
+static int SetProcTitle(struct cmdargs const *);
+#ifndef NONAT
+static int NatEnable(struct cmdargs const *);
+static int NatOption(struct cmdargs const *);
+#endif
+
+static const char *
+showcx(struct cmdtab const *cmd)
+{
+  if (cmd->lauth & LOCAL_CX)
+    return "(c)";
+  else if (cmd->lauth & LOCAL_CX_OPT)
+    return "(o)";
+
+  return "";
+}
+
+static int
+HelpCommand(struct cmdargs const *arg)
+{
+  struct cmdtab const *cmd;
+  int n, cmax, dmax, cols, cxlen;
+  const char *cx;
+
+  if (!arg->prompt) {
+    log_Printf(LogWARN, "help: Cannot help without a prompt\n");
+    return 0;
+  }
+
+  if (arg->argc > arg->argn) {
+    for (cmd = arg->cmdtab; cmd->name || cmd->alias; cmd++)
+      if ((cmd->lauth & arg->prompt->auth) &&
+          ((cmd->name && !strcasecmp(cmd->name, arg->argv[arg->argn])) ||
+           (cmd->alias && !strcasecmp(cmd->alias, arg->argv[arg->argn])))) {
+	prompt_Printf(arg->prompt, "%s %s\n", cmd->syntax, showcx(cmd));
+	return 0;
+      }
+    return -1;
+  }
+
+  cmax = dmax = 0;
+  for (cmd = arg->cmdtab; cmd->func; cmd++)
+    if (cmd->name && (cmd->lauth & arg->prompt->auth)) {
+      if ((n = strlen(cmd->name) + strlen(showcx(cmd))) > cmax)
+        cmax = n;
+      if ((n = strlen(cmd->helpmes)) > dmax)
+        dmax = n;
+    }
+
+  cols = 80 / (dmax + cmax + 3);
+  n = 0;
+  prompt_Printf(arg->prompt, "(o) = Optional context,"
+                " (c) = Context required\n");
+  for (cmd = arg->cmdtab; cmd->func; cmd++)
+    if (cmd->name && (cmd->lauth & arg->prompt->auth)) {
+      cx = showcx(cmd);
+      cxlen = cmax - strlen(cmd->name);
+      if (n % cols != 0)
+        prompt_Printf(arg->prompt, " ");
+      prompt_Printf(arg->prompt, "%s%-*.*s: %-*.*s",
+              cmd->name, cxlen, cxlen, cx, dmax, dmax, cmd->helpmes);
+      if (++n % cols == 0)
+        prompt_Printf(arg->prompt, "\n");
+    }
+  if (n % cols != 0)
+    prompt_Printf(arg->prompt, "\n");
+
+  return 0;
+}
+
+static int
+IdentCommand(struct cmdargs const *arg)
+{
+  Concatinate(arg->cx->physical->link.lcp.cfg.ident,
+              sizeof arg->cx->physical->link.lcp.cfg.ident,
+              arg->argc - arg->argn, arg->argv + arg->argn);
+  return 0;
+}
+
+static int
+SendIdentification(struct cmdargs const *arg)
+{
+  if (arg->cx->state < DATALINK_LCP) {
+    log_Printf(LogWARN, "sendident: link has not reached LCP\n");
+    return 2;
+  }
+  return lcp_SendIdentification(&arg->cx->physical->link.lcp) ? 0 : 1;
+}
+
+static int
+CloneCommand(struct cmdargs const *arg)
+{
+  char namelist[LINE_LEN];
+  char *name;
+  int f;
+
+  if (arg->argc == arg->argn)
+    return -1;
+
+  namelist[sizeof namelist - 1] = '\0';
+  for (f = arg->argn; f < arg->argc; f++) {
+    strncpy(namelist, arg->argv[f], sizeof namelist - 1);
+    for(name = strtok(namelist, ", "); name; name = strtok(NULL,", "))
+      bundle_DatalinkClone(arg->bundle, arg->cx, name);
+  }
+
+  return 0;
+}
+
+static int
+RemoveCommand(struct cmdargs const *arg)
+{
+  if (arg->argc != arg->argn)
+    return -1;
+
+  if (arg->cx->state != DATALINK_CLOSED) {
+    log_Printf(LogWARN, "remove: Cannot delete links that aren't closed\n");
+    return 2;
+  }
+
+  bundle_DatalinkRemove(arg->bundle, arg->cx);
+  return 0;
+}
+
+static int
+RenameCommand(struct cmdargs const *arg)
+{
+  if (arg->argc != arg->argn + 1)
+    return -1;
+
+  if (bundle_RenameDatalink(arg->bundle, arg->cx, arg->argv[arg->argn]))
+    return 0;
+
+  log_Printf(LogWARN, "%s -> %s: target name already exists\n",
+             arg->cx->name, arg->argv[arg->argn]);
+  return 1;
+}
+
+static int
+LoadCommand(struct cmdargs const *arg)
+{
+  const char *err;
+  int n, mode;
+
+  mode = arg->bundle->phys_type.all;
+
+  if (arg->argn < arg->argc) {
+    for (n = arg->argn; n < arg->argc; n++)
+      if ((err = system_IsValid(arg->argv[n], arg->prompt, mode)) != NULL) {
+        log_Printf(LogWARN, "%s: %s\n", arg->argv[n], err);
+        return 1;
+      }
+
+    for (n = arg->argn; n < arg->argc; n++) {
+      bundle_SetLabel(arg->bundle, arg->argv[arg->argc - 1]);
+      system_Select(arg->bundle, arg->argv[n], CONFFILE, arg->prompt, arg->cx);
+    }
+    bundle_SetLabel(arg->bundle, arg->argv[arg->argc - 1]);
+  } else if ((err = system_IsValid("default", arg->prompt, mode)) != NULL) {
+    log_Printf(LogWARN, "default: %s\n", err);
+    return 1;
+  } else {
+    bundle_SetLabel(arg->bundle, "default");
+    system_Select(arg->bundle, "default", CONFFILE, arg->prompt, arg->cx);
+    bundle_SetLabel(arg->bundle, "default");
+  }
+
+  return 0;
+}
+
+static int
+LogCommand(struct cmdargs const *arg)
+{
+  char buf[LINE_LEN];
+
+  if (arg->argn < arg->argc) {
+    char *argv[MAXARGS];
+    int argc = arg->argc - arg->argn;
+
+    if (argc >= (int)(sizeof argv / sizeof argv[0])) {
+      argc = sizeof argv / sizeof argv[0] - 1;
+      log_Printf(LogWARN, "Truncating log command to %d args\n", argc);
+    }
+    command_Expand(argv, argc, arg->argv + arg->argn, arg->bundle, 1, getpid());
+    Concatinate(buf, sizeof buf, argc, (const char *const *)argv);
+    log_Printf(LogLOG, "%s\n", buf);
+    command_Free(argc, argv);
+    return 0;
+  }
+
+  return -1;
+}
+
+static int
+SaveCommand(struct cmdargs const *arg __unused)
+{
+  log_Printf(LogWARN, "save command is not yet implemented.\n");
+  return 1;
+}
+
+static int
+DialCommand(struct cmdargs const *arg)
+{
+  int res;
+
+  if ((arg->cx && !(arg->cx->physical->type & (PHYS_INTERACTIVE|PHYS_AUTO)))
+      || (!arg->cx &&
+          (arg->bundle->phys_type.all & ~(PHYS_INTERACTIVE|PHYS_AUTO)))) {
+    log_Printf(LogWARN, "Manual dial is only available for auto and"
+              " interactive links\n");
+    return 1;
+  }
+
+  if (arg->argc > arg->argn && (res = LoadCommand(arg)) != 0)
+    return res;
+
+  bundle_Open(arg->bundle, arg->cx ? arg->cx->name : NULL, PHYS_ALL, 1);
+
+  return 0;
+}
+
+#define isinword(ch) (isalnum(ch) || (ch) == '_')
+
+static char *
+strstrword(char *big, const char *little)
+{
+  /* Get the first occurance of the word ``little'' in ``big'' */
+  char *pos;
+  int len;
+
+  pos = big;
+  len = strlen(little);
+
+  while ((pos = strstr(pos, little)) != NULL)
+    if ((pos != big && isinword(pos[-1])) || isinword(pos[len]))
+      pos++;
+    else if (pos != big && pos[-1] == '\\')
+      memmove(pos - 1, pos, strlen(pos) + 1);
+    else
+      break;
+
+  return pos;
+}
+
+static char *
+subst(char *tgt, const char *oldstr, const char *newstr)
+{
+  /* tgt is a malloc()d area... realloc() as necessary */
+  char *word, *ntgt;
+  int ltgt, loldstr, lnewstr, pos;
+
+  if ((word = strstrword(tgt, oldstr)) == NULL)
+    return tgt;
+
+  ltgt = strlen(tgt) + 1;
+  loldstr = strlen(oldstr);
+  lnewstr = strlen(newstr);
+  do {
+    pos = word - tgt;
+    if (loldstr > lnewstr)
+      bcopy(word + loldstr, word + lnewstr, ltgt - pos - loldstr);
+    if (loldstr != lnewstr) {
+      ntgt = realloc(tgt, ltgt += lnewstr - loldstr);
+      if (ntgt == NULL)
+        break;			/* Oh wonderful ! */
+      word = ntgt + pos;
+      tgt = ntgt;
+    }
+    if (lnewstr > loldstr)
+      bcopy(word + loldstr, word + lnewstr, ltgt - pos - lnewstr);
+    bcopy(newstr, word, lnewstr);
+  } while ((word = strstrword(word, oldstr)));
+
+  return tgt;
+}
+
+static char *
+substip(char *tgt, const char *oldstr, struct in_addr ip)
+{
+  return subst(tgt, oldstr, inet_ntoa(ip));
+}
+
+static char *
+substlong(char *tgt, const char *oldstr, long l)
+{
+  char buf[23];
+
+  snprintf(buf, sizeof buf, "%ld", l);
+
+  return subst(tgt, oldstr, buf);
+}
+
+static char *
+substull(char *tgt, const char *oldstr, unsigned long long ull)
+{
+  char buf[21];
+
+  snprintf(buf, sizeof buf, "%llu", ull);
+
+  return subst(tgt, oldstr, buf);
+}
+
+
+#ifndef NOINET6
+static char *
+substipv6(char *tgt, const char *oldstr, const struct ncpaddr *ip)
+{
+    return subst(tgt, oldstr, ncpaddr_ntoa(ip));
+}
+
+#ifndef NORADIUS
+static char *
+substipv6prefix(char *tgt, const char *oldstr, const uint8_t *ipv6prefix)
+{
+  uint8_t ipv6addr[INET6_ADDRSTRLEN];
+  uint8_t prefix[INET6_ADDRSTRLEN + sizeof("/128") - 1];
+
+  if (ipv6prefix) {
+    inet_ntop(AF_INET6, &ipv6prefix[2], ipv6addr, sizeof(ipv6addr));
+    snprintf(prefix, sizeof(prefix), "%s/%d", ipv6addr, ipv6prefix[1]);
+  } else
+    prefix[0] = '\0';
+  return subst(tgt, oldstr, prefix);
+}
+#endif
+#endif
+
+void
+command_Expand(char **nargv, int argc, char const *const *oargv,
+               struct bundle *bundle, int inc0, pid_t pid)
+{
+  int arg, secs;
+  char uptime[20];
+  unsigned long long oin, oout, pin, pout;
+
+  if (inc0)
+    arg = 0;		/* Start at arg 0 */
+  else {
+    nargv[0] = strdup(oargv[0]);
+    arg = 1;
+  }
+
+  secs = bundle_Uptime(bundle);
+  snprintf(uptime, sizeof uptime, "%d:%02d:%02d",
+           secs / 3600, (secs / 60) % 60, secs % 60);
+  oin = bundle->ncp.ipcp.throughput.OctetsIn;
+  oout = bundle->ncp.ipcp.throughput.OctetsOut;
+  pin = bundle->ncp.ipcp.throughput.PacketsIn;
+  pout = bundle->ncp.ipcp.throughput.PacketsOut;
+#ifndef NOINET6
+  oin += bundle->ncp.ipv6cp.throughput.OctetsIn;
+  oout += bundle->ncp.ipv6cp.throughput.OctetsOut;
+  pin += bundle->ncp.ipv6cp.throughput.PacketsIn;
+  pout += bundle->ncp.ipv6cp.throughput.PacketsOut;
+#endif
+
+  for (; arg < argc; arg++) {
+    nargv[arg] = strdup(oargv[arg]);
+    nargv[arg] = subst(nargv[arg], "AUTHNAME", bundle->cfg.auth.name);
+    nargv[arg] = substip(nargv[arg], "DNS0", bundle->ncp.ipcp.ns.dns[0]);
+    nargv[arg] = substip(nargv[arg], "DNS1", bundle->ncp.ipcp.ns.dns[1]);
+    nargv[arg] = subst(nargv[arg], "ENDDISC",
+                       mp_Enddisc(bundle->ncp.mp.cfg.enddisc.class,
+                                  bundle->ncp.mp.cfg.enddisc.address,
+                                  bundle->ncp.mp.cfg.enddisc.len));
+    nargv[arg] = substip(nargv[arg], "HISADDR", bundle->ncp.ipcp.peer_ip);
+#ifndef NOINET6
+    nargv[arg] = substipv6(nargv[arg], "HISADDR6", &bundle->ncp.ipv6cp.hisaddr);
+#endif
+    nargv[arg] = subst(nargv[arg], "INTERFACE", bundle->iface->name);
+    nargv[arg] = substull(nargv[arg], "IPOCTETSIN",
+                          bundle->ncp.ipcp.throughput.OctetsIn);
+    nargv[arg] = substull(nargv[arg], "IPOCTETSOUT",
+                          bundle->ncp.ipcp.throughput.OctetsOut);
+    nargv[arg] = substull(nargv[arg], "IPPACKETSIN",
+                          bundle->ncp.ipcp.throughput.PacketsIn);
+    nargv[arg] = substull(nargv[arg], "IPPACKETSOUT",
+                          bundle->ncp.ipcp.throughput.PacketsOut);
+#ifndef NOINET6
+    nargv[arg] = substull(nargv[arg], "IPV6OCTETSIN",
+                          bundle->ncp.ipv6cp.throughput.OctetsIn);
+    nargv[arg] = substull(nargv[arg], "IPV6OCTETSOUT",
+                          bundle->ncp.ipv6cp.throughput.OctetsOut);
+    nargv[arg] = substull(nargv[arg], "IPV6PACKETSIN",
+                          bundle->ncp.ipv6cp.throughput.PacketsIn);
+    nargv[arg] = substull(nargv[arg], "IPV6PACKETSOUT",
+                          bundle->ncp.ipv6cp.throughput.PacketsOut);
+#endif
+    nargv[arg] = subst(nargv[arg], "LABEL", bundle_GetLabel(bundle));
+    nargv[arg] = substip(nargv[arg], "MYADDR", bundle->ncp.ipcp.my_ip);
+#ifndef NOINET6
+    nargv[arg] = substipv6(nargv[arg], "MYADDR6", &bundle->ncp.ipv6cp.myaddr);
+#ifndef NORADIUS
+    nargv[arg] = substipv6prefix(nargv[arg], "IPV6PREFIX",
+				 bundle->radius.ipv6prefix);
+#endif
+#endif
+    nargv[arg] = substull(nargv[arg], "OCTETSIN", oin);
+    nargv[arg] = substull(nargv[arg], "OCTETSOUT", oout);
+    nargv[arg] = substull(nargv[arg], "PACKETSIN", pin);
+    nargv[arg] = substull(nargv[arg], "PACKETSOUT", pout);
+    nargv[arg] = subst(nargv[arg], "PEER_ENDDISC",
+                       mp_Enddisc(bundle->ncp.mp.peer.enddisc.class,
+                                  bundle->ncp.mp.peer.enddisc.address,
+                                  bundle->ncp.mp.peer.enddisc.len));
+    nargv[arg] = substlong(nargv[arg], "PROCESSID", pid);
+    if (server.cfg.port)
+      nargv[arg] = substlong(nargv[arg], "SOCKNAME", server.cfg.port);
+    else
+      nargv[arg] = subst(nargv[arg], "SOCKNAME", server.cfg.sockname);
+    nargv[arg] = subst(nargv[arg], "UPTIME", uptime);
+    nargv[arg] = subst(nargv[arg], "USER", bundle->ncp.mp.peer.authname);
+    nargv[arg] = subst(nargv[arg], "VERSION", Version);
+  }
+  nargv[arg] = NULL;
+}
+
+void
+command_Free(int argc, char **argv)
+{
+  while (argc) {
+    free(*argv);
+    argc--;
+    argv++;
+  }
+}
+
+static int
+ShellCommand(struct cmdargs const *arg, int bg)
+{
+  const char *shell;
+  pid_t shpid, pid;
+
+#ifdef SHELL_ONLY_INTERACTIVELY
+  /* we're only allowed to shell when we run ppp interactively */
+  if (arg->prompt && arg->prompt->owner) {
+    log_Printf(LogWARN, "Can't start a shell from a network connection\n");
+    return 1;
+  }
+#endif
+
+  if (arg->argc == arg->argn) {
+    if (!arg->prompt) {
+      log_Printf(LogWARN, "Can't start an interactive shell from"
+                " a config file\n");
+      return 1;
+    } else if (arg->prompt->owner) {
+      log_Printf(LogWARN, "Can't start an interactive shell from"
+                " a socket connection\n");
+      return 1;
+    } else if (bg) {
+      log_Printf(LogWARN, "Can only start an interactive shell in"
+		" the foreground mode\n");
+      return 1;
+    }
+  }
+
+  pid = getpid();
+  if ((shpid = fork()) == 0) {
+    int i, fd;
+
+    if ((shell = getenv("SHELL")) == 0)
+      shell = _PATH_BSHELL;
+
+    timer_TermService();
+
+    if (arg->prompt)
+      fd = arg->prompt->fd_out;
+    else if ((fd = open(_PATH_DEVNULL, O_RDWR)) == -1) {
+      log_Printf(LogALERT, "Failed to open %s: %s\n",
+                _PATH_DEVNULL, strerror(errno));
+      exit(1);
+    }
+    dup2(fd, STDIN_FILENO);
+    dup2(fd, STDOUT_FILENO);
+    dup2(fd, STDERR_FILENO);
+    for (i = getdtablesize(); i > STDERR_FILENO; i--)
+      fcntl(i, F_SETFD, 1);
+
+#ifndef NOSUID
+    setuid(ID0realuid());
+#endif
+    if (arg->argc > arg->argn) {
+      /* substitute pseudo args */
+      char *argv[MAXARGS];
+      int argc = arg->argc - arg->argn;
+
+      if (argc >= (int)(sizeof argv / sizeof argv[0])) {
+        argc = sizeof argv / sizeof argv[0] - 1;
+        log_Printf(LogWARN, "Truncating shell command to %d args\n", argc);
+      }
+      command_Expand(argv, argc, arg->argv + arg->argn, arg->bundle, 0, pid);
+      if (bg) {
+	pid_t p;
+
+	p = getpid();
+	if (daemon(1, 1) == -1) {
+	  log_Printf(LogERROR, "%ld: daemon: %s\n", (long)p, strerror(errno));
+	  exit(1);
+	}
+      } else if (arg->prompt)
+        printf("ppp: Pausing until %s finishes\n", arg->argv[arg->argn]);
+      execvp(argv[0], argv);
+    } else {
+      if (arg->prompt)
+        printf("ppp: Pausing until %s finishes\n", shell);
+      prompt_TtyOldMode(arg->prompt);
+      execl(shell, shell, (char *)NULL);
+    }
+
+    log_Printf(LogWARN, "exec() of %s failed: %s\n",
+              arg->argc > arg->argn ? arg->argv[arg->argn] : shell,
+              strerror(errno));
+    _exit(255);
+  }
+
+  if (shpid == (pid_t)-1)
+    log_Printf(LogERROR, "Fork failed: %s\n", strerror(errno));
+  else {
+    int status;
+    waitpid(shpid, &status, 0);
+  }
+
+  if (arg->prompt && !arg->prompt->owner)
+    prompt_TtyCommandMode(arg->prompt);
+
+  return 0;
+}
+
+static int
+BgShellCommand(struct cmdargs const *arg)
+{
+  if (arg->argc == arg->argn)
+    return -1;
+  return ShellCommand(arg, 1);
+}
+
+static int
+FgShellCommand(struct cmdargs const *arg)
+{
+  return ShellCommand(arg, 0);
+}
+
+static int
+ResolvCommand(struct cmdargs const *arg)
+{
+  if (arg->argc == arg->argn + 1) {
+    if (!strcasecmp(arg->argv[arg->argn], "reload"))
+      ipcp_LoadDNS(&arg->bundle->ncp.ipcp);
+    else if (!strcasecmp(arg->argv[arg->argn], "restore"))
+      ipcp_RestoreDNS(&arg->bundle->ncp.ipcp);
+    else if (!strcasecmp(arg->argv[arg->argn], "rewrite"))
+      ipcp_WriteDNS(&arg->bundle->ncp.ipcp);
+    else if (!strcasecmp(arg->argv[arg->argn], "readonly"))
+      arg->bundle->ncp.ipcp.ns.writable = 0;
+    else if (!strcasecmp(arg->argv[arg->argn], "writable"))
+      arg->bundle->ncp.ipcp.ns.writable = 1;
+    else
+      return -1;
+
+    return 0;
+  }
+
+  return -1;
+}
+
+#ifndef NONAT
+static struct cmdtab const NatCommands[] =
+{
+  {"addr", NULL, nat_RedirectAddr, LOCAL_AUTH,
+   "static address translation", "nat addr [addr_local addr_alias]", NULL},
+  {"deny_incoming", NULL, NatOption, LOCAL_AUTH,
+   "stop incoming connections", "nat deny_incoming yes|no",
+   (const void *) PKT_ALIAS_DENY_INCOMING},
+  {"enable", NULL, NatEnable, LOCAL_AUTH,
+   "enable NAT", "nat enable yes|no", NULL},
+  {"log", NULL, NatOption, LOCAL_AUTH,
+   "log NAT link creation", "nat log yes|no",
+   (const void *) PKT_ALIAS_LOG},
+  {"port", NULL, nat_RedirectPort, LOCAL_AUTH, "port redirection",
+   "nat port proto localaddr:port[-port] aliasport[-aliasport]", NULL},
+  {"proto", NULL, nat_RedirectProto, LOCAL_AUTH, "protocol redirection",
+   "nat proto proto localIP [publicIP [remoteIP]]", NULL},
+  {"proxy", NULL, nat_ProxyRule, LOCAL_AUTH,
+   "proxy control", "nat proxy server host[:port] ...", NULL},
+#ifndef NO_FW_PUNCH
+  {"punch_fw", NULL, nat_PunchFW, LOCAL_AUTH,
+   "firewall control", "nat punch_fw [base count]", NULL},
+#endif
+  {"skinny_port", NULL, nat_SkinnyPort, LOCAL_AUTH,
+   "TCP port used by Skinny Station protocol", "nat skinny_port [port]", NULL},
+  {"same_ports", NULL, NatOption, LOCAL_AUTH,
+   "try to leave port numbers unchanged", "nat same_ports yes|no",
+   (const void *) PKT_ALIAS_SAME_PORTS},
+  {"target", NULL, nat_SetTarget, LOCAL_AUTH,
+   "Default address for incoming connections", "nat target addr", NULL},
+  {"unregistered_only", NULL, NatOption, LOCAL_AUTH,
+   "translate unregistered (private) IP address space only",
+   "nat unregistered_only yes|no",
+   (const void *) PKT_ALIAS_UNREGISTERED_ONLY},
+  {"use_sockets", NULL, NatOption, LOCAL_AUTH,
+   "allocate host sockets", "nat use_sockets yes|no",
+   (const void *) PKT_ALIAS_USE_SOCKETS},
+  {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+   "Display this message", "nat help|? [command]", NatCommands},
+  {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+#endif
+
+static struct cmdtab const AllowCommands[] = {
+  {"modes", "mode", AllowModes, LOCAL_AUTH,
+  "Only allow certain ppp modes", "allow modes mode...", NULL},
+  {"users", "user", AllowUsers, LOCAL_AUTH,
+  "Only allow ppp access to certain users", "allow users logname...", NULL},
+  {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+  "Display this message", "allow help|? [command]", AllowCommands},
+  {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+
+static struct cmdtab const IfaceCommands[] =
+{
+  {"add", NULL, IfaceAddCommand, LOCAL_AUTH,
+   "Add iface address", "iface add addr[/bits| mask] peer", NULL},
+  {NULL, "add!", IfaceAddCommand, LOCAL_AUTH,
+   "Add or change an iface address", "iface add! addr[/bits| mask] peer",
+   (void *)1},
+  {"clear", NULL, IfaceClearCommand, LOCAL_AUTH,
+   "Clear iface address(es)", "iface clear [INET | INET6]", NULL},
+  {"delete", "rm", IfaceDeleteCommand, LOCAL_AUTH,
+   "Delete iface address", "iface delete addr", NULL},
+  {NULL, "rm!", IfaceDeleteCommand, LOCAL_AUTH,
+   "Delete iface address", "iface delete addr", (void *)1},
+  {NULL, "delete!", IfaceDeleteCommand, LOCAL_AUTH,
+   "Delete iface address", "iface delete addr", (void *)1},
+  {"show", NULL, iface_Show, LOCAL_AUTH,
+   "Show iface address(es)", "iface show", NULL},
+  {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+   "Display this message", "nat help|? [command]", IfaceCommands},
+  {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+
+static struct cmdtab const Commands[] = {
+  {"accept", NULL, NegotiateCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+  "accept option request", "accept option ..", NULL},
+  {"add", NULL, AddCommand, LOCAL_AUTH,
+  "add route", "add dest mask gateway", NULL},
+  {NULL, "add!", AddCommand, LOCAL_AUTH,
+  "add or change route", "add! dest mask gateway", (void *)1},
+  {"allow", "auth", RunListCommand, LOCAL_AUTH,
+  "Allow ppp access", "allow users|modes ....", AllowCommands},
+  {"bg", "!bg", BgShellCommand, LOCAL_AUTH,
+  "Run a background command", "[!]bg command", NULL},
+  {"clear", NULL, ClearCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Clear throughput statistics",
+  "clear ipcp|ipv6cp|physical [current|overall|peak]...", NULL},
+  {"clone", NULL, CloneCommand, LOCAL_AUTH | LOCAL_CX,
+  "Clone a link", "clone newname...", NULL},
+  {"close", NULL, CloseCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Close an FSM", "close [lcp|ccp]", NULL},
+  {"delete", NULL, DeleteCommand, LOCAL_AUTH,
+  "delete route", "delete dest", NULL},
+  {NULL, "delete!", DeleteCommand, LOCAL_AUTH,
+  "delete a route if it exists", "delete! dest", (void *)1},
+  {"deny", NULL, NegotiateCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Deny option request", "deny option ..", NULL},
+  {"dial", "call", DialCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Dial and login", "dial|call [system ...]", NULL},
+  {"disable", NULL, NegotiateCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Disable option", "disable option ..", NULL},
+  {"down", NULL, DownCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Generate a down event", "down [ccp|lcp]", NULL},
+  {"enable", NULL, NegotiateCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Enable option", "enable option ..", NULL},
+  {"ident", NULL, IdentCommand, LOCAL_AUTH | LOCAL_CX,
+  "Set the link identity", "ident text...", NULL},
+  {"iface", "interface", RunListCommand, LOCAL_AUTH,
+  "interface control", "iface option ...", IfaceCommands},
+  {"link", "datalink", LinkCommand, LOCAL_AUTH,
+  "Link specific commands", "link name command ...", NULL},
+  {"load", NULL, LoadCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Load settings", "load [system ...]", NULL},
+  {"log", NULL, LogCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+  "log information", "log word ...", NULL},
+#ifndef NONAT
+  {"nat", "alias", RunListCommand, LOCAL_AUTH,
+  "NAT control", "nat option yes|no", NatCommands},
+#endif
+  {"open", NULL, OpenCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Open an FSM", "open! [lcp|ccp|ipcp]", (void *)1},
+  {"passwd", NULL, PasswdCommand, LOCAL_NO_AUTH,
+  "Password for manipulation", "passwd LocalPassword", NULL},
+  {"quit", "bye", QuitCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+  "Quit PPP program", "quit|bye [all]", NULL},
+  {"remove", "rm", RemoveCommand, LOCAL_AUTH | LOCAL_CX,
+  "Remove a link", "remove", NULL},
+  {"rename", "mv", RenameCommand, LOCAL_AUTH | LOCAL_CX,
+  "Rename a link", "rename name", NULL},
+  {"resolv", NULL, ResolvCommand, LOCAL_AUTH,
+  "Manipulate resolv.conf", "resolv readonly|reload|restore|rewrite|writable",
+  NULL},
+  {"save", NULL, SaveCommand, LOCAL_AUTH,
+  "Save settings", "save", NULL},
+  {"sendident", NULL, SendIdentification, LOCAL_AUTH | LOCAL_CX,
+  "Transmit the link identity", "sendident", NULL},
+  {"set", "setup", SetCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Set parameters", "set[up] var value", NULL},
+  {"shell", "!", FgShellCommand, LOCAL_AUTH,
+  "Run a subshell", "shell|! [sh command]", NULL},
+  {"show", NULL, ShowCommand, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Show status and stats", "show var", NULL},
+  {"term", NULL, TerminalCommand, LOCAL_AUTH | LOCAL_CX,
+  "Enter terminal mode", "term", NULL},
+  {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+  "Display this message", "help|? [command]", Commands},
+  {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+
+static int
+ShowEscape(struct cmdargs const *arg)
+{
+  if (arg->cx->physical->async.cfg.EscMap[32]) {
+    int code, bit;
+    const char *sep = "";
+
+    for (code = 0; code < 32; code++)
+      if (arg->cx->physical->async.cfg.EscMap[code])
+	for (bit = 0; bit < 8; bit++)
+	  if (arg->cx->physical->async.cfg.EscMap[code] & (1 << bit)) {
+	    prompt_Printf(arg->prompt, "%s0x%02x", sep, (code << 3) + bit);
+            sep = ", ";
+          }
+    prompt_Printf(arg->prompt, "\n");
+  }
+  return 0;
+}
+
+static int
+ShowTimerList(struct cmdargs const *arg)
+{
+  timer_Show(0, arg->prompt);
+  return 0;
+}
+
+static int
+ShowStopped(struct cmdargs const *arg)
+{
+  prompt_Printf(arg->prompt, " Stopped Timer:  LCP: ");
+  if (!arg->cx->physical->link.lcp.fsm.StoppedTimer.load)
+    prompt_Printf(arg->prompt, "Disabled");
+  else
+    prompt_Printf(arg->prompt, "%ld secs",
+                  arg->cx->physical->link.lcp.fsm.StoppedTimer.load / SECTICKS);
+
+  prompt_Printf(arg->prompt, ", CCP: ");
+  if (!arg->cx->physical->link.ccp.fsm.StoppedTimer.load)
+    prompt_Printf(arg->prompt, "Disabled");
+  else
+    prompt_Printf(arg->prompt, "%ld secs",
+                  arg->cx->physical->link.ccp.fsm.StoppedTimer.load / SECTICKS);
+
+  prompt_Printf(arg->prompt, "\n");
+
+  return 0;
+}
+
+static int
+ShowVersion(struct cmdargs const *arg)
+{
+  prompt_Printf(arg->prompt, "PPP Version %s\n", Version);
+  return 0;
+}
+
+static int
+ShowProtocolStats(struct cmdargs const *arg)
+{
+  struct link *l = command_ChooseLink(arg);
+
+  prompt_Printf(arg->prompt, "%s:\n", l->name);
+  link_ReportProtocolStatus(l, arg->prompt);
+  return 0;
+}
+
+static struct cmdtab const ShowCommands[] = {
+  {"bundle", NULL, bundle_ShowStatus, LOCAL_AUTH,
+  "bundle details", "show bundle", NULL},
+  {"ccp", NULL, ccp_ReportStatus, LOCAL_AUTH | LOCAL_CX_OPT,
+  "CCP status", "show cpp", NULL},
+  {"compress", NULL, sl_Show, LOCAL_AUTH,
+  "VJ compression stats", "show compress", NULL},
+  {"escape", NULL, ShowEscape, LOCAL_AUTH | LOCAL_CX,
+  "escape characters", "show escape", NULL},
+  {"filter", NULL, filter_Show, LOCAL_AUTH,
+  "packet filters", "show filter [in|out|dial|alive]", NULL},
+  {"hdlc", NULL, hdlc_ReportStatus, LOCAL_AUTH | LOCAL_CX,
+  "HDLC errors", "show hdlc", NULL},
+  {"iface", "interface", iface_Show, LOCAL_AUTH,
+  "Interface status", "show iface", NULL},
+  {"ipcp", NULL, ipcp_Show, LOCAL_AUTH,
+  "IPCP status", "show ipcp", NULL},
+#ifndef NOINET6
+  {"ipv6cp", NULL, ipv6cp_Show, LOCAL_AUTH,
+  "IPV6CP status", "show ipv6cp", NULL},
+#endif
+  {"layers", NULL, link_ShowLayers, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Protocol layers", "show layers", NULL},
+  {"lcp", NULL, lcp_ReportStatus, LOCAL_AUTH | LOCAL_CX,
+  "LCP status", "show lcp", NULL},
+  {"link", "datalink", datalink_Show, LOCAL_AUTH | LOCAL_CX,
+  "(high-level) link info", "show link", NULL},
+  {"links", NULL, bundle_ShowLinks, LOCAL_AUTH,
+  "available link names", "show links", NULL},
+  {"log", NULL, log_ShowLevel, LOCAL_AUTH,
+  "log levels", "show log", NULL},
+  {"mem", NULL, mbuf_Show, LOCAL_AUTH,
+  "mbuf allocations", "show mem", NULL},
+  {"ncp", NULL, ncp_Show, LOCAL_AUTH,
+  "NCP status", "show ncp", NULL},
+  {"physical", NULL, physical_ShowStatus, LOCAL_AUTH | LOCAL_CX,
+  "(low-level) link info", "show physical", NULL},
+  {"mp", "multilink", mp_ShowStatus, LOCAL_AUTH,
+  "multilink setup", "show mp", NULL},
+  {"proto", NULL, ShowProtocolStats, LOCAL_AUTH | LOCAL_CX_OPT,
+  "protocol summary", "show proto", NULL},
+  {"route", NULL, route_Show, LOCAL_AUTH,
+  "routing table", "show route", NULL},
+  {"stopped", NULL, ShowStopped, LOCAL_AUTH | LOCAL_CX,
+  "STOPPED timeout", "show stopped", NULL},
+  {"timers", NULL, ShowTimerList, LOCAL_AUTH,
+  "alarm timers", "show timers", NULL},
+  {"version", NULL, ShowVersion, LOCAL_NO_AUTH | LOCAL_AUTH,
+  "version string", "show version", NULL},
+  {"who", NULL, log_ShowWho, LOCAL_AUTH,
+  "client list", "show who", NULL},
+  {"help", "?", HelpCommand, LOCAL_NO_AUTH | LOCAL_AUTH,
+  "Display this message", "show help|? [command]", ShowCommands},
+  {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+
+static struct cmdtab const *
+FindCommand(struct cmdtab const *cmds, const char *str, int *pmatch)
+{
+  int nmatch;
+  int len;
+  struct cmdtab const *found;
+
+  found = NULL;
+  len = strlen(str);
+  nmatch = 0;
+  while (cmds->func) {
+    if (cmds->name && strncasecmp(str, cmds->name, len) == 0) {
+      if (cmds->name[len] == '\0') {
+	*pmatch = 1;
+	return cmds;
+      }
+      nmatch++;
+      found = cmds;
+    } else if (cmds->alias && strncasecmp(str, cmds->alias, len) == 0) {
+      if (cmds->alias[len] == '\0') {
+	*pmatch = 1;
+	return cmds;
+      }
+      nmatch++;
+      found = cmds;
+    }
+    cmds++;
+  }
+  *pmatch = nmatch;
+  return found;
+}
+
+static const char *
+mkPrefix(int argc, char const *const *argv, char *tgt, int sz)
+{
+  int f, tlen, len;
+
+  tlen = 0;
+  for (f = 0; f < argc && tlen < sz - 2; f++) {
+    if (f)
+      tgt[tlen++] = ' ';
+    len = strlen(argv[f]);
+    if (len > sz - tlen - 1)
+      len = sz - tlen - 1;
+    strncpy(tgt+tlen, argv[f], len);
+    tlen += len;
+  }
+  tgt[tlen] = '\0';
+  return tgt;
+}
+
+static int
+FindExec(struct bundle *bundle, struct cmdtab const *cmds, int argc, int argn,
+         char const *const *argv, struct prompt *prompt, struct datalink *cx)
+{
+  struct cmdtab const *cmd;
+  int val = 1;
+  int nmatch;
+  struct cmdargs arg;
+  char prefix[100];
+
+  cmd = FindCommand(cmds, argv[argn], &nmatch);
+  if (nmatch > 1)
+    log_Printf(LogWARN, "%s: Ambiguous command\n",
+              mkPrefix(argn+1, argv, prefix, sizeof prefix));
+  else if (cmd && (!prompt || (cmd->lauth & prompt->auth))) {
+    if ((cmd->lauth & LOCAL_CX) && !cx)
+      /* We've got no context, but we require it */
+      cx = bundle2datalink(bundle, NULL);
+
+    if ((cmd->lauth & LOCAL_CX) && !cx)
+      log_Printf(LogWARN, "%s: No context (use the `link' command)\n",
+                mkPrefix(argn+1, argv, prefix, sizeof prefix));
+    else {
+      if (cx && !(cmd->lauth & (LOCAL_CX|LOCAL_CX_OPT))) {
+        log_Printf(LogWARN, "%s: Redundant context (%s) ignored\n",
+                  mkPrefix(argn+1, argv, prefix, sizeof prefix), cx->name);
+        cx = NULL;
+      }
+      arg.cmdtab = cmds;
+      arg.cmd = cmd;
+      arg.argc = argc;
+      arg.argn = argn+1;
+      arg.argv = argv;
+      arg.bundle = bundle;
+      arg.cx = cx;
+      arg.prompt = prompt;
+      val = (*cmd->func) (&arg);
+    }
+  } else
+    log_Printf(LogWARN, "%s: Invalid command\n",
+              mkPrefix(argn+1, argv, prefix, sizeof prefix));
+
+  if (val == -1)
+    log_Printf(LogWARN, "usage: %s\n", cmd->syntax);
+  else if (val)
+    log_Printf(LogWARN, "%s: Failed %d\n",
+              mkPrefix(argn+1, argv, prefix, sizeof prefix), val);
+
+  return val;
+}
+
+int
+command_Expand_Interpret(char *buff, int nb, char *argv[MAXARGS], int offset)
+{
+  char buff2[LINE_LEN-offset];
+
+  InterpretArg(buff, buff2);
+  strncpy(buff, buff2, LINE_LEN - offset - 1);
+  buff[LINE_LEN - offset - 1] = '\0';
+
+  return command_Interpret(buff, nb, argv);
+}
+
+int
+command_Interpret(char *buff, int nb, char *argv[MAXARGS])
+{
+  char *cp;
+
+  if (nb > 0) {
+    cp = buff + strcspn(buff, "\r\n");
+    if (cp)
+      *cp = '\0';
+    return MakeArgs(buff, argv, MAXARGS, PARSE_REDUCE);
+  }
+  return 0;
+}
+
+static int
+arghidden(char const *const *argv, int n)
+{
+  /* Is arg n of the given command to be hidden from the log ? */
+
+  /* set authkey xxxxx */
+  /* set key xxxxx */
+  if (n == 2 && !strncasecmp(argv[0], "se", 2) &&
+      (!strncasecmp(argv[1], "authk", 5) || !strncasecmp(argv[1], "ke", 2)))
+    return 1;
+
+  /* passwd xxxxx */
+  if (n == 1 && !strncasecmp(argv[0], "p", 1))
+    return 1;
+
+  /* set server port xxxxx .... */
+  if (n == 3 && !strncasecmp(argv[0], "se", 2) &&
+      !strncasecmp(argv[1], "se", 2))
+    return 1;
+
+  return 0;
+}
+
+void
+command_Run(struct bundle *bundle, int argc, char const *const *argv,
+           struct prompt *prompt, const char *label, struct datalink *cx)
+{
+  if (argc > 0) {
+    if (log_IsKept(LogCOMMAND)) {
+      char buf[LINE_LEN];
+      int f;
+      size_t n;
+
+      if (label) {
+        strncpy(buf, label, sizeof buf - 3);
+        buf[sizeof buf - 3] = '\0';
+        strcat(buf, ": ");
+        n = strlen(buf);
+      } else {
+        *buf = '\0';
+        n = 0;
+      }
+      buf[sizeof buf - 1] = '\0';	/* In case we run out of room in buf */
+
+      for (f = 0; f < argc; f++) {
+        if (n < sizeof buf - 1 && f)
+          buf[n++] = ' ';
+        if (arghidden(argv, f))
+          strncpy(buf+n, "********", sizeof buf - n - 1);
+        else
+          strncpy(buf+n, argv[f], sizeof buf - n - 1);
+        n += strlen(buf+n);
+      }
+      log_Printf(LogCOMMAND, "%s\n", buf);
+    }
+    FindExec(bundle, Commands, argc, 0, argv, prompt, cx);
+  }
+}
+
+int
+command_Decode(struct bundle *bundle, char *buff, int nb, struct prompt *prompt,
+              const char *label)
+{
+  int argc;
+  char *argv[MAXARGS];
+
+  if ((argc = command_Expand_Interpret(buff, nb, argv, 0)) < 0)
+    return 0;
+
+  command_Run(bundle, argc, (char const *const *)argv, prompt, label, NULL);
+  return 1;
+}
+
+static int
+ShowCommand(struct cmdargs const *arg)
+{
+  if (!arg->prompt)
+    log_Printf(LogWARN, "show: Cannot show without a prompt\n");
+  else if (arg->argc > arg->argn)
+    FindExec(arg->bundle, ShowCommands, arg->argc, arg->argn, arg->argv,
+             arg->prompt, arg->cx);
+  else
+    prompt_Printf(arg->prompt, "Use ``show ?'' to get a list.\n");
+
+  return 0;
+}
+
+static int
+TerminalCommand(struct cmdargs const *arg)
+{
+  if (!arg->prompt) {
+    log_Printf(LogWARN, "term: Need a prompt\n");
+    return 1;
+  }
+
+  if (arg->cx->physical->link.lcp.fsm.state > ST_CLOSED) {
+    prompt_Printf(arg->prompt, "LCP state is [%s]\n",
+                  State2Nam(arg->cx->physical->link.lcp.fsm.state));
+    return 1;
+  }
+
+  datalink_Up(arg->cx, 0, 0);
+  prompt_TtyTermMode(arg->prompt, arg->cx);
+  return 0;
+}
+
+static int
+QuitCommand(struct cmdargs const *arg)
+{
+  if (!arg->prompt || prompt_IsController(arg->prompt) ||
+      (arg->argc > arg->argn && !strcasecmp(arg->argv[arg->argn], "all") &&
+       (arg->prompt->auth & LOCAL_AUTH)))
+    Cleanup();
+  if (arg->prompt)
+    prompt_Destroy(arg->prompt, 1);
+
+  return 0;
+}
+
+static int
+OpenCommand(struct cmdargs const *arg)
+{
+  if (arg->argc == arg->argn)
+    bundle_Open(arg->bundle, arg->cx ? arg->cx->name : NULL, PHYS_ALL, 1);
+  else if (arg->argc == arg->argn + 1) {
+    if (!strcasecmp(arg->argv[arg->argn], "lcp")) {
+      struct datalink *cx = arg->cx ?
+        arg->cx : bundle2datalink(arg->bundle, NULL);
+      if (cx) {
+        if (cx->physical->link.lcp.fsm.state == ST_OPENED)
+          fsm_Reopen(&cx->physical->link.lcp.fsm);
+        else
+          bundle_Open(arg->bundle, cx->name, PHYS_ALL, 1);
+      } else
+        log_Printf(LogWARN, "open lcp: You must specify a link\n");
+    } else if (!strcasecmp(arg->argv[arg->argn], "ccp")) {
+      struct fsm *fp;
+
+      fp = &command_ChooseLink(arg)->ccp.fsm;
+      if (fp->link->lcp.fsm.state != ST_OPENED)
+        log_Printf(LogWARN, "open: LCP must be open before opening CCP\n");
+      else if (fp->state == ST_OPENED)
+        fsm_Reopen(fp);
+      else {
+        fp->open_mode = 0;	/* Not passive any more */
+        if (fp->state == ST_STOPPED) {
+          fsm_Down(fp);
+          fsm_Up(fp);
+        } else {
+          fsm_Up(fp);
+          fsm_Open(fp);
+        }
+      }
+    } else if (!strcasecmp(arg->argv[arg->argn], "ipcp")) {
+      if (arg->cx)
+        log_Printf(LogWARN, "open ipcp: You need not specify a link\n");
+      if (arg->bundle->ncp.ipcp.fsm.state == ST_OPENED)
+        fsm_Reopen(&arg->bundle->ncp.ipcp.fsm);
+      else
+        bundle_Open(arg->bundle, NULL, PHYS_ALL, 1);
+    } else
+      return -1;
+  } else
+    return -1;
+
+  return 0;
+}
+
+static int
+CloseCommand(struct cmdargs const *arg)
+{
+  if (arg->argc == arg->argn)
+    bundle_Close(arg->bundle, arg->cx ? arg->cx->name : NULL, CLOSE_STAYDOWN);
+  else if (arg->argc == arg->argn + 1) {
+    if (!strcasecmp(arg->argv[arg->argn], "lcp"))
+      bundle_Close(arg->bundle, arg->cx ? arg->cx->name : NULL, CLOSE_LCP);
+    else if (!strcasecmp(arg->argv[arg->argn], "ccp") ||
+             !strcasecmp(arg->argv[arg->argn], "ccp!")) {
+      struct fsm *fp;
+
+      fp = &command_ChooseLink(arg)->ccp.fsm;
+      if (fp->state == ST_OPENED) {
+        fsm_Close(fp);
+        if (arg->argv[arg->argn][3] == '!')
+          fp->open_mode = 0;		/* Stay ST_CLOSED */
+        else
+          fp->open_mode = OPEN_PASSIVE;	/* Wait for the peer to start */
+      }
+    } else
+      return -1;
+  } else
+    return -1;
+
+  return 0;
+}
+
+static int
+DownCommand(struct cmdargs const *arg)
+{
+  if (arg->argc == arg->argn) {
+      if (arg->cx)
+        datalink_Down(arg->cx, CLOSE_STAYDOWN);
+      else
+        bundle_Down(arg->bundle, CLOSE_STAYDOWN);
+  } else if (arg->argc == arg->argn + 1) {
+    if (!strcasecmp(arg->argv[arg->argn], "lcp")) {
+      if (arg->cx)
+        datalink_Down(arg->cx, CLOSE_LCP);
+      else
+        bundle_Down(arg->bundle, CLOSE_LCP);
+    } else if (!strcasecmp(arg->argv[arg->argn], "ccp")) {
+      struct fsm *fp = arg->cx ? &arg->cx->physical->link.ccp.fsm :
+                                 &arg->bundle->ncp.mp.link.ccp.fsm;
+      fsm2initial(fp);
+    } else
+      return -1;
+  } else
+    return -1;
+
+  return 0;
+}
+
+static int
+SetModemSpeed(struct cmdargs const *arg)
+{
+  long speed;
+  char *end;
+
+  if (arg->argc > arg->argn && *arg->argv[arg->argn]) {
+    if (arg->argc > arg->argn+1) {
+      log_Printf(LogWARN, "SetModemSpeed: Too many arguments\n");
+      return -1;
+    }
+    if (strcasecmp(arg->argv[arg->argn], "sync") == 0) {
+      physical_SetSync(arg->cx->physical);
+      return 0;
+    }
+    end = NULL;
+    speed = strtol(arg->argv[arg->argn], &end, 10);
+    if (*end || speed < 0) {
+      log_Printf(LogWARN, "SetModemSpeed: Bad argument \"%s\"",
+                arg->argv[arg->argn]);
+      return -1;
+    }
+    if (physical_SetSpeed(arg->cx->physical, speed))
+      return 0;
+    log_Printf(LogWARN, "%s: Invalid speed\n", arg->argv[arg->argn]);
+  } else
+    log_Printf(LogWARN, "SetModemSpeed: No speed specified\n");
+
+  return -1;
+}
+
+static int
+SetStoppedTimeout(struct cmdargs const *arg)
+{
+  struct link *l = &arg->cx->physical->link;
+
+  l->lcp.fsm.StoppedTimer.load = 0;
+  l->ccp.fsm.StoppedTimer.load = 0;
+  if (arg->argc <= arg->argn+2) {
+    if (arg->argc > arg->argn) {
+      l->lcp.fsm.StoppedTimer.load = atoi(arg->argv[arg->argn]) * SECTICKS;
+      if (arg->argc > arg->argn+1)
+        l->ccp.fsm.StoppedTimer.load = atoi(arg->argv[arg->argn+1]) * SECTICKS;
+    }
+    return 0;
+  }
+  return -1;
+}
+
+static int
+SetServer(struct cmdargs const *arg)
+{
+  int res = -1;
+
+  if (arg->argc > arg->argn && arg->argc < arg->argn+4) {
+    const char *port, *passwd, *mask;
+    size_t mlen;
+
+    /* What's what ? */
+    port = arg->argv[arg->argn];
+    if (arg->argc == arg->argn + 2) {
+      passwd = arg->argv[arg->argn+1];
+      mask = NULL;
+    } else if (arg->argc == arg->argn + 3) {
+      passwd = arg->argv[arg->argn+1];
+      mask = arg->argv[arg->argn+2];
+      mlen = strlen(mask);
+      if (mlen == 0 || mlen > 4 || strspn(mask, "01234567") != mlen ||
+          (mlen == 4 && *mask != '0')) {
+        log_Printf(LogWARN, "%s %s: %s: Invalid mask\n",
+                   arg->argv[arg->argn - 2], arg->argv[arg->argn - 1], mask);
+        return -1;
+      }
+    } else if (arg->argc != arg->argn + 1)
+      return -1;
+    else if (strcasecmp(port, "none") == 0) {
+      if (server_Clear(arg->bundle))
+        log_Printf(LogPHASE, "Disabled server socket\n");
+      return 0;
+    } else if (strcasecmp(port, "open") == 0) {
+      switch (server_Reopen(arg->bundle)) {
+        case SERVER_OK:
+          return 0;
+        case SERVER_FAILED:
+          log_Printf(LogWARN, "Failed to reopen server port\n");
+          return 1;
+        case SERVER_UNSET:
+          log_Printf(LogWARN, "Cannot reopen unset server socket\n");
+          return 1;
+        default:
+          break;
+      }
+      return -1;
+    } else if (strcasecmp(port, "closed") == 0) {
+      if (server_Close(arg->bundle))
+        log_Printf(LogPHASE, "Closed server socket\n");
+      else
+        log_Printf(LogWARN, "Server socket not open\n");
+
+      return 0;
+    } else
+      return -1;
+
+    strncpy(server.cfg.passwd, passwd, sizeof server.cfg.passwd - 1);
+    server.cfg.passwd[sizeof server.cfg.passwd - 1] = '\0';
+
+    if (*port == '/') {
+      mode_t imask;
+      char *ptr, name[LINE_LEN + 12];
+
+      if (mask == NULL)
+        imask = (mode_t)-1;
+      else for (imask = mlen = 0; mask[mlen]; mlen++)
+        imask = (imask * 8) + mask[mlen] - '0';
+
+      ptr = strstr(port, "%d");
+      if (ptr) {
+        snprintf(name, sizeof name, "%.*s%d%s",
+                 (int)(ptr - port), port, arg->bundle->unit, ptr + 2);
+        port = name;
+      }
+      res = server_LocalOpen(arg->bundle, port, imask);
+    } else {
+      int iport, add = 0;
+
+      if (mask != NULL)
+        return -1;
+
+      if (*port == '+') {
+        port++;
+        add = 1;
+      }
+      if (strspn(port, "0123456789") != strlen(port)) {
+        struct servent *s;
+
+        if ((s = getservbyname(port, "tcp")) == NULL) {
+	  iport = 0;
+	  log_Printf(LogWARN, "%s: Invalid port or service\n", port);
+	} else
+	  iport = ntohs(s->s_port);
+      } else
+        iport = atoi(port);
+
+      if (iport) {
+        if (add)
+          iport += arg->bundle->unit;
+        res = server_TcpOpen(arg->bundle, iport);
+      } else
+        res = -1;
+    }
+  }
+
+  return res;
+}
+
+static int
+SetEscape(struct cmdargs const *arg)
+{
+  int code;
+  int argc = arg->argc - arg->argn;
+  char const *const *argv = arg->argv + arg->argn;
+
+  for (code = 0; code < 33; code++)
+    arg->cx->physical->async.cfg.EscMap[code] = 0;
+
+  while (argc-- > 0) {
+    sscanf(*argv++, "%x", &code);
+    code &= 0xff;
+    arg->cx->physical->async.cfg.EscMap[code >> 3] |= (1 << (code & 7));
+    arg->cx->physical->async.cfg.EscMap[32] = 1;
+  }
+  return 0;
+}
+
+static int
+SetInterfaceAddr(struct cmdargs const *arg)
+{
+  struct ncp *ncp = &arg->bundle->ncp;
+  struct ncpaddr ncpaddr;
+  const char *hisaddr;
+
+  if (arg->argc > arg->argn + 4)
+    return -1;
+
+  hisaddr = NULL;
+  memset(&ncp->ipcp.cfg.my_range, '\0', sizeof ncp->ipcp.cfg.my_range);
+  memset(&ncp->ipcp.cfg.peer_range, '\0', sizeof ncp->ipcp.cfg.peer_range);
+  ncp->ipcp.cfg.HaveTriggerAddress = 0;
+  ncp->ipcp.cfg.netmask.s_addr = INADDR_ANY;
+  iplist_reset(&ncp->ipcp.cfg.peer_list);
+
+  if (arg->argc > arg->argn) {
+    if (!ncprange_aton(&ncp->ipcp.cfg.my_range, ncp, arg->argv[arg->argn]))
+      return 1;
+    if (arg->argc > arg->argn+1) {
+      hisaddr = arg->argv[arg->argn+1];
+      if (arg->argc > arg->argn+2) {
+        ncp->ipcp.ifmask = ncp->ipcp.cfg.netmask =
+          GetIpAddr(arg->argv[arg->argn+2]);
+	if (arg->argc > arg->argn+3) {
+	  ncp->ipcp.cfg.TriggerAddress = GetIpAddr(arg->argv[arg->argn+3]);
+	  ncp->ipcp.cfg.HaveTriggerAddress = 1;
+	}
+      }
+    }
+  }
+
+  /* 0.0.0.0 means any address (0 bits) */
+  ncprange_getaddr(&ncp->ipcp.cfg.my_range, &ncpaddr);
+  ncpaddr_getip4(&ncpaddr, &ncp->ipcp.my_ip);
+  if (ncp->ipcp.my_ip.s_addr == INADDR_ANY)
+    ncprange_setwidth(&ncp->ipcp.cfg.my_range, 0);
+  bundle_AdjustFilters(arg->bundle, &ncpaddr, NULL);
+
+  if (hisaddr && !ipcp_UseHisaddr(arg->bundle, hisaddr,
+                                  arg->bundle->phys_type.all & PHYS_AUTO))
+    return 4;
+
+  return 0;
+}
+
+static int
+SetRetry(int argc, char const *const *argv, u_int *timeout, u_int *maxreq,
+          u_int *maxtrm, int def)
+{
+  if (argc == 0) {
+    *timeout = DEF_FSMRETRY;
+    *maxreq = def;
+    if (maxtrm != NULL)
+      *maxtrm = def;
+  } else {
+    long l = atol(argv[0]);
+
+    if (l < MIN_FSMRETRY) {
+      log_Printf(LogWARN, "%ld: Invalid FSM retry period - min %d\n",
+                 l, MIN_FSMRETRY);
+      return 1;
+    } else
+      *timeout = l;
+
+    if (argc > 1) {
+      l = atol(argv[1]);
+      if (l < 1) {
+        log_Printf(LogWARN, "%ld: Invalid FSM REQ tries - changed to 1\n", l);
+        l = 1;
+      }
+      *maxreq = l;
+
+      if (argc > 2 && maxtrm != NULL) {
+        l = atol(argv[2]);
+        if (l < 1) {
+          log_Printf(LogWARN, "%ld: Invalid FSM TRM tries - changed to 1\n", l);
+          l = 1;
+        }
+        *maxtrm = l;
+      }
+    }
+  }
+
+  return 0;
+}
+
+static int
+SetVariable(struct cmdargs const *arg)
+{
+  long long_val, param = (long)arg->cmd->args;
+  int mode, dummyint, f, first, res;
+  u_short *change;
+  const char *argp;
+  struct datalink *cx = arg->cx;	/* LOCAL_CX uses this */
+  struct link *l = command_ChooseLink(arg);	/* LOCAL_CX_OPT uses this */
+  struct in_addr *ipaddr;
+  struct ncpaddr ncpaddr[2];
+
+  if (arg->argc > arg->argn)
+    argp = arg->argv[arg->argn];
+  else
+    argp = "";
+
+  res = 0;
+
+  if ((arg->cmd->lauth & LOCAL_CX) && !cx) {
+    log_Printf(LogWARN, "set %s: No context (use the `link' command)\n",
+              arg->cmd->name);
+    return 1;
+  } else if (cx && !(arg->cmd->lauth & (LOCAL_CX|LOCAL_CX_OPT))) {
+    log_Printf(LogWARN, "set %s: Redundant context (%s) ignored\n",
+              arg->cmd->name, cx->name);
+    cx = NULL;
+  }
+
+  switch (param) {
+  case VAR_AUTHKEY:
+    strncpy(arg->bundle->cfg.auth.key, argp,
+            sizeof arg->bundle->cfg.auth.key - 1);
+    arg->bundle->cfg.auth.key[sizeof arg->bundle->cfg.auth.key - 1] = '\0';
+    break;
+
+  case VAR_AUTHNAME:
+    switch (bundle_Phase(arg->bundle)) {
+      default:
+        log_Printf(LogWARN, "Altering authname while at phase %s\n",
+                   bundle_PhaseName(arg->bundle));
+        /* drop through */
+      case PHASE_DEAD:
+      case PHASE_ESTABLISH:
+        strncpy(arg->bundle->cfg.auth.name, argp,
+                sizeof arg->bundle->cfg.auth.name - 1);
+        arg->bundle->cfg.auth.name[sizeof arg->bundle->cfg.auth.name-1] = '\0';
+        break;
+    }
+    break;
+
+  case VAR_AUTOLOAD:
+    if (arg->argc == arg->argn + 3) {
+      int v1, v2, v3;
+      char *end;
+
+      v1 = strtol(arg->argv[arg->argn], &end, 0);
+      if (v1 < 0 || *end) {
+        log_Printf(LogWARN, "autoload: %s: Invalid min percentage\n",
+                   arg->argv[arg->argn]);
+        res = 1;
+        break;
+      }
+
+      v2 = strtol(arg->argv[arg->argn + 1], &end, 0);
+      if (v2 < 0 || *end) {
+        log_Printf(LogWARN, "autoload: %s: Invalid max percentage\n",
+                   arg->argv[arg->argn + 1]);
+        res = 1;
+        break;
+      }
+      if (v2 < v1) {
+        v3 = v1;
+        v1 = v2;
+        v2 = v3;
+      }
+
+      v3 = strtol(arg->argv[arg->argn + 2], &end, 0);
+      if (v3 <= 0 || *end) {
+        log_Printf(LogWARN, "autoload: %s: Invalid throughput period\n",
+                   arg->argv[arg->argn + 2]);
+        res = 1;
+        break;
+      }
+
+      arg->bundle->ncp.mp.cfg.autoload.min = v1;
+      arg->bundle->ncp.mp.cfg.autoload.max = v2;
+      arg->bundle->ncp.mp.cfg.autoload.period = v3;
+      mp_RestartAutoloadTimer(&arg->bundle->ncp.mp);
+    } else {
+      log_Printf(LogWARN, "Set autoload requires three arguments\n");
+      res = 1;
+    }
+    break;
+
+  case VAR_DIAL:
+    strncpy(cx->cfg.script.dial, argp, sizeof cx->cfg.script.dial - 1);
+    cx->cfg.script.dial[sizeof cx->cfg.script.dial - 1] = '\0';
+    break;
+
+  case VAR_LOGIN:
+    strncpy(cx->cfg.script.login, argp, sizeof cx->cfg.script.login - 1);
+    cx->cfg.script.login[sizeof cx->cfg.script.login - 1] = '\0';
+    break;
+
+  case VAR_WINSIZE:
+    if (arg->argc > arg->argn) {
+      l->ccp.cfg.deflate.out.winsize = atoi(arg->argv[arg->argn]);
+      if (l->ccp.cfg.deflate.out.winsize < 8 ||
+          l->ccp.cfg.deflate.out.winsize > 15) {
+          log_Printf(LogWARN, "%d: Invalid outgoing window size\n",
+                    l->ccp.cfg.deflate.out.winsize);
+          l->ccp.cfg.deflate.out.winsize = 15;
+      }
+      if (arg->argc > arg->argn+1) {
+        l->ccp.cfg.deflate.in.winsize = atoi(arg->argv[arg->argn+1]);
+        if (l->ccp.cfg.deflate.in.winsize < 8 ||
+            l->ccp.cfg.deflate.in.winsize > 15) {
+            log_Printf(LogWARN, "%d: Invalid incoming window size\n",
+                      l->ccp.cfg.deflate.in.winsize);
+            l->ccp.cfg.deflate.in.winsize = 15;
+        }
+      } else
+        l->ccp.cfg.deflate.in.winsize = 0;
+    } else {
+      log_Printf(LogWARN, "No window size specified\n");
+      res = 1;
+    }
+    break;
+
+#ifndef NODES
+  case VAR_MPPE:
+    if (arg->argc > arg->argn + 2) {
+      res = -1;
+      break;
+    }
+
+    if (arg->argc == arg->argn) {
+      l->ccp.cfg.mppe.keybits = 0;
+      l->ccp.cfg.mppe.state = MPPE_ANYSTATE;
+      l->ccp.cfg.mppe.required = 0;
+      break;
+    }
+
+    if (!strcmp(argp, "*"))
+      long_val = 0;
+    else {
+      long_val = atol(argp);
+      if (long_val != 40 && long_val != 56 && long_val != 128) {
+        log_Printf(LogWARN, "%s: Invalid bits value\n", argp);
+        res = -1;
+        break;
+      }
+    }
+
+    if (arg->argc == arg->argn + 2) {
+      if (!strcmp(arg->argv[arg->argn + 1], "*"))
+        l->ccp.cfg.mppe.state = MPPE_ANYSTATE;
+      else if (!strcasecmp(arg->argv[arg->argn + 1], "stateless"))
+        l->ccp.cfg.mppe.state = MPPE_STATELESS;
+      else if (!strcasecmp(arg->argv[arg->argn + 1], "stateful"))
+        l->ccp.cfg.mppe.state = MPPE_STATEFUL;
+      else {
+        log_Printf(LogWARN, "%s: Invalid state value\n",
+                   arg->argv[arg->argn + 1]);
+        res = -1;
+        break;
+      }
+    } else
+      l->ccp.cfg.mppe.state = MPPE_ANYSTATE;
+    l->ccp.cfg.mppe.keybits = long_val;
+    l->ccp.cfg.mppe.required = 1;
+    break;
+#endif
+
+  case VAR_DEVICE:
+    physical_SetDeviceList(cx->physical, arg->argc - arg->argn,
+                           arg->argv + arg->argn);
+    break;
+
+  case VAR_ACCMAP:
+    if (arg->argc > arg->argn) {
+      u_long ulong_val;
+      sscanf(argp, "%lx", &ulong_val);
+      cx->physical->link.lcp.cfg.accmap = (u_int32_t)ulong_val;
+    } else {
+      log_Printf(LogWARN, "No accmap specified\n");
+      res = 1;
+    }
+    break;
+
+  case VAR_MODE:
+    mode = Nam2mode(argp);
+    if (mode == PHYS_NONE || mode == PHYS_ALL) {
+      log_Printf(LogWARN, "%s: Invalid mode\n", argp);
+      res = -1;
+      break;
+    }
+    bundle_SetMode(arg->bundle, cx, mode);
+    break;
+
+  case VAR_MRRU:
+    switch (bundle_Phase(arg->bundle)) {
+      case PHASE_DEAD:
+        break;
+      case PHASE_ESTABLISH:
+        /* Make sure none of our links are DATALINK_LCP or greater */
+        if (bundle_HighestState(arg->bundle) >= DATALINK_LCP) {
+          log_Printf(LogWARN, "mrru: Only changable before LCP negotiations\n");
+          res = 1;
+          break;
+        }
+        break;
+      default:
+        log_Printf(LogWARN, "mrru: Only changable at phase DEAD/ESTABLISH\n");
+        res = 1;
+        break;
+    }
+    if (res != 0)
+      break;
+    long_val = atol(argp);
+    if (long_val && long_val < MIN_MRU) {
+      log_Printf(LogWARN, "MRRU %ld: too small - min %d\n", long_val, MIN_MRU);
+      res = 1;
+      break;
+    } else if (long_val > MAX_MRU) {
+      log_Printf(LogWARN, "MRRU %ld: too big - max %d\n", long_val, MAX_MRU);
+      res = 1;
+      break;
+    } else
+      arg->bundle->ncp.mp.cfg.mrru = long_val;
+    break;
+
+  case VAR_MRU:
+    long_val = 0;	/* silence gcc */
+    change = NULL;	/* silence gcc */
+    switch(arg->argc - arg->argn) {
+    case 1:
+      if (argp[strspn(argp, "0123456789")] != '\0') {
+        res = -1;
+        break;
+      }
+      /*FALLTHRU*/
+    case 0:
+      long_val = atol(argp);
+      change = &l->lcp.cfg.mru;
+      if (long_val > l->lcp.cfg.max_mru) {
+        log_Printf(LogWARN, "MRU %ld: too large - max set to %d\n", long_val,
+                   l->lcp.cfg.max_mru);
+        res = 1;
+        break;
+      }
+      break;
+    case 2:
+      if (strcasecmp(argp, "max") && strcasecmp(argp, "maximum")) {
+        res = -1;
+        break;
+      }
+      long_val = atol(arg->argv[arg->argn + 1]);
+      change = &l->lcp.cfg.max_mru;
+      if (long_val > MAX_MRU) {
+        log_Printf(LogWARN, "MRU %ld: too large - maximum is %d\n", long_val,
+                   MAX_MRU);
+        res = 1;
+        break;
+      }
+      break;
+    default:
+      res = -1;
+      break;
+    }
+    if (res != 0)
+      break;
+
+    if (long_val == 0)
+      *change = 0;
+    else if (long_val < MIN_MRU) {
+      log_Printf(LogWARN, "MRU %ld: too small - min %d\n", long_val, MIN_MRU);
+      res = 1;
+      break;
+    } else if (long_val > MAX_MRU) {
+      log_Printf(LogWARN, "MRU %ld: too big - max %d\n", long_val, MAX_MRU);
+      res = 1;
+      break;
+    } else
+      *change = long_val;
+    if (l->lcp.cfg.mru > *change)
+      l->lcp.cfg.mru = *change;
+    break;
+
+  case VAR_MTU:
+    long_val = 0;	/* silence gcc */
+    change = NULL;	/* silence gcc */
+    switch(arg->argc - arg->argn) {
+    case 1:
+      if (argp[strspn(argp, "0123456789")] != '\0') {
+        res = -1;
+        break;
+      }
+      /*FALLTHRU*/
+    case 0:
+      long_val = atol(argp);
+      change = &l->lcp.cfg.mtu;
+      if (long_val > l->lcp.cfg.max_mtu) {
+        log_Printf(LogWARN, "MTU %ld: too large - max set to %d\n", long_val,
+                   l->lcp.cfg.max_mtu);
+        res = 1;
+        break;
+      }
+      break;
+    case 2:
+      if (strcasecmp(argp, "max") && strcasecmp(argp, "maximum")) {
+        res = -1;
+        break;
+      }
+      long_val = atol(arg->argv[arg->argn + 1]);
+      change = &l->lcp.cfg.max_mtu;
+      if (long_val > MAX_MTU) {
+        log_Printf(LogWARN, "MTU %ld: too large - maximum is %d\n", long_val,
+                   MAX_MTU);
+        res = 1;
+        break;
+      }
+      break;
+    default:
+      res = -1;
+      break;
+    }
+
+    if (res != 0)
+      break;
+
+    if (long_val && long_val < MIN_MTU) {
+      log_Printf(LogWARN, "MTU %ld: too small - min %d\n", long_val, MIN_MTU);
+      res = 1;
+      break;
+    } else if (long_val > MAX_MTU) {
+      log_Printf(LogWARN, "MTU %ld: too big - max %d\n", long_val, MAX_MTU);
+      res = 1;
+      break;
+    } else
+      *change = long_val;
+    if (l->lcp.cfg.mtu > *change)
+      l->lcp.cfg.mtu = *change;
+    break;
+
+  case VAR_OPENMODE:
+    if (strcasecmp(argp, "active") == 0)
+      cx->physical->link.lcp.cfg.openmode = arg->argc > arg->argn+1 ?
+        atoi(arg->argv[arg->argn+1]) : 1;
+    else if (strcasecmp(argp, "passive") == 0)
+      cx->physical->link.lcp.cfg.openmode = OPEN_PASSIVE;
+    else {
+      log_Printf(LogWARN, "%s: Invalid openmode\n", argp);
+      res = 1;
+    }
+    break;
+
+  case VAR_PHONE:
+    strncpy(cx->cfg.phone.list, argp, sizeof cx->cfg.phone.list - 1);
+    cx->cfg.phone.list[sizeof cx->cfg.phone.list - 1] = '\0';
+    cx->phone.alt = cx->phone.next = NULL;
+    break;
+
+  case VAR_HANGUP:
+    strncpy(cx->cfg.script.hangup, argp, sizeof cx->cfg.script.hangup - 1);
+    cx->cfg.script.hangup[sizeof cx->cfg.script.hangup - 1] = '\0';
+    break;
+
+  case VAR_IFQUEUE:
+    long_val = atol(argp);
+    arg->bundle->cfg.ifqueue = long_val < 0 ? 0 : long_val;
+    break;
+
+  case VAR_LOGOUT:
+    strncpy(cx->cfg.script.logout, argp, sizeof cx->cfg.script.logout - 1);
+    cx->cfg.script.logout[sizeof cx->cfg.script.logout - 1] = '\0';
+    break;
+
+  case VAR_IDLETIMEOUT:
+    if (arg->argc > arg->argn+2) {
+      log_Printf(LogWARN, "Too many idle timeout values\n");
+      res = 1;
+    } else if (arg->argc == arg->argn) {
+      log_Printf(LogWARN, "Too few idle timeout values\n");
+      res = 1;
+    } else {
+      unsigned long timeout, min;
+
+      timeout = strtoul(argp, NULL, 10);
+      min = arg->bundle->cfg.idle.min_timeout;
+      if (arg->argc == arg->argn + 2)
+	min = strtoul(arg->argv[arg->argn + 1], NULL, 10);
+      bundle_SetIdleTimer(arg->bundle, timeout, min);
+    }
+    break;
+    
+#ifndef NORADIUS
+  case VAR_RAD_ALIVE:
+    if (arg->argc > arg->argn + 2) {
+      log_Printf(LogWARN, "Too many RADIUS alive interval values\n");
+      res = 1;
+    } else if (arg->argc == arg->argn) {
+      log_Printf(LogWARN, "Too few RADIUS alive interval values\n");
+      res = 1;
+    } else {
+      arg->bundle->radius.alive.interval = atoi(argp);
+      if (arg->bundle->radius.alive.interval && !arg->bundle->radius.cfg.file) {
+        log_Printf(LogWARN, "rad_alive requires radius to be configured\n");
+	res = 1;
+      } else if (arg->bundle->ncp.ipcp.fsm.state == ST_OPENED) {
+	if (arg->bundle->radius.alive.interval)
+	  radius_StartTimer(arg->bundle);
+	else
+	  radius_StopTimer(&arg->bundle->radius);
+      }
+    }
+    break;
+#endif
+   
+  case VAR_LQRPERIOD:
+    long_val = atol(argp);
+    if (long_val < MIN_LQRPERIOD) {
+      log_Printf(LogWARN, "%ld: Invalid lqr period - min %d\n",
+                 long_val, MIN_LQRPERIOD);
+      res = 1;
+    } else
+      l->lcp.cfg.lqrperiod = long_val;
+    break;
+
+  case VAR_LCPRETRY:
+    res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn,
+                   &cx->physical->link.lcp.cfg.fsm.timeout,
+                   &cx->physical->link.lcp.cfg.fsm.maxreq,
+                   &cx->physical->link.lcp.cfg.fsm.maxtrm, DEF_FSMTRIES);
+    break;
+
+  case VAR_CHAPRETRY:
+    res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn,
+                   &cx->chap.auth.cfg.fsm.timeout,
+                   &cx->chap.auth.cfg.fsm.maxreq, NULL, DEF_FSMAUTHTRIES);
+    break;
+
+  case VAR_PAPRETRY:
+    res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn,
+                   &cx->pap.cfg.fsm.timeout, &cx->pap.cfg.fsm.maxreq,
+                   NULL, DEF_FSMAUTHTRIES);
+    break;
+
+  case VAR_CCPRETRY:
+    res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn,
+                   &l->ccp.cfg.fsm.timeout, &l->ccp.cfg.fsm.maxreq,
+                   &l->ccp.cfg.fsm.maxtrm, DEF_FSMTRIES);
+    break;
+
+  case VAR_IPCPRETRY:
+    res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn,
+                   &arg->bundle->ncp.ipcp.cfg.fsm.timeout,
+                   &arg->bundle->ncp.ipcp.cfg.fsm.maxreq,
+                   &arg->bundle->ncp.ipcp.cfg.fsm.maxtrm, DEF_FSMTRIES);
+    break;
+
+#ifndef NOINET6
+  case VAR_IPV6CPRETRY:
+    res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn,
+                   &arg->bundle->ncp.ipv6cp.cfg.fsm.timeout,
+                   &arg->bundle->ncp.ipv6cp.cfg.fsm.maxreq,
+                   &arg->bundle->ncp.ipv6cp.cfg.fsm.maxtrm, DEF_FSMTRIES);
+    break;
+#endif
+
+  case VAR_NBNS:
+  case VAR_DNS:
+    if (param == VAR_DNS) {
+      ipaddr = arg->bundle->ncp.ipcp.cfg.ns.dns;
+      ipaddr[0].s_addr = ipaddr[1].s_addr = INADDR_NONE;
+    } else {
+      ipaddr = arg->bundle->ncp.ipcp.cfg.ns.nbns;
+      ipaddr[0].s_addr = ipaddr[1].s_addr = INADDR_ANY;
+    }
+
+    if (arg->argc > arg->argn) {
+      ncpaddr_aton(ncpaddr, &arg->bundle->ncp, arg->argv[arg->argn]);
+      if (!ncpaddr_getip4(ncpaddr, ipaddr))
+        return -1;
+      if (arg->argc > arg->argn+1) {
+        ncpaddr_aton(ncpaddr + 1, &arg->bundle->ncp, arg->argv[arg->argn + 1]);
+        if (!ncpaddr_getip4(ncpaddr + 1, ipaddr + 1))
+          return -1;
+      }
+
+      if (ipaddr[0].s_addr == INADDR_ANY) {
+        ipaddr[0] = ipaddr[1];
+        ipaddr[1].s_addr = INADDR_ANY;
+      }
+      if (ipaddr[0].s_addr == INADDR_NONE) {
+        ipaddr[0] = ipaddr[1];
+        ipaddr[1].s_addr = INADDR_NONE;
+      }
+    }
+    break;
+
+  case VAR_CALLBACK:
+    cx->cfg.callback.opmask = 0;
+    for (dummyint = arg->argn; dummyint < arg->argc; dummyint++) {
+      if (!strcasecmp(arg->argv[dummyint], "auth"))
+        cx->cfg.callback.opmask |= CALLBACK_BIT(CALLBACK_AUTH);
+      else if (!strcasecmp(arg->argv[dummyint], "cbcp"))
+        cx->cfg.callback.opmask |= CALLBACK_BIT(CALLBACK_CBCP);
+      else if (!strcasecmp(arg->argv[dummyint], "e.164")) {
+        if (dummyint == arg->argc - 1)
+          log_Printf(LogWARN, "No E.164 arg (E.164 ignored) !\n");
+        else {
+          cx->cfg.callback.opmask |= CALLBACK_BIT(CALLBACK_E164);
+          strncpy(cx->cfg.callback.msg, arg->argv[++dummyint],
+                  sizeof cx->cfg.callback.msg - 1);
+          cx->cfg.callback.msg[sizeof cx->cfg.callback.msg - 1] = '\0';
+        }
+      } else if (!strcasecmp(arg->argv[dummyint], "none"))
+        cx->cfg.callback.opmask |= CALLBACK_BIT(CALLBACK_NONE);
+      else {
+        res = -1;
+        break;
+      }
+    }
+    if (cx->cfg.callback.opmask == CALLBACK_BIT(CALLBACK_NONE))
+      cx->cfg.callback.opmask = 0;
+    break;
+
+  case VAR_CBCP:
+    cx->cfg.cbcp.delay = 0;
+    *cx->cfg.cbcp.phone = '\0';
+    cx->cfg.cbcp.fsmretry = DEF_FSMRETRY;
+    if (arg->argc > arg->argn) {
+      strncpy(cx->cfg.cbcp.phone, arg->argv[arg->argn],
+              sizeof cx->cfg.cbcp.phone - 1);
+      cx->cfg.cbcp.phone[sizeof cx->cfg.cbcp.phone - 1] = '\0';
+      if (arg->argc > arg->argn + 1) {
+        cx->cfg.cbcp.delay = atoi(arg->argv[arg->argn + 1]);
+        if (arg->argc > arg->argn + 2) {
+          long_val = atol(arg->argv[arg->argn + 2]);
+          if (long_val < MIN_FSMRETRY)
+            log_Printf(LogWARN, "%ld: Invalid CBCP FSM retry period - min %d\n",
+                       long_val, MIN_FSMRETRY);
+          else
+            cx->cfg.cbcp.fsmretry = long_val;
+        }
+      }
+    }
+    break;
+
+  case VAR_CHOKED:
+    arg->bundle->cfg.choked.timeout = atoi(argp);
+    if (arg->bundle->cfg.choked.timeout <= 0)
+      arg->bundle->cfg.choked.timeout = CHOKED_TIMEOUT;
+    break;
+
+  case VAR_SENDPIPE:
+    long_val = atol(argp);
+    arg->bundle->ncp.cfg.sendpipe = long_val;
+    break;
+
+  case VAR_RECVPIPE:
+    long_val = atol(argp);
+    arg->bundle->ncp.cfg.recvpipe = long_val;
+    break;
+
+#ifndef NORADIUS
+  case VAR_RADIUS:
+    if (!*argp)
+      *arg->bundle->radius.cfg.file = '\0';
+    else if (access(argp, R_OK)) {
+      log_Printf(LogWARN, "%s: %s\n", argp, strerror(errno));
+      res = 1;
+      break;
+    } else {
+      strncpy(arg->bundle->radius.cfg.file, argp,
+              sizeof arg->bundle->radius.cfg.file - 1);
+      arg->bundle->radius.cfg.file
+        [sizeof arg->bundle->radius.cfg.file - 1] = '\0';
+    }
+    break;
+#endif
+
+  case VAR_CD:
+    if (*argp) {
+      if (strcasecmp(argp, "off")) {
+        long_val = atol(argp);
+        if (long_val < 0)
+          long_val = 0;
+        cx->physical->cfg.cd.delay = long_val;
+        cx->physical->cfg.cd.necessity = argp[strlen(argp)-1] == '!' ?
+          CD_REQUIRED : CD_VARIABLE;
+      } else
+        cx->physical->cfg.cd.necessity = CD_NOTREQUIRED;
+    } else {
+      cx->physical->cfg.cd.delay = 0;
+      cx->physical->cfg.cd.necessity = CD_DEFAULT;
+    }
+    break;
+
+  case VAR_PARITY:
+    if (arg->argc == arg->argn + 1)
+      res = physical_SetParity(arg->cx->physical, argp);
+    else {
+      log_Printf(LogWARN, "Parity value must be odd, even or none\n");
+      res = 1;
+    }
+    break;
+
+  case VAR_CRTSCTS:
+    if (strcasecmp(argp, "on") == 0)
+      physical_SetRtsCts(arg->cx->physical, 1);
+    else if (strcasecmp(argp, "off") == 0)
+      physical_SetRtsCts(arg->cx->physical, 0);
+    else {
+      log_Printf(LogWARN, "RTS/CTS value must be on or off\n");
+      res = 1;
+    }
+    break;
+
+  case VAR_URGENTPORTS:
+    if (arg->argn == arg->argc) {
+      ncp_SetUrgentTOS(&arg->bundle->ncp);
+      ncp_ClearUrgentTcpPorts(&arg->bundle->ncp);
+      ncp_ClearUrgentUdpPorts(&arg->bundle->ncp);
+    } else if (!strcasecmp(arg->argv[arg->argn], "udp")) {
+      ncp_SetUrgentTOS(&arg->bundle->ncp);
+      if (arg->argn == arg->argc - 1)
+        ncp_ClearUrgentUdpPorts(&arg->bundle->ncp);
+      else for (f = arg->argn + 1; f < arg->argc; f++)
+        if (*arg->argv[f] == '+')
+          ncp_AddUrgentUdpPort(&arg->bundle->ncp, atoi(arg->argv[f] + 1));
+        else if (*arg->argv[f] == '-')
+          ncp_RemoveUrgentUdpPort(&arg->bundle->ncp, atoi(arg->argv[f] + 1));
+        else {
+          if (f == arg->argn)
+            ncp_ClearUrgentUdpPorts(&arg->bundle->ncp);
+          ncp_AddUrgentUdpPort(&arg->bundle->ncp, atoi(arg->argv[f]));
+        }
+    } else if (arg->argn == arg->argc - 1 &&
+               !strcasecmp(arg->argv[arg->argn], "none")) {
+      ncp_ClearUrgentTcpPorts(&arg->bundle->ncp);
+      ncp_ClearUrgentUdpPorts(&arg->bundle->ncp);
+      ncp_ClearUrgentTOS(&arg->bundle->ncp);
+    } else {
+      ncp_SetUrgentTOS(&arg->bundle->ncp);
+      first = arg->argn;
+      if (!strcasecmp(arg->argv[first], "tcp") && ++first == arg->argc)
+        ncp_ClearUrgentTcpPorts(&arg->bundle->ncp);
+
+      for (f = first; f < arg->argc; f++)
+        if (*arg->argv[f] == '+')
+          ncp_AddUrgentTcpPort(&arg->bundle->ncp, atoi(arg->argv[f] + 1));
+        else if (*arg->argv[f] == '-')
+          ncp_RemoveUrgentTcpPort(&arg->bundle->ncp, atoi(arg->argv[f] + 1));
+        else {
+          if (f == first)
+            ncp_ClearUrgentTcpPorts(&arg->bundle->ncp);
+          ncp_AddUrgentTcpPort(&arg->bundle->ncp, atoi(arg->argv[f]));
+        }
+    }
+    break;
+
+  case VAR_PPPOE:
+    if (strcasecmp(argp, "3Com") == 0)
+      physical_SetPPPoEnonstandard(arg->cx->physical, 1);
+    else if (strcasecmp(argp, "standard") == 0)
+      physical_SetPPPoEnonstandard(arg->cx->physical, 0);
+    else {
+      log_Printf(LogWARN, "PPPoE standard value must be \"standard\" or \"3Com\"\n");
+      res = 1;
+    }
+    break;
+
+#ifndef NORADIUS
+  case VAR_PORT_ID:
+    if (strcasecmp(argp, "default") == 0)
+	    arg->bundle->radius.port_id_type = RPI_DEFAULT;
+    else if (strcasecmp(argp, "pid") == 0)
+	    arg->bundle->radius.port_id_type = RPI_PID;
+    else if (strcasecmp(argp, "ifnum") == 0)
+	    arg->bundle->radius.port_id_type = RPI_IFNUM; 
+    else if (strcasecmp(argp, "tunnum") == 0)
+	    arg->bundle->radius.port_id_type = RPI_TUNNUM;
+    else {
+	   log_Printf(LogWARN,
+		"RADIUS port id must be one of \"default\", \"pid\", \"ifnum\" or \"tunnum\"\n");
+	   res = 1;
+    }
+
+    if (arg->bundle->radius.port_id_type && !arg->bundle->radius.cfg.file) {
+	    log_Printf(LogWARN, "rad_port_id requires radius to be configured\n");
+	    res = 1;
+    }
+
+    break;
+#endif
+  }
+
+  return res;
+}
+
+static struct cmdtab const SetCommands[] = {
+  {"accmap", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+  "accmap value", "set accmap hex-value", (const void *)VAR_ACCMAP},
+  {"authkey", "key", SetVariable, LOCAL_AUTH,
+  "authentication key", "set authkey|key key", (const void *)VAR_AUTHKEY},
+  {"authname", NULL, SetVariable, LOCAL_AUTH,
+  "authentication name", "set authname name", (const void *)VAR_AUTHNAME},
+  {"autoload", NULL, SetVariable, LOCAL_AUTH,
+  "auto link [de]activation", "set autoload maxtime maxload mintime minload",
+  (const void *)VAR_AUTOLOAD},
+  {"bandwidth", NULL, mp_SetDatalinkBandwidth, LOCAL_AUTH | LOCAL_CX,
+  "datalink bandwidth", "set bandwidth value", NULL},
+  {"callback", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+  "callback control", "set callback [none|auth|cbcp|"
+  "E.164 *|number[,number]...]...", (const void *)VAR_CALLBACK},
+  {"cbcp", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+  "CBCP control", "set cbcp [*|phone[,phone...] [delay [timeout]]]",
+  (const void *)VAR_CBCP},
+  {"ccpretry", "ccpretries", SetVariable, LOCAL_AUTH | LOCAL_CX_OPT,
+   "CCP retries", "set ccpretry value [attempts]", (const void *)VAR_CCPRETRY},
+  {"cd", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "Carrier delay requirement",
+   "set cd value[!]", (const void *)VAR_CD},
+  {"chapretry", "chapretries", SetVariable, LOCAL_AUTH | LOCAL_CX,
+   "CHAP retries", "set chapretry value [attempts]",
+   (const void *)VAR_CHAPRETRY},
+  {"choked", NULL, SetVariable, LOCAL_AUTH,
+  "choked timeout", "set choked [secs]", (const void *)VAR_CHOKED},
+  {"ctsrts", "crtscts", SetVariable, LOCAL_AUTH | LOCAL_CX,
+   "Use hardware flow control", "set ctsrts [on|off]",
+   (const char *)VAR_CRTSCTS},
+  {"deflate", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX_OPT,
+  "deflate window sizes", "set deflate out-winsize in-winsize",
+  (const void *) VAR_WINSIZE},
+#ifndef NODES
+  {"mppe", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX_OPT,
+  "MPPE key size and state", "set mppe [40|56|128|* [stateful|stateless|*]]",
+  (const void *) VAR_MPPE},
+#endif
+  {"device", "line", SetVariable, LOCAL_AUTH | LOCAL_CX,
+  "physical device name", "set device|line device-name[,device-name]",
+  (const void *) VAR_DEVICE},
+  {"dial", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+  "dialing script", "set dial chat-script", (const void *) VAR_DIAL},
+  {"dns", NULL, SetVariable, LOCAL_AUTH, "Domain Name Server",
+  "set dns pri-addr [sec-addr]", (const void *)VAR_DNS},
+  {"enddisc", NULL, mp_SetEnddisc, LOCAL_AUTH,
+  "Endpoint Discriminator", "set enddisc [IP|magic|label|psn value]", NULL},
+  {"escape", NULL, SetEscape, LOCAL_AUTH | LOCAL_CX,
+  "escape characters", "set escape hex-digit ...", NULL},
+  {"filter", NULL, filter_Set, LOCAL_AUTH,
+  "packet filters", "set filter alive|dial|in|out rule-no permit|deny "
+  "[src_addr[/width]] [dst_addr[/width]] [proto "
+  "[src [lt|eq|gt port]] [dst [lt|eq|gt port]] [estab] [syn] [finrst]]", NULL},
+  {"hangup", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+  "hangup script", "set hangup chat-script", (const void *) VAR_HANGUP},
+  {"ifaddr", NULL, SetInterfaceAddr, LOCAL_AUTH, "destination address",
+  "set ifaddr [src-addr [dst-addr [netmask [trg-addr]]]]", NULL},
+  {"ifqueue", NULL, SetVariable, LOCAL_AUTH, "interface queue",
+  "set ifqueue packets", (const void *)VAR_IFQUEUE},
+  {"ipcpretry", "ipcpretries", SetVariable, LOCAL_AUTH, "IPCP retries",
+   "set ipcpretry value [attempts]", (const void *)VAR_IPCPRETRY},
+  {"ipv6cpretry", "ipv6cpretries", SetVariable, LOCAL_AUTH, "IPV6CP retries",
+   "set ipv6cpretry value [attempts]", (const void *)VAR_IPV6CPRETRY},
+  {"lcpretry", "lcpretries", SetVariable, LOCAL_AUTH | LOCAL_CX, "LCP retries",
+   "set lcpretry value [attempts]", (const void *)VAR_LCPRETRY},
+  {"log", NULL, log_SetLevel, LOCAL_AUTH, "log level",
+  "set log [local] [+|-]all|async|cbcp|ccp|chat|command|connect|debug|dns|hdlc|"
+  "id0|ipcp|lcp|lqm|phase|physical|radius|sync|tcp/ip|timer|tun...", NULL},
+  {"login", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+  "login script", "set login chat-script", (const void *) VAR_LOGIN},
+  {"logout", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+  "logout script", "set logout chat-script", (const void *) VAR_LOGOUT},
+  {"lqrperiod", "echoperiod", SetVariable, LOCAL_AUTH | LOCAL_CX_OPT,
+  "LQR period", "set lqr/echo period value", (const void *)VAR_LQRPERIOD},
+  {"mode", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "mode value",
+  "set mode interactive|auto|ddial|background", (const void *)VAR_MODE},
+  {"mrru", NULL, SetVariable, LOCAL_AUTH, "MRRU value",
+  "set mrru value", (const void *)VAR_MRRU},
+  {"mru", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+  "MRU value", "set mru [max[imum]] [value]", (const void *)VAR_MRU},
+  {"mtu", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+  "interface MTU value", "set mtu [max[imum]] [value]", (const void *)VAR_MTU},
+  {"nbns", NULL, SetVariable, LOCAL_AUTH, "NetBIOS Name Server",
+  "set nbns pri-addr [sec-addr]", (const void *)VAR_NBNS},
+  {"openmode", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "open mode",
+  "set openmode active|passive [secs]", (const void *)VAR_OPENMODE},
+  {"papretry", "papretries", SetVariable, LOCAL_AUTH | LOCAL_CX, "PAP retries",
+   "set papretry value [attempts]", (const void *)VAR_PAPRETRY},
+  {"parity", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "serial parity",
+   "set parity [odd|even|none]", (const void *)VAR_PARITY},
+  {"phone", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "telephone number(s)",
+  "set phone phone1[:phone2[...]]", (const void *)VAR_PHONE},
+  {"proctitle", "title", SetProcTitle, LOCAL_AUTH,
+  "Process title", "set proctitle [value]", NULL},
+#ifndef NORADIUS
+  {"radius", NULL, SetVariable, LOCAL_AUTH,
+  "RADIUS Config", "set radius cfgfile", (const void *)VAR_RADIUS},
+  {"rad_alive", NULL, SetVariable, LOCAL_AUTH,
+  "Raduis alive interval", "set rad_alive value",
+  (const void *)VAR_RAD_ALIVE},
+  {"rad_port_id", NULL, SetVariable, LOCAL_AUTH,
+  "NAS-Port-Id", "set rad_port_id [default|pid|ifnum|tunnum]", (const void *)VAR_PORT_ID},
+#endif
+  {"reconnect", NULL, datalink_SetReconnect, LOCAL_AUTH | LOCAL_CX,
+  "Reconnect timeout", "set reconnect value ntries", NULL},
+  {"recvpipe", NULL, SetVariable, LOCAL_AUTH,
+  "RECVPIPE value", "set recvpipe value", (const void *)VAR_RECVPIPE},
+  {"redial", NULL, datalink_SetRedial, LOCAL_AUTH | LOCAL_CX,
+  "Redial timeout", "set redial secs[+inc[-incmax]][.next] [attempts]", NULL},
+  {"sendpipe", NULL, SetVariable, LOCAL_AUTH,
+  "SENDPIPE value", "set sendpipe value", (const void *)VAR_SENDPIPE},
+  {"server", "socket", SetServer, LOCAL_AUTH, "diagnostic port",
+  "set server|socket TcpPort|LocalName|none|open|closed [password [mask]]",
+  NULL},
+  {"speed", NULL, SetModemSpeed, LOCAL_AUTH | LOCAL_CX,
+  "physical speed", "set speed value|sync", NULL},
+  {"stopped", NULL, SetStoppedTimeout, LOCAL_AUTH | LOCAL_CX,
+  "STOPPED timeouts", "set stopped [LCPseconds [CCPseconds]]", NULL},
+  {"timeout", NULL, SetVariable, LOCAL_AUTH, "Idle timeout",
+  "set timeout idletime", (const void *)VAR_IDLETIMEOUT},
+  {"urgent", NULL, SetVariable, LOCAL_AUTH, "urgent ports",
+  "set urgent [tcp|udp] [+|-]port...", (const void *)VAR_URGENTPORTS},
+  {"vj", NULL, ipcp_vjset, LOCAL_AUTH,
+  "vj values", "set vj slots|slotcomp [value]", NULL},
+  {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+  "Display this message", "set help|? [command]", SetCommands},
+  {"pppoe", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX,
+   "Connect using standard/3Com mode", "set pppoe [standard|3Com]",
+   (const char *)VAR_PPPOE},
+  {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+
+static int
+SetCommand(struct cmdargs const *arg)
+{
+  if (arg->argc > arg->argn)
+    FindExec(arg->bundle, SetCommands, arg->argc, arg->argn, arg->argv,
+             arg->prompt, arg->cx);
+  else if (arg->prompt)
+    prompt_Printf(arg->prompt, "Use `set ?' to get a list or `set ? <var>' for"
+	          " syntax help.\n");
+  else
+    log_Printf(LogWARN, "set command must have arguments\n");
+
+  return 0;
+}
+
+static int
+AddCommand(struct cmdargs const *arg)
+{
+  struct ncpaddr gw;
+  struct ncprange dest;
+  struct in_addr host;
+#ifndef NOINET6
+  struct in6_addr host6;
+#endif
+  int dest_default, gw_arg, addrs;
+
+  if (arg->argc != arg->argn+3 && arg->argc != arg->argn+2)
+    return -1;
+
+  addrs = 0;
+  dest_default = 0;
+  if (arg->argc == arg->argn + 2) {
+    if (!strcasecmp(arg->argv[arg->argn], "default"))
+      dest_default = 1;
+    else {
+      if (!ncprange_aton(&dest, &arg->bundle->ncp, arg->argv[arg->argn]))
+        return -1;
+      if (!strncasecmp(arg->argv[arg->argn], "MYADDR", 6))
+        addrs = ROUTE_DSTMYADDR;
+      else if (!strncasecmp(arg->argv[arg->argn], "MYADDR6", 7))
+        addrs = ROUTE_DSTMYADDR6;
+      else if (!strncasecmp(arg->argv[arg->argn], "HISADDR", 7))
+        addrs = ROUTE_DSTHISADDR;
+      else if (!strncasecmp(arg->argv[arg->argn], "HISADDR6", 8))
+        addrs = ROUTE_DSTHISADDR6;
+      else if (!strncasecmp(arg->argv[arg->argn], "DNS0", 4))
+        addrs = ROUTE_DSTDNS0;
+      else if (!strncasecmp(arg->argv[arg->argn], "DNS1", 4))
+        addrs = ROUTE_DSTDNS1;
+    }
+    gw_arg = 1;
+  } else {
+    if (strcasecmp(arg->argv[arg->argn], "MYADDR") == 0) {
+      addrs = ROUTE_DSTMYADDR;
+      host = arg->bundle->ncp.ipcp.my_ip;
+    } else if (strcasecmp(arg->argv[arg->argn], "HISADDR") == 0) {
+      addrs = ROUTE_DSTHISADDR;
+      host = arg->bundle->ncp.ipcp.peer_ip;
+    } else if (strcasecmp(arg->argv[arg->argn], "DNS0") == 0) {
+      addrs = ROUTE_DSTDNS0;
+      host = arg->bundle->ncp.ipcp.ns.dns[0];
+    } else if (strcasecmp(arg->argv[arg->argn], "DNS1") == 0) {
+      addrs = ROUTE_DSTDNS1;
+      host = arg->bundle->ncp.ipcp.ns.dns[1];
+    } else {
+      host = GetIpAddr(arg->argv[arg->argn]);
+      if (host.s_addr == INADDR_NONE) {
+        log_Printf(LogWARN, "%s: Invalid destination address\n",
+                   arg->argv[arg->argn]);
+        return -1;
+      }
+    }
+    ncprange_setip4(&dest, host, GetIpAddr(arg->argv[arg->argn + 1]));
+    gw_arg = 2;
+  }
+
+  if (strcasecmp(arg->argv[arg->argn + gw_arg], "HISADDR") == 0) {
+    ncpaddr_setip4(&gw, arg->bundle->ncp.ipcp.peer_ip);
+    addrs |= ROUTE_GWHISADDR;
+#ifndef NOINET6
+  } else if (strcasecmp(arg->argv[arg->argn + gw_arg], "HISADDR6") == 0) {
+    if (!ncpaddr_getip6(&arg->bundle->ncp.ipv6cp.hisaddr, &host6))
+      memset(&host6, '\0', sizeof host6);
+    ncpaddr_setip6(&gw, &host6);
+    addrs |= ROUTE_GWHISADDR6;
+#endif
+  } else {
+    if (!ncpaddr_aton(&gw, &arg->bundle->ncp, arg->argv[arg->argn + gw_arg])) {
+      log_Printf(LogWARN, "%s: Invalid gateway address\n",
+                 arg->argv[arg->argn + gw_arg]);
+      return -1;
+    }
+  }
+
+  if (dest_default)
+    ncprange_setdefault(&dest, ncpaddr_family(&gw));
+
+  if (rt_Set(arg->bundle, RTM_ADD, &dest, &gw, arg->cmd->args ? 1 : 0,
+             ((addrs & ROUTE_GWHISADDR) || (addrs & ROUTE_GWHISADDR6)) ? 1 : 0)
+      && addrs != ROUTE_STATIC)
+    route_Add(&arg->bundle->ncp.route, addrs, &dest, &gw);
+
+  return 0;
+}
+
+static int
+DeleteCommand(struct cmdargs const *arg)
+{
+  struct ncprange dest;
+  int addrs;
+
+  if (arg->argc == arg->argn+1) {
+    if(strcasecmp(arg->argv[arg->argn], "all") == 0) {
+      route_IfDelete(arg->bundle, 0);
+      route_DeleteAll(&arg->bundle->ncp.route);
+    } else {
+      addrs = 0;
+      if (strcasecmp(arg->argv[arg->argn], "MYADDR") == 0) {
+        ncprange_setip4host(&dest, arg->bundle->ncp.ipcp.my_ip);
+        addrs = ROUTE_DSTMYADDR;
+#ifndef NOINET6
+      } else if (strcasecmp(arg->argv[arg->argn], "MYADDR6") == 0) {
+        ncprange_sethost(&dest, &arg->bundle->ncp.ipv6cp.myaddr);
+        addrs = ROUTE_DSTMYADDR6;
+#endif
+      } else if (strcasecmp(arg->argv[arg->argn], "HISADDR") == 0) {
+        ncprange_setip4host(&dest, arg->bundle->ncp.ipcp.peer_ip);
+        addrs = ROUTE_DSTHISADDR;
+#ifndef NOINET6
+      } else if (strcasecmp(arg->argv[arg->argn], "HISADDR6") == 0) {
+        ncprange_sethost(&dest, &arg->bundle->ncp.ipv6cp.hisaddr);
+        addrs = ROUTE_DSTHISADDR6;
+#endif
+      } else if (strcasecmp(arg->argv[arg->argn], "DNS0") == 0) {
+        ncprange_setip4host(&dest, arg->bundle->ncp.ipcp.ns.dns[0]);
+        addrs = ROUTE_DSTDNS0;
+      } else if (strcasecmp(arg->argv[arg->argn], "DNS1") == 0) {
+        ncprange_setip4host(&dest, arg->bundle->ncp.ipcp.ns.dns[1]);
+        addrs = ROUTE_DSTDNS1;
+      } else {
+        ncprange_aton(&dest, &arg->bundle->ncp, arg->argv[arg->argn]);
+        addrs = ROUTE_STATIC;
+      }
+      rt_Set(arg->bundle, RTM_DELETE, &dest, NULL, arg->cmd->args ? 1 : 0, 0);
+      route_Delete(&arg->bundle->ncp.route, addrs, &dest);
+    }
+  } else
+    return -1;
+
+  return 0;
+}
+
+#ifndef NONAT
+static int
+NatEnable(struct cmdargs const *arg)
+{
+  if (arg->argc == arg->argn+1) {
+    if (strcasecmp(arg->argv[arg->argn], "yes") == 0) {
+      if (!arg->bundle->NatEnabled) {
+        if (arg->bundle->ncp.ipcp.fsm.state == ST_OPENED)
+          PacketAliasSetAddress(arg->bundle->ncp.ipcp.my_ip);
+        arg->bundle->NatEnabled = 1;
+      }
+      return 0;
+    } else if (strcasecmp(arg->argv[arg->argn], "no") == 0) {
+      arg->bundle->NatEnabled = 0;
+      opt_disable(arg->bundle, OPT_IFACEALIAS);
+      /* Don't iface_Clear() - there may be manually configured addresses */
+      return 0;
+    }
+  }
+
+  return -1;
+}
+
+
+static int
+NatOption(struct cmdargs const *arg)
+{
+  long param = (long)arg->cmd->args;
+
+  if (arg->argc == arg->argn+1) {
+    if (strcasecmp(arg->argv[arg->argn], "yes") == 0) {
+      if (arg->bundle->NatEnabled) {
+	PacketAliasSetMode(param, param);
+	return 0;
+      }
+      log_Printf(LogWARN, "nat not enabled\n");
+    } else if (strcmp(arg->argv[arg->argn], "no") == 0) {
+      if (arg->bundle->NatEnabled) {
+	PacketAliasSetMode(0, param);
+	return 0;
+      }
+      log_Printf(LogWARN, "nat not enabled\n");
+    }
+  }
+  return -1;
+}
+#endif /* #ifndef NONAT */
+
+static int
+LinkCommand(struct cmdargs const *arg)
+{
+  if (arg->argc > arg->argn+1) {
+    char namelist[LINE_LEN];
+    struct datalink *cx;
+    char *name;
+    int result = 0;
+
+    if (!strcmp(arg->argv[arg->argn], "*")) {
+      struct datalink *dl;
+
+      cx = arg->bundle->links;
+      while (cx) {
+        /* Watch it, the command could be a ``remove'' */
+        dl = cx->next;
+        FindExec(arg->bundle, Commands, arg->argc, arg->argn+1, arg->argv,
+                 arg->prompt, cx);
+        for (cx = arg->bundle->links; cx; cx = cx->next)
+          if (cx == dl)
+            break;		/* Pointer's still valid ! */
+      }
+    } else {
+      strncpy(namelist, arg->argv[arg->argn], sizeof namelist - 1);
+      namelist[sizeof namelist - 1] = '\0';
+      for(name = strtok(namelist, ", "); name; name = strtok(NULL,", "))
+        if (!bundle2datalink(arg->bundle, name)) {
+          log_Printf(LogWARN, "link: %s: Invalid link name\n", name);
+          return 1;
+        }
+
+      strncpy(namelist, arg->argv[arg->argn], sizeof namelist - 1);
+      namelist[sizeof namelist - 1] = '\0';
+      for(name = strtok(namelist, ", "); name; name = strtok(NULL,", ")) {
+        cx = bundle2datalink(arg->bundle, name);
+        if (cx)
+          FindExec(arg->bundle, Commands, arg->argc, arg->argn+1, arg->argv,
+                   arg->prompt, cx);
+        else {
+          log_Printf(LogWARN, "link: %s: Invalidated link name !\n", name);
+          result++;
+        }
+      }
+    }
+    return result;
+  }
+
+  log_Printf(LogWARN, "usage: %s\n", arg->cmd->syntax);
+  return 2;
+}
+
+struct link *
+command_ChooseLink(struct cmdargs const *arg)
+{
+  if (arg->cx)
+    return &arg->cx->physical->link;
+  else if (!arg->bundle->ncp.mp.cfg.mrru) {
+    struct datalink *dl = bundle2datalink(arg->bundle, NULL);
+    if (dl)
+      return &dl->physical->link;
+  }
+  return &arg->bundle->ncp.mp.link;
+}
+
+static const char *
+ident_cmd(const char *cmd, unsigned *keep, unsigned *add)
+{
+  const char *result;
+
+  switch (*cmd) {
+    case 'A':
+    case 'a':
+      result = "accept";
+      *keep = NEG_MYMASK;
+      *add = NEG_ACCEPTED;
+      break;
+    case 'D':
+    case 'd':
+      switch (cmd[1]) {
+        case 'E':
+        case 'e':
+          result = "deny";
+          *keep = NEG_MYMASK;
+          *add = 0;
+          break;
+        case 'I':
+        case 'i':
+          result = "disable";
+          *keep = NEG_HISMASK;
+          *add = 0;
+          break;
+        default:
+          return NULL;
+      }
+      break;
+    case 'E':
+    case 'e':
+      result = "enable";
+      *keep = NEG_HISMASK;
+      *add = NEG_ENABLED;
+      break;
+    default:
+      return NULL;
+  }
+
+  return result;
+}
+
+static int
+OptSet(struct cmdargs const *arg)
+{
+  int opt = (int)(long)arg->cmd->args;
+  unsigned keep;			/* Keep this opt */
+  unsigned add;				/* Add this opt */
+
+  if (ident_cmd(arg->argv[arg->argn - 2], &keep, &add) == NULL)
+    return 1;
+
+#ifndef NOINET6
+  if (add == NEG_ENABLED && opt == OPT_IPV6CP && !probe.ipv6_available) {
+    log_Printf(LogWARN, "IPv6 is not available on this machine\n");
+    return 1;
+  }
+#endif
+  if (!add && ((opt == OPT_NAS_IP_ADDRESS &&
+                !Enabled(arg->bundle, OPT_NAS_IDENTIFIER)) ||
+               (opt == OPT_NAS_IDENTIFIER &&
+                !Enabled(arg->bundle, OPT_NAS_IP_ADDRESS)))) {
+    log_Printf(LogWARN,
+               "Cannot disable both NAS-IP-Address and NAS-Identifier\n");
+    return 1;
+  }
+
+  if (add)
+    opt_enable(arg->bundle, opt);
+  else
+    opt_disable(arg->bundle, opt);
+
+  return 0;
+}
+
+static int
+IfaceAliasOptSet(struct cmdargs const *arg)
+{
+  unsigned long long save = arg->bundle->cfg.optmask;
+  int result = OptSet(arg);
+
+  if (result == 0)
+    if (Enabled(arg->bundle, OPT_IFACEALIAS) && !arg->bundle->NatEnabled) {
+      arg->bundle->cfg.optmask = save;
+      log_Printf(LogWARN, "Cannot enable iface-alias without NAT\n");
+      result = 2;
+    }
+
+  return result;
+}
+
+static int
+NegotiateSet(struct cmdargs const *arg)
+{
+  long param = (long)arg->cmd->args;
+  struct link *l = command_ChooseLink(arg);	/* LOCAL_CX_OPT uses this */
+  struct datalink *cx = arg->cx;	/* LOCAL_CX uses this */
+  const char *cmd;
+  unsigned keep;			/* Keep these bits */
+  unsigned add;				/* Add these bits */
+
+  if ((cmd = ident_cmd(arg->argv[arg->argn-2], &keep, &add)) == NULL)
+    return 1;
+
+  if ((arg->cmd->lauth & LOCAL_CX) && !cx) {
+    log_Printf(LogWARN, "%s %s: No context (use the `link' command)\n",
+              cmd, arg->cmd->name);
+    return 2;
+  } else if (cx && !(arg->cmd->lauth & (LOCAL_CX|LOCAL_CX_OPT))) {
+    log_Printf(LogWARN, "%s %s: Redundant context (%s) ignored\n",
+              cmd, arg->cmd->name, cx->name);
+    cx = NULL;
+  }
+
+  switch (param) {
+    case NEG_ACFCOMP:
+      cx->physical->link.lcp.cfg.acfcomp &= keep;
+      cx->physical->link.lcp.cfg.acfcomp |= add;
+      break;
+    case NEG_CHAP05:
+      cx->physical->link.lcp.cfg.chap05 &= keep;
+      cx->physical->link.lcp.cfg.chap05 |= add;
+      break;
+#ifndef NODES
+    case NEG_CHAP80:
+      cx->physical->link.lcp.cfg.chap80nt &= keep;
+      cx->physical->link.lcp.cfg.chap80nt |= add;
+      break;
+    case NEG_CHAP80LM:
+      cx->physical->link.lcp.cfg.chap80lm &= keep;
+      cx->physical->link.lcp.cfg.chap80lm |= add;
+      break;
+    case NEG_CHAP81:
+      cx->physical->link.lcp.cfg.chap81 &= keep;
+      cx->physical->link.lcp.cfg.chap81 |= add;
+      break;
+    case NEG_MPPE:
+      l->ccp.cfg.neg[CCP_NEG_MPPE] &= keep;
+      l->ccp.cfg.neg[CCP_NEG_MPPE] |= add;
+      break;
+#endif
+    case NEG_DEFLATE:
+      l->ccp.cfg.neg[CCP_NEG_DEFLATE] &= keep;
+      l->ccp.cfg.neg[CCP_NEG_DEFLATE] |= add;
+      break;
+    case NEG_DNS:
+      arg->bundle->ncp.ipcp.cfg.ns.dns_neg &= keep;
+      arg->bundle->ncp.ipcp.cfg.ns.dns_neg |= add;
+      break;
+    case NEG_ECHO:	/* probably misplaced in this function ! */
+      if (cx->physical->link.lcp.cfg.echo && !add) {
+        cx->physical->link.lcp.cfg.echo = 0;
+        cx->physical->hdlc.lqm.method &= ~LQM_ECHO;
+        if (cx->physical->hdlc.lqm.method & LQM_ECHO &&
+            !cx->physical->link.lcp.want_lqrperiod && 
+            cx->physical->hdlc.lqm.timer.load) {
+          cx->physical->hdlc.lqm.timer.load = 0;
+          lqr_StopTimer(cx->physical);
+        }
+      } else if (!cx->physical->link.lcp.cfg.echo && add) {
+        cx->physical->link.lcp.cfg.echo = 1;
+        cx->physical->hdlc.lqm.method |= LQM_ECHO;
+        cx->physical->hdlc.lqm.timer.load =
+	    cx->physical->link.lcp.cfg.lqrperiod * SECTICKS;
+        if (cx->physical->link.lcp.fsm.state == ST_OPENED)
+          (*cx->physical->hdlc.lqm.timer.func)(&cx->physical->link.lcp);
+      }
+      break;
+    case NEG_ENDDISC:
+      arg->bundle->ncp.mp.cfg.negenddisc &= keep;
+      arg->bundle->ncp.mp.cfg.negenddisc |= add;
+      break;
+    case NEG_LQR:
+      cx->physical->link.lcp.cfg.lqr &= keep;
+      cx->physical->link.lcp.cfg.lqr |= add;
+      break;
+    case NEG_PAP:
+      cx->physical->link.lcp.cfg.pap &= keep;
+      cx->physical->link.lcp.cfg.pap |= add;
+      break;
+    case NEG_PPPDDEFLATE:
+      l->ccp.cfg.neg[CCP_NEG_DEFLATE24] &= keep;
+      l->ccp.cfg.neg[CCP_NEG_DEFLATE24] |= add;
+      break;
+    case NEG_PRED1:
+      l->ccp.cfg.neg[CCP_NEG_PRED1] &= keep;
+      l->ccp.cfg.neg[CCP_NEG_PRED1] |= add;
+      break;
+    case NEG_PROTOCOMP:
+      cx->physical->link.lcp.cfg.protocomp &= keep;
+      cx->physical->link.lcp.cfg.protocomp |= add;
+      break;
+    case NEG_SHORTSEQ:
+      switch (bundle_Phase(arg->bundle)) {
+        case PHASE_DEAD:
+          break;
+        case PHASE_ESTABLISH:
+          /* Make sure none of our links are DATALINK_LCP or greater */
+          if (bundle_HighestState(arg->bundle) >= DATALINK_LCP) {
+            log_Printf(LogWARN, "shortseq: Only changable before"
+                       " LCP negotiations\n");
+            return 1;
+          }
+          break;
+        default:
+          log_Printf(LogWARN, "shortseq: Only changable at phase"
+                     " DEAD/ESTABLISH\n");
+          return 1;
+      }
+      arg->bundle->ncp.mp.cfg.shortseq &= keep;
+      arg->bundle->ncp.mp.cfg.shortseq |= add;
+      break;
+    case NEG_VJCOMP:
+      arg->bundle->ncp.ipcp.cfg.vj.neg &= keep;
+      arg->bundle->ncp.ipcp.cfg.vj.neg |= add;
+      break;
+  }
+
+  return 0;
+}
+
+static struct cmdtab const NegotiateCommands[] = {
+  {"echo", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX, "Send echo requests",
+  "disable|enable", (const void *)NEG_ECHO},
+  {"filter-decapsulation", NULL, OptSet, LOCAL_AUTH,
+  "filter on PPPoUDP payloads", "disable|enable",
+  (const void *)OPT_FILTERDECAP},
+  {"force-scripts", NULL, OptSet, LOCAL_AUTH,
+   "Force execution of the configured chat scripts", "disable|enable",
+   (const void *)OPT_FORCE_SCRIPTS},
+  {"idcheck", NULL, OptSet, LOCAL_AUTH, "Check FSM reply ids",
+  "disable|enable", (const void *)OPT_IDCHECK},
+  {"iface-alias", NULL, IfaceAliasOptSet, LOCAL_AUTH,
+  "retain interface addresses", "disable|enable",
+  (const void *)OPT_IFACEALIAS},
+#ifndef NOINET6
+  {"ipcp", NULL, OptSet, LOCAL_AUTH, "IP Network Control Protocol",
+  "disable|enable", (const void *)OPT_IPCP},
+  {"ipv6cp", NULL, OptSet, LOCAL_AUTH, "IPv6 Network Control Protocol",
+  "disable|enable", (const void *)OPT_IPV6CP},
+#endif
+  {"keep-session", NULL, OptSet, LOCAL_AUTH, "Retain device session leader",
+  "disable|enable", (const void *)OPT_KEEPSESSION},
+  {"loopback", NULL, OptSet, LOCAL_AUTH, "Loop packets for local iface",
+  "disable|enable", (const void *)OPT_LOOPBACK},
+  {"nas-ip-address", NULL, OptSet, LOCAL_AUTH, "Send NAS-IP-Address to RADIUS",
+  "disable|enable", (const void *)OPT_NAS_IP_ADDRESS},
+  {"nas-identifier", NULL, OptSet, LOCAL_AUTH, "Send NAS-Identifier to RADIUS",
+  "disable|enable", (const void *)OPT_NAS_IDENTIFIER},
+  {"passwdauth", NULL, OptSet, LOCAL_AUTH, "Use passwd file",
+  "disable|enable", (const void *)OPT_PASSWDAUTH},
+  {"proxy", NULL, OptSet, LOCAL_AUTH, "Create a proxy ARP entry",
+  "disable|enable", (const void *)OPT_PROXY},
+  {"proxyall", NULL, OptSet, LOCAL_AUTH, "Proxy ARP for all remote hosts",
+  "disable|enable", (const void *)OPT_PROXYALL},
+  {"sroutes", NULL, OptSet, LOCAL_AUTH, "Use sticky routes",
+  "disable|enable", (const void *)OPT_SROUTES},
+  {"tcpmssfixup", "mssfixup", OptSet, LOCAL_AUTH, "Modify MSS options",
+  "disable|enable", (const void *)OPT_TCPMSSFIXUP},
+  {"throughput", NULL, OptSet, LOCAL_AUTH, "Rolling throughput",
+  "disable|enable", (const void *)OPT_THROUGHPUT},
+  {"utmp", NULL, OptSet, LOCAL_AUTH, "Log connections in utmp",
+  "disable|enable", (const void *)OPT_UTMP},
+
+#ifndef NOINET6
+#define NEG_OPT_MAX 17	/* accept/deny allowed below and not above */
+#else
+#define NEG_OPT_MAX 15
+#endif
+
+  {"acfcomp", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+  "Address & Control field compression", "accept|deny|disable|enable",
+  (const void *)NEG_ACFCOMP},
+  {"chap", "chap05", NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+  "Challenge Handshake Authentication Protocol", "accept|deny|disable|enable",
+  (const void *)NEG_CHAP05},
+#ifndef NODES
+  {"mschap", "chap80nt", NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+  "Microsoft (NT) CHAP", "accept|deny|disable|enable",
+  (const void *)NEG_CHAP80},
+  {"LANMan", "chap80lm", NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+  "Microsoft (NT) CHAP", "accept|deny|disable|enable",
+  (const void *)NEG_CHAP80LM},
+  {"mschapv2", "chap81", NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+  "Microsoft CHAP v2", "accept|deny|disable|enable",
+  (const void *)NEG_CHAP81},
+  {"mppe", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX_OPT,
+  "MPPE encryption", "accept|deny|disable|enable",
+  (const void *)NEG_MPPE},
+#endif
+  {"deflate", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Deflate compression", "accept|deny|disable|enable",
+  (const void *)NEG_DEFLATE},
+  {"deflate24", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Deflate (type 24) compression", "accept|deny|disable|enable",
+  (const void *)NEG_PPPDDEFLATE},
+  {"dns", NULL, NegotiateSet, LOCAL_AUTH,
+  "DNS specification", "accept|deny|disable|enable", (const void *)NEG_DNS},
+  {"enddisc", NULL, NegotiateSet, LOCAL_AUTH, "ENDDISC negotiation",
+  "accept|deny|disable|enable", (const void *)NEG_ENDDISC},
+  {"lqr", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+  "Link Quality Reports", "accept|deny|disable|enable",
+  (const void *)NEG_LQR},
+  {"pap", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+  "Password Authentication protocol", "accept|deny|disable|enable",
+  (const void *)NEG_PAP},
+  {"pred1", "predictor1", NegotiateSet, LOCAL_AUTH | LOCAL_CX_OPT,
+  "Predictor 1 compression", "accept|deny|disable|enable",
+  (const void *)NEG_PRED1},
+  {"protocomp", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX,
+  "Protocol field compression", "accept|deny|disable|enable",
+  (const void *)NEG_PROTOCOMP},
+  {"shortseq", NULL, NegotiateSet, LOCAL_AUTH,
+  "MP Short Sequence Numbers", "accept|deny|disable|enable",
+  (const void *)NEG_SHORTSEQ},
+  {"vjcomp", NULL, NegotiateSet, LOCAL_AUTH,
+  "Van Jacobson header compression", "accept|deny|disable|enable",
+  (const void *)NEG_VJCOMP},
+  {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH,
+  "Display this message", "accept|deny|disable|enable help|? [value]",
+  NegotiateCommands},
+  {NULL, NULL, NULL, 0, NULL, NULL, NULL},
+};
+
+static int
+NegotiateCommand(struct cmdargs const *arg)
+{
+  if (arg->argc > arg->argn) {
+    char const *argv[3];
+    unsigned keep, add;
+    int n;
+
+    if ((argv[0] = ident_cmd(arg->argv[arg->argn-1], &keep, &add)) == NULL)
+      return -1;
+    argv[2] = NULL;
+
+    for (n = arg->argn; n < arg->argc; n++) {
+      argv[1] = arg->argv[n];
+      FindExec(arg->bundle, NegotiateCommands + (keep == NEG_HISMASK ?
+               0 : NEG_OPT_MAX), 2, 1, argv, arg->prompt, arg->cx);
+    }
+  } else if (arg->prompt)
+    prompt_Printf(arg->prompt, "Use `%s ?' to get a list.\n",
+	    arg->argv[arg->argn-1]);
+  else
+    log_Printf(LogWARN, "%s command must have arguments\n",
+              arg->argv[arg->argn] );
+
+  return 0;
+}
+
+const char *
+command_ShowNegval(unsigned val)
+{
+  switch (val&3) {
+    case 1: return "disabled & accepted";
+    case 2: return "enabled & denied";
+    case 3: return "enabled & accepted";
+  }
+  return "disabled & denied";
+}
+
+static int
+ClearCommand(struct cmdargs const *arg)
+{
+  struct pppThroughput *t;
+  struct datalink *cx;
+  int i, clear_type;
+
+  if (arg->argc < arg->argn + 1)
+    return -1;
+
+  if (strcasecmp(arg->argv[arg->argn], "physical") == 0) {
+    cx = arg->cx;
+    if (!cx)
+      cx = bundle2datalink(arg->bundle, NULL);
+    if (!cx) {
+      log_Printf(LogWARN, "A link must be specified for ``clear physical''\n");
+      return 1;
+    }
+    t = &cx->physical->link.stats.total;
+  } else if (strcasecmp(arg->argv[arg->argn], "ipcp") == 0)
+    t = &arg->bundle->ncp.ipcp.throughput;
+#ifndef NOINET6
+  else if (strcasecmp(arg->argv[arg->argn], "ipv6cp") == 0)
+    t = &arg->bundle->ncp.ipv6cp.throughput;
+#endif
+  else
+    return -1;
+
+  if (arg->argc > arg->argn + 1) {
+    clear_type = 0;
+    for (i = arg->argn + 1; i < arg->argc; i++)
+      if (strcasecmp(arg->argv[i], "overall") == 0)
+        clear_type |= THROUGHPUT_OVERALL;
+      else if (strcasecmp(arg->argv[i], "current") == 0)
+        clear_type |= THROUGHPUT_CURRENT;
+      else if (strcasecmp(arg->argv[i], "peak") == 0)
+        clear_type |= THROUGHPUT_PEAK;
+      else
+        return -1;
+  } else
+    clear_type = THROUGHPUT_ALL;
+
+  throughput_clear(t, clear_type, arg->prompt);
+  return 0;
+}
+
+static int
+RunListCommand(struct cmdargs const *arg)
+{
+  const char *cmd = arg->argc ? arg->argv[arg->argc - 1] : "???";
+
+#ifndef NONAT
+  if (arg->cmd->args == NatCommands &&
+      tolower(*arg->argv[arg->argn - 1]) == 'a') {
+    if (arg->prompt)
+      prompt_Printf(arg->prompt, "The alias command is deprecated\n");
+    else
+      log_Printf(LogWARN, "The alias command is deprecated\n");
+  }
+#endif
+
+  if (arg->argc > arg->argn)
+    FindExec(arg->bundle, arg->cmd->args, arg->argc, arg->argn, arg->argv,
+             arg->prompt, arg->cx);
+  else if (arg->prompt)
+    prompt_Printf(arg->prompt, "Use `%s help' to get a list or `%s help"
+                  " <option>' for syntax help.\n", cmd, cmd);
+  else
+    log_Printf(LogWARN, "%s command must have arguments\n", cmd);
+
+  return 0;
+}
+
+static int
+IfaceAddCommand(struct cmdargs const *arg)
+{
+  struct ncpaddr peer, addr;
+  struct ncprange ifa;
+  struct in_addr mask;
+  int n, how;
+
+  if (arg->argc == arg->argn + 1) {
+    if (!ncprange_aton(&ifa, NULL, arg->argv[arg->argn]))
+      return -1;
+    ncpaddr_init(&peer);
+  } else {
+    if (arg->argc == arg->argn + 2) {
+      if (!ncprange_aton(&ifa, NULL, arg->argv[arg->argn]))
+        return -1;
+      n = 1;
+    } else if (arg->argc == arg->argn + 3) {
+      if (!ncpaddr_aton(&addr, NULL, arg->argv[arg->argn]))
+        return -1;
+      if (ncpaddr_family(&addr) != AF_INET)
+        return -1;
+      ncprange_sethost(&ifa, &addr);
+      if (!ncpaddr_aton(&addr, NULL, arg->argv[arg->argn + 1]))
+        return -1;
+      if (!ncpaddr_getip4(&addr, &mask))
+        return -1;
+      if (!ncprange_setip4mask(&ifa, mask))
+        return -1;
+      n = 2;
+    } else
+      return -1;
+
+    if (!ncpaddr_aton(&peer, NULL, arg->argv[arg->argn + n]))
+      return -1;
+
+    if (ncprange_family(&ifa) != ncpaddr_family(&peer)) {
+      log_Printf(LogWARN, "IfaceAddCommand: src and dst address families"
+                 " differ\n");
+      return -1;
+    }
+  }
+
+  how = IFACE_ADD_LAST;
+  if (arg->cmd->args)
+    how |= IFACE_FORCE_ADD;
+
+  return !iface_Add(arg->bundle->iface, &arg->bundle->ncp, &ifa, &peer, how);
+}
+
+static int
+IfaceDeleteCommand(struct cmdargs const *arg)
+{
+  struct ncpaddr ifa;
+  struct in_addr ifa4;
+  int ok;
+
+  if (arg->argc != arg->argn + 1)
+    return -1;
+
+  if (!ncpaddr_aton(&ifa, NULL, arg->argv[arg->argn]))
+    return -1;
+
+  if (arg->bundle->ncp.ipcp.fsm.state == ST_OPENED &&
+      ncpaddr_getip4(&ifa, &ifa4) &&
+      arg->bundle->ncp.ipcp.my_ip.s_addr == ifa4.s_addr) {
+    log_Printf(LogWARN, "%s: Cannot remove active interface address\n",
+               ncpaddr_ntoa(&ifa));
+    return 1;
+  }
+
+  ok = iface_Delete(arg->bundle->iface, &arg->bundle->ncp, &ifa);
+  if (!ok) {
+    if (arg->cmd->args)
+      ok = 1;
+    else if (arg->prompt)
+      prompt_Printf(arg->prompt, "%s: No such interface address\n",
+                    ncpaddr_ntoa(&ifa));
+    else
+      log_Printf(LogWARN, "%s: No such interface address\n",
+                 ncpaddr_ntoa(&ifa));
+  }
+
+  return !ok;
+}
+
+static int
+IfaceClearCommand(struct cmdargs const *arg)
+{
+  int family, how;
+
+  family = 0;
+  if (arg->argc == arg->argn + 1) {
+    if (strcasecmp(arg->argv[arg->argn], "inet") == 0)
+      family = AF_INET;
+#ifndef NOINET6
+    else if (strcasecmp(arg->argv[arg->argn], "inet6") == 0)
+      family = AF_INET6;
+#endif
+    else
+      return -1;
+  } else if (arg->argc != arg->argn)
+    return -1;
+
+  how = arg->bundle->ncp.ipcp.fsm.state == ST_OPENED ||
+        arg->bundle->phys_type.all & PHYS_AUTO ?
+        IFACE_CLEAR_ALIASES : IFACE_CLEAR_ALL;
+  iface_Clear(arg->bundle->iface, &arg->bundle->ncp, family, how);
+
+  return 0;
+}
+
+static int
+SetProcTitle(struct cmdargs const *arg)
+{
+  static char title[LINE_LEN];
+  char *argv[MAXARGS];
+  int argc = arg->argc - arg->argn;
+
+  if (arg->argc <= arg->argn) {
+    SetTitle(NULL);
+    return 0;
+  }
+
+  if ((unsigned)argc >= sizeof argv / sizeof argv[0]) {
+    argc = sizeof argv / sizeof argv[0] - 1;
+    log_Printf(LogWARN, "Truncating proc title to %d args\n", argc);
+  }
+  command_Expand(argv, argc, arg->argv + arg->argn, arg->bundle, 1, getpid());
+  Concatinate(title, sizeof title, argc, (const char *const *)argv);
+  SetTitle(title);
+  command_Free(argc, argv);
+
+  return 0;
+}
diff --git a/src/command.h b/src/command.h
new file mode 100644
index 0000000..f29a036
--- /dev/null
+++ b/src/command.h
@@ -0,0 +1,75 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/command.h,v 1.23.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct cmdtab;
+struct bundle;
+struct datalink;
+struct prompt;
+
+struct cmdargs {
+  struct cmdtab const *cmdtab;		/* The entire command table */
+  struct cmdtab const *cmd;		/* This command entry */
+  int argc;				/* Number of arguments (excluding cmd */
+  int argn;				/* Argument to start processing from */
+  char const *const *argv;		/* Arguments */
+  struct bundle *bundle;		/* Our bundle */
+  struct datalink *cx;			/* Our context */
+  struct prompt *prompt;		/* Who executed us */
+};
+
+struct cmdtab {
+  const char *name;
+  const char *alias;
+  int (*func) (struct cmdargs const *);
+  u_char lauth;
+  const char *helpmes;
+  const char *syntax;
+  const void *args;
+};
+
+#define NEG_ACCEPTED (1)
+#define NEG_ENABLED (2)
+#define IsAccepted(x) ((x) & NEG_ACCEPTED)
+#define IsEnabled(x) ((x) & NEG_ENABLED)
+
+extern const char Version[];
+
+extern void command_Expand(char **, int, char const *const *, struct bundle *,
+                           int, pid_t);
+extern void command_Free(int, char **);
+extern int command_Expand_Interpret(char *, int, char *vector[MAXARGS], int);
+extern int command_Interpret(char *, int, char *vector[MAXARGS]);
+extern void command_Run(struct bundle *, int, char const *const *,
+                        struct prompt *, const char *, struct datalink *);
+extern int command_Decode(struct bundle *, char *, int, struct prompt *,
+                           const char *);
+extern struct link *command_ChooseLink(struct cmdargs const *);
+extern const char *command_ShowNegval(unsigned);
+
diff --git a/src/datalink.c b/src/datalink.c
new file mode 100644
index 0000000..c715823
--- /dev/null
+++ b/src/datalink.c
@@ -0,0 +1,1478 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/datalink.c,v 1.77.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "descriptor.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "async.h"
+#include "throughput.h"
+#include "ccp.h"
+#include "link.h"
+#include "physical.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "chat.h"
+#include "auth.h"
+#include "prompt.h"
+#include "proto.h"
+#include "pap.h"
+#include "chap.h"
+#include "command.h"
+#include "cbcp.h"
+#include "datalink.h"
+
+static void datalink_LoginDone(struct datalink *);
+static void datalink_NewState(struct datalink *, unsigned);
+static char *datalink_NextName(struct datalink *);
+
+static void
+datalink_OpenTimeout(void *v)
+{
+  struct datalink *dl = (struct datalink *)v;
+
+  timer_Stop(&dl->dial.timer);
+  if (dl->state == DATALINK_OPENING)
+    log_Printf(LogCHAT, "%s: Redial timer expired.\n", dl->name);
+}
+
+static int
+datalink_StartDialTimer(struct datalink *dl, int Timeout)
+{
+  int result = Timeout;
+
+  timer_Stop(&dl->dial.timer);
+  if (Timeout < 0)
+    result = (random() % DIAL_TIMEOUT) + 1;
+  dl->dial.timer.load = result ? result * SECTICKS : 1;
+  dl->dial.timer.func = datalink_OpenTimeout;
+  dl->dial.timer.name = "dial";
+  dl->dial.timer.arg = dl;
+  timer_Start(&dl->dial.timer);
+  if (dl->state == DATALINK_OPENING)
+    log_Printf(LogPHASE, "%s: Enter pause (%d) for redialing.\n",
+               dl->name, result);
+  return result;
+}
+
+static void
+datalink_HangupDone(struct datalink *dl)
+{
+  if (dl->physical->type == PHYS_DEDICATED && !dl->bundle->CleaningUp &&
+      dl->physical->fd != -1) {
+    /* Don't close our device if the link is dedicated */
+    datalink_LoginDone(dl);
+    return;
+  }
+
+  chat_Finish(&dl->chat);
+  physical_Close(dl->physical);
+  dl->phone.chosen = "N/A";
+
+  if (dl->cbcp.required) {
+    log_Printf(LogPHASE, "Call peer back on %s\n", dl->cbcp.fsm.phone);
+    dl->cfg.callback.opmask = 0;
+    strncpy(dl->cfg.phone.list, dl->cbcp.fsm.phone,
+            sizeof dl->cfg.phone.list - 1);
+    dl->cfg.phone.list[sizeof dl->cfg.phone.list - 1] = '\0';
+    dl->phone.alt = dl->phone.next = NULL;
+    dl->reconnect_tries = dl->cfg.reconnect.max;
+    dl->dial.tries = dl->cfg.dial.max;
+    dl->dial.incs = 0;
+    dl->script.run = 1;
+    dl->script.packetmode = 1;
+    if (!physical_SetMode(dl->physical, PHYS_BACKGROUND))
+      log_Printf(LogERROR, "Oops - can't change mode to BACKGROUND (gulp) !\n");
+    bundle_LinksRemoved(dl->bundle);
+    /* if dial.timeout is < 0 (random), we don't override fsm.delay */
+    if (dl->cbcp.fsm.delay < dl->cfg.dial.timeout)
+      dl->cbcp.fsm.delay = dl->cfg.dial.timeout;
+    datalink_StartDialTimer(dl, dl->cbcp.fsm.delay);
+    cbcp_Down(&dl->cbcp);
+    datalink_NewState(dl, DATALINK_OPENING);
+    if (bundle_Phase(dl->bundle) == PHASE_DEAD ||
+        bundle_Phase(dl->bundle) == PHASE_TERMINATE)
+      bundle_NewPhase(dl->bundle, PHASE_ESTABLISH);
+  } else if (dl->bundle->CleaningUp ||
+      (dl->physical->type == PHYS_DIRECT) ||
+      ((!dl->dial.tries || (dl->dial.tries < 0 && !dl->reconnect_tries)) &&
+       !(dl->physical->type & (PHYS_DDIAL|PHYS_DEDICATED)))) {
+    datalink_NewState(dl, DATALINK_CLOSED);
+    dl->dial.tries = -1;
+    dl->dial.incs = 0;
+    dl->reconnect_tries = 0;
+    bundle_LinkClosed(dl->bundle, dl);
+    if (!dl->bundle->CleaningUp &&
+        !(dl->physical->type & (PHYS_DIRECT|PHYS_BACKGROUND|PHYS_FOREGROUND)))
+      datalink_StartDialTimer(dl, datalink_GetDialTimeout(dl));
+  } else {
+    datalink_NewState(dl, DATALINK_OPENING);
+    if (bundle_Phase(dl->bundle) == PHASE_DEAD ||
+        bundle_Phase(dl->bundle) == PHASE_TERMINATE)
+      bundle_NewPhase(dl->bundle, PHASE_ESTABLISH);
+    if (dl->dial.tries < 0) {
+      datalink_StartDialTimer(dl, dl->cfg.reconnect.timeout);
+      dl->dial.tries = dl->cfg.dial.max;
+      dl->dial.incs = 0;
+      dl->reconnect_tries--;
+      log_Printf(LogCHAT, "%s: Reconnect try %d of %d\n",
+                 dl->name, dl->cfg.reconnect.max - dl->reconnect_tries,
+                 dl->cfg.reconnect.max);
+      bundle_Notify(dl->bundle, EX_RECONNECT);
+    } else {
+      if (dl->phone.next == NULL)
+        datalink_StartDialTimer(dl, datalink_GetDialTimeout(dl));
+      else
+        datalink_StartDialTimer(dl, dl->cfg.dial.next_timeout);
+      bundle_Notify(dl->bundle, EX_REDIAL);
+    }
+  }
+}
+
+const char *
+datalink_ChoosePhoneNumber(struct datalink *dl)
+{
+  char *phone;
+
+  if (dl->phone.alt == NULL) {
+    if (dl->phone.next == NULL) {
+      strncpy(dl->phone.list, dl->cfg.phone.list, sizeof dl->phone.list - 1);
+      dl->phone.list[sizeof dl->phone.list - 1] = '\0';
+      if (*dl->phone.list == '\0')
+        return "";
+      dl->phone.next = dl->phone.list;
+    }
+    dl->phone.alt = strsep(&dl->phone.next, ":");
+  }
+  phone = strsep(&dl->phone.alt, "|");
+  dl->phone.chosen = *phone ? phone : "[NONE]";
+  if (*phone)
+    log_Printf(LogCHAT, "Phone: %s\n", phone);
+  return phone;
+}
+
+static void
+datalink_LoginDone(struct datalink *dl)
+{
+  chat_Finish(&dl->chat);
+
+  if (!dl->script.packetmode) {
+    dl->dial.tries = -1;
+    dl->dial.incs = 0;
+    datalink_NewState(dl, DATALINK_READY);
+  } else if (!physical_Raw(dl->physical)) {
+    dl->dial.tries = 0;
+    log_Printf(LogWARN, "datalink_LoginDone: Not connected.\n");
+    if (dl->script.run) {
+      datalink_NewState(dl, DATALINK_LOGOUT);
+      if (!chat_Setup(&dl->chat, dl->cfg.script.logout, NULL))
+        log_Printf(LogWARN, "Invalid logout script\n");
+    } else {
+      physical_StopDeviceTimer(dl->physical);
+      if (dl->physical->type == PHYS_DEDICATED)
+        /* force a redial timeout */
+        physical_Close(dl->physical);
+      datalink_HangupDone(dl);
+    }
+  } else {
+    dl->dial.tries = -1;
+    dl->dial.incs = 0;
+
+    hdlc_Init(&dl->physical->hdlc, &dl->physical->link.lcp);
+    async_Setup(&dl->physical->async);
+
+    lcp_Setup(&dl->physical->link.lcp, dl->state == DATALINK_READY ?
+              0 : dl->physical->link.lcp.cfg.openmode);
+    ccp_Setup(&dl->physical->link.ccp);
+
+    datalink_NewState(dl, DATALINK_LCP);
+    fsm_Up(&dl->physical->link.lcp.fsm);
+    fsm_Open(&dl->physical->link.lcp.fsm);
+  }
+}
+
+static int
+datalink_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e,
+                   int *n)
+{
+  struct datalink *dl = descriptor2datalink(d);
+  int result;
+
+  result = 0;
+  switch (dl->state) {
+    case DATALINK_CLOSED:
+      if ((dl->physical->type & (PHYS_DIRECT|PHYS_DEDICATED|PHYS_BACKGROUND|
+                                 PHYS_FOREGROUND|PHYS_DDIAL)) &&
+          !dl->bundle->CleaningUp)
+        /*
+         * Our first time in - DEDICATED & DDIAL never come down, and
+         * DIRECT, FOREGROUND & BACKGROUND get deleted when they enter
+         * DATALINK_CLOSED.  Go to DATALINK_OPENING via datalink_Up()
+         * and fall through.
+         */
+        datalink_Up(dl, 1, 1);
+      else
+        break;
+      /* FALLTHROUGH */
+
+    case DATALINK_OPENING:
+      if (dl->dial.timer.state != TIMER_RUNNING) {
+        if (--dl->dial.tries < 0)
+          dl->dial.tries = 0;
+        if (physical_Open(dl->physical) >= 0) {
+          log_WritePrompts(dl, "%s: Entering terminal mode on %s\r\n"
+                           "Type `~?' for help\r\n", dl->name,
+                           dl->physical->name.full);
+          if (dl->script.run) {
+            datalink_NewState(dl, DATALINK_DIAL);
+            if (!chat_Setup(&dl->chat, dl->cfg.script.dial,
+                            *dl->cfg.script.dial ?
+                            datalink_ChoosePhoneNumber(dl) : ""))
+              log_Printf(LogWARN, "Invalid dial script\n");
+            if (!(dl->physical->type & (PHYS_DDIAL|PHYS_DEDICATED)) &&
+                dl->cfg.dial.max)
+              log_Printf(LogCHAT, "%s: Dial attempt %u of %d\n",
+                        dl->name, dl->cfg.dial.max - dl->dial.tries,
+                        dl->cfg.dial.max);
+          } else
+            datalink_NewState(dl, DATALINK_CARRIER);
+          return datalink_UpdateSet(d, r, w, e, n);
+        } else {
+          if (!(dl->physical->type & (PHYS_DDIAL|PHYS_DEDICATED)) &&
+              dl->cfg.dial.max)
+            log_Printf(LogCHAT, "Failed to open device (attempt %u of %d)\n",
+                       dl->cfg.dial.max - dl->dial.tries, dl->cfg.dial.max);
+          else
+            log_Printf(LogCHAT, "Failed to open device\n");
+
+          if (dl->bundle->CleaningUp ||
+              (!(dl->physical->type & (PHYS_DDIAL|PHYS_DEDICATED)) &&
+               dl->cfg.dial.max && dl->dial.tries == 0)) {
+            datalink_NewState(dl, DATALINK_CLOSED);
+            dl->reconnect_tries = 0;
+            dl->dial.tries = -1;
+            log_WritePrompts(dl, "Failed to open %s\n",
+                             dl->physical->name.full);
+            bundle_LinkClosed(dl->bundle, dl);
+          }
+          if (!dl->bundle->CleaningUp) {
+            int timeout;
+
+            timeout = datalink_StartDialTimer(dl, datalink_GetDialTimeout(dl));
+            bundle_Notify(dl->bundle, EX_REDIAL);
+            log_WritePrompts(dl, "Failed to open %s, pause %d seconds\n",
+                             dl->physical->name.full, timeout);
+          }
+        }
+      }
+      break;
+
+    case DATALINK_CARRIER:
+      /* Wait for carrier on the device */
+      switch (physical_AwaitCarrier(dl->physical)) {
+        case CARRIER_PENDING:
+          log_Printf(LogDEBUG, "Waiting for carrier\n");
+          return 0;	/* A device timer is running to wake us up again */
+
+        case CARRIER_OK:
+          if (dl->script.run) {
+            datalink_NewState(dl, DATALINK_LOGIN);
+            if (!chat_Setup(&dl->chat, dl->cfg.script.login, NULL))
+              log_Printf(LogWARN, "Invalid login script\n");
+          } else
+            datalink_LoginDone(dl);
+          return datalink_UpdateSet(d, r, w, e, n);
+
+        case CARRIER_LOST:
+          physical_Offline(dl->physical);	/* Is this required ? */
+          if (dl->script.run) {
+            datalink_NewState(dl, DATALINK_HANGUP);
+            if (!chat_Setup(&dl->chat, dl->cfg.script.hangup, NULL))
+              log_Printf(LogWARN, "Invalid hangup script\n");
+            return datalink_UpdateSet(d, r, w, e, n);
+          } else {
+            datalink_HangupDone(dl);
+            return 0;	/* Maybe bundle_CleanDatalinks() has something to do */
+          }
+      }
+
+    case DATALINK_HANGUP:
+    case DATALINK_DIAL:
+    case DATALINK_LOGOUT:
+    case DATALINK_LOGIN:
+      result = descriptor_UpdateSet(&dl->chat.desc, r, w, e, n);
+      switch (dl->chat.state) {
+        case CHAT_DONE:
+          /* script succeeded */
+          switch(dl->state) {
+            case DATALINK_HANGUP:
+              datalink_HangupDone(dl);
+              break;
+            case DATALINK_DIAL:
+              datalink_NewState(dl, DATALINK_CARRIER);
+              return datalink_UpdateSet(d, r, w, e, n);
+            case DATALINK_LOGOUT:
+              datalink_NewState(dl, DATALINK_HANGUP);
+              physical_Offline(dl->physical);
+              if (!chat_Setup(&dl->chat, dl->cfg.script.hangup, NULL))
+                log_Printf(LogWARN, "Invalid hangup script\n");
+              return datalink_UpdateSet(d, r, w, e, n);
+            case DATALINK_LOGIN:
+              dl->phone.alt = NULL;
+              datalink_LoginDone(dl);
+              return datalink_UpdateSet(d, r, w, e, n);
+          }
+          break;
+        case CHAT_FAILED:
+          /* Going down - script failed */
+          log_Printf(LogWARN, "Chat script failed\n");
+          switch(dl->state) {
+            case DATALINK_HANGUP:
+              datalink_HangupDone(dl);
+              break;
+            case DATALINK_DIAL:
+            case DATALINK_LOGOUT:
+            case DATALINK_LOGIN:
+              datalink_NewState(dl, DATALINK_HANGUP);
+              physical_Offline(dl->physical);
+              if (!chat_Setup(&dl->chat, dl->cfg.script.hangup, NULL))
+                log_Printf(LogWARN, "Invalid hangup script\n");
+              return datalink_UpdateSet(d, r, w, e, n);
+          }
+          break;
+      }
+      break;
+
+    case DATALINK_READY:
+    case DATALINK_LCP:
+    case DATALINK_AUTH:
+    case DATALINK_CBCP:
+    case DATALINK_OPEN:
+      result = descriptor_UpdateSet(&dl->chap.desc, r, w, e, n) +
+               descriptor_UpdateSet(&dl->physical->desc, r, w, e, n);
+      break;
+  }
+  return result;
+}
+
+int
+datalink_RemoveFromSet(struct datalink *dl, fd_set *r, fd_set *w, fd_set *e)
+{
+  return physical_RemoveFromSet(dl->physical, r, w, e);
+}
+
+static int
+datalink_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+  struct datalink *dl = descriptor2datalink(d);
+
+  switch (dl->state) {
+    case DATALINK_CLOSED:
+    case DATALINK_OPENING:
+      break;
+
+    case DATALINK_HANGUP:
+    case DATALINK_DIAL:
+    case DATALINK_LOGOUT:
+    case DATALINK_LOGIN:
+      return descriptor_IsSet(&dl->chat.desc, fdset);
+
+    case DATALINK_READY:
+    case DATALINK_LCP:
+    case DATALINK_AUTH:
+    case DATALINK_CBCP:
+    case DATALINK_OPEN:
+      return descriptor_IsSet(&dl->chap.desc, fdset) ? 1 :
+             descriptor_IsSet(&dl->physical->desc, fdset);
+  }
+  return 0;
+}
+
+static void
+datalink_Read(struct fdescriptor *d, struct bundle *bundle, const fd_set *fdset)
+{
+  struct datalink *dl = descriptor2datalink(d);
+
+  switch (dl->state) {
+    case DATALINK_CLOSED:
+    case DATALINK_OPENING:
+      break;
+
+    case DATALINK_HANGUP:
+    case DATALINK_DIAL:
+    case DATALINK_LOGOUT:
+    case DATALINK_LOGIN:
+      descriptor_Read(&dl->chat.desc, bundle, fdset);
+      break;
+
+    case DATALINK_READY:
+    case DATALINK_LCP:
+    case DATALINK_AUTH:
+    case DATALINK_CBCP:
+    case DATALINK_OPEN:
+      if (descriptor_IsSet(&dl->chap.desc, fdset))
+        descriptor_Read(&dl->chap.desc, bundle, fdset);
+      if (descriptor_IsSet(&dl->physical->desc, fdset))
+        descriptor_Read(&dl->physical->desc, bundle, fdset);
+      break;
+  }
+}
+
+static int
+datalink_Write(struct fdescriptor *d, struct bundle *bundle,
+               const fd_set *fdset)
+{
+  struct datalink *dl = descriptor2datalink(d);
+  int result = 0;
+
+  switch (dl->state) {
+    case DATALINK_CLOSED:
+    case DATALINK_OPENING:
+      break;
+
+    case DATALINK_HANGUP:
+    case DATALINK_DIAL:
+    case DATALINK_LOGOUT:
+    case DATALINK_LOGIN:
+      if ((result = descriptor_Write(&dl->chat.desc, bundle, fdset)) == -1) {
+        datalink_ComeDown(dl, CLOSE_NORMAL);
+        result = 0;
+      }
+      break;
+
+    case DATALINK_READY:
+    case DATALINK_LCP:
+    case DATALINK_AUTH:
+    case DATALINK_CBCP:
+    case DATALINK_OPEN:
+      if (descriptor_IsSet(&dl->chap.desc, fdset))
+        switch (descriptor_Write(&dl->chap.desc, bundle, fdset)) {
+        case -1:
+          datalink_ComeDown(dl, CLOSE_NORMAL);
+          break;
+        case 1:
+          result++;
+        }
+      if (descriptor_IsSet(&dl->physical->desc, fdset))
+        switch (descriptor_Write(&dl->physical->desc, bundle, fdset)) {
+        case -1:
+          datalink_ComeDown(dl, CLOSE_NORMAL);
+          break;
+        case 1:
+          result++;
+        }
+      break;
+  }
+
+  return result;
+}
+
+void
+datalink_ComeDown(struct datalink *dl, int how)
+{
+  int stayonline;
+
+  if (how == CLOSE_LCP)
+    datalink_DontHangup(dl);
+  else if (how == CLOSE_STAYDOWN)
+    datalink_StayDown(dl);
+
+  stayonline = dl->stayonline;
+  dl->stayonline = 0;
+
+  if (dl->state >= DATALINK_READY && stayonline) {
+    physical_StopDeviceTimer(dl->physical);
+    datalink_NewState(dl, DATALINK_READY);
+  } else if (dl->state != DATALINK_CLOSED && dl->state != DATALINK_HANGUP) {
+    physical_Offline(dl->physical);
+    if (dl->script.run && dl->state != DATALINK_OPENING) {
+      if (dl->state == DATALINK_LOGOUT) {
+        datalink_NewState(dl, DATALINK_HANGUP);
+        if (!chat_Setup(&dl->chat, dl->cfg.script.hangup, NULL))
+          log_Printf(LogWARN, "Invalid hangup script\n");
+      } else {
+        datalink_NewState(dl, DATALINK_LOGOUT);
+        if (!chat_Setup(&dl->chat, dl->cfg.script.logout, NULL))
+          log_Printf(LogWARN, "Invalid logout script\n");
+      }
+    } else
+      datalink_HangupDone(dl);
+  }
+}
+
+static void
+datalink_LayerStart(void *v, struct fsm *fp)
+{
+  /* The given FSM is about to start up ! */
+  struct datalink *dl = (struct datalink *)v;
+
+  if (fp->proto == PROTO_LCP)
+    (*dl->parent->LayerStart)(dl->parent->object, fp);
+}
+
+static void
+datalink_LayerUp(void *v, struct fsm *fp)
+{
+  /* The given fsm is now up */
+  struct datalink *dl = (struct datalink *)v;
+  struct lcp *lcp = &dl->physical->link.lcp;
+
+  if (fp->proto == PROTO_LCP) {
+    datalink_GotAuthname(dl, "");
+    lcp->auth_ineed = lcp->want_auth;
+    lcp->auth_iwait = lcp->his_auth;
+    if (lcp->his_auth || lcp->want_auth) {
+      if (bundle_Phase(dl->bundle) != PHASE_NETWORK)
+        bundle_NewPhase(dl->bundle, PHASE_AUTHENTICATE);
+      log_Printf(LogPHASE, "%s: his = %s, mine = %s\n", dl->name,
+                Auth2Nam(lcp->his_auth, lcp->his_authtype),
+                Auth2Nam(lcp->want_auth, lcp->want_authtype));
+      if (lcp->his_auth == PROTO_PAP)
+        auth_StartReq(&dl->pap);
+      if (lcp->want_auth == PROTO_CHAP)
+        auth_StartReq(&dl->chap.auth);
+    } else
+      datalink_AuthOk(dl);
+  } else if (fp->proto == PROTO_CCP)
+    (*dl->parent->LayerUp)(dl->parent->object, &dl->physical->link.ccp.fsm);
+}
+
+static void
+datalink_AuthReInit(struct datalink *dl)
+{
+  auth_StopTimer(&dl->pap);
+  auth_StopTimer(&dl->chap.auth);
+  chap_ReInit(&dl->chap);
+}
+
+void
+datalink_GotAuthname(struct datalink *dl, const char *name)
+{
+  strncpy(dl->peer.authname, name, sizeof dl->peer.authname - 1);
+  dl->peer.authname[sizeof dl->peer.authname - 1] = '\0';
+}
+
+void
+datalink_NCPUp(struct datalink *dl)
+{
+  int ccpok = ccp_SetOpenMode(&dl->physical->link.ccp);
+
+  if (dl->physical->link.lcp.want_mrru && dl->physical->link.lcp.his_mrru) {
+    /* we've authenticated in multilink mode ! */
+    switch (mp_Up(&dl->bundle->ncp.mp, dl)) {
+      case MP_LINKSENT:
+        /* We've handed the link off to another ppp (well, we will soon) ! */
+        return;
+      case MP_UP:
+        /* First link in the bundle */
+        auth_Select(dl->bundle, dl->peer.authname);
+        bundle_CalculateBandwidth(dl->bundle);
+        /* FALLTHROUGH */
+      case MP_ADDED:
+        /* We're in multilink mode ! */
+        dl->physical->link.ccp.fsm.open_mode = OPEN_PASSIVE;	/* override */
+        bundle_CalculateBandwidth(dl->bundle);
+        break;
+      case MP_FAILED:
+        datalink_AuthNotOk(dl);
+        return;
+    }
+  } else if (bundle_Phase(dl->bundle) == PHASE_NETWORK) {
+    log_Printf(LogPHASE, "%s: Already in NETWORK phase\n", dl->name);
+    datalink_NewState(dl, DATALINK_OPEN);
+    bundle_CalculateBandwidth(dl->bundle);
+    (*dl->parent->LayerUp)(dl->parent->object, &dl->physical->link.lcp.fsm);
+    return;
+  } else {
+    dl->bundle->ncp.mp.peer = dl->peer;
+    ncp_SetLink(&dl->bundle->ncp, &dl->physical->link);
+    auth_Select(dl->bundle, dl->peer.authname);
+  }
+
+  if (ccpok) {
+    fsm_Up(&dl->physical->link.ccp.fsm);
+    fsm_Open(&dl->physical->link.ccp.fsm);
+  }
+  datalink_NewState(dl, DATALINK_OPEN);
+  bundle_NewPhase(dl->bundle, PHASE_NETWORK);
+  (*dl->parent->LayerUp)(dl->parent->object, &dl->physical->link.lcp.fsm);
+}
+
+void
+datalink_CBCPComplete(struct datalink *dl)
+{
+  datalink_NewState(dl, DATALINK_LCP);
+  datalink_AuthReInit(dl);
+  fsm_Close(&dl->physical->link.lcp.fsm);
+}
+
+void
+datalink_CBCPFailed(struct datalink *dl)
+{
+  cbcp_Down(&dl->cbcp);
+  datalink_CBCPComplete(dl);
+}
+
+void
+datalink_AuthOk(struct datalink *dl)
+{
+  if ((dl->physical->link.lcp.his_callback.opmask &
+       CALLBACK_BIT(CALLBACK_CBCP) ||
+       dl->physical->link.lcp.want_callback.opmask &
+       CALLBACK_BIT(CALLBACK_CBCP)) &&
+      !(dl->physical->link.lcp.want_callback.opmask &
+        CALLBACK_BIT(CALLBACK_AUTH))) {
+    /* We must have agreed CBCP if AUTH isn't there any more */
+    datalink_NewState(dl, DATALINK_CBCP);
+    cbcp_Up(&dl->cbcp);
+  } else if (dl->physical->link.lcp.want_callback.opmask) {
+    /* It's not CBCP */
+    log_Printf(LogPHASE, "%s: Shutdown and await peer callback\n", dl->name);
+    datalink_NewState(dl, DATALINK_LCP);
+    datalink_AuthReInit(dl);
+    fsm_Close(&dl->physical->link.lcp.fsm);
+  } else
+    switch (dl->physical->link.lcp.his_callback.opmask) {
+      case 0:
+        datalink_NCPUp(dl);
+        break;
+
+      case CALLBACK_BIT(CALLBACK_AUTH):
+        auth_SetPhoneList(dl->peer.authname, dl->cbcp.fsm.phone,
+                          sizeof dl->cbcp.fsm.phone);
+        if (*dl->cbcp.fsm.phone == '\0' || !strcmp(dl->cbcp.fsm.phone, "*")) {
+          log_Printf(LogPHASE, "%s: %s cannot be called back\n", dl->name,
+                     dl->peer.authname);
+          *dl->cbcp.fsm.phone = '\0';
+        } else {
+          char *ptr = strchr(dl->cbcp.fsm.phone, ',');
+          if (ptr)
+            *ptr = '\0';	/* Call back on the first number */
+          log_Printf(LogPHASE, "%s: Calling peer back on %s\n", dl->name,
+                     dl->cbcp.fsm.phone);
+          dl->cbcp.required = 1;
+        }
+        dl->cbcp.fsm.delay = 0;
+        datalink_NewState(dl, DATALINK_LCP);
+        datalink_AuthReInit(dl);
+        fsm_Close(&dl->physical->link.lcp.fsm);
+        break;
+
+      case CALLBACK_BIT(CALLBACK_E164):
+        strncpy(dl->cbcp.fsm.phone, dl->physical->link.lcp.his_callback.msg,
+                sizeof dl->cbcp.fsm.phone - 1);
+        dl->cbcp.fsm.phone[sizeof dl->cbcp.fsm.phone - 1] = '\0';
+        log_Printf(LogPHASE, "%s: Calling peer back on %s\n", dl->name,
+                   dl->cbcp.fsm.phone);
+        dl->cbcp.required = 1;
+        dl->cbcp.fsm.delay = 0;
+        datalink_NewState(dl, DATALINK_LCP);
+        datalink_AuthReInit(dl);
+        fsm_Close(&dl->physical->link.lcp.fsm);
+        break;
+
+      default:
+        log_Printf(LogPHASE, "%s: Oops - Should have NAK'd peer callback !\n",
+                   dl->name);
+        datalink_NewState(dl, DATALINK_LCP);
+        datalink_AuthReInit(dl);
+        fsm_Close(&dl->physical->link.lcp.fsm);
+        break;
+    }
+}
+
+void
+datalink_AuthNotOk(struct datalink *dl)
+{
+  datalink_NewState(dl, DATALINK_LCP);
+  datalink_AuthReInit(dl);
+  fsm_Close(&dl->physical->link.lcp.fsm);
+}
+
+static void
+datalink_LayerDown(void *v, struct fsm *fp)
+{
+  /* The given FSM has been told to come down */
+  struct datalink *dl = (struct datalink *)v;
+
+  if (fp->proto == PROTO_LCP) {
+    switch (dl->state) {
+      case DATALINK_OPEN:
+        peerid_Init(&dl->peer);
+        fsm2initial(&dl->physical->link.ccp.fsm);
+        datalink_NewState(dl, DATALINK_LCP);  /* before parent TLD */
+        (*dl->parent->LayerDown)(dl->parent->object, fp);
+        /* FALLTHROUGH (just in case) */
+
+      case DATALINK_CBCP:
+        if (!dl->cbcp.required)
+          cbcp_Down(&dl->cbcp);
+        /* FALLTHROUGH (just in case) */
+
+      case DATALINK_AUTH:
+        timer_Stop(&dl->pap.authtimer);
+        timer_Stop(&dl->chap.auth.authtimer);
+    }
+    datalink_NewState(dl, DATALINK_LCP);
+    datalink_AuthReInit(dl);
+  }
+}
+
+static void
+datalink_LayerFinish(void *v, struct fsm *fp)
+{
+  /* The given fsm is now down */
+  struct datalink *dl = (struct datalink *)v;
+
+  if (fp->proto == PROTO_LCP) {
+    fsm2initial(fp);
+    (*dl->parent->LayerFinish)(dl->parent->object, fp);
+    datalink_ComeDown(dl, CLOSE_NORMAL);
+  } else if (fp->state == ST_CLOSED && fp->open_mode == OPEN_PASSIVE)
+    fsm_Open(fp);		/* CCP goes to ST_STOPPED */
+}
+
+struct datalink *
+datalink_Create(const char *name, struct bundle *bundle, int type)
+{
+  struct datalink *dl;
+
+  dl = (struct datalink *)malloc(sizeof(struct datalink));
+  if (dl == NULL)
+    return dl;
+
+  dl->desc.type = DATALINK_DESCRIPTOR;
+  dl->desc.UpdateSet = datalink_UpdateSet;
+  dl->desc.IsSet = datalink_IsSet;
+  dl->desc.Read = datalink_Read;
+  dl->desc.Write = datalink_Write;
+
+  dl->state = DATALINK_CLOSED;
+
+  *dl->cfg.script.dial = '\0';
+  *dl->cfg.script.login = '\0';
+  *dl->cfg.script.logout = '\0';
+  *dl->cfg.script.hangup = '\0';
+  *dl->cfg.phone.list = '\0';
+  *dl->phone.list = '\0';
+  dl->phone.next = NULL;
+  dl->phone.alt = NULL;
+  dl->phone.chosen = "N/A";
+  dl->stayonline = 0;
+  dl->script.run = 1;
+  dl->script.packetmode = 1;
+  mp_linkInit(&dl->mp);
+
+  dl->bundle = bundle;
+  dl->next = NULL;
+
+  memset(&dl->dial.timer, '\0', sizeof dl->dial.timer);
+
+  dl->dial.tries = 0;
+  dl->cfg.dial.max = 1;
+  dl->cfg.dial.next_timeout = DIAL_NEXT_TIMEOUT;
+  dl->cfg.dial.timeout = DIAL_TIMEOUT;
+  dl->cfg.dial.inc = 0;
+  dl->cfg.dial.maxinc = 10;
+
+  dl->reconnect_tries = 0;
+  dl->cfg.reconnect.max = 0;
+  dl->cfg.reconnect.timeout = RECONNECT_TIMEOUT;
+
+  dl->cfg.callback.opmask = 0;
+  dl->cfg.cbcp.delay = 0;
+  *dl->cfg.cbcp.phone = '\0';
+  dl->cfg.cbcp.fsmretry = DEF_FSMRETRY;
+
+  dl->name = strdup(name);
+  peerid_Init(&dl->peer);
+  dl->parent = &bundle->fsm;
+  dl->fsmp.LayerStart = datalink_LayerStart;
+  dl->fsmp.LayerUp = datalink_LayerUp;
+  dl->fsmp.LayerDown = datalink_LayerDown;
+  dl->fsmp.LayerFinish = datalink_LayerFinish;
+  dl->fsmp.object = dl;
+
+  if ((dl->physical = physical_Create(dl, type)) == NULL) {
+    free(dl->name);
+    free(dl);
+    return NULL;
+  }
+
+  pap_Init(&dl->pap, dl->physical);
+  chap_Init(&dl->chap, dl->physical);
+  cbcp_Init(&dl->cbcp, dl->physical);
+
+  memset(&dl->chat, '\0', sizeof dl->chat);	/* Force buf{start,end} reset */
+  chat_Init(&dl->chat, dl->physical);
+
+  log_Printf(LogPHASE, "%s: Created in %s state\n",
+             dl->name, datalink_State(dl));
+
+  return dl;
+}
+
+struct datalink *
+datalink_Clone(struct datalink *odl, const char *name)
+{
+  struct datalink *dl;
+
+  dl = (struct datalink *)malloc(sizeof(struct datalink));
+  if (dl == NULL)
+    return dl;
+
+  dl->desc.type = DATALINK_DESCRIPTOR;
+  dl->desc.UpdateSet = datalink_UpdateSet;
+  dl->desc.IsSet = datalink_IsSet;
+  dl->desc.Read = datalink_Read;
+  dl->desc.Write = datalink_Write;
+
+  dl->state = DATALINK_CLOSED;
+
+  memcpy(&dl->cfg, &odl->cfg, sizeof dl->cfg);
+  mp_linkInit(&dl->mp);
+  *dl->phone.list = '\0';
+  dl->phone.next = NULL;
+  dl->phone.alt = NULL;
+  dl->phone.chosen = "N/A";
+  dl->bundle = odl->bundle;
+  dl->next = NULL;
+  memset(&dl->dial.timer, '\0', sizeof dl->dial.timer);
+  dl->dial.tries = 0;
+  dl->reconnect_tries = 0;
+  dl->name = strdup(name);
+  peerid_Init(&dl->peer);
+  dl->parent = odl->parent;
+  memcpy(&dl->fsmp, &odl->fsmp, sizeof dl->fsmp);
+  dl->fsmp.object = dl;
+
+  if ((dl->physical = physical_Create(dl, PHYS_INTERACTIVE)) == NULL) {
+    free(dl->name);
+    free(dl);
+    return NULL;
+  }
+  pap_Init(&dl->pap, dl->physical);
+  dl->pap.cfg = odl->pap.cfg;
+
+  chap_Init(&dl->chap, dl->physical);
+  dl->chap.auth.cfg = odl->chap.auth.cfg;
+
+  memcpy(&dl->physical->cfg, &odl->physical->cfg, sizeof dl->physical->cfg);
+  memcpy(&dl->physical->link.lcp.cfg, &odl->physical->link.lcp.cfg,
+         sizeof dl->physical->link.lcp.cfg);
+  memcpy(&dl->physical->link.ccp.cfg, &odl->physical->link.ccp.cfg,
+         sizeof dl->physical->link.ccp.cfg);
+  memcpy(&dl->physical->async.cfg, &odl->physical->async.cfg,
+         sizeof dl->physical->async.cfg);
+
+  cbcp_Init(&dl->cbcp, dl->physical);
+
+  memset(&dl->chat, '\0', sizeof dl->chat);	/* Force buf{start,end} reset */
+  chat_Init(&dl->chat, dl->physical);
+
+  log_Printf(LogPHASE, "%s: Cloned in %s state\n",
+             dl->name, datalink_State(dl));
+
+  return dl;
+}
+
+struct datalink *
+datalink_Destroy(struct datalink *dl)
+{
+  struct datalink *result;
+
+  if (dl->state != DATALINK_CLOSED) {
+    log_Printf(LogERROR, "Oops, destroying a datalink in state %s\n",
+              datalink_State(dl));
+    switch (dl->state) {
+      case DATALINK_HANGUP:
+      case DATALINK_DIAL:
+      case DATALINK_LOGIN:
+        chat_Finish(&dl->chat);		/* Gotta blat the timers ! */
+        break;
+    }
+  }
+
+  chat_Destroy(&dl->chat);
+  timer_Stop(&dl->dial.timer);
+  result = dl->next;
+  physical_Destroy(dl->physical);
+  free(dl->name);
+  free(dl);
+
+  return result;
+}
+
+void
+datalink_Up(struct datalink *dl, int runscripts, int packetmode)
+{
+  if (!Enabled(dl->bundle, OPT_FORCE_SCRIPTS) &&
+      (dl->physical->type & (PHYS_DIRECT|PHYS_DEDICATED)))
+    /* Ignore scripts */
+    runscripts = 0;
+
+  switch (dl->state) {
+    case DATALINK_CLOSED:
+      if (bundle_Phase(dl->bundle) == PHASE_DEAD ||
+          bundle_Phase(dl->bundle) == PHASE_TERMINATE)
+        bundle_NewPhase(dl->bundle, PHASE_ESTABLISH);
+      datalink_NewState(dl, DATALINK_OPENING);
+      dl->reconnect_tries =
+        dl->physical->type == PHYS_DIRECT ? 0 : dl->cfg.reconnect.max;
+      dl->dial.tries = dl->cfg.dial.max;
+      dl->script.run = runscripts;
+      dl->script.packetmode = packetmode;
+      break;
+
+    case DATALINK_OPENING:
+      if (!dl->script.run && runscripts)
+        dl->script.run = 1;
+      /* FALLTHROUGH */
+
+    case DATALINK_DIAL:
+    case DATALINK_LOGIN:
+    case DATALINK_READY:
+      if (!dl->script.packetmode && packetmode) {
+        dl->script.packetmode = 1;
+        if (dl->state == DATALINK_READY) {
+          dl->script.run = 0;
+          datalink_NewState(dl, DATALINK_CARRIER);
+        }
+      }
+      break;
+  }
+}
+
+void
+datalink_Close(struct datalink *dl, int how)
+{
+  /* Please close */
+  switch (dl->state) {
+    case DATALINK_OPEN:
+      peerid_Init(&dl->peer);
+      fsm2initial(&dl->physical->link.ccp.fsm);
+      /* FALLTHROUGH */
+
+    case DATALINK_CBCP:
+    case DATALINK_AUTH:
+    case DATALINK_LCP:
+      datalink_AuthReInit(dl);
+      if (how == CLOSE_LCP)
+        datalink_DontHangup(dl);
+      else if (how == CLOSE_STAYDOWN)
+        datalink_StayDown(dl);
+      fsm_Close(&dl->physical->link.lcp.fsm);
+      break;
+
+    default:
+      datalink_ComeDown(dl, how);
+  }
+}
+
+void
+datalink_Down(struct datalink *dl, int how)
+{
+  /* Carrier is lost */
+  switch (dl->state) {
+    case DATALINK_OPEN:
+      peerid_Init(&dl->peer);
+      fsm2initial(&dl->physical->link.ccp.fsm);
+      /* FALLTHROUGH */
+
+    case DATALINK_CBCP:
+    case DATALINK_AUTH:
+    case DATALINK_LCP:
+      fsm2initial(&dl->physical->link.lcp.fsm);
+      if (dl->state == DATALINK_OPENING)
+        return;			/* we're doing a callback... */
+      /* FALLTHROUGH */
+
+    default:
+      datalink_ComeDown(dl, how);
+  }
+}
+
+void
+datalink_StayDown(struct datalink *dl)
+{
+  dl->dial.tries = -1;
+  dl->reconnect_tries = 0;
+  dl->stayonline = 0;
+}
+
+void
+datalink_DontHangup(struct datalink *dl)
+{
+  dl->dial.tries = -1;
+  dl->reconnect_tries = 0;
+  dl->stayonline = dl->state >= DATALINK_LCP ? 1 : 0;
+}
+
+int
+datalink_Show(struct cmdargs const *arg)
+{
+  prompt_Printf(arg->prompt, "Name: %s\n", arg->cx->name);
+  prompt_Printf(arg->prompt, " State:              %s\n",
+                datalink_State(arg->cx));
+  prompt_Printf(arg->prompt, " Peer name:          ");
+  if (*arg->cx->peer.authname)
+    prompt_Printf(arg->prompt, "%s\n", arg->cx->peer.authname);
+  else if (arg->cx->state == DATALINK_OPEN)
+    prompt_Printf(arg->prompt, "None requested\n");
+  else
+    prompt_Printf(arg->prompt, "N/A\n");
+  prompt_Printf(arg->prompt, " Discriminator:      %s\n",
+                mp_Enddisc(arg->cx->peer.enddisc.class,
+                           arg->cx->peer.enddisc.address,
+                           arg->cx->peer.enddisc.len));
+
+  prompt_Printf(arg->prompt, "\nDefaults:\n");
+  prompt_Printf(arg->prompt, " Phone List:         %s\n",
+                arg->cx->cfg.phone.list);
+  if (arg->cx->cfg.dial.max)
+    prompt_Printf(arg->prompt, " Dial tries:         %d, delay ",
+                  arg->cx->cfg.dial.max);
+  else
+    prompt_Printf(arg->prompt, " Dial tries:         infinite, delay ");
+  if (arg->cx->cfg.dial.next_timeout >= 0)
+    prompt_Printf(arg->prompt, "%ds/", arg->cx->cfg.dial.next_timeout);
+  else
+    prompt_Printf(arg->prompt, "random/");
+  if (arg->cx->cfg.dial.timeout >= 0)
+    prompt_Printf(arg->prompt, "%ds\n", arg->cx->cfg.dial.timeout);
+  else
+    prompt_Printf(arg->prompt, "random\n");
+  prompt_Printf(arg->prompt, " Reconnect tries:    %d, delay ",
+                arg->cx->cfg.reconnect.max);
+  if (arg->cx->cfg.reconnect.timeout > 0)
+    prompt_Printf(arg->prompt, "%ds\n", arg->cx->cfg.reconnect.timeout);
+  else
+    prompt_Printf(arg->prompt, "random\n");
+  prompt_Printf(arg->prompt, " Callback %s ", arg->cx->physical->type ==
+                PHYS_DIRECT ?  "accepted: " : "requested:");
+  if (!arg->cx->cfg.callback.opmask)
+    prompt_Printf(arg->prompt, "none\n");
+  else {
+    int comma = 0;
+
+    if (arg->cx->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_NONE)) {
+      prompt_Printf(arg->prompt, "none");
+      comma = 1;
+    }
+    if (arg->cx->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_AUTH)) {
+      prompt_Printf(arg->prompt, "%sauth", comma ? ", " : "");
+      comma = 1;
+    }
+    if (arg->cx->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_E164)) {
+      prompt_Printf(arg->prompt, "%sE.164", comma ? ", " : "");
+      if (arg->cx->physical->type != PHYS_DIRECT)
+        prompt_Printf(arg->prompt, " (%s)", arg->cx->cfg.callback.msg);
+      comma = 1;
+    }
+    if (arg->cx->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_CBCP)) {
+      prompt_Printf(arg->prompt, "%scbcp\n", comma ? ", " : "");
+      prompt_Printf(arg->prompt, " CBCP:               delay: %ds\n",
+                    arg->cx->cfg.cbcp.delay);
+      prompt_Printf(arg->prompt, "                     phone: ");
+      if (!strcmp(arg->cx->cfg.cbcp.phone, "*")) {
+        if (arg->cx->physical->type & PHYS_DIRECT)
+          prompt_Printf(arg->prompt, "Caller decides\n");
+        else
+          prompt_Printf(arg->prompt, "Dialback server decides\n");
+      } else
+        prompt_Printf(arg->prompt, "%s\n", arg->cx->cfg.cbcp.phone);
+      prompt_Printf(arg->prompt, "                     timeout: %lds\n",
+                    arg->cx->cfg.cbcp.fsmretry);
+    } else
+      prompt_Printf(arg->prompt, "\n");
+  }
+
+  prompt_Printf(arg->prompt, " Dial Script:        %s\n",
+                arg->cx->cfg.script.dial);
+  prompt_Printf(arg->prompt, " Login Script:       %s\n",
+                arg->cx->cfg.script.login);
+  prompt_Printf(arg->prompt, " Logout Script:      %s\n",
+                arg->cx->cfg.script.logout);
+  prompt_Printf(arg->prompt, " Hangup Script:      %s\n",
+                arg->cx->cfg.script.hangup);
+  return 0;
+}
+
+int
+datalink_SetReconnect(struct cmdargs const *arg)
+{
+  if (arg->argc == arg->argn+2) {
+    arg->cx->cfg.reconnect.timeout = atoi(arg->argv[arg->argn]);
+    arg->cx->cfg.reconnect.max = atoi(arg->argv[arg->argn+1]);
+    return 0;
+  }
+  return -1;
+}
+
+int
+datalink_SetRedial(struct cmdargs const *arg)
+{
+  const char *sep, *osep;
+  int timeout, inc, maxinc, tries;
+
+  if (arg->argc == arg->argn+1 || arg->argc == arg->argn+2) {
+    if (strncasecmp(arg->argv[arg->argn], "random", 6) == 0 &&
+	(arg->argv[arg->argn][6] == '\0' || arg->argv[arg->argn][6] == '.')) {
+      arg->cx->cfg.dial.timeout = -1;
+      randinit();
+    } else {
+      timeout = atoi(arg->argv[arg->argn]);
+
+      if (timeout >= 0)
+	arg->cx->cfg.dial.timeout = timeout;
+      else {
+	log_Printf(LogWARN, "Invalid redial timeout\n");
+	return -1;
+      }
+    }
+
+    sep = strchr(arg->argv[arg->argn], '+');
+    if (sep) {
+      inc = atoi(++sep);
+      osep = sep;
+      if (inc >= 0)
+        arg->cx->cfg.dial.inc = inc;
+      else {
+        log_Printf(LogWARN, "Invalid timeout increment\n");
+        return -1;
+      }
+      sep = strchr(sep, '-');
+      if (sep) {
+        maxinc = atoi(++sep);
+        if (maxinc >= 0)
+          arg->cx->cfg.dial.maxinc = maxinc;
+        else {
+          log_Printf(LogWARN, "Invalid maximum timeout increments\n");
+          return -1;
+        }
+      } else {
+        /* Default timeout increment */
+        arg->cx->cfg.dial.maxinc = 10;
+        sep = osep;
+      }
+    } else {
+      /* Default timeout increment & max increment */
+      arg->cx->cfg.dial.inc = 0;
+      arg->cx->cfg.dial.maxinc = 10;
+      sep = arg->argv[arg->argn];
+    }
+
+    sep = strchr(sep, '.');
+    if (sep) {
+      if (strcasecmp(++sep, "random") == 0) {
+	arg->cx->cfg.dial.next_timeout = -1;
+	randinit();
+      } else {
+	timeout = atoi(sep);
+	if (timeout >= 0)
+	  arg->cx->cfg.dial.next_timeout = timeout;
+	else {
+	  log_Printf(LogWARN, "Invalid next redial timeout\n");
+	  return -1;
+	}
+      }
+    } else
+      /* Default next timeout */
+      arg->cx->cfg.dial.next_timeout = DIAL_NEXT_TIMEOUT;
+
+    if (arg->argc == arg->argn+2) {
+      tries = atoi(arg->argv[arg->argn+1]);
+
+      if (tries >= 0) {
+	arg->cx->cfg.dial.max = tries;
+      } else {
+	log_Printf(LogWARN, "Invalid retry value\n");
+	return 1;
+      }
+    }
+    return 0;
+  }
+
+  return -1;
+}
+
+static const char * const states[] = {
+  "closed",
+  "opening",
+  "hangup",
+  "dial",
+  "carrier",
+  "logout",
+  "login",
+  "ready",
+  "lcp",
+  "auth",
+  "cbcp",
+  "open"
+};
+
+const char *
+datalink_State(struct datalink *dl)
+{
+  if (dl->state >= sizeof states / sizeof states[0])
+    return "unknown";
+  return states[dl->state];
+}
+
+static void
+datalink_NewState(struct datalink *dl, unsigned state)
+{
+  if (state != dl->state) {
+    if (state < sizeof states / sizeof states[0]) {
+      log_Printf(LogPHASE, "%s: %s -> %s\n", dl->name, datalink_State(dl),
+                 states[state]);
+      dl->state = state;
+    } else
+      log_Printf(LogERROR, "%s: Can't enter state %d !\n", dl->name, state);
+  }
+}
+
+struct datalink *
+iov2datalink(struct bundle *bundle, struct iovec *iov, int *niov, int maxiov,
+             int fd, int *auxfd, int *nauxfd)
+{
+  struct datalink *dl, *cdl;
+  struct fsm_retry copy;
+  char *oname, *pname;
+
+  dl = (struct datalink *)iov[(*niov)++].iov_base;
+  dl->name = iov[*niov].iov_base;
+
+  if (dl->name[DATALINK_MAXNAME-1]) {
+    dl->name[DATALINK_MAXNAME-1] = '\0';
+    if (strlen(dl->name) == DATALINK_MAXNAME - 1)
+      log_Printf(LogWARN, "Datalink name truncated to \"%s\"\n", dl->name);
+  }
+
+  /* Make sure the name is unique ! */
+  oname = NULL;
+  do {
+    for (cdl = bundle->links; cdl; cdl = cdl->next)
+      if (!strcasecmp(dl->name, cdl->name)) {
+        if ((pname = datalink_NextName(dl)) == NULL) {
+	  for ((*niov)--; *niov < maxiov; (*niov)++)
+	    free(iov[*niov].iov_base);
+	  return NULL;
+	} else if (oname)
+          free(pname);
+        else
+          oname = pname;
+        break;	/* Keep renaming 'till we have no conflicts */
+      }
+  } while (cdl);
+
+  if (oname) {
+    log_Printf(LogPHASE, "Rename link %s to %s\n", oname, dl->name);
+    free(oname);
+  } else {
+    dl->name = strdup(dl->name);
+    free(iov[*niov].iov_base);
+  }
+  (*niov)++;
+
+  dl->desc.type = DATALINK_DESCRIPTOR;
+  dl->desc.UpdateSet = datalink_UpdateSet;
+  dl->desc.IsSet = datalink_IsSet;
+  dl->desc.Read = datalink_Read;
+  dl->desc.Write = datalink_Write;
+
+  mp_linkInit(&dl->mp);
+  *dl->phone.list = '\0';
+  dl->phone.next = NULL;
+  dl->phone.alt = NULL;
+  dl->phone.chosen = "N/A";
+
+  dl->bundle = bundle;
+  dl->next = NULL;
+  memset(&dl->dial.timer, '\0', sizeof dl->dial.timer);
+  dl->dial.tries = 0;
+  dl->reconnect_tries = 0;
+  dl->parent = &bundle->fsm;
+  dl->fsmp.LayerStart = datalink_LayerStart;
+  dl->fsmp.LayerUp = datalink_LayerUp;
+  dl->fsmp.LayerDown = datalink_LayerDown;
+  dl->fsmp.LayerFinish = datalink_LayerFinish;
+  dl->fsmp.object = dl;
+
+  dl->physical = iov2physical(dl, iov, niov, maxiov, fd, auxfd, nauxfd);
+
+  if (!dl->physical) {
+    free(dl->name);
+    free(dl);
+    dl = NULL;
+  } else {
+    copy = dl->pap.cfg.fsm;
+    pap_Init(&dl->pap, dl->physical);
+    dl->pap.cfg.fsm = copy;
+
+    copy = dl->chap.auth.cfg.fsm;
+    chap_Init(&dl->chap, dl->physical);
+    dl->chap.auth.cfg.fsm = copy;
+
+    cbcp_Init(&dl->cbcp, dl->physical);
+
+    memset(&dl->chat, '\0', sizeof dl->chat);	/* Force buf{start,end} reset */
+    chat_Init(&dl->chat, dl->physical);
+
+    log_Printf(LogPHASE, "%s: Transferred in %s state\n",
+              dl->name, datalink_State(dl));
+  }
+
+  return dl;
+}
+
+int
+datalink2iov(struct datalink *dl, struct iovec *iov, int *niov, int maxiov,
+             int *auxfd, int *nauxfd)
+{
+  /* If `dl' is NULL, we're allocating before a Fromiov() */
+  int link_fd;
+
+  if (dl) {
+    timer_Stop(&dl->dial.timer);
+    /* The following is purely for the sake of paranoia */
+    cbcp_Down(&dl->cbcp);
+    timer_Stop(&dl->pap.authtimer);
+    timer_Stop(&dl->chap.auth.authtimer);
+  }
+
+  if (*niov >= maxiov - 1) {
+    log_Printf(LogERROR, "Toiov: No room for datalink !\n");
+    if (dl) {
+      free(dl->name);
+      free(dl);
+    }
+    return -1;
+  }
+
+  iov[*niov].iov_base = (void *)dl;
+  iov[(*niov)++].iov_len = sizeof *dl;
+  iov[*niov].iov_base = dl ? realloc(dl->name, DATALINK_MAXNAME) : NULL;
+  iov[(*niov)++].iov_len = DATALINK_MAXNAME;
+
+  link_fd = physical2iov(dl ? dl->physical : NULL, iov, niov, maxiov, auxfd,
+                         nauxfd);
+
+  if (link_fd == -1 && dl) {
+    free(dl->name);
+    free(dl);
+  }
+
+  return link_fd;
+}
+
+void
+datalink_Rename(struct datalink *dl, const char *name)
+{
+  free(dl->name);
+  dl->physical->link.name = dl->name = strdup(name);
+}
+
+static char *
+datalink_NextName(struct datalink *dl)
+{
+  int f, n;
+  char *name, *oname;
+
+  n = strlen(dl->name);
+  if ((name = (char *)malloc(n+3)) == NULL) {
+    log_Printf(LogERROR, "datalink_NextName: Out of memory !\n");
+    return NULL;
+  }
+  for (f = n - 1; f >= 0; f--)
+    if (!isdigit(dl->name[f]))
+      break;
+  n = sprintf(name, "%.*s-", dl->name[f] == '-' ? f : f + 1, dl->name);
+  sprintf(name + n, "%d", atoi(dl->name + f + 1) + 1);
+  oname = dl->name;
+  dl->name = name;
+  /* our physical link name isn't updated (it probably isn't created yet) */
+  return oname;
+}
+
+int
+datalink_SetMode(struct datalink *dl, int mode)
+{
+  if (!physical_SetMode(dl->physical, mode))
+    return 0;
+  if (dl->physical->type & (PHYS_DIRECT|PHYS_DEDICATED))
+    dl->script.run = 0;
+  if (dl->physical->type == PHYS_DIRECT)
+    dl->reconnect_tries = 0;
+  if (mode & (PHYS_DDIAL|PHYS_BACKGROUND|PHYS_FOREGROUND) &&
+      dl->state <= DATALINK_READY)
+    datalink_Up(dl, 1, 1);
+  return 1;
+}
+
+int
+datalink_GetDialTimeout(struct datalink *dl)
+{
+  int result = dl->cfg.dial.timeout + dl->dial.incs * dl->cfg.dial.inc;
+
+  if (dl->dial.incs < dl->cfg.dial.maxinc)
+    dl->dial.incs++;
+
+  return result;
+}
diff --git a/src/datalink.h b/src/datalink.h
new file mode 100644
index 0000000..dd21d5d
--- /dev/null
+++ b/src/datalink.h
@@ -0,0 +1,156 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/datalink.h,v 1.16.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define DATALINK_CLOSED  (0)
+#define DATALINK_OPENING (1)
+#define DATALINK_HANGUP  (2)
+#define DATALINK_DIAL    (3)
+#define DATALINK_CARRIER (4)
+#define DATALINK_LOGOUT  (5)
+#define DATALINK_LOGIN   (6)
+#define DATALINK_READY   (7)
+#define DATALINK_LCP     (8)
+#define DATALINK_AUTH    (9)
+#define DATALINK_CBCP    (10)
+#define DATALINK_OPEN    (11)
+
+#define DATALINK_MAXNAME (20)   /* Maximum datalink::name length */
+
+/* How to close the link */
+#define CLOSE_NORMAL		0
+#define CLOSE_STAYDOWN		1
+#define CLOSE_LCP		2
+
+struct iovec;
+struct prompt;
+struct physical;
+struct bundle;
+
+struct datalink {
+  struct fdescriptor desc;	/* We play either a physical or a chat */
+  unsigned state;		/* Our DATALINK_* state */
+  struct physical *physical;	/* Our link */
+
+  struct chat chat;		/* For bringing the link up & down */
+
+  unsigned stayonline : 1;	/* stay online when LCP is closed ? */
+  struct {
+    unsigned run : 1;		/* run scripts ? */
+    unsigned packetmode : 1;	/* Go into packet mode after login ? */
+  } script;
+
+  struct {
+    struct {
+      char dial[SCRIPT_LEN];
+      char login[SCRIPT_LEN];
+      char logout[SCRIPT_LEN];
+      char hangup[SCRIPT_LEN];
+    } script;			/* various chat scripts */
+    struct {
+      char list[SCRIPT_LEN];	/* Telephone Numbers */
+    } phone;
+    struct {
+      int max;			/* initially try again this number of times */
+      int next_timeout;		/* Redial next timeout value */
+      int inc;			/* Increment timeout by `inc' each time read */
+      int maxinc;		/* Maximum number of increments */
+      int timeout;		/* Redial timeout value (end of phone list) */
+    } dial;
+    struct {
+      int max;			/* initially try again this number of times */
+      int timeout;		/* Timeout before reconnect on carrier loss */
+    } reconnect;
+    struct callback callback;	/* Direction depends on physical type */
+    struct cbcpcfg cbcp;	/* Direction depends on phys type & callback */
+  } cfg;			/* All our config data is in here */
+
+  struct {
+    char list[SCRIPT_LEN];	/* copy of cfg.list for strsep() */
+    char *next;			/* Next phone from the list */
+    char *alt;			/* Alternate (after fail) phone from the list */
+    const char *chosen;		/* Chosen phone number after DIAL */
+  } phone;
+
+  struct cbcp cbcp;
+
+  struct {
+    struct pppTimer timer;	/* For timing between close & open */
+    int tries;			/* currently try again this number of times */
+    int incs;			/* # times our timeout has been incremented */
+  } dial;
+
+  unsigned reconnect_tries;	/* currently try again this number of times */
+
+  char *name;			/* Our name */
+
+  struct peerid peer;		/* Peer identification */
+
+  struct fsm_parent fsmp;	   /* Our callback functions */
+  const struct fsm_parent *parent; /* Our parent */
+
+  struct authinfo pap;             /* Authentication using pap */
+  struct chap chap;                /* Authentication using chap */
+
+  struct mp_link mp;               /* multilink data */
+
+  struct bundle *bundle;	   /* for the moment */
+  struct datalink *next;	   /* Next in the list */
+};
+
+#define descriptor2datalink(d) \
+  ((d)->type == DATALINK_DESCRIPTOR ? (struct datalink *)(d) : NULL)
+
+extern struct datalink *datalink_Create(const char *name, struct bundle *, int);
+extern struct datalink *datalink_Clone(struct datalink *, const char *);
+extern struct datalink *iov2datalink(struct bundle *, struct iovec *, int *,
+                                     int, int, int *, int *);
+extern int datalink2iov(struct datalink *, struct iovec *, int *, int, int *,
+                        int *);
+extern struct datalink *datalink_Destroy(struct datalink *);
+extern void datalink_GotAuthname(struct datalink *, const char *);
+extern void datalink_Up(struct datalink *, int, int);
+extern void datalink_Close(struct datalink *, int);
+extern void datalink_Down(struct datalink *, int);
+extern void datalink_StayDown(struct datalink *);
+extern void datalink_DontHangup(struct datalink *);
+extern void datalink_AuthOk(struct datalink *);
+extern void datalink_AuthNotOk(struct datalink *);
+extern void datalink_NCPUp(struct datalink *);
+extern void datalink_CBCPComplete(struct datalink *);
+extern void datalink_CBCPFailed(struct datalink *);
+extern int datalink_Show(struct cmdargs const *);
+extern int datalink_SetRedial(struct cmdargs const *);
+extern int datalink_SetReconnect(struct cmdargs const *);
+extern const char *datalink_State(struct datalink *);
+extern void datalink_Rename(struct datalink *, const char *);
+extern int datalink_RemoveFromSet(struct datalink *, fd_set *, fd_set *,
+                                  fd_set *);
+extern int datalink_SetMode(struct datalink *, int);
+extern int datalink_GetDialTimeout(struct datalink *);
+extern const char *datalink_ChoosePhoneNumber(struct datalink *);
+extern void datalink_ComeDown(struct datalink *, int);
diff --git a/src/deflate.c b/src/deflate.c
new file mode 100644
index 0000000..7593717
--- /dev/null
+++ b/src/deflate.c
@@ -0,0 +1,601 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/deflate.c,v 1.26.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <zlib.h>
+
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "ccp.h"
+#include "deflate.h"
+
+/* Our state */
+struct deflate_state {
+    u_short seqno;
+    int uncomp_rec;
+    int winsize;
+    z_stream cx;
+};
+
+static char garbage[10];
+static u_char EMPTY_BLOCK[4] = { 0x00, 0x00, 0xff, 0xff };
+
+#define DEFLATE_CHUNK_LEN (1536 - sizeof(struct mbuf))
+
+static int
+DeflateResetOutput(void *v)
+{
+  struct deflate_state *state = (struct deflate_state *)v;
+
+  state->seqno = 0;
+  state->uncomp_rec = 0;
+  deflateReset(&state->cx);
+  log_Printf(LogCCP, "Deflate: Output channel reset\n");
+
+  return 1;		/* Ask FSM to ACK */
+}
+
+static struct mbuf *
+DeflateOutput(void *v, struct ccp *ccp, struct link *l __unused,
+	      int pri __unused, u_short *proto, struct mbuf *mp)
+{
+  struct deflate_state *state = (struct deflate_state *)v;
+  u_char *wp, *rp;
+  int olen, ilen, len, res, flush;
+  struct mbuf *mo_head, *mo, *mi_head, *mi;
+
+  ilen = m_length(mp);
+  log_Printf(LogDEBUG, "DeflateOutput: Proto %02x (%d bytes)\n", *proto, ilen);
+  log_DumpBp(LogDEBUG, "DeflateOutput: Compress packet:", mp);
+
+  /* Stuff the protocol in front of the input */
+  mi_head = mi = m_get(2, MB_CCPOUT);
+  mi->m_next = mp;
+  rp = MBUF_CTOP(mi);
+  if (*proto < 0x100) {			/* Compress the protocol */
+    rp[0] = *proto & 0377;
+    mi->m_len = 1;
+  } else {				/* Don't compress the protocol */
+    rp[0] = *proto >> 8;
+    rp[1] = *proto & 0377;
+    mi->m_len = 2;
+  }
+
+  /* Allocate the initial output mbuf */
+  mo_head = mo = m_get(DEFLATE_CHUNK_LEN, MB_CCPOUT);
+  mo->m_len = 2;
+  wp = MBUF_CTOP(mo);
+  *wp++ = state->seqno >> 8;
+  *wp++ = state->seqno & 0377;
+  log_Printf(LogDEBUG, "DeflateOutput: Seq %d\n", state->seqno);
+  state->seqno++;
+
+  /* Set up the deflation context */
+  state->cx.next_out = wp;
+  state->cx.avail_out = DEFLATE_CHUNK_LEN - 2;
+  state->cx.next_in = MBUF_CTOP(mi);
+  state->cx.avail_in = mi->m_len;
+  flush = Z_NO_FLUSH;
+
+  olen = 0;
+  while (1) {
+    if ((res = deflate(&state->cx, flush)) != Z_OK) {
+      if (res == Z_STREAM_END)
+        break;			/* Done */
+      log_Printf(LogWARN, "DeflateOutput: deflate returned %d (%s)\n",
+                res, state->cx.msg ? state->cx.msg : "");
+      m_freem(mo_head);
+      m_free(mi_head);
+      state->seqno--;
+      return mp;		/* Our dictionary's probably dead now :-( */
+    }
+
+    if (flush == Z_SYNC_FLUSH && state->cx.avail_out != 0)
+      break;
+
+    if (state->cx.avail_in == 0 && mi->m_next != NULL) {
+      mi = mi->m_next;
+      state->cx.next_in = MBUF_CTOP(mi);
+      state->cx.avail_in = mi->m_len;
+      if (mi->m_next == NULL)
+        flush = Z_SYNC_FLUSH;
+    }
+
+    if (state->cx.avail_out == 0) {
+      mo->m_next = m_get(DEFLATE_CHUNK_LEN, MB_CCPOUT);
+      olen += (mo->m_len = DEFLATE_CHUNK_LEN);
+      mo = mo->m_next;
+      mo->m_len = 0;
+      state->cx.next_out = MBUF_CTOP(mo);
+      state->cx.avail_out = DEFLATE_CHUNK_LEN;
+    }
+  }
+
+  olen += (mo->m_len = DEFLATE_CHUNK_LEN - state->cx.avail_out);
+  olen -= 4;		/* exclude the trailing EMPTY_BLOCK */
+
+  /*
+   * If the output packet (including seqno and excluding the EMPTY_BLOCK)
+   * got bigger, send the original.
+   */
+  if (olen >= ilen) {
+    m_freem(mo_head);
+    m_free(mi_head);
+    log_Printf(LogDEBUG, "DeflateOutput: %d => %d: Uncompressible (0x%04x)\n",
+              ilen, olen, *proto);
+    ccp->uncompout += ilen;
+    ccp->compout += ilen;	/* We measure this stuff too */
+    return mp;
+  }
+
+  m_freem(mi_head);
+
+  /*
+   * Lose the last four bytes of our output.
+   * XXX: We should probably assert that these are the same as the
+   *      contents of EMPTY_BLOCK.
+   */
+  mo = mo_head;
+  for (len = mo->m_len; len < olen; mo = mo->m_next, len += mo->m_len)
+    ;
+  mo->m_len -= len - olen;
+  if (mo->m_next != NULL) {
+    m_freem(mo->m_next);
+    mo->m_next = NULL;
+  }
+
+  ccp->uncompout += ilen;
+  ccp->compout += olen;
+
+  log_Printf(LogDEBUG, "DeflateOutput: %d => %d bytes, proto 0x%04x\n",
+            ilen, olen, *proto);
+
+  *proto = ccp_Proto(ccp);
+  return mo_head;
+}
+
+static void
+DeflateResetInput(void *v)
+{
+  struct deflate_state *state = (struct deflate_state *)v;
+
+  state->seqno = 0;
+  state->uncomp_rec = 0;
+  inflateReset(&state->cx);
+  log_Printf(LogCCP, "Deflate: Input channel reset\n");
+}
+
+static struct mbuf *
+DeflateInput(void *v, struct ccp *ccp, u_short *proto, struct mbuf *mi)
+{
+  struct deflate_state *state = (struct deflate_state *)v;
+  struct mbuf *mo, *mo_head, *mi_head;
+  u_char *wp;
+  int ilen, olen;
+  int seq, flush, res, first;
+  u_char hdr[2];
+
+  log_DumpBp(LogDEBUG, "DeflateInput: Decompress packet:", mi);
+  mi_head = mi = mbuf_Read(mi, hdr, 2);
+  ilen = 2;
+
+  /* Check the sequence number. */
+  seq = (hdr[0] << 8) + hdr[1];
+  log_Printf(LogDEBUG, "DeflateInput: Seq %d\n", seq);
+  if (seq != state->seqno) {
+    if (seq <= state->uncomp_rec)
+      /*
+       * So the peer's started at zero again - fine !  If we're wrong,
+       * inflate() will fail.  This is better than getting into a loop
+       * trying to get a ResetReq to a busy sender.
+       */
+      state->seqno = seq;
+    else {
+      log_Printf(LogCCP, "DeflateInput: Seq error: Got %d, expected %d\n",
+                seq, state->seqno);
+      m_freem(mi_head);
+      ccp_SendResetReq(&ccp->fsm);
+      return NULL;
+    }
+  }
+  state->seqno++;
+  state->uncomp_rec = 0;
+
+  /* Allocate an output mbuf */
+  mo_head = mo = m_get(DEFLATE_CHUNK_LEN, MB_CCPIN);
+
+  /* Our proto starts with 0 if it's compressed */
+  wp = MBUF_CTOP(mo);
+  wp[0] = '\0';
+
+  /*
+   * We set avail_out to 1 initially so we can look at the first
+   * byte of the output and decide whether we have a compressed
+   * proto field.
+   */
+  state->cx.next_in = MBUF_CTOP(mi);
+  state->cx.avail_in = mi->m_len;
+  state->cx.next_out = wp + 1;
+  state->cx.avail_out = 1;
+  ilen += mi->m_len;
+
+  flush = mi->m_next ? Z_NO_FLUSH : Z_SYNC_FLUSH;
+  first = 1;
+  olen = 0;
+
+  while (1) {
+    if ((res = inflate(&state->cx, flush)) != Z_OK) {
+      if (res == Z_STREAM_END)
+        break;			/* Done */
+      log_Printf(LogCCP, "DeflateInput: inflate returned %d (%s)\n",
+                res, state->cx.msg ? state->cx.msg : "");
+      m_freem(mo_head);
+      m_freem(mi);
+      ccp_SendResetReq(&ccp->fsm);
+      return NULL;
+    }
+
+    if (flush == Z_SYNC_FLUSH && state->cx.avail_out != 0)
+      break;
+
+    if (state->cx.avail_in == 0 && mi && (mi = m_free(mi)) != NULL) {
+      /* underflow */
+      state->cx.next_in = MBUF_CTOP(mi);
+      ilen += (state->cx.avail_in = mi->m_len);
+      if (mi->m_next == NULL)
+        flush = Z_SYNC_FLUSH;
+    }
+
+    if (state->cx.avail_out == 0) {
+      /* overflow */
+      if (first) {
+        if (!(wp[1] & 1)) {
+          /* 2 byte proto, shuffle it back in output */
+          wp[0] = wp[1];
+          state->cx.next_out--;
+          state->cx.avail_out = DEFLATE_CHUNK_LEN-1;
+        } else
+          state->cx.avail_out = DEFLATE_CHUNK_LEN-2;
+        first = 0;
+      } else {
+        olen += (mo->m_len = DEFLATE_CHUNK_LEN);
+        mo->m_next = m_get(DEFLATE_CHUNK_LEN, MB_CCPIN);
+        mo = mo->m_next;
+        state->cx.next_out = MBUF_CTOP(mo);
+        state->cx.avail_out = DEFLATE_CHUNK_LEN;
+      }
+    }
+  }
+
+  if (mi != NULL)
+    m_freem(mi);
+
+  if (first) {
+    log_Printf(LogCCP, "DeflateInput: Length error\n");
+    m_freem(mo_head);
+    ccp_SendResetReq(&ccp->fsm);
+    return NULL;
+  }
+
+  olen += (mo->m_len = DEFLATE_CHUNK_LEN - state->cx.avail_out);
+
+  *proto = ((u_short)wp[0] << 8) | wp[1];
+  mo_head->m_offset += 2;
+  mo_head->m_len -= 2;
+  olen -= 2;
+
+  ccp->compin += ilen;
+  ccp->uncompin += olen;
+
+  log_Printf(LogDEBUG, "DeflateInput: %d => %d bytes, proto 0x%04x\n",
+            ilen, olen, *proto);
+
+  /*
+   * Simulate an EMPTY_BLOCK so that our dictionary stays in sync.
+   * The peer will have silently removed this!
+   */
+  state->cx.next_out = garbage;
+  state->cx.avail_out = sizeof garbage;
+  state->cx.next_in = EMPTY_BLOCK;
+  state->cx.avail_in = sizeof EMPTY_BLOCK;
+  inflate(&state->cx, Z_SYNC_FLUSH);
+
+  return mo_head;
+}
+
+static void
+DeflateDictSetup(void *v, struct ccp *ccp, u_short proto, struct mbuf *mi)
+{
+  struct deflate_state *state = (struct deflate_state *)v;
+  int res, flush, expect_error;
+  u_char *rp;
+  struct mbuf *mi_head;
+  short len;
+
+  log_Printf(LogDEBUG, "DeflateDictSetup: Got seq %d\n", state->seqno);
+
+  /*
+   * Stuff an ``uncompressed data'' block header followed by the
+   * protocol in front of the input
+   */
+  mi_head = m_get(7, MB_CCPOUT);
+  mi_head->m_next = mi;
+  len = m_length(mi);
+  mi = mi_head;
+  rp = MBUF_CTOP(mi);
+  if (proto < 0x100) {			/* Compress the protocol */
+    rp[5] = proto & 0377;
+    mi->m_len = 6;
+    len++;
+  } else {				/* Don't compress the protocol */
+    rp[5] = proto >> 8;
+    rp[6] = proto & 0377;
+    mi->m_len = 7;
+    len += 2;
+  }
+  rp[0] = 0x80;				/* BITS: 100xxxxx */
+  rp[1] = len & 0377;			/* The length */
+  rp[2] = len >> 8;
+  rp[3] = (~len) & 0377;		/* One's compliment of the length */
+  rp[4] = (~len) >> 8;
+
+  state->cx.next_in = rp;
+  state->cx.avail_in = mi->m_len;
+  state->cx.next_out = garbage;
+  state->cx.avail_out = sizeof garbage;
+  flush = Z_NO_FLUSH;
+  expect_error = 0;
+
+  while (1) {
+    if ((res = inflate(&state->cx, flush)) != Z_OK) {
+      if (res == Z_STREAM_END)
+        break;			/* Done */
+      if (expect_error && res == Z_BUF_ERROR)
+        break;
+      log_Printf(LogCCP, "DeflateDictSetup: inflate returned %d (%s)\n",
+                res, state->cx.msg ? state->cx.msg : "");
+      log_Printf(LogCCP, "DeflateDictSetup: avail_in %d, avail_out %d\n",
+                state->cx.avail_in, state->cx.avail_out);
+      ccp_SendResetReq(&ccp->fsm);
+      m_free(mi_head);		/* lose our allocated ``head'' buf */
+      return;
+    }
+
+    if (flush == Z_SYNC_FLUSH && state->cx.avail_out != 0)
+      break;
+
+    if (state->cx.avail_in == 0 && mi && (mi = mi->m_next) != NULL) {
+      /* underflow */
+      state->cx.next_in = MBUF_CTOP(mi);
+      state->cx.avail_in = mi->m_len;
+      if (mi->m_next == NULL)
+        flush = Z_SYNC_FLUSH;
+    }
+
+    if (state->cx.avail_out == 0) {
+      if (state->cx.avail_in == 0)
+        /*
+         * This seems to be a bug in libz !  If inflate() finished
+         * with 0 avail_in and 0 avail_out *and* this is the end of
+         * our input *and* inflate() *has* actually written all the
+         * output it's going to, it *doesn't* return Z_STREAM_END !
+         * When we subsequently call it with no more input, it gives
+         * us Z_BUF_ERROR :-(  It seems pretty safe to ignore this
+         * error (the dictionary seems to stay in sync).  In the worst
+         * case, we'll drop the next compressed packet and do a
+         * CcpReset() then.
+         */
+        expect_error = 1;
+      /* overflow */
+      state->cx.next_out = garbage;
+      state->cx.avail_out = sizeof garbage;
+    }
+  }
+
+  ccp->compin += len;
+  ccp->uncompin += len;
+
+  state->seqno++;
+  state->uncomp_rec++;
+  m_free(mi_head);		/* lose our allocated ``head'' buf */
+}
+
+static const char *
+DeflateDispOpts(struct fsm_opt *o)
+{
+  static char disp[7];		/* Must be used immediately */
+
+  sprintf(disp, "win %d", (o->data[0]>>4) + 8);
+  return disp;
+}
+
+static void
+DeflateInitOptsOutput(struct bundle *bundle __unused, struct fsm_opt *o,
+                      const struct ccp_config *cfg)
+{
+  o->hdr.len = 4;
+  o->data[0] = ((cfg->deflate.out.winsize - 8) << 4) + 8;
+  o->data[1] = '\0';
+}
+
+static int
+DeflateSetOptsOutput(struct bundle *bundle __unused, struct fsm_opt *o,
+                     const struct ccp_config *cfg __unused)
+{
+  if (o->hdr.len != 4 || (o->data[0] & 15) != 8 || o->data[1] != '\0')
+    return MODE_REJ;
+
+  if ((o->data[0] >> 4) + 8 > 15) {
+    o->data[0] = ((15 - 8) << 4) + 8;
+    return MODE_NAK;
+  }
+
+  return MODE_ACK;
+}
+
+static int
+DeflateSetOptsInput(struct bundle *bundle __unused, struct fsm_opt *o,
+                    const struct ccp_config *cfg)
+{
+  int want;
+
+  if (o->hdr.len != 4 || (o->data[0] & 15) != 8 || o->data[1] != '\0')
+    return MODE_REJ;
+
+  want = (o->data[0] >> 4) + 8;
+  if (cfg->deflate.in.winsize == 0) {
+    if (want < 8 || want > 15) {
+      o->data[0] = ((15 - 8) << 4) + 8;
+    }
+  } else if (want != cfg->deflate.in.winsize) {
+    o->data[0] = ((cfg->deflate.in.winsize - 8) << 4) + 8;
+    return MODE_NAK;
+  }
+
+  return MODE_ACK;
+}
+
+static void *
+DeflateInitInput(struct bundle *bundle __unused, struct fsm_opt *o)
+{
+  struct deflate_state *state;
+
+  state = (struct deflate_state *)malloc(sizeof(struct deflate_state));
+  if (state != NULL) {
+    state->winsize = (o->data[0] >> 4) + 8;
+    state->cx.zalloc = NULL;
+    state->cx.opaque = NULL;
+    state->cx.zfree = NULL;
+    state->cx.next_out = NULL;
+    if (inflateInit2(&state->cx, -state->winsize) == Z_OK)
+      DeflateResetInput(state);
+    else {
+      free(state);
+      state = NULL;
+    }
+  }
+
+  return state;
+}
+
+static void *
+DeflateInitOutput(struct bundle *bundle __unused, struct fsm_opt *o)
+{
+  struct deflate_state *state;
+
+  state = (struct deflate_state *)malloc(sizeof(struct deflate_state));
+  if (state != NULL) {
+    state->winsize = (o->data[0] >> 4) + 8;
+    state->cx.zalloc = NULL;
+    state->cx.opaque = NULL;
+    state->cx.zfree = NULL;
+    state->cx.next_in = NULL;
+    if (deflateInit2(&state->cx, Z_DEFAULT_COMPRESSION, 8,
+                     -state->winsize, 8, Z_DEFAULT_STRATEGY) == Z_OK)
+      DeflateResetOutput(state);
+    else {
+      free(state);
+      state = NULL;
+    }
+  }
+
+  return state;
+}
+
+static void
+DeflateTermInput(void *v)
+{
+  struct deflate_state *state = (struct deflate_state *)v;
+
+  inflateEnd(&state->cx);
+  free(state);
+}
+
+static void
+DeflateTermOutput(void *v)
+{
+  struct deflate_state *state = (struct deflate_state *)v;
+
+  deflateEnd(&state->cx);
+  free(state);
+}
+
+const struct ccp_algorithm PppdDeflateAlgorithm = {
+  TY_PPPD_DEFLATE,	/* Older versions of pppd expected this ``type'' */
+  CCP_NEG_DEFLATE24,
+  DeflateDispOpts,
+  ccp_DefaultUsable,
+  ccp_DefaultRequired,
+  {
+    DeflateSetOptsInput,
+    DeflateInitInput,
+    DeflateTermInput,
+    DeflateResetInput,
+    DeflateInput,
+    DeflateDictSetup
+  },
+  {
+    0,
+    DeflateInitOptsOutput,
+    DeflateSetOptsOutput,
+    DeflateInitOutput,
+    DeflateTermOutput,
+    DeflateResetOutput,
+    DeflateOutput
+  },
+};
+
+const struct ccp_algorithm DeflateAlgorithm = {
+  TY_DEFLATE,		/* rfc 1979 */
+  CCP_NEG_DEFLATE,
+  DeflateDispOpts,
+  ccp_DefaultUsable,
+  ccp_DefaultRequired,
+  {
+    DeflateSetOptsInput,
+    DeflateInitInput,
+    DeflateTermInput,
+    DeflateResetInput,
+    DeflateInput,
+    DeflateDictSetup
+  },
+  {
+    0,
+    DeflateInitOptsOutput,
+    DeflateSetOptsOutput,
+    DeflateInitOutput,
+    DeflateTermOutput,
+    DeflateResetOutput,
+    DeflateOutput
+  },
+};
diff --git a/src/deflate.h b/src/deflate.h
new file mode 100644
index 0000000..997a4d1
--- /dev/null
+++ b/src/deflate.h
@@ -0,0 +1,30 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/deflate.h,v 1.4.62.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+extern const struct ccp_algorithm PppdDeflateAlgorithm;
+extern const struct ccp_algorithm DeflateAlgorithm;
diff --git a/src/defs.c b/src/defs.c
new file mode 100644
index 0000000..a517e03
--- /dev/null
+++ b/src/defs.c
@@ -0,0 +1,450 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/defs.c,v 1.48.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+
+#include <sys/param.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+#include <sys/module.h>
+#endif
+#include <termios.h>
+#if !defined(__FreeBSD__) || __FreeBSD__ < 3
+#include <time.h>
+#endif
+#include <unistd.h>
+
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+#include "id.h"
+#include "log.h"
+#endif
+#include "defs.h"
+
+#define	issep(c)	((c) == '\t' || (c) == ' ')
+
+#if defined(__NetBSD__) || __FreeBSD__ < 3
+void
+randinit()
+{
+#if defined(__FreeBSD__)
+  static int initdone;		/* srandomdev() call is only required once */
+
+  if (!initdone) {
+    initdone = 1;
+    srandomdev();
+  }
+#else
+  srandom((time(NULL)^getpid())+random());
+#endif
+}
+#endif
+
+ssize_t
+fullread(int fd, void *v, size_t n)
+{
+  size_t got, total;
+
+  for (total = 0; total < n; total += got)
+    switch ((got = read(fd, (char *)v + total, n - total))) {
+      case 0:
+        return total;
+      case -1:
+        if (errno == EINTR)
+          got = 0;
+        else
+          return -1;
+    }
+  return total;
+}
+
+static struct {
+  int mode;
+  const char *name;
+} modes[] = {
+  { PHYS_INTERACTIVE, "interactive" },
+  { PHYS_AUTO, "auto" },
+  { PHYS_DIRECT, "direct" },
+  { PHYS_DEDICATED, "dedicated" },
+  { PHYS_DDIAL, "ddial" },
+  { PHYS_BACKGROUND, "background" },
+  { PHYS_FOREGROUND, "foreground" },
+  { PHYS_ALL, "*" },
+  { 0, 0 }
+};
+
+const char *
+mode2Nam(int mode)
+{
+  int m;
+
+  for (m = 0; modes[m].mode; m++)
+    if (modes[m].mode == mode)
+      return modes[m].name;
+
+  return "unknown";
+}
+
+int
+Nam2mode(const char *name)
+{
+  int m, got, len;
+
+  len = strlen(name);
+  got = -1;
+  for (m = 0; modes[m].mode; m++)
+    if (!strncasecmp(name, modes[m].name, len)) {
+      if (modes[m].name[len] == '\0')
+	return modes[m].mode;
+      if (got != -1)
+        return 0;
+      got = m;
+    }
+
+  return got == -1 ? 0 : modes[got].mode;
+}
+
+struct in_addr
+GetIpAddr(const char *cp)
+{
+  struct in_addr ipaddr;
+
+  if (!strcasecmp(cp, "default"))
+    ipaddr.s_addr = INADDR_ANY;
+  else if (inet_aton(cp, &ipaddr) == 0) {
+    const char *ptr;
+
+    /* Any illegal characters ? */
+    for (ptr = cp; *ptr != '\0'; ptr++)
+      if (!isalnum(*ptr) && strchr("-.", *ptr) == NULL)
+        break;
+
+    if (*ptr == '\0') {
+      struct hostent *hp;
+
+      hp = gethostbyname(cp);
+      if (hp && hp->h_addrtype == AF_INET)
+        memcpy(&ipaddr, hp->h_addr, hp->h_length);
+      else
+        ipaddr.s_addr = INADDR_NONE;
+    } else
+      ipaddr.s_addr = INADDR_NONE;
+  }
+
+  return ipaddr;
+}
+
+static const struct speeds {
+  unsigned nspeed;
+  speed_t speed;
+} speeds[] = {
+#ifdef B50
+  { 50, B50, },
+#endif
+#ifdef B75
+  { 75, B75, },
+#endif
+#ifdef B110
+  { 110, B110, },
+#endif
+#ifdef B134
+  { 134, B134, },
+#endif
+#ifdef B150
+  { 150, B150, },
+#endif
+#ifdef B200
+  { 200, B200, },
+#endif
+#ifdef B300
+  { 300, B300, },
+#endif
+#ifdef B600
+  { 600, B600, },
+#endif
+#ifdef B1200
+  { 1200, B1200, },
+#endif
+#ifdef B1800
+  { 1800, B1800, },
+#endif
+#ifdef B2400
+  { 2400, B2400, },
+#endif
+#ifdef B4800
+  { 4800, B4800, },
+#endif
+#ifdef B9600
+  { 9600, B9600, },
+#endif
+#ifdef B19200
+  { 19200, B19200, },
+#endif
+#ifdef B38400
+  { 38400, B38400, },
+#endif
+#ifndef _POSIX_SOURCE
+#ifdef B7200
+  { 7200, B7200, },
+#endif
+#ifdef B14400
+  { 14400, B14400, },
+#endif
+#ifdef B28800
+  { 28800, B28800, },
+#endif
+#ifdef B57600
+  { 57600, B57600, },
+#endif
+#ifdef B76800
+  { 76800, B76800, },
+#endif
+#ifdef B115200
+  { 115200, B115200, },
+#endif
+#ifdef B230400
+  { 230400, B230400, },
+#endif
+#ifdef B460800
+  { 460800, B460800, },
+#endif
+#ifdef B921600
+  { 921600, B921600, },
+#endif
+#ifdef EXTA
+  { 19200, EXTA, },
+#endif
+#ifdef EXTB
+  { 38400, EXTB, },
+#endif
+#endif				/* _POSIX_SOURCE */
+  { 0, 0 }
+};
+
+unsigned
+SpeedToUnsigned(speed_t speed)
+{
+  const struct speeds *sp;
+
+  for (sp = speeds; sp->nspeed; sp++) {
+    if (sp->speed == speed) {
+      return sp->nspeed;
+    }
+  }
+  return 0;
+}
+
+speed_t
+UnsignedToSpeed(unsigned nspeed)
+{
+  const struct speeds *sp;
+
+  for (sp = speeds; sp->nspeed; sp++) {
+    if (sp->nspeed == nspeed) {
+      return sp->speed;
+    }
+  }
+  return B0;
+}
+
+char *
+findblank(char *p, int flags)
+{
+  int instring;
+
+  instring = 0;
+  while (*p) {
+    if (*p == '\\') {
+      if (flags & PARSE_REDUCE) {
+        memmove(p, p + 1, strlen(p));
+        if (!*p)
+          break;
+      } else
+        p++;
+    } else if (*p == '"') {
+      memmove(p, p + 1, strlen(p));
+      instring = !instring;
+      continue;
+    } else if (!instring && (issep(*p) ||
+                             (*p == '#' && !(flags & PARSE_NOHASH))))
+      return p;
+    p++;
+  }
+
+  return instring ? NULL : p;
+}
+
+int
+MakeArgs(char *script, char **pvect, int maxargs, int flags)
+{
+  int nargs;
+
+  nargs = 0;
+  while (*script) {
+    script += strspn(script, " \t");
+    if (*script == '#' && !(flags & PARSE_NOHASH)) {
+      *script = '\0';
+      break;
+    }
+    if (*script) {
+      if (nargs >= maxargs - 1)
+        break;
+      *pvect++ = script;
+      nargs++;
+      script = findblank(script, flags);
+      if (script == NULL)
+        return -1;
+      else if (!(flags & PARSE_NOHASH) && *script == '#')
+        *script = '\0';
+      else if (*script)
+        *script++ = '\0';
+    }
+  }
+  *pvect = NULL;
+  return nargs;
+}
+
+const char *
+NumStr(long val, char *buf, size_t sz)
+{
+  static char result[23];		/* handles 64 bit numbers */
+
+  if (buf == NULL || sz == 0) {
+    buf = result;
+    sz = sizeof result;
+  }
+  snprintf(buf, sz, "<%ld>", val);
+  return buf;
+}
+
+const char *
+HexStr(long val, char *buf, size_t sz)
+{
+  static char result[21];		/* handles 64 bit numbers */
+
+  if (buf == NULL || sz == 0) {
+    buf = result;
+    sz = sizeof result;
+  }
+  snprintf(buf, sz, "<0x%lx>", val);
+  return buf;
+}
+
+const char *
+ex_desc(int ex)
+{
+  static char num[12];		/* Used immediately if returned */
+  static const char * const desc[] = {
+    "normal", "start", "sock", "modem", "dial", "dead", "done",
+    "reboot", "errdead", "hangup", "term", "nodial", "nologin",
+    "redial", "reconnect"
+  };
+
+  if (ex >= 0 && ex < (int)(sizeof desc / sizeof *desc))
+    return desc[ex];
+  snprintf(num, sizeof num, "%d", ex);
+  return num;
+}
+
+void
+SetTitle(const char *title)
+{
+  if (title == NULL)
+    setproctitle(NULL);
+  else if (title[0] == '-' && title[1] != '\0')
+    setproctitle("-%s", title + 1);
+  else
+    setproctitle("%s", title);
+}
+
+fd_set *
+mkfdset()
+{
+  return (fd_set *)malloc(howmany(getdtablesize(), NFDBITS) * sizeof (fd_mask));
+}
+
+void
+zerofdset(fd_set *s)
+{
+  memset(s, '\0', howmany(getdtablesize(), NFDBITS) * sizeof (fd_mask));
+}
+
+void
+Concatinate(char *buf, size_t sz, int argc, const char *const *argv)
+{
+  int i, n;
+  unsigned pos;
+
+  *buf = '\0';
+  for (pos = i = 0; i < argc; i++) {
+    n = snprintf(buf + pos, sz - pos, "%s%s", i ? " " : "", argv[i]);
+    if (n < 0) {
+      buf[pos] = '\0';
+      break;
+    }
+    if ((pos += n) >= sz)
+      break;
+  }
+}
+
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+int
+loadmodules(int how, const char *module, ...)
+{
+  int loaded = 0;
+  va_list ap;
+
+  va_start(ap, module);
+  while (module != NULL) {
+    if (modfind(module) == -1) {
+      if (ID0kldload(module) == -1) {
+        if (how == LOAD_VERBOSLY)
+          log_Printf(LogWARN, "%s: Cannot load module\n", module);
+      } else
+        loaded++;
+    }
+    module = va_arg(ap, const char *);
+  }
+  va_end(ap);
+  return loaded;
+}
+#else
+int
+loadmodules(int how __unused, const char *module __unused, ...)
+{
+  return 0;
+}
+#endif
diff --git a/src/defs.h b/src/defs.h
new file mode 100644
index 0000000..e2c1042
--- /dev/null
+++ b/src/defs.h
@@ -0,0 +1,142 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/defs.h,v 1.68.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+/* Check the following definitions for your machine environment */
+#ifdef __FreeBSD__
+# define  MODEM_LIST	"/dev/cuad1\0/dev/cuad0"	/* name of tty device */
+#else
+# ifdef __OpenBSD__
+#  define MODEM_LIST	"/dev/cua01\0/dev/cua00"	/* name of tty device */
+# else
+#  define MODEM_LIST	"/dev/tty01\0/dev/tty00"	/* name of tty device */
+# endif
+#endif
+#define NMODEMS		2
+
+#ifndef PPP_CONFDIR
+#define PPP_CONFDIR	"/etc/ppp"
+#endif
+
+#define TUN_NAME	"tun"
+#define TUN_PREFIX	(_PATH_DEV TUN_NAME)	/* /dev/tun */
+
+#define MODEM_SPEED	B38400	/* tty speed */
+#define	SERVER_PORT	3000	/* Base server port no. */
+#define	MODEM_CTSRTS	1	/* Default (true): use CTS/RTS signals */
+#define	RECONNECT_TIMEOUT 3	/* Default timer for carrier loss */
+#define	DIAL_TIMEOUT	30	/* Default and Max random time to redial */
+#define	DIAL_NEXT_TIMEOUT 3	/* Default Hold time to next number redial */
+#define SCRIPT_LEN 512		/* Size of login/dial/hangup scripts */
+#define LINE_LEN SCRIPT_LEN 	/* Size of lines */
+#define DEVICE_LEN SCRIPT_LEN	/* Size of individual devices */
+#define AUTHLEN 100 		/* Size of authname/authkey */
+#define CHAPDIGESTLEN 100	/* Maximum chap digest */
+#define CHAPCHALLENGELEN 48	/* Maximum chap challenge */
+#define CHAPAUTHRESPONSELEN 48	/* Maximum chap authresponse (chap81) */
+#define MAXARGS 40		/* How many args per config line */
+#define NCP_IDLE_TIMEOUT 180	/* Drop all links */
+#define CHOKED_TIMEOUT 120	/* Delete queued packets w/ blocked tun */
+
+#define MIN_LQRPERIOD 1		/* Minimum LQR frequency */
+#define DEF_LQRPERIOD 30	/* Default LQR frequency */
+#define MIN_FSMRETRY 1		/* Minimum FSM retry frequency */
+#define DEF_FSMRETRY 3		/* FSM retry frequency */
+#define DEF_FSMTRIES 5		/* Default max retries */
+#define DEF_FSMAUTHTRIES 3	/* Default max auth retries */
+#define DEF_IFQUEUE 30		/* Default interface queue size */
+
+#define	CONFFILE 	"ppp.conf"
+#define	LINKUPFILE 	"ppp.linkup"
+#define	LINKDOWNFILE 	"ppp.linkdown"
+#define	SECRETFILE	"ppp.secret"
+
+#define	EX_SIG		-1
+#define	EX_NORMAL	0
+#define	EX_START	1
+#define	EX_SOCK		2
+#define	EX_MODEM	3
+#define	EX_DIAL		4
+#define	EX_DEAD		5
+#define	EX_DONE		6
+#define	EX_REBOOT	7
+#define	EX_ERRDEAD	8
+#define	EX_HANGUP	9
+#define	EX_TERM		10
+#define EX_NODIAL	11
+#define EX_NOLOGIN	12
+/* return values for -background mode, not really exits */
+#define EX_REDIAL	13
+#define EX_RECONNECT	14
+
+/* physical::type values (OR'd in bundle::phys_type) */
+#define PHYS_NONE		0
+#define PHYS_INTERACTIVE	1  /* Manual link */
+#define PHYS_AUTO		2  /* Dial-on-demand link */
+#define	PHYS_DIRECT		4  /* Incoming link, deleted when closed */
+#define	PHYS_DEDICATED		8  /* Dedicated link */
+#define	PHYS_DDIAL		16 /* Dial immediately, stay connected */
+#define PHYS_BACKGROUND		32 /* Dial immediately, deleted when closed */
+#define PHYS_FOREGROUND		64 /* Pseudo mode, same as background */
+#define PHYS_ALL		127
+
+/* flags passed to findblank() and MakeArgs() */
+#define PARSE_NORMAL	0
+#define PARSE_REDUCE	1
+#define PARSE_NOHASH	2
+
+/* flags passed to loadmodules */
+#define	LOAD_QUIETLY	1
+#define	LOAD_VERBOSLY	2
+
+#define ROUNDUP(x) ((x) ? (1 + (((x) - 1) | (sizeof(long) - 1))) : sizeof(long))
+
+#if defined(__NetBSD__) || __FreeBSD__ < 3
+extern void randinit(void);
+#else
+#define random arc4random
+#define randinit()
+#endif
+
+extern ssize_t fullread(int, void *, size_t);
+extern const char *mode2Nam(int);
+extern int Nam2mode(const char *);
+extern struct in_addr GetIpAddr(const char *);
+extern unsigned SpeedToUnsigned(speed_t);
+extern speed_t UnsignedToSpeed(unsigned);
+extern char *findblank(char *, int);
+extern int MakeArgs(char *, char **, int, int);
+extern const char *NumStr(long, char *, size_t);
+extern const char *HexStr(long, char *, size_t);
+extern const char *ex_desc(int);
+extern void SetTitle(const char *);
+extern fd_set *mkfdset(void);
+extern void zerofdset(fd_set *);
+extern void Concatinate(char *, size_t, int, const char *const *);
+extern int loadmodules(int, const char *, ...);
diff --git a/src/descriptor.h b/src/descriptor.h
new file mode 100644
index 0000000..6a6f76c
--- /dev/null
+++ b/src/descriptor.h
@@ -0,0 +1,53 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/descriptor.h,v 1.8.42.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define PHYSICAL_DESCRIPTOR (1)
+#define SERVER_DESCRIPTOR (2)
+#define PROMPT_DESCRIPTOR (3)
+#define CHAT_DESCRIPTOR (4)
+#define DATALINK_DESCRIPTOR (5)
+#define BUNDLE_DESCRIPTOR (6)
+#define MPSERVER_DESCRIPTOR (7)
+#define RADIUS_DESCRIPTOR (8)
+#define CHAP_DESCRIPTOR (9)
+
+struct bundle;
+
+struct fdescriptor {
+  int type;
+
+  int (*UpdateSet)(struct fdescriptor *, fd_set *, fd_set *, fd_set *, int *);
+  int (*IsSet)(struct fdescriptor *, const fd_set *);
+  void (*Read)(struct fdescriptor *, struct bundle *, const fd_set *);
+  int (*Write)(struct fdescriptor *, struct bundle *, const fd_set *);
+};
+
+#define descriptor_UpdateSet(d, r, w, e, n) ((*(d)->UpdateSet)(d, r, w, e, n))
+#define descriptor_IsSet(d, s) ((*(d)->IsSet)(d, s))
+#define descriptor_Read(d, b, f) ((*(d)->Read)(d, b, f))
+#define descriptor_Write(d, b, f) ((*(d)->Write)(d, b, f))
diff --git a/src/ether.c b/src/ether.c
new file mode 100644
index 0000000..5e2075a
--- /dev/null
+++ b/src/ether.c
@@ -0,0 +1,737 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ether.c,v 1.30.10.1.4.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netgraph.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netgraph/ng_ether.h>
+#include <netgraph/ng_message.h>
+#include <netgraph/ng_pppoe.h>
+#include <netgraph/ng_socket.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <sys/time.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "main.h"
+#include "mp.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "slcompress.h"
+#include "iplist.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "filter.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "id.h"
+#include "iface.h"
+#include "route.h"
+#include "ether.h"
+
+
+#define PPPOE_NODE_TYPE_LEN (sizeof NG_PPPOE_NODE_TYPE - 1) /* "PPPoE" */
+
+struct etherdevice {
+  struct device dev;			/* What struct physical knows about */
+  int cs;				/* Control socket */
+  int connected;			/* Are we connected yet ? */
+  int timeout;				/* Seconds attempting to connect */
+  char hook[sizeof TUN_NAME + 11];	/* Our socket node hook */
+  u_int32_t slot;			/* ifindex << 24 | unit */
+};
+
+#define device2ether(d) \
+  ((d)->type == ETHER_DEVICE ? (struct etherdevice *)d : NULL)
+
+unsigned
+ether_DeviceSize(void)
+{
+  return sizeof(struct etherdevice);
+}
+
+static ssize_t
+ether_Write(struct physical *p, const void *v, size_t n)
+{
+  struct etherdevice *dev = device2ether(p->handler);
+
+  return NgSendData(p->fd, dev->hook, v, n) == -1 ? -1 : (ssize_t)n;
+}
+
+static ssize_t
+ether_Read(struct physical *p, void *v, size_t n)
+{
+  char hook[sizeof TUN_NAME + 11];
+
+  return NgRecvData(p->fd, v, n, hook);
+}
+
+static int
+ether_RemoveFromSet(struct physical *p, fd_set *r, fd_set *w, fd_set *e)
+{
+  struct etherdevice *dev = device2ether(p->handler);
+  int result;
+
+  if (r && dev->cs >= 0 && FD_ISSET(dev->cs, r)) {
+    FD_CLR(dev->cs, r);
+    log_Printf(LogTIMER, "%s: fdunset(ctrl) %d\n", p->link.name, dev->cs);
+    result = 1;
+  } else
+    result = 0;
+
+  /* Careful... physical_RemoveFromSet() called us ! */
+
+  p->handler->removefromset = NULL;
+  result += physical_RemoveFromSet(p, r, w, e);
+  p->handler->removefromset = ether_RemoveFromSet;
+
+  return result;
+}
+
+static void
+ether_Free(struct physical *p)
+{
+  struct etherdevice *dev = device2ether(p->handler);
+
+  physical_SetDescriptor(p);
+  if (dev->cs != -1)
+    close(dev->cs);
+  free(dev);
+}
+
+static const char *
+ether_OpenInfo(struct physical *p)
+{
+  struct etherdevice *dev = device2ether(p->handler);
+
+  switch (dev->connected) {
+    case CARRIER_PENDING:
+      return "negotiating";
+    case CARRIER_OK:
+      return "established";
+  }
+
+  return "disconnected";
+}
+
+static int
+ether_Slot(struct physical *p)
+{
+  struct etherdevice *dev = device2ether(p->handler);
+
+  return dev->slot;
+}
+
+
+static void
+ether_device2iov(struct device *d, struct iovec *iov, int *niov,
+                 int maxiov __unused, int *auxfd, int *nauxfd)
+{
+  struct etherdevice *dev;
+  int sz = physical_MaxDeviceSize();
+
+  iov[*niov].iov_base = d = realloc(d, sz);
+  if (d == NULL) {
+    log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
+    AbortProgram(EX_OSERR);
+  }
+  iov[*niov].iov_len = sz;
+  (*niov)++;
+
+  dev = device2ether(d);
+  if (dev->cs >= 0) {
+    *auxfd = dev->cs;
+    (*nauxfd)++;
+  }
+}
+
+static void
+ether_MessageIn(struct etherdevice *dev)
+{
+  char msgbuf[sizeof(struct ng_mesg) + sizeof(struct ngpppoe_sts)];
+  struct ng_mesg *rep = (struct ng_mesg *)msgbuf;
+  struct ngpppoe_sts *sts = (struct ngpppoe_sts *)(msgbuf + sizeof *rep);
+  char *end, unknown[14], sessionid[5];
+  const char *msg;
+  struct timeval t;
+  fd_set *r;
+  u_long slot;
+  int asciilen, ret;
+
+  if (dev->cs < 0)
+    return;
+
+  if ((r = mkfdset()) == NULL) {
+    log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n");
+    return;
+  }
+
+  while (1) {
+    zerofdset(r);
+    FD_SET(dev->cs, r);
+    t.tv_sec = t.tv_usec = 0;
+    ret = select(dev->cs + 1, r, NULL, NULL, &t);
+
+    if (ret <= 0)
+      break;
+
+    if (NgRecvMsg(dev->cs, rep, sizeof msgbuf, NULL) <= 0)
+      break;
+
+    if (rep->header.version != NG_VERSION) {
+      log_Printf(LogWARN, "%ld: Unexpected netgraph version, expected %ld\n",
+                 (long)rep->header.version, (long)NG_VERSION);
+      break;
+    }
+
+    if (rep->header.typecookie != NGM_PPPOE_COOKIE) {
+      log_Printf(LogWARN, "%ld: Unexpected netgraph cookie, expected %ld\n",
+                 (long)rep->header.typecookie, (long)NGM_PPPOE_COOKIE);
+      break;
+    }
+
+    asciilen = 0;
+    switch (rep->header.cmd) {
+      case NGM_PPPOE_SET_FLAG:	msg = "SET_FLAG";	break;
+      case NGM_PPPOE_CONNECT:	msg = "CONNECT";	break;
+      case NGM_PPPOE_LISTEN:	msg = "LISTEN";		break;
+      case NGM_PPPOE_OFFER:	msg = "OFFER";		break;
+      case NGM_PPPOE_SUCCESS:	msg = "SUCCESS";	break;
+      case NGM_PPPOE_FAIL:	msg = "FAIL";		break;
+      case NGM_PPPOE_CLOSE:	msg = "CLOSE";		break;
+      case NGM_PPPOE_GET_STATUS:	msg = "GET_STATUS";	break;
+      case NGM_PPPOE_ACNAME:
+        msg = "ACNAME";
+        if (setenv("ACNAME", sts->hook, 1) != 0)
+          log_Printf(LogWARN, "setenv: cannot set ACNAME=%s: %m", sts->hook);
+        asciilen = rep->header.arglen;
+        break;
+      case NGM_PPPOE_SESSIONID:
+        msg = "SESSIONID";
+        snprintf(sessionid, sizeof sessionid, "%04x", *(u_int16_t *)sts);
+        if (setenv("SESSIONID", sessionid, 1) != 0)
+          syslog(LOG_WARNING, "setenv: cannot set SESSIONID=%s: %m",
+                 sessionid);
+        /* Use this in preference to our interface index */
+        slot = strtoul(sessionid, &end, 16);
+        if (end != sessionid && *end == '\0')
+            dev->slot = slot;
+        break;
+      default:
+        snprintf(unknown, sizeof unknown, "<%d>", (int)rep->header.cmd);
+        msg = unknown;
+        break;
+    }
+
+    if (asciilen)
+      log_Printf(LogPHASE, "Received NGM_PPPOE_%s (hook \"%.*s\")\n",
+                 msg, asciilen, sts->hook);
+    else
+      log_Printf(LogPHASE, "Received NGM_PPPOE_%s\n", msg);
+
+    switch (rep->header.cmd) {
+      case NGM_PPPOE_SUCCESS:
+        dev->connected = CARRIER_OK;
+        break;
+      case NGM_PPPOE_FAIL:
+      case NGM_PPPOE_CLOSE:
+        dev->connected = CARRIER_LOST;
+        break;
+    }
+  }
+  free(r);
+}
+
+static int
+ether_AwaitCarrier(struct physical *p)
+{
+  struct etherdevice *dev = device2ether(p->handler);
+
+  if (dev->connected != CARRIER_OK && !dev->timeout--)
+    dev->connected = CARRIER_LOST;
+  else if (dev->connected == CARRIER_PENDING)
+    ether_MessageIn(dev);
+
+  return dev->connected;
+}
+
+static const struct device baseetherdevice = {
+  ETHER_DEVICE,
+  "ether",
+  1492,
+  { CD_REQUIRED, DEF_ETHERCDDELAY },
+  ether_AwaitCarrier,
+  ether_RemoveFromSet,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  ether_Free,
+  ether_Read,
+  ether_Write,
+  ether_device2iov,
+  NULL,
+  ether_OpenInfo,
+  ether_Slot
+};
+
+struct device *
+ether_iov2device(int type, struct physical *p, struct iovec *iov, int *niov,
+                 int maxiov __unused, int *auxfd, int *nauxfd)
+{
+  if (type == ETHER_DEVICE) {
+    struct etherdevice *dev = (struct etherdevice *)iov[(*niov)++].iov_base;
+
+    dev = realloc(dev, sizeof *dev);	/* Reduce to the correct size */
+    if (dev == NULL) {
+      log_Printf(LogALERT, "Failed to allocate memory: %d\n",
+                 (int)(sizeof *dev));
+      AbortProgram(EX_OSERR);
+    }
+
+    if (*nauxfd) {
+      dev->cs = *auxfd;
+      (*nauxfd)--;
+    } else
+      dev->cs = -1;
+
+    /* Refresh function pointers etc */
+    memcpy(&dev->dev, &baseetherdevice, sizeof dev->dev);
+
+    physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF);
+    return &dev->dev;
+  }
+
+  return NULL;
+}
+
+static int
+ether_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n)
+{
+  struct physical *p = descriptor2physical(d);
+  struct etherdevice *dev = device2ether(p->handler);
+  int result;
+
+  if (r && dev->cs >= 0) {
+    FD_SET(dev->cs, r);
+    log_Printf(LogTIMER, "%s(ctrl): fdset(r) %d\n", p->link.name, dev->cs);
+    result = 1;
+  } else
+    result = 0;
+
+  result += physical_doUpdateSet(d, r, w, e, n, 0);
+
+  return result;
+}
+
+static int
+ether_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+  struct physical *p = descriptor2physical(d);
+  struct etherdevice *dev = device2ether(p->handler);
+  int result;
+
+  result = dev->cs >= 0 && FD_ISSET(dev->cs, fdset);
+  result += physical_IsSet(d, fdset);
+
+  return result;
+}
+
+static void
+ether_DescriptorRead(struct fdescriptor *d, struct bundle *bundle,
+                     const fd_set *fdset)
+{
+  struct physical *p = descriptor2physical(d);
+  struct etherdevice *dev = device2ether(p->handler);
+
+  if (dev->cs >= 0 && FD_ISSET(dev->cs, fdset)) {
+    ether_MessageIn(dev);
+    if (dev->connected == CARRIER_LOST) {
+      log_Printf(LogPHASE, "%s: Device disconnected\n", p->link.name);
+      datalink_Down(p->dl, CLOSE_NORMAL);
+      return;
+    }
+  }
+
+  if (physical_IsSet(d, fdset))
+    physical_DescriptorRead(d, bundle, fdset);
+}
+
+static struct device *
+ether_Abandon(struct etherdevice *dev, struct physical *p)
+{
+  /* Abandon our node construction */
+  close(dev->cs);
+  close(p->fd);
+  p->fd = -2;	/* Nobody else need try.. */
+  free(dev);
+
+  return NULL;
+}
+
+struct device *
+ether_Create(struct physical *p)
+{
+  u_char rbuf[2048];
+  struct etherdevice *dev;
+  struct ng_mesg *resp;
+  const struct hooklist *hlist;
+  const struct nodeinfo *ninfo;
+  char *path, *sessionid;
+  const char *mode;
+  size_t ifacelen;
+  unsigned f;
+
+  dev = NULL;
+  path = NULL;
+  ifacelen = 0;
+  if (p->fd < 0 && !strncasecmp(p->name.full, NG_PPPOE_NODE_TYPE,
+                                PPPOE_NODE_TYPE_LEN) &&
+      p->name.full[PPPOE_NODE_TYPE_LEN] == ':') {
+    const struct linkinfo *nlink;
+    struct ngpppoe_init_data *data;
+    struct ngm_mkpeer mkp;
+    struct ngm_connect ngc;
+    const char *iface, *provider;
+    char etherid[12];
+    int providerlen;
+    char connectpath[sizeof dev->hook + 2];	/* .:<hook> */
+
+    p->fd--;				/* We own the device - change fd */
+
+    loadmodules(LOAD_VERBOSLY, "netgraph", "ng_ether", "ng_pppoe", "ng_socket",
+                NULL);
+
+    if ((dev = malloc(sizeof *dev)) == NULL)
+      return NULL;
+
+    iface = p->name.full + PPPOE_NODE_TYPE_LEN + 1;
+
+    provider = strchr(iface, ':');
+    if (provider) {
+      ifacelen = provider - iface;
+      provider++;
+      providerlen = strlen(provider);
+    } else {
+      ifacelen = strlen(iface);
+      provider = "";
+      providerlen = 0;
+    }
+
+    /*
+     * We're going to do this (where tunN is our tunnel device):
+     *
+     * .---------.
+     * |  ether  |
+     * | <iface> |                         dev->cs
+     * `---------'                           |
+     *  (orphan)                     p->fd   |
+     *     |                           |     |
+     *     |                           |     |
+     * (ethernet)                      |     |
+     * .---------.                  .-----------.
+     * |  pppoe  |                  |  socket   |
+     * | <iface> |(tunN)<---->(tunN)| <unnamed> |
+     * `---------                   `-----------'
+     *   (tunX)
+     *     ^
+     *     |
+     *     `--->(tunX)
+     */
+
+    /* Create a socket node */
+    if (ID0NgMkSockNode(NULL, &dev->cs, &p->fd) == -1) {
+      log_Printf(LogWARN, "Cannot create netgraph socket node: %s\n",
+                 strerror(errno));
+      free(dev);
+      p->fd = -2;
+      return NULL;
+    }
+
+    /*
+     * Ask for a list of hooks attached to the "ether" node.  This node should
+     * magically exist as a way of hooking stuff onto an ethernet device
+     */
+    path = (char *)alloca(ifacelen + 2);
+    sprintf(path, "%.*s:", (int)ifacelen, iface);
+    if (NgSendMsg(dev->cs, path, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
+                  NULL, 0) < 0) {
+      log_Printf(LogWARN, "%s Cannot send a netgraph message: %s\n",
+                 path, strerror(errno));
+      return ether_Abandon(dev, p);
+    }
+
+    /* Get our list back */
+    resp = (struct ng_mesg *)rbuf;
+    if (NgRecvMsg(dev->cs, resp, sizeof rbuf, NULL) <= 0) {
+      log_Printf(LogWARN, "Cannot get netgraph response: %s\n",
+                 strerror(errno));
+      return ether_Abandon(dev, p);
+    }
+
+    hlist = (const struct hooklist *)resp->data;
+    ninfo = &hlist->nodeinfo;
+
+    /* Make sure we've got the right type of node */
+    if (strncmp(ninfo->type, NG_ETHER_NODE_TYPE,
+                sizeof NG_ETHER_NODE_TYPE - 1)) {
+      log_Printf(LogWARN, "%s Unexpected node type ``%s'' (wanted ``"
+                 NG_ETHER_NODE_TYPE "'')\n", path, ninfo->type);
+      return ether_Abandon(dev, p);
+    }
+
+    log_Printf(LogDEBUG, "List of netgraph node ``%s'' (id %x) hooks:\n",
+               path, ninfo->id);
+
+    /* look for a hook already attached.  */
+    for (f = 0; f < ninfo->hooks; f++) {
+      nlink = &hlist->link[f];
+
+      log_Printf(LogDEBUG, "  Found %s -> %s\n", nlink->ourhook,
+                 nlink->peerhook);
+
+      if (!strcmp(nlink->ourhook, NG_ETHER_HOOK_ORPHAN) ||
+          !strcmp(nlink->ourhook, NG_ETHER_HOOK_DIVERT)) {
+        /*
+         * Something is using the data coming out of this ``ether'' node.
+         * If it's a PPPoE node, we use that node, otherwise we complain that
+         * someone else is using the node.
+         */
+        if (!strcmp(nlink->nodeinfo.type, NG_PPPOE_NODE_TYPE))
+          /* Use this PPPoE node ! */
+          snprintf(ngc.path, sizeof ngc.path, "[%x]:", nlink->nodeinfo.id);
+        else {
+          log_Printf(LogWARN, "%s Node type ``%s'' is currently active\n",
+                     path, nlink->nodeinfo.type);
+          return ether_Abandon(dev, p);
+        }
+        break;
+      }
+    }
+
+    if (f == ninfo->hooks) {
+      /*
+       * Create a new ``PPPoE'' node connected to the ``ether'' node using
+       * the ``orphan'' and ``ethernet'' hooks
+       */
+      snprintf(mkp.type, sizeof mkp.type, "%s", NG_PPPOE_NODE_TYPE);
+      snprintf(mkp.ourhook, sizeof mkp.ourhook, "%s", NG_ETHER_HOOK_ORPHAN);
+      snprintf(mkp.peerhook, sizeof mkp.peerhook, "%s", NG_PPPOE_HOOK_ETHERNET);
+      snprintf(etherid, sizeof etherid, "[%x]:", ninfo->id);
+
+      log_Printf(LogDEBUG, "Creating PPPoE netgraph node %s%s -> %s\n",
+                 etherid, mkp.ourhook, mkp.peerhook);
+
+      if (NgSendMsg(dev->cs, etherid, NGM_GENERIC_COOKIE,
+                    NGM_MKPEER, &mkp, sizeof mkp) < 0) {
+        log_Printf(LogWARN, "%s Cannot create PPPoE netgraph node: %s\n",
+                   etherid, strerror(errno));
+        return ether_Abandon(dev, p);
+      }
+
+      snprintf(ngc.path, sizeof ngc.path, "%s%s", path, NG_ETHER_HOOK_ORPHAN);
+    }
+
+    snprintf(dev->hook, sizeof dev->hook, "%s%d",
+             TUN_NAME, p->dl->bundle->unit);
+
+    /*
+     * Connect the PPPoE node to our socket node.
+     * ngc.path has already been set up
+     */
+    snprintf(ngc.ourhook, sizeof ngc.ourhook, "%s", dev->hook);
+    memcpy(ngc.peerhook, ngc.ourhook, sizeof ngc.peerhook);
+
+    log_Printf(LogDEBUG, "Connecting netgraph socket .:%s -> %s:%s\n",
+               ngc.ourhook, ngc.path, ngc.peerhook);
+    if (NgSendMsg(dev->cs, ".:", NGM_GENERIC_COOKIE,
+                  NGM_CONNECT, &ngc, sizeof ngc) < 0) {
+      log_Printf(LogWARN, "Cannot connect PPPoE and socket netgraph "
+                 "nodes: %s\n", strerror(errno));
+      return ether_Abandon(dev, p);
+    }
+
+    /* Bring the Ethernet interface up */
+    path[ifacelen] = '\0';	/* Remove the trailing ':' */
+    if (!iface_SetFlags(path, IFF_UP))
+      log_Printf(LogWARN, "%s: Failed to set the IFF_UP flag on %s\n",
+                 p->link.name, path);
+
+    snprintf(connectpath, sizeof connectpath, ".:%s", dev->hook);
+
+    /* Configure node to 3Com mode if needed */
+    if (p->cfg.pppoe_configured) {
+      mode = p->cfg.nonstandard_pppoe ? NG_PPPOE_NONSTANDARD : NG_PPPOE_STANDARD;
+      if (NgSendMsg(dev->cs, connectpath, NGM_PPPOE_COOKIE,
+		NGM_PPPOE_SETMODE, mode, strlen(mode) + 1) == -1) {
+        log_Printf(LogWARN, "``%s'': Cannot configure netgraph node: %s\n",
+                 connectpath, strerror(errno));
+        return ether_Abandon(dev, p);
+      }
+    }
+
+    /* And finally, request a connection to the given provider */
+
+    data = (struct ngpppoe_init_data *)alloca(sizeof *data + providerlen);
+    snprintf(data->hook, sizeof data->hook, "%s", dev->hook);
+    memcpy(data->data, provider, providerlen);
+    data->data_len = providerlen;
+
+    log_Printf(LogDEBUG, "Sending PPPOE_CONNECT to %s\n", connectpath);
+    if (NgSendMsg(dev->cs, connectpath, NGM_PPPOE_COOKIE,
+                  NGM_PPPOE_CONNECT, data, sizeof *data + providerlen) == -1) {
+      log_Printf(LogWARN, "``%s'': Cannot start netgraph node: %s\n",
+                 connectpath, strerror(errno));
+      return ether_Abandon(dev, p);
+    }
+
+    /* Hook things up so that we monitor dev->cs */
+    p->desc.UpdateSet = ether_UpdateSet;
+    p->desc.IsSet = ether_IsSet;
+    p->desc.Read = ether_DescriptorRead;
+
+    memcpy(&dev->dev, &baseetherdevice, sizeof dev->dev);
+    switch (p->cfg.cd.necessity) {
+      case CD_VARIABLE:
+        dev->dev.cd.delay = p->cfg.cd.delay;
+        break;
+      case CD_REQUIRED:
+        dev->dev.cd = p->cfg.cd;
+        break;
+      case CD_NOTREQUIRED:
+        log_Printf(LogWARN, "%s: Carrier must be set, using ``set cd %d!''\n",
+                   p->link.name, dev->dev.cd.delay);
+      case CD_DEFAULT:
+        break;
+    }
+
+    dev->timeout = dev->dev.cd.delay;
+    dev->connected = CARRIER_PENDING;
+    /* This will be overridden by our session id - if provided by netgraph */
+    dev->slot = GetIfIndex(path);
+  } else {
+    /* See if we're a netgraph socket */
+    struct stat st;
+
+    if (fstat(p->fd, &st) != -1 && (st.st_mode & S_IFSOCK)) {
+      struct sockaddr_storage ssock;
+      struct sockaddr *sock = (struct sockaddr *)&ssock;
+      int sz;
+
+      sz = sizeof ssock;
+      if (getsockname(p->fd, sock, &sz) == -1) {
+        log_Printf(LogPHASE, "%s: Link is a closed socket !\n", p->link.name);
+        close(p->fd);
+        p->fd = -1;
+        return NULL;
+      }
+
+      if (sock->sa_family == AF_NETGRAPH) {
+        /*
+         * It's a netgraph node... We can't determine hook names etc, so we
+         * stay pretty impartial....
+         */
+        log_Printf(LogPHASE, "%s: Link is a netgraph node\n", p->link.name);
+
+        if ((dev = malloc(sizeof *dev)) == NULL) {
+          log_Printf(LogWARN, "%s: Cannot allocate an ether device: %s\n",
+                     p->link.name, strerror(errno));
+          return NULL;
+        }
+
+        memcpy(&dev->dev, &baseetherdevice, sizeof dev->dev);
+        dev->cs = -1;
+        dev->timeout = 0;
+        dev->connected = CARRIER_OK;
+        *dev->hook = '\0';
+
+        /*
+         * If we're being envoked from pppoed(8), we may have a SESSIONID
+         * set in the environment.  If so, use it as the slot
+         */
+        if ((sessionid = getenv("SESSIONID")) != NULL) {
+          char *end;
+          u_long slot;
+
+          slot = strtoul(sessionid, &end, 16);
+          dev->slot = end != sessionid && *end == '\0' ? slot : 0;
+        } else
+          dev->slot = 0;
+      }
+    }
+  }
+
+  if (dev) {
+    physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF);
+    return &dev->dev;
+  }
+
+  return NULL;
+}
diff --git a/src/ether.h b/src/ether.h
new file mode 100644
index 0000000..3d48dc1
--- /dev/null
+++ b/src/ether.h
@@ -0,0 +1,37 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ether.h,v 1.3.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct physical;
+struct device;
+
+#define DEF_ETHERCDDELAY	5	/* Default ``set cd'' value */
+
+extern struct device *ether_Create(struct physical *);
+extern struct device *ether_iov2device(int, struct physical *, struct iovec *,
+                                       int *, int, int *, int *);
+extern unsigned ether_DeviceSize(void);
diff --git a/src/exec.c b/src/exec.c
new file mode 100644
index 0000000..8e0a554
--- /dev/null
+++ b/src/exec.c
@@ -0,0 +1,410 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/exec.c,v 1.29.10.1.4.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "mp.h"
+#include "chat.h"
+#include "command.h"
+#include "auth.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "id.h"
+#include "main.h"
+#include "exec.h"
+
+
+struct execdevice {
+  struct device dev;		/* What struct physical knows about */
+  int fd_out;			/* output descriptor */
+};
+
+#define device2exec(d) ((d)->type == EXEC_DEVICE ? (struct execdevice *)d : NULL)
+
+unsigned
+exec_DeviceSize(void)
+{
+  return sizeof(struct execdevice);
+}
+
+static void
+exec_Free(struct physical *p)
+{
+  struct execdevice *dev = device2exec(p->handler);
+
+  if (dev->fd_out != -1)
+    close(dev->fd_out);
+  free(dev);
+}
+
+static void
+exec_device2iov(struct device *d, struct iovec *iov, int *niov,
+               int maxiov __unused, int *auxfd, int *nauxfd)
+{
+  struct execdevice *dev;
+  int sz = physical_MaxDeviceSize();
+
+  iov[*niov].iov_base = d = realloc(d, sz);
+  if (d == NULL) {
+    log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
+    AbortProgram(EX_OSERR);
+  }
+  iov[*niov].iov_len = sz;
+  (*niov)++;
+
+  dev = device2exec(d);
+  if (dev->fd_out >= 0) {
+    *auxfd = dev->fd_out;
+    (*nauxfd)++;
+  }
+}
+
+static int
+exec_RemoveFromSet(struct physical *p, fd_set *r, fd_set *w, fd_set *e)
+{
+  struct execdevice *dev = device2exec(p->handler);
+  int sets;
+
+  p->handler->removefromset = NULL;
+  sets = physical_RemoveFromSet(p, r, w, e);
+  p->handler->removefromset = exec_RemoveFromSet;
+
+  if (dev->fd_out >= 0) {
+    if (w && FD_ISSET(dev->fd_out, w)) {
+      FD_CLR(dev->fd_out, w);
+      log_Printf(LogTIMER, "%s: fdunset(w) %d\n", p->link.name, dev->fd_out);
+      sets++;
+    }
+    if (e && FD_ISSET(dev->fd_out, e)) {
+      FD_CLR(dev->fd_out, e);
+      log_Printf(LogTIMER, "%s: fdunset(e) %d\n", p->link.name, dev->fd_out);
+      sets++;
+    }
+  }
+
+  return sets;
+}
+
+static ssize_t
+exec_Write(struct physical *p, const void *v, size_t n)
+{
+  struct execdevice *dev = device2exec(p->handler);
+  int fd = dev->fd_out == -1 ? p->fd : dev->fd_out;
+
+  return write(fd, v, n);
+}
+
+static struct device baseexecdevice = {
+  EXEC_DEVICE,
+  "exec",
+  0,
+  { CD_NOTREQUIRED, 0 },
+  NULL,
+  exec_RemoveFromSet,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  exec_Free,
+  NULL,
+  exec_Write,
+  exec_device2iov,
+  NULL,
+  NULL,
+  NULL
+};
+
+struct device *
+exec_iov2device(int type, struct physical *p, struct iovec *iov,
+                int *niov, int maxiov __unused, int *auxfd, int *nauxfd)
+{
+  if (type == EXEC_DEVICE) {
+    struct execdevice *dev = (struct execdevice *)iov[(*niov)++].iov_base;
+
+    dev = realloc(dev, sizeof *dev);	/* Reduce to the correct size */
+    if (dev == NULL) {
+      log_Printf(LogALERT, "Failed to allocate memory: %d\n",
+                 (int)(sizeof *dev));
+      AbortProgram(EX_OSERR);
+    }
+
+    if (*nauxfd) {
+      dev->fd_out = *auxfd;
+      (*nauxfd)--;
+    } else
+      dev->fd_out = -1;
+
+    /* Refresh function pointers etc */
+    memcpy(&dev->dev, &baseexecdevice, sizeof dev->dev);
+
+    physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
+    return &dev->dev;
+  }
+
+  return NULL;
+}
+
+static int
+exec_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n)
+{
+  struct physical *p = descriptor2physical(d);
+  struct execdevice *dev = device2exec(p->handler);
+  int result = 0;
+
+  if (w && dev->fd_out >= 0) {
+    FD_SET(dev->fd_out, w);
+    log_Printf(LogTIMER, "%s: fdset(w) %d\n", p->link.name, dev->fd_out);
+    result++;
+    w = NULL;
+  }
+
+  if (e && dev->fd_out >= 0) {
+    FD_SET(dev->fd_out, e);
+    log_Printf(LogTIMER, "%s: fdset(e) %d\n", p->link.name, dev->fd_out);
+    result++;
+  }
+
+  if (result && *n <= dev->fd_out)
+    *n = dev->fd_out + 1;
+
+  return result + physical_doUpdateSet(d, r, w, e, n, 0);
+}
+
+static int
+exec_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+  struct physical *p = descriptor2physical(d);
+  struct execdevice *dev = device2exec(p->handler);
+  int result = dev->fd_out >= 0 && FD_ISSET(dev->fd_out, fdset);
+  result += physical_IsSet(d, fdset);
+
+  return result;
+}
+
+struct device *
+exec_Create(struct physical *p)
+{
+  struct execdevice *dev;
+
+  dev = NULL;
+  if (p->fd < 0) {
+    if (*p->name.full == '!') {
+      int fids[2], type;
+  
+      if ((dev = malloc(sizeof *dev)) == NULL) {
+        log_Printf(LogWARN, "%s: Cannot allocate an exec device: %s\n",
+                   p->link.name, strerror(errno));
+        return NULL;
+      }
+      dev->fd_out = -1;
+  
+      p->fd--;	/* We own the device but maybe can't use it - change fd */
+      type = physical_IsSync(p) ? SOCK_DGRAM : SOCK_STREAM;
+  
+      if (socketpair(AF_UNIX, type, PF_UNSPEC, fids) < 0) {
+        log_Printf(LogPHASE, "Unable to create pipe for line exec: %s\n",
+                   strerror(errno));
+        free(dev);
+        dev = NULL;
+      } else {
+        static int child_status;		/* This variable is abused ! */
+        int stat, argc, i, ret, wret, pidpipe[2];
+        pid_t pid, realpid;
+        char *argv[MAXARGS];
+  
+        stat = fcntl(fids[0], F_GETFL, 0);
+        if (stat > 0) {
+          stat |= O_NONBLOCK;
+          fcntl(fids[0], F_SETFL, stat);
+        }
+        realpid = getpid();
+        if (pipe(pidpipe) == -1) {
+          log_Printf(LogPHASE, "Unable to pipe for line exec: %s\n",
+                     strerror(errno));
+          close(fids[1]);
+          close(fids[0]);
+          free(dev);
+          dev = NULL;
+        } else switch ((pid = fork())) {
+          case -1:
+            log_Printf(LogPHASE, "Unable to fork for line exec: %s\n",
+                       strerror(errno));
+            close(pidpipe[0]);
+            close(pidpipe[1]);
+            close(fids[1]);
+            close(fids[0]);
+            break;
+  
+          case 0:
+            close(pidpipe[0]);
+            close(fids[0]);
+            timer_TermService();
+  #ifndef NOSUID
+            setuid(ID0realuid());
+  #endif
+  
+            child_status = 0;
+            switch ((pid = vfork())) {
+              case 0:
+                close(pidpipe[1]);
+                break;
+  
+              case -1:
+                ret = errno;
+                log_Printf(LogPHASE, "Unable to vfork to drop parent: %s\n",
+                           strerror(errno));
+                close(pidpipe[1]);
+                _exit(ret);
+  
+              default:
+                write(pidpipe[1], &pid, sizeof pid);
+                close(pidpipe[1]);
+                _exit(child_status);	/* The error from exec() ! */
+            }
+  
+            log_Printf(LogDEBUG, "Exec'ing ``%s''\n", p->name.base);
+  
+            if ((argc = MakeArgs(p->name.base, argv, VECSIZE(argv),
+                                 PARSE_REDUCE|PARSE_NOHASH)) < 0) {
+              log_Printf(LogWARN, "Syntax error in exec command\n");
+              _exit(ESRCH);
+            }
+  
+            command_Expand(argv, argc, (char const *const *)argv,
+                           p->dl->bundle, 0, realpid);
+  
+            dup2(fids[1], STDIN_FILENO);
+            dup2(fids[1], STDOUT_FILENO);
+            dup2(fids[1], STDERR_FILENO);
+            for (i = getdtablesize(); i > STDERR_FILENO; i--)
+              fcntl(i, F_SETFD, 1);
+  
+            execvp(*argv, argv);
+            child_status = errno;		/* Only works for vfork() */
+            printf("execvp failed: %s: %s\r\n", *argv, strerror(child_status));
+            _exit(child_status);
+            break;
+  
+          default:
+            close(pidpipe[1]);
+            close(fids[1]);
+            if (read(pidpipe[0], &p->session_owner, sizeof p->session_owner) !=
+                sizeof p->session_owner)
+              p->session_owner = (pid_t)-1;
+            close(pidpipe[0]);
+            while ((wret = waitpid(pid, &stat, 0)) == -1 && errno == EINTR)
+              ;
+            if (wret == -1) {
+              log_Printf(LogWARN, "Waiting for child process: %s\n",
+                         strerror(errno));
+              close(fids[0]);
+              p->session_owner = (pid_t)-1;
+              break;
+            } else if (WIFSIGNALED(stat)) {
+              log_Printf(LogWARN, "Child process received sig %d !\n",
+                         WTERMSIG(stat));
+              close(fids[0]);
+              p->session_owner = (pid_t)-1;
+              break;
+            } else if (WIFSTOPPED(stat)) {
+              log_Printf(LogWARN, "Child process received stop sig %d !\n",
+                         WSTOPSIG(stat));
+              /* I guess that's ok.... */
+            } else if ((ret = WEXITSTATUS(stat))) {
+              log_Printf(LogWARN, "Cannot exec \"%s\": %s\n", p->name.base,
+                         strerror(ret));
+              close(fids[0]);
+              p->session_owner = (pid_t)-1;
+              break;
+            }
+            p->fd = fids[0];
+            log_Printf(LogDEBUG, "Using descriptor %d for child\n", p->fd);
+        }
+      }
+    }
+  } else {
+    struct stat st;
+
+    if (fstat(p->fd, &st) != -1 && (st.st_mode & S_IFIFO)) {
+      if ((dev = malloc(sizeof *dev)) == NULL)
+        log_Printf(LogWARN, "%s: Cannot allocate an exec device: %s\n",
+                   p->link.name, strerror(errno));
+      else if (p->fd == STDIN_FILENO) {
+        log_Printf(LogPHASE, "%s: Using stdin/stdout to communicate with "
+                   "parent (pipe mode)\n", p->link.name);
+        dev->fd_out = dup(STDOUT_FILENO);
+
+        /* Hook things up so that we monitor dev->fd_out */
+        p->desc.UpdateSet = exec_UpdateSet;
+        p->desc.IsSet = exec_IsSet;
+      } else
+        dev->fd_out = -1;
+    }
+  }
+
+  if (dev) {
+    memcpy(&dev->dev, &baseexecdevice, sizeof dev->dev);
+    physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
+    if (p->cfg.cd.necessity != CD_DEFAULT)
+      log_Printf(LogWARN, "Carrier settings ignored\n");
+    return &dev->dev;
+  }
+
+  return NULL;
+}
diff --git a/src/exec.h b/src/exec.h
new file mode 100644
index 0000000..694acae
--- /dev/null
+++ b/src/exec.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/exec.h,v 1.5.46.1.4.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct physical;
+struct device;
+
+extern struct device *exec_Create(struct physical *);
+extern struct device *exec_iov2device(int, struct physical *,
+                                      struct iovec *, int *, int, int *, int *);
+extern unsigned exec_DeviceSize(void);
diff --git a/src/filter.c b/src/filter.c
new file mode 100644
index 0000000..26c26d3
--- /dev/null
+++ b/src/filter.c
@@ -0,0 +1,604 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/filter.c,v 1.52.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "iplist.h"
+#include "timer.h"
+#include "throughput.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+static unsigned filter_Nam2Op(const char *);
+
+static int
+ParsePort(const char *service, const char *proto)
+{
+  struct servent *servent;
+  char *cp;
+  int port;
+
+  servent = getservbyname(service, proto);
+  if (servent != 0)
+    return ntohs(servent->s_port);
+
+  port = strtol(service, &cp, 0);
+  if (cp == service) {
+    log_Printf(LogWARN, "ParsePort: %s is not a port name or number.\n",
+	      service);
+    return 0;
+  }
+  return port;
+}
+
+/*
+ *	ICMP Syntax:	src eq icmp_message_type
+ */
+static int
+ParseIcmp(int argc, char const *const *argv, struct filterent *tgt)
+{
+  int type;
+  char *cp;
+
+  switch (argc) {
+  case 0:
+    /* permit/deny all ICMP types */
+    tgt->f_srcop = tgt->f_dstop = OP_NONE;
+    break;
+
+  case 3:
+    if (!strcmp(*argv, "src") && !strcmp(argv[1], "eq")) {
+      type = strtol(argv[2], &cp, 0);
+      if (cp == argv[2]) {
+	log_Printf(LogWARN, "ParseIcmp: type is expected.\n");
+	return 0;
+      }
+      tgt->f_srcop = OP_EQ;
+      tgt->f_srcport = type;
+      tgt->f_dstop = OP_NONE;
+    }
+    break;
+
+  default:
+    log_Printf(LogWARN, "ParseIcmp: bad icmp syntax.\n");
+    return 0;
+  }
+  return 1;
+}
+
+/*
+ *	UDP Syntax: [src op port] [dst op port]
+ */
+static int
+ParseUdpOrTcp(int argc, char const *const *argv, const struct protoent *pe,
+              struct filterent *tgt)
+{
+  tgt->f_srcop = tgt->f_dstop = OP_NONE;
+  tgt->f_estab = tgt->f_syn = tgt->f_finrst = 0;
+
+  if (argc >= 3 && !strcmp(*argv, "src")) {
+    tgt->f_srcop = filter_Nam2Op(argv[1]);
+    if (tgt->f_srcop == OP_NONE) {
+      log_Printf(LogWARN, "ParseUdpOrTcp: bad operator\n");
+      return 0;
+    }
+    if (pe == NULL)
+      return 0;
+    tgt->f_srcport = ParsePort(argv[2], pe->p_name);
+    if (tgt->f_srcport == 0)
+      return 0;
+    argc -= 3;
+    argv += 3;
+  }
+
+  if (argc >= 3 && !strcmp(argv[0], "dst")) {
+    tgt->f_dstop = filter_Nam2Op(argv[1]);
+    if (tgt->f_dstop == OP_NONE) {
+      log_Printf(LogWARN, "ParseUdpOrTcp: bad operator\n");
+      return 0;
+    }
+    if (pe == NULL)
+      return 0;
+    tgt->f_dstport = ParsePort(argv[2], pe->p_name);
+    if (tgt->f_dstport == 0)
+      return 0;
+    argc -= 3;
+    argv += 3;
+  }
+
+  if (pe && pe->p_proto == IPPROTO_TCP) {
+    for (; argc > 0; argc--, argv++)
+      if (!strcmp(*argv, "estab"))
+        tgt->f_estab = 1;
+      else if (!strcmp(*argv, "syn"))
+        tgt->f_syn = 1;
+      else if (!strcmp(*argv, "finrst"))
+        tgt->f_finrst = 1;
+      else
+        break;
+  }
+
+  if (argc > 0) {
+    log_Printf(LogWARN, "ParseUdpOrTcp: bad src/dst port syntax: %s\n", *argv);
+    return 0;
+  }
+
+  return 1;
+}
+
+static int
+ParseGeneric(int argc, struct filterent *tgt)
+{
+  /*
+   * Filter currently is a catch-all. Requests are either permitted or
+   * dropped.
+   */
+  if (argc != 0) {
+    log_Printf(LogWARN, "ParseGeneric: Too many parameters\n");
+    return 0;
+  } else
+    tgt->f_srcop = tgt->f_dstop = OP_NONE;
+
+  return 1;
+}
+
+static unsigned
+addrtype(const char *addr)
+{
+  if (!strncasecmp(addr, "MYADDR", 6) && (addr[6] == '\0' || addr[6] == '/'))
+    return T_MYADDR;
+  if (!strncasecmp(addr, "MYADDR6", 7) && (addr[7] == '\0' || addr[7] == '/'))
+    return T_MYADDR6;
+  if (!strncasecmp(addr, "HISADDR", 7) && (addr[7] == '\0' || addr[7] == '/'))
+    return T_HISADDR;
+  if (!strncasecmp(addr, "HISADDR6", 8) && (addr[8] == '\0' || addr[8] == '/'))
+    return T_HISADDR6;
+  if (!strncasecmp(addr, "DNS0", 4) && (addr[4] == '\0' || addr[4] == '/'))
+    return T_DNS0;
+  if (!strncasecmp(addr, "DNS1", 4) && (addr[4] == '\0' || addr[4] == '/'))
+    return T_DNS1;
+
+  return T_ADDR;
+}
+
+static const char *
+addrstr(struct ncprange *addr, unsigned type)
+{
+  switch (type) {
+    case T_MYADDR:
+      return "MYADDR";
+    case T_HISADDR:
+      return "HISADDR";
+    case T_DNS0:
+      return "DNS0";
+    case T_DNS1:
+      return "DNS1";
+  }
+  return ncprange_ntoa(addr);
+}
+
+static int
+filter_Parse(struct ncp *ncp, int argc, char const *const *argv,
+             struct filterent *ofp)
+{
+  struct filterent fe;
+  struct protoent *pe;
+  char *wp;
+  int action, family, ruleno, val, width;
+
+  ruleno = strtol(*argv, &wp, 0);
+  if (*argv == wp || ruleno >= MAXFILTERS) {
+    log_Printf(LogWARN, "Parse: invalid filter number.\n");
+    return 0;
+  }
+  if (ruleno < 0) {
+    for (ruleno = 0; ruleno < MAXFILTERS; ruleno++) {
+      ofp->f_action = A_NONE;
+      ofp++;
+    }
+    log_Printf(LogWARN, "Parse: filter cleared.\n");
+    return 1;
+  }
+  ofp += ruleno;
+
+  if (--argc == 0) {
+    log_Printf(LogWARN, "Parse: missing action.\n");
+    return 0;
+  }
+  argv++;
+
+  memset(&fe, '\0', sizeof fe);
+
+  val = strtol(*argv, &wp, 0);
+  if (!*wp && val >= 0 && val < MAXFILTERS) {
+    if (val <= ruleno) {
+      log_Printf(LogWARN, "Parse: Can only jump forward from rule %d\n",
+                 ruleno);
+      return 0;
+    }
+    action = val;
+  } else if (!strcmp(*argv, "permit")) {
+    action = A_PERMIT;
+  } else if (!strcmp(*argv, "deny")) {
+    action = A_DENY;
+  } else if (!strcmp(*argv, "clear")) {
+    ofp->f_action = A_NONE;
+    return 1;
+  } else {
+    log_Printf(LogWARN, "Parse: %s: bad action\n", *argv);
+    return 0;
+  }
+  fe.f_action = action;
+
+  argc--;
+  argv++;
+
+  if (argc && argv[0][0] == '!' && !argv[0][1]) {
+    fe.f_invert = 1;
+    argc--;
+    argv++;
+  }
+
+  ncprange_init(&fe.f_src);
+  ncprange_init(&fe.f_dst);
+
+  if (argc == 0)
+    pe = NULL;
+  else if ((pe = getprotobyname(*argv)) == NULL && strcmp(*argv, "all") != 0) {
+    if (argc < 2) {
+      log_Printf(LogWARN, "Parse: Protocol or address pair expected\n");
+      return 0;
+    } else if (strcasecmp(*argv, "any") == 0 ||
+               ncprange_aton(&fe.f_src, ncp, *argv)) {
+      family = ncprange_family(&fe.f_src);
+      if (!ncprange_getwidth(&fe.f_src, &width))
+        width = 0;
+      if (width == 0)
+        ncprange_init(&fe.f_src);
+      fe.f_srctype = addrtype(*argv);
+      argc--;
+      argv++;
+
+      if (strcasecmp(*argv, "any") == 0 ||
+          ncprange_aton(&fe.f_dst, ncp, *argv)) {
+        if (ncprange_family(&fe.f_dst) != AF_UNSPEC &&
+            ncprange_family(&fe.f_src) != AF_UNSPEC &&
+            family != ncprange_family(&fe.f_dst)) {
+          log_Printf(LogWARN, "Parse: src and dst address families differ\n");
+          return 0;
+        }
+        if (!ncprange_getwidth(&fe.f_dst, &width))
+          width = 0;
+        if (width == 0)
+          ncprange_init(&fe.f_dst);
+        fe.f_dsttype = addrtype(*argv);
+        argc--;
+        argv++;
+      } else {
+        log_Printf(LogWARN, "Parse: Protocol or address pair expected\n");
+        return 0;
+      }
+
+      if (argc) {
+        if ((pe = getprotobyname(*argv)) == NULL && strcmp(*argv, "all") != 0) {
+          log_Printf(LogWARN, "Parse: %s: Protocol expected\n", *argv);
+          return 0;
+        } else {
+          argc--;
+          argv++;
+        }
+      }
+    } else {
+      log_Printf(LogWARN, "Parse: Protocol or address pair expected\n");
+      return 0;
+    }
+  } else {
+    argc--;
+    argv++;
+  }
+
+  if (argc >= 2 && strcmp(*argv, "timeout") == 0) {
+    fe.timeout = strtoul(argv[1], NULL, 10);
+    argc -= 2;
+    argv += 2;
+  }
+
+  val = 1;
+  fe.f_proto = (pe == NULL) ? 0 : pe->p_proto;
+
+  switch (fe.f_proto) {
+  case IPPROTO_TCP:
+  case IPPROTO_UDP:
+  case IPPROTO_IPIP:
+#ifndef NOINET6
+  case IPPROTO_IPV6:
+#endif
+    val = ParseUdpOrTcp(argc, argv, pe, &fe);
+    break;
+  case IPPROTO_ICMP:
+#ifndef NOINET6
+  case IPPROTO_ICMPV6:
+#endif
+    val = ParseIcmp(argc, argv, &fe);
+    break;
+  default:
+    val = ParseGeneric(argc, &fe);
+    break;
+  }
+
+  log_Printf(LogDEBUG, "Parse: Src: %s\n", ncprange_ntoa(&fe.f_src));
+  log_Printf(LogDEBUG, "Parse: Dst: %s\n", ncprange_ntoa(&fe.f_dst));
+  log_Printf(LogDEBUG, "Parse: Proto: %d\n", fe.f_proto);
+
+  log_Printf(LogDEBUG, "Parse: src:  %s (%d)\n",
+            filter_Op2Nam(fe.f_srcop), fe.f_srcport);
+  log_Printf(LogDEBUG, "Parse: dst:  %s (%d)\n",
+            filter_Op2Nam(fe.f_dstop), fe.f_dstport);
+  log_Printf(LogDEBUG, "Parse: estab: %u\n", fe.f_estab);
+  log_Printf(LogDEBUG, "Parse: syn: %u\n", fe.f_syn);
+  log_Printf(LogDEBUG, "Parse: finrst: %u\n", fe.f_finrst);
+
+  if (val)
+    *ofp = fe;
+
+  return val;
+}
+
+int
+filter_Set(struct cmdargs const *arg)
+{
+  struct filter *filter;
+
+  if (arg->argc < arg->argn+2)
+    return -1;
+
+  if (!strcmp(arg->argv[arg->argn], "in"))
+    filter = &arg->bundle->filter.in;
+  else if (!strcmp(arg->argv[arg->argn], "out"))
+    filter = &arg->bundle->filter.out;
+  else if (!strcmp(arg->argv[arg->argn], "dial"))
+    filter = &arg->bundle->filter.dial;
+  else if (!strcmp(arg->argv[arg->argn], "alive"))
+    filter = &arg->bundle->filter.alive;
+  else {
+    log_Printf(LogWARN, "filter_Set: %s: Invalid filter name.\n",
+              arg->argv[arg->argn]);
+    return -1;
+  }
+
+  filter_Parse(&arg->bundle->ncp, arg->argc - arg->argn - 1,
+        arg->argv + arg->argn + 1, filter->rule);
+  return 0;
+}
+
+const char *
+filter_Action2Nam(unsigned act)
+{
+  static const char * const actname[] = { "  none ", "permit ", "  deny " };
+  static char buf[8];
+
+  if (act < MAXFILTERS) {
+    snprintf(buf, sizeof buf, "%6d ", act);
+    return buf;
+  } else if (act >= A_NONE && act < A_NONE + sizeof(actname)/sizeof(char *))
+    return actname[act - A_NONE];
+  else
+    return "?????? ";
+}
+
+static void
+doShowFilter(struct filterent *fp, struct prompt *prompt)
+{
+  struct protoent *pe;
+  int n;
+
+  for (n = 0; n < MAXFILTERS; n++, fp++) {
+    if (fp->f_action != A_NONE) {
+      prompt_Printf(prompt, "  %2d %s", n, filter_Action2Nam(fp->f_action));
+      prompt_Printf(prompt, "%c ", fp->f_invert ? '!' : ' ');
+
+      if (ncprange_isset(&fp->f_src))
+        prompt_Printf(prompt, "%s ", addrstr(&fp->f_src, fp->f_srctype));
+      else
+        prompt_Printf(prompt, "any ");
+
+      if (ncprange_isset(&fp->f_dst))
+        prompt_Printf(prompt, "%s ", addrstr(&fp->f_dst, fp->f_dsttype));
+      else
+        prompt_Printf(prompt, "any ");
+
+      if (fp->f_proto) {
+        if ((pe = getprotobynumber(fp->f_proto)) == NULL)
+	  prompt_Printf(prompt, "P:%d", fp->f_proto);
+        else
+	  prompt_Printf(prompt, "%s", pe->p_name);
+
+	if (fp->f_srcop)
+	  prompt_Printf(prompt, " src %s %d", filter_Op2Nam(fp->f_srcop),
+		  fp->f_srcport);
+	if (fp->f_dstop)
+	  prompt_Printf(prompt, " dst %s %d", filter_Op2Nam(fp->f_dstop),
+		  fp->f_dstport);
+	if (fp->f_estab)
+	  prompt_Printf(prompt, " estab");
+	if (fp->f_syn)
+	  prompt_Printf(prompt, " syn");
+	if (fp->f_finrst)
+	  prompt_Printf(prompt, " finrst");
+      } else
+	prompt_Printf(prompt, "all");
+      if (fp->timeout != 0)
+	  prompt_Printf(prompt, " timeout %u", fp->timeout);
+      prompt_Printf(prompt, "\n");
+    }
+  }
+}
+
+int
+filter_Show(struct cmdargs const *arg)
+{
+  if (arg->argc > arg->argn+1)
+    return -1;
+
+  if (arg->argc == arg->argn+1) {
+    struct filter *filter;
+
+    if (!strcmp(arg->argv[arg->argn], "in"))
+      filter = &arg->bundle->filter.in;
+    else if (!strcmp(arg->argv[arg->argn], "out"))
+      filter = &arg->bundle->filter.out;
+    else if (!strcmp(arg->argv[arg->argn], "dial"))
+      filter = &arg->bundle->filter.dial;
+    else if (!strcmp(arg->argv[arg->argn], "alive"))
+      filter = &arg->bundle->filter.alive;
+    else
+      return -1;
+    doShowFilter(filter->rule, arg->prompt);
+  } else {
+    struct filter *filter[4];
+    int f;
+
+    filter[0] = &arg->bundle->filter.in;
+    filter[1] = &arg->bundle->filter.out;
+    filter[2] = &arg->bundle->filter.dial;
+    filter[3] = &arg->bundle->filter.alive;
+    for (f = 0; f < 4; f++) {
+      if (f)
+        prompt_Printf(arg->prompt, "\n");
+      prompt_Printf(arg->prompt, "%s:\n", filter[f]->name);
+      doShowFilter(filter[f]->rule, arg->prompt);
+    }
+  }
+
+  return 0;
+}
+
+static const char * const opname[] = {"none", "eq", "gt", "lt"};
+
+const char *
+filter_Op2Nam(unsigned op)
+{
+  if (op >= sizeof opname / sizeof opname[0])
+    return "unknown";
+  return opname[op];
+
+}
+
+static unsigned
+filter_Nam2Op(const char *cp)
+{
+  unsigned op;
+
+  for (op = sizeof opname / sizeof opname[0] - 1; op; op--)
+    if (!strcasecmp(cp, opname[op]))
+      break;
+
+  return op;
+}
+
+void
+filter_AdjustAddr(struct filter *filter, struct ncpaddr *local,
+                  struct ncpaddr *remote, struct in_addr *dns)
+{
+  struct filterent *fp;
+  int n;
+
+  for (fp = filter->rule, n = 0; n < MAXFILTERS; fp++, n++)
+    if (fp->f_action != A_NONE) {
+      if (local) {
+        if (fp->f_srctype == T_MYADDR && ncpaddr_family(local) == AF_INET)
+          ncprange_sethost(&fp->f_src, local);
+        if (fp->f_dsttype == T_MYADDR && ncpaddr_family(local) == AF_INET)
+          ncprange_sethost(&fp->f_dst, local);
+#ifndef NOINET6
+        if (fp->f_srctype == T_MYADDR6 && ncpaddr_family(local) == AF_INET6)
+          ncprange_sethost(&fp->f_src, local);
+        if (fp->f_dsttype == T_MYADDR6 && ncpaddr_family(local) == AF_INET6)
+          ncprange_sethost(&fp->f_dst, local);
+#endif
+      }
+      if (remote) {
+        if (fp->f_srctype == T_HISADDR && ncpaddr_family(remote) == AF_INET)
+          ncprange_sethost(&fp->f_src, remote);
+        if (fp->f_dsttype == T_HISADDR && ncpaddr_family(remote) == AF_INET)
+          ncprange_sethost(&fp->f_dst, remote);
+#ifndef NOINET6
+        if (fp->f_srctype == T_HISADDR6 && ncpaddr_family(remote) == AF_INET6)
+          ncprange_sethost(&fp->f_src, remote);
+        if (fp->f_dsttype == T_HISADDR6 && ncpaddr_family(remote) == AF_INET6)
+          ncprange_sethost(&fp->f_dst, remote);
+#endif
+      }
+      if (dns) {
+        if (fp->f_srctype == T_DNS0)
+          ncprange_setip4host(&fp->f_src, dns[0]);
+        if (fp->f_dsttype == T_DNS0)
+          ncprange_setip4host(&fp->f_dst, dns[0]);
+        if (fp->f_srctype == T_DNS1)
+          ncprange_setip4host(&fp->f_src, dns[1]);
+        if (fp->f_dsttype == T_DNS1)
+          ncprange_setip4host(&fp->f_dst, dns[1]);
+      }
+    }
+}
diff --git a/src/filter.h b/src/filter.h
new file mode 100644
index 0000000..e91d67f
--- /dev/null
+++ b/src/filter.h
@@ -0,0 +1,101 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/filter.h,v 1.29.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+/* Operations - f_srcop, f_dstop */
+#define	OP_NONE	0
+#define	OP_EQ	1
+#define	OP_GT	2
+#define	OP_LT	3
+
+/* srctype or dsttype */
+#define T_ADDR		0
+#define T_MYADDR	1
+#define T_MYADDR6	2
+#define T_HISADDR	3
+#define T_HISADDR6	4
+#define T_DNS0		5
+#define T_DNS1		6
+
+/*
+ * There's a struct filterent for each possible filter rule.  The
+ * layout is designed to minimise size (there are 4 * MAXFILTERS of
+ * them) - which is also conveniently a power of 2 (32 bytes) on
+ * architectures where sizeof(int)==4 (this makes indexing faster).
+ *
+ * Note that there are four free bits in the initial word for future
+ * extensions.
+ */
+struct filterent {
+  int f_proto;			/* Protocol: getprotoby*() */
+  unsigned f_action : 8;	/* Filtering action: goto or A_... */
+  unsigned f_srcop : 2;		/* Source port operation: OP_... */
+  unsigned f_dstop : 2;		/* Destination port operation: OP_... */
+  unsigned f_srctype : 3;	/* T_ value of src */
+  unsigned f_dsttype : 3;	/* T_ value of dst */
+  unsigned f_estab : 1;		/* Check TCP ACK bit */
+  unsigned f_syn : 1;		/* Check TCP SYN bit */
+  unsigned f_finrst : 1;	/* Check TCP FIN/RST bits */
+  unsigned f_invert : 1;	/* true to complement match */
+  struct ncprange f_src;	/* Source address and mask */
+  struct ncprange f_dst;	/* Destination address and mask */
+  u_short f_srcport;		/* Source port, compared with f_srcop */
+  u_short f_dstport;		/* Destination port, compared with f_dstop */
+  unsigned timeout;		/* Keep alive value for passed packet */
+};
+
+#define	MAXFILTERS	40	/* in each filter set */
+
+/* f_action values [0..MAXFILTERS) specify the next filter rule, others are: */
+#define	A_NONE		(MAXFILTERS)
+#define	A_PERMIT	(A_NONE+1)
+#define	A_DENY		(A_PERMIT+1)
+
+struct filter {
+  struct filterent rule[MAXFILTERS];	/* incoming packet filter */
+  const char *name;
+  unsigned fragok : 1;
+  unsigned logok : 1;
+};
+
+/* Which filter set */
+#define FL_IN		0
+#define FL_OUT		1
+#define FL_DIAL		2
+#define FL_KEEP		3
+
+struct ipcp;
+struct cmdargs;
+
+extern int filter_Show(struct cmdargs const *);
+extern int filter_Set(struct cmdargs const *);
+extern const char * filter_Action2Nam(unsigned);
+extern const char *filter_Op2Nam(unsigned);
+extern void filter_AdjustAddr(struct filter *, struct ncpaddr *,
+                              struct ncpaddr *, struct in_addr *);
diff --git a/src/fsm.c b/src/fsm.c
new file mode 100644
index 0000000..1f82f4f
--- /dev/null
+++ b/src/fsm.c
@@ -0,0 +1,1213 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/fsm.c,v 1.71.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "ua.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "async.h"
+#include "physical.h"
+#include "proto.h"
+
+static void FsmSendConfigReq(struct fsm *);
+static void FsmSendTerminateReq(struct fsm *);
+static void FsmInitRestartCounter(struct fsm *, int);
+
+typedef void (recvfn)(struct fsm *, struct fsmheader *, struct mbuf *);
+static recvfn FsmRecvConfigReq, FsmRecvConfigAck, FsmRecvConfigNak,
+              FsmRecvConfigRej, FsmRecvTermReq, FsmRecvTermAck,
+              FsmRecvCodeRej, FsmRecvProtoRej, FsmRecvEchoReq,
+              FsmRecvEchoRep, FsmRecvDiscReq, FsmRecvIdent,
+              FsmRecvTimeRemain, FsmRecvResetReq, FsmRecvResetAck;
+
+static const struct fsmcodedesc {
+  recvfn *recv;
+  unsigned check_reqid : 1;
+  unsigned inc_reqid : 1;
+  const char *name;
+} FsmCodes[] = {
+  { FsmRecvConfigReq, 0, 0, "ConfigReq"    },
+  { FsmRecvConfigAck, 1, 1, "ConfigAck"    },
+  { FsmRecvConfigNak, 1, 1, "ConfigNak"    },
+  { FsmRecvConfigRej, 1, 1, "ConfigRej"    },
+  { FsmRecvTermReq,   0, 0, "TerminateReq" },
+  { FsmRecvTermAck,   1, 1, "TerminateAck" },
+  { FsmRecvCodeRej,   0, 0, "CodeRej"      },
+  { FsmRecvProtoRej,  0, 0, "ProtocolRej"  },
+  { FsmRecvEchoReq,   0, 0, "EchoRequest"  },
+  { FsmRecvEchoRep,   0, 0, "EchoReply"    },
+  { FsmRecvDiscReq,   0, 0, "DiscardReq"   },
+  { FsmRecvIdent,     0, 1, "Ident"        },
+  { FsmRecvTimeRemain,0, 0, "TimeRemain"   },
+  { FsmRecvResetReq,  0, 0, "ResetReq"     },
+  { FsmRecvResetAck,  0, 1, "ResetAck"     }
+};
+
+static const char *
+Code2Nam(u_int code)
+{
+  if (code == 0 || code > sizeof FsmCodes / sizeof FsmCodes[0])
+    return "Unknown";
+  return FsmCodes[code-1].name;
+}
+
+const char *
+State2Nam(u_int state)
+{
+  static const char * const StateNames[] = {
+    "Initial", "Starting", "Closed", "Stopped", "Closing", "Stopping",
+    "Req-Sent", "Ack-Rcvd", "Ack-Sent", "Opened",
+  };
+
+  if (state >= sizeof StateNames / sizeof StateNames[0])
+    return "unknown";
+  return StateNames[state];
+}
+
+static void
+StoppedTimeout(void *v)
+{
+  struct fsm *fp = (struct fsm *)v;
+
+  log_Printf(fp->LogLevel, "%s: Stopped timer expired\n", fp->link->name);
+  if (fp->OpenTimer.state == TIMER_RUNNING) {
+    log_Printf(LogWARN, "%s: %s: aborting open delay due to stopped timer\n",
+              fp->link->name, fp->name);
+    timer_Stop(&fp->OpenTimer);
+  }
+  if (fp->state == ST_STOPPED)
+    fsm2initial(fp);
+}
+
+void
+fsm_Init(struct fsm *fp, const char *name, u_short proto, int mincode,
+         int maxcode, int LogLevel, struct bundle *bundle,
+         struct link *l, const struct fsm_parent *parent,
+         struct fsm_callbacks *fn, const char * const timer_names[3])
+{
+  fp->name = name;
+  fp->proto = proto;
+  fp->min_code = mincode;
+  fp->max_code = maxcode;
+  fp->state = fp->min_code > CODE_TERMACK ? ST_OPENED : ST_INITIAL;
+  fp->reqid = 1;
+  fp->restart = 1;
+  fp->more.reqs = fp->more.naks = fp->more.rejs = 3;
+  memset(&fp->FsmTimer, '\0', sizeof fp->FsmTimer);
+  memset(&fp->OpenTimer, '\0', sizeof fp->OpenTimer);
+  memset(&fp->StoppedTimer, '\0', sizeof fp->StoppedTimer);
+  fp->LogLevel = LogLevel;
+  fp->link = l;
+  fp->bundle = bundle;
+  fp->parent = parent;
+  fp->fn = fn;
+  fp->FsmTimer.name = timer_names[0];
+  fp->OpenTimer.name = timer_names[1];
+  fp->StoppedTimer.name = timer_names[2];
+}
+
+static void
+NewState(struct fsm *fp, int new)
+{
+  log_Printf(fp->LogLevel, "%s: State change %s --> %s\n",
+             fp->link->name, State2Nam(fp->state), State2Nam(new));
+  if (fp->state == ST_STOPPED && fp->StoppedTimer.state == TIMER_RUNNING)
+    timer_Stop(&fp->StoppedTimer);
+  fp->state = new;
+  if ((new >= ST_INITIAL && new <= ST_STOPPED) || (new == ST_OPENED)) {
+    timer_Stop(&fp->FsmTimer);
+    if (new == ST_STOPPED && fp->StoppedTimer.load) {
+      timer_Stop(&fp->StoppedTimer);
+      fp->StoppedTimer.func = StoppedTimeout;
+      fp->StoppedTimer.arg = (void *) fp;
+      timer_Start(&fp->StoppedTimer);
+    }
+  }
+}
+
+void
+fsm_Output(struct fsm *fp, u_int code, u_int id, u_char *ptr, unsigned count,
+           int mtype)
+{
+  int plen;
+  struct fsmheader lh;
+  struct mbuf *bp;
+
+  if (log_IsKept(fp->LogLevel)) {
+    log_Printf(fp->LogLevel, "%s: Send%s(%d) state = %s\n",
+              fp->link->name, Code2Nam(code), id, State2Nam(fp->state));
+    switch (code) {
+      case CODE_CONFIGREQ:
+      case CODE_CONFIGACK:
+      case CODE_CONFIGREJ:
+      case CODE_CONFIGNAK:
+        (*fp->fn->DecodeConfig)(fp, ptr, ptr + count, MODE_NOP, NULL);
+        if (count < sizeof(struct fsm_opt_hdr))
+          log_Printf(fp->LogLevel, "  [EMPTY]\n");
+        break;
+    }
+  }
+
+  plen = sizeof(struct fsmheader) + count;
+  lh.code = code;
+  lh.id = id;
+  lh.length = htons(plen);
+  bp = m_get(plen, mtype);
+  memcpy(MBUF_CTOP(bp), &lh, sizeof(struct fsmheader));
+  if (count)
+    memcpy(MBUF_CTOP(bp) + sizeof(struct fsmheader), ptr, count);
+  log_DumpBp(LogDEBUG, "fsm_Output", bp);
+  link_PushPacket(fp->link, bp, fp->bundle, LINK_QUEUES(fp->link) - 1,
+                  fp->proto);
+
+  if (code == CODE_CONFIGREJ)
+    lcp_SendIdentification(&fp->link->lcp);
+}
+
+static void
+FsmOpenNow(void *v)
+{
+  struct fsm *fp = (struct fsm *)v;
+
+  timer_Stop(&fp->OpenTimer);
+  if (fp->state <= ST_STOPPED) {
+    if (fp->state != ST_STARTING) {
+      /*
+       * In practice, we're only here in ST_STOPPED (when delaying the
+       * first config request) or ST_CLOSED (when openmode == 0).
+       *
+       * The ST_STOPPED bit is breaking the RFC already :-(
+       *
+       * According to the RFC (1661) state transition table, a TLS isn't
+       * required for an Open event when state == Closed, but the RFC
+       * must be wrong as TLS hasn't yet been called (since the last TLF)
+       * ie, Initial gets an `Up' event, Closing gets a RTA etc.
+       */
+      (*fp->fn->LayerStart)(fp);
+      (*fp->parent->LayerStart)(fp->parent->object, fp);
+    }
+    FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+    FsmSendConfigReq(fp);
+    NewState(fp, ST_REQSENT);
+  }
+}
+
+void
+fsm_Open(struct fsm *fp)
+{
+  switch (fp->state) {
+  case ST_INITIAL:
+    NewState(fp, ST_STARTING);
+    (*fp->fn->LayerStart)(fp);
+    (*fp->parent->LayerStart)(fp->parent->object, fp);
+    break;
+  case ST_CLOSED:
+    if (fp->open_mode == OPEN_PASSIVE) {
+      NewState(fp, ST_STOPPED);		/* XXX: This is a hack ! */
+    } else if (fp->open_mode > 0) {
+      if (fp->open_mode > 1)
+        log_Printf(LogPHASE, "%s: Entering STOPPED state for %d seconds\n",
+                  fp->link->name, fp->open_mode);
+      NewState(fp, ST_STOPPED);		/* XXX: This is a not-so-bad hack ! */
+      timer_Stop(&fp->OpenTimer);
+      fp->OpenTimer.load = fp->open_mode * SECTICKS;
+      fp->OpenTimer.func = FsmOpenNow;
+      fp->OpenTimer.arg = (void *)fp;
+      timer_Start(&fp->OpenTimer);
+    } else
+      FsmOpenNow(fp);
+    break;
+  case ST_STOPPED:		/* XXX: restart option */
+  case ST_REQSENT:
+  case ST_ACKRCVD:
+  case ST_ACKSENT:
+  case ST_OPENED:		/* XXX: restart option */
+    break;
+  case ST_CLOSING:		/* XXX: restart option */
+  case ST_STOPPING:		/* XXX: restart option */
+    NewState(fp, ST_STOPPING);
+    break;
+  }
+}
+
+void
+fsm_Up(struct fsm *fp)
+{
+  switch (fp->state) {
+  case ST_INITIAL:
+    log_Printf(fp->LogLevel, "FSM: Using \"%s\" as a transport\n",
+              fp->link->name);
+    NewState(fp, ST_CLOSED);
+    break;
+  case ST_STARTING:
+    FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+    FsmSendConfigReq(fp);
+    NewState(fp, ST_REQSENT);
+    break;
+  default:
+    log_Printf(fp->LogLevel, "%s: Oops, Up at %s\n",
+              fp->link->name, State2Nam(fp->state));
+    break;
+  }
+}
+
+void
+fsm_Down(struct fsm *fp)
+{
+  switch (fp->state) {
+  case ST_CLOSED:
+    NewState(fp, ST_INITIAL);
+    break;
+  case ST_CLOSING:
+    /* This TLF contradicts the RFC (1661), which ``misses it out'' ! */
+    (*fp->fn->LayerFinish)(fp);
+    NewState(fp, ST_INITIAL);
+    (*fp->parent->LayerFinish)(fp->parent->object, fp);
+    break;
+  case ST_STOPPED:
+    NewState(fp, ST_STARTING);
+    (*fp->fn->LayerStart)(fp);
+    (*fp->parent->LayerStart)(fp->parent->object, fp);
+    break;
+  case ST_STOPPING:
+  case ST_REQSENT:
+  case ST_ACKRCVD:
+  case ST_ACKSENT:
+    NewState(fp, ST_STARTING);
+    break;
+  case ST_OPENED:
+    (*fp->fn->LayerDown)(fp);
+    NewState(fp, ST_STARTING);
+    (*fp->parent->LayerDown)(fp->parent->object, fp);
+    break;
+  }
+}
+
+void
+fsm_Close(struct fsm *fp)
+{
+  switch (fp->state) {
+  case ST_STARTING:
+    (*fp->fn->LayerFinish)(fp);
+    NewState(fp, ST_INITIAL);
+    (*fp->parent->LayerFinish)(fp->parent->object, fp);
+    break;
+  case ST_STOPPED:
+    NewState(fp, ST_CLOSED);
+    break;
+  case ST_STOPPING:
+    NewState(fp, ST_CLOSING);
+    break;
+  case ST_OPENED:
+    (*fp->fn->LayerDown)(fp);
+    if (fp->state == ST_OPENED) {
+      FsmInitRestartCounter(fp, FSM_TRM_TIMER);
+      FsmSendTerminateReq(fp);
+      NewState(fp, ST_CLOSING);
+      (*fp->parent->LayerDown)(fp->parent->object, fp);
+    }
+    break;
+  case ST_REQSENT:
+  case ST_ACKRCVD:
+  case ST_ACKSENT:
+    FsmInitRestartCounter(fp, FSM_TRM_TIMER);
+    FsmSendTerminateReq(fp);
+    NewState(fp, ST_CLOSING);
+    break;
+  }
+}
+
+/*
+ *	Send functions
+ */
+static void
+FsmSendConfigReq(struct fsm *fp)
+{
+  if (fp->more.reqs-- > 0 && fp->restart-- > 0) {
+    (*fp->fn->SendConfigReq)(fp);
+    timer_Start(&fp->FsmTimer);		/* Start restart timer */
+  } else {
+    if (fp->more.reqs < 0)
+      log_Printf(LogPHASE, "%s: Too many %s REQs sent - abandoning "
+                 "negotiation\n", fp->link->name, fp->name);
+    lcp_SendIdentification(&fp->link->lcp);
+    fsm_Close(fp);
+  }
+}
+
+static void
+FsmSendTerminateReq(struct fsm *fp)
+{
+  fsm_Output(fp, CODE_TERMREQ, fp->reqid, NULL, 0, MB_UNKNOWN);
+  (*fp->fn->SentTerminateReq)(fp);
+  timer_Start(&fp->FsmTimer);	/* Start restart timer */
+  fp->restart--;		/* Decrement restart counter */
+}
+
+/*
+ *	Timeout actions
+ */
+static void
+FsmTimeout(void *v)
+{
+  struct fsm *fp = (struct fsm *)v;
+
+  if (fp->restart) {
+    switch (fp->state) {
+    case ST_CLOSING:
+    case ST_STOPPING:
+      FsmSendTerminateReq(fp);
+      break;
+    case ST_REQSENT:
+    case ST_ACKSENT:
+      FsmSendConfigReq(fp);
+      break;
+    case ST_ACKRCVD:
+      FsmSendConfigReq(fp);
+      NewState(fp, ST_REQSENT);
+      break;
+    }
+    timer_Start(&fp->FsmTimer);
+  } else {
+    switch (fp->state) {
+    case ST_CLOSING:
+      (*fp->fn->LayerFinish)(fp);
+      NewState(fp, ST_CLOSED);
+      (*fp->parent->LayerFinish)(fp->parent->object, fp);
+      break;
+    case ST_STOPPING:
+      (*fp->fn->LayerFinish)(fp);
+      NewState(fp, ST_STOPPED);
+      (*fp->parent->LayerFinish)(fp->parent->object, fp);
+      break;
+    case ST_REQSENT:		/* XXX: 3p */
+    case ST_ACKSENT:
+    case ST_ACKRCVD:
+      (*fp->fn->LayerFinish)(fp);
+      NewState(fp, ST_STOPPED);
+      (*fp->parent->LayerFinish)(fp->parent->object, fp);
+      break;
+    }
+  }
+}
+
+static void
+FsmInitRestartCounter(struct fsm *fp, int what)
+{
+  timer_Stop(&fp->FsmTimer);
+  fp->FsmTimer.func = FsmTimeout;
+  fp->FsmTimer.arg = (void *)fp;
+  (*fp->fn->InitRestartCounter)(fp, what);
+}
+
+/*
+ * Actions when receive packets
+ */
+static void
+FsmRecvConfigReq(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+/* RCR */
+{
+  struct fsm_decode dec;
+  int plen, flen;
+  int ackaction = 0;
+  u_char *cp;
+
+  bp = m_pullup(bp);
+  plen = m_length(bp);
+  flen = ntohs(lhp->length) - sizeof *lhp;
+  if (plen < flen) {
+    log_Printf(LogWARN, "%s: FsmRecvConfigReq: plen (%d) < flen (%d)\n",
+               fp->link->name, plen, flen);
+    m_freem(bp);
+    return;
+  }
+
+  /* Some things must be done before we Decode the packet */
+  switch (fp->state) {
+  case ST_OPENED:
+    (*fp->fn->LayerDown)(fp);
+  }
+
+  dec.ackend = dec.ack;
+  dec.nakend = dec.nak;
+  dec.rejend = dec.rej;
+  cp = MBUF_CTOP(bp);
+  (*fp->fn->DecodeConfig)(fp, cp, cp + flen, MODE_REQ, &dec);
+  if (flen < (int)sizeof(struct fsm_opt_hdr))
+    log_Printf(fp->LogLevel, "  [EMPTY]\n");
+
+  if (dec.nakend == dec.nak && dec.rejend == dec.rej)
+    ackaction = 1;
+
+  /* Check and process easy case */
+  switch (fp->state) {
+  case ST_INITIAL:
+    if (fp->proto == PROTO_CCP && fp->link->lcp.fsm.state == ST_OPENED) {
+      /*
+       * ccp_SetOpenMode() leaves us in initial if we're disabling
+       * & denying everything.
+       */
+      bp = m_prepend(bp, lhp, sizeof *lhp, 2);
+      bp = proto_Prepend(bp, fp->proto, 0, 0);
+      bp = m_pullup(bp);
+      lcp_SendProtoRej(&fp->link->lcp, MBUF_CTOP(bp), bp->m_len);
+      m_freem(bp);
+      return;
+    }
+    /* Drop through */
+  case ST_STARTING:
+    log_Printf(fp->LogLevel, "%s: Oops, RCR in %s.\n",
+              fp->link->name, State2Nam(fp->state));
+    m_freem(bp);
+    return;
+  case ST_CLOSED:
+    (*fp->fn->SendTerminateAck)(fp, lhp->id);
+    m_freem(bp);
+    return;
+  case ST_CLOSING:
+    log_Printf(fp->LogLevel, "%s: Error: Got ConfigReq while state = %s\n",
+              fp->link->name, State2Nam(fp->state));
+  case ST_STOPPING:
+    m_freem(bp);
+    return;
+  case ST_STOPPED:
+    FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+    /* Drop through */
+  case ST_OPENED:
+    FsmSendConfigReq(fp);
+    break;
+  }
+
+  if (dec.rejend != dec.rej)
+    fsm_Output(fp, CODE_CONFIGREJ, lhp->id, dec.rej, dec.rejend - dec.rej,
+               MB_UNKNOWN);
+  if (dec.nakend != dec.nak)
+    fsm_Output(fp, CODE_CONFIGNAK, lhp->id, dec.nak, dec.nakend - dec.nak,
+               MB_UNKNOWN);
+  if (ackaction)
+    fsm_Output(fp, CODE_CONFIGACK, lhp->id, dec.ack, dec.ackend - dec.ack,
+               MB_UNKNOWN);
+
+  switch (fp->state) {
+  case ST_STOPPED:
+      /*
+       * According to the RFC (1661) state transition table, a TLS isn't
+       * required for a RCR when state == ST_STOPPED, but the RFC
+       * must be wrong as TLS hasn't yet been called (since the last TLF)
+       */
+    (*fp->fn->LayerStart)(fp);
+    (*fp->parent->LayerStart)(fp->parent->object, fp);
+    /* FALLTHROUGH */
+
+  case ST_OPENED:
+    if (ackaction)
+      NewState(fp, ST_ACKSENT);
+    else
+      NewState(fp, ST_REQSENT);
+    (*fp->parent->LayerDown)(fp->parent->object, fp);
+    break;
+  case ST_REQSENT:
+    if (ackaction)
+      NewState(fp, ST_ACKSENT);
+    break;
+  case ST_ACKRCVD:
+    if (ackaction) {
+      NewState(fp, ST_OPENED);
+      if ((*fp->fn->LayerUp)(fp))
+        (*fp->parent->LayerUp)(fp->parent->object, fp);
+      else {
+        (*fp->fn->LayerDown)(fp);
+        FsmInitRestartCounter(fp, FSM_TRM_TIMER);
+        FsmSendTerminateReq(fp);
+        NewState(fp, ST_CLOSING);
+        lcp_SendIdentification(&fp->link->lcp);
+      }
+    }
+    break;
+  case ST_ACKSENT:
+    if (!ackaction)
+      NewState(fp, ST_REQSENT);
+    break;
+  }
+  m_freem(bp);
+
+  if (dec.rejend != dec.rej && --fp->more.rejs <= 0) {
+    log_Printf(LogPHASE, "%s: Too many %s REJs sent - abandoning negotiation\n",
+               fp->link->name, fp->name);
+    lcp_SendIdentification(&fp->link->lcp);
+    fsm_Close(fp);
+  }
+
+  if (dec.nakend != dec.nak && --fp->more.naks <= 0) {
+    log_Printf(LogPHASE, "%s: Too many %s NAKs sent - abandoning negotiation\n",
+               fp->link->name, fp->name);
+    lcp_SendIdentification(&fp->link->lcp);
+    fsm_Close(fp);
+  }
+}
+
+static void
+FsmRecvConfigAck(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+/* RCA */
+{
+  struct fsm_decode dec;
+  int plen, flen;
+  u_char *cp;
+
+  plen = m_length(bp);
+  flen = ntohs(lhp->length) - sizeof *lhp;
+  if (plen < flen) {
+    m_freem(bp);
+    return;
+  }
+
+  bp = m_pullup(bp);
+  dec.ackend = dec.ack;
+  dec.nakend = dec.nak;
+  dec.rejend = dec.rej;
+  cp = MBUF_CTOP(bp);
+  (*fp->fn->DecodeConfig)(fp, cp, cp + flen, MODE_ACK, &dec);
+  if (flen < (int)sizeof(struct fsm_opt_hdr))
+    log_Printf(fp->LogLevel, "  [EMPTY]\n");
+
+  switch (fp->state) {
+    case ST_CLOSED:
+    case ST_STOPPED:
+    (*fp->fn->SendTerminateAck)(fp, lhp->id);
+    break;
+  case ST_CLOSING:
+  case ST_STOPPING:
+    break;
+  case ST_REQSENT:
+    FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+    NewState(fp, ST_ACKRCVD);
+    break;
+  case ST_ACKRCVD:
+    FsmSendConfigReq(fp);
+    NewState(fp, ST_REQSENT);
+    break;
+  case ST_ACKSENT:
+    FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+    NewState(fp, ST_OPENED);
+    if ((*fp->fn->LayerUp)(fp))
+      (*fp->parent->LayerUp)(fp->parent->object, fp);
+    else {
+      (*fp->fn->LayerDown)(fp);
+      FsmInitRestartCounter(fp, FSM_TRM_TIMER);
+      FsmSendTerminateReq(fp);
+      NewState(fp, ST_CLOSING);
+      lcp_SendIdentification(&fp->link->lcp);
+    }
+    break;
+  case ST_OPENED:
+    (*fp->fn->LayerDown)(fp);
+    FsmSendConfigReq(fp);
+    NewState(fp, ST_REQSENT);
+    (*fp->parent->LayerDown)(fp->parent->object, fp);
+    break;
+  }
+  m_freem(bp);
+}
+
+static void
+FsmRecvConfigNak(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+/* RCN */
+{
+  struct fsm_decode dec;
+  int plen, flen;
+  u_char *cp;
+
+  plen = m_length(bp);
+  flen = ntohs(lhp->length) - sizeof *lhp;
+  if (plen < flen) {
+    m_freem(bp);
+    return;
+  }
+
+  /*
+   * Check and process easy case
+   */
+  switch (fp->state) {
+  case ST_INITIAL:
+  case ST_STARTING:
+    log_Printf(fp->LogLevel, "%s: Oops, RCN in %s.\n",
+              fp->link->name, State2Nam(fp->state));
+    m_freem(bp);
+    return;
+  case ST_CLOSED:
+  case ST_STOPPED:
+    (*fp->fn->SendTerminateAck)(fp, lhp->id);
+    m_freem(bp);
+    return;
+  case ST_CLOSING:
+  case ST_STOPPING:
+    m_freem(bp);
+    return;
+  }
+
+  bp = m_pullup(bp);
+  dec.ackend = dec.ack;
+  dec.nakend = dec.nak;
+  dec.rejend = dec.rej;
+  cp = MBUF_CTOP(bp);
+  (*fp->fn->DecodeConfig)(fp, cp, cp + flen, MODE_NAK, &dec);
+  if (flen < (int)sizeof(struct fsm_opt_hdr))
+    log_Printf(fp->LogLevel, "  [EMPTY]\n");
+
+  switch (fp->state) {
+  case ST_REQSENT:
+  case ST_ACKSENT:
+    FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+    FsmSendConfigReq(fp);
+    break;
+  case ST_OPENED:
+    (*fp->fn->LayerDown)(fp);
+    FsmSendConfigReq(fp);
+    NewState(fp, ST_REQSENT);
+    (*fp->parent->LayerDown)(fp->parent->object, fp);
+    break;
+  case ST_ACKRCVD:
+    FsmSendConfigReq(fp);
+    NewState(fp, ST_REQSENT);
+    break;
+  }
+
+  m_freem(bp);
+}
+
+static void
+FsmRecvTermReq(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+/* RTR */
+{
+  switch (fp->state) {
+  case ST_INITIAL:
+  case ST_STARTING:
+    log_Printf(fp->LogLevel, "%s: Oops, RTR in %s\n",
+              fp->link->name, State2Nam(fp->state));
+    break;
+  case ST_CLOSED:
+  case ST_STOPPED:
+  case ST_CLOSING:
+  case ST_STOPPING:
+  case ST_REQSENT:
+    (*fp->fn->SendTerminateAck)(fp, lhp->id);
+    break;
+  case ST_ACKRCVD:
+  case ST_ACKSENT:
+    (*fp->fn->SendTerminateAck)(fp, lhp->id);
+    NewState(fp, ST_REQSENT);
+    break;
+  case ST_OPENED:
+    (*fp->fn->LayerDown)(fp);
+    (*fp->fn->SendTerminateAck)(fp, lhp->id);
+    FsmInitRestartCounter(fp, FSM_TRM_TIMER);
+    timer_Start(&fp->FsmTimer);			/* Start restart timer */
+    fp->restart = 0;
+    NewState(fp, ST_STOPPING);
+    (*fp->parent->LayerDown)(fp->parent->object, fp);
+    /* A delayed ST_STOPPED is now scheduled */
+    break;
+  }
+  m_freem(bp);
+}
+
+static void
+FsmRecvTermAck(struct fsm *fp, struct fsmheader *lhp __unused, struct mbuf *bp)
+/* RTA */
+{
+  switch (fp->state) {
+  case ST_CLOSING:
+    (*fp->fn->LayerFinish)(fp);
+    NewState(fp, ST_CLOSED);
+    (*fp->parent->LayerFinish)(fp->parent->object, fp);
+    break;
+  case ST_STOPPING:
+    (*fp->fn->LayerFinish)(fp);
+    NewState(fp, ST_STOPPED);
+    (*fp->parent->LayerFinish)(fp->parent->object, fp);
+    break;
+  case ST_ACKRCVD:
+    NewState(fp, ST_REQSENT);
+    break;
+  case ST_OPENED:
+    (*fp->fn->LayerDown)(fp);
+    FsmSendConfigReq(fp);
+    NewState(fp, ST_REQSENT);
+    (*fp->parent->LayerDown)(fp->parent->object, fp);
+    break;
+  }
+  m_freem(bp);
+}
+
+static void
+FsmRecvConfigRej(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+/* RCJ */
+{
+  struct fsm_decode dec;
+  size_t plen;
+  int flen;
+  u_char *cp;
+
+  plen = m_length(bp);
+  flen = ntohs(lhp->length) - sizeof *lhp;
+  if ((int)plen < flen) {
+    m_freem(bp);
+    return;
+  }
+
+  lcp_SendIdentification(&fp->link->lcp);
+
+  /*
+   * Check and process easy case
+   */
+  switch (fp->state) {
+  case ST_INITIAL:
+  case ST_STARTING:
+    log_Printf(fp->LogLevel, "%s: Oops, RCJ in %s.\n",
+              fp->link->name, State2Nam(fp->state));
+    m_freem(bp);
+    return;
+  case ST_CLOSED:
+  case ST_STOPPED:
+    (*fp->fn->SendTerminateAck)(fp, lhp->id);
+    m_freem(bp);
+    return;
+  case ST_CLOSING:
+  case ST_STOPPING:
+    m_freem(bp);
+    return;
+  }
+
+  bp = m_pullup(bp);
+  dec.ackend = dec.ack;
+  dec.nakend = dec.nak;
+  dec.rejend = dec.rej;
+  cp = MBUF_CTOP(bp);
+  (*fp->fn->DecodeConfig)(fp, cp, cp + flen, MODE_REJ, &dec);
+  if (flen < (int)sizeof(struct fsm_opt_hdr))
+    log_Printf(fp->LogLevel, "  [EMPTY]\n");
+
+  switch (fp->state) {
+  case ST_REQSENT:
+  case ST_ACKSENT:
+    FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+    FsmSendConfigReq(fp);
+    break;
+  case ST_OPENED:
+    (*fp->fn->LayerDown)(fp);
+    FsmSendConfigReq(fp);
+    NewState(fp, ST_REQSENT);
+    (*fp->parent->LayerDown)(fp->parent->object, fp);
+    break;
+  case ST_ACKRCVD:
+    FsmSendConfigReq(fp);
+    NewState(fp, ST_REQSENT);
+    break;
+  }
+  m_freem(bp);
+}
+
+static void
+FsmRecvCodeRej(struct fsm *fp __unused, struct fsmheader *lhp __unused,
+	       struct mbuf *bp)
+{
+  m_freem(bp);
+}
+
+static void
+FsmRecvProtoRej(struct fsm *fp, struct fsmheader *lhp __unused, struct mbuf *bp)
+{
+  struct physical *p = link2physical(fp->link);
+  u_short proto;
+
+  if (m_length(bp) < 2) {
+    m_freem(bp);
+    return;
+  }
+  bp = mbuf_Read(bp, &proto, 2);
+  proto = ntohs(proto);
+  log_Printf(fp->LogLevel, "%s: -- Protocol 0x%04x (%s) was rejected!\n",
+            fp->link->name, proto, hdlc_Protocol2Nam(proto));
+
+  switch (proto) {
+  case PROTO_LQR:
+    if (p)
+      lqr_Stop(p, LQM_LQR);
+    else
+      log_Printf(LogERROR, "%s: FsmRecvProtoRej: Not a physical link !\n",
+                fp->link->name);
+    break;
+  case PROTO_CCP:
+    if (fp->proto == PROTO_LCP) {
+      fp = &fp->link->ccp.fsm;
+      /* Despite the RFC (1661), don't do an out-of-place TLF */
+      /* (*fp->fn->LayerFinish)(fp); */
+      switch (fp->state) {
+      case ST_CLOSED:
+      case ST_CLOSING:
+        NewState(fp, ST_CLOSED);
+        break;
+      default:
+        NewState(fp, ST_STOPPED);
+        break;
+      }
+      /* See above */
+      /* (*fp->parent->LayerFinish)(fp->parent->object, fp); */
+    }
+    break;
+  case PROTO_IPCP:
+    if (fp->proto == PROTO_LCP) {
+      log_Printf(LogPHASE, "%s: IPCP protocol reject closes IPCP !\n",
+                fp->link->name);
+      fsm_Close(&fp->bundle->ncp.ipcp.fsm);
+    }
+    break;
+#ifndef NOINET6
+  case PROTO_IPV6CP:
+    if (fp->proto == PROTO_LCP) {
+      log_Printf(LogPHASE, "%s: IPV6CP protocol reject closes IPV6CP !\n",
+                fp->link->name);
+      fsm_Close(&fp->bundle->ncp.ipv6cp.fsm);
+    }
+    break;
+#endif
+  case PROTO_MP:
+    if (fp->proto == PROTO_LCP) {
+      struct lcp *lcp = fsm2lcp(fp);
+
+      if (lcp->want_mrru && lcp->his_mrru) {
+        log_Printf(LogPHASE, "%s: MP protocol reject is fatal !\n",
+                  fp->link->name);
+        fsm_Close(fp);
+      }
+    }
+    break;
+  }
+  m_freem(bp);
+}
+
+static void
+FsmRecvEchoReq(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+{
+  struct lcp *lcp = fsm2lcp(fp);
+  u_char *cp;
+  u_int32_t magic;
+
+  bp = m_pullup(bp);
+  m_settype(bp, MB_ECHOIN);
+
+  if (lcp && ntohs(lhp->length) - sizeof *lhp >= 4) {
+    cp = MBUF_CTOP(bp);
+    ua_ntohl(cp, &magic);
+    if (magic != lcp->his_magic) {
+      log_Printf(fp->LogLevel, "%s: RecvEchoReq: magic 0x%08lx is wrong,"
+                 " expecting 0x%08lx\n", fp->link->name, (u_long)magic,
+                 (u_long)lcp->his_magic);
+      /* XXX: We should send terminate request */
+    }
+    if (fp->state == ST_OPENED) {
+      ua_htonl(&lcp->want_magic, cp);		/* local magic */
+      fsm_Output(fp, CODE_ECHOREP, lhp->id, cp,
+                 ntohs(lhp->length) - sizeof *lhp, MB_ECHOOUT);
+    }
+  }
+  m_freem(bp);
+}
+
+static void
+FsmRecvEchoRep(struct fsm *fp, struct fsmheader *lhp __unused, struct mbuf *bp)
+{
+  if (fsm2lcp(fp))
+    bp = lqr_RecvEcho(fp, bp);
+
+  m_freem(bp);
+}
+
+static void
+FsmRecvDiscReq(struct fsm *fp __unused, struct fsmheader *lhp __unused,
+	       struct mbuf *bp)
+{
+  m_freem(bp);
+}
+
+static void
+FsmRecvIdent(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+{
+  u_int32_t magic;
+  u_short len;
+  u_char *cp;
+
+  len = ntohs(lhp->length) - sizeof *lhp;
+  if (len >= 4) {
+    bp = m_pullup(m_append(bp, "", 1));
+    cp = MBUF_CTOP(bp);
+    ua_ntohl(cp, &magic);
+    if (magic != fp->link->lcp.his_magic)
+      log_Printf(fp->LogLevel, "%s: RecvIdent: magic 0x%08lx is wrong,"
+                 " expecting 0x%08lx\n", fp->link->name, (u_long)magic,
+                 (u_long)fp->link->lcp.his_magic);
+    cp[len] = '\0';
+    lcp_RecvIdentification(&fp->link->lcp, cp + 4);
+  }
+  m_freem(bp);
+}
+
+static void
+FsmRecvTimeRemain(struct fsm *fp __unused, struct fsmheader *lhp __unused,
+		  struct mbuf *bp)
+{
+  m_freem(bp);
+}
+
+static void
+FsmRecvResetReq(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+{
+  if ((*fp->fn->RecvResetReq)(fp)) {
+    /*
+     * All sendable compressed packets are queued in the first (lowest
+     * priority) modem output queue.... dump 'em to the priority queue
+     * so that they arrive at the peer before our ResetAck.
+     */
+    link_SequenceQueue(fp->link);
+    fsm_Output(fp, CODE_RESETACK, lhp->id, NULL, 0, MB_CCPOUT);
+  }
+  m_freem(bp);
+}
+
+static void
+FsmRecvResetAck(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp)
+{
+  (*fp->fn->RecvResetAck)(fp, lhp->id);
+  m_freem(bp);
+}
+
+void
+fsm_Input(struct fsm *fp, struct mbuf *bp)
+{
+  size_t len;
+  struct fsmheader lh;
+  const struct fsmcodedesc *codep;
+
+  len = m_length(bp);
+  if (len < sizeof(struct fsmheader)) {
+    m_freem(bp);
+    return;
+  }
+  bp = mbuf_Read(bp, &lh, sizeof lh);
+
+  if (ntohs(lh.length) > len) {
+    log_Printf(LogWARN, "%s: Oops: Got %zu bytes but %d byte payload "
+               "- dropped\n", fp->link->name, len, (int)ntohs(lh.length));
+    m_freem(bp);
+    return;
+  }
+
+  if (lh.code < fp->min_code || lh.code > fp->max_code ||
+      lh.code > sizeof FsmCodes / sizeof *FsmCodes) {
+    /*
+     * Use a private id.  This is really a response-type packet, but we
+     * MUST send a unique id for each REQ....
+     */
+    static u_char id;
+
+    bp = m_prepend(bp, &lh, sizeof lh, 0);
+    bp = m_pullup(bp);
+    fsm_Output(fp, CODE_CODEREJ, id++, MBUF_CTOP(bp), bp->m_len, MB_UNKNOWN);
+    m_freem(bp);
+    return;
+  }
+
+  codep = FsmCodes + lh.code - 1;
+  if (lh.id != fp->reqid && codep->check_reqid &&
+      Enabled(fp->bundle, OPT_IDCHECK)) {
+    log_Printf(fp->LogLevel, "%s: Recv%s(%d), dropped (expected %d)\n",
+               fp->link->name, codep->name, lh.id, fp->reqid);
+    return;
+  }
+
+  log_Printf(fp->LogLevel, "%s: Recv%s(%d) state = %s\n",
+             fp->link->name, codep->name, lh.id, State2Nam(fp->state));
+
+  if (codep->inc_reqid && (lh.id == fp->reqid ||
+      (!Enabled(fp->bundle, OPT_IDCHECK) && codep->check_reqid)))
+    fp->reqid++;	/* That's the end of that ``exchange''.... */
+
+  (*codep->recv)(fp, &lh, bp);
+}
+
+int
+fsm_NullRecvResetReq(struct fsm *fp)
+{
+  log_Printf(fp->LogLevel, "%s: Oops - received unexpected reset req\n",
+            fp->link->name);
+  return 1;
+}
+
+void
+fsm_NullRecvResetAck(struct fsm *fp, u_char id __unused)
+{
+  log_Printf(fp->LogLevel, "%s: Oops - received unexpected reset ack\n",
+            fp->link->name);
+}
+
+void
+fsm_Reopen(struct fsm *fp)
+{
+  if (fp->state == ST_OPENED) {
+    (*fp->fn->LayerDown)(fp);
+    FsmInitRestartCounter(fp, FSM_REQ_TIMER);
+    FsmSendConfigReq(fp);
+    NewState(fp, ST_REQSENT);
+    (*fp->parent->LayerDown)(fp->parent->object, fp);
+  }
+}
+
+void
+fsm2initial(struct fsm *fp)
+{
+  timer_Stop(&fp->FsmTimer);
+  timer_Stop(&fp->OpenTimer);
+  timer_Stop(&fp->StoppedTimer);
+  if (fp->state == ST_STOPPED)
+    fsm_Close(fp);
+  if (fp->state > ST_INITIAL)
+    fsm_Down(fp);
+  if (fp->state > ST_INITIAL)
+    fsm_Close(fp);
+}
+
+struct fsm_opt *
+fsm_readopt(u_char **cp)
+{
+  struct fsm_opt *o = (struct fsm_opt *)*cp;
+
+  if (o->hdr.len < sizeof(struct fsm_opt_hdr)) {
+    log_Printf(LogERROR, "Bad option length %d (out of phase?)\n", o->hdr.len);
+    return NULL;
+  }
+
+  *cp += o->hdr.len;
+
+  if (o->hdr.len > sizeof(struct fsm_opt)) {
+    log_Printf(LogERROR, "Warning: Truncating option length from %d to %d\n",
+               o->hdr.len, (int)sizeof(struct fsm_opt));
+    o->hdr.len = sizeof(struct fsm_opt);
+  }
+
+  return o;
+}
+
+static int
+fsm_opt(u_char *opt, int optlen, const struct fsm_opt *o)
+{
+  unsigned cplen = o->hdr.len;
+
+  if (optlen < (int)sizeof(struct fsm_opt_hdr))
+    optlen = 0;
+
+  if ((int)cplen > optlen) {
+    log_Printf(LogERROR, "Can't REJ length %d - trunating to %d\n",
+      cplen, optlen);
+    cplen = optlen;
+  }
+  memcpy(opt, o, cplen);
+  if (cplen)
+    opt[1] = cplen;
+
+  return cplen;
+}
+
+void
+fsm_rej(struct fsm_decode *dec, const struct fsm_opt *o)
+{
+  if (!dec)
+    return;
+  dec->rejend += fsm_opt(dec->rejend, FSM_OPTLEN - (dec->rejend - dec->rej), o);
+}
+
+void
+fsm_ack(struct fsm_decode *dec, const struct fsm_opt *o)
+{
+  if (!dec)
+    return;
+  dec->ackend += fsm_opt(dec->ackend, FSM_OPTLEN - (dec->ackend - dec->ack), o);
+}
+
+void
+fsm_nak(struct fsm_decode *dec, const struct fsm_opt *o)
+{
+  if (!dec)
+    return;
+  dec->nakend += fsm_opt(dec->nakend, FSM_OPTLEN - (dec->nakend - dec->nak), o);
+}
+
+void
+fsm_opt_normalise(struct fsm_decode *dec)
+{
+  if (dec->rejend != dec->rej) {
+    /* rejects are preferred */
+    dec->ackend = dec->ack;
+    dec->nakend = dec->nak;
+  } else if (dec->nakend != dec->nak)
+    /* then NAKs */
+    dec->ackend = dec->ack;
+}
diff --git a/src/fsm.h b/src/fsm.h
new file mode 100644
index 0000000..a7b4e69
--- /dev/null
+++ b/src/fsm.h
@@ -0,0 +1,201 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/fsm.h,v 1.29.14.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+/*
+ *  State of machine
+ */
+#define	ST_INITIAL	0
+#define	ST_STARTING	1
+#define	ST_CLOSED	2
+#define	ST_STOPPED	3
+#define	ST_CLOSING	4
+#define	ST_STOPPING	5
+#define	ST_REQSENT	6
+#define	ST_ACKRCVD	7
+#define	ST_ACKSENT	8
+#define	ST_OPENED	9
+
+#define	ST_MAX		10
+#define	ST_UNDEF	-1
+
+#define	MODE_REQ	0
+#define	MODE_NAK	1
+#define	MODE_REJ	2
+#define	MODE_NOP	3
+#define	MODE_ACK	4	/* pseudo mode for ccp negotiations */
+
+#define	OPEN_PASSIVE	-1
+
+#define FSM_REQ_TIMER	1
+#define FSM_TRM_TIMER	2
+
+#define FSM_OPTLEN	100
+
+struct fsm;
+
+struct fsm_retry {
+  u_int timeout;                             /* FSM retry frequency */
+  u_int maxreq;                              /* Max Config REQ retries */
+  u_int maxtrm;                              /* Max Term REQ retries */
+};
+
+struct fsm_decode {
+  u_char ack[FSM_OPTLEN], *ackend;
+  u_char nak[FSM_OPTLEN], *nakend;
+  u_char rej[FSM_OPTLEN], *rejend;
+};
+
+struct fsm_callbacks {
+  int (*LayerUp)(struct fsm *);                 /* Layer is now up (tlu) */
+  void (*LayerDown)(struct fsm *);              /* About to come down (tld) */
+  void (*LayerStart)(struct fsm *);             /* Layer about to start (tls) */
+  void (*LayerFinish)(struct fsm *);            /* Layer now down (tlf) */
+  void (*InitRestartCounter)(struct fsm *, int);/* Set fsm timer load */
+  void (*SendConfigReq)(struct fsm *);          /* Send REQ please */
+  void (*SentTerminateReq)(struct fsm *);       /* Term REQ just sent */
+  void (*SendTerminateAck)(struct fsm *, u_char); /* Send Term ACK please */
+  void (*DecodeConfig)(struct fsm *, u_char *, u_char *, int,
+                       struct fsm_decode *);    /* Deal with incoming data */
+  int (*RecvResetReq)(struct fsm *fp);          /* Reset output */
+  void (*RecvResetAck)(struct fsm *fp, u_char); /* Reset input */
+};
+
+struct fsm_parent {
+  void (*LayerStart) (void *, struct fsm *);         /* tls */
+  void (*LayerUp) (void *, struct fsm *);            /* tlu */
+  void (*LayerDown) (void *, struct fsm *);          /* tld */
+  void (*LayerFinish) (void *, struct fsm *);        /* tlf */
+  void *object;
+};
+
+struct link;
+struct bundle;
+
+struct fsm {
+  const char *name;		/* Name of protocol */
+  u_short proto;		/* Protocol number */
+  u_short min_code;
+  u_short max_code;
+  int open_mode;		/* Delay before config REQ (-1 forever) */
+  unsigned state;		/* State of the machine */
+  u_char reqid;			/* Next request id */
+  int restart;			/* Restart counter value */
+
+  struct {
+    int reqs;			/* Max config REQs before a close() */
+    int naks;			/* Max config NAKs before a close() */
+    int rejs;			/* Max config REJs before a close() */
+  } more;
+
+  struct pppTimer FsmTimer;	/* Restart Timer */
+  struct pppTimer OpenTimer;	/* Delay before opening */
+
+  /*
+   * This timer times the ST_STOPPED state out after the given value
+   * (specified via "set stopped ...").  Although this isn't specified in the
+   * rfc, the rfc *does* say that "the application may use higher level
+   * timers to avoid deadlock". The StoppedTimer takes effect when the other
+   * side ABENDs rather than going into ST_ACKSENT (and sending the ACK),
+   * causing ppp to time out and drop into ST_STOPPED.  At this point,
+   * nothing will change this state :-(
+   */
+  struct pppTimer StoppedTimer;
+  int LogLevel;
+
+  /* The link layer active with this FSM (may be our bundle below) */
+  struct link *link;
+
+  /* Our high-level link */
+  struct bundle *bundle;
+
+  const struct fsm_parent *parent;
+  const struct fsm_callbacks *fn;
+};
+
+struct fsmheader {
+  u_char code;			/* Request code */
+  u_char id;			/* Identification */
+  u_short length;		/* Length of packet */
+};
+
+#define	CODE_CONFIGREQ	1
+#define	CODE_CONFIGACK	2
+#define	CODE_CONFIGNAK	3
+#define	CODE_CONFIGREJ	4
+#define	CODE_TERMREQ	5
+#define	CODE_TERMACK	6
+#define	CODE_CODEREJ	7
+#define	CODE_PROTOREJ	8
+#define	CODE_ECHOREQ	9	/* Used in LCP */
+#define	CODE_ECHOREP	10	/* Used in LCP */
+#define	CODE_DISCREQ	11
+#define	CODE_IDENT	12	/* Used in LCP Extension */
+#define	CODE_TIMEREM	13	/* Used in LCP Extension */
+#define	CODE_RESETREQ	14	/* Used in CCP */
+#define	CODE_RESETACK	15	/* Used in CCP */
+
+struct fsm_opt_hdr {
+  u_char id;
+  u_char len;
+} __packed;
+
+#define MAX_FSM_OPT_LEN 52
+struct fsm_opt {
+  struct fsm_opt_hdr hdr;
+  u_char data[MAX_FSM_OPT_LEN-2];
+};
+
+#define INC_FSM_OPT(ty, length, o)                      \
+  do {                                                  \
+    (o)->hdr.id = (ty);                                 \
+    (o)->hdr.len = (length);                            \
+    (o) = (struct fsm_opt *)((u_char *)(o) + (length)); \
+  } while (0)
+
+
+extern void fsm_Init(struct fsm *, const char *, u_short, int, int, int,
+                     struct bundle *, struct link *, const  struct fsm_parent *,
+                     struct fsm_callbacks *, const char * const [3]);
+extern void fsm_Output(struct fsm *, u_int, u_int, u_char *, unsigned, int);
+extern void fsm_Open(struct fsm *);
+extern void fsm_Up(struct fsm *);
+extern void fsm_Down(struct fsm *);
+extern void fsm_Input(struct fsm *, struct mbuf *);
+extern void fsm_Close(struct fsm *);
+extern int fsm_NullRecvResetReq(struct fsm *);
+extern void fsm_NullRecvResetAck(struct fsm *, u_char);
+extern void fsm_Reopen(struct fsm *);
+extern void fsm2initial(struct fsm *);
+extern const char *State2Nam(u_int);
+extern struct fsm_opt *fsm_readopt(u_char **);
+extern void fsm_rej(struct fsm_decode *, const struct fsm_opt *);
+extern void fsm_ack(struct fsm_decode *, const struct fsm_opt *);
+extern void fsm_nak(struct fsm_decode *, const struct fsm_opt *);
+extern void fsm_opt_normalise(struct fsm_decode *);
diff --git a/src/hdlc.c b/src/hdlc.c
new file mode 100644
index 0000000..f2b9c7e
--- /dev/null
+++ b/src/hdlc.c
@@ -0,0 +1,438 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/hdlc.c,v 1.51.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <sys/un.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "layer.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "auth.h"
+#include "lcp.h"
+#include "async.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "chap.h"
+#include "physical.h"
+#include "prompt.h"
+#include "chat.h"
+#include "mp.h"
+#include "cbcp.h"
+#include "datalink.h"
+
+static u_int16_t const fcstab[256] = {
+   /* 00 */ 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
+   /* 08 */ 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
+   /* 10 */ 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
+   /* 18 */ 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
+   /* 20 */ 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
+   /* 28 */ 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
+   /* 30 */ 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
+   /* 38 */ 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
+   /* 40 */ 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
+   /* 48 */ 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
+   /* 50 */ 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
+   /* 58 */ 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
+   /* 60 */ 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
+   /* 68 */ 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
+   /* 70 */ 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
+   /* 78 */ 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
+   /* 80 */ 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
+   /* 88 */ 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
+   /* 90 */ 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
+   /* 98 */ 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
+   /* a0 */ 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
+   /* a8 */ 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
+   /* b0 */ 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
+   /* b8 */ 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
+   /* c0 */ 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
+   /* c8 */ 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
+   /* d0 */ 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
+   /* d8 */ 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
+   /* e0 */ 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
+   /* e8 */ 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
+   /* f0 */ 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
+   /* f8 */ 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
+};
+
+void
+hdlc_Init(struct hdlc *hdlc, struct lcp *lcp)
+{
+  memset(hdlc, '\0', sizeof(struct hdlc));
+  hdlc->lqm.owner = lcp;
+}
+
+/*
+ *  HDLC FCS computation. Read RFC 1171 Appendix B and CCITT X.25 section
+ *  2.27 for further details.
+ */
+u_short
+hdlc_Fcs(u_char *cp, size_t len)
+{
+  u_short fcs = INITFCS;
+
+  while (len--)
+    fcs = (fcs >> 8) ^ fcstab[(fcs ^ *cp++) & 0xff];
+
+  return fcs;
+}
+
+static inline u_short
+HdlcFcsBuf(u_short fcs, struct mbuf *m)
+{
+  int len;
+  u_char *pos, *end;
+
+  len = m_length(m);
+  pos = MBUF_CTOP(m);
+  end = pos + m->m_len;
+  while (len--) {
+    fcs = (fcs >> 8) ^ fcstab[(fcs ^ *pos++) & 0xff];
+    if (pos == end && len) {
+      m = m->m_next;
+      pos = MBUF_CTOP(m);
+      end = pos + m->m_len;
+    }
+  }
+  return (fcs);
+}
+
+static struct mbuf *
+hdlc_LayerPush(struct bundle *bundle __unused, struct link *l __unused,
+	       struct mbuf *bp, int pri __unused, u_short *proto __unused)
+{
+  struct mbuf *last;
+  u_char *cp;
+  u_short fcs;
+
+  m_settype(bp, MB_HDLCOUT);
+  fcs = HdlcFcsBuf(INITFCS, bp);
+  fcs = ~fcs;
+
+  for (last = bp; last->m_next; last = last->m_next)
+    ;
+
+  if (last->m_size - last->m_offset - last->m_len >= 2) {
+    cp = MBUF_CTOP(last) + last->m_len;
+    last->m_len += 2;
+  } else {
+    struct mbuf *tail = m_get(2, MB_HDLCOUT);
+    last->m_next = tail;
+    cp = MBUF_CTOP(tail);
+  }
+
+  *cp++ = fcs & 0377;		/* Low byte first (nothing like consistency) */
+  *cp++ = fcs >> 8;
+
+  log_DumpBp(LogHDLC, "hdlc_Output", bp);
+
+  return bp;
+}
+
+/* Check out the latest ``Assigned numbers'' rfc (rfc1700.txt) */
+static struct {
+  u_short from;
+  u_short to;
+  const char *name;
+} protocols[] = {
+  { 0x0001, 0x0001, "Padding Protocol" },
+  { 0x0003, 0x001f, "reserved (transparency inefficient)" },
+  { 0x0021, 0x0021, "Internet Protocol" },
+  { 0x0023, 0x0023, "OSI Network Layer" },
+  { 0x0025, 0x0025, "Xerox NS IDP" },
+  { 0x0027, 0x0027, "DECnet Phase IV" },
+  { 0x0029, 0x0029, "Appletalk" },
+  { 0x002b, 0x002b, "Novell IPX" },
+  { 0x002d, 0x002d, "Van Jacobson Compressed TCP/IP" },
+  { 0x002f, 0x002f, "Van Jacobson Uncompressed TCP/IP" },
+  { 0x0031, 0x0031, "Bridging PDU" },
+  { 0x0033, 0x0033, "Stream Protocol (ST-II)" },
+  { 0x0035, 0x0035, "Banyan Vines" },
+  { 0x0037, 0x0037, "reserved (until 1993)" },
+  { 0x0039, 0x0039, "AppleTalk EDDP" },
+  { 0x003b, 0x003b, "AppleTalk SmartBuffered" },
+  { 0x003d, 0x003d, "Multi-Link" },
+  { 0x003f, 0x003f, "NETBIOS Framing" },
+  { 0x0041, 0x0041, "Cisco Systems" },
+  { 0x0043, 0x0043, "Ascom Timeplex" },
+  { 0x0045, 0x0045, "Fujitsu Link Backup and Load Balancing (LBLB)" },
+  { 0x0047, 0x0047, "DCA Remote Lan" },
+  { 0x0049, 0x0049, "Serial Data Transport Protocol (PPP-SDTP)" },
+  { 0x004b, 0x004b, "SNA over 802.2" },
+  { 0x004d, 0x004d, "SNA" },
+  { 0x004f, 0x004f, "IP6 Header Compression" },
+  { 0x0051, 0x0051, "KNX Bridging Data" },
+  { 0x0053, 0x0053, "Encryption" },
+  { 0x0055, 0x0055, "Individual Link Encryption" },
+  { 0x0057, 0x0057, "Internet Protocol V6" },
+  { 0x006f, 0x006f, "Stampede Bridging" },
+  { 0x0071, 0x0071, "BAP Bandwidth Allocation Protocol" },
+  { 0x0073, 0x0073, "MP+ Protocol" },
+  { 0x007d, 0x007d, "reserved (Control Escape)" },
+  { 0x007f, 0x007f, "reserved (compression inefficient)" },
+  { 0x00cf, 0x00cf, "reserved (PPP NLPID)" },
+  { 0x00fb, 0x00fb, "compression on single link in multilink group" },
+  { 0x00fd, 0x00fd, "1st choice compression" },
+  { 0x00ff, 0x00ff, "reserved (compression inefficient)" },
+  { 0x0200, 0x02ff, "(compression inefficient)" },
+  { 0x0201, 0x0201, "802.1d Hello Packets" },
+  { 0x0203, 0x0203, "IBM Source Routing BPDU" },
+  { 0x0205, 0x0205, "DEC LANBridge100 Spanning Tree" },
+  { 0x0207, 0x0207, "Cisco Discovery Protocol" },
+  { 0x0209, 0x0209, "Netcs Twin Routing" },
+  { 0x0231, 0x0231, "Luxcom" },
+  { 0x0233, 0x0233, "Sigma Network Systems" },
+  { 0x0235, 0x0235, "Apple Client Server Protocol" },
+  { 0x1e00, 0x1eff, "(compression inefficient)" },
+  { 0x4001, 0x4001, "Cray Communications Control Protocol" },
+  { 0x4003, 0x4003, "CDPD Mobile Network Registration Protocol" },
+  { 0x4021, 0x4021, "Stacker LZS" },
+  { 0x8001, 0x801f, "Not Used - reserved" },
+  { 0x8021, 0x8021, "Internet Protocol Control Protocol" },
+  { 0x8023, 0x8023, "OSI Network Layer Control Protocol" },
+  { 0x8025, 0x8025, "Xerox NS IDP Control Protocol" },
+  { 0x8027, 0x8027, "DECnet Phase IV Control Protocol" },
+  { 0x8029, 0x8029, "Appletalk Control Protocol" },
+  { 0x802b, 0x802b, "Novell IPX Control Protocol" },
+  { 0x802d, 0x802d, "reserved" },
+  { 0x802f, 0x802f, "reserved" },
+  { 0x8031, 0x8031, "Bridging NCP" },
+  { 0x8033, 0x8033, "Stream Protocol Control Protocol" },
+  { 0x8035, 0x8035, "Banyan Vines Control Protocol" },
+  { 0x8037, 0x8037, "reserved till 1993" },
+  { 0x8039, 0x8039, "reserved" },
+  { 0x803b, 0x803b, "reserved" },
+  { 0x803d, 0x803d, "Multi-Link Control Protocol" },
+  { 0x803f, 0x803f, "NETBIOS Framing Control Protocol" },
+  { 0x8041, 0x8041, "Cisco Systems Control Protocol" },
+  { 0x8043, 0x8043, "Ascom Timeplex" },
+  { 0x8045, 0x8045, "Fujitsu LBLB Control Protocol" },
+  { 0x8047, 0x8047, "DCA Remote Lan Network Control Protocol (RLNCP)" },
+  { 0x8049, 0x8049, "Serial Data Control Protocol (PPP-SDCP)" },
+  { 0x804b, 0x804b, "SNA over 802.2 Control Protocol" },
+  { 0x804d, 0x804d, "SNA Control Protocol" },
+  { 0x804f, 0x804f, "IP6 Header Compression Control Protocol" },
+  { 0x8051, 0x8051, "KNX Bridging Control Protocol" },
+  { 0x8053, 0x8053, "Encryption Control Protocol" },
+  { 0x8055, 0x8055, "Individual Link Encryption Control Protocol" },
+  { 0x8057, 0x8057, "Internet Protocol V6 Control Protocol" },
+  { 0x806f, 0x806f, "Stampede Bridging Control Protocol" },
+  { 0x8073, 0x8073, "MP+ Control Protocol" },
+  { 0x8071, 0x8071, "BACP Bandwidth Allocation Control Protocol" },
+  { 0x807d, 0x807d, "Not Used - reserved" },
+  { 0x80cf, 0x80cf, "Not Used - reserved" },
+  { 0x80fb, 0x80fb, "compression on single link in multilink group control" },
+  { 0x80fd, 0x80fd, "Compression Control Protocol" },
+  { 0x80ff, 0x80ff, "Not Used - reserved" },
+  { 0x8207, 0x8207, "Cisco Discovery Protocol Control" },
+  { 0x8209, 0x8209, "Netcs Twin Routing" },
+  { 0x8235, 0x8235, "Apple Client Server Protocol Control" },
+  { 0xc021, 0xc021, "Link Control Protocol" },
+  { 0xc023, 0xc023, "Password Authentication Protocol" },
+  { 0xc025, 0xc025, "Link Quality Report" },
+  { 0xc027, 0xc027, "Shiva Password Authentication Protocol" },
+  { 0xc029, 0xc029, "CallBack Control Protocol (CBCP)" },
+  { 0xc081, 0xc081, "Container Control Protocol" },
+  { 0xc223, 0xc223, "Challenge Handshake Authentication Protocol" },
+  { 0xc225, 0xc225, "RSA Authentication Protocol" },
+  { 0xc227, 0xc227, "Extensible Authentication Protocol" },
+  { 0xc26f, 0xc26f, "Stampede Bridging Authorization Protocol" },
+  { 0xc281, 0xc281, "Proprietary Authentication Protocol" },
+  { 0xc283, 0xc283, "Proprietary Authentication Protocol" },
+  { 0xc481, 0xc481, "Proprietary Node ID Authentication Protocol" }
+};
+
+#define NPROTOCOLS (sizeof protocols/sizeof protocols[0])
+
+const char *
+hdlc_Protocol2Nam(u_short proto)
+{
+  unsigned f;
+
+  for (f = 0; f < NPROTOCOLS; f++)
+    if (proto >= protocols[f].from && proto <= protocols[f].to)
+      return protocols[f].name;
+    else if (proto < protocols[f].from)
+      break;
+  return "unrecognised protocol";
+}
+
+static struct mbuf *
+hdlc_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+               u_short *proto __unused)
+{
+  struct physical *p = link2physical(l);
+  u_short fcs;
+  int len;
+
+  if (!p) {
+    log_Printf(LogERROR, "Can't Pull a hdlc packet from a logical link\n");
+    return bp;
+  }
+
+  log_DumpBp(LogHDLC, "hdlc_LayerPull:", bp);
+
+  bp = m_pullup(bp);
+  len = m_length(bp);
+  fcs = hdlc_Fcs(MBUF_CTOP(bp), len);
+
+  log_Printf(LogDEBUG, "%s: hdlc_LayerPull: fcs = %04x (%s)\n",
+             p->link.name, fcs, (fcs == GOODFCS) ? "good" : "BAD!");
+
+  p->hdlc.lqm.ifInOctets += len + 1;			/* plus 1 flag octet! */
+
+  if (fcs != GOODFCS) {
+    p->hdlc.lqm.ifInErrors++;
+    p->hdlc.stats.badfcs++;
+    m_freem(bp);
+    return NULL;
+  }
+
+  /* Either done here or by the sync layer */
+  p->hdlc.lqm.lqr.InGoodOctets += len + 1;		/* plus 1 flag octet! */
+  p->hdlc.lqm.ifInUniPackets++;
+
+  if (len < 4) {			/* rfc1662 section 4.3 */
+    m_freem(bp);
+    bp = NULL;
+  }
+
+  bp = m_adj(bp, -2);			/* discard the FCS */
+  m_settype(bp, MB_HDLCIN);
+
+  return bp;
+}
+
+/* Detect a HDLC frame */
+
+static const struct frameheader {
+  const u_char *data;
+  int len;
+} FrameHeaders[] = {
+  { "\176\377\003\300\041", 5 },
+  { "\176\377\175\043\300\041", 6 },
+  { "\176\177\175\043\100\041", 6 },
+  { "\176\175\337\175\043\300\041", 7 },
+  { "\176\175\137\175\043\100\041", 7 },
+  { NULL, 0 }
+};
+
+int
+hdlc_Detect(u_char const **cp, unsigned n, int issync)
+{
+  const struct frameheader *fh;
+  const u_char *h;
+  size_t len, cmp;
+
+  while (n) {
+    for (fh = FrameHeaders; fh->len; fh++) {
+      h = issync ? fh->data + 1 : fh->data;
+      len = issync ? fh->len - 1 : fh->len;
+      cmp = n >= len ? len : n;
+      if (memcmp(*cp, h, cmp) == 0)
+        return cmp == len;
+    }
+    n--;
+    (*cp)++;
+  }
+
+  return 0;
+}
+
+int
+hdlc_ReportStatus(struct cmdargs const *arg)
+{
+  struct hdlc *hdlc = &arg->cx->physical->hdlc;
+
+  prompt_Printf(arg->prompt, "%s HDLC level errors:\n", arg->cx->name);
+  prompt_Printf(arg->prompt, " Bad Frame Check Sequence fields: %u\n",
+	        hdlc->stats.badfcs);
+  prompt_Printf(arg->prompt, " Bad address (!= 0x%02x) fields:    %u\n",
+	        HDLC_ADDR, hdlc->stats.badaddr);
+  prompt_Printf(arg->prompt, " Bad command (!= 0x%02x) fields:    %u\n",
+	        HDLC_UI, hdlc->stats.badcommand);
+  prompt_Printf(arg->prompt, " Unrecognised protocol fields:    %u\n",
+	        hdlc->stats.unknownproto);
+  return 0;
+}
+
+static void
+hdlc_ReportTime(void *v)
+{
+  /* Moan about HDLC errors */
+  struct hdlc *hdlc = (struct hdlc *)v;
+
+  timer_Stop(&hdlc->ReportTimer);
+
+  if (memcmp(&hdlc->laststats, &hdlc->stats, sizeof hdlc->stats)) {
+    log_Printf(LogPHASE,
+              "%s: HDLC errors -> FCS: %u, ADDR: %u, COMD: %u, PROTO: %u\n",
+              hdlc->lqm.owner->fsm.link->name,
+	      hdlc->stats.badfcs - hdlc->laststats.badfcs,
+              hdlc->stats.badaddr - hdlc->laststats.badaddr,
+              hdlc->stats.badcommand - hdlc->laststats.badcommand,
+              hdlc->stats.unknownproto - hdlc->laststats.unknownproto);
+    hdlc->laststats = hdlc->stats;
+  }
+
+  timer_Start(&hdlc->ReportTimer);
+}
+
+void
+hdlc_StartTimer(struct hdlc *hdlc)
+{
+  timer_Stop(&hdlc->ReportTimer);
+  hdlc->ReportTimer.load = 60 * SECTICKS;
+  hdlc->ReportTimer.arg = hdlc;
+  hdlc->ReportTimer.func = hdlc_ReportTime;
+  hdlc->ReportTimer.name = "hdlc";
+  timer_Start(&hdlc->ReportTimer);
+}
+
+void
+hdlc_StopTimer(struct hdlc *hdlc)
+{
+  timer_Stop(&hdlc->ReportTimer);
+}
+
+struct layer hdlclayer = { LAYER_HDLC, "hdlc", hdlc_LayerPush, hdlc_LayerPull };
diff --git a/src/hdlc.h b/src/hdlc.h
new file mode 100644
index 0000000..bf503fe
--- /dev/null
+++ b/src/hdlc.h
@@ -0,0 +1,116 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/hdlc.h,v 1.24.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+/*
+ *  Definition for Async HDLC
+ */
+#define HDLC_SYN 0x7e		/* SYNC character */
+#define HDLC_ESC 0x7d		/* Escape character */
+#define HDLC_XOR 0x20		/* Modifier value */
+
+#define	HDLC_ADDR 0xff
+#define	HDLC_UI	  0x03
+/*
+ *  Definition for HDLC Frame Check Sequence
+ */
+#define INITFCS 0xffff		/* Initial value for FCS computation */
+#define GOODFCS 0xf0b8		/* Good FCS value */
+
+#define	DEF_MRU		1500
+#define	MAX_MRU		2048
+#define	MIN_MRU		296
+
+#define	DEF_MTU		0	/* whatever peer says */
+#define	MAX_MTU		2048
+#define	MIN_MTU		296
+
+struct physical;
+struct link;
+struct lcp;
+struct bundle;
+struct mbuf;
+struct cmdargs;
+
+struct hdlc {
+  struct pppTimer ReportTimer;
+
+  struct {
+    int badfcs;
+    int badaddr;
+    int badcommand;
+    int unknownproto;
+  } laststats, stats;
+
+  struct {
+    struct lcp *owner;			/* parent LCP */
+    struct pppTimer timer;		/* When to send */
+    int method;				/* bit-mask for LQM_* from lqr.h */
+
+    u_int32_t ifOutUniPackets;		/* Packets sent by me */
+    u_int32_t ifOutOctets;		/* Octets sent by me */
+    u_int32_t ifInUniPackets;		/* Packets received from peer */
+    u_int32_t ifInDiscards;		/* Discards */
+    u_int32_t ifInErrors;		/* Errors */
+    u_int32_t ifInOctets;		/* Octets received from peer (unused) */
+
+    struct {
+      u_int32_t InGoodOctets;		/* Good octets received from peer */
+      u_int32_t OutLQRs;		/* LQRs sent by me */
+      u_int32_t InLQRs;			/* LQRs received from peer */
+
+      struct lqrsavedata Save;		/* Our last LQR */
+      struct lqrsavedata prevSave;	/* Our last-but-one LQR (analysis) */
+
+      struct lqrdata peer;		/* Last LQR from peer */
+      int peer_timeout;			/* peers max lqr timeout */
+      int resent;			/* Resent last packet `resent' times */
+    } lqr;
+
+    struct {
+      u_int32_t seq_sent;		/* last echo sent */
+      u_int32_t seq_recv;		/* last echo received */
+    } echo;
+  } lqm;
+};
+
+
+extern void hdlc_Init(struct hdlc *, struct lcp *);
+extern void hdlc_StartTimer(struct hdlc *);
+extern void hdlc_StopTimer(struct hdlc *);
+extern int hdlc_ReportStatus(struct cmdargs const *);
+extern const char *hdlc_Protocol2Nam(u_short);
+extern void hdlc_DecodePacket(struct bundle *, u_short, struct mbuf *,
+                              struct link *);
+
+extern u_short hdlc_Fcs(u_char *, size_t);
+extern int hdlc_Detect(u_char const **, unsigned, int);
+#define hdlc_WrapperOctets() (2)
+
+extern struct layer hdlclayer;
diff --git a/src/i4b.c b/src/i4b.c
new file mode 100644
index 0000000..be019c9
--- /dev/null
+++ b/src/i4b.c
@@ -0,0 +1,446 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/i4b.c,v 1.16.12.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+
+#include <sys/un.h>
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+#include <sys/ioctl.h>
+#endif
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#ifdef __NetBSD__
+#include <netisdn/i4b_ioctl.h>
+#include <netisdn/i4b_rbch_ioctl.h>
+#else
+#include <i4b/i4b_ioctl.h>
+#include <i4b/i4b_rbch_ioctl.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "mp.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "main.h"
+#include "i4b.h"
+
+#define	Online(dev)	((dev)->mbits & TIOCM_CD)
+
+struct i4bdevice {
+  struct device dev;		/* What struct physical knows about */
+  struct pppTimer Timer;	/* CD checks */
+  int mbits;			/* Current DCD status */
+  int carrier_seconds;		/* seconds before CD is *required* */
+};
+
+#define device2i4b(d) ((d)->type == I4B_DEVICE ? (struct i4bdevice *)d : NULL)
+
+unsigned
+i4b_DeviceSize(void)
+{
+  return sizeof(struct i4bdevice);
+}
+
+/*
+ * i4b_Timeout() watches the DCD signal and mentions it if it's status
+ * changes.
+ */
+static void
+i4b_Timeout(void *data)
+{
+  struct physical *p = data;
+  struct i4bdevice *dev = device2i4b(p->handler);
+  int ombits, change;
+
+  timer_Stop(&dev->Timer);
+  dev->Timer.load = SECTICKS;		/* Once a second please */
+  timer_Start(&dev->Timer);
+  ombits = dev->mbits;
+
+  if (p->fd >= 0) {
+    if (ioctl(p->fd, TIOCMGET, &dev->mbits) < 0) {
+      log_Printf(LogPHASE, "%s: ioctl error (%s)!\n", p->link.name,
+                 strerror(errno));
+      datalink_Down(p->dl, CLOSE_NORMAL);
+      timer_Stop(&dev->Timer);
+      return;
+    }
+  } else
+    dev->mbits = 0;
+
+  if (ombits == -1) {
+    /* First time looking for carrier */
+    if (Online(dev))
+      log_Printf(LogPHASE, "%s: %s: CD detected\n", p->link.name, p->name.full);
+    else if (++dev->carrier_seconds >= dev->dev.cd.delay) {
+      log_Printf(LogPHASE, "%s: %s: No carrier"
+                 " (increase ``set cd'' from %d ?)\n",
+                 p->link.name, p->name.full, dev->dev.cd.delay);
+      timer_Stop(&dev->Timer);
+      /* i4b_AwaitCarrier() will notice */
+    } else {
+      /* Keep waiting */
+      log_Printf(LogDEBUG, "%s: %s: Still no carrier (%d/%d)\n",
+                 p->link.name, p->name.full, dev->carrier_seconds,
+                 dev->dev.cd.delay);
+      dev->mbits = -1;
+    }
+  } else {
+    change = ombits ^ dev->mbits;
+    if (change & TIOCM_CD) {
+      if (dev->mbits & TIOCM_CD)
+        log_Printf(LogDEBUG, "%s: offline -> online\n", p->link.name);
+      else {
+        log_Printf(LogDEBUG, "%s: online -> offline\n", p->link.name);
+        log_Printf(LogPHASE, "%s: Carrier lost\n", p->link.name);
+        datalink_Down(p->dl, CLOSE_NORMAL);
+        timer_Stop(&dev->Timer);
+      }
+    } else
+      log_Printf(LogDEBUG, "%s: Still %sline\n", p->link.name,
+                 Online(dev) ? "on" : "off");
+  }
+}
+
+static void
+i4b_StartTimer(struct physical *p)
+{
+  struct i4bdevice *dev = device2i4b(p->handler);
+
+  timer_Stop(&dev->Timer);
+  dev->Timer.load = SECTICKS;
+  dev->Timer.func = i4b_Timeout;
+  dev->Timer.name = "i4b CD";
+  dev->Timer.arg = p;
+  log_Printf(LogDEBUG, "%s: Using i4b_Timeout [%p]\n",
+             p->link.name, i4b_Timeout);
+  timer_Start(&dev->Timer);
+}
+
+static int
+i4b_AwaitCarrier(struct physical *p)
+{
+  struct i4bdevice *dev = device2i4b(p->handler);
+
+  if (dev->mbits == -1) {
+    if (dev->Timer.state == TIMER_STOPPED) {
+      dev->carrier_seconds = 0;
+      i4b_StartTimer(p);
+    }
+    return CARRIER_PENDING;			/* Not yet ! */
+  }
+
+  return Online(dev) ? CARRIER_OK : CARRIER_LOST;
+}
+
+static int
+i4b_Raw(struct physical *p)
+{
+  int oldflag;
+
+  log_Printf(LogDEBUG, "%s: Entering i4b_Raw\n", p->link.name);
+
+  oldflag = fcntl(p->fd, F_GETFL, 0);
+  if (oldflag < 0)
+    return 0;
+  fcntl(p->fd, F_SETFL, oldflag | O_NONBLOCK);
+
+  return 1;
+}
+
+static void
+i4b_Offline(struct physical *p)
+{
+  struct i4bdevice *dev = device2i4b(p->handler);
+
+  if (p->fd >= 0) {
+    timer_Stop(&dev->Timer);
+    if (Online(dev)) {
+      int dummy;
+
+      dummy = 1;
+      ioctl(p->fd, TIOCCDTR, &dummy);
+    }
+  }
+}
+
+static void
+i4b_Cooked(struct physical *p)
+{
+  int oldflag;
+
+  i4b_Offline(p);	/* In case of emergency close()s */
+
+  if ((oldflag = fcntl(p->fd, F_GETFL, 0)) != -1)
+    fcntl(p->fd, F_SETFL, oldflag & ~O_NONBLOCK);
+}
+
+static void
+i4b_StopTimer(struct physical *p)
+{
+  struct i4bdevice *dev = device2i4b(p->handler);
+
+  timer_Stop(&dev->Timer);
+}
+
+static void
+i4b_Free(struct physical *p)
+{
+  struct i4bdevice *dev = device2i4b(p->handler);
+
+  i4b_Offline(p);	/* In case of emergency close()s */
+  free(dev);
+}
+
+static unsigned
+i4b_Speed(struct physical *p)
+{
+  struct termios ios;
+  unsigned ret;
+
+  if (tcgetattr(p->fd, &ios) == -1 ||
+      (ret = SpeedToUnsigned(cfgetispeed(&ios))) == 0)
+    ret = 64000;
+
+  return ret;
+}
+
+static const char *
+i4b_OpenInfo(struct physical *p)
+{
+  struct i4bdevice *dev = device2i4b(p->handler);
+  static char buf[26];
+
+  if (Online(dev))
+    snprintf(buf, sizeof buf, "carrier took %ds", dev->carrier_seconds);
+  else
+    *buf = '\0';
+
+  return buf;
+}
+
+static int
+i4b_Slot(struct physical *p)
+{
+  struct stat st;
+
+  if (fstat(p->fd, &st) == 0)
+    return minor(st.st_rdev);
+
+  return -1;
+}
+
+static void
+i4b_device2iov(struct device *d, struct iovec *iov, int *niov,
+               int maxiov __unused, int *auxfd __unused, int *nauxfd __unused)
+{
+  struct i4bdevice *dev = device2i4b(d);
+  int sz = physical_MaxDeviceSize();
+
+  iov[*niov].iov_base = realloc(d, sz);
+  if (iov[*niov].iov_base == NULL) {
+    log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
+    AbortProgram(EX_OSERR);
+  }
+  iov[*niov].iov_len = sz;
+  (*niov)++;
+
+  if (dev->Timer.state != TIMER_STOPPED) {
+    timer_Stop(&dev->Timer);
+    dev->Timer.state = TIMER_RUNNING;
+  }
+}
+
+static struct device basei4bdevice = {
+  I4B_DEVICE,
+  "i4b",
+  0,
+  { CD_REQUIRED, DEF_I4BCDDELAY },
+  i4b_AwaitCarrier,
+  NULL,
+  i4b_Raw,
+  i4b_Offline,
+  i4b_Cooked,
+  NULL,
+  i4b_StopTimer,
+  i4b_Free,
+  NULL,
+  NULL,
+  i4b_device2iov,
+  i4b_Speed,
+  i4b_OpenInfo,
+  i4b_Slot
+};
+
+struct device *
+i4b_iov2device(int type, struct physical *p, struct iovec *iov, int *niov,
+               int maxiov __unused, int *auxfd __unused, int *nauxfd __unused)
+{
+  if (type == I4B_DEVICE) {
+    struct i4bdevice *dev = (struct i4bdevice *)iov[(*niov)++].iov_base;
+
+    dev = realloc(dev, sizeof *dev);	/* Reduce to the correct size */
+    if (dev == NULL) {
+      log_Printf(LogALERT, "Failed to allocate memory: %d\n",
+                 (int)(sizeof *dev));
+      AbortProgram(EX_OSERR);
+    }
+
+    /* Refresh function pointers etc */
+    memcpy(&dev->dev, &basei4bdevice, sizeof dev->dev);
+
+    physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
+    if (dev->Timer.state != TIMER_STOPPED) {
+      dev->Timer.state = TIMER_STOPPED;
+      p->handler = &dev->dev;		/* For the benefit of StartTimer */
+      i4b_StartTimer(p);
+    }
+    return &dev->dev;
+  }
+
+  return NULL;
+}
+
+struct device *
+i4b_Create(struct physical *p)
+{
+  struct i4bdevice *dev;
+  int oldflag, dial;
+  msg_vr_req_t req;
+  telno_t number;
+
+  if (p->fd < 0 || ioctl(p->fd, I4B_RBCH_VR_REQ, &req))
+    /* Don't want this */
+    return NULL;
+
+  /*
+   * We don't bother validating the version.... all versions of i4b that
+   * support I4B_RBCH_VR_REQ are fair game :-)
+   */
+
+  if (*p->name.full == '\0') {
+    physical_SetDevice(p, ttyname(p->fd));
+    log_Printf(LogDEBUG, "%s: Input is an i4b version %d.%d.%d isdn "
+               "device (%s)\n", p->link.name, req.version, req.release,
+               req.step, p->name.full);
+    dial = 0;
+  } else {
+    log_Printf(LogDEBUG, "%s: Opened %s (i4b version %d.%d.%d)\n",
+               p->link.name, p->name.full, req.version, req.release, req.step);
+    dial = 1;
+  }
+
+  /* We're gonna return an i4bdevice (unless something goes horribly wrong) */
+
+  if ((dev = malloc(sizeof *dev)) == NULL) {
+    /* Complete failure - parent doesn't continue trying to ``create'' */
+    close(p->fd);
+    p->fd = -1;
+    return NULL;
+  }
+
+  memcpy(&dev->dev, &basei4bdevice, sizeof dev->dev);
+  memset(&dev->Timer, '\0', sizeof dev->Timer);
+  dev->mbits = -1;
+
+  switch (p->cfg.cd.necessity) {
+    case CD_VARIABLE:
+      dev->dev.cd.delay = p->cfg.cd.delay;
+      break;
+    case CD_REQUIRED:
+      dev->dev.cd = p->cfg.cd;
+      break;
+    case CD_NOTREQUIRED:
+      log_Printf(LogWARN, "%s: Carrier must be set, using ``set cd %d!''\n",
+                 p->link.name, dev->dev.cd.delay);
+    case CD_DEFAULT:
+      break;
+  }
+
+  oldflag = fcntl(p->fd, F_GETFL, 0);
+  if (oldflag < 0) {
+    /* Complete failure - parent doesn't continue trying to ``create'' */
+
+    log_Printf(LogWARN, "%s: Open: Cannot get physical flags: %s\n",
+               p->link.name, strerror(errno));
+    i4b_Cooked(p);
+    close(p->fd);
+    p->fd = -1;
+    free(dev);
+    return NULL;
+  } else
+    fcntl(p->fd, F_SETFL, oldflag & ~O_NONBLOCK);
+
+  if (dial) {
+    strncpy(number, datalink_ChoosePhoneNumber(p->dl), sizeof number - 1);
+    number[sizeof number - 1] = '\0';
+    if (number[0] == '\0')
+      dial = 0;
+  }
+  if (dial && ioctl(p->fd, I4B_RBCH_DIALOUT, number) == -1) {
+    /* Complete failure - parent doesn't continue trying to ``create'' */
+
+    log_Printf(LogWARN, "%s: ioctl(I4B_RBCH_DIALOUT): %s\n",
+               p->link.name, strerror(errno));
+    i4b_Cooked(p);
+    close(p->fd);
+    p->fd = -1;
+    free(dev);
+    return NULL;
+  }
+
+  physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNC);
+
+  return &dev->dev;
+}
diff --git a/src/i4b.h b/src/i4b.h
new file mode 100644
index 0000000..f88ddbd
--- /dev/null
+++ b/src/i4b.h
@@ -0,0 +1,37 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/i4b.h,v 1.5.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct physical;
+struct device;
+
+#define DEF_I4BCDDELAY	6		/* Default ``set cd'' value */
+
+extern struct device *i4b_Create(struct physical *);
+extern struct device *i4b_iov2device(int, struct physical *,
+                                     struct iovec *, int *, int, int *, int *);
+extern unsigned i4b_DeviceSize(void);
diff --git a/src/id.c b/src/id.c
new file mode 100644
index 0000000..785431d
--- /dev/null
+++ b/src/id.c
@@ -0,0 +1,303 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/id.c,v 1.23.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#ifndef NONETGRAPH
+#include <netgraph.h>
+#endif
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sysexits.h>
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+#include <sys/linker.h>
+#endif
+#include <unistd.h>
+#ifdef __OpenBSD__
+#include <util.h>
+#else
+#include <libutil.h>
+#endif
+#include <utmp.h>
+
+#include "log.h"
+#include "main.h"
+#include "id.h"
+
+static int uid;
+static int euid;
+
+void
+ID0init()
+{
+  uid = getuid();
+  euid = geteuid();
+}
+
+static void
+ID0setuser(void)
+{
+  if (seteuid(uid) == -1) {
+    log_Printf(LogERROR, "ID0setuser: Unable to seteuid!\n");
+    AbortProgram(EX_NOPERM);
+  }
+}
+
+uid_t
+ID0realuid()
+{
+  return uid;
+}
+
+static void
+ID0set0(void)
+{
+  if (seteuid(euid) == -1) {
+    log_Printf(LogERROR, "ID0set0: Unable to seteuid!\n");
+    AbortProgram(EX_NOPERM);
+  }
+}
+
+int
+ID0ioctl(int fd, unsigned long req, void *arg)
+{
+  int ret;
+
+  ID0set0();
+  ret = ioctl(fd, req, arg);
+  log_Printf(LogID0, "%d = ioctl(%d, %lu, %p)\n", ret, fd, req, arg);
+  ID0setuser();
+  return ret;
+}
+
+int
+ID0unlink(const char *name)
+{
+  int ret;
+
+  ID0set0();
+  ret = unlink(name);
+  log_Printf(LogID0, "%d = unlink(\"%s\")\n", ret, name);
+  ID0setuser();
+  return ret;
+}
+
+int
+ID0socket(int domain, int type, int protocol)
+{
+  int ret;
+
+  ID0set0();
+  ret = socket(domain, type, protocol);
+  log_Printf(LogID0, "%d = socket(%d, %d, %d)\n", ret, domain, type, protocol);
+  ID0setuser();
+  return ret;
+}
+
+FILE *
+ID0fopen(const char *path, const char *mode)
+{
+  FILE *ret;
+
+  ID0set0();
+  ret = fopen(path, mode);
+  log_Printf(LogID0, "%p = fopen(\"%s\", \"%s\")\n", ret, path, mode);
+  ID0setuser();
+  return ret;
+}
+
+int
+ID0open(const char *path, int flags, ...)
+{
+  int ret;
+  va_list ap;
+
+  va_start(ap, flags);
+  ID0set0();
+  ret = open(path, flags, va_arg(ap, int));
+  log_Printf(LogID0, "%d = open(\"%s\", %d)\n", ret, path, flags);
+  ID0setuser();
+  va_end(ap);
+  return ret;
+}
+
+int
+ID0write(int fd, const void *data, size_t len)
+{
+  int ret;
+
+  ID0set0();
+  ret = write(fd, data, len);
+  log_Printf(LogID0, "%d = write(%d, data, %ld)\n", ret, fd, (long)len);
+  ID0setuser();
+  return ret;
+}
+
+int
+ID0uu_lock(const char *basettyname)
+{
+  int ret;
+
+  ID0set0();
+  ret = uu_lock(basettyname);
+  log_Printf(LogID0, "%d = uu_lock(\"%s\")\n", ret, basettyname);
+  ID0setuser();
+  return ret;
+}
+
+int
+ID0uu_lock_txfr(const char *basettyname, pid_t newpid)
+{
+  int ret;
+
+  ID0set0();
+  ret = uu_lock_txfr(basettyname, newpid);
+  log_Printf(LogID0, "%d = uu_lock_txfr(\"%s\", %ld)\n", ret, basettyname,
+             (long)newpid);
+  ID0setuser();
+  return ret;
+}
+
+int
+ID0uu_unlock(const char *basettyname)
+{
+  int ret;
+
+  ID0set0();
+  ret = uu_unlock(basettyname);
+  log_Printf(LogID0, "%d = uu_unlock(\"%s\")\n", ret, basettyname);
+  ID0setuser();
+  return ret;
+}
+
+void
+ID0login(struct utmp *ut)
+{
+  ID0set0();
+  if (logout(ut->ut_line)) {
+    log_Printf(LogID0, "logout(\"%s\")\n", ut->ut_line);
+    logwtmp(ut->ut_line, "", "");
+    log_Printf(LogID0, "logwtmp(\"%s\", \"\", \"\")\n", ut->ut_line);
+  }
+  login(ut);
+  log_Printf(LogID0, "login(\"%s\", \"%.*s\")\n",
+            ut->ut_line, (int)(sizeof ut->ut_name), ut->ut_name);
+  ID0setuser();
+}
+
+void
+ID0logout(const char *device, int nologout)
+{
+  struct utmp ut;
+  char ut_line[sizeof ut.ut_line + 1];
+
+  strncpy(ut_line, device, sizeof ut_line - 1);
+  ut_line[sizeof ut_line - 1] = '\0';
+
+  ID0set0();
+  if (nologout || logout(ut_line)) {
+    log_Printf(LogID0, "logout(\"%s\")\n", ut_line);
+    logwtmp(ut_line, "", "");
+    log_Printf(LogID0, "logwtmp(\"%s\", \"\", \"\")\n", ut_line);
+  } else
+    log_Printf(LogERROR, "ID0logout: No longer logged in on %s\n", ut_line);
+  ID0setuser();
+}
+
+int
+ID0bind_un(int s, const struct sockaddr_un *name)
+{
+  int result;
+
+  ID0set0();
+  result = bind(s, (const struct sockaddr *)name, sizeof *name);
+  log_Printf(LogID0, "%d = bind(%d, \"%s\", %d)\n",
+            result, s, name->sun_path, (int)sizeof(*name));
+  ID0setuser();
+  return result;
+}
+
+int
+ID0connect_un(int s, const struct sockaddr_un *name)
+{
+  int result;
+
+  ID0set0();
+  result = connect(s, (const struct sockaddr *)name, sizeof *name);
+  log_Printf(LogID0, "%d = connect(%d, \"%s\", %d)\n",
+            result, s, name->sun_path, (int)sizeof(*name));
+  ID0setuser();
+  return result;
+}
+
+int
+ID0kill(pid_t pid, int sig)
+{
+  int result;
+
+  ID0set0();
+  result = kill(pid, sig);
+  log_Printf(LogID0, "%d = kill(%ld, %d)\n", result, (long)pid, sig);
+  ID0setuser();
+  return result;
+}
+
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+int
+ID0kldload(const char *dev)
+{
+  int result;
+
+  ID0set0();
+  result = kldload(dev);
+  log_Printf(LogID0, "%d = kldload(\"%s\")\n", result, dev);
+  ID0setuser();
+  return result;
+}
+#endif
+
+#ifndef NONETGRAPH
+int
+ID0NgMkSockNode(const char *name, int *cs, int *ds)
+{
+  int result;
+
+  ID0set0();
+  result = NgMkSockNode(name, cs, ds);
+  log_Printf(LogID0, "%d = NgMkSockNode(\"%s\", &cs, &ds)\n",
+             result, name ? name : "");
+  ID0setuser();
+  return result;
+}
+#endif
diff --git a/src/id.h b/src/id.h
new file mode 100644
index 0000000..9181936
--- /dev/null
+++ b/src/id.h
@@ -0,0 +1,93 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/id.h,v 1.16.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#ifndef NOSUID
+struct utmp;
+struct sockaddr_un;
+
+extern void ID0init(void);
+extern uid_t ID0realuid(void);
+extern int ID0ioctl(int, unsigned long, void *);
+extern int ID0unlink(const char *);
+extern int ID0socket(int, int, int);
+extern FILE *ID0fopen(const char *, const char *);
+extern int ID0open(const char *, int, ...);
+extern int ID0write(int, const void *, size_t);
+extern int ID0uu_lock(const char *);
+extern int ID0uu_lock_txfr(const char *, pid_t);
+extern int ID0uu_unlock(const char *);
+extern void ID0login(struct utmp *);
+extern void ID0logout(const char *, int);
+extern int ID0bind_un(int, const struct sockaddr_un *);
+extern int ID0connect_un(int, const struct sockaddr_un *);
+extern int ID0kill(pid_t, int);
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+extern int ID0kldload(const char *);
+#endif
+#ifndef NONETGRAPH
+extern int ID0NgMkSockNode(const char *, int *, int *);
+#endif
+#else	/* NOSUID */
+#define ID0init()
+#define ID0realuid() (0)
+#define ID0ioctl ioctl
+#define ID0unlink unlink
+#define ID0socket socket
+#define ID0fopen fopen
+#define ID0open open
+#define ID0write write
+#define ID0uu_lock uu_lock
+#define ID0uu_lock_txfr uu_lock_txfr
+#define ID0uu_unlock uu_unlock
+#define ID0login(u)			\
+  do {					\
+    if (logout((u)->ut_line))		\
+      logwtmp((u)->ut_line, "", "");	\
+    login(u);				\
+  } while (0)
+#define ID0logout(dev, no)				\
+  do {							\
+    struct utmp ut;					\
+    strncpy(ut.ut_line, dev, sizeof ut.ut_line - 1);	\
+    ut.ut_line[sizeof ut.ut_line - 1] = '\0';		\
+    if (no || logout(ut.ut_line))			\
+      logwtmp(ut.ut_line, "", ""); 			\
+  } while (0)
+#define ID0bind_un(s, n) bind(s, (const struct sockaddr *)(n), sizeof *(n))
+#define ID0connect_un(s, n) \
+	connect(s, (const struct sockaddr *)(n), sizeof *(n))
+#define ID0kill kill
+#if defined(__FreeBSD__) && !defined(NOKLDLOAD)
+#include <sys/param.h>
+#include <sys/linker.h>
+#define ID0kldload kldload
+#endif
+#ifndef NONETGRAPH
+#define ID0NgMkSockNode NgMkSockNode
+#endif
+#endif
diff --git a/src/iface.c b/src/iface.c
new file mode 100644
index 0000000..7e11de9
--- /dev/null
+++ b/src/iface.c
@@ -0,0 +1,729 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/iface.c,v 1.38.24.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <net/if_dl.h>
+#ifdef __FreeBSD__
+#include <net/if_var.h>
+#endif
+#include <net/route.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+#ifndef NOINET6
+#include <netinet6/nd6.h>
+#endif
+#include <sys/un.h>
+
+#include <errno.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "id.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "descriptor.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "prompt.h"
+#include "iface.h"
+
+#define IN6MASK128	{{{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+			    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}}
+static const struct in6_addr in6mask128 = IN6MASK128;
+
+
+struct iface *
+iface_Create(const char *name)
+{
+  int mib[6], maxtries, err;
+  size_t needed, namelen;
+  char *buf, *ptr, *end;
+  struct if_msghdr *ifm;
+  struct ifa_msghdr *ifam;
+  struct sockaddr_dl *dl;
+  struct sockaddr *sa[RTAX_MAX];
+  struct iface *iface;
+  struct iface_addr *addr;
+
+  mib[0] = CTL_NET;
+  mib[1] = PF_ROUTE;
+  mib[2] = 0;
+  mib[3] = 0;
+  mib[4] = NET_RT_IFLIST;
+  mib[5] = 0;
+
+  maxtries = 20;
+  err = 0;
+  do {
+    if (maxtries-- == 0 || (err && err != ENOMEM)) {
+      fprintf(stderr, "iface_Create: sysctl: %s\n", strerror(err));
+      return NULL;
+    }
+
+    if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+      fprintf(stderr, "iface_Create: sysctl: estimate: %s\n",
+                strerror(errno));
+      return NULL;
+    }
+
+    if ((buf = (char *)malloc(needed)) == NULL) {
+      fprintf(stderr, "iface_Create: malloc failed: %s\n", strerror(errno));
+      return NULL;
+    }
+
+    if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
+      err = errno;
+      free(buf);
+      buf = NULL;
+    }
+  } while (buf == NULL);
+
+  ptr = buf;
+  end = buf + needed;
+  iface = NULL;
+  namelen = strlen(name);
+
+  while (ptr < end && iface == NULL) {
+    ifm = (struct if_msghdr *)ptr;			/* On if_msghdr */
+    if (ifm->ifm_type != RTM_IFINFO)
+      break;
+    dl = (struct sockaddr_dl *)(ifm + 1);		/* Single _dl at end */
+    if (dl->sdl_nlen == namelen && !strncmp(name, dl->sdl_data, namelen)) {
+      iface = (struct iface *)malloc(sizeof *iface);
+      if (iface == NULL) {
+        fprintf(stderr, "iface_Create: malloc: %s\n", strerror(errno));
+        return NULL;
+      }
+      iface->name = strdup(name);
+      iface->index = ifm->ifm_index;
+      iface->flags = ifm->ifm_flags;
+      iface->mtu = 0;
+      iface->addrs = 0;
+      iface->addr = NULL;
+    }
+    ptr += ifm->ifm_msglen;				/* First ifa_msghdr */
+    for (; ptr < end; ptr += ifam->ifam_msglen) {
+      ifam = (struct ifa_msghdr *)ptr;			/* Next if address */
+
+      if (ifam->ifam_type != RTM_NEWADDR)		/* finished this if */
+        break;
+
+      if (iface != NULL && ifam->ifam_addrs & RTA_IFA) {
+        /* Found a configured interface ! */
+        iface_ParseHdr(ifam, sa);
+
+        if (sa[RTAX_IFA] && (sa[RTAX_IFA]->sa_family == AF_INET
+#ifndef NOINET6
+                             || sa[RTAX_IFA]->sa_family == AF_INET6
+#endif
+                             )) {
+          /* Record the address */
+
+          addr = (struct iface_addr *)
+            realloc(iface->addr, (iface->addrs + 1) * sizeof iface->addr[0]);
+          if (addr == NULL)
+            break;
+          iface->addr = addr;
+
+          addr += iface->addrs;
+          iface->addrs++;
+
+          ncprange_setsa(&addr->ifa, sa[RTAX_IFA], sa[RTAX_NETMASK]);
+          if (sa[RTAX_BRD])
+            ncpaddr_setsa(&addr->peer, sa[RTAX_BRD]);
+          else
+            ncpaddr_init(&addr->peer);
+        }
+      }
+    }
+  }
+
+  free(buf);
+
+  return iface;
+}
+
+static int
+iface_addr_Zap(const char *name, struct iface_addr *addr, int s)
+{
+  struct ifaliasreq ifra;
+#ifndef NOINET6
+  struct in6_aliasreq ifra6;
+#endif
+  struct sockaddr_in *me4, *msk4, *peer4;
+  struct sockaddr_storage ssme, sspeer, ssmsk;
+  int res;
+
+  ncprange_getsa(&addr->ifa, &ssme, &ssmsk);
+  ncpaddr_getsa(&addr->peer, &sspeer);
+  res = 0;
+
+  switch (ncprange_family(&addr->ifa)) {
+  case AF_INET:
+    memset(&ifra, '\0', sizeof ifra);
+    strncpy(ifra.ifra_name, name, sizeof ifra.ifra_name - 1);
+
+    me4 = (struct sockaddr_in *)&ifra.ifra_addr;
+    memcpy(me4, &ssme, sizeof *me4);
+
+    msk4 = (struct sockaddr_in *)&ifra.ifra_mask;
+    memcpy(msk4, &ssmsk, sizeof *msk4);
+
+    peer4 = (struct sockaddr_in *)&ifra.ifra_broadaddr;
+    if (ncpaddr_family(&addr->peer) == AF_UNSPEC) {
+      peer4->sin_family = AF_INET;
+      peer4->sin_len = sizeof(*peer4);
+      peer4->sin_addr.s_addr = INADDR_NONE;
+    } else
+      memcpy(peer4, &sspeer, sizeof *peer4);
+
+    res = ID0ioctl(s, SIOCDIFADDR, &ifra);
+    if (log_IsKept(LogDEBUG)) {
+      char buf[100];
+
+      snprintf(buf, sizeof buf, "%s", ncprange_ntoa(&addr->ifa));
+      log_Printf(LogWARN, "%s: DIFADDR %s -> %s returns %d\n",
+                 ifra.ifra_name, buf, ncpaddr_ntoa(&addr->peer), res);
+    }
+    break;
+
+#ifndef NOINET6
+  case AF_INET6:
+    memset(&ifra6, '\0', sizeof ifra6);
+    strncpy(ifra6.ifra_name, name, sizeof ifra6.ifra_name - 1);
+
+    memcpy(&ifra6.ifra_addr, &ssme, sizeof ifra6.ifra_addr);
+    memcpy(&ifra6.ifra_prefixmask, &ssmsk, sizeof ifra6.ifra_prefixmask);
+    ifra6.ifra_prefixmask.sin6_family = AF_UNSPEC;
+    if (ncpaddr_family(&addr->peer) == AF_UNSPEC)
+      ifra6.ifra_dstaddr.sin6_family = AF_UNSPEC;
+    else
+      memcpy(&ifra6.ifra_dstaddr, &sspeer, sizeof ifra6.ifra_dstaddr);
+    ifra6.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
+    ifra6.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
+
+    res = ID0ioctl(s, SIOCDIFADDR_IN6, &ifra6);
+    break;
+#endif
+  }
+
+  if (res == -1) {
+    char dst[40];
+    const char *end =
+#ifndef NOINET6
+      ncprange_family(&addr->ifa) == AF_INET6 ? "_IN6" :
+#endif
+      "";
+
+    if (ncpaddr_family(&addr->peer) == AF_UNSPEC)
+      log_Printf(LogWARN, "iface rm: ioctl(SIOCDIFADDR%s, %s): %s\n",
+                 end, ncprange_ntoa(&addr->ifa), strerror(errno));
+    else {
+      snprintf(dst, sizeof dst, "%s", ncpaddr_ntoa(&addr->peer));
+      log_Printf(LogWARN, "iface rm: ioctl(SIOCDIFADDR%s, %s -> %s): %s\n",
+                 end, ncprange_ntoa(&addr->ifa), dst, strerror(errno));
+    }
+  }
+
+  return res != -1;
+}
+
+static int
+iface_addr_Add(const char *name, struct iface_addr *addr, int s)
+{
+  struct ifaliasreq ifra;
+#ifndef NOINET6
+  struct in6_aliasreq ifra6;
+#endif
+  struct sockaddr_in *me4, *msk4, *peer4;
+  struct sockaddr_storage ssme, sspeer, ssmsk;
+  int res;
+
+  ncprange_getsa(&addr->ifa, &ssme, &ssmsk);
+  ncpaddr_getsa(&addr->peer, &sspeer);
+  res = 0;
+
+  switch (ncprange_family(&addr->ifa)) {
+  case AF_INET:
+    memset(&ifra, '\0', sizeof ifra);
+    strncpy(ifra.ifra_name, name, sizeof ifra.ifra_name - 1);
+
+    me4 = (struct sockaddr_in *)&ifra.ifra_addr;
+    memcpy(me4, &ssme, sizeof *me4);
+
+    msk4 = (struct sockaddr_in *)&ifra.ifra_mask;
+    memcpy(msk4, &ssmsk, sizeof *msk4);
+
+    peer4 = (struct sockaddr_in *)&ifra.ifra_broadaddr;
+    if (ncpaddr_family(&addr->peer) == AF_UNSPEC) {
+      peer4->sin_family = AF_INET;
+      peer4->sin_len = sizeof(*peer4);
+      peer4->sin_addr.s_addr = INADDR_NONE;
+    } else
+      memcpy(peer4, &sspeer, sizeof *peer4);
+
+    res = ID0ioctl(s, SIOCAIFADDR, &ifra);
+    if (log_IsKept(LogDEBUG)) {
+      char buf[100];
+
+      snprintf(buf, sizeof buf, "%s", ncprange_ntoa(&addr->ifa));
+      log_Printf(LogWARN, "%s: AIFADDR %s -> %s returns %d\n",
+                 ifra.ifra_name, buf, ncpaddr_ntoa(&addr->peer), res);
+    }
+    break;
+
+#ifndef NOINET6
+  case AF_INET6:
+    memset(&ifra6, '\0', sizeof ifra6);
+    strncpy(ifra6.ifra_name, name, sizeof ifra6.ifra_name - 1);
+
+    memcpy(&ifra6.ifra_addr, &ssme, sizeof ifra6.ifra_addr);
+    memcpy(&ifra6.ifra_prefixmask, &ssmsk, sizeof ifra6.ifra_prefixmask);
+    if (ncpaddr_family(&addr->peer) == AF_UNSPEC)
+      ifra6.ifra_dstaddr.sin6_family = AF_UNSPEC;
+    else if (memcmp(&((struct sockaddr_in6 *)&ssmsk)->sin6_addr, &in6mask128,
+		    sizeof in6mask128) == 0)
+      memcpy(&ifra6.ifra_dstaddr, &sspeer, sizeof ifra6.ifra_dstaddr);
+    ifra6.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
+    ifra6.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
+
+    res = ID0ioctl(s, SIOCAIFADDR_IN6, &ifra6);
+    break;
+#endif
+  }
+
+  if (res == -1) {
+    char dst[40];
+    const char *end =
+#ifndef NOINET6
+      ncprange_family(&addr->ifa) == AF_INET6 ? "_IN6" :
+#endif
+      "";
+
+    if (ncpaddr_family(&addr->peer) == AF_UNSPEC)
+      log_Printf(LogWARN, "iface add: ioctl(SIOCAIFADDR%s, %s): %s\n",
+                 end, ncprange_ntoa(&addr->ifa), strerror(errno));
+    else {
+      snprintf(dst, sizeof dst, "%s", ncpaddr_ntoa(&addr->peer));
+      log_Printf(LogWARN, "iface add: ioctl(SIOCAIFADDR%s, %s -> %s): %s\n",
+                 end, ncprange_ntoa(&addr->ifa), dst, strerror(errno));
+    }
+  }
+
+  return res != -1;
+}
+
+
+void
+iface_Clear(struct iface *iface, struct ncp *ncp, int family, int how)
+{
+  int addrs, af, inskip, in6skip, s4 = -1, s6 = -1, *s;
+  unsigned n;
+
+  if (iface->addrs) {
+    inskip = in6skip = how == IFACE_CLEAR_ALL ? 0 : 1;
+    addrs = 0;
+
+    for (n = 0; n < iface->addrs; n++) {
+      af = ncprange_family(&iface->addr[n].ifa);
+      if (family == 0 || family == af) {
+        if (!iface->addr[n].system && (how & IFACE_SYSTEM))
+          continue;
+        switch (af) {
+        case AF_INET:
+          if (inskip) {
+            inskip = 0;
+            continue;
+          }
+          s = &s4;
+          break;
+
+#ifndef NOINET6
+        case AF_INET6:
+          if (in6skip) {
+            in6skip = 0;
+            continue;
+          }
+          s = &s6;
+          break;
+#endif
+        default:
+          continue;
+        }
+
+        if (*s == -1 && (*s = ID0socket(af, SOCK_DGRAM, 0)) == -1)
+          log_Printf(LogERROR, "iface_Clear: socket(): %s\n", strerror(errno));
+        else if (iface_addr_Zap(iface->name, iface->addr + n, *s)) {
+          ncp_IfaceAddrDeleted(ncp, iface->addr + n);
+          bcopy(iface->addr + n + 1, iface->addr + n,
+                (iface->addrs - n - 1) * sizeof *iface->addr);
+          iface->addrs--;
+          n--;
+        }
+      }
+    }
+
+    /* Don't bother realloc()ing - we have little to gain */
+
+    if (s4)
+      close(s4);
+    if (s6)
+      close(s6);
+  }
+}
+
+int
+iface_Add(struct iface *iface, struct ncp *ncp, const struct ncprange *ifa,
+          const struct ncpaddr *peer, int how)
+{
+  int af, removed, s;
+  unsigned n;
+  struct ncpaddr ncplocal;
+  struct iface_addr *addr, newaddr;
+
+  af = ncprange_family(ifa);
+  if ((s = ID0socket(af, SOCK_DGRAM, 0)) == -1) {
+    log_Printf(LogERROR, "iface_Add: socket(): %s\n", strerror(errno));
+    return 0;
+  }
+  ncprange_getaddr(ifa, &ncplocal);
+
+  for (n = 0; n < iface->addrs; n++) {
+    if (ncprange_contains(&iface->addr[n].ifa, &ncplocal) ||
+        ncpaddr_equal(&iface->addr[n].peer, peer)) {
+      /* Replace this sockaddr */
+      if (!(how & IFACE_FORCE_ADD)) {
+        close(s);
+        return 0;	/* errno = EEXIST; */
+      }
+
+      if (ncprange_equal(&iface->addr[n].ifa, ifa) &&
+          ncpaddr_equal(&iface->addr[n].peer, peer)) {
+        close(s);
+        return 1;	/* Already there */
+      }
+
+      removed = iface_addr_Zap(iface->name, iface->addr + n, s);
+      if (removed)
+        ncp_IfaceAddrDeleted(ncp, iface->addr + n);
+      ncprange_copy(&iface->addr[n].ifa, ifa);
+      ncpaddr_copy(&iface->addr[n].peer, peer);
+      if (!iface_addr_Add(iface->name, iface->addr + n, s)) {
+        if (removed) {
+          bcopy(iface->addr + n + 1, iface->addr + n,
+                (iface->addrs - n - 1) * sizeof *iface->addr);
+          iface->addrs--;
+          n--;
+        }
+        close(s);
+        return 0;
+      }
+      close(s);
+      ncp_IfaceAddrAdded(ncp, iface->addr + n);
+      return 1;
+    }
+  }
+
+  addr = (struct iface_addr *)realloc
+    (iface->addr, (iface->addrs + 1) * sizeof iface->addr[0]);
+  if (addr == NULL) {
+    log_Printf(LogERROR, "iface_inAdd: realloc: %s\n", strerror(errno));
+    close(s);
+    return 0;
+  }
+  iface->addr = addr;
+
+  ncprange_copy(&newaddr.ifa, ifa);
+  ncpaddr_copy(&newaddr.peer, peer);
+  newaddr.system = !!(how & IFACE_SYSTEM);
+  if (!iface_addr_Add(iface->name, &newaddr, s)) {
+    close(s);
+    return 0;
+  }
+
+  if (how & IFACE_ADD_FIRST) {
+    /* Stuff it at the start of our list */
+    n = 0;
+    bcopy(iface->addr, iface->addr + 1, iface->addrs * sizeof *iface->addr);
+  } else
+    n = iface->addrs;
+
+  iface->addrs++;
+  memcpy(iface->addr + n, &newaddr, sizeof(*iface->addr));
+
+  close(s);
+  ncp_IfaceAddrAdded(ncp, iface->addr + n);
+
+  return 1;
+}
+
+int
+iface_Delete(struct iface *iface, struct ncp *ncp, const struct ncpaddr *del)
+{
+  struct ncpaddr found;
+  unsigned n;
+  int res, s;
+
+  if ((s = ID0socket(ncpaddr_family(del), SOCK_DGRAM, 0)) == -1) {
+    log_Printf(LogERROR, "iface_Delete: socket(): %s\n", strerror(errno));
+    return 0;
+  }
+
+  for (n = res = 0; n < iface->addrs; n++) {
+    ncprange_getaddr(&iface->addr[n].ifa, &found);
+    if (ncpaddr_equal(&found, del)) {
+      if (iface_addr_Zap(iface->name, iface->addr + n, s)) {
+        ncp_IfaceAddrDeleted(ncp, iface->addr + n);
+        bcopy(iface->addr + n + 1, iface->addr + n,
+              (iface->addrs - n - 1) * sizeof *iface->addr);
+        iface->addrs--;
+        res = 1;
+      }
+      break;
+    }
+  }
+
+  close(s);
+
+  return res;
+}
+
+#define IFACE_ADDFLAGS 1
+#define IFACE_DELFLAGS 2
+
+static int
+iface_ChangeFlags(const char *ifname, int flags, int how)
+{
+  struct ifreq ifrq;
+  int s, new_flags;
+
+  s = ID0socket(PF_INET, SOCK_DGRAM, 0);
+  if (s < 0) {
+    log_Printf(LogERROR, "iface_ChangeFlags: socket: %s\n", strerror(errno));
+    return 0;
+  }
+
+  memset(&ifrq, '\0', sizeof ifrq);
+  strncpy(ifrq.ifr_name, ifname, sizeof ifrq.ifr_name - 1);
+  ifrq.ifr_name[sizeof ifrq.ifr_name - 1] = '\0';
+  if (ID0ioctl(s, SIOCGIFFLAGS, &ifrq) < 0) {
+    log_Printf(LogERROR, "iface_ChangeFlags: ioctl(SIOCGIFFLAGS): %s\n",
+       strerror(errno));
+    close(s);
+    return 0;
+  }
+#ifdef __FreeBSD__
+  new_flags = (ifrq.ifr_flags & 0xffff) | (ifrq.ifr_flagshigh << 16);
+#else
+  new_flags = ifrq.ifr_flags & 0xffff;
+#endif
+
+  if (how == IFACE_ADDFLAGS)
+    new_flags |= flags;
+  else
+    new_flags &= ~flags;
+  ifrq.ifr_flags = new_flags & 0xffff;
+#ifdef __FreeBSD__
+  ifrq.ifr_flagshigh = new_flags >> 16;
+#endif
+
+  if (ID0ioctl(s, SIOCSIFFLAGS, &ifrq) < 0) {
+    log_Printf(LogERROR, "iface_ChangeFlags: ioctl(SIOCSIFFLAGS): %s\n",
+       strerror(errno));
+    close(s);
+    return 0;
+  }
+  close(s);
+
+  return 1;	/* Success */
+}
+
+int
+iface_SetFlags(const char *ifname, int flags)
+{
+  return iface_ChangeFlags(ifname, flags, IFACE_ADDFLAGS);
+}
+
+int
+iface_ClearFlags(const char *ifname, int flags)
+{
+  return iface_ChangeFlags(ifname, flags, IFACE_DELFLAGS);
+}
+
+void
+iface_Destroy(struct iface *iface)
+{
+  /*
+   * iface_Clear(iface, IFACE_CLEAR_ALL) must be called manually
+   * if that's what the user wants.  It's better to leave the interface
+   * allocated so that existing connections can continue to work.
+   */
+
+  if (iface != NULL) {
+    free(iface->name);
+    free(iface->addr);
+    free(iface);
+  }
+}
+
+#define if_entry(x) { IFF_##x, #x }
+
+struct {
+  int flag;
+  const char *value;
+} if_flags[] = {
+  if_entry(UP),
+  if_entry(BROADCAST),
+  if_entry(DEBUG),
+  if_entry(LOOPBACK),
+  if_entry(POINTOPOINT),
+  if_entry(RUNNING),
+  if_entry(NOARP),
+  if_entry(PROMISC),
+  if_entry(ALLMULTI),
+  if_entry(OACTIVE),
+  if_entry(SIMPLEX),
+  if_entry(LINK0),
+  if_entry(LINK1),
+  if_entry(LINK2),
+  if_entry(MULTICAST),
+  { 0, "???" }
+};
+
+int
+iface_Show(struct cmdargs const *arg)
+{
+  struct ncpaddr ncpaddr;
+  struct iface *iface = arg->bundle->iface, *current;
+  unsigned f;
+  int flags;
+#ifndef NOINET6
+  int scopeid, width;
+#endif
+  struct in_addr mask;
+
+  current = iface_Create(iface->name);
+  flags = iface->flags = current->flags;
+  iface_Destroy(current);
+
+  prompt_Printf(arg->prompt, "%s (idx %d) <", iface->name, iface->index);
+  for (f = 0; f < sizeof if_flags / sizeof if_flags[0]; f++)
+    if ((if_flags[f].flag & flags)) {
+      prompt_Printf(arg->prompt, "%s%s", flags == iface->flags ? "" : ",",
+                    if_flags[f].value);
+      flags &= ~if_flags[f].flag;
+    }
+
+#if 0
+  if (flags)
+    prompt_Printf(arg->prompt, "%s0x%x", flags == iface->flags ? "" : ",",
+                  flags);
+#endif
+
+  prompt_Printf(arg->prompt, "> mtu %lu has %d address%s:\n", iface->mtu,
+                iface->addrs, iface->addrs == 1 ? "" : "es");
+
+  for (f = 0; f < iface->addrs; f++) {
+    ncprange_getaddr(&iface->addr[f].ifa, &ncpaddr);
+    switch (ncprange_family(&iface->addr[f].ifa)) {
+    case AF_INET:
+      prompt_Printf(arg->prompt, "  inet %s --> ", ncpaddr_ntoa(&ncpaddr));
+      if (ncpaddr_family(&iface->addr[f].peer) == AF_UNSPEC)
+        prompt_Printf(arg->prompt, "255.255.255.255");
+      else
+        prompt_Printf(arg->prompt, "%s", ncpaddr_ntoa(&iface->addr[f].peer));
+      ncprange_getip4mask(&iface->addr[f].ifa, &mask);
+      prompt_Printf(arg->prompt, " netmask 0x%08lx", (long)ntohl(mask.s_addr));
+      break;
+
+#ifndef NOINET6
+    case AF_INET6:
+      prompt_Printf(arg->prompt, "  inet6 %s", ncpaddr_ntoa(&ncpaddr));
+      if (ncpaddr_family(&iface->addr[f].peer) != AF_UNSPEC)
+        prompt_Printf(arg->prompt, " --> %s",
+                      ncpaddr_ntoa(&iface->addr[f].peer));
+      ncprange_getwidth(&iface->addr[f].ifa, &width);
+      if (ncpaddr_family(&iface->addr[f].peer) == AF_UNSPEC)
+        prompt_Printf(arg->prompt, " prefixlen %d", width);
+      if ((scopeid = ncprange_scopeid(&iface->addr[f].ifa)) != -1)
+        prompt_Printf(arg->prompt, " scopeid 0x%x", (unsigned)scopeid);
+      break;
+#endif
+    }
+    prompt_Printf(arg->prompt, "\n");
+  }
+
+  return 0;
+}
+
+void
+iface_ParseHdr(struct ifa_msghdr *ifam, struct sockaddr *sa[RTAX_MAX])
+{
+  char *wp;
+  int rtax;
+
+  wp = (char *)(ifam + 1);
+
+  for (rtax = 0; rtax < RTAX_MAX; rtax++)
+    if (ifam->ifam_addrs & (1 << rtax)) {
+      sa[rtax] = (struct sockaddr *)wp;
+      wp += ROUNDUP(sa[rtax]->sa_len);
+    } else
+      sa[rtax] = NULL;
+}
diff --git a/src/iface.h b/src/iface.h
new file mode 100644
index 0000000..05199e5
--- /dev/null
+++ b/src/iface.h
@@ -0,0 +1,65 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/iface.h,v 1.8.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct ifa_msghdr;
+
+struct iface_addr {
+  unsigned system : 1;		/* System alias ? */
+  struct ncprange ifa;		/* local address/mask */
+  struct ncpaddr peer;		/* peer address */
+};
+
+struct iface {
+  char *name;			/* Interface name (malloc'd) */
+  int index;			/* Interface index */
+  int flags;			/* Interface flags (IFF_*) */
+  unsigned long mtu;		/* struct tuninfo MTU */
+
+  unsigned addrs;		/* How many in_addr's */
+  struct iface_addr *addr;	/* Array of addresses (malloc'd) */
+};
+
+#define IFACE_CLEAR_ALL		0	/* Nuke 'em all */
+#define IFACE_CLEAR_ALIASES	1	/* Leave the NCP address */
+
+#define IFACE_ADD_LAST		0	/* Just another alias */
+#define IFACE_ADD_FIRST		1	/* The IPCP address */
+#define IFACE_FORCE_ADD		2	/* OR'd with IFACE_ADD_{FIRST,LAST} */
+
+#define IFACE_SYSTEM		4	/* Set/clear SYSTEM entries */
+
+extern struct iface *iface_Create(const char *name);
+extern void iface_Clear(struct iface *, struct ncp *, int, int);
+extern int iface_Add(struct iface *, struct ncp *, const struct ncprange *,
+                     const struct ncpaddr *, int);
+extern int iface_Delete(struct iface *, struct ncp *, const struct ncpaddr *);
+extern int iface_Show(struct cmdargs const *);
+extern int iface_SetFlags(const char *, int);
+extern int iface_ClearFlags(const char *, int);
+extern void iface_Destroy(struct iface *);
+extern void iface_ParseHdr(struct ifa_msghdr *, struct sockaddr *[RTAX_MAX]);
diff --git a/src/ip.c b/src/ip.c
new file mode 100644
index 0000000..eab8547
--- /dev/null
+++ b/src/ip.c
@@ -0,0 +1,993 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ip.c,v 1.104.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#ifndef NOINET6
+#include <netinet/icmp6.h>
+#include <netinet/ip6.h>
+#endif
+#include <netinet/ip_icmp.h>
+#include <netinet/udp.h>
+#include <netinet/tcp.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "proto.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "tun.h"
+
+
+#define OPCODE_QUERY	0
+#define OPCODE_IQUERY	1
+#define OPCODE_STATUS	2
+
+struct dns_header {
+  u_short id;
+  unsigned qr : 1;
+  unsigned opcode : 4;
+  unsigned aa : 1;
+  unsigned tc : 1;
+  unsigned rd : 1;
+  unsigned ra : 1;
+  unsigned z : 3;
+  unsigned rcode : 4;
+  u_short qdcount;
+  u_short ancount;
+  u_short nscount;
+  u_short arcount;
+};
+
+static const char *
+dns_Qclass2Txt(u_short qclass)
+{
+  static char failure[6];
+  struct {
+    u_short id;
+    const char *txt;
+  } qtxt[] = {
+    /* rfc1035 */
+    { 1, "IN" }, { 2, "CS" }, { 3, "CH" }, { 4, "HS" }, { 255, "*" }
+  };
+  unsigned f;
+
+  for (f = 0; f < sizeof qtxt / sizeof *qtxt; f++)
+    if (qtxt[f].id == qclass)
+      return qtxt[f].txt;
+
+  return HexStr(qclass, failure, sizeof failure);
+}
+
+static const char *
+dns_Qtype2Txt(u_short qtype)
+{
+  static char failure[6];
+  struct {
+    u_short id;
+    const char *txt;
+  } qtxt[] = {
+    /* rfc1035/rfc1700 */
+    { 1, "A" }, { 2, "NS" }, { 3, "MD" }, { 4, "MF" }, { 5, "CNAME" },
+    { 6, "SOA" }, { 7, "MB" }, { 8, "MG" }, { 9, "MR" }, { 10, "NULL" },
+    { 11, "WKS" }, { 12, "PTR" }, { 13, "HINFO" }, { 14, "MINFO" },
+    { 15, "MX" }, { 16, "TXT" }, { 17, "RP" }, { 18, "AFSDB" },
+    { 19, "X25" }, { 20, "ISDN" }, { 21, "RT" }, { 22, "NSAP" },
+    { 23, "NSAP-PTR" }, { 24, "SIG" }, { 25, "KEY" }, { 26, "PX" },
+    { 27, "GPOS" }, { 28, "AAAA" }, { 252, "AXFR" }, { 253, "MAILB" },
+    { 254, "MAILA" }, { 255, "*" }
+  };
+  unsigned f;
+
+  for (f = 0; f < sizeof qtxt / sizeof *qtxt; f++)
+    if (qtxt[f].id == qtype)
+      return qtxt[f].txt;
+
+  return HexStr(qtype, failure, sizeof failure);
+}
+
+static __inline int
+PortMatch(int op, u_short pport, u_short rport)
+{
+  switch (op) {
+  case OP_EQ:
+    return pport == rport;
+  case OP_GT:
+    return pport > rport;
+  case OP_LT:
+    return pport < rport;
+  default:
+    return 0;
+  }
+}
+
+/*
+ * Return a text string representing the cproto protocol number.
+ *
+ * The purpose of this routine is calculate this result, for
+ * the many times it is needed in FilterCheck, only on demand
+ * (i.e. when the corresponding logging functions are invoked).
+ *
+ * This optimization saves, over the previous implementation, which
+ * calculated prototxt at the beginning of FilterCheck, an
+ * open/read/close system call sequence per packet, approximately
+ * halving the ppp system overhead and reducing the overall (u + s)
+ * time by 38%.
+ *
+ * The caching performed here is just a side effect.
+ */
+static const char *
+prototxt(int cproto)
+{
+  static int oproto = -1;
+  static char protobuff[16] = "-1";
+  struct protoent *pe;
+
+  if (cproto == oproto)
+	return protobuff;
+  if ((pe = getprotobynumber(cproto)) == NULL)
+    snprintf(protobuff, sizeof protobuff, "%d", cproto);
+  else
+    snprintf(protobuff, sizeof protobuff, "%s", pe->p_name);
+  oproto = cproto;
+  return (protobuff);
+}
+
+/*
+ * Check a packet against the given filter
+ * Returns 0 to accept the packet, non-zero to drop the packet.
+ * If psecs is not NULL, populate it with the timeout associated
+ * with the filter rule matched.
+ *
+ * If filtering is enabled, the initial fragment of a datagram must
+ * contain the complete protocol header, and subsequent fragments
+ * must not attempt to over-write it.
+ *
+ * One (and only one) of pip or pip6 must be set.
+ */
+int
+FilterCheck(const unsigned char *packet,
+#ifdef NOINET6
+	    u_int32_t family __unused,
+#else
+	    u_int32_t family,
+#endif
+            const struct filter *filter, unsigned *psecs)
+{
+  int gotinfo;			/* true if IP payload decoded */
+  int cproto;			/* IPPROTO_* protocol number if (gotinfo) */
+  int estab, syn, finrst;	/* TCP state flags if (gotinfo) */
+  u_short sport, dport;		/* src, dest port from packet if (gotinfo) */
+  int n;			/* filter rule to process */
+  int len;			/* bytes used in dbuff */
+  int didname;			/* true if filter header printed */
+  int match;			/* true if condition matched */
+  int mindata;			/* minimum data size or zero */
+  const struct filterent *fp = filter->rule;
+  char dbuff[100], dstip[16];
+  struct ncpaddr srcaddr, dstaddr;
+  const char *payload;		/* IP payload */
+  int datalen;			/* IP datagram length */
+
+  if (fp->f_action == A_NONE)
+    return 0;		/* No rule is given. Permit this packet */
+
+#ifndef NOINET6
+  if (family == AF_INET6) {
+    const struct ip6_hdr *pip6 = (const struct ip6_hdr *)packet;
+
+    ncpaddr_setip6(&srcaddr, &pip6->ip6_src);
+    ncpaddr_setip6(&dstaddr, &pip6->ip6_dst);
+    datalen = ntohs(pip6->ip6_plen);
+    payload = packet + sizeof *pip6;
+    cproto = pip6->ip6_nxt;
+  } else
+#endif
+  {
+    /*
+     * Deny any packet fragment that tries to over-write the header.
+     * Since we no longer have the real header available, punt on the
+     * largest normal header - 20 bytes for TCP without options, rounded
+     * up to the next possible fragment boundary.  Since the smallest
+     * `legal' MTU is 576, and the smallest recommended MTU is 296, any
+     * fragmentation within this range is dubious at best
+     */
+    const struct ip *pip = (const struct ip *)packet;
+
+    len = ntohs(pip->ip_off) & IP_OFFMASK;	/* fragment offset */
+    if (len > 0) {		/* Not first fragment within datagram */
+      if (len < (24 >> 3)) {	/* don't allow fragment to over-write header */
+        log_Printf(LogFILTER, " error: illegal header\n");
+        return 1;
+      }
+      /* permit fragments on in and out filter */
+      if (!filter->fragok) {
+        log_Printf(LogFILTER, " error: illegal fragmentation\n");
+        return 1;
+      } else
+        return 0;
+    }
+
+    ncpaddr_setip4(&srcaddr, pip->ip_src);
+    ncpaddr_setip4(&dstaddr, pip->ip_dst);
+    datalen = ntohs(pip->ip_len) - (pip->ip_hl << 2);
+    payload = packet + (pip->ip_hl << 2);
+    cproto = pip->ip_p;
+  }
+
+
+  gotinfo = estab = syn = finrst = didname = 0;
+  sport = dport = 0;
+
+  for (n = 0; n < MAXFILTERS; ) {
+    if (fp->f_action == A_NONE) {
+      n++;
+      fp++;
+      continue;
+    }
+
+    if (!didname) {
+      log_Printf(LogDEBUG, "%s filter:\n", filter->name);
+      didname = 1;
+    }
+
+    match = 0;
+
+    if ((ncprange_family(&fp->f_src) == AF_UNSPEC ||
+         ncprange_contains(&fp->f_src, &srcaddr)) &&
+        (ncprange_family(&fp->f_dst) == AF_UNSPEC ||
+         ncprange_contains(&fp->f_dst, &dstaddr))) {
+      if (fp->f_proto != 0) {
+        if (!gotinfo) {
+          const struct tcphdr *th;
+          const struct udphdr *uh;
+          const struct icmp *ih;
+#ifndef NOINET6
+          const struct icmp6_hdr *ih6;
+#endif
+          mindata = 0;
+          sport = dport = 0;
+          estab = syn = finrst = -1;
+
+          switch (cproto) {
+          case IPPROTO_ICMP:
+            mindata = 8;	/* ICMP must be at least 8 octets */
+            ih = (const struct icmp *)payload;
+            sport = ih->icmp_type;
+            if (log_IsKept(LogDEBUG))
+              snprintf(dbuff, sizeof dbuff, "sport = %d", sport);
+            break;
+
+#ifndef NOINET6
+          case IPPROTO_ICMPV6:
+            mindata = 8;	/* ICMP must be at least 8 octets */
+            ih6 = (const struct icmp6_hdr *)payload;
+            sport = ih6->icmp6_type;
+            if (log_IsKept(LogDEBUG))
+              snprintf(dbuff, sizeof dbuff, "sport = %d", sport);
+            break;
+#endif
+
+          case IPPROTO_IGMP:
+            mindata = 8;	/* IGMP uses 8-octet messages */
+            break;
+
+#ifdef IPPROTO_GRE
+          case IPPROTO_GRE:
+            mindata = 2;	/* GRE uses 2-octet+ messages */
+            break;
+#endif
+#ifdef IPPROTO_OSPFIGP
+          case IPPROTO_OSPFIGP:
+            mindata = 8;	/* IGMP uses 8-octet messages */
+            break;
+#endif
+#ifndef NOINET6
+          case IPPROTO_IPV6:
+            mindata = 20;	/* RFC2893 Section 3.5: 5 * 32bit words */
+            break;
+#endif
+
+          case IPPROTO_UDP:
+            mindata = 8;	/* UDP header is 8 octets */
+            uh = (const struct udphdr *)payload;
+            sport = ntohs(uh->uh_sport);
+            dport = ntohs(uh->uh_dport);
+            if (log_IsKept(LogDEBUG))
+              snprintf(dbuff, sizeof dbuff, "sport = %d, dport = %d",
+                       sport, dport);
+            break;
+
+          case IPPROTO_TCP:
+            th = (const struct tcphdr *)payload;
+            /*
+             * TCP headers are variable length.  The following code
+             * ensures that the TCP header length isn't de-referenced if
+             * the datagram is too short
+             */
+            if (datalen < 20 || datalen < (th->th_off << 2)) {
+              log_Printf(LogFILTER, " error: TCP header incorrect\n");
+              return 1;
+            }
+            sport = ntohs(th->th_sport);
+            dport = ntohs(th->th_dport);
+            estab = (th->th_flags & TH_ACK);
+            syn = (th->th_flags & TH_SYN);
+            finrst = (th->th_flags & (TH_FIN|TH_RST));
+            if (log_IsKept(LogDEBUG)) {
+              if (!estab)
+                snprintf(dbuff, sizeof dbuff,
+                         "flags = %02x, sport = %d, dport = %d",
+                         th->th_flags, sport, dport);
+              else
+                *dbuff = '\0';
+            }
+            break;
+          default:
+            break;
+          }
+
+          if (datalen < mindata) {
+            log_Printf(LogFILTER, " error: proto %s must be at least"
+                       " %d octets\n", prototxt(cproto), mindata);
+            return 1;
+          }
+
+          if (log_IsKept(LogDEBUG)) {
+            if (estab != -1) {
+              len = strlen(dbuff);
+              snprintf(dbuff + len, sizeof dbuff - len,
+                       ", estab = %d, syn = %d, finrst = %d",
+                       estab, syn, finrst);
+            }
+            log_Printf(LogDEBUG, " Filter: proto = %s, %s\n",
+                       prototxt(cproto), dbuff);
+          }
+          gotinfo = 1;
+        }
+
+        if (log_IsKept(LogDEBUG)) {
+          if (fp->f_srcop != OP_NONE) {
+            snprintf(dbuff, sizeof dbuff, ", src %s %d",
+                     filter_Op2Nam(fp->f_srcop), fp->f_srcport);
+            len = strlen(dbuff);
+          } else
+            len = 0;
+          if (fp->f_dstop != OP_NONE) {
+            snprintf(dbuff + len, sizeof dbuff - len,
+                     ", dst %s %d", filter_Op2Nam(fp->f_dstop),
+                     fp->f_dstport);
+          } else if (!len)
+            *dbuff = '\0';
+
+          log_Printf(LogDEBUG, "  rule = %d: Address match, "
+                     "check against proto %d%s, action = %s\n",
+                     n, fp->f_proto, dbuff, filter_Action2Nam(fp->f_action));
+        }
+
+        if (cproto == fp->f_proto) {
+          if ((fp->f_srcop == OP_NONE ||
+               PortMatch(fp->f_srcop, sport, fp->f_srcport)) &&
+              (fp->f_dstop == OP_NONE ||
+               PortMatch(fp->f_dstop, dport, fp->f_dstport)) &&
+              (fp->f_estab == 0 || estab) &&
+              (fp->f_syn == 0 || syn) &&
+              (fp->f_finrst == 0 || finrst)) {
+            match = 1;
+          }
+        }
+      } else {
+        /* Address is matched and no protocol specified. Make a decision. */
+        log_Printf(LogDEBUG, "  rule = %d: Address match, action = %s\n", n,
+                   filter_Action2Nam(fp->f_action));
+        match = 1;
+      }
+    } else
+      log_Printf(LogDEBUG, "  rule = %d: Address mismatch\n", n);
+
+    if (match != fp->f_invert) {
+      /* Take specified action */
+      if (fp->f_action < A_NONE)
+        fp = &filter->rule[n = fp->f_action];
+      else {
+        if (fp->f_action == A_PERMIT) {
+          if (psecs != NULL)
+            *psecs = fp->timeout;
+          if (strcmp(filter->name, "DIAL") == 0) {
+            /* If dial filter then even print out accept packets */
+            if (log_IsKept(LogFILTER)) {
+              snprintf(dstip, sizeof dstip, "%s", ncpaddr_ntoa(&dstaddr));
+              log_Printf(LogFILTER, "%sbound rule = %d accept %s "
+                         "src = %s:%d dst = %s:%d\n", filter->name, n,
+                         prototxt(cproto), ncpaddr_ntoa(&srcaddr), sport,
+                         dstip, dport);
+            }
+          }
+          return 0;
+        } else {
+          if (log_IsKept(LogFILTER)) {
+            snprintf(dstip, sizeof dstip, "%s", ncpaddr_ntoa(&dstaddr));
+            log_Printf(LogFILTER,
+                       "%sbound rule = %d deny %s src = %s/%d dst = %s/%d\n",
+                       filter->name, n, prototxt(cproto),
+                       ncpaddr_ntoa(&srcaddr), sport, dstip, dport);
+          }
+          return 1;
+        }		/* Explict match.  Deny this packet */
+      }
+    } else {
+      n++;
+      fp++;
+    }
+  }
+
+  if (log_IsKept(LogFILTER)) {
+    snprintf(dstip, sizeof dstip, "%s", ncpaddr_ntoa(&dstaddr));
+    log_Printf(LogFILTER,
+               "%sbound rule = implicit deny %s src = %s/%d dst = %s/%d\n",
+               filter->name, prototxt(cproto), ncpaddr_ntoa(&srcaddr),
+               sport, dstip, dport);
+  }
+
+  return 1;		/* No rule matched, deny this packet */
+}
+
+static void
+ip_LogDNS(const struct udphdr *uh, const char *direction)
+{
+  struct dns_header header;
+  const u_short *pktptr;
+  const u_char *ptr;
+  u_short *hptr, tmp;
+  unsigned len;
+
+  ptr = (const char *)uh + sizeof *uh;
+  len = ntohs(uh->uh_ulen) - sizeof *uh;
+  if (len < sizeof header + 5)		/* rfc1024 */
+    return;
+
+  pktptr = (const u_short *)ptr;
+  hptr = (u_short *)&header;
+  ptr += sizeof header;
+  len -= sizeof header;
+
+  while (pktptr < (const u_short *)ptr) {
+    *hptr++ = ntohs(*pktptr);		/* Careful of macro side-effects ! */
+    pktptr++;
+  }
+
+  if (header.opcode == OPCODE_QUERY && header.qr == 0) {
+    /* rfc1035 */
+    char namewithdot[MAXHOSTNAMELEN + 1], *n;
+    const char *qtype, *qclass;
+    const u_char *end;
+
+    n = namewithdot;
+    end = ptr + len - 4;
+    if (end - ptr >= (int)sizeof namewithdot)
+      end = ptr + sizeof namewithdot - 1;
+    while (ptr < end) {
+      len = *ptr++;
+      if ((int)len > end - ptr)
+        len = end - ptr;
+      if (n != namewithdot)
+        *n++ = '.';
+      memcpy(n, ptr, len);
+      ptr += len;
+      n += len;
+    }
+    *n = '\0';
+
+    if (log_IsKept(LogDNS)) {
+      memcpy(&tmp, end, sizeof tmp);
+      qtype = dns_Qtype2Txt(ntohs(tmp));
+      memcpy(&tmp, end + 2, sizeof tmp);
+      qclass = dns_Qclass2Txt(ntohs(tmp));
+
+      log_Printf(LogDNS, "%sbound query %s %s %s\n",
+                 direction, qclass, qtype, namewithdot);
+    }
+  }
+}
+
+/*
+ * Check if the given packet matches the given filter.
+ * One of pip or pip6 must be set.
+ */
+int
+PacketCheck(struct bundle *bundle, u_int32_t family,
+            const unsigned char *packet, int nb, struct filter *filter,
+            const char *prefix, unsigned *psecs)
+{
+  char logbuf[200];
+  static const char *const TcpFlags[] = {
+    "FIN", "SYN", "RST", "PSH", "ACK", "URG"
+  };
+  const struct tcphdr *th;
+  const struct udphdr *uh;
+  const struct icmp *icmph;
+#ifndef NOINET6
+  const struct icmp6_hdr *icmp6h;
+#endif
+  const unsigned char *payload;
+  struct ncpaddr srcaddr, dstaddr;
+  int cproto, mask, len, n, pri, logit, result, datalen, frag;
+  unsigned loglen;
+  u_char tos;
+
+  logit = (log_IsKept(LogTCPIP) || log_IsKept(LogDNS)) &&
+          (!filter || filter->logok);
+  loglen = 0;
+  pri = 0;
+
+#ifndef NOINET6
+  if (family == AF_INET6) {
+    const struct ip6_hdr *pip6 = (const struct ip6_hdr *)packet;
+
+    ncpaddr_setip6(&srcaddr, &pip6->ip6_src);
+    ncpaddr_setip6(&dstaddr, &pip6->ip6_dst);
+    datalen = ntohs(pip6->ip6_plen);
+    payload = packet + sizeof *pip6;
+    cproto = pip6->ip6_nxt;
+    tos = 0;					/* XXX: pip6->ip6_vfc >> 4 ? */
+    frag = 0;					/* XXX: ??? */
+  } else
+#endif
+  {
+    const struct ip *pip = (const struct ip *)packet;
+
+    ncpaddr_setip4(&srcaddr, pip->ip_src);
+    ncpaddr_setip4(&dstaddr, pip->ip_dst);
+    datalen = ntohs(pip->ip_len) - (pip->ip_hl << 2);
+    payload = packet + (pip->ip_hl << 2);
+    cproto = pip->ip_p;
+    tos = pip->ip_tos;
+    frag = ntohs(pip->ip_off) & IP_OFFMASK;
+  }
+
+  uh = NULL;
+
+  if (logit && loglen < sizeof logbuf) {
+    if (prefix)
+      snprintf(logbuf + loglen, sizeof logbuf - loglen, "%s", prefix);
+    else if (filter)
+      snprintf(logbuf + loglen, sizeof logbuf - loglen, "%s ", filter->name);
+    else
+      snprintf(logbuf + loglen, sizeof logbuf - loglen, "  ");
+    loglen += strlen(logbuf + loglen);
+  }
+
+  switch (cproto) {
+  case IPPROTO_ICMP:
+    if (logit && loglen < sizeof logbuf) {
+      len = datalen - sizeof *icmph;
+      icmph = (const struct icmp *)payload;
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "ICMP: %s:%d ---> ", ncpaddr_ntoa(&srcaddr), icmph->icmp_type);
+      loglen += strlen(logbuf + loglen);
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "%s (%d/%d)", ncpaddr_ntoa(&dstaddr), len, nb);
+      loglen += strlen(logbuf + loglen);
+    }
+    break;
+
+#ifndef NOINET6
+  case IPPROTO_ICMPV6:
+    if (logit && loglen < sizeof logbuf) {
+      len = datalen - sizeof *icmp6h;
+      icmp6h = (const struct icmp6_hdr *)payload;
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "ICMP: %s:%d ---> ", ncpaddr_ntoa(&srcaddr), icmp6h->icmp6_type);
+      loglen += strlen(logbuf + loglen);
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "%s (%d/%d)", ncpaddr_ntoa(&dstaddr), len, nb);
+      loglen += strlen(logbuf + loglen);
+    }
+    break;
+#endif
+
+  case IPPROTO_UDP:
+    uh = (const struct udphdr *)payload;
+    if (tos == IPTOS_LOWDELAY && bundle->ncp.cfg.urgent.tos)
+      pri++;
+
+    if (!frag && ncp_IsUrgentUdpPort(&bundle->ncp, ntohs(uh->uh_sport),
+                                     ntohs(uh->uh_dport)))
+      pri++;
+
+    if (logit && loglen < sizeof logbuf) {
+      len = datalen - sizeof *uh;
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "UDP: %s:%d ---> ", ncpaddr_ntoa(&srcaddr), ntohs(uh->uh_sport));
+      loglen += strlen(logbuf + loglen);
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "%s:%d (%d/%d)", ncpaddr_ntoa(&dstaddr), ntohs(uh->uh_dport),
+               len, nb);
+      loglen += strlen(logbuf + loglen);
+    }
+
+    if (Enabled(bundle, OPT_FILTERDECAP) &&
+        payload[sizeof *uh] == HDLC_ADDR &&
+        payload[sizeof *uh + 1] == HDLC_UI) {
+      u_short proto;
+      const char *type;
+
+      memcpy(&proto, payload + sizeof *uh + 2, sizeof proto);
+      type = NULL;
+
+      switch (ntohs(proto)) {
+        case PROTO_IP:
+          snprintf(logbuf + loglen, sizeof logbuf - loglen, " contains ");
+          result = PacketCheck(bundle, AF_INET, payload + sizeof *uh + 4,
+                               nb - (payload - packet) - sizeof *uh - 4, filter,
+                               logbuf, psecs);
+          if (result != -2)
+              return result;
+          type = "IP";
+          break;
+
+        case PROTO_VJUNCOMP: type = "compressed VJ";   break;
+        case PROTO_VJCOMP:   type = "uncompressed VJ"; break;
+        case PROTO_MP:       type = "Multi-link"; break;
+        case PROTO_ICOMPD:   type = "Individual link CCP"; break;
+        case PROTO_COMPD:    type = "CCP"; break;
+        case PROTO_IPCP:     type = "IPCP"; break;
+        case PROTO_LCP:      type = "LCP"; break;
+        case PROTO_PAP:      type = "PAP"; break;
+        case PROTO_CBCP:     type = "CBCP"; break;
+        case PROTO_LQR:      type = "LQR"; break;
+        case PROTO_CHAP:     type = "CHAP"; break;
+      }
+      if (type) {
+        snprintf(logbuf + loglen, sizeof logbuf - loglen,
+                 " - %s data", type);
+        loglen += strlen(logbuf + loglen);
+      }
+    }
+
+    break;
+
+#ifdef IPPROTO_GRE
+  case IPPROTO_GRE:
+    if (logit && loglen < sizeof logbuf) {
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+          "GRE: %s ---> ", ncpaddr_ntoa(&srcaddr));
+      loglen += strlen(logbuf + loglen);
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+              "%s (%d/%d)", ncpaddr_ntoa(&dstaddr), datalen, nb);
+      loglen += strlen(logbuf + loglen);
+    }
+    break;
+#endif
+
+#ifdef IPPROTO_OSPFIGP
+  case IPPROTO_OSPFIGP:
+    if (logit && loglen < sizeof logbuf) {
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "OSPF: %s ---> ", ncpaddr_ntoa(&srcaddr));
+      loglen += strlen(logbuf + loglen);
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "%s (%d/%d)", ncpaddr_ntoa(&dstaddr), datalen, nb);
+      loglen += strlen(logbuf + loglen);
+    }
+    break;
+#endif
+
+#ifndef NOINET6
+  case IPPROTO_IPV6:
+    if (logit && loglen < sizeof logbuf) {
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "IPv6: %s ---> ", ncpaddr_ntoa(&srcaddr));
+      loglen += strlen(logbuf + loglen);
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "%s (%d/%d)", ncpaddr_ntoa(&dstaddr), datalen, nb);
+      loglen += strlen(logbuf + loglen);
+    }
+
+    if (Enabled(bundle, OPT_FILTERDECAP)) {
+      snprintf(logbuf + loglen, sizeof logbuf - loglen, " contains ");
+      result = PacketCheck(bundle, AF_INET6, payload, nb - (payload - packet),
+                           filter, logbuf, psecs);
+      if (result != -2)
+        return result;
+    }
+    break;
+#endif
+
+  case IPPROTO_IPIP:
+    if (logit && loglen < sizeof logbuf) {
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "IPIP: %s ---> ", ncpaddr_ntoa(&srcaddr));
+      loglen += strlen(logbuf + loglen);
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "%s", ncpaddr_ntoa(&dstaddr));
+      loglen += strlen(logbuf + loglen);
+    }
+
+    if (Enabled(bundle, OPT_FILTERDECAP) &&
+        ((const struct ip *)payload)->ip_v == 4) {
+      snprintf(logbuf + loglen, sizeof logbuf - loglen, " contains ");
+      result = PacketCheck(bundle, AF_INET, payload, nb - (payload - packet),
+                           filter, logbuf, psecs);
+      loglen += strlen(logbuf + loglen);
+      if (result != -2)
+        return result;
+    }
+    break;
+
+  case IPPROTO_ESP:
+    if (logit && loglen < sizeof logbuf) {
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "ESP: %s ---> ", ncpaddr_ntoa(&srcaddr));
+      loglen += strlen(logbuf + loglen);
+      snprintf(logbuf + loglen, sizeof logbuf - loglen, "%s, spi %p",
+               ncpaddr_ntoa(&dstaddr), payload);
+      loglen += strlen(logbuf + loglen);
+    }
+    break;
+
+  case IPPROTO_AH:
+    if (logit && loglen < sizeof logbuf) {
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "AH: %s ---> ", ncpaddr_ntoa(&srcaddr));
+      loglen += strlen(logbuf + loglen);
+      snprintf(logbuf + loglen, sizeof logbuf - loglen, "%s, spi %p",
+               ncpaddr_ntoa(&dstaddr), payload + sizeof(u_int32_t));
+      loglen += strlen(logbuf + loglen);
+    }
+    break;
+
+  case IPPROTO_IGMP:
+    if (logit && loglen < sizeof logbuf) {
+      uh = (const struct udphdr *)payload;
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "IGMP: %s:%d ---> ", ncpaddr_ntoa(&srcaddr),
+               ntohs(uh->uh_sport));
+      loglen += strlen(logbuf + loglen);
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "%s:%d", ncpaddr_ntoa(&dstaddr), ntohs(uh->uh_dport));
+      loglen += strlen(logbuf + loglen);
+    }
+    break;
+
+  case IPPROTO_TCP:
+    th = (const struct tcphdr *)payload;
+    if (tos == IPTOS_LOWDELAY && bundle->ncp.cfg.urgent.tos)
+      pri++;
+
+    if (!frag && ncp_IsUrgentTcpPort(&bundle->ncp, ntohs(th->th_sport),
+                                     ntohs(th->th_dport)))
+      pri++;
+
+    if (logit && loglen < sizeof logbuf) {
+      len = datalen - (th->th_off << 2);
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+           "TCP: %s:%d ---> ", ncpaddr_ntoa(&srcaddr), ntohs(th->th_sport));
+      loglen += strlen(logbuf + loglen);
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "%s:%d", ncpaddr_ntoa(&dstaddr), ntohs(th->th_dport));
+      loglen += strlen(logbuf + loglen);
+      n = 0;
+      for (mask = TH_FIN; mask != 0x40; mask <<= 1) {
+        if (th->th_flags & mask) {
+          snprintf(logbuf + loglen, sizeof logbuf - loglen, " %s", TcpFlags[n]);
+          loglen += strlen(logbuf + loglen);
+        }
+        n++;
+      }
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "  seq:%lx  ack:%lx (%d/%d)",
+               (u_long)ntohl(th->th_seq), (u_long)ntohl(th->th_ack), len, nb);
+      loglen += strlen(logbuf + loglen);
+      if ((th->th_flags & TH_SYN) && nb > 40) {
+        const u_short *sp;
+
+        sp = (const u_short *)(payload + 20);
+        if (ntohs(sp[0]) == 0x0204) {
+          snprintf(logbuf + loglen, sizeof logbuf - loglen,
+                   " MSS = %d", ntohs(sp[1]));
+          loglen += strlen(logbuf + loglen);
+        }
+      }
+    }
+    break;
+
+  default:
+    if (prefix)
+      return -2;
+
+    if (logit && loglen < sizeof logbuf) {
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "<%d>: %s ---> ", cproto, ncpaddr_ntoa(&srcaddr));
+      loglen += strlen(logbuf + loglen);
+      snprintf(logbuf + loglen, sizeof logbuf - loglen,
+               "%s (%d)", ncpaddr_ntoa(&dstaddr), nb);
+      loglen += strlen(logbuf + loglen);
+    }
+    break;
+  }
+
+  if (filter && FilterCheck(packet, family, filter, psecs)) {
+    if (logit)
+      log_Printf(LogTCPIP, "%s - BLOCKED\n", logbuf);
+    result = -1;
+  } else {
+    /* Check Keep Alive filter */
+    if (logit && log_IsKept(LogTCPIP)) {
+      unsigned alivesecs;
+
+      alivesecs = 0;
+      if (filter &&
+          FilterCheck(packet, family, &bundle->filter.alive, &alivesecs))
+        log_Printf(LogTCPIP, "%s - NO KEEPALIVE\n", logbuf);
+      else if (psecs != NULL) {
+        if(*psecs == 0)
+          *psecs = alivesecs;
+        if (*psecs) {
+          if (*psecs != alivesecs)
+            log_Printf(LogTCPIP, "%s - (timeout = %d / ALIVE = %d secs)\n",
+                       logbuf, *psecs, alivesecs);
+          else
+            log_Printf(LogTCPIP, "%s - (timeout = %d secs)\n", logbuf, *psecs);
+        } else
+          log_Printf(LogTCPIP, "%s\n", logbuf);
+      }
+    }
+    result = pri;
+  }
+
+  if (filter && uh && ntohs(uh->uh_dport) == 53 && log_IsKept(LogDNS))
+    ip_LogDNS(uh, filter->name);
+
+  return result;
+}
+
+static size_t
+ip_Input(struct bundle *bundle, struct link *l, struct mbuf *bp, u_int32_t af)
+{
+  ssize_t nw;
+  size_t nb;
+  struct tun_data tun;
+  char *data;
+  unsigned secs, alivesecs;
+
+  nb = m_length(bp);
+  if (nb > sizeof tun.data) {
+    log_Printf(LogWARN, "ip_Input: %s: Packet too large (got %zd, max %d)\n",
+               l->name, nb, (int)(sizeof tun.data));
+    m_freem(bp);
+    return 0;
+  }
+  mbuf_Read(bp, tun.data, nb);
+
+  secs = 0;
+  if (PacketCheck(bundle, af, tun.data, nb, &bundle->filter.in,
+                  NULL, &secs) < 0)
+    return 0;
+
+  alivesecs = 0;
+  if (!FilterCheck(tun.data, af, &bundle->filter.alive, &alivesecs)) {
+    if (secs == 0)
+      secs = alivesecs;
+    bundle_StartIdleTimer(bundle, secs);
+  }
+
+  if (bundle->dev.header) {
+    tun.header.family = htonl(af);
+    nb += sizeof tun - sizeof tun.data;
+    data = (char *)&tun;
+  } else
+    data = tun.data;
+
+  nw = write(bundle->dev.fd, data, nb);
+  if (nw != (ssize_t)nb) {
+    if (nw == -1)
+      log_Printf(LogERROR, "ip_Input: %s: wrote %zd, got %s\n",
+                 l->name, nb, strerror(errno));
+    else
+      log_Printf(LogERROR, "ip_Input: %s: wrote %zd, got %zd\n", l->name, nb,
+	  nw);
+  }
+
+  return nb;
+}
+
+struct mbuf *
+ipv4_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+  int nb;
+
+  if (bundle->ncp.ipcp.fsm.state != ST_OPENED) {
+    log_Printf(LogWARN, "ipv4_Input: IPCP not open - packet dropped\n");
+    m_freem(bp);
+    return NULL;
+  }
+
+  m_settype(bp, MB_IPIN);
+
+  nb = ip_Input(bundle, l, bp, AF_INET);
+  ipcp_AddInOctets(&bundle->ncp.ipcp, nb);
+
+  return NULL;
+}
+
+#ifndef NOINET6
+struct mbuf *
+ipv6_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+  int nb;
+
+  if (bundle->ncp.ipv6cp.fsm.state != ST_OPENED) {
+    log_Printf(LogWARN, "ipv6_Input: IPV6CP not open - packet dropped\n");
+    m_freem(bp);
+    return NULL;
+  }
+
+  m_settype(bp, MB_IPV6IN);
+
+  nb = ip_Input(bundle, l, bp, AF_INET6);
+  ipv6cp_AddInOctets(&bundle->ncp.ipv6cp, nb);
+
+  return NULL;
+}
+#endif
diff --git a/src/ip.h b/src/ip.h
new file mode 100644
index 0000000..3d98c8c
--- /dev/null
+++ b/src/ip.h
@@ -0,0 +1,44 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ip.h,v 1.18.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct mbuf;
+struct filter;
+struct link;
+struct bundle;
+
+extern int ip_PushPacket(struct link *, struct bundle *);
+extern int PacketCheck(struct bundle *, u_int32_t, const unsigned char *, int,
+                          struct filter *, const char *, unsigned *secs);
+extern int FilterCheck(const unsigned char *, u_int32_t, const struct filter *,
+                       unsigned *);
+extern struct mbuf *ipv4_Input(struct bundle *, struct link *, struct mbuf *);
+#ifndef NOINET6
+extern struct mbuf *ipv6_Input(struct bundle *, struct link *, struct mbuf *);
+#endif
diff --git a/src/ipcp.c b/src/ipcp.c
new file mode 100644
index 0000000..0464a6d
--- /dev/null
+++ b/src/ipcp.c
@@ -0,0 +1,1477 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ipcp.c,v 1.123.24.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <netdb.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <resolv.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+
+#ifndef NONAT
+#ifdef LOCALNAT
+#include "alias.h"
+#else
+#include <alias.h>
+#endif
+#endif
+
+#include "layer.h"
+#include "ua.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "proto.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "vjcomp.h"
+#include "async.h"
+#include "ccp.h"
+#include "link.h"
+#include "physical.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "id.h"
+#include "arp.h"
+#include "systems.h"
+#include "prompt.h"
+#include "route.h"
+#include "iface.h"
+
+#undef REJECTED
+#define	REJECTED(p, x)	((p)->peer_reject & (1<<(x)))
+#define issep(ch) ((ch) == ' ' || (ch) == '\t')
+#define isip(ch) (((ch) >= '0' && (ch) <= '9') || (ch) == '.')
+
+struct compreq {
+  u_short proto;
+  u_char slots;
+  u_char compcid;
+};
+
+static int IpcpLayerUp(struct fsm *);
+static void IpcpLayerDown(struct fsm *);
+static void IpcpLayerStart(struct fsm *);
+static void IpcpLayerFinish(struct fsm *);
+static void IpcpInitRestartCounter(struct fsm *, int);
+static void IpcpSendConfigReq(struct fsm *);
+static void IpcpSentTerminateReq(struct fsm *);
+static void IpcpSendTerminateAck(struct fsm *, u_char);
+static void IpcpDecodeConfig(struct fsm *, u_char *, u_char *, int,
+                             struct fsm_decode *);
+
+static struct fsm_callbacks ipcp_Callbacks = {
+  IpcpLayerUp,
+  IpcpLayerDown,
+  IpcpLayerStart,
+  IpcpLayerFinish,
+  IpcpInitRestartCounter,
+  IpcpSendConfigReq,
+  IpcpSentTerminateReq,
+  IpcpSendTerminateAck,
+  IpcpDecodeConfig,
+  fsm_NullRecvResetReq,
+  fsm_NullRecvResetAck
+};
+
+static const char *
+protoname(int proto)
+{
+  static struct {
+    int id;
+    const char *txt;
+  } cftypes[] = {
+    /* Check out the latest ``Assigned numbers'' rfc (rfc1700.txt) */
+    { 1, "IPADDRS" },		/* IP-Addresses */	/* deprecated */
+    { 2, "COMPPROTO" },		/* IP-Compression-Protocol */
+    { 3, "IPADDR" },		/* IP-Address */
+    { 129, "PRIDNS" },		/* 129: Primary DNS Server Address */
+    { 130, "PRINBNS" },		/* 130: Primary NBNS Server Address */
+    { 131, "SECDNS" },		/* 131: Secondary DNS Server Address */
+    { 132, "SECNBNS" }		/* 132: Secondary NBNS Server Address */
+  };
+  unsigned f;
+
+  for (f = 0; f < sizeof cftypes / sizeof *cftypes; f++)
+    if (cftypes[f].id == proto)
+      return cftypes[f].txt;
+
+  return NumStr(proto, NULL, 0);
+}
+
+void
+ipcp_AddInOctets(struct ipcp *ipcp, int n)
+{
+  throughput_addin(&ipcp->throughput, n);
+}
+
+void
+ipcp_AddOutOctets(struct ipcp *ipcp, int n)
+{
+  throughput_addout(&ipcp->throughput, n);
+}
+
+void
+ipcp_LoadDNS(struct ipcp *ipcp)
+{
+  int fd;
+
+  ipcp->ns.dns[0].s_addr = ipcp->ns.dns[1].s_addr = INADDR_NONE;
+
+  if (ipcp->ns.resolv != NULL) {
+    free(ipcp->ns.resolv);
+    ipcp->ns.resolv = NULL;
+  }
+  if (ipcp->ns.resolv_nons != NULL) {
+    free(ipcp->ns.resolv_nons);
+    ipcp->ns.resolv_nons = NULL;
+  }
+  ipcp->ns.resolver = 0;
+
+  if ((fd = open(_PATH_RESCONF, O_RDONLY)) != -1) {
+    struct stat st;
+
+    if (fstat(fd, &st) == 0) {
+      ssize_t got;
+
+      /*
+       * Note, ns.resolv and ns.resolv_nons are assumed to always point to
+       * buffers of the same size!  See the strcpy() below.
+       */
+      if ((ipcp->ns.resolv_nons = (char *)malloc(st.st_size + 1)) == NULL)
+        log_Printf(LogERROR, "Failed to malloc %lu for %s: %s\n",
+                   (unsigned long)st.st_size, _PATH_RESCONF, strerror(errno));
+      else if ((ipcp->ns.resolv = (char *)malloc(st.st_size + 1)) == NULL) {
+        log_Printf(LogERROR, "Failed(2) to malloc %lu for %s: %s\n",
+                   (unsigned long)st.st_size, _PATH_RESCONF, strerror(errno));
+        free(ipcp->ns.resolv_nons);
+        ipcp->ns.resolv_nons = NULL;
+      } else if ((got = read(fd, ipcp->ns.resolv, st.st_size)) != st.st_size) {
+        if (got == -1)
+          log_Printf(LogERROR, "Failed to read %s: %s\n",
+                     _PATH_RESCONF, strerror(errno));
+        else
+          log_Printf(LogERROR, "Failed to read %s, got %lu not %lu\n",
+                     _PATH_RESCONF, (unsigned long)got,
+                     (unsigned long)st.st_size);
+        free(ipcp->ns.resolv_nons);
+        ipcp->ns.resolv_nons = NULL;
+        free(ipcp->ns.resolv);
+        ipcp->ns.resolv = NULL;
+      } else {
+        char *cp, *cp_nons, *ncp, ch;
+        int n;
+
+        ipcp->ns.resolv[st.st_size] = '\0';
+        ipcp->ns.resolver = 1;
+
+        cp_nons = ipcp->ns.resolv_nons;
+        cp = ipcp->ns.resolv;
+        n = 0;
+
+        while ((ncp = strstr(cp, "nameserver")) != NULL) {
+          if (ncp != cp) {
+            memcpy(cp_nons, cp, ncp - cp);
+            cp_nons += ncp - cp;
+          }
+          if ((ncp != cp && ncp[-1] != '\n') || !issep(ncp[10])) {
+            memcpy(cp_nons, ncp, 9);
+            cp_nons += 9;
+            cp = ncp + 9;	/* Can't match "nameserver" at cp... */
+            continue;
+          }
+
+          for (cp = ncp + 11; issep(*cp); cp++)	/* Skip whitespace */
+            ;
+
+          for (ncp = cp; isip(*ncp); ncp++)		/* Jump over IP */
+            ;
+
+          ch = *ncp;
+          *ncp = '\0';
+          if (n < 2 && inet_aton(cp, ipcp->ns.dns))
+            n++;
+          *ncp = ch;
+
+          if ((cp = strchr(ncp, '\n')) == NULL)	/* Point at next line */
+            cp = ncp + strlen(ncp);
+          else
+            cp++;
+        }
+        /*
+         * Note, cp_nons and cp always point to buffers of the same size, so
+         * strcpy is ok!
+         */
+        strcpy(cp_nons, cp);	/* Copy the end - including the NUL */
+        cp_nons += strlen(cp_nons) - 1;
+        while (cp_nons >= ipcp->ns.resolv_nons && *cp_nons == '\n')
+          *cp_nons-- = '\0';
+        if (n == 2 && ipcp->ns.dns[0].s_addr == INADDR_ANY) {
+          ipcp->ns.dns[0].s_addr = ipcp->ns.dns[1].s_addr;
+          ipcp->ns.dns[1].s_addr = INADDR_ANY;
+        }
+        bundle_AdjustDNS(ipcp->fsm.bundle);
+      }
+    } else
+      log_Printf(LogERROR, "Failed to stat opened %s: %s\n",
+                 _PATH_RESCONF, strerror(errno));
+
+    close(fd);
+  }
+}
+
+int
+ipcp_WriteDNS(struct ipcp *ipcp)
+{
+  const char *paddr;
+  mode_t mask;
+  FILE *fp;
+
+  if (ipcp->ns.dns[0].s_addr == INADDR_ANY &&
+      ipcp->ns.dns[1].s_addr == INADDR_ANY) {
+    log_Printf(LogIPCP, "%s not modified: All nameservers NAKd\n",
+              _PATH_RESCONF);
+    return 0;
+  }
+
+  if (ipcp->ns.dns[0].s_addr == INADDR_ANY) {
+    ipcp->ns.dns[0].s_addr = ipcp->ns.dns[1].s_addr;
+    ipcp->ns.dns[1].s_addr = INADDR_ANY;
+  }
+
+  mask = umask(022);
+  if ((fp = ID0fopen(_PATH_RESCONF, "w")) != NULL) {
+    umask(mask);
+    if (ipcp->ns.resolv_nons)
+      fputs(ipcp->ns.resolv_nons, fp);
+    paddr = inet_ntoa(ipcp->ns.dns[0]);
+    log_Printf(LogIPCP, "Primary nameserver set to %s\n", paddr);
+    fprintf(fp, "\nnameserver %s\n", paddr);
+    if (ipcp->ns.dns[1].s_addr != INADDR_ANY &&
+        ipcp->ns.dns[1].s_addr != INADDR_NONE &&
+        ipcp->ns.dns[1].s_addr != ipcp->ns.dns[0].s_addr) {
+      paddr = inet_ntoa(ipcp->ns.dns[1]);
+      log_Printf(LogIPCP, "Secondary nameserver set to %s\n", paddr);
+      fprintf(fp, "nameserver %s\n", paddr);
+    }
+    if (fclose(fp) == EOF) {
+      log_Printf(LogERROR, "write(): Failed updating %s: %s\n", _PATH_RESCONF,
+                 strerror(errno));
+      return 0;
+    }
+  } else
+    umask(mask);
+
+  return 1;
+}
+
+void
+ipcp_RestoreDNS(struct ipcp *ipcp)
+{
+  if (ipcp->ns.resolver) {
+    ssize_t got, len;
+    int fd;
+
+    if ((fd = ID0open(_PATH_RESCONF, O_WRONLY|O_TRUNC, 0644)) != -1) {
+      len = strlen(ipcp->ns.resolv);
+      if ((got = write(fd, ipcp->ns.resolv, len)) != len) {
+        if (got == -1)
+          log_Printf(LogERROR, "Failed rewriting %s: write: %s\n",
+                     _PATH_RESCONF, strerror(errno));
+        else
+          log_Printf(LogERROR, "Failed rewriting %s: wrote %ld of %ld\n",
+                     _PATH_RESCONF, (long)got, (long)len);
+      }
+      close(fd);
+    } else
+      log_Printf(LogERROR, "Failed rewriting %s: open: %s\n", _PATH_RESCONF,
+                 strerror(errno));
+  } else if (remove(_PATH_RESCONF) == -1)
+    log_Printf(LogERROR, "Failed removing %s: %s\n", _PATH_RESCONF,
+               strerror(errno));
+
+}
+
+int
+ipcp_Show(struct cmdargs const *arg)
+{
+  struct ipcp *ipcp = &arg->bundle->ncp.ipcp;
+
+  prompt_Printf(arg->prompt, "%s [%s]\n", ipcp->fsm.name,
+                State2Nam(ipcp->fsm.state));
+  if (ipcp->fsm.state == ST_OPENED) {
+    prompt_Printf(arg->prompt, " His side:        %s, %s\n",
+                  inet_ntoa(ipcp->peer_ip), vj2asc(ipcp->peer_compproto));
+    prompt_Printf(arg->prompt, " My side:         %s, %s\n",
+                  inet_ntoa(ipcp->my_ip), vj2asc(ipcp->my_compproto));
+    prompt_Printf(arg->prompt, " Queued packets:  %lu\n",
+                  (unsigned long)ipcp_QueueLen(ipcp));
+  }
+
+  prompt_Printf(arg->prompt, "\nDefaults:\n");
+  prompt_Printf(arg->prompt, " FSM retry = %us, max %u Config"
+                " REQ%s, %u Term REQ%s\n", ipcp->cfg.fsm.timeout,
+                ipcp->cfg.fsm.maxreq, ipcp->cfg.fsm.maxreq == 1 ? "" : "s",
+                ipcp->cfg.fsm.maxtrm, ipcp->cfg.fsm.maxtrm == 1 ? "" : "s");
+  prompt_Printf(arg->prompt, " My Address:      %s\n",
+                ncprange_ntoa(&ipcp->cfg.my_range));
+  if (ipcp->cfg.HaveTriggerAddress)
+    prompt_Printf(arg->prompt, " Trigger address: %s\n",
+                  inet_ntoa(ipcp->cfg.TriggerAddress));
+
+  prompt_Printf(arg->prompt, " VJ compression:  %s (%d slots %s slot "
+                "compression)\n", command_ShowNegval(ipcp->cfg.vj.neg),
+                ipcp->cfg.vj.slots, ipcp->cfg.vj.slotcomp ? "with" : "without");
+
+  if (iplist_isvalid(&ipcp->cfg.peer_list))
+    prompt_Printf(arg->prompt, " His Address:     %s\n",
+                  ipcp->cfg.peer_list.src);
+  else
+    prompt_Printf(arg->prompt, " His Address:     %s\n",
+                  ncprange_ntoa(&ipcp->cfg.peer_range));
+
+  prompt_Printf(arg->prompt, " DNS:             %s",
+                ipcp->cfg.ns.dns[0].s_addr == INADDR_NONE ?
+                "none" : inet_ntoa(ipcp->cfg.ns.dns[0]));
+  if (ipcp->cfg.ns.dns[1].s_addr != INADDR_NONE)
+    prompt_Printf(arg->prompt, ", %s",
+                  inet_ntoa(ipcp->cfg.ns.dns[1]));
+  prompt_Printf(arg->prompt, ", %s\n",
+                command_ShowNegval(ipcp->cfg.ns.dns_neg));
+  prompt_Printf(arg->prompt, " Resolver DNS:    %s",
+                ipcp->ns.dns[0].s_addr == INADDR_NONE ?
+                "none" : inet_ntoa(ipcp->ns.dns[0]));
+  if (ipcp->ns.dns[1].s_addr != INADDR_NONE &&
+      ipcp->ns.dns[1].s_addr != ipcp->ns.dns[0].s_addr)
+    prompt_Printf(arg->prompt, ", %s",
+                  inet_ntoa(ipcp->ns.dns[1]));
+  prompt_Printf(arg->prompt, "\n NetBIOS NS:      %s, ",
+                inet_ntoa(ipcp->cfg.ns.nbns[0]));
+  prompt_Printf(arg->prompt, "%s\n\n",
+                inet_ntoa(ipcp->cfg.ns.nbns[1]));
+
+  throughput_disp(&ipcp->throughput, arg->prompt);
+
+  return 0;
+}
+
+int
+ipcp_vjset(struct cmdargs const *arg)
+{
+  if (arg->argc != arg->argn+2)
+    return -1;
+  if (!strcasecmp(arg->argv[arg->argn], "slots")) {
+    int slots;
+
+    slots = atoi(arg->argv[arg->argn+1]);
+    if (slots < 4 || slots > 16)
+      return 1;
+    arg->bundle->ncp.ipcp.cfg.vj.slots = slots;
+    return 0;
+  } else if (!strcasecmp(arg->argv[arg->argn], "slotcomp")) {
+    if (!strcasecmp(arg->argv[arg->argn+1], "on"))
+      arg->bundle->ncp.ipcp.cfg.vj.slotcomp = 1;
+    else if (!strcasecmp(arg->argv[arg->argn+1], "off"))
+      arg->bundle->ncp.ipcp.cfg.vj.slotcomp = 0;
+    else
+      return 2;
+    return 0;
+  }
+  return -1;
+}
+
+void
+ipcp_Init(struct ipcp *ipcp, struct bundle *bundle, struct link *l,
+          const struct fsm_parent *parent)
+{
+  struct hostent *hp;
+  struct in_addr host;
+  char name[MAXHOSTNAMELEN];
+  static const char * const timer_names[] =
+    {"IPCP restart", "IPCP openmode", "IPCP stopped"};
+
+  fsm_Init(&ipcp->fsm, "IPCP", PROTO_IPCP, 1, IPCP_MAXCODE, LogIPCP,
+           bundle, l, parent, &ipcp_Callbacks, timer_names);
+
+  ipcp->cfg.vj.slots = DEF_VJ_STATES;
+  ipcp->cfg.vj.slotcomp = 1;
+  memset(&ipcp->cfg.my_range, '\0', sizeof ipcp->cfg.my_range);
+
+  host.s_addr = htonl(INADDR_LOOPBACK);
+  ipcp->cfg.netmask.s_addr = INADDR_ANY;
+  if (gethostname(name, sizeof name) == 0) {
+    hp = gethostbyname(name);
+    if (hp && hp->h_addrtype == AF_INET && hp->h_length == sizeof host.s_addr)
+      memcpy(&host.s_addr, hp->h_addr, sizeof host.s_addr);
+  }
+  ncprange_setip4(&ipcp->cfg.my_range, host, ipcp->cfg.netmask);
+  ncprange_setip4(&ipcp->cfg.peer_range, ipcp->cfg.netmask, ipcp->cfg.netmask);
+
+  iplist_setsrc(&ipcp->cfg.peer_list, "");
+  ipcp->cfg.HaveTriggerAddress = 0;
+
+  ipcp->cfg.ns.dns[0].s_addr = INADDR_NONE;
+  ipcp->cfg.ns.dns[1].s_addr = INADDR_NONE;
+  ipcp->cfg.ns.dns_neg = 0;
+  ipcp->cfg.ns.nbns[0].s_addr = INADDR_ANY;
+  ipcp->cfg.ns.nbns[1].s_addr = INADDR_ANY;
+
+  ipcp->cfg.fsm.timeout = DEF_FSMRETRY;
+  ipcp->cfg.fsm.maxreq = DEF_FSMTRIES;
+  ipcp->cfg.fsm.maxtrm = DEF_FSMTRIES;
+  ipcp->cfg.vj.neg = NEG_ENABLED|NEG_ACCEPTED;
+
+  memset(&ipcp->vj, '\0', sizeof ipcp->vj);
+
+  ipcp->ns.resolv = NULL;
+  ipcp->ns.resolv_nons = NULL;
+  ipcp->ns.writable = 1;
+  ipcp_LoadDNS(ipcp);
+
+  throughput_init(&ipcp->throughput, SAMPLE_PERIOD);
+  memset(ipcp->Queue, '\0', sizeof ipcp->Queue);
+  ipcp_Setup(ipcp, INADDR_NONE);
+}
+
+void
+ipcp_Destroy(struct ipcp *ipcp)
+{
+  throughput_destroy(&ipcp->throughput);
+
+  if (ipcp->ns.resolv != NULL) {
+    free(ipcp->ns.resolv);
+    ipcp->ns.resolv = NULL;
+  }
+  if (ipcp->ns.resolv_nons != NULL) {
+    free(ipcp->ns.resolv_nons);
+    ipcp->ns.resolv_nons = NULL;
+  }
+}
+
+void
+ipcp_SetLink(struct ipcp *ipcp, struct link *l)
+{
+  ipcp->fsm.link = l;
+}
+
+void
+ipcp_Setup(struct ipcp *ipcp, u_int32_t mask)
+{
+  struct iface *iface = ipcp->fsm.bundle->iface;
+  struct ncpaddr ipaddr;
+  struct in_addr peer;
+  int pos;
+  unsigned n;
+
+  ipcp->fsm.open_mode = 0;
+  ipcp->ifmask.s_addr = mask == INADDR_NONE ? ipcp->cfg.netmask.s_addr : mask;
+
+  if (iplist_isvalid(&ipcp->cfg.peer_list)) {
+    /* Try to give the peer a previously configured IP address */
+    for (n = 0; n < iface->addrs; n++) {
+      if (!ncpaddr_getip4(&iface->addr[n].peer, &peer))
+        continue;
+      if ((pos = iplist_ip2pos(&ipcp->cfg.peer_list, peer)) != -1) {
+        ncpaddr_setip4(&ipaddr, iplist_setcurpos(&ipcp->cfg.peer_list, pos));
+        break;
+      }
+    }
+    if (n == iface->addrs)
+      /* Ok, so none of 'em fit.... pick a random one */
+      ncpaddr_setip4(&ipaddr, iplist_setrandpos(&ipcp->cfg.peer_list));
+
+    ncprange_sethost(&ipcp->cfg.peer_range, &ipaddr);
+  }
+
+  ipcp->heis1172 = 0;
+  ipcp->peer_req = 0;
+  ncprange_getip4addr(&ipcp->cfg.peer_range, &ipcp->peer_ip);
+  ipcp->peer_compproto = 0;
+
+  if (ipcp->cfg.HaveTriggerAddress) {
+    /*
+     * Some implementations of PPP require that we send a
+     * *special* value as our address, even though the rfc specifies
+     * full negotiation (e.g. "0.0.0.0" or Not "0.0.0.0").
+     */
+    ipcp->my_ip = ipcp->cfg.TriggerAddress;
+    log_Printf(LogIPCP, "Using trigger address %s\n",
+              inet_ntoa(ipcp->cfg.TriggerAddress));
+  } else {
+    /*
+     * Otherwise, if we've used an IP number before and it's still within
+     * the network specified on the ``set ifaddr'' line, we really
+     * want to keep that IP number so that we can keep any existing
+     * connections that are bound to that IP.
+     */
+    for (n = 0; n < iface->addrs; n++) {
+      ncprange_getaddr(&iface->addr[n].ifa, &ipaddr);
+      if (ncprange_contains(&ipcp->cfg.my_range, &ipaddr)) {
+        ncpaddr_getip4(&ipaddr, &ipcp->my_ip);
+        break;
+      }
+    }
+    if (n == iface->addrs)
+      ncprange_getip4addr(&ipcp->cfg.my_range, &ipcp->my_ip);
+  }
+
+  if (IsEnabled(ipcp->cfg.vj.neg)
+#ifndef NORADIUS
+      || (ipcp->fsm.bundle->radius.valid && ipcp->fsm.bundle->radius.vj)
+#endif
+     )
+    ipcp->my_compproto = (PROTO_VJCOMP << 16) +
+                         ((ipcp->cfg.vj.slots - 1) << 8) +
+                         ipcp->cfg.vj.slotcomp;
+  else
+    ipcp->my_compproto = 0;
+  sl_compress_init(&ipcp->vj.cslc, ipcp->cfg.vj.slots - 1);
+
+  ipcp->peer_reject = 0;
+  ipcp->my_reject = 0;
+
+  /* Copy startup values into ipcp->ns.dns */
+  if (ipcp->cfg.ns.dns[0].s_addr != INADDR_NONE)
+    memcpy(ipcp->ns.dns, ipcp->cfg.ns.dns, sizeof ipcp->ns.dns);
+}
+
+static int
+numaddresses(struct in_addr mask)
+{
+  u_int32_t bit, haddr;
+  int n;
+
+  haddr = ntohl(mask.s_addr);
+  bit = 1;
+  n = 1;
+
+  do {
+    if (!(haddr & bit))
+      n <<= 1;
+  } while (bit <<= 1);
+
+  return n;
+}
+
+static int
+ipcp_proxyarp(struct ipcp *ipcp,
+              int (*proxyfun)(struct bundle *, struct in_addr),
+              const struct iface_addr *addr)
+{
+  struct bundle *bundle = ipcp->fsm.bundle;
+  struct in_addr peer, mask, ip;
+  int n, ret;
+
+  if (!ncpaddr_getip4(&addr->peer, &peer)) {
+    log_Printf(LogERROR, "Oops, ipcp_proxyarp() called with unexpected addr\n");
+    return 0;
+  }
+
+  ret = 0;
+
+  if (Enabled(bundle, OPT_PROXYALL)) {
+    ncprange_getip4mask(&addr->ifa, &mask);
+    if ((n = numaddresses(mask)) > 256) {
+      log_Printf(LogWARN, "%s: Too many addresses for proxyall\n",
+                 ncprange_ntoa(&addr->ifa));
+      return 0;
+    }
+    ip.s_addr = peer.s_addr & mask.s_addr;
+    if (n >= 4) {
+      ip.s_addr = htonl(ntohl(ip.s_addr) + 1);
+      n -= 2;
+    }
+    while (n) {
+      if (!((ip.s_addr ^ peer.s_addr) & mask.s_addr)) {
+        if (!(ret = (*proxyfun)(bundle, ip)))
+          break;
+        n--;
+      }
+      ip.s_addr = htonl(ntohl(ip.s_addr) + 1);
+    }
+    ret = !n;
+  } else if (Enabled(bundle, OPT_PROXY))
+    ret = (*proxyfun)(bundle, peer);
+
+  return ret;
+}
+
+static int
+ipcp_SetIPaddress(struct ipcp *ipcp, struct in_addr myaddr,
+                  struct in_addr hisaddr)
+{
+  struct bundle *bundle = ipcp->fsm.bundle;
+  struct ncpaddr myncpaddr, hisncpaddr;
+  struct ncprange myrange;
+  struct in_addr mask;
+  struct sockaddr_storage ssdst, ssgw, ssmask;
+  struct sockaddr *sadst, *sagw, *samask;
+
+  sadst = (struct sockaddr *)&ssdst;
+  sagw = (struct sockaddr *)&ssgw;
+  samask = (struct sockaddr *)&ssmask;
+
+  ncpaddr_setip4(&hisncpaddr, hisaddr);
+  ncpaddr_setip4(&myncpaddr, myaddr);
+  ncprange_sethost(&myrange, &myncpaddr);
+
+  mask = addr2mask(myaddr);
+
+  if (ipcp->ifmask.s_addr != INADDR_ANY &&
+      (ipcp->ifmask.s_addr & mask.s_addr) == mask.s_addr)
+    ncprange_setip4mask(&myrange, ipcp->ifmask);
+
+  if (!iface_Add(bundle->iface, &bundle->ncp, &myrange, &hisncpaddr,
+                 IFACE_ADD_FIRST|IFACE_FORCE_ADD|IFACE_SYSTEM))
+    return 0;
+
+  if (!Enabled(bundle, OPT_IFACEALIAS))
+    iface_Clear(bundle->iface, &bundle->ncp, AF_INET,
+                IFACE_CLEAR_ALIASES|IFACE_SYSTEM);
+
+  if (bundle->ncp.cfg.sendpipe > 0 || bundle->ncp.cfg.recvpipe > 0) {
+    ncprange_getsa(&myrange, &ssgw, &ssmask);
+    ncpaddr_getsa(&hisncpaddr, &ssdst);
+    rt_Update(bundle, sadst, sagw, samask);
+  }
+
+  if (Enabled(bundle, OPT_SROUTES))
+    route_Change(bundle, bundle->ncp.route, &myncpaddr, &hisncpaddr);
+
+#ifndef NORADIUS
+  if (bundle->radius.valid)
+    route_Change(bundle, bundle->radius.routes, &myncpaddr, &hisncpaddr);
+#endif
+
+  return 1;	/* Ok */
+}
+
+static struct in_addr
+ChooseHisAddr(struct bundle *bundle, struct in_addr gw)
+{
+  struct in_addr try;
+  u_long f;
+
+  for (f = 0; f < bundle->ncp.ipcp.cfg.peer_list.nItems; f++) {
+    try = iplist_next(&bundle->ncp.ipcp.cfg.peer_list);
+    log_Printf(LogDEBUG, "ChooseHisAddr: Check item %ld (%s)\n",
+              f, inet_ntoa(try));
+    if (ipcp_SetIPaddress(&bundle->ncp.ipcp, gw, try)) {
+      log_Printf(LogIPCP, "Selected IP address %s\n", inet_ntoa(try));
+      break;
+    }
+  }
+
+  if (f == bundle->ncp.ipcp.cfg.peer_list.nItems) {
+    log_Printf(LogDEBUG, "ChooseHisAddr: All addresses in use !\n");
+    try.s_addr = INADDR_ANY;
+  }
+
+  return try;
+}
+
+static void
+IpcpInitRestartCounter(struct fsm *fp, int what)
+{
+  /* Set fsm timer load */
+  struct ipcp *ipcp = fsm2ipcp(fp);
+
+  fp->FsmTimer.load = ipcp->cfg.fsm.timeout * SECTICKS;
+  switch (what) {
+    case FSM_REQ_TIMER:
+      fp->restart = ipcp->cfg.fsm.maxreq;
+      break;
+    case FSM_TRM_TIMER:
+      fp->restart = ipcp->cfg.fsm.maxtrm;
+      break;
+    default:
+      fp->restart = 1;
+      break;
+  }
+}
+
+static void
+IpcpSendConfigReq(struct fsm *fp)
+{
+  /* Send config REQ please */
+  struct physical *p = link2physical(fp->link);
+  struct ipcp *ipcp = fsm2ipcp(fp);
+  u_char buff[MAX_FSM_OPT_LEN];
+  struct fsm_opt *o;
+
+  o = (struct fsm_opt *)buff;
+
+  if ((p && !physical_IsSync(p)) || !REJECTED(ipcp, TY_IPADDR)) {
+    memcpy(o->data, &ipcp->my_ip.s_addr, 4);
+    INC_FSM_OPT(TY_IPADDR, 6, o);
+  }
+
+  if (ipcp->my_compproto && !REJECTED(ipcp, TY_COMPPROTO)) {
+    if (ipcp->heis1172) {
+      u_int16_t proto = PROTO_VJCOMP;
+
+      ua_htons(&proto, o->data);
+      INC_FSM_OPT(TY_COMPPROTO, 4, o);
+    } else {
+      struct compreq req;
+
+      req.proto = htons(ipcp->my_compproto >> 16);
+      req.slots = (ipcp->my_compproto >> 8) & 255;
+      req.compcid = ipcp->my_compproto & 1;
+      memcpy(o->data, &req, 4);
+      INC_FSM_OPT(TY_COMPPROTO, 6, o);
+    }
+  }
+
+  if (IsEnabled(ipcp->cfg.ns.dns_neg)) {
+    if (!REJECTED(ipcp, TY_PRIMARY_DNS - TY_ADJUST_NS)) {
+      memcpy(o->data, &ipcp->ns.dns[0].s_addr, 4);
+      INC_FSM_OPT(TY_PRIMARY_DNS, 6, o);
+    }
+
+    if (!REJECTED(ipcp, TY_SECONDARY_DNS - TY_ADJUST_NS)) {
+      memcpy(o->data, &ipcp->ns.dns[1].s_addr, 4);
+      INC_FSM_OPT(TY_SECONDARY_DNS, 6, o);
+    }
+  }
+
+  fsm_Output(fp, CODE_CONFIGREQ, fp->reqid, buff, (u_char *)o - buff,
+             MB_IPCPOUT);
+}
+
+static void
+IpcpSentTerminateReq(struct fsm *fp __unused)
+{
+  /* Term REQ just sent by FSM */
+}
+
+static void
+IpcpSendTerminateAck(struct fsm *fp, u_char id)
+{
+  /* Send Term ACK please */
+  fsm_Output(fp, CODE_TERMACK, id, NULL, 0, MB_IPCPOUT);
+}
+
+static void
+IpcpLayerStart(struct fsm *fp)
+{
+  /* We're about to start up ! */
+  struct ipcp *ipcp = fsm2ipcp(fp);
+
+  log_Printf(LogIPCP, "%s: LayerStart.\n", fp->link->name);
+  throughput_start(&ipcp->throughput, "IPCP throughput",
+                   Enabled(fp->bundle, OPT_THROUGHPUT));
+  fp->more.reqs = fp->more.naks = fp->more.rejs = ipcp->cfg.fsm.maxreq * 3;
+  ipcp->peer_req = 0;
+}
+
+static void
+IpcpLayerFinish(struct fsm *fp)
+{
+  /* We're now down */
+  struct ipcp *ipcp = fsm2ipcp(fp);
+
+  log_Printf(LogIPCP, "%s: LayerFinish.\n", fp->link->name);
+  throughput_stop(&ipcp->throughput);
+  throughput_log(&ipcp->throughput, LogIPCP, NULL);
+}
+
+/*
+ * Called from iface_Add() via ncp_IfaceAddrAdded()
+ */
+void
+ipcp_IfaceAddrAdded(struct ipcp *ipcp, const struct iface_addr *addr)
+{
+  struct bundle *bundle = ipcp->fsm.bundle;
+
+  if (Enabled(bundle, OPT_PROXY) || Enabled(bundle, OPT_PROXYALL))
+    ipcp_proxyarp(ipcp, arp_SetProxy, addr);
+}
+
+/*
+ * Called from iface_Clear() and iface_Delete() via ncp_IfaceAddrDeleted()
+ */
+void
+ipcp_IfaceAddrDeleted(struct ipcp *ipcp, const struct iface_addr *addr)
+{
+  struct bundle *bundle = ipcp->fsm.bundle;
+
+  if (Enabled(bundle, OPT_PROXY) || Enabled(bundle, OPT_PROXYALL))
+    ipcp_proxyarp(ipcp, arp_ClearProxy, addr);
+}
+
+static void
+IpcpLayerDown(struct fsm *fp)
+{
+  /* About to come down */
+  struct ipcp *ipcp = fsm2ipcp(fp);
+  static int recursing;
+  char addr[16];
+
+  if (!recursing++) {
+    snprintf(addr, sizeof addr, "%s", inet_ntoa(ipcp->my_ip));
+    log_Printf(LogIPCP, "%s: LayerDown: %s\n", fp->link->name, addr);
+
+#ifndef NORADIUS
+    radius_Flush(&fp->bundle->radius);
+    radius_Account(&fp->bundle->radius, &fp->bundle->radacct,
+                   fp->bundle->links, RAD_STOP, &ipcp->throughput);
+
+    if (fp->bundle->radius.cfg.file && fp->bundle->radius.filterid)
+      system_Select(fp->bundle, fp->bundle->radius.filterid, LINKDOWNFILE,
+                    NULL, NULL);
+    radius_StopTimer(&fp->bundle->radius);
+#endif
+
+    /*
+     * XXX this stuff should really live in the FSM.  Our config should
+     * associate executable sections in files with events.
+     */
+    if (system_Select(fp->bundle, addr, LINKDOWNFILE, NULL, NULL) < 0) {
+      if (bundle_GetLabel(fp->bundle)) {
+         if (system_Select(fp->bundle, bundle_GetLabel(fp->bundle),
+                          LINKDOWNFILE, NULL, NULL) < 0)
+         system_Select(fp->bundle, "MYADDR", LINKDOWNFILE, NULL, NULL);
+      } else
+        system_Select(fp->bundle, "MYADDR", LINKDOWNFILE, NULL, NULL);
+    }
+
+    ipcp_Setup(ipcp, INADDR_NONE);
+  }
+  recursing--;
+}
+
+int
+ipcp_InterfaceUp(struct ipcp *ipcp)
+{
+  if (!ipcp_SetIPaddress(ipcp, ipcp->my_ip, ipcp->peer_ip)) {
+    log_Printf(LogERROR, "ipcp_InterfaceUp: unable to set ip address\n");
+    return 0;
+  }
+
+  if (!iface_SetFlags(ipcp->fsm.bundle->iface->name, IFF_UP)) {
+    log_Printf(LogERROR, "ipcp_InterfaceUp: Can't set the IFF_UP flag on %s\n",
+               ipcp->fsm.bundle->iface->name);
+    return 0;
+  }
+
+#ifndef NONAT
+  if (ipcp->fsm.bundle->NatEnabled)
+    PacketAliasSetAddress(ipcp->my_ip);
+#endif
+
+  return 1;
+}
+
+static int
+IpcpLayerUp(struct fsm *fp)
+{
+  /* We're now up */
+  struct ipcp *ipcp = fsm2ipcp(fp);
+  char tbuff[16];
+
+  log_Printf(LogIPCP, "%s: LayerUp.\n", fp->link->name);
+  snprintf(tbuff, sizeof tbuff, "%s", inet_ntoa(ipcp->my_ip));
+  log_Printf(LogIPCP, "myaddr %s hisaddr = %s\n",
+             tbuff, inet_ntoa(ipcp->peer_ip));
+
+  if (ipcp->peer_compproto >> 16 == PROTO_VJCOMP)
+    sl_compress_init(&ipcp->vj.cslc, (ipcp->peer_compproto >> 8) & 255);
+
+  if (!ipcp_InterfaceUp(ipcp))
+    return 0;
+
+#ifndef NORADIUS
+  radius_Account_Set_Ip(&fp->bundle->radacct, &ipcp->peer_ip, &ipcp->ifmask);
+  radius_Account(&fp->bundle->radius, &fp->bundle->radacct, fp->bundle->links,
+                 RAD_START, &ipcp->throughput);
+
+  if (fp->bundle->radius.cfg.file && fp->bundle->radius.filterid)
+    system_Select(fp->bundle, fp->bundle->radius.filterid, LINKUPFILE,
+                  NULL, NULL);
+  radius_StartTimer(fp->bundle);
+#endif
+
+  /*
+   * XXX this stuff should really live in the FSM.  Our config should
+   * associate executable sections in files with events.
+   */
+  if (system_Select(fp->bundle, tbuff, LINKUPFILE, NULL, NULL) < 0) {
+    if (bundle_GetLabel(fp->bundle)) {
+      if (system_Select(fp->bundle, bundle_GetLabel(fp->bundle),
+                       LINKUPFILE, NULL, NULL) < 0)
+        system_Select(fp->bundle, "MYADDR", LINKUPFILE, NULL, NULL);
+    } else
+      system_Select(fp->bundle, "MYADDR", LINKUPFILE, NULL, NULL);
+  }
+
+  fp->more.reqs = fp->more.naks = fp->more.rejs = ipcp->cfg.fsm.maxreq * 3;
+  log_DisplayPrompts();
+
+  return 1;
+}
+
+static void
+ipcp_ValidateReq(struct ipcp *ipcp, struct in_addr ip, struct fsm_decode *dec)
+{
+  struct bundle *bundle = ipcp->fsm.bundle;
+  struct iface *iface = bundle->iface;
+  struct in_addr myaddr, peer;
+  unsigned n;
+
+  if (iplist_isvalid(&ipcp->cfg.peer_list)) {
+    ncprange_getip4addr(&ipcp->cfg.my_range, &myaddr);
+    if (ip.s_addr == INADDR_ANY ||
+        iplist_ip2pos(&ipcp->cfg.peer_list, ip) < 0 ||
+        !ipcp_SetIPaddress(ipcp, myaddr, ip)) {
+      log_Printf(LogIPCP, "%s: Address invalid or already in use\n",
+                 inet_ntoa(ip));
+      /*
+       * If we've already had a valid address configured for the peer,
+       * try NAKing with that so that we don't have to upset things
+       * too much.
+       */
+      for (n = 0; n < iface->addrs; n++) {
+        if (!ncpaddr_getip4(&iface->addr[n].peer, &peer))
+          continue;
+        if (iplist_ip2pos(&ipcp->cfg.peer_list, peer) >= 0) {
+          ipcp->peer_ip = peer;
+          break;
+        }
+      }
+
+      if (n == iface->addrs) {
+        /* Just pick an IP number from our list */
+        ipcp->peer_ip = ChooseHisAddr(bundle, myaddr);
+      }
+
+      if (ipcp->peer_ip.s_addr == INADDR_ANY) {
+        *dec->rejend++ = TY_IPADDR;
+        *dec->rejend++ = 6;
+        memcpy(dec->rejend, &ip.s_addr, 4);
+        dec->rejend += 4;
+      } else {
+        *dec->nakend++ = TY_IPADDR;
+        *dec->nakend++ = 6;
+        memcpy(dec->nakend, &ipcp->peer_ip.s_addr, 4);
+        dec->nakend += 4;
+      }
+      return;
+    }
+  } else if (ip.s_addr == INADDR_ANY ||
+             !ncprange_containsip4(&ipcp->cfg.peer_range, ip)) {
+    /*
+     * If the destination address is not acceptable, NAK with what we
+     * want to use.
+     */
+    *dec->nakend++ = TY_IPADDR;
+    *dec->nakend++ = 6;
+    for (n = 0; n < iface->addrs; n++)
+      if (ncprange_contains(&ipcp->cfg.peer_range, &iface->addr[n].peer)) {
+        /* We prefer the already-configured address */
+        ncpaddr_getip4addr(&iface->addr[n].peer, (u_int32_t *)dec->nakend);
+        break;
+      }
+
+    if (n == iface->addrs)
+      memcpy(dec->nakend, &ipcp->peer_ip.s_addr, 4);
+
+    dec->nakend += 4;
+    return;
+  }
+
+  ipcp->peer_ip = ip;
+  *dec->ackend++ = TY_IPADDR;
+  *dec->ackend++ = 6;
+  memcpy(dec->ackend, &ip.s_addr, 4);
+  dec->ackend += 4;
+}
+
+static void
+IpcpDecodeConfig(struct fsm *fp, u_char *cp, u_char *end, int mode_type,
+                 struct fsm_decode *dec)
+{
+  /* Deal with incoming PROTO_IPCP */
+  struct ncpaddr ncpaddr;
+  struct ipcp *ipcp = fsm2ipcp(fp);
+  int gotdnsnak;
+  u_int32_t compproto;
+  struct compreq pcomp;
+  struct in_addr ipaddr, dstipaddr, have_ip;
+  char tbuff[100], tbuff2[100];
+  struct fsm_opt *opt, nak;
+
+  gotdnsnak = 0;
+
+  while (end - cp >= (int)sizeof(opt->hdr)) {
+    if ((opt = fsm_readopt(&cp)) == NULL)
+      break;
+
+    snprintf(tbuff, sizeof tbuff, " %s[%d]", protoname(opt->hdr.id),
+             opt->hdr.len);
+
+    switch (opt->hdr.id) {
+    case TY_IPADDR:		/* RFC1332 */
+      memcpy(&ipaddr.s_addr, opt->data, 4);
+      log_Printf(LogIPCP, "%s %s\n", tbuff, inet_ntoa(ipaddr));
+
+      switch (mode_type) {
+      case MODE_REQ:
+        ipcp->peer_req = 1;
+        ipcp_ValidateReq(ipcp, ipaddr, dec);
+        break;
+
+      case MODE_NAK:
+        if (ncprange_containsip4(&ipcp->cfg.my_range, ipaddr)) {
+          /* Use address suggested by peer */
+          snprintf(tbuff2, sizeof tbuff2, "%s changing address: %s ", tbuff,
+                   inet_ntoa(ipcp->my_ip));
+          log_Printf(LogIPCP, "%s --> %s\n", tbuff2, inet_ntoa(ipaddr));
+          ipcp->my_ip = ipaddr;
+          ncpaddr_setip4(&ncpaddr, ipcp->my_ip);
+          bundle_AdjustFilters(fp->bundle, &ncpaddr, NULL);
+        } else {
+          log_Printf(log_IsKept(LogIPCP) ? LogIPCP : LogPHASE,
+                    "%s: Unacceptable address!\n", inet_ntoa(ipaddr));
+          fsm_Close(&ipcp->fsm);
+        }
+        break;
+
+      case MODE_REJ:
+        ipcp->peer_reject |= (1 << opt->hdr.id);
+        break;
+      }
+      break;
+
+    case TY_COMPPROTO:
+      memcpy(&pcomp, opt->data, sizeof pcomp);
+      compproto = (ntohs(pcomp.proto) << 16) + ((int)pcomp.slots << 8) +
+                  pcomp.compcid;
+      log_Printf(LogIPCP, "%s %s\n", tbuff, vj2asc(compproto));
+
+      switch (mode_type) {
+      case MODE_REQ:
+        if (!IsAccepted(ipcp->cfg.vj.neg))
+          fsm_rej(dec, opt);
+        else {
+          switch (opt->hdr.len) {
+          case 4:		/* RFC1172 */
+            if (ntohs(pcomp.proto) == PROTO_VJCOMP) {
+              log_Printf(LogWARN, "Peer is speaking RFC1172 compression "
+                         "protocol !\n");
+              ipcp->heis1172 = 1;
+              ipcp->peer_compproto = compproto;
+              fsm_ack(dec, opt);
+            } else {
+              pcomp.proto = htons(PROTO_VJCOMP);
+              nak.hdr.id = TY_COMPPROTO;
+              nak.hdr.len = 4;
+              memcpy(nak.data, &pcomp, 2);
+              fsm_nak(dec, &nak);
+            }
+            break;
+          case 6:		/* RFC1332 */
+            if (ntohs(pcomp.proto) == PROTO_VJCOMP) {
+	      /* We know pcomp.slots' max value == MAX_VJ_STATES */
+              if (pcomp.slots >= MIN_VJ_STATES) {
+                /* Ok, we can do that */
+                ipcp->peer_compproto = compproto;
+                ipcp->heis1172 = 0;
+                fsm_ack(dec, opt);
+              } else {
+                /* Get as close as we can to what he wants */
+                ipcp->heis1172 = 0;
+                pcomp.slots = MIN_VJ_STATES;
+                nak.hdr.id = TY_COMPPROTO;
+                nak.hdr.len = 4;
+                memcpy(nak.data, &pcomp, 2);
+                fsm_nak(dec, &nak);
+              }
+            } else {
+              /* What we really want */
+              pcomp.proto = htons(PROTO_VJCOMP);
+              pcomp.slots = DEF_VJ_STATES;
+              pcomp.compcid = 1;
+              nak.hdr.id = TY_COMPPROTO;
+              nak.hdr.len = 6;
+              memcpy(nak.data, &pcomp, sizeof pcomp);
+              fsm_nak(dec, &nak);
+            }
+            break;
+          default:
+            fsm_rej(dec, opt);
+            break;
+          }
+        }
+        break;
+
+      case MODE_NAK:
+        if (ntohs(pcomp.proto) == PROTO_VJCOMP) {
+	  /* We know pcomp.slots' max value == MAX_VJ_STATES */
+          if (pcomp.slots < MIN_VJ_STATES)
+            pcomp.slots = MIN_VJ_STATES;
+          compproto = (ntohs(pcomp.proto) << 16) + (pcomp.slots << 8) +
+                      pcomp.compcid;
+        } else
+          compproto = 0;
+        log_Printf(LogIPCP, "%s changing compproto: %08x --> %08x\n",
+                   tbuff, ipcp->my_compproto, compproto);
+        ipcp->my_compproto = compproto;
+        break;
+
+      case MODE_REJ:
+        ipcp->peer_reject |= (1 << opt->hdr.id);
+        break;
+      }
+      break;
+
+    case TY_IPADDRS:		/* RFC1172 */
+      memcpy(&ipaddr.s_addr, opt->data, 4);
+      memcpy(&dstipaddr.s_addr, opt->data + 4, 4);
+      snprintf(tbuff2, sizeof tbuff2, "%s %s,", tbuff, inet_ntoa(ipaddr));
+      log_Printf(LogIPCP, "%s %s\n", tbuff2, inet_ntoa(dstipaddr));
+
+      switch (mode_type) {
+      case MODE_REQ:
+        fsm_rej(dec, opt);
+        break;
+
+      case MODE_NAK:
+      case MODE_REJ:
+        break;
+      }
+      break;
+
+    case TY_PRIMARY_DNS:	/* DNS negotiation (rfc1877) */
+    case TY_SECONDARY_DNS:
+      memcpy(&ipaddr.s_addr, opt->data, 4);
+      log_Printf(LogIPCP, "%s %s\n", tbuff, inet_ntoa(ipaddr));
+
+      switch (mode_type) {
+      case MODE_REQ:
+        if (!IsAccepted(ipcp->cfg.ns.dns_neg)) {
+          ipcp->my_reject |= (1 << (opt->hdr.id - TY_ADJUST_NS));
+          fsm_rej(dec, opt);
+          break;
+        }
+        have_ip = ipcp->ns.dns[opt->hdr.id == TY_PRIMARY_DNS ? 0 : 1];
+
+        if (opt->hdr.id == TY_PRIMARY_DNS && ipaddr.s_addr != have_ip.s_addr &&
+            ipaddr.s_addr == ipcp->ns.dns[1].s_addr) {
+          /* Swap 'em 'round */
+          ipcp->ns.dns[0] = ipcp->ns.dns[1];
+          ipcp->ns.dns[1] = have_ip;
+          have_ip = ipcp->ns.dns[0];
+        }
+
+        if (ipaddr.s_addr != have_ip.s_addr) {
+          /*
+           * The client has got the DNS stuff wrong (first request) so
+           * we'll tell 'em how it is
+           */
+          nak.hdr.id = opt->hdr.id;
+          nak.hdr.len = 6;
+          memcpy(nak.data, &have_ip.s_addr, 4);
+          fsm_nak(dec, &nak);
+        } else {
+          /*
+           * Otherwise they have it right (this time) so we send an ack packet
+           * back confirming it... end of story
+           */
+          fsm_ack(dec, opt);
+        }
+        break;
+
+      case MODE_NAK:
+        if (IsEnabled(ipcp->cfg.ns.dns_neg)) {
+          gotdnsnak = 1;
+          memcpy(&ipcp->ns.dns[opt->hdr.id == TY_PRIMARY_DNS ? 0 : 1].s_addr,
+                 opt->data, 4);
+        }
+        break;
+
+      case MODE_REJ:		/* Can't do much, stop asking */
+        ipcp->peer_reject |= (1 << (opt->hdr.id - TY_ADJUST_NS));
+        break;
+      }
+      break;
+
+    case TY_PRIMARY_NBNS:	/* M$ NetBIOS nameserver hack (rfc1877) */
+    case TY_SECONDARY_NBNS:
+      memcpy(&ipaddr.s_addr, opt->data, 4);
+      log_Printf(LogIPCP, "%s %s\n", tbuff, inet_ntoa(ipaddr));
+
+      switch (mode_type) {
+      case MODE_REQ:
+        have_ip.s_addr =
+          ipcp->cfg.ns.nbns[opt->hdr.id == TY_PRIMARY_NBNS ? 0 : 1].s_addr;
+
+        if (have_ip.s_addr == INADDR_ANY) {
+          log_Printf(LogIPCP, "NBNS REQ - rejected - nbns not set\n");
+          ipcp->my_reject |= (1 << (opt->hdr.id - TY_ADJUST_NS));
+          fsm_rej(dec, opt);
+          break;
+        }
+
+        if (ipaddr.s_addr != have_ip.s_addr) {
+          nak.hdr.id = opt->hdr.id;
+          nak.hdr.len = 6;
+          memcpy(nak.data, &have_ip.s_addr, 4);
+          fsm_nak(dec, &nak);
+        } else
+          fsm_ack(dec, opt);
+        break;
+
+      case MODE_NAK:
+        log_Printf(LogIPCP, "MS NBNS req %d - NAK??\n", opt->hdr.id);
+        break;
+
+      case MODE_REJ:
+        log_Printf(LogIPCP, "MS NBNS req %d - REJ??\n", opt->hdr.id);
+        break;
+      }
+      break;
+
+    default:
+      if (mode_type != MODE_NOP) {
+        ipcp->my_reject |= (1 << opt->hdr.id);
+        fsm_rej(dec, opt);
+      }
+      break;
+    }
+  }
+
+  if (gotdnsnak) {
+    if (ipcp->ns.writable) {
+      log_Printf(LogDEBUG, "Updating resolver\n");
+      if (!ipcp_WriteDNS(ipcp)) {
+        ipcp->peer_reject |= (1 << (TY_PRIMARY_DNS - TY_ADJUST_NS));
+        ipcp->peer_reject |= (1 << (TY_SECONDARY_DNS - TY_ADJUST_NS));
+      } else
+        bundle_AdjustDNS(fp->bundle);
+    } else {
+      log_Printf(LogDEBUG, "Not updating resolver (readonly)\n");
+      bundle_AdjustDNS(fp->bundle);
+    }
+  }
+
+  if (mode_type != MODE_NOP) {
+    if (mode_type == MODE_REQ && !ipcp->peer_req) {
+      if (dec->rejend == dec->rej && dec->nakend == dec->nak) {
+        /*
+         * Pretend the peer has requested an IP.
+         * We do this to ensure that we only send one NAK if the only
+         * reason for the NAK is because the peer isn't sending a
+         * TY_IPADDR REQ.  This stops us from repeatedly trying to tell
+         * the peer that we have to have an IP address on their end.
+         */
+        ipcp->peer_req = 1;
+      }
+      ipaddr.s_addr = INADDR_ANY;
+      ipcp_ValidateReq(ipcp, ipaddr, dec);
+    }
+    fsm_opt_normalise(dec);
+  }
+}
+
+extern struct mbuf *
+ipcp_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+  /* Got PROTO_IPCP from link */
+  m_settype(bp, MB_IPCPIN);
+  if (bundle_Phase(bundle) == PHASE_NETWORK)
+    fsm_Input(&bundle->ncp.ipcp.fsm, bp);
+  else {
+    if (bundle_Phase(bundle) < PHASE_NETWORK)
+      log_Printf(LogIPCP, "%s: Error: Unexpected IPCP in phase %s (ignored)\n",
+                 l->name, bundle_PhaseName(bundle));
+    m_freem(bp);
+  }
+  return NULL;
+}
+
+int
+ipcp_UseHisIPaddr(struct bundle *bundle, struct in_addr hisaddr)
+{
+  struct ipcp *ipcp = &bundle->ncp.ipcp;
+  struct in_addr myaddr;
+
+  memset(&ipcp->cfg.peer_range, '\0', sizeof ipcp->cfg.peer_range);
+  iplist_reset(&ipcp->cfg.peer_list);
+  ipcp->peer_ip = hisaddr;
+  ncprange_setip4host(&ipcp->cfg.peer_range, hisaddr);
+  ncprange_getip4addr(&ipcp->cfg.my_range, &myaddr);
+
+  return ipcp_SetIPaddress(ipcp, myaddr, hisaddr);
+}
+
+int
+ipcp_UseHisaddr(struct bundle *bundle, const char *hisaddr, int setaddr)
+{
+  struct in_addr myaddr;
+  struct ncp *ncp = &bundle->ncp;
+  struct ipcp *ipcp = &ncp->ipcp;
+  struct ncpaddr ncpaddr;
+
+  /* Use `hisaddr' for the peers address (set iface if `setaddr') */
+  memset(&ipcp->cfg.peer_range, '\0', sizeof ipcp->cfg.peer_range);
+  iplist_reset(&ipcp->cfg.peer_list);
+  if (strpbrk(hisaddr, ",-")) {
+    iplist_setsrc(&ipcp->cfg.peer_list, hisaddr);
+    if (iplist_isvalid(&ipcp->cfg.peer_list)) {
+      iplist_setrandpos(&ipcp->cfg.peer_list);
+      ipcp->peer_ip = ChooseHisAddr(bundle, ipcp->my_ip);
+      if (ipcp->peer_ip.s_addr == INADDR_ANY) {
+        log_Printf(LogWARN, "%s: None available !\n", ipcp->cfg.peer_list.src);
+        return 0;
+      }
+      ncprange_setip4host(&ipcp->cfg.peer_range, ipcp->peer_ip);
+    } else {
+      log_Printf(LogWARN, "%s: Invalid range !\n", hisaddr);
+      return 0;
+    }
+  } else if (ncprange_aton(&ipcp->cfg.peer_range, ncp, hisaddr) != 0) {
+    if (ncprange_family(&ipcp->cfg.my_range) != AF_INET) {
+      log_Printf(LogWARN, "%s: Not an AF_INET address !\n", hisaddr);
+      return 0;
+    }
+    ncprange_getip4addr(&ipcp->cfg.my_range, &myaddr);
+    ncprange_getip4addr(&ipcp->cfg.peer_range, &ipcp->peer_ip);
+
+    if (setaddr && !ipcp_SetIPaddress(ipcp, myaddr, ipcp->peer_ip))
+      return 0;
+  } else
+    return 0;
+
+  ncpaddr_setip4(&ncpaddr, ipcp->peer_ip);
+  bundle_AdjustFilters(bundle, NULL, &ncpaddr);
+
+  return 1;	/* Ok */
+}
+
+struct in_addr
+addr2mask(struct in_addr addr)
+{
+  u_int32_t haddr = ntohl(addr.s_addr);
+
+  haddr = IN_CLASSA(haddr) ? IN_CLASSA_NET :
+          IN_CLASSB(haddr) ? IN_CLASSB_NET :
+          IN_CLASSC_NET;
+  addr.s_addr = htonl(haddr);
+
+  return addr;
+}
+
+size_t
+ipcp_QueueLen(struct ipcp *ipcp)
+{
+  struct mqueue *q;
+  size_t result;
+
+  result = 0;
+  for (q = ipcp->Queue; q < ipcp->Queue + IPCP_QUEUES(ipcp); q++)
+    result += q->len;
+
+  return result;
+}
+
+int
+ipcp_PushPacket(struct ipcp *ipcp, struct link *l)
+{
+  struct bundle *bundle = ipcp->fsm.bundle;
+  struct mqueue *queue;
+  struct mbuf *bp;
+  int m_len;
+  u_int32_t secs = 0;
+  unsigned alivesecs = 0;
+
+  if (ipcp->fsm.state != ST_OPENED)
+    return 0;
+
+  /*
+   * If ccp is not open but is required, do nothing.
+   */
+  if (l->ccp.fsm.state != ST_OPENED && ccp_Required(&l->ccp)) {
+    log_Printf(LogPHASE, "%s: Not transmitting... waiting for CCP\n", l->name);
+    return 0;
+  }
+
+  queue = ipcp->Queue + IPCP_QUEUES(ipcp) - 1;
+  do {
+    if (queue->top) {
+      bp = m_dequeue(queue);
+      bp = mbuf_Read(bp, &secs, sizeof secs);
+      bp = m_pullup(bp);
+      m_len = m_length(bp);
+      if (!FilterCheck(MBUF_CTOP(bp), AF_INET, &bundle->filter.alive,
+                       &alivesecs)) {
+        if (secs == 0)
+          secs = alivesecs;
+        bundle_StartIdleTimer(bundle, secs);
+      }
+      link_PushPacket(l, bp, bundle, 0, PROTO_IP);
+      ipcp_AddOutOctets(ipcp, m_len);
+      return 1;
+    }
+  } while (queue-- != ipcp->Queue);
+
+  return 0;
+}
diff --git a/src/ipcp.h b/src/ipcp.h
new file mode 100644
index 0000000..79b9471
--- /dev/null
+++ b/src/ipcp.h
@@ -0,0 +1,132 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ipcp.h,v 1.35.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define	IPCP_MAXCODE	CODE_CODEREJ
+
+#define	TY_IPADDRS	1
+#define	TY_COMPPROTO	2
+#define	TY_IPADDR	3
+
+/* Domain NameServer and NetBIOS NameServer options */
+
+#define TY_PRIMARY_DNS		129
+#define TY_PRIMARY_NBNS		130
+#define TY_SECONDARY_DNS	131
+#define TY_SECONDARY_NBNS	132
+#define TY_ADJUST_NS		119 /* subtract from NS val for REJECT bit */
+
+struct ipcp {
+  struct fsm fsm;			/* The finite state machine */
+
+  struct {
+    struct {
+      int slots;			/* Maximum VJ slots */
+      unsigned slotcomp : 1;		/* Slot compression */
+      unsigned neg : 2;			/* VJ negotiation */
+    } vj;
+
+    struct ncprange  my_range;		/* MYADDR spec */
+    struct in_addr   netmask;		/* Iface netmask (unused by most OSs) */
+    struct ncprange  peer_range;	/* HISADDR spec */
+    struct iplist    peer_list;		/* Ranges of HISADDR values */
+
+    struct in_addr   TriggerAddress;	/* Address to suggest in REQ */
+    unsigned HaveTriggerAddress : 1;	/* Trigger address specified */
+
+    struct {
+      struct in_addr dns[2];		/* DNS addresses offered */
+      unsigned dns_neg : 2;		/* dns negotiation */
+      struct in_addr nbns[2];		/* NetBIOS NS addresses offered */
+    } ns;
+
+    struct fsm_retry fsm;		/* frequency to resend requests */
+  } cfg;
+
+  struct {
+    struct slcompress cslc;		/* VJ state */
+    struct slstat slstat;		/* VJ statistics */
+  } vj;
+
+  struct {
+    unsigned resolver : 1;		/* Found resolv.conf ? */
+    unsigned writable : 1;		/* Can write resolv.conf ? */
+    struct in_addr dns[2];		/* Current DNS addresses */
+    char *resolv;			/* Contents of resolv.conf */
+    char *resolv_nons;			/* Contents of resolv.conf without ns */
+  } ns;
+
+  unsigned heis1172 : 1;		/* True if he is speaking rfc1172 */
+
+  unsigned peer_req : 1;		/* Any TY_IPADDR REQs from the peer ? */
+  struct in_addr peer_ip;		/* IP address he's willing to use */
+  u_int32_t peer_compproto;		/* VJ params he's willing to use */
+
+  struct in_addr ifmask;		/* Interface netmask */
+
+  struct in_addr my_ip;			/* IP address I'm willing to use */
+  u_int32_t my_compproto;		/* VJ params I'm willing to use */
+
+  u_int32_t peer_reject;		/* Request codes rejected by peer */
+  u_int32_t my_reject;			/* Request codes I have rejected */
+
+  struct pppThroughput throughput;	/* throughput statistics */
+  struct mqueue Queue[3];		/* Output packet queues */
+};
+
+#define fsm2ipcp(fp) (fp->proto == PROTO_IPCP ? (struct ipcp *)fp : NULL)
+#define IPCP_QUEUES(ipcp) (sizeof ipcp->Queue / sizeof ipcp->Queue[0])
+
+struct bundle;
+struct link;
+struct cmdargs;
+struct iface_addr;
+
+extern void ipcp_Init(struct ipcp *, struct bundle *, struct link *,
+                      const struct fsm_parent *);
+extern void ipcp_Destroy(struct ipcp *);
+extern void ipcp_Setup(struct ipcp *, u_int32_t);
+extern void ipcp_SetLink(struct ipcp *, struct link *);
+
+extern int  ipcp_Show(struct cmdargs const *);
+extern struct mbuf *ipcp_Input(struct bundle *, struct link *, struct mbuf *);
+extern void ipcp_AddInOctets(struct ipcp *, int);
+extern void ipcp_AddOutOctets(struct ipcp *, int);
+extern int  ipcp_UseHisIPaddr(struct bundle *, struct in_addr);
+extern int  ipcp_UseHisaddr(struct bundle *, const char *, int);
+extern int  ipcp_vjset(struct cmdargs const *);
+extern void ipcp_IfaceAddrAdded(struct ipcp *, const struct iface_addr *);
+extern void ipcp_IfaceAddrDeleted(struct ipcp *, const struct iface_addr *);
+extern int  ipcp_InterfaceUp(struct ipcp *);
+extern struct in_addr addr2mask(struct in_addr);
+extern int ipcp_WriteDNS(struct ipcp *);
+extern void ipcp_RestoreDNS(struct ipcp *);
+extern void ipcp_LoadDNS(struct ipcp *);
+extern size_t ipcp_QueueLen(struct ipcp *);
+extern int ipcp_PushPacket(struct ipcp *, struct link *);
diff --git a/src/iplist.c b/src/iplist.c
new file mode 100644
index 0000000..0f93636
--- /dev/null
+++ b/src/iplist.c
@@ -0,0 +1,225 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/iplist.c,v 1.10.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#include "log.h"
+#include "defs.h"
+#include "iplist.h"
+
+static int
+do_inet_aton(const char *start, const char *end, struct in_addr *ip)
+{
+  char ipstr[16];
+
+  if (end - start > 15) {
+    log_Printf(LogWARN, "%.*s: Invalid IP address\n", (int)(end-start), start);
+    return 0;
+  }
+  strncpy(ipstr, start, end-start);
+  ipstr[end-start] = '\0';
+  return inet_aton(ipstr, ip);
+}
+
+static void
+iplist_first(struct iplist *list)
+{
+  list->cur.pos = -1;
+}
+
+static int
+iplist_setrange(struct iplist *list, char *range)
+{
+  char *ptr, *to;
+
+  if ((ptr = strpbrk(range, ",-")) == NULL) {
+    if (!inet_aton(range, &list->cur.ip))
+      return 0;
+    list->cur.lstart = ntohl(list->cur.ip.s_addr);
+    list->cur.nItems = 1;
+  } else {
+    if (!do_inet_aton(range, ptr, &list->cur.ip))
+      return 0;
+    if (*ptr == ',') {
+      list->cur.lstart = ntohl(list->cur.ip.s_addr);
+      list->cur.nItems = 1;
+    } else {
+      struct in_addr endip;
+
+      to = ptr+1;
+      if ((ptr = strpbrk(to, ",-")) == NULL)
+        ptr = to + strlen(to);
+      if (*to == '-')
+        return 0;
+      if (!do_inet_aton(to, ptr, &endip))
+        return 0;
+      list->cur.lstart = ntohl(list->cur.ip.s_addr);
+      list->cur.nItems = ntohl(endip.s_addr) - list->cur.lstart + 1;
+      if (list->cur.nItems < 1)
+        return 0;
+    }
+  }
+  list->cur.srcitem = 0;
+  list->cur.srcptr = range;
+  return 1;
+}
+
+static int
+iplist_nextrange(struct iplist *list)
+{
+  char *ptr, *to, *end;
+
+  ptr = list->cur.srcptr;
+  if (ptr != NULL && (ptr = strchr(ptr, ',')) != NULL)
+    ptr++;
+  else
+    ptr = list->src;
+
+  while (*ptr != '\0' && !iplist_setrange(list, ptr)) {
+    if ((end = strchr(ptr, ',')) == NULL)
+      end = ptr + strlen(ptr);
+    if (end == ptr)
+      return 0;
+    log_Printf(LogWARN, "%.*s: Invalid IP range (skipping)\n",
+               (int)(end - ptr), ptr);
+    to = ptr;
+    do
+      *to = *end++;
+    while (*to++ != '\0');
+    if (*ptr == '\0')
+      ptr = list->src;
+  }
+
+  return 1;
+}
+
+struct in_addr
+iplist_next(struct iplist *list)
+{
+  if (list->cur.pos == -1) {
+    list->cur.srcptr = NULL;
+    if (!iplist_nextrange(list)) {
+      list->cur.ip.s_addr = INADDR_ANY;
+      return list->cur.ip;
+    }
+  } else if (++list->cur.srcitem == list->cur.nItems) {
+    if (!iplist_nextrange(list)) {
+      list->cur.ip.s_addr = INADDR_ANY;
+      list->cur.pos = -1;
+      return list->cur.ip;
+    }
+  } else
+    list->cur.ip.s_addr = htonl(list->cur.lstart + list->cur.srcitem);
+  list->cur.pos++;
+
+  return list->cur.ip;
+}
+
+int
+iplist_setsrc(struct iplist *list, const char *src)
+{
+  strncpy(list->src, src, sizeof list->src - 1);
+  list->src[sizeof list->src - 1] = '\0';
+  list->cur.srcptr = list->src;
+  do {
+    if (iplist_nextrange(list))
+      list->nItems += list->cur.nItems;
+    else
+      return 0;
+  } while (list->cur.srcptr != list->src);
+  return 1;
+}
+
+void
+iplist_reset(struct iplist *list)
+{
+  list->src[0] = '\0';
+  list->nItems = 0;
+  list->cur.pos = -1;
+}
+
+struct in_addr
+iplist_setcurpos(struct iplist *list, long pos)
+{
+  if (pos < 0 || (unsigned)pos >= list->nItems) {
+    list->cur.pos = -1;
+    list->cur.ip.s_addr = INADDR_ANY;
+    return list->cur.ip;
+  }
+
+  list->cur.srcptr = NULL;
+  list->cur.pos = 0;
+  while (1) {
+    iplist_nextrange(list);
+    if (pos < (int)list->cur.nItems) {
+      if (pos) {
+        list->cur.srcitem = pos;
+        list->cur.pos += pos;
+        list->cur.ip.s_addr = htonl(list->cur.lstart + list->cur.srcitem);
+      }
+      break;
+    }
+    pos -= list->cur.nItems;
+    list->cur.pos += list->cur.nItems;
+  }
+
+  return list->cur.ip;
+}
+
+struct in_addr
+iplist_setrandpos(struct iplist *list)
+{
+  randinit();
+  return iplist_setcurpos(list, random() % list->nItems);
+}
+
+int
+iplist_ip2pos(struct iplist *list, struct in_addr ip)
+{
+  struct iplist_cur cur;
+  u_long f;
+  int result;
+
+  result = -1;
+  memcpy(&cur, &list->cur, sizeof cur);
+
+  for (iplist_first(list), f = 0; f < list->nItems; f++)
+    if (iplist_next(list).s_addr == ip.s_addr) {
+      result = list->cur.pos;
+      break;
+    }
+
+  memcpy(&list->cur, &cur, sizeof list->cur);
+  return result;
+}
diff --git a/src/iplist.h b/src/iplist.h
new file mode 100644
index 0000000..c31e48f
--- /dev/null
+++ b/src/iplist.h
@@ -0,0 +1,51 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/iplist.h,v 1.4.62.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct iplist_cur {
+  struct in_addr ip;
+  int pos;
+  char *srcptr;
+  u_long srcitem;
+  u_int32_t lstart;
+  u_long nItems;
+};
+
+struct iplist {
+  struct iplist_cur cur;
+  u_long nItems;
+  char src[LINE_LEN];
+};
+
+extern int iplist_setsrc(struct iplist *, const char *);
+extern void iplist_reset(struct iplist *);
+extern struct in_addr iplist_setcurpos(struct iplist *, long);
+extern struct in_addr iplist_setrandpos(struct iplist *);
+extern int iplist_ip2pos(struct iplist *, struct in_addr);
+extern struct in_addr iplist_next(struct iplist *);
+
+#define iplist_isvalid(x) ((x)->src[0] != '\0')
diff --git a/src/ipv6cp.c b/src/ipv6cp.c
new file mode 100644
index 0000000..fccc602
--- /dev/null
+++ b/src/ipv6cp.c
@@ -0,0 +1,786 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ipv6cp.c,v 1.17.10.1.2.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <net/route.h>
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/if_dl.h>
+#include <sys/un.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <ifaddrs.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ncp.h"
+#include "bundle.h"
+#include "route.h"
+#include "iface.h"
+#include "log.h"
+#include "proto.h"
+#include "command.h"
+#include "prompt.h"
+#include "async.h"
+#include "physical.h"
+#include "probe.h"
+#include "systems.h"
+
+
+#ifndef NOINET6
+#define IN6ADDR_LINKLOCAL_MCAST_INIT \
+	{{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+	    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}}
+static const struct in6_addr in6addr_linklocal_mcast =
+	IN6ADDR_LINKLOCAL_MCAST_INIT;
+
+static int ipv6cp_LayerUp(struct fsm *);
+static void ipv6cp_LayerDown(struct fsm *);
+static void ipv6cp_LayerStart(struct fsm *);
+static void ipv6cp_LayerFinish(struct fsm *);
+static void ipv6cp_InitRestartCounter(struct fsm *, int);
+static void ipv6cp_SendConfigReq(struct fsm *);
+static void ipv6cp_SentTerminateReq(struct fsm *);
+static void ipv6cp_SendTerminateAck(struct fsm *, u_char);
+static void ipv6cp_DecodeConfig(struct fsm *, u_char *, u_char *, int,
+                                struct fsm_decode *);
+
+static struct fsm_callbacks ipv6cp_Callbacks = {
+  ipv6cp_LayerUp,
+  ipv6cp_LayerDown,
+  ipv6cp_LayerStart,
+  ipv6cp_LayerFinish,
+  ipv6cp_InitRestartCounter,
+  ipv6cp_SendConfigReq,
+  ipv6cp_SentTerminateReq,
+  ipv6cp_SendTerminateAck,
+  ipv6cp_DecodeConfig,
+  fsm_NullRecvResetReq,
+  fsm_NullRecvResetAck
+};
+
+static void
+SetInterfaceID(u_char *ifid, int userandom)
+{
+  struct ifaddrs *ifa, *ifap = NULL;
+  struct sockaddr_dl *sdl;
+  const u_long i32_max = 0xffffffff;
+  u_long r1, r2;
+
+  /* configure an interface ID based on Section 4.1 of RFC 2472 */
+  memset(ifid, 0, IPV6CP_IFIDLEN);
+
+  /*
+   * 1) If an IEEE global identifier (EUI-48 or EUI-64) is
+   * available anywhere on the node, it should be used to construct
+   * the tentative Interface-Identifier due to its uniqueness
+   * properties.
+   */
+  if (userandom)
+    goto randomid;
+  if (getifaddrs(&ifap) < 0)
+    goto randomid;
+	
+  for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+    char *cp;
+
+    if (ifa->ifa_addr->sa_family != AF_LINK)
+      continue;
+
+    sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+    if (sdl->sdl_alen < 6)
+      continue;
+    /* we're only interested in IEEE hardware addresses */
+    switch(sdl->sdl_type) {
+    case IFT_ETHER:
+    case IFT_FDDI:
+    case IFT_L2VLAN:
+      /* XXX need more cases? */
+      break;
+    default:
+      continue;
+    }
+
+    cp = (char *)(sdl->sdl_data + sdl->sdl_nlen);
+    ifid[0] = cp[0];
+    ifid[0] ^= 0x02; /* reverse the u/l bit*/
+    ifid[1] = cp[1];
+    ifid[2] = cp[2];
+    ifid[3] = 0xff;
+    ifid[4] = 0xfe;
+    ifid[5] = cp[3];
+    ifid[6] = cp[4];
+    ifid[7] = cp[5];
+
+    freeifaddrs(ifap);
+    return;
+  }
+
+  freeifaddrs(ifap);
+
+  /*
+   * 2) If an IEEE global identifier is not available a different source
+   * of uniqueness should be used.
+   * XXX: we skip this case.
+   */
+
+  /*
+   * 3) If a good source of uniqueness cannot be found, it is
+   * recommended that a random number be generated.  In this case the
+   * "u" bit of the interface identifier MUST be set to zero (0).
+   */
+ randomid:
+  randinit();
+  r1 = (((u_long)random()) % i32_max) + 1;
+  r2 = (((u_long)random()) % i32_max) + 1;
+  memcpy(ifid, &r1, sizeof(r1));
+  memcpy(ifid + 4, &r2, sizeof(r2));
+  ifid[0] &= 0xfd;
+  return;
+}
+
+static int
+ipcp_SetIPv6address(struct ipv6cp *ipv6cp, u_char *myifid, u_char *hisifid)
+{
+  struct bundle *bundle = ipv6cp->fsm.bundle;
+  struct in6_addr myaddr, hisaddr;
+  struct ncprange myrange, range;
+  struct ncpaddr addr;
+  struct sockaddr_storage ssdst, ssgw, ssmask;
+  struct sockaddr *sadst, *sagw, *samask;
+
+  sadst = (struct sockaddr *)&ssdst;
+  sagw = (struct sockaddr *)&ssgw;
+  samask = (struct sockaddr *)&ssmask;
+
+  memset(&myaddr, '\0', sizeof myaddr);
+  memset(&hisaddr, '\0', sizeof hisaddr);
+
+  myaddr.s6_addr[0] = 0xfe;
+  myaddr.s6_addr[1] = 0x80;
+  memcpy(&myaddr.s6_addr[8], myifid, IPV6CP_IFIDLEN);
+#if 0
+  myaddr.s6_addr[8] |= 0x02;	/* set 'universal' bit */
+#endif
+
+  hisaddr.s6_addr[0] = 0xfe;
+  hisaddr.s6_addr[1] = 0x80;
+  memcpy(&hisaddr.s6_addr[8], hisifid, IPV6CP_IFIDLEN);
+#if 0
+  hisaddr.s6_addr[8] |= 0x02;	/* set 'universal' bit */
+#endif
+
+  ncpaddr_setip6(&ipv6cp->myaddr, &myaddr);
+  ncpaddr_setip6(&ipv6cp->hisaddr, &hisaddr);
+  ncprange_set(&myrange, &ipv6cp->myaddr, 64);
+
+  if (!iface_Add(bundle->iface, &bundle->ncp, &myrange, &ipv6cp->hisaddr,
+                 IFACE_ADD_FIRST|IFACE_FORCE_ADD|IFACE_SYSTEM))
+    return 0;
+
+  if (!Enabled(bundle, OPT_IFACEALIAS))
+    iface_Clear(bundle->iface, &bundle->ncp, AF_INET6,
+                IFACE_CLEAR_ALIASES|IFACE_SYSTEM);
+
+  ncpaddr_setip6(&addr, &in6addr_linklocal_mcast);
+  ncprange_set(&range, &addr, 32);
+  rt_Set(bundle, RTM_ADD, &range, &ipv6cp->myaddr, 1, 0);
+
+  if (bundle->ncp.cfg.sendpipe > 0 || bundle->ncp.cfg.recvpipe > 0) {
+    ncprange_getsa(&myrange, &ssgw, &ssmask);
+    if (ncpaddr_isset(&ipv6cp->hisaddr))
+      ncpaddr_getsa(&ipv6cp->hisaddr, &ssdst);
+    else
+      sadst = NULL;
+    rt_Update(bundle, sadst, sagw, samask);
+  }
+
+  if (Enabled(bundle, OPT_SROUTES))
+    route_Change(bundle, bundle->ncp.route, &ipv6cp->myaddr, &ipv6cp->hisaddr);
+
+#ifndef NORADIUS
+  if (bundle->radius.valid)
+    route_Change(bundle, bundle->radius.ipv6routes, &ipv6cp->myaddr,
+                 &ipv6cp->hisaddr);
+#endif
+
+  return 1;	/* Ok */
+}
+
+void
+ipv6cp_Init(struct ipv6cp *ipv6cp, struct bundle *bundle, struct link *l,
+                 const struct fsm_parent *parent)
+{
+  static const char * const timer_names[] =
+    {"IPV6CP restart", "IPV6CP openmode", "IPV6CP stopped"};
+  int n;
+
+  fsm_Init(&ipv6cp->fsm, "IPV6CP", PROTO_IPV6CP, 1, IPV6CP_MAXCODE, LogIPV6CP,
+           bundle, l, parent, &ipv6cp_Callbacks, timer_names);
+
+  ipv6cp->cfg.fsm.timeout = DEF_FSMRETRY;
+  ipv6cp->cfg.fsm.maxreq = DEF_FSMTRIES;
+  ipv6cp->cfg.fsm.maxtrm = DEF_FSMTRIES;
+
+  SetInterfaceID(ipv6cp->my_ifid, 0);
+  do {
+    SetInterfaceID(ipv6cp->his_ifid, 1);
+  } while (memcmp(ipv6cp->his_ifid, ipv6cp->my_ifid, IPV6CP_IFIDLEN) == 0);
+
+  if (probe.ipv6_available) {
+    n = 100;
+    while (n &&
+           !ipcp_SetIPv6address(ipv6cp, ipv6cp->my_ifid, ipv6cp->his_ifid)) {
+      do {
+	n--;
+    	SetInterfaceID(ipv6cp->my_ifid, 1);
+      } while (n
+	&& memcmp(ipv6cp->his_ifid, ipv6cp->my_ifid, IPV6CP_IFIDLEN) == 0);
+    }
+  }
+
+  throughput_init(&ipv6cp->throughput, SAMPLE_PERIOD);
+  memset(ipv6cp->Queue, '\0', sizeof ipv6cp->Queue);
+  ipv6cp_Setup(ipv6cp);
+}
+
+void
+ipv6cp_Destroy(struct ipv6cp *ipv6cp)
+{
+  throughput_destroy(&ipv6cp->throughput);
+}
+
+void
+ipv6cp_Setup(struct ipv6cp *ipv6cp)
+{
+  ncpaddr_init(&ipv6cp->myaddr);
+  ncpaddr_init(&ipv6cp->hisaddr);
+
+  ipv6cp->his_reject = 0;
+  ipv6cp->my_reject = 0;
+}
+
+void
+ipv6cp_SetLink(struct ipv6cp *ipv6cp, struct link *l)
+{
+  ipv6cp->fsm.link = l;
+}
+
+int
+ipv6cp_Show(struct cmdargs const *arg)
+{
+  struct ipv6cp *ipv6cp = &arg->bundle->ncp.ipv6cp;
+
+  prompt_Printf(arg->prompt, "%s [%s]\n", ipv6cp->fsm.name,
+                State2Nam(ipv6cp->fsm.state));
+  if (ipv6cp->fsm.state == ST_OPENED) {
+    prompt_Printf(arg->prompt, " His side:        %s\n",
+                  ncpaddr_ntoa(&ipv6cp->hisaddr));
+    prompt_Printf(arg->prompt, " My side:         %s\n",
+                  ncpaddr_ntoa(&ipv6cp->myaddr));
+    prompt_Printf(arg->prompt, " Queued packets:  %lu\n",
+                  (unsigned long)ipv6cp_QueueLen(ipv6cp));
+  }
+
+  prompt_Printf(arg->prompt, "\nDefaults:\n");
+  prompt_Printf(arg->prompt, "  FSM retry = %us, max %u Config"
+                " REQ%s, %u Term REQ%s\n\n", ipv6cp->cfg.fsm.timeout,
+                ipv6cp->cfg.fsm.maxreq, ipv6cp->cfg.fsm.maxreq == 1 ? "" : "s",
+                ipv6cp->cfg.fsm.maxtrm, ipv6cp->cfg.fsm.maxtrm == 1 ? "" : "s");
+
+  throughput_disp(&ipv6cp->throughput, arg->prompt);
+
+  return 0;
+}
+
+struct mbuf *
+ipv6cp_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+  /* Got PROTO_IPV6CP from link */
+  m_settype(bp, MB_IPV6CPIN);
+  if (bundle_Phase(bundle) == PHASE_NETWORK)
+    fsm_Input(&bundle->ncp.ipv6cp.fsm, bp);
+  else {
+    if (bundle_Phase(bundle) < PHASE_NETWORK)
+      log_Printf(LogIPV6CP, "%s: Error: Unexpected IPV6CP in phase %s"
+                 " (ignored)\n", l->name, bundle_PhaseName(bundle));
+    m_freem(bp);
+  }
+  return NULL;
+}
+
+void
+ipv6cp_AddInOctets(struct ipv6cp *ipv6cp, int n)
+{
+  throughput_addin(&ipv6cp->throughput, n);
+}
+
+void
+ipv6cp_AddOutOctets(struct ipv6cp *ipv6cp, int n)
+{
+  throughput_addout(&ipv6cp->throughput, n);
+}
+
+void
+ipv6cp_IfaceAddrAdded(struct ipv6cp *ipv6cp __unused,
+		      const struct iface_addr *addr __unused)
+{
+}
+
+void
+ipv6cp_IfaceAddrDeleted(struct ipv6cp *ipv6cp __unused,
+			const struct iface_addr *addr __unused)
+{
+}
+
+int
+ipv6cp_InterfaceUp(struct ipv6cp *ipv6cp)
+{
+  if (!ipcp_SetIPv6address(ipv6cp, ipv6cp->my_ifid, ipv6cp->his_ifid)) {
+    log_Printf(LogERROR, "ipv6cp_InterfaceUp: unable to set ipv6 address\n");
+    return 0;
+  }
+
+  if (!iface_SetFlags(ipv6cp->fsm.bundle->iface->name, IFF_UP)) {
+    log_Printf(LogERROR, "ipv6cp_InterfaceUp: Can't set the IFF_UP"
+               " flag on %s\n", ipv6cp->fsm.bundle->iface->name);
+    return 0;
+  }
+
+  return 1;
+}
+
+size_t
+ipv6cp_QueueLen(struct ipv6cp *ipv6cp)
+{
+  struct mqueue *q;
+  size_t result;
+
+  result = 0;
+  for (q = ipv6cp->Queue; q < ipv6cp->Queue + IPV6CP_QUEUES(ipv6cp); q++)
+    result += q->len;
+
+  return result;
+}
+
+int
+ipv6cp_PushPacket(struct ipv6cp *ipv6cp, struct link *l)
+{
+  struct bundle *bundle = ipv6cp->fsm.bundle;
+  struct mqueue *queue;
+  struct mbuf *bp;
+  int m_len;
+  u_int32_t secs = 0;
+  unsigned alivesecs = 0;
+
+  if (ipv6cp->fsm.state != ST_OPENED)
+    return 0;
+
+  /*
+   * If ccp is not open but is required, do nothing.
+   */
+  if (l->ccp.fsm.state != ST_OPENED && ccp_Required(&l->ccp)) {
+    log_Printf(LogPHASE, "%s: Not transmitting... waiting for CCP\n", l->name);
+    return 0;
+  }
+
+  queue = ipv6cp->Queue + IPV6CP_QUEUES(ipv6cp) - 1;
+  do {
+    if (queue->top) {
+      bp = m_dequeue(queue);
+      bp = mbuf_Read(bp, &secs, sizeof secs);
+      bp = m_pullup(bp);
+      m_len = m_length(bp);
+      if (!FilterCheck(MBUF_CTOP(bp), AF_INET6, &bundle->filter.alive,
+                       &alivesecs)) {
+        if (secs == 0)
+          secs = alivesecs;
+        bundle_StartIdleTimer(bundle, secs);
+      }
+      link_PushPacket(l, bp, bundle, 0, PROTO_IPV6);
+      ipv6cp_AddOutOctets(ipv6cp, m_len);
+      return 1;
+    }
+  } while (queue-- != ipv6cp->Queue);
+
+  return 0;
+}
+
+static int
+ipv6cp_LayerUp(struct fsm *fp)
+{
+  /* We're now up */
+  struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+  char tbuff[40];
+
+  log_Printf(LogIPV6CP, "%s: LayerUp.\n", fp->link->name);
+  if (!ipv6cp_InterfaceUp(ipv6cp))
+    return 0;
+
+  snprintf(tbuff, sizeof tbuff, "%s", ncpaddr_ntoa(&ipv6cp->myaddr));
+  log_Printf(LogIPV6CP, "myaddr %s hisaddr = %s\n",
+             tbuff, ncpaddr_ntoa(&ipv6cp->hisaddr));
+
+#ifndef NORADIUS
+  radius_Account_Set_Ipv6(&fp->bundle->radacct6, ipv6cp->his_ifid);
+  radius_Account(&fp->bundle->radius, &fp->bundle->radacct6,
+		 fp->bundle->links, RAD_START, &ipv6cp->throughput);
+
+  /*
+   * XXX: Avoid duplicate evaluation of filterid between IPCP and
+   * IPV6CP.  When IPCP is enabled and rejected, filterid is not
+   * evaluated.
+   */
+  if (!Enabled(fp->bundle, OPT_IPCP)) {
+    if (fp->bundle->radius.cfg.file && fp->bundle->radius.filterid)
+      system_Select(fp->bundle, fp->bundle->radius.filterid, LINKUPFILE,
+		    NULL, NULL);
+  }
+#endif
+
+  /*
+   * XXX this stuff should really live in the FSM.  Our config should
+   * associate executable sections in files with events.
+   */
+  if (system_Select(fp->bundle, tbuff, LINKUPFILE, NULL, NULL) < 0) {
+    /*
+     * XXX: Avoid duplicate evaluation of label between IPCP and
+     * IPV6CP.  When IPCP is enabled and rejected, label is not
+     * evaluated.
+     */
+    if (bundle_GetLabel(fp->bundle) && !Enabled(fp->bundle, OPT_IPCP)) {
+      if (system_Select(fp->bundle, bundle_GetLabel(fp->bundle),
+			LINKUPFILE, NULL, NULL) < 0)
+	system_Select(fp->bundle, "MYADDR6", LINKUPFILE, NULL, NULL);
+    } else
+      system_Select(fp->bundle, "MYADDR6", LINKUPFILE, NULL, NULL);
+  }
+
+  fp->more.reqs = fp->more.naks = fp->more.rejs = ipv6cp->cfg.fsm.maxreq * 3;
+  log_DisplayPrompts();
+
+  return 1;
+}
+
+static void
+ipv6cp_LayerDown(struct fsm *fp)
+{
+  /* About to come down */
+  struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+  static int recursing;
+  char addr[40];
+
+  if (!recursing++) {
+    snprintf(addr, sizeof addr, "%s", ncpaddr_ntoa(&ipv6cp->myaddr));
+    log_Printf(LogIPV6CP, "%s: LayerDown: %s\n", fp->link->name, addr);
+
+#ifndef NORADIUS
+    radius_Flush(&fp->bundle->radius);
+    radius_Account(&fp->bundle->radius, &fp->bundle->radacct6,
+		   fp->bundle->links, RAD_STOP, &ipv6cp->throughput);
+
+    /*
+     * XXX: Avoid duplicate evaluation of filterid between IPCP and
+     * IPV6CP.  When IPCP is enabled and rejected, filterid is not
+     * evaluated.
+     */
+    if (!Enabled(fp->bundle, OPT_IPCP)) {
+      if (fp->bundle->radius.cfg.file && fp->bundle->radius.filterid)
+	system_Select(fp->bundle, fp->bundle->radius.filterid, LINKDOWNFILE,
+		      NULL, NULL);
+    }
+#endif
+
+    /*
+     * XXX this stuff should really live in the FSM.  Our config should
+     * associate executable sections in files with events.
+     */
+    if (system_Select(fp->bundle, addr, LINKDOWNFILE, NULL, NULL) < 0) {
+      /*
+       * XXX: Avoid duplicate evaluation of label between IPCP and
+       * IPV6CP.  When IPCP is enabled and rejected, label is not
+       * evaluated.
+       */
+      if (bundle_GetLabel(fp->bundle) && !Enabled(fp->bundle, OPT_IPCP)) {
+	if (system_Select(fp->bundle, bundle_GetLabel(fp->bundle),
+			  LINKDOWNFILE, NULL, NULL) < 0)
+	  system_Select(fp->bundle, "MYADDR6", LINKDOWNFILE, NULL, NULL);
+      } else
+	system_Select(fp->bundle, "MYADDR6", LINKDOWNFILE, NULL, NULL);
+    }
+
+    ipv6cp_Setup(ipv6cp);
+  }
+  recursing--;
+}
+
+static void
+ipv6cp_LayerStart(struct fsm *fp)
+{
+  /* We're about to start up ! */
+  struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+
+  log_Printf(LogIPV6CP, "%s: LayerStart.\n", fp->link->name);
+  throughput_start(&ipv6cp->throughput, "IPV6CP throughput",
+                   Enabled(fp->bundle, OPT_THROUGHPUT));
+  fp->more.reqs = fp->more.naks = fp->more.rejs = ipv6cp->cfg.fsm.maxreq * 3;
+  ipv6cp->peer_tokenreq = 0;
+}
+
+static void
+ipv6cp_LayerFinish(struct fsm *fp)
+{
+  /* We're now down */
+  struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+
+  log_Printf(LogIPV6CP, "%s: LayerFinish.\n", fp->link->name);
+  throughput_stop(&ipv6cp->throughput);
+  throughput_log(&ipv6cp->throughput, LogIPV6CP, NULL);
+}
+
+static void
+ipv6cp_InitRestartCounter(struct fsm *fp, int what)
+{
+  /* Set fsm timer load */
+  struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+
+  fp->FsmTimer.load = ipv6cp->cfg.fsm.timeout * SECTICKS;
+  switch (what) {
+    case FSM_REQ_TIMER:
+      fp->restart = ipv6cp->cfg.fsm.maxreq;
+      break;
+    case FSM_TRM_TIMER:
+      fp->restart = ipv6cp->cfg.fsm.maxtrm;
+      break;
+    default:
+      fp->restart = 1;
+      break;
+  }
+}
+
+static void
+ipv6cp_SendConfigReq(struct fsm *fp)
+{
+  /* Send config REQ please */
+  struct physical *p = link2physical(fp->link);
+  struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+  u_char buff[IPV6CP_IFIDLEN+2];
+  struct fsm_opt *o;
+
+  o = (struct fsm_opt *)buff;
+
+  if ((p && !physical_IsSync(p)) || !REJECTED(ipv6cp, TY_TOKEN)) {
+    memcpy(o->data, ipv6cp->my_ifid, IPV6CP_IFIDLEN);
+    INC_FSM_OPT(TY_TOKEN, IPV6CP_IFIDLEN + 2, o);
+  }
+
+  fsm_Output(fp, CODE_CONFIGREQ, fp->reqid, buff, (u_char *)o - buff,
+             MB_IPV6CPOUT);
+}
+
+static void
+ipv6cp_SentTerminateReq(struct fsm *fp __unused)
+{
+  /* Term REQ just sent by FSM */
+}
+
+static void
+ipv6cp_SendTerminateAck(struct fsm *fp, u_char id)
+{
+  /* Send Term ACK please */
+  fsm_Output(fp, CODE_TERMACK, id, NULL, 0, MB_IPV6CPOUT);
+}
+
+static const char *
+protoname(unsigned proto)
+{
+  static const char *cftypes[] = { "IFACEID", "COMPPROTO" };
+
+  if (proto > 0 && proto <= sizeof cftypes / sizeof *cftypes)
+    return cftypes[proto - 1];
+
+  return NumStr(proto, NULL, 0);
+}
+
+static void
+ipv6cp_ValidateInterfaceID(struct ipv6cp *ipv6cp, u_char *ifid,
+			   struct fsm_decode *dec)
+{
+  struct fsm_opt opt;
+  u_char zero[IPV6CP_IFIDLEN];
+
+  memset(zero, 0, IPV6CP_IFIDLEN);
+
+  if (memcmp(ifid, zero, IPV6CP_IFIDLEN) != 0
+      && memcmp(ifid, ipv6cp->my_ifid, IPV6CP_IFIDLEN) != 0)
+    memcpy(ipv6cp->his_ifid, ifid, IPV6CP_IFIDLEN);
+
+  opt.hdr.id = TY_TOKEN;
+  opt.hdr.len = IPV6CP_IFIDLEN + 2;
+  memcpy(opt.data, &ipv6cp->his_ifid, IPV6CP_IFIDLEN);
+  if (memcmp(ifid, ipv6cp->his_ifid, IPV6CP_IFIDLEN) == 0)
+    fsm_ack(dec, &opt);
+  else
+    fsm_nak(dec, &opt);
+}
+
+static void
+ipv6cp_DecodeConfig(struct fsm *fp, u_char *cp, u_char *end, int mode_type,
+                    struct fsm_decode *dec)
+{
+  /* Deal with incoming PROTO_IPV6CP */
+  struct ipv6cp *ipv6cp = fsm2ipv6cp(fp);
+  int n;
+  char tbuff[100];
+  u_char ifid[IPV6CP_IFIDLEN], zero[IPV6CP_IFIDLEN];
+  struct fsm_opt *opt;
+
+  memset(zero, 0, IPV6CP_IFIDLEN);
+
+  while (end - cp >= (int)sizeof(opt->hdr)) {
+    if ((opt = fsm_readopt(&cp)) == NULL)
+      break;
+
+    snprintf(tbuff, sizeof tbuff, " %s[%d]", protoname(opt->hdr.id),
+             opt->hdr.len);
+
+    switch (opt->hdr.id) {
+    case TY_TOKEN:
+      memcpy(ifid, opt->data, IPV6CP_IFIDLEN);
+      log_Printf(LogIPV6CP, "%s 0x%02x%02x%02x%02x%02x%02x%02x%02x\n", tbuff,
+		 ifid[0], ifid[1], ifid[2], ifid[3], ifid[4], ifid[5], ifid[6], ifid[7]);
+
+      switch (mode_type) {
+      case MODE_REQ:
+        ipv6cp->peer_tokenreq = 1;
+        ipv6cp_ValidateInterfaceID(ipv6cp, ifid, dec);
+        break;
+
+      case MODE_NAK:
+        if (memcmp(ifid, zero, IPV6CP_IFIDLEN) == 0) {
+          log_Printf(log_IsKept(LogIPV6CP) ? LogIPV6CP : LogPHASE,
+		     "0x0000000000000000: Unacceptable IntefaceID!\n");
+          fsm_Close(&ipv6cp->fsm);
+        } else if (memcmp(ifid, ipv6cp->his_ifid, IPV6CP_IFIDLEN) == 0) {
+          log_Printf(log_IsKept(LogIPV6CP) ? LogIPV6CP : LogPHASE,
+		     "0x%02x%02x%02x%02x%02x%02x%02x%02x: "
+		     "Unacceptable IntefaceID!\n",
+		     ifid[0], ifid[1], ifid[2], ifid[3],
+		     ifid[4], ifid[5], ifid[6], ifid[7]);
+        } else if (memcmp(ifid, ipv6cp->my_ifid, IPV6CP_IFIDLEN) != 0) {
+          n = 100;
+	  while (n && !ipcp_SetIPv6address(ipv6cp, ifid, ipv6cp->his_ifid)) {
+	    do {
+	      n--;
+	      SetInterfaceID(ifid, 1);
+	    } while (n && memcmp(ifid, ipv6cp->his_ifid, IPV6CP_IFIDLEN) == 0);
+	  }
+
+          if (n == 0) {
+            log_Printf(log_IsKept(LogIPV6CP) ? LogIPV6CP : LogPHASE,
+                       "0x0000000000000000: Unacceptable IntefaceID!\n");
+            fsm_Close(&ipv6cp->fsm);
+          } else {
+	    log_Printf(LogIPV6CP, "%s changing IntefaceID: "
+		       "0x%02x%02x%02x%02x%02x%02x%02x%02x "
+		       "--> 0x%02x%02x%02x%02x%02x%02x%02x%02x\n", tbuff,
+		       ipv6cp->my_ifid[0], ipv6cp->my_ifid[1],
+		       ipv6cp->my_ifid[2], ipv6cp->my_ifid[3],
+		       ipv6cp->my_ifid[4], ipv6cp->my_ifid[5],
+		       ipv6cp->my_ifid[6], ipv6cp->my_ifid[7],
+		       ifid[0], ifid[1], ifid[2], ifid[3],
+		       ifid[4], ifid[5], ifid[6], ifid[7]);
+            memcpy(ipv6cp->my_ifid, ifid, IPV6CP_IFIDLEN);
+            bundle_AdjustFilters(fp->bundle, &ipv6cp->myaddr, NULL);
+          }
+        }
+        break;
+
+      case MODE_REJ:
+        ipv6cp->his_reject |= (1 << opt->hdr.id);
+        break;
+      }
+      break;
+
+    default:
+      if (mode_type != MODE_NOP) {
+        ipv6cp->my_reject |= (1 << opt->hdr.id);
+        fsm_rej(dec, opt);
+      }
+      break;
+    }
+  }
+
+  if (mode_type != MODE_NOP) {
+    if (mode_type == MODE_REQ && !ipv6cp->peer_tokenreq) {
+      if (dec->rejend == dec->rej && dec->nakend == dec->nak) {
+        /*
+         * Pretend the peer has requested a TOKEN.
+         * We do this to ensure that we only send one NAK if the only
+         * reason for the NAK is because the peer isn't sending a
+         * TY_TOKEN REQ.  This stops us from repeatedly trying to tell
+         * the peer that we have to have an IP address on their end.
+         */
+        ipv6cp->peer_tokenreq = 1;
+      }
+      memset(ifid, 0, IPV6CP_IFIDLEN);
+      ipv6cp_ValidateInterfaceID(ipv6cp, ifid, dec);
+    }
+    fsm_opt_normalise(dec);
+  }
+}
+#endif
diff --git a/src/ipv6cp.h b/src/ipv6cp.h
new file mode 100644
index 0000000..57188fe
--- /dev/null
+++ b/src/ipv6cp.h
@@ -0,0 +1,83 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ipv6cp.h,v 1.2.42.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#ifndef NOINET6
+#define	IPV6CP_MAXCODE	CODE_CODEREJ
+
+#define	TY_TOKEN	1
+#define	TY_COMPPROTO	2
+
+#define	IPV6CP_IFIDLEN	8		/* RFC2472 */
+
+struct ipv6cp {
+  struct fsm fsm;			/* The finite state machine */
+
+  struct {
+    struct fsm_retry fsm;		/* frequency to resend requests */
+  } cfg;
+
+  unsigned peer_tokenreq : 1;		/* Any TY_TOKEN REQs from the peer ? */
+
+  u_char my_ifid[IPV6CP_IFIDLEN];	/* Local Interface Identifier */
+  u_char his_ifid[IPV6CP_IFIDLEN];	/* Peer Interface Identifier */
+
+  struct ncpaddr myaddr;		/* Local address */
+  struct ncpaddr hisaddr;		/* Peer address */
+
+  u_int32_t his_reject;			/* Request codes rejected by peer */
+  u_int32_t my_reject;			/* Request codes I have rejected */
+
+  struct pppThroughput throughput;	/* throughput statistics */
+  struct mqueue Queue[2];		/* Output packet queues */
+};
+
+#define fsm2ipv6cp(fp) (fp->proto == PROTO_IPV6CP ? (struct ipv6cp *)fp : NULL)
+#define IPV6CP_QUEUES(ipv6cp) (sizeof ipv6cp->Queue / sizeof ipv6cp->Queue[0])
+
+struct bundle;
+struct link;
+struct cmdargs;
+struct iface_addr;
+
+extern void ipv6cp_Init(struct ipv6cp *, struct bundle *, struct link *,
+                        const struct fsm_parent *);
+extern void ipv6cp_Destroy(struct ipv6cp *);
+extern void ipv6cp_Setup(struct ipv6cp *);
+extern void ipv6cp_SetLink(struct ipv6cp *, struct link *);
+
+extern int  ipv6cp_Show(struct cmdargs const *);
+extern struct mbuf *ipv6cp_Input(struct bundle *, struct link *, struct mbuf *);
+extern void ipv6cp_AddInOctets(struct ipv6cp *, int);
+extern void ipv6cp_AddOutOctets(struct ipv6cp *, int);
+
+extern void ipv6cp_IfaceAddrAdded(struct ipv6cp *, const struct iface_addr *);
+extern void ipv6cp_IfaceAddrDeleted(struct ipv6cp *, const struct iface_addr *);
+extern int  ipv6cp_InterfaceUp(struct ipv6cp *);
+extern size_t ipv6cp_QueueLen(struct ipv6cp *);
+extern int ipv6cp_PushPacket(struct ipv6cp *, struct link *);
+#endif
diff --git a/src/layer.h b/src/layer.h
new file mode 100644
index 0000000..9920cb0
--- /dev/null
+++ b/src/layer.h
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/layer.h,v 1.3.62.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define	LAYER_ASYNC	2
+#define	LAYER_SYNC	3
+#define	LAYER_HDLC	4
+#define	LAYER_ACF	5
+#define	LAYER_PROTO	6
+#define	LAYER_LQR	7
+#define	LAYER_CCP	8
+#define	LAYER_VJ	9
+#define	LAYER_NAT	10
+
+#define	LAYER_MAX	10	/* How many layers we can handle on a link */
+
+struct mbuf;
+struct link;
+struct bundle;
+
+struct layer {
+  int type;
+  const char *name;
+  struct mbuf *(*push)(struct bundle *, struct link *, struct mbuf *,
+                       int pri, u_short *proto);
+  struct mbuf *(*pull)(struct bundle *, struct link *, struct mbuf *,
+                       u_short *);
+};
diff --git a/src/lcp.c b/src/lcp.c
new file mode 100644
index 0000000..2b59d99
--- /dev/null
+++ b/src/lcp.c
@@ -0,0 +1,1305 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/lcp.c,v 1.110.14.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "ua.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "proto.h"
+#include "descriptor.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "async.h"
+#include "link.h"
+#include "physical.h"
+#include "prompt.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "mp.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+/* for received LQRs */
+struct lqrreq {
+  struct fsm_opt_hdr hdr;
+  u_short proto;		/* Quality protocol */
+  u_int32_t period;		/* Reporting interval */
+};
+
+static int LcpLayerUp(struct fsm *);
+static void LcpLayerDown(struct fsm *);
+static void LcpLayerStart(struct fsm *);
+static void LcpLayerFinish(struct fsm *);
+static void LcpInitRestartCounter(struct fsm *, int);
+static void LcpSendConfigReq(struct fsm *);
+static void LcpSentTerminateReq(struct fsm *);
+static void LcpSendTerminateAck(struct fsm *, u_char);
+static void LcpDecodeConfig(struct fsm *, u_char *, u_char *, int,
+                            struct fsm_decode *);
+
+static struct fsm_callbacks lcp_Callbacks = {
+  LcpLayerUp,
+  LcpLayerDown,
+  LcpLayerStart,
+  LcpLayerFinish,
+  LcpInitRestartCounter,
+  LcpSendConfigReq,
+  LcpSentTerminateReq,
+  LcpSendTerminateAck,
+  LcpDecodeConfig,
+  fsm_NullRecvResetReq,
+  fsm_NullRecvResetAck
+};
+
+static const char * const lcp_TimerNames[] =
+  {"LCP restart", "LCP openmode", "LCP stopped"};
+
+static const char *
+protoname(unsigned proto)
+{
+  static const char * const cftypes[] = {
+    /* Check out the latest ``Assigned numbers'' rfc (1700) */
+    NULL,
+    "MRU",		/* 1: Maximum-Receive-Unit */
+    "ACCMAP",		/* 2: Async-Control-Character-Map */
+    "AUTHPROTO",	/* 3: Authentication-Protocol */
+    "QUALPROTO",	/* 4: Quality-Protocol */
+    "MAGICNUM",		/* 5: Magic-Number */
+    "RESERVED",		/* 6: RESERVED */
+    "PROTOCOMP",	/* 7: Protocol-Field-Compression */
+    "ACFCOMP",		/* 8: Address-and-Control-Field-Compression */
+    "FCSALT",		/* 9: FCS-Alternatives */
+    "SDP",		/* 10: Self-Describing-Pad */
+    "NUMMODE",		/* 11: Numbered-Mode */
+    "MULTIPROC",	/* 12: Multi-Link-Procedure */
+    "CALLBACK",		/* 13: Callback */
+    "CONTIME",		/* 14: Connect-Time */
+    "COMPFRAME",	/* 15: Compound-Frames */
+    "NDE",		/* 16: Nominal-Data-Encapsulation */
+    "MRRU",		/* 17: Multilink-MRRU */
+    "SHORTSEQ",		/* 18: Multilink-Short-Sequence-Number-Header */
+    "ENDDISC",		/* 19: Multilink-Endpoint-Discriminator */
+    "PROPRIETRY",	/* 20: Proprietary */
+    "DCEID",		/* 21: DCE-Identifier */
+    "MULTIPP",		/* 22: Multi-Link-Plus-Procedure */
+    "LDBACP",		/* 23: Link Discriminator for BACP */
+  };
+
+  if (proto > sizeof cftypes / sizeof *cftypes || cftypes[proto] == NULL)
+    return HexStr(proto, NULL, 0);
+
+  return cftypes[proto];
+}
+
+int
+lcp_ReportStatus(struct cmdargs const *arg)
+{
+  struct link *l;
+  struct lcp *lcp;
+
+  l = command_ChooseLink(arg);
+  lcp = &l->lcp;
+
+  prompt_Printf(arg->prompt, "%s: %s [%s]\n", l->name, lcp->fsm.name,
+                State2Nam(lcp->fsm.state));
+  prompt_Printf(arg->prompt,
+                " his side: MRU %d, ACCMAP %08lx, PROTOCOMP %s, ACFCOMP %s,\n"
+                "           MAGIC %08lx, MRRU %u, SHORTSEQ %s, REJECT %04x\n",
+                lcp->his_mru, (u_long)lcp->his_accmap,
+                lcp->his_protocomp ? "on" : "off",
+                lcp->his_acfcomp ? "on" : "off",
+                (u_long)lcp->his_magic, lcp->his_mrru,
+                lcp->his_shortseq ? "on" : "off", lcp->his_reject);
+  prompt_Printf(arg->prompt,
+                " my  side: MRU %d, ACCMAP %08lx, PROTOCOMP %s, ACFCOMP %s,\n"
+                "           MAGIC %08lx, MRRU %u, SHORTSEQ %s, REJECT %04x\n",
+                lcp->want_mru, (u_long)lcp->want_accmap,
+                lcp->want_protocomp ? "on" : "off",
+                lcp->want_acfcomp ? "on" : "off",
+                (u_long)lcp->want_magic, lcp->want_mrru,
+                lcp->want_shortseq ? "on" : "off", lcp->my_reject);
+
+  if (lcp->cfg.mru)
+    prompt_Printf(arg->prompt, "\n Defaults: MRU = %d (max %d), ",
+                  lcp->cfg.mru, lcp->cfg.max_mru);
+  else
+    prompt_Printf(arg->prompt, "\n Defaults: MRU = any (max %d), ",
+                  lcp->cfg.max_mru);
+  if (lcp->cfg.mtu)
+    prompt_Printf(arg->prompt, "MTU = %d (max %d), ",
+                  lcp->cfg.mtu, lcp->cfg.max_mtu);
+  else
+    prompt_Printf(arg->prompt, "MTU = any (max %d), ", lcp->cfg.max_mtu);
+  prompt_Printf(arg->prompt, "ACCMAP = %08lx\n", (u_long)lcp->cfg.accmap);
+  prompt_Printf(arg->prompt, "           LQR period = %us, ",
+                lcp->cfg.lqrperiod);
+  prompt_Printf(arg->prompt, "Open Mode = %s",
+                lcp->cfg.openmode == OPEN_PASSIVE ? "passive" : "active");
+  if (lcp->cfg.openmode > 0)
+    prompt_Printf(arg->prompt, " (delay %ds)", lcp->cfg.openmode);
+  prompt_Printf(arg->prompt, "\n           FSM retry = %us, max %u Config"
+                " REQ%s, %u Term REQ%s\n", lcp->cfg.fsm.timeout,
+                lcp->cfg.fsm.maxreq, lcp->cfg.fsm.maxreq == 1 ? "" : "s",
+                lcp->cfg.fsm.maxtrm, lcp->cfg.fsm.maxtrm == 1 ? "" : "s");
+  prompt_Printf(arg->prompt, "    Ident: %s\n", lcp->cfg.ident);
+  prompt_Printf(arg->prompt, "\n Negotiation:\n");
+  prompt_Printf(arg->prompt, "           ACFCOMP =   %s\n",
+                command_ShowNegval(lcp->cfg.acfcomp));
+  prompt_Printf(arg->prompt, "           CHAP =      %s\n",
+                command_ShowNegval(lcp->cfg.chap05));
+#ifndef NODES
+  prompt_Printf(arg->prompt, "           CHAP80 =    %s\n",
+                command_ShowNegval(lcp->cfg.chap80nt));
+  prompt_Printf(arg->prompt, "           LANMan =    %s\n",
+                command_ShowNegval(lcp->cfg.chap80lm));
+  prompt_Printf(arg->prompt, "           CHAP81 =    %s\n",
+                command_ShowNegval(lcp->cfg.chap81));
+#endif
+  prompt_Printf(arg->prompt, "           LQR =       %s\n",
+                command_ShowNegval(lcp->cfg.lqr));
+  prompt_Printf(arg->prompt, "           LCP ECHO =  %s\n",
+                lcp->cfg.echo ? "enabled" : "disabled");
+  prompt_Printf(arg->prompt, "           PAP =       %s\n",
+                command_ShowNegval(lcp->cfg.pap));
+  prompt_Printf(arg->prompt, "           PROTOCOMP = %s\n",
+                command_ShowNegval(lcp->cfg.protocomp));
+
+  return 0;
+}
+
+static u_int32_t
+GenerateMagic(void)
+{
+  /* Generate random number which will be used as magic number */
+  randinit();
+  return random();
+}
+
+void
+lcp_SetupCallbacks(struct lcp *lcp)
+{
+  lcp->fsm.fn = &lcp_Callbacks;
+  lcp->fsm.FsmTimer.name = lcp_TimerNames[0];
+  lcp->fsm.OpenTimer.name = lcp_TimerNames[1];
+  lcp->fsm.StoppedTimer.name = lcp_TimerNames[2];
+}
+
+void
+lcp_Init(struct lcp *lcp, struct bundle *bundle, struct link *l,
+         const struct fsm_parent *parent)
+{
+  /* Initialise ourselves */
+  int mincode = parent ? 1 : LCP_MINMPCODE;
+
+  fsm_Init(&lcp->fsm, "LCP", PROTO_LCP, mincode, LCP_MAXCODE, LogLCP,
+           bundle, l, parent, &lcp_Callbacks, lcp_TimerNames);
+
+  lcp->cfg.mru = 0;
+  lcp->cfg.max_mru = MAX_MRU;
+  lcp->cfg.mtu = 0;
+  lcp->cfg.max_mtu = MAX_MTU;
+  lcp->cfg.accmap = 0;
+  lcp->cfg.openmode = 1;
+  lcp->cfg.lqrperiod = DEF_LQRPERIOD;
+  lcp->cfg.fsm.timeout = DEF_FSMRETRY;
+  lcp->cfg.fsm.maxreq = DEF_FSMTRIES;
+  lcp->cfg.fsm.maxtrm = DEF_FSMTRIES;
+
+  lcp->cfg.acfcomp = NEG_ENABLED|NEG_ACCEPTED;
+  lcp->cfg.chap05 = NEG_ACCEPTED;
+#ifndef NODES
+  lcp->cfg.chap80nt = NEG_ACCEPTED;
+  lcp->cfg.chap80lm = 0;
+  lcp->cfg.chap81 = NEG_ACCEPTED;
+#endif
+  lcp->cfg.lqr = NEG_ACCEPTED;
+  lcp->cfg.echo = 0;
+  lcp->cfg.pap = NEG_ACCEPTED;
+  lcp->cfg.protocomp = NEG_ENABLED|NEG_ACCEPTED;
+  *lcp->cfg.ident = '\0';
+
+  lcp_Setup(lcp, lcp->cfg.openmode);
+}
+
+void
+lcp_Setup(struct lcp *lcp, int openmode)
+{
+  struct physical *p = link2physical(lcp->fsm.link);
+
+  lcp->fsm.open_mode = openmode;
+
+  lcp->his_mru = DEF_MRU;
+  lcp->his_mrru = 0;
+  lcp->his_magic = 0;
+  lcp->his_lqrperiod = 0;
+  lcp->his_acfcomp = 0;
+  lcp->his_auth = 0;
+  lcp->his_authtype = 0;
+  lcp->his_callback.opmask = 0;
+  lcp->his_shortseq = 0;
+  lcp->mru_req = 0;
+
+  if ((lcp->want_mru = lcp->cfg.mru) == 0)
+    lcp->want_mru = DEF_MRU;
+  lcp->want_mrru = lcp->fsm.bundle->ncp.mp.cfg.mrru;
+  lcp->want_shortseq = IsEnabled(lcp->fsm.bundle->ncp.mp.cfg.shortseq) ? 1 : 0;
+  lcp->want_acfcomp = IsEnabled(lcp->cfg.acfcomp) ? 1 : 0;
+
+  if (lcp->fsm.parent) {
+    lcp->his_accmap = 0xffffffff;
+    lcp->want_accmap = lcp->cfg.accmap;
+    lcp->his_protocomp = 0;
+    lcp->want_protocomp = IsEnabled(lcp->cfg.protocomp) ? 1 : 0;
+    lcp->want_magic = GenerateMagic();
+
+    if (IsEnabled(lcp->cfg.chap05)) {
+      lcp->want_auth = PROTO_CHAP;
+      lcp->want_authtype = 0x05;
+#ifndef NODES
+    } else if (IsEnabled(lcp->cfg.chap80nt) ||
+               IsEnabled(lcp->cfg.chap80lm)) {
+      lcp->want_auth = PROTO_CHAP;
+      lcp->want_authtype = 0x80;
+    } else if (IsEnabled(lcp->cfg.chap81)) {
+      lcp->want_auth = PROTO_CHAP;
+      lcp->want_authtype = 0x81;
+#endif
+    } else if (IsEnabled(lcp->cfg.pap)) {
+      lcp->want_auth = PROTO_PAP;
+      lcp->want_authtype = 0;
+    } else {
+      lcp->want_auth = 0;
+      lcp->want_authtype = 0;
+    }
+
+    if (p->type != PHYS_DIRECT)
+      memcpy(&lcp->want_callback, &p->dl->cfg.callback,
+             sizeof(struct callback));
+    else
+      lcp->want_callback.opmask = 0;
+    lcp->want_lqrperiod = IsEnabled(lcp->cfg.lqr) ?
+                          lcp->cfg.lqrperiod * 100 : 0;
+  } else {
+    lcp->his_accmap = lcp->want_accmap = 0;
+    lcp->his_protocomp = lcp->want_protocomp = 1;
+    lcp->want_magic = 0;
+    lcp->want_auth = 0;
+    lcp->want_authtype = 0;
+    lcp->want_callback.opmask = 0;
+    lcp->want_lqrperiod = 0;
+  }
+
+  lcp->his_reject = lcp->my_reject = 0;
+  lcp->auth_iwait = lcp->auth_ineed = 0;
+  lcp->LcpFailedMagic = 0;
+}
+
+static void
+LcpInitRestartCounter(struct fsm *fp, int what)
+{
+  /* Set fsm timer load */
+  struct lcp *lcp = fsm2lcp(fp);
+
+  fp->FsmTimer.load = lcp->cfg.fsm.timeout * SECTICKS;
+  switch (what) {
+    case FSM_REQ_TIMER:
+      fp->restart = lcp->cfg.fsm.maxreq;
+      break;
+    case FSM_TRM_TIMER:
+      fp->restart = lcp->cfg.fsm.maxtrm;
+      break;
+    default:
+      fp->restart = 1;
+      break;
+  }
+}
+
+static void
+LcpSendConfigReq(struct fsm *fp)
+{
+  /* Send config REQ please */
+  struct physical *p = link2physical(fp->link);
+  struct lcp *lcp = fsm2lcp(fp);
+  u_char buff[200];
+  struct fsm_opt *o;
+  struct mp *mp;
+  u_int16_t proto;
+  u_short maxmru;
+
+  if (!p) {
+    log_Printf(LogERROR, "%s: LcpSendConfigReq: Not a physical link !\n",
+              fp->link->name);
+    return;
+  }
+
+  o = (struct fsm_opt *)buff;
+  if (!physical_IsSync(p)) {
+    if (lcp->want_acfcomp && !REJECTED(lcp, TY_ACFCOMP))
+      INC_FSM_OPT(TY_ACFCOMP, 2, o);
+
+    if (lcp->want_protocomp && !REJECTED(lcp, TY_PROTOCOMP))
+      INC_FSM_OPT(TY_PROTOCOMP, 2, o);
+
+    if (!REJECTED(lcp, TY_ACCMAP)) {
+      ua_htonl(&lcp->want_accmap, o->data);
+      INC_FSM_OPT(TY_ACCMAP, 6, o);
+    }
+  }
+
+  maxmru = p ? physical_DeviceMTU(p) : 0;
+  if (lcp->cfg.max_mru && (!maxmru || maxmru > lcp->cfg.max_mru))
+    maxmru = lcp->cfg.max_mru;
+  if (maxmru && lcp->want_mru > maxmru) {
+    log_Printf(LogWARN, "%s: Reducing configured MRU from %u to %u\n",
+               fp->link->name, lcp->want_mru, maxmru);
+    lcp->want_mru = maxmru;
+  }
+  if (!REJECTED(lcp, TY_MRU)) {
+    ua_htons(&lcp->want_mru, o->data);
+    INC_FSM_OPT(TY_MRU, 4, o);
+  }
+
+  if (lcp->want_magic && !REJECTED(lcp, TY_MAGICNUM)) {
+    ua_htonl(&lcp->want_magic, o->data);
+    INC_FSM_OPT(TY_MAGICNUM, 6, o);
+  }
+
+  if (lcp->want_lqrperiod && !REJECTED(lcp, TY_QUALPROTO)) {
+    proto = PROTO_LQR;
+    ua_htons(&proto, o->data);
+    ua_htonl(&lcp->want_lqrperiod, o->data + 2);
+    INC_FSM_OPT(TY_QUALPROTO, 8, o);
+  }
+
+  switch (lcp->want_auth) {
+  case PROTO_PAP:
+    proto = PROTO_PAP;
+    ua_htons(&proto, o->data);
+    INC_FSM_OPT(TY_AUTHPROTO, 4, o);
+    break;
+
+  case PROTO_CHAP:
+    proto = PROTO_CHAP;
+    ua_htons(&proto, o->data);
+    o->data[2] = lcp->want_authtype;
+    INC_FSM_OPT(TY_AUTHPROTO, 5, o);
+    break;
+  }
+
+  if (!REJECTED(lcp, TY_CALLBACK)) {
+    if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_AUTH)) {
+      *o->data = CALLBACK_AUTH;
+      INC_FSM_OPT(TY_CALLBACK, 3, o);
+    } else if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_CBCP)) {
+      *o->data = CALLBACK_CBCP;
+      INC_FSM_OPT(TY_CALLBACK, 3, o);
+    } else if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_E164)) {
+      size_t sz = strlen(lcp->want_callback.msg);
+
+      if (sz > sizeof o->data - 1) {
+        sz = sizeof o->data - 1;
+        log_Printf(LogWARN, "Truncating E164 data to %zu octets (oops!)\n",
+	    sz);
+      }
+      *o->data = CALLBACK_E164;
+      memcpy(o->data + 1, lcp->want_callback.msg, sz);
+      INC_FSM_OPT(TY_CALLBACK, sz + 3, o);
+    }
+  }
+
+  if (lcp->want_mrru && !REJECTED(lcp, TY_MRRU)) {
+    ua_htons(&lcp->want_mrru, o->data);
+    INC_FSM_OPT(TY_MRRU, 4, o);
+
+    if (lcp->want_shortseq && !REJECTED(lcp, TY_SHORTSEQ))
+      INC_FSM_OPT(TY_SHORTSEQ, 2, o);
+  }
+
+  mp = &lcp->fsm.bundle->ncp.mp;
+  if (mp->cfg.enddisc.class != 0 && IsEnabled(mp->cfg.negenddisc) &&
+      !REJECTED(lcp, TY_ENDDISC)) {
+    *o->data = mp->cfg.enddisc.class;
+    memcpy(o->data+1, mp->cfg.enddisc.address, mp->cfg.enddisc.len);
+    INC_FSM_OPT(TY_ENDDISC, mp->cfg.enddisc.len + 3, o);
+  }
+
+  fsm_Output(fp, CODE_CONFIGREQ, fp->reqid, buff, (u_char *)o - buff,
+             MB_LCPOUT);
+}
+
+void
+lcp_SendProtoRej(struct lcp *lcp, u_char *option, int count)
+{
+  /* Don't understand `option' */
+  fsm_Output(&lcp->fsm, CODE_PROTOREJ, lcp->fsm.reqid, option, count,
+             MB_LCPOUT);
+}
+
+int
+lcp_SendIdentification(struct lcp *lcp)
+{
+  static u_char id;		/* Use a private id */
+  u_char msg[DEF_MRU - 3];
+  const char *argv[2];
+  char *exp[2];
+
+  if (*lcp->cfg.ident == '\0')
+    return 0;
+
+  argv[0] = lcp->cfg.ident;
+  argv[1] = NULL;
+
+  command_Expand(exp, 1, argv, lcp->fsm.bundle, 1, getpid());
+
+  ua_htonl(&lcp->want_magic, msg);
+  strncpy(msg + 4, exp[0], sizeof msg - 5);
+  msg[sizeof msg - 1] = '\0';
+
+  fsm_Output(&lcp->fsm, CODE_IDENT, id++, msg, 4 + strlen(msg + 4), MB_LCPOUT);
+  log_Printf(LogLCP, " MAGICNUM %08x\n", lcp->want_magic);
+  log_Printf(LogLCP, " TEXT %s\n", msg + 4);
+
+  command_Free(1, exp);
+  return 1;
+}
+
+void
+lcp_RecvIdentification(struct lcp *lcp, char *data)
+{
+  log_Printf(LogLCP, " MAGICNUM %08x\n", lcp->his_magic);
+  log_Printf(LogLCP, " TEXT %s\n", data);
+}
+
+static void
+LcpSentTerminateReq(struct fsm *fp __unused)
+{
+  /* Term REQ just sent by FSM */
+}
+
+static void
+LcpSendTerminateAck(struct fsm *fp, u_char id)
+{
+  /* Send Term ACK please */
+  struct physical *p = link2physical(fp->link);
+
+  if (p && p->dl->state == DATALINK_CBCP)
+    cbcp_ReceiveTerminateReq(p);
+
+  fsm_Output(fp, CODE_TERMACK, id, NULL, 0, MB_LCPOUT);
+}
+
+static void
+LcpLayerStart(struct fsm *fp)
+{
+  /* We're about to start up ! */
+  struct lcp *lcp = fsm2lcp(fp);
+
+  log_Printf(LogLCP, "%s: LayerStart\n", fp->link->name);
+  lcp->LcpFailedMagic = 0;
+  fp->more.reqs = fp->more.naks = fp->more.rejs = lcp->cfg.fsm.maxreq * 3;
+  lcp->mru_req = 0;
+}
+
+static void
+LcpLayerFinish(struct fsm *fp)
+{
+  /* We're now down */
+  log_Printf(LogLCP, "%s: LayerFinish\n", fp->link->name);
+}
+
+static int
+LcpLayerUp(struct fsm *fp)
+{
+  /* We're now up */
+  struct physical *p = link2physical(fp->link);
+  struct lcp *lcp = fsm2lcp(fp);
+
+  log_Printf(LogLCP, "%s: LayerUp\n", fp->link->name);
+  physical_SetAsyncParams(p, lcp->want_accmap, lcp->his_accmap);
+  lqr_Start(lcp);
+  hdlc_StartTimer(&p->hdlc);
+  fp->more.reqs = fp->more.naks = fp->more.rejs = lcp->cfg.fsm.maxreq * 3;
+
+  lcp_SendIdentification(lcp);
+
+  return 1;
+}
+
+static void
+LcpLayerDown(struct fsm *fp)
+{
+  /* About to come down */
+  struct physical *p = link2physical(fp->link);
+
+  log_Printf(LogLCP, "%s: LayerDown\n", fp->link->name);
+  hdlc_StopTimer(&p->hdlc);
+  lqr_StopTimer(p);
+  lcp_Setup(fsm2lcp(fp), 0);
+}
+
+static int
+E164ok(struct callback *cb, char *req, int sz)
+{
+  char list[sizeof cb->msg], *next;
+  int len;
+
+  if (!strcmp(cb->msg, "*"))
+    return 1;
+
+  strncpy(list, cb->msg, sizeof list - 1);
+  list[sizeof list - 1] = '\0';
+  for (next = strtok(list, ","); next; next = strtok(NULL, ",")) {
+    len = strlen(next);
+    if (sz == len && !memcmp(list, req, sz))
+      return 1;
+  }
+  return 0;
+}
+
+static int
+lcp_auth_nak(struct lcp *lcp, struct fsm_decode *dec)
+{
+  struct fsm_opt nak;
+
+  nak.hdr.id = TY_AUTHPROTO;
+
+  if (IsAccepted(lcp->cfg.pap)) {
+    nak.hdr.len = 4;
+    nak.data[0] = (unsigned char)(PROTO_PAP >> 8);
+    nak.data[1] = (unsigned char)PROTO_PAP;
+    fsm_nak(dec, &nak);
+    return 1;
+  }
+
+  nak.hdr.len = 5;
+  nak.data[0] = (unsigned char)(PROTO_CHAP >> 8);
+  nak.data[1] = (unsigned char)PROTO_CHAP;
+
+  if (IsAccepted(lcp->cfg.chap05)) {
+    nak.data[2] = 0x05;
+    fsm_nak(dec, &nak);
+#ifndef NODES
+  } else if (IsAccepted(lcp->cfg.chap80nt) ||
+             IsAccepted(lcp->cfg.chap80lm)) {
+    nak.data[2] = 0x80;
+    fsm_nak(dec, &nak);
+  } else if (IsAccepted(lcp->cfg.chap81)) {
+    nak.data[2] = 0x81;
+    fsm_nak(dec, &nak);
+#endif
+  } else {
+    return 0;
+  }
+
+  return 1;
+}
+
+static void
+LcpDecodeConfig(struct fsm *fp, u_char *cp, u_char *end, int mode_type,
+                struct fsm_decode *dec)
+{
+  /* Deal with incoming PROTO_LCP */
+  struct lcp *lcp = fsm2lcp(fp);
+  int pos, op, callback_req, chap_type;
+  size_t sz;
+  u_int32_t magic, accmap;
+  u_short mru, phmtu, maxmtu, maxmru, wantmtu, wantmru, proto;
+  struct lqrreq req;
+  char request[20], desc[22];
+  struct mp *mp;
+  struct physical *p = link2physical(fp->link);
+  struct fsm_opt *opt, nak;
+
+  sz = 0;
+  op = callback_req = 0;
+
+  while (end - cp >= (int)sizeof(opt->hdr)) {
+    if ((opt = fsm_readopt(&cp)) == NULL)
+      break;
+
+    snprintf(request, sizeof request, " %s[%d]", protoname(opt->hdr.id),
+             opt->hdr.len);
+
+    switch (opt->hdr.id) {
+    case TY_MRRU:
+      mp = &lcp->fsm.bundle->ncp.mp;
+      ua_ntohs(opt->data, &mru);
+      log_Printf(LogLCP, "%s %u\n", request, mru);
+
+      switch (mode_type) {
+      case MODE_REQ:
+        if (mp->cfg.mrru) {
+          if (REJECTED(lcp, TY_MRRU))
+            /* Ignore his previous reject so that we REQ next time */
+            lcp->his_reject &= ~(1 << opt->hdr.id);
+
+          if (mru > MAX_MRU) {
+            /* Push him down to MAX_MRU */
+            lcp->his_mrru = MAX_MRU;
+            nak.hdr.id = TY_MRRU;
+            nak.hdr.len = 4;
+            ua_htons(&lcp->his_mrru, nak.data);
+            fsm_nak(dec, &nak);
+          } else if (mru < MIN_MRU) {
+            /* Push him up to MIN_MRU */
+            lcp->his_mrru = MIN_MRU;
+            nak.hdr.id = TY_MRRU;
+            nak.hdr.len = 4;
+            ua_htons(&lcp->his_mrru, nak.data);
+            fsm_nak(dec, &nak);
+          } else {
+            lcp->his_mrru = mru;
+            fsm_ack(dec, opt);
+          }
+          break;
+        } else {
+          fsm_rej(dec, opt);
+          lcp->my_reject |= (1 << opt->hdr.id);
+        }
+        break;
+      case MODE_NAK:
+        if (mp->cfg.mrru) {
+          if (REJECTED(lcp, TY_MRRU))
+            /* Must have changed his mind ! */
+            lcp->his_reject &= ~(1 << opt->hdr.id);
+
+          if (mru > MAX_MRU)
+            lcp->want_mrru = MAX_MRU;
+          else if (mru < MIN_MRU)
+            lcp->want_mrru = MIN_MRU;
+          else
+            lcp->want_mrru = mru;
+        }
+        /* else we honour our config and don't send the suggested REQ */
+        break;
+      case MODE_REJ:
+        lcp->his_reject |= (1 << opt->hdr.id);
+        lcp->want_mrru = 0;		/* Ah well, no multilink :-( */
+        break;
+      }
+      break;
+
+    case TY_MRU:
+      lcp->mru_req = 1;
+      ua_ntohs(opt->data, &mru);
+      log_Printf(LogLCP, "%s %d\n", request, mru);
+
+      switch (mode_type) {
+      case MODE_REQ:
+        maxmtu = p ? physical_DeviceMTU(p) : 0;
+        if (lcp->cfg.max_mtu && (!maxmtu || maxmtu > lcp->cfg.max_mtu))
+          maxmtu = lcp->cfg.max_mtu;
+        wantmtu = lcp->cfg.mtu;
+        if (maxmtu && wantmtu > maxmtu) {
+          log_Printf(LogWARN, "%s: Reducing configured MTU from %u to %u\n",
+                     fp->link->name, wantmtu, maxmtu);
+          wantmtu = maxmtu;
+        }
+
+        if (maxmtu && mru > maxmtu) {
+          lcp->his_mru = maxmtu;
+          nak.hdr.id = TY_MRU;
+          nak.hdr.len = 4;
+          ua_htons(&lcp->his_mru, nak.data);
+          fsm_nak(dec, &nak);
+        } else if (wantmtu && mru < wantmtu) {
+          /* Push him up to MTU or MIN_MRU */
+          lcp->his_mru = wantmtu;
+          nak.hdr.id = TY_MRU;
+          nak.hdr.len = 4;
+          ua_htons(&lcp->his_mru, nak.data);
+          fsm_nak(dec, &nak);
+        } else {
+          lcp->his_mru = mru;
+          fsm_ack(dec, opt);
+        }
+        break;
+      case MODE_NAK:
+        maxmru = p ? physical_DeviceMTU(p) : 0;
+        if (lcp->cfg.max_mru && (!maxmru || maxmru > lcp->cfg.max_mru))
+          maxmru = lcp->cfg.max_mru;
+        wantmru = lcp->cfg.mru > maxmru ? maxmru : lcp->cfg.mru;
+
+        if (wantmru && mru > wantmru)
+          lcp->want_mru = wantmru;
+        else if (mru > maxmru)
+          lcp->want_mru = maxmru;
+        else if (mru < MIN_MRU)
+          lcp->want_mru = MIN_MRU;
+        else
+          lcp->want_mru = mru;
+        break;
+      case MODE_REJ:
+        lcp->his_reject |= (1 << opt->hdr.id);
+        /* Set the MTU to what we want anyway - the peer won't care! */
+        if (lcp->his_mru > lcp->want_mru)
+          lcp->his_mru = lcp->want_mru;
+        break;
+      }
+      break;
+
+    case TY_ACCMAP:
+      ua_ntohl(opt->data, &accmap);
+      log_Printf(LogLCP, "%s 0x%08lx\n", request, (u_long)accmap);
+
+      switch (mode_type) {
+      case MODE_REQ:
+        lcp->his_accmap = accmap;
+        fsm_ack(dec, opt);
+        break;
+      case MODE_NAK:
+        lcp->want_accmap = accmap;
+        break;
+      case MODE_REJ:
+        lcp->his_reject |= (1 << opt->hdr.id);
+        break;
+      }
+      break;
+
+    case TY_AUTHPROTO:
+      ua_ntohs(opt->data, &proto);
+      chap_type = opt->hdr.len == 5 ? opt->data[2] : 0;
+
+      log_Printf(LogLCP, "%s 0x%04x (%s)\n", request, proto,
+                 Auth2Nam(proto, chap_type));
+
+      switch (mode_type) {
+      case MODE_REQ:
+        switch (proto) {
+        case PROTO_PAP:
+          if (opt->hdr.len == 4 && IsAccepted(lcp->cfg.pap)) {
+            lcp->his_auth = proto;
+            lcp->his_authtype = 0;
+            fsm_ack(dec, opt);
+          } else if (!lcp_auth_nak(lcp, dec)) {
+            lcp->my_reject |= (1 << opt->hdr.id);
+            fsm_rej(dec, opt);
+          }
+          break;
+
+        case PROTO_CHAP:
+          if ((chap_type == 0x05 && IsAccepted(lcp->cfg.chap05))
+#ifndef NODES
+              || (chap_type == 0x80 && (IsAccepted(lcp->cfg.chap80nt) ||
+                                   (IsAccepted(lcp->cfg.chap80lm))))
+              || (chap_type == 0x81 && IsAccepted(lcp->cfg.chap81))
+#endif
+             ) {
+            lcp->his_auth = proto;
+            lcp->his_authtype = chap_type;
+            fsm_ack(dec, opt);
+          } else {
+#ifdef NODES
+            if (chap_type == 0x80) {
+              log_Printf(LogWARN, "CHAP 0x80 not available without DES\n");
+            } else if (chap_type == 0x81) {
+              log_Printf(LogWARN, "CHAP 0x81 not available without DES\n");
+            } else
+#endif
+            if (chap_type != 0x05)
+              log_Printf(LogWARN, "%s not supported\n",
+                         Auth2Nam(PROTO_CHAP, chap_type));
+
+            if (!lcp_auth_nak(lcp, dec)) {
+              lcp->my_reject |= (1 << opt->hdr.id);
+              fsm_rej(dec, opt);
+            }
+          }
+          break;
+
+        default:
+          log_Printf(LogLCP, "%s 0x%04x - not recognised\n",
+                    request, proto);
+          if (!lcp_auth_nak(lcp, dec)) {
+            lcp->my_reject |= (1 << opt->hdr.id);
+            fsm_rej(dec, opt);
+          }
+          break;
+        }
+        break;
+
+      case MODE_NAK:
+        switch (proto) {
+        case PROTO_PAP:
+          if (IsEnabled(lcp->cfg.pap)) {
+            lcp->want_auth = PROTO_PAP;
+            lcp->want_authtype = 0;
+          } else {
+            log_Printf(LogLCP, "Peer will only send PAP (not enabled)\n");
+            lcp->his_reject |= (1 << opt->hdr.id);
+          }
+          break;
+        case PROTO_CHAP:
+          if (chap_type == 0x05 && IsEnabled(lcp->cfg.chap05)) {
+            lcp->want_auth = PROTO_CHAP;
+            lcp->want_authtype = 0x05;
+#ifndef NODES
+          } else if (chap_type == 0x80 && (IsEnabled(lcp->cfg.chap80nt) ||
+                                           IsEnabled(lcp->cfg.chap80lm))) {
+            lcp->want_auth = PROTO_CHAP;
+            lcp->want_authtype = 0x80;
+          } else if (chap_type == 0x81 && IsEnabled(lcp->cfg.chap81)) {
+            lcp->want_auth = PROTO_CHAP;
+            lcp->want_authtype = 0x81;
+#endif
+          } else {
+#ifdef NODES
+            if (chap_type == 0x80) {
+              log_Printf(LogLCP, "Peer will only send MSCHAP (not available"
+                         " without DES)\n");
+            } else if (chap_type == 0x81) {
+              log_Printf(LogLCP, "Peer will only send MSCHAPV2 (not available"
+                         " without DES)\n");
+            } else
+#endif
+            log_Printf(LogLCP, "Peer will only send %s (not %s)\n",
+                       Auth2Nam(PROTO_CHAP, chap_type),
+#ifndef NODES
+                       (chap_type == 0x80 || chap_type == 0x81) ? "configured" :
+#endif
+                       "supported");
+            lcp->his_reject |= (1 << opt->hdr.id);
+          }
+          break;
+        default:
+          /* We've been NAK'd with something we don't understand :-( */
+          lcp->his_reject |= (1 << opt->hdr.id);
+          break;
+        }
+        break;
+
+      case MODE_REJ:
+        lcp->his_reject |= (1 << opt->hdr.id);
+        break;
+      }
+      break;
+
+    case TY_QUALPROTO:
+      memcpy(&req, opt, sizeof req);
+      log_Printf(LogLCP, "%s proto %x, interval %lums\n",
+                request, ntohs(req.proto), (u_long)ntohl(req.period) * 10);
+      switch (mode_type) {
+      case MODE_REQ:
+        if (ntohs(req.proto) != PROTO_LQR || !IsAccepted(lcp->cfg.lqr)) {
+          fsm_rej(dec, opt);
+          lcp->my_reject |= (1 << opt->hdr.id);
+        } else {
+          lcp->his_lqrperiod = ntohl(req.period);
+          if (lcp->his_lqrperiod < MIN_LQRPERIOD * 100)
+            lcp->his_lqrperiod = MIN_LQRPERIOD * 100;
+          req.period = htonl(lcp->his_lqrperiod);
+          fsm_ack(dec, opt);
+        }
+        break;
+      case MODE_NAK:
+        lcp->want_lqrperiod = ntohl(req.period);
+        break;
+      case MODE_REJ:
+        lcp->his_reject |= (1 << opt->hdr.id);
+        break;
+      }
+      break;
+
+    case TY_MAGICNUM:
+      ua_ntohl(opt->data, &magic);
+      log_Printf(LogLCP, "%s 0x%08lx\n", request, (u_long)magic);
+
+      switch (mode_type) {
+      case MODE_REQ:
+        if (lcp->want_magic) {
+          /* Validate magic number */
+          if (magic == lcp->want_magic) {
+            sigset_t emptyset;
+
+            log_Printf(LogLCP, "Magic is same (%08lx) - %d times\n",
+                      (u_long)magic, ++lcp->LcpFailedMagic);
+            lcp->want_magic = GenerateMagic();
+            fsm_nak(dec, opt);
+            ualarm(TICKUNIT * (4 + 4 * lcp->LcpFailedMagic), 0);
+            sigemptyset(&emptyset);
+            sigsuspend(&emptyset);
+          } else {
+            lcp->his_magic = magic;
+            lcp->LcpFailedMagic = 0;
+            fsm_ack(dec, opt);
+          }
+        } else {
+          lcp->my_reject |= (1 << opt->hdr.id);
+          fsm_rej(dec, opt);
+        }
+        break;
+      case MODE_NAK:
+        log_Printf(LogLCP, " Magic 0x%08lx is NAKed!\n", (u_long)magic);
+        lcp->want_magic = GenerateMagic();
+        break;
+      case MODE_REJ:
+        log_Printf(LogLCP, " Magic 0x%08x is REJected!\n", magic);
+        lcp->want_magic = 0;
+        lcp->his_reject |= (1 << opt->hdr.id);
+        break;
+      }
+      break;
+
+    case TY_PROTOCOMP:
+      log_Printf(LogLCP, "%s\n", request);
+
+      switch (mode_type) {
+      case MODE_REQ:
+        if (IsAccepted(lcp->cfg.protocomp)) {
+          lcp->his_protocomp = 1;
+          fsm_ack(dec, opt);
+        } else {
+#ifdef OLDMST
+          /* MorningStar before v1.3 needs NAK */
+          fsm_nak(dec, opt);
+#else
+          fsm_rej(dec, opt);
+          lcp->my_reject |= (1 << opt->hdr.id);
+#endif
+        }
+        break;
+      case MODE_NAK:
+      case MODE_REJ:
+        lcp->want_protocomp = 0;
+        lcp->his_reject |= (1 << opt->hdr.id);
+        break;
+      }
+      break;
+
+    case TY_ACFCOMP:
+      log_Printf(LogLCP, "%s\n", request);
+      switch (mode_type) {
+      case MODE_REQ:
+        if (IsAccepted(lcp->cfg.acfcomp)) {
+          lcp->his_acfcomp = 1;
+          fsm_ack(dec, opt);
+        } else {
+#ifdef OLDMST
+          /* MorningStar before v1.3 needs NAK */
+          fsm_nak(dec, opt);
+#else
+          fsm_rej(dec, opt);
+          lcp->my_reject |= (1 << opt->hdr.id);
+#endif
+        }
+        break;
+      case MODE_NAK:
+      case MODE_REJ:
+        lcp->want_acfcomp = 0;
+        lcp->his_reject |= (1 << opt->hdr.id);
+        break;
+      }
+      break;
+
+    case TY_SDP:
+      log_Printf(LogLCP, "%s\n", request);
+      switch (mode_type) {
+      case MODE_REQ:
+      case MODE_NAK:
+      case MODE_REJ:
+        break;
+      }
+      break;
+
+    case TY_CALLBACK:
+      if (opt->hdr.len == 2) {
+        op = CALLBACK_NONE;
+        sz = 0;
+      } else {
+        op = (int)opt->data[0];
+        sz = opt->hdr.len - 3;
+      }
+      switch (op) {
+        case CALLBACK_AUTH:
+          log_Printf(LogLCP, "%s Auth\n", request);
+          break;
+        case CALLBACK_DIALSTRING:
+		log_Printf(LogLCP, "%s Dialstring %.*s\n", request, (int)sz,
+                     opt->data + 1);
+          break;
+        case CALLBACK_LOCATION:
+		log_Printf(LogLCP, "%s Location %.*s\n", request, (int)sz,
+		    opt->data + 1);
+          break;
+        case CALLBACK_E164:
+		log_Printf(LogLCP, "%s E.164 (%.*s)\n", request, (int)sz,
+		    opt->data + 1);
+          break;
+        case CALLBACK_NAME:
+		log_Printf(LogLCP, "%s Name %.*s\n", request, (int)sz,
+		    opt->data + 1);
+          break;
+        case CALLBACK_CBCP:
+          log_Printf(LogLCP, "%s CBCP\n", request);
+          break;
+        default:
+          log_Printf(LogLCP, "%s ???\n", request);
+          break;
+      }
+
+      switch (mode_type) {
+      case MODE_REQ:
+        callback_req = 1;
+        if (p->type != PHYS_DIRECT) {
+          fsm_rej(dec, opt);
+          lcp->my_reject |= (1 << opt->hdr.id);
+        }
+        nak.hdr.id = opt->hdr.id;
+        nak.hdr.len = 3;
+        if ((p->dl->cfg.callback.opmask & CALLBACK_BIT(op)) &&
+            (op != CALLBACK_AUTH || p->link.lcp.want_auth) &&
+            (op != CALLBACK_E164 ||
+             E164ok(&p->dl->cfg.callback, opt->data + 1, sz))) {
+          lcp->his_callback.opmask = CALLBACK_BIT(op);
+          if (sz > sizeof lcp->his_callback.msg - 1) {
+            sz = sizeof lcp->his_callback.msg - 1;
+            log_Printf(LogWARN, "Truncating option arg to %zu octets\n", sz);
+          }
+          memcpy(lcp->his_callback.msg, opt->data + 1, sz);
+          lcp->his_callback.msg[sz] = '\0';
+          fsm_ack(dec, opt);
+        } else if ((p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_AUTH)) &&
+                    p->link.lcp.auth_ineed) {
+          nak.data[0] = CALLBACK_AUTH;
+          fsm_nak(dec, &nak);
+        } else if (p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_CBCP)) {
+          nak.data[0] = CALLBACK_CBCP;
+          fsm_nak(dec, &nak);
+        } else if (p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_E164)) {
+          nak.data[0] = CALLBACK_E164;
+          fsm_nak(dec, &nak);
+        } else if (p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_AUTH)) {
+          log_Printf(LogWARN, "Cannot insist on auth callback without"
+                     " PAP or CHAP enabled !\n");
+          nak.data[0] = 2;
+          fsm_nak(dec, &nak);
+        } else {
+          lcp->my_reject |= (1 << opt->hdr.id);
+          fsm_rej(dec, opt);
+        }
+        break;
+      case MODE_NAK:
+        /* We don't do what he NAKs with, we do things in our preferred order */
+        if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_AUTH))
+          lcp->want_callback.opmask &= ~CALLBACK_BIT(CALLBACK_AUTH);
+        else if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_CBCP))
+          lcp->want_callback.opmask &= ~CALLBACK_BIT(CALLBACK_CBCP);
+        else if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_E164))
+          lcp->want_callback.opmask &= ~CALLBACK_BIT(CALLBACK_E164);
+        if (lcp->want_callback.opmask == CALLBACK_BIT(CALLBACK_NONE)) {
+          log_Printf(LogPHASE, "Peer NAKd all callbacks, trying none\n");
+          lcp->want_callback.opmask = 0;
+        } else if (!lcp->want_callback.opmask) {
+          log_Printf(LogPHASE, "Peer NAKd last configured callback\n");
+          fsm_Close(&lcp->fsm);
+        }
+        break;
+      case MODE_REJ:
+        if (lcp->want_callback.opmask & CALLBACK_BIT(CALLBACK_NONE)) {
+          lcp->his_reject |= (1 << opt->hdr.id);
+          lcp->want_callback.opmask = 0;
+        } else {
+          log_Printf(LogPHASE, "Peer rejected *required* callback\n");
+          fsm_Close(&lcp->fsm);
+        }
+        break;
+      }
+      break;
+
+    case TY_SHORTSEQ:
+      mp = &lcp->fsm.bundle->ncp.mp;
+      log_Printf(LogLCP, "%s\n", request);
+
+      switch (mode_type) {
+      case MODE_REQ:
+        if (lcp->want_mrru && IsAccepted(mp->cfg.shortseq)) {
+          lcp->his_shortseq = 1;
+          fsm_ack(dec, opt);
+        } else {
+          fsm_rej(dec, opt);
+          lcp->my_reject |= (1 << opt->hdr.id);
+        }
+        break;
+      case MODE_NAK:
+        /*
+         * He's trying to get us to ask for short sequence numbers.
+         * We ignore the NAK and honour our configuration file instead.
+         */
+        break;
+      case MODE_REJ:
+        lcp->his_reject |= (1 << opt->hdr.id);
+        lcp->want_shortseq = 0;		/* For when we hit MP */
+        break;
+      }
+      break;
+
+    case TY_ENDDISC:
+      mp = &lcp->fsm.bundle->ncp.mp;
+      log_Printf(LogLCP, "%s %s\n", request,
+                 mp_Enddisc(opt->data[0], opt->data + 1, opt->hdr.len - 3));
+      switch (mode_type) {
+      case MODE_REQ:
+        if (!p) {
+          log_Printf(LogLCP, " ENDDISC rejected - not a physical link\n");
+          fsm_rej(dec, opt);
+          lcp->my_reject |= (1 << opt->hdr.id);
+        } else if (!IsAccepted(mp->cfg.negenddisc)) {
+          lcp->my_reject |= (1 << opt->hdr.id);
+          fsm_rej(dec, opt);
+        } else if (opt->hdr.len < sizeof p->dl->peer.enddisc.address + 3 &&
+                   opt->data[0] <= MAX_ENDDISC_CLASS) {
+          p->dl->peer.enddisc.class = opt->data[0];
+          p->dl->peer.enddisc.len = opt->hdr.len - 3;
+          memcpy(p->dl->peer.enddisc.address, opt->data + 1, opt->hdr.len - 3);
+          p->dl->peer.enddisc.address[opt->hdr.len - 3] = '\0';
+          /* XXX: If mp->active, compare and NAK with mp->peer ? */
+          fsm_ack(dec, opt);
+        } else {
+          if (opt->data[0] > MAX_ENDDISC_CLASS)
+            log_Printf(LogLCP, " ENDDISC rejected - unrecognised class %d\n",
+                      opt->data[0]);
+          else
+            log_Printf(LogLCP, " ENDDISC rejected - local max length is %ld\n",
+                      (long)(sizeof p->dl->peer.enddisc.address - 1));
+          fsm_rej(dec, opt);
+          lcp->my_reject |= (1 << opt->hdr.id);
+        }
+        break;
+
+      case MODE_NAK:	/* Treat this as a REJ, we don't vary our disc (yet) */
+      case MODE_REJ:
+        lcp->his_reject |= (1 << opt->hdr.id);
+        break;
+      }
+      break;
+
+    default:
+      sz = (sizeof desc - 2) / 2;
+      if (sz + 2 > opt->hdr.len)
+        sz = opt->hdr.len - 2;
+      pos = 0;
+      desc[0] = sz ? ' ' : '\0';
+      for (pos = 0; sz--; pos++)
+        sprintf(desc+(pos<<1)+1, "%02x", opt->data[pos]);
+
+      log_Printf(LogLCP, "%s%s\n", request, desc);
+
+      if (mode_type == MODE_REQ) {
+        fsm_rej(dec, opt);
+        lcp->my_reject |= (1 << opt->hdr.id);
+      }
+      break;
+    }
+  }
+
+  if (mode_type != MODE_NOP) {
+    if (mode_type == MODE_REQ && p && p->type == PHYS_DIRECT &&
+        p->dl->cfg.callback.opmask && !callback_req &&
+        !(p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_NONE))) {
+      /* We *REQUIRE* that the peer requests callback */
+      nak.hdr.id = TY_CALLBACK;
+      nak.hdr.len = 3;
+      if ((p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_AUTH)) &&
+          p->link.lcp.want_auth)
+        nak.data[0] = CALLBACK_AUTH;
+      else if (p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_CBCP))
+        nak.data[0] = CALLBACK_CBCP;
+      else if (p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_E164))
+        nak.data[0] = CALLBACK_E164;
+      else {
+        log_Printf(LogWARN, "Cannot insist on auth callback without"
+                   " PAP or CHAP enabled !\n");
+        nak.hdr.len = 2;	/* XXX: Silly ! */
+      }
+      fsm_nak(dec, &nak);
+    }
+    if (mode_type == MODE_REQ && !lcp->mru_req) {
+      mru = DEF_MRU;
+      phmtu = p ? physical_DeviceMTU(p) : 0;
+      if (phmtu && mru > phmtu)
+        mru = phmtu;
+      if (mru > lcp->cfg.max_mtu)
+        mru = lcp->cfg.max_mtu;
+      if (mru < DEF_MRU) {
+        /* Don't let the peer use the default MRU */
+        lcp->his_mru = lcp->cfg.mtu && lcp->cfg.mtu < mru ? lcp->cfg.mtu : mru;
+        nak.hdr.id = TY_MRU;
+        nak.hdr.len = 4;
+        ua_htons(&lcp->his_mru, nak.data);
+        fsm_nak(dec, &nak);
+        lcp->mru_req = 1;	/* Don't keep NAK'ing this */
+      }
+    }
+    fsm_opt_normalise(dec);
+  }
+}
+
+extern struct mbuf *
+lcp_Input(struct bundle *bundle __unused, struct link *l, struct mbuf *bp)
+{
+  /* Got PROTO_LCP from link */
+  m_settype(bp, MB_LCPIN);
+  fsm_Input(&l->lcp.fsm, bp);
+  return NULL;
+}
diff --git a/src/lcp.h b/src/lcp.h
new file mode 100644
index 0000000..63b71d8
--- /dev/null
+++ b/src/lcp.h
@@ -0,0 +1,143 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/lcp.h,v 1.31.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+/* callback::opmask values */
+#define CALLBACK_AUTH		(0)
+#define CALLBACK_DIALSTRING	(1)	/* Don't do this */
+#define CALLBACK_LOCATION	(2)	/* Don't do this */
+#define CALLBACK_E164		(3)
+#define CALLBACK_NAME		(4)	/* Don't do this */
+#define CALLBACK_CBCP		(6)
+#define CALLBACK_NONE		(14)	/* No callback is ok */
+
+#define CALLBACK_BIT(n) ((n) < 0 ? 0 : 1 << (n))
+
+struct callback {
+  int opmask;			/* want these types of callback */
+  char msg[SCRIPT_LEN];		/* with this data (E.164) */
+};
+
+#define	REJECTED(p, x)	((p)->his_reject & (1<<(x)))
+
+struct lcp {
+  struct fsm fsm;		/* The finite state machine */
+  u_int16_t his_mru;		/* Peers maximum packet size */
+  u_int16_t his_mrru;		/* Peers maximum reassembled packet size (MP) */
+  u_int32_t his_accmap;		/* Peeers async char control map */
+  u_int32_t his_magic;		/* Peers magic number */
+  u_int32_t his_lqrperiod;	/* Peers LQR frequency (100ths of seconds) */
+  u_short his_auth;		/* Peer wants this type of authentication */
+  u_char his_authtype;		/* Fifth octet of REQ/NAK/REJ */
+  struct callback his_callback;	/* Peer wants callback ? */
+  unsigned his_shortseq : 1;	/* Peer would like only 12bit seqs (MP) */
+  unsigned his_protocomp : 1;	/* Does peer do Protocol field compression */
+  unsigned his_acfcomp : 1;	/* Does peer do addr & cntrl fld compression */
+  unsigned mru_req : 1;		/* Has the peer requested an MRU */
+
+  u_short want_mru;		/* Our maximum packet size */
+  u_short want_mrru;		/* Our maximum reassembled packet size (MP) */
+  u_int32_t want_accmap;	/* Our async char control map */
+  u_int32_t want_magic;		/* Our magic number */
+  u_int32_t want_lqrperiod;	/* Our LQR frequency (100ths of seconds) */
+  u_short want_auth;		/* We want this type of authentication */
+  u_char want_authtype;		/* Fifth octet of REQ/NAK/REJ */
+  struct callback want_callback;/* We want callback ? */
+  unsigned want_shortseq : 1;	/* I'd like only 12bit seqs (MP) */
+  unsigned want_protocomp : 1;	/* Do we do protocol field compression */
+  unsigned want_acfcomp : 1;	/* Do we do addr & cntrl fld compression */
+
+  u_int32_t his_reject;		/* Request codes rejected by peer */
+  u_int32_t my_reject;		/* Request codes I have rejected */
+
+  u_short auth_iwait;		/* I must authenticate to the peer */
+  u_short auth_ineed;		/* I require that the peer authenticates */
+
+  int LcpFailedMagic;		/* Number of `magic is same' errors */
+
+  struct {
+    u_short mru;		/* Preferred MRU value */
+    u_short max_mru;		/* Preferred MRU value */
+    u_short mtu;		/* Preferred MTU */
+    u_short max_mtu;		/* Preferred MTU */
+    u_int32_t accmap;		/* Initial ACCMAP value */
+    int openmode;		/* when to start CFG REQs */
+    u_int32_t lqrperiod;	/* LQR frequency (seconds) */
+    struct fsm_retry fsm;	/* How often/frequently to resend requests */
+    unsigned acfcomp : 2;	/* Address & Control Field Compression neg */
+    unsigned chap05 : 2;	/* Challenge Handshake Authentication proto */
+#ifndef NODES
+    unsigned chap80nt : 2;	/* Microsoft (NT) CHAP */
+    unsigned chap80lm : 2;	/* Microsoft (LANMan) CHAP */
+    unsigned chap81 : 2;	/* Microsoft CHAP v2 */
+#endif
+    unsigned lqr : 2;		/* Link Quality Report */
+    unsigned echo : 1;		/* Send echo Requests */
+    unsigned pap : 2;		/* Password Authentication protocol */
+    unsigned protocomp : 2;	/* Protocol field compression */
+    char ident[DEF_MRU - 7];	/* SendIdentification() data */
+  } cfg;
+};
+
+#define	LCP_MAXCODE	CODE_IDENT
+#define	LCP_MINMPCODE	CODE_CODEREJ
+
+#define	TY_MRU		1	/* Maximum-Receive-Unit */
+#define	TY_ACCMAP	2	/* Async-Control-Character-Map */
+#define	TY_AUTHPROTO	3	/* Authentication-Protocol */
+#define	TY_QUALPROTO	4	/* Quality-Protocol */
+#define	TY_MAGICNUM	5	/* Magic-Number */
+#define	TY_RESERVED	6	/* RESERVED */
+#define	TY_PROTOCOMP	7	/* Protocol-Field-Compression */
+#define	TY_ACFCOMP	8	/* Address-and-Control-Field-Compression */
+#define	TY_FCSALT	9	/* FCS-Alternatives */
+#define	TY_SDP		10	/* Self-Describing-Padding */
+#define	TY_CALLBACK	13	/* Callback */
+#define	TY_CFRAMES	15	/* Compound-frames */
+#define	TY_MRRU		17	/* Max Reconstructed Receive Unit (MP) */
+#define	TY_SHORTSEQ	18	/* Want short seqs (12bit) please (see mp.h) */
+#define	TY_ENDDISC	19	/* Endpoint discriminator */
+
+struct mbuf;
+struct link;
+struct bundle;
+struct cmdargs;
+
+#define fsm2lcp(fp) (fp->proto == PROTO_LCP ? (struct lcp *)fp : NULL)
+
+extern void lcp_Init(struct lcp *, struct bundle *, struct link *,
+                     const struct fsm_parent *);
+extern void lcp_Setup(struct lcp *, int);
+
+extern void lcp_SendProtoRej(struct lcp *, u_char *, int);
+extern int lcp_SendIdentification(struct lcp *);
+extern void lcp_RecvIdentification(struct lcp *, char *);
+extern int lcp_ReportStatus(struct cmdargs const *);
+extern struct mbuf *lcp_Input(struct bundle *, struct link *, struct mbuf *);
+extern void lcp_SetupCallbacks(struct lcp *);
diff --git a/src/link.c b/src/link.c
new file mode 100644
index 0000000..44be029
--- /dev/null
+++ b/src/link.c
@@ -0,0 +1,412 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/link.c,v 1.21.26.1 2010/12/21 17:10:29 kensmith Exp $
+ *
+ */
+
+#include <sys/types.h>
+#include <netinet/in_systm.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "proto.h"
+#include "fsm.h"
+#include "descriptor.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "prompt.h"
+#include "async.h"
+#include "physical.h"
+#include "mp.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "auth.h"
+#include "pap.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "command.h"
+
+static void Despatch(struct bundle *, struct link *, struct mbuf *, u_short);
+
+static inline void
+link_AddInOctets(struct link *l, int n)
+{
+  if (l->stats.gather) {
+    throughput_addin(&l->stats.total, n);
+    if (l->stats.parent)
+      throughput_addin(l->stats.parent, n);
+  }
+}
+
+static inline void
+link_AddOutOctets(struct link *l, int n)
+{
+  if (l->stats.gather) {
+    throughput_addout(&l->stats.total, n);
+    if (l->stats.parent)
+      throughput_addout(l->stats.parent, n);
+  }
+}
+
+void
+link_SequenceQueue(struct link *l)
+{
+  struct mqueue *queue, *highest;
+
+  log_Printf(LogDEBUG, "link_SequenceQueue\n");
+
+  highest = LINK_HIGHQ(l);
+  for (queue = l->Queue; queue < highest; queue++)
+    while (queue->len)
+      m_enqueue(highest, m_dequeue(queue));
+}
+
+void
+link_DeleteQueue(struct link *l)
+{
+  struct mqueue *queue, *highest;
+
+  highest = LINK_HIGHQ(l);
+  for (queue = l->Queue; queue <= highest; queue++)
+    while (queue->top)
+      m_freem(m_dequeue(queue));
+}
+
+size_t
+link_QueueLen(struct link *l)
+{
+  unsigned i;
+  size_t len;
+
+  for (i = 0, len = 0; i < LINK_QUEUES(l); i++)
+    len += l->Queue[i].len;
+
+  return len;
+}
+
+size_t
+link_QueueBytes(struct link *l)
+{
+  unsigned i;
+  size_t len, bytes;
+  struct mbuf *m;
+
+  bytes = 0;
+  for (i = 0, len = 0; i < LINK_QUEUES(l); i++) {
+    len = l->Queue[i].len;
+    m = l->Queue[i].top;
+    while (len--) {
+      bytes += m_length(m);
+      m = m->m_nextpkt;
+    }
+  }
+
+  return bytes;
+}
+
+void
+link_PendingLowPriorityData(struct link *l, size_t *pkts, size_t *octets)
+{
+  struct mqueue *queue, *highest;
+  struct mbuf *m;
+  size_t len;
+
+  /*
+   * This is all rfc1989 stuff... because our LQR packet is going to bypass
+   * everything that's not in the highest priority queue, we must be able to
+   * subtract that data from our outgoing packet/octet counts.  However,
+   * we've already async-encoded our data at this point, but the async
+   * encodings MUSTn't be a part of the LQR-reported payload :(  So, we have
+   * the async layer record how much it's padded the packet in the mbuf's
+   * priv field, and when we calculate our outgoing LQR values we subtract
+   * this value for each packet from the octet count sent.
+   */
+
+  highest = LINK_HIGHQ(l);
+  *pkts = *octets = 0;
+  for (queue = l->Queue; queue < highest; queue++) {
+    len = queue->len;
+    *pkts += len;
+    for (m = queue->top; len--; m = m->m_nextpkt)
+      *octets += m_length(m) - m->priv;
+  }
+}
+
+struct mbuf *
+link_Dequeue(struct link *l)
+{
+  int pri;
+  struct mbuf *bp;
+
+  for (bp = NULL, pri = LINK_QUEUES(l) - 1; pri >= 0; pri--)
+    if (l->Queue[pri].len) {
+      bp = m_dequeue(l->Queue + pri);
+      log_Printf(LogDEBUG, "link_Dequeue: Dequeued from queue %d,"
+                " containing %lu more packets\n", pri,
+                (u_long)l->Queue[pri].len);
+      break;
+    }
+
+  return bp;
+}
+
+static struct protostatheader {
+  u_short number;
+  const char *name;
+} ProtocolStat[NPROTOSTAT] = {
+  { PROTO_IP, "IP" },
+  { PROTO_VJUNCOMP, "VJ_UNCOMP" },
+  { PROTO_VJCOMP, "VJ_COMP" },
+  { PROTO_COMPD, "COMPD" },
+  { PROTO_ICOMPD, "ICOMPD" },
+  { PROTO_LCP, "LCP" },
+  { PROTO_IPCP, "IPCP" },
+  { PROTO_CCP, "CCP" },
+  { PROTO_PAP, "PAP" },
+  { PROTO_LQR, "LQR" },
+  { PROTO_CHAP, "CHAP" },
+  { PROTO_MP, "MULTILINK" },
+  { 0, "Others" }
+};
+
+void
+link_ProtocolRecord(struct link *l, u_short proto, int type)
+{
+  int i;
+
+  for (i = 0; i < NPROTOSTAT; i++)
+    if (ProtocolStat[i].number == proto)
+      break;
+
+  if (type == PROTO_IN)
+    l->proto_in[i]++;
+  else
+    l->proto_out[i]++;
+}
+
+void
+link_ReportProtocolStatus(struct link *l, struct prompt *prompt)
+{
+  int i;
+
+  prompt_Printf(prompt, "    Protocol     in        out      "
+                "Protocol      in       out\n");
+  for (i = 0; i < NPROTOSTAT; i++) {
+    prompt_Printf(prompt, "   %-9s: %8lu, %8lu",
+	    ProtocolStat[i].name, l->proto_in[i], l->proto_out[i]);
+    if ((i % 2) == 0)
+      prompt_Printf(prompt, "\n");
+  }
+  if (!(i % 2))
+    prompt_Printf(prompt, "\n");
+}
+
+void
+link_PushPacket(struct link *l, struct mbuf *bp, struct bundle *b, int pri,
+                u_short proto)
+{
+  int layer;
+
+  /*
+   * When we ``push'' a packet into the link, it gets processed by the
+   * ``push'' function in each layer starting at the top.
+   * We never expect the result of a ``push'' to be more than one
+   * packet (as we do with ``pull''s).
+   */
+
+  if(pri < 0 || (unsigned)pri >= LINK_QUEUES(l))
+    pri = 0;
+
+  bp->priv = 0;		/* Adjusted by the async layer ! */
+  for (layer = l->nlayers; layer && bp; layer--)
+    if (l->layer[layer - 1]->push != NULL)
+      bp = (*l->layer[layer - 1]->push)(b, l, bp, pri, &proto);
+
+  if (bp) {
+    link_AddOutOctets(l, m_length(bp));
+    log_Printf(LogDEBUG, "link_PushPacket: Transmit proto 0x%04x\n", proto);
+    m_enqueue(l->Queue + pri, m_pullup(bp));
+  }
+}
+
+void
+link_PullPacket(struct link *l, char *buf, size_t len, struct bundle *b)
+{
+  struct mbuf *bp, *lbp[LAYER_MAX], *next;
+  u_short lproto[LAYER_MAX], proto;
+  int layer;
+
+  /*
+   * When we ``pull'' a packet from the link, it gets processed by the
+   * ``pull'' function in each layer starting at the bottom.
+   * Each ``pull'' may produce multiple packets, chained together using
+   * bp->m_nextpkt.
+   * Each packet that results from each pull has to be pulled through
+   * all of the higher layers before the next resulting packet is pulled
+   * through anything; this ensures that packets that depend on the
+   * fsm state resulting from the receipt of the previous packet aren't
+   * surprised.
+   */
+
+  link_AddInOctets(l, len);
+
+  memset(lbp, '\0', sizeof lbp);
+  lbp[0] = m_get(len, MB_UNKNOWN);
+  memcpy(MBUF_CTOP(lbp[0]), buf, len);
+  lproto[0] = 0;
+  layer = 0;
+
+  while (layer || lbp[layer]) {
+    if (lbp[layer] == NULL) {
+      layer--;
+      continue;
+    }
+    bp = lbp[layer];
+    lbp[layer] = bp->m_nextpkt;
+    bp->m_nextpkt = NULL;
+    proto = lproto[layer];
+
+    if (l->layer[layer]->pull != NULL)
+      bp = (*l->layer[layer]->pull)(b, l, bp, &proto);
+
+    if (layer == l->nlayers - 1) {
+      /* We've just done the top layer, despatch the packet(s) */
+      while (bp) {
+        next = bp->m_nextpkt;
+        bp->m_nextpkt = NULL;
+        log_Printf(LogDEBUG, "link_PullPacket: Despatch proto 0x%04x\n", proto);
+        Despatch(b, l, bp, proto);
+        bp = next;
+      }
+    } else {
+      lbp[++layer] = bp;
+      lproto[layer] = proto;
+    }
+  }
+}
+
+int
+link_Stack(struct link *l, struct layer *layer)
+{
+  if (l->nlayers == sizeof l->layer / sizeof l->layer[0]) {
+    log_Printf(LogERROR, "%s: Oops, cannot stack a %s layer...\n",
+               l->name, layer->name);
+    return 0;
+  }
+  l->layer[l->nlayers++] = layer;
+  return 1;
+}
+
+void
+link_EmptyStack(struct link *l)
+{
+  l->nlayers = 0;
+}
+
+static const struct {
+  u_short proto;
+  struct mbuf *(*fn)(struct bundle *, struct link *, struct mbuf *);
+} despatcher[] = {
+  { PROTO_IP, ipv4_Input },
+#ifndef NOINET6
+  { PROTO_IPV6, ipv6_Input },
+#endif
+  { PROTO_MP, mp_Input },
+  { PROTO_LCP, lcp_Input },
+  { PROTO_IPCP, ipcp_Input },
+#ifndef NOINET6
+  { PROTO_IPV6CP, ipv6cp_Input },
+#endif
+  { PROTO_PAP, pap_Input },
+  { PROTO_CHAP, chap_Input },
+  { PROTO_CCP, ccp_Input },
+  { PROTO_LQR, lqr_Input },
+  { PROTO_CBCP, cbcp_Input }
+};
+
+#define DSIZE (sizeof despatcher / sizeof despatcher[0])
+
+static void
+Despatch(struct bundle *bundle, struct link *l, struct mbuf *bp, u_short proto)
+{
+  unsigned f;
+
+  for (f = 0; f < DSIZE; f++)
+    if (despatcher[f].proto == proto) {
+      bp = (*despatcher[f].fn)(bundle, l, bp);
+      break;
+    }
+
+  if (bp) {
+    struct physical *p = link2physical(l);
+
+    log_Printf(LogPHASE, "%s protocol 0x%04x (%s)\n",
+               f == DSIZE ? "Unknown" : "Unexpected", proto,
+               hdlc_Protocol2Nam(proto));
+    bp = m_pullup(proto_Prepend(bp, proto, 0, 0));
+    lcp_SendProtoRej(&l->lcp, MBUF_CTOP(bp), bp->m_len);
+    if (p) {
+      p->hdlc.lqm.ifInDiscards++;
+      p->hdlc.stats.unknownproto++;
+    }
+    m_freem(bp);
+  }
+}
+
+int
+link_ShowLayers(struct cmdargs const *arg)
+{
+  struct link *l = command_ChooseLink(arg);
+  int layer;
+
+  for (layer = l->nlayers; layer; layer--)
+    prompt_Printf(arg->prompt, "%s%s", layer == l->nlayers ? "" : ", ",
+                  l->layer[layer - 1]->name);
+  if (l->nlayers)
+    prompt_Printf(arg->prompt, "\n");
+
+  return 0;
+}
diff --git a/src/link.h b/src/link.h
new file mode 100644
index 0000000..e824d7d
--- /dev/null
+++ b/src/link.h
@@ -0,0 +1,81 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/link.h,v 1.11.34.1 2010/12/21 17:10:29 kensmith Exp $
+ *
+ */
+
+
+#define PHYSICAL_LINK	1
+#define LOGICAL_LINK	2
+
+#define NPROTOSTAT 13
+
+struct bundle;
+struct prompt;
+struct cmdargs;
+
+struct link {
+  int type;                               /* _LINK type */
+  const char *name;                       /* Points to datalink::name */
+  int len;                                /* full size of parent struct */
+  struct {
+    unsigned gather : 1;                  /* Gather statistics ourself ? */
+    struct pppThroughput total;           /* Link throughput statistics */
+    struct pppThroughput *parent;         /* MP link throughput statistics */
+  } stats;
+  struct mqueue Queue[2];                 /* Our output queue of mbufs */
+
+  u_long proto_in[NPROTOSTAT];            /* outgoing protocol stats */
+  u_long proto_out[NPROTOSTAT];           /* incoming protocol stats */
+
+  struct lcp lcp;                         /* Our line control FSM */
+  struct ccp ccp;                         /* Our compression FSM */
+
+  struct layer const *layer[LAYER_MAX];   /* i/o layers */
+  int nlayers;
+};
+
+#define LINK_QUEUES(link) (sizeof (link)->Queue / sizeof (link)->Queue[0])
+#define LINK_HIGHQ(link) ((link)->Queue + LINK_QUEUES(link) - 1)
+
+extern void link_SequenceQueue(struct link *);
+extern void link_DeleteQueue(struct link *);
+extern size_t link_QueueLen(struct link *);
+extern size_t link_QueueBytes(struct link *);
+extern void link_PendingLowPriorityData(struct link *, size_t *, size_t *);
+extern struct mbuf *link_Dequeue(struct link *);
+
+extern void link_PushPacket(struct link *, struct mbuf *, struct bundle *,
+                            int, u_short);
+extern void link_PullPacket(struct link *, char *, size_t, struct bundle *);
+extern int link_Stack(struct link *, struct layer *);
+extern void link_EmptyStack(struct link *);
+
+#define PROTO_IN  1                       /* third arg to link_ProtocolRecord */
+#define PROTO_OUT 2
+extern void link_ProtocolRecord(struct link *, u_short, int);
+extern void link_ReportProtocolStatus(struct link *, struct prompt *);
+extern int link_ShowLayers(struct cmdargs const *);
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..facbd4a
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,521 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/log.c,v 1.53.34.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "descriptor.h"
+#include "prompt.h"
+
+static const char *const LogNames[] = {
+  "Async",
+  "CBCP",
+  "CCP",
+  "Chat",
+  "Command",
+  "Connect",
+  "Debug",
+  "DNS",
+  "Filter",			/* Log discarded packets */
+  "HDLC",
+  "ID0",
+  "IPCP",
+  "IPV6CP",
+  "LCP",
+  "LQM",
+  "Phase",
+  "Physical",
+  "Radius",
+  "Sync",
+  "TCP/IP",
+  "Timer",
+  "Tun",
+  "Warning",
+  "Error",
+  "Alert"
+};
+
+#define MSK(n) (1<<((n)-1))
+
+static u_long LogMask = MSK(LogPHASE);
+static u_long LogMaskLocal = MSK(LogERROR) | MSK(LogALERT) | MSK(LogWARN);
+static int LogTunno = -1;
+static struct prompt *promptlist;	/* Where to log local stuff */
+struct prompt *log_PromptContext;
+int log_PromptListChanged;
+
+struct prompt *
+log_PromptList()
+{
+  return promptlist;
+}
+
+void
+log_RegisterPrompt(struct prompt *prompt)
+{
+  prompt->next = promptlist;
+  promptlist = prompt;
+  prompt->active = 1;
+  log_DiscardAllLocal(&prompt->logmask);
+}
+
+void
+log_ActivatePrompt(struct prompt *prompt)
+{
+  prompt->active = 1;
+  LogMaskLocal |= prompt->logmask;
+}
+
+static void
+LogSetMaskLocal(void)
+{
+  struct prompt *p;
+
+  LogMaskLocal = MSK(LogERROR) | MSK(LogALERT) | MSK(LogWARN);
+  for (p = promptlist; p; p = p->next)
+    LogMaskLocal |= p->logmask;
+}
+
+void
+log_DeactivatePrompt(struct prompt *prompt)
+{
+  if (prompt->active) {
+    prompt->active = 0;
+    LogSetMaskLocal();
+  }
+}
+
+void
+log_UnRegisterPrompt(struct prompt *prompt)
+{
+  if (prompt) {
+    struct prompt **p;
+
+    for (p = &promptlist; *p; p = &(*p)->next)
+      if (*p == prompt) {
+        *p = prompt->next;
+        prompt->next = NULL;
+        break;
+      }
+    LogSetMaskLocal();
+    log_PromptListChanged++;
+  }
+}
+
+void
+log_DestroyPrompts(struct server *s)
+{
+  struct prompt *p, *pn, *pl;
+
+  p = promptlist;
+  pl = NULL;
+  while (p) {
+    pn = p->next;
+    if (s && p->owner == s) {
+      if (pl)
+        pl->next = p->next;
+      else
+        promptlist = p->next;
+      p->next = NULL;
+      prompt_Destroy(p, 1);
+    } else
+      pl = p;
+    p = pn;
+  }
+}
+
+void
+log_DisplayPrompts()
+{
+  struct prompt *p;
+
+  for (p = promptlist; p; p = p->next)
+    prompt_Required(p);
+}
+
+void
+log_WritePrompts(struct datalink *dl, const char *fmt,...)
+{
+  va_list ap;
+  struct prompt *p;
+
+  va_start(ap, fmt);
+  for (p = promptlist; p; p = p->next)
+    if (prompt_IsTermMode(p, dl))
+      prompt_vPrintf(p, fmt, ap);
+  va_end(ap);
+}
+
+void
+log_SetTtyCommandMode(struct datalink *dl)
+{
+  struct prompt *p;
+
+  for (p = promptlist; p; p = p->next)
+    if (prompt_IsTermMode(p, dl))
+      prompt_TtyCommandMode(p);
+}
+
+static int
+syslogLevel(int lev)
+{
+  switch (lev) {
+  case LogLOG:
+    return LOG_INFO;
+  case LogDEBUG:
+  case LogTIMER:
+    return LOG_DEBUG;
+  case LogWARN:
+    return LOG_WARNING;
+  case LogERROR:
+    return LOG_ERR;
+  case LogALERT:
+    return LOG_ALERT;
+  }
+  return lev >= LogMIN && lev <= LogMAX ? LOG_INFO : 0;
+}
+
+const char *
+log_Name(int id)
+{
+  if (id == LogLOG)
+    return "LOG";
+  return id < LogMIN || id > LogMAX ? "Unknown" : LogNames[id - 1];
+}
+
+void
+log_Keep(int id)
+{
+  if (id >= LogMIN && id <= LogMAXCONF)
+    LogMask |= MSK(id);
+}
+
+void
+log_KeepLocal(int id, u_long *mask)
+{
+  if (id >= LogMIN && id <= LogMAXCONF) {
+    LogMaskLocal |= MSK(id);
+    *mask |= MSK(id);
+  }
+}
+
+void
+log_Discard(int id)
+{
+  if (id >= LogMIN && id <= LogMAXCONF)
+    LogMask &= ~MSK(id);
+}
+
+void
+log_DiscardLocal(int id, u_long *mask)
+{
+  if (id >= LogMIN && id <= LogMAXCONF) {
+    *mask &= ~MSK(id);
+    LogSetMaskLocal();
+  }
+}
+
+void
+log_DiscardAll()
+{
+  LogMask = 0;
+}
+
+void
+log_DiscardAllLocal(u_long *mask)
+{
+  *mask = MSK(LogERROR) | MSK(LogALERT) | MSK(LogWARN);
+  LogSetMaskLocal();
+}
+
+int
+log_IsKept(int id)
+{
+  if (id == LogLOG)
+    return LOG_KEPT_SYSLOG;
+  if (id < LogMIN || id > LogMAX)
+    return 0;
+  if (id > LogMAXCONF)
+    return LOG_KEPT_LOCAL | LOG_KEPT_SYSLOG;
+
+  return ((LogMaskLocal & MSK(id)) ? LOG_KEPT_LOCAL : 0) |
+    ((LogMask & MSK(id)) ? LOG_KEPT_SYSLOG : 0);
+}
+
+int
+log_IsKeptLocal(int id, u_long mask)
+{
+  if (id < LogMIN || id > LogMAX)
+    return 0;
+  if (id > LogMAXCONF)
+    return LOG_KEPT_LOCAL | LOG_KEPT_SYSLOG;
+
+  return ((mask & MSK(id)) ? LOG_KEPT_LOCAL : 0) |
+    ((LogMask & MSK(id)) ? LOG_KEPT_SYSLOG : 0);
+}
+
+void
+log_Open(const char *Name)
+{
+  openlog(Name, LOG_PID, LOG_DAEMON);
+}
+
+void
+log_SetTun(int tunno)
+{
+  LogTunno = tunno;
+}
+
+void
+log_Close()
+{
+  closelog();
+  LogTunno = -1;
+}
+
+void
+log_Printf(int lev, const char *fmt,...)
+{
+  va_list ap;
+  struct prompt *prompt;
+
+  if (log_IsKept(lev)) {
+    char nfmt[200];
+
+    va_start(ap, fmt);
+    if (promptlist && (log_IsKept(lev) & LOG_KEPT_LOCAL)) {
+      if ((log_IsKept(LogTUN) & LOG_KEPT_LOCAL) && LogTunno != -1)
+        snprintf(nfmt, sizeof nfmt, "%s%d: %s: %s", TUN_NAME,
+	         LogTunno, log_Name(lev), fmt);
+      else
+        snprintf(nfmt, sizeof nfmt, "%s: %s", log_Name(lev), fmt);
+
+      if (log_PromptContext && lev == LogWARN)
+        /* Warnings just go to the current prompt */
+        prompt_vPrintf(log_PromptContext, nfmt, ap);
+      else for (prompt = promptlist; prompt; prompt = prompt->next)
+        if (lev > LogMAXCONF || (prompt->logmask & MSK(lev)))
+          prompt_vPrintf(prompt, nfmt, ap);
+    }
+    va_end(ap);
+
+    va_start(ap, fmt);
+    if ((log_IsKept(lev) & LOG_KEPT_SYSLOG) &&
+        (lev != LogWARN || !log_PromptContext)) {
+      if ((log_IsKept(LogTUN) & LOG_KEPT_SYSLOG) && LogTunno != -1)
+        snprintf(nfmt, sizeof nfmt, "%s%d: %s: %s", TUN_NAME,
+	         LogTunno, log_Name(lev), fmt);
+      else
+        snprintf(nfmt, sizeof nfmt, "%s: %s", log_Name(lev), fmt);
+      vsyslog(syslogLevel(lev), nfmt, ap);
+    }
+    va_end(ap);
+  }
+}
+
+void
+log_DumpBp(int lev, const char *hdr, const struct mbuf *bp)
+{
+  if (log_IsKept(lev)) {
+    char buf[68];
+    char *b, *c;
+    const u_char *ptr;
+    int f;
+
+    if (hdr && *hdr)
+      log_Printf(lev, "%s\n", hdr);
+
+    b = buf;
+    c = b + 50;
+    do {
+      f = bp->m_len;
+      ptr = CONST_MBUF_CTOP(bp);
+      while (f--) {
+	sprintf(b, " %02x", (int) *ptr);
+        *c++ = isprint(*ptr) ? *ptr : '.';
+        ptr++;
+        b += 3;
+        if (b == buf + 48) {
+          memset(b, ' ', 2);
+          *c = '\0';
+          log_Printf(lev, "%s\n", buf);
+          b = buf;
+          c = b + 50;
+        }
+      }
+    } while ((bp = bp->m_next) != NULL);
+
+    if (b > buf) {
+      memset(b, ' ', 50 - (b - buf));
+      *c = '\0';
+      log_Printf(lev, "%s\n", buf);
+    }
+  }
+}
+
+void
+log_DumpBuff(int lev, const char *hdr, const u_char *ptr, int n)
+{
+  if (log_IsKept(lev)) {
+    char buf[68];
+    char *b, *c;
+
+    if (hdr && *hdr)
+      log_Printf(lev, "%s\n", hdr);
+    while (n > 0) {
+      b = buf;
+      c = b + 50;
+      for (b = buf; b != buf + 48 && n--; b += 3, ptr++) {
+	sprintf(b, " %02x", (int) *ptr);
+        *c++ = isprint(*ptr) ? *ptr : '.';
+      }
+      memset(b, ' ', 50 - (b - buf));
+      *c = '\0';
+      log_Printf(lev, "%s\n", buf);
+    }
+  }
+}
+
+int
+log_ShowLevel(struct cmdargs const *arg)
+{
+  int i;
+
+  prompt_Printf(arg->prompt, "Log:  ");
+  for (i = LogMIN; i <= LogMAX; i++)
+    if (log_IsKept(i) & LOG_KEPT_SYSLOG)
+      prompt_Printf(arg->prompt, " %s", log_Name(i));
+
+  prompt_Printf(arg->prompt, "\nLocal:");
+  for (i = LogMIN; i <= LogMAX; i++)
+    if (log_IsKeptLocal(i, arg->prompt->logmask) & LOG_KEPT_LOCAL)
+      prompt_Printf(arg->prompt, " %s", log_Name(i));
+
+  prompt_Printf(arg->prompt, "\n");
+
+  return 0;
+}
+
+int
+log_SetLevel(struct cmdargs const *arg)
+{
+  int i, res, argc, local;
+  char const *const *argv, *argp;
+
+  argc = arg->argc - arg->argn;
+  argv = arg->argv + arg->argn;
+  res = 0;
+
+  if (argc == 0 || strcasecmp(argv[0], "local"))
+    local = 0;
+  else {
+    if (arg->prompt == NULL) {
+      log_Printf(LogWARN, "set log local: Only available on the"
+                 " command line\n");
+      return 1;
+    }
+    argc--;
+    argv++;
+    local = 1;
+  }
+
+  if (argc == 0 || (argv[0][0] != '+' && argv[0][0] != '-')) {
+    if (local)
+      log_DiscardAllLocal(&arg->prompt->logmask);
+    else
+      log_DiscardAll();
+  }
+
+  while (argc--) {
+    argp = **argv == '+' || **argv == '-' ? *argv + 1 : *argv;
+    /* Special case 'all' */
+    if (strcasecmp(argp, "all") == 0) {
+        if (**argv == '-') {
+          if (local)
+            for (i = LogMIN; i <= LogMAX; i++)
+              log_DiscardLocal(i, &arg->prompt->logmask);
+          else
+            for (i = LogMIN; i <= LogMAX; i++)
+              log_Discard(i);
+        } else if (local)
+          for (i = LogMIN; i <= LogMAX; i++)
+            log_KeepLocal(i, &arg->prompt->logmask);
+        else
+          for (i = LogMIN; i <= LogMAX; i++)
+            log_Keep(i);
+        argv++;
+        continue;
+    }
+    for (i = LogMIN; i <= LogMAX; i++)
+      if (strcasecmp(argp, log_Name(i)) == 0) {
+	if (**argv == '-') {
+          if (local)
+            log_DiscardLocal(i, &arg->prompt->logmask);
+          else
+	    log_Discard(i);
+	} else if (local)
+          log_KeepLocal(i, &arg->prompt->logmask);
+        else
+          log_Keep(i);
+	break;
+      }
+    if (i > LogMAX) {
+      log_Printf(LogWARN, "%s: Invalid log value\n", argp);
+      res = -1;
+    }
+    argv++;
+  }
+  return res;
+}
+
+int
+log_ShowWho(struct cmdargs const *arg)
+{
+  struct prompt *p;
+
+  for (p = promptlist; p; p = p->next) {
+    prompt_Printf(arg->prompt, "%s (%s)", p->src.type, p->src.from);
+    if (p == arg->prompt)
+      prompt_Printf(arg->prompt, " *");
+    if (!p->active)
+      prompt_Printf(arg->prompt, " ^Z");
+    prompt_Printf(arg->prompt, "\n");
+  }
+
+  return 0;
+}
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..69fd1d4
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,105 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/log.h,v 1.34.34.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define LogLOG		(0)
+#define LogMIN		(1)
+#define LogASYNC	(1)	/* syslog(LOG_INFO, ....)	 */
+#define LogCBCP		(2)
+#define LogCCP		(3)
+#define LogCHAT		(4)
+#define LogCOMMAND	(5)
+#define LogCONNECT	(6)
+#define LogDEBUG	(7)	/* syslog(LOG_DEBUG, ....)	 */
+#define LogDNS		(8)
+#define LogFILTER       (9)
+#define LogHDLC		(10)
+#define LogID0		(11)
+#define LogIPCP		(12)
+#define LogIPV6CP	(13)
+#define LogLCP		(14)
+#define LogLQM		(15)
+#define LogPHASE	(16)
+#define LogPHYSICAL	(17)	/* syslog(LOG_INFO, ....)	 */
+#define LogRADIUS	(18)	/* syslog(LOG_INFO, ....)	 */
+#define LogSYNC		(19)	/* syslog(LOG_INFO, ....)	 */
+#define LogTCPIP	(20)
+#define LogTIMER	(21)	/* syslog(LOG_DEBUG, ....)	 */
+#define LogTUN		(22)	/* If set, tun%d is output with each message */
+#define LogWARN		(23)	/* Sent to VarTerm else syslog(LOG_WARNING, ) */
+#define LogERROR	(24)	/* syslog(LOG_ERR, ....), + sent to VarTerm */
+#define LogALERT	(25)	/* syslog(LOG_ALERT, ....)	 */
+
+#define LogMAXCONF	(22)
+#define LogMAX		(25)
+
+struct mbuf;
+struct cmdargs;
+struct prompt;
+struct server;
+struct datalink;
+
+/* The first int arg for all of the following is one of the above values */
+extern const char *log_Name(int);
+extern void log_Keep(int);
+extern void log_KeepLocal(int, u_long *);
+extern void log_Discard(int);
+extern void log_DiscardLocal(int, u_long *);
+extern void log_DiscardAll(void);
+extern void log_DiscardAllLocal(u_long *);
+#define LOG_KEPT_SYSLOG (1)	/* Results of log_IsKept() */
+#define LOG_KEPT_LOCAL  (2)	/* Results of log_IsKept() */
+extern int log_IsKept(int);
+extern int log_IsKeptLocal(int, u_long);
+extern void log_Open(const char *);
+extern void log_SetTun(int);
+extern void log_Close(void);
+#ifdef __GNUC__
+extern void log_Printf(int, const char *,...)
+            __attribute__ ((format (printf, 2, 3)));
+extern void log_WritePrompts(struct datalink *, const char *, ...)
+            __attribute__ ((format (printf, 2, 3)));
+#else
+extern void log_Printf(int, const char *,...);
+extern void log_WritePrompts(struct datalink *, const char *, ...);
+#endif
+extern void log_DumpBp(int, const char *, const struct mbuf *);
+extern void log_DumpBuff(int, const char *, const u_char *, int);
+extern int log_ShowLevel(struct cmdargs const *);
+extern int log_SetLevel(struct cmdargs const *);
+extern int log_ShowWho(struct cmdargs const *);
+
+extern struct prompt *log_PromptContext;
+extern int log_PromptListChanged;
+extern void log_RegisterPrompt(struct prompt *);
+extern void log_UnRegisterPrompt(struct prompt *);
+extern void log_DestroyPrompts(struct server *);
+extern void log_DisplayPrompts(void);
+extern void log_ActivatePrompt(struct prompt *);
+extern void log_DeactivatePrompt(struct prompt *);
+extern void log_SetTtyCommandMode(struct datalink *);
+extern struct prompt *log_PromptList(void);
diff --git a/src/lqr.c b/src/lqr.c
new file mode 100644
index 0000000..cb9f12d
--- /dev/null
+++ b/src/lqr.c
@@ -0,0 +1,532 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/lqr.c,v 1.49.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+
+#ifdef __FreeBSD__
+#include <netinet/in.h>
+#endif
+#include <sys/un.h>
+
+#include <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "acf.h"
+#include "proto.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "async.h"
+#include "throughput.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "mp.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "command.h"
+#include "cbcp.h"
+#include "datalink.h"
+
+struct echolqr {
+  u_int32_t magic;
+  u_int32_t signature;
+  u_int32_t sequence;
+};
+
+#define	SIGNATURE  0x594e4f54
+
+static void
+SendEchoReq(struct lcp *lcp)
+{
+  struct hdlc *hdlc = &link2physical(lcp->fsm.link)->hdlc;
+  struct echolqr echo;
+
+  echo.magic = htonl(lcp->want_magic);
+  echo.signature = htonl(SIGNATURE);
+  echo.sequence = htonl(hdlc->lqm.echo.seq_sent);
+  fsm_Output(&lcp->fsm, CODE_ECHOREQ, hdlc->lqm.echo.seq_sent++,
+            (u_char *)&echo, sizeof echo, MB_ECHOOUT);
+}
+
+struct mbuf *
+lqr_RecvEcho(struct fsm *fp, struct mbuf *bp)
+{
+  struct hdlc *hdlc = &link2physical(fp->link)->hdlc;
+  struct lcp *lcp = fsm2lcp(fp);
+  struct echolqr lqr;
+
+  if (m_length(bp) >= sizeof lqr) {
+    m_freem(mbuf_Read(bp, &lqr, sizeof lqr));
+    bp = NULL;
+    lqr.magic = ntohl(lqr.magic);
+    lqr.signature = ntohl(lqr.signature);
+    lqr.sequence = ntohl(lqr.sequence);
+
+    /* Tolerate echo replies with either magic number */
+    if (lqr.magic != 0 && lqr.magic != lcp->his_magic &&
+        lqr.magic != lcp->want_magic) {
+      log_Printf(LogWARN, "%s: lqr_RecvEcho: Bad magic: expected 0x%08x,"
+                 " got 0x%08x\n", fp->link->name, lcp->his_magic, lqr.magic);
+      /*
+       * XXX: We should send a terminate request. But poor implementations may
+       *      die as a result.
+       */
+    }
+    if (lqr.signature == SIGNATURE) {
+      /* careful not to update lqm.echo.seq_recv with older values */
+      if ((hdlc->lqm.echo.seq_recv > (u_int32_t)0 - 5 && lqr.sequence < 5) ||
+          (hdlc->lqm.echo.seq_recv <= (u_int32_t)0 - 5 &&
+           lqr.sequence > hdlc->lqm.echo.seq_recv))
+        hdlc->lqm.echo.seq_recv = lqr.sequence;
+    } else
+      log_Printf(LogWARN, "lqr_RecvEcho: Got sig 0x%08lx, not 0x%08lx !\n",
+                (u_long)lqr.signature, (u_long)SIGNATURE);
+  } else
+    log_Printf(LogWARN, "lqr_RecvEcho: Got packet size %zd, expecting %ld !\n",
+              m_length(bp), (long)sizeof(struct echolqr));
+  return bp;
+}
+
+void
+lqr_ChangeOrder(struct lqrdata *src, struct lqrdata *dst)
+{
+  u_int32_t *sp, *dp;
+  unsigned n;
+
+  sp = (u_int32_t *) src;
+  dp = (u_int32_t *) dst;
+  for (n = 0; n < sizeof(struct lqrdata) / sizeof(u_int32_t); n++, sp++, dp++)
+    *dp = ntohl(*sp);
+}
+
+static void
+SendLqrData(struct lcp *lcp)
+{
+  struct mbuf *bp;
+  int extra;
+
+  extra = proto_WrapperOctets(lcp, PROTO_LQR) +
+          acf_WrapperOctets(lcp, PROTO_LQR);
+  bp = m_get(sizeof(struct lqrdata) + extra, MB_LQROUT);
+  bp->m_len -= extra;
+  bp->m_offset += extra;
+
+  /*
+   * Send on the highest priority queue.  We send garbage - the real data
+   * is written by lqr_LayerPush() where we know how to fill in all the
+   * fields.  Note, lqr_LayerPush() ``knows'' that we're pushing onto the
+   * highest priority queue, and factors out packet & octet values from
+   * other queues!
+   */
+  link_PushPacket(lcp->fsm.link, bp, lcp->fsm.bundle,
+                  LINK_QUEUES(lcp->fsm.link) - 1, PROTO_LQR);
+}
+
+static void
+SendLqrReport(void *v)
+{
+  struct lcp *lcp = (struct lcp *)v;
+  struct physical *p = link2physical(lcp->fsm.link);
+
+  timer_Stop(&p->hdlc.lqm.timer);
+
+  if (p->hdlc.lqm.method & LQM_LQR) {
+    if (p->hdlc.lqm.lqr.resent > 5) {
+      /* XXX: Should implement LQM strategy */
+      log_Printf(LogPHASE, "%s: ** Too many LQR packets lost **\n",
+                lcp->fsm.link->name);
+      log_Printf(LogLQM, "%s: Too many LQR packets lost\n",
+                lcp->fsm.link->name);
+      p->hdlc.lqm.method = 0;
+      datalink_Down(p->dl, CLOSE_NORMAL);
+    } else {
+      SendLqrData(lcp);
+      p->hdlc.lqm.lqr.resent++;
+    }
+  } else if (p->hdlc.lqm.method & LQM_ECHO) {
+    if ((p->hdlc.lqm.echo.seq_sent > 5 &&
+         p->hdlc.lqm.echo.seq_sent - 5 > p->hdlc.lqm.echo.seq_recv) ||
+        (p->hdlc.lqm.echo.seq_sent <= 5 &&
+         p->hdlc.lqm.echo.seq_sent > p->hdlc.lqm.echo.seq_recv + 5)) {
+      log_Printf(LogPHASE, "%s: ** Too many LCP ECHO packets lost **\n",
+                lcp->fsm.link->name);
+      log_Printf(LogLQM, "%s: Too many LCP ECHO packets lost\n",
+                lcp->fsm.link->name);
+      p->hdlc.lqm.method = 0;
+      datalink_Down(p->dl, CLOSE_NORMAL);
+    } else
+      SendEchoReq(lcp);
+  }
+  if (p->hdlc.lqm.method && p->hdlc.lqm.timer.load)
+    timer_Start(&p->hdlc.lqm.timer);
+}
+
+struct mbuf *
+lqr_Input(struct bundle *bundle __unused, struct link *l, struct mbuf *bp)
+{
+  struct physical *p = link2physical(l);
+  struct lcp *lcp = p->hdlc.lqm.owner;
+  int len;
+
+  if (p == NULL) {
+    log_Printf(LogERROR, "lqr_Input: Not a physical link - dropped\n");
+    m_freem(bp);
+    return NULL;
+  }
+
+  len = m_length(bp);
+  if (len != sizeof(struct lqrdata))
+    log_Printf(LogWARN, "lqr_Input: Got packet size %d, expecting %ld !\n",
+              len, (long)sizeof(struct lqrdata));
+  else if (!IsAccepted(l->lcp.cfg.lqr) && !(p->hdlc.lqm.method & LQM_LQR)) {
+    bp = m_pullup(proto_Prepend(bp, PROTO_LQR, 0, 0));
+    lcp_SendProtoRej(lcp, MBUF_CTOP(bp), bp->m_len);
+  } else {
+    struct lqrdata *lqr;
+
+    bp = m_pullup(bp);
+    lqr = (struct lqrdata *)MBUF_CTOP(bp);
+    if (ntohl(lqr->MagicNumber) != lcp->his_magic)
+      log_Printf(LogWARN, "lqr_Input: magic 0x%08lx is wrong,"
+                 " expecting 0x%08lx\n",
+		 (u_long)ntohl(lqr->MagicNumber), (u_long)lcp->his_magic);
+    else {
+      struct lqrdata lastlqr;
+
+      memcpy(&lastlqr, &p->hdlc.lqm.lqr.peer, sizeof lastlqr);
+      lqr_ChangeOrder(lqr, &p->hdlc.lqm.lqr.peer);
+      lqr_Dump(l->name, "Input", &p->hdlc.lqm.lqr.peer);
+      /* we have received an LQR from our peer */
+      p->hdlc.lqm.lqr.resent = 0;
+
+      /* Snapshot our state when the LQR packet was received */
+      memcpy(&p->hdlc.lqm.lqr.prevSave, &p->hdlc.lqm.lqr.Save,
+             sizeof p->hdlc.lqm.lqr.prevSave);
+      p->hdlc.lqm.lqr.Save.InLQRs = ++p->hdlc.lqm.lqr.InLQRs;
+      p->hdlc.lqm.lqr.Save.InPackets = p->hdlc.lqm.ifInUniPackets;
+      p->hdlc.lqm.lqr.Save.InDiscards = p->hdlc.lqm.ifInDiscards;
+      p->hdlc.lqm.lqr.Save.InErrors = p->hdlc.lqm.ifInErrors;
+      p->hdlc.lqm.lqr.Save.InOctets = p->hdlc.lqm.lqr.InGoodOctets;
+
+      lqr_Analyse(&p->hdlc, &lastlqr, &p->hdlc.lqm.lqr.peer);
+
+      /*
+       * Generate an LQR response if we're not running an LQR timer OR
+       * two successive LQR's PeerInLQRs are the same.
+       */
+      if (p->hdlc.lqm.timer.load == 0 || !(p->hdlc.lqm.method & LQM_LQR) ||
+          (lastlqr.PeerInLQRs &&
+           lastlqr.PeerInLQRs == p->hdlc.lqm.lqr.peer.PeerInLQRs))
+        SendLqrData(lcp);
+    }
+  }
+  m_freem(bp);
+  return NULL;
+}
+
+/*
+ *  When LCP is reached to opened state, We'll start LQM activity.
+ */
+static void
+lqr_Setup(struct lcp *lcp)
+{
+  struct physical *physical = link2physical(lcp->fsm.link);
+  int period;
+
+  physical->hdlc.lqm.lqr.resent = 0;
+  physical->hdlc.lqm.echo.seq_sent = 0;
+  physical->hdlc.lqm.echo.seq_recv = 0;
+  memset(&physical->hdlc.lqm.lqr.peer, '\0',
+         sizeof physical->hdlc.lqm.lqr.peer);
+
+  physical->hdlc.lqm.method = lcp->cfg.echo ? LQM_ECHO : 0;
+  if (IsEnabled(lcp->cfg.lqr) && !REJECTED(lcp, TY_QUALPROTO))
+    physical->hdlc.lqm.method |= LQM_LQR;
+  timer_Stop(&physical->hdlc.lqm.timer);
+
+  physical->hdlc.lqm.lqr.peer_timeout = lcp->his_lqrperiod;
+  if (lcp->his_lqrperiod)
+    log_Printf(LogLQM, "%s: Expecting LQR every %d.%02d secs\n",
+              physical->link.name, lcp->his_lqrperiod / 100,
+              lcp->his_lqrperiod % 100);
+
+  period = lcp->want_lqrperiod ?
+    lcp->want_lqrperiod : lcp->cfg.lqrperiod * 100;
+  physical->hdlc.lqm.timer.func = SendLqrReport;
+  physical->hdlc.lqm.timer.name = "lqm";
+  physical->hdlc.lqm.timer.arg = lcp;
+
+  if (lcp->want_lqrperiod || physical->hdlc.lqm.method & LQM_ECHO) {
+    log_Printf(LogLQM, "%s: Will send %s every %d.%02d secs\n",
+              physical->link.name, lcp->want_lqrperiod ? "LQR" : "LCP ECHO",
+              period / 100, period % 100);
+    physical->hdlc.lqm.timer.load = period * SECTICKS / 100;
+  } else {
+    physical->hdlc.lqm.timer.load = 0;
+    if (!lcp->his_lqrperiod)
+      log_Printf(LogLQM, "%s: LQR/LCP ECHO not negotiated\n",
+                 physical->link.name);
+  }
+}
+
+void
+lqr_Start(struct lcp *lcp)
+{
+  struct physical *p = link2physical(lcp->fsm.link);
+
+  lqr_Setup(lcp);
+  if (p->hdlc.lqm.timer.load)
+    SendLqrReport(lcp);
+}
+
+void
+lqr_reStart(struct lcp *lcp)
+{
+  struct physical *p = link2physical(lcp->fsm.link);
+
+  lqr_Setup(lcp);
+  if (p->hdlc.lqm.timer.load)
+    timer_Start(&p->hdlc.lqm.timer);
+}
+
+void
+lqr_StopTimer(struct physical *physical)
+{
+  timer_Stop(&physical->hdlc.lqm.timer);
+}
+
+void
+lqr_Stop(struct physical *physical, int method)
+{
+  if (method == LQM_LQR)
+    log_Printf(LogLQM, "%s: Stop sending LQR, Use LCP ECHO instead.\n",
+               physical->link.name);
+  if (method == LQM_ECHO)
+    log_Printf(LogLQM, "%s: Stop sending LCP ECHO.\n",
+               physical->link.name);
+  physical->hdlc.lqm.method &= ~method;
+  if (physical->hdlc.lqm.method)
+    SendLqrReport(physical->hdlc.lqm.owner);
+  else
+    timer_Stop(&physical->hdlc.lqm.timer);
+}
+
+void
+lqr_Dump(const char *link, const char *message, const struct lqrdata *lqr)
+{
+  if (log_IsKept(LogLQM)) {
+    log_Printf(LogLQM, "%s: %s:\n", link, message);
+    log_Printf(LogLQM, "  Magic:          %08x   LastOutLQRs:    %08x\n",
+	      lqr->MagicNumber, lqr->LastOutLQRs);
+    log_Printf(LogLQM, "  LastOutPackets: %08x   LastOutOctets:  %08x\n",
+	      lqr->LastOutPackets, lqr->LastOutOctets);
+    log_Printf(LogLQM, "  PeerInLQRs:     %08x   PeerInPackets:  %08x\n",
+	      lqr->PeerInLQRs, lqr->PeerInPackets);
+    log_Printf(LogLQM, "  PeerInDiscards: %08x   PeerInErrors:   %08x\n",
+	      lqr->PeerInDiscards, lqr->PeerInErrors);
+    log_Printf(LogLQM, "  PeerInOctets:   %08x   PeerOutLQRs:    %08x\n",
+	      lqr->PeerInOctets, lqr->PeerOutLQRs);
+    log_Printf(LogLQM, "  PeerOutPackets: %08x   PeerOutOctets:  %08x\n",
+	      lqr->PeerOutPackets, lqr->PeerOutOctets);
+  }
+}
+
+void
+lqr_Analyse(const struct hdlc *hdlc, const struct lqrdata *oldlqr,
+            const struct lqrdata *newlqr)
+{
+  u_int32_t LQRs, transitLQRs, pkts, octets, disc, err;
+
+  if (!newlqr->PeerInLQRs)	/* No analysis possible yet! */
+    return;
+
+  log_Printf(LogLQM, "Analysis:\n");
+
+  LQRs = (newlqr->LastOutLQRs - oldlqr->LastOutLQRs) -
+         (newlqr->PeerInLQRs - oldlqr->PeerInLQRs);
+  transitLQRs = hdlc->lqm.lqr.OutLQRs - newlqr->LastOutLQRs;
+  pkts = (newlqr->LastOutPackets - oldlqr->LastOutPackets) -
+         (newlqr->PeerInPackets - oldlqr->PeerInPackets);
+  octets = (newlqr->LastOutOctets - oldlqr->LastOutOctets) -
+           (newlqr->PeerInOctets - oldlqr->PeerInOctets);
+  log_Printf(LogLQM, "  Outbound lossage: %d LQR%s (%d en route), %d packet%s,"
+             " %d octet%s\n", (int)LQRs, LQRs == 1 ? "" : "s", (int)transitLQRs,
+	     (int)pkts, pkts == 1 ? "" : "s",
+	     (int)octets, octets == 1 ? "" : "s");
+
+  pkts = (newlqr->PeerOutPackets - oldlqr->PeerOutPackets) -
+    (hdlc->lqm.lqr.Save.InPackets - hdlc->lqm.lqr.prevSave.InPackets);
+  octets = (newlqr->PeerOutOctets - oldlqr->PeerOutOctets) -
+    (hdlc->lqm.lqr.Save.InOctets - hdlc->lqm.lqr.prevSave.InOctets);
+  log_Printf(LogLQM, "  Inbound lossage: %d packet%s, %d octet%s\n",
+	     (int)pkts, pkts == 1 ? "" : "s",
+	     (int)octets, octets == 1 ? "" : "s");
+
+  disc = newlqr->PeerInDiscards - oldlqr->PeerInDiscards;
+  err = newlqr->PeerInErrors - oldlqr->PeerInErrors;
+  if (disc && err)
+    log_Printf(LogLQM, "                   Likely due to both peer congestion"
+               " and physical errors\n");
+  else if (disc)
+    log_Printf(LogLQM, "                   Likely due to peer congestion\n");
+  else if (err)
+    log_Printf(LogLQM, "                   Likely due to physical errors\n");
+  else if (pkts)
+    log_Printf(LogLQM, "                   Likely due to transport "
+	       "congestion\n");
+}
+
+static struct mbuf *
+lqr_LayerPush(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+              int pri __unused, u_short *proto)
+{
+  struct physical *p = link2physical(l);
+  int len, layer, extra_async_bytes;
+
+  if (!p) {
+    /* Oops - can't happen :-] */
+    m_freem(bp);
+    return NULL;
+  }
+
+  bp = m_pullup(bp);
+  len = m_length(bp);
+
+  /*-
+   * From rfc1989:
+   *
+   *  All octets which are included in the FCS calculation MUST be counted,
+   *  including the packet header, the information field, and any padding.
+   *  The FCS octets MUST also be counted, and one flag octet per frame
+   *  MUST be counted.  All other octets (such as additional flag
+   *  sequences, and escape bits or octets) MUST NOT be counted.
+   *
+   * As we're stacked higher than the HDLC layer (otherwise HDLC wouldn't be
+   * able to calculate the FCS), we must not forget about these additional
+   * bytes when we're asynchronous.
+   *
+   * We're also expecting to be stacked *before* the likes of the proto and
+   * acf layers (to avoid alignment issues), so deal with this too.
+   */
+
+  extra_async_bytes = 0;
+  p->hdlc.lqm.ifOutUniPackets++;
+  p->hdlc.lqm.ifOutOctets += len + 1;		/* plus 1 flag octet! */
+  for (layer = 0; layer < l->nlayers; layer++)
+    switch (l->layer[layer]->type) {
+      case LAYER_ACF:
+        p->hdlc.lqm.ifOutOctets += acf_WrapperOctets(&l->lcp, *proto);
+        break;
+      case LAYER_ASYNC:
+        /* Not included - see rfc1989 */
+        break;
+      case LAYER_HDLC:
+        p->hdlc.lqm.ifOutOctets += hdlc_WrapperOctets();
+        break;
+      case LAYER_LQR:
+        layer = l->nlayers;
+        break;
+      case LAYER_PROTO:
+        p->hdlc.lqm.ifOutOctets += proto_WrapperOctets(&l->lcp, *proto);
+        break;
+      case LAYER_SYNC:
+        /* Nothing to add on */
+        break;
+      default:
+        log_Printf(LogWARN, "Oops, don't know how to do octets for %s layer\n",
+                   l->layer[layer]->name);
+        break;
+    }
+
+  if (*proto == PROTO_LQR) {
+    /* Overwrite the entire packet (created in SendLqrData()) */
+    struct lqrdata lqr;
+    size_t pending_pkts, pending_octets;
+
+    p->hdlc.lqm.lqr.OutLQRs++;
+
+    /*
+     * We need to compensate for the fact that we're pushing our data
+     * onto the highest priority queue by factoring out packet & octet
+     * values from other queues!
+     */
+    link_PendingLowPriorityData(l, &pending_pkts, &pending_octets);
+
+    memset(&lqr, '\0', sizeof lqr);
+    lqr.MagicNumber = p->link.lcp.want_magic;
+    lqr.LastOutLQRs = p->hdlc.lqm.lqr.peer.PeerOutLQRs;
+    lqr.LastOutPackets = p->hdlc.lqm.lqr.peer.PeerOutPackets;
+    lqr.LastOutOctets = p->hdlc.lqm.lqr.peer.PeerOutOctets;
+    lqr.PeerInLQRs = p->hdlc.lqm.lqr.Save.InLQRs;
+    lqr.PeerInPackets = p->hdlc.lqm.lqr.Save.InPackets;
+    lqr.PeerInDiscards = p->hdlc.lqm.lqr.Save.InDiscards;
+    lqr.PeerInErrors = p->hdlc.lqm.lqr.Save.InErrors;
+    lqr.PeerInOctets = p->hdlc.lqm.lqr.Save.InOctets;
+    lqr.PeerOutLQRs = p->hdlc.lqm.lqr.OutLQRs;
+    lqr.PeerOutPackets = p->hdlc.lqm.ifOutUniPackets - pending_pkts;
+    /* Don't forget our ``flag'' octets.... */
+    lqr.PeerOutOctets = p->hdlc.lqm.ifOutOctets - pending_octets - pending_pkts;
+    lqr_Dump(l->name, "Output", &lqr);
+    lqr_ChangeOrder(&lqr, (struct lqrdata *)MBUF_CTOP(bp));
+  }
+
+  return bp;
+}
+
+static struct mbuf *
+lqr_LayerPull(struct bundle *b __unused, struct link *l __unused,
+	      struct mbuf *bp, u_short *proto)
+{
+  /*
+   * This is the ``Rx'' process from rfc1989, although a part of it is
+   * actually performed by sync_LayerPull() & hdlc_LayerPull() so that
+   * our octet counts are correct.
+   */
+
+  if (*proto == PROTO_LQR)
+    m_settype(bp, MB_LQRIN);
+  return bp;
+}
+
+/*
+ * Statistics for pulled packets are recorded either in hdlc_PullPacket()
+ * or sync_PullPacket()
+ */
+
+struct layer lqrlayer = { LAYER_LQR, "lqr", lqr_LayerPush, lqr_LayerPull };
diff --git a/src/lqr.h b/src/lqr.h
new file mode 100644
index 0000000..abe2975
--- /dev/null
+++ b/src/lqr.h
@@ -0,0 +1,82 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/lqr.h,v 1.18.34.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+/*
+ *  Structure of LQR packet defined in RFC1989
+ */
+struct lqrdata {
+  u_int32_t MagicNumber;
+  u_int32_t LastOutLQRs;	/* most recently received PeerOutLQRs */
+  u_int32_t LastOutPackets;	/* most recently received PeerOutPackets */
+  u_int32_t LastOutOctets;	/* most recently received PeerOutOctets */
+  u_int32_t PeerInLQRs;		/* Peers SaveInLQRs */
+  u_int32_t PeerInPackets;	/* Peers SaveInPackets */
+  u_int32_t PeerInDiscards;	/* Peers SaveInDiscards */
+  u_int32_t PeerInErrors;	/* Peers SaveInErrors */
+  u_int32_t PeerInOctets;	/* Peers SaveInOctets */
+  u_int32_t PeerOutLQRs;	/* Peers OutLQRs (hdlc.h) */
+  u_int32_t PeerOutPackets;	/* Peers OutPackets (hdlc.h) */
+  u_int32_t PeerOutOctets;	/* Peers OutOctets (hdlc.h) */
+};
+
+struct lqrsavedata {	/* Saved on receipt of an LQR */
+  u_int32_t InLQRs;	/* From ifInLQRs */
+  u_int32_t InPackets;	/* From ifInPackets */
+  u_int32_t InDiscards;	/* From ifInDiscards */
+  u_int32_t InErrors;	/* From ifInErrors */
+  u_int32_t InOctets;	/* From InGoodOctets ! */
+};
+
+/*
+ *  We support LQR and ECHO as LQM method
+ */
+#define	LQM_LQR	  1
+#define	LQM_ECHO  2
+
+struct mbuf;
+struct physical;
+struct lcp;
+struct fsm;
+struct hdlc;
+struct link;
+struct bundle;
+
+extern void lqr_Dump(const char *, const char *, const struct lqrdata *);
+extern void lqr_Analyse(const struct hdlc *, const struct lqrdata *,
+                        const struct lqrdata *);
+extern void lqr_ChangeOrder(struct lqrdata *, struct lqrdata *);
+extern void lqr_Start(struct lcp *);
+extern void lqr_reStart(struct lcp *);
+extern void lqr_Stop(struct physical *, int);
+extern void lqr_StopTimer(struct physical *);
+extern struct mbuf *lqr_RecvEcho(struct fsm *, struct mbuf *);
+extern struct mbuf *lqr_Input(struct bundle *, struct link *, struct mbuf *);
+
+extern struct layer lqrlayer;
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..7587b32
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,678 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/main.c,v 1.193.10.1.4.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <net/route.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#ifndef NONAT
+#ifdef LOCALNAT
+#include "alias.h"
+#else
+#include <alias.h>
+#endif
+#endif
+
+#include "layer.h"
+#include "probe.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "id.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "auth.h"
+#include "systems.h"
+#include "sig.h"
+#include "main.h"
+#include "server.h"
+#include "prompt.h"
+#include "chat.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "iface.h"
+
+#ifndef O_NONBLOCK
+#ifdef O_NDELAY
+#define	O_NONBLOCK O_NDELAY
+#endif
+#endif
+
+static void DoLoop(struct bundle *);
+static void TerminalStop(int);
+
+static struct bundle *SignalBundle;
+static struct prompt *SignalPrompt;
+
+void
+Cleanup()
+{
+  SignalBundle->CleaningUp = 1;
+  bundle_Close(SignalBundle, NULL, CLOSE_STAYDOWN);
+}
+
+void
+AbortProgram(int excode)
+{
+  if (SignalBundle)
+    server_Close(SignalBundle);
+  log_Printf(LogPHASE, "PPP Terminated (%s).\n", ex_desc(excode));
+  if (SignalBundle) {
+    bundle_Close(SignalBundle, NULL, CLOSE_STAYDOWN);
+    bundle_Destroy(SignalBundle);
+  }
+  log_Close();
+  exit(excode);
+}
+
+static void
+CloseConnection(int signo)
+{
+  /* NOTE, these are manual, we've done a setsid() */
+  sig_signal(SIGINT, SIG_IGN);
+  log_Printf(LogPHASE, "Caught signal %d, abort connection(s)\n", signo);
+  bundle_Down(SignalBundle, CLOSE_STAYDOWN);
+  sig_signal(SIGINT, CloseConnection);
+}
+
+static void
+CloseSession(int signo)
+{
+  log_Printf(LogPHASE, "Signal %d, terminate.\n", signo);
+  Cleanup();
+}
+
+static pid_t BGPid = 0;
+
+static void
+KillChild(int signo)
+{
+  signal(signo, SIG_IGN);
+  log_Printf(LogPHASE, "Parent: Signal %d\n", signo);
+  kill(BGPid, SIGINT);
+}
+
+static void
+TerminalCont(int signo __unused)
+{
+  signal(SIGCONT, SIG_DFL);
+  prompt_Continue(SignalPrompt);
+}
+
+static void
+TerminalStop(int signo __unused)
+{
+  prompt_Suspend(SignalPrompt);
+  signal(SIGCONT, TerminalCont);
+  raise(SIGSTOP);
+}
+
+static void
+BringDownServer(int signo __unused)
+{
+  /* Drops all child prompts too ! */
+  if (server_Close(SignalBundle))
+    log_Printf(LogPHASE, "Closed server socket\n");
+}
+
+static void
+RestartServer(int signo __unused)
+{
+  /* Drops all child prompts and re-opens the socket */
+  server_Reopen(SignalBundle);
+}
+
+static void
+Usage(void)
+{
+  fprintf(stderr, "usage: ppp [-auto | -foreground | -background | -direct |"
+          " -dedicated | -ddial | -interactive]"
+#ifndef NONAT
+          " [-nat]"
+#endif
+          " [-quiet] [-unit N] [system ...]\n");
+  exit(EX_START);
+}
+
+struct switches {
+  unsigned nat : 1;
+  unsigned fg : 1;
+  unsigned quiet : 1;
+  int mode;
+  int unit;
+};
+
+static int
+ProcessArgs(int argc, char **argv, struct switches *sw)
+{
+  int optc, newmode, arg;
+  char *cp;
+
+  optc = 0;
+  memset(sw, '\0', sizeof *sw);
+  sw->mode = PHYS_INTERACTIVE;
+  sw->unit = -1;
+
+  for (arg = 1; arg < argc && *argv[arg] == '-'; arg++, optc++) {
+    cp = argv[arg] + 1;
+    newmode = Nam2mode(cp);
+    switch (newmode) {
+      case PHYS_NONE:
+        if (strcmp(cp, "nat") == 0) {
+#ifdef NONAT
+          log_Printf(LogWARN, "%s ignored: NAT is compiled out\n", argv[arg]);
+#else
+          sw->nat = 1;
+#endif
+          optc--;			/* this option isn't exclusive */
+        } else if (strcmp(cp, "alias") == 0) {
+#ifdef NONAT
+          log_Printf(LogWARN, "%s ignored: NAT is compiled out\n", argv[arg]);
+          fprintf(stderr, "%s ignored: NAT is compiled out\n", argv[arg]);
+#else
+          log_Printf(LogWARN, "%s is deprecated\n", argv[arg]);
+          fprintf(stderr, "%s is deprecated\n", argv[arg]);
+          sw->nat = 1;
+#endif
+          optc--;			/* this option isn't exclusive */
+        } else if (strncmp(cp, "unit", 4) == 0) {
+          optc--;			/* this option isn't exclusive */
+          if (cp[4] == '\0') {
+            optc--;			/* nor is the argument */
+            if (++arg == argc) {
+              fprintf(stderr, "-unit: Expected unit number\n");
+              Usage();
+            } else
+              sw->unit = atoi(argv[arg]);
+          } else
+            sw->unit = atoi(cp + 4);
+        } else if (strcmp(cp, "quiet") == 0) {
+          sw->quiet = 1;
+          optc--;			/* this option isn't exclusive */
+        } else
+          Usage();
+        break;
+
+      case PHYS_ALL:
+        Usage();
+        break;
+
+      default:
+        sw->mode = newmode;
+        if (newmode == PHYS_FOREGROUND)
+          sw->fg = 1;
+    }
+  }
+
+  if (optc > 1) {
+    fprintf(stderr, "You may specify only one mode.\n");
+    exit(EX_START);
+  }
+
+  if (sw->mode == PHYS_AUTO && arg == argc) {
+    fprintf(stderr, "A system must be specified in auto mode.\n");
+    exit(EX_START);
+  }
+
+  return arg;		/* Don't SetLabel yet ! */
+}
+
+static void
+CheckLabel(const char *label, struct prompt *prompt, int mode)
+{
+  const char *err;
+
+  if ((err = system_IsValid(label, prompt, mode)) != NULL) {
+    fprintf(stderr, "%s: %s\n", label, err);
+    if (mode == PHYS_DIRECT)
+      log_Printf(LogWARN, "Label %s rejected -direct connection: %s\n",
+                 label, err);
+    log_Close();
+    exit(1);
+  }
+}
+
+
+int
+main(int argc, char **argv)
+{
+  char *name;
+  const char *lastlabel;
+  int arg, holdfd[3], label;
+  unsigned f;
+  struct bundle *bundle;
+  struct prompt *prompt;
+  struct switches sw;
+
+  probe_Init();
+
+  /*
+   * We open 3 descriptors to ensure that STDIN_FILENO, STDOUT_FILENO and
+   * STDERR_FILENO are always open.  These are closed before DoLoop(),
+   * but *after* we've avoided the possibility of erroneously closing
+   * an important descriptor with close(STD{IN,OUT,ERR}_FILENO).
+   */
+  if ((holdfd[0] = open(_PATH_DEVNULL, O_RDWR)) == -1) {
+    fprintf(stderr, "Cannot open %s !\n", _PATH_DEVNULL);
+    return 2;
+  }
+  for (f = 1; f < sizeof holdfd / sizeof *holdfd; f++)
+    holdfd[f] = dup(holdfd[0]);
+
+  name = strrchr(argv[0], '/');
+  log_Open(name ? name + 1 : argv[0]);
+
+#ifndef NONAT
+  PacketAliasInit();
+#endif
+  label = ProcessArgs(argc, argv, &sw);
+
+  /*
+   * A FreeBSD & OpenBSD hack to dodge a bug in the tty driver that drops
+   * output occasionally.... I must find the real reason some time.  To
+   * display the dodgy behaviour, comment out this bit, make yourself a large
+   * routing table and then run ppp in interactive mode.  The `show route'
+   * command will drop chunks of data !!!
+   */
+  if (sw.mode == PHYS_INTERACTIVE) {
+    close(STDIN_FILENO);
+    if (open(_PATH_TTY, O_RDONLY) != STDIN_FILENO) {
+      fprintf(stderr, "Cannot open %s for input !\n", _PATH_TTY);
+      return 2;
+    }
+  }
+
+  /* Allow output for the moment (except in direct mode) */
+  if (sw.mode == PHYS_DIRECT)
+    prompt = NULL;
+  else
+    SignalPrompt = prompt = prompt_Create(NULL, NULL, PROMPT_STD);
+
+  ID0init();
+  if (ID0realuid() != 0) {
+    char conf[200], *ptr;
+
+    snprintf(conf, sizeof conf, "%s/%s", PPP_CONFDIR, CONFFILE);
+    do {
+      struct stat sb;
+
+      if (stat(conf, &sb) == 0 && sb.st_mode & S_IWOTH) {
+        log_Printf(LogALERT, "ppp: Access violation: Please protect %s\n",
+                   conf);
+        return -1;
+      }
+      ptr = conf + strlen(conf)-2;
+      while (ptr > conf && *ptr != '/')
+        *ptr-- = '\0';
+    } while (ptr >= conf);
+  }
+
+  if (label < argc)
+    for (arg = label; arg < argc; arg++)
+      CheckLabel(argv[arg], prompt, sw.mode);
+  else
+    CheckLabel("default", prompt, sw.mode);
+
+  if (!sw.quiet)
+    prompt_Printf(prompt, "Working in %s mode\n", mode2Nam(sw.mode));
+
+  if ((bundle = bundle_Create(TUN_PREFIX, sw.mode, sw.unit)) == NULL)
+    return EX_START;
+
+  /* NOTE:  We may now have changed argv[1] via a ``set proctitle'' */
+
+  if (prompt) {
+    prompt->bundle = bundle;	/* couldn't do it earlier */
+    if (!sw.quiet)
+      prompt_Printf(prompt, "Using interface: %s\n", bundle->iface->name);
+  }
+  SignalBundle = bundle;
+  bundle->NatEnabled = sw.nat;
+  if (sw.nat)
+    opt_enable(bundle, OPT_IFACEALIAS);
+
+  if (system_Select(bundle, "default", CONFFILE, prompt, NULL) < 0)
+    prompt_Printf(prompt, "Warning: No default entry found in config file.\n");
+
+  sig_signal(SIGHUP, CloseSession);
+  sig_signal(SIGTERM, CloseSession);
+  sig_signal(SIGINT, CloseConnection);
+  sig_signal(SIGQUIT, CloseSession);
+  sig_signal(SIGALRM, SIG_IGN);
+  signal(SIGPIPE, SIG_IGN);
+
+  if (sw.mode == PHYS_INTERACTIVE)
+    sig_signal(SIGTSTP, TerminalStop);
+
+  sig_signal(SIGUSR1, RestartServer);
+  sig_signal(SIGUSR2, BringDownServer);
+
+  lastlabel = argv[argc - 1];
+  for (arg = label; arg < argc; arg++) {
+    /* In case we use LABEL or ``set enddisc label'' */
+    bundle_SetLabel(bundle, lastlabel);
+    system_Select(bundle, argv[arg], CONFFILE, prompt, NULL);
+  }
+
+  if (label < argc)
+    /* In case the last label did a ``load'' */
+    bundle_SetLabel(bundle, lastlabel);
+
+  if (sw.mode == PHYS_AUTO &&
+      ncprange_family(&bundle->ncp.ipcp.cfg.peer_range) == AF_UNSPEC) {
+    prompt_Printf(prompt, "You must ``set ifaddr'' with a peer address "
+                  "in auto mode.\n");
+    AbortProgram(EX_START);
+  }
+
+  if (sw.mode != PHYS_INTERACTIVE) {
+    if (sw.mode != PHYS_DIRECT) {
+      if (!sw.fg) {
+        int bgpipe[2];
+        pid_t bgpid;
+
+        if (sw.mode == PHYS_BACKGROUND && pipe(bgpipe)) {
+          log_Printf(LogERROR, "pipe: %s\n", strerror(errno));
+	  AbortProgram(EX_SOCK);
+        }
+
+        bgpid = fork();
+        if (bgpid == -1) {
+	  log_Printf(LogERROR, "fork: %s\n", strerror(errno));
+	  AbortProgram(EX_SOCK);
+        }
+
+        if (bgpid) {
+	  char c = EX_NORMAL;
+          int ret;
+
+	  if (sw.mode == PHYS_BACKGROUND) {
+	    close(bgpipe[1]);
+	    BGPid = bgpid;
+            /* If we get a signal, kill the child */
+            signal(SIGHUP, KillChild);
+            signal(SIGTERM, KillChild);
+            signal(SIGINT, KillChild);
+            signal(SIGQUIT, KillChild);
+
+	    /* Wait for our child to close its pipe before we exit */
+            while ((ret = read(bgpipe[0], &c, 1)) == 1) {
+              switch (c) {
+                case EX_NORMAL:
+                  if (!sw.quiet) {
+	            prompt_Printf(prompt, "PPP enabled\n");
+	            log_Printf(LogPHASE, "Parent: PPP enabled\n");
+                  }
+	          break;
+                case EX_REDIAL:
+                  if (!sw.quiet)
+	            prompt_Printf(prompt, "Attempting redial\n");
+                  continue;
+                case EX_RECONNECT:
+                  if (!sw.quiet)
+	            prompt_Printf(prompt, "Attempting reconnect\n");
+                  continue;
+	        default:
+	          prompt_Printf(prompt, "Child failed (%s)\n",
+                                ex_desc((int)c));
+	          log_Printf(LogPHASE, "Parent: Child failed (%s)\n",
+		             ex_desc((int) c));
+	      }
+	      break;
+            }
+            if (ret != 1) {
+	      prompt_Printf(prompt, "Child exit, no status.\n");
+	      log_Printf(LogPHASE, "Parent: Child exit, no status.\n");
+	    }
+	    close(bgpipe[0]);
+	  }
+	  return c;
+        } else if (sw.mode == PHYS_BACKGROUND) {
+	  close(bgpipe[0]);
+          bundle->notify.fd = bgpipe[1];
+        }
+
+        bundle_ChangedPID(bundle);
+        bundle_LockTun(bundle);	/* we have a new pid */
+      }
+
+      /* -auto, -dedicated, -ddial, -foreground & -background */
+      prompt_Destroy(prompt, 0);
+      close(STDOUT_FILENO);
+      close(STDERR_FILENO);
+      close(STDIN_FILENO);
+      if (!sw.fg)
+        setsid();
+    } else {
+      /*
+       * -direct - STDIN_FILENO gets used by physical_Open.  STDOUT_FILENO
+       * *may* get used in exec/pipe mode.
+       */
+      prompt_TtyInit(NULL);
+      close(STDERR_FILENO);
+    }
+  } else {
+    /* -interactive */
+    close(STDERR_FILENO);
+    prompt_TtyInit(prompt);
+    prompt_TtyCommandMode(prompt);
+    prompt_Required(prompt);
+  }
+
+  /* We can get rid of these now */
+  for (f = 0; f < sizeof holdfd / sizeof *holdfd; f++)
+    close(holdfd[f]);
+
+  log_Printf(LogPHASE, "PPP Started (%s mode).\n", mode2Nam(sw.mode));
+  DoLoop(bundle);
+  AbortProgram(EX_NORMAL);
+
+  return EX_NORMAL;
+}
+
+static void
+DoLoop(struct bundle *bundle)
+{
+  fd_set *rfds, *wfds, *efds;
+  int i, nfds, nothing_done;
+
+  if ((rfds = mkfdset()) == NULL) {
+    log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n");
+    return;
+  }
+
+  if ((wfds = mkfdset()) == NULL) {
+    log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n");
+    free(rfds);
+    return;
+  }
+
+  if ((efds = mkfdset()) == NULL) {
+    log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n");
+    free(rfds);
+    free(wfds);
+    return;
+  }
+
+  for (; !bundle_IsDead(bundle); bundle_CleanDatalinks(bundle)) {
+    nfds = 0;
+    zerofdset(rfds);
+    zerofdset(wfds);
+    zerofdset(efds);
+
+    /* All our datalinks, the tun device and the MP socket */
+    descriptor_UpdateSet(&bundle->desc, rfds, wfds, efds, &nfds);
+
+    /* All our prompts and the diagnostic socket */
+    descriptor_UpdateSet(&server.desc, rfds, NULL, NULL, &nfds);
+
+    bundle_CleanDatalinks(bundle);
+    if (bundle_IsDead(bundle))
+      /* Don't select - we'll be here forever */
+      break;
+
+    /*
+     * It's possible that we've had a signal since we last checked.  If
+     * we don't check again before calling select(), we may end up stuck
+     * after having missed the event.... sig_Handle() tries to be as
+     * quick as possible if nothing is likely to have happened.
+     * This is only really likely if we block in open(... O_NONBLOCK)
+     * which will happen with a misconfigured device.
+     */
+    if (sig_Handle())
+      continue;
+
+    i = select(nfds, rfds, wfds, efds, NULL);
+
+    if (i < 0 && errno != EINTR) {
+      log_Printf(LogERROR, "DoLoop: select(): %s\n", strerror(errno));
+      if (log_IsKept(LogTIMER)) {
+        struct timeval t;
+
+        for (i = 0; i <= nfds; i++) {
+          if (FD_ISSET(i, rfds)) {
+            log_Printf(LogTIMER, "Read set contains %d\n", i);
+            FD_CLR(i, rfds);
+            t.tv_sec = t.tv_usec = 0;
+            if (select(nfds, rfds, wfds, efds, &t) != -1) {
+              log_Printf(LogTIMER, "The culprit !\n");
+              break;
+            }
+          }
+          if (FD_ISSET(i, wfds)) {
+            log_Printf(LogTIMER, "Write set contains %d\n", i);
+            FD_CLR(i, wfds);
+            t.tv_sec = t.tv_usec = 0;
+            if (select(nfds, rfds, wfds, efds, &t) != -1) {
+              log_Printf(LogTIMER, "The culprit !\n");
+              break;
+            }
+          }
+          if (FD_ISSET(i, efds)) {
+            log_Printf(LogTIMER, "Error set contains %d\n", i);
+            FD_CLR(i, efds);
+            t.tv_sec = t.tv_usec = 0;
+            if (select(nfds, rfds, wfds, efds, &t) != -1) {
+              log_Printf(LogTIMER, "The culprit !\n");
+              break;
+            }
+          }
+        }
+      }
+      break;
+    }
+
+    log_Printf(LogTIMER, "Select returns %d\n", i);
+
+    sig_Handle();
+
+    if (i <= 0)
+      continue;
+
+    for (i = 0; i <= nfds; i++)
+      if (FD_ISSET(i, efds)) {
+        log_Printf(LogPHASE, "Exception detected on descriptor %d\n", i);
+        /* We deal gracefully with link descriptor exceptions */
+        if (!bundle_Exception(bundle, i)) {
+          log_Printf(LogERROR, "Exception cannot be handled !\n");
+          break;
+        }
+      }
+
+    if (i <= nfds)
+      break;
+
+    nothing_done = 1;
+
+    if (descriptor_IsSet(&server.desc, rfds)) {
+      descriptor_Read(&server.desc, bundle, rfds);
+      nothing_done = 0;
+    }
+
+    if (descriptor_IsSet(&bundle->desc, rfds)) {
+      descriptor_Read(&bundle->desc, bundle, rfds);
+      nothing_done = 0;
+    }
+
+    if (descriptor_IsSet(&bundle->desc, wfds))
+      if (descriptor_Write(&bundle->desc, bundle, wfds) <= 0 && nothing_done) {
+        /*
+         * This is disastrous.  The OS has told us that something is
+         * writable, and all our write()s have failed.  Rather than
+         * going back immediately to do our UpdateSet()s and select(),
+         * we sleep for a bit to avoid gobbling up all cpu time.
+         */
+        struct timeval t;
+
+        t.tv_sec = 0;
+        t.tv_usec = 100000;
+        select(0, NULL, NULL, NULL, &t);
+      }
+  }
+
+  log_Printf(LogDEBUG, "DoLoop done.\n");
+}
diff --git a/src/main.h b/src/main.h
new file mode 100644
index 0000000..36bf80f
--- /dev/null
+++ b/src/main.h
@@ -0,0 +1,32 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/main.h,v 1.13.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+extern void Cleanup(void);
+extern void AbortProgram(int);
diff --git a/src/mbuf.c b/src/mbuf.c
new file mode 100644
index 0000000..b9346ec
--- /dev/null
+++ b/src/mbuf.c
@@ -0,0 +1,440 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/mbuf.c,v 1.46.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "main.h"
+
+#define BUCKET_CHUNK	20
+#define BUCKET_HASH	256
+
+struct mbucket;
+
+struct mfree {
+  struct mbucket *next;
+  size_t count;
+};
+
+static struct mbucket {
+  union {
+    struct mbuf m;
+    struct mfree f;
+  } u;
+} *bucket[(M_MAXLEN + sizeof(struct mbuf)) / BUCKET_HASH];
+
+#define M_BINDEX(sz)	(((sz) + sizeof(struct mbuf) - 1) / BUCKET_HASH)
+#define M_BUCKET(sz)	(bucket + M_BINDEX(sz))
+#define M_ROUNDUP(sz)	((M_BINDEX(sz) + 1) * BUCKET_HASH)
+
+static struct memmap {
+  struct mbuf *queue;
+  size_t fragments;
+  size_t octets;
+} MemMap[MB_MAX + 1];
+
+static unsigned long long mbuf_Mallocs, mbuf_Frees;
+
+size_t
+m_length(struct mbuf *bp)
+{
+  size_t len;
+
+  for (len = 0; bp; bp = bp->m_next)
+    len += bp->m_len;
+  return len;
+}
+
+static const char *
+mbuftype(int type)
+{
+  static const char * const mbufdesc[MB_MAX] = {
+    "ip in", "ip out", "ipv6 in", "ipv6 out", "nat in", "nat out",
+    "mp in", "mp out", "vj in", "vj out", "icompd in", "icompd out",
+    "compd in", "compd out", "lqr in", "lqr out", "echo in", "echo out",
+    "proto in", "proto out", "acf in", "acf out", "sync in", "sync out",
+    "hdlc in", "hdlc out", "async in", "async out", "cbcp in", "cbcp out",
+    "chap in", "chap out", "pap in", "pap out", "ccp in", "ccp out",
+    "ipcp in", "ipcp out", "ipv6cp in", "ipv6cp out", "lcp in", "lcp out"
+  };
+
+  return type < 0 || type >= MB_MAX ? "unknown" : mbufdesc[type];
+}
+
+struct mbuf *
+m_get(size_t m_len, int type)
+{
+  struct mbucket **mb;
+  struct mbuf *bp;
+  size_t size;
+
+  if (type > MB_MAX) {
+    log_Printf(LogERROR, "Bad mbuf type %d\n", type);
+    type = MB_UNKNOWN;
+  }
+
+  if (m_len > M_MAXLEN || m_len == 0) {
+    log_Printf(LogERROR, "Request for mbuf size %lu (\"%s\") denied !\n",
+               (u_long)m_len, mbuftype(type));
+    AbortProgram(EX_OSERR);
+  }
+
+  mb = M_BUCKET(m_len);
+  size = M_ROUNDUP(m_len);
+
+  if (*mb) {
+    /* We've got some free blocks of the right size */
+    bp = &(*mb)->u.m;
+    if (--(*mb)->u.f.count == 0)
+      *mb = (*mb)->u.f.next;
+    else {
+      ((struct mbucket *)((char *)*mb + size))->u.f.count = (*mb)->u.f.count;
+      *mb = (struct mbucket *)((char *)*mb + size);
+      (*mb)->u.f.next = NULL;
+    }
+  } else {
+    /*
+     * Allocate another chunk of mbufs, use the first and put the rest on
+     * the free list
+     */
+    *mb = (struct mbucket *)malloc(BUCKET_CHUNK * size);
+    if (*mb == NULL) {
+      log_Printf(LogALERT, "Failed to allocate memory (%lu)\n",
+                 (unsigned long)BUCKET_CHUNK * size);
+      AbortProgram(EX_OSERR);
+    }
+    bp = &(*mb)->u.m;
+    *mb = (struct mbucket *)((char *)*mb + size);
+    (*mb)->u.f.count = BUCKET_CHUNK - 1;
+    (*mb)->u.f.next = NULL;
+  }
+
+  mbuf_Mallocs++;
+
+  memset(bp, '\0', sizeof(struct mbuf));
+  bp->m_size = size - sizeof *bp;
+  bp->m_len = m_len;
+  bp->m_type = type;
+
+  MemMap[type].fragments++;
+  MemMap[type].octets += bp->m_size;
+
+  return bp;
+}
+
+struct mbuf *
+m_free(struct mbuf *bp)
+{
+  struct mbucket **mb, *f;
+  struct mbuf *nbp;
+
+  if ((f = (struct mbucket *)bp) != NULL) {
+    MemMap[bp->m_type].fragments--;
+    MemMap[bp->m_type].octets -= bp->m_size;
+
+    nbp = bp->m_next;
+    mb = M_BUCKET(bp->m_size);
+    f->u.f.next = *mb;
+    f->u.f.count = 1;
+    *mb = f;
+
+    mbuf_Frees++;
+    bp = nbp;
+  }
+
+  return bp;
+}
+
+void
+m_freem(struct mbuf *bp)
+{
+  while (bp)
+    bp = m_free(bp);
+}
+
+struct mbuf *
+mbuf_Read(struct mbuf *bp, void *v, size_t len)
+{
+  int nb;
+  u_char *ptr = v;
+
+  while (bp && len > 0) {
+    if (len > bp->m_len)
+      nb = bp->m_len;
+    else
+      nb = len;
+    if (nb) {
+      memcpy(ptr, MBUF_CTOP(bp), nb);
+      ptr += nb;
+      bp->m_len -= nb;
+      len -= nb;
+      bp->m_offset += nb;
+    }
+    if (bp->m_len == 0)
+      bp = m_free(bp);
+  }
+
+  while (bp && bp->m_len == 0)
+    bp = m_free(bp);
+
+  return bp;
+}
+
+size_t
+mbuf_View(struct mbuf *bp, void *v, size_t len)
+{
+  size_t nb, l = len;
+  u_char *ptr = v;
+
+  while (bp && l > 0) {
+    if (l > bp->m_len)
+      nb = bp->m_len;
+    else
+      nb = l;
+    memcpy(ptr, MBUF_CTOP(bp), nb);
+    ptr += nb;
+    l -= nb;
+    bp = bp->m_next;
+  }
+
+  return len - l;
+}
+
+struct mbuf *
+m_prepend(struct mbuf *bp, const void *ptr, size_t len, u_short extra)
+{
+  struct mbuf *head;
+
+  if (bp && bp->m_offset) {
+    if (bp->m_offset >= len) {
+      bp->m_offset -= len;
+      bp->m_len += len;
+      if (ptr)
+        memcpy(MBUF_CTOP(bp), ptr, len);
+      return bp;
+    }
+    len -= bp->m_offset;
+    if (ptr)
+      memcpy(bp + 1, (const char *)ptr + len, bp->m_offset);
+    bp->m_len += bp->m_offset;
+    bp->m_offset = 0;
+  }
+
+  head = m_get(len + extra, bp ? bp->m_type : MB_UNKNOWN);
+  head->m_offset = extra;
+  head->m_len -= extra;
+  if (ptr)
+    memcpy(MBUF_CTOP(head), ptr, len);
+  head->m_next = bp;
+
+  return head;
+}
+
+struct mbuf *
+m_adj(struct mbuf *bp, ssize_t n)
+{
+  if (n > 0) {
+    while (bp) {
+      if ((size_t)n < bp->m_len) {
+        bp->m_len = n;
+        bp->m_offset += n;
+        return bp;
+      }
+      n -= bp->m_len;
+      bp = m_free(bp);
+    }
+  } else {
+    if ((n = m_length(bp) + n) <= 0) {
+      m_freem(bp);
+      return NULL;
+    }
+    for (; bp; bp = bp->m_next, n -= bp->m_len)
+      if ((size_t)n < bp->m_len) {
+        bp->m_len = n;
+        m_freem(bp->m_next);
+        bp->m_next = NULL;
+        break;
+      }
+  }
+
+  return bp;
+}
+
+void
+mbuf_Write(struct mbuf *bp, const void *ptr, size_t m_len)
+{
+  size_t plen;
+  int nb;
+
+  plen = m_length(bp);
+  if (plen < m_len)
+    m_len = plen;
+
+  while (m_len > 0) {
+    nb = (m_len < bp->m_len) ? m_len : bp->m_len;
+    memcpy(MBUF_CTOP(bp), ptr, nb);
+    m_len -= bp->m_len;
+    bp = bp->m_next;
+  }
+}
+
+int
+mbuf_Show(struct cmdargs const *arg)
+{
+  int i;
+
+  prompt_Printf(arg->prompt, "Fragments (octets) in use:\n");
+  for (i = 0; i < MB_MAX; i += 2)
+    prompt_Printf(arg->prompt, "%10.10s: %04lu (%06lu)\t"
+                  "%10.10s: %04lu (%06lu)\n",
+	          mbuftype(i), (u_long)MemMap[i].fragments,
+                  (u_long)MemMap[i].octets, mbuftype(i+1),
+                  (u_long)MemMap[i+1].fragments, (u_long)MemMap[i+1].octets);
+
+  if (i == MB_MAX)
+    prompt_Printf(arg->prompt, "%10.10s: %04lu (%06lu)\n",
+                  mbuftype(i), (u_long)MemMap[i].fragments,
+                  (u_long)MemMap[i].octets);
+
+  prompt_Printf(arg->prompt, "Mallocs: %llu,   Frees: %llu\n",
+                mbuf_Mallocs, mbuf_Frees);
+
+  return 0;
+}
+
+struct mbuf *
+m_dequeue(struct mqueue *q)
+{
+  struct mbuf *bp;
+
+  log_Printf(LogDEBUG, "m_dequeue: queue len = %lu\n", (u_long)q->len);
+  bp = q->top;
+  if (bp) {
+    q->top = q->top->m_nextpkt;
+    q->len--;
+    if (q->top == NULL) {
+      q->last = q->top;
+      if (q->len)
+	log_Printf(LogERROR, "m_dequeue: Not zero (%lu)!!!\n",
+                   (u_long)q->len);
+    }
+    bp->m_nextpkt = NULL;
+  }
+
+  return bp;
+}
+
+void
+m_enqueue(struct mqueue *queue, struct mbuf *bp)
+{
+  if (bp != NULL) {
+    if (queue->last) {
+      queue->last->m_nextpkt = bp;
+      queue->last = bp;
+    } else
+      queue->last = queue->top = bp;
+    queue->len++;
+    log_Printf(LogDEBUG, "m_enqueue: len = %lu\n", (unsigned long)queue->len);
+  }
+}
+
+struct mbuf *
+m_pullup(struct mbuf *bp)
+{
+  /* Put it all in one contigous (aligned) mbuf */
+
+  if (bp != NULL) {
+    if (bp->m_next != NULL) {
+      struct mbuf *nbp;
+      u_char *cp;
+
+      nbp = m_get(m_length(bp), bp->m_type);
+
+      for (cp = MBUF_CTOP(nbp); bp; bp = m_free(bp)) {
+        memcpy(cp, MBUF_CTOP(bp), bp->m_len);
+        cp += bp->m_len;
+      }
+      bp = nbp;
+    }
+#ifndef __i386__	/* Do any other archs not care about alignment ? */
+    else if ((bp->m_offset & (sizeof(long) - 1)) != 0) {
+      bcopy(MBUF_CTOP(bp), bp + 1, bp->m_len);
+      bp->m_offset = 0;
+    }
+#endif
+  }
+
+  return bp;
+}
+
+void
+m_settype(struct mbuf *bp, int type)
+{
+  for (; bp; bp = bp->m_next)
+    if (type != bp->m_type) {
+      MemMap[bp->m_type].fragments--;
+      MemMap[bp->m_type].octets -= bp->m_size;
+      bp->m_type = type;
+      MemMap[type].fragments++;
+      MemMap[type].octets += bp->m_size;
+    }
+}
+
+struct mbuf *
+m_append(struct mbuf *bp, const void *v, size_t sz)
+{
+  struct mbuf *m = bp;
+
+  if (m) {
+    while (m->m_next)
+      m = m->m_next;
+    if (m->m_size - m->m_len >= sz) {
+      if (v)
+        memcpy((char *)(m + 1) + m->m_len, v, sz);
+      m->m_len += sz;
+    } else
+      m->m_next = m_prepend(NULL, v, sz, 0);
+  } else
+    bp = m_prepend(NULL, v, sz, 0);
+
+  return bp;
+}
diff --git a/src/mbuf.h b/src/mbuf.h
new file mode 100644
index 0000000..abc7c06
--- /dev/null
+++ b/src/mbuf.h
@@ -0,0 +1,119 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/mbuf.h,v 1.29.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct mbuf {
+  size_t m_size;		/* size allocated (excluding header) */
+  u_short m_offset;		/* offset from header end to start position */
+  size_t m_len;			/* available byte count in buffer */
+  short m_type;			/* MB_* below */
+  struct mbuf *m_next;		/* link to next mbuf */
+  struct mbuf *m_nextpkt;	/* link to next packet */
+  unsigned long priv;		/* private data - holds HDLC escape count */
+  /* buffer space is malloc()d directly after the header */
+};
+
+struct mqueue {
+  struct mbuf *top;
+  struct mbuf *last;
+  size_t len;
+};
+
+#define MBUF_CTOP(bp) \
+	((bp) ? (u_char *)((bp)+1) + (bp)->m_offset : (u_char *)bp)
+
+#define CONST_MBUF_CTOP(bp) \
+	((bp) ? (const u_char *)((bp)+1) + (bp)->m_offset : (const u_char *)bp)
+
+#define MB_IPIN		0
+#define MB_IPOUT	1
+#define MB_IPV6IN	2
+#define MB_IPV6OUT	3
+#define MB_NATIN	4
+#define MB_NATOUT	5
+#define MB_MPIN		6
+#define MB_MPOUT	7
+#define MB_VJIN		8
+#define MB_VJOUT	9
+#define MB_ICOMPDIN	10
+#define MB_ICOMPDOUT	11
+#define MB_COMPDIN	12
+#define MB_COMPDOUT	13
+#define MB_LQRIN	14
+#define MB_LQROUT	15
+#define MB_ECHOIN	16
+#define MB_ECHOOUT	17
+#define MB_PROTOIN	18
+#define MB_PROTOOUT	19
+#define MB_ACFIN	20
+#define MB_ACFOUT	21
+#define MB_SYNCIN	22
+#define MB_SYNCOUT	23
+#define MB_HDLCIN	24
+#define MB_HDLCOUT	25
+#define MB_ASYNCIN	26
+#define MB_ASYNCOUT	27
+#define MB_CBCPIN	28
+#define MB_CBCPOUT	29
+#define MB_CHAPIN	30
+#define MB_CHAPOUT	31
+#define MB_PAPIN	32
+#define MB_PAPOUT	33
+#define MB_CCPIN	34
+#define MB_CCPOUT	35
+#define MB_IPCPIN	36
+#define MB_IPCPOUT	37
+#define MB_IPV6CPIN	38
+#define MB_IPV6CPOUT	39
+#define MB_LCPIN	40
+#define MB_LCPOUT	41
+#define MB_UNKNOWN	42
+#define MB_MAX		MB_UNKNOWN
+
+#define M_MAXLEN	(4352 - sizeof(struct mbuf))	/* > HDLCSIZE */
+
+struct cmdargs;
+
+extern size_t m_length(struct mbuf *);
+extern struct mbuf *m_get(size_t, int);
+extern struct mbuf *m_free(struct mbuf *);
+extern void m_freem(struct mbuf *);
+extern void mbuf_Write(struct mbuf *, const void *, size_t);
+extern struct mbuf *mbuf_Read(struct mbuf *, void *, size_t);
+extern size_t mbuf_View(struct mbuf *, void *, size_t);
+extern struct mbuf *m_prepend(struct mbuf *, const void *, size_t, u_short);
+extern struct mbuf *m_adj(struct mbuf *, ssize_t);
+extern struct mbuf *m_pullup(struct mbuf *);
+extern void m_settype(struct mbuf *, int);
+extern struct mbuf *m_append(struct mbuf *, const void *, size_t);
+
+extern int mbuf_Show(struct cmdargs const *);
+
+extern void m_enqueue(struct mqueue *, struct mbuf *);
+extern struct mbuf *m_dequeue(struct mqueue *);
diff --git a/src/mp.c b/src/mp.c
new file mode 100644
index 0000000..6ddd569
--- /dev/null
+++ b/src/mp.c
@@ -0,0 +1,1209 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/mp.c,v 1.55.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <net/if_dl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#ifndef NONAT
+#include "nat_cmd.h"
+#endif
+#include "vjcomp.h"
+#include "ua.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "auth.h"
+#include "lcp.h"
+#include "async.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "chat.h"
+#include "proto.h"
+#include "filter.h"
+#include "mp.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "prompt.h"
+#include "id.h"
+#include "arp.h"
+
+void
+peerid_Init(struct peerid *peer)
+{
+  peer->enddisc.class = 0;
+  *peer->enddisc.address = '\0';
+  peer->enddisc.len = 0;
+  *peer->authname = '\0';
+}
+
+int
+peerid_Equal(const struct peerid *p1, const struct peerid *p2)
+{
+  return !strcmp(p1->authname, p2->authname) &&
+         p1->enddisc.class == p2->enddisc.class &&
+         p1->enddisc.len == p2->enddisc.len &&
+         !memcmp(p1->enddisc.address, p2->enddisc.address, p1->enddisc.len);
+}
+
+static u_int32_t
+inc_seq(unsigned is12bit, u_int32_t seq)
+{
+  seq++;
+  if (is12bit) {
+    if (seq & 0xfffff000)
+      seq = 0;
+  } else if (seq & 0xff000000)
+    seq = 0;
+  return seq;
+}
+
+static int
+isbefore(unsigned is12bit, u_int32_t seq1, u_int32_t seq2)
+{
+  u_int32_t max = (is12bit ? 0xfff : 0xffffff) - 0x200;
+
+  if (seq1 > max) {
+    if (seq2 < 0x200 || seq2 > seq1)
+      return 1;
+  } else if ((seq1 > 0x200 || seq2 <= max) && seq1 < seq2)
+    return 1;
+
+  return 0;
+}
+
+static int
+mp_ReadHeader(struct mp *mp, struct mbuf *m, struct mp_header *header)
+{
+  if (mp->local_is12bit) {
+    u_int16_t val;
+
+    ua_ntohs(MBUF_CTOP(m), &val);
+    if (val & 0x3000) {
+      log_Printf(LogWARN, "Oops - MP header without required zero bits\n");
+      return 0;
+    }
+    header->begin = val & 0x8000 ? 1 : 0;
+    header->end = val & 0x4000 ? 1 : 0;
+    header->seq = val & 0x0fff;
+    return 2;
+  } else {
+    ua_ntohl(MBUF_CTOP(m), &header->seq);
+    if (header->seq & 0x3f000000) {
+      log_Printf(LogWARN, "Oops - MP header without required zero bits\n");
+      return 0;
+    }
+    header->begin = header->seq & 0x80000000 ? 1 : 0;
+    header->end = header->seq & 0x40000000 ? 1 : 0;
+    header->seq &= 0x00ffffff;
+    return 4;
+  }
+}
+
+static void
+mp_LayerStart(void *v __unused, struct fsm *fp __unused)
+{
+  /* The given FSM (ccp) is about to start up ! */
+}
+
+static void
+mp_LayerUp(void *v __unused, struct fsm *fp)
+{
+  /* The given fsm (ccp) is now up */
+
+  bundle_CalculateBandwidth(fp->bundle);	/* Against ccp_MTUOverhead */
+}
+
+static void
+mp_LayerDown(void *v __unused, struct fsm *fp __unused)
+{
+  /* The given FSM (ccp) has been told to come down */
+}
+
+static void
+mp_LayerFinish(void *v __unused, struct fsm *fp)
+{
+  /* The given fsm (ccp) is now down */
+  if (fp->state == ST_CLOSED && fp->open_mode == OPEN_PASSIVE)
+    fsm_Open(fp);		/* CCP goes to ST_STOPPED */
+}
+
+static void
+mp_UpDown(void *v)
+{
+  struct mp *mp = (struct mp *)v;
+  int percent;
+
+  percent = MAX(mp->link.stats.total.in.OctetsPerSecond,
+                mp->link.stats.total.out.OctetsPerSecond) * 800 /
+            mp->bundle->bandwidth;
+  if (percent >= mp->cfg.autoload.max) {
+    log_Printf(LogDEBUG, "%d%% saturation - bring a link up ?\n", percent);
+    bundle_AutoAdjust(mp->bundle, percent, AUTO_UP);
+  } else if (percent <= mp->cfg.autoload.min) {
+    log_Printf(LogDEBUG, "%d%% saturation - bring a link down ?\n", percent);
+    bundle_AutoAdjust(mp->bundle, percent, AUTO_DOWN);
+  }
+}
+
+void
+mp_StopAutoloadTimer(struct mp *mp)
+{
+  throughput_stop(&mp->link.stats.total);
+}
+
+void
+mp_CheckAutoloadTimer(struct mp *mp)
+{
+  if (mp->link.stats.total.SamplePeriod != mp->cfg.autoload.period) {
+    throughput_destroy(&mp->link.stats.total);
+    throughput_init(&mp->link.stats.total, mp->cfg.autoload.period);
+    throughput_callback(&mp->link.stats.total, mp_UpDown, mp);
+  }
+
+  if (bundle_WantAutoloadTimer(mp->bundle))
+    throughput_start(&mp->link.stats.total, "MP throughput", 1);
+  else
+    mp_StopAutoloadTimer(mp);
+}
+
+void
+mp_RestartAutoloadTimer(struct mp *mp)
+{
+  if (mp->link.stats.total.SamplePeriod != mp->cfg.autoload.period)
+    mp_CheckAutoloadTimer(mp);
+  else
+    throughput_clear(&mp->link.stats.total, THROUGHPUT_OVERALL, NULL);
+}
+
+void
+mp_Init(struct mp *mp, struct bundle *bundle)
+{
+  mp->peer_is12bit = mp->local_is12bit = 0;
+  mp->peer_mrru = mp->local_mrru = 0;
+
+  peerid_Init(&mp->peer);
+
+  mp->out.seq = 0;
+  mp->out.link = 0;
+  mp->out.af = AF_INET;
+  mp->seq.min_in = 0;
+  mp->seq.next_in = 0;
+  mp->inbufs = NULL;
+  mp->bundle = bundle;
+
+  mp->link.type = LOGICAL_LINK;
+  mp->link.name = "mp";
+  mp->link.len = sizeof *mp;
+
+  mp->cfg.autoload.period = SAMPLE_PERIOD;
+  mp->cfg.autoload.min = mp->cfg.autoload.max = 0;
+  throughput_init(&mp->link.stats.total, mp->cfg.autoload.period);
+  throughput_callback(&mp->link.stats.total, mp_UpDown, mp);
+  mp->link.stats.parent = NULL;
+  mp->link.stats.gather = 0;	/* Let the physical links gather stats */
+  memset(mp->link.Queue, '\0', sizeof mp->link.Queue);
+  memset(mp->link.proto_in, '\0', sizeof mp->link.proto_in);
+  memset(mp->link.proto_out, '\0', sizeof mp->link.proto_out);
+
+  mp->fsmp.LayerStart = mp_LayerStart;
+  mp->fsmp.LayerUp = mp_LayerUp;
+  mp->fsmp.LayerDown = mp_LayerDown;
+  mp->fsmp.LayerFinish = mp_LayerFinish;
+  mp->fsmp.object = mp;
+
+  mpserver_Init(&mp->server);
+
+  mp->cfg.mrru = 0;
+  mp->cfg.shortseq = NEG_ENABLED|NEG_ACCEPTED;
+  mp->cfg.negenddisc = NEG_ENABLED|NEG_ACCEPTED;
+  mp->cfg.enddisc.class = 0;
+  *mp->cfg.enddisc.address = '\0';
+  mp->cfg.enddisc.len = 0;
+
+  lcp_Init(&mp->link.lcp, mp->bundle, &mp->link, NULL);
+  ccp_Init(&mp->link.ccp, mp->bundle, &mp->link, &mp->fsmp);
+
+  link_EmptyStack(&mp->link);
+  link_Stack(&mp->link, &protolayer);
+  link_Stack(&mp->link, &ccplayer);
+  link_Stack(&mp->link, &vjlayer);
+#ifndef NONAT
+  link_Stack(&mp->link, &natlayer);
+#endif
+}
+
+int
+mp_Up(struct mp *mp, struct datalink *dl)
+{
+  struct lcp *lcp = &dl->physical->link.lcp;
+
+  if (mp->active) {
+    /* We're adding a link - do a last validation on our parameters */
+    if (!peerid_Equal(&dl->peer, &mp->peer)) {
+      log_Printf(LogPHASE, "%s: Inappropriate peer !\n", dl->name);
+      log_Printf(LogPHASE, "  Attached to peer %s/%s\n", mp->peer.authname,
+                 mp_Enddisc(mp->peer.enddisc.class, mp->peer.enddisc.address,
+                            mp->peer.enddisc.len));
+      log_Printf(LogPHASE, "  New link is peer %s/%s\n", dl->peer.authname,
+                 mp_Enddisc(dl->peer.enddisc.class, dl->peer.enddisc.address,
+                            dl->peer.enddisc.len));
+      return MP_FAILED;
+    }
+    if (mp->local_mrru != lcp->want_mrru ||
+        mp->peer_mrru != lcp->his_mrru ||
+        mp->local_is12bit != lcp->want_shortseq ||
+        mp->peer_is12bit != lcp->his_shortseq) {
+      log_Printf(LogPHASE, "%s: Invalid MRRU/SHORTSEQ MP parameters !\n",
+                dl->name);
+      return MP_FAILED;
+    }
+    return MP_ADDED;
+  } else {
+    /* First link in multilink mode */
+
+    mp->local_mrru = lcp->want_mrru;
+    mp->peer_mrru = lcp->his_mrru;
+    mp->local_is12bit = lcp->want_shortseq;
+    mp->peer_is12bit = lcp->his_shortseq;
+    mp->peer = dl->peer;
+
+    throughput_destroy(&mp->link.stats.total);
+    throughput_init(&mp->link.stats.total, mp->cfg.autoload.period);
+    throughput_callback(&mp->link.stats.total, mp_UpDown, mp);
+    memset(mp->link.Queue, '\0', sizeof mp->link.Queue);
+    memset(mp->link.proto_in, '\0', sizeof mp->link.proto_in);
+    memset(mp->link.proto_out, '\0', sizeof mp->link.proto_out);
+
+    /* Tell the link who it belongs to */
+    dl->physical->link.stats.parent = &mp->link.stats.total;
+
+    mp->out.seq = 0;
+    mp->out.link = 0;
+    mp->out.af = AF_INET;
+    mp->seq.min_in = 0;
+    mp->seq.next_in = 0;
+
+    /*
+     * Now we create our server socket.
+     * If it already exists, join it.  Otherwise, create and own it
+     */
+    switch (mpserver_Open(&mp->server, &mp->peer)) {
+    case MPSERVER_CONNECTED:
+      log_Printf(LogPHASE, "mp: Transfer link on %s\n",
+                mp->server.socket.sun_path);
+      mp->server.send.dl = dl;		/* Defer 'till it's safe to send */
+      return MP_LINKSENT;
+    case MPSERVER_FAILED:
+      return MP_FAILED;
+    case MPSERVER_LISTENING:
+      log_Printf(LogPHASE, "mp: Listening on %s\n", mp->server.socket.sun_path);
+      log_Printf(LogPHASE, "    First link: %s\n", dl->name);
+
+      /* Re-point our NCP layers at our MP link */
+      ncp_SetLink(&mp->bundle->ncp, &mp->link);
+
+      /* Our lcp's already up 'cos of the NULL parent */
+      if (ccp_SetOpenMode(&mp->link.ccp)) {
+        fsm_Up(&mp->link.ccp.fsm);
+        fsm_Open(&mp->link.ccp.fsm);
+      }
+
+      mp->active = 1;
+      break;
+    }
+  }
+
+  return MP_UP;
+}
+
+void
+mp_Down(struct mp *mp)
+{
+  if (mp->active) {
+    struct mbuf *next;
+
+    /* Stop that ! */
+    mp_StopAutoloadTimer(mp);
+
+    /* Don't want any more of these */
+    mpserver_Close(&mp->server);
+
+    /* CCP goes down with a bang */
+    fsm2initial(&mp->link.ccp.fsm);
+
+    /* Received fragments go in the bit-bucket */
+    while (mp->inbufs) {
+      next = mp->inbufs->m_nextpkt;
+      m_freem(mp->inbufs);
+      mp->inbufs = next;
+    }
+
+    peerid_Init(&mp->peer);
+    mp->active = 0;
+  }
+}
+
+void
+mp_linkInit(struct mp_link *mplink)
+{
+  mplink->seq = 0;
+  mplink->bandwidth = 0;
+}
+
+static void
+mp_Assemble(struct mp *mp, struct mbuf *m, struct physical *p)
+{
+  struct mp_header mh, h;
+  struct mbuf *q, *last;
+  u_int32_t seq;
+
+  /*
+   * When `m' and `p' are NULL, it means our oldest link has gone down.
+   * We want to determine a new min, and process any intermediate stuff
+   * as normal
+   */
+
+  if (m && mp_ReadHeader(mp, m, &mh) == 0) {
+    m_freem(m);
+    return;
+  }
+
+  if (p) {
+    seq = p->dl->mp.seq;
+    p->dl->mp.seq = mh.seq;
+  } else
+    seq = mp->seq.min_in;
+
+  if (mp->seq.min_in == seq) {
+    /*
+     * We've received new data on the link that has our min (oldest) seq.
+     * Figure out which link now has the smallest (oldest) seq.
+     */
+    struct datalink *dl;
+
+    mp->seq.min_in = (u_int32_t)-1;
+    for (dl = mp->bundle->links; dl; dl = dl->next)
+      if (dl->state == DATALINK_OPEN &&
+          (mp->seq.min_in == (u_int32_t)-1 ||
+           isbefore(mp->local_is12bit, dl->mp.seq, mp->seq.min_in)))
+        mp->seq.min_in = dl->mp.seq;
+  }
+
+  /*
+   * Now process as many of our fragments as we can, adding our new
+   * fragment in as we go, and ordering with the oldest at the top of
+   * the queue.
+   */
+
+  last = NULL;
+  seq = mp->seq.next_in;
+  q = mp->inbufs;
+  while (q || m) {
+    if (!q) {
+      if (last)
+        last->m_nextpkt = m;
+      else
+        mp->inbufs = m;
+      q = m;
+      m = NULL;
+      h = mh;
+    } else {
+      mp_ReadHeader(mp, q, &h);
+
+      if (m && isbefore(mp->local_is12bit, mh.seq, h.seq)) {
+        /* Our received fragment fits in before this one, so link it in */
+        if (last)
+          last->m_nextpkt = m;
+        else
+          mp->inbufs = m;
+        m->m_nextpkt = q;
+        q = m;
+        h = mh;
+        m = NULL;
+      }
+    }
+
+    if (h.seq != seq) {
+      /* we're missing something :-( */
+      if (isbefore(mp->local_is12bit, seq, mp->seq.min_in)) {
+        /* we're never gonna get it */
+        struct mbuf *next;
+
+        /* Zap all older fragments */
+        while (mp->inbufs != q) {
+          log_Printf(LogDEBUG, "Drop frag\n");
+          next = mp->inbufs->m_nextpkt;
+          m_freem(mp->inbufs);
+          mp->inbufs = next;
+        }
+
+        /*
+         * Zap everything until the next `end' fragment OR just before
+         * the next `begin' fragment OR 'till seq.min_in - whichever
+         * comes first.
+         */
+        do {
+          mp_ReadHeader(mp, mp->inbufs, &h);
+          if (h.begin) {
+            /* We might be able to process this ! */
+            h.seq--;  /* We're gonna look for fragment with h.seq+1 */
+            break;
+          }
+          next = mp->inbufs->m_nextpkt;
+          log_Printf(LogDEBUG, "Drop frag %u\n", h.seq);
+          m_freem(mp->inbufs);
+          mp->inbufs = next;
+        } while (mp->inbufs && (isbefore(mp->local_is12bit, mp->seq.min_in,
+                                         h.seq) || h.end));
+
+        /*
+         * Continue processing things from here.
+         * This deals with the possibility that we received a fragment
+         * on the slowest link that invalidates some of our data (because
+         * of the hole at `q'), but where there are subsequent `whole'
+         * packets that have already been received.
+         */
+
+        mp->seq.next_in = seq = inc_seq(mp->local_is12bit, h.seq);
+        last = NULL;
+        q = mp->inbufs;
+      } else
+        /* we may still receive the missing fragment */
+        break;
+    } else if (h.end) {
+      /* We've got something, reassemble */
+      struct mbuf **frag = &q;
+      int len;
+      long long first = -1;
+
+      do {
+        *frag = mp->inbufs;
+        mp->inbufs = mp->inbufs->m_nextpkt;
+        len = mp_ReadHeader(mp, *frag, &h);
+        if (first == -1)
+          first = h.seq;
+        if (frag == &q && !h.begin) {
+          log_Printf(LogWARN, "Oops - MP frag %lu should have a begin flag\n",
+                    (u_long)h.seq);
+          m_freem(q);
+          q = NULL;
+        } else if (frag != &q && h.begin) {
+          log_Printf(LogWARN, "Oops - MP frag %lu should have an end flag\n",
+                    (u_long)h.seq - 1);
+          /*
+           * Stuff our fragment back at the front of the queue and zap
+           * our half-assembled packet.
+           */
+          (*frag)->m_nextpkt = mp->inbufs;
+          mp->inbufs = *frag;
+          *frag = NULL;
+          m_freem(q);
+          q = NULL;
+          frag = &q;
+          h.end = 0;	/* just in case it's a whole packet */
+        } else {
+          (*frag)->m_offset += len;
+          (*frag)->m_len -= len;
+          (*frag)->m_nextpkt = NULL;
+          do
+            frag = &(*frag)->m_next;
+          while (*frag != NULL);
+        }
+      } while (!h.end);
+
+      if (q) {
+        q = m_pullup(q);
+        log_Printf(LogDEBUG, "MP: Reassembled frags %lu-%lu, length %zd\n",
+                   (u_long)first, (u_long)h.seq, m_length(q));
+        link_PullPacket(&mp->link, MBUF_CTOP(q), q->m_len, mp->bundle);
+        m_freem(q);
+      }
+
+      mp->seq.next_in = seq = inc_seq(mp->local_is12bit, h.seq);
+      last = NULL;
+      q = mp->inbufs;
+    } else {
+      /* Look for the next fragment */
+      seq = inc_seq(mp->local_is12bit, seq);
+      last = q;
+      q = q->m_nextpkt;
+    }
+  }
+
+  if (m) {
+    /* We still have to find a home for our new fragment */
+    last = NULL;
+    for (q = mp->inbufs; q; last = q, q = q->m_nextpkt) {
+      mp_ReadHeader(mp, q, &h);
+      if (isbefore(mp->local_is12bit, mh.seq, h.seq))
+        break;
+    }
+    /* Our received fragment fits in here */
+    if (last)
+      last->m_nextpkt = m;
+    else
+      mp->inbufs = m;
+    m->m_nextpkt = q;
+  }
+}
+
+struct mbuf *
+mp_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+  struct physical *p = link2physical(l);
+
+  if (!bundle->ncp.mp.active)
+    /* Let someone else deal with it ! */
+    return bp;
+
+  if (p == NULL) {
+    log_Printf(LogWARN, "DecodePacket: Can't do MP inside MP !\n");
+    m_freem(bp);
+  } else {
+    m_settype(bp, MB_MPIN);
+    mp_Assemble(&bundle->ncp.mp, bp, p);
+  }
+
+  return NULL;
+}
+
+static void
+mp_Output(struct mp *mp, struct bundle *bundle, struct link *l,
+          struct mbuf *m, u_int32_t begin, u_int32_t end)
+{
+  char prepend[4];
+
+  /* Stuff an MP header on the front of our packet and send it */
+
+  if (mp->peer_is12bit) {
+    u_int16_t val;
+
+    val = (begin << 15) | (end << 14) | (u_int16_t)mp->out.seq;
+    ua_htons(&val, prepend);
+    m = m_prepend(m, prepend, 2, 0);
+  } else {
+    u_int32_t val;
+
+    val = (begin << 31) | (end << 30) | (u_int32_t)mp->out.seq;
+    ua_htonl(&val, prepend);
+    m = m_prepend(m, prepend, 4, 0);
+  }
+  if (log_IsKept(LogDEBUG))
+    log_Printf(LogDEBUG, "MP[frag %d]: Send %zd bytes on link `%s'\n",
+               mp->out.seq, m_length(m), l->name);
+  mp->out.seq = inc_seq(mp->peer_is12bit, mp->out.seq);
+
+  if (l->ccp.fsm.state != ST_OPENED && ccp_Required(&l->ccp)) {
+    log_Printf(LogPHASE, "%s: Not transmitting... waiting for CCP\n", l->name);
+    return;
+  }
+
+  link_PushPacket(l, m, bundle, LINK_QUEUES(l) - 1, PROTO_MP);
+}
+
+int
+mp_FillPhysicalQueues(struct bundle *bundle)
+{
+  struct mp *mp = &bundle->ncp.mp;
+  struct datalink *dl, *fdl;
+  size_t total, add, len;
+  int thislink, nlinks, nopenlinks, sendasip;
+  u_int32_t begin, end;
+  struct mbuf *m, *mo;
+  struct link *bestlink;
+
+  thislink = nlinks = nopenlinks = 0;
+  for (fdl = NULL, dl = bundle->links; dl; dl = dl->next) {
+    /* Include non-open links here as mp->out.link will stay more correct */
+    if (!fdl) {
+      if (thislink == mp->out.link)
+        fdl = dl;
+      else
+        thislink++;
+    }
+    nlinks++;
+    if (dl->state == DATALINK_OPEN)
+      nopenlinks++;
+  }
+
+  if (!fdl) {
+    fdl = bundle->links;
+    if (!fdl)
+      return 0;
+    thislink = 0;
+  }
+
+  total = 0;
+  for (dl = fdl; nlinks > 0; dl = dl->next, nlinks--, thislink++) {
+    if (!dl) {
+      dl = bundle->links;
+      thislink = 0;
+    }
+
+    if (dl->state != DATALINK_OPEN)
+      continue;
+
+    if (dl->physical->out)
+      /* this link has suffered a short write.  Let it continue */
+      continue;
+
+    add = link_QueueLen(&dl->physical->link);
+    if (add) {
+      /* this link has got stuff already queued.  Let it continue */
+      total += add;
+      continue;
+    }
+
+    if (!mp_QueueLen(mp)) {
+      int mrutoosmall;
+
+      /*
+       * If there's only a single open link in our bundle and we haven't got
+       * MP level link compression, queue outbound traffic directly via that
+       * link's protocol stack rather than using the MP link.  This results
+       * in the outbound traffic going out as PROTO_IP or PROTO_IPV6 rather
+       * than PROTO_MP.
+       */
+
+      mrutoosmall = 0;
+      sendasip = nopenlinks < 2;
+      if (sendasip) {
+        if (dl->physical->link.lcp.his_mru < mp->peer_mrru) {
+          /*
+           * Actually, forget it.  This test is done against the MRRU rather
+           * than the packet size so that we don't end up sending some data
+           * in MP fragments and some data in PROTO_IP packets.  That's just
+           * too likely to upset some ppp implementations.
+           */
+          mrutoosmall = 1;
+          sendasip = 0;
+        }
+      }
+
+      bestlink = sendasip ? &dl->physical->link : &mp->link;
+      if (!ncp_PushPacket(&bundle->ncp, &mp->out.af, bestlink))
+        break;	/* Nothing else to send */
+
+      if (mrutoosmall)
+        log_Printf(LogDEBUG, "Don't send data as PROTO_IP, MRU < MRRU\n");
+      else if (sendasip)
+        log_Printf(LogDEBUG, "Sending data as PROTO_IP, not PROTO_MP\n");
+
+      if (sendasip) {
+        add = link_QueueLen(&dl->physical->link);
+        if (add) {
+          /* this link has got stuff already queued.  Let it continue */
+          total += add;
+          continue;
+        }
+      }
+    }
+
+    m = link_Dequeue(&mp->link);
+    if (m) {
+      len = m_length(m);
+      begin = 1;
+      end = 0;
+
+      while (!end) {
+        if (dl->state == DATALINK_OPEN) {
+          /* Write at most his_mru bytes to the physical link */
+          if (len <= dl->physical->link.lcp.his_mru) {
+            mo = m;
+            end = 1;
+            m_settype(mo, MB_MPOUT);
+          } else {
+            /* It's > his_mru, chop the packet (`m') into bits */
+            mo = m_get(dl->physical->link.lcp.his_mru, MB_MPOUT);
+            len -= mo->m_len;
+            m = mbuf_Read(m, MBUF_CTOP(mo), mo->m_len);
+          }
+          mp_Output(mp, bundle, &dl->physical->link, mo, begin, end);
+          begin = 0;
+        }
+
+        if (!end) {
+          nlinks--;
+          dl = dl->next;
+          if (!dl) {
+            dl = bundle->links;
+            thislink = 0;
+          } else
+            thislink++;
+        }
+      }
+    }
+  }
+  mp->out.link = thislink;		/* Start here next time */
+
+  return total;
+}
+
+int
+mp_SetDatalinkBandwidth(struct cmdargs const *arg)
+{
+  int val;
+
+  if (arg->argc != arg->argn+1)
+    return -1;
+
+  val = atoi(arg->argv[arg->argn]);
+  if (val <= 0) {
+    log_Printf(LogWARN, "The link bandwidth must be greater than zero\n");
+    return 1;
+  }
+  arg->cx->mp.bandwidth = val;
+
+  if (arg->cx->state == DATALINK_OPEN)
+    bundle_CalculateBandwidth(arg->bundle);
+
+  return 0;
+}
+
+int
+mp_ShowStatus(struct cmdargs const *arg)
+{
+  struct mp *mp = &arg->bundle->ncp.mp;
+
+  prompt_Printf(arg->prompt, "Multilink is %sactive\n", mp->active ? "" : "in");
+  if (mp->active) {
+    struct mbuf *m, *lm;
+    int bufs = 0;
+
+    lm = NULL;
+    prompt_Printf(arg->prompt, "Socket:         %s\n",
+                  mp->server.socket.sun_path);
+    for (m = mp->inbufs; m; m = m->m_nextpkt) {
+      bufs++;
+      lm = m;
+    }
+    prompt_Printf(arg->prompt, "Pending frags:  %d", bufs);
+    if (bufs) {
+      struct mp_header mh;
+      unsigned long first, last;
+
+      first = mp_ReadHeader(mp, mp->inbufs, &mh) ? mh.seq : 0;
+      last = mp_ReadHeader(mp, lm, &mh) ? mh.seq : 0;
+      prompt_Printf(arg->prompt, " (Have %lu - %lu, want %lu, lowest %lu)\n",
+                    first, last, (unsigned long)mp->seq.next_in,
+                    (unsigned long)mp->seq.min_in);
+      prompt_Printf(arg->prompt, "                First has %sbegin bit and "
+                    "%send bit", mh.begin ? "" : "no ", mh.end ? "" : "no ");
+    }
+    prompt_Printf(arg->prompt, "\n");
+  }
+
+  prompt_Printf(arg->prompt, "\nMy Side:\n");
+  if (mp->active) {
+    prompt_Printf(arg->prompt, " Output SEQ:    %u\n", mp->out.seq);
+    prompt_Printf(arg->prompt, " MRRU:          %u\n", mp->local_mrru);
+    prompt_Printf(arg->prompt, " Short Seq:     %s\n",
+                  mp->local_is12bit ? "on" : "off");
+  }
+  prompt_Printf(arg->prompt, " Discriminator: %s\n",
+                mp_Enddisc(mp->cfg.enddisc.class, mp->cfg.enddisc.address,
+                           mp->cfg.enddisc.len));
+
+  prompt_Printf(arg->prompt, "\nHis Side:\n");
+  if (mp->active) {
+    prompt_Printf(arg->prompt, " Auth Name:     %s\n", mp->peer.authname);
+    prompt_Printf(arg->prompt, " Input SEQ:     %u\n", mp->seq.next_in);
+    prompt_Printf(arg->prompt, " MRRU:          %u\n", mp->peer_mrru);
+    prompt_Printf(arg->prompt, " Short Seq:     %s\n",
+                  mp->peer_is12bit ? "on" : "off");
+  }
+  prompt_Printf(arg->prompt,   " Discriminator: %s\n",
+                mp_Enddisc(mp->peer.enddisc.class, mp->peer.enddisc.address,
+                           mp->peer.enddisc.len));
+
+  prompt_Printf(arg->prompt, "\nDefaults:\n");
+
+  prompt_Printf(arg->prompt, " MRRU:          ");
+  if (mp->cfg.mrru)
+    prompt_Printf(arg->prompt, "%d (multilink enabled)\n", mp->cfg.mrru);
+  else
+    prompt_Printf(arg->prompt, "disabled\n");
+  prompt_Printf(arg->prompt, " Short Seq:     %s\n",
+                  command_ShowNegval(mp->cfg.shortseq));
+  prompt_Printf(arg->prompt, " Discriminator: %s\n",
+                  command_ShowNegval(mp->cfg.negenddisc));
+  prompt_Printf(arg->prompt, " AutoLoad:      min %d%%, max %d%%,"
+                " period %d secs\n", mp->cfg.autoload.min,
+                mp->cfg.autoload.max, mp->cfg.autoload.period);
+
+  return 0;
+}
+
+const char *
+mp_Enddisc(u_char c, const char *address, size_t len)
+{
+  static char result[100];	/* Used immediately after it's returned */
+  unsigned f, header;
+
+  switch (c) {
+    case ENDDISC_NULL:
+      sprintf(result, "Null Class");
+      break;
+
+    case ENDDISC_LOCAL:
+	    snprintf(result, sizeof result, "Local Addr: %.*s", (int)len,
+		address);
+      break;
+
+    case ENDDISC_IP:
+      if (len == 4)
+        snprintf(result, sizeof result, "IP %s",
+                 inet_ntoa(*(const struct in_addr *)address));
+      else
+        sprintf(result, "IP[%zd] ???", len);
+      break;
+
+    case ENDDISC_MAC:
+      if (len == 6) {
+        const u_char *m = (const u_char *)address;
+        snprintf(result, sizeof result, "MAC %02x:%02x:%02x:%02x:%02x:%02x",
+                 m[0], m[1], m[2], m[3], m[4], m[5]);
+      } else
+        sprintf(result, "MAC[%zd] ???", len);
+      break;
+
+    case ENDDISC_MAGIC:
+      sprintf(result, "Magic: 0x");
+      header = strlen(result);
+      if (len + header + 1 > sizeof result)
+        len = sizeof result - header - 1;
+      for (f = 0; f < len; f++)
+        sprintf(result + header + 2 * f, "%02x", address[f]);
+      break;
+
+    case ENDDISC_PSN:
+	    snprintf(result, sizeof result, "PSN: %.*s", (int)len, address);
+      break;
+
+    default:
+      sprintf(result, "%d: ", (int)c);
+      header = strlen(result);
+      if (len + header + 1 > sizeof result)
+        len = sizeof result - header - 1;
+      for (f = 0; f < len; f++)
+        sprintf(result + header + 2 * f, "%02x", address[f]);
+      break;
+  }
+  return result;
+}
+
+int
+mp_SetEnddisc(struct cmdargs const *arg)
+{
+  struct mp *mp = &arg->bundle->ncp.mp;
+  struct in_addr addr;
+
+  switch (bundle_Phase(arg->bundle)) {
+    case PHASE_DEAD:
+      break;
+    case PHASE_ESTABLISH:
+      /* Make sure none of our links are DATALINK_LCP or greater */
+      if (bundle_HighestState(arg->bundle) >= DATALINK_LCP) {
+        log_Printf(LogWARN, "enddisc: Only changable before"
+                   " LCP negotiations\n");
+        return 1;
+      }
+      break;
+    default:
+      log_Printf(LogWARN, "enddisc: Only changable at phase DEAD/ESTABLISH\n");
+      return 1;
+  }
+
+  if (arg->argc == arg->argn) {
+    mp->cfg.enddisc.class = 0;
+    *mp->cfg.enddisc.address = '\0';
+    mp->cfg.enddisc.len = 0;
+  } else if (arg->argc > arg->argn) {
+    if (!strcasecmp(arg->argv[arg->argn], "label")) {
+      mp->cfg.enddisc.class = ENDDISC_LOCAL;
+      strcpy(mp->cfg.enddisc.address, arg->bundle->cfg.label);
+      mp->cfg.enddisc.len = strlen(mp->cfg.enddisc.address);
+    } else if (!strcasecmp(arg->argv[arg->argn], "ip")) {
+      if (arg->bundle->ncp.ipcp.my_ip.s_addr == INADDR_ANY)
+        ncprange_getip4addr(&arg->bundle->ncp.ipcp.cfg.my_range, &addr);
+      else
+        addr = arg->bundle->ncp.ipcp.my_ip;
+      memcpy(mp->cfg.enddisc.address, &addr.s_addr, sizeof addr.s_addr);
+      mp->cfg.enddisc.class = ENDDISC_IP;
+      mp->cfg.enddisc.len = sizeof arg->bundle->ncp.ipcp.my_ip.s_addr;
+    } else if (!strcasecmp(arg->argv[arg->argn], "mac")) {
+      struct sockaddr_dl hwaddr;
+
+      if (arg->bundle->ncp.ipcp.my_ip.s_addr == INADDR_ANY)
+        ncprange_getip4addr(&arg->bundle->ncp.ipcp.cfg.my_range, &addr);
+      else
+        addr = arg->bundle->ncp.ipcp.my_ip;
+
+      if (arp_EtherAddr(addr, &hwaddr, 1)) {
+        mp->cfg.enddisc.class = ENDDISC_MAC;
+        memcpy(mp->cfg.enddisc.address, hwaddr.sdl_data + hwaddr.sdl_nlen,
+               hwaddr.sdl_alen);
+        mp->cfg.enddisc.len = hwaddr.sdl_alen;
+      } else {
+        log_Printf(LogWARN, "set enddisc: Can't locate MAC address for %s\n",
+                  inet_ntoa(addr));
+        return 4;
+      }
+    } else if (!strcasecmp(arg->argv[arg->argn], "magic")) {
+      int f;
+
+      randinit();
+      for (f = 0; f < 20; f += sizeof(long))
+        *(long *)(mp->cfg.enddisc.address + f) = random();
+      mp->cfg.enddisc.class = ENDDISC_MAGIC;
+      mp->cfg.enddisc.len = 20;
+    } else if (!strcasecmp(arg->argv[arg->argn], "psn")) {
+      if (arg->argc > arg->argn+1) {
+        mp->cfg.enddisc.class = ENDDISC_PSN;
+        strcpy(mp->cfg.enddisc.address, arg->argv[arg->argn+1]);
+        mp->cfg.enddisc.len = strlen(mp->cfg.enddisc.address);
+      } else {
+        log_Printf(LogWARN, "PSN endpoint requires additional data\n");
+        return 5;
+      }
+    } else {
+      log_Printf(LogWARN, "%s: Unrecognised endpoint type\n",
+                arg->argv[arg->argn]);
+      return 6;
+    }
+  }
+
+  return 0;
+}
+
+static int
+mpserver_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e,
+                   int *n)
+{
+  struct mpserver *s = descriptor2mpserver(d);
+  int result;
+
+  result = 0;
+  if (s->send.dl != NULL) {
+    /* We've connect()ed */
+    if (!link_QueueLen(&s->send.dl->physical->link) &&
+        !s->send.dl->physical->out) {
+      /* Only send if we've transmitted all our data (i.e. the ConfigAck) */
+      result -= datalink_RemoveFromSet(s->send.dl, r, w, e);
+      bundle_SendDatalink(s->send.dl, s->fd, &s->socket);
+      s->send.dl = NULL;
+      s->fd = -1;
+    } else
+      /* Never read from a datalink that's on death row ! */
+      result -= datalink_RemoveFromSet(s->send.dl, r, NULL, NULL);
+  } else if (r && s->fd >= 0) {
+    if (*n < s->fd + 1)
+      *n = s->fd + 1;
+    FD_SET(s->fd, r);
+    log_Printf(LogTIMER, "mp: fdset(r) %d\n", s->fd);
+    result++;
+  }
+  return result;
+}
+
+static int
+mpserver_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+  struct mpserver *s = descriptor2mpserver(d);
+  return s->fd >= 0 && FD_ISSET(s->fd, fdset);
+}
+
+static void
+mpserver_Read(struct fdescriptor *d, struct bundle *bundle,
+	      const fd_set *fdset __unused)
+{
+  struct mpserver *s = descriptor2mpserver(d);
+
+  bundle_ReceiveDatalink(bundle, s->fd);
+}
+
+static int
+mpserver_Write(struct fdescriptor *d __unused, struct bundle *bundle __unused,
+               const fd_set *fdset __unused)
+{
+  /* We never want to write here ! */
+  log_Printf(LogALERT, "mpserver_Write: Internal error: Bad call !\n");
+  return 0;
+}
+
+void
+mpserver_Init(struct mpserver *s)
+{
+  s->desc.type = MPSERVER_DESCRIPTOR;
+  s->desc.UpdateSet = mpserver_UpdateSet;
+  s->desc.IsSet = mpserver_IsSet;
+  s->desc.Read = mpserver_Read;
+  s->desc.Write = mpserver_Write;
+  s->send.dl = NULL;
+  s->fd = -1;
+  memset(&s->socket, '\0', sizeof s->socket);
+}
+
+int
+mpserver_Open(struct mpserver *s, struct peerid *peer)
+{
+  int f, l;
+  mode_t mask;
+
+  if (s->fd != -1) {
+    log_Printf(LogALERT, "Internal error !  mpserver already open\n");
+    mpserver_Close(s);
+  }
+
+  l = snprintf(s->socket.sun_path, sizeof s->socket.sun_path, "%sppp-%s-%02x-",
+               _PATH_VARRUN, peer->authname, peer->enddisc.class);
+  if (l < 0) {
+    log_Printf(LogERROR, "mpserver: snprintf(): %s\n", strerror(errno));
+    return MPSERVER_FAILED;
+  }
+
+  for (f = 0;
+       f < peer->enddisc.len && (size_t)l < sizeof s->socket.sun_path - 2;
+       f++) {
+    snprintf(s->socket.sun_path + l, sizeof s->socket.sun_path - l,
+             "%02x", *(u_char *)(peer->enddisc.address+f));
+    l += 2;
+  }
+
+  s->socket.sun_family = AF_LOCAL;
+  s->socket.sun_len = sizeof s->socket;
+  s->fd = ID0socket(PF_LOCAL, SOCK_DGRAM, 0);
+  if (s->fd < 0) {
+    log_Printf(LogERROR, "mpserver: socket(): %s\n", strerror(errno));
+    return MPSERVER_FAILED;
+  }
+
+  setsockopt(s->fd, SOL_SOCKET, SO_REUSEADDR, (struct sockaddr *)&s->socket,
+             sizeof s->socket);
+  mask = umask(0177);
+
+  /*
+   * Try to bind the socket.  If we succeed we play server, if we fail
+   * we connect() and hand the link off.
+   */
+
+  if (ID0bind_un(s->fd, &s->socket) < 0) {
+    if (errno != EADDRINUSE) {
+      log_Printf(LogPHASE, "mpserver: can't create bundle socket %s (%s)\n",
+                s->socket.sun_path, strerror(errno));
+      umask(mask);
+      close(s->fd);
+      s->fd = -1;
+      return MPSERVER_FAILED;
+    }
+
+    /* So we're the sender */
+    umask(mask);
+    if (ID0connect_un(s->fd, &s->socket) < 0) {
+      log_Printf(LogPHASE, "mpserver: can't connect to bundle socket %s (%s)\n",
+                s->socket.sun_path, strerror(errno));
+      if (errno == ECONNREFUSED)
+        log_Printf(LogPHASE, "          The previous server died badly !\n");
+      close(s->fd);
+      s->fd = -1;
+      return MPSERVER_FAILED;
+    }
+
+    /* Donate our link to the other guy */
+    return MPSERVER_CONNECTED;
+  }
+
+  return MPSERVER_LISTENING;
+}
+
+void
+mpserver_Close(struct mpserver *s)
+{
+  if (s->send.dl != NULL) {
+    bundle_SendDatalink(s->send.dl, s->fd, &s->socket);
+    s->send.dl = NULL;
+    s->fd = -1;
+  } else if (s->fd >= 0) {
+    close(s->fd);
+    if (ID0unlink(s->socket.sun_path) == -1)
+      log_Printf(LogERROR, "%s: Failed to remove: %s\n", s->socket.sun_path,
+                strerror(errno));
+    memset(&s->socket, '\0', sizeof s->socket);
+    s->fd = -1;
+  }
+}
+
+void
+mp_LinkLost(struct mp *mp, struct datalink *dl)
+{
+  if (mp->seq.min_in == dl->mp.seq)
+    /* We've lost the link that's holding everything up ! */
+    mp_Assemble(mp, NULL, NULL);
+}
+
+size_t
+mp_QueueLen(struct mp *mp)
+{
+  return link_QueueLen(&mp->link);
+}
diff --git a/src/mp.h b/src/mp.h
new file mode 100644
index 0000000..0936805
--- /dev/null
+++ b/src/mp.h
@@ -0,0 +1,146 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/mp.h,v 1.12.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct mbuf;
+struct physical;
+struct bundle;
+struct cmdargs;
+struct datalink;
+
+#define ENDDISC_NULL	0
+#define ENDDISC_LOCAL	1
+#define ENDDISC_IP	2
+#define ENDDISC_MAC	3
+#define ENDDISC_MAGIC	4
+#define ENDDISC_PSN	5
+
+#define MP_LINKSENT	0	/* We attached the link to another ppp */
+#define MP_UP		1	/* We've started MP */
+#define MP_ADDED	2	/* We've added the link to our MP */
+#define MP_FAILED	3	/* No go */
+
+#define MPSERVER_CONNECTED	0
+#define MPSERVER_LISTENING	1
+#define MPSERVER_FAILED		2
+
+struct enddisc {
+  u_char class;
+  char address[50];
+  int len;
+};
+
+struct peerid {
+  struct enddisc enddisc;	/* Peers endpoint discriminator */
+  char authname[AUTHLEN];	/* Peers name (authenticated) */
+};
+
+struct mpserver {
+  struct fdescriptor desc;
+  int fd;			/* listen()ing or connect()ing here */
+  struct sockaddr_un socket;	/* On this socket */
+
+  struct {
+    struct datalink *dl;	/* Send this datalink */
+  } send;			/* (in UpdateSet()) */
+};
+
+struct mp {
+  struct link link;
+
+  unsigned active : 1;
+  unsigned peer_is12bit : 1;	/* 12/24bit seq nos */
+  unsigned local_is12bit : 1;
+  u_short peer_mrru;
+  u_short local_mrru;
+
+  struct peerid peer;		/* Who are we talking to */
+  struct mpserver server;	/* Our ``sharing'' socket */
+
+  struct {
+    u_int32_t seq;		/* next outgoing seq */
+    int link;			/* Next link to send on */
+    int af;			/* Next address family to send */
+  } out;
+
+  struct {
+    u_int32_t min_in;		/* minimum received incoming seq */
+    u_int32_t next_in;		/* next incoming seq to process */
+  } seq;
+
+  struct {
+    u_short mrru;		/* Max Reconstructed Receive Unit */
+    unsigned shortseq : 2;	/* I want short Sequence Numbers */
+    unsigned negenddisc : 2;	/* I want an endpoint discriminator */
+    struct enddisc enddisc;	/* endpoint discriminator */
+    struct {
+      int min;			/* Lowest percent of bundle->bandwidth */
+      int max;			/* Highest percent of bundle->bandwidth out */
+      int period;		/* link->throughput sample period */
+    } autoload;
+  } cfg;
+
+  struct mbuf *inbufs;		/* Received fragments */
+  struct fsm_parent fsmp;	/* Our callback functions */
+  struct bundle *bundle;	/* Parent */
+};
+
+struct mp_link {
+  u_int32_t seq;		/* 12 or 24 bit incoming seq */
+  unsigned bandwidth;		/* Our link bandwidth (or zero) */
+};
+
+struct mp_header {
+  unsigned begin : 1;
+  unsigned end : 1;
+  u_int32_t seq;
+};
+
+#define descriptor2mpserver(d) \
+  ((d)->type == MPSERVER_DESCRIPTOR ? (struct mpserver *)(d) : NULL)
+#define mpserver_IsOpen(s) ((s)->fd != -1)
+
+extern void peerid_Init(struct peerid *);
+extern int peerid_Equal(const struct peerid *, const struct peerid *);
+extern void mpserver_Init(struct mpserver *);
+extern int mpserver_Open(struct mpserver *, struct peerid *);
+extern void mpserver_Close(struct mpserver *);
+extern void mp_Init(struct mp *, struct bundle *);
+extern void mp_linkInit(struct mp_link *);
+extern int mp_Up(struct mp *, struct datalink *);
+extern void mp_Down(struct mp *);
+extern struct mbuf *mp_Input(struct bundle *, struct link *, struct mbuf *);
+extern int mp_FillPhysicalQueues(struct bundle *);
+extern int mp_SetDatalinkBandwidth(struct cmdargs const *);
+extern int mp_ShowStatus(struct cmdargs const *);
+extern const char *mp_Enddisc(u_char, const char *, size_t);
+extern int mp_SetEnddisc(struct cmdargs const *);
+extern void mp_LinkLost(struct mp *, struct datalink *);
+extern void mp_RestartAutoloadTimer(struct mp *);
+extern void mp_CheckAutoloadTimer(struct mp *);
+extern void mp_StopAutoloadTimer(struct mp *);
+extern size_t mp_QueueLen(struct mp *);
diff --git a/src/mppe.c b/src/mppe.c
new file mode 100644
index 0000000..897ba62
--- /dev/null
+++ b/src/mppe.c
@@ -0,0 +1,817 @@
+/*-
+ * Copyright (c) 2000 Semen Ustimenko <semenu@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/mppe.c,v 1.28.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+
+#include <sys/socket.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/un.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <openssl/rc4.h>
+
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "throughput.h"
+#include "layer.h"
+#include "link.h"
+#include "chap_ms.h"
+#include "proto.h"
+#include "mppe.h"
+#include "ua.h"
+#include "descriptor.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ncpaddr.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "filter.h"
+#include "mp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+/*
+ * Documentation:
+ *
+ * draft-ietf-pppext-mppe-04.txt
+ * draft-ietf-pppext-mppe-keys-02.txt
+ */
+
+#define	MPPE_OPT_STATELESS	0x1000000
+#define	MPPE_OPT_COMPRESSED	0x01
+#define	MPPE_OPT_40BIT		0x20
+#define	MPPE_OPT_56BIT		0x80
+#define	MPPE_OPT_128BIT		0x40
+#define	MPPE_OPT_BITMASK	0xe0
+#define	MPPE_OPT_MASK		(MPPE_OPT_STATELESS | MPPE_OPT_BITMASK)
+
+#define	MPPE_FLUSHED			0x8000
+#define	MPPE_ENCRYPTED			0x1000
+#define	MPPE_HEADER_BITMASK		0xf000
+#define	MPPE_HEADER_FLAG		0x00ff
+#define	MPPE_HEADER_FLAGMASK		0x00ff
+#define	MPPE_HEADER_FLAGSHIFT		8
+#define	MPPE_HEADER_STATEFUL_KEYCHANGES	16
+
+struct mppe_state {
+  unsigned	stateless : 1;
+  unsigned	flushnext : 1;
+  unsigned	flushrequired : 1;
+  int		cohnum;
+  unsigned	keylen;			/* 8 or 16 bytes */
+  int 		keybits;		/* 40, 56 or 128 bits */
+  char		sesskey[MPPE_KEY_LEN];
+  char		mastkey[MPPE_KEY_LEN];
+  RC4_KEY	rc4key;
+};
+
+int MPPE_MasterKeyValid = 0;
+int MPPE_IsServer = 0;
+char MPPE_MasterKey[MPPE_KEY_LEN];
+
+/*
+ * The peer has missed a packet.  Mark the next output frame to be FLUSHED
+ */
+static int
+MPPEResetOutput(void *v)
+{
+  struct mppe_state *mop = (struct mppe_state *)v;
+
+  if (mop->stateless)
+    log_Printf(LogCCP, "MPPE: Unexpected output channel reset\n");
+  else {
+    log_Printf(LogCCP, "MPPE: Output channel reset\n");
+    mop->flushnext = 1;
+  }
+
+  return 0;		/* Ask FSM not to ACK */
+}
+
+static void
+MPPEReduceSessionKey(struct mppe_state *mp)
+{
+  switch(mp->keybits) {
+  case 40:
+    mp->sesskey[2] = 0x9e;
+    mp->sesskey[1] = 0x26;
+  case 56:
+    mp->sesskey[0] = 0xd1;
+  case 128:
+    break;
+  }
+}
+
+static void
+MPPEKeyChange(struct mppe_state *mp)
+{
+  char InterimKey[MPPE_KEY_LEN];
+  RC4_KEY RC4Key;
+
+  GetNewKeyFromSHA(mp->mastkey, mp->sesskey, mp->keylen, InterimKey);
+  RC4_set_key(&RC4Key, mp->keylen, InterimKey);
+  RC4(&RC4Key, mp->keylen, InterimKey, mp->sesskey);
+
+  MPPEReduceSessionKey(mp);
+}
+
+static struct mbuf *
+MPPEOutput(void *v, struct ccp *ccp, struct link *l __unused, int pri __unused,
+	   u_short *proto, struct mbuf *mp)
+{
+  struct mppe_state *mop = (struct mppe_state *)v;
+  struct mbuf *mo;
+  u_short nproto, prefix;
+  int dictinit, ilen, len;
+  char *rp;
+
+  ilen = m_length(mp);
+  dictinit = 0;
+
+  log_Printf(LogDEBUG, "MPPE: Output: Proto %02x (%d bytes)\n", *proto, ilen);
+  if (*proto < 0x21 && *proto > 0xFA) {
+    log_Printf(LogDEBUG, "MPPE: Output: Not encrypting\n");
+    ccp->compout += ilen;
+    ccp->uncompout += ilen;
+    return mp;
+  }
+
+  log_DumpBp(LogDEBUG, "MPPE: Output: Encrypt packet:", mp);
+
+  /* Get mbuf for prefixes */
+  mo = m_get(4, MB_CCPOUT);
+  mo->m_next = mp;
+
+  rp = MBUF_CTOP(mo);
+  prefix = MPPE_ENCRYPTED | mop->cohnum;
+
+  if (mop->stateless ||
+      (mop->cohnum & MPPE_HEADER_FLAGMASK) == MPPE_HEADER_FLAG) {
+    /* Change our key */
+    log_Printf(LogDEBUG, "MPPEOutput: Key changed [%d]\n", mop->cohnum);
+    MPPEKeyChange(mop);
+    dictinit = 1;
+  }
+
+  if (mop->stateless || mop->flushnext) {
+    prefix |= MPPE_FLUSHED;
+    dictinit = 1;
+    mop->flushnext = 0;
+  }
+
+  if (dictinit) {
+    /* Initialise our dictionary */
+    log_Printf(LogDEBUG, "MPPEOutput: Dictionary initialised [%d]\n",
+               mop->cohnum);
+    RC4_set_key(&mop->rc4key, mop->keylen, mop->sesskey);
+  }
+
+  /* Set MPPE packet prefix */
+  ua_htons(&prefix, rp);
+
+  /* Save encrypted protocol number */
+  nproto = htons(*proto);
+  RC4(&mop->rc4key, 2, (char *)&nproto, rp + 2);
+
+  /* Encrypt main packet */
+  rp = MBUF_CTOP(mp);
+  RC4(&mop->rc4key, ilen, rp, rp);
+
+  mop->cohnum++;
+  mop->cohnum &= ~MPPE_HEADER_BITMASK;
+
+  /* Set the protocol number */
+  *proto = ccp_Proto(ccp);
+  len = m_length(mo);
+  ccp->uncompout += ilen;
+  ccp->compout += len;
+
+  log_Printf(LogDEBUG, "MPPE: Output: Encrypted: Proto %02x (%d bytes)\n",
+             *proto, len);
+
+  return mo;
+}
+
+static void
+MPPEResetInput(void *v __unused)
+{
+  log_Printf(LogCCP, "MPPE: Unexpected input channel ack\n");
+}
+
+static struct mbuf *
+MPPEInput(void *v, struct ccp *ccp, u_short *proto, struct mbuf *mp)
+{
+  struct mppe_state *mip = (struct mppe_state *)v;
+  u_short prefix;
+  char *rp;
+  int dictinit, flushed, ilen, len, n;
+
+  ilen = m_length(mp);
+  dictinit = 0;
+  ccp->compin += ilen;
+
+  log_Printf(LogDEBUG, "MPPE: Input: Proto %02x (%d bytes)\n", *proto, ilen);
+  log_DumpBp(LogDEBUG, "MPPE: Input: Packet:", mp);
+
+  mp = mbuf_Read(mp, &prefix, 2);
+  prefix = ntohs(prefix);
+  flushed = prefix & MPPE_FLUSHED;
+  prefix &= ~flushed;
+  if ((prefix & MPPE_HEADER_BITMASK) != MPPE_ENCRYPTED) {
+    log_Printf(LogERROR, "MPPE: Input: Invalid packet (flags = 0x%x)\n",
+               (prefix & MPPE_HEADER_BITMASK) | flushed);
+    m_freem(mp);
+    return NULL;
+  }
+
+  prefix &= ~MPPE_HEADER_BITMASK;
+
+  if (!flushed && mip->stateless) {
+    log_Printf(LogCCP, "MPPEInput: Packet without MPPE_FLUSHED set"
+               " in stateless mode\n");
+    flushed = MPPE_FLUSHED;
+    /* Should we really continue ? */
+  }
+
+  if (mip->stateless) {
+    /* Change our key for each missed packet in stateless mode */
+    while (prefix != mip->cohnum) {
+      log_Printf(LogDEBUG, "MPPEInput: Key changed [%u]\n", prefix);
+      MPPEKeyChange(mip);
+      /*
+       * mip->cohnum contains what we received last time in stateless
+       * mode.
+       */
+      mip->cohnum++;
+      mip->cohnum &= ~MPPE_HEADER_BITMASK;
+    }
+    dictinit = 1;
+  } else {
+    if (flushed) {
+      /*
+       * We can always process a flushed packet.
+       * Catch up on any outstanding key changes.
+       */
+      n = (prefix >> MPPE_HEADER_FLAGSHIFT) -
+          (mip->cohnum >> MPPE_HEADER_FLAGSHIFT);
+      if (n < 0)
+        n += MPPE_HEADER_STATEFUL_KEYCHANGES;
+      while (n--) {
+        log_Printf(LogDEBUG, "MPPEInput: Key changed during catchup [%u]\n",
+                   prefix);
+        MPPEKeyChange(mip);
+      }
+      mip->flushrequired = 0;
+      mip->cohnum = prefix;
+      dictinit = 1;
+    }
+
+    if (mip->flushrequired) {
+      /*
+       * Perhaps we should be lenient if
+       * (prefix & MPPE_HEADER_FLAGMASK) == MPPE_HEADER_FLAG
+       * The spec says that we shouldn't be though....
+       */
+      log_Printf(LogDEBUG, "MPPE: Not flushed - discarded\n");
+      fsm_Output(&ccp->fsm, CODE_RESETREQ, ccp->fsm.reqid++, NULL, 0,
+                 MB_CCPOUT);
+      m_freem(mp);
+      return NULL;
+    }
+
+    if (prefix != mip->cohnum) {
+      /*
+       * We're in stateful mode and didn't receive the expected
+       * packet.  Send a reset request, but don't tell the CCP layer
+       * about it as we don't expect to receive a Reset ACK !
+       * Guess what... M$ invented this !
+       */
+      log_Printf(LogCCP, "MPPE: Input: Got seq %u, not %u\n",
+                 prefix, mip->cohnum);
+      fsm_Output(&ccp->fsm, CODE_RESETREQ, ccp->fsm.reqid++, NULL, 0,
+                 MB_CCPOUT);
+      mip->flushrequired = 1;
+      m_freem(mp);
+      return NULL;
+    }
+
+    if ((prefix & MPPE_HEADER_FLAGMASK) == MPPE_HEADER_FLAG) {
+      log_Printf(LogDEBUG, "MPPEInput: Key changed [%u]\n", prefix);
+      MPPEKeyChange(mip);
+      dictinit = 1;
+    } else if (flushed)
+      dictinit = 1;
+
+    /*
+     * mip->cohnum contains what we expect to receive next time in stateful
+     * mode.
+     */
+    mip->cohnum++;
+    mip->cohnum &= ~MPPE_HEADER_BITMASK;
+  }
+
+  if (dictinit) {
+    log_Printf(LogDEBUG, "MPPEInput: Dictionary initialised [%u]\n", prefix);
+    RC4_set_key(&mip->rc4key, mip->keylen, mip->sesskey);
+  }
+
+  mp = mbuf_Read(mp, proto, 2);
+  RC4(&mip->rc4key, 2, (char *)proto, (char *)proto);
+  *proto = ntohs(*proto);
+
+  rp = MBUF_CTOP(mp);
+  len = m_length(mp);
+  RC4(&mip->rc4key, len, rp, rp);
+
+  log_Printf(LogDEBUG, "MPPEInput: Decrypted: Proto %02x (%d bytes)\n",
+             *proto, len);
+  log_DumpBp(LogDEBUG, "MPPEInput: Decrypted: Packet:", mp);
+
+  ccp->uncompin += len;
+
+  return mp;
+}
+
+static void
+MPPEDictSetup(void *v __unused, struct ccp *ccp __unused,
+	      u_short proto __unused, struct mbuf *mp __unused)
+{
+  /* Nothing to see here */
+}
+
+static const char *
+MPPEDispOpts(struct fsm_opt *o)
+{
+  static char buf[70];
+  u_int32_t val;
+  char ch;
+  int len, n;
+
+  ua_ntohl(o->data, &val);
+  len = 0;
+  if ((n = snprintf(buf, sizeof buf, "value 0x%08x ", (unsigned)val)) > 0)
+    len += n;
+  if (!(val & MPPE_OPT_BITMASK)) {
+    if ((n = snprintf(buf + len, sizeof buf - len, "(0")) > 0)
+      len += n;
+  } else {
+    ch = '(';
+    if (val & MPPE_OPT_128BIT) {
+      if ((n = snprintf(buf + len, sizeof buf - len, "%c128", ch)) > 0)
+        len += n;
+      ch = '/';
+    }
+    if (val & MPPE_OPT_56BIT) {
+      if ((n = snprintf(buf + len, sizeof buf - len, "%c56", ch)) > 0)
+        len += n;
+      ch = '/';
+    }
+    if (val & MPPE_OPT_40BIT) {
+      if ((n = snprintf(buf + len, sizeof buf - len, "%c40", ch)) > 0)
+        len += n;
+      ch = '/';
+    }
+  }
+
+  if ((n = snprintf(buf + len, sizeof buf - len, " bits, state%s",
+                    (val & MPPE_OPT_STATELESS) ? "less" : "ful")) > 0)
+    len += n;
+
+  if (val & MPPE_OPT_COMPRESSED) {
+    if ((n = snprintf(buf + len, sizeof buf - len, ", compressed")) > 0)
+      len += n;
+  }
+
+  snprintf(buf + len, sizeof buf - len, ")");
+
+  return buf;
+}
+
+static int
+MPPEUsable(struct fsm *fp)
+{
+  int ok;
+#ifndef NORADIUS
+  struct radius *r = &fp->bundle->radius;
+
+  /*
+   * If the radius server gave us RAD_MICROSOFT_MS_MPPE_ENCRYPTION_TYPES,
+   * use that instead of our configuration value.
+   */
+  if (*r->cfg.file) {
+    ok = r->mppe.sendkeylen && r->mppe.recvkeylen;
+    if (!ok)
+      log_Printf(LogCCP, "MPPE: Not permitted by RADIUS server\n");
+  } else
+#endif
+  {
+    struct lcp *lcp = &fp->link->lcp;
+    ok = (lcp->want_auth == PROTO_CHAP && lcp->want_authtype == 0x81) ||
+         (lcp->his_auth == PROTO_CHAP && lcp->his_authtype == 0x81);
+    if (!ok)
+      log_Printf(LogCCP, "MPPE: Not usable without CHAP81\n");
+  }
+
+  return ok;
+}
+
+static int
+MPPERequired(struct fsm *fp)
+{
+#ifndef NORADIUS
+  /*
+   * If the radius server gave us RAD_MICROSOFT_MS_MPPE_ENCRYPTION_POLICY,
+   * use that instead of our configuration value.
+   */
+  if (*fp->bundle->radius.cfg.file && fp->bundle->radius.mppe.policy)
+    return fp->bundle->radius.mppe.policy == MPPE_POLICY_REQUIRED ? 1 : 0;
+#endif
+
+  return fp->link->ccp.cfg.mppe.required;
+}
+
+static u_int32_t
+MPPE_ConfigVal(struct bundle *bundle __unused, const struct ccp_config *cfg)
+{
+  u_int32_t val;
+
+  val = cfg->mppe.state == MPPE_STATELESS ? MPPE_OPT_STATELESS : 0;
+#ifndef NORADIUS
+  /*
+   * If the radius server gave us RAD_MICROSOFT_MS_MPPE_ENCRYPTION_TYPES,
+   * use that instead of our configuration value.
+   */
+  if (*bundle->radius.cfg.file && bundle->radius.mppe.types) {
+    if (bundle->radius.mppe.types & MPPE_TYPE_40BIT)
+      val |= MPPE_OPT_40BIT;
+    if (bundle->radius.mppe.types & MPPE_TYPE_128BIT)
+      val |= MPPE_OPT_128BIT;
+  } else
+#endif
+    switch(cfg->mppe.keybits) {
+    case 128:
+      val |= MPPE_OPT_128BIT;
+      break;
+    case 56:
+      val |= MPPE_OPT_56BIT;
+      break;
+    case 40:
+      val |= MPPE_OPT_40BIT;
+      break;
+    case 0:
+      val |= MPPE_OPT_128BIT | MPPE_OPT_56BIT | MPPE_OPT_40BIT;
+      break;
+    }
+
+  return val;
+}
+
+/*
+ * What options should we use for our first configure request
+ */
+static void
+MPPEInitOptsOutput(struct bundle *bundle, struct fsm_opt *o,
+                   const struct ccp_config *cfg)
+{
+  u_int32_t mval;
+
+  o->hdr.len = 6;
+
+  if (!MPPE_MasterKeyValid) {
+    log_Printf(LogCCP, "MPPE: MasterKey is invalid,"
+               " MPPE is available only with CHAP81 authentication\n");
+    mval = 0;
+    ua_htonl(&mval, o->data);
+    return;
+  }
+
+
+  mval = MPPE_ConfigVal(bundle, cfg);
+  ua_htonl(&mval, o->data);
+}
+
+/*
+ * Our CCP request was NAK'd with the given options
+ */
+static int
+MPPESetOptsOutput(struct bundle *bundle, struct fsm_opt *o,
+                  const struct ccp_config *cfg)
+{
+  u_int32_t mval, peer;
+
+  ua_ntohl(o->data, &peer);
+
+  if (!MPPE_MasterKeyValid)
+    /* Treat their NAK as a REJ */
+    return MODE_NAK;
+
+  mval = MPPE_ConfigVal(bundle, cfg);
+
+  /*
+   * If we haven't been configured with a specific number of keybits, allow
+   * whatever the peer asks for.
+   */
+  if (!cfg->mppe.keybits) {
+    mval &= ~MPPE_OPT_BITMASK;
+    mval |= (peer & MPPE_OPT_BITMASK);
+    if (!(mval & MPPE_OPT_BITMASK))
+      mval |= MPPE_OPT_128BIT;
+  }
+
+  /* Adjust our statelessness */
+  if (cfg->mppe.state == MPPE_ANYSTATE) {
+    mval &= ~MPPE_OPT_STATELESS;
+    mval |= (peer & MPPE_OPT_STATELESS);
+  }
+
+  ua_htonl(&mval, o->data);
+
+  return MODE_ACK;
+}
+
+/*
+ * The peer has requested the given options
+ */
+static int
+MPPESetOptsInput(struct bundle *bundle, struct fsm_opt *o,
+                 const struct ccp_config *cfg)
+{
+  u_int32_t mval, peer;
+  int res = MODE_ACK;
+
+  ua_ntohl(o->data, &peer);
+  if (!MPPE_MasterKeyValid) {
+    if (peer != 0) {
+      peer = 0;
+      ua_htonl(&peer, o->data);
+      return MODE_NAK;
+    } else
+      return MODE_ACK;
+  }
+
+  mval = MPPE_ConfigVal(bundle, cfg);
+
+  if (peer & ~MPPE_OPT_MASK)
+    /* He's asking for bits we don't know about */
+    res = MODE_NAK;
+
+  if (peer & MPPE_OPT_STATELESS) {
+    if (cfg->mppe.state == MPPE_STATEFUL)
+      /* Peer can't have stateless */
+      res = MODE_NAK;
+    else
+      /* Peer wants stateless, that's ok */
+      mval |= MPPE_OPT_STATELESS;
+  } else {
+    if (cfg->mppe.state == MPPE_STATELESS)
+      /* Peer must have stateless */
+      res = MODE_NAK;
+    else
+      /* Peer doesn't want stateless, that's ok */
+      mval &= ~MPPE_OPT_STATELESS;
+  }
+
+  /* If we've got a configured number of keybits - the peer must use that */
+  if (cfg->mppe.keybits) {
+    ua_htonl(&mval, o->data);
+    return peer == mval ? res : MODE_NAK;
+  }
+
+  /* If a specific number of bits hasn't been requested, we'll need to NAK */
+  switch (peer & MPPE_OPT_BITMASK) {
+  case MPPE_OPT_128BIT:
+  case MPPE_OPT_56BIT:
+  case MPPE_OPT_40BIT:
+    break;
+  default:
+    res = MODE_NAK;
+  }
+
+  /* Suggest the best number of bits */
+  mval &= ~MPPE_OPT_BITMASK;
+  if (peer & MPPE_OPT_128BIT)
+    mval |= MPPE_OPT_128BIT;
+  else if (peer & MPPE_OPT_56BIT)
+    mval |= MPPE_OPT_56BIT;
+  else if (peer & MPPE_OPT_40BIT)
+    mval |= MPPE_OPT_40BIT;
+  else
+    mval |= MPPE_OPT_128BIT;
+  ua_htonl(&mval, o->data);
+
+  return res;
+}
+
+static struct mppe_state *
+MPPE_InitState(struct fsm_opt *o)
+{
+  struct mppe_state *mp;
+  u_int32_t val;
+
+  if ((mp = calloc(1, sizeof *mp)) != NULL) {
+    ua_ntohl(o->data, &val);
+
+    switch (val & MPPE_OPT_BITMASK) {
+    case MPPE_OPT_128BIT:
+      mp->keylen = 16;
+      mp->keybits = 128;
+      break;
+    case MPPE_OPT_56BIT:
+      mp->keylen = 8;
+      mp->keybits = 56;
+      break;
+    case MPPE_OPT_40BIT:
+      mp->keylen = 8;
+      mp->keybits = 40;
+      break;
+    default:
+      log_Printf(LogWARN, "Unexpected MPPE options 0x%08x\n", val);
+      free(mp);
+      return NULL;
+    }
+
+    mp->stateless = !!(val & MPPE_OPT_STATELESS);
+  }
+
+  return mp;
+}
+
+static void *
+MPPEInitInput(struct bundle *bundle __unused, struct fsm_opt *o)
+{
+  struct mppe_state *mip;
+
+  if (!MPPE_MasterKeyValid) {
+    log_Printf(LogWARN, "MPPE: Cannot initialise without CHAP81\n");
+    return NULL;
+  }
+
+  if ((mip = MPPE_InitState(o)) == NULL) {
+    log_Printf(LogWARN, "MPPEInput: Cannot initialise - unexpected options\n");
+    return NULL;
+  }
+
+  log_Printf(LogDEBUG, "MPPE: InitInput: %d-bits\n", mip->keybits);
+
+#ifndef NORADIUS
+  if (*bundle->radius.cfg.file && bundle->radius.mppe.recvkey) {
+    if (mip->keylen > bundle->radius.mppe.recvkeylen)
+      mip->keylen = bundle->radius.mppe.recvkeylen;
+    if (mip->keylen > sizeof mip->mastkey)
+      mip->keylen = sizeof mip->mastkey;
+    memcpy(mip->mastkey, bundle->radius.mppe.recvkey, mip->keylen);
+  } else
+#endif
+    GetAsymetricStartKey(MPPE_MasterKey, mip->mastkey, mip->keylen, 0,
+                         MPPE_IsServer);
+
+  GetNewKeyFromSHA(mip->mastkey, mip->mastkey, mip->keylen, mip->sesskey);
+
+  MPPEReduceSessionKey(mip);
+
+  log_Printf(LogCCP, "MPPE: Input channel initiated\n");
+
+  if (!mip->stateless) {
+    /*
+     * We need to initialise our dictionary here as the first packet we
+     * receive is unlikely to have the FLUSHED bit set.
+     */
+    log_Printf(LogDEBUG, "MPPEInitInput: Dictionary initialised [%d]\n",
+               mip->cohnum);
+    RC4_set_key(&mip->rc4key, mip->keylen, mip->sesskey);
+  } else {
+    /*
+     * We do the first key change here as the first packet is expected
+     * to have a sequence number of 0 and we'll therefore not expect
+     * to have to change the key at that point.
+     */
+    log_Printf(LogDEBUG, "MPPEInitInput: Key changed [%d]\n", mip->cohnum);
+    MPPEKeyChange(mip);
+  }
+
+  return mip;
+}
+
+static void *
+MPPEInitOutput(struct bundle *bundle __unused, struct fsm_opt *o)
+{
+  struct mppe_state *mop;
+
+  if (!MPPE_MasterKeyValid) {
+    log_Printf(LogWARN, "MPPE: Cannot initialise without CHAP81\n");
+    return NULL;
+  }
+
+  if ((mop = MPPE_InitState(o)) == NULL) {
+    log_Printf(LogWARN, "MPPEOutput: Cannot initialise - unexpected options\n");
+    return NULL;
+  }
+
+  log_Printf(LogDEBUG, "MPPE: InitOutput: %d-bits\n", mop->keybits);
+
+#ifndef NORADIUS
+  if (*bundle->radius.cfg.file && bundle->radius.mppe.sendkey) {
+    if (mop->keylen > bundle->radius.mppe.sendkeylen)
+      mop->keylen = bundle->radius.mppe.sendkeylen;
+    if (mop->keylen > sizeof mop->mastkey)
+      mop->keylen = sizeof mop->mastkey;
+    memcpy(mop->mastkey, bundle->radius.mppe.sendkey, mop->keylen);
+  } else
+#endif
+    GetAsymetricStartKey(MPPE_MasterKey, mop->mastkey, mop->keylen, 1,
+                         MPPE_IsServer);
+
+  GetNewKeyFromSHA(mop->mastkey, mop->mastkey, mop->keylen, mop->sesskey);
+
+  MPPEReduceSessionKey(mop);
+
+  log_Printf(LogCCP, "MPPE: Output channel initiated\n");
+
+  if (!mop->stateless) {
+    /*
+     * We need to initialise our dictionary now as the first packet we
+     * send won't have the FLUSHED bit set.
+     */
+    log_Printf(LogDEBUG, "MPPEInitOutput: Dictionary initialised [%d]\n",
+               mop->cohnum);
+    RC4_set_key(&mop->rc4key, mop->keylen, mop->sesskey);
+  }
+
+  return mop;
+}
+
+static void
+MPPETermInput(void *v)
+{
+  free(v);
+}
+
+static void
+MPPETermOutput(void *v)
+{
+  free(v);
+}
+
+const struct ccp_algorithm MPPEAlgorithm = {
+  TY_MPPE,
+  CCP_NEG_MPPE,
+  MPPEDispOpts,
+  MPPEUsable,
+  MPPERequired,
+  {
+    MPPESetOptsInput,
+    MPPEInitInput,
+    MPPETermInput,
+    MPPEResetInput,
+    MPPEInput,
+    MPPEDictSetup
+  },
+  {
+    2,
+    MPPEInitOptsOutput,
+    MPPESetOptsOutput,
+    MPPEInitOutput,
+    MPPETermOutput,
+    MPPEResetOutput,
+    MPPEOutput
+  },
+};
diff --git a/src/mppe.h b/src/mppe.h
new file mode 100644
index 0000000..399d7b6
--- /dev/null
+++ b/src/mppe.h
@@ -0,0 +1,33 @@
+/*-
+ * Copyright (c) 2000 Semen Ustimenko <semenu@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/mppe.h,v 1.2.42.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define MPPE_KEY_LEN	16
+extern const struct ccp_algorithm MPPEAlgorithm;
+extern int MPPE_MasterKeyValid;
+extern int MPPE_IsServer;
+extern char MPPE_MasterKey[];
diff --git a/src/nat_cmd.c b/src/nat_cmd.c
new file mode 100644
index 0000000..d94a59a
--- /dev/null
+++ b/src/nat_cmd.c
@@ -0,0 +1,600 @@
+/*-
+ * Copyright (c) 2001 Charles Mott <cm@linktel.net>
+ *                    Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/nat_cmd.c,v 1.62.10.1.4.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#ifdef LOCALNAT
+#include "alias.h"
+#else
+#include <alias.h>
+#endif
+
+#include "layer.h"
+#include "proto.h"
+#include "defs.h"
+#include "command.h"
+#include "log.h"
+#include "nat_cmd.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "timer.h"
+#include "fsm.h"
+#include "slcompress.h"
+#include "throughput.h"
+#include "iplist.h"
+#include "mbuf.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#include "filter.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ncp.h"
+#include "bundle.h"
+
+
+#define NAT_EXTRABUF (13)
+
+static int StrToAddr(const char *, struct in_addr *);
+static int StrToPortRange(const char *, u_short *, u_short *, const char *);
+static int StrToAddrAndPort(const char *, struct in_addr *, u_short *,
+                            u_short *, const char *);
+
+static void
+lowhigh(u_short *a, u_short *b)
+{
+  if (a > b) {
+    u_short c;
+
+    c = *b;
+    *b = *a;
+    *a = c;
+  }
+}
+
+int
+nat_RedirectPort(struct cmdargs const *arg)
+{
+  if (!arg->bundle->NatEnabled) {
+    prompt_Printf(arg->prompt, "Alias not enabled\n");
+    return 1;
+  } else if (arg->argc == arg->argn + 3 || arg->argc == arg->argn + 4) {
+    char proto_constant;
+    const char *proto;
+    struct in_addr localaddr;
+    u_short hlocalport, llocalport;
+    struct in_addr aliasaddr;
+    u_short haliasport, laliasport;
+    struct in_addr remoteaddr;
+    u_short hremoteport, lremoteport;
+    struct alias_link *link;
+    int error;
+
+    proto = arg->argv[arg->argn];
+    if (strcmp(proto, "tcp") == 0) {
+      proto_constant = IPPROTO_TCP;
+    } else if (strcmp(proto, "udp") == 0) {
+      proto_constant = IPPROTO_UDP;
+    } else {
+      prompt_Printf(arg->prompt, "port redirect: protocol must be"
+                    " tcp or udp\n");
+      return -1;
+    }
+
+    error = StrToAddrAndPort(arg->argv[arg->argn+1], &localaddr, &llocalport,
+                             &hlocalport, proto);
+    if (error) {
+      prompt_Printf(arg->prompt, "nat port: error reading localaddr:port\n");
+      return -1;
+    }
+
+    error = StrToPortRange(arg->argv[arg->argn+2], &laliasport, &haliasport,
+                           proto);
+    if (error) {
+      prompt_Printf(arg->prompt, "nat port: error reading alias port\n");
+      return -1;
+    }
+    aliasaddr.s_addr = INADDR_ANY;
+
+    if (arg->argc == arg->argn + 4) {
+      error = StrToAddrAndPort(arg->argv[arg->argn+3], &remoteaddr,
+                               &lremoteport, &hremoteport, proto);
+      if (error) {
+        prompt_Printf(arg->prompt, "nat port: error reading "
+                      "remoteaddr:port\n");
+        return -1;
+      }
+    } else {
+      remoteaddr.s_addr = INADDR_ANY;
+      lremoteport = hremoteport = 0;
+    }
+
+    lowhigh(&llocalport, &hlocalport);
+    lowhigh(&laliasport, &haliasport);
+    lowhigh(&lremoteport, &hremoteport);
+
+    if (haliasport - laliasport != hlocalport - llocalport) {
+      prompt_Printf(arg->prompt, "nat port: local & alias port ranges "
+                    "are not equal\n");
+      return -1;
+    }
+
+    if (hremoteport && hremoteport - lremoteport != hlocalport - llocalport) {
+      prompt_Printf(arg->prompt, "nat port: local & remote port ranges "
+                    "are not equal\n");
+      return -1;
+    }
+
+    do {
+      link = PacketAliasRedirectPort(localaddr, htons(llocalport),
+				     remoteaddr, htons(lremoteport),
+                                     aliasaddr, htons(laliasport),
+				     proto_constant);
+
+      if (link == NULL) {
+        prompt_Printf(arg->prompt, "nat port: %d: error %d\n", laliasport,
+                      error);
+        return 1;
+      }
+      llocalport++;
+      if (hremoteport)
+        lremoteport++;
+    } while (laliasport++ < haliasport);
+
+    return 0;
+  }
+
+  return -1;
+}
+
+
+int
+nat_RedirectAddr(struct cmdargs const *arg)
+{
+  if (!arg->bundle->NatEnabled) {
+    prompt_Printf(arg->prompt, "nat not enabled\n");
+    return 1;
+  } else if (arg->argc == arg->argn+2) {
+    int error;
+    struct in_addr localaddr, aliasaddr;
+    struct alias_link *link;
+
+    error = StrToAddr(arg->argv[arg->argn], &localaddr);
+    if (error) {
+      prompt_Printf(arg->prompt, "address redirect: invalid local address\n");
+      return 1;
+    }
+    error = StrToAddr(arg->argv[arg->argn+1], &aliasaddr);
+    if (error) {
+      prompt_Printf(arg->prompt, "address redirect: invalid alias address\n");
+      prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
+                    arg->cmd->syntax);
+      return 1;
+    }
+    link = PacketAliasRedirectAddr(localaddr, aliasaddr);
+    if (link == NULL) {
+      prompt_Printf(arg->prompt, "address redirect: packet aliasing"
+                    " engine error\n");
+      prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
+                    arg->cmd->syntax);
+    }
+  } else
+    return -1;
+
+  return 0;
+}
+
+
+int
+nat_RedirectProto(struct cmdargs const *arg)
+{
+  if (!arg->bundle->NatEnabled) {
+    prompt_Printf(arg->prompt, "nat not enabled\n");
+    return 1;
+  } else if (arg->argc >= arg->argn + 2 && arg->argc <= arg->argn + 4) {
+    struct in_addr localIP, publicIP, remoteIP;
+    struct alias_link *link;
+    struct protoent *pe;
+    int error;
+    unsigned len;
+
+    len = strlen(arg->argv[arg->argn]);
+    if (len == 0) {
+      prompt_Printf(arg->prompt, "proto redirect: invalid protocol\n");
+      return 1;
+    }
+    if (strspn(arg->argv[arg->argn], "01234567") == len)
+      pe = getprotobynumber(atoi(arg->argv[arg->argn]));
+    else
+      pe = getprotobyname(arg->argv[arg->argn]);
+    if (pe == NULL) {
+      prompt_Printf(arg->prompt, "proto redirect: invalid protocol\n");
+      return 1;
+    }
+
+    error = StrToAddr(arg->argv[arg->argn + 1], &localIP);
+    if (error) {
+      prompt_Printf(arg->prompt, "proto redirect: invalid src address\n");
+      return 1;
+    }
+
+    if (arg->argc >= arg->argn + 3) {
+      error = StrToAddr(arg->argv[arg->argn + 2], &publicIP);
+      if (error) {
+        prompt_Printf(arg->prompt, "proto redirect: invalid alias address\n");
+        prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
+                      arg->cmd->syntax);
+        return 1;
+      }
+    } else
+      publicIP.s_addr = INADDR_ANY;
+
+    if (arg->argc == arg->argn + 4) {
+      error = StrToAddr(arg->argv[arg->argn + 2], &remoteIP);
+      if (error) {
+        prompt_Printf(arg->prompt, "proto redirect: invalid dst address\n");
+        prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
+                      arg->cmd->syntax);
+        return 1;
+      }
+    } else
+      remoteIP.s_addr = INADDR_ANY;
+
+    link = PacketAliasRedirectProto(localIP, remoteIP, publicIP, pe->p_proto);
+    if (link == NULL) {
+      prompt_Printf(arg->prompt, "proto redirect: packet aliasing"
+                    " engine error\n");
+      prompt_Printf(arg->prompt, "usage: nat %s %s\n", arg->cmd->name,
+                    arg->cmd->syntax);
+    }
+  } else
+    return -1;
+
+  return 0;
+}
+
+
+static int
+StrToAddr(const char *str, struct in_addr *addr)
+{
+  struct hostent *hp;
+
+  if (inet_aton(str, addr))
+    return 0;
+
+  hp = gethostbyname(str);
+  if (!hp) {
+    log_Printf(LogWARN, "StrToAddr: Unknown host %s.\n", str);
+    return -1;
+  }
+  *addr = *((struct in_addr *) hp->h_addr);
+  return 0;
+}
+
+
+static int
+StrToPort(const char *str, u_short *port, const char *proto)
+{
+  struct servent *sp;
+  char *end;
+
+  *port = strtol(str, &end, 10);
+  if (*end != '\0') {
+    sp = getservbyname(str, proto);
+    if (sp == NULL) {
+      log_Printf(LogWARN, "StrToAddr: Unknown port or service %s/%s.\n",
+	        str, proto);
+      return -1;
+    }
+    *port = ntohs(sp->s_port);
+  }
+
+  return 0;
+}
+
+static int
+StrToPortRange(const char *str, u_short *low, u_short *high, const char *proto)
+{
+  char *minus;
+  int res;
+
+  minus = strchr(str, '-');
+  if (minus)
+    *minus = '\0';		/* Cheat the const-ness ! */
+
+  res = StrToPort(str, low, proto);
+
+  if (minus)
+    *minus = '-';		/* Cheat the const-ness ! */
+
+  if (res == 0) {
+    if (minus)
+      res = StrToPort(minus + 1, high, proto);
+    else
+      *high = *low;
+  }
+
+  return res;
+}
+
+static int
+StrToAddrAndPort(const char *str, struct in_addr *addr, u_short *low,
+                 u_short *high, const char *proto)
+{
+  char *colon;
+  int res;
+
+  colon = strchr(str, ':');
+  if (!colon) {
+    log_Printf(LogWARN, "StrToAddrAndPort: %s is missing port number.\n", str);
+    return -1;
+  }
+
+  *colon = '\0';		/* Cheat the const-ness ! */
+  res = StrToAddr(str, addr);
+  *colon = ':';			/* Cheat the const-ness ! */
+  if (res != 0)
+    return -1;
+
+  return StrToPortRange(colon + 1, low, high, proto);
+}
+
+int
+nat_ProxyRule(struct cmdargs const *arg)
+{
+  char cmd[LINE_LEN];
+  int f, pos;
+  size_t len;
+
+  if (arg->argn >= arg->argc)
+    return -1;
+
+  for (f = arg->argn, pos = 0; f < arg->argc; f++) {
+    len = strlen(arg->argv[f]);
+    if (sizeof cmd - pos < len + (len ? 1 : 0))
+      break;
+    if (len)
+      cmd[pos++] = ' ';
+    strcpy(cmd + pos, arg->argv[f]);
+    pos += len;
+  }
+
+  return PacketAliasProxyRule(cmd);
+}
+
+int
+nat_SetTarget(struct cmdargs const *arg)
+{
+  struct in_addr addr;
+
+  if (arg->argc == arg->argn) {
+    addr.s_addr = INADDR_ANY;
+    PacketAliasSetTarget(addr);
+    return 0;
+  }
+
+  if (arg->argc != arg->argn + 1)
+    return -1;
+
+  if (!strcasecmp(arg->argv[arg->argn], "MYADDR")) {
+    addr.s_addr = INADDR_ANY;
+    PacketAliasSetTarget(addr);
+    return 0;
+  }
+
+  addr = GetIpAddr(arg->argv[arg->argn]);
+  if (addr.s_addr == INADDR_NONE) {
+    log_Printf(LogWARN, "%s: invalid address\n", arg->argv[arg->argn]);
+    return 1;
+  }
+
+  PacketAliasSetTarget(addr);
+  return 0;
+}
+
+#ifndef NO_FW_PUNCH
+int
+nat_PunchFW(struct cmdargs const *arg)
+{
+  char *end;
+  long base, count;
+
+  if (arg->argc == arg->argn) {
+    PacketAliasSetMode(0, PKT_ALIAS_PUNCH_FW);
+    return 0;
+  }
+
+  if (arg->argc != arg->argn + 2)
+    return -1;
+
+  base = strtol(arg->argv[arg->argn], &end, 10);
+  if (*end != '\0' || base < 0)
+    return -1;
+
+  count = strtol(arg->argv[arg->argn + 1], &end, 10);
+  if (*end != '\0' || count < 0)
+    return -1;
+
+  PacketAliasSetFWBase(base, count);
+  PacketAliasSetMode(PKT_ALIAS_PUNCH_FW, PKT_ALIAS_PUNCH_FW);
+
+  return 0;
+}
+#endif
+
+int
+nat_SkinnyPort(struct cmdargs const *arg)
+{
+  char *end;
+  long port;
+
+  if (arg->argc == arg->argn) {
+    PacketAliasSetSkinnyPort(0);
+    return 0;
+  }
+
+  if (arg->argc != arg->argn + 1)
+    return -1;
+
+  port = strtol(arg->argv[arg->argn], &end, 10);
+  if (*end != '\0' || port < 0)
+    return -1;
+
+  PacketAliasSetSkinnyPort(port);
+
+  return 0;
+}
+
+static struct mbuf *
+nat_LayerPush(struct bundle *bundle, struct link *l __unused, struct mbuf *bp,
+                int pri __unused, u_short *proto)
+{
+  if (!bundle->NatEnabled || *proto != PROTO_IP)
+    return bp;
+
+  log_Printf(LogDEBUG, "nat_LayerPush: PROTO_IP -> PROTO_IP\n");
+  m_settype(bp, MB_NATOUT);
+  /* Ensure there's a bit of extra buffer for the NAT code... */
+  bp = m_pullup(m_append(bp, NULL, NAT_EXTRABUF));
+  PacketAliasOut(MBUF_CTOP(bp), bp->m_len);
+  bp->m_len = ntohs(((struct ip *)MBUF_CTOP(bp))->ip_len);
+
+  return bp;
+}
+
+static struct mbuf *
+nat_LayerPull(struct bundle *bundle, struct link *l __unused, struct mbuf *bp,
+                u_short *proto)
+{
+  static int gfrags;
+  int ret, len, nfrags;
+  struct mbuf **last;
+  char *fptr;
+
+  if (!bundle->NatEnabled || *proto != PROTO_IP)
+    return bp;
+
+  log_Printf(LogDEBUG, "nat_LayerPull: PROTO_IP -> PROTO_IP\n");
+  m_settype(bp, MB_NATIN);
+  /* Ensure there's a bit of extra buffer for the NAT code... */
+  bp = m_pullup(m_append(bp, NULL, NAT_EXTRABUF));
+  ret = PacketAliasIn(MBUF_CTOP(bp), bp->m_len);
+
+  bp->m_len = ntohs(((struct ip *)MBUF_CTOP(bp))->ip_len);
+  if (bp->m_len > MAX_MRU) {
+    log_Printf(LogWARN, "nat_LayerPull: Problem with IP header length (%lu)\n",
+               (unsigned long)bp->m_len);
+    m_freem(bp);
+    return NULL;
+  }
+
+  switch (ret) {
+    case PKT_ALIAS_OK:
+      break;
+
+    case PKT_ALIAS_UNRESOLVED_FRAGMENT:
+      /* Save the data for later */
+      if ((fptr = malloc(bp->m_len)) == NULL) {
+	log_Printf(LogWARN, "nat_LayerPull: Dropped unresolved fragment -"
+		   " out of memory!\n");
+	m_freem(bp);
+	bp = NULL;
+      } else {
+	bp = mbuf_Read(bp, fptr, bp->m_len);
+	PacketAliasSaveFragment(fptr);
+	log_Printf(LogDEBUG, "Store another frag (%lu) - now %d\n",
+		   (unsigned long)((struct ip *)fptr)->ip_id, ++gfrags);
+      }
+      break;
+
+    case PKT_ALIAS_FOUND_HEADER_FRAGMENT:
+      /* Fetch all the saved fragments and chain them on the end of `bp' */
+      last = &bp->m_nextpkt;
+      nfrags = 0;
+      while ((fptr = PacketAliasGetFragment(MBUF_CTOP(bp))) != NULL) {
+        nfrags++;
+        PacketAliasFragmentIn(MBUF_CTOP(bp), fptr);
+        len = ntohs(((struct ip *)fptr)->ip_len);
+        *last = m_get(len, MB_NATIN);
+        memcpy(MBUF_CTOP(*last), fptr, len);
+        free(fptr);
+        last = &(*last)->m_nextpkt;
+      }
+      gfrags -= nfrags;
+      log_Printf(LogDEBUG, "Found a frag header (%lu) - plus %d more frags (no"
+                 "w %d)\n", (unsigned long)((struct ip *)MBUF_CTOP(bp))->ip_id,
+                 nfrags, gfrags);
+      break;
+
+    case PKT_ALIAS_IGNORED:
+      if (PacketAliasSetMode(0, 0) & PKT_ALIAS_DENY_INCOMING) {
+        log_Printf(LogTCPIP, "NAT engine denied data:\n");
+        m_freem(bp);
+        bp = NULL;
+      } else if (log_IsKept(LogTCPIP)) {
+        log_Printf(LogTCPIP, "NAT engine ignored data:\n");
+        PacketCheck(bundle, AF_INET, MBUF_CTOP(bp), bp->m_len, NULL,
+                    NULL, NULL);
+      }
+      break;
+
+    default:
+      log_Printf(LogWARN, "nat_LayerPull: Dropped a packet (%d)....\n", ret);
+      m_freem(bp);
+      bp = NULL;
+      break;
+  }
+
+  return bp;
+}
+
+struct layer natlayer =
+  { LAYER_NAT, "nat", nat_LayerPush, nat_LayerPull };
diff --git a/src/nat_cmd.h b/src/nat_cmd.h
new file mode 100644
index 0000000..a1c56d9
--- /dev/null
+++ b/src/nat_cmd.h
@@ -0,0 +1,42 @@
+/*-
+ * Copyright (c) 2001 Charles Mott <cm@linktel.net>
+ *                    Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/nat_cmd.h,v 1.20.36.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct cmdargs;
+
+extern int nat_RedirectPort(struct cmdargs const *);
+extern int nat_RedirectAddr(struct cmdargs const *);
+extern int nat_RedirectProto(struct cmdargs const *);
+extern int nat_ProxyRule(struct cmdargs const *);
+extern int nat_SetTarget(struct cmdargs const *);
+#ifndef NO_FW_PUNCH
+extern int nat_PunchFW(struct cmdargs const *);
+#endif
+extern int nat_SkinnyPort(struct cmdargs const *);
+
+extern struct layer natlayer;
diff --git a/src/ncp.c b/src/ncp.c
new file mode 100644
index 0000000..916e38f
--- /dev/null
+++ b/src/ncp.c
@@ -0,0 +1,547 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ncp.c,v 1.8.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <net/route.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <resolv.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "async.h"
+#include "ccp.h"
+#include "link.h"
+#include "physical.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "prompt.h"
+#include "route.h"
+#include "iface.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+
+
+static u_short default_urgent_tcp_ports[] = {
+  21,	/* ftp */
+  22,	/* ssh */
+  23,	/* telnet */
+  513,	/* login */
+  514,	/* shell */
+  543,	/* klogin */
+  544	/* kshell */
+};
+
+#define NDEFTCPPORTS \
+  (sizeof default_urgent_tcp_ports / sizeof default_urgent_tcp_ports[0])
+
+void
+ncp_Init(struct ncp *ncp, struct bundle *bundle)
+{
+  ncp->afq = AF_INET;
+  ncp->route = NULL;
+
+  ncp->cfg.urgent.tcp.port = (u_short *)malloc(NDEFTCPPORTS * sizeof(u_short));
+  if (ncp->cfg.urgent.tcp.port == NULL) {
+    log_Printf(LogERROR, "ncp_Init: Out of memory allocating urgent ports\n");
+    ncp->cfg.urgent.tcp.nports = ncp->cfg.urgent.tcp.maxports = 0;
+  } else {
+    ncp->cfg.urgent.tcp.nports = ncp->cfg.urgent.tcp.maxports = NDEFTCPPORTS;
+    memcpy(ncp->cfg.urgent.tcp.port, default_urgent_tcp_ports,
+	   NDEFTCPPORTS * sizeof(u_short));
+  }
+  ncp->cfg.urgent.tos = 1;
+
+  ncp->cfg.urgent.udp.nports = ncp->cfg.urgent.udp.maxports = 0;
+  ncp->cfg.urgent.udp.port = NULL;
+
+  mp_Init(&ncp->mp, bundle);
+
+  /* Send over the first physical link by default */
+  ipcp_Init(&ncp->ipcp, bundle, &bundle->links->physical->link,
+            &bundle->fsm);
+#ifndef NOINET6
+  ipv6cp_Init(&ncp->ipv6cp, bundle, &bundle->links->physical->link,
+              &bundle->fsm);
+#endif
+}
+
+void
+ncp_Destroy(struct ncp *ncp)
+{
+  ipcp_Destroy(&ncp->ipcp);
+#ifndef NOINET6
+  ipv6cp_Destroy(&ncp->ipv6cp);
+#endif
+
+  if (ncp->cfg.urgent.tcp.maxports) {
+    ncp->cfg.urgent.tcp.nports = ncp->cfg.urgent.tcp.maxports = 0;
+    free(ncp->cfg.urgent.tcp.port);
+    ncp->cfg.urgent.tcp.port = NULL;
+  }
+  if (ncp->cfg.urgent.udp.maxports) {
+    ncp->cfg.urgent.udp.nports = ncp->cfg.urgent.udp.maxports = 0;
+    free(ncp->cfg.urgent.udp.port);
+    ncp->cfg.urgent.udp.port = NULL;
+  }
+}
+
+int
+ncp_fsmStart(struct ncp *ncp,
+#ifdef NOINET6
+	     struct bundle *bundle __unused
+#else
+	     struct bundle *bundle
+#endif
+	     )
+{
+  int res = 0;
+
+#ifndef NOINET6
+  if (Enabled(bundle, OPT_IPCP)) {
+#endif
+    fsm_Up(&ncp->ipcp.fsm);
+    fsm_Open(&ncp->ipcp.fsm);
+    res++;
+#ifndef NOINET6
+  }
+
+  if (Enabled(bundle, OPT_IPV6CP)) {
+    fsm_Up(&ncp->ipv6cp.fsm);
+    fsm_Open(&ncp->ipv6cp.fsm);
+    res++;
+  }
+#endif
+
+  return res;
+}
+
+void
+ncp_IfaceAddrAdded(struct ncp *ncp, const struct iface_addr *addr)
+{
+  switch (ncprange_family(&addr->ifa)) {
+  case AF_INET:
+    ipcp_IfaceAddrAdded(&ncp->ipcp, addr);
+    break;
+#ifndef NOINET6
+  case AF_INET6:
+    ipv6cp_IfaceAddrAdded(&ncp->ipv6cp, addr);
+    break;
+#endif
+  }
+}
+
+void
+ncp_IfaceAddrDeleted(struct ncp *ncp, const struct iface_addr *addr)
+{
+  if (ncprange_family(&addr->ifa) == AF_INET)
+    ipcp_IfaceAddrDeleted(&ncp->ipcp, addr);
+}
+
+void
+ncp_SetLink(struct ncp *ncp, struct link *l)
+{
+  ipcp_SetLink(&ncp->ipcp, l);
+#ifndef NOINET6
+  ipv6cp_SetLink(&ncp->ipv6cp, l);
+#endif
+}
+
+/*
+ * Enqueue a packet of the given address family.  Nothing will make it
+ * down to the physical link level 'till ncp_FillPhysicalQueues() is used.
+ */
+void
+ncp_Enqueue(struct ncp *ncp, int af, unsigned pri, char *ptr, int count)
+{
+#ifndef NOINET6
+  struct ipv6cp *ipv6cp = &ncp->ipv6cp;
+#endif
+  struct ipcp *ipcp = &ncp->ipcp;
+  struct mbuf *bp;
+
+  /*
+   * We allocate an extra 6 bytes, four at the front and two at the end.
+   * This is an optimisation so that we need to do less work in
+   * m_prepend() in acf_LayerPush() and proto_LayerPush() and
+   * appending in hdlc_LayerPush().
+   */
+
+  switch (af) {
+  case AF_INET:
+    if (pri >= IPCP_QUEUES(ipcp)) {
+      log_Printf(LogERROR, "Can't store in ip queue %u\n", pri);
+      break;
+    }
+
+    bp = m_get(count + 6, MB_IPOUT);
+    bp->m_offset += 4;
+    bp->m_len -= 6;
+    memcpy(MBUF_CTOP(bp), ptr, count);
+    m_enqueue(ipcp->Queue + pri, bp);
+    break;
+
+#ifndef NOINET6
+  case AF_INET6:
+    if (pri >= IPV6CP_QUEUES(ipcp)) {
+      log_Printf(LogERROR, "Can't store in ipv6 queue %u\n", pri);
+      break;
+    }
+
+    bp = m_get(count + 6, MB_IPOUT);
+    bp->m_offset += 4;
+    bp->m_len -= 6;
+    memcpy(MBUF_CTOP(bp), ptr, count);
+    m_enqueue(ipv6cp->Queue + pri, bp);
+    break;
+#endif
+
+  default:
+      log_Printf(LogERROR, "Can't enqueue protocol family %d\n", af);
+  }
+}
+
+/*
+ * How many packets are queued to go out ?
+ */
+size_t
+ncp_QueueLen(struct ncp *ncp)
+{
+  size_t result;
+
+  result = ipcp_QueueLen(&ncp->ipcp);
+#ifndef NOINET6
+  result += ipv6cp_QueueLen(&ncp->ipv6cp);
+#endif
+  result += mp_QueueLen(&ncp->mp);	/* Usually empty */
+
+  return result;
+}
+
+/*
+ * Ditch all queued packets.  This is usually done after our choked timer
+ * has fired - which happens because we couldn't send any traffic over
+ * any links for some time.
+ */
+void
+ncp_DeleteQueues(struct ncp *ncp)
+{
+#ifndef NOINET6
+  struct ipv6cp *ipv6cp = &ncp->ipv6cp;
+#endif
+  struct ipcp *ipcp = &ncp->ipcp;
+  struct mp *mp = &ncp->mp;
+  struct mqueue *q;
+
+  for (q = ipcp->Queue; q < ipcp->Queue + IPCP_QUEUES(ipcp); q++)
+    while (q->top)
+      m_freem(m_dequeue(q));
+
+#ifndef NOINET6
+  for (q = ipv6cp->Queue; q < ipv6cp->Queue + IPV6CP_QUEUES(ipv6cp); q++)
+    while (q->top)
+      m_freem(m_dequeue(q));
+#endif
+
+  link_DeleteQueue(&mp->link);	/* Usually empty anyway */
+}
+
+/*
+ * Arrange that each of our links has at least one packet.  We keep the
+ * number of packets queued at the link level to a minimum so that the
+ * loss of a link in multi-link mode results in the minimum number of
+ * dropped packets.
+ */
+size_t
+ncp_FillPhysicalQueues(struct ncp *ncp, struct bundle *bundle)
+{
+  size_t total;
+
+  if (bundle->ncp.mp.active)
+    total = mp_FillPhysicalQueues(bundle);
+  else {
+    struct datalink *dl;
+    size_t add;
+
+    for (total = 0, dl = bundle->links; dl; dl = dl->next)
+      if (dl->state == DATALINK_OPEN) {
+        add = link_QueueLen(&dl->physical->link);
+        if (add == 0 && dl->physical->out == NULL)
+          add = ncp_PushPacket(ncp, &ncp->afq, &dl->physical->link);
+        total += add;
+      }
+  }
+
+  return total + ncp_QueueLen(&bundle->ncp);
+}
+
+/*
+ * Push a packet into the given link.  ``af'' is used as a persistent record
+ * of what is to be pushed next, coming either from mp->out or ncp->afq.
+ */
+int
+ncp_PushPacket(struct ncp *ncp __unused,
+#ifdef NOINET6
+	       int *af __unused,
+#else
+	       int *af,
+#endif
+	       struct link *l)
+{
+  struct bundle *bundle = l->lcp.fsm.bundle;
+  int res;
+
+#ifndef NOINET6
+  if (*af == AF_INET) {
+    if ((res = ipcp_PushPacket(&bundle->ncp.ipcp, l)))
+      *af = AF_INET6;
+    else
+      res = ipv6cp_PushPacket(&bundle->ncp.ipv6cp, l);
+  } else {
+    if ((res = ipv6cp_PushPacket(&bundle->ncp.ipv6cp, l)))
+      *af = AF_INET;
+    else
+      res = ipcp_PushPacket(&bundle->ncp.ipcp, l);
+  }
+#else
+  res = ipcp_PushPacket(&bundle->ncp.ipcp, l);
+#endif
+
+  return res;
+}
+
+int
+ncp_IsUrgentPort(struct port_range *range, u_short src, u_short dst)
+{
+  unsigned f;
+
+  for (f = 0; f < range->nports; f++)
+    if (range->port[f] == src || range->port[f] == dst)
+      return 1;
+
+  return 0;
+}
+
+void
+ncp_AddUrgentPort(struct port_range *range, u_short port)
+{
+  u_short *newport;
+  unsigned p;
+
+  if (range->nports == range->maxports) {
+    range->maxports += 10;
+    newport = (u_short *)realloc(range->port,
+                                 range->maxports * sizeof(u_short));
+    if (newport == NULL) {
+      log_Printf(LogERROR, "ncp_AddUrgentPort: realloc: %s\n",
+                 strerror(errno));
+      range->maxports -= 10;
+      return;
+    }
+    range->port = newport;
+  }
+
+  for (p = 0; p < range->nports; p++)
+    if (range->port[p] == port) {
+      log_Printf(LogWARN, "%u: Port already set to urgent\n", port);
+      break;
+    } else if (range->port[p] > port) {
+      memmove(range->port + p + 1, range->port + p,
+              (range->nports - p) * sizeof(u_short));
+      range->port[p] = port;
+      range->nports++;
+      break;
+    }
+
+  if (p == range->nports)
+    range->port[range->nports++] = port;
+}
+
+void
+ncp_RemoveUrgentPort(struct port_range *range, u_short port)
+{
+  unsigned p;
+
+  for (p = 0; p < range->nports; p++)
+    if (range->port[p] == port) {
+      if (p + 1 != range->nports)
+        memmove(range->port + p, range->port + p + 1,
+                (range->nports - p - 1) * sizeof(u_short));
+      range->nports--;
+      return;
+    }
+
+  if (p == range->nports)
+    log_Printf(LogWARN, "%u: Port not set to urgent\n", port);
+}
+
+void
+ncp_ClearUrgentPorts(struct port_range *range)
+{
+  range->nports = 0;
+}
+
+int
+ncp_Show(struct cmdargs const *arg)
+{
+  struct ncp *ncp = &arg->bundle->ncp;
+  unsigned p;
+
+#ifndef NOINET6
+  prompt_Printf(arg->prompt, "Next queued AF: %s\n",
+                ncp->afq == AF_INET6 ? "inet6" : "inet");
+#endif
+
+  if (ncp->route) {
+    prompt_Printf(arg->prompt, "\n");
+    route_ShowSticky(arg->prompt, ncp->route, "Sticky routes", 1);
+  }
+
+  prompt_Printf(arg->prompt, "\nDefaults:\n");
+  prompt_Printf(arg->prompt, "  sendpipe:      ");
+  if (ncp->cfg.sendpipe > 0)
+    prompt_Printf(arg->prompt, "%-20ld\n", ncp->cfg.sendpipe);
+  else
+    prompt_Printf(arg->prompt, "unspecified\n");
+  prompt_Printf(arg->prompt, "  recvpipe:      ");
+  if (ncp->cfg.recvpipe > 0)
+    prompt_Printf(arg->prompt, "%ld\n", ncp->cfg.recvpipe);
+  else
+    prompt_Printf(arg->prompt, "unspecified\n");
+
+  prompt_Printf(arg->prompt, "\n  Urgent ports\n");
+  prompt_Printf(arg->prompt, "         TCP:    ");
+  if (ncp->cfg.urgent.tcp.nports == 0)
+    prompt_Printf(arg->prompt, "none");
+  else
+    for (p = 0; p < ncp->cfg.urgent.tcp.nports; p++) {
+      if (p)
+        prompt_Printf(arg->prompt, ", ");
+      prompt_Printf(arg->prompt, "%u", ncp->cfg.urgent.tcp.port[p]);
+    }
+
+  prompt_Printf(arg->prompt, "\n         UDP:    ");
+  if (ncp->cfg.urgent.udp.nports == 0)
+    prompt_Printf(arg->prompt, "none");
+  else
+    for (p = 0; p < ncp->cfg.urgent.udp.nports; p++) {
+      if (p)
+        prompt_Printf(arg->prompt, ", ");
+      prompt_Printf(arg->prompt, "%u", ncp->cfg.urgent.udp.port[p]);
+    }
+  prompt_Printf(arg->prompt, "\n         TOS:    %s\n\n",
+                ncp->cfg.urgent.tos ? "yes" : "no");
+
+  return 0;
+}
+
+int
+ncp_LayersOpen(struct ncp *ncp)
+{
+  int n;
+
+  n = !!(ncp->ipcp.fsm.state == ST_OPENED);
+#ifndef NOINET6
+  n += !!(ncp->ipv6cp.fsm.state == ST_OPENED);
+#endif
+
+  return n;
+}
+
+int
+ncp_LayersUnfinished(struct ncp *ncp)
+{
+  int n = 0;
+
+  if (ncp->ipcp.fsm.state > ST_CLOSED ||
+      ncp->ipcp.fsm.state == ST_STARTING)
+    n++;
+
+#ifndef NOINET6
+  if (ncp->ipv6cp.fsm.state > ST_CLOSED ||
+      ncp->ipv6cp.fsm.state == ST_STARTING)
+    n++;
+#endif
+
+  return n;
+}
+
+void
+ncp_Close(struct ncp *ncp)
+{
+  if (ncp->ipcp.fsm.state > ST_CLOSED ||
+      ncp->ipcp.fsm.state == ST_STARTING)
+    fsm_Close(&ncp->ipcp.fsm);
+
+#ifndef NOINET6
+  if (ncp->ipv6cp.fsm.state > ST_CLOSED ||
+      ncp->ipv6cp.fsm.state == ST_STARTING)
+    fsm_Close(&ncp->ipv6cp.fsm);
+#endif
+}
+
+void
+ncp2initial(struct ncp *ncp)
+{
+  fsm2initial(&ncp->ipcp.fsm);
+#ifndef NOINET6
+  fsm2initial(&ncp->ipv6cp.fsm);
+#endif
+}
diff --git a/src/ncp.h b/src/ncp.h
new file mode 100644
index 0000000..7597590
--- /dev/null
+++ b/src/ncp.h
@@ -0,0 +1,101 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ncp.h,v 1.2.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct port_range {
+  unsigned nports;		/* How many ports */
+  unsigned maxports;		/* How many allocated (malloc) ports */
+  u_short *port;		/* The actual ports */
+};
+
+struct ncp {
+  struct {
+    u_long sendpipe;			/* route sendpipe size */
+    u_long recvpipe;			/* route recvpipe size */
+
+    struct {
+      struct port_range tcp, udp;	/* The range of urgent ports */
+      unsigned tos : 1;			/* Urgent IPTOS_LOWDELAY packets ? */
+    } urgent;
+  } cfg;
+
+  int afq;			/* Next address family to queue */
+
+  struct sticky_route *route;	/* List of dynamic routes */
+
+  struct ipcp ipcp;		/* Our IPCP FSM */
+#ifndef NOINET6
+  struct ipv6cp ipv6cp;		/* Our IPV6CP FSM */
+#endif
+  struct mp mp;			/* Our MP */
+};
+
+extern void ncp_Init(struct ncp *, struct bundle *);
+extern void ncp_Destroy(struct ncp *);
+extern int ncp_fsmStart(struct ncp *, struct bundle *);
+extern void ncp_IfaceAddrAdded(struct ncp *, const struct iface_addr *);
+extern void ncp_IfaceAddrDeleted(struct ncp *, const struct iface_addr *);
+extern void ncp_SetLink(struct ncp *, struct link *);
+extern void ncp_Enqueue(struct ncp *, int, unsigned, char *, int);
+extern void ncp_DeleteQueues(struct ncp *);
+extern size_t ncp_QueueLen(struct ncp *);
+extern size_t ncp_FillPhysicalQueues(struct ncp *, struct bundle *);
+extern int ncp_PushPacket(struct ncp *, int *, struct link *);
+extern int ncp_IsUrgentPort(struct port_range *, u_short, u_short);
+extern void ncp_AddUrgentPort(struct port_range *, u_short);
+extern void ncp_RemoveUrgentPort(struct port_range *, u_short);
+extern void ncp_ClearUrgentPorts(struct port_range *);
+extern int ncp_Show(struct cmdargs const *);
+extern int ncp_LayersOpen(struct ncp *);
+extern int ncp_LayersUnfinished(struct ncp *);
+extern void ncp_Close(struct ncp *);
+extern void ncp2initial(struct ncp *);
+
+#define ncp_IsUrgentTcpPort(ncp, p1, p2) \
+          ncp_IsUrgentPort(&(ncp)->cfg.urgent.tcp, p1, p2)
+#define ncp_IsUrgentUdpPort(ncp, p1, p2) \
+          ncp_IsUrgentPort(&(ncp)->cfg.urgent.udp, p1, p2)
+#define ncp_AddUrgentTcpPort(ncp, p) \
+          ncp_AddUrgentPort(&(ncp)->cfg.urgent.tcp, p)
+#define ncp_AddUrgentUdpPort(ncp, p) \
+          ncp_AddUrgentPort(&(ncp)->cfg.urgent.udp, p)
+#define ncp_RemoveUrgentTcpPort(ncp, p) \
+          ncp_RemoveUrgentPort(&(ncp)->cfg.urgent.tcp, p)
+#define ncp_RemoveUrgentUdpPort(ncp, p) \
+          ncp_RemoveUrgentPort(&(ncp)->cfg.urgent.udp, p)
+#define ncp_ClearUrgentTcpPorts(ncp) \
+          ncp_ClearUrgentPorts(&(ncp)->cfg.urgent.tcp)
+#define ncp_ClearUrgentUdpPorts(ncp) \
+          ncp_ClearUrgentPorts(&(ncp)->cfg.urgent.udp)
+#define ncp_ClearUrgentTOS(ncp) (ncp)->cfg.urgent.tos = 0;
+#define ncp_SetUrgentTOS(ncp) (ncp)->cfg.urgent.tos = 1;
+
+#ifndef NOINET6
+#define isncp(proto) ((proto) == PROTO_IPCP || (proto) == PROTO_IPV6CP)
+#else
+#define isncp(proto) ((proto) == PROTO_IPCP)
+#endif
diff --git a/src/ncpaddr.c b/src/ncpaddr.c
new file mode 100644
index 0000000..9bf9ba3
--- /dev/null
+++ b/src/ncpaddr.c
@@ -0,0 +1,1010 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ncpaddr.c,v 1.16.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#ifdef __OpenBSD__
+#include <net/if_types.h>
+#include <net/route.h>
+#endif
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#include "log.h"
+#include "ncpaddr.h"
+#include "timer.h"
+#include "fsm.h"
+#include "defs.h"
+#include "slcompress.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "mbuf.h"
+#include "ipcp.h"
+#include "descriptor.h"
+#include "layer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#include "ipv6cp.h"
+#include "ncp.h"
+
+
+#define ncprange_ip4addr	u.ip4.ipaddr
+#define ncprange_ip4mask	u.ip4.mask
+#define ncprange_ip4width	u.ip4.width
+#define ncpaddr_ip4addr		u.ip4addr
+#ifndef NOINET6
+#define ncprange_ip6addr	u.ip6.ipaddr
+#define ncprange_ip6width	u.ip6.width
+#define ncpaddr_ip6addr		u.ip6addr
+#endif
+
+#define	NCP_ASCIIBUFFERSIZE	52
+
+static struct in_addr
+bits2mask4(int bits)
+{
+  struct in_addr result;
+  u_int32_t bit = 0x80000000;
+
+  result.s_addr = 0;
+
+  while (bits) {
+    result.s_addr |= bit;
+    bit >>= 1;
+    bits--;
+  }
+
+  result.s_addr = htonl(result.s_addr);
+  return result;
+}
+
+static int
+mask42bits(struct in_addr mask)
+{
+  u_int32_t msk = ntohl(mask.s_addr);
+  u_int32_t tst;
+  int ret;
+
+  for (ret = 32, tst = 1; tst; ret--, tst <<= 1)
+    if (msk & tst)
+      break;
+
+  for (tst <<= 1; tst; tst <<= 1)
+    if (!(msk & tst))
+      break;
+
+  return tst ? -1 : ret;
+}
+
+#ifndef NOINET6
+static struct in6_addr
+bits2mask6(int bits)
+{
+  struct in6_addr result;
+  u_int32_t bit = 0x80;
+  u_char *c = result.s6_addr;
+
+  memset(&result, '\0', sizeof result);
+
+  while (bits) {
+    if (bit == 0) {
+      bit = 0x80;
+      c++;
+    }
+    *c |= bit;
+    bit >>= 1;
+    bits--;
+  }
+
+  return result;
+}
+
+static int
+mask62bits(const struct in6_addr *mask)
+{
+  const u_char masks[] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe };
+  const u_char *c, *p, *end;
+  int masklen, m;
+
+  p = (const u_char *)mask;
+  for (masklen = 0, end = p + 16; p < end && *p == 0xff; p++)
+    masklen += 8;
+
+  if (p < end) {
+    for (c = masks, m = 0; c < masks + sizeof masks; c++, m++)
+      if (*c == *p) {
+        masklen += m;
+        break;
+      }
+  }
+
+  return masklen;
+}
+
+#if 0
+static void
+adjust_linklocal(struct sockaddr_in6 *sin6)
+{
+    /* XXX: ?????!?!?!!!!!  This is horrible ! */
+    /*
+     * The kernel does not understand sin6_scope_id for routing at this moment.
+     * We should rather keep the embedded ID.
+     * jinmei@kame.net, 20011026
+     */
+    if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) ||
+        IN6_IS_ADDR_MC_LINKLOCAL(&sin6->sin6_addr)) {
+      sin6->sin6_scope_id =
+        ntohs(*(u_short *)&sin6->sin6_addr.s6_addr[2]);
+      *(u_short *)&sin6->sin6_addr.s6_addr[2] = 0;
+    }
+}
+#endif
+#endif
+
+void
+ncpaddr_init(struct ncpaddr *addr)
+{
+  addr->ncpaddr_family = AF_UNSPEC;
+}
+
+int
+ncpaddr_isset(const struct ncpaddr *addr)
+{
+  return addr->ncpaddr_family != AF_UNSPEC;
+}
+
+int
+ncpaddr_isdefault(const struct ncpaddr *addr)
+{
+  switch (addr->ncpaddr_family) {
+  case AF_INET:
+    if (addr->ncpaddr_ip4addr.s_addr == INADDR_ANY)
+      return 1;
+    break;
+
+#ifndef NOINET6
+  case AF_INET6:
+    if (IN6_IS_ADDR_UNSPECIFIED(&addr->ncpaddr_ip6addr))
+      return 1;
+    break;
+#endif
+  }
+
+  return 0;
+}
+
+int
+ncpaddr_equal(const struct ncpaddr *addr, const struct ncpaddr *cmp)
+{
+  if (addr->ncpaddr_family != cmp->ncpaddr_family)
+    return 0;
+
+  switch (addr->ncpaddr_family) {
+  case AF_INET:
+    return addr->ncpaddr_ip4addr.s_addr == cmp->ncpaddr_ip4addr.s_addr;
+
+#ifndef NOINET6
+  case AF_INET6:
+    return !memcmp(&addr->ncpaddr_ip6addr, &cmp->ncpaddr_ip6addr,
+                   sizeof addr->ncpaddr_ip6addr);
+#endif
+
+  case AF_UNSPEC:
+    return 1;
+  }
+
+  return 0;
+}
+
+void
+ncpaddr_copy(struct ncpaddr *addr, const struct ncpaddr *from)
+{
+  switch (from->ncpaddr_family) {
+  case AF_INET:
+    addr->ncpaddr_family = AF_INET;
+    addr->ncpaddr_ip4addr = from->ncpaddr_ip4addr;
+    break;
+#ifndef NOINET6
+  case AF_INET6:
+    addr->ncpaddr_family = AF_INET6;
+    addr->ncpaddr_ip6addr = from->ncpaddr_ip6addr;
+    break;
+#endif
+  default:
+    addr->ncpaddr_family = AF_UNSPEC;
+  }
+}
+
+void
+ncpaddr_setip4addr(struct ncpaddr *addr, u_int32_t ip)
+{
+  addr->ncpaddr_family = AF_INET;
+  addr->ncpaddr_ip4addr.s_addr = ip;
+}
+
+int
+ncpaddr_getip4addr(const struct ncpaddr *addr, u_int32_t *ip)
+{
+  if (addr->ncpaddr_family != AF_INET)
+    return 0;
+  *ip = addr->ncpaddr_ip4addr.s_addr;
+  return 1;
+}
+
+void
+ncpaddr_setip4(struct ncpaddr *addr, struct in_addr ip)
+{
+  addr->ncpaddr_family = AF_INET;
+  addr->ncpaddr_ip4addr = ip;
+}
+
+int
+ncpaddr_getip4(const struct ncpaddr *addr, struct in_addr *ip)
+{
+  if (addr->ncpaddr_family != AF_INET)
+    return 0;
+  *ip = addr->ncpaddr_ip4addr;
+  return 1;
+}
+
+#ifndef NOINET6
+void
+ncpaddr_setip6(struct ncpaddr *addr, const struct in6_addr *ip6)
+{
+  addr->ncpaddr_family = AF_INET6;
+  addr->ncpaddr_ip6addr = *ip6;
+}
+
+int
+ncpaddr_getip6(const struct ncpaddr *addr, struct in6_addr *ip6)
+{
+  if (addr->ncpaddr_family != AF_INET6)
+    return 0;
+  *ip6 = addr->ncpaddr_ip6addr;
+  return 1;
+}
+#endif
+
+void
+ncpaddr_getsa(const struct ncpaddr *addr, struct sockaddr_storage *host)
+{
+  struct sockaddr_in *host4 = (struct sockaddr_in *)host;
+#ifndef NOINET6
+  struct sockaddr_in6 *host6 = (struct sockaddr_in6 *)host;
+#endif
+
+  memset(host, '\0', sizeof(*host));
+
+  switch (addr->ncpaddr_family) {
+  case AF_INET:
+    host4->sin_family = AF_INET;
+    host4->sin_len = sizeof(*host4);
+    host4->sin_addr = addr->ncpaddr_ip4addr;
+    break;
+
+#ifndef NOINET6
+  case AF_INET6:
+    host6->sin6_family = AF_INET6;
+    host6->sin6_len = sizeof(*host6);
+    host6->sin6_addr = addr->ncpaddr_ip6addr;
+    break;
+#endif
+
+  default:
+    host->ss_family = AF_UNSPEC;
+    break;
+  }
+}
+
+void
+ncpaddr_setsa(struct ncpaddr *addr, const struct sockaddr *host)
+{
+  const struct sockaddr_in *host4 = (const struct sockaddr_in *)host;
+#ifndef NOINET6
+  const struct sockaddr_in6 *host6 = (const struct sockaddr_in6 *)host;
+#endif
+
+  switch (host->sa_family) {
+  case AF_INET:
+    addr->ncpaddr_family = AF_INET;
+    addr->ncpaddr_ip4addr = host4->sin_addr;
+    break;
+
+#ifndef NOINET6
+  case AF_INET6:
+    if (IN6_IS_ADDR_V4MAPPED(&host6->sin6_addr)) {
+      addr->ncpaddr_family = AF_INET;
+      addr->ncpaddr_ip4addr.s_addr =
+        *(const u_int32_t *)(host6->sin6_addr.s6_addr + 12);
+    } else {
+      addr->ncpaddr_family = AF_INET6;
+      addr->ncpaddr_ip6addr = host6->sin6_addr;
+    }
+    break;
+#endif
+
+  default:
+    addr->ncpaddr_family = AF_UNSPEC;
+  }
+}
+
+static char *
+ncpaddr_ntowa(const struct ncpaddr *addr)
+{
+  static char res[NCP_ASCIIBUFFERSIZE];
+#ifndef NOINET6
+  struct sockaddr_in6 sin6;
+#endif
+
+  switch (addr->ncpaddr_family) {
+  case AF_INET:
+    snprintf(res, sizeof res, "%s", inet_ntoa(addr->ncpaddr_ip4addr));
+    return res;
+
+#ifndef NOINET6
+  case AF_INET6:
+    memset(&sin6, '\0', sizeof(sin6));
+    sin6.sin6_len = sizeof(sin6);
+    sin6.sin6_family = AF_INET6;
+    sin6.sin6_addr = addr->ncpaddr_ip6addr;
+#if 0
+    adjust_linklocal(&sin6);
+#endif
+    if (getnameinfo((struct sockaddr *)&sin6, sizeof sin6, res, sizeof(res),
+                    NULL, 0, NI_NUMERICHOST) != 0)
+      break;
+
+    return res;
+#endif
+  }
+
+  snprintf(res, sizeof res, "<AF_UNSPEC>");
+  return res;
+}
+
+const char *
+ncpaddr_ntoa(const struct ncpaddr *addr)
+{
+  return ncpaddr_ntowa(addr);
+}
+
+
+int
+ncpaddr_aton(struct ncpaddr *addr, struct ncp *ncp, const char *data)
+{
+  struct ncprange range;
+
+  if (!ncprange_aton(&range, ncp, data))
+    return 0;
+
+  if (range.ncprange_family == AF_INET && range.ncprange_ip4width != 32 &&
+      range.ncprange_ip4addr.s_addr != INADDR_ANY) {
+    log_Printf(LogWARN, "ncpaddr_aton: %s: Only 32 bits allowed\n", data);
+    return 0;
+  }
+
+#ifndef NOINET6
+  if (range.ncprange_family == AF_INET6 && range.ncprange_ip6width != 128 &&
+      !IN6_IS_ADDR_UNSPECIFIED(&range.ncprange_ip6addr)) {
+    log_Printf(LogWARN, "ncpaddr_aton: %s: Only 128 bits allowed\n", data);
+    return 0;
+  }
+#endif
+
+  switch (range.ncprange_family) {
+  case AF_INET:
+    addr->ncpaddr_family = range.ncprange_family;
+    addr->ncpaddr_ip4addr = range.ncprange_ip4addr;
+    return 1;
+
+#ifndef NOINET6
+  case AF_INET6:
+    addr->ncpaddr_family = range.ncprange_family;
+    addr->ncpaddr_ip6addr = range.ncprange_ip6addr;
+    return 1;
+#endif
+  }
+
+  return 0;
+}
+
+void
+ncprange_init(struct ncprange *range)
+{
+  range->ncprange_family = AF_UNSPEC;
+}
+
+int
+ncprange_isset(const struct ncprange *range)
+{
+  return range->ncprange_family != AF_UNSPEC;
+}
+
+int
+ncprange_equal(const struct ncprange *range, const struct ncprange *cmp)
+{
+  if (range->ncprange_family != cmp->ncprange_family)
+    return 0;
+
+  switch (range->ncprange_family) {
+  case AF_INET:
+    if (range->ncprange_ip4addr.s_addr != cmp->ncprange_ip4addr.s_addr)
+      return 0;
+    return range->ncprange_ip4mask.s_addr == cmp->ncprange_ip4mask.s_addr;
+
+#ifndef NOINET6
+  case AF_INET6:
+    if (range->ncprange_ip6width != cmp->ncprange_ip6width)
+      return 0;
+    return !memcmp(&range->ncprange_ip6addr, &cmp->ncprange_ip6addr,
+                   sizeof range->ncprange_ip6addr);
+#endif
+
+  case AF_UNSPEC:
+    return 1;
+  }
+
+  return 0;
+}
+
+int
+ncprange_isdefault(const struct ncprange *range)
+{
+  switch (range->ncprange_family) {
+  case AF_INET:
+    if (range->ncprange_ip4addr.s_addr == INADDR_ANY)
+      return 1;
+    break;
+
+#ifndef NOINET6
+  case AF_INET6:
+    if (range->ncprange_ip6width == 0 &&
+        IN6_IS_ADDR_UNSPECIFIED(&range->ncprange_ip6addr))
+      return 1;
+    break;
+#endif
+  }
+
+  return 0;
+}
+
+void
+ncprange_setdefault(struct ncprange *range, int af)
+{
+  memset(range, '\0', sizeof *range);
+  range->ncprange_family = af;
+}
+
+int
+ncprange_contains(const struct ncprange *range, const struct ncpaddr *addr)
+{
+#ifndef NOINET6
+  const u_char masks[] = { 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
+  const u_char *addrp, *rangep;
+  int bits;
+#endif
+
+  if (range->ncprange_family != addr->ncpaddr_family)
+    return 0;
+
+  switch (range->ncprange_family) {
+  case AF_INET:
+    return !((addr->ncpaddr_ip4addr.s_addr ^ range->ncprange_ip4addr.s_addr) &
+             range->ncprange_ip4mask.s_addr);
+
+#ifndef NOINET6
+  case AF_INET6:
+    rangep = (const u_char *)range->ncprange_ip6addr.s6_addr;
+    addrp = (const u_char *)addr->ncpaddr_ip6addr.s6_addr;
+
+    for (bits = range->ncprange_ip6width; bits > 0; bits -= 8)
+      if ((*addrp++ ^ *rangep++) & masks[bits > 7 ? 7 : bits - 1])
+        return 0;
+
+    return 1;
+#endif
+  }
+
+  return 0;
+}
+
+int
+ncprange_containsip4(const struct ncprange *range, struct in_addr addr)
+{
+  switch (range->ncprange_family) {
+  case AF_INET:
+    return !((addr.s_addr ^ range->ncprange_ip4addr.s_addr) &
+             range->ncprange_ip4mask.s_addr);
+  }
+
+  return 0;
+}
+
+void
+ncprange_copy(struct ncprange *range, const struct ncprange *from)
+{
+  switch (from->ncprange_family) {
+  case AF_INET:
+    range->ncprange_family = AF_INET;
+    range->ncprange_ip4addr = from->ncprange_ip4addr;
+    range->ncprange_ip4mask = from->ncprange_ip4mask;
+    range->ncprange_ip4width = from->ncprange_ip4width;
+    break;
+
+#ifndef NOINET6
+  case AF_INET6:
+    range->ncprange_family = AF_INET6;
+    range->ncprange_ip6addr = from->ncprange_ip6addr;
+    range->ncprange_ip6width = from->ncprange_ip6width;
+    break;
+#endif
+
+  default:
+    range->ncprange_family = AF_UNSPEC;
+  }
+}
+
+void
+ncprange_set(struct ncprange *range, const struct ncpaddr *addr, int width)
+{
+  ncprange_sethost(range, addr);
+  ncprange_setwidth(range, width);
+}
+
+void
+ncprange_sethost(struct ncprange *range, const struct ncpaddr *from)
+{
+  switch (from->ncpaddr_family) {
+  case AF_INET:
+    range->ncprange_family = AF_INET;
+    range->ncprange_ip4addr = from->ncpaddr_ip4addr;
+    if (from->ncpaddr_ip4addr.s_addr == INADDR_ANY) {
+      range->ncprange_ip4mask.s_addr = INADDR_ANY;
+      range->ncprange_ip4width = 0;
+    } else {
+      range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+      range->ncprange_ip4width = 32;
+    }
+    break;
+
+#ifndef NOINET6
+  case AF_INET6:
+    range->ncprange_family = AF_INET6;
+    range->ncprange_ip6addr = from->ncpaddr_ip6addr;
+    range->ncprange_ip6width = 128;
+    break;
+#endif
+
+  default:
+    range->ncprange_family = AF_UNSPEC;
+  }
+}
+
+int
+ncprange_ishost(const struct ncprange *range)
+{
+  switch (range->ncprange_family) {
+  case AF_INET:
+    return range->ncprange_ip4width == 32;
+#ifndef NOINET6
+  case AF_INET6:
+    return range->ncprange_ip6width == 128;
+#endif
+  }
+
+  return (0);
+}
+
+int
+ncprange_setwidth(struct ncprange *range, int width)
+{
+  switch (range->ncprange_family) {
+  case AF_INET:
+    if (width < 0 || width > 32)
+      break;
+    range->ncprange_ip4width = width;
+    range->ncprange_ip4mask = bits2mask4(width);
+    break;
+
+#ifndef NOINET6
+  case AF_INET6:
+    if (width < 0 || width > 128)
+      break;
+    range->ncprange_ip6width = width;
+    break;
+#endif
+
+  case AF_UNSPEC:
+    return 1;
+  }
+
+  return 0;
+}
+
+void
+ncprange_setip4host(struct ncprange *range, struct in_addr from)
+{
+  range->ncprange_family = AF_INET;
+  range->ncprange_ip4addr = from;
+  if (from.s_addr == INADDR_ANY) {
+    range->ncprange_ip4mask.s_addr = INADDR_ANY;
+    range->ncprange_ip4width = 0;
+  } else {
+    range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+    range->ncprange_ip4width = 32;
+  }
+}
+
+void
+ncprange_setip4(struct ncprange *range, struct in_addr from, struct in_addr msk)
+{
+  range->ncprange_family = AF_INET;
+  range->ncprange_ip4addr = from;
+  range->ncprange_ip4mask = msk;
+  range->ncprange_ip4width = mask42bits(msk);
+}
+
+
+int
+ncprange_setip4mask(struct ncprange *range, struct in_addr mask)
+{
+  if (range->ncprange_family != AF_INET)
+    return 0;
+  range->ncprange_ip4mask = mask;
+  range->ncprange_ip4width = mask42bits(mask);
+  return 1;
+}
+
+void
+ncprange_setsa(struct ncprange *range, const struct sockaddr *host,
+               const struct sockaddr *mask)
+{
+  const struct sockaddr_in *host4 = (const struct sockaddr_in *)host;
+  const struct sockaddr_in *mask4 = (const struct sockaddr_in *)mask;
+#ifndef NOINET6
+  const struct sockaddr_in6 *host6 = (const struct sockaddr_in6 *)host;
+  const struct sockaddr_in6 *mask6 = (const struct sockaddr_in6 *)mask;
+#endif
+
+  switch (host->sa_family) {
+  case AF_INET:
+    range->ncprange_family = AF_INET;
+    range->ncprange_ip4addr = host4->sin_addr;
+    if (host4->sin_addr.s_addr == INADDR_ANY) {
+      range->ncprange_ip4mask.s_addr = INADDR_ANY;
+      range->ncprange_ip4width = 0;
+    } else if (mask4 && mask4->sin_family == AF_INET) {
+      range->ncprange_ip4mask.s_addr = mask4->sin_addr.s_addr;
+      range->ncprange_ip4width = mask42bits(mask4->sin_addr);
+    } else {
+      range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+      range->ncprange_ip4width = 32;
+    }
+    break;
+
+#ifndef NOINET6
+  case AF_INET6:
+    range->ncprange_family = AF_INET6;
+    range->ncprange_ip6addr = host6->sin6_addr;
+    if (IN6_IS_ADDR_UNSPECIFIED(&host6->sin6_addr))
+      range->ncprange_ip6width = 0;
+    else
+      range->ncprange_ip6width = mask6 ? mask62bits(&mask6->sin6_addr) : 128;
+    break;
+#endif
+
+  default:
+    range->ncprange_family = AF_UNSPEC;
+  }
+}
+
+void
+ncprange_getsa(const struct ncprange *range, struct sockaddr_storage *host,
+               struct sockaddr_storage *mask)
+{
+  struct sockaddr_in *host4 = (struct sockaddr_in *)host;
+  struct sockaddr_in *mask4 = (struct sockaddr_in *)mask;
+#ifndef NOINET6
+  struct sockaddr_in6 *host6 = (struct sockaddr_in6 *)host;
+  struct sockaddr_in6 *mask6 = (struct sockaddr_in6 *)mask;
+#endif
+
+  memset(host, '\0', sizeof(*host));
+  if (mask)
+    memset(mask, '\0', sizeof(*mask));
+
+  switch (range->ncprange_family) {
+  case AF_INET:
+    host4->sin_family = AF_INET;
+    host4->sin_len = sizeof(*host4);
+    host4->sin_addr = range->ncprange_ip4addr;
+    if (mask4) {
+      mask4->sin_family = AF_INET;
+      mask4->sin_len = sizeof(*host4);
+      mask4->sin_addr = range->ncprange_ip4mask;
+    }
+    break;
+
+#ifndef NOINET6
+  case AF_INET6:
+    host6->sin6_family = AF_INET6;
+    host6->sin6_len = sizeof(*host6);
+    host6->sin6_addr = range->ncprange_ip6addr;
+    if (mask6) {
+      mask6->sin6_family = AF_INET6;
+      mask6->sin6_len = sizeof(*host6);
+      mask6->sin6_addr = bits2mask6(range->ncprange_ip6width);
+    }
+    break;
+#endif
+
+  default:
+    host->ss_family = AF_UNSPEC;
+    if (mask)
+      mask->ss_family = AF_UNSPEC;
+    break;
+  }
+}
+
+int
+ncprange_getaddr(const struct ncprange *range, struct ncpaddr *addr)
+{
+  switch (range->ncprange_family) {
+  case AF_INET:
+    addr->ncpaddr_family = AF_INET;
+    addr->ncpaddr_ip4addr = range->ncprange_ip4addr;
+    return 1;
+#ifndef NOINET6
+  case AF_INET6:
+    addr->ncpaddr_family = AF_INET6;
+    addr->ncpaddr_ip6addr =  range->ncprange_ip6addr;
+    return 1;
+#endif
+  }
+
+  return 0;
+}
+
+int
+ncprange_getip4addr(const struct ncprange *range, struct in_addr *addr)
+{
+  if (range->ncprange_family != AF_INET)
+    return 0;
+
+  *addr = range->ncprange_ip4addr;
+  return 1;
+}
+
+int
+ncprange_getip4mask(const struct ncprange *range, struct in_addr *mask)
+{
+  switch (range->ncprange_family) {
+  case AF_INET:
+    *mask = range->ncprange_ip4mask;
+    return 1;
+  }
+
+  return 0;
+}
+
+int
+ncprange_getwidth(const struct ncprange *range, int *width)
+{
+  switch (range->ncprange_family) {
+  case AF_INET:
+    *width = range->ncprange_ip4width;
+    return 1;
+#ifndef NOINET6
+  case AF_INET6:
+    *width = range->ncprange_ip6width;
+    return 1;
+#endif
+  }
+
+  return 0;
+}
+
+const char *
+ncprange_ntoa(const struct ncprange *range)
+{
+  char *res;
+  struct ncpaddr addr;
+  int len;
+
+  if (!ncprange_getaddr(range, &addr))
+    return "<AF_UNSPEC>";
+
+  res = ncpaddr_ntowa(&addr);
+  len = strlen(res);
+  if (len >= NCP_ASCIIBUFFERSIZE - 1)
+    return res;
+
+  switch (range->ncprange_family) {
+  case AF_INET:
+    if (range->ncprange_ip4width == -1) {
+      /* A non-contiguous mask */
+      for (; len >= 3; res[len -= 2] = '\0')
+        if (strcmp(res + len - 2, ".0"))
+          break;
+      snprintf(res + len, sizeof res - len, "&0x%08lx",
+               (unsigned long)ntohl(range->ncprange_ip4mask.s_addr));
+    } else if (range->ncprange_ip4width < 32)
+      snprintf(res + len, sizeof res - len, "/%d", range->ncprange_ip4width);
+
+    return res;
+
+#ifndef NOINET6
+  case AF_INET6:
+    if (range->ncprange_ip6width != 128)
+      snprintf(res + len, sizeof res - len, "/%d", range->ncprange_ip6width);
+
+    return res;
+#endif
+  }
+
+  return "<AF_UNSPEC>";
+}
+
+#ifndef NOINET6
+int
+ncprange_scopeid(const struct ncprange *range)
+{
+  const struct in6_addr *sin6;
+  int scopeid = -1;
+
+  if (range->ncprange_family == AF_INET6) {
+    sin6 = &range->ncprange_ip6addr;
+    if (IN6_IS_ADDR_LINKLOCAL(sin6) || IN6_IS_ADDR_MC_LINKLOCAL(sin6))
+      if ((scopeid = ntohs(*(const u_short *)&sin6->s6_addr[2])) == 0)
+        scopeid = -1;
+  }
+
+  return scopeid;
+}
+#endif
+
+int
+ncprange_aton(struct ncprange *range, struct ncp *ncp, const char *data)
+{
+  int bits, len;
+  char *wp;
+  const char *cp;
+  char *s;
+
+  len = strcspn(data, "/");
+
+  if (ncp && strncasecmp(data, "HISADDR", len) == 0) {
+    range->ncprange_family = AF_INET;
+    range->ncprange_ip4addr = ncp->ipcp.peer_ip;
+    range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+    range->ncprange_ip4width = 32;
+    return 1;
+#ifndef NOINET6
+  } else if (ncp && strncasecmp(data, "HISADDR6", len) == 0) {
+    range->ncprange_family = AF_INET6;
+    range->ncprange_ip6addr = ncp->ipv6cp.hisaddr.ncpaddr_ip6addr;
+    range->ncprange_ip6width = 128;
+    return 1;
+#endif
+  } else if (ncp && strncasecmp(data, "MYADDR", len) == 0) {
+    range->ncprange_family = AF_INET;
+    range->ncprange_ip4addr = ncp->ipcp.my_ip;
+    range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+    range->ncprange_ip4width = 32;
+    return 1;
+#ifndef NOINET6
+  } else if (ncp && strncasecmp(data, "MYADDR6", len) == 0) {
+    range->ncprange_family = AF_INET6;
+    range->ncprange_ip6addr = ncp->ipv6cp.myaddr.ncpaddr_ip6addr;
+    range->ncprange_ip6width = 128;
+    return 1;
+#endif
+  } else if (ncp && strncasecmp(data, "DNS0", len) == 0) {
+    range->ncprange_family = AF_INET;
+    range->ncprange_ip4addr = ncp->ipcp.ns.dns[0];
+    range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+    range->ncprange_ip4width = 32;
+    return 1;
+  } else if (ncp && strncasecmp(data, "DNS1", len) == 0) {
+    range->ncprange_family = AF_INET;
+    range->ncprange_ip4addr = ncp->ipcp.ns.dns[1];
+    range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+    range->ncprange_ip4width = 32;
+    return 1;
+  }
+
+  s = (char *)alloca(len + 1);
+  strncpy(s, data, len);
+  s[len] = '\0';
+  bits = -1;
+
+  if (data[len] != '\0') {
+    bits = strtol(data + len + 1, &wp, 0);
+    if (*wp || wp == data + len + 1 || bits < 0 || bits > 128) {
+      log_Printf(LogWARN, "ncprange_aton: bad mask width.\n");
+      return 0;
+    }
+  }
+
+  if ((cp = strchr(data, ':')) == NULL) {
+    range->ncprange_family = AF_INET;
+
+    range->ncprange_ip4addr = GetIpAddr(s);
+
+    if (range->ncprange_ip4addr.s_addr == INADDR_NONE) {
+      log_Printf(LogWARN, "ncprange_aton: %s: Bad address\n", s);
+      return 0;
+    }
+
+    if (range->ncprange_ip4addr.s_addr == INADDR_ANY) {
+      range->ncprange_ip4mask.s_addr = INADDR_ANY;
+      range->ncprange_ip4width = 0;
+    } else if (bits == -1) {
+      range->ncprange_ip4mask.s_addr = INADDR_BROADCAST;
+      range->ncprange_ip4width = 32;
+    } else if (bits > 32) {
+      log_Printf(LogWARN, "ncprange_aton: bad mask width.\n");
+      return 0;
+    } else {
+      range->ncprange_ip4mask = bits2mask4(bits);
+      range->ncprange_ip4width = bits;
+    }
+
+    return 1;
+#ifndef NOINET6
+  } else if (strchr(cp + 1, ':') != NULL) {
+    range->ncprange_family = AF_INET6;
+
+    if (inet_pton(AF_INET6, s, &range->ncprange_ip6addr) != 1) {
+      log_Printf(LogWARN, "ncprange_aton: %s: Bad address\n", s);
+      return 0;
+    }
+
+    if (IN6_IS_ADDR_UNSPECIFIED(&range->ncprange_ip6addr))
+      range->ncprange_ip6width = 0;
+    else
+      range->ncprange_ip6width = (bits == -1) ? 128 : bits;
+    return 1;
+#endif
+  }
+
+  return 0;
+}
diff --git a/src/ncpaddr.h b/src/ncpaddr.h
new file mode 100644
index 0000000..e8e3886
--- /dev/null
+++ b/src/ncpaddr.h
@@ -0,0 +1,109 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ncpaddr.h,v 1.3.42.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+/*
+ * These structures should be treated as opaque.
+ */
+struct ncprange {
+  sa_family_t ncprange_family;
+  union {
+    struct {
+      struct in_addr ipaddr;
+      struct in_addr mask;
+      int width;
+    } ip4;
+#ifndef NOINET6
+    struct {
+      struct in6_addr ipaddr;
+      int width;
+    } ip6;
+#endif
+  } u;
+};
+
+struct ncpaddr {
+  sa_family_t ncpaddr_family;
+  union {
+    struct in_addr ip4addr;
+#ifndef NOINET6
+    struct in6_addr ip6addr;
+#endif
+  } u;
+};
+
+struct ncp;
+
+extern void ncpaddr_init(struct ncpaddr *);
+extern int ncpaddr_isset(const struct ncpaddr *);
+extern int ncpaddr_isdefault(const struct ncpaddr *);
+extern int ncpaddr_equal(const struct ncpaddr *, const struct ncpaddr *);
+extern void ncpaddr_copy(struct ncpaddr *, const struct ncpaddr *);
+extern void ncpaddr_setip4addr(struct ncpaddr *, u_int32_t);
+extern int ncpaddr_getip4addr(const struct ncpaddr *, u_int32_t *);
+extern void ncpaddr_setip4(struct ncpaddr *, struct in_addr);
+extern int ncpaddr_getip4(const struct ncpaddr *, struct in_addr *);
+#ifndef NOINET6
+extern void ncpaddr_setip6(struct ncpaddr *, const struct in6_addr *);
+extern int ncpaddr_getip6(const struct ncpaddr *, struct in6_addr *);
+#endif
+extern void ncpaddr_getsa(const struct ncpaddr *, struct sockaddr_storage *);
+extern void ncpaddr_setsa(struct ncpaddr *, const struct sockaddr *);
+extern const char *ncpaddr_ntoa(const struct ncpaddr *);
+extern int ncpaddr_aton(struct ncpaddr *, struct ncp *, const char *);
+
+extern void ncprange_init(struct ncprange *);
+extern int ncprange_isset(const struct ncprange *);
+extern int ncprange_equal(const struct ncprange *, const struct ncprange *);
+extern int ncprange_isdefault(const struct ncprange *);
+extern void ncprange_setdefault(struct ncprange *, int);
+extern int ncprange_contains(const struct ncprange *, const struct ncpaddr *);
+extern int ncprange_containsip4(const struct ncprange *, struct in_addr);
+extern void ncprange_copy(struct ncprange *, const struct ncprange *);
+extern void ncprange_set(struct ncprange *, const struct ncpaddr *, int);
+extern void ncprange_sethost(struct ncprange *, const struct ncpaddr *);
+extern int ncprange_ishost(const struct ncprange *);
+extern int ncprange_setwidth(struct ncprange *, int);
+extern void ncprange_setip4(struct ncprange *, struct in_addr, struct in_addr);
+extern void ncprange_setip4host(struct ncprange *, struct in_addr);
+extern int ncprange_setip4mask(struct ncprange *, struct in_addr);
+extern void ncprange_setsa(struct ncprange *, const struct sockaddr *,
+                           const struct sockaddr *);
+extern void ncprange_getsa(const struct ncprange *, struct sockaddr_storage *,
+                           struct sockaddr_storage *);
+extern int ncprange_getaddr(const struct ncprange *, struct ncpaddr *);
+extern int ncprange_getip4addr(const struct ncprange *, struct in_addr *);
+extern int ncprange_getip4mask(const struct ncprange *, struct in_addr *);
+extern int ncprange_getwidth(const struct ncprange *, int *);
+extern const char *ncprange_ntoa(const struct ncprange *);
+#ifndef NOINET6
+extern int ncprange_scopeid(const struct ncprange *);
+#endif
+extern int ncprange_aton(struct ncprange *, struct ncp *, const char *);
+
+#define ncpaddr_family(a) ((a)->ncpaddr_family)
+#define ncprange_family(r) ((r)->ncprange_family)
diff --git a/src/netgraph.c b/src/netgraph.c
new file mode 100644
index 0000000..ed9ae91
--- /dev/null
+++ b/src/netgraph.c
@@ -0,0 +1,743 @@
+/*-
+ * Copyright (c) 2000 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/netgraph.c,v 1.6.10.1.4.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netgraph.h>
+#include <net/ethernet.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netgraph/ng_ether.h>
+#include <netgraph/ng_message.h>
+#include <netgraph/ng_socket.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/fcntl.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "main.h"
+#include "mp.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "slcompress.h"
+#include "iplist.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "filter.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "bundle.h"
+#include "id.h"
+#include "netgraph.h"
+
+
+struct ngdevice {
+  struct device dev;			/* What struct physical knows about */
+  int cs;				/* Control socket */
+  char hook[NG_HOOKSIZ];		/* Our socket node hook */
+};
+
+#define device2ng(d)	((d)->type == NG_DEVICE ? (struct ngdevice *)d : NULL)
+#define NG_MSGBUFSZ	4096
+#define NETGRAPH_PREFIX	"netgraph:"
+
+unsigned
+ng_DeviceSize(void)
+{
+  return sizeof(struct ngdevice);
+}
+
+static int
+ng_MessageOut(struct ngdevice *dev, const char *data)
+{
+  char path[NG_PATHSIZ];
+  char *fmt;
+  size_t len;
+  int pos, dpos;
+
+  /*
+   * We expect a node path, one or more spaces, a command, one or more
+   * spaces and an ascii netgraph structure.
+   */
+  data += strspn(data, " \t");
+  len = strcspn(data, " \t");
+  if (len >= sizeof path) {
+    log_Printf(LogWARN, "%s: %.*s: Node path too long\n",
+                 dev->dev.name, len, data);
+    return 0;
+  }
+  memcpy(path, data, len);
+  path[len] = '\0';
+  data += len;
+
+  data += strspn(data, " \t");
+  len = strcspn(data, " \t");
+  for (pos = len; pos >= 0; pos--)
+    if (data[pos] == '%')
+      len++;
+  if ((fmt = alloca(len + 4)) == NULL) {
+    log_Printf(LogWARN, "%s: alloca(%d) failure... %s\n",
+               dev->dev.name, len + 4, strerror(errno));
+    return 0;
+  }
+
+  /*
+   * This is probably a waste of time, but we really don't want to end
+   * up stuffing unexpected % escapes into the kernel....
+   */
+  for (pos = dpos = 0; pos < (int)len;) {
+    if (data[dpos] == '%')
+      fmt[pos++] = '%';
+    fmt[pos++] = data[dpos++];
+  }
+  strcpy(fmt + pos, " %s");
+  data += dpos;
+
+  data += strspn(data, " \t");
+  if (NgSendAsciiMsg(dev->cs, path, fmt, data) < 0) {
+    log_Printf(LogDEBUG, "%s: NgSendAsciiMsg (to %s): \"%s\", \"%s\": %s\n",
+               dev->dev.name, path, fmt, data, strerror(errno));
+    return 0;
+  }
+
+  return 1;
+}
+
+/*
+ * Get a netgraph message
+ */
+static ssize_t
+ng_MessageIn(struct physical *p, char *buf, size_t sz)
+{
+  char msgbuf[sizeof(struct ng_mesg) * 2 + NG_MSGBUFSZ];
+  struct ngdevice *dev = device2ng(p->handler);
+  struct ng_mesg *rep = (struct ng_mesg *)msgbuf;
+  char path[NG_PATHSIZ];
+  size_t len;
+
+#ifdef BROKEN_SELECT
+  struct timeval t;
+  fd_set *r;
+  int ret;
+
+  if (dev->cs < 0)
+    return 0;
+
+  if ((r = mkfdset()) == NULL) {
+    log_Printf(LogERROR, "DoLoop: Cannot create fd_set\n");
+    return -1;
+  }
+  zerofdset(r);
+  FD_SET(dev->cs, r);
+  t.tv_sec = t.tv_usec = 0;
+  ret = select(dev->cs + 1, r, NULL, NULL, &t);
+  free(r);
+
+  if (ret <= 0)
+    return 0;
+#endif
+
+  if (NgRecvAsciiMsg(dev->cs, rep, sizeof msgbuf, path)) {
+    log_Printf(LogWARN, "%s: NgRecvAsciiMsg: %s\n",
+               dev->dev.name, strerror(errno));
+    return -1;
+  }
+
+  /* XXX: Should we check rep->header.version ? */
+
+  if (sz == 0)
+    log_Printf(LogWARN, "%s: Unexpected message: %s\n", dev->dev.name,
+               rep->header.cmdstr);
+  else {
+    log_Printf(LogDEBUG, "%s: Received message: %s\n", dev->dev.name,
+               rep->header.cmdstr);
+    len = strlen(rep->header.cmdstr);
+    if (sz > len)
+      sz = len;
+    memcpy(buf, rep->header.cmdstr, sz);
+  }
+
+  return sz;
+}
+
+static ssize_t
+ng_Write(struct physical *p, const void *v, size_t n)
+{
+  struct ngdevice *dev = device2ng(p->handler);
+
+  switch (p->dl->state) {
+    case DATALINK_DIAL:
+    case DATALINK_LOGIN:
+      return ng_MessageOut(dev, v) ? (ssize_t)n : -1;
+  }
+  return NgSendData(p->fd, dev->hook, v, n) == -1 ? -1 : (ssize_t)n;
+}
+
+static ssize_t
+ng_Read(struct physical *p, void *v, size_t n)
+{
+  char hook[NG_HOOKSIZ];
+
+  switch (p->dl->state) {
+    case DATALINK_DIAL:
+    case DATALINK_LOGIN:
+      return ng_MessageIn(p, v, n);
+  }
+
+  return NgRecvData(p->fd, v, n, hook);
+}
+
+static int
+ng_RemoveFromSet(struct physical *p, fd_set *r, fd_set *w, fd_set *e)
+{
+  struct ngdevice *dev = device2ng(p->handler);
+  int result;
+
+  if (r && dev->cs >= 0 && FD_ISSET(dev->cs, r)) {
+    FD_CLR(dev->cs, r);
+    log_Printf(LogTIMER, "%s: fdunset(ctrl) %d\n", p->link.name, dev->cs);
+    result = 1;
+  } else
+    result = 0;
+
+  /* Careful... physical_RemoveFromSet() called us ! */
+
+  p->handler->removefromset = NULL;
+  result += physical_RemoveFromSet(p, r, w, e);
+  p->handler->removefromset = ng_RemoveFromSet;
+
+  return result;
+}
+
+static void
+ng_Free(struct physical *p)
+{
+  struct ngdevice *dev = device2ng(p->handler);
+
+  physical_SetDescriptor(p);
+  if (dev->cs != -1)
+    close(dev->cs);
+  free(dev);
+}
+
+static void
+ng_device2iov(struct device *d, struct iovec *iov, int *niov,
+              int maxiov __unused, int *auxfd, int *nauxfd)
+{
+  struct ngdevice *dev;
+  int sz = physical_MaxDeviceSize();
+
+  iov[*niov].iov_base = d = realloc(d, sz);
+  if (d == NULL) {
+    log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
+    AbortProgram(EX_OSERR);
+  }
+  iov[*niov].iov_len = sz;
+  (*niov)++;
+
+  dev = device2ng(d);
+  *auxfd = dev->cs;
+  (*nauxfd)++;
+}
+
+static const struct device basengdevice = {
+  NG_DEVICE,
+  "netgraph",
+  0,
+  { CD_REQUIRED, DEF_NGCDDELAY },
+  NULL,
+  ng_RemoveFromSet,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  ng_Free,
+  ng_Read,
+  ng_Write,
+  ng_device2iov,
+  NULL,
+  NULL,
+  NULL
+};
+
+struct device *
+ng_iov2device(int type, struct physical *p, struct iovec *iov, int *niov,
+              int maxiov __unused, int *auxfd, int *nauxfd)
+{
+  if (type == NG_DEVICE) {
+    struct ngdevice *dev = (struct ngdevice *)iov[(*niov)++].iov_base;
+
+    dev = realloc(dev, sizeof *dev);	/* Reduce to the correct size */
+    if (dev == NULL) {
+      log_Printf(LogALERT, "Failed to allocate memory: %d\n",
+                 (int)(sizeof *dev));
+      AbortProgram(EX_OSERR);
+    }
+
+    if (*nauxfd) {
+      dev->cs = *auxfd;
+      (*nauxfd)--;
+    } else
+      dev->cs = -1;
+
+    /* Refresh function pointers etc */
+    memcpy(&dev->dev, &basengdevice, sizeof dev->dev);
+
+    /* XXX: Are netgraph always synchronous ? */
+    physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF);
+    return &dev->dev;
+  }
+
+  return NULL;
+}
+
+static int
+ng_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n)
+{
+  struct physical *p = descriptor2physical(d);
+  struct ngdevice *dev = device2ng(p->handler);
+  int result;
+
+  switch (p->dl->state) {
+    case DATALINK_DIAL:
+    case DATALINK_LOGIN:
+      if (r) {
+        FD_SET(dev->cs, r);
+        log_Printf(LogTIMER, "%s(ctrl): fdset(r) %d\n", p->link.name, dev->cs);
+        result = 1;
+      } else
+        result = 0;
+      break;
+
+    default:
+      result = physical_doUpdateSet(d, r, w, e, n, 0);
+      break;
+  }
+
+  return result;
+}
+
+static int
+ng_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+  struct physical *p = descriptor2physical(d);
+  struct ngdevice *dev = device2ng(p->handler);
+  int result;
+
+  result = dev->cs >= 0 && FD_ISSET(dev->cs, fdset);
+  result += physical_IsSet(d, fdset);
+
+  return result;
+}
+
+static void
+ng_DescriptorRead(struct fdescriptor *d, struct bundle *bundle,
+                  const fd_set *fdset)
+{
+  struct physical *p = descriptor2physical(d);
+  struct ngdevice *dev = device2ng(p->handler);
+
+  if (dev->cs >= 0 && FD_ISSET(dev->cs, fdset))
+    ng_MessageIn(p, NULL, 0);
+
+  if (physical_IsSet(d, fdset))
+    physical_DescriptorRead(d, bundle, fdset);
+}
+
+static struct device *
+ng_Abandon(struct ngdevice *dev, struct physical *p)
+{
+  /* Abandon our node construction */
+  close(dev->cs);
+  close(p->fd);
+  p->fd = -2;	/* Nobody else need try.. */
+  free(dev);
+
+  return NULL;
+}
+
+
+/*
+ * Populate the ``word'' (of size ``sz'') named ``what'' from ``from''
+ * ending with any character from ``sep''.  Point ``endp'' at the next
+ * word.
+ */
+
+#define GETSEGMENT(what, from, sep, endp) \
+	getsegment(#what, (what), sizeof(what), from, sep, endp)
+
+static int
+getsegment(const char *what, char *word, size_t sz, const char *from,
+           const char *sep, const char **endp)
+{
+  size_t len;
+
+  if ((len = strcspn(from, sep)) == 0) {
+    log_Printf(LogWARN, "%s name should not be empty !\n", what);
+    return 0;
+  }
+
+  if (len >= sz) {
+    log_Printf(LogWARN, "%s name too long, max %d !\n", what, sz - 1);
+    return 0;
+  }
+
+  strncpy(word, from, len);
+  word[len] = '\0';
+
+  *endp = from + len;
+  *endp += strspn(*endp, sep);
+
+  return 1;
+}
+
+struct device *
+ng_Create(struct physical *p)
+{
+  struct sockaddr_ng ngsock;
+  u_char rbuf[2048];
+  struct sockaddr *sock = (struct sockaddr *)&ngsock;
+  const struct hooklist *hlist;
+  const struct nodeinfo *ninfo;
+  const struct linkinfo *nlink;
+  struct ngdevice *dev;
+  struct ng_mesg *resp;
+  struct ngm_mkpeer mkp;
+  struct ngm_connect ngc;
+  const char *devp, *endp;
+  char lasthook[NG_HOOKSIZ];
+  char hook[NG_HOOKSIZ];
+  char nodetype[NG_TYPESIZ + NG_NODESIZ];
+  char modname[NG_TYPESIZ + 3];
+  char path[NG_PATHSIZ];
+  char *nodename;
+  int len, sz, done;
+  unsigned f;
+
+  dev = NULL;
+  if (p->fd < 0 && !strncasecmp(p->name.full, NETGRAPH_PREFIX,
+                                sizeof NETGRAPH_PREFIX - 1)) {
+    p->fd--;				/* We own the device - change fd */
+
+    if ((dev = malloc(sizeof *dev)) == NULL)
+      return NULL;
+
+    loadmodules(LOAD_VERBOSLY, "netgraph", "ng_socket", NULL);
+
+    /* Create a socket node */
+    if (ID0NgMkSockNode(NULL, &dev->cs, &p->fd) == -1) {
+      log_Printf(LogWARN, "Cannot create netgraph socket node: %s\n",
+                 strerror(errno));
+      free(dev);
+      p->fd = -2;
+      return NULL;
+    }
+
+    devp = p->name.full + sizeof NETGRAPH_PREFIX - 1;
+    *lasthook = *path = '\0';
+    log_Printf(LogDEBUG, "%s: Opening netgraph device \"%s\"\n",
+               p->link.name, devp);
+    done = 0;
+
+    while (*devp != '\0' && !done) {
+      if (*devp != '[') {
+        if (*lasthook == '\0') {
+          log_Printf(LogWARN, "%s: Netgraph devices must start with"
+                     " [nodetype:nodename]\n", p->link.name);
+          return ng_Abandon(dev, p);
+        }
+
+        /* Get the hook name of the new node */
+        if (!GETSEGMENT(hook, devp, ".[", &endp))
+          return ng_Abandon(dev, p);
+        log_Printf(LogDEBUG, "%s: Got hook \"%s\"\n", p->link.name, hook);
+        devp = endp;
+        if (*devp == '\0') {
+          log_Printf(LogWARN, "%s: Netgraph device must not end with a second"
+                     " hook\n", p->link.name);
+          return ng_Abandon(dev, p);
+        }
+        if (devp[-1] != '[') {
+          log_Printf(LogWARN, "%s: Expected a [nodetype:nodename] at device"
+                     " pos %d\n", p->link.name, devp - p->link.name - 1);
+          return ng_Abandon(dev, p);
+        }
+      } else {
+        /* Use lasthook as the hook name */
+        strcpy(hook, lasthook);
+        devp++;
+      }
+
+      /* We've got ``lasthook'' and ``hook'', get the node type */
+      if (!GETSEGMENT(nodetype, devp, "]", &endp))
+        return ng_Abandon(dev, p);
+      log_Printf(LogDEBUG, "%s: Got node \"%s\"\n", p->link.name, nodetype);
+
+      if ((nodename = strchr(nodetype, ':')) != NULL) {
+        *nodename++ = '\0';
+        if (*nodename == '\0' && *nodetype == '\0') {
+          log_Printf(LogWARN, "%s: Empty [nodetype:nodename] at device"
+                     " pos %d\n", p->link.name, devp - p->link.name - 1);
+          return ng_Abandon(dev, p);
+        }
+      }
+
+      /* Ignore optional colons after nodes */
+      devp = *endp == ':' ? endp + 1 : endp;
+      if (*devp == '.')
+        devp++;
+
+      if (*lasthook == '\0') {
+        /* This is the first node in the chain */
+        if (nodename == NULL || *nodename == '\0') {
+          log_Printf(LogWARN, "%s: %s: No initial device nodename\n",
+                     p->link.name, devp);
+          return ng_Abandon(dev, p);
+        }
+
+        if (*nodetype != '\0') {
+          /* Attempt to load the module */
+          snprintf(modname, sizeof modname, "ng_%s", nodetype);
+          log_Printf(LogDEBUG, "%s: Attempting to load %s.ko\n",
+                     p->link.name, modname);
+          loadmodules(LOAD_QUIETLY, modname, NULL);
+        }
+
+        snprintf(path, sizeof path, "%s:", nodename);
+        /* XXX: If we have a node type, ensure it's correct */
+      } else {
+        /*
+         * Ask for a list of hooks attached to the previous node.  If we
+         * find the one we're interested in, and if it's connected to a
+         * node of the right type using the correct hook, use that.
+         * If we find the hook connected to something else, fail.
+         * If we find no match, mkpeer the new node.
+         */
+        if (*nodetype == '\0') {
+          log_Printf(LogWARN, "%s: Nodetype missing at device offset %d\n",
+                     p->link.name,
+                     devp - p->name.full + sizeof NETGRAPH_PREFIX - 1);
+          return ng_Abandon(dev, p);
+        }
+
+        /* Get a list of node hooks */
+        if (NgSendMsg(dev->cs, path, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
+                      NULL, 0) < 0) {
+          log_Printf(LogWARN, "%s: %s Cannot send a LISTHOOOKS message: %s\n",
+                     p->link.name, path, strerror(errno));
+          return ng_Abandon(dev, p);
+        }
+
+        /* Get our list back */
+        resp = (struct ng_mesg *)rbuf;
+        if (NgRecvMsg(dev->cs, resp, sizeof rbuf, NULL) <= 0) {
+          log_Printf(LogWARN, "%s: Cannot get netgraph response: %s\n",
+                     p->link.name, strerror(errno));
+          return ng_Abandon(dev, p);
+        }
+
+        hlist = (const struct hooklist *)resp->data;
+        ninfo = &hlist->nodeinfo;
+
+        log_Printf(LogDEBUG, "List of netgraph node ``%s'' (id %x) hooks:\n",
+                   path, ninfo->id);
+
+        /* look for a hook already attached.  */
+        for (f = 0; f < ninfo->hooks; f++) {
+          nlink = &hlist->link[f];
+
+          log_Printf(LogDEBUG, "  Found %s -> %s (type %s)\n", nlink->ourhook,
+                     nlink->peerhook, nlink->nodeinfo.type);
+
+          if (!strcmp(nlink->ourhook, lasthook)) {
+            if (strcmp(nlink->peerhook, hook) ||
+                strcmp(nlink->nodeinfo.type, nodetype)) {
+              log_Printf(LogWARN, "%s: hook %s:%s is already in use\n",
+                         p->link.name, nlink->ourhook, path);
+              return ng_Abandon(dev, p);
+            }
+            /* The node is already hooked up nicely.... reuse it */
+            break;
+          }
+        }
+
+        if (f == ninfo->hooks) {
+          /* Attempt to load the module */
+          snprintf(modname, sizeof modname, "ng_%s", nodetype);
+          log_Printf(LogDEBUG, "%s: Attempting to load %s.ko\n",
+                     p->link.name, modname);
+          loadmodules(LOAD_QUIETLY, modname, NULL);
+
+          /* Create (mkpeer) the new node */
+
+          snprintf(mkp.type, sizeof mkp.type, "%s", nodetype);
+          snprintf(mkp.ourhook, sizeof mkp.ourhook, "%s", lasthook);
+          snprintf(mkp.peerhook, sizeof mkp.peerhook, "%s", hook);
+
+          log_Printf(LogDEBUG, "%s: Doing MKPEER %s%s -> %s (type %s)\n",
+                     p->link.name, path, mkp.ourhook, mkp.peerhook, nodetype);
+
+          if (NgSendMsg(dev->cs, path, NGM_GENERIC_COOKIE,
+                        NGM_MKPEER, &mkp, sizeof mkp) < 0) {
+            log_Printf(LogWARN, "%s Cannot create %s netgraph node: %s\n",
+                       path, nodetype, strerror(errno));
+            return ng_Abandon(dev, p);
+          }
+        }
+        len = strlen(path);
+        snprintf(path + len, sizeof path - len, "%s%s",
+                 path[len - 1] == ':' ? "" : ".", lasthook);
+      }
+
+      /* Get a list of node hooks */
+      if (NgSendMsg(dev->cs, path, NGM_GENERIC_COOKIE, NGM_LISTHOOKS,
+                    NULL, 0) < 0) {
+        log_Printf(LogWARN, "%s: %s Cannot send a LISTHOOOKS message: %s\n",
+                   p->link.name, path, strerror(errno));
+        return ng_Abandon(dev, p);
+      }
+
+      /* Get our list back */
+      resp = (struct ng_mesg *)rbuf;
+      if (NgRecvMsg(dev->cs, resp, sizeof rbuf, NULL) <= 0) {
+        log_Printf(LogWARN, "%s: Cannot get netgraph response: %s\n",
+                   p->link.name, strerror(errno));
+        return ng_Abandon(dev, p);
+      }
+
+      hlist = (const struct hooklist *)resp->data;
+      ninfo = &hlist->nodeinfo;
+
+      if (*lasthook != '\0' && nodename != NULL && *nodename != '\0' &&
+          strcmp(ninfo->name, nodename) &&
+          NgNameNode(dev->cs, path, "%s", nodename) < 0) {
+        log_Printf(LogWARN, "%s: %s: Cannot name netgraph node: %s\n",
+                   p->link.name, path, strerror(errno));
+        return ng_Abandon(dev, p);
+      }
+
+      if (!GETSEGMENT(lasthook, devp, " \t.[", &endp))
+        return ng_Abandon(dev, p);
+      log_Printf(LogDEBUG, "%s: Got hook \"%s\"\n", p->link.name, lasthook);
+
+      len = strlen(lasthook);
+      done = strchr(" \t", devp[len]) ? 1 : 0;
+      devp = endp;
+
+      if (*devp != '\0') {
+        if (devp[-1] == '[')
+          devp--;
+      } /* else should moan about devp[-1] being '[' ? */
+    }
+
+    snprintf(dev->hook, sizeof dev->hook, "%s", lasthook);
+
+    /* Connect the node to our socket node */
+    snprintf(ngc.path, sizeof ngc.path, "%s", path);
+    snprintf(ngc.ourhook, sizeof ngc.ourhook, "%s", dev->hook);
+    memcpy(ngc.peerhook, ngc.ourhook, sizeof ngc.peerhook);
+
+    log_Printf(LogDEBUG, "Connecting netgraph socket .:%s -> %s.%s\n",
+               ngc.ourhook, ngc.path, ngc.peerhook);
+    if (NgSendMsg(dev->cs, ".:", NGM_GENERIC_COOKIE,
+                  NGM_CONNECT, &ngc, sizeof ngc) < 0) {
+      log_Printf(LogWARN, "Cannot connect %s and socket netgraph "
+                 "nodes: %s\n", path, strerror(errno));
+      return ng_Abandon(dev, p);
+    }
+
+    /* Hook things up so that we monitor dev->cs */
+    p->desc.UpdateSet = ng_UpdateSet;
+    p->desc.IsSet = ng_IsSet;
+    p->desc.Read = ng_DescriptorRead;
+
+    memcpy(&dev->dev, &basengdevice, sizeof dev->dev);
+
+  } else {
+    /* See if we're a netgraph socket */
+
+    sz = sizeof ngsock;
+    if (getsockname(p->fd, sock, &sz) != -1 && sock->sa_family == AF_NETGRAPH) {
+      /*
+       * It's a netgraph node... We can't determine hook names etc, so we
+       * stay pretty impartial....
+       */
+      log_Printf(LogPHASE, "%s: Link is a netgraph node\n", p->link.name);
+
+      if ((dev = malloc(sizeof *dev)) == NULL) {
+        log_Printf(LogWARN, "%s: Cannot allocate an ether device: %s\n",
+                   p->link.name, strerror(errno));
+        return NULL;
+      }
+
+      memcpy(&dev->dev, &basengdevice, sizeof dev->dev);
+      dev->cs = -1;
+      *dev->hook = '\0';
+    }
+  }
+
+  if (dev) {
+    physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF);
+    return &dev->dev;
+  }
+
+  return NULL;
+}
diff --git a/src/netgraph.h b/src/netgraph.h
new file mode 100644
index 0000000..6941422
--- /dev/null
+++ b/src/netgraph.h
@@ -0,0 +1,37 @@
+/*-
+ * Copyright (c) 2000 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/netgraph.h,v 1.3.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct physical;
+struct device;
+
+#define DEF_NGCDDELAY	5	/* Default ``set cd'' value */
+
+extern struct device *ng_Create(struct physical *);
+extern struct device *ng_iov2device(int, struct physical *, struct iovec *,
+                                    int *, int, int *, int *);
+extern unsigned ng_DeviceSize(void);
diff --git a/src/pap.c b/src/pap.c
new file mode 100644
index 0000000..fa73953
--- /dev/null
+++ b/src/pap.c
@@ -0,0 +1,303 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/pap.c,v 1.51.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdlib.h>
+#include <string.h>		/* strlen/memcpy */
+#include <termios.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "defs.h"
+#include "timer.h"
+#include "fsm.h"
+#include "auth.h"
+#include "pap.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "proto.h"
+#include "async.h"
+#include "throughput.h"
+#include "ccp.h"
+#include "link.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "chat.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+
+static const char * const papcodes[] = {
+  "???", "REQUEST", "SUCCESS", "FAILURE"
+};
+#define MAXPAPCODE (sizeof papcodes / sizeof papcodes[0] - 1)
+
+static void
+pap_Req(struct authinfo *authp)
+{
+  struct bundle *bundle = authp->physical->dl->bundle;
+  struct fsmheader lh;
+  struct mbuf *bp;
+  u_char *cp;
+  int namelen, keylen, plen;
+
+  namelen = strlen(bundle->cfg.auth.name);
+  keylen = strlen(bundle->cfg.auth.key);
+  plen = namelen + keylen + 2;
+  log_Printf(LogDEBUG, "pap_Req: namelen = %d, keylen = %d\n", namelen, keylen);
+  log_Printf(LogPHASE, "Pap Output: %s ********\n", bundle->cfg.auth.name);
+  if (*bundle->cfg.auth.name == '\0')
+    log_Printf(LogWARN, "Sending empty PAP authname!\n");
+  lh.code = PAP_REQUEST;
+  lh.id = authp->id;
+  lh.length = htons(plen + sizeof(struct fsmheader));
+  bp = m_get(plen + sizeof(struct fsmheader), MB_PAPOUT);
+  memcpy(MBUF_CTOP(bp), &lh, sizeof(struct fsmheader));
+  cp = MBUF_CTOP(bp) + sizeof(struct fsmheader);
+  *cp++ = namelen;
+  memcpy(cp, bundle->cfg.auth.name, namelen);
+  cp += namelen;
+  *cp++ = keylen;
+  memcpy(cp, bundle->cfg.auth.key, keylen);
+  link_PushPacket(&authp->physical->link, bp, bundle,
+                  LINK_QUEUES(&authp->physical->link) - 1, PROTO_PAP);
+}
+
+static void
+SendPapCode(struct authinfo *authp, int code, const char *message)
+{
+  struct fsmheader lh;
+  struct mbuf *bp;
+  u_char *cp;
+  int plen, mlen;
+
+  lh.code = code;
+  lh.id = authp->id;
+  mlen = strlen(message);
+  plen = mlen + 1;
+  lh.length = htons(plen + sizeof(struct fsmheader));
+  bp = m_get(plen + sizeof(struct fsmheader), MB_PAPOUT);
+  memcpy(MBUF_CTOP(bp), &lh, sizeof(struct fsmheader));
+  cp = MBUF_CTOP(bp) + sizeof(struct fsmheader);
+  /*
+   * If our message is longer than 255 bytes, truncate the length to
+   * 255 and send the entire message anyway.  Maybe the other end will
+   * display it... (see pap_Input() !)
+   */
+  *cp++ = mlen > 255 ? 255 : mlen;
+  memcpy(cp, message, mlen);
+  log_Printf(LogPHASE, "Pap Output: %s\n", papcodes[code]);
+
+  link_PushPacket(&authp->physical->link, bp, authp->physical->dl->bundle,
+                  LINK_QUEUES(&authp->physical->link) - 1, PROTO_PAP);
+}
+
+static void
+pap_Success(struct authinfo *authp)
+{
+  struct bundle *bundle = authp->physical->dl->bundle;
+
+  datalink_GotAuthname(authp->physical->dl, authp->in.name);
+#ifndef NORADIUS
+  if (*bundle->radius.cfg.file && bundle->radius.repstr)
+    SendPapCode(authp, PAP_ACK, bundle->radius.repstr);
+  else
+#endif
+    SendPapCode(authp, PAP_ACK, "Greetings!!");
+  authp->physical->link.lcp.auth_ineed = 0;
+  if (Enabled(bundle, OPT_UTMP))
+    physical_Login(authp->physical, authp->in.name);
+
+  if (authp->physical->link.lcp.auth_iwait == 0)
+    /*
+     * Either I didn't need to authenticate, or I've already been
+     * told that I got the answer right.
+     */
+    datalink_AuthOk(authp->physical->dl);
+}
+
+static void
+pap_Failure(struct authinfo *authp)
+{
+  SendPapCode(authp, PAP_NAK, "Login incorrect");
+  datalink_AuthNotOk(authp->physical->dl);
+}
+
+void
+pap_Init(struct authinfo *pap, struct physical *p)
+{
+  auth_Init(pap, p, pap_Req, pap_Success, pap_Failure);
+}
+
+struct mbuf *
+pap_Input(struct bundle *bundle, struct link *l, struct mbuf *bp)
+{
+  struct physical *p = link2physical(l);
+  struct authinfo *authp = &p->dl->pap;
+  u_char nlen, klen, *key;
+  const char *txt;
+  int txtlen;
+
+  if (p == NULL) {
+    log_Printf(LogERROR, "pap_Input: Not a physical link - dropped\n");
+    m_freem(bp);
+    return NULL;
+  }
+
+  if (bundle_Phase(bundle) != PHASE_NETWORK &&
+      bundle_Phase(bundle) != PHASE_AUTHENTICATE) {
+    log_Printf(LogPHASE, "Unexpected pap input - dropped !\n");
+    m_freem(bp);
+    return NULL;
+  }
+
+  if ((bp = auth_ReadHeader(authp, bp)) == NULL &&
+      ntohs(authp->in.hdr.length) == 0) {
+    log_Printf(LogWARN, "Pap Input: Truncated header !\n");
+    return NULL;
+  }
+
+  if (authp->in.hdr.code == 0 || authp->in.hdr.code > MAXPAPCODE) {
+    log_Printf(LogPHASE, "Pap Input: %d: Bad PAP code !\n", authp->in.hdr.code);
+    m_freem(bp);
+    return NULL;
+  }
+
+  if (authp->in.hdr.code != PAP_REQUEST && authp->id != authp->in.hdr.id &&
+      Enabled(bundle, OPT_IDCHECK)) {
+    /* Wrong conversation dude ! */
+    log_Printf(LogPHASE, "Pap Input: %s dropped (got id %d, not %d)\n",
+               papcodes[authp->in.hdr.code], authp->in.hdr.id, authp->id);
+    m_freem(bp);
+    return NULL;
+  }
+  m_settype(bp, MB_PAPIN);
+  authp->id = authp->in.hdr.id;		/* We respond with this id */
+
+  if (bp) {
+    bp = mbuf_Read(bp, &nlen, 1);
+    if (authp->in.hdr.code == PAP_ACK) {
+      /*
+       * Don't restrict the length of our acknowledgement freetext to
+       * nlen (a one-byte length).  Show the rest of the ack packet
+       * instead.  This isn't really part of the protocol.....
+       */
+      bp = m_pullup(bp);
+      txt = MBUF_CTOP(bp);
+      txtlen = m_length(bp);
+    } else {
+      bp = auth_ReadName(authp, bp, nlen);
+      txt = authp->in.name;
+      txtlen = strlen(authp->in.name);
+    }
+  } else {
+    txt = "";
+    txtlen = 0;
+  }
+
+  log_Printf(LogPHASE, "Pap Input: %s (%.*s)\n",
+             papcodes[authp->in.hdr.code], txtlen, txt);
+
+  switch (authp->in.hdr.code) {
+    case PAP_REQUEST:
+      if (bp == NULL) {
+        log_Printf(LogPHASE, "Pap Input: No key given !\n");
+        break;
+      }
+      bp = mbuf_Read(bp, &klen, 1);
+      if (m_length(bp) < klen) {
+        log_Printf(LogERROR, "Pap Input: Truncated key !\n");
+        break;
+      }
+      if ((key = malloc(klen+1)) == NULL) {
+        log_Printf(LogERROR, "Pap Input: Out of memory !\n");
+        break;
+      }
+      bp = mbuf_Read(bp, key, klen);
+      key[klen] = '\0';
+
+#ifndef NORADIUS
+      if (*bundle->radius.cfg.file) {
+        if (!radius_Authenticate(&bundle->radius, authp, authp->in.name,
+                                 key, strlen(key), NULL, 0))
+          pap_Failure(authp);
+      } else
+#endif
+      if (auth_Validate(bundle, authp->in.name, key))
+        pap_Success(authp);
+      else
+        pap_Failure(authp);
+
+      free(key);
+      break;
+
+    case PAP_ACK:
+      auth_StopTimer(authp);
+      if (p->link.lcp.auth_iwait == PROTO_PAP) {
+        p->link.lcp.auth_iwait = 0;
+        if (p->link.lcp.auth_ineed == 0)
+          /*
+           * We've succeeded in our ``login''
+           * If we're not expecting  the peer to authenticate (or he already
+           * has), proceed to network phase.
+           */
+          datalink_AuthOk(p->dl);
+      }
+      break;
+
+    case PAP_NAK:
+      auth_StopTimer(authp);
+      datalink_AuthNotOk(p->dl);
+      break;
+  }
+
+  m_freem(bp);
+  return NULL;
+}
diff --git a/src/pap.h b/src/pap.h
new file mode 100644
index 0000000..10184ae
--- /dev/null
+++ b/src/pap.h
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/pap.h,v 1.12.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define	PAP_REQUEST	1
+#define	PAP_ACK		2
+#define	PAP_NAK		3
+
+struct mbuf;
+struct physical;
+struct authinfo;
+
+extern void pap_Init(struct authinfo *, struct physical *);
+extern struct mbuf *pap_Input(struct bundle *, struct link *, struct mbuf *);
diff --git a/src/physical.c b/src/physical.c
new file mode 100644
index 0000000..eae99eb
--- /dev/null
+++ b/src/physical.c
@@ -0,0 +1,1151 @@
+/*
+ * Written by Eivind Eklund <eivind@yes.no>
+ *    for Yes Interactive
+ *
+ * Copyright (C) 1998, Yes Interactive.  All rights reserved.
+ *
+ * Redistribution and use in any form is permitted.  Redistribution in
+ * source form should include the above copyright and this set of
+ * conditions, because large sections american law seems to have been
+ * created by a bunch of jerks on drugs that are now illegal, forcing
+ * me to include this copyright-stuff instead of placing this in the
+ * public domain.  The name of of 'Yes Interactive' or 'Eivind Eklund'
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/physical.c,v 1.59.10.1.4.1 2010/12/21 17:10:29 kensmith Exp $
+ *
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#ifdef NOSUID
+#include <signal.h>
+#endif
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/tty.h>	/* TIOCOUTQ */
+#include <sys/uio.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+#include <utmp.h>
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+#include <sys/ioctl.h>
+#include <util.h>
+#else
+#include <libutil.h>
+#endif
+
+#include "layer.h"
+#ifndef NONAT
+#include "nat_cmd.h"
+#endif
+#include "proto.h"
+#include "acf.h"
+#include "vjcomp.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "id.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "throughput.h"
+#include "sync.h"
+#include "async.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "ccp.h"
+#include "link.h"
+#include "physical.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "prompt.h"
+#include "chat.h"
+#include "auth.h"
+#include "main.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "tcp.h"
+#include "udp.h"
+#include "exec.h"
+#include "tty.h"
+#ifndef NOI4B
+#include "i4b.h"
+#endif
+#ifndef NONETGRAPH
+#include "ether.h"
+#include "netgraph.h"
+#endif
+#ifndef NOATM
+#include "atm.h"
+#endif
+#include "tcpmss.h"
+
+#define PPPOTCPLINE "ppp"
+
+static int physical_DescriptorWrite(struct fdescriptor *, struct bundle *,
+                                    const fd_set *);
+
+static unsigned
+physical_DeviceSize(void)
+{
+  return sizeof(struct device);
+}
+
+struct {
+  struct device *(*create)(struct physical *);
+  struct device *(*iov2device)(int, struct physical *, struct iovec *,
+                               int *, int, int *, int *);
+  unsigned (*DeviceSize)(void);
+} devices[] = {
+#ifndef NOI4B
+  /*
+   * This must come before ``tty'' so that the probe routine is
+   * able to identify it as a more specific type of terminal device.
+   */
+  { i4b_Create, i4b_iov2device, i4b_DeviceSize },
+#endif
+  { tty_Create, tty_iov2device, tty_DeviceSize },
+#ifndef NONETGRAPH
+  /*
+   * This must come before ``udp'' so that the probe routine is
+   * able to identify it as a more specific type of SOCK_DGRAM.
+   */
+  { ether_Create, ether_iov2device, ether_DeviceSize },
+#ifdef EXPERIMENTAL_NETGRAPH
+  { ng_Create, ng_iov2device, ng_DeviceSize },
+#endif
+#endif
+#ifndef NOATM
+  /* Ditto for ATM devices */
+  { atm_Create, atm_iov2device, atm_DeviceSize },
+#endif
+  { tcp_Create, tcp_iov2device, tcp_DeviceSize },
+  { udp_Create, udp_iov2device, udp_DeviceSize },
+  { exec_Create, exec_iov2device, exec_DeviceSize }
+};
+
+#define NDEVICES (sizeof devices / sizeof devices[0])
+
+static int
+physical_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e,
+                   int *n)
+{
+  return physical_doUpdateSet(d, r, w, e, n, 0);
+}
+
+void
+physical_SetDescriptor(struct physical *p)
+{
+  p->desc.type = PHYSICAL_DESCRIPTOR;
+  p->desc.UpdateSet = physical_UpdateSet;
+  p->desc.IsSet = physical_IsSet;
+  p->desc.Read = physical_DescriptorRead;
+  p->desc.Write = physical_DescriptorWrite;
+}
+
+struct physical *
+physical_Create(struct datalink *dl, int type)
+{
+  struct physical *p;
+
+  p = (struct physical *)malloc(sizeof(struct physical));
+  if (!p)
+    return NULL;
+
+  p->link.type = PHYSICAL_LINK;
+  p->link.name = dl->name;
+  p->link.len = sizeof *p;
+
+  /* The sample period is fixed - see physical2iov() & iov2physical() */
+  throughput_init(&p->link.stats.total, SAMPLE_PERIOD);
+  p->link.stats.parent = dl->bundle->ncp.mp.active ?
+    &dl->bundle->ncp.mp.link.stats.total : NULL;
+  p->link.stats.gather = 1;
+
+  memset(p->link.Queue, '\0', sizeof p->link.Queue);
+  memset(p->link.proto_in, '\0', sizeof p->link.proto_in);
+  memset(p->link.proto_out, '\0', sizeof p->link.proto_out);
+  link_EmptyStack(&p->link);
+
+  p->handler = NULL;
+  physical_SetDescriptor(p);
+  p->type = type;
+
+  hdlc_Init(&p->hdlc, &p->link.lcp);
+  async_Init(&p->async);
+
+  p->fd = -1;
+  p->out = NULL;
+  p->connect_count = 0;
+  p->dl = dl;
+  p->input.sz = 0;
+  *p->name.full = '\0';
+  p->name.base = p->name.full;
+
+  p->Utmp = 0;
+  p->session_owner = (pid_t)-1;
+
+  p->cfg.rts_cts = MODEM_CTSRTS;
+  p->cfg.speed = MODEM_SPEED;
+  p->cfg.parity = CS8;
+  memcpy(p->cfg.devlist, MODEM_LIST, sizeof MODEM_LIST);
+  p->cfg.ndev = NMODEMS;
+  p->cfg.cd.necessity = CD_DEFAULT;
+  p->cfg.cd.delay = 0;		/* reconfigured or device specific default */
+
+  lcp_Init(&p->link.lcp, dl->bundle, &p->link, &dl->fsmp);
+  ccp_Init(&p->link.ccp, dl->bundle, &p->link, &dl->fsmp);
+
+  return p;
+}
+
+static const struct parity {
+  const char *name;
+  const char *name1;
+  int set;
+} validparity[] = {
+  { "even", "P_EVEN", CS7 | PARENB },
+  { "odd", "P_ODD", CS7 | PARENB | PARODD },
+  { "none", "P_ZERO", CS8 },
+  { NULL, NULL, 0 },
+};
+
+static int
+GetParityValue(const char *str)
+{
+  const struct parity *pp;
+
+  for (pp = validparity; pp->name; pp++) {
+    if (strcasecmp(pp->name, str) == 0 ||
+	strcasecmp(pp->name1, str) == 0) {
+      return pp->set;
+    }
+  }
+  return (-1);
+}
+
+int
+physical_SetParity(struct physical *p, const char *str)
+{
+  struct termios rstio;
+  int val;
+
+  val = GetParityValue(str);
+  if (val > 0) {
+    p->cfg.parity = val;
+    if (p->fd >= 0) {
+      tcgetattr(p->fd, &rstio);
+      rstio.c_cflag &= ~(CSIZE | PARODD | PARENB);
+      rstio.c_cflag |= val;
+      tcsetattr(p->fd, TCSADRAIN, &rstio);
+    }
+    return 0;
+  }
+  log_Printf(LogWARN, "%s: %s: Invalid parity\n", p->link.name, str);
+  return -1;
+}
+
+unsigned
+physical_GetSpeed(struct physical *p)
+{
+  if (p->handler && p->handler->speed)
+    return (*p->handler->speed)(p);
+
+  return 0;
+}
+
+int
+physical_SetSpeed(struct physical *p, unsigned speed)
+{
+  if (UnsignedToSpeed(speed) != B0) {
+      p->cfg.speed = speed;
+      return 1;
+  }
+
+  return 0;
+}
+
+int
+physical_Raw(struct physical *p)
+{
+  if (p->handler && p->handler->raw)
+    return (*p->handler->raw)(p);
+
+  return 1;
+}
+
+void
+physical_Offline(struct physical *p)
+{
+  if (p->handler && p->handler->offline)
+    (*p->handler->offline)(p);
+  log_Printf(LogPHASE, "%s: Disconnected!\n", p->link.name);
+}
+
+static int
+physical_Lock(struct physical *p)
+{
+  int res;
+
+  if (*p->name.full == '/' && p->type != PHYS_DIRECT &&
+      (res = ID0uu_lock(p->name.base)) != UU_LOCK_OK) {
+    if (res == UU_LOCK_INUSE)
+      log_Printf(LogPHASE, "%s: %s is in use\n", p->link.name, p->name.full);
+    else
+      log_Printf(LogPHASE, "%s: %s is in use: uu_lock: %s\n",
+                 p->link.name, p->name.full, uu_lockerr(res));
+    return 0;
+  }
+
+  return 1;
+}
+
+static void
+physical_Unlock(struct physical *p)
+{
+  if (*p->name.full == '/' && p->type != PHYS_DIRECT &&
+      ID0uu_unlock(p->name.base) == -1)
+    log_Printf(LogALERT, "%s: Can't uu_unlock %s\n", p->link.name,
+               p->name.base);
+}
+
+void
+physical_Close(struct physical *p)
+{
+  int newsid;
+  char fn[PATH_MAX];
+
+  if (p->fd < 0)
+    return;
+
+  log_Printf(LogDEBUG, "%s: Close\n", p->link.name);
+
+  if (p->handler && p->handler->cooked)
+    (*p->handler->cooked)(p);
+
+  physical_StopDeviceTimer(p);
+  if (p->Utmp) {
+    if (p->handler && (p->handler->type == TCP_DEVICE ||
+                       p->handler->type == UDP_DEVICE))
+      /* Careful - we logged in on line ``ppp'' with IP as our host */
+      ID0logout(PPPOTCPLINE, 1);
+    else
+      ID0logout(p->name.base, 0);
+    p->Utmp = 0;
+  }
+  newsid = tcgetpgrp(p->fd) == getpgrp();
+  close(p->fd);
+  p->fd = -1;
+  log_SetTtyCommandMode(p->dl);
+
+  throughput_stop(&p->link.stats.total);
+  throughput_log(&p->link.stats.total, LogPHASE, p->link.name);
+
+  if (p->session_owner != (pid_t)-1) {
+    log_Printf(LogPHASE, "%s: HUPing %ld\n", p->link.name,
+               (long)p->session_owner);
+    ID0kill(p->session_owner, SIGHUP);
+    p->session_owner = (pid_t)-1;
+  }
+
+  if (newsid)
+    bundle_setsid(p->dl->bundle, 0);
+
+  if (*p->name.full == '/') {
+    snprintf(fn, sizeof fn, "%s%s.if", _PATH_VARRUN, p->name.base);
+#ifndef RELEASE_CRUNCH
+    if (ID0unlink(fn) == -1)
+      log_Printf(LogALERT, "%s: Can't remove %s: %s\n",
+                 p->link.name, fn, strerror(errno));
+#else
+    ID0unlink(fn);
+#endif
+  }
+  physical_Unlock(p);
+  if (p->handler && p->handler->destroy)
+    (*p->handler->destroy)(p);
+  p->handler = NULL;
+  p->name.base = p->name.full;
+  *p->name.full = '\0';
+}
+
+void
+physical_Destroy(struct physical *p)
+{
+  physical_Close(p);
+  throughput_destroy(&p->link.stats.total);
+  free(p);
+}
+
+static int
+physical_DescriptorWrite(struct fdescriptor *d, struct bundle *bundle __unused,
+                         const fd_set *fdset __unused)
+{
+  struct physical *p = descriptor2physical(d);
+  int nw, result = 0;
+
+  if (p->out == NULL)
+    p->out = link_Dequeue(&p->link);
+
+  if (p->out) {
+    nw = physical_Write(p, MBUF_CTOP(p->out), p->out->m_len);
+    log_Printf(LogDEBUG, "%s: DescriptorWrite: wrote %d(%lu) to %d\n",
+               p->link.name, nw, (unsigned long)p->out->m_len, p->fd);
+    if (nw > 0) {
+      p->out->m_len -= nw;
+      p->out->m_offset += nw;
+      if (p->out->m_len == 0)
+	p->out = m_free(p->out);
+      result = 1;
+    } else if (nw < 0) {
+      if (errno == EAGAIN)
+        result = 1;
+      else if (errno != ENOBUFS) {
+	log_Printf(LogPHASE, "%s: write (fd %d, len %zd): %s\n", p->link.name,
+                   p->fd, p->out->m_len, strerror(errno));
+        datalink_Down(p->dl, CLOSE_NORMAL);
+      }
+    }
+    /* else we shouldn't really have been called !  select() is broken ! */
+  }
+
+  return result;
+}
+
+int
+physical_ShowStatus(struct cmdargs const *arg)
+{
+  struct physical *p = arg->cx->physical;
+  struct cd *cd;
+  const char *dev;
+  int n, slot;
+
+  prompt_Printf(arg->prompt, "Name: %s\n", p->link.name);
+  prompt_Printf(arg->prompt, " State:           ");
+  if (p->fd < 0)
+    prompt_Printf(arg->prompt, "closed\n");
+  else {
+    slot = physical_Slot(p);
+    if (p->handler && p->handler->openinfo) {
+      if (slot == -1)
+        prompt_Printf(arg->prompt, "open (%s)\n", (*p->handler->openinfo)(p));
+      else
+        prompt_Printf(arg->prompt, "open (%s, port %d)\n",
+                      (*p->handler->openinfo)(p), slot);
+    } else if (slot == -1)
+      prompt_Printf(arg->prompt, "open\n");
+    else
+      prompt_Printf(arg->prompt, "open (port %d)\n", slot);
+  }
+
+  prompt_Printf(arg->prompt, " Device:          %s",
+                *p->name.full ?  p->name.full :
+                p->type == PHYS_DIRECT ? "unknown" : "N/A");
+  if (p->session_owner != (pid_t)-1)
+    prompt_Printf(arg->prompt, " (session owner: %ld)", (long)p->session_owner);
+
+  prompt_Printf(arg->prompt, "\n Link Type:       %s\n", mode2Nam(p->type));
+  prompt_Printf(arg->prompt, " Connect Count:   %d\n", p->connect_count);
+#ifdef TIOCOUTQ
+  if (p->fd >= 0 && ioctl(p->fd, TIOCOUTQ, &n) >= 0)
+      prompt_Printf(arg->prompt, " Physical outq:   %d\n", n);
+#endif
+
+  prompt_Printf(arg->prompt, " Queued Packets:  %lu\n",
+                (u_long)link_QueueLen(&p->link));
+  prompt_Printf(arg->prompt, " Phone Number:    %s\n", arg->cx->phone.chosen);
+
+  prompt_Printf(arg->prompt, "\nDefaults:\n");
+
+  prompt_Printf(arg->prompt, " Device List:     ");
+  dev = p->cfg.devlist;
+  for (n = 0; n < p->cfg.ndev; n++) {
+    if (n)
+      prompt_Printf(arg->prompt, ", ");
+    prompt_Printf(arg->prompt, "\"%s\"", dev);
+    dev += strlen(dev) + 1;
+  }
+
+  prompt_Printf(arg->prompt, "\n Characteristics: ");
+  if (physical_IsSync(arg->cx->physical))
+    prompt_Printf(arg->prompt, "sync");
+  else
+    prompt_Printf(arg->prompt, "%dbps", p->cfg.speed);
+
+  switch (p->cfg.parity & CSIZE) {
+  case CS7:
+    prompt_Printf(arg->prompt, ", cs7");
+    break;
+  case CS8:
+    prompt_Printf(arg->prompt, ", cs8");
+    break;
+  }
+  if (p->cfg.parity & PARENB) {
+    if (p->cfg.parity & PARODD)
+      prompt_Printf(arg->prompt, ", odd parity");
+    else
+      prompt_Printf(arg->prompt, ", even parity");
+  } else
+    prompt_Printf(arg->prompt, ", no parity");
+
+  prompt_Printf(arg->prompt, ", CTS/RTS %s\n", (p->cfg.rts_cts ? "on" : "off"));
+
+  prompt_Printf(arg->prompt, " CD check delay:  ");
+  cd = p->handler ? &p->handler->cd : &p->cfg.cd;
+  if (cd->necessity == CD_NOTREQUIRED)
+    prompt_Printf(arg->prompt, "no cd");
+  else if (p->cfg.cd.necessity == CD_DEFAULT) {
+    prompt_Printf(arg->prompt, "device specific");
+  } else {
+    prompt_Printf(arg->prompt, "%d second%s", p->cfg.cd.delay,
+                  p->cfg.cd.delay == 1 ? "" : "s");
+    if (p->cfg.cd.necessity == CD_REQUIRED)
+      prompt_Printf(arg->prompt, " (required!)");
+  }
+  prompt_Printf(arg->prompt, "\n\n");
+
+  throughput_disp(&p->link.stats.total, arg->prompt);
+
+  return 0;
+}
+
+void
+physical_DescriptorRead(struct fdescriptor *d, struct bundle *bundle,
+                     const fd_set *fdset __unused)
+{
+  struct physical *p = descriptor2physical(d);
+  u_char *rbuff;
+  int n, found;
+
+  rbuff = p->input.buf + p->input.sz;
+
+  /* something to read */
+  n = physical_Read(p, rbuff, sizeof p->input.buf - p->input.sz);
+  log_Printf(LogDEBUG, "%s: DescriptorRead: read %d/%d from %d\n",
+             p->link.name, n, (int)(sizeof p->input.buf - p->input.sz), p->fd);
+  if (n <= 0) {
+    if (n < 0)
+      log_Printf(LogPHASE, "%s: read (%d): %s\n", p->link.name, p->fd,
+                 strerror(errno));
+    else
+      log_Printf(LogPHASE, "%s: read (%d): Got zero bytes\n",
+                 p->link.name, p->fd);
+    datalink_Down(p->dl, CLOSE_NORMAL);
+    return;
+  }
+
+  rbuff -= p->input.sz;
+  n += p->input.sz;
+
+  if (p->link.lcp.fsm.state <= ST_CLOSED) {
+    if (p->type != PHYS_DEDICATED) {
+      found = hdlc_Detect((u_char const **)&rbuff, n, physical_IsSync(p));
+      if (rbuff != p->input.buf)
+        log_WritePrompts(p->dl, "%.*s", (int)(rbuff - p->input.buf),
+                         p->input.buf);
+      p->input.sz = n - (rbuff - p->input.buf);
+
+      if (found) {
+        /* LCP packet is detected. Turn ourselves into packet mode */
+        log_Printf(LogPHASE, "%s: PPP packet detected, coming up\n",
+                   p->link.name);
+        log_SetTtyCommandMode(p->dl);
+        datalink_Up(p->dl, 0, 1);
+        link_PullPacket(&p->link, rbuff, p->input.sz, bundle);
+        p->input.sz = 0;
+      } else
+        bcopy(rbuff, p->input.buf, p->input.sz);
+    } else
+      /* In -dedicated mode, we just discard input until LCP is started */
+      p->input.sz = 0;
+  } else if (n > 0)
+    link_PullPacket(&p->link, rbuff, n, bundle);
+}
+
+struct physical *
+iov2physical(struct datalink *dl, struct iovec *iov, int *niov, int maxiov,
+             int fd, int *auxfd, int *nauxfd)
+{
+  struct physical *p;
+  int len, type;
+  unsigned h;
+
+  p = (struct physical *)iov[(*niov)++].iov_base;
+  p->link.name = dl->name;
+  memset(p->link.Queue, '\0', sizeof p->link.Queue);
+
+  p->desc.UpdateSet = physical_UpdateSet;
+  p->desc.IsSet = physical_IsSet;
+  p->desc.Read = physical_DescriptorRead;
+  p->desc.Write = physical_DescriptorWrite;
+  p->type = PHYS_DIRECT;
+  p->dl = dl;
+  len = strlen(_PATH_DEV);
+  p->out = NULL;
+  p->connect_count = 1;
+
+  physical_SetDevice(p, p->name.full);
+
+  p->link.lcp.fsm.bundle = dl->bundle;
+  p->link.lcp.fsm.link = &p->link;
+  memset(&p->link.lcp.fsm.FsmTimer, '\0', sizeof p->link.lcp.fsm.FsmTimer);
+  memset(&p->link.lcp.fsm.OpenTimer, '\0', sizeof p->link.lcp.fsm.OpenTimer);
+  memset(&p->link.lcp.fsm.StoppedTimer, '\0',
+         sizeof p->link.lcp.fsm.StoppedTimer);
+  p->link.lcp.fsm.parent = &dl->fsmp;
+  lcp_SetupCallbacks(&p->link.lcp);
+
+  p->link.ccp.fsm.bundle = dl->bundle;
+  p->link.ccp.fsm.link = &p->link;
+  /* Our in.state & out.state are NULL (no link-level ccp yet) */
+  memset(&p->link.ccp.fsm.FsmTimer, '\0', sizeof p->link.ccp.fsm.FsmTimer);
+  memset(&p->link.ccp.fsm.OpenTimer, '\0', sizeof p->link.ccp.fsm.OpenTimer);
+  memset(&p->link.ccp.fsm.StoppedTimer, '\0',
+         sizeof p->link.ccp.fsm.StoppedTimer);
+  p->link.ccp.fsm.parent = &dl->fsmp;
+  ccp_SetupCallbacks(&p->link.ccp);
+
+  p->hdlc.lqm.owner = &p->link.lcp;
+  p->hdlc.ReportTimer.state = TIMER_STOPPED;
+  p->hdlc.lqm.timer.state = TIMER_STOPPED;
+
+  p->fd = fd;
+  p->link.stats.total.in.SampleOctets = (long long *)iov[(*niov)++].iov_base;
+  p->link.stats.total.out.SampleOctets = (long long *)iov[(*niov)++].iov_base;
+  p->link.stats.parent = dl->bundle->ncp.mp.active ?
+    &dl->bundle->ncp.mp.link.stats.total : NULL;
+  p->link.stats.gather = 1;
+
+  type = (long)p->handler;
+  p->handler = NULL;
+  for (h = 0; h < NDEVICES && p->handler == NULL; h++)
+    p->handler = (*devices[h].iov2device)(type, p, iov, niov, maxiov,
+                                          auxfd, nauxfd);
+  if (p->handler == NULL) {
+    log_Printf(LogPHASE, "%s: Unknown link type\n", p->link.name);
+    free(iov[(*niov)++].iov_base);
+    physical_SetupStack(p, "unknown", PHYSICAL_NOFORCE);
+  } else
+    log_Printf(LogPHASE, "%s: Device %s, link type is %s\n",
+               p->link.name, p->name.full, p->handler->name);
+
+  if (p->hdlc.lqm.method && p->hdlc.lqm.timer.load)
+    lqr_reStart(&p->link.lcp);
+  hdlc_StartTimer(&p->hdlc);
+
+  throughput_restart(&p->link.stats.total, "physical throughput",
+                     Enabled(dl->bundle, OPT_THROUGHPUT));
+
+  return p;
+}
+
+unsigned
+physical_MaxDeviceSize()
+{
+  unsigned biggest, sz, n;
+
+  biggest = sizeof(struct device);
+  for (n = 0; n < NDEVICES; n++)
+    if (devices[n].DeviceSize) {
+      sz = (*devices[n].DeviceSize)();
+      if (biggest < sz)
+        biggest = sz;
+    }
+
+  return biggest;
+}
+
+int
+physical2iov(struct physical *p, struct iovec *iov, int *niov, int maxiov,
+             int *auxfd, int *nauxfd)
+{
+  struct device *h;
+  int sz;
+
+  h = NULL;
+  if (p) {
+    hdlc_StopTimer(&p->hdlc);
+    lqr_StopTimer(p);
+    timer_Stop(&p->link.lcp.fsm.FsmTimer);
+    timer_Stop(&p->link.ccp.fsm.FsmTimer);
+    timer_Stop(&p->link.lcp.fsm.OpenTimer);
+    timer_Stop(&p->link.ccp.fsm.OpenTimer);
+    timer_Stop(&p->link.lcp.fsm.StoppedTimer);
+    timer_Stop(&p->link.ccp.fsm.StoppedTimer);
+    if (p->handler) {
+      h = p->handler;
+      p->handler = (struct device *)(long)p->handler->type;
+    }
+
+    if (Enabled(p->dl->bundle, OPT_KEEPSESSION) ||
+        tcgetpgrp(p->fd) == getpgrp())
+      p->session_owner = getpid();      /* So I'll eventually get HUP'd */
+    else
+      p->session_owner = (pid_t)-1;
+    timer_Stop(&p->link.stats.total.Timer);
+  }
+
+  if (*niov + 2 >= maxiov) {
+    log_Printf(LogERROR, "physical2iov: No room for physical + throughput"
+               " + device !\n");
+    if (p)
+      free(p);
+    return -1;
+  }
+
+  iov[*niov].iov_base = (void *)p;
+  iov[*niov].iov_len = sizeof *p;
+  (*niov)++;
+
+  iov[*niov].iov_base = p ? (void *)p->link.stats.total.in.SampleOctets : NULL;
+  iov[*niov].iov_len = SAMPLE_PERIOD * sizeof(long long);
+  (*niov)++;
+  iov[*niov].iov_base = p ? (void *)p->link.stats.total.out.SampleOctets : NULL;
+  iov[*niov].iov_len = SAMPLE_PERIOD * sizeof(long long);
+  (*niov)++;
+
+  sz = physical_MaxDeviceSize();
+  if (p) {
+    if (h && h->device2iov)
+      (*h->device2iov)(h, iov, niov, maxiov, auxfd, nauxfd);
+    else {
+      if ((iov[*niov].iov_base = malloc(sz)) == NULL) {
+	log_Printf(LogALERT, "physical2iov: Out of memory (%d bytes)\n", sz);
+	AbortProgram(EX_OSERR);
+      }
+      if (h)
+        memcpy(iov[*niov].iov_base, h, sizeof *h);
+      iov[*niov].iov_len = sz;
+      (*niov)++;
+    }
+  } else {
+    iov[*niov].iov_base = NULL;
+    iov[*niov].iov_len = sz;
+    (*niov)++;
+  }
+
+  return p ? p->fd : 0;
+}
+
+const char *
+physical_LockedDevice(struct physical *p)
+{
+  if (p->fd >= 0 && *p->name.full == '/' && p->type != PHYS_DIRECT)
+    return p->name.base;
+
+  return NULL;
+}
+
+void
+physical_ChangedPid(struct physical *p, pid_t newpid)
+{
+  if (physical_LockedDevice(p)) {
+    int res;
+
+    if ((res = ID0uu_lock_txfr(p->name.base, newpid)) != UU_LOCK_OK)
+      log_Printf(LogPHASE, "uu_lock_txfr: %s\n", uu_lockerr(res));
+  }
+}
+
+int
+physical_IsSync(struct physical *p)
+{
+   return p->cfg.speed == 0;
+}
+
+u_short
+physical_DeviceMTU(struct physical *p)
+{
+  return p->handler ? p->handler->mtu : 0;
+}
+
+const char *physical_GetDevice(struct physical *p)
+{
+   return p->name.full;
+}
+
+void
+physical_SetDeviceList(struct physical *p, int argc, const char *const *argv)
+{
+  unsigned pos;
+  int f;
+
+  p->cfg.devlist[sizeof p->cfg.devlist - 1] = '\0';
+  for (f = 0, pos = 0; f < argc && pos < sizeof p->cfg.devlist - 1; f++) {
+    if (pos)
+      p->cfg.devlist[pos++] = '\0';
+    strncpy(p->cfg.devlist + pos, argv[f], sizeof p->cfg.devlist - pos - 1);
+    pos += strlen(p->cfg.devlist + pos);
+  }
+  p->cfg.ndev = f;
+}
+
+void
+physical_SetSync(struct physical *p)
+{
+   p->cfg.speed = 0;
+}
+
+int
+physical_SetRtsCts(struct physical *p, int enable)
+{
+   p->cfg.rts_cts = enable ? 1 : 0;
+   return 1;
+}
+
+ssize_t
+physical_Read(struct physical *p, void *buf, size_t nbytes)
+{
+  ssize_t ret;
+
+  if (p->handler && p->handler->read)
+    ret = (*p->handler->read)(p, buf, nbytes);
+  else
+    ret = read(p->fd, buf, nbytes);
+
+  log_DumpBuff(LogPHYSICAL, "read", buf, ret);
+
+  return ret;
+}
+
+ssize_t
+physical_Write(struct physical *p, const void *buf, size_t nbytes)
+{
+  log_DumpBuff(LogPHYSICAL, "write", buf, nbytes);
+
+  if (p->handler && p->handler->write)
+    return (*p->handler->write)(p, buf, nbytes);
+
+  return write(p->fd, buf, nbytes);
+}
+
+int
+physical_doUpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e,
+                     int *n, int force)
+{
+  struct physical *p = descriptor2physical(d);
+  int sets;
+
+  sets = 0;
+  if (p->fd >= 0) {
+    if (r) {
+      FD_SET(p->fd, r);
+      log_Printf(LogTIMER, "%s: fdset(r) %d\n", p->link.name, p->fd);
+      sets++;
+    }
+    if (e) {
+      FD_SET(p->fd, e);
+      log_Printf(LogTIMER, "%s: fdset(e) %d\n", p->link.name, p->fd);
+      sets++;
+    }
+    if (w && (force || link_QueueLen(&p->link) || p->out)) {
+      FD_SET(p->fd, w);
+      log_Printf(LogTIMER, "%s: fdset(w) %d\n", p->link.name, p->fd);
+      sets++;
+    }
+    if (sets && *n < p->fd + 1)
+      *n = p->fd + 1;
+  }
+
+  return sets;
+}
+
+int
+physical_RemoveFromSet(struct physical *p, fd_set *r, fd_set *w, fd_set *e)
+{
+  if (p->handler && p->handler->removefromset)
+    return (*p->handler->removefromset)(p, r, w, e);
+  else {
+    int sets;
+
+    sets = 0;
+    if (p->fd >= 0) {
+      if (r && FD_ISSET(p->fd, r)) {
+        FD_CLR(p->fd, r);
+        log_Printf(LogTIMER, "%s: fdunset(r) %d\n", p->link.name, p->fd);
+        sets++;
+      }
+      if (e && FD_ISSET(p->fd, e)) {
+        FD_CLR(p->fd, e);
+        log_Printf(LogTIMER, "%s: fdunset(e) %d\n", p->link.name, p->fd);
+        sets++;
+      }
+      if (w && FD_ISSET(p->fd, w)) {
+        FD_CLR(p->fd, w);
+        log_Printf(LogTIMER, "%s: fdunset(w) %d\n", p->link.name, p->fd);
+        sets++;
+      }
+    }
+
+    return sets;
+  }
+}
+
+int
+physical_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+  struct physical *p = descriptor2physical(d);
+  return p->fd >= 0 && FD_ISSET(p->fd, fdset);
+}
+
+void
+physical_Login(struct physical *p, const char *name)
+{
+  if (p->type == PHYS_DIRECT && *p->name.base && !p->Utmp) {
+    struct utmp ut;
+    const char *connstr;
+    char *colon;
+
+    memset(&ut, 0, sizeof ut);
+    ut.ut_time = time(NULL);
+    strncpy(ut.ut_name, name, sizeof ut.ut_name);
+    if (p->handler && (p->handler->type == TCP_DEVICE ||
+                       p->handler->type == UDP_DEVICE)) {
+      strncpy(ut.ut_line, PPPOTCPLINE, sizeof ut.ut_line);
+      strncpy(ut.ut_host, p->name.base, sizeof ut.ut_host);
+      colon = memchr(ut.ut_host, ':', sizeof ut.ut_host);
+      if (colon)
+        *colon = '\0';
+    } else
+      strncpy(ut.ut_line, p->name.base, sizeof ut.ut_line);
+    if ((connstr = getenv("CONNECT")))
+      /* mgetty sets this to the connection speed */
+      strncpy(ut.ut_host, connstr, sizeof ut.ut_host);
+    ID0login(&ut);
+    p->Utmp = ut.ut_time;
+  }
+}
+
+int
+physical_SetMode(struct physical *p, int mode)
+{
+  if ((p->type & (PHYS_DIRECT|PHYS_DEDICATED) ||
+       mode & (PHYS_DIRECT|PHYS_DEDICATED)) &&
+      (!(p->type & PHYS_DIRECT) || !(mode & PHYS_BACKGROUND))) {
+    /* Note:  The -direct -> -background is for callback ! */
+    log_Printf(LogWARN, "%s: Cannot change mode %s to %s\n", p->link.name,
+               mode2Nam(p->type), mode2Nam(mode));
+    return 0;
+  }
+  p->type = mode;
+  return 1;
+}
+
+void
+physical_DeleteQueue(struct physical *p)
+{
+  if (p->out) {
+    m_freem(p->out);
+    p->out = NULL;
+  }
+  link_DeleteQueue(&p->link);
+}
+
+void
+physical_SetDevice(struct physical *p, const char *name)
+{
+  int len = strlen(_PATH_DEV);
+
+  if (name != p->name.full) {
+    strncpy(p->name.full, name, sizeof p->name.full - 1);
+    p->name.full[sizeof p->name.full - 1] = '\0';
+  }
+  p->name.base = *p->name.full == '!' ?  p->name.full + 1 :
+                 strncmp(p->name.full, _PATH_DEV, len) ?
+                 p->name.full : p->name.full + len;
+}
+
+static void
+physical_Found(struct physical *p)
+{
+  FILE *lockfile;
+  char fn[PATH_MAX];
+
+  if (*p->name.full == '/') {
+    snprintf(fn, sizeof fn, "%s%s.if", _PATH_VARRUN, p->name.base);
+    lockfile = ID0fopen(fn, "w");
+    if (lockfile != NULL) {
+      fprintf(lockfile, "%s%d\n", TUN_NAME, p->dl->bundle->unit);
+      fclose(lockfile);
+    }
+#ifndef RELEASE_CRUNCH
+    else
+      log_Printf(LogALERT, "%s: Can't create %s: %s\n",
+                 p->link.name, fn, strerror(errno));
+#endif
+  }
+
+  throughput_start(&p->link.stats.total, "physical throughput",
+                   Enabled(p->dl->bundle, OPT_THROUGHPUT));
+  p->connect_count++;
+  p->input.sz = 0;
+
+  log_Printf(LogPHASE, "%s: Connected!\n", p->link.name);
+}
+
+int
+physical_Open(struct physical *p)
+{
+  char *dev;
+  int devno, wasfd, err;
+  unsigned h;
+
+  if (p->fd >= 0)
+    log_Printf(LogDEBUG, "%s: Open: Modem is already open!\n", p->link.name);
+    /* We're going back into "term" mode */
+  else if (p->type == PHYS_DIRECT) {
+    physical_SetDevice(p, "");
+    p->fd = STDIN_FILENO;
+    for (h = 0; h < NDEVICES && p->handler == NULL && p->fd >= 0; h++)
+      p->handler = (*devices[h].create)(p);
+    close(STDOUT_FILENO);
+    if (p->fd >= 0) {
+      if (p->handler == NULL) {
+        physical_SetupStack(p, "unknown", PHYSICAL_NOFORCE);
+        log_Printf(LogDEBUG, "%s: stdin is unidentified\n", p->link.name);
+      }
+      physical_Found(p);
+    }
+  } else {
+    dev = p->cfg.devlist;
+    devno = 0;
+    while (devno < p->cfg.ndev && p->fd < 0) {
+      physical_SetDevice(p, dev);
+      if (physical_Lock(p)) {
+        err = 0;
+
+        if (*p->name.full == '/') {
+          p->fd = ID0open(p->name.full, O_RDWR | O_NONBLOCK);
+          if (p->fd < 0)
+            err = errno;
+        }
+
+        wasfd = p->fd;
+        for (h = 0; h < NDEVICES && p->handler == NULL; h++)
+          if ((p->handler = (*devices[h].create)(p)) == NULL && wasfd != p->fd)
+            break;
+
+        if (p->fd < 0) {
+          if (h == NDEVICES) {
+            if (err)
+	      log_Printf(LogWARN, "%s: %s: %s\n", p->link.name, p->name.full,
+                         strerror(errno));
+            else
+	      log_Printf(LogWARN, "%s: Device (%s) must begin with a '/',"
+                         " a '!' or contain at least one ':'\n", p->link.name,
+                         p->name.full);
+          }
+          physical_Unlock(p);
+        } else
+          physical_Found(p);
+      }
+      dev += strlen(dev) + 1;
+      devno++;
+    }
+  }
+
+  return p->fd;
+}
+
+void
+physical_SetupStack(struct physical *p, const char *who, int how)
+{
+  link_EmptyStack(&p->link);
+  if (how == PHYSICAL_FORCE_SYNC || how == PHYSICAL_FORCE_SYNCNOACF ||
+      (how == PHYSICAL_NOFORCE && physical_IsSync(p)))
+    link_Stack(&p->link, &synclayer);
+  else {
+    link_Stack(&p->link, &asynclayer);
+    link_Stack(&p->link, &hdlclayer);
+  }
+  if (how != PHYSICAL_FORCE_SYNCNOACF)
+    link_Stack(&p->link, &acflayer);
+  link_Stack(&p->link, &protolayer);
+  link_Stack(&p->link, &lqrlayer);
+  link_Stack(&p->link, &ccplayer);
+  link_Stack(&p->link, &vjlayer);
+  link_Stack(&p->link, &tcpmsslayer);
+#ifndef NONAT
+  link_Stack(&p->link, &natlayer);
+#endif
+  if (how == PHYSICAL_FORCE_ASYNC && physical_IsSync(p)) {
+    log_Printf(LogWARN, "Sync device setting ignored for ``%s'' device\n", who);
+    p->cfg.speed = MODEM_SPEED;
+  } else if (how == PHYSICAL_FORCE_SYNC && !physical_IsSync(p)) {
+    log_Printf(LogWARN, "Async device setting ignored for ``%s'' device\n",
+               who);
+    physical_SetSync(p);
+  }
+}
+
+void
+physical_StopDeviceTimer(struct physical *p)
+{
+  if (p->handler && p->handler->stoptimer)
+    (*p->handler->stoptimer)(p);
+}
+
+int
+physical_AwaitCarrier(struct physical *p)
+{
+  if (p->handler && p->handler->awaitcarrier)
+    return (*p->handler->awaitcarrier)(p);
+
+  return CARRIER_OK;
+}
+
+
+void
+physical_SetAsyncParams(struct physical *p, u_int32_t mymap, u_int32_t hismap)
+{
+  if (p->handler && p->handler->setasyncparams)
+    return (*p->handler->setasyncparams)(p, mymap, hismap);
+
+  async_SetLinkParams(&p->async, mymap, hismap);
+}
+
+int
+physical_Slot(struct physical *p)
+{
+  if (p->handler && p->handler->slot)
+    return (*p->handler->slot)(p);
+
+  return -1;
+}
+
+int
+physical_SetPPPoEnonstandard(struct physical *p, int enable)
+{
+   p->cfg.nonstandard_pppoe = enable ? 1 : 0;
+   p->cfg.pppoe_configured = 1;
+   return 1;
+}
diff --git a/src/physical.h b/src/physical.h
new file mode 100644
index 0000000..20108a4
--- /dev/null
+++ b/src/physical.h
@@ -0,0 +1,176 @@
+/*
+ * Written by Eivind Eklund <eivind@yes.no>
+ *    for Yes Interactive
+ *
+ * Copyright (C) 1998, Yes Interactive.  All rights reserved.
+ *
+ * Redistribution and use in any form is permitted.  Redistribution in
+ * source form should include the above copyright and this set of
+ * conditions, because large sections american law seems to have been
+ * created by a bunch of jerks on drugs that are now illegal, forcing
+ * me to include this copyright-stuff instead of placing this in the
+ * public domain.  The name of of 'Yes Interactive' or 'Eivind Eklund'
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/physical.h,v 1.28.24.1 2010/12/21 17:10:29 kensmith Exp $
+ *
+ */
+
+struct datalink;
+struct bundle;
+struct iovec;
+struct physical;
+struct bundle;
+struct ccp;
+struct cmdargs;
+
+/* Device types (don't use zero, it'll be confused with NULL in physical2iov */
+#define I4B_DEVICE	1
+#define TTY_DEVICE	2
+#define TCP_DEVICE	3
+#define UDP_DEVICE	4
+#define ETHER_DEVICE	5
+#define EXEC_DEVICE	6
+#define ATM_DEVICE	7
+#define NG_DEVICE	8
+
+/* Returns from awaitcarrier() */
+#define CARRIER_PENDING	1
+#define CARRIER_OK	2
+#define CARRIER_LOST	3
+
+/* A cd ``necessity'' value */
+#define CD_VARIABLE	0
+#define CD_REQUIRED	1
+#define CD_NOTREQUIRED	2
+#define CD_DEFAULT	3
+
+struct cd {
+  unsigned necessity : 2;  /* A CD_ value */
+  int delay;               /* Wait this many seconds after login script */
+};
+
+struct device {
+  int type;
+  const char *name;
+  u_short mtu;
+  struct cd cd;
+
+  int (*awaitcarrier)(struct physical *);
+  int (*removefromset)(struct physical *, fd_set *, fd_set *, fd_set *);
+  int (*raw)(struct physical *);
+  void (*offline)(struct physical *);
+  void (*cooked)(struct physical *);
+  void (*setasyncparams)(struct physical *, u_int32_t, u_int32_t);
+  void (*stoptimer)(struct physical *);
+  void (*destroy)(struct physical *);
+  ssize_t (*read)(struct physical *, void *, size_t);
+  ssize_t (*write)(struct physical *, const void *, size_t);
+  void (*device2iov)(struct device *, struct iovec *, int *, int, int *, int *);
+  unsigned (*speed)(struct physical *);
+  const char *(*openinfo)(struct physical *);
+  int (*slot)(struct physical *);
+};
+
+struct physical {
+  struct link link;
+  struct fdescriptor desc;
+  int type;                    /* What sort of PHYS_* link are we ? */
+  struct async async;          /* Our async state */
+  struct hdlc hdlc;            /* Our hdlc state */
+  int fd;                      /* File descriptor for this device */
+  struct mbuf *out;            /* mbuf that suffered a short write */
+  int connect_count;
+  struct datalink *dl;         /* my owner */
+
+  struct {
+    u_char buf[MAX_MRU];       /* Our input data buffer */
+    size_t sz;
+  } input;
+
+  struct {
+    char full[DEVICE_LEN];     /* Our current device name */
+    char *base;
+  } name;
+
+  time_t Utmp;                 /* Are we in utmp ? */
+  pid_t session_owner;         /* HUP this when closing the link */
+
+  struct device *handler;      /* device specific handler */
+
+  struct {
+    unsigned rts_cts : 1;      /* Is rts/cts enabled ? */
+    unsigned nonstandard_pppoe : 1; /* Is PPPoE mode nonstandard */
+    unsigned pppoe_configured : 1; /* temporary hack */
+    unsigned parity;           /* What parity is enabled? (tty flags) */
+    unsigned speed;            /* tty speed */
+
+    char devlist[LINE_LEN];    /* NUL separated list of devices */
+    int ndev;                  /* number of devices in list */
+    struct cd cd;
+  } cfg;
+};
+
+#define field2phys(fp, name) \
+  ((struct physical *)((char *)fp - (int)(&((struct physical *)0)->name)))
+
+#define link2physical(l) \
+  ((l)->type == PHYSICAL_LINK ? field2phys(l, link) : NULL)
+
+#define descriptor2physical(d) \
+  ((d)->type == PHYSICAL_DESCRIPTOR ? field2phys(d, desc) : NULL)
+
+#define PHYSICAL_NOFORCE		1
+#define PHYSICAL_FORCE_ASYNC		2
+#define PHYSICAL_FORCE_SYNC		3
+#define PHYSICAL_FORCE_SYNCNOACF	4
+
+extern struct physical *physical_Create(struct datalink *, int);
+extern int physical_Open(struct physical *);
+extern int physical_Raw(struct physical *);
+extern unsigned physical_GetSpeed(struct physical *);
+extern int physical_SetSpeed(struct physical *, unsigned);
+extern int physical_SetParity(struct physical *, const char *);
+extern int physical_SetRtsCts(struct physical *, int);
+extern void physical_SetSync(struct physical *);
+extern int physical_ShowStatus(struct cmdargs const *);
+extern void physical_Offline(struct physical *);
+extern void physical_Close(struct physical *);
+extern void physical_Destroy(struct physical *);
+extern struct physical *iov2physical(struct datalink *, struct iovec *, int *,
+                                     int, int, int *, int *);
+extern int physical2iov(struct physical *, struct iovec *, int *, int, int *,
+                        int *);
+extern const char *physical_LockedDevice(struct physical *);
+extern void physical_ChangedPid(struct physical *, pid_t);
+
+extern int physical_IsSync(struct physical *);
+extern u_short physical_DeviceMTU(struct physical *);
+extern const char *physical_GetDevice(struct physical *);
+extern void physical_SetDeviceList(struct physical *, int, const char *const *);
+extern void physical_SetDevice(struct physical *, const char *);
+
+extern ssize_t physical_Read(struct physical *, void *, size_t);
+extern ssize_t physical_Write(struct physical *, const void *, size_t);
+extern int physical_doUpdateSet(struct fdescriptor *, fd_set *, fd_set *,
+                                fd_set *, int *, int);
+extern int physical_IsSet(struct fdescriptor *, const fd_set *);
+extern void physical_DescriptorRead(struct fdescriptor *, struct bundle *,
+                                    const fd_set *);
+extern void physical_Login(struct physical *, const char *);
+extern int physical_RemoveFromSet(struct physical *, fd_set *, fd_set *,
+                                  fd_set *);
+extern int physical_SetMode(struct physical *, int);
+extern void physical_DeleteQueue(struct physical *);
+extern void physical_SetupStack(struct physical *, const char *, int);
+extern void physical_StopDeviceTimer(struct physical *);
+extern unsigned physical_MaxDeviceSize(void);
+extern int physical_AwaitCarrier(struct physical *);
+extern void physical_SetDescriptor(struct physical *);
+extern void physical_SetAsyncParams(struct physical *, u_int32_t, u_int32_t);
+extern int physical_Slot(struct physical *);
+extern int physical_SetPPPoEnonstandard(struct physical *, int);
diff --git a/src/ppp.8.m4 b/src/ppp.8.m4
new file mode 100644
index 0000000..18319de
--- /dev/null
+++ b/src/ppp.8.m4
@@ -0,0 +1,6134 @@
+changequote({,})dnl
+changecom(,)dnl
+.\"
+.\" Copyright (c) 2001 Brian Somers <brian@Awfulhak.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD: src/usr.sbin/ppp/ppp.8.m4,v 1.327.2.2.4.1 2010/12/21 17:10:29 kensmith Exp $
+.\"
+.Dd August 25, 2009
+.Dt PPP 8
+.Os
+.Sh NAME
+.Nm ppp
+.Nd Point to Point Protocol (a.k.a. user-ppp)
+.Sh SYNOPSIS
+.Nm
+.Op Fl Va mode
+.Op Fl nat
+.Op Fl quiet
+.Op Fl unit Ns Ar N
+.Op Ar system ...
+.Sh DESCRIPTION
+This is a user process
+.Em PPP
+software package.
+Normally,
+.Em PPP
+is implemented as a part of the kernel (e.g., as managed by
+.Xr pppd 8 )
+and it is thus somewhat hard to debug and/or modify its behaviour.
+However, in this implementation
+.Em PPP
+is done as a user process with the help of the
+tunnel device driver (tun).
+.Pp
+The
+.Fl nat
+flag does the equivalent of a
+.Dq nat enable yes ,
+enabling
+.Nm Ns No 's
+network address translation features.
+This allows
+.Nm
+to act as a NAT or masquerading engine for all machines on an internal
+LAN.
+ifdef({LOCALNAT},{},{Refer to
+.Xr libalias 3
+for details on the technical side of the NAT engine.
+})dnl
+Refer to the
+.Sx NETWORK ADDRESS TRANSLATION (PACKET ALIASING)
+section of this manual page for details on how to configure NAT in
+.Nm .
+.Pp
+The
+.Fl quiet
+flag tells
+.Nm
+to be silent at startup rather than displaying the mode and interface
+to standard output.
+.Pp
+The
+.Fl unit
+flag tells
+.Nm
+to only attempt to open
+.Pa /dev/tun Ns Ar N .
+Normally,
+.Nm
+will start with a value of 0 for
+.Ar N ,
+and keep trying to open a tunnel device by incrementing the value of
+.Ar N
+by one each time until it succeeds.
+If it fails three times in a row
+because the device file is missing, it gives up.
+.Pp
+The following
+.Va mode Ns No s
+are understood by
+.Nm :
+.Bl -tag -width XXX -offset XXX
+.It Fl auto
+.Nm
+opens the tun interface, configures it then goes into the background.
+The link is not brought up until outgoing data is detected on the tun
+interface at which point
+.Nm
+attempts to bring up the link.
+Packets received (including the first one) while
+.Nm
+is trying to bring the link up will remain queued for a default of
+2 minutes.
+See the
+.Dq set choked
+command below.
+.Pp
+In
+.Fl auto
+mode, at least one
+.Dq system
+must be given on the command line (see below) and a
+.Dq set ifaddr
+must be done in the system profile that specifies a peer IP address to
+use when configuring the interface.
+Something like
+.Dq 10.0.0.1/0
+is usually appropriate.
+See the
+.Dq pmdemand
+system in
+.Pa /usr/share/examples/ppp/ppp.conf.sample
+for an example.
+.It Fl background
+Here,
+.Nm
+attempts to establish a connection with the peer immediately.
+If it succeeds,
+.Nm
+goes into the background and the parent process returns an exit code
+of 0.
+If it fails,
+.Nm
+exits with a non-zero result.
+.It Fl foreground
+In foreground mode,
+.Nm
+attempts to establish a connection with the peer immediately, but never
+becomes a daemon.
+The link is created in background mode.
+This is useful if you wish to control
+.Nm Ns No 's
+invocation from another process.
+.It Fl direct
+This is used for communicating over an already established connection,
+usually when receiving incoming connections accepted by
+.Xr getty 8 .
+.Nm
+ignores the
+.Dq set device
+line and uses descriptor 0 as the link.
+.Nm
+will also ignore any configured chat scripts unless the
+.Dq force-scripts
+option has been enabled.
+.Pp
+If callback is configured,
+.Nm
+will use the
+.Dq set device
+information when dialing back.
+.Pp
+When run in
+.Fl direct
+mode,
+.Nm
+will behave slightly differently if descriptor 0 was created by
+.Xr pipe 2 .
+As pipes are not bi-directional, ppp will redirect all writes to descriptor
+1 (standard output), leaving only reads acting on descriptor 0.
+No special action is taken if descriptor 0 was created by
+.Xr socketpair 2 .
+.It Fl dedicated
+This option is designed for machines connected with a dedicated
+wire.
+.Nm
+will always keep the device open and will ignore any configured
+chat scripts unless the
+.Dq force-scripts
+option has been enabled.
+.It Fl ddial
+This mode is equivalent to
+.Fl auto
+mode except that
+.Nm
+will bring the link back up any time it is dropped for any reason.
+.It Fl interactive
+This is a no-op, and gives the same behaviour as if none of the above
+modes have been specified.
+.Nm
+loads any sections specified on the command line then provides an
+interactive prompt.
+.El
+.Pp
+One or more configuration entries or systems
+(as specified in
+.Pa /etc/ppp/ppp.conf )
+may also be specified on the command line.
+.Nm
+will read the
+.Dq default
+system from
+.Pa /etc/ppp/ppp.conf
+at startup, followed by each of the systems specified on the command line.
+.Sh Major Features
+.Bl -diag
+.It Provides an interactive user interface.
+Using its command mode, the user can
+easily enter commands to establish the connection with the remote end, check
+the status of connection and close the connection.
+All functions can also be optionally password protected for security.
+.It Supports both manual and automatic dialing.
+Interactive mode has a
+.Dq term
+command which enables you to talk to the device directly.
+When you are connected to the remote peer and it starts to talk
+.Em PPP ,
+.Nm
+detects it and switches to packet mode automatically.
+Once you have
+determined the proper sequence for connecting with the remote host, you
+can write a chat script to {define} the necessary dialing and login
+procedure for later convenience.
+.It Supports on-demand dialup capability.
+By using
+.Fl auto
+mode,
+.Nm
+will act as a daemon and wait for a packet to be sent over the
+.Em PPP
+link.
+When this happens, the daemon automatically dials and establishes the
+connection.
+In almost the same manner
+.Fl ddial
+mode (direct-dial mode) also automatically dials and establishes the
+connection.
+However, it differs in that it will dial the remote site
+any time it detects the link is down, even if there are no packets to be
+sent.
+This mode is useful for full-time connections where we worry less
+about line charges and more about being connected full time.
+A third
+.Fl dedicated
+mode is also available.
+This mode is targeted at a dedicated link between two machines.
+.Nm
+will never voluntarily quit from dedicated mode - you must send it the
+.Dq quit all
+command via its diagnostic socket.
+A
+.Dv SIGHUP
+will force an LCP renegotiation, and a
+.Dv SIGTERM
+will force it to exit.
+.It Supports client callback.
+.Nm
+can use either the standard LCP callback protocol or the Microsoft
+CallBack Control Protocol (ftp://ftp.microsoft.com/developr/rfc/cbcp.txt).
+.It Supports NAT or packet aliasing.
+Packet aliasing (a.k.a.\& IP masquerading) allows computers on a
+private, unregistered network to access the Internet.
+The
+.Em PPP
+host acts as a masquerading gateway.
+IP addresses as well as TCP and
+UDP port numbers are NAT'd for outgoing packets and de-NAT'd for
+returning packets.
+.It Supports background PPP connections.
+In background mode, if
+.Nm
+successfully establishes the connection, it will become a daemon.
+Otherwise, it will exit with an error.
+This allows the setup of
+scripts that wish to execute certain commands only if the connection
+is successfully established.
+.It Supports server-side PPP connections.
+In direct mode,
+.Nm
+acts as server which accepts incoming
+.Em PPP
+connections on stdin/stdout.
+.It "Supports PAP and CHAP (rfc 1994, 2433 and 2759) authentication.
+With PAP or CHAP, it is possible to skip the Unix style
+.Xr login 1
+procedure, and use the
+.Em PPP
+protocol for authentication instead.
+If the peer requests Microsoft CHAP authentication and
+.Nm
+is compiled with DES support, an appropriate MD4/DES response will be
+made.
+.It Supports RADIUS (rfc 2138 & 2548) authentication.
+An extension to PAP and CHAP,
+.Em \&R Ns No emote
+.Em \&A Ns No ccess
+.Em \&D Ns No ial
+.Em \&I Ns No n
+.Em \&U Ns No ser
+.Em \&S Ns No ervice
+allows authentication information to be stored in a central or
+distributed database along with various per-user framed connection
+characteristics.
+ifdef({LOCALRAD},{},{If
+.Xr libradius 3
+is available at compile time,
+.Nm
+will use it to make
+.Em RADIUS
+requests when configured to do so.
+})dnl
+.It Supports Proxy Arp.
+.Nm
+can be configured to make one or more proxy arp entries on behalf of
+the peer.
+This allows routing from the peer to the LAN without
+configuring each machine on that LAN.
+.It Supports packet filtering.
+User can {define} four kinds of filters: the
+.Em in
+filter for incoming packets, the
+.Em out
+filter for outgoing packets, the
+.Em dial
+filter to {define} a dialing trigger packet and the
+.Em alive
+filter for keeping a connection alive with the trigger packet.
+.It Tunnel driver supports bpf.
+The user can use
+.Xr tcpdump 1
+to check the packet flow over the
+.Em PPP
+link.
+.It Supports PPP over TCP and PPP over UDP.
+If a device name is specified as
+.Em host Ns No : Ns Em port Ns
+.Xo
+.Op / Ns tcp|udp ,
+.Xc
+.Nm
+will open a TCP or UDP connection for transporting data rather than using a
+conventional serial device.
+UDP connections force
+.Nm
+into synchronous mode.
+.It Supports PPP over ISDN.
+If
+.Nm
+is given a raw B-channel i4b device to open as a link, it is able to talk
+to the
+.Xr isdnd 8
+daemon to establish an ISDN connection.
+.It Supports PPP over Ethernet (rfc 2516).
+If
+.Nm
+is given a device specification of the format
+.No PPPoE: Ns Ar iface Ns Xo
+.Op \&: Ns Ar provider Ns
+.Xc
+and if
+.Xr netgraph 4
+is available,
+.Nm
+will attempt talk
+.Em PPP
+over Ethernet to
+.Ar provider
+using the
+.Ar iface
+network interface.
+.Pp
+On systems that do not support
+.Xr netgraph 4 ,
+an external program such as
+.Xr pppoed 8
+may be used.
+.It "Supports IETF draft Predictor-1 (rfc 1978) and DEFLATE (rfc 1979) compression."
+.Nm
+supports not only VJ-compression but also Predictor-1 and DEFLATE compression.
+Normally, a modem has built-in compression (e.g., v42.bis) and the system
+may receive higher data rates from it as a result of such compression.
+While this is generally a good thing in most other situations, this
+higher speed data imposes a penalty on the system by increasing the
+number of serial interrupts the system has to process in talking to the
+modem and also increases latency.
+Unlike VJ-compression, Predictor-1 and DEFLATE compression pre-compresses
+.Em all
+network traffic flowing through the link, thus reducing overheads to a
+minimum.
+.It Supports Microsoft's IPCP extensions (rfc 1877).
+Name Server Addresses and NetBIOS Name Server Addresses can be negotiated
+with clients using the Microsoft
+.Em PPP
+stack (i.e., Win95, WinNT)
+.It Supports Multi-link PPP (rfc 1990)
+It is possible to configure
+.Nm
+to open more than one physical connection to the peer, combining the
+bandwidth of all links for better throughput.
+.It Supports MPPE (draft-ietf-pppext-mppe)
+MPPE is Microsoft Point to Point Encryption scheme.
+It is possible to configure
+.Nm
+to participate in Microsoft's Windows VPN.
+For now,
+.Nm
+can only get encryption keys from CHAP 81 authentication.
+.Nm
+must be compiled with DES for MPPE to operate.
+.It Supports IPV6CP (rfc 2023).
+An IPv6 connection can be made in addition to or instead of the normal
+IPv4 connection.
+.El
+.Sh PERMISSIONS
+.Nm
+is installed as user
+.Dv root
+and group
+.Dv network ,
+with permissions
+.Dv 04554 .
+By default,
+.Nm
+will not run if the invoking user id is not zero.
+This may be overridden by using the
+.Dq allow users
+command in
+.Pa /etc/ppp/ppp.conf .
+When running as a normal user,
+.Nm
+switches to user id 0 in order to alter the system routing table, set up
+system lock files and read the ppp configuration files.
+All external commands (executed via the "shell" or "!bg" commands) are executed
+as the user id that invoked
+.Nm .
+Refer to the
+.Sq ID0
+logging facility if you are interested in what exactly is done as user id
+zero.
+.Sh GETTING STARTED
+When you first run
+.Nm
+you may need to deal with some initial configuration details.
+.Bl -bullet
+.It
+Make sure that your system has a group named
+.Dq network
+in the
+.Pa /etc/group
+file and that the group contains the names of all users expected to use
+.Nm .
+Refer to the
+.Xr group 5
+manual page for details.
+Each of these users must also be given access using the
+.Dq allow users
+command in
+.Pa /etc/ppp/ppp.conf .
+.It
+Create a log file.
+.Nm
+uses
+.Xr syslog 3
+to log information.
+A common log file name is
+.Pa /var/log/ppp.log .
+To make output go to this file, put the following lines in the
+.Pa /etc/syslog.conf
+file:
+.Bd -literal -offset indent
+!ppp
+*.*<TAB>/var/log/ppp.log
+.Ed
+.Pp
+It is possible to have more than one
+.Em PPP
+log file by creating a link to the
+.Nm
+executable:
+.Pp
+.Dl # cd /usr/sbin
+.Dl # ln ppp ppp0
+.Pp
+and using
+.Bd -literal -offset indent
+!ppp0
+*.*<TAB>/var/log/ppp0.log
+.Ed
+.Pp
+in
+.Pa /etc/syslog.conf .
+Do not forget to send a
+.Dv HUP
+signal to
+.Xr syslogd 8
+after altering
+.Pa /etc/syslog.conf .
+.It
+Although not strictly relevant to
+.Nm Ns No 's
+operation, you should configure your resolver so that it works correctly.
+This can be done by configuring a local DNS
+(using
+.Xr named 8 )
+or by adding the correct
+.Sq nameserver
+lines to the file
+.Pa /etc/resolv.conf .
+Refer to the
+.Xr resolv.conf 5
+manual page for details.
+.Pp
+Alternatively, if the peer supports it,
+.Nm
+can be configured to ask the peer for the nameserver address(es) and to
+update
+.Pa /etc/resolv.conf
+automatically.
+Refer to the
+.Dq enable dns
+and
+.Dq resolv
+commands below for details.
+.El
+.Sh MANUAL DIALING
+In the following examples, we assume that your machine name is
+.Dv awfulhak .
+when you invoke
+.Nm
+(see
+.Sx PERMISSIONS
+above) with no arguments, you are presented with a prompt:
+.Bd -literal -offset indent
+ppp ON awfulhak>
+.Ed
+.Pp
+The
+.Sq ON
+part of your prompt should always be in upper case.
+If it is in lower case, it means that you must supply a password using the
+.Dq passwd
+command.
+This only ever happens if you connect to a running version of
+.Nm
+and have not authenticated yourself using the correct password.
+.Pp
+You can start by specifying the device name and speed:
+.Bd -literal -offset indent
+ppp ON awfulhak> set device /dev/cuad0
+ppp ON awfulhak> set speed 38400
+.Ed
+.Pp
+Normally, hardware flow control (CTS/RTS) is used.
+However, under
+certain circumstances (as may happen when you are connected directly
+to certain PPP-capable terminal servers), this may result in
+.Nm
+hanging as soon as it tries to write data to your communications link
+as it is waiting for the CTS (clear to send) signal - which will never
+come.
+Thus, if you have a direct line and cannot seem to make a
+connection, try turning CTS/RTS off with
+.Dq set ctsrts off .
+If you need to do this, check the
+.Dq set accmap
+description below too - you will probably need to
+.Dq set accmap 000a0000 .
+.Pp
+Usually, parity is set to
+.Dq none ,
+and this is
+.Nm Ns No 's
+default.
+Parity is a rather archaic error checking mechanism that is no
+longer used because modern modems do their own error checking, and most
+link-layer protocols (that is what
+.Nm
+is) use much more reliable checking mechanisms.
+Parity has a relatively
+huge overhead (a 12.5% increase in traffic) and as a result, it is always
+disabled
+(set to
+.Dq none )
+when
+.Dv PPP
+is opened.
+However, some ISPs (Internet Service Providers) may use
+specific parity settings at connection time (before
+.Dv PPP
+is opened).
+Notably, Compuserve insist on even parity when logging in:
+.Bd -literal -offset indent
+ppp ON awfulhak> set parity even
+.Ed
+.Pp
+You can now see what your current device settings look like:
+.Bd -literal -offset indent
+ppp ON awfulhak> show physical
+Name: deflink
+ State:           closed
+ Device:          N/A
+ Link Type:       interactive
+ Connect Count:   0
+ Queued Packets:  0
+ Phone Number:    N/A
+
+Defaults:
+ Device List:     /dev/cuad0
+ Characteristics: 38400bps, cs8, even parity, CTS/RTS on
+
+Connect time: 0 secs
+0 octets in, 0 octets out
+Overall 0 bytes/sec
+ppp ON awfulhak>
+.Ed
+.Pp
+The term command can now be used to talk directly to the device:
+.Bd -literal -offset indent
+ppp ON awfulhak> term
+at
+OK
+atdt123456
+CONNECT
+login: myispusername
+Password: myisppassword
+Protocol: ppp
+.Ed
+.Pp
+When the peer starts to talk in
+.Em PPP ,
+.Nm
+detects this automatically and returns to command mode.
+.Bd -literal -offset indent
+ppp ON awfulhak>               # No link has been established
+Ppp ON awfulhak>               # We've connected & finished LCP
+PPp ON awfulhak>               # We've authenticated
+PPP ON awfulhak>               # We've agreed IP numbers
+.Ed
+.Pp
+If it does not, it is probable that the peer is waiting for your end to
+start negotiating.
+To force
+.Nm
+to start sending
+.Em PPP
+configuration packets to the peer, use the
+.Dq ~p
+command to drop out of terminal mode and enter packet mode.
+.Pp
+If you never even receive a login prompt, it is quite likely that the
+peer wants to use PAP or CHAP authentication instead of using Unix-style
+login/password authentication.
+To set things up properly, drop back to
+the prompt and set your authentication name and key, then reconnect:
+.Bd -literal -offset indent
+~.
+ppp ON awfulhak> set authname myispusername
+ppp ON awfulhak> set authkey myisppassword
+ppp ON awfulhak> term
+at
+OK
+atdt123456
+CONNECT
+.Ed
+.Pp
+You may need to tell ppp to initiate negotiations with the peer here too:
+.Bd -literal -offset indent
+~p
+ppp ON awfulhak>               # No link has been established
+Ppp ON awfulhak>               # We've connected & finished LCP
+PPp ON awfulhak>               # We've authenticated
+PPP ON awfulhak>               # We've agreed IP numbers
+.Ed
+.Pp
+You are now connected!
+Note that
+.Sq PPP
+in the prompt has changed to capital letters to indicate that you have
+a peer connection.
+If only some of the three Ps go uppercase, wait until
+either everything is uppercase or lowercase.
+If they revert to lowercase, it means that
+.Nm
+could not successfully negotiate with the peer.
+A good first step for troubleshooting at this point would be to
+.Bd -literal -offset indent
+ppp ON awfulhak> set log local phase lcp ipcp
+.Ed
+.Pp
+and try again.
+Refer to the
+.Dq set log
+command description below for further details.
+If things fail at this point,
+it is quite important that you turn logging on and try again.
+It is also
+important that you note any prompt changes and report them to anyone trying
+to help you.
+.Pp
+When the link is established, the show command can be used to see how
+things are going:
+.Bd -literal -offset indent
+PPP ON awfulhak> show physical
+* Modem related information is shown here *
+PPP ON awfulhak> show ccp
+* CCP (compression) related information is shown here *
+PPP ON awfulhak> show lcp
+* LCP (line control) related information is shown here *
+PPP ON awfulhak> show ipcp
+* IPCP (IP) related information is shown here *
+PPP ON awfulhak> show ipv6cp
+* IPV6CP (IPv6) related information is shown here *
+PPP ON awfulhak> show link
+* Link (high level) related information is shown here *
+PPP ON awfulhak> show bundle
+* Logical (high level) connection related information is shown here *
+.Ed
+.Pp
+At this point, your machine has a host route to the peer.
+This means
+that you can only make a connection with the host on the other side
+of the link.
+If you want to add a default route entry (telling your
+machine to send all packets without another routing entry to the other
+side of the
+.Em PPP
+link), enter the following command:
+.Bd -literal -offset indent
+PPP ON awfulhak> add default HISADDR
+.Ed
+.Pp
+The string
+.Sq HISADDR
+represents the IP address of the connected peer.
+If the
+.Dq add
+command fails due to an existing route, you can overwrite the existing
+route using:
+.Bd -literal -offset indent
+PPP ON awfulhak> add! default HISADDR
+.Ed
+.Pp
+This command can also be executed before actually making the connection.
+If a new IP address is negotiated at connection time,
+.Nm
+will update your default route accordingly.
+.Pp
+You can now use your network applications (ping, telnet, ftp, etc.)
+in other windows or terminals on your machine.
+If you wish to reuse the current terminal, you can put
+.Nm
+into the background using your standard shell suspend and background
+commands (usually
+.Dq ^Z
+followed by
+.Dq bg ) .
+.Pp
+Refer to the
+.Sx PPP COMMAND LIST
+section for details on all available commands.
+.Sh AUTOMATIC DIALING
+To use automatic dialing, you must prepare some Dial and Login chat scripts.
+See the example definitions in
+.Pa /usr/share/examples/ppp/ppp.conf.sample
+(the format of
+.Pa /etc/ppp/ppp.conf
+is pretty simple).
+Each line contains one comment, inclusion, label or command:
+.Bl -bullet
+.It
+A line starting with a
+.Pq Dq #
+character is treated as a comment line.
+Leading whitespace are ignored when identifying comment lines.
+.It
+An inclusion is a line beginning with the word
+.Sq {!include} .
+It must have one argument - the file to {include}.
+You may wish to
+.Dq {!include} ~/.ppp.conf
+for compatibility with older versions of
+.Nm .
+.It
+A label name starts in the first column and is followed by
+a colon
+.Pq Dq \&: .
+.It
+A command line must contain a space or tab in the first column.
+.It
+A string starting with the
+.Dq $
+character is substituted with the value of the environment variable by
+the same name.
+Likewise, a string starting with the
+.Dq ~
+character is substituted with the full path to the home directory of
+the user account by the same name, and the
+.Dq ~
+character by itself is substituted with the full path to the home directory
+of the current user.
+If you want to include a literal
+.Dq $
+or
+.Dq ~
+character in a command or argument, enclose them in double quotes, e.g.,
+.Bd -literal -offset indent
+set password "pa$ss~word"
+.Ed
+.El
+.Pp
+The
+.Pa /etc/ppp/ppp.conf
+file should consist of at least a
+.Dq default
+section.
+This section is always executed.
+It should also contain
+one or more sections, named according to their purpose, for example,
+.Dq MyISP
+would represent your ISP, and
+.Dq ppp-in
+would represent an incoming
+.Nm
+configuration.
+You can now specify the destination label name when you invoke
+.Nm .
+Commands associated with the
+.Dq default
+label are executed, followed by those associated with the destination
+label provided.
+When
+.Nm
+is started with no arguments, the
+.Dq default
+section is still executed.
+The load command can be used to manually load a section from the
+.Pa /etc/ppp/ppp.conf
+file:
+.Bd -literal -offset indent
+ppp ON awfulhak> load MyISP
+.Ed
+.Pp
+Note, no action is taken by
+.Nm
+after a section is loaded, whether it is the result of passing a label on
+the command line or using the
+.Dq load
+command.
+Only the commands specified for that label in the configuration
+file are executed.
+However, when invoking
+.Nm
+with the
+.Fl background ,
+.Fl ddial ,
+or
+.Fl dedicated
+switches, the link mode tells
+.Nm
+to establish a connection.
+Refer to the
+.Dq set mode
+command below for further details.
+.Pp
+Once the connection is made, the
+.Sq ppp
+portion of the prompt will change to
+.Sq PPP :
+.Bd -literal -offset indent
+# ppp MyISP
+\&...
+ppp ON awfulhak> dial
+Ppp ON awfulhak>
+PPp ON awfulhak>
+PPP ON awfulhak>
+.Ed
+.Pp
+The Ppp prompt indicates that
+.Nm
+has entered the authentication phase.
+The PPp prompt indicates that
+.Nm
+has entered the network phase.
+The PPP prompt indicates that
+.Nm
+has successfully negotiated a network layer protocol and is in
+a usable state.
+.Pp
+If the
+.Pa /etc/ppp/ppp.linkup
+file is available, its contents are executed
+when the
+.Em PPP
+connection is established.
+See the provided
+.Dq pmdemand
+example in
+.Pa /usr/share/examples/ppp/ppp.conf.sample
+which runs a script in the background after the connection is established
+(refer to the
+.Dq shell
+and
+.Dq bg
+commands below for a description of possible substitution strings).
+Similarly, when a connection is closed, the contents of the
+.Pa /etc/ppp/ppp.linkdown
+file are executed.
+Both of these files have the same format as
+.Pa /etc/ppp/ppp.conf .
+.Pp
+In previous versions of
+.Nm ,
+it was necessary to re-add routes such as the default route in the
+.Pa ppp.linkup
+file.
+.Nm
+supports
+.Sq sticky routes ,
+where all routes that contain the
+.Dv HISADDR ,
+.Dv MYADDR ,
+.Dv HISADDR6
+or
+.Dv MYADDR6
+literals will automatically be updated when the values of these variables
+change.
+.Sh BACKGROUND DIALING
+If you want to establish a connection using
+.Nm
+non-interactively (such as from a
+.Xr crontab 5
+entry or an
+.Xr at 1
+job) you should use the
+.Fl background
+option.
+When
+.Fl background
+is specified,
+.Nm
+attempts to establish the connection immediately.
+If multiple phone
+numbers are specified, each phone number will be tried once.
+If the attempt fails,
+.Nm
+exits immediately with a non-zero exit code.
+If it succeeds, then
+.Nm
+becomes a daemon, and returns an exit status of zero to its caller.
+The daemon exits automatically if the connection is dropped by the
+remote system, or it receives a
+.Dv TERM
+signal.
+.Sh DIAL ON DEMAND
+Demand dialing is enabled with the
+.Fl auto
+or
+.Fl ddial
+options.
+You must also specify the destination label in
+.Pa /etc/ppp/ppp.conf
+to use.
+It must contain the
+.Dq set ifaddr
+command to {define} the remote peers IP address.
+(refer to
+.Pa /usr/share/examples/ppp/ppp.conf.sample )
+.Bd -literal -offset indent
+# ppp -auto pmdemand
+.Ed
+.Pp
+When
+.Fl auto
+or
+.Fl ddial
+is specified,
+.Nm
+runs as a daemon but you can still configure or examine its
+configuration by using the
+.Dq set server
+command in
+.Pa /etc/ppp/ppp.conf ,
+(for example,
+.Dq Li "set server +3000 mypasswd" )
+and connecting to the diagnostic port as follows:
+.Bd -literal -offset indent
+# pppctl 3000	(assuming tun0)
+Password:
+PPP ON awfulhak> show who
+tcp (127.0.0.1:1028) *
+.Ed
+.Pp
+The
+.Dq show who
+command lists users that are currently connected to
+.Nm
+itself.
+If the diagnostic socket is closed or changed to a different
+socket, all connections are immediately dropped.
+.Pp
+In
+.Fl auto
+mode, when an outgoing packet is detected,
+.Nm
+will perform the dialing action (chat script) and try to connect
+with the peer.
+In
+.Fl ddial
+mode, the dialing action is performed any time the line is found
+to be down.
+If the connect fails, the default behaviour is to wait 30 seconds
+and then attempt to connect when another outgoing packet is detected.
+This behaviour can be changed using the
+.Dq set redial
+command:
+.Pp
+.No set redial Ar secs Ns Xo
+.Oo + Ns Ar inc Ns
+.Op - Ns Ar max Ns
+.Oc Ns Op . Ns Ar next
+.Op Ar attempts
+.Xc
+.Pp
+.Bl -tag -width attempts -compact
+.It Ar secs
+is the number of seconds to wait before attempting
+to connect again.
+If the argument is the literal string
+.Sq Li random ,
+the delay period is a random value between 1 and 30 seconds inclusive.
+.It Ar inc
+is the number of seconds that
+.Ar secs
+should be incremented each time a new dial attempt is made.
+The timeout reverts to
+.Ar secs
+only after a successful connection is established.
+The default value for
+.Ar inc
+is zero.
+.It Ar max
+is the maximum number of times
+.Nm
+should increment
+.Ar secs .
+The default value for
+.Ar max
+is 10.
+.It Ar next
+is the number of seconds to wait before attempting
+to dial the next number in a list of numbers (see the
+.Dq set phone
+command).
+The default is 3 seconds.
+Again, if the argument is the literal string
+.Sq Li random ,
+the delay period is a random value between 1 and 30 seconds.
+.It Ar attempts
+is the maximum number of times to try to connect for each outgoing packet
+that triggers a dial.
+The previous value is unchanged if this parameter is omitted.
+If a value of zero is specified for
+.Ar attempts ,
+.Nm
+will keep trying until a connection is made.
+.El
+.Pp
+So, for example:
+.Bd -literal -offset indent
+set redial 10.3 4
+.Ed
+.Pp
+will attempt to connect 4 times for each outgoing packet that causes
+a dial attempt with a 3 second delay between each number and a 10 second
+delay after all numbers have been tried.
+If multiple phone numbers
+are specified, the total number of attempts is still 4 (it does not
+attempt each number 4 times).
+.Pp
+Alternatively,
+.Pp
+.Bd -literal -offset indent
+set redial 10+10-5.3 20
+.Ed
+.Pp
+tells
+.Nm
+to attempt to connect 20 times.
+After the first attempt,
+.Nm
+pauses for 10 seconds.
+After the next attempt it pauses for 20 seconds
+and so on until after the sixth attempt it pauses for 1 minute.
+The next 14 pauses will also have a duration of one minute.
+If
+.Nm
+connects, disconnects and fails to connect again, the timeout starts again
+at 10 seconds.
+.Pp
+Modifying the dial delay is very useful when running
+.Nm
+in
+.Fl auto
+mode on both ends of the link.
+If each end has the same timeout,
+both ends wind up calling each other at the same time if the link
+drops and both ends have packets queued.
+At some locations, the serial link may not be reliable, and carrier
+may be lost at inappropriate times.
+It is possible to have
+.Nm
+redial should carrier be unexpectedly lost during a session.
+.Bd -literal -offset indent
+set reconnect timeout ntries
+.Ed
+.Pp
+This command tells
+.Nm
+to re-establish the connection
+.Ar ntries
+times on loss of carrier with a pause of
+.Ar timeout
+seconds before each try.
+For example,
+.Bd -literal -offset indent
+set reconnect 3 5
+.Ed
+.Pp
+tells
+.Nm
+that on an unexpected loss of carrier, it should wait
+.Ar 3
+seconds before attempting to reconnect.
+This may happen up to
+.Ar 5
+times before
+.Nm
+gives up.
+The default value of ntries is zero (no reconnect).
+Care should be taken with this option.
+If the local timeout is slightly
+longer than the remote timeout, the reconnect feature will always be
+triggered (up to the given number of times) after the remote side
+times out and hangs up.
+NOTE: In this context, losing too many LQRs constitutes a loss of
+carrier and will trigger a reconnect.
+If the
+.Fl background
+flag is specified, all phone numbers are dialed at most once until
+a connection is made.
+The next number redial period specified with the
+.Dq set redial
+command is honoured, as is the reconnect tries value.
+If your redial
+value is less than the number of phone numbers specified, not all
+the specified numbers will be tried.
+To terminate the program, type
+.Bd -literal -offset indent
+PPP ON awfulhak> close
+ppp ON awfulhak> quit all
+.Ed
+.Pp
+A simple
+.Dq quit
+command will terminate the
+.Xr pppctl 8
+or
+.Xr telnet 1
+connection but not the
+.Nm
+program itself.
+You must use
+.Dq quit all
+to terminate
+.Nm
+as well.
+.Sh RECEIVING INCOMING PPP CONNECTIONS (Method 1)
+To handle an incoming
+.Em PPP
+connection request, follow these steps:
+.Bl -enum
+.It
+Make sure the modem and (optionally)
+.Pa /etc/rc.serial
+is configured correctly.
+.Bl -bullet -compact
+.It
+Use Hardware Handshake (CTS/RTS) for flow control.
+.It
+Modem should be set to NO echo back (ATE0) and NO results string (ATQ1).
+.El
+.Pp
+.It
+Edit
+.Pa /etc/ttys
+to enable a
+.Xr getty 8
+on the port where the modem is attached.
+For example:
+.Pp
+.Dl ttyd1 Qo /usr/libexec/getty std.38400 Qc dialup on secure
+.Pp
+Do not forget to send a
+.Dv HUP
+signal to the
+.Xr init 8
+process to start the
+.Xr getty 8 :
+.Pp
+.Dl # kill -HUP 1
+.Pp
+It is usually also necessary to train your modem to the same DTR speed
+as the getty:
+.Bd -literal -offset indent
+# ppp
+ppp ON awfulhak> set device /dev/cuad1
+ppp ON awfulhak> set speed 38400
+ppp ON awfulhak> term
+deflink: Entering terminal mode on /dev/cuad1
+Type `~?' for help
+at
+OK
+at
+OK
+atz
+OK
+at
+OK
+~.
+ppp ON awfulhak> quit
+.Ed
+.It
+Create a
+.Pa /usr/local/bin/ppplogin
+file with the following contents:
+.Bd -literal -offset indent
+#! /bin/sh
+exec /usr/sbin/ppp -direct incoming
+.Ed
+.Pp
+Direct mode
+.Pq Fl direct
+lets
+.Nm
+work with stdin and stdout.
+You can also use
+.Xr pppctl 8
+to connect to a configured diagnostic port, in the same manner as with
+client-side
+.Nm .
+.Pp
+Here, the
+.Ar incoming
+section must be set up in
+.Pa /etc/ppp/ppp.conf .
+.Pp
+Make sure that the
+.Ar incoming
+section contains the
+.Dq allow users
+command as appropriate.
+.It
+Prepare an account for the incoming user.
+.Bd -literal
+ppp:xxxx:66:66:PPP Login User:/home/ppp:/usr/local/bin/ppplogin
+.Ed
+.Pp
+Refer to the manual entries for
+.Xr adduser 8
+and
+.Xr vipw 8
+for details.
+.It
+Support for IPCP Domain Name Server and NetBIOS Name Server negotiation
+can be enabled using the
+.Dq accept dns
+and
+.Dq set nbns
+commands.
+Refer to their descriptions below.
+.El
+.Sh RECEIVING INCOMING PPP CONNECTIONS (Method 2)
+This method differs in that we use
+.Nm
+to authenticate the connection rather than
+.Xr login 1 :
+.Bl -enum
+.It
+Configure your default section in
+.Pa /etc/gettytab
+with automatic ppp recognition by specifying the
+.Dq pp
+capability:
+.Bd -literal
+default:\\
+	:pp=/usr/local/bin/ppplogin:\\
+	.....
+.Ed
+.It
+Configure your serial device(s), enable a
+.Xr getty 8
+and create
+.Pa /usr/local/bin/ppplogin
+as in the first three steps for method 1 above.
+.It
+Add either
+.Dq enable chap
+or
+.Dq enable pap
+(or both)
+to
+.Pa /etc/ppp/ppp.conf
+under the
+.Sq incoming
+label (or whatever label
+.Pa ppplogin
+uses).
+.It
+Create an entry in
+.Pa /etc/ppp/ppp.secret
+for each incoming user:
+.Bd -literal
+Pfred<TAB>xxxx
+Pgeorge<TAB>yyyy
+.Ed
+.El
+.Pp
+Now, as soon as
+.Xr getty 8
+detects a ppp connection (by recognising the HDLC frame headers), it runs
+.Dq /usr/local/bin/ppplogin .
+.Pp
+It is
+.Em VITAL
+that either PAP or CHAP are enabled as above.
+If they are not, you are
+allowing anybody to establish a ppp session with your machine
+.Em without
+a password, opening yourself up to all sorts of potential attacks.
+.Sh AUTHENTICATING INCOMING CONNECTIONS
+Normally, the receiver of a connection requires that the peer
+authenticates itself.
+This may be done using
+.Xr login 1 ,
+but alternatively, you can use PAP or CHAP.
+CHAP is the more secure of the two, but some clients may not support it.
+Once you decide which you wish to use, add the command
+.Sq enable chap
+or
+.Sq enable pap
+to the relevant section of
+.Pa ppp.conf .
+.Pp
+You must then configure the
+.Pa /etc/ppp/ppp.secret
+file.
+This file contains one line per possible client, each line
+containing up to five fields:
+.Pp
+.Ar name Ar key Oo
+.Ar hisaddr Op Ar label Op Ar callback-number
+.Oc
+.Pp
+The
+.Ar name
+and
+.Ar key
+specify the client username and password.
+If
+.Ar key
+is
+.Dq \&*
+and PAP is being used,
+.Nm
+will look up the password database
+.Pq Xr passwd 5
+when authenticating.
+If the client does not offer a suitable response based on any
+.Ar name Ns No / Ns Ar key
+combination in
+.Pa ppp.secret ,
+authentication fails.
+.Pp
+If authentication is successful,
+.Ar hisaddr
+(if specified)
+is used when negotiating IP numbers.
+See the
+.Dq set ifaddr
+command for details.
+.Pp
+If authentication is successful and
+.Ar label
+is specified, the current system label is changed to match the given
+.Ar label .
+This will change the subsequent parsing of the
+.Pa ppp.linkup
+and
+.Pa ppp.linkdown
+files.
+.Pp
+If authentication is successful and
+.Ar callback-number
+is specified and
+.Dq set callback
+has been used in
+.Pa ppp.conf ,
+the client will be called back on the given number.
+If CBCP is being used,
+.Ar callback-number
+may also contain a list of numbers or a
+.Dq \&* ,
+as if passed to the
+.Dq set cbcp
+command.
+The value will be used in
+.Nm Ns No 's
+subsequent CBCP phase.
+.Sh PPP OVER TCP and UDP (a.k.a Tunnelling)
+Instead of running
+.Nm
+over a serial link, it is possible to
+use a TCP connection instead by specifying the host, port and protocol as the
+device:
+.Pp
+.Dl set device ui-gate:6669/tcp
+.Pp
+Instead of opening a serial device,
+.Nm
+will open a TCP connection to the given machine on the given
+socket.
+It should be noted however that
+.Nm
+does not use the telnet protocol and will be unable to negotiate
+with a telnet server.
+You should set up a port for receiving this
+.Em PPP
+connection on the receiving machine (ui-gate).
+This is done by first updating
+.Pa /etc/services
+to name the service:
+.Pp
+.Dl ppp-in 6669/tcp # Incoming PPP connections over TCP
+.Pp
+and updating
+.Pa /etc/inetd.conf
+to tell
+.Xr inetd 8
+how to deal with incoming connections on that port:
+.Pp
+.Dl ppp-in stream tcp nowait root /usr/sbin/ppp ppp -direct ppp-in
+.Pp
+Do not forget to send a
+.Dv HUP
+signal to
+.Xr inetd 8
+after you have updated
+.Pa /etc/inetd.conf .
+Here, we use a label named
+.Dq ppp-in .
+The entry in
+.Pa /etc/ppp/ppp.conf
+on ui-gate (the receiver) should contain the following:
+.Bd -literal -offset indent
+ppp-in:
+ set timeout 0
+ set ifaddr 10.0.4.1 10.0.4.2
+.Ed
+.Pp
+and the entry in
+.Pa /etc/ppp/ppp.linkup
+should contain:
+.Bd -literal -offset indent
+ppp-in:
+ add 10.0.1.0/24 HISADDR
+.Ed
+.Pp
+It is necessary to put the
+.Dq add
+command in
+.Pa ppp.linkup
+to ensure that the route is only added after
+.Nm
+has negotiated and assigned addresses to its interface.
+.Pp
+You may also want to enable PAP or CHAP for security.
+To enable PAP, add the following line:
+.Bd -literal -offset indent
+ enable PAP
+.Ed
+.Pp
+You will also need to create the following entry in
+.Pa /etc/ppp/ppp.secret :
+.Bd -literal -offset indent
+MyAuthName MyAuthPasswd
+.Ed
+.Pp
+If
+.Ar MyAuthPasswd
+is a
+.Dq * ,
+the password is looked up in the
+.Xr passwd 5
+database.
+.Pp
+The entry in
+.Pa /etc/ppp/ppp.conf
+on awfulhak (the initiator) should contain the following:
+.Bd -literal -offset indent
+ui-gate:
+ set escape 0xff
+ set device ui-gate:ppp-in/tcp
+ set dial
+ set timeout 30
+ set log Phase Chat Connect hdlc LCP IPCP IPV6CP CCP tun
+ set ifaddr 10.0.4.2 10.0.4.1
+.Ed
+.Pp
+with the route setup in
+.Pa /etc/ppp/ppp.linkup :
+.Bd -literal -offset indent
+ui-gate:
+ add 10.0.2.0/24 HISADDR
+.Ed
+.Pp
+Again, if you are enabling PAP, you will also need this in the
+.Pa /etc/ppp/ppp.conf
+profile:
+.Bd -literal -offset indent
+ set authname MyAuthName
+ set authkey MyAuthKey
+.Ed
+.Pp
+We are assigning the address of 10.0.4.1 to ui-gate, and the address
+10.0.4.2 to awfulhak.
+To open the connection, just type
+.Pp
+.Dl awfulhak # ppp -background ui-gate
+.Pp
+The result will be an additional "route" on awfulhak to the
+10.0.2.0/24 network via the TCP connection, and an additional
+"route" on ui-gate to the 10.0.1.0/24 network.
+The networks are effectively bridged - the underlying TCP
+connection may be across a public network (such as the
+Internet), and the
+.Em PPP
+traffic is conceptually encapsulated
+(although not packet by packet) inside the TCP stream between
+the two gateways.
+.Pp
+The major disadvantage of this mechanism is that there are two
+"guaranteed delivery" mechanisms in place - the underlying TCP
+stream and whatever protocol is used over the
+.Em PPP
+link - probably TCP again.
+If packets are lost, both levels will
+get in each others way trying to negotiate sending of the missing
+packet.
+.Pp
+To avoid this overhead, it is also possible to do all this using
+UDP instead of TCP as the transport by simply changing the protocol
+from "tcp" to "udp".
+When using UDP as a transport,
+.Nm
+will operate in synchronous mode.
+This is another gain as the incoming
+data does not have to be rearranged into packets.
+.Pp
+Care should be taken when adding a default route through a tunneled
+setup like this.
+It is quite common for the default route
+(added in
+.Pa /etc/ppp/ppp.linkup )
+to end up routing the link's TCP connection through the tunnel,
+effectively garrotting the connection.
+To avoid this, make sure you add a static route for the benefit of
+the link:
+.Bd -literal -offset indent
+ui-gate:
+ set escape 0xff
+ set device ui-gate:ppp-in/tcp
+ add ui-gate x.x.x.x
+ .....
+.Ed
+.Pp
+where
+.Dq x.x.x.x
+is the IP number that your route to
+.Dq ui-gate
+would normally use.
+.Pp
+When routing your connection accross a public network such as the Internet,
+it is preferable to encrypt the data.
+This can be done with the help of the MPPE protocol, although currently this
+means that you will not be able to also compress the traffic as MPPE is
+implemented as a compression layer (thank Microsoft for this).
+To enable MPPE encryption, add the following lines to
+.Pa /etc/ppp/ppp.conf
+on the server:
+.Bd -literal -offset indent
+  enable MSCHAPv2
+  disable deflate pred1
+  deny deflate pred1
+.Ed
+.Pp
+ensuring that you have put the requisite entry in
+.Pa /etc/ppp/ppp.secret
+(MSCHAPv2 is challenge based, so
+.Xr passwd 5
+cannot be used)
+.Pp
+MSCHAPv2 and MPPE are accepted by default, so the client end should work
+without any additional changes (although ensure you have
+.Dq set authname
+and
+.Dq set authkey
+in your profile).
+.Sh NETWORK ADDRESS TRANSLATION (PACKET ALIASING)
+The
+.Fl nat
+command line option enables network address translation (a.k.a.\& packet
+aliasing).
+This allows the
+.Nm
+host to act as a masquerading gateway for other computers over
+a local area network.
+Outgoing IP packets are NAT'd so that they appear to come from the
+.Nm
+host, and incoming packets are de-NAT'd so that they are routed
+to the correct machine on the local area network.
+NAT allows computers on private, unregistered subnets to have Internet
+access, although they are invisible from the outside world.
+In general, correct
+.Nm
+operation should first be verified with network address translation disabled.
+Then, the
+.Fl nat
+option should be switched on, and network applications (web browser,
+.Xr telnet 1 ,
+.Xr ftp 1 ,
+.Xr ping 8 ,
+.Xr traceroute 8 )
+should be checked on the
+.Nm
+host.
+Finally, the same or similar applications should be checked on other
+computers in the LAN.
+If network applications work correctly on the
+.Nm
+host, but not on other machines in the LAN, then the masquerading
+software is working properly, but the host is either not forwarding
+or possibly receiving IP packets.
+Check that IP forwarding is enabled in
+.Pa /etc/rc.conf
+and that other machines have designated the
+.Nm
+host as the gateway for the LAN.
+.Sh PACKET FILTERING
+This implementation supports packet filtering.
+There are four kinds of
+filters: the
+.Em in
+filter, the
+.Em out
+filter, the
+.Em dial
+filter and the
+.Em alive
+filter.
+Here are the basics:
+.Bl -bullet
+.It
+A filter definition has the following syntax:
+.Pp
+set filter
+.Ar name
+.Ar rule-no
+.Ar action
+.Op !\&
+.Oo
+.Op host
+.Ar src_addr Ns Op / Ns Ar width
+.Op Ar dst_addr Ns Op / Ns Ar width
+.Oc
+.Ar [ proto Op src Ar cmp port
+.Op dst Ar cmp port
+.Op estab
+.Op syn
+.Op finrst
+.Op timeout Ar secs ]
+.Bl -enum
+.It
+.Ar Name
+should be one of
+.Sq in ,
+.Sq out ,
+.Sq dial
+or
+.Sq alive .
+.It
+.Ar Rule-no
+is a numeric value between
+.Sq 0
+and
+.Sq 39
+specifying the rule number.
+Rules are specified in numeric order according to
+.Ar rule-no ,
+but only if rule
+.Sq 0
+is defined.
+.It
+.Ar Action
+may be specified as
+.Sq permit
+or
+.Sq deny ,
+in which case, if a given packet matches the rule, the associated action
+is taken immediately.
+.Ar Action
+can also be specified as
+.Sq clear
+to clear the action associated with that particular rule, or as a new
+rule number greater than the current rule.
+In this case, if a given
+packet matches the current rule, the packet will next be matched against
+the new rule number (rather than the next rule number).
+.Pp
+The
+.Ar action
+may optionally be followed with an exclamation mark
+.Pq Dq !\& ,
+telling
+.Nm
+to reverse the sense of the following match.
+.It
+.Op Ar src_addr Ns Op / Ns Ar width
+and
+.Op Ar dst_addr Ns Op / Ns Ar width
+are the source and destination IP number specifications.
+If
+.Op / Ns Ar width
+is specified, it gives the number of relevant netmask bits,
+allowing the specification of an address range.
+.Pp
+Either
+.Ar src_addr
+or
+.Ar dst_addr
+may be given the values
+.Dv MYADDR ,
+.Dv HISADDR ,
+.Dv MYADDR6
+or
+.Dv HISADDR6
+(refer to the description of the
+.Dq bg
+command for a description of these values).
+When these values are used,
+the filters will be updated any time the values change.
+This is similar to the behaviour of the
+.Dq add
+command below.
+.It
+.Ar Proto
+may be any protocol from
+.Xr protocols 5 .
+.It
+.Ar Cmp
+is one of
+.Sq \&lt ,
+.Sq \&eq
+or
+.Sq \&gt ,
+meaning less-than, equal and greater-than respectively.
+.Ar Port
+can be specified as a numeric port or by service name from
+.Pa /etc/services .
+.It
+The
+.Sq estab ,
+.Sq syn ,
+and
+.Sq finrst
+flags are only allowed when
+.Ar proto
+is set to
+.Sq tcp ,
+and represent the TH_ACK, TH_SYN and TH_FIN or TH_RST TCP flags respectively.
+.It
+The timeout value adjusts the current idle timeout to at least
+.Ar secs
+seconds.
+If a timeout is given in the alive filter as well as in the in/out
+filter, the in/out value is used.
+If no timeout is given, the default timeout (set using
+.Ic set timeout
+and defaulting to 180 seconds) is used.
+.El
+.Pp
+.It
+Each filter can hold up to 40 rules, starting from rule 0.
+The entire rule set is not effective until rule 0 is defined,
+i.e., the default is to allow everything through.
+.It
+If no rule in a defined set of rules matches a packet, that packet will
+be discarded (blocked).
+If there are no rules in a given filter, the packet will be permitted.
+.It
+It is possible to filter based on the payload of UDP frames where those
+frames contain a
+.Em PROTO_IP
+.Em PPP
+frame header.
+See the
+.Ar filter-decapsulation
+option below for further details.
+.It
+Use
+.Dq set filter Ar name No -1
+to flush all rules.
+.El
+.Pp
+See
+.Pa /usr/share/examples/ppp/ppp.conf.sample .
+.Sh SETTING THE IDLE TIMER
+To check/set the idle timer, use the
+.Dq show bundle
+and
+.Dq set timeout
+commands:
+.Bd -literal -offset indent
+ppp ON awfulhak> set timeout 600
+.Ed
+.Pp
+The timeout period is measured in seconds, the default value for which
+is 180 seconds
+(or 3 min).
+To disable the idle timer function, use the command
+.Bd -literal -offset indent
+ppp ON awfulhak> set timeout 0
+.Ed
+.Pp
+In
+.Fl ddial
+and
+.Fl dedicated
+modes, the idle timeout is ignored.
+In
+.Fl auto
+mode, when the idle timeout causes the
+.Em PPP
+session to be
+closed, the
+.Nm
+program itself remains running.
+Another trigger packet will cause it to attempt to re-establish the link.
+.Sh PREDICTOR-1 and DEFLATE COMPRESSION
+.Nm
+supports both Predictor type 1 and deflate compression.
+By default,
+.Nm
+will attempt to use (or be willing to accept) both compression protocols
+when the peer agrees
+(or requests them).
+The deflate protocol is preferred by
+.Nm .
+Refer to the
+.Dq disable
+and
+.Dq deny
+commands if you wish to disable this functionality.
+.Pp
+It is possible to use a different compression algorithm in each direction
+by using only one of
+.Dq disable deflate
+and
+.Dq deny deflate
+(assuming that the peer supports both algorithms).
+.Pp
+By default, when negotiating DEFLATE,
+.Nm
+will use a window size of 15.
+Refer to the
+.Dq set deflate
+command if you wish to change this behaviour.
+.Pp
+A special algorithm called DEFLATE24 is also available, and is disabled
+and denied by default.
+This is exactly the same as DEFLATE except that
+it uses CCP ID 24 to negotiate.
+This allows
+.Nm
+to successfully negotiate DEFLATE with
+.Nm pppd
+version 2.3.*.
+.Sh CONTROLLING IP ADDRESS
+For IPv4,
+.Nm
+uses IPCP to negotiate IP addresses.
+Each side of the connection
+specifies the IP address that it is willing to use, and if the requested
+IP address is acceptable then
+.Nm
+returns an ACK to the requester.
+Otherwise,
+.Nm
+returns NAK to suggest that the peer use a different IP address.
+When
+both sides of the connection agree to accept the received request (and
+send an ACK), IPCP is set to the open state and a network level connection
+is established.
+To control this IPCP behaviour, this implementation has the
+.Dq set ifaddr
+command for defining the local and remote IP address:
+.Bd -ragged -offset indent
+.No set ifaddr Oo Ar src_addr Ns
+.Op / Ns Ar \&nn
+.Oo Ar dst_addr Ns Op / Ns Ar \&nn
+.Oo Ar netmask
+.Op Ar trigger_addr
+.Oc
+.Oc
+.Oc
+.Ed
+.Pp
+where,
+.Sq src_addr
+is the IP address that the local side is willing to use,
+.Sq dst_addr
+is the IP address which the remote side should use and
+.Sq netmask
+is the netmask that should be used.
+.Sq Src_addr
+defaults to the current
+.Xr hostname 1 ,
+.Sq dst_addr
+defaults to 0.0.0.0, and
+.Sq netmask
+defaults to whatever mask is appropriate for
+.Sq src_addr .
+It is only possible to make
+.Sq netmask
+smaller than the default.
+The usual value is 255.255.255.255, as
+most kernels ignore the netmask of a POINTOPOINT interface.
+.Pp
+Some incorrect
+.Em PPP
+implementations require that the peer negotiates a specific IP
+address instead of
+.Sq src_addr .
+If this is the case,
+.Sq trigger_addr
+may be used to specify this IP number.
+This will not affect the
+routing table unless the other side agrees with this proposed number.
+.Bd -literal -offset indent
+set ifaddr 192.244.177.38 192.244.177.2 255.255.255.255 0.0.0.0
+.Ed
+.Pp
+The above specification means:
+.Pp
+.Bl -bullet -compact
+.It
+I will first suggest that my IP address should be 0.0.0.0, but I
+will only accept an address of 192.244.177.38.
+.It
+I strongly insist that the peer uses 192.244.177.2 as his own
+address and will not permit the use of any IP address but 192.244.177.2.
+When the peer requests another IP address, I will always suggest that
+it uses 192.244.177.2.
+.It
+The routing table entry will have a netmask of 0xffffffff.
+.El
+.Pp
+This is all fine when each side has a pre-determined IP address, however
+it is often the case that one side is acting as a server which controls
+all IP addresses and the other side should go along with it.
+In order to allow more flexible behaviour, the
+.Dq set ifaddr
+command allows the user to specify IP addresses more loosely:
+.Pp
+.Dl set ifaddr 192.244.177.38/24 192.244.177.2/20
+.Pp
+A number followed by a slash
+.Pq Dq /
+represents the number of bits significant in the IP address.
+The above example means:
+.Pp
+.Bl -bullet -compact
+.It
+I would like to use 192.244.177.38 as my address if it is possible, but I will
+also accept any IP address between 192.244.177.0 and 192.244.177.255.
+.It
+I would like to make him use 192.244.177.2 as his own address, but I will also
+permit him to use any IP address between 192.244.176.0 and
+192.244.191.255.
+.It
+As you may have already noticed, 192.244.177.2 is equivalent to saying
+192.244.177.2/32.
+.It
+As an exception, 0 is equivalent to 0.0.0.0/0, meaning that I have no
+preferred IP address and will obey the remote peers selection.
+When using zero, no routing table entries will be made until a connection
+is established.
+.It
+192.244.177.2/0 means that I will accept/permit any IP address but I will
+suggest that 192.244.177.2 be used first.
+.El
+.Pp
+When negotiating IPv6 addresses, no control is given to the user.
+IPV6CP negotiation is fully automatic.
+.Sh CONNECTING WITH YOUR INTERNET SERVICE PROVIDER
+The following steps should be taken when connecting to your ISP:
+.Bl -enum
+.It
+Describe your providers phone number(s) in the dial script using the
+.Dq set phone
+command.
+This command allows you to set multiple phone numbers for
+dialing and redialing separated by either a pipe
+.Pq Dq \&|
+or a colon
+.Pq Dq \&: :
+.Bd -ragged -offset indent
+.No set phone Ar telno Ns Xo
+.Oo \&| Ns Ar backupnumber
+.Oc Ns ... Ns Oo : Ns Ar nextnumber
+.Oc Ns ...
+.Xc
+.Ed
+.Pp
+Numbers after the first in a pipe-separated list are only used if the
+previous number was used in a failed dial or login script.
+Numbers
+separated by a colon are used sequentially, irrespective of what happened
+as a result of using the previous number.
+For example:
+.Bd -literal -offset indent
+set phone "1234567|2345678:3456789|4567890"
+.Ed
+.Pp
+Here, the 1234567 number is attempted.
+If the dial or login script fails,
+the 2345678 number is used next time, but *only* if the dial or login script
+fails.
+On the dial after this, the 3456789 number is used.
+The 4567890
+number is only used if the dial or login script using the 3456789 fails.
+If the login script of the 2345678 number fails, the next number is still the
+3456789 number.
+As many pipes and colons can be used as are necessary
+(although a given site would usually prefer to use either the pipe or the
+colon, but not both).
+The next number redial timeout is used between all numbers.
+When the end of the list is reached, the normal redial period is
+used before starting at the beginning again.
+The selected phone number is substituted for the \\\\T string in the
+.Dq set dial
+command (see below).
+.It
+Set up your redial requirements using
+.Dq set redial .
+For example, if you have a bad telephone line or your provider is
+usually engaged (not so common these days), you may want to specify
+the following:
+.Bd -literal -offset indent
+set redial 10 4
+.Ed
+.Pp
+This says that up to 4 phone calls should be attempted with a pause of 10
+seconds before dialing the first number again.
+.It
+Describe your login procedure using the
+.Dq set dial
+and
+.Dq set login
+commands.
+The
+.Dq set dial
+command is used to talk to your modem and establish a link with your
+ISP, for example:
+.Bd -literal -offset indent
+set dial "ABORT BUSY ABORT NO\\\\sCARRIER TIMEOUT 4 \\"\\" \e
+  ATZ OK-ATZ-OK ATDT\\\\T TIMEOUT 60 CONNECT"
+.Ed
+.Pp
+This modem "chat" string means:
+.Bl -bullet
+.It
+Abort if the string "BUSY" or "NO CARRIER" are received.
+.It
+Set the timeout to 4 seconds.
+.It
+Expect nothing.
+.It
+Send ATZ.
+.It
+Expect OK.
+If that is not received within the 4 second timeout, send ATZ
+and expect OK.
+.It
+Send ATDTxxxxxxx where xxxxxxx is the next number in the phone list from
+above.
+.It
+Set the timeout to 60.
+.It
+Wait for the CONNECT string.
+.El
+.Pp
+Once the connection is established, the login script is executed.
+This script is written in the same style as the dial script, but care should
+be taken to avoid having your password logged:
+.Bd -literal -offset indent
+set authkey MySecret
+set login "TIMEOUT 15 login:-\\\\r-login: awfulhak \e
+  word: \\\\P ocol: PPP HELLO"
+.Ed
+.Pp
+This login "chat" string means:
+.Bl -bullet
+.It
+Set the timeout to 15 seconds.
+.It
+Expect "login:".
+If it is not received, send a carriage return and expect
+"login:" again.
+.It
+Send "awfulhak"
+.It
+Expect "word:" (the tail end of a "Password:" prompt).
+.It
+Send whatever our current
+.Ar authkey
+value is set to.
+.It
+Expect "ocol:" (the tail end of a "Protocol:" prompt).
+.It
+Send "PPP".
+.It
+Expect "HELLO".
+.El
+.Pp
+The
+.Dq set authkey
+command is logged specially.
+When
+.Ar command
+or
+.Ar chat
+logging is enabled, the actual password is not logged;
+.Sq ********
+is logged instead.
+.Pp
+Login scripts vary greatly between ISPs.
+If you are setting one up for the first time,
+.Em ENABLE CHAT LOGGING
+so that you can see if your script is behaving as you expect.
+.It
+Use
+.Dq set device
+and
+.Dq set speed
+to specify your serial line and speed, for example:
+.Bd -literal -offset indent
+set device /dev/cuad0
+set speed 115200
+.Ed
+.Pp
+Cuad0 is the first serial port on
+.Fx .
+If you are running
+.Nm
+on
+.Ox ,
+cua00 is the first.
+A speed of 115200 should be specified
+if you have a modem capable of bit rates of 28800 or more.
+In general, the serial speed should be about four times the modem speed.
+.It
+Use the
+.Dq set ifaddr
+command to {define} the IP address.
+.Bl -bullet
+.It
+If you know what IP address your provider uses, then use it as the remote
+address (dst_addr), otherwise choose something like 10.0.0.2/0 (see below).
+.It
+If your provider has assigned a particular IP address to you, then use
+it as your address (src_addr).
+.It
+If your provider assigns your address dynamically, choose a suitably
+unobtrusive and unspecific IP number as your address.
+10.0.0.1/0 would be appropriate.
+The bit after the / specifies how many bits of the
+address you consider to be important, so if you wanted to insist on
+something in the class C network 1.2.3.0, you could specify 1.2.3.1/24.
+.It
+If you find that your ISP accepts the first IP number that you suggest,
+specify third and forth arguments of
+.Dq 0.0.0.0 .
+This will force your ISP to assign a number.
+(The third argument will
+be ignored as it is less restrictive than the default mask for your
+.Sq src_addr ) .
+.El
+.Pp
+An example for a connection where you do not know your IP number or your
+ISPs IP number would be:
+.Bd -literal -offset indent
+set ifaddr 10.0.0.1/0 10.0.0.2/0 0.0.0.0 0.0.0.0
+.Ed
+.Pp
+.It
+In most cases, your ISP will also be your default router.
+If this is the case, add the line
+.Bd -literal -offset indent
+add default HISADDR
+.Ed
+.Pp
+to
+.Pa /etc/ppp/ppp.conf
+(or to
+.Pa /etc/ppp/ppp.linkup
+for setups that do not use
+.Fl auto
+mode).
+.Pp
+This tells
+.Nm
+to add a default route to whatever the peer address is
+(10.0.0.2 in this example).
+This route is
+.Sq sticky ,
+meaning that should the value of
+.Dv HISADDR
+change, the route will be updated accordingly.
+.It
+If your provider requests that you use PAP/CHAP authentication methods, add
+the next lines to your
+.Pa /etc/ppp/ppp.conf
+file:
+.Bd -literal -offset indent
+set authname MyName
+set authkey MyPassword
+.Ed
+.Pp
+Both are accepted by default, so
+.Nm
+will provide whatever your ISP requires.
+.Pp
+It should be noted that a login script is rarely (if ever) required
+when PAP or CHAP are in use.
+.It
+Ask your ISP to authenticate your nameserver address(es) with the line
+.Bd -literal -offset indent
+enable dns
+.Ed
+.Pp
+Do
+.Em NOT
+do this if you are running a local DNS unless you also either use
+.Dq resolv readonly
+or have
+.Dq resolv restore
+in
+.Pa /etc/ppp/ppp.linkdown ,
+as
+.Nm
+will simply circumvent its use by entering some nameserver lines in
+.Pa /etc/resolv.conf .
+.El
+.Pp
+Please refer to
+.Pa /usr/share/examples/ppp/ppp.conf.sample
+and
+.Pa /usr/share/examples/ppp/ppp.linkup.sample
+for some real examples.
+The pmdemand label should be appropriate for most ISPs.
+.Sh LOGGING FACILITY
+.Nm
+is able to generate the following log info either via
+.Xr syslog 3
+or directly to the screen:
+.Pp
+.Bl -tag -width XXXXXXXXX -offset XXX -compact
+.It Li All
+Enable all logging facilities.
+This generates a lot of log.
+The most common use of 'all' is as a basis, where you remove some facilities
+after enabling 'all' ('debug' and 'timer' are usually best disabled.)
+.It Li Async
+Dump async level packet in hex.
+.It Li CBCP
+Generate CBCP (CallBack Control Protocol) logs.
+.It Li CCP
+Generate a CCP packet trace.
+.It Li Chat
+Generate
+.Sq dial ,
+.Sq login ,
+.Sq logout
+and
+.Sq hangup
+chat script trace logs.
+.It Li Command
+Log commands executed either from the command line or any of the configuration
+files.
+.It Li Connect
+Log Chat lines containing the string "CONNECT".
+.It Li Debug
+Log debug information.
+.It Li DNS
+Log DNS QUERY packets.
+.It Li Filter
+Log packets permitted by the dial filter and denied by any filter.
+.It Li HDLC
+Dump HDLC packet in hex.
+.It Li ID0
+Log all function calls specifically made as user id 0.
+.It Li IPCP
+Generate an IPCP packet trace.
+.It Li LCP
+Generate an LCP packet trace.
+.It Li LQM
+Generate LQR reports.
+.It Li Phase
+Phase transition log output.
+.It Li Physical
+Dump physical level packet in hex.
+.It Li Radius
+Dump RADIUS information.
+RADIUS information resulting from the link coming up or down is logged at
+.Dq Phase
+level unless
+.Dq Radius
+logging is enabled.
+This log level is most useful for monitoring RADIUS alive information.
+.It Li Sync
+Dump sync level packet in hex.
+.It Li TCP/IP
+Dump all TCP/IP packets.
+.It Li Timer
+Log timer manipulation.
+.It Li TUN
+Include the tun device on each log line.
+.It Li Warning
+Output to the terminal device.
+If there is currently no terminal,
+output is sent to the log file using syslogs
+.Dv LOG_WARNING .
+.It Li Error
+Output to both the terminal device
+and the log file using syslogs
+.Dv LOG_ERROR .
+.It Li Alert
+Output to the log file using
+.Dv LOG_ALERT .
+.El
+.Pp
+The
+.Dq set log
+command allows you to set the logging output level.
+Multiple levels can be specified on a single command line.
+The default is equivalent to
+.Dq set log Phase .
+.Pp
+It is also possible to log directly to the screen.
+The syntax is the same except that the word
+.Dq local
+should immediately follow
+.Dq set log .
+The default is
+.Dq set log local
+(i.e., only the un-maskable warning, error and alert output).
+.Pp
+If The first argument to
+.Dq set log Op local
+begins with a
+.Sq +
+or a
+.Sq -
+character, the current log levels are
+not cleared, for example:
+.Bd -literal -offset indent
+PPP ON awfulhak> set log phase
+PPP ON awfulhak> show log
+Log:   Phase Warning Error Alert
+Local: Warning Error Alert
+PPP ON awfulhak> set log +tcp/ip -warning
+PPP ON awfulhak> set log local +command
+PPP ON awfulhak> show log
+Log:   Phase TCP/IP Warning Error Alert
+Local: Command Warning Error Alert
+.Ed
+.Pp
+Log messages of level Warning, Error and Alert are not controllable
+using
+.Dq set log Op local .
+.Pp
+The
+.Ar Warning
+level is special in that it will not be logged if it can be displayed
+locally.
+.Sh SIGNAL HANDLING
+.Nm
+deals with the following signals:
+.Bl -tag -width "USR2"
+.It INT
+Receipt of this signal causes the termination of the current connection
+(if any).
+This will cause
+.Nm
+to exit unless it is in
+.Fl auto
+or
+.Fl ddial
+mode.
+.It HUP, TERM & QUIT
+These signals tell
+.Nm
+to exit.
+.It USR1
+This signal, tells
+.Nm
+to re-open any existing server socket, dropping all existing diagnostic
+connections.
+Sockets that could not previously be opened will be retried.
+.It USR2
+This signal, tells
+.Nm
+to close any existing server socket, dropping all existing diagnostic
+connections.
+.Dv SIGUSR1
+can still be used to re-open the socket.
+.El
+.Sh MULTI-LINK PPP
+If you wish to use more than one physical link to connect to a
+.Em PPP
+peer, that peer must also understand the
+.Em MULTI-LINK PPP
+protocol.
+Refer to RFC 1990 for specification details.
+.Pp
+The peer is identified using a combination of his
+.Dq endpoint discriminator
+and his
+.Dq authentication id .
+Either or both of these may be specified.
+It is recommended that
+at least one is specified, otherwise there is no way of ensuring that
+all links are actually connected to the same peer program, and some
+confusing lock-ups may result.
+Locally, these identification variables are specified using the
+.Dq set enddisc
+and
+.Dq set authname
+commands.
+The
+.Sq authname
+(and
+.Sq authkey )
+must be agreed in advance with the peer.
+.Pp
+Multi-link capabilities are enabled using the
+.Dq set mrru
+command (set maximum reconstructed receive unit).
+Once multi-link is enabled,
+.Nm
+will attempt to negotiate a multi-link connection with the peer.
+.Pp
+By default, only one
+.Sq link
+is available
+(called
+.Sq deflink ) .
+To create more links, the
+.Dq clone
+command is used.
+This command will clone existing links, where all
+characteristics are the same except:
+.Bl -enum
+.It
+The new link has its own name as specified on the
+.Dq clone
+command line.
+.It
+The new link is an
+.Sq interactive
+link.
+Its mode may subsequently be changed using the
+.Dq set mode
+command.
+.It
+The new link is in a
+.Sq closed
+state.
+.El
+.Pp
+A summary of all available links can be seen using the
+.Dq show links
+command.
+.Pp
+Once a new link has been created, command usage varies.
+All link specific commands must be prefixed with the
+.Dq link Ar name
+command, specifying on which link the command is to be applied.
+When only a single link is available,
+.Nm
+is smart enough not to require the
+.Dq link Ar name
+prefix.
+.Pp
+Some commands can still be used without specifying a link - resulting
+in an operation at the
+.Sq bundle
+level.
+For example, once two or more links are available, the command
+.Dq show ccp
+will show CCP configuration and statistics at the multi-link level, and
+.Dq link deflink show ccp
+will show the same information at the
+.Dq deflink
+link level.
+.Pp
+Armed with this information, the following configuration might be used:
+.Pp
+.Bd -literal -offset indent
+mp:
+ set timeout 0
+ set log phase chat
+ set device /dev/cuad0 /dev/cuad1 /dev/cuad2
+ set phone "123456789"
+ set dial "ABORT BUSY ABORT NO\\sCARRIER TIMEOUT 5 \\"\\" ATZ \e
+           OK-AT-OK \\\\dATDT\\\\T TIMEOUT 45 CONNECT"
+ set login
+ set ifaddr 10.0.0.1/0 10.0.0.2/0 0.0.0.0 0.0.0.0
+ set authname ppp
+ set authkey ppppassword
+
+ set mrru 1500
+ clone 1,2,3		# Create 3 new links - duplicates of the default
+ link deflink remove	# Delete the default link (called ``deflink'')
+.Ed
+.Pp
+Note how all cloning is done at the end of the configuration.
+Usually, the link will be configured first, then cloned.
+If you wish all links
+to be up all the time, you can add the following line to the end of your
+configuration.
+.Pp
+.Bd -literal -offset indent
+  link 1,2,3 set mode ddial
+.Ed
+.Pp
+If you want the links to dial on demand, this command could be used:
+.Pp
+.Bd -literal -offset indent
+  link * set mode auto
+.Ed
+.Pp
+Links may be tied to specific names by removing the
+.Dq set device
+line above, and specifying the following after the
+.Dq clone
+command:
+.Pp
+.Bd -literal -offset indent
+ link 1 set device /dev/cuad0
+ link 2 set device /dev/cuad1
+ link 3 set device /dev/cuad2
+.Ed
+.Pp
+Use the
+.Dq help
+command to see which commands require context (using the
+.Dq link
+command), which have optional
+context and which should not have any context.
+.Pp
+When
+.Nm
+has negotiated
+.Em MULTI-LINK
+mode with the peer, it creates a local domain socket in the
+.Pa /var/run
+directory.
+This socket is used to pass link information (including
+the actual link file descriptor) between different
+.Nm
+invocations.
+This facilitates
+.Nm Ns No 's
+ability to be run from a
+.Xr getty 8
+or directly from
+.Pa /etc/gettydefs
+(using the
+.Sq pp=
+capability), without needing to have initial control of the serial
+line.
+Once
+.Nm
+negotiates multi-link mode, it will pass its open link to any
+already running process.
+If there is no already running process,
+.Nm
+will act as the master, creating the socket and listening for new
+connections.
+.Sh PPP COMMAND LIST
+This section lists the available commands and their effect.
+They are usable either from an interactive
+.Nm
+session, from a configuration file or from a
+.Xr pppctl 8
+or
+.Xr telnet 1
+session.
+.Bl -tag -width 2n
+.It accept|deny|enable|disable Ar option....
+These directives tell
+.Nm
+how to negotiate the initial connection with the peer.
+Each
+.Dq option
+has a default of either accept or deny and enable or disable.
+.Dq Accept
+means that the option will be ACK'd if the peer asks for it.
+.Dq Deny
+means that the option will be NAK'd if the peer asks for it.
+.Dq Enable
+means that the option will be requested by us.
+.Dq Disable
+means that the option will not be requested by us.
+.Pp
+.Dq Option
+may be one of the following:
+.Bl -tag -width 2n
+.It acfcomp
+Default: Enabled and Accepted.
+ACFComp stands for Address and Control Field Compression.
+Non LCP packets will usually have an address
+field of 0xff (the All-Stations address) and a control field of
+0x03 (the Unnumbered Information command).
+If this option is
+negotiated, these two bytes are simply not sent, thus minimising
+traffic.
+.Pp
+See
+.Pa rfc1662
+for details.
+.It chap Ns Op \&05
+Default: Disabled and Accepted.
+CHAP stands for Challenge Handshake Authentication Protocol.
+Only one of CHAP and PAP (below) may be negotiated.
+With CHAP, the authenticator sends a "challenge" message to its peer.
+The peer uses a one-way hash function to encrypt the
+challenge and sends the result back.
+The authenticator does the same, and compares the results.
+The advantage of this mechanism is that no
+passwords are sent across the connection.
+A challenge is made when the connection is first made.
+Subsequent challenges may occur.
+If you want to have your peer authenticate itself, you must
+.Dq enable chap .
+in
+.Pa /etc/ppp/ppp.conf ,
+and have an entry in
+.Pa /etc/ppp/ppp.secret
+for the peer.
+.Pp
+When using CHAP as the client, you need only specify
+.Dq AuthName
+and
+.Dq AuthKey
+in
+.Pa /etc/ppp/ppp.conf .
+CHAP is accepted by default.
+Some
+.Em PPP
+implementations use "MS-CHAP" rather than MD5 when encrypting the
+challenge.
+MS-CHAP is a combination of MD4 and DES.
+If
+.Nm
+was built on a machine with DES libraries available, it will respond
+to MS-CHAP authentication requests, but will never request them.
+.It deflate
+Default: Enabled and Accepted.
+This option decides if deflate
+compression will be used by the Compression Control Protocol (CCP).
+This is the same algorithm as used by the
+.Xr gzip 1
+program.
+Note: There is a problem negotiating
+.Ar deflate
+capabilities with
+.Xr pppd 8
+- a
+.Em PPP
+implementation available under many operating systems.
+.Nm pppd
+(version 2.3.1) incorrectly attempts to negotiate
+.Ar deflate
+compression using type
+.Em 24
+as the CCP configuration type rather than type
+.Em 26
+as specified in
+.Pa rfc1979 .
+Type
+.Ar 24
+is actually specified as
+.Dq PPP Magna-link Variable Resource Compression
+in
+.Pa rfc1975 !
+.Nm
+is capable of negotiating with
+.Nm pppd ,
+but only if
+.Dq deflate24
+is
+.Ar enable Ns No d
+and
+.Ar accept Ns No ed .
+.It deflate24
+Default: Disabled and Denied.
+This is a variance of the
+.Ar deflate
+option, allowing negotiation with the
+.Xr pppd 8
+program.
+Refer to the
+.Ar deflate
+section above for details.
+It is disabled by default as it violates
+.Pa rfc1975 .
+.It dns
+Default: Disabled and Denied.
+This option allows DNS negotiation.
+.Pp
+If
+.Dq enable Ns No d,
+.Nm
+will request that the peer confirms the entries in
+.Pa /etc/resolv.conf .
+If the peer NAKs our request (suggesting new IP numbers),
+.Pa /etc/resolv.conf
+is updated and another request is sent to confirm the new entries.
+.Pp
+If
+.Dq accept Ns No ed,
+.Nm
+will answer any DNS queries requested by the peer rather than rejecting
+them.
+The answer is taken from
+.Pa /etc/resolv.conf
+unless the
+.Dq set dns
+command is used as an override.
+.It enddisc
+Default: Enabled and Accepted.
+This option allows control over whether we
+negotiate an endpoint discriminator.
+We only send our discriminator if
+.Dq set enddisc
+is used and
+.Ar enddisc
+is enabled.
+We reject the peers discriminator if
+.Ar enddisc
+is denied.
+.It LANMan|chap80lm
+Default: Disabled and Accepted.
+The use of this authentication protocol
+is discouraged as it partially violates the authentication protocol by
+implementing two different mechanisms (LANMan & NT) under the guise of
+a single CHAP type (0x80).
+.Dq LANMan
+uses a simple DES encryption mechanism and is the least secure of the
+CHAP alternatives (although is still more secure than PAP).
+.Pp
+Refer to the
+.Dq MSChap
+description below for more details.
+.It lqr
+Default: Disabled and Accepted.
+This option decides if Link Quality Requests will be sent or accepted.
+LQR is a protocol that allows
+.Nm
+to determine that the link is down without relying on the modems
+carrier detect.
+When LQR is enabled,
+.Nm
+sends the
+.Em QUALPROTO
+option (see
+.Dq set lqrperiod
+below) as part of the LCP request.
+If the peer agrees, both sides will
+exchange LQR packets at the agreed frequency, allowing detailed link
+quality monitoring by enabling LQM logging.
+If the peer does not agree, and if the
+.Dq echo
+option is enabled,
+.Nm
+will send
+.Em LCP ECHO
+requests instead.
+These packets pass no information of interest, but they
+.Em MUST
+be replied to by the peer.
+.Pp
+Whether using
+.Em LQR
+or
+.Em LCP ECHO ,
+.Nm
+will abruptly drop the connection if 5 unacknowledged packets have been
+sent rather than sending a 6th.
+A message is logged at the
+.Em PHASE
+level, and any appropriate
+.Dq reconnect
+values are honoured as if the peer were responsible for dropping the
+connection.
+.Pp
+Refer to the
+.Dq enable echo
+command description for differences in behaviour prior to
+.Nm
+version 3.4.2.
+.It mppe
+Default: Enabled and Accepted.
+This is Microsoft Point to Point Encryption scheme.
+MPPE key size can be
+40-, 56- and 128-bits.
+Refer to
+.Dq set mppe
+command.
+.It MSChapV2|chap81
+Default: Disabled and Accepted.
+It is very similar to standard CHAP (type 0x05)
+except that it issues challenges of a fixed 16 bytes in length and uses a
+combination of MD4, SHA-1 and DES to encrypt the challenge rather than using the
+standard MD5 mechanism.
+.It MSChap|chap80nt
+Default: Disabled and Accepted.
+The use of this authentication protocol
+is discouraged as it partially violates the authentication protocol by
+implementing two different mechanisms (LANMan & NT) under the guise of
+a single CHAP type (0x80).
+It is very similar to standard CHAP (type 0x05)
+except that it issues challenges of a fixed 8 bytes in length and uses a
+combination of MD4 and DES to encrypt the challenge rather than using the
+standard MD5 mechanism.
+CHAP type 0x80 for LANMan is also supported - see
+.Dq enable LANMan
+for details.
+.Pp
+Because both
+.Dq LANMan
+and
+.Dq NT
+use CHAP type 0x80, when acting as authenticator with both
+.Dq enable Ns No d ,
+.Nm
+will rechallenge the peer up to three times if it responds using the wrong
+one of the two protocols.
+This gives the peer a chance to attempt using both protocols.
+.Pp
+Conversely, when
+.Nm
+acts as the authenticatee with both protocols
+.Dq accept Ns No ed ,
+the protocols are used alternately in response to challenges.
+.Pp
+Note: If only LANMan is enabled,
+.Xr pppd 8
+(version 2.3.5) misbehaves when acting as authenticatee.
+It provides both
+the NT and the LANMan answers, but also suggests that only the NT answer
+should be used.
+.It pap
+Default: Disabled and Accepted.
+PAP stands for Password Authentication Protocol.
+Only one of PAP and CHAP (above) may be negotiated.
+With PAP, the ID and Password are sent repeatedly to the peer until
+authentication is acknowledged or the connection is terminated.
+This is a rather poor security mechanism.
+It is only performed when the connection is first established.
+If you want to have your peer authenticate itself, you must
+.Dq enable pap .
+in
+.Pa /etc/ppp/ppp.conf ,
+and have an entry in
+.Pa /etc/ppp/ppp.secret
+for the peer (although see the
+.Dq passwdauth
+and
+.Dq set radius
+options below).
+.Pp
+When using PAP as the client, you need only specify
+.Dq AuthName
+and
+.Dq AuthKey
+in
+.Pa /etc/ppp/ppp.conf .
+PAP is accepted by default.
+.It pred1
+Default: Enabled and Accepted.
+This option decides if Predictor 1
+compression will be used by the Compression Control Protocol (CCP).
+.It protocomp
+Default: Enabled and Accepted.
+This option is used to negotiate
+PFC (Protocol Field Compression), a mechanism where the protocol
+field number is reduced to one octet rather than two.
+.It shortseq
+Default: Enabled and Accepted.
+This option determines if
+.Nm
+will request and accept requests for short
+(12 bit)
+sequence numbers when negotiating multi-link mode.
+This is only applicable if our MRRU is set (thus enabling multi-link).
+.It vjcomp
+Default: Enabled and Accepted.
+This option determines if Van Jacobson header compression will be used.
+.El
+.Pp
+The following options are not actually negotiated with the peer.
+Therefore, accepting or denying them makes no sense.
+.Bl -tag -width 2n
+.It echo
+Default: Disabled.
+When this option is enabled,
+.Nm
+will send
+.Em LCP ECHO
+requests to the peer at the frequency defined by
+.Dq echoperiod .
+Note,
+.Em LQR
+requests will supersede
+.Em LCP ECHO
+requests if enabled and negotiated.
+See
+.Dq set lqrperiod
+below for details.
+.Pp
+Prior to
+.Nm
+version 3.4.2,
+.Dq echo
+was considered enabled if lqr was enabled and negotiated, otherwise it was
+considered disabled.
+For the same behaviour, it is now necessary to
+.Dq enable lqr echo
+rather than just
+.Dq enable lqr .
+.It filter-decapsulation
+Default: Disabled.
+When this option is enabled,
+.Nm
+will examine UDP frames to see if they actually contain a
+.Em PPP
+frame as their payload.
+If this is the case, all filters will operate on the payload rather
+than the actual packet.
+.Pp
+This is useful if you want to send PPPoUDP traffic over a
+.Em PPP
+link, but want that link to do smart things with the real data rather than
+the UDP wrapper.
+.Pp
+The UDP frame payload must not be compressed in any way, otherwise
+.Nm
+will not be able to interpret it.
+It is therefore recommended that you
+.Ic disable vj pred1 deflate
+and
+.Ic deny vj pred1 deflate
+in the configuration for the
+.Nm
+invocation with the udp link.
+.It force-scripts
+Default: Disabled.
+Forces execution of the configured chat scripts in
+.Dv direct
+and
+.Dv dedicated
+modes.
+.It idcheck
+Default: Enabled.
+When
+.Nm
+exchanges low-level LCP, CCP and IPCP configuration traffic, the
+.Em Identifier
+field of any replies is expected to be the same as that of the request.
+By default,
+.Nm
+drops any reply packets that do not contain the expected identifier
+field, reporting the fact at the respective log level.
+If
+.Ar idcheck
+is disabled,
+.Nm
+will ignore the identifier field.
+.It iface-alias
+Default: Enabled if
+.Fl nat
+is specified.
+This option simply tells
+.Nm
+to add new interface addresses to the interface rather than replacing them.
+The option can only be enabled if network address translation is enabled
+.Pq Dq nat enable yes .
+.Pp
+With this option enabled,
+.Nm
+will pass traffic for old interface addresses through the NAT
+ifdef({LOCALNAT},{engine,},{engine
+(see
+.Xr libalias 3 ) ,})
+resulting in the ability (in
+.Fl auto
+mode) to properly connect the process that caused the PPP link to
+come up in the first place.
+.Pp
+Disabling NAT with
+.Dq nat enable no
+will also disable
+.Sq iface-alias .
+.It ipcp
+Default: Enabled.
+This option allows
+.Nm
+to attempt to negotiate IP control protocol capabilities and if
+successful to exchange IP datagrams with the peer.
+.It ipv6cp
+Default: Enabled.
+This option allows
+.Nm
+to attempt to negotiate IPv6 control protocol capabilities and if
+successful to exchange IPv6 datagrams with the peer.
+.It keep-session
+Default: Disabled.
+When
+.Nm
+runs as a Multi-link server, a different
+.Nm
+instance initially receives each connection.
+After determining that
+the link belongs to an already existing bundle (controlled by another
+.Nm
+invocation),
+.Nm
+will transfer the link to that process.
+.Pp
+If the link is a tty device or if this option is enabled,
+.Nm
+will not exit, but will change its process name to
+.Dq session owner
+and wait for the controlling
+.Nm
+to finish with the link and deliver a signal back to the idle process.
+This prevents the confusion that results from
+.Nm Ns No 's
+parent considering the link resource available again.
+.Pp
+For tty devices that have entries in
+.Pa /etc/ttys ,
+this is necessary to prevent another
+.Xr getty 8
+from being started, and for program links such as
+.Xr sshd 8 ,
+it prevents
+.Xr sshd 8
+from exiting due to the death of its child.
+As
+.Nm
+cannot determine its parents requirements (except for the tty case), this
+option must be enabled manually depending on the circumstances.
+.It loopback
+Default: Enabled.
+When
+.Ar loopback
+is enabled,
+.Nm
+will automatically loop back packets being sent
+out with a destination address equal to that of the
+.Em PPP
+interface.
+If disabled,
+.Nm
+will send the packet, probably resulting in an ICMP redirect from
+the other end.
+It is convenient to have this option enabled when
+the interface is also the default route as it avoids the necessity
+of a loopback route.
+.It NAS-IP-Address
+Default: Enabled.
+This option controls whether
+.Nm
+sends the
+.Dq NAS-IP-Address
+attribute to the RADIUS server when RADIUS is in use
+.Pq see Dq set radius .
+.Pp
+Note, at least one of
+.Dq NAS-IP-Address
+and
+.Dq NAS-Identifier
+must be enabled.
+.Pp
+Versions of
+.Nm
+prior to version 3.4.1 did not send the
+.Dq NAS-IP-Address
+attribute as it was reported to break the Radiator RADIUS server.
+As the latest rfc (2865) no longer hints that only one of
+.Dq NAS-IP-Address
+and
+.Dq NAS-Identifier
+should be sent (as rfc 2138 did),
+.Nm
+now sends both and leaves it up to the administrator that chooses to use
+bad RADIUS implementations to
+.Dq disable NAS-IP-Address .
+.It NAS-Identifier
+Default: Enabled.
+This option controls whether
+.Nm
+sends the
+.Dq NAS-Identifier
+attribute to the RADIUS server when RADIUS is in use
+.Pq see Dq set radius .
+.Pp
+Note, at least one of
+.Dq NAS-IP-Address
+and
+.Dq NAS-Identifier
+must be enabled.
+.It passwdauth
+Default: Disabled.
+Enabling this option will tell the PAP authentication
+code to use the password database (see
+.Xr passwd 5 )
+to authenticate the caller if they cannot be found in the
+.Pa /etc/ppp/ppp.secret
+file.
+.Pa /etc/ppp/ppp.secret
+is always checked first.
+If you wish to use passwords from
+.Xr passwd 5 ,
+but also to specify an IP number or label for a given client, use
+.Dq \&*
+as the client password in
+.Pa /etc/ppp/ppp.secret .
+.It proxy
+Default: Disabled.
+Enabling this option will tell
+.Nm
+to proxy ARP for the peer.
+This means that
+.Nm
+will make an entry in the ARP table using
+.Dv HISADDR
+and the
+.Dv MAC
+address of the local network in which
+.Dv HISADDR
+appears.
+This allows other machines connecteed to the LAN to talk to
+the peer as if the peer itself was connected to the LAN.
+The proxy entry cannot be made unless
+.Dv HISADDR
+is an address from a LAN.
+.It proxyall
+Default: Disabled.
+Enabling this will tell
+.Nm
+to add proxy arp entries for every IP address in all class C or
+smaller subnets routed via the tun interface.
+.Pp
+Proxy arp entries are only made for sticky routes that are added
+using the
+.Dq add
+command.
+No proxy arp entries are made for the interface address itself
+(as created by the
+.Dq set ifaddr
+command).
+.It sroutes
+Default: Enabled.
+When the
+.Dq add
+command is used with the
+.Dv HISADDR ,
+.Dv MYADDR ,
+.Dv HISADDR6
+or
+.Dv MYADDR6
+values, entries are stored in the
+.Sq sticky route
+list.
+Each time these variables change, this list is re-applied to the routing table.
+.Pp
+Disabling this option will prevent the re-application of sticky routes,
+although the
+.Sq stick route
+list will still be maintained.
+.It Op tcp Ns Xo
+.No mssfixup
+.Xc
+Default: Enabled.
+This option tells
+.Nm
+to adjust TCP SYN packets so that the maximum receive segment
+size is not greater than the amount allowed by the interface MTU.
+.It throughput
+Default: Enabled.
+This option tells
+.Nm
+to gather throughput statistics.
+Input and output is sampled over
+a rolling 5 second window, and current, best and total figures are retained.
+This data is output when the relevant
+.Em PPP
+layer shuts down, and is also available using the
+.Dq show
+command.
+Throughput statistics are available at the
+.Dq IPCP
+and
+.Dq physical
+levels.
+.It utmp
+Default: Enabled.
+Normally, when a user is authenticated using PAP or CHAP, and when
+.Nm
+is running in
+.Fl direct
+mode, an entry is made in the utmp and wtmp files for that user.
+Disabling this option will tell
+.Nm
+not to make any utmp or wtmp entries.
+This is usually only necessary if
+you require the user to both login and authenticate themselves.
+.El
+.Pp
+.It add Ns Xo
+.Op !\&
+.Ar dest Ns Op / Ns Ar nn
+.Op Ar mask
+.Op Ar gateway
+.Xc
+.Ar Dest
+is the destination IP address.
+The netmask is specified either as a number of bits with
+.Ar /nn
+or as an IP number using
+.Ar mask .
+.Ar 0 0
+or simply
+.Ar 0
+with no mask refers to the default route.
+It is also possible to use the literal name
+.Sq default
+instead of
+.Ar 0 .
+.Ar Gateway
+is the next hop gateway to get to the given
+.Ar dest
+machine/network.
+Refer to the
+.Xr route 8
+command for further details.
+.Pp
+It is possible to use the symbolic names
+.Sq MYADDR ,
+.Sq HISADDR ,
+.Sq MYADDR6
+or
+.Sq HISADDR6
+as the destination, and
+.Sq HISADDR
+or
+.Sq HISADDR6
+as the
+.Ar gateway .
+.Sq MYADDR
+is replaced with the interface IP address,
+.Sq HISADDR
+is replaced with the interface IP destination (peer) address,
+.Sq MYADDR6
+is replaced with the interface IPv6 address, and
+.Sq HISADDR6
+is replaced with the interface IPv6 destination address,
+.Pp
+If the
+.Ar add!\&
+command is used
+(note the trailing
+.Dq !\& ) ,
+then if the route already exists, it will be updated as with the
+.Sq route change
+command (see
+.Xr route 8
+for further details).
+.Pp
+Routes that contain the
+.Dq HISADDR ,
+.Dq MYADDR ,
+.Dq HISADDR6 ,
+.Dq MYADDR6 ,
+.Dq DNS0 ,
+or
+.Dq DNS1
+constants are considered
+.Sq sticky .
+They are stored in a list (use
+.Dq show ncp
+to see the list), and each time the value of one of these variables
+changes, the appropriate routing table entries are updated.
+This facility may be disabled using
+.Dq disable sroutes .
+.It allow Ar command Op Ar args
+This command controls access to
+.Nm
+and its configuration files.
+It is possible to allow user-level access,
+depending on the configuration file label and on the mode that
+.Nm
+is being run in.
+For example, you may wish to configure
+.Nm
+so that only user
+.Sq fred
+may access label
+.Sq fredlabel
+in
+.Fl background
+mode.
+.Pp
+User id 0 is immune to these commands.
+.Bl -tag -width 2n
+.It allow user Ns Xo
+.Op s
+.Ar logname Ns No ...
+.Xc
+By default, only user id 0 is allowed access to
+.Nm .
+If this command is used, all of the listed users are allowed access to
+the section in which the
+.Dq allow users
+command is found.
+The
+.Sq default
+section is always checked first (even though it is only ever automatically
+loaded at startup).
+.Dq allow users
+commands are cumulative in a given section, but users allowed in any given
+section override users allowed in the default section, so it is possible to
+allow users access to everything except a given label by specifying default
+users in the
+.Sq default
+section, and then specifying a new user list for that label.
+.Pp
+If user
+.Sq *
+is specified, access is allowed to all users.
+.It allow mode Ns Xo
+.Op s
+.Ar mode Ns No ...
+.Xc
+By default, access using any
+.Nm
+mode is possible.
+If this command is used, it restricts the access
+.Ar modes
+allowed to load the label under which this command is specified.
+Again, as with the
+.Dq allow users
+command, each
+.Dq allow modes
+command overrides any previous settings, and the
+.Sq default
+section is always checked first.
+.Pp
+Possible modes are:
+.Sq interactive ,
+.Sq auto ,
+.Sq direct ,
+.Sq dedicated ,
+.Sq ddial ,
+.Sq background
+and
+.Sq * .
+.Pp
+When running in multi-link mode, a section can be loaded if it allows
+.Em any
+of the currently existing line modes.
+.El
+.Pp
+.It nat Ar command Op Ar args
+This command allows the control of the network address translation (also
+known as masquerading or IP aliasing) facilities that are built into
+.Nm .
+NAT is done on the external interface only, and is unlikely to make sense
+if used with the
+.Fl direct
+flag.
+.Pp
+If nat is enabled on your system (it may be omitted at compile time),
+the following commands are possible:
+.Bl -tag -width 2n
+.It nat enable yes|no
+This command either switches network address translation on or turns it off.
+The
+.Fl nat
+command line flag is synonymous with
+.Dq nat enable yes .
+.It nat addr Op Ar addr_local addr_alias
+This command allows data for
+.Ar addr_alias
+to be redirected to
+.Ar addr_local .
+It is useful if you own a small number of real IP numbers that
+you wish to map to specific machines behind your gateway.
+.It nat deny_incoming yes|no
+If set to yes, this command will refuse all incoming packets where an
+aliasing link does not already exist.
+ifdef({LOCALNAT},{},{Refer to the
+.Sx CONCEPTUAL BACKGROUND
+section of
+.Xr libalias 3
+for a description of what an
+.Dq aliasing link
+is.
+})dnl
+.Pp
+It should be noted under what circumstances an aliasing link is
+ifdef({LOCALNAT},{created.},{created by
+.Xr libalias 3 .})
+It may be necessary to further protect your network from outside
+connections using the
+.Dq set filter
+or
+.Dq nat target
+commands.
+.It nat help|?
+This command gives a summary of available nat commands.
+.It nat log yes|no
+This option causes various NAT statistics and information to
+be logged to the file
+.Pa /var/log/alias.log .
+.It nat port Ar proto Ar targetIP Ns Xo
+.No : Ns Ar targetPort Ns
+.Oo
+.No - Ns Ar targetPort
+.Oc Ar aliasPort Ns
+.Oo
+.No - Ns Ar aliasPort
+.Oc Oo Ar remoteIP : Ns
+.Ar remotePort Ns
+.Oo
+.No - Ns Ar remotePort
+.Oc Ns
+.Oc
+.Xc
+This command causes incoming
+.Ar proto
+connections to
+.Ar aliasPort
+to be redirected to
+.Ar targetPort
+on
+.Ar targetIP .
+.Ar proto
+is either
+.Dq tcp
+or
+.Dq udp .
+.Pp
+A range of port numbers may be specified as shown above.
+The ranges must be of the same size.
+.Pp
+If
+.Ar remoteIP
+is specified, only data coming from that IP number is redirected.
+.Ar remotePort
+must either be
+.Dq 0
+(indicating any source port)
+or a range of ports the same size as the other ranges.
+.Pp
+This option is useful if you wish to run things like Internet phone on
+machines behind your gateway, but is limited in that connections to only
+one interior machine per source machine and target port are possible.
+.It nat proto Ar proto localIP Oo
+.Ar publicIP Op Ar remoteIP
+.Oc
+This command tells
+.Nm
+to redirect packets of protocol type
+.Ar proto
+(see
+.Xr protocols 5 )
+to the internal address
+.Ar localIP .
+.Pp
+If
+.Ar publicIP
+is specified, only packets destined for that address are matched,
+otherwise the default alias address is used.
+.Pp
+If
+.Ar remoteIP
+is specified, only packets matching that source address are matched,
+.Pp
+This command is useful for redirecting tunnel endpoints to an internal machine,
+for example:
+.Pp
+.Dl nat proto ipencap 10.0.0.1
+.It "nat proxy cmd" Ar arg Ns No ...
+This command tells
+.Nm
+to proxy certain connections, redirecting them to a given server.
+ifdef({LOCALNAT},{},{Refer to the description of
+.Fn PacketAliasProxyRule
+in
+.Xr libalias 3
+for details of the available commands.
+})dnl
+.It nat punch_fw Op Ar base count
+This command tells
+.Nm
+to punch holes in the firewall for FTP or IRC DCC connections.
+This is done dynamically by installing termporary firewall rules which
+allow a particular connection (and only that connection) to go through
+the firewall.
+The rules are removed once the corresponding connection terminates.
+.Pp
+A maximum of
+.Ar count
+rules starting from rule number
+.Ar base
+will be used for punching firewall holes.
+The range will be cleared when the
+.Dq nat punch_fw
+command is run.
+.Pp
+If no arguments are given, firewall punching is disabled.
+.It nat skinny_port Op Ar port
+This command tells
+.Nm
+which TCP port is used by the Skinny Station protocol.
+Skinny is used by
+Cisco IP phones to communicate with Cisco Call Managers to setup voice
+over IP calls.
+The typical port used by Skinny is 2000.
+.Pp
+If no argument is given, skinny aliasing is disabled.
+.It nat same_ports yes|no
+When enabled, this command will tell the network address translation engine to
+attempt to avoid changing the port number on outgoing packets.
+This is useful
+if you want to support protocols such as RPC and LPD which require
+connections to come from a well known port.
+.It nat target Op Ar address
+Set the given target address or clear it if no address is given.
+The target address is used
+ifdef({LOCALNAT},{},{by libalias })dnl
+to specify how to NAT incoming packets by default.
+If a target address is not set or if
+.Dq default
+is given, packets are not altered and are allowed to route to the internal
+network.
+.Pp
+The target address may be set to
+.Dq MYADDR ,
+in which case
+ifdef({LOCALNAT},{all packets will be redirected},
+{libalias will redirect all packets})
+to the interface address.
+.It nat use_sockets yes|no
+When enabled, this option tells the network address translation engine to
+create a socket so that it can guarantee a correct incoming ftp data or
+IRC connection.
+.It nat unregistered_only yes|no
+Only alter outgoing packets with an unregistered source address.
+According to RFC 1918, unregistered source addresses
+are 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16.
+.El
+.Pp
+These commands are also discussed in the file
+.Pa README.nat
+which comes with the source distribution.
+.Pp
+.It Op !\& Ns Xo
+.No bg Ar command
+.Xc
+The given
+.Ar command
+is executed in the background with the following words replaced:
+.Bl -tag -width COMPILATIONDATE
+.It Li AUTHNAME
+This is replaced with the local
+.Ar authname
+value.
+See the
+.Dq set authname
+command below.
+.It Li COMPILATIONDATE
+In previous software revisions, this was replaced with the date on which
+.Nm
+was compiled.
+This is no longer supported as it breaks the ability to recompile the same
+code to produce an exact duplicate of a previous compilation.
+.It Li DNS0 & DNS1
+These are replaced with the primary and secondary nameserver IP numbers.
+If nameservers are negotiated by IPCP, the values of these macros will change.
+.It Li ENDDISC
+This is replaced with the local endpoint discriminator value.
+See the
+.Dq set enddisc
+command below.
+.It Li HISADDR
+This is replaced with the peers IP number.
+.It Li HISADDR6
+This is replaced with the peers IPv6 number.
+.It Li INTERFACE
+This is replaced with the name of the interface that is in use.
+.It Li IPOCTETSIN
+This is replaced with the number of IP bytes received since the connection
+was established.
+.It Li IPOCTETSOUT
+This is replaced with the number of IP bytes sent since the connection
+was established.
+.It Li IPPACKETSIN
+This is replaced with the number of IP packets received since the connection
+was established.
+.It Li IPPACKETSOUT
+This is replaced with the number of IP packets sent since the connection
+was established.
+.It Li IPV6OCTETSIN
+This is replaced with the number of IPv6 bytes received since the connection
+was established.
+.It Li IPV6OCTETSOUT
+This is replaced with the number of IPv6 bytes sent since the connection
+was established.
+.It Li IPV6PACKETSIN
+This is replaced with the number of IPv6 packets received since the connection
+was established.
+.It Li IPV6PACKETSOUT
+This is replaced with the number of IPv6 packets sent since the connection
+was established.
+.It Li LABEL
+This is replaced with the last label name used.
+A label may be specified on the
+.Nm
+command line, via the
+.Dq load
+or
+.Dq dial
+commands and in the
+.Pa ppp.secret
+file.
+.It Li MYADDR
+This is replaced with the IP number assigned to the local interface.
+.It Li MYADDR6
+This is replaced with the IPv6 number assigned to the local interface.
+.It Li OCTETSIN
+This is replaced with the number of bytes received since the connection
+was established.
+.It Li OCTETSOUT
+This is replaced with the number of bytes sent since the connection
+was established.
+.It Li PACKETSIN
+This is replaced with the number of packets received since the connection
+was established.
+.It Li PACKETSOUT
+This is replaced with the number of packets sent since the connection
+was established.
+.It Li PEER_ENDDISC
+This is replaced with the value of the peers endpoint discriminator.
+.It Li PROCESSID
+This is replaced with the current process id.
+.It Li SOCKNAME
+This is replaced with the name of the diagnostic socket.
+.It Li UPTIME
+This is replaced with the bundle uptime in HH:MM:SS format.
+.It Li USER
+This is replaced with the username that has been authenticated with PAP or
+CHAP.
+Normally, this variable is assigned only in -direct mode.
+This value is available irrespective of whether utmp logging is enabled.
+.It Li VERSION
+This is replaced with the current version number of
+.Nm .
+.El
+.Pp
+These substitutions are also done by the
+.Dq set proctitle ,
+.Dq ident
+and
+.Dq log
+commands.
+.Pp
+If you wish to pause
+.Nm
+while the command executes, use the
+.Dq shell
+command instead.
+.It clear physical|ipcp|ipv6 Op current|overall|peak...
+Clear the specified throughput values at either the
+.Dq physical ,
+.Dq ipcp
+or
+.Dq ipv6cp
+level.
+If
+.Dq physical
+is specified, context must be given (see the
+.Dq link
+command below).
+If no second argument is given, all values are cleared.
+.It clone Ar name Ns Xo
+.Op \&, Ns Ar name Ns
+.No ...
+.Xc
+Clone the specified link, creating one or more new links according to the
+.Ar name
+argument(s).
+This command must be used from the
+.Dq link
+command below unless you have only got a single link (in which case that
+link becomes the default).
+Links may be removed using the
+.Dq remove
+command below.
+.Pp
+The default link name is
+.Dq deflink .
+.It close Op lcp|ccp Ns Op !\&
+If no arguments are given, the relevant protocol layers will be brought
+down and the link will be closed.
+If
+.Dq lcp
+is specified, the LCP layer is brought down, but
+.Nm
+will not bring the link offline.
+It is subsequently possible to use
+.Dq term
+(see below)
+to talk to the peer machine if, for example, something like
+.Dq slirp
+is being used.
+If
+.Dq ccp
+is specified, only the relevant compression layer is closed.
+If the
+.Dq !\&
+is used, the compression layer will remain in the closed state, otherwise
+it will re-enter the STOPPED state, waiting for the peer to initiate
+further CCP negotiation.
+In any event, this command does not disconnect the user from
+.Nm
+or exit
+.Nm .
+See the
+.Dq quit
+command below.
+.It delete Ns Xo
+.Op !\&
+.Ar dest
+.Xc
+This command deletes the route with the given
+.Ar dest
+IP address.
+If
+.Ar dest
+is specified as
+.Sq ALL ,
+all non-direct entries in the routing table for the current interface,
+and all
+.Sq sticky route
+entries are deleted.
+If
+.Ar dest
+is specified as
+.Sq default ,
+the default route is deleted.
+.Pp
+If the
+.Ar delete!\&
+command is used
+(note the trailing
+.Dq !\& ) ,
+.Nm
+will not complain if the route does not already exist.
+.It dial|call Op Ar label Ns Xo
+.No ...
+.Xc
+This command is the equivalent of
+.Dq load label
+followed by
+.Dq open ,
+and is provided for backwards compatibility.
+.It down Op Ar lcp|ccp
+Bring the relevant layer down ungracefully, as if the underlying layer
+had become unavailable.
+It is not considered polite to use this command on
+a Finite State Machine that is in the OPEN state.
+If no arguments are
+supplied, the entire link is closed (or if no context is given, all links
+are terminated).
+If
+.Sq lcp
+is specified, the
+.Em LCP
+layer is terminated but the device is not brought offline and the link
+is not closed.
+If
+.Sq ccp
+is specified, only the relevant compression layer(s) are terminated.
+.It help|? Op Ar command
+Show a list of available commands.
+If
+.Ar command
+is specified, show the usage string for that command.
+.It ident Op Ar text Ns No ...
+Identify the link to the peer using
+.Ar text .
+If
+.Ar text
+is empty, link identification is disabled.
+It is possible to use any of the words described for the
+.Ic bg
+command above.
+Refer to the
+.Ic sendident
+command for details of when
+.Nm
+identifies itself to the peer.
+.It iface Ar command Op args
+This command is used to control the interface used by
+.Nm .
+.Ar Command
+may be one of the following:
+.Bl -tag -width 2n
+.It iface add Ns Xo
+.Op !\&
+.Ar addr Ns Op / Ns Ar bits
+.Op Ar peer
+.Xc
+.It iface add Ns Xo
+.Op !\&
+.Ar addr
+.Ar mask
+.Ar peer
+.Xc
+Add the given
+.Ar addr mask peer
+combination to the interface.
+Instead of specifying
+.Ar mask ,
+.Ar /bits
+can be used
+(with no space between it and
+.Ar addr ) .
+If the given address already exists, the command fails unless the
+.Dq !\&
+is used - in which case the previous interface address entry is overwritten
+with the new one, allowing a change of netmask or peer address.
+.Pp
+If only
+.Ar addr
+is specified,
+.Ar bits
+defaults to
+.Dq 32
+and
+.Ar peer
+defaults to
+.Dq 255.255.255.255 .
+This address (the broadcast address) is the only duplicate peer address that
+.Nm
+allows.
+.It iface clear Op INET | INET6
+If this command is used while
+.Nm
+is in the OPENED state or while in
+.Fl auto
+mode, all addresses except for the NCP negotiated address are deleted
+from the interface.
+If
+.Nm
+is not in the OPENED state and is not in
+.Fl auto
+mode, all interface addresses are deleted.
+.Pp
+If the INET or INET6 arguments are used, only addresses for that address
+family are cleared.
+.Pp
+.It iface delete Ns Xo
+.Op !\& Ns
+.No |rm Ns Op !\&
+.Ar addr
+.Xc
+This command deletes the given
+.Ar addr
+from the interface.
+If the
+.Dq !\&
+is used, no error is given if the address is not currently assigned to
+the interface (and no deletion takes place).
+.It iface show
+Shows the current state and current addresses for the interface.
+It is much the same as running
+.Dq ifconfig INTERFACE .
+.It iface help Op Ar sub-command
+This command, when invoked without
+.Ar sub-command ,
+will show a list of possible
+.Dq iface
+sub-commands and a brief synopsis for each.
+When invoked with
+.Ar sub-command ,
+only the synopsis for the given sub-command is shown.
+.El
+.It Op data Ns Xo
+.No link
+.Ar name Ns Op , Ns Ar name Ns
+.No ... Ar command Op Ar args
+.Xc
+This command may prefix any other command if the user wishes to
+specify which link the command should affect.
+This is only applicable after multiple links have been created in Multi-link
+mode using the
+.Dq clone
+command.
+.Pp
+.Ar Name
+specifies the name of an existing link.
+If
+.Ar name
+is a comma separated list,
+.Ar command
+is executed on each link.
+If
+.Ar name
+is
+.Dq * ,
+.Ar command
+is executed on all links.
+.It load Op Ar label Ns Xo
+.No ...
+.Xc
+Load the given
+.Ar label Ns No (s)
+from the
+.Pa ppp.conf
+file.
+If
+.Ar label
+is not given, the
+.Ar default
+label is used.
+.Pp
+Unless the
+.Ar label
+section uses the
+.Dq set mode ,
+.Dq open
+or
+.Dq dial
+commands,
+.Nm
+will not attempt to make an immediate connection.
+.It log Ar word Ns No ...
+Send the given word(s) to the log file with the prefix
+.Dq LOG: .
+Word substitutions are done as explained under the
+.Dq !bg
+command above.
+.It open Op lcp|ccp|ipcp
+This is the opposite of the
+.Dq close
+command.
+All closed links are immediately brought up apart from second and subsequent
+.Ar demand-dial
+links - these will come up based on the
+.Dq set autoload
+command that has been used.
+.Pp
+If the
+.Dq lcp
+argument is used while the LCP layer is already open, LCP will be
+renegotiated.
+This allows various LCP options to be changed, after which
+.Dq open lcp
+can be used to put them into effect.
+After renegotiating LCP,
+any agreed authentication will also take place.
+.Pp
+If the
+.Dq ccp
+argument is used, the relevant compression layer is opened.
+Again, if it is already open, it will be renegotiated.
+.Pp
+If the
+.Dq ipcp
+argument is used, the link will be brought up as normal, but if
+IPCP is already open, it will be renegotiated and the network
+interface will be reconfigured.
+.Pp
+It is probably not good practice to re-open the PPP state machines
+like this as it is possible that the peer will not behave correctly.
+It
+.Em is
+however useful as a way of forcing the CCP or VJ dictionaries to be reset.
+.It passwd Ar pass
+Specify the password required for access to the full
+.Nm
+command set.
+This password is required when connecting to the diagnostic port (see the
+.Dq set server
+command).
+.Ar Pass
+is specified on the
+.Dq set server
+command line.
+The value of
+.Ar pass
+is not logged when
+.Ar command
+logging is active, instead, the literal string
+.Sq ********
+is logged.
+.It quit|bye Op all
+If
+.Dq quit
+is executed from the controlling connection or from a command file,
+ppp will exit after closing all connections.
+Otherwise, if the user
+is connected to a diagnostic socket, the connection is simply dropped.
+.Pp
+If the
+.Ar all
+argument is given,
+.Nm
+will exit despite the source of the command after closing all existing
+connections.
+.It remove|rm
+This command removes the given link.
+It is only really useful in multi-link mode.
+A link must be in the
+.Dv CLOSED
+state before it is removed.
+.It rename|mv Ar name
+This command renames the given link to
+.Ar name .
+It will fail if
+.Ar name
+is already used by another link.
+.Pp
+The default link name is
+.Sq deflink .
+Renaming it to
+.Sq modem ,
+.Sq cuad0
+or
+.Sq USR
+may make the log file more readable.
+.It resolv Ar command
+This command controls
+.Nm Ns No 's
+manipulation of the
+.Xr resolv.conf 5
+file.
+When
+.Nm
+starts up, it loads the contents of this file into memory and retains this
+image for future use.
+.Ar command
+is one of the following:
+.Bl -tag -width readonly
+.It Em readonly
+Treat
+.Pa /etc/resolv.conf
+as read only.
+If
+.Dq dns
+is enabled,
+.Nm
+will still attempt to negotiate nameservers with the peer, making the results
+available via the
+.Dv DNS0
+and
+.Dv DNS1
+macros.
+This is the opposite of the
+.Dq resolv writable
+command.
+.It Em reload
+Reload
+.Pa /etc/resolv.conf
+into memory.
+This may be necessary if for example a DHCP client overwrote
+.Pa /etc/resolv.conf .
+.It Em restore
+Replace
+.Pa /etc/resolv.conf
+with the version originally read at startup or with the last
+.Dq resolv reload
+command.
+This is sometimes a useful command to put in the
+.Pa /etc/ppp/ppp.linkdown
+file.
+.It Em rewrite
+Rewrite the
+.Pa /etc/resolv.conf
+file.
+This command will work even if the
+.Dq resolv readonly
+command has been used.
+It may be useful as a command in the
+.Pa /etc/ppp/ppp.linkup
+file if you wish to defer updating
+.Pa /etc/resolv.conf
+until after other commands have finished.
+.It Em writable
+Allow
+.Nm
+to update
+.Pa /etc/resolv.conf
+if
+.Dq dns
+is enabled and
+.Nm
+successfully negotiates a DNS.
+This is the opposite of the
+.Dq resolv readonly
+command.
+.El
+.It save
+This option is not (yet) implemented.
+.It sendident
+This command tells
+.Nm
+to identify itself to the peer.
+The link must be in LCP state or higher.
+If no identity has been set (via the
+.Ic ident
+command),
+.Ic sendident
+will fail.
+.Pp
+When an identity has been set,
+.Nm
+will automatically identify itself when it sends or receives a configure
+reject, when negotiation fails or when LCP reaches the opened state.
+.Pp
+Received identification packets are logged to the LCP log (see
+.Ic set log
+for details) and are never responded to.
+.It set Ns Xo
+.Op up
+.Ar var value
+.Xc
+This option allows the setting of any of the following variables:
+.Bl -tag -width 2n
+.It set accmap Ar hex-value
+ACCMap stands for Asynchronous Control Character Map.
+This is always
+negotiated with the peer, and defaults to a value of 00000000 in hex.
+This protocol is required to defeat hardware that depends on passing
+certain characters from end to end (such as XON/XOFF etc).
+.Pp
+For the XON/XOFF scenario, use
+.Dq set accmap 000a0000 .
+.It set Op auth Ns Xo
+.No key Ar value
+.Xc
+This sets the authentication key (or password) used in client mode
+PAP or CHAP negotiation to the given value.
+It also specifies the
+password to be used in the dial or login scripts in place of the
+.Sq \eP
+sequence, preventing the actual password from being logged.
+If
+.Ar command
+or
+.Ar chat
+logging is in effect,
+.Ar value
+is logged as
+.Sq ********
+for security reasons.
+.Pp
+If the first character of
+.Ar value
+is an exclamation mark
+.Pq Dq !\& ,
+.Nm
+treats the remainder of the string as a program that must be executed
+to determine the
+.Dq authname
+and
+.Dq authkey
+values.
+.Pp
+If the
+.Dq !\&
+is doubled up
+(to
+.Dq !! ) ,
+it is treated as a single literal
+.Dq !\& ,
+otherwise, ignoring the
+.Dq !\& ,
+.Ar value
+is parsed as a program to execute in the same was as the
+.Dq !bg
+command above, substituting special names in the same manner.
+Once executed,
+.Nm
+will feed the program three lines of input, each terminated by a newline
+character:
+.Bl -bullet
+.It
+The host name as sent in the CHAP challenge.
+.It
+The challenge string as sent in the CHAP challenge.
+.It
+The locally defined
+.Dq authname .
+.El
+.Pp
+Two lines of output are expected:
+.Bl -bullet
+.It
+The
+.Dq authname
+to be sent with the CHAP response.
+.It
+The
+.Dq authkey ,
+which is encrypted with the challenge and request id, the answer being sent
+in the CHAP response packet.
+.El
+.Pp
+When configuring
+.Nm
+in this manner, it is expected that the host challenge is a series of ASCII
+digits or characters.
+An encryption device or Secure ID card is usually
+required to calculate the secret appropriate for the given challenge.
+.It set authname Ar id
+This sets the authentication id used in client mode PAP or CHAP negotiation.
+.Pp
+If used in
+.Fl direct
+mode with CHAP enabled,
+.Ar id
+is used in the initial authentication challenge and should normally be set to
+the local machine name.
+.It set autoload Xo
+.Ar min-percent max-percent period
+.Xc
+These settings apply only in multi-link mode and default to zero, zero and
+five respectively.
+When more than one
+.Ar demand-dial
+(also known as
+.Fl auto )
+mode link is available, only the first link is made active when
+.Nm
+first reads data from the tun device.
+The next
+.Ar demand-dial
+link will be opened only when the current bundle throughput is at least
+.Ar max-percent
+percent of the total bundle bandwidth for
+.Ar period
+seconds.
+When the current bundle throughput decreases to
+.Ar min-percent
+percent or less of the total bundle bandwidth for
+.Ar period
+seconds, a
+.Ar demand-dial
+link will be brought down as long as it is not the last active link.
+.Pp
+Bundle throughput is measured as the maximum of inbound and outbound
+traffic.
+.Pp
+The default values cause
+.Ar demand-dial
+links to simply come up one at a time.
+.Pp
+Certain devices cannot determine their physical bandwidth, so it
+is sometimes necessary to use the
+.Dq set bandwidth
+command (described below) to make
+.Dq set autoload
+work correctly.
+.It set bandwidth Ar value
+This command sets the connection bandwidth in bits per second.
+.Ar value
+must be greater than zero.
+It is currently only used by the
+.Dq set autoload
+command above.
+.It set callback Ar option Ns No ...
+If no arguments are given, callback is disabled, otherwise,
+.Nm
+will request (or in
+.Fl direct
+mode, will accept) one of the given
+.Ar option Ns No s .
+In client mode, if an
+.Ar option
+is NAK'd
+.Nm
+will request a different
+.Ar option ,
+until no options remain at which point
+.Nm
+will terminate negotiations (unless
+.Dq none
+is one of the specified
+.Ar option ) .
+In server mode,
+.Nm
+will accept any of the given protocols - but the client
+.Em must
+request one of them.
+If you wish callback to be optional, you must {include}
+.Ar none
+as an option.
+.Pp
+The
+.Ar option Ns No s
+are as follows (in this order of preference):
+.Pp
+.Bl -tag -width Ds
+.It auth
+The callee is expected to decide the callback number based on
+authentication.
+If
+.Nm
+is the callee, the number should be specified as the fifth field of
+the peers entry in
+.Pa /etc/ppp/ppp.secret .
+.It cbcp
+Microsoft's callback control protocol is used.
+See
+.Dq set cbcp
+below.
+.Pp
+If you wish to negotiate
+.Ar cbcp
+in client mode but also wish to allow the server to request no callback at
+CBCP negotiation time, you must specify both
+.Ar cbcp
+and
+.Ar none
+as callback options.
+.It E.164 *| Ns Xo
+.Ar number Ns Op , Ns Ar number Ns
+.No ...
+.Xc
+The caller specifies the
+.Ar number .
+If
+.Nm
+is the callee,
+.Ar number
+should be either a comma separated list of allowable numbers or a
+.Dq \&* ,
+meaning any number is permitted.
+If
+.Nm
+is the caller, only a single number should be specified.
+.Pp
+Note, this option is very unsafe when used with a
+.Dq \&*
+as a malicious caller can tell
+.Nm
+to call any (possibly international) number without first authenticating
+themselves.
+.It none
+If the peer does not wish to do callback at all,
+.Nm
+will accept the fact and continue without callback rather than terminating
+the connection.
+This is required (in addition to one or more other callback
+options) if you wish callback to be optional.
+.El
+.Pp
+.It set cbcp Oo
+.No *| Ns Ar number Ns Oo
+.No , Ns Ar number Ns ...\& Oc
+.Op Ar delay Op Ar retry
+.Oc
+If no arguments are given, CBCP (Microsoft's CallBack Control Protocol)
+is disabled - ie, configuring CBCP in the
+.Dq set callback
+command will result in
+.Nm
+requesting no callback in the CBCP phase.
+Otherwise,
+.Nm
+attempts to use the given phone
+.Ar number Ns No (s).
+.Pp
+In server mode
+.Pq Fl direct ,
+.Nm
+will insist that the client uses one of these numbers, unless
+.Dq \&*
+is used in which case the client is expected to specify the number.
+.Pp
+In client mode,
+.Nm
+will attempt to use one of the given numbers (whichever it finds to
+be agreeable with the peer), or if
+.Dq \&*
+is specified,
+.Nm
+will expect the peer to specify the number.
+.It set cd Oo
+.No off| Ns Ar seconds Ns Op !\&
+.Oc
+Normally,
+.Nm
+checks for the existence of carrier depending on the type of device
+that has been opened:
+.Bl -tag -width XXX -offset XXX
+.It Terminal Devices
+Carrier is checked one second after the login script is complete.
+If it is not set,
+.Nm
+assumes that this is because the device does not support carrier (which
+is true for most
+.Dq laplink
+NULL-modem cables), logs the fact and stops checking
+for carrier.
+.Pp
+As ptys do not support the TIOCMGET ioctl, the tty device will switch all
+carrier detection off when it detects that the device is a pty.
+.It ISDN (i4b) Devices
+Carrier is checked once per second for 6 seconds.
+If it is not set after
+the sixth second, the connection attempt is considered to have failed and
+the device is closed.
+Carrier is always required for i4b devices.
+.It PPPoE (netgraph) Devices
+Carrier is checked once per second for 5 seconds.
+If it is not set after
+the fifth second, the connection attempt is considered to have failed and
+the device is closed.
+Carrier is always required for PPPoE devices.
+.El
+.Pp
+All other device types do not support carrier.
+Setting a carrier value will
+result in a warning when the device is opened.
+.Pp
+Some modems take more than one second after connecting to assert the carrier
+signal.
+If this delay is not increased, this will result in
+.Nm Ns No 's
+inability to detect when the link is dropped, as
+.Nm
+assumes that the device is not asserting carrier.
+.Pp
+The
+.Dq set cd
+command overrides the default carrier behaviour.
+.Ar seconds
+specifies the maximum number of seconds that
+.Nm
+should wait after the dial script has finished before deciding if
+carrier is available or not.
+.Pp
+If
+.Dq off
+is specified,
+.Nm
+will not check for carrier on the device, otherwise
+.Nm
+will not proceed to the login script until either carrier is detected
+or until
+.Ar seconds
+has elapsed, at which point
+.Nm
+assumes that the device will not set carrier.
+.Pp
+If no arguments are given, carrier settings will go back to their default
+values.
+.Pp
+If
+.Ar seconds
+is followed immediately by an exclamation mark
+.Pq Dq !\& ,
+.Nm
+will
+.Em require
+carrier.
+If carrier is not detected after
+.Ar seconds
+seconds, the link will be disconnected.
+.It set choked Op Ar timeout
+This sets the number of seconds that
+.Nm
+will keep a choked output queue before dropping all pending output packets.
+If
+.Ar timeout
+is less than or equal to zero or if
+.Ar timeout
+is not specified, it is set to the default value of
+.Em 120 seconds .
+.Pp
+A choked output queue occurs when
+.Nm
+has read a certain number of packets from the local network for transmission,
+but cannot send the data due to link failure (the peer is busy etc.).
+.Nm
+will not read packets indefinitely.
+Instead, it reads up to
+.Em 30
+packets (or
+.Em 30 No +
+.Em nlinks No *
+.Em 2
+packets in multi-link mode), then stops reading the network interface
+until either
+.Ar timeout
+seconds have passed or at least one packet has been sent.
+.Pp
+If
+.Ar timeout
+seconds pass, all pending output packets are dropped.
+.It set ctsrts|crtscts on|off
+This sets hardware flow control.
+Hardware flow control is
+.Ar on
+by default.
+.It set deflate Ar out-winsize Op Ar in-winsize
+This sets the DEFLATE algorithms default outgoing and incoming window
+sizes.
+Both
+.Ar out-winsize
+and
+.Ar in-winsize
+must be values between
+.Em 8
+and
+.Em 15 .
+If
+.Ar in-winsize
+is specified,
+.Nm
+will insist that this window size is used and will not accept any other
+values from the peer.
+.It set dns Op Ar primary Op Ar secondary
+This command specifies DNS overrides for the
+.Dq accept dns
+command.
+Refer to the
+.Dq accept
+command description above for details.
+This command does not affect the IP numbers requested using
+.Dq enable dns .
+.It set device|line Xo
+.Ar value Ns No ...
+.Xc
+This sets the device(s) to which
+.Nm
+will talk to the given
+.Dq value .
+.Pp
+All ISDN and serial device names are expected to begin with
+.Pa /dev/ .
+ISDN devices are usually called
+.Pa i4brbchX
+and serial devices are usually called
+.Pa cuaXX .
+.Pp
+If
+.Dq value
+does not begin with
+.Pa /dev/ ,
+it must either begin with an exclamation mark
+.Pq Dq !\& ,
+be of the format
+.No PPPoE: Ns Ar iface Ns Xo
+.Op \&: Ns Ar provider Ns
+.Xc
+(on
+.Xr netgraph 4
+enabled systems), or be of the format
+.Sm off
+.Ar host : port Op /tcp|udp .
+.Sm on
+.Pp
+If it begins with an exclamation mark, the rest of the device name is
+treated as a program name, and that program is executed when the device
+is opened.
+Standard input, output and error are fed back to
+.Nm
+and are read and written as if they were a regular device.
+.Pp
+If a
+.No PPPoE: Ns Ar iface Ns Xo
+.Op \&: Ns Ar provider Ns
+.Xc
+specification is given,
+.Nm
+will attempt to create a
+.Em PPP
+over Ethernet connection using the given
+.Ar iface
+interface by using
+.Xr netgraph 4 .
+If
+.Xr netgraph 4
+is not available,
+.Nm
+will attempt to load it using
+.Xr kldload 2 .
+If this fails, an external program must be used such as the
+.Xr pppoed 8
+program available under
+.Ox .
+The given
+.Ar provider
+is passed as the service name in the PPPoE Discovery Initiation (PADI)
+packet.
+If no provider is given, an empty value will be used.
+.Pp
+When a PPPoE connection is established,
+.Nm
+will place the name of the Access Concentrator in the environment variable
+.Ev ACNAME .
+.Pp
+Refer to
+.Xr netgraph 4
+and
+.Xr ng_pppoe 4
+for further details.
+.Pp
+If a
+.Ar host Ns No : Ns Ar port Ns Oo
+.No /tcp|udp
+.Oc
+specification is given,
+.Nm
+will attempt to connect to the given
+.Ar host
+on the given
+.Ar port .
+If a
+.Dq /tcp
+or
+.Dq /udp
+suffix is not provided, the default is
+.Dq /tcp .
+Refer to the section on
+.Em PPP OVER TCP and UDP
+above for further details.
+.Pp
+If multiple
+.Dq values
+are specified,
+.Nm
+will attempt to open each one in turn until it succeeds or runs out of
+devices.
+.It set dial Ar chat-script
+This specifies the chat script that will be used to dial the other
+side.
+See also the
+.Dq set login
+command below.
+Refer to
+.Xr chat 8
+and to the example configuration files for details of the chat script
+format.
+It is possible to specify some special
+.Sq values
+in your chat script as follows:
+.Bl -tag -width 2n
+.It Li \ec
+When used as the last character in a
+.Sq send
+string, this indicates that a newline should not be appended.
+.It Li \ed
+When the chat script encounters this sequence, it delays two seconds.
+.It Li \ep
+When the chat script encounters this sequence, it delays for one quarter of
+a second.
+.It Li \en
+This is replaced with a newline character.
+.It Li \er
+This is replaced with a carriage return character.
+.It Li \es
+This is replaced with a space character.
+.It Li \et
+This is replaced with a tab character.
+.It Li \eT
+This is replaced by the current phone number (see
+.Dq set phone
+below).
+.It Li \eP
+This is replaced by the current
+.Ar authkey
+value (see
+.Dq set authkey
+above).
+.It Li \eU
+This is replaced by the current
+.Ar authname
+value (see
+.Dq set authname
+above).
+.El
+.Pp
+Note that two parsers will examine these escape sequences, so in order to
+have the
+.Sq chat parser
+see the escape character, it is necessary to escape it from the
+.Sq command parser .
+This means that in practice you should use two escapes, for example:
+.Bd -literal -offset indent
+set dial "... ATDT\\\\T CONNECT"
+.Ed
+.Pp
+It is also possible to execute external commands from the chat script.
+To do this, the first character of the expect or send string is an
+exclamation mark
+.Pq Dq !\& .
+If a literal exclamation mark is required, double it up to
+.Dq !!\&
+and it will be treated as a single literal
+.Dq !\& .
+When the command is executed, standard input and standard output are
+directed to the open device (see the
+.Dq set device
+command), and standard error is read by
+.Nm
+and substituted as the expect or send string.
+If
+.Nm
+is running in interactive mode, file descriptor 3 is attached to
+.Pa /dev/tty .
+.Pp
+For example (wrapped for readability):
+.Bd -literal -offset indent
+set login "TIMEOUT 5 \\"\\" \\"\\" login:--login: ppp \e
+word: ppp \\"!sh \\\\-c \\\\\\"echo \\\\-n label: >&2\\\\\\"\\" \e
+\\"!/bin/echo in\\" HELLO"
+.Ed
+.Pp
+would result in the following chat sequence (output using the
+.Sq set log local chat
+command before dialing):
+.Bd -literal -offset indent
+Dial attempt 1 of 1
+dial OK!
+Chat: Expecting:
+Chat: Sending:
+Chat: Expecting: login:--login:
+Chat: Wait for (5): login:
+Chat: Sending: ppp
+Chat: Expecting: word:
+Chat: Wait for (5): word:
+Chat: Sending: ppp
+Chat: Expecting: !sh \\-c "echo \\-n label: >&2"
+Chat: Exec: sh -c "echo -n label: >&2"
+Chat: Wait for (5): !sh \\-c "echo \\-n label: >&2" --> label:
+Chat: Exec: /bin/echo in
+Chat: Sending:
+Chat: Expecting: HELLO
+Chat: Wait for (5): HELLO
+login OK!
+.Ed
+.Pp
+Note (again) the use of the escape character, allowing many levels of
+nesting.
+Here, there are four parsers at work.
+The first parses the original line, reading it as three arguments.
+The second parses the third argument, reading it as 11 arguments.
+At this point, it is
+important that the
+.Dq \&-
+signs are escaped, otherwise this parser will see them as constituting
+an expect-send-expect sequence.
+When the
+.Dq !\&
+character is seen, the execution parser reads the first command as three
+arguments, and then
+.Xr sh 1
+itself expands the argument after the
+.Fl c .
+As we wish to send the output back to the modem, in the first example
+we redirect our output to file descriptor 2 (stderr) so that
+.Nm
+itself sends and logs it, and in the second example, we just output to stdout,
+which is attached directly to the modem.
+.Pp
+This, of course means that it is possible to execute an entirely external
+.Dq chat
+command rather than using the internal one.
+See
+.Xr chat 8
+for a good alternative.
+.Pp
+The external command that is executed is subjected to the same special
+word expansions as the
+.Dq !bg
+command.
+.It set enddisc Op label|IP|MAC|magic|psn value
+This command sets our local endpoint discriminator.
+If set prior to LCP negotiation, and if no
+.Dq disable enddisc
+command has been used,
+.Nm
+will send the information to the peer using the LCP endpoint discriminator
+option.
+The following discriminators may be set:
+.Bl -tag -width indent
+.It Li label
+The current label is used.
+.It Li IP
+Our local IP number is used.
+As LCP is negotiated prior to IPCP, it is
+possible that the IPCP layer will subsequently change this value.
+If
+it does, the endpoint discriminator stays at the old value unless manually
+reset.
+.It Li MAC
+This is similar to the
+.Ar IP
+option above, except that the MAC address associated with the local IP
+number is used.
+If the local IP number is not resident on any Ethernet
+interface, the command will fail.
+.Pp
+As the local IP number defaults to whatever the machine host name is,
+.Dq set enddisc mac
+is usually done prior to any
+.Dq set ifaddr
+commands.
+.It Li magic
+A 20 digit random number is used.
+Care should be taken when using magic numbers as restarting
+.Nm
+or creating a link using a different
+.Nm
+invocation will also use a different magic number and will therefore not
+be recognised by the peer as belonging to the same bundle.
+This makes it unsuitable for
+.Fl direct
+connections.
+.It Li psn Ar value
+The given
+.Ar value
+is used.
+.Ar Value
+should be set to an absolute public switched network number with the
+country code first.
+.El
+.Pp
+If no arguments are given, the endpoint discriminator is reset.
+.It set escape Ar value...
+This option is similar to the
+.Dq set accmap
+option above.
+It allows the user to specify a set of characters that will be
+.Sq escaped
+as they travel across the link.
+.It set filter dial|alive|in|out Ar rule-no Xo
+.No permit|deny|clear| Ns Ar rule-no
+.Op !\&
+.Oo Op host
+.Ar src_addr Ns Op / Ns Ar width
+.Op Ar dst_addr Ns Op / Ns Ar width
+.Oc [ Ns Ar proto
+.Op src lt|eq|gt Ar port
+.Op dst lt|eq|gt Ar port
+.Op estab
+.Op syn
+.Op finrst
+.Op timeout Ar secs ]
+.Xc
+.Nm
+supports four filter sets.
+The
+.Em alive
+filter specifies packets that keep the connection alive - resetting the
+idle timer.
+The
+.Em dial
+filter specifies packets that cause
+.Nm
+to dial when in
+.Fl auto
+mode.
+The
+.Em in
+filter specifies packets that are allowed to travel
+into the machine and the
+.Em out
+filter specifies packets that are allowed out of the machine.
+.Pp
+Filtering is done prior to any IP alterations that might be done by the
+NAT engine on outgoing packets and after any IP alterations that might
+be done by the NAT engine on incoming packets.
+By default all empty filter sets allow all packets to pass.
+Rules are processed in order according to
+.Ar rule-no
+(unless skipped by specifying a rule number as the
+.Ar action ) .
+Up to 40 rules may be given for each set.
+If a packet does not match
+any of the rules in a given set, it is discarded.
+In the case of
+.Em in
+and
+.Em out
+filters, this means that the packet is dropped.
+In the case of
+.Em alive
+filters it means that the packet will not reset the idle timer (even if
+the
+.Ar in Ns No / Ns Ar out
+filter has a
+.Dq timeout
+value) and in the case of
+.Em dial
+filters it means that the packet will not trigger a dial.
+A packet failing to trigger a dial will be dropped rather than queued.
+Refer to the
+section on
+.Sx PACKET FILTERING
+above for further details.
+.It set hangup Ar chat-script
+This specifies the chat script that will be used to reset the device
+before it is closed.
+It should not normally be necessary, but can
+be used for devices that fail to reset themselves properly on close.
+.It set help|? Op Ar command
+This command gives a summary of available set commands, or if
+.Ar command
+is specified, the command usage is shown.
+.It set ifaddr Oo Ar myaddr Ns
+.Op / Ns Ar \&nn
+.Oo Ar hisaddr Ns Op / Ns Ar \&nn
+.Oo Ar netmask
+.Op Ar triggeraddr
+.Oc Oc
+.Oc
+This command specifies the IP addresses that will be used during
+IPCP negotiation.
+Addresses are specified using the format
+.Pp
+.Dl a.b.c.d/nn
+.Pp
+Where
+.Dq a.b.c.d
+is the preferred IP, but
+.Ar nn
+specifies how many bits of the address we will insist on.
+If
+.No / Ns Ar nn
+is omitted, it defaults to
+.Dq /32
+unless the IP address is 0.0.0.0 in which case it defaults to
+.Dq /0 .
+.Pp
+If you wish to assign a dynamic IP number to the peer,
+.Ar hisaddr
+may also be specified as a range of IP numbers in the format
+.Bd -ragged -offset indent
+.Ar \&IP Ns Oo \&- Ns Ar \&IP Ns Xo
+.Oc Ns Oo , Ns Ar \&IP Ns
+.Op \&- Ns Ar \&IP Ns
+.Oc Ns ...
+.Xc
+.Ed
+.Pp
+for example:
+.Pp
+.Dl set ifaddr 10.0.0.1 10.0.1.2-10.0.1.10,10.0.1.20
+.Pp
+will only negotiate
+.Dq 10.0.0.1
+as the local IP number, but may assign any of the given 10 IP
+numbers to the peer.
+If the peer requests one of these numbers,
+and that number is not already in use,
+.Nm
+will grant the peers request.
+This is useful if the peer wants
+to re-establish a link using the same IP number as was previously
+allocated (thus maintaining any existing tcp or udp connections).
+.Pp
+If the peer requests an IP number that is either outside
+of this range or is already in use,
+.Nm
+will suggest a random unused IP number from the range.
+.Pp
+If
+.Ar triggeraddr
+is specified, it is used in place of
+.Ar myaddr
+in the initial IPCP negotiation.
+However, only an address in the
+.Ar myaddr
+range will be accepted.
+This is useful when negotiating with some
+.Dv PPP
+implementations that will not assign an IP number unless their peer
+requests
+.Dq 0.0.0.0 .
+.Pp
+It should be noted that in
+.Fl auto
+mode,
+.Nm
+will configure the interface immediately upon reading the
+.Dq set ifaddr
+line in the config file.
+In any other mode, these values are just
+used for IPCP negotiations, and the interface is not configured
+until the IPCP layer is up.
+.Pp
+Note that the
+.Ar HISADDR
+argument may be overridden by the third field in the
+.Pa ppp.secret
+file once the client has authenticated itself
+(if PAP or CHAP are
+.Dq enabled ) .
+Refer to the
+.Sx AUTHENTICATING INCOMING CONNECTIONS
+section for details.
+.Pp
+In all cases, if the interface is already configured,
+.Nm
+will try to maintain the interface IP numbers so that any existing
+bound sockets will remain valid.
+.It set ifqueue Ar packets
+Set the maximum number of packets that
+.Nm
+will read from the tunnel interface while data cannot be sent to any of
+the available links.
+This queue limit is necessary to flow control outgoing data as the tunnel
+interface is likely to be far faster than the combined links available to
+.Nm .
+.Pp
+If
+.Ar packets
+is set to a value less than the number of links,
+.Nm
+will read up to that value regardless.
+This prevents any possible latency problems.
+.Pp
+The default value for
+.Ar packets
+is
+.Dq 30 .
+.It set ccpretry|ccpretries Oo Ar timeout
+.Op Ar reqtries Op Ar trmtries
+.Oc
+.It set chapretry|chapretries Oo Ar timeout
+.Op Ar reqtries
+.Oc
+.It set ipcpretry|ipcpretries Oo Ar timeout
+.Op Ar reqtries Op Ar trmtries
+.Oc
+.It set ipv6cpretry|ipv6cpretries Oo Ar timeout
+.Op Ar reqtries Op Ar trmtries
+.Oc
+.It set lcpretry|lcpretries Oo Ar timeout
+.Op Ar reqtries Op Ar trmtries
+.Oc
+.It set papretry|papretries Oo Ar timeout
+.Op Ar reqtries
+.Oc
+These commands set the number of seconds that
+.Nm
+will wait before resending Finite State Machine (FSM) Request packets.
+The default
+.Ar timeout
+for all FSMs is 3 seconds (which should suffice in most cases).
+.Pp
+If
+.Ar reqtries
+is specified, it tells
+.Nm
+how many configuration request attempts it should make while receiving
+no reply from the peer before giving up.
+The default is 5 attempts for
+CCP, LCP and IPCP and 3 attempts for PAP and CHAP.
+.Pp
+If
+.Ar trmtries
+is specified, it tells
+.Nm
+how many terminate requests should be sent before giving up waiting for the
+peers response.
+The default is 3 attempts.
+Authentication protocols are
+not terminated and it is therefore invalid to specify
+.Ar trmtries
+for PAP or CHAP.
+.Pp
+In order to avoid negotiations with the peer that will never converge,
+.Nm
+will only send at most 3 times the configured number of
+.Ar reqtries
+in any given negotiation session before giving up and closing that layer.
+.It set log Xo
+.Op local
+.Op +|- Ns
+.Ar value Ns No ...
+.Xc
+This command allows the adjustment of the current log level.
+Refer to the Logging Facility section for further details.
+.It set login Ar chat-script
+This
+.Ar chat-script
+compliments the dial-script.
+If both are specified, the login
+script will be executed after the dial script.
+Escape sequences available in the dial script are also available here.
+.It set logout Ar chat-script
+This specifies the chat script that will be used to logout
+before the hangup script is called.
+It should not normally be necessary.
+.It set lqrperiod|echoperiod Ar frequency
+This command sets the
+.Ar frequency
+in seconds at which
+.Em LQR
+or
+.Em LCP ECHO
+packets are sent.
+The default is 30 seconds.
+You must also use the
+.Dq enable lqr
+and/or
+.Dq enable echo
+commands if you wish to send
+.Em LQR
+or
+.Em LCP ECHO
+requests to the peer.
+.It set mode Ar interactive|auto|ddial|background
+This command allows you to change the
+.Sq mode
+of the specified link.
+This is normally only useful in multi-link mode,
+but may also be used in uni-link mode.
+.Pp
+It is not possible to change a link that is
+.Sq direct
+or
+.Sq dedicated .
+.Pp
+Note: If you issue the command
+.Dq set mode auto ,
+and have network address translation enabled, it may be useful to
+.Dq enable iface-alias
+afterwards.
+This will allow
+.Nm
+to do the necessary address translations to enable the process that
+triggers the connection to connect once the link is up despite the
+peer assigning us a new (dynamic) IP address.
+.It set mppe Op 40|56|128|* Op stateless|stateful|*
+This option selects the encryption parameters used when negotiation
+MPPE.
+MPPE can be disabled entirely with the
+.Dq disable mppe
+command.
+If no arguments are given,
+.Nm
+will attempt to negotiate a stateful link with a 128 bit key, but
+will agree to whatever the peer requests (including no encryption
+at all).
+.Pp
+If any arguments are given,
+.Nm
+will
+.Em insist
+on using MPPE and will close the link if it is rejected by the peer (Note;
+this behaviour can be overridden by a configured RADIUS server).
+.Pp
+The first argument specifies the number of bits that
+.Nm
+should insist on during negotiations and the second specifies whether
+.Nm
+should insist on stateful or stateless mode.
+In stateless mode, the
+encryption dictionary is re-initialised with every packet according to
+an encryption key that is changed with every packet.
+In stateful mode,
+the encryption dictionary is re-initialised every 256 packets or after
+the loss of any data and the key is changed every 256 packets.
+Stateless mode is less efficient but is better for unreliable transport
+layers.
+.It set mrru Op Ar value
+Setting this option enables Multi-link PPP negotiations, also known as
+Multi-link Protocol or MP.
+There is no default MRRU (Maximum Reconstructed Receive Unit) value.
+If no argument is given, multi-link mode is disabled.
+.It set mru Xo
+.Op max Ns Op imum
+.Op Ar value
+.Xc
+The default MRU (Maximum Receive Unit) is 1500.
+If it is increased, the other side *may* increase its MTU.
+In theory there is no point in decreasing the MRU to below the default as the
+.Em PPP
+protocol says implementations *must* be able to accept packets of at
+least 1500 octets.
+.Pp
+If the
+.Dq maximum
+keyword is used,
+.Nm
+will refuse to negotiate a higher value.
+The maximum MRU can be set to 2048 at most.
+Setting a maximum of less than 1500 violates the
+.Em PPP
+rfc, but may sometimes be necessary.
+For example,
+.Em PPPoE
+imposes a maximum of 1492 due to hardware limitations.
+.Pp
+If no argument is given, 1500 is assumed.
+A value must be given when
+.Dq maximum
+is specified.
+.It set mtu Xo
+.Op max Ns Op imum
+.Op Ar value
+.Xc
+The default MTU is 1500.
+At negotiation time,
+.Nm
+will accept whatever MRU the peer requests (assuming it is
+not less than 296 bytes or greater than the assigned maximum).
+If the MTU is set,
+.Nm
+will not accept MRU values less than
+.Ar value .
+When negotiations are complete, the MTU is used when writing to the
+interface, even if the peer requested a higher value MRU.
+This can be useful for
+limiting your packet size (giving better bandwidth sharing at the expense
+of more header data).
+.Pp
+If the
+.Dq maximum
+keyword is used,
+.Nm
+will refuse to negotiate a higher value.
+The maximum MTU can be set to 2048 at most.
+Note, it is necessary to use the
+.Dq maximum
+keyword to limit the MTU when using PPPoE.
+.Pp
+If no
+.Ar value
+is given, 1500, or whatever the peer asks for is used.
+A value must be given when
+.Dq maximum
+is specified.
+.It set nbns Op Ar x.x.x.x Op Ar y.y.y.y
+This option allows the setting of the Microsoft NetBIOS name server
+values to be returned at the peers request.
+If no values are given,
+.Nm
+will reject any such requests.
+.It set openmode active|passive Op Ar delay
+By default,
+.Ar openmode
+is always
+.Ar active
+with a one second
+.Ar delay .
+That is,
+.Nm
+will always initiate LCP/IPCP/CCP negotiation one second after the line
+comes up.
+If you want to wait for the peer to initiate negotiations, you
+can use the value
+.Ar passive .
+If you want to initiate negotiations immediately or after more than one
+second, the appropriate
+.Ar delay
+may be specified here in seconds.
+.It set parity odd|even|none|mark
+This allows the line parity to be set.
+The default value is
+.Ar none .
+.It set phone Ar telno Ns Xo
+.Oo \&| Ns Ar backupnumber
+.Oc Ns ... Ns Oo : Ns Ar nextnumber
+.Oc Ns ...
+.Xc
+This allows the specification of the phone number to be used in
+place of the \\\\T string in the dial and login chat scripts.
+Multiple phone numbers may be given separated either by a pipe
+.Pq Dq \&|
+or a colon
+.Pq Dq \&: .
+.Pp
+Numbers after the pipe are only dialed if the dial or login
+script for the previous number failed.
+.Pp
+Numbers after the colon are tried sequentially, irrespective of
+the reason the line was dropped.
+.Pp
+If multiple numbers are given,
+.Nm
+will dial them according to these rules until a connection is made, retrying
+the maximum number of times specified by
+.Dq set redial
+below.
+In
+.Fl background
+mode, each number is attempted at most once.
+.It set pppoe Op standard|3Com
+This option configures the underlying
+.Xr ng_pppoe 4
+node to either standard RFC2516 PPPoE or proprietary 3Com mode.
+If not set the system default will be used.
+.It set Op proc Ns Xo
+.No title Op Ar value
+.Xc
+The current process title as displayed by
+.Xr ps 1
+is changed according to
+.Ar value .
+If
+.Ar value
+is not specified, the original process title is restored.
+All the
+word replacements done by the shell commands (see the
+.Dq bg
+command above) are done here too.
+.Pp
+Note, if USER is required in the process title, the
+.Dq set proctitle
+command must appear in
+.Pa ppp.linkup ,
+as it is not known when the commands in
+.Pa ppp.conf
+are executed.
+.It set radius Op Ar config-file
+This command enables RADIUS support (if it is compiled in).
+.Ar config-file
+refers to the radius client configuration file as described in
+.Xr radius.conf 5 .
+If PAP, CHAP, MSCHAP or MSCHAPv2 are
+.Dq enable Ns No d ,
+.Nm
+behaves as a
+.Em \&N Ns No etwork
+.Em \&A Ns No ccess
+.Em \&S Ns No erver
+and uses the configured RADIUS server to authenticate rather than
+authenticating from the
+.Pa ppp.secret
+file or from the passwd database.
+.Pp
+If none of PAP, CHAP, MSCHAP or MSCHAPv2 are enabled,
+.Dq set radius
+will do nothing.
+.Pp
+.Nm
+uses the following attributes from the RADIUS reply:
+.Bl -tag -width XXX -offset XXX
+.It RAD_FRAMED_IP_ADDRESS
+The peer IP address is set to the given value.
+.It RAD_FRAMED_IP_NETMASK
+The tun interface netmask is set to the given value.
+.It RAD_FRAMED_MTU
+If the given MTU is less than the peers MRU as agreed during LCP
+negotiation, *and* it is less that any configured MTU (see the
+.Dq set mru
+command), the tun interface MTU is set to the given value.
+.It RAD_FRAMED_COMPRESSION
+If the received compression type is
+.Dq 1 ,
+.Nm
+will request VJ compression during IPCP negotiations despite any
+.Dq disable vj
+configuration command.
+.It RAD_FILTER_ID
+If this attribute is supplied,
+.Nm
+will attempt to use it as an additional label to load from the
+.Pa ppp.linkup
+and
+.Pa ppp.linkdown
+files.
+The load will be attempted before (and in addition to) the normal
+label search.
+If the label does not exist, no action is taken and
+.Nm
+proceeds to the normal load using the current label.
+.It RAD_FRAMED_ROUTE
+The received string is expected to be in the format
+.Ar dest Ns Op / Ns Ar bits
+.Ar gw
+.Op Ar metrics .
+Any specified metrics are ignored.
+.Dv MYADDR
+and
+.Dv HISADDR
+are understood as valid values for
+.Ar dest
+and
+.Ar gw ,
+.Dq default
+can be used for
+.Ar dest
+to sepcify the default route, and
+.Dq 0.0.0.0
+is understood to be the same as
+.Dq default
+for
+.Ar dest
+and
+.Dv HISADDR
+for
+.Ar gw .
+.Pp
+For example, a returned value of
+.Dq 1.2.3.4/24 0.0.0.0 1 2 -1 3 400
+would result in a routing table entry to the 1.2.3.0/24 network via
+.Dv HISADDR
+and a returned value of
+.Dq 0.0.0.0 0.0.0.0
+or
+.Dq default HISADDR
+would result in a default route to
+.Dv HISADDR .
+.Pp
+All RADIUS routes are applied after any sticky routes are applied, making
+RADIUS routes override configured routes.
+This also applies for RADIUS routes that do not {include} the
+.Dv MYADDR
+or
+.Dv HISADDR
+keywords.
+.Pp
+.It RAD_FRAMED_IPV6_PREFIX
+If this attribute is supplied, the value is substituted for IPV6PREFIX
+in a command.
+You may pass it to an upper layer protocol such as DHCPv6 for delegating an
+IPv6 prefix to a peer.
+.It RAD_FRAMED_IPV6_ROUTE
+The received string is expected to be in the format
+.Ar dest Ns Op / Ns Ar bits
+.Ar gw
+.Op Ar metrics .
+Any specified metrics are ignored.
+.Dv MYADDR6
+and
+.Dv HISADDR6
+are understood as valid values for
+.Ar dest
+and
+.Ar gw ,
+.Dq default
+can be used for
+.Ar dest
+to sepcify the default route, and
+.Dq ::
+is understood to be the same as
+.Dq default
+for
+.Ar dest
+and
+.Dv HISADDR6
+for
+.Ar gw .
+.Pp
+For example, a returned value of
+.Dq 3ffe:505:abcd::/48 ::
+would result in a routing table entry to the 3ffe:505:abcd::/48 network via
+.Dv HISADDR6
+and a returned value of
+.Dq :: ::
+or
+.Dq default HISADDR6
+would result in a default route to
+.Dv HISADDR6 .
+.Pp
+All RADIUS IPv6 routes are applied after any sticky routes are
+applied, making RADIUS IPv6 routes override configured routes.
+This
+also applies for RADIUS IPv6 routes that do not {include} the
+.Dv MYADDR6
+or
+.Dv HISADDR6
+keywords.
+.Pp
+.It RAD_SESSION_TIMEOUT
+If supplied, the client connection is closed after the given number of
+seconds.
+.It RAD_REPLY_MESSAGE
+If supplied, this message is passed back to the peer as the authentication
+SUCCESS text.
+.It RAD_MICROSOFT_MS_CHAP_ERROR
+If this
+.Dv RAD_VENDOR_MICROSOFT
+vendor specific attribute is supplied, it is passed back to the peer as the
+authentication FAILURE text.
+.It RAD_MICROSOFT_MS_CHAP2_SUCCESS
+If this
+.Dv RAD_VENDOR_MICROSOFT
+vendor specific attribute is supplied and if MS-CHAPv2 authentication is
+being used, it is passed back to the peer as the authentication SUCCESS text.
+.It RAD_MICROSOFT_MS_MPPE_ENCRYPTION_POLICY
+If this
+.Dv RAD_VENDOR_MICROSOFT
+vendor specific attribute is supplied and has a value of 2 (Required),
+.Nm
+will insist that MPPE encryption is used (even if no
+.Dq set mppe
+configuration command has been given with arguments).
+If it is supplied with a value of 1 (Allowed), encryption is made optional
+(despite any
+.Dq set mppe
+configuration commands with arguments).
+.It RAD_MICROSOFT_MS_MPPE_ENCRYPTION_TYPES
+If this
+.Dv RAD_VENDOR_MICROSOFT
+vendor specific attribute is supplied, bits 1 and 2 are examined.
+If either or both are set, 40 bit and/or 128 bit (respectively) encryption
+options are set, overriding any given first argument to the
+.Dq set mppe
+command.
+Note, it is not currently possible for the RADIUS server to specify 56 bit
+encryption.
+.It RAD_MICROSOFT_MS_MPPE_RECV_KEY
+If this
+.Dv RAD_VENDOR_MICROSOFT
+vendor specific attribute is supplied, it is value is used as the master
+key for decryption of incoming data.
+When clients are authenticated using
+MSCHAPv2, the RADIUS server MUST provide this attribute if inbound MPPE is
+to function.
+.It RAD_MICROSOFT_MS_MPPE_SEND_KEY
+If this
+.Dv RAD_VENDOR_MICROSOFT
+vendor specific attribute is supplied, it is value is used as the master
+key for encryption of outgoing data.
+When clients are authenticated using
+MSCHAPv2, the RADIUS server MUST provide this attribute if outbound MPPE is
+to function.
+.El
+.Pp
+Values received from the RADIUS server may be viewed using
+.Dq show bundle .
+.It set rad_alive Ar timeout
+When RADIUS is configured, setting
+.Dq rad_alive
+to a non-zero
+.Ar timeout
+value will tell
+.Nm
+to sent RADIUS accounting information to the RADIUS server every
+.Ar timeout
+seconds.
+.It set rad_port_id Ar option
+When RADIUS is configured, setting the
+.Dq rad_port_id
+value allows to specify what should be sent to the RADIUS server as
+NAS-Port-Id.
+The
+.Ar option Ns No s
+are as follows:
+.Pp
+.Bl -tag -width Ds
+.It pid
+PID of the corresponding tunnel.
+.It tunnum
+.Xr tun 4
+interface number.
+.It ifnum
+index of the interface as returned by
+.Xr if_nametoindex 3 .
+.It default
+keeps the default behavior.
+.El
+.It set reconnect Ar timeout ntries
+Should the line drop unexpectedly (due to loss of CD or LQR
+failure), a connection will be re-established after the given
+.Ar timeout .
+The line will be re-connected at most
+.Ar ntries
+times.
+.Ar Ntries
+defaults to zero.
+A value of
+.Ar random
+for
+.Ar timeout
+will result in a variable pause, somewhere between 1 and 30 seconds.
+.It set recvpipe Op Ar value
+This sets the routing table RECVPIPE value.
+The optimum value is just over twice the MTU value.
+If
+.Ar value
+is unspecified or zero, the default kernel controlled value is used.
+.It set redial Ar secs Ns Xo
+.Oo + Ns Ar inc Ns
+.Op - Ns Ar max Ns
+.Oc Ns Op . Ns Ar next
+.Op Ar attempts
+.Xc
+.Nm
+can be instructed to attempt to redial
+.Ar attempts
+times.
+If more than one phone number is specified (see
+.Dq set phone
+above), a pause of
+.Ar next
+is taken before dialing each number.
+A pause of
+.Ar secs
+is taken before starting at the first number again.
+A literal value of
+.Dq Li random
+may be used here in place of
+.Ar secs
+and
+.Ar next ,
+causing a random delay of between 1 and 30 seconds.
+.Pp
+If
+.Ar inc
+is specified, its value is added onto
+.Ar secs
+each time
+.Nm
+tries a new number.
+.Ar secs
+will only be incremented at most
+.Ar max
+times.
+.Ar max
+defaults to 10.
+.Pp
+Note, the
+.Ar secs
+delay will be effective, even after
+.Ar attempts
+has been exceeded, so an immediate manual dial may appear to have
+done nothing.
+If an immediate dial is required, a
+.Dq !\&
+should immediately follow the
+.Dq open
+keyword.
+See the
+.Dq open
+description above for further details.
+.It set sendpipe Op Ar value
+This sets the routing table SENDPIPE value.
+The optimum value is just over twice the MTU value.
+If
+.Ar value
+is unspecified or zero, the default kernel controlled value is used.
+.It "set server|socket" Ar TcpPort Ns No \&| Ns Xo
+.Ar LocalName Ns No |none|open|closed
+.Op password Op Ar mask
+.Xc
+This command tells
+.Nm
+to listen on the given socket or
+.Sq diagnostic port
+for incoming command connections.
+.Pp
+The word
+.Dq none
+instructs
+.Nm
+to close any existing socket and clear the socket configuration.
+The word
+.Dq open
+instructs
+.Nm
+to attempt to re-open the port.
+The word
+.Dq closed
+instructs
+.Nm
+to close the open port.
+.Pp
+If you wish to specify a local domain socket,
+.Ar LocalName
+must be specified as an absolute file name, otherwise it is assumed
+to be the name or number of a TCP port.
+You may specify the octal umask to be used with a local domain socket.
+Refer to
+.Xr umask 2
+for umask details.
+Refer to
+.Xr services 5
+for details of how to translate TCP port names.
+.Pp
+You must also specify the password that must be entered by the client
+(using the
+.Dq passwd
+variable above) when connecting to this socket.
+If the password is
+specified as an empty string, no password is required for connecting clients.
+.Pp
+When specifying a local domain socket, the first
+.Dq %d
+sequence found in the socket name will be replaced with the current
+interface unit number.
+This is useful when you wish to use the same
+profile for more than one connection.
+.Pp
+In a similar manner TCP sockets may be prefixed with the
+.Dq +
+character, in which case the current interface unit number is added to
+the port number.
+.Pp
+When using
+.Nm
+with a server socket, the
+.Xr pppctl 8
+command is the preferred mechanism of communications.
+Currently,
+.Xr telnet 1
+can also be used, but link encryption may be implemented in the future, so
+.Xr telnet 1
+should be avoided.
+.Pp
+Note;
+.Dv SIGUSR1
+and
+.Dv SIGUSR2
+interact with the diagnostic socket.
+.It set speed Ar value
+This sets the speed of the serial device.
+If speed is specified as
+.Dq sync ,
+.Nm
+treats the device as a synchronous device.
+.Pp
+Certain device types will know whether they should be specified as
+synchronous or asynchronous.
+These devices will override incorrect
+settings and log a warning to this effect.
+.It set stopped Op Ar LCPseconds Op Ar CCPseconds
+If this option is set,
+.Nm
+will time out after the given FSM (Finite State Machine) has been in
+the stopped state for the given number of
+.Dq seconds .
+This option may be useful if the peer sends a terminate request,
+but never actually closes the connection despite our sending a terminate
+acknowledgement.
+This is also useful if you wish to
+.Dq set openmode passive
+and time out if the peer does not send a Configure Request within the
+given time.
+Use
+.Dq set log +lcp +ccp
+to make
+.Nm
+log the appropriate state transitions.
+.Pp
+The default value is zero, where
+.Nm
+does not time out in the stopped state.
+.Pp
+This value should not be set to less than the openmode delay (see
+.Dq set openmode
+above).
+.It set timeout Ar idleseconds Op Ar mintimeout
+This command allows the setting of the idle timer.
+Refer to the section titled
+.Sx SETTING THE IDLE TIMER
+for further details.
+.Pp
+If
+.Ar mintimeout
+is specified,
+.Nm
+will never idle out before the link has been up for at least that number
+of seconds.
+.It set urgent Xo
+.Op tcp|udp|none
+.Oo Op +|- Ns
+.Ar port
+.Oc No ...
+.Xc
+This command controls the ports that
+.Nm
+prioritizes when transmitting data.
+The default priority TCP ports
+are ports 21 (ftp control), 22 (ssh), 23 (telnet), 513 (login), 514 (shell),
+543 (klogin) and 544 (kshell).
+There are no priority UDP ports by default.
+See
+.Xr services 5
+for details.
+.Pp
+If neither
+.Dq tcp
+or
+.Dq udp
+are specified,
+.Dq tcp
+is assumed.
+.Pp
+If no
+.Ar port Ns No s
+are given, the priority port lists are cleared (although if
+.Dq tcp
+or
+.Dq udp
+is specified, only that list is cleared).
+If the first
+.Ar port
+argument is prefixed with a plus
+.Pq Dq \&+
+or a minus
+.Pq Dq \&- ,
+the current list is adjusted, otherwise the list is reassigned.
+.Ar port Ns No s
+prefixed with a plus or not prefixed at all are added to the list and
+.Ar port Ns No s
+prefixed with a minus are removed from the list.
+.Pp
+If
+.Dq none
+is specified, all priority port lists are disabled and even
+.Dv IPTOS_LOWDELAY
+packets are not prioritised.
+.It set vj slotcomp on|off
+This command tells
+.Nm
+whether it should attempt to negotiate VJ slot compression.
+By default, slot compression is turned
+.Ar on .
+.It set vj slots Ar nslots
+This command sets the initial number of slots that
+.Nm
+will try to negotiate with the peer when VJ compression is enabled (see the
+.Sq enable
+command above).
+It defaults to a value of 16.
+.Ar Nslots
+must be between
+.Ar 4
+and
+.Ar 16
+inclusive.
+.El
+.Pp
+.It shell|! Op Ar command
+If
+.Ar command
+is not specified a shell is invoked according to the
+.Dv SHELL
+environment variable.
+Otherwise, the given
+.Ar command
+is executed.
+Word replacement is done in the same way as for the
+.Dq !bg
+command as described above.
+.Pp
+Use of the !\& character
+requires a following space as with any of the other commands.
+You should note that this command is executed in the foreground;
+.Nm
+will not continue running until this process has exited.
+Use the
+.Dv bg
+command if you wish processing to happen in the background.
+.It show Ar var
+This command allows the user to examine the following:
+.Bl -tag -width 2n
+.It show bundle
+Show the current bundle settings.
+.It show ccp
+Show the current CCP compression statistics.
+.It show compress
+Show the current VJ compression statistics.
+.It show escape
+Show the current escape characters.
+.It show filter Op Ar name
+List the current rules for the given filter.
+If
+.Ar name
+is not specified, all filters are shown.
+.It show hdlc
+Show the current HDLC statistics.
+.It show help|?
+Give a summary of available show commands.
+.It show iface
+Show the current interface information
+(the same as
+.Dq iface show ) .
+.It show ipcp
+Show the current IPCP statistics.
+.It show layers
+Show the protocol layers currently in use.
+.It show lcp
+Show the current LCP statistics.
+.It show Op data Ns Xo
+.No link
+.Xc
+Show high level link information.
+.It show links
+Show a list of available logical links.
+.It show log
+Show the current log values.
+.It show mem
+Show current memory statistics.
+.It show ncp
+Show the current NCP statistics.
+.It show physical
+Show low level link information.
+.It show mp
+Show Multi-link information.
+.It show proto
+Show current protocol totals.
+.It show route
+Show the current routing tables.
+.It show stopped
+Show the current stopped timeouts.
+.It show timer
+Show the active alarm timers.
+.It show version
+Show the current version number of
+.Nm .
+.El
+.Pp
+.It term
+Go into terminal mode.
+Characters typed at the keyboard are sent to the device.
+Characters read from the device are displayed on the screen.
+When a remote
+.Em PPP
+peer is detected,
+.Nm
+automatically enables Packet Mode and goes back into command mode.
+.El
+.Sh MORE DETAILS
+.Bl -bullet
+.It
+Read the example configuration files.
+They are a good source of information.
+.It
+Use
+.Dq help ,
+.Dq nat \&? ,
+.Dq enable \&? ,
+.Dq set ?\&
+and
+.Dq show ?\&
+to get online information about what is available.
+.It
+The following URLs contain useful information:
+.Bl -bullet -compact
+.It
+http://www.FreeBSD.org/doc/en_US.ISO8859-1/books/faq/ppp.html
+.It
+http://www.FreeBSD.org/doc/handbook/userppp.html
+.El
+.Pp
+.El
+.Sh FILES
+.Nm
+refers to four files:
+.Pa ppp.conf ,
+.Pa ppp.linkup ,
+.Pa ppp.linkdown
+and
+.Pa ppp.secret .
+These files are placed in the
+.Pa /etc/ppp
+directory.
+.Bl -tag -width 2n
+.It Pa /etc/ppp/ppp.conf
+System default configuration file.
+.It Pa /etc/ppp/ppp.secret
+An authorisation file for each system.
+.It Pa /etc/ppp/ppp.linkup
+A file to check when
+.Nm
+establishes a network level connection.
+.It Pa /etc/ppp/ppp.linkdown
+A file to check when
+.Nm
+closes a network level connection.
+.It Pa /var/log/ppp.log
+Logging and debugging information file.
+Note, this name is specified in
+.Pa /etc/syslog.conf .
+See
+.Xr syslog.conf 5
+for further details.
+.It Pa /var/spool/lock/LCK..*
+tty port locking file.
+Refer to
+.Xr uucplock 3
+for further details.
+.It Pa /var/run/tunN.pid
+The process id (pid) of the
+.Nm
+program connected to the tunN device, where
+.Sq N
+is the number of the device.
+.It Pa /var/run/ttyXX.if
+The tun interface used by this port.
+Again, this file is only created in
+.Fl background ,
+.Fl auto
+and
+.Fl ddial
+modes.
+.It Pa /etc/services
+Get port number if port number is using service name.
+.It Pa /var/run/ppp-authname-class-value
+In multi-link mode, local domain sockets are created using the peer
+authentication name
+.Pq Sq authname ,
+the peer endpoint discriminator class
+.Pq Sq class
+and the peer endpoint discriminator value
+.Pq Sq value .
+As the endpoint discriminator value may be a binary value, it is turned
+to HEX to determine the actual file name.
+.Pp
+This socket is used to pass links between different instances of
+.Nm .
+.El
+.Sh SEE ALSO
+.Xr at 1 ,
+.Xr ftp 1 ,
+.Xr gzip 1 ,
+.Xr hostname 1 ,
+.Xr login 1 ,
+.Xr tcpdump 1 ,
+.Xr telnet 1 ,
+.Xr kldload 2 ,
+.Xr pipe 2 ,
+.Xr socketpair 2 ,
+ifdef({LOCALNAT},{},{.Xr libalias 3 ,
+})dnl
+ifdef({LOCALRAD},{},{.Xr libradius 3 ,
+})dnl
+.Xr syslog 3 ,
+.Xr uucplock 3 ,
+.Xr netgraph 4 ,
+.Xr ng_pppoe 4 ,
+.Xr crontab 5 ,
+.Xr group 5 ,
+.Xr passwd 5 ,
+.Xr protocols 5 ,
+.Xr radius.conf 5 ,
+.Xr resolv.conf 5 ,
+.Xr syslog.conf 5 ,
+.Xr adduser 8 ,
+.Xr chat 8 ,
+.Xr getty 8 ,
+.Xr inetd 8 ,
+.Xr init 8 ,
+.Xr isdnd 8 ,
+.Xr named 8 ,
+.Xr ping 8 ,
+.Xr pppctl 8 ,
+.Xr pppd 8 ,
+.Xr pppoed 8 ,
+.Xr route 8 ,
+.Xr sshd 8 ,
+.Xr syslogd 8 ,
+.Xr traceroute 8 ,
+.Xr vipw 8
+.Sh HISTORY
+This program was originally written by
+.An Toshiharu OHNO Aq tony-o@iij.ad.jp ,
+and was submitted to
+.Fx 2.0.5
+by
+.An Atsushi Murai Aq amurai@spec.co.jp .
+.Pp
+It was substantially modified during 1997 by
+.An Brian Somers Aq brian@Awfulhak.org ,
+and was ported to
+.Ox
+in November that year
+(just after the 2.2 release).
+.Pp
+Most of the code was rewritten by
+.An Brian Somers
+in early 1998 when multi-link ppp support was added.
diff --git a/src/pred.c b/src/pred.c
new file mode 100644
index 0000000..3f830d1
--- /dev/null
+++ b/src/pred.c
@@ -0,0 +1,345 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ *                    Ian Donaldson <iand@labtam.labtam.oz.au>
+ *                    Carsten Bormann <cabo@cs.tu-berlin.de>
+ *                    Dave Rand <dlr@bungi.com>/<dave_rand@novell.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/pred.c,v 1.35.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "throughput.h"
+#include "link.h"
+#include "pred.h"
+
+/* The following hash code is the heart of the algorithm:
+ * It builds a sliding hash sum of the previous 3-and-a-bit characters
+ * which will be used to index the guess table.
+ * A better hash function would result in additional compression,
+ * at the expense of time.
+ */
+#define HASH(state, x) state->hash = (state->hash << 4) ^ (x)
+#define GUESS_TABLE_SIZE 65536
+
+struct pred1_state {
+  u_short hash;
+  u_char dict[GUESS_TABLE_SIZE];
+};
+
+static int
+compress(struct pred1_state *state, u_char *source, u_char *dest, int len)
+{
+  int i, bitmask;
+  unsigned char *flagdest, flags, *orgdest;
+
+  orgdest = dest;
+  while (len) {
+    flagdest = dest++;
+    flags = 0;			/* All guess wrong initially */
+    for (bitmask = 1, i = 0; i < 8 && len; i++, bitmask <<= 1) {
+      if (state->dict[state->hash] == *source) {
+	flags |= bitmask;	/* Guess was right - don't output */
+      } else {
+	state->dict[state->hash] = *source;
+	*dest++ = *source;	/* Guess wrong, output char */
+      }
+      HASH(state, *source++);
+      len--;
+    }
+    *flagdest = flags;
+  }
+  return (dest - orgdest);
+}
+
+static void
+SyncTable(struct pred1_state *state, u_char *source, u_char *dest, int len)
+{
+  while (len--) {
+    *dest++ = state->dict[state->hash] = *source;
+    HASH(state, *source++);
+  }
+}
+
+static int
+decompress(struct pred1_state *state, u_char *source, u_char *dest, int len)
+{
+  int i, bitmask;
+  unsigned char flags, *orgdest;
+
+  orgdest = dest;
+  while (len) {
+    flags = *source++;
+    len--;
+    for (i = 0, bitmask = 1; i < 8; i++, bitmask <<= 1) {
+      if (flags & bitmask) {
+	*dest = state->dict[state->hash];	/* Guess correct */
+      } else {
+	if (!len)
+	  break;		/* we seem to be really done -- cabo */
+	state->dict[state->hash] = *source;	/* Guess wrong */
+	*dest = *source++;	/* Read from source */
+	len--;
+      }
+      HASH(state, *dest++);
+    }
+  }
+  return (dest - orgdest);
+}
+
+static void
+Pred1Term(void *v)
+{
+  struct pred1_state *state = (struct pred1_state *)v;
+  free(state);
+}
+
+static void
+Pred1ResetInput(void *v)
+{
+  struct pred1_state *state = (struct pred1_state *)v;
+  state->hash = 0;
+  memset(state->dict, '\0', sizeof state->dict);
+  log_Printf(LogCCP, "Predictor1: Input channel reset\n");
+}
+
+static int
+Pred1ResetOutput(void *v)
+{
+  struct pred1_state *state = (struct pred1_state *)v;
+  state->hash = 0;
+  memset(state->dict, '\0', sizeof state->dict);
+  log_Printf(LogCCP, "Predictor1: Output channel reset\n");
+
+  return 1;		/* Ask FSM to ACK */
+}
+
+static void *
+Pred1InitInput(struct bundle *bundle __unused, struct fsm_opt *o __unused)
+{
+  struct pred1_state *state;
+  state = (struct pred1_state *)malloc(sizeof(struct pred1_state));
+  if (state != NULL)
+    Pred1ResetInput(state);
+  return state;
+}
+
+static void *
+Pred1InitOutput(struct bundle *bundle __unused, struct fsm_opt *o __unused)
+{
+  struct pred1_state *state;
+  state = (struct pred1_state *)malloc(sizeof(struct pred1_state));
+  if (state != NULL)
+    Pred1ResetOutput(state);
+  return state;
+}
+
+static struct mbuf *
+Pred1Output(void *v, struct ccp *ccp, struct link *l __unused,
+	    int pri __unused, u_short *proto, struct mbuf *bp)
+{
+  struct pred1_state *state = (struct pred1_state *)v;
+  struct mbuf *mwp;
+  u_char *cp, *wp, *hp;
+  int orglen, len;
+  u_char bufp[MAX_MTU + 2];
+  u_short fcs;
+
+  orglen = m_length(bp) + 2;	/* add count of proto */
+  mwp = m_get((orglen + 2) / 8 * 9 + 12, MB_CCPOUT);
+  hp = wp = MBUF_CTOP(mwp);
+  cp = bufp;
+  *wp++ = *cp++ = orglen >> 8;
+  *wp++ = *cp++ = orglen & 0377;
+  *cp++ = *proto >> 8;
+  *cp++ = *proto & 0377;
+  mbuf_Read(bp, cp, orglen - 2);
+  fcs = hdlc_Fcs(bufp, 2 + orglen);
+  fcs = ~fcs;
+
+  len = compress(state, bufp + 2, wp, orglen);
+  log_Printf(LogDEBUG, "Pred1Output: orglen (%d) --> len (%d)\n", orglen, len);
+  ccp->uncompout += orglen;
+  if (len < orglen) {
+    *hp |= 0x80;
+    wp += len;
+    ccp->compout += len;
+  } else {
+    memcpy(wp, bufp + 2, orglen);
+    wp += orglen;
+    ccp->compout += orglen;
+  }
+
+  *wp++ = fcs & 0377;
+  *wp++ = fcs >> 8;
+  mwp->m_len = wp - MBUF_CTOP(mwp);
+  *proto = ccp_Proto(ccp);
+  return mwp;
+}
+
+static struct mbuf *
+Pred1Input(void *v, struct ccp *ccp, u_short *proto, struct mbuf *bp)
+{
+  struct pred1_state *state = (struct pred1_state *)v;
+  u_char *cp, *pp;
+  int len, olen, len1;
+  struct mbuf *wp;
+  u_char *bufp;
+  u_short fcs;
+
+  wp = m_get(MAX_MRU + 2, MB_CCPIN);
+  cp = MBUF_CTOP(bp);
+  olen = m_length(bp);
+  pp = bufp = MBUF_CTOP(wp);
+  *pp++ = *cp & 0177;
+  len = *cp++ << 8;
+  *pp++ = *cp;
+  len += *cp++;
+  ccp->uncompin += len & 0x7fff;
+  if (len & 0x8000) {
+    len1 = decompress(state, cp, pp, olen - 4);
+    ccp->compin += olen;
+    len &= 0x7fff;
+    if (len != len1) {		/* Error is detected. Send reset request */
+      log_Printf(LogCCP, "Pred1: Length error (got %d, not %d)\n", len1, len);
+      fsm_Reopen(&ccp->fsm);
+      m_freem(bp);
+      m_freem(wp);
+      return NULL;
+    }
+    cp += olen - 4;
+    pp += len1;
+  } else if (len + 4 != olen) {
+    log_Printf(LogCCP, "Pred1: Length error (got %d, not %d)\n", len + 4, olen);
+    fsm_Reopen(&ccp->fsm);
+    m_freem(wp);
+    m_freem(bp);
+    return NULL;
+  } else {
+    ccp->compin += len;
+    SyncTable(state, cp, pp, len);
+    cp += len;
+    pp += len;
+  }
+  *pp++ = *cp++;		/* CRC */
+  *pp++ = *cp++;
+  fcs = hdlc_Fcs(bufp, wp->m_len = pp - bufp);
+  if (fcs == GOODFCS) {
+    wp->m_offset += 2;		/* skip length */
+    wp->m_len -= 4;		/* skip length & CRC */
+    pp = MBUF_CTOP(wp);
+    *proto = *pp++;
+    if (*proto & 1) {
+      wp->m_offset++;
+      wp->m_len--;
+    } else {
+      wp->m_offset += 2;
+      wp->m_len -= 2;
+      *proto = (*proto << 8) | *pp++;
+    }
+    m_freem(bp);
+    return wp;
+  } else {
+    const char *pre = *MBUF_CTOP(bp) & 0x80 ? "" : "un";
+    log_Printf(LogDEBUG, "Pred1Input: fcs = 0x%04x (%scompressed), len = 0x%x,"
+	      " olen = 0x%x\n", fcs, pre, len, olen);
+    log_Printf(LogCCP, "%s: Bad %scompressed CRC-16\n",
+               ccp->fsm.link->name, pre);
+    fsm_Reopen(&ccp->fsm);
+    m_freem(wp);
+  }
+  m_freem(bp);
+  return NULL;
+}
+
+static void
+Pred1DictSetup(void *v __unused, struct ccp *ccp __unused,
+	       u_short proto __unused, struct mbuf *bp __unused)
+{
+  /* Nothing to see here */
+}
+
+static const char *
+Pred1DispOpts(struct fsm_opt *o __unused)
+{
+  return NULL;
+}
+
+static void
+Pred1InitOptsOutput(struct bundle *bundle __unused, struct fsm_opt *o,
+                    const struct ccp_config *cfg __unused)
+{
+  o->hdr.len = 2;
+}
+
+static int
+Pred1SetOpts(struct bundle *bundle __unused, struct fsm_opt *o,
+             const struct ccp_config *cfg __unused)
+{
+  if (o->hdr.len != 2) {
+    o->hdr.len = 2;
+    return MODE_NAK;
+  }
+  return MODE_ACK;
+}
+
+const struct ccp_algorithm Pred1Algorithm = {
+  TY_PRED1,
+  CCP_NEG_PRED1,
+  Pred1DispOpts,
+  ccp_DefaultUsable,
+  ccp_DefaultRequired,
+  {
+    Pred1SetOpts,
+    Pred1InitInput,
+    Pred1Term,
+    Pred1ResetInput,
+    Pred1Input,
+    Pred1DictSetup
+  },
+  {
+    0,
+    Pred1InitOptsOutput,
+    Pred1SetOpts,
+    Pred1InitOutput,
+    Pred1Term,
+    Pred1ResetOutput,
+    Pred1Output
+  },
+};
diff --git a/src/pred.h b/src/pred.h
new file mode 100644
index 0000000..50a1129
--- /dev/null
+++ b/src/pred.h
@@ -0,0 +1,31 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/pred.h,v 1.9.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+extern const struct ccp_algorithm Pred1Algorithm;
diff --git a/src/probe.c b/src/probe.c
new file mode 100644
index 0000000..6719e11
--- /dev/null
+++ b/src/probe.c
@@ -0,0 +1,78 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/probe.c,v 1.6.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include "probe.h"
+#include "log.h"
+#include "id.h"
+
+struct probe probe;
+
+/* Does select() alter the passed time value ? */
+static int
+select_changes_time(void)
+{
+  struct timeval t;
+
+  t.tv_sec = 0;
+  t.tv_usec = 100000;
+  select(0, NULL, NULL, NULL, &t);
+  return t.tv_usec != 100000;
+}
+
+#ifndef NOINET6
+static int
+ipv6_available(void)
+{
+  int s;
+
+  if ((s = ID0socket(PF_INET6, SOCK_DGRAM, 0)) == -1)
+    return 0;
+
+  close(s);
+  return 1;
+}
+#endif
+
+void
+probe_Init()
+{
+  probe.select_changes_time = select_changes_time() ? 1 : 0;
+  log_Printf(LogDEBUG, "Select changes time: %s\n",
+             probe.select_changes_time ? "yes" : "no");
+#ifndef NOINET6
+  probe.ipv6_available = ipv6_available() ? 1 : 0;
+  log_Printf(LogDEBUG, "IPv6 available: %s\n",
+             probe.ipv6_available ? "yes" : "no");
+#endif
+}
diff --git a/src/probe.h b/src/probe.h
new file mode 100644
index 0000000..596c2b9
--- /dev/null
+++ b/src/probe.h
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/probe.h,v 1.3.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct probe {
+  unsigned select_changes_time : 1;
+#ifndef NOINET6
+  unsigned ipv6_available : 1;
+#endif
+};
+
+extern struct probe probe;
+
+extern void probe_Init(void);
diff --git a/src/prompt.c b/src/prompt.c
new file mode 100644
index 0000000..265d30a
--- /dev/null
+++ b/src/prompt.c
@@ -0,0 +1,574 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/prompt.c,v 1.31.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "timer.h"
+#include "command.h"
+#include "log.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "fsm.h"
+#include "auth.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "slcompress.h"
+#include "mbuf.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "async.h"
+#include "ccp.h"
+#include "link.h"
+#include "physical.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "chat.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "server.h"
+#include "main.h"
+
+static void
+prompt_Display(struct prompt *p)
+{
+  /* XXX: See Index2Nam() - should we only figure this out once ? */
+  static char shostname[MAXHOSTNAMELEN];
+  const char *pconnect, *pauth;
+
+  if (p->TermMode || !p->needprompt)
+    return;
+
+  p->needprompt = 0;
+
+  if (p->nonewline)
+    p->nonewline = 0;
+  else
+    fprintf(p->Term, "\n");
+
+  if (p->auth == LOCAL_AUTH)
+    pauth = " ON ";
+  else
+    pauth = " on ";
+
+  if (p->bundle->ncp.ipcp.fsm.state == ST_OPENED)
+    pconnect = "PPP";
+#ifndef NOINET6
+  else if (!Enabled(p->bundle, OPT_IPCP) &&
+	   p->bundle->ncp.ipv6cp.fsm.state == ST_OPENED)
+    pconnect = "PPP";
+#endif
+  else if (bundle_Phase(p->bundle) == PHASE_NETWORK)
+    pconnect = "PPp";
+  else if (bundle_Phase(p->bundle) == PHASE_AUTHENTICATE)
+    pconnect = "Ppp";
+  else
+    pconnect = "ppp";
+
+  if (*shostname == '\0') {
+    char *dot;
+
+    if (gethostname(shostname, sizeof shostname) || *shostname == '\0')
+      strcpy(shostname, "localhost");
+    else if ((dot = strchr(shostname, '.')))
+      *dot = '\0';
+  }
+
+  fprintf(p->Term, "%s%s%s> ", pconnect, pauth, shostname);
+  fflush(p->Term);
+}
+
+static int
+prompt_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w __unused,
+		 fd_set *e, int *n)
+{
+  struct prompt *p = descriptor2prompt(d);
+  int sets;
+
+  sets = 0;
+
+  if (!p->active)
+    return sets;
+
+  if (p->fd_in >= 0) {
+    if (r) {
+      FD_SET(p->fd_in, r);
+      log_Printf(LogTIMER, "prompt %s: fdset(r) %d\n", p->src.from, p->fd_in);
+      sets++;
+    }
+    if (e) {
+      FD_SET(p->fd_in, e);
+      log_Printf(LogTIMER, "prompt %s: fdset(e) %d\n", p->src.from, p->fd_in);
+      sets++;
+    }
+    if (sets && *n < p->fd_in + 1)
+      *n = p->fd_in + 1;
+  }
+
+  prompt_Display(p);
+
+  return sets;
+}
+
+static int
+prompt_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+  struct prompt *p = descriptor2prompt(d);
+  return p->fd_in >= 0 && FD_ISSET(p->fd_in, fdset);
+}
+
+
+static void
+prompt_ShowHelp(struct prompt *p)
+{
+  prompt_Printf(p, "The following commands are available:\n");
+  prompt_Printf(p, " ~p\tEnter Packet mode\n");
+  prompt_Printf(p, " ~t\tShow timers\n");
+  prompt_Printf(p, " ~m\tShow memory map\n");
+  prompt_Printf(p, " ~.\tTerminate program\n");
+  prompt_Printf(p, " ~?\tThis help\n");
+}
+
+static void
+prompt_Read(struct fdescriptor *d, struct bundle *bundle,
+	    const fd_set *fdset __unused)
+{
+  struct prompt *p = descriptor2prompt(d);
+  struct prompt *op;
+  int n;
+  char ch;
+  char linebuff[LINE_LEN];
+
+  if (p->TermMode == NULL) {
+    n = read(p->fd_in, linebuff, sizeof linebuff - 1);
+    if (n > 0) {
+      if (linebuff[n-1] == '\n')
+        linebuff[--n] = '\0';
+      else
+        linebuff[n] = '\0';
+      p->nonewline = 1;		/* Maybe command_Decode does a prompt */
+      prompt_Required(p);
+      if (n) {
+        if ((op = log_PromptContext) == NULL)
+          log_PromptContext = p;
+        if (!command_Decode(bundle, linebuff, n, p, p->src.from))
+          prompt_Printf(p, "Syntax error\n");
+        log_PromptContext = op;
+      }
+    } else if (n <= 0) {
+      log_Printf(LogPHASE, "%s: Client connection closed.\n", p->src.from);
+      if (!p->owner)
+        Cleanup();
+      prompt_Destroy(p, 0);
+    }
+    return;
+  }
+
+  switch (p->TermMode->state) {
+    case DATALINK_CLOSED:
+      prompt_Printf(p, "Link lost, terminal mode.\n");
+      prompt_TtyCommandMode(p);
+      p->nonewline = 0;
+      prompt_Required(p);
+      return;
+
+    case DATALINK_READY:
+      break;
+
+    case DATALINK_OPEN:
+      prompt_Printf(p, "\nPacket mode detected.\n");
+      prompt_TtyCommandMode(p);
+      p->nonewline = 0;
+      /* We'll get a prompt because of our status change */
+      /* FALLTHROUGH */
+
+    default:
+      /* Wait 'till we're in a state we care about */
+      return;
+  }
+
+  /*
+   * We are in terminal mode, decode special sequences
+   */
+  n = read(p->fd_in, &ch, 1);
+  log_Printf(LogDEBUG, "Got %d bytes (reading from the terminal)\n", n);
+
+  if (n > 0) {
+    switch (p->readtilde) {
+    case 0:
+      if (ch == '~')
+        p->readtilde = 1;
+      else
+	if (physical_Write(p->TermMode->physical, &ch, n) < 0) {
+	  log_Printf(LogWARN, "error writing to modem: %s\n", strerror(errno));
+          prompt_TtyCommandMode(p);
+        }
+      break;
+    case 1:
+      switch (ch) {
+      case '?':
+	prompt_ShowHelp(p);
+	break;
+      case 'p':
+        datalink_Up(p->TermMode, 0, 1);
+        prompt_Printf(p, "\nPacket mode.\n");
+	prompt_TtyCommandMode(p);
+        break;
+      case '.':
+	prompt_TtyCommandMode(p);
+        p->nonewline = 0;
+        prompt_Required(p);
+	break;
+      case 't':
+	timer_Show(0, p);
+	break;
+      case 'm':
+        {
+          struct cmdargs arg;
+
+          arg.cmdtab = NULL;
+          arg.cmd = NULL;
+          arg.argc = 0;
+          arg.argn = 0;
+          arg.argv = NULL;
+          arg.bundle = bundle;
+          arg.cx = p->TermMode;
+          arg.prompt = p;
+
+	  mbuf_Show(&arg);
+        }
+	break;
+      default:
+	if (physical_Write(p->TermMode->physical, &ch, n) < 0) {
+	  log_Printf(LogWARN, "error writing to modem: %s\n", strerror(errno));
+          prompt_TtyCommandMode(p);
+        }
+	break;
+      }
+      p->readtilde = 0;
+      break;
+    }
+  }
+}
+
+static int
+prompt_Write(struct fdescriptor *d __unused, struct bundle *bundle __unused,
+	     const fd_set *fdset __unused)
+{
+  /* We never want to write here ! */
+  log_Printf(LogALERT, "prompt_Write: Internal error: Bad call !\n");
+  return 0;
+}
+
+struct prompt *
+prompt_Create(struct server *s, struct bundle *bundle, int fd)
+{
+  struct prompt *p = (struct prompt *)malloc(sizeof(struct prompt));
+
+  if (p != NULL) {
+    p->desc.type = PROMPT_DESCRIPTOR;
+    p->desc.UpdateSet = prompt_UpdateSet;
+    p->desc.IsSet = prompt_IsSet;
+    p->desc.Read = prompt_Read;
+    p->desc.Write = prompt_Write;
+
+    if (fd == PROMPT_STD) {
+      char *tty = ttyname(STDIN_FILENO);
+
+      if (!tty) {
+        free(p);
+        return NULL;
+      }
+      p->fd_in = STDIN_FILENO;
+      p->fd_out = STDOUT_FILENO;
+      p->Term = stdout;
+      p->owner = NULL;
+      p->auth = LOCAL_AUTH;
+      p->src.type = "Controller";
+      strncpy(p->src.from, tty, sizeof p->src.from - 1);
+      p->src.from[sizeof p->src.from - 1] = '\0';
+      tcgetattr(p->fd_in, &p->oldtio);	/* Save original tty mode */
+    } else {
+      p->fd_in = p->fd_out = fd;
+      p->Term = fdopen(fd, "a+");
+      p->owner = s;
+      p->auth = *s->cfg.passwd ? LOCAL_NO_AUTH : LOCAL_AUTH;
+      p->src.type = "unknown";
+      *p->src.from = '\0';
+    }
+    p->TermMode = NULL;
+    p->nonewline = 1;
+    p->needprompt = 1;
+    p->readtilde = 0;
+    p->bundle = bundle;
+    log_RegisterPrompt(p);
+  }
+
+  return p;
+}
+
+void
+prompt_Destroy(struct prompt *p, int verbose)
+{
+  if (p) {
+    if (p->Term != stdout) {
+      fclose(p->Term);
+      close(p->fd_in);
+      if (p->fd_out != p->fd_in)
+        close(p->fd_out);
+      if (verbose)
+        log_Printf(LogPHASE, "%s: Client connection dropped.\n", p->src.from);
+    } else
+      prompt_TtyOldMode(p);
+
+    log_UnRegisterPrompt(p);
+    free(p);
+  }
+}
+
+void
+prompt_Printf(struct prompt *p, const char *fmt,...)
+{
+  if (p && p->active) {
+    va_list ap;
+
+    va_start(ap, fmt);
+    prompt_vPrintf(p, fmt, ap);
+    va_end(ap);
+  }
+}
+
+void
+prompt_vPrintf(struct prompt *p, const char *fmt, va_list ap)
+{
+  if (p && p->active) {
+    char nfmt[LINE_LEN];
+    const char *pfmt;
+
+    if (p->TermMode) {
+      /* Stuff '\r' in front of '\n' 'cos we're in raw mode */
+      size_t len = strlen(fmt);
+
+      if (len && len < sizeof nfmt - 1 && fmt[len-1] == '\n' &&
+          (len == 1 || fmt[len-2] != '\r')) {
+        strcpy(nfmt, fmt);
+        strcpy(nfmt + len - 1, "\r\n");
+        pfmt = nfmt;
+      } else
+        pfmt = fmt;
+    } else
+      pfmt = fmt;
+    vfprintf(p->Term, pfmt, ap);
+    fflush(p->Term);
+    p->nonewline = 1;
+  }
+}
+
+void
+prompt_TtyInit(struct prompt *p)
+{
+  int stat, fd = p ? p->fd_in : STDIN_FILENO;
+  struct termios newtio;
+
+  stat = fcntl(fd, F_GETFL, 0);
+  if (stat > 0) {
+    stat |= O_NONBLOCK;
+    fcntl(fd, F_SETFL, stat);
+  }
+
+  if (p)
+    newtio = p->oldtio;
+  else
+    tcgetattr(fd, &newtio);
+
+  newtio.c_lflag &= ~(ECHO | ISIG | ICANON);
+  newtio.c_iflag = 0;
+  newtio.c_oflag &= ~OPOST;
+  if (!p)
+    newtio.c_cc[VINTR] = _POSIX_VDISABLE;
+  newtio.c_cc[VMIN] = 1;
+  newtio.c_cc[VTIME] = 0;
+  newtio.c_cflag |= CS8;
+  tcsetattr(fd, TCSANOW, &newtio);
+  if (p)
+    p->comtio = newtio;
+}
+
+/*
+ *  Set tty into command mode. We allow canonical input and echo processing.
+ */
+void
+prompt_TtyCommandMode(struct prompt *p)
+{
+  struct termios newtio;
+  int stat;
+
+  tcgetattr(p->fd_in, &newtio);
+  newtio.c_lflag |= (ECHO | ISIG | ICANON);
+  newtio.c_iflag = p->oldtio.c_iflag;
+  newtio.c_oflag |= OPOST;
+  tcsetattr(p->fd_in, TCSADRAIN, &newtio);
+
+  stat = fcntl(p->fd_in, F_GETFL, 0);
+  if (stat > 0) {
+    stat |= O_NONBLOCK;
+    fcntl(p->fd_in, F_SETFL, stat);
+  }
+
+  p->TermMode = NULL;
+}
+
+/*
+ * Set tty into terminal mode which is used while we invoke term command.
+ */
+void
+prompt_TtyTermMode(struct prompt *p, struct datalink *dl)
+{
+  int stat;
+
+  if (p->Term == stdout)
+    tcsetattr(p->fd_in, TCSADRAIN, &p->comtio);
+
+  stat = fcntl(p->fd_in, F_GETFL, 0);
+  if (stat > 0) {
+    stat &= ~O_NONBLOCK;
+    fcntl(p->fd_in, F_SETFL, stat);
+  }
+  p->TermMode = dl;
+}
+
+void
+prompt_TtyOldMode(struct prompt *p)
+{
+  int stat;
+
+  stat = fcntl(p->fd_in, F_GETFL, 0);
+  if (stat > 0) {
+    stat &= ~O_NONBLOCK;
+    fcntl(p->fd_in, F_SETFL, stat);
+  }
+
+  if (p->Term == stdout)
+    tcsetattr(p->fd_in, TCSADRAIN, &p->oldtio);
+}
+
+pid_t
+prompt_pgrp(struct prompt *p)
+{
+  return tcgetpgrp(p->fd_in);
+}
+
+int
+PasswdCommand(struct cmdargs const *arg)
+{
+  const char *pass;
+
+  if (!arg->prompt) {
+    log_Printf(LogWARN, "passwd: Cannot specify without a prompt\n");
+    return 0;
+  }
+
+  if (arg->prompt->owner == NULL) {
+    log_Printf(LogWARN, "passwd: Not required\n");
+    return 0;
+  }
+
+  if (arg->argc == arg->argn)
+    pass = "";
+  else if (arg->argc > arg->argn+1)
+    return -1;
+  else
+    pass = arg->argv[arg->argn];
+
+  if (!strcmp(arg->prompt->owner->cfg.passwd, pass))
+    arg->prompt->auth = LOCAL_AUTH;
+  else
+    arg->prompt->auth = LOCAL_NO_AUTH;
+
+  return 0;
+}
+
+static struct pppTimer bgtimer;
+
+static void
+prompt_TimedContinue(void *v)
+{
+  prompt_Continue((struct prompt *)v);
+}
+
+void
+prompt_Continue(struct prompt *p)
+{
+  timer_Stop(&bgtimer);
+  if (getpgrp() == prompt_pgrp(p)) {
+    prompt_TtyCommandMode(p);
+    p->nonewline = 1;
+    prompt_Required(p);
+    log_ActivatePrompt(p);
+  } else if (!p->owner) {
+    bgtimer.func = prompt_TimedContinue;
+    bgtimer.name = "prompt bg";
+    bgtimer.load = SECTICKS;
+    bgtimer.arg = p;
+    timer_Start(&bgtimer);
+  }
+}
+
+void
+prompt_Suspend(struct prompt *p)
+{
+  if (getpgrp() == prompt_pgrp(p)) {
+    prompt_TtyOldMode(p);
+    log_DeactivatePrompt(p);
+  }
+}
diff --git a/src/prompt.h b/src/prompt.h
new file mode 100644
index 0000000..32efc20
--- /dev/null
+++ b/src/prompt.h
@@ -0,0 +1,96 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/prompt.h,v 1.10.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define LOCAL_AUTH	0x01
+#define LOCAL_NO_AUTH	0x02
+#define LOCAL_DENY	0x03
+#define LOCAL_CX	0x04	/* OR'd value - require a context */
+#define LOCAL_CX_OPT	0x08	/* OR'd value - optional context */
+
+struct server;
+struct datalink;
+struct bundle;
+struct cmdargs;
+
+struct prompt {
+  struct fdescriptor desc;
+  int fd_in, fd_out;
+  struct datalink *TermMode;	/* The modem we're talking directly to */
+  FILE *Term;			/* sits on top of fd_out */
+  u_char auth;			/* Local Authorized status */
+  struct server *owner;         /* who created me */
+  struct bundle *bundle;	/* who I'm controlling */
+  unsigned nonewline : 1;	/* need a newline before our prompt ? */
+  unsigned needprompt : 1;	/* Show a prompt at the next UpdateSet() */
+  unsigned active : 1;		/* Is the prompt active (^Z) */
+  unsigned readtilde : 1;	/* We've read a ``~'' from fd_in */
+
+  struct {
+    const char *type;		/* Type of connection */
+    char from[40];		/* Source of connection */
+  } src;
+
+  struct prompt *next;		/* Maintained in log.c */
+  u_long logmask;		/* Maintained in log.c */
+
+  struct termios oldtio;	/* Original tty mode */
+  struct termios comtio;	/* Command level tty mode */
+};
+
+#define descriptor2prompt(d) \
+  ((d)->type == PROMPT_DESCRIPTOR ? (struct prompt *)(d) : NULL)
+
+#define PROMPT_STD (-1)
+extern struct prompt *prompt_Create(struct server *, struct bundle *, int);
+extern void prompt_Destroy(struct prompt *, int);
+extern void prompt_Required(struct prompt *);
+#ifdef __GNUC__
+extern void prompt_Printf(struct prompt *, const char *, ...)
+                          __attribute__ ((format (printf, 2, 3)));
+#else
+extern void prompt_Printf(struct prompt *, const char *, ...);
+#endif
+#ifdef __GNUC__
+extern void prompt_vPrintf(struct prompt *, const char *, va_list)
+			   __attribute__ ((format (printf, 2, 0)));
+#else
+extern void prompt_vPrintf(struct prompt *, const char *, va_list);
+#endif
+#define PROMPT_DONT_WANT_INT 1
+#define PROMPT_WANT_INT 0
+extern void prompt_TtyInit(struct prompt *);
+extern void prompt_TtyCommandMode(struct prompt *);
+extern void prompt_TtyTermMode(struct prompt *, struct datalink *);
+extern void prompt_TtyOldMode(struct prompt *);
+extern pid_t prompt_pgrp(struct prompt *);
+extern int PasswdCommand(struct cmdargs const *);
+extern void prompt_Suspend(struct prompt *);
+extern void prompt_Continue(struct prompt *);
+#define prompt_IsTermMode(p, dl) ((p)->TermMode == (dl) ? 1 : 0)
+#define prompt_IsController(p) (!(p) || (p)->owner ? 0 : 1)
+#define prompt_Required(p) ((p)->needprompt = 1)
diff --git a/src/proto.c b/src/proto.c
new file mode 100644
index 0000000..051e8db
--- /dev/null
+++ b/src/proto.c
@@ -0,0 +1,115 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/proto.c,v 1.7.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "acf.h"
+#include "defs.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "mbuf.h"
+#include "proto.h"
+#include "throughput.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+
+int
+proto_WrapperOctets(struct lcp *lcp, u_short proto)
+{
+  return (lcp->his_protocomp && !(proto & 0xff00)) ? 1 : 2;
+}
+
+struct mbuf *
+proto_Prepend(struct mbuf *bp, u_short proto, unsigned comp, int extra)
+{
+  u_char cp[2];
+
+  cp[0] = proto >> 8;
+  cp[1] = proto & 0xff;
+
+  if (comp && cp[0] == 0)
+    bp = m_prepend(bp, cp + 1, 1, extra);
+  else
+    bp = m_prepend(bp, cp, 2, extra);
+
+  return bp;
+}
+
+static struct mbuf *
+proto_LayerPush(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+                int pri __unused, u_short *proto)
+{
+  log_Printf(LogDEBUG, "proto_LayerPush: Using 0x%04x\n", *proto);
+  bp = proto_Prepend(bp, *proto, l->lcp.his_protocomp,
+                     acf_WrapperOctets(&l->lcp, *proto));
+  m_settype(bp, MB_PROTOOUT);
+  link_ProtocolRecord(l, *proto, PROTO_OUT);
+
+  return bp;
+}
+
+static struct mbuf *
+proto_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+                u_short *proto)
+{
+  u_char cp[2];
+  size_t got;
+
+  if ((got = mbuf_View(bp, cp, 2)) == 0) {
+    m_freem(bp);
+    return NULL;
+  }
+
+  *proto = cp[0];
+  if (!(*proto & 1)) {
+    if (got == 1) {
+      m_freem(bp);
+      return NULL;
+    }
+    bp = mbuf_Read(bp, cp, 2);
+    *proto = (*proto << 8) | cp[1];
+  } else
+    bp = mbuf_Read(bp, cp, 1);
+
+  log_Printf(LogDEBUG, "proto_LayerPull: unknown -> 0x%04x\n", *proto);
+  m_settype(bp, MB_PROTOIN);
+  link_ProtocolRecord(l, *proto, PROTO_IN);
+
+  return bp;
+}
+
+struct layer protolayer =
+  { LAYER_PROTO, "proto", proto_LayerPush, proto_LayerPull };
diff --git a/src/proto.h b/src/proto.h
new file mode 100644
index 0000000..311567c
--- /dev/null
+++ b/src/proto.h
@@ -0,0 +1,64 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/proto.h,v 1.5.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+/*
+ *  Definition of protocol numbers
+ */
+#define	PROTO_IP	0x0021	/* IP */
+#define	PROTO_VJUNCOMP	0x002f	/* VJ Uncompressed */
+#define	PROTO_VJCOMP	0x002d	/* VJ Compressed */
+#define	PROTO_MP	0x003d	/* Multilink fragment */
+#ifndef NOINET6
+#define	PROTO_IPV6	0x0057	/* IPv6 */
+#endif
+#define	PROTO_ICOMPD	0x00fb	/* Individual link compressed */
+#define	PROTO_COMPD	0x00fd	/* Compressed datagram */
+
+#define PROTO_COMPRESSIBLE(p) (((p) & 0xff81) == 0x01)
+
+#define	PROTO_IPCP	0x8021
+#ifndef NOINET6
+#define	PROTO_IPV6CP	0x8057
+#endif
+#define	PROTO_ICCP	0x80fb
+#define	PROTO_CCP	0x80fd
+
+#define	PROTO_LCP	0xc021
+#define	PROTO_PAP	0xc023
+#define	PROTO_CBCP	0xc029
+#define	PROTO_LQR	0xc025
+#define	PROTO_CHAP	0xc223
+
+struct lcp;
+
+extern int proto_WrapperOctets(struct lcp *, u_short);
+struct mbuf *proto_Prepend(struct mbuf *, u_short, unsigned, int);
+
+extern struct layer protolayer;
diff --git a/src/radius.c b/src/radius.c
new file mode 100644
index 0000000..5128170
--- /dev/null
+++ b/src/radius.c
@@ -0,0 +1,1361 @@
+/*
+ * Copyright 1999 Internet Business Solutions Ltd., Switzerland
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/radius.c,v 1.54.14.1 2010/12/21 17:10:29 kensmith Exp $
+ *
+ */
+
+#include <stdint.h>
+#include <sys/param.h>
+
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <net/route.h>
+
+#ifdef LOCALRAD
+#include "radlib.h"
+#include "radlib_vs.h"
+#else
+#include <radlib.h>
+#include <radlib_vs.h>
+#endif
+
+#include <errno.h>
+#ifndef NODES
+#include <md5.h>
+#endif
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <termios.h>
+#include <unistd.h>
+#include <netdb.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "log.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "timer.h"
+#include "fsm.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "throughput.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "mbuf.h"
+#include "ncpaddr.h"
+#include "ip.h"
+#include "ipcp.h"
+#include "ipv6cp.h"
+#include "route.h"
+#include "command.h"
+#include "filter.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#include "radius.h"
+#include "auth.h"
+#include "async.h"
+#include "physical.h"
+#include "chat.h"
+#include "cbcp.h"
+#include "chap.h"
+#include "datalink.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "proto.h"
+#include "iface.h"
+
+#ifndef NODES
+struct mschap_response {
+  u_char ident;
+  u_char flags;
+  u_char lm_response[24];
+  u_char nt_response[24];
+};
+
+struct mschap2_response {
+  u_char ident;
+  u_char flags;
+  u_char pchallenge[16];
+  u_char reserved[8];
+  u_char response[24];
+};
+
+#define	AUTH_LEN	16
+#define	SALT_LEN	2
+#endif
+
+static const char *
+radius_policyname(int policy)
+{
+  switch(policy) {
+  case MPPE_POLICY_ALLOWED:
+    return "Allowed";
+  case MPPE_POLICY_REQUIRED:
+    return "Required";
+  }
+  return NumStr(policy, NULL, 0);
+}
+
+static const char *
+radius_typesname(int types)
+{
+  switch(types) {
+  case MPPE_TYPE_40BIT:
+    return "40 bit";
+  case MPPE_TYPE_128BIT:
+    return "128 bit";
+  case MPPE_TYPE_40BIT|MPPE_TYPE_128BIT:
+    return "40 or 128 bit";
+  }
+  return NumStr(types, NULL, 0);
+}
+
+#ifndef NODES
+static void
+demangle(struct radius *r, const void *mangled, size_t mlen,
+         char **buf, size_t *len)
+{
+  char R[AUTH_LEN];		/* variable names as per rfc2548 */
+  const char *S;
+  u_char b[16];
+  const u_char *A, *C;
+  MD5_CTX Context;
+  int Slen, i, Clen, Ppos;
+  u_char *P;
+
+  if (mlen % 16 != SALT_LEN) {
+    log_Printf(LogWARN, "Cannot interpret mangled data of length %ld\n",
+               (u_long)mlen);
+    *buf = NULL;
+    *len = 0;
+    return;
+  }
+
+  /* We need the RADIUS Request-Authenticator */
+  if (rad_request_authenticator(r->cx.rad, R, sizeof R) != AUTH_LEN) {
+    log_Printf(LogWARN, "Cannot obtain the RADIUS request authenticator\n");
+    *buf = NULL;
+    *len = 0;
+    return;
+  }
+
+  A = (const u_char *)mangled;			/* Salt comes first */
+  C = (const u_char *)mangled + SALT_LEN;	/* Then the ciphertext */
+  Clen = mlen - SALT_LEN;
+  S = rad_server_secret(r->cx.rad);		/* We need the RADIUS secret */
+  Slen = strlen(S);
+  P = alloca(Clen);				/* We derive our plaintext */
+
+  MD5Init(&Context);
+  MD5Update(&Context, S, Slen);
+  MD5Update(&Context, R, AUTH_LEN);
+  MD5Update(&Context, A, SALT_LEN);
+  MD5Final(b, &Context);
+  Ppos = 0;
+
+  while (Clen) {
+    Clen -= 16;
+
+    for (i = 0; i < 16; i++)
+      P[Ppos++] = C[i] ^ b[i];
+
+    if (Clen) {
+      MD5Init(&Context);
+      MD5Update(&Context, S, Slen);
+      MD5Update(&Context, C, 16);
+      MD5Final(b, &Context);
+    }
+
+    C += 16;
+  }
+
+  /*
+   * The resulting plain text consists of a one-byte length, the text and
+   * maybe some padding.
+   */
+  *len = *P;
+  if (*len > mlen - 1) {
+    log_Printf(LogWARN, "Mangled data seems to be garbage\n");
+    *buf = NULL;
+    *len = 0;
+    return;
+  }
+
+  if ((*buf = malloc(*len)) == NULL) {
+    log_Printf(LogWARN, "demangle: Out of memory (%lu bytes)\n", (u_long)*len);
+    *len = 0;
+  } else
+    memcpy(*buf, P + 1, *len);
+}
+#endif
+
+/* XXX: This should go into librarius. */
+#ifndef NOINET6
+static uint8_t *
+rad_cvt_ipv6prefix(const void *data, size_t len)
+{
+	const size_t ipv6len = sizeof(struct in6_addr) + 2;
+	uint8_t *s;
+
+	if (len > ipv6len)
+		return NULL;
+	s = malloc(ipv6len);
+	if (s != NULL) {
+		memset(s, 0, ipv6len);
+		memcpy(s, data, len);
+	}
+	return s;
+}
+#endif
+
+/*
+ * rad_continue_send_request() has given us `got' (non-zero).  Deal with it.
+ */
+static void
+radius_Process(struct radius *r, int got)
+{
+  char *argv[MAXARGS], *nuke;
+  struct bundle *bundle;
+  int argc, addrs, res, width;
+  size_t len;
+  struct ncprange dest;
+  struct ncpaddr gw;
+  const void *data;
+  const char *stype;
+  u_int32_t ipaddr, vendor;
+  struct in_addr ip;
+#ifndef NOINET6
+  uint8_t ipv6addr[INET6_ADDRSTRLEN];
+  struct in6_addr ip6;
+#endif
+
+  r->cx.fd = -1;		/* Stop select()ing */
+  stype = r->cx.auth ? "auth" : "acct";
+
+  switch (got) {
+    case RAD_ACCESS_ACCEPT:
+      log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		 "Radius(%s): ACCEPT received\n", stype);
+      if (!r->cx.auth) {
+        rad_close(r->cx.rad);
+        return;
+      }
+      break;
+
+    case RAD_ACCESS_REJECT:
+      log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		 "Radius(%s): REJECT received\n", stype);
+      if (!r->cx.auth) {
+        rad_close(r->cx.rad);
+        return;
+      }
+      break;
+
+    case RAD_ACCESS_CHALLENGE:
+      /* we can't deal with this (for now) ! */
+      log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		 "Radius: CHALLENGE received (can't handle yet)\n");
+      if (r->cx.auth)
+        auth_Failure(r->cx.auth);
+      rad_close(r->cx.rad);
+      return;
+
+    case RAD_ACCOUNTING_RESPONSE:
+      /*
+       * It's probably not ideal to log this at PHASE level as we'll see
+       * too much stuff going to the log when ``set rad_alive'' is used.
+       * So we differ from older behaviour (ppp version 3.1 and before)
+       * and just log accounting responses to LogRADIUS.
+       */
+      log_Printf(LogRADIUS, "Radius(%s): Accounting response received\n",
+		 stype);
+      if (r->cx.auth)
+        auth_Failure(r->cx.auth);		/* unexpected !!! */
+
+      /* No further processing for accounting requests, please */
+      rad_close(r->cx.rad);
+      return;
+
+    case -1:
+      log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		 "radius(%s): %s\n", stype, rad_strerror(r->cx.rad));
+      if (r->cx.auth)
+        auth_Failure(r->cx.auth);
+      rad_close(r->cx.rad);
+      return;
+
+    default:
+      log_Printf(LogERROR, "rad_send_request(%s): Failed %d: %s\n", stype,
+                 got, rad_strerror(r->cx.rad));
+      if (r->cx.auth)
+        auth_Failure(r->cx.auth);
+      rad_close(r->cx.rad);
+      return;
+  }
+
+  /* Let's see what we've got in our reply */
+  r->ip.s_addr = r->mask.s_addr = INADDR_NONE;
+  r->mtu = 0;
+  r->vj = 0;
+  while ((res = rad_get_attr(r->cx.rad, &data, &len)) > 0) {
+    switch (res) {
+      case RAD_FRAMED_IP_ADDRESS:
+        r->ip = rad_cvt_addr(data);
+	log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		   " IP %s\n", inet_ntoa(r->ip));
+        break;
+
+      case RAD_FILTER_ID:
+        free(r->filterid);
+        if ((r->filterid = rad_cvt_string(data, len)) == NULL) {
+          log_Printf(LogERROR, "rad_cvt_string: %s\n", rad_strerror(r->cx.rad));
+          auth_Failure(r->cx.auth);
+          rad_close(r->cx.rad);
+          return;
+        }
+	log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		   " Filter \"%s\"\n", r->filterid);
+        break;
+
+      case RAD_SESSION_TIMEOUT:
+        r->sessiontime = rad_cvt_int(data);
+	log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		   " Session-Timeout %lu\n", r->sessiontime);
+        break;
+
+      case RAD_FRAMED_IP_NETMASK:
+        r->mask = rad_cvt_addr(data);
+	log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		   " Netmask %s\n", inet_ntoa(r->mask));
+        break;
+
+      case RAD_FRAMED_MTU:
+        r->mtu = rad_cvt_int(data);
+	log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		   " MTU %lu\n", r->mtu);
+        break;
+
+      case RAD_FRAMED_ROUTING:
+        /* Disabled for now - should we automatically set up some filters ? */
+        /* rad_cvt_int(data); */
+        /* bit 1 = Send routing packets */
+        /* bit 2 = Receive routing packets */
+        break;
+
+      case RAD_FRAMED_COMPRESSION:
+        r->vj = rad_cvt_int(data) == 1 ? 1 : 0;
+	log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		   " VJ %sabled\n", r->vj ? "en" : "dis");
+        break;
+
+      case RAD_FRAMED_ROUTE:
+        /*
+         * We expect a string of the format ``dest[/bits] gw [metrics]''
+         * Any specified metrics are ignored.  MYADDR and HISADDR are
+         * understood for ``dest'' and ``gw'' and ``0.0.0.0'' is the same
+         * as ``HISADDR''.
+         */
+
+        if ((nuke = rad_cvt_string(data, len)) == NULL) {
+          log_Printf(LogERROR, "rad_cvt_string: %s\n", rad_strerror(r->cx.rad));
+          auth_Failure(r->cx.auth);
+          rad_close(r->cx.rad);
+          return;
+        }
+
+	log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		   " Route: %s\n", nuke);
+        bundle = r->cx.auth->physical->dl->bundle;
+        ip.s_addr = INADDR_ANY;
+        ncpaddr_setip4(&gw, ip);
+        ncprange_setip4host(&dest, ip);
+        argc = command_Interpret(nuke, strlen(nuke), argv);
+        if (argc < 0)
+          log_Printf(LogWARN, "radius: %s: Syntax error\n",
+                     argc == 1 ? argv[0] : "\"\"");
+        else if (argc < 2)
+          log_Printf(LogWARN, "radius: %s: Invalid route\n",
+                     argc == 1 ? argv[0] : "\"\"");
+        else if ((strcasecmp(argv[0], "default") != 0 &&
+                  !ncprange_aton(&dest, &bundle->ncp, argv[0])) ||
+                 !ncpaddr_aton(&gw, &bundle->ncp, argv[1]))
+          log_Printf(LogWARN, "radius: %s %s: Invalid route\n",
+                     argv[0], argv[1]);
+        else {
+          ncprange_getwidth(&dest, &width);
+          if (width == 32 && strchr(argv[0], '/') == NULL) {
+            /* No mask specified - use the natural mask */
+            ncprange_getip4addr(&dest, &ip);
+            ncprange_setip4mask(&dest, addr2mask(ip));
+          }
+          addrs = 0;
+
+          if (!strncasecmp(argv[0], "HISADDR", 7))
+            addrs = ROUTE_DSTHISADDR;
+          else if (!strncasecmp(argv[0], "MYADDR", 6))
+            addrs = ROUTE_DSTMYADDR;
+
+          if (ncpaddr_getip4addr(&gw, &ipaddr) && ipaddr == INADDR_ANY) {
+            addrs |= ROUTE_GWHISADDR;
+            ncpaddr_setip4(&gw, bundle->ncp.ipcp.peer_ip);
+          } else if (strcasecmp(argv[1], "HISADDR") == 0)
+            addrs |= ROUTE_GWHISADDR;
+
+          route_Add(&r->routes, addrs, &dest, &gw);
+        }
+        free(nuke);
+        break;
+
+      case RAD_REPLY_MESSAGE:
+        free(r->repstr);
+        if ((r->repstr = rad_cvt_string(data, len)) == NULL) {
+          log_Printf(LogERROR, "rad_cvt_string: %s\n", rad_strerror(r->cx.rad));
+          auth_Failure(r->cx.auth);
+          rad_close(r->cx.rad);
+          return;
+        }
+	log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		   " Reply-Message \"%s\"\n", r->repstr);
+        break;
+
+#ifndef NOINET6
+      case RAD_FRAMED_IPV6_PREFIX:
+	free(r->ipv6prefix);
+	if ((r->ipv6prefix = rad_cvt_ipv6prefix(data, len)) == NULL) {
+	  log_Printf(LogERROR, "rad_cvt_ipv6prefix: %s\n",
+		     "Malformed attribute in response");
+	  auth_Failure(r->cx.auth);
+	  rad_close(r->cx.rad);
+	  return;
+	}
+	inet_ntop(AF_INET6, &r->ipv6prefix[2], ipv6addr, sizeof(ipv6addr));
+	log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		   " IPv6 %s/%d\n", ipv6addr, r->ipv6prefix[1]);
+        break;
+
+      case RAD_FRAMED_IPV6_ROUTE:
+        /*
+         * We expect a string of the format ``dest[/bits] gw [metrics]''
+         * Any specified metrics are ignored.  MYADDR6 and HISADDR6 are
+         * understood for ``dest'' and ``gw'' and ``::'' is the same
+         * as ``HISADDR6''.
+         */
+
+        if ((nuke = rad_cvt_string(data, len)) == NULL) {
+          log_Printf(LogERROR, "rad_cvt_string: %s\n", rad_strerror(r->cx.rad));
+          auth_Failure(r->cx.auth);
+          rad_close(r->cx.rad);
+          return;
+        }
+
+	log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+		   " IPv6 Route: %s\n", nuke);
+        bundle = r->cx.auth->physical->dl->bundle;
+	ncpaddr_setip6(&gw, &in6addr_any);
+	ncprange_set(&dest, &gw, 0);
+        argc = command_Interpret(nuke, strlen(nuke), argv);
+        if (argc < 0)
+          log_Printf(LogWARN, "radius: %s: Syntax error\n",
+                     argc == 1 ? argv[0] : "\"\"");
+        else if (argc < 2)
+          log_Printf(LogWARN, "radius: %s: Invalid route\n",
+                     argc == 1 ? argv[0] : "\"\"");
+        else if ((strcasecmp(argv[0], "default") != 0 &&
+                  !ncprange_aton(&dest, &bundle->ncp, argv[0])) ||
+                 !ncpaddr_aton(&gw, &bundle->ncp, argv[1]))
+          log_Printf(LogWARN, "radius: %s %s: Invalid route\n",
+                     argv[0], argv[1]);
+        else {
+          addrs = 0;
+
+          if (!strncasecmp(argv[0], "HISADDR6", 8))
+            addrs = ROUTE_DSTHISADDR6;
+          else if (!strncasecmp(argv[0], "MYADDR6", 7))
+            addrs = ROUTE_DSTMYADDR6;
+
+          if (ncpaddr_getip6(&gw, &ip6) && IN6_IS_ADDR_UNSPECIFIED(&ip6)) {
+            addrs |= ROUTE_GWHISADDR6;
+            ncpaddr_copy(&gw, &bundle->ncp.ipv6cp.hisaddr);
+          } else if (strcasecmp(argv[1], "HISADDR6") == 0)
+            addrs |= ROUTE_GWHISADDR6;
+
+          route_Add(&r->ipv6routes, addrs, &dest, &gw);
+        }
+        free(nuke);
+        break;
+#endif
+
+      case RAD_VENDOR_SPECIFIC:
+        if ((res = rad_get_vendor_attr(&vendor, &data, &len)) <= 0) {
+          log_Printf(LogERROR, "rad_get_vendor_attr: %s (failing!)\n",
+                     rad_strerror(r->cx.rad));
+          auth_Failure(r->cx.auth);
+          rad_close(r->cx.rad);
+          return;
+        }
+
+	switch (vendor) {
+          case RAD_VENDOR_MICROSOFT:
+            switch (res) {
+#ifndef NODES
+              case RAD_MICROSOFT_MS_CHAP_ERROR:
+                free(r->errstr);
+                if (len == 0)
+                  r->errstr = NULL;
+                else {
+                  if (len < 3 || ((const char *)data)[1] != '=') {
+                    /*
+                     * Only point at the String field if we don't think the
+                     * peer has misformatted the response.
+                     */
+                    data = (const char *)data + 1;
+                    len--;
+                  } else
+                    log_Printf(LogWARN, "Warning: The MS-CHAP-Error "
+                               "attribute is mis-formatted.  Compensating\n");
+                  if ((r->errstr = rad_cvt_string((const char *)data,
+                                                  len)) == NULL) {
+                    log_Printf(LogERROR, "rad_cvt_string: %s\n",
+                               rad_strerror(r->cx.rad));
+                    auth_Failure(r->cx.auth);
+                    rad_close(r->cx.rad);
+                    return;
+                  }
+		  log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+			     " MS-CHAP-Error \"%s\"\n", r->errstr);
+                }
+                break;
+
+              case RAD_MICROSOFT_MS_CHAP2_SUCCESS:
+                free(r->msrepstr);
+                if (len == 0)
+                  r->msrepstr = NULL;
+                else {
+                  if (len < 3 || ((const char *)data)[1] != '=') {
+                    /*
+                     * Only point at the String field if we don't think the
+                     * peer has misformatted the response.
+                     */
+                    data = (const char *)data + 1;
+                    len--;
+                  } else
+                    log_Printf(LogWARN, "Warning: The MS-CHAP2-Success "
+                               "attribute is mis-formatted.  Compensating\n");
+                  if ((r->msrepstr = rad_cvt_string((const char *)data,
+                                                    len)) == NULL) {
+                    log_Printf(LogERROR, "rad_cvt_string: %s\n",
+                               rad_strerror(r->cx.rad));
+                    auth_Failure(r->cx.auth);
+                    rad_close(r->cx.rad);
+                    return;
+                  }
+		  log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+			     " MS-CHAP2-Success \"%s\"\n", r->msrepstr);
+                }
+                break;
+
+              case RAD_MICROSOFT_MS_MPPE_ENCRYPTION_POLICY:
+                r->mppe.policy = rad_cvt_int(data);
+		log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+			   " MS-MPPE-Encryption-Policy %s\n",
+                           radius_policyname(r->mppe.policy));
+                break;
+
+              case RAD_MICROSOFT_MS_MPPE_ENCRYPTION_TYPES:
+                r->mppe.types = rad_cvt_int(data);
+		log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+			   " MS-MPPE-Encryption-Types %s\n",
+                           radius_typesname(r->mppe.types));
+                break;
+
+              case RAD_MICROSOFT_MS_MPPE_RECV_KEY:
+                free(r->mppe.recvkey);
+		demangle(r, data, len, &r->mppe.recvkey, &r->mppe.recvkeylen);
+		log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+			   " MS-MPPE-Recv-Key ********\n");
+                break;
+
+              case RAD_MICROSOFT_MS_MPPE_SEND_KEY:
+		demangle(r, data, len, &r->mppe.sendkey, &r->mppe.sendkeylen);
+		log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+			   " MS-MPPE-Send-Key ********\n");
+                break;
+#endif
+
+              default:
+                log_Printf(LogDEBUG, "Dropping MICROSOFT vendor specific "
+                           "RADIUS attribute %d\n", res);
+                break;
+            }
+            break;
+
+          default:
+            log_Printf(LogDEBUG, "Dropping vendor %lu RADIUS attribute %d\n",
+                       (unsigned long)vendor, res);
+            break;
+        }
+        break;
+
+      default:
+        log_Printf(LogDEBUG, "Dropping RADIUS attribute %d\n", res);
+        break;
+    }
+  }
+
+  if (res == -1) {
+    log_Printf(LogERROR, "rad_get_attr: %s (failing!)\n",
+               rad_strerror(r->cx.rad));
+    auth_Failure(r->cx.auth);
+  } else if (got == RAD_ACCESS_REJECT)
+    auth_Failure(r->cx.auth);
+  else {
+    r->valid = 1;
+    auth_Success(r->cx.auth);
+  }
+  rad_close(r->cx.rad);
+}
+
+/*
+ * We've either timed out or select()ed on the read descriptor
+ */
+static void
+radius_Continue(struct radius *r, int sel)
+{
+  struct timeval tv;
+  int got;
+
+  timer_Stop(&r->cx.timer);
+  if ((got = rad_continue_send_request(r->cx.rad, sel, &r->cx.fd, &tv)) == 0) {
+    log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+	       "Radius: Request re-sent\n");
+    r->cx.timer.load = tv.tv_usec / TICKUNIT + tv.tv_sec * SECTICKS;
+    timer_Start(&r->cx.timer);
+    return;
+  }
+
+  radius_Process(r, got);
+}
+
+/*
+ * Time to call rad_continue_send_request() - timed out.
+ */
+static void
+radius_Timeout(void *v)
+{
+  radius_Continue((struct radius *)v, 0);
+}
+
+/*
+ * Time to call rad_continue_send_request() - something to read.
+ */
+static void
+radius_Read(struct fdescriptor *d, struct bundle *bundle __unused,
+	    const fd_set *fdset __unused)
+{
+  radius_Continue(descriptor2radius(d), 1);
+}
+
+/*
+ * Flush any pending transactions
+ */
+void
+radius_Flush(struct radius *r)
+{
+  struct timeval tv;
+  fd_set s;
+
+  while (r->cx.fd != -1) {
+    FD_ZERO(&s);
+    FD_SET(r->cx.fd, &s);
+    tv.tv_sec = 0;
+    tv.tv_usec = TICKUNIT;
+    select(r->cx.fd + 1, &s, NULL, NULL, &tv);
+    radius_Continue(r, 1);
+  }
+}
+
+/*
+ * Behave as a struct fdescriptor (descriptor.h)
+ */
+static int
+radius_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w __unused,
+		 fd_set *e __unused, int *n)
+{
+  struct radius *rad = descriptor2radius(d);
+
+  if (r && rad->cx.fd != -1) {
+    FD_SET(rad->cx.fd, r);
+    if (*n < rad->cx.fd + 1)
+      *n = rad->cx.fd + 1;
+    log_Printf(LogTIMER, "Radius: fdset(r) %d\n", rad->cx.fd);
+    return 1;
+  }
+
+  return 0;
+}
+
+/*
+ * Behave as a struct fdescriptor (descriptor.h)
+ */
+static int
+radius_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+  struct radius *r = descriptor2radius(d);
+
+  return r && r->cx.fd != -1 && FD_ISSET(r->cx.fd, fdset);
+}
+
+/*
+ * Behave as a struct fdescriptor (descriptor.h)
+ */
+static int
+radius_Write(struct fdescriptor *d __unused, struct bundle *bundle __unused,
+	     const fd_set *fdset __unused)
+{
+  /* We never want to write here ! */
+  log_Printf(LogALERT, "radius_Write: Internal error: Bad call !\n");
+  return 0;
+}
+
+/*
+ * Initialise ourselves
+ */
+void
+radius_Init(struct radius *r)
+{
+  r->desc.type = RADIUS_DESCRIPTOR;
+  r->desc.UpdateSet = radius_UpdateSet;
+  r->desc.IsSet = radius_IsSet;
+  r->desc.Read = radius_Read;
+  r->desc.Write = radius_Write;
+  r->cx.fd = -1;
+  r->cx.rad = NULL;
+  memset(&r->cx.timer, '\0', sizeof r->cx.timer);
+  r->cx.auth = NULL;
+  r->valid = 0;
+  r->vj = 0;
+  r->ip.s_addr = INADDR_ANY;
+  r->mask.s_addr = INADDR_NONE;
+  r->routes = NULL;
+  r->mtu = DEF_MTU;
+  r->msrepstr = NULL;
+  r->repstr = NULL;
+#ifndef NOINET6
+  r->ipv6prefix = NULL;
+  r->ipv6routes = NULL;
+#endif
+  r->errstr = NULL;
+  r->mppe.policy = 0;
+  r->mppe.types = 0;
+  r->mppe.recvkey = NULL;
+  r->mppe.recvkeylen = 0;
+  r->mppe.sendkey = NULL;
+  r->mppe.sendkeylen = 0;
+  *r->cfg.file = '\0';;
+  log_Printf(LogDEBUG, "Radius: radius_Init\n");
+}
+
+/*
+ * Forget everything and go back to initialised state.
+ */
+void
+radius_Destroy(struct radius *r)
+{
+  r->valid = 0;
+  log_Printf(LogDEBUG, "Radius: radius_Destroy\n");
+  timer_Stop(&r->cx.timer);
+  route_DeleteAll(&r->routes);
+#ifndef NOINET6
+  route_DeleteAll(&r->ipv6routes);
+#endif
+  free(r->filterid);
+  r->filterid = NULL;
+  free(r->msrepstr);
+  r->msrepstr = NULL;
+  free(r->repstr);
+  r->repstr = NULL;
+#ifndef NOINET6
+  free(r->ipv6prefix);
+  r->ipv6prefix = NULL;
+#endif
+  free(r->errstr);
+  r->errstr = NULL;
+  free(r->mppe.recvkey);
+  r->mppe.recvkey = NULL;
+  r->mppe.recvkeylen = 0;
+  free(r->mppe.sendkey);
+  r->mppe.sendkey = NULL;
+  r->mppe.sendkeylen = 0;
+  if (r->cx.fd != -1) {
+    r->cx.fd = -1;
+    rad_close(r->cx.rad);
+  }
+}
+
+static int
+radius_put_physical_details(struct radius *rad, struct physical *p)
+{
+  int slot, type;
+
+  type = RAD_VIRTUAL;
+  if (p->handler)
+    switch (p->handler->type) {
+      case I4B_DEVICE:
+        type = RAD_ISDN_SYNC;
+        break;
+
+      case TTY_DEVICE:
+        type = RAD_ASYNC;
+        break;
+
+      case ETHER_DEVICE:
+        type = RAD_ETHERNET;
+        break;
+
+      case TCP_DEVICE:
+      case UDP_DEVICE:
+      case EXEC_DEVICE:
+      case ATM_DEVICE:
+      case NG_DEVICE:
+        type = RAD_VIRTUAL;
+        break;
+    }
+
+  if (rad_put_int(rad->cx.rad, RAD_NAS_PORT_TYPE, type) != 0) {
+    log_Printf(LogERROR, "rad_put: rad_put_int: %s\n", rad_strerror(rad->cx.rad));
+    rad_close(rad->cx.rad);
+    return 0;
+  }
+
+  switch (rad->port_id_type) {
+    case RPI_PID:
+      slot = (int)getpid();
+      break;
+    case RPI_IFNUM:
+      slot = p->dl->bundle->iface->index;
+      break;
+    case RPI_TUNNUM:
+      slot = p->dl->bundle->unit;
+      break;
+    case RPI_DEFAULT:
+    default:
+      slot = physical_Slot(p);
+      break;
+  }
+  
+  if (slot >= 0)
+    if (rad_put_int(rad->cx.rad, RAD_NAS_PORT, slot) != 0) {
+      log_Printf(LogERROR, "rad_put: rad_put_int: %s\n", rad_strerror(rad->cx.rad));
+      rad_close(rad->cx.rad);
+      return 0;
+    }
+
+  return 1;
+}
+
+/*
+ * Start an authentication request to the RADIUS server.
+ */
+int
+radius_Authenticate(struct radius *r, struct authinfo *authp, const char *name,
+                    const char *key, int klen, const char *nchallenge,
+                    int nclen)
+{
+  char hostname[MAXHOSTNAMELEN];
+  struct timeval tv;
+  const char *what = "questionable";	/* silence warnings! */
+  char *mac_addr;
+  int got;
+  struct hostent *hp;
+  struct in_addr hostaddr;
+#ifndef NODES
+  struct mschap_response msresp;
+  struct mschap2_response msresp2;
+  const struct MSCHAPv2_resp *keyv2;
+#endif
+
+  if (!*r->cfg.file)
+    return 0;
+
+  if (r->cx.fd != -1)
+    /*
+     * We assume that our name/key/challenge is the same as last time,
+     * and just continue to wait for the RADIUS server(s).
+     */
+    return 1;
+
+  radius_Destroy(r);
+
+  if ((r->cx.rad = rad_auth_open()) == NULL) {
+    log_Printf(LogERROR, "rad_auth_open: %s\n", strerror(errno));
+    return 0;
+  }
+
+  if (rad_config(r->cx.rad, r->cfg.file) != 0) {
+    log_Printf(LogERROR, "rad_config: %s\n", rad_strerror(r->cx.rad));
+    rad_close(r->cx.rad);
+    return 0;
+  }
+
+  if (rad_create_request(r->cx.rad, RAD_ACCESS_REQUEST) != 0) {
+    log_Printf(LogERROR, "rad_create_request: %s\n", rad_strerror(r->cx.rad));
+    rad_close(r->cx.rad);
+    return 0;
+  }
+
+  if (rad_put_string(r->cx.rad, RAD_USER_NAME, name) != 0 ||
+      rad_put_int(r->cx.rad, RAD_SERVICE_TYPE, RAD_FRAMED) != 0 ||
+      rad_put_int(r->cx.rad, RAD_FRAMED_PROTOCOL, RAD_PPP) != 0) {
+    log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+    rad_close(r->cx.rad);
+    return 0;
+  }
+
+  switch (authp->physical->link.lcp.want_auth) {
+  case PROTO_PAP:
+    /* We're talking PAP */
+    if (rad_put_attr(r->cx.rad, RAD_USER_PASSWORD, key, klen) != 0) {
+      log_Printf(LogERROR, "PAP: rad_put_string: %s\n",
+                 rad_strerror(r->cx.rad));
+      rad_close(r->cx.rad);
+      return 0;
+    }
+    what = "PAP";
+    break;
+
+  case PROTO_CHAP:
+    switch (authp->physical->link.lcp.want_authtype) {
+    case 0x5:
+      if (rad_put_attr(r->cx.rad, RAD_CHAP_PASSWORD, key, klen) != 0 ||
+          rad_put_attr(r->cx.rad, RAD_CHAP_CHALLENGE, nchallenge, nclen) != 0) {
+        log_Printf(LogERROR, "CHAP: rad_put_string: %s\n",
+                   rad_strerror(r->cx.rad));
+        rad_close(r->cx.rad);
+        return 0;
+      }
+      what = "CHAP";
+      break;
+
+#ifndef NODES
+    case 0x80:
+      if (klen != 50) {
+        log_Printf(LogERROR, "CHAP80: Unrecognised key length %d\n", klen);
+        rad_close(r->cx.rad);
+        return 0;
+      }
+
+      rad_put_vendor_attr(r->cx.rad, RAD_VENDOR_MICROSOFT,
+                          RAD_MICROSOFT_MS_CHAP_CHALLENGE, nchallenge, nclen);
+      msresp.ident = *key;
+      msresp.flags = 0x01;
+      memcpy(msresp.lm_response, key + 1, 24);
+      memcpy(msresp.nt_response, key + 25, 24);
+      rad_put_vendor_attr(r->cx.rad, RAD_VENDOR_MICROSOFT,
+                          RAD_MICROSOFT_MS_CHAP_RESPONSE, &msresp,
+                          sizeof msresp);
+      what = "MSCHAP";
+      break;
+
+    case 0x81:
+      if (klen != sizeof(*keyv2) + 1) {
+        log_Printf(LogERROR, "CHAP81: Unrecognised key length %d\n", klen);
+        rad_close(r->cx.rad);
+        return 0;
+      }
+
+      keyv2 = (const struct MSCHAPv2_resp *)(key + 1);
+      rad_put_vendor_attr(r->cx.rad, RAD_VENDOR_MICROSOFT,
+                          RAD_MICROSOFT_MS_CHAP_CHALLENGE, nchallenge, nclen);
+      msresp2.ident = *key;
+      msresp2.flags = keyv2->Flags;
+      memcpy(msresp2.response, keyv2->NTResponse, sizeof msresp2.response);
+      memset(msresp2.reserved, '\0', sizeof msresp2.reserved);
+      memcpy(msresp2.pchallenge, keyv2->PeerChallenge,
+             sizeof msresp2.pchallenge);
+      rad_put_vendor_attr(r->cx.rad, RAD_VENDOR_MICROSOFT,
+                          RAD_MICROSOFT_MS_CHAP2_RESPONSE, &msresp2,
+                          sizeof msresp2);
+      what = "MSCHAPv2";
+      break;
+#endif
+    default:
+      log_Printf(LogERROR, "CHAP: Unrecognised type 0x%02x\n",
+                 authp->physical->link.lcp.want_authtype);
+      rad_close(r->cx.rad);
+      return 0;
+    }
+  }
+
+  if (gethostname(hostname, sizeof hostname) != 0)
+    log_Printf(LogERROR, "rad_put: gethostname(): %s\n", strerror(errno));
+  else {
+    if (Enabled(authp->physical->dl->bundle, OPT_NAS_IP_ADDRESS) &&
+        (hp = gethostbyname(hostname)) != NULL) {
+      hostaddr.s_addr = *(u_long *)hp->h_addr;
+      if (rad_put_addr(r->cx.rad, RAD_NAS_IP_ADDRESS, hostaddr) != 0) {
+        log_Printf(LogERROR, "rad_put: rad_put_string: %s\n",
+                   rad_strerror(r->cx.rad));
+        rad_close(r->cx.rad);
+        return 0;
+      }
+    }
+    if (Enabled(authp->physical->dl->bundle, OPT_NAS_IDENTIFIER) &&
+        rad_put_string(r->cx.rad, RAD_NAS_IDENTIFIER, hostname) != 0) {
+      log_Printf(LogERROR, "rad_put: rad_put_string: %s\n",
+                 rad_strerror(r->cx.rad));
+      rad_close(r->cx.rad);
+      return 0;
+    }
+  }
+
+  if ((mac_addr = getenv("HISMACADDR")) != NULL &&
+      rad_put_string(r->cx.rad, RAD_CALLING_STATION_ID, mac_addr) != 0) {
+    log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+    rad_close(r->cx.rad);
+    return 0;
+  }
+
+  radius_put_physical_details(r, authp->physical);
+
+  log_Printf(LogRADIUS, "Radius(auth): %s data sent for %s\n", what, name);
+
+  r->cx.auth = authp;
+  if ((got = rad_init_send_request(r->cx.rad, &r->cx.fd, &tv)))
+    radius_Process(r, got);
+  else {
+    log_Printf(log_IsKept(LogRADIUS) ? LogRADIUS : LogPHASE,
+	       "Radius: Request sent\n");
+    log_Printf(LogDEBUG, "Using radius_Timeout [%p]\n", radius_Timeout);
+    r->cx.timer.load = tv.tv_usec / TICKUNIT + tv.tv_sec * SECTICKS;
+    r->cx.timer.func = radius_Timeout;
+    r->cx.timer.name = "radius auth";
+    r->cx.timer.arg = r;
+    timer_Start(&r->cx.timer);
+  }
+
+  return 1;
+}
+
+/* Fetch IP, netmask from IPCP */
+void
+radius_Account_Set_Ip(struct radacct *ac, struct in_addr *peer_ip,
+		      struct in_addr *netmask)
+{
+  ac->proto = PROTO_IPCP;
+  memcpy(&ac->peer.ip.addr, peer_ip, sizeof(ac->peer.ip.addr));
+  memcpy(&ac->peer.ip.mask, netmask, sizeof(ac->peer.ip.mask));
+}
+
+#ifndef NOINET6
+/* Fetch interface-id from IPV6CP */
+void
+radius_Account_Set_Ipv6(struct radacct *ac, u_char *ifid)
+{
+  ac->proto = PROTO_IPV6CP;
+  memcpy(&ac->peer.ipv6.ifid, ifid, sizeof(ac->peer.ipv6.ifid));
+}
+#endif
+
+/*
+ * Send an accounting request to the RADIUS server
+ */
+void
+radius_Account(struct radius *r, struct radacct *ac, struct datalink *dl,
+               int acct_type, struct pppThroughput *stats)
+{
+  struct timeval tv;
+  int got;
+  char hostname[MAXHOSTNAMELEN];
+  char *mac_addr;
+  struct hostent *hp;
+  struct in_addr hostaddr;
+
+  if (!*r->cfg.file)
+    return;
+
+  if (r->cx.fd != -1)
+    /*
+     * We assume that our name/key/challenge is the same as last time,
+     * and just continue to wait for the RADIUS server(s).
+     */
+    return;
+
+  timer_Stop(&r->cx.timer);
+
+  if ((r->cx.rad = rad_acct_open()) == NULL) {
+    log_Printf(LogERROR, "rad_auth_open: %s\n", strerror(errno));
+    return;
+  }
+
+  if (rad_config(r->cx.rad, r->cfg.file) != 0) {
+    log_Printf(LogERROR, "rad_config: %s\n", rad_strerror(r->cx.rad));
+    rad_close(r->cx.rad);
+    return;
+  }
+
+  if (rad_create_request(r->cx.rad, RAD_ACCOUNTING_REQUEST) != 0) {
+    log_Printf(LogERROR, "rad_create_request: %s\n", rad_strerror(r->cx.rad));
+    rad_close(r->cx.rad);
+    return;
+  }
+
+  /* Grab some accounting data and initialize structure */
+  if (acct_type == RAD_START) {
+    ac->rad_parent = r;
+    /* Fetch username from datalink */
+    strncpy(ac->user_name, dl->peer.authname, sizeof ac->user_name);
+    ac->user_name[AUTHLEN-1] = '\0';
+
+    ac->authentic = 2;		/* Assume RADIUS verified auth data */
+
+    /* Generate a session ID */
+    snprintf(ac->session_id, sizeof ac->session_id, "%s%ld-%s%lu",
+             dl->bundle->cfg.auth.name, (long)getpid(),
+             dl->peer.authname, (unsigned long)stats->uptime);
+
+    /* And grab our MP socket name */
+    snprintf(ac->multi_session_id, sizeof ac->multi_session_id, "%s",
+             dl->bundle->ncp.mp.active ?
+             dl->bundle->ncp.mp.server.socket.sun_path : "");
+  };
+
+  if (rad_put_string(r->cx.rad, RAD_USER_NAME, ac->user_name) != 0 ||
+      rad_put_int(r->cx.rad, RAD_SERVICE_TYPE, RAD_FRAMED) != 0 ||
+      rad_put_int(r->cx.rad, RAD_FRAMED_PROTOCOL, RAD_PPP) != 0) {
+    log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+    rad_close(r->cx.rad);
+    return;
+  }
+  switch (ac->proto) {
+  case PROTO_IPCP:
+    if (rad_put_addr(r->cx.rad, RAD_FRAMED_IP_ADDRESS,
+		     ac->peer.ip.addr) != 0 ||
+	rad_put_addr(r->cx.rad, RAD_FRAMED_IP_NETMASK,
+		     ac->peer.ip.mask) != 0) {
+      log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+      rad_close(r->cx.rad);
+      return;
+    }
+    break;
+#ifndef NOINET6
+  case PROTO_IPV6CP:
+    if (rad_put_attr(r->cx.rad, RAD_FRAMED_INTERFACE_ID, ac->peer.ipv6.ifid,
+		     sizeof(ac->peer.ipv6.ifid)) != 0) {
+      log_Printf(LogERROR, "rad_put_attr: %s\n", rad_strerror(r->cx.rad));
+      rad_close(r->cx.rad);
+      return;
+    }
+    if (r->ipv6prefix) {
+      /*
+       * Since PPP doesn't delegate an IPv6 prefix to a peer,
+       * Framed-IPv6-Prefix may be not used, actually.
+       */
+      if (rad_put_attr(r->cx.rad, RAD_FRAMED_IPV6_PREFIX, r->ipv6prefix,
+		       sizeof(struct in6_addr) + 2) != 0) {
+	log_Printf(LogERROR, "rad_put_attr: %s\n", rad_strerror(r->cx.rad));
+	rad_close(r->cx.rad);
+	return;
+      }
+    }
+    break;
+#endif
+  default:
+    /* We don't log any protocol specific information */
+    break;
+  }
+
+  if ((mac_addr = getenv("HISMACADDR")) != NULL &&
+      rad_put_string(r->cx.rad, RAD_CALLING_STATION_ID, mac_addr) != 0) {
+    log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+    rad_close(r->cx.rad);
+    return;
+  }
+
+  if (gethostname(hostname, sizeof hostname) != 0)
+    log_Printf(LogERROR, "rad_put: gethostname(): %s\n", strerror(errno));
+  else {
+    if (Enabled(dl->bundle, OPT_NAS_IP_ADDRESS) &&
+        (hp = gethostbyname(hostname)) != NULL) {
+      hostaddr.s_addr = *(u_long *)hp->h_addr;
+      if (rad_put_addr(r->cx.rad, RAD_NAS_IP_ADDRESS, hostaddr) != 0) {
+        log_Printf(LogERROR, "rad_put: rad_put_string: %s\n",
+                   rad_strerror(r->cx.rad));
+        rad_close(r->cx.rad);
+        return;
+      }
+    }
+    if (Enabled(dl->bundle, OPT_NAS_IDENTIFIER) &&
+        rad_put_string(r->cx.rad, RAD_NAS_IDENTIFIER, hostname) != 0) {
+      log_Printf(LogERROR, "rad_put: rad_put_string: %s\n",
+                 rad_strerror(r->cx.rad));
+      rad_close(r->cx.rad);
+      return;
+    }
+  }
+
+  radius_put_physical_details(r, dl->physical);
+
+  if (rad_put_int(r->cx.rad, RAD_ACCT_STATUS_TYPE, acct_type) != 0 ||
+      rad_put_string(r->cx.rad, RAD_ACCT_SESSION_ID, ac->session_id) != 0 ||
+      rad_put_string(r->cx.rad, RAD_ACCT_MULTI_SESSION_ID,
+                     ac->multi_session_id) != 0 ||
+      rad_put_int(r->cx.rad, RAD_ACCT_DELAY_TIME, 0) != 0) {
+/* XXX ACCT_DELAY_TIME should be increased each time a packet is waiting */
+    log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+    rad_close(r->cx.rad);
+    return;
+  }
+
+  if (acct_type == RAD_STOP || acct_type == RAD_ALIVE)
+    /* Show some statistics */
+    if (rad_put_int(r->cx.rad, RAD_ACCT_INPUT_OCTETS, stats->OctetsIn % UINT32_MAX) != 0 ||
+        rad_put_int(r->cx.rad, RAD_ACCT_INPUT_GIGAWORDS, stats->OctetsIn / UINT32_MAX) != 0 ||
+        rad_put_int(r->cx.rad, RAD_ACCT_INPUT_PACKETS, stats->PacketsIn) != 0 ||
+        rad_put_int(r->cx.rad, RAD_ACCT_OUTPUT_OCTETS, stats->OctetsOut % UINT32_MAX) != 0 ||
+        rad_put_int(r->cx.rad, RAD_ACCT_OUTPUT_GIGAWORDS, stats->OctetsOut / UINT32_MAX) != 0 ||
+        rad_put_int(r->cx.rad, RAD_ACCT_OUTPUT_PACKETS, stats->PacketsOut)
+        != 0 ||
+        rad_put_int(r->cx.rad, RAD_ACCT_SESSION_TIME, throughput_uptime(stats))
+        != 0) {
+      log_Printf(LogERROR, "rad_put: %s\n", rad_strerror(r->cx.rad));
+      rad_close(r->cx.rad);
+      return;
+    }
+
+  if (log_IsKept(LogPHASE) || log_IsKept(LogRADIUS)) {
+    const char *what;
+    int level;
+
+    switch (acct_type) {
+    case RAD_START:
+      what = "START";
+      level = log_IsKept(LogPHASE) ? LogPHASE : LogRADIUS;
+      break;
+    case RAD_STOP:
+      what = "STOP";
+      level = log_IsKept(LogPHASE) ? LogPHASE : LogRADIUS;
+      break;
+    case RAD_ALIVE:
+      what = "ALIVE";
+      level = LogRADIUS;
+      break;
+    default:
+      what = "<unknown>";
+      level = log_IsKept(LogPHASE) ? LogPHASE : LogRADIUS;
+      break;
+    }
+    log_Printf(level, "Radius(acct): %s data sent\n", what);
+  }
+  
+  r->cx.auth = NULL;			/* Not valid for accounting requests */
+  if ((got = rad_init_send_request(r->cx.rad, &r->cx.fd, &tv)))
+    radius_Process(r, got);
+  else {
+    log_Printf(LogDEBUG, "Using radius_Timeout [%p]\n", radius_Timeout);
+    r->cx.timer.load = tv.tv_usec / TICKUNIT + tv.tv_sec * SECTICKS;
+    r->cx.timer.func = radius_Timeout;
+    r->cx.timer.name = "radius acct";
+    r->cx.timer.arg = r;
+    timer_Start(&r->cx.timer);
+  }
+}
+
+/*
+ * How do things look at the moment ?
+ */
+void
+radius_Show(struct radius *r, struct prompt *p)
+{
+  prompt_Printf(p, " Radius config:     %s",
+                *r->cfg.file ? r->cfg.file : "none");
+  if (r->valid) {
+    prompt_Printf(p, "\n                IP: %s\n", inet_ntoa(r->ip));
+    prompt_Printf(p, "           Netmask: %s\n", inet_ntoa(r->mask));
+    prompt_Printf(p, "               MTU: %lu\n", r->mtu);
+    prompt_Printf(p, "                VJ: %sabled\n", r->vj ? "en" : "dis");
+    prompt_Printf(p, "           Message: %s\n", r->repstr ? r->repstr : "");
+    prompt_Printf(p, "   MPPE Enc Policy: %s\n",
+                  radius_policyname(r->mppe.policy));
+    prompt_Printf(p, "    MPPE Enc Types: %s\n",
+                  radius_typesname(r->mppe.types));
+    prompt_Printf(p, "     MPPE Recv Key: %seceived\n",
+                  r->mppe.recvkey ? "R" : "Not r");
+    prompt_Printf(p, "     MPPE Send Key: %seceived\n",
+                  r->mppe.sendkey ? "R" : "Not r");
+    prompt_Printf(p, " MS-CHAP2-Response: %s\n",
+                  r->msrepstr ? r->msrepstr : "");
+    prompt_Printf(p, "     Error Message: %s\n", r->errstr ? r->errstr : "");
+    if (r->routes)
+      route_ShowSticky(p, r->routes, "            Routes", 16);
+#ifndef NOINET6
+    if (r->ipv6routes)
+      route_ShowSticky(p, r->ipv6routes, "            IPv6 Routes", 16);
+#endif
+  } else
+    prompt_Printf(p, " (not authenticated)\n");
+}
+
+static void
+radius_alive(void *v)
+{
+  struct bundle *bundle = (struct bundle *)v;
+
+  timer_Stop(&bundle->radius.alive.timer);
+  bundle->radius.alive.timer.load = bundle->radius.alive.interval * SECTICKS;
+  if (bundle->radius.alive.timer.load) {
+    radius_Account(&bundle->radius, &bundle->radacct,
+                   bundle->links, RAD_ALIVE, &bundle->ncp.ipcp.throughput);
+    timer_Start(&bundle->radius.alive.timer);
+  }
+}
+
+void
+radius_StartTimer(struct bundle *bundle)
+{
+  if (bundle->radius.cfg.file && bundle->radius.alive.interval) {
+    bundle->radius.alive.timer.func = radius_alive;
+    bundle->radius.alive.timer.name = "radius alive";
+    bundle->radius.alive.timer.load = bundle->radius.alive.interval * SECTICKS;
+    bundle->radius.alive.timer.arg = bundle;
+    radius_alive(bundle);
+  }
+}
+
+void
+radius_StopTimer(struct radius *r)
+{
+  timer_Stop(&r->alive.timer);
+}
diff --git a/src/radius.h b/src/radius.h
new file mode 100644
index 0000000..9f5caa5
--- /dev/null
+++ b/src/radius.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright 1999 Internet Business Solutions Ltd., Switzerland
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/radius.h,v 1.22.14.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define	MPPE_POLICY_ALLOWED	1
+#define	MPPE_POLICY_REQUIRED	2
+
+#define	MPPE_TYPE_40BIT		2
+#define	MPPE_TYPE_128BIT	4
+
+#define	RPI_DEFAULT		1
+#define	RPI_PID			2
+#define	RPI_IFNUM		3
+#define	RPI_TUNNUM		4
+
+struct radius {
+  struct fdescriptor desc;	/* We're a sort of (selectable) fdescriptor */
+  struct {
+    int fd;			/* We're selecting on this */
+    struct rad_handle *rad;	/* Using this to talk to our lib */
+    struct pppTimer timer;	/* for this long */
+    struct authinfo *auth;	/* Tell this about success/failure */
+  } cx;
+  unsigned valid : 1;           /* Is this structure valid ? */
+  unsigned vj : 1;              /* FRAMED Compression */
+  struct in_addr ip;            /* FRAMED IP */
+  struct in_addr mask;          /* FRAMED Netmask */
+  unsigned long mtu;            /* FRAMED MTU */
+  unsigned long sessiontime;    /* Session-Timeout */
+  char *filterid;		/* FRAMED Filter Id */
+  struct sticky_route *routes;  /* FRAMED Routes */
+  char *msrepstr;		/* MS-CHAP2-Response */
+  char *repstr;			/* Reply-Message */
+  char *errstr;			/* Error-Message */
+#ifndef NOINET6
+  uint8_t *ipv6prefix;		/* FRAMED IPv6 Prefix */
+  struct sticky_route *ipv6routes;  /* FRAMED IPv6 Routes */
+#endif
+  struct {
+    int policy;			/* MPPE_POLICY_* */
+    int types;			/* MPPE_TYPE_*BIT bitmask */
+    char *recvkey;
+    size_t recvkeylen;
+    char *sendkey;
+    size_t sendkeylen;
+  } mppe;
+  struct {
+    char file[PATH_MAX];	/* Radius config file */
+  } cfg;
+  struct {
+    struct pppTimer timer;	/* for this long */
+    int interval;
+  } alive;
+  short unsigned int port_id_type;
+};
+
+struct radacct {
+  struct radius *rad_parent;	/* "Parent" struct radius stored in bundle */
+  char user_name[AUTHLEN];	/* Session User-Name */
+  char session_id[256];		/* Unique session ID */
+  char multi_session_id[51];	/* Unique MP session ID */
+  int  authentic;		/* How the session has been authenticated */
+  u_short proto;		/* Protocol number */
+  union {
+    struct {
+      struct in_addr addr;
+      struct in_addr mask;
+    } ip;
+#ifndef NOINET6
+    struct {
+      u_char ifid[8];
+    } ipv6;
+#endif
+  } peer;
+};
+
+#define descriptor2radius(d) \
+  ((d)->type == RADIUS_DESCRIPTOR ? (struct radius *)(d) : NULL)
+
+struct bundle;
+
+extern void radius_Flush(struct radius *);
+extern void radius_Init(struct radius *);
+extern void radius_Destroy(struct radius *);
+
+extern void radius_Show(struct radius *, struct prompt *);
+extern void radius_StartTimer(struct bundle *);
+extern void radius_StopTimer(struct radius *);
+extern int radius_Authenticate(struct radius *, struct authinfo *,
+                               const char *, const char *, int,
+                               const char *, int);
+extern void radius_Account_Set_Ip(struct radacct *, struct in_addr *,
+				  struct in_addr *);
+#ifndef NOINET6
+extern void radius_Account_Set_Ipv6(struct radacct *, u_char *);
+#endif
+extern void radius_Account(struct radius *, struct radacct *,
+                           struct datalink *, int, struct pppThroughput *);
+
+/* An (int) parameter to radius_Account, from radlib.h */
+#if !defined(RAD_START)
+#define RAD_START	1
+#define RAD_STOP	2
+#endif
+
+#define RAD_ALIVE	3
+
+/* Get address from NAS pool */
+#define RADIUS_INADDR_POOL	htonl(0xfffffffe)	/* 255.255.255.254 */
diff --git a/src/route.c b/src/route.c
new file mode 100644
index 0000000..f5c98ee
--- /dev/null
+++ b/src/route.c
@@ -0,0 +1,929 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/route.c,v 1.94.10.2.4.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <net/if_types.h>
+#include <net/route.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if_dl.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/sysctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "iplist.h"
+#include "timer.h"
+#include "throughput.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "route.h"
+#include "prompt.h"
+#include "iface.h"
+#include "id.h"
+
+
+static void
+p_sockaddr(struct prompt *prompt, struct sockaddr *phost,
+           struct sockaddr *pmask, int width)
+{
+  struct ncprange range;
+  char buf[29];
+  struct sockaddr_dl *dl = (struct sockaddr_dl *)phost;
+
+  if (log_IsKept(LogDEBUG)) {
+    char tmp[50];
+
+    log_Printf(LogDEBUG, "Found the following sockaddr:\n");
+    log_Printf(LogDEBUG, "  Family %d, len %d\n",
+               (int)phost->sa_family, (int)phost->sa_len);
+    inet_ntop(phost->sa_family, phost->sa_data, tmp, sizeof tmp);
+    log_Printf(LogDEBUG, "  Addr %s\n", tmp);
+    if (pmask) {
+      inet_ntop(pmask->sa_family, pmask->sa_data, tmp, sizeof tmp);
+      log_Printf(LogDEBUG, "  Mask %s\n", tmp);
+    }
+  }
+
+  switch (phost->sa_family) {
+  case AF_INET:
+#ifndef NOINET6
+  case AF_INET6:
+#endif
+    ncprange_setsa(&range, phost, pmask);
+    if (ncprange_isdefault(&range))
+      prompt_Printf(prompt, "%-*s ", width - 1, "default");
+    else
+      prompt_Printf(prompt, "%-*s ", width - 1, ncprange_ntoa(&range));
+    return;
+
+  case AF_LINK:
+    if (dl->sdl_nlen)
+      snprintf(buf, sizeof buf, "%.*s", dl->sdl_nlen, dl->sdl_data);
+    else if (dl->sdl_alen) {
+      if (dl->sdl_type == IFT_ETHER) {
+        if (dl->sdl_alen < sizeof buf / 3) {
+          int f;
+          u_char *MAC;
+
+          MAC = (u_char *)dl->sdl_data + dl->sdl_nlen;
+          for (f = 0; f < dl->sdl_alen; f++)
+            sprintf(buf+f*3, "%02x:", MAC[f]);
+          buf[f*3-1] = '\0';
+        } else
+          strcpy(buf, "??:??:??:??:??:??");
+      } else
+        sprintf(buf, "<IFT type %d>", dl->sdl_type);
+    }  else if (dl->sdl_slen)
+      sprintf(buf, "<slen %d?>", dl->sdl_slen);
+    else
+      sprintf(buf, "link#%d", dl->sdl_index);
+    break;
+
+  default:
+    sprintf(buf, "<AF type %d>", phost->sa_family);
+    break;
+  }
+
+  prompt_Printf(prompt, "%-*s ", width-1, buf);
+}
+
+static struct bits {
+  u_int32_t b_mask;
+  char b_val;
+} bits[] = {
+  { RTF_UP, 'U' },
+  { RTF_GATEWAY, 'G' },
+  { RTF_HOST, 'H' },
+  { RTF_REJECT, 'R' },
+  { RTF_DYNAMIC, 'D' },
+  { RTF_MODIFIED, 'M' },
+  { RTF_DONE, 'd' },
+  { RTF_CLONING, 'C' },
+  { RTF_XRESOLVE, 'X' },
+  { RTF_LLINFO, 'L' },
+  { RTF_STATIC, 'S' },
+  { RTF_PROTO1, '1' },
+  { RTF_PROTO2, '2' },
+  { RTF_BLACKHOLE, 'B' },
+#ifdef RTF_WASCLONED
+  { RTF_WASCLONED, 'W' },
+#endif
+#ifdef RTF_PRCLONING
+  { RTF_PRCLONING, 'c' },
+#endif
+#ifdef RTF_PROTO3
+  { RTF_PROTO3, '3' },
+#endif
+#ifdef RTF_BROADCAST
+  { RTF_BROADCAST, 'b' },
+#endif
+  { 0, '\0' }
+};
+
+#ifndef RTF_WASCLONED
+#define RTF_WASCLONED (0)
+#endif
+
+static void
+p_flags(struct prompt *prompt, u_int32_t f, unsigned max)
+{
+  char name[33], *flags;
+  register struct bits *p = bits;
+
+  if (max > sizeof name - 1)
+    max = sizeof name - 1;
+
+  for (flags = name; p->b_mask && flags - name < (int)max; p++)
+    if (p->b_mask & f)
+      *flags++ = p->b_val;
+  *flags = '\0';
+  prompt_Printf(prompt, "%-*.*s", (int)max, (int)max, name);
+}
+
+static int route_nifs = -1;
+
+const char *
+Index2Nam(int idx)
+{
+  /*
+   * XXX: Maybe we should select() on the routing socket so that we can
+   *      notice interfaces that come & go (PCCARD support).
+   *      Or we could even support a signal that resets these so that
+   *      the PCCARD insert/remove events can signal ppp.
+   */
+  static char **ifs;		/* Figure these out once */
+  static int debug_done;	/* Debug once */
+
+  if (idx > route_nifs || (idx > 0 && ifs[idx-1] == NULL)) {
+    int mib[6], have, had;
+    size_t needed;
+    char *buf, *ptr, *end;
+    struct sockaddr_dl *dl;
+    struct if_msghdr *ifm;
+
+    if (ifs) {
+      free(ifs);
+      ifs = NULL;
+      route_nifs = 0;
+    }
+    debug_done = 0;
+
+    mib[0] = CTL_NET;
+    mib[1] = PF_ROUTE;
+    mib[2] = 0;
+    mib[3] = 0;
+    mib[4] = NET_RT_IFLIST;
+    mib[5] = 0;
+
+    if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+      log_Printf(LogERROR, "Index2Nam: sysctl: estimate: %s\n",
+                 strerror(errno));
+      return NumStr(idx, NULL, 0);
+    }
+    if ((buf = malloc(needed)) == NULL)
+      return NumStr(idx, NULL, 0);
+    if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
+      free(buf);
+      return NumStr(idx, NULL, 0);
+    }
+    end = buf + needed;
+
+    have = 0;
+    for (ptr = buf; ptr < end; ptr += ifm->ifm_msglen) {
+      ifm = (struct if_msghdr *)ptr;
+      if (ifm->ifm_type != RTM_IFINFO)
+        continue;
+      dl = (struct sockaddr_dl *)(ifm + 1);
+      if (ifm->ifm_index > 0) {
+        if (ifm->ifm_index > have) {
+          char **newifs;
+
+          had = have;
+          have = ifm->ifm_index + 5;
+          if (had)
+            newifs = (char **)realloc(ifs, sizeof(char *) * have);
+          else
+            newifs = (char **)malloc(sizeof(char *) * have);
+          if (!newifs) {
+            log_Printf(LogDEBUG, "Index2Nam: %s\n", strerror(errno));
+            route_nifs = 0;
+            if (ifs) {
+              free(ifs);
+              ifs = NULL;
+            }
+            free(buf);
+            return NumStr(idx, NULL, 0);
+          }
+          ifs = newifs;
+          memset(ifs + had, '\0', sizeof(char *) * (have - had));
+        }
+        if (ifs[ifm->ifm_index-1] == NULL) {
+          ifs[ifm->ifm_index-1] = (char *)malloc(dl->sdl_nlen+1);
+          if (ifs[ifm->ifm_index-1] == NULL)
+	    log_Printf(LogDEBUG, "Skipping interface %d: Out of memory\n",
+                  ifm->ifm_index);
+	  else {
+	    memcpy(ifs[ifm->ifm_index-1], dl->sdl_data, dl->sdl_nlen);
+	    ifs[ifm->ifm_index-1][dl->sdl_nlen] = '\0';
+	    if (route_nifs < ifm->ifm_index)
+	      route_nifs = ifm->ifm_index;
+	  }
+        }
+      } else if (log_IsKept(LogDEBUG))
+        log_Printf(LogDEBUG, "Skipping out-of-range interface %d!\n",
+                  ifm->ifm_index);
+    }
+    free(buf);
+  }
+
+  if (log_IsKept(LogDEBUG) && !debug_done) {
+    int f;
+
+    log_Printf(LogDEBUG, "Found the following interfaces:\n");
+    for (f = 0; f < route_nifs; f++)
+      if (ifs[f] != NULL)
+        log_Printf(LogDEBUG, " Index %d, name \"%s\"\n", f+1, ifs[f]);
+    debug_done = 1;
+  }
+
+  if (idx < 1 || idx > route_nifs || ifs[idx-1] == NULL)
+    return NumStr(idx, NULL, 0);
+
+  return ifs[idx-1];
+}
+
+void
+route_ParseHdr(struct rt_msghdr *rtm, struct sockaddr *sa[RTAX_MAX])
+{
+  char *wp;
+  int rtax;
+
+  wp = (char *)(rtm + 1);
+
+  for (rtax = 0; rtax < RTAX_MAX; rtax++)
+    if (rtm->rtm_addrs & (1 << rtax)) {
+      sa[rtax] = (struct sockaddr *)wp;
+      wp += ROUNDUP(sa[rtax]->sa_len);
+      if (sa[rtax]->sa_family == 0)
+        sa[rtax] = NULL;	/* ??? */
+    } else
+      sa[rtax] = NULL;
+}
+
+int
+route_Show(struct cmdargs const *arg)
+{
+  struct rt_msghdr *rtm;
+  struct sockaddr *sa[RTAX_MAX];
+  char *sp, *ep, *cp;
+  size_t needed;
+  int mib[6];
+
+  mib[0] = CTL_NET;
+  mib[1] = PF_ROUTE;
+  mib[2] = 0;
+  mib[3] = 0;
+  mib[4] = NET_RT_DUMP;
+  mib[5] = 0;
+  if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+    log_Printf(LogERROR, "route_Show: sysctl: estimate: %s\n", strerror(errno));
+    return (1);
+  }
+  sp = malloc(needed);
+  if (sp == NULL)
+    return (1);
+  if (sysctl(mib, 6, sp, &needed, NULL, 0) < 0) {
+    log_Printf(LogERROR, "route_Show: sysctl: getroute: %s\n", strerror(errno));
+    free(sp);
+    return (1);
+  }
+  ep = sp + needed;
+
+  prompt_Printf(arg->prompt, "%-20s%-20sFlags  Netif\n",
+                "Destination", "Gateway");
+  for (cp = sp; cp < ep; cp += rtm->rtm_msglen) {
+    rtm = (struct rt_msghdr *)cp;
+
+    route_ParseHdr(rtm, sa);
+
+    if (sa[RTAX_DST] && sa[RTAX_GATEWAY]) {
+      p_sockaddr(arg->prompt, sa[RTAX_DST], sa[RTAX_NETMASK], 20);
+      p_sockaddr(arg->prompt, sa[RTAX_GATEWAY], NULL, 20);
+
+      p_flags(arg->prompt, rtm->rtm_flags, 6);
+      prompt_Printf(arg->prompt, " %s\n", Index2Nam(rtm->rtm_index));
+    } else
+      prompt_Printf(arg->prompt, "<can't parse routing entry>\n");
+  }
+  free(sp);
+  return 0;
+}
+
+/*
+ *  Delete routes associated with our interface
+ */
+void
+route_IfDelete(struct bundle *bundle, int all)
+{
+  struct rt_msghdr *rtm;
+  struct sockaddr *sa[RTAX_MAX];
+  struct ncprange range;
+  int pass;
+  size_t needed;
+  char *sp, *cp, *ep;
+  int mib[6];
+
+  log_Printf(LogDEBUG, "route_IfDelete (%d)\n", bundle->iface->index);
+
+  mib[0] = CTL_NET;
+  mib[1] = PF_ROUTE;
+  mib[2] = 0;
+  mib[3] = 0;
+  mib[4] = NET_RT_DUMP;
+  mib[5] = 0;
+  if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+    log_Printf(LogERROR, "route_IfDelete: sysctl: estimate: %s\n",
+              strerror(errno));
+    return;
+  }
+
+  sp = malloc(needed);
+  if (sp == NULL)
+    return;
+
+  if (sysctl(mib, 6, sp, &needed, NULL, 0) < 0) {
+    log_Printf(LogERROR, "route_IfDelete: sysctl: getroute: %s\n",
+              strerror(errno));
+    free(sp);
+    return;
+  }
+  ep = sp + needed;
+
+  for (pass = 0; pass < 2; pass++) {
+    /*
+     * We do 2 passes.  The first deletes all cloned routes.  The second
+     * deletes all non-cloned routes.  This is done to avoid
+     * potential errors from trying to delete route X after route Y where
+     * route X was cloned from route Y (and is no longer there 'cos it
+     * may have gone with route Y).
+     */
+    if (RTF_WASCLONED == 0 && pass == 0)
+      /* So we can't tell ! */
+      continue;
+    for (cp = sp; cp < ep; cp += rtm->rtm_msglen) {
+      rtm = (struct rt_msghdr *)cp;
+      route_ParseHdr(rtm, sa);
+      if (rtm->rtm_index == bundle->iface->index &&
+          sa[RTAX_DST] && sa[RTAX_GATEWAY] &&
+          (sa[RTAX_DST]->sa_family == AF_INET
+#ifndef NOINET6
+           || sa[RTAX_DST]->sa_family == AF_INET6
+#endif
+           ) &&
+          (all || (rtm->rtm_flags & RTF_GATEWAY))) {
+        if (log_IsKept(LogDEBUG)) {
+          char gwstr[41];
+          struct ncpaddr gw;
+          ncprange_setsa(&range, sa[RTAX_DST], sa[RTAX_NETMASK]);
+          ncpaddr_setsa(&gw, sa[RTAX_GATEWAY]);
+          snprintf(gwstr, sizeof gwstr, "%s", ncpaddr_ntoa(&gw));
+          log_Printf(LogDEBUG, "Found %s %s\n", ncprange_ntoa(&range), gwstr);
+        }
+        if (sa[RTAX_GATEWAY]->sa_family == AF_INET ||
+#ifndef NOINET6
+            sa[RTAX_GATEWAY]->sa_family == AF_INET6 ||
+#endif
+            sa[RTAX_GATEWAY]->sa_family == AF_LINK) {
+          if ((pass == 0 && (rtm->rtm_flags & RTF_WASCLONED)) ||
+              (pass == 1 && !(rtm->rtm_flags & RTF_WASCLONED))) {
+            ncprange_setsa(&range, sa[RTAX_DST], sa[RTAX_NETMASK]);
+            rt_Set(bundle, RTM_DELETE, &range, NULL, 0, 0);
+          } else
+            log_Printf(LogDEBUG, "route_IfDelete: Skip it (pass %d)\n", pass);
+        } else
+          log_Printf(LogDEBUG,
+                    "route_IfDelete: Can't remove routes for family %d\n",
+                    sa[RTAX_GATEWAY]->sa_family);
+      }
+    }
+  }
+  free(sp);
+}
+
+
+/*
+ *  Update the MTU on all routes for the given interface
+ */
+void
+route_UpdateMTU(struct bundle *bundle)
+{
+  struct rt_msghdr *rtm;
+  struct sockaddr *sa[RTAX_MAX];
+  struct ncprange dst;
+  size_t needed;
+  char *sp, *cp, *ep;
+  int mib[6];
+
+  log_Printf(LogDEBUG, "route_UpdateMTU (%d)\n", bundle->iface->index);
+
+  mib[0] = CTL_NET;
+  mib[1] = PF_ROUTE;
+  mib[2] = 0;
+  mib[3] = 0;
+  mib[4] = NET_RT_DUMP;
+  mib[5] = 0;
+  if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+    log_Printf(LogERROR, "route_IfDelete: sysctl: estimate: %s\n",
+              strerror(errno));
+    return;
+  }
+
+  sp = malloc(needed);
+  if (sp == NULL)
+    return;
+
+  if (sysctl(mib, 6, sp, &needed, NULL, 0) < 0) {
+    log_Printf(LogERROR, "route_IfDelete: sysctl: getroute: %s\n",
+              strerror(errno));
+    free(sp);
+    return;
+  }
+  ep = sp + needed;
+
+  for (cp = sp; cp < ep; cp += rtm->rtm_msglen) {
+    rtm = (struct rt_msghdr *)cp;
+    route_ParseHdr(rtm, sa);
+    if (sa[RTAX_DST] && (sa[RTAX_DST]->sa_family == AF_INET
+#ifndef NOINET6
+                         || sa[RTAX_DST]->sa_family == AF_INET6
+#endif
+                        ) &&
+        sa[RTAX_GATEWAY] && rtm->rtm_index == bundle->iface->index) {
+      if (log_IsKept(LogTCPIP)) {
+        ncprange_setsa(&dst, sa[RTAX_DST], sa[RTAX_NETMASK]);
+        log_Printf(LogTCPIP, "route_UpdateMTU: Netif: %d (%s), dst %s,"
+                   " mtu %lu\n", rtm->rtm_index, Index2Nam(rtm->rtm_index),
+                   ncprange_ntoa(&dst), bundle->iface->mtu);
+      }
+      rt_Update(bundle, sa[RTAX_DST], sa[RTAX_GATEWAY], sa[RTAX_NETMASK]);
+    }
+  }
+
+  free(sp);
+}
+
+int
+GetIfIndex(char *name)
+{
+  int idx;
+
+  idx = 1;
+  while (route_nifs == -1 || idx < route_nifs)
+    if (strcmp(Index2Nam(idx), name) == 0)
+      return idx;
+    else
+      idx++;
+  return -1;
+}
+
+void
+route_Change(struct bundle *bundle, struct sticky_route *r,
+             const struct ncpaddr *me, const struct ncpaddr *peer)
+{
+  struct ncpaddr dst;
+
+  for (; r; r = r->next) {
+    ncprange_getaddr(&r->dst, &dst);
+    if (ncpaddr_family(me) == AF_INET) {
+      if ((r->type & ROUTE_DSTMYADDR) && !ncpaddr_equal(&dst, me)) {
+        rt_Set(bundle, RTM_DELETE, &r->dst, NULL, 1, 0);
+        ncprange_sethost(&r->dst, me);
+        if (r->type & ROUTE_GWHISADDR)
+          ncpaddr_copy(&r->gw, peer);
+      } else if ((r->type & ROUTE_DSTHISADDR) && !ncpaddr_equal(&dst, peer)) {
+        rt_Set(bundle, RTM_DELETE, &r->dst, NULL, 1, 0);
+        ncprange_sethost(&r->dst, peer);
+        if (r->type & ROUTE_GWHISADDR)
+          ncpaddr_copy(&r->gw, peer);
+      } else if ((r->type & ROUTE_DSTDNS0) && !ncpaddr_equal(&dst, peer)) {
+        if (bundle->ncp.ipcp.ns.dns[0].s_addr == INADDR_NONE)
+          continue;
+        rt_Set(bundle, RTM_DELETE, &r->dst, NULL, 1, 0);
+        if (r->type & ROUTE_GWHISADDR)
+          ncpaddr_copy(&r->gw, peer);
+      } else if ((r->type & ROUTE_DSTDNS1) && !ncpaddr_equal(&dst, peer)) {
+        if (bundle->ncp.ipcp.ns.dns[1].s_addr == INADDR_NONE)
+          continue;
+        rt_Set(bundle, RTM_DELETE, &r->dst, NULL, 1, 0);
+        if (r->type & ROUTE_GWHISADDR)
+          ncpaddr_copy(&r->gw, peer);
+      } else if ((r->type & ROUTE_GWHISADDR) && !ncpaddr_equal(&r->gw, peer))
+        ncpaddr_copy(&r->gw, peer);
+#ifndef NOINET6
+    } else if (ncpaddr_family(me) == AF_INET6) {
+      if ((r->type & ROUTE_DSTMYADDR6) && !ncpaddr_equal(&dst, me)) {
+        rt_Set(bundle, RTM_DELETE, &r->dst, NULL, 1, 0);
+        ncprange_sethost(&r->dst, me);
+        if (r->type & ROUTE_GWHISADDR)
+          ncpaddr_copy(&r->gw, peer);
+      } else if ((r->type & ROUTE_DSTHISADDR6) && !ncpaddr_equal(&dst, peer)) {
+        rt_Set(bundle, RTM_DELETE, &r->dst, NULL, 1, 0);
+        ncprange_sethost(&r->dst, peer);
+        if (r->type & ROUTE_GWHISADDR)
+          ncpaddr_copy(&r->gw, peer);
+      } else if ((r->type & ROUTE_GWHISADDR6) && !ncpaddr_equal(&r->gw, peer))
+        ncpaddr_copy(&r->gw, peer);
+#endif
+    }
+    rt_Set(bundle, RTM_ADD, &r->dst, &r->gw, 1, 0);
+  }
+}
+
+void
+route_Add(struct sticky_route **rp, int type, const struct ncprange *dst,
+          const struct ncpaddr *gw)
+{
+  struct sticky_route *r;
+  int dsttype = type & ROUTE_DSTANY;
+
+  r = NULL;
+  while (*rp) {
+    if ((dsttype && dsttype == ((*rp)->type & ROUTE_DSTANY)) ||
+        (!dsttype && ncprange_equal(&(*rp)->dst, dst))) {
+      /* Oops, we already have this route - unlink it */
+      free(r);			/* impossible really  */
+      r = *rp;
+      *rp = r->next;
+    } else
+      rp = &(*rp)->next;
+  }
+
+  if (r == NULL) {
+    r = (struct sticky_route *)malloc(sizeof(struct sticky_route));
+    if (r == NULL) {
+      log_Printf(LogERROR, "route_Add: Out of memory!\n");
+      return;
+    }
+  }
+  r->type = type;
+  r->next = NULL;
+  ncprange_copy(&r->dst, dst);
+  ncpaddr_copy(&r->gw, gw);
+  *rp = r;
+}
+
+void
+route_Delete(struct sticky_route **rp, int type, const struct ncprange *dst)
+{
+  struct sticky_route *r;
+  int dsttype = type & ROUTE_DSTANY;
+
+  for (; *rp; rp = &(*rp)->next) {
+    if ((dsttype && dsttype == ((*rp)->type & ROUTE_DSTANY)) ||
+        (!dsttype && ncprange_equal(dst, &(*rp)->dst))) {
+      r = *rp;
+      *rp = r->next;
+      free(r);
+      break;
+    }
+  }
+}
+
+void
+route_DeleteAll(struct sticky_route **rp)
+{
+  struct sticky_route *r, *rn;
+
+  for (r = *rp; r; r = rn) {
+    rn = r->next;
+    free(r);
+  }
+  *rp = NULL;
+}
+
+void
+route_ShowSticky(struct prompt *p, struct sticky_route *r, const char *tag,
+                 int indent)
+{
+  int tlen = strlen(tag);
+
+  if (tlen + 2 > indent)
+    prompt_Printf(p, "%s:\n%*s", tag, indent, "");
+  else
+    prompt_Printf(p, "%s:%*s", tag, indent - tlen - 1, "");
+
+  for (; r; r = r->next) {
+    prompt_Printf(p, "%*sadd ", tlen ? 0 : indent, "");
+    tlen = 0;
+    if (r->type & ROUTE_DSTMYADDR)
+      prompt_Printf(p, "MYADDR");
+    else if (r->type & ROUTE_DSTMYADDR6)
+      prompt_Printf(p, "MYADDR6");
+    else if (r->type & ROUTE_DSTHISADDR)
+      prompt_Printf(p, "HISADDR");
+    else if (r->type & ROUTE_DSTHISADDR6)
+      prompt_Printf(p, "HISADDR6");
+    else if (r->type & ROUTE_DSTDNS0)
+      prompt_Printf(p, "DNS0");
+    else if (r->type & ROUTE_DSTDNS1)
+      prompt_Printf(p, "DNS1");
+    else if (ncprange_isdefault(&r->dst))
+      prompt_Printf(p, "default");
+    else
+      prompt_Printf(p, "%s", ncprange_ntoa(&r->dst));
+
+    if (r->type & ROUTE_GWHISADDR)
+      prompt_Printf(p, " HISADDR\n");
+    else if (r->type & ROUTE_GWHISADDR6)
+      prompt_Printf(p, " HISADDR6\n");
+    else
+      prompt_Printf(p, " %s\n", ncpaddr_ntoa(&r->gw));
+  }
+}
+
+struct rtmsg {
+  struct rt_msghdr m_rtm;
+  char m_space[256];
+};
+
+static size_t
+memcpy_roundup(char *cp, const void *data, size_t len)
+{
+  size_t padlen;
+
+  padlen = ROUNDUP(len);
+  memcpy(cp, data, len);
+  if (padlen > len)
+    memset(cp + len, '\0', padlen - len);
+
+  return padlen;
+}
+
+#if defined(__KAME__) && !defined(NOINET6)
+static void
+add_scope(struct sockaddr *sa, int ifindex)
+{
+  struct sockaddr_in6 *sa6;
+
+  if (sa->sa_family != AF_INET6)
+    return;
+  sa6 = (struct sockaddr_in6 *)sa;
+  if (!IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr) &&
+      !IN6_IS_ADDR_MC_LINKLOCAL(&sa6->sin6_addr))
+    return;
+  if (*(u_int16_t *)&sa6->sin6_addr.s6_addr[2] != 0)
+    return;
+  *(u_int16_t *)&sa6->sin6_addr.s6_addr[2] = htons(ifindex);
+}
+#endif
+
+int
+rt_Set(struct bundle *bundle, int cmd, const struct ncprange *dst,
+       const struct ncpaddr *gw, int bang, int quiet)
+{
+  struct rtmsg rtmes;
+  int s, nb, wb;
+  char *cp;
+  const char *cmdstr;
+  struct sockaddr_storage sadst, samask, sagw;
+  int result = 1;
+
+  if (bang)
+    cmdstr = (cmd == RTM_ADD ? "Add!" : "Delete!");
+  else
+    cmdstr = (cmd == RTM_ADD ? "Add" : "Delete");
+  s = ID0socket(PF_ROUTE, SOCK_RAW, 0);
+  if (s < 0) {
+    log_Printf(LogERROR, "rt_Set: socket(): %s\n", strerror(errno));
+    return result;
+  }
+  memset(&rtmes, '\0', sizeof rtmes);
+  rtmes.m_rtm.rtm_version = RTM_VERSION;
+  rtmes.m_rtm.rtm_type = cmd;
+  rtmes.m_rtm.rtm_addrs = RTA_DST;
+  rtmes.m_rtm.rtm_seq = ++bundle->routing_seq;
+  rtmes.m_rtm.rtm_pid = getpid();
+  rtmes.m_rtm.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
+
+  if (cmd == RTM_ADD) {
+    if (bundle->ncp.cfg.sendpipe > 0) {
+      rtmes.m_rtm.rtm_rmx.rmx_sendpipe = bundle->ncp.cfg.sendpipe;
+      rtmes.m_rtm.rtm_inits |= RTV_SPIPE;
+    }
+    if (bundle->ncp.cfg.recvpipe > 0) {
+      rtmes.m_rtm.rtm_rmx.rmx_recvpipe = bundle->ncp.cfg.recvpipe;
+      rtmes.m_rtm.rtm_inits |= RTV_RPIPE;
+    }
+  }
+
+  ncprange_getsa(dst, &sadst, &samask);
+#if defined(__KAME__) && !defined(NOINET6)
+  add_scope((struct sockaddr *)&sadst, bundle->iface->index);
+#endif
+
+  cp = rtmes.m_space;
+  cp += memcpy_roundup(cp, &sadst, sadst.ss_len);
+  if (cmd == RTM_ADD) {
+    if (gw == NULL) {
+      log_Printf(LogERROR, "rt_Set: Program error\n");
+      close(s);
+      return result;
+    }
+    ncpaddr_getsa(gw, &sagw);
+#if defined(__KAME__) && !defined(NOINET6)
+    add_scope((struct sockaddr *)&sagw, bundle->iface->index);
+#endif
+    if (ncpaddr_isdefault(gw)) {
+      if (!quiet)
+        log_Printf(LogERROR, "rt_Set: Cannot add a route with"
+                   " gateway 0.0.0.0\n");
+      close(s);
+      return result;
+    } else {
+      cp += memcpy_roundup(cp, &sagw, sagw.ss_len);
+      rtmes.m_rtm.rtm_addrs |= RTA_GATEWAY;
+    }
+  }
+
+  if (!ncprange_ishost(dst)) {
+    cp += memcpy_roundup(cp, &samask, samask.ss_len);
+    rtmes.m_rtm.rtm_addrs |= RTA_NETMASK;
+  }
+
+  nb = cp - (char *)&rtmes;
+  rtmes.m_rtm.rtm_msglen = nb;
+  wb = ID0write(s, &rtmes, nb);
+  if (wb < 0) {
+    log_Printf(LogTCPIP, "rt_Set failure:\n");
+    log_Printf(LogTCPIP, "rt_Set:  Cmd = %s\n", cmdstr);
+    log_Printf(LogTCPIP, "rt_Set:  Dst = %s\n", ncprange_ntoa(dst));
+    if (gw != NULL)
+      log_Printf(LogTCPIP, "rt_Set:  Gateway = %s\n", ncpaddr_ntoa(gw));
+failed:
+    if (cmd == RTM_ADD && (rtmes.m_rtm.rtm_errno == EEXIST ||
+                           (rtmes.m_rtm.rtm_errno == 0 && errno == EEXIST))) {
+      if (!bang) {
+        log_Printf(LogWARN, "Add route failed: %s already exists\n",
+		   ncprange_ntoa(dst));
+        result = 0;	/* Don't add to our dynamic list */
+      } else {
+        rtmes.m_rtm.rtm_type = cmd = RTM_CHANGE;
+        if ((wb = ID0write(s, &rtmes, nb)) < 0)
+          goto failed;
+      }
+    } else if (cmd == RTM_DELETE &&
+             (rtmes.m_rtm.rtm_errno == ESRCH ||
+              (rtmes.m_rtm.rtm_errno == 0 && errno == ESRCH))) {
+      if (!bang)
+        log_Printf(LogWARN, "Del route failed: %s: Non-existent\n",
+                  ncprange_ntoa(dst));
+    } else if (rtmes.m_rtm.rtm_errno == 0) {
+      if (!quiet || errno != ENETUNREACH)
+        log_Printf(LogWARN, "%s route failed: %s: errno: %s\n", cmdstr,
+                   ncprange_ntoa(dst), strerror(errno));
+    } else
+      log_Printf(LogWARN, "%s route failed: %s: %s\n",
+		 cmdstr, ncprange_ntoa(dst), strerror(rtmes.m_rtm.rtm_errno));
+  }
+
+  if (log_IsKept(LogDEBUG)) {
+    char gwstr[40];
+
+    if (gw)
+      snprintf(gwstr, sizeof gwstr, "%s", ncpaddr_ntoa(gw));
+    else
+      snprintf(gwstr, sizeof gwstr, "<none>");
+    log_Printf(LogDEBUG, "wrote %d: cmd = %s, dst = %s, gateway = %s\n",
+               wb, cmdstr, ncprange_ntoa(dst), gwstr);
+  }
+  close(s);
+
+  return result;
+}
+
+void
+rt_Update(struct bundle *bundle, const struct sockaddr *dst,
+          const struct sockaddr *gw, const struct sockaddr *mask)
+{
+  struct ncprange ncpdst;
+  struct rtmsg rtmes;
+  char *p;
+  int s, wb;
+
+  s = ID0socket(PF_ROUTE, SOCK_RAW, 0);
+  if (s < 0) {
+    log_Printf(LogERROR, "rt_Update: socket(): %s\n", strerror(errno));
+    return;
+  }
+
+  memset(&rtmes, '\0', sizeof rtmes);
+  rtmes.m_rtm.rtm_version = RTM_VERSION;
+  rtmes.m_rtm.rtm_type = RTM_CHANGE;
+  rtmes.m_rtm.rtm_addrs = 0;
+  rtmes.m_rtm.rtm_seq = ++bundle->routing_seq;
+  rtmes.m_rtm.rtm_pid = getpid();
+  rtmes.m_rtm.rtm_flags = RTF_UP | RTF_STATIC;
+
+  if (bundle->ncp.cfg.sendpipe > 0) {
+    rtmes.m_rtm.rtm_rmx.rmx_sendpipe = bundle->ncp.cfg.sendpipe;
+    rtmes.m_rtm.rtm_inits |= RTV_SPIPE;
+  }
+
+  if (bundle->ncp.cfg.recvpipe > 0) {
+    rtmes.m_rtm.rtm_rmx.rmx_recvpipe = bundle->ncp.cfg.recvpipe;
+    rtmes.m_rtm.rtm_inits |= RTV_RPIPE;
+  }
+
+  rtmes.m_rtm.rtm_rmx.rmx_mtu = bundle->iface->mtu;
+  rtmes.m_rtm.rtm_inits |= RTV_MTU;
+  p = rtmes.m_space;
+
+  if (dst) {
+    rtmes.m_rtm.rtm_addrs |= RTA_DST;
+    p += memcpy_roundup(p, dst, dst->sa_len);
+  }
+
+  rtmes.m_rtm.rtm_addrs |= RTA_GATEWAY;
+  p += memcpy_roundup(p, gw, gw->sa_len);
+  if (mask) {
+    rtmes.m_rtm.rtm_addrs |= RTA_NETMASK;
+    p += memcpy_roundup(p, mask, mask->sa_len);
+  }
+
+  rtmes.m_rtm.rtm_msglen = p - (char *)&rtmes;
+
+  wb = ID0write(s, &rtmes, rtmes.m_rtm.rtm_msglen);
+  if (wb < 0) {
+    ncprange_setsa(&ncpdst, dst, mask);
+
+    log_Printf(LogTCPIP, "rt_Update failure:\n");
+    log_Printf(LogTCPIP, "rt_Update:  Dst = %s\n", ncprange_ntoa(&ncpdst));
+
+    if (rtmes.m_rtm.rtm_errno == 0)
+      log_Printf(LogWARN, "%s: Change route failed: errno: %s\n",
+                 ncprange_ntoa(&ncpdst), strerror(errno));
+    else
+      log_Printf(LogWARN, "%s: Change route failed: %s\n",
+		 ncprange_ntoa(&ncpdst), strerror(rtmes.m_rtm.rtm_errno));
+  }
+  close(s);
+}
diff --git a/src/route.h b/src/route.h
new file mode 100644
index 0000000..d52e928
--- /dev/null
+++ b/src/route.h
@@ -0,0 +1,73 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/route.h,v 1.19.38.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct bundle;
+struct cmdargs;
+struct rt_msghdr;
+struct sockaddr;
+
+#define ROUTE_STATIC		0x0000
+#define ROUTE_DSTMYADDR		0x0001
+#define ROUTE_DSTMYADDR6	0x0002
+#define ROUTE_DSTHISADDR	0x0004
+#define ROUTE_DSTHISADDR6	0x0008
+#define ROUTE_DSTDNS0		0x0010
+#define ROUTE_DSTDNS1		0x0020
+#define ROUTE_DSTANY		0x0040
+#define ROUTE_GWHISADDR		0x0080	/* May be ORd with DST_* */
+#define ROUTE_GWHISADDR6	0x0100	/* May be ORd with DST_* */
+
+struct sticky_route {
+  int type;				/* ROUTE_* value (not _STATIC) */
+  struct sticky_route *next;		/* next in list */
+
+  struct ncprange dst;
+  struct ncpaddr gw;
+};
+
+extern int GetIfIndex(char *);
+extern int route_Show(struct cmdargs const *);
+extern void route_IfDelete(struct bundle *, int);
+extern void route_UpdateMTU(struct bundle *);
+extern const char *Index2Nam(int);
+extern void route_Change(struct bundle *, struct sticky_route *,
+                         const struct ncpaddr *, const struct ncpaddr *);
+extern void route_Add(struct sticky_route **, int, const struct ncprange *,
+                      const struct ncpaddr *);
+extern void route_Delete(struct sticky_route **, int, const struct ncprange *);
+extern void route_DeleteAll(struct sticky_route **);
+extern void route_Clean(struct bundle *, struct sticky_route *);
+extern void route_ShowSticky(struct prompt *, struct sticky_route *,
+                             const char *, int);
+extern void route_ParseHdr(struct rt_msghdr *, struct sockaddr *[RTAX_MAX]);
+extern int rt_Set(struct bundle *, int, const struct ncprange *,
+                  const struct ncpaddr *, int, int);
+extern void rt_Update(struct bundle *, const struct sockaddr *,
+                      const struct sockaddr *, const struct sockaddr *);
diff --git a/src/server.c b/src/server.c
new file mode 100644
index 0000000..d713abc
--- /dev/null
+++ b/src/server.c
@@ -0,0 +1,422 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/server.c,v 1.44.14.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "descriptor.h"
+#include "server.h"
+#include "prompt.h"
+#include "ncpaddr.h"
+#include "probe.h"
+
+static int
+server_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n)
+{
+  struct server *s = descriptor2server(d);
+  struct prompt *p;
+  int sets;
+
+  sets = 0;
+  if (r && s->fd >= 0) {
+    if (*n < s->fd + 1)
+      *n = s->fd + 1;
+    FD_SET(s->fd, r);
+    log_Printf(LogTIMER, "server: fdset(r) %d\n", s->fd);
+    sets++;
+  }
+
+  for (p = log_PromptList(); p; p = p->next)
+    sets += descriptor_UpdateSet(&p->desc, r, w, e, n);
+
+  return sets;
+}
+
+static int
+server_IsSet(struct fdescriptor *d, const fd_set *fdset)
+{
+  struct server *s = descriptor2server(d);
+  struct prompt *p;
+
+  if (s->fd >= 0 && FD_ISSET(s->fd, fdset))
+    return 1;
+
+  for (p = log_PromptList(); p; p = p->next)
+    if (descriptor_IsSet(&p->desc, fdset))
+      return 1;
+
+  return 0;
+}
+
+static void
+server_Read(struct fdescriptor *d, struct bundle *bundle, const fd_set *fdset)
+{
+  struct server *s = descriptor2server(d);
+  struct sockaddr_storage ss;
+  struct sockaddr *sa = (struct sockaddr *)&ss;
+  struct sockaddr_in *sin = (struct sockaddr_in *)&ss;
+#ifndef NOINET6
+  struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss;
+#endif
+  int ssize = sizeof ss, wfd;
+  struct prompt *p;
+  struct ncpaddr addr;
+
+  if (s->fd >= 0 && FD_ISSET(s->fd, fdset)) {
+    wfd = accept(s->fd, sa, &ssize);
+    if (wfd < 0)
+      log_Printf(LogERROR, "server_Read: accept(): %s\n", strerror(errno));
+    else if (sa->sa_len == 0) {
+      close(wfd);
+      wfd = -1;
+    }
+  } else
+    wfd = -1;
+
+  if (wfd >= 0)
+    switch (sa->sa_family) {
+      case AF_LOCAL:
+        log_Printf(LogPHASE, "Connected to local client.\n");
+        break;
+
+      case AF_INET:
+        ncpaddr_setsa(&addr, sa);
+        if (ntohs(sin->sin_port) < 1024) {
+          log_Printf(LogALERT, "Rejected client connection from %s:%u"
+                    "(invalid port number) !\n",
+                    ncpaddr_ntoa(&addr), ntohs(sin->sin_port));
+          close(wfd);
+          wfd = -1;
+          break;
+        }
+        log_Printf(LogPHASE, "Connected to client from %s:%u\n",
+                  ncpaddr_ntoa(&addr), ntohs(sin->sin_port));
+        break;
+
+#ifndef NOINET6
+      case AF_INET6:
+        ncpaddr_setsa(&addr, sa);
+        if (ntohs(sin6->sin6_port) < 1024) {
+          log_Printf(LogALERT, "Rejected client connection from %s:%u"
+                    "(invalid port number) !\n",
+                    ncpaddr_ntoa(&addr), ntohs(sin6->sin6_port));
+          close(wfd);
+          wfd = -1;
+          break;
+        }
+        log_Printf(LogPHASE, "Connected to client from %s:%u\n",
+                  ncpaddr_ntoa(&addr), ntohs(sin6->sin6_port));
+        break;
+#endif
+
+      default:
+        write(wfd, "Unrecognised access !\n", 22);
+        close(wfd);
+        wfd = -1;
+        break;
+    }
+
+  if (wfd >= 0) {
+    if ((p = prompt_Create(s, bundle, wfd)) == NULL) {
+      write(wfd, "Connection refused.\n", 20);
+      close(wfd);
+    } else {
+      switch (sa->sa_family) {
+        case AF_LOCAL:
+          p->src.type = "local";
+          strncpy(p->src.from, s->cfg.sockname, sizeof p->src.from - 1);
+          p->src.from[sizeof p->src.from - 1] = '\0';
+          break;
+        case AF_INET:
+          p->src.type = "ip";
+          snprintf(p->src.from, sizeof p->src.from, "%s:%u",
+                   ncpaddr_ntoa(&addr), ntohs(sin->sin_port));
+          break;
+#ifndef NOINET6
+        case AF_INET6:
+          p->src.type = "ip6";
+          snprintf(p->src.from, sizeof p->src.from, "%s:%u",
+                   ncpaddr_ntoa(&addr), ntohs(sin6->sin6_port));
+          break;
+#endif
+      }
+      prompt_TtyCommandMode(p);
+      prompt_Required(p);
+    }
+  }
+
+  log_PromptListChanged = 0;
+  for (p = log_PromptList(); p; p = p->next)
+    if (descriptor_IsSet(&p->desc, fdset)) {
+      descriptor_Read(&p->desc, bundle, fdset);
+      if (log_PromptListChanged)
+        break;
+    }
+}
+
+static int
+server_Write(struct fdescriptor *d __unused, struct bundle *bundle __unused,
+	     const fd_set *fdset __unused)
+{
+  /* We never want to write here ! */
+  log_Printf(LogALERT, "server_Write: Internal error: Bad call !\n");
+  return 0;
+}
+
+struct server server = {
+  {
+    SERVER_DESCRIPTOR,
+    server_UpdateSet,
+    server_IsSet,
+    server_Read,
+    server_Write
+  },
+  -1,
+  { "", "", 0, 0 }
+};
+
+enum server_stat
+server_Reopen(struct bundle *bundle)
+{
+  char name[sizeof server.cfg.sockname];
+  struct stat st;
+  u_short port;
+  mode_t mask;
+  enum server_stat ret;
+
+  if (server.cfg.sockname[0] != '\0') {
+    strcpy(name, server.cfg.sockname);
+    mask = server.cfg.mask;
+    server_Close(bundle);
+    if (server.cfg.sockname[0] != '\0' && stat(server.cfg.sockname, &st) == 0)
+      if (!(st.st_mode & S_IFSOCK) || unlink(server.cfg.sockname) != 0)
+        return SERVER_FAILED;
+    ret = server_LocalOpen(bundle, name, mask);
+  } else if (server.cfg.port != 0) {
+    port = server.cfg.port;
+    server_Close(bundle);
+    ret = server_TcpOpen(bundle, port);
+  } else
+    ret = SERVER_UNSET;
+
+  return ret;
+}
+
+enum server_stat
+server_LocalOpen(struct bundle *bundle, const char *name, mode_t mask)
+{
+  struct sockaddr_un ifsun;
+  mode_t oldmask;
+  int s;
+
+  oldmask = (mode_t)-1;		/* Silence compiler */
+
+  if (server.cfg.sockname && !strcmp(server.cfg.sockname, name))
+    server_Close(bundle);
+
+  memset(&ifsun, '\0', sizeof ifsun);
+  ifsun.sun_len = strlen(name);
+  if (ifsun.sun_len > sizeof ifsun.sun_path - 1) {
+    log_Printf(LogERROR, "Local: %s: Path too long\n", name);
+    return SERVER_INVALID;
+  }
+  ifsun.sun_family = AF_LOCAL;
+  strcpy(ifsun.sun_path, name);
+
+  s = socket(PF_LOCAL, SOCK_STREAM, 0);
+  if (s < 0) {
+    log_Printf(LogERROR, "Local: socket: %s\n", strerror(errno));
+    goto failed;
+  }
+  setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &s, sizeof s);
+  if (mask != (mode_t)-1)
+    oldmask = umask(mask);
+  if (bind(s, (struct sockaddr *)&ifsun, sizeof ifsun) < 0) {
+    if (mask != (mode_t)-1)
+      umask(oldmask);
+    log_Printf(LogWARN, "Local: bind: %s\n", strerror(errno));
+    close(s);
+    goto failed;
+  }
+  if (mask != (mode_t)-1)
+    umask(oldmask);
+  if (listen(s, 5) != 0) {
+    log_Printf(LogERROR, "Local: Unable to listen to socket -"
+               " BUNDLE overload?\n");
+    close(s);
+    unlink(name);
+    goto failed;
+  }
+  server_Close(bundle);
+  server.fd = s;
+  server.cfg.port = 0;
+  strncpy(server.cfg.sockname, ifsun.sun_path, sizeof server.cfg.sockname - 1);
+  server.cfg.sockname[sizeof server.cfg.sockname - 1] = '\0';
+  server.cfg.mask = mask;
+  log_Printf(LogPHASE, "Listening at local socket %s.\n", name);
+
+  return SERVER_OK;
+
+failed:
+  if (server.fd == -1) {
+    server.fd = -1;
+    server.cfg.port = 0;
+    strncpy(server.cfg.sockname, ifsun.sun_path,
+            sizeof server.cfg.sockname - 1);
+    server.cfg.sockname[sizeof server.cfg.sockname - 1] = '\0';
+    server.cfg.mask = mask;
+  }
+  return SERVER_FAILED;
+}
+
+enum server_stat
+server_TcpOpen(struct bundle *bundle, u_short port)
+{
+  struct sockaddr_storage ss;
+  struct sockaddr_in *sin = (struct sockaddr_in *)&ss;
+#ifndef NOINET6
+  struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss;
+#endif
+  int s, sz;
+
+  if (server.cfg.port == port)
+    server_Close(bundle);
+
+  if (port == 0)
+    return SERVER_INVALID;
+
+  memset(&ss, '\0', sizeof ss);
+#ifndef NOINET6
+  if (probe.ipv6_available) {
+    sin6->sin6_family = AF_INET6;
+    sin6->sin6_port = htons(port);
+    sin6->sin6_len = (u_int8_t)sizeof ss;
+    sz = sizeof *sin6;
+    s = socket(PF_INET6, SOCK_STREAM, 0);
+  } else
+#endif
+  {
+    sin->sin_family = AF_INET;
+    sin->sin_port = htons(port);
+    sin->sin_len = (u_int8_t)sizeof ss;
+    sin->sin_addr.s_addr = INADDR_ANY;
+    sz = sizeof *sin;
+    s = socket(PF_INET, SOCK_STREAM, 0);
+  }
+
+  if (s < 0) {
+    log_Printf(LogERROR, "Tcp: socket: %s\n", strerror(errno));
+    goto failed;
+  }
+
+#ifndef NOINET6
+  if (probe.ipv6_available) {
+    int off = 0;
+    setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&off, sizeof(off));
+  }
+#endif
+
+  setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &s, sizeof s);
+  if (bind(s, (struct sockaddr *)&ss, sz) < 0) {
+    log_Printf(LogWARN, "Tcp: bind: %s\n", strerror(errno));
+    close(s);
+    goto failed;
+  }
+  if (listen(s, 5) != 0) {
+    log_Printf(LogERROR, "Tcp: Unable to listen to socket: %s\n",
+               strerror(errno));
+    close(s);
+    goto failed;
+  }
+  server_Close(bundle);
+  server.fd = s;
+  server.cfg.port = port;
+  *server.cfg.sockname = '\0';
+  server.cfg.mask = 0;
+  log_Printf(LogPHASE, "Listening at port %d.\n", port);
+  return SERVER_OK;
+
+failed:
+  if (server.fd == -1) {
+    server.fd = -1;
+    server.cfg.port = port;
+    *server.cfg.sockname = '\0';
+    server.cfg.mask = 0;
+  }
+  return SERVER_FAILED;
+}
+
+int
+server_Close(struct bundle *bundle __unused)
+{
+  if (server.fd >= 0) {
+    if (*server.cfg.sockname != '\0') {
+      struct sockaddr_un un;
+      int sz = sizeof un;
+
+      if (getsockname(server.fd, (struct sockaddr *)&un, &sz) == 0 &&
+          un.sun_family == AF_LOCAL && sz == sizeof un)
+        unlink(un.sun_path);
+    }
+    close(server.fd);
+    server.fd = -1;
+    /* Drop associated prompts */
+    log_DestroyPrompts(&server);
+
+    return 1;
+  }
+
+  return 0;
+}
+
+int
+server_Clear(struct bundle *bundle)
+{
+  int ret;
+
+  ret = server_Close(bundle);
+
+  server.fd = -1;
+  server.cfg.port = 0;
+  *server.cfg.sockname = '\0';
+  server.cfg.mask = 0;
+
+  return ret;
+}
diff --git a/src/server.h b/src/server.h
new file mode 100644
index 0000000..d8e0f4d
--- /dev/null
+++ b/src/server.h
@@ -0,0 +1,61 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/server.h,v 1.9.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct bundle;
+
+struct server {
+  struct fdescriptor desc;
+  int fd;
+
+  struct {
+    char passwd[50];
+
+    char sockname[PATH_MAX];		/* Points to local socket path */
+    mode_t mask;
+
+    u_short port;			/* tcp socket */
+  } cfg;
+};
+
+enum server_stat {
+  SERVER_OK,				/* Diagnostic socket available */
+  SERVER_INVALID,			/* Bad args, can't be set up */
+  SERVER_FAILED,			/* Failed - lack of resources */
+  SERVER_UNSET				/* Not already set up */
+};
+
+#define descriptor2server(d) \
+  ((d)->type == SERVER_DESCRIPTOR ? (struct server *)(d) : NULL)
+
+extern struct server server;
+
+extern enum server_stat server_LocalOpen(struct bundle *, const char *, mode_t);
+extern enum server_stat server_TcpOpen(struct bundle *, u_short);
+extern enum server_stat server_Reopen(struct bundle *);
+extern int server_Close(struct bundle *);
+extern int server_Clear(struct bundle *);
diff --git a/src/sig.c b/src/sig.c
new file mode 100644
index 0000000..6707dc7
--- /dev/null
+++ b/src/sig.c
@@ -0,0 +1,119 @@
+/*-
+ * Copyright (c) 1997 - 1999, 2001 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/sig.c,v 1.18.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+
+#include <signal.h>
+
+#include "log.h"
+#include "sig.h"
+
+static int caused[NSIG];	/* An array of pending signals */
+static int necessary;		/* Anything set ? */
+static sig_type handler[NSIG];	/* all start at SIG_DFL */
+
+
+/*
+ * Record a signal in the "caused" array
+ *
+ * This function is the only thing actually called in signal context.  It
+ * records that a signal has been caused and that sig_Handle() should be
+ * called (in non-signal context) as soon as possible to process that
+ * signal.
+ */
+static void
+signal_recorder(int sig)
+{
+  caused[sig - 1]++;
+  necessary = 1;
+}
+
+
+/*
+ * Set up signal_recorder to handle the given sig and record ``fn'' as
+ * the function to ultimately call in sig_Handle().  ``fn'' will not be
+ * called in signal context (as sig_Handle() is not called in signal
+ * context).
+ */
+sig_type
+sig_signal(int sig, sig_type fn)
+{
+  sig_type Result;
+
+  if (sig <= 0 || sig > NSIG) {
+    /* Oops - we must be a bit out of date (too many sigs ?) */
+    log_Printf(LogALERT, "Eeek! %s:%d: I must be out of date!\n",
+	      __FILE__, __LINE__);
+    return signal(sig, fn);
+  }
+  Result = handler[sig - 1];
+  if (fn == SIG_DFL || fn == SIG_IGN) {
+    signal(sig, fn);
+    handler[sig - 1] = (sig_type) 0;
+  } else {
+    handler[sig - 1] = fn;
+    signal(sig, signal_recorder);
+  }
+  caused[sig - 1] = 0;
+  return Result;
+}
+
+
+/*
+ * Call the handlers for any pending signals
+ *
+ * This function is called from a non-signal context - in fact, it's
+ * called every time select() in DoLoop() returns - just in case
+ * select() returned due to a signal being recorded by signal_recorder().
+ */
+int
+sig_Handle()
+{
+  int sig;
+  int got;
+  int result;
+
+  result = 0;
+  if (necessary) {
+    /* We've *probably* got something in `caused' set */
+    necessary = 0;
+    /* `necessary' might go back to 1 while we're in here.... */
+    do {
+      got = 0;
+      for (sig = 0; sig < NSIG; sig++)
+        if (caused[sig]) {
+	  caused[sig]--;
+	  got++;
+	  result++;
+	  (*handler[sig])(sig + 1);
+        }
+    } while (got);
+  }
+
+  return result;
+}
diff --git a/src/sig.h b/src/sig.h
new file mode 100644
index 0000000..2dbf8ab
--- /dev/null
+++ b/src/sig.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/sig.h,v 1.14.62.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+typedef void (*sig_type)(int);
+
+/* Call this instead of signal() */
+extern sig_type sig_signal(int, sig_type);
+
+/* Call this when you want things to *actually* happen */
+extern int sig_Handle(void);
diff --git a/src/slcompress.c b/src/slcompress.c
new file mode 100644
index 0000000..ef421c0
--- /dev/null
+++ b/src/slcompress.c
@@ -0,0 +1,605 @@
+/*-
+ * Copyright (c) 1989, 1993, 1994
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)slcompress.c	8.2 (Berkeley) 4/16/94
+ */
+
+/*
+ * Routines to compress and uncompess tcp packets (for transmission
+ * over low speed serial lines.
+ *
+ * Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989:
+ *	- Initial distribution.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/slcompress.c,v 1.39.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "command.h"
+#include "mbuf.h"
+#include "log.h"
+#include "slcompress.h"
+#include "descriptor.h"
+#include "prompt.h"
+#include "timer.h"
+#include "fsm.h"
+#include "throughput.h"
+#include "iplist.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+void
+sl_compress_init(struct slcompress *comp, int max_state)
+{
+  register u_int i;
+  register struct cstate *tstate = comp->tstate;
+
+  memset(comp, '\0', sizeof *comp);
+  for (i = max_state; i > 0; --i) {
+    tstate[i].cs_id = i;
+    tstate[i].cs_next = &tstate[i - 1];
+  }
+  tstate[0].cs_next = &tstate[max_state];
+  tstate[0].cs_id = 0;
+  comp->last_cs = &tstate[0];
+  comp->last_recv = 255;
+  comp->last_xmit = 255;
+  comp->flags = SLF_TOSS;
+}
+
+
+/* ENCODE encodes a number that is known to be non-zero.  ENCODEZ
+ * checks for zero (since zero has to be encoded in the 32-bit, 3 byte
+ * form).
+ */
+#define ENCODE(n) { \
+	if ((u_short)(n) >= 256) { \
+		*cp++ = 0; \
+		cp[1] = (n); \
+		cp[0] = (n) >> 8; \
+		cp += 2; \
+	} else { \
+		*cp++ = (n); \
+	} \
+}
+#define ENCODEZ(n) { \
+	if ((u_short)(n) >= 256 || (u_short)(n) == 0) { \
+		*cp++ = 0; \
+		cp[1] = (n); \
+		cp[0] = (n) >> 8; \
+		cp += 2; \
+	} else { \
+		*cp++ = (n); \
+	} \
+}
+
+#define DECODEL(f) { \
+	if (*cp == 0) {\
+		(f) = htonl(ntohl(f) + ((cp[1] << 8) | cp[2])); \
+		cp += 3; \
+	} else { \
+		(f) = htonl(ntohl(f) + (u_int32_t)*cp++); \
+	} \
+}
+
+#define DECODES(f) { \
+	if (*cp == 0) {\
+		(f) = htons(ntohs(f) + ((cp[1] << 8) | cp[2])); \
+		cp += 3; \
+	} else { \
+		(f) = htons(ntohs(f) + (u_int32_t)*cp++); \
+	} \
+}
+
+#define DECODEU(f) { \
+	if (*cp == 0) {\
+		(f) = htons((cp[1] << 8) | cp[2]); \
+		cp += 3; \
+	} else { \
+		(f) = htons((u_int32_t)*cp++); \
+	} \
+}
+
+
+u_char
+sl_compress_tcp(struct mbuf * m,
+		struct ip * ip,
+		struct slcompress *comp,
+                struct slstat *slstat,
+		int compress_cid)
+{
+  register struct cstate *cs = comp->last_cs->cs_next;
+  register u_int hlen = ip->ip_hl;
+  register struct tcphdr *oth;
+  register struct tcphdr *th;
+  register u_int deltaS, deltaA;
+  register u_int changes = 0;
+  u_char new_seq[16];
+  register u_char *cp = new_seq;
+
+  /*
+   * Bail if this is an IP fragment or if the TCP packet isn't `compressible'
+   * (i.e., ACK isn't set or some other control bit is set).  (We assume that
+   * the caller has already made sure the packet is IP proto TCP).
+   */
+  if ((ip->ip_off & htons(0x3fff)) || m->m_len < 40) {
+    log_Printf(LogDEBUG, "??? 1 ip_off = %x, m_len = %lu\n",
+	      ip->ip_off, (unsigned long)m->m_len);
+    log_DumpBp(LogDEBUG, "", m);
+    return (TYPE_IP);
+  }
+  th = (struct tcphdr *) & ((int *) ip)[hlen];
+  if ((th->th_flags & (TH_SYN | TH_FIN | TH_RST | TH_ACK)) != TH_ACK) {
+    log_Printf(LogDEBUG, "??? 2 th_flags = %x\n", th->th_flags);
+    log_DumpBp(LogDEBUG, "", m);
+    return (TYPE_IP);
+  }
+
+  /*
+   * Packet is compressible -- we're going to send either a COMPRESSED_TCP or
+   * UNCOMPRESSED_TCP packet.  Either way we need to locate (or create) the
+   * connection state.  Special case the most recently used connection since
+   * it's most likely to be used again & we don't have to do any reordering
+   * if it's used.
+   */
+  slstat->sls_packets++;
+  if (ip->ip_src.s_addr != cs->cs_ip.ip_src.s_addr ||
+      ip->ip_dst.s_addr != cs->cs_ip.ip_dst.s_addr ||
+      *(int *) th != ((int *) &cs->cs_ip)[cs->cs_ip.ip_hl]) {
+
+    /*
+     * Wasn't the first -- search for it.
+     *
+     * States are kept in a circularly linked list with last_cs pointing to the
+     * end of the list.  The list is kept in lru order by moving a state to
+     * the head of the list whenever it is referenced.  Since the list is
+     * short and, empirically, the connection we want is almost always near
+     * the front, we locate states via linear search.  If we don't find a
+     * state for the datagram, the oldest state is (re-)used.
+     */
+    register struct cstate *lcs;
+    register struct cstate *lastcs = comp->last_cs;
+
+    do {
+      lcs = cs;
+      cs = cs->cs_next;
+      slstat->sls_searches++;
+      if (ip->ip_src.s_addr == cs->cs_ip.ip_src.s_addr
+	  && ip->ip_dst.s_addr == cs->cs_ip.ip_dst.s_addr
+	  && *(int *) th == ((int *) &cs->cs_ip)[cs->cs_ip.ip_hl])
+	goto found;
+    } while (cs != lastcs);
+
+    /*
+     * Didn't find it -- re-use oldest cstate.  Send an uncompressed packet
+     * that tells the other side what connection number we're using for this
+     * conversation. Note that since the state list is circular, the oldest
+     * state points to the newest and we only need to set last_cs to update
+     * the lru linkage.
+     */
+    slstat->sls_misses++;
+      comp->last_cs = lcs;
+#define	THOFFSET(th)	(th->th_off)
+    hlen += th->th_off;
+    hlen <<= 2;
+    if (hlen > m->m_len)
+      return (TYPE_IP);
+    goto uncompressed;
+
+found:
+
+    /*
+     * Found it -- move to the front on the connection list.
+     */
+    if (cs == lastcs)
+      comp->last_cs = lcs;
+    else {
+      lcs->cs_next = cs->cs_next;
+      cs->cs_next = lastcs->cs_next;
+      lastcs->cs_next = cs;
+    }
+  }
+
+  /*
+   * Make sure that only what we expect to change changed. The first line of
+   * the `if' checks the IP protocol version, header length & type of
+   * service.  The 2nd line checks the "Don't fragment" bit. The 3rd line
+   * checks the time-to-live and protocol (the protocol check is unnecessary
+   * but costless).  The 4th line checks the TCP header length.  The 5th line
+   * checks IP options, if any.  The 6th line checks TCP options, if any.  If
+   * any of these things are different between the previous & current
+   * datagram, we send the current datagram `uncompressed'.
+   */
+  oth = (struct tcphdr *) & ((int *) &cs->cs_ip)[hlen];
+  deltaS = hlen;
+  hlen += th->th_off;
+  hlen <<= 2;
+  if (hlen > m->m_len)
+    return (TYPE_IP);
+
+  if (((u_short *) ip)[0] != ((u_short *) & cs->cs_ip)[0] ||
+      ((u_short *) ip)[3] != ((u_short *) & cs->cs_ip)[3] ||
+      ((u_short *) ip)[4] != ((u_short *) & cs->cs_ip)[4] ||
+      THOFFSET(th) != THOFFSET(oth) ||
+      (deltaS > 5 &&
+       memcmp(ip + 1, &cs->cs_ip + 1, (deltaS - 5) << 2)) ||
+      (THOFFSET(th) > 5 &&
+       memcmp(th + 1, oth + 1, (THOFFSET(th) - 5) << 2))) {
+    goto uncompressed;
+  }
+
+  /*
+   * Figure out which of the changing fields changed.  The receiver expects
+   * changes in the order: urgent, window, ack, seq (the order minimizes the
+   * number of temporaries needed in this section of code).
+   */
+  if (th->th_flags & TH_URG) {
+    deltaS = ntohs(th->th_urp);
+    ENCODEZ(deltaS);
+    changes |= NEW_U;
+  } else if (th->th_urp != oth->th_urp) {
+
+    /*
+     * argh! URG not set but urp changed -- a sensible implementation should
+     * never do this but RFC793 doesn't prohibit the change so we have to
+     * deal with it.
+     */
+    goto uncompressed;
+  }
+  deltaS = (u_short) (ntohs(th->th_win) - ntohs(oth->th_win));
+  if (deltaS) {
+    ENCODE(deltaS);
+    changes |= NEW_W;
+  }
+  deltaA = ntohl(th->th_ack) - ntohl(oth->th_ack);
+  if (deltaA) {
+    if (deltaA > 0xffff) {
+      goto uncompressed;
+    }
+    ENCODE(deltaA);
+    changes |= NEW_A;
+  }
+  deltaS = ntohl(th->th_seq) - ntohl(oth->th_seq);
+  if (deltaS) {
+    if (deltaS > 0xffff) {
+      goto uncompressed;
+    }
+    ENCODE(deltaS);
+    changes |= NEW_S;
+  }
+  switch (changes) {
+
+  case 0:
+
+    /*
+     * Nothing changed. If this packet contains data and the last one didn't,
+     * this is probably a data packet following an ack (normal on an
+     * interactive connection) and we send it compressed.  Otherwise it's
+     * probably a retransmit, retransmitted ack or window probe.  Send it
+     * uncompressed in case the other side missed the compressed version.
+     */
+    if (ip->ip_len != cs->cs_ip.ip_len &&
+	ntohs(cs->cs_ip.ip_len) == hlen)
+      break;
+
+    /* FALLTHROUGH */
+
+  case SPECIAL_I:
+  case SPECIAL_D:
+
+    /*
+     * actual changes match one of our special case encodings -- send packet
+     * uncompressed.
+     */
+    goto uncompressed;
+
+  case NEW_S | NEW_A:
+    if (deltaS == deltaA &&
+	deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
+      /* special case for echoed terminal traffic */
+      changes = SPECIAL_I;
+      cp = new_seq;
+    }
+    break;
+
+  case NEW_S:
+    if (deltaS == ntohs(cs->cs_ip.ip_len) - hlen) {
+      /* special case for data xfer */
+      changes = SPECIAL_D;
+      cp = new_seq;
+    }
+    break;
+  }
+
+  deltaS = ntohs(ip->ip_id) - ntohs(cs->cs_ip.ip_id);
+  if (deltaS != 1) {
+    ENCODEZ(deltaS);
+    changes |= NEW_I;
+  }
+  if (th->th_flags & TH_PUSH)
+    changes |= TCP_PUSH_BIT;
+
+  /*
+   * Grab the cksum before we overwrite it below.  Then update our state with
+   * this packet's header.
+   */
+  deltaA = ntohs(th->th_sum);
+  memcpy(&cs->cs_ip, ip, hlen);
+
+  /*
+   * We want to use the original packet as our compressed packet. (cp -
+   * new_seq) is the number of bytes we need for compressed sequence numbers.
+   * In addition we need one byte for the change mask, one for the connection
+   * id and two for the tcp checksum. So, (cp - new_seq) + 4 bytes of header
+   * are needed.  hlen is how many bytes of the original packet to toss so
+   * subtract the two to get the new packet size.
+   */
+  deltaS = cp - new_seq;
+  cp = (u_char *) ip;
+
+  /*
+   * Since fastq traffic can jump ahead of the background traffic, we don't
+   * know what order packets will go on the line.  In this case, we always
+   * send a "new" connection id so the receiver state stays synchronized.
+   */
+  if (comp->last_xmit == cs->cs_id && compress_cid) {
+    hlen -= deltaS + 3;
+    cp += hlen;
+    *cp++ = changes;
+  } else {
+    comp->last_xmit = cs->cs_id;
+    hlen -= deltaS + 4;
+    cp += hlen;
+    *cp++ = changes | NEW_C;
+    *cp++ = cs->cs_id;
+  }
+  m->m_len -= hlen;
+  m->m_offset += hlen;
+  *cp++ = deltaA >> 8;
+  *cp++ = deltaA;
+  memcpy(cp, new_seq, deltaS);
+  slstat->sls_compressed++;
+  return (TYPE_COMPRESSED_TCP);
+
+  /*
+   * Update connection state cs & send uncompressed packet ('uncompressed'
+   * means a regular ip/tcp packet but with the 'conversation id' we hope to
+   * use on future compressed packets in the protocol field).
+   */
+uncompressed:
+  memcpy(&cs->cs_ip, ip, hlen);
+  ip->ip_p = cs->cs_id;
+  comp->last_xmit = cs->cs_id;
+  return (TYPE_UNCOMPRESSED_TCP);
+}
+
+
+int
+sl_uncompress_tcp(u_char ** bufp, int len, u_int type, struct slcompress *comp,
+                  struct slstat *slstat, int max_state)
+{
+  register u_char *cp;
+  register u_int hlen, changes;
+  register struct tcphdr *th;
+  register struct cstate *cs;
+  register struct ip *ip;
+  u_short *bp;
+
+  switch (type) {
+
+  case TYPE_UNCOMPRESSED_TCP:
+    ip = (struct ip *) * bufp;
+    if (ip->ip_p > max_state)
+      goto bad;
+    cs = &comp->rstate[comp->last_recv = ip->ip_p];
+    comp->flags &= ~SLF_TOSS;
+    ip->ip_p = IPPROTO_TCP;
+
+    /*
+     * Calculate the size of the TCP/IP header and make sure that we don't
+     * overflow the space we have available for it.
+     */
+    hlen = ip->ip_hl << 2;
+    if ((int)(hlen + sizeof(struct tcphdr)) > len)
+      goto bad;
+    th = (struct tcphdr *) & ((char *) ip)[hlen];
+    hlen += THOFFSET(th) << 2;
+    if (hlen > MAX_HDR)
+      goto bad;
+    memcpy(&cs->cs_ip, ip, hlen);
+    cs->cs_hlen = hlen;
+    slstat->sls_uncompressedin++;
+    return (len);
+
+  default:
+    goto bad;
+
+  case TYPE_COMPRESSED_TCP:
+    break;
+  }
+
+  /* We've got a compressed packet. */
+  slstat->sls_compressedin++;
+  cp = *bufp;
+  changes = *cp++;
+  log_Printf(LogDEBUG, "compressed: changes = %02x\n", changes);
+
+  if (changes & NEW_C) {
+    /*
+     * Make sure the state index is in range, then grab the state. If we have
+     * a good state index, clear the 'discard' flag.
+     */
+    if (*cp > max_state || comp->last_recv == 255)
+      goto bad;
+
+    comp->flags &= ~SLF_TOSS;
+    comp->last_recv = *cp++;
+  } else {
+    /*
+     * this packet has an implicit state index.  If we've had a line error
+     * since the last time we got an explicit state index, we have to toss
+     * the packet.
+     */
+    if (comp->flags & SLF_TOSS) {
+      slstat->sls_tossed++;
+      return (0);
+    }
+  }
+  cs = &comp->rstate[comp->last_recv];
+  hlen = cs->cs_ip.ip_hl << 2;
+  th = (struct tcphdr *) & ((u_char *) & cs->cs_ip)[hlen];
+  th->th_sum = htons((*cp << 8) | cp[1]);
+  cp += 2;
+  if (changes & TCP_PUSH_BIT)
+    th->th_flags |= TH_PUSH;
+  else
+    th->th_flags &= ~TH_PUSH;
+
+  switch (changes & SPECIALS_MASK) {
+  case SPECIAL_I:
+    {
+      register u_int i = ntohs(cs->cs_ip.ip_len) - cs->cs_hlen;
+
+      th->th_ack = htonl(ntohl(th->th_ack) + i);
+      th->th_seq = htonl(ntohl(th->th_seq) + i);
+    }
+    break;
+
+  case SPECIAL_D:
+    th->th_seq = htonl(ntohl(th->th_seq) + ntohs(cs->cs_ip.ip_len)
+		       - cs->cs_hlen);
+    break;
+
+  default:
+    if (changes & NEW_U) {
+      th->th_flags |= TH_URG;
+      DECODEU(th->th_urp)
+    } else
+      th->th_flags &= ~TH_URG;
+    if (changes & NEW_W)
+      DECODES(th->th_win)
+	if (changes & NEW_A)
+	DECODEL(th->th_ack)
+	  if (changes & NEW_S) {
+	  log_Printf(LogDEBUG, "NEW_S: %02x, %02x, %02x\n",
+		    *cp, cp[1], cp[2]);
+	  DECODEL(th->th_seq)
+	}
+    break;
+  }
+  if (changes & NEW_I) {
+    DECODES(cs->cs_ip.ip_id)
+  } else
+    cs->cs_ip.ip_id = htons(ntohs(cs->cs_ip.ip_id) + 1);
+
+  log_Printf(LogDEBUG, "Uncompress: id = %04x, seq = %08lx\n",
+	    cs->cs_ip.ip_id, (u_long)ntohl(th->th_seq));
+
+  /*
+   * At this point, cp points to the first byte of data in the packet.
+   * Back up cp by the tcp/ip header length to make room for the
+   * reconstructed header (we assume the packet we were handed has enough
+   * space to prepend 128 bytes of header).  Adjust the length to account
+   * for the new header & fill in the IP total length.
+   */
+  len -= (cp - *bufp);
+  if (len < 0)
+    /*
+     * we must have dropped some characters (crc should detect this but the
+     * old slip framing won't)
+     */
+    goto bad;
+
+  *bufp = cp - cs->cs_hlen;
+  len += cs->cs_hlen;
+  cs->cs_ip.ip_len = htons(len);
+
+  /* recompute the ip header checksum */
+  cs->cs_ip.ip_sum = 0;
+  bp = (u_short *)&cs->cs_ip;
+  for (changes = 0; hlen > 0; hlen -= 2)
+    changes += *bp++;
+  changes = (changes & 0xffff) + (changes >> 16);
+  changes = (changes & 0xffff) + (changes >> 16);
+  cs->cs_ip.ip_sum = ~changes;
+
+  /* And copy the result into our buffer */
+  memcpy(*bufp, &cs->cs_ip, cs->cs_hlen);
+
+  return (len);
+bad:
+  comp->flags |= SLF_TOSS;
+  slstat->sls_errorin++;
+  return (0);
+}
+
+int
+sl_Show(struct cmdargs const *arg)
+{
+  prompt_Printf(arg->prompt, "VJ compression statistics:\n");
+  prompt_Printf(arg->prompt, "  Out:  %d (compress) / %d (total)",
+	        arg->bundle->ncp.ipcp.vj.slstat.sls_compressed,
+                arg->bundle->ncp.ipcp.vj.slstat.sls_packets);
+  prompt_Printf(arg->prompt, "  %d (miss) / %d (search)\n",
+	        arg->bundle->ncp.ipcp.vj.slstat.sls_misses,
+                arg->bundle->ncp.ipcp.vj.slstat.sls_searches);
+  prompt_Printf(arg->prompt, "  In:  %d (compress), %d (uncompress)",
+	        arg->bundle->ncp.ipcp.vj.slstat.sls_compressedin,
+                arg->bundle->ncp.ipcp.vj.slstat.sls_uncompressedin);
+  prompt_Printf(arg->prompt, "  %d (error),  %d (tossed)\n",
+	        arg->bundle->ncp.ipcp.vj.slstat.sls_errorin,
+                arg->bundle->ncp.ipcp.vj.slstat.sls_tossed);
+  return 0;
+}
diff --git a/src/slcompress.h b/src/slcompress.h
new file mode 100644
index 0000000..b7b54ef
--- /dev/null
+++ b/src/slcompress.h
@@ -0,0 +1,161 @@
+/*
+ * Definitions for tcp compression routines.
+ *
+ * Copyright (c) 1989, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989:
+ *	- Initial distribution.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/slcompress.h,v 1.17.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define MIN_VJ_STATES 3
+#define MAX_VJ_STATES 255
+#define DEF_VJ_STATES 16		/* must be > 2 and < 256 */
+#define MAX_HDR 128
+
+/*
+ * Compressed packet format:
+ *
+ * The first octet contains the packet type (top 3 bits), TCP
+ * 'push' bit, and flags that indicate which of the 4 TCP sequence
+ * numbers have changed (bottom 5 bits).  The next octet is a
+ * conversation number that associates a saved IP/TCP header with
+ * the compressed packet.  The next two octets are the TCP checksum
+ * from the original datagram.  The next 0 to 15 octets are
+ * sequence number changes, one change per bit set in the header
+ * (there may be no changes and there are two special cases where
+ * the receiver implicitly knows what changed -- see below).
+ *
+ * There are 5 numbers which can change (they are always inserted
+ * in the following order): TCP urgent pointer, window,
+ * acknowlegement, sequence number and IP ID.  (The urgent pointer
+ * is different from the others in that its value is sent, not the
+ * change in value.)  Since typical use of SLIP links is biased
+ * toward small packets (see comments on MTU/MSS below), changes
+ * use a variable length coding with one octet for numbers in the
+ * range 1 - 255 and 3 octets (0, MSB, LSB) for numbers in the
+ * range 256 - 65535 or 0.  (If the change in sequence number or
+ * ack is more than 65535, an uncompressed packet is sent.)
+ */
+
+/*
+ * Packet types (must not conflict with IP protocol version)
+ *
+ * The top nibble of the first octet is the packet type.  There are
+ * three possible types: IP (not proto TCP or tcp with one of the
+ * control flags set); uncompressed TCP (a normal IP/TCP packet but
+ * with the 8-bit protocol field replaced by an 8-bit connection id --
+ * this type of packet syncs the sender & receiver); and compressed
+ * TCP (described above).
+ *
+ * LSB of 4-bit field is TCP "PUSH" bit (a worthless anachronism) and
+ * is logically part of the 4-bit "changes" field that follows.  Top
+ * three bits are actual packet type.  For backward compatibility
+ * and in the interest of conserving bits, numbers are chosen so the
+ * IP protocol version number (4) which normally appears in this nibble
+ * means "IP packet".
+ */
+
+/* packet types */
+#define TYPE_IP 0x40
+#define TYPE_UNCOMPRESSED_TCP 0x70
+#define TYPE_COMPRESSED_TCP 0x80
+#define TYPE_ERROR 0x00
+
+/* Bits in first octet of compressed packet */
+#define NEW_C	0x40		/* flag bits for what changed in a packet */
+#define NEW_I	0x20
+#define NEW_S	0x08
+#define NEW_A	0x04
+#define NEW_W	0x02
+#define NEW_U	0x01
+
+/* reserved, special-case values of above */
+#define SPECIAL_I (NEW_S|NEW_W|NEW_U)	/* echoed interactive traffic */
+#define SPECIAL_D (NEW_S|NEW_A|NEW_W|NEW_U)	/* unidirectional data */
+#define SPECIALS_MASK (NEW_S|NEW_A|NEW_W|NEW_U)
+
+#define TCP_PUSH_BIT 0x10
+
+/*
+ * "state" data for each active tcp conversation on the wire.  This is
+ * basically a copy of the entire IP/TCP header from the last packet
+ * we saw from the conversation together with a small identifier
+ * the transmit & receive ends of the line use to locate saved header.
+ */
+struct cstate {
+  struct cstate *cs_next;	/* next most recently used cstate (xmit only) */
+  u_short cs_hlen;		/* size of hdr (receive only) */
+  u_char cs_id;			/* connection # associated with this state */
+  u_char cs_filler;
+  union {
+    char csu_hdr[MAX_HDR];
+    struct ip csu_ip;		/* ip/tcp hdr from most recent packet */
+  } slcs_u;
+};
+
+#define cs_ip slcs_u.csu_ip
+#define cs_hdr slcs_u.csu_hdr
+
+/*
+ * all the state data for one serial line (we need one of these
+ * per line).
+ */
+struct slcompress {
+  struct cstate *last_cs;	/* most recently used tstate */
+  u_char last_recv;		/* last rcvd conn. id */
+  u_char last_xmit;		/* last sent conn. id */
+  u_short flags;
+  struct cstate tstate[MAX_VJ_STATES];	/* xmit connection states */
+  struct cstate rstate[MAX_VJ_STATES];	/* receive connection states */
+};
+
+struct slstat {
+  int sls_packets;		/* outbound packets */
+  int sls_compressed;		/* outbound compressed packets */
+  int sls_searches;		/* searches for connection state */
+  int sls_misses;		/* times couldn't find conn. state */
+  int sls_uncompressedin;	/* inbound uncompressed packets */
+  int sls_compressedin;		/* inbound compressed packets */
+  int sls_errorin;		/* inbound unknown type packets */
+  int sls_tossed;		/* inbound packets tossed because of error */
+};
+
+/* flag values */
+#define SLF_TOSS 1		/* tossing rcvd frames because of input err */
+
+struct mbuf;
+struct cmdargs;
+
+extern void sl_compress_init(struct slcompress *, int);
+extern u_char sl_compress_tcp(struct mbuf *, struct ip *, struct slcompress *,
+                              struct slstat *, int);
+extern int sl_uncompress_tcp(u_char **, int, u_int, struct slcompress *,
+                             struct slstat *, int);
+extern int sl_Show(struct cmdargs const *);
diff --git a/src/sync.c b/src/sync.c
new file mode 100644
index 0000000..2e354f0
--- /dev/null
+++ b/src/sync.c
@@ -0,0 +1,84 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/sync.c,v 1.8.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+
+#include <stdio.h>
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "sync.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+
+static struct mbuf *
+sync_LayerPush(struct bundle *bundle __unused, struct link *l __unused,
+	       struct mbuf *bp, int pri __unused, u_short *proto __unused)
+{
+  log_DumpBp(LogSYNC, "Write", bp);
+  m_settype(bp, MB_SYNCOUT);
+  bp->priv = 0;
+  return bp;
+}
+
+static struct mbuf *
+sync_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp,
+               u_short *proto __unused)
+{
+  struct physical *p = link2physical(l);
+  int len;
+
+  if (!p)
+    log_Printf(LogERROR, "Can't Pull a sync packet from a logical link\n");
+  else {
+    log_DumpBp(LogSYNC, "Read", bp);
+
+    /* Either done here or by the HDLC layer */
+    len = m_length(bp);
+    p->hdlc.lqm.ifInOctets += len + 1;		/* plus 1 flag octet! */
+    p->hdlc.lqm.lqr.InGoodOctets += len + 1;	/* plus 1 flag octet! */
+    p->hdlc.lqm.ifInUniPackets++;
+    m_settype(bp, MB_SYNCIN);
+  }
+
+  return bp;
+}
+
+struct layer synclayer = { LAYER_SYNC, "sync", sync_LayerPush, sync_LayerPull };
diff --git a/src/sync.h b/src/sync.h
new file mode 100644
index 0000000..cba7968
--- /dev/null
+++ b/src/sync.h
@@ -0,0 +1,29 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/sync.h,v 1.2.62.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+extern struct layer synclayer;
diff --git a/src/systems.c b/src/systems.c
new file mode 100644
index 0000000..61bef70
--- /dev/null
+++ b/src/systems.c
@@ -0,0 +1,483 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/systems.c,v 1.68.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+
+#include "defs.h"
+#include "command.h"
+#include "log.h"
+#include "id.h"
+#include "systems.h"
+
+#define issep(ch) ((ch) == ' ' || (ch) == '\t')
+
+FILE *
+OpenSecret(const char *file)
+{
+  FILE *fp;
+  char line[100];
+
+  snprintf(line, sizeof line, "%s/%s", PPP_CONFDIR, file);
+  fp = ID0fopen(line, "r");
+  if (fp == NULL)
+    log_Printf(LogWARN, "OpenSecret: Can't open %s.\n", line);
+  return (fp);
+}
+
+void
+CloseSecret(FILE *fp)
+{
+  fclose(fp);
+}
+
+/* Move string from ``from'' to ``to'', interpreting ``~'' and $.... */
+const char *
+InterpretArg(const char *from, char *to)
+{
+  char *ptr, *startto, *endto;
+  struct passwd *pwd;
+  int instring;
+  size_t len;
+  const char *env;
+
+  instring = 0;
+  startto = to;
+  endto = to + LINE_LEN - 1;
+
+  while(issep(*from))
+    from++;
+
+  while (*from != '\0') {
+    switch (*from) {
+      case '"':
+        instring = !instring;
+        *to++ = *from++;
+        break;
+      case '\\':
+        switch (*++from) {
+          case '$':
+          case '~':
+            break;		/* Swallow the escapes */
+
+          default:
+            *to++ = '\\';	/* Pass the escapes on, maybe skipping \# */
+            break;
+        }
+        *to++ = *from++;
+        break;
+      case '$':
+        if (from[1] == '$') {
+          *to = '\0';	/* For an empty var name below */
+          from += 2;
+        } else if (from[1] == '{') {
+          ptr = strchr(from+2, '}');
+          if (ptr) {
+            len = ptr - from - 2;
+            if (endto - to < (int)len )
+              len = endto - to;
+            if (len) {
+              strncpy(to, from+2, len);
+              to[len] = '\0';
+              from = ptr+1;
+            } else {
+              *to++ = *from++;
+              continue;
+            }
+          } else {
+            *to++ = *from++;
+            continue;
+          }
+        } else {
+          ptr = to;
+          for (from++; (isalnum(*from) || *from == '_') && ptr < endto; from++)
+            *ptr++ = *from;
+          *ptr = '\0';
+        }
+        if (*to == '\0')
+          *to++ = '$';
+        else if ((env = getenv(to)) != NULL) {
+          strncpy(to, env, endto - to);
+          *endto = '\0';
+          to += strlen(to);
+        }
+        break;
+
+      case '~':
+        ptr = strchr(++from, '/');
+        len = ptr ? (size_t)(ptr - from) : strlen(from);
+        if (len == 0)
+          pwd = getpwuid(ID0realuid());
+        else {
+          strncpy(to, from, len);
+          to[len] = '\0';
+          pwd = getpwnam(to);
+        }
+        if (pwd == NULL)
+          *to++ = '~';
+        else {
+          strncpy(to, pwd->pw_dir, endto - to);
+          *endto = '\0';
+          to += strlen(to);
+          from += len;
+        }
+        endpwent();
+        break;
+
+      default:
+        *to++ = *from++;
+        break;
+    }
+  }
+
+  while (to > startto) {
+    to--;
+    if (!issep(*to)) {
+      to++;
+      break;
+    }
+  }
+  *to = '\0';
+
+  return from;
+}
+
+#define CTRL_UNKNOWN (0)
+#define CTRL_INCLUDE (1)
+
+static int
+DecodeCtrlCommand(char *line, char *arg)
+{
+  const char *end;
+
+  if (!strncasecmp(line, "include", 7) && issep(line[7])) {
+    end = InterpretArg(line+8, arg);
+    if (*end && *end != '#')
+      log_Printf(LogWARN, "usage: !include filename\n");
+    else
+      return CTRL_INCLUDE;
+  }
+  return CTRL_UNKNOWN;
+}
+
+/*
+ * Initialised in system_IsValid(), set in ReadSystem(),
+ * used by system_IsValid()
+ */
+static int modeok;
+static int userok;
+static int modereq;
+
+int
+AllowUsers(struct cmdargs const *arg)
+{
+  /* arg->bundle may be NULL (see system_IsValid()) ! */
+  int f;
+  struct passwd *pwd;
+
+  if (userok == -1)
+    userok = 0;
+
+  pwd = getpwuid(ID0realuid());
+  if (pwd != NULL)
+    for (f = arg->argn; f < arg->argc; f++)
+      if (!strcmp("*", arg->argv[f]) || !strcmp(pwd->pw_name, arg->argv[f])) {
+        userok = 1;
+        break;
+      }
+  endpwent();
+
+  return 0;
+}
+
+int
+AllowModes(struct cmdargs const *arg)
+{
+  /* arg->bundle may be NULL (see system_IsValid()) ! */
+  int f, mode, allowed;
+
+  allowed = 0;
+  for (f = arg->argn; f < arg->argc; f++) {
+    mode = Nam2mode(arg->argv[f]);
+    if (mode == PHYS_NONE || mode == PHYS_ALL)
+      log_Printf(LogWARN, "allow modes: %s: Invalid mode\n", arg->argv[f]);
+    else
+      allowed |= mode;
+  }
+
+  modeok = modereq & allowed ? 1 : 0;
+  return 0;
+}
+
+static char *
+strip(char *line)
+{
+  int len;
+
+  len = strlen(line);
+  while (len && (line[len-1] == '\n' || line[len-1] == '\r' ||
+                 issep(line[len-1])))
+    line[--len] = '\0';
+
+  while (issep(*line))
+    line++;
+
+  if (*line == '#')
+    *line = '\0';
+
+  return line;
+}
+
+static int
+xgets(char *buf, int buflen, FILE *fp)
+{
+  int len, n;
+
+  n = 0;
+  while (fgets(buf, buflen-1, fp)) {
+    n++;
+    buf[buflen-1] = '\0';
+    len = strlen(buf);
+    while (len && (buf[len-1] == '\n' || buf[len-1] == '\r'))
+      buf[--len] = '\0';
+    if (len && buf[len-1] == '\\') {
+      buf += len - 1;
+      buflen -= len - 1;
+      if (!buflen)        /* No buffer space */
+        break;
+    } else
+      break;
+  }
+  return n;
+}
+
+/* Values for ``how'' in ReadSystem */
+#define SYSTEM_EXISTS	1
+#define SYSTEM_VALIDATE	2
+#define SYSTEM_EXEC	3
+
+static char *
+GetLabel(char *line, const char *filename, int linenum)
+{
+  char *argv[MAXARGS];
+  int argc, len;
+
+  argc = MakeArgs(line, argv, MAXARGS, PARSE_REDUCE);
+
+  if (argc == 2 && !strcmp(argv[1], ":"))
+    return argv[0];
+
+  if (argc != 1 || (len = strlen(argv[0])) < 2 || argv[0][len-1] != ':') {
+      log_Printf(LogWARN, "Bad label in %s (line %d) - missing colon\n",
+                 filename, linenum);
+      return NULL;
+  }
+  argv[0][len-1] = '\0';	/* Lose the ':' */
+
+  return argv[0];
+}
+
+/* Returns -2 for ``file not found'' and -1 for ``label not found'' */
+
+static int
+ReadSystem(struct bundle *bundle, const char *name, const char *file,
+           struct prompt *prompt, struct datalink *cx, int how)
+{
+  FILE *fp;
+  char *cp;
+  int n, len;
+  char line[LINE_LEN];
+  char filename[PATH_MAX];
+  int linenum;
+  int argc;
+  char *argv[MAXARGS];
+  int allowcmd;
+  int indent;
+  char arg[LINE_LEN];
+  struct prompt *op;
+
+  if (*file == '/')
+    snprintf(filename, sizeof filename, "%s", file);
+  else
+    snprintf(filename, sizeof filename, "%s/%s", PPP_CONFDIR, file);
+  fp = ID0fopen(filename, "r");
+  if (fp == NULL) {
+    log_Printf(LogDEBUG, "ReadSystem: Can't open %s.\n", filename);
+    return -2;
+  }
+  log_Printf(LogDEBUG, "ReadSystem: Checking %s (%s).\n", name, filename);
+
+  linenum = 0;
+  while ((n = xgets(line, sizeof line, fp))) {
+    linenum += n;
+    if (issep(*line))
+      continue;
+
+    cp = strip(line);
+
+    switch (*cp) {
+    case '\0':			/* empty/comment */
+      break;
+
+    case '!':
+      switch (DecodeCtrlCommand(cp+1, arg)) {
+      case CTRL_INCLUDE:
+        log_Printf(LogCOMMAND, "%s: Including \"%s\"\n", filename, arg);
+        n = ReadSystem(bundle, name, arg, prompt, cx, how);
+        log_Printf(LogCOMMAND, "%s: Done include of \"%s\"\n", filename, arg);
+        if (!n) {
+          fclose(fp);
+          return 0;	/* got it */
+        }
+        break;
+      default:
+        log_Printf(LogWARN, "%s: %s: Invalid command\n", filename, cp);
+        break;
+      }
+      break;
+
+    default:
+      if ((cp = GetLabel(cp, filename, linenum)) == NULL)
+        continue;
+
+      if (strcmp(cp, name) == 0) {
+        /* We're in business */
+        if (how == SYSTEM_EXISTS) {
+          fclose(fp);
+	  return 0;
+	}
+	while ((n = xgets(line, sizeof line, fp))) {
+          linenum += n;
+          indent = issep(*line);
+          cp = strip(line);
+
+          if (*cp == '\0')			/* empty / comment */
+            continue;
+
+          if (!indent) {			/* start of next section */
+            if (*cp != '!' && how == SYSTEM_EXEC)
+              cp = GetLabel(cp, filename, linenum);
+            break;
+          }
+
+          len = strlen(cp);
+          if ((argc = command_Expand_Interpret(cp, len, argv, cp - line)) < 0)
+            log_Printf(LogWARN, "%s: %d: Syntax error\n", filename, linenum);
+          else {
+            allowcmd = argc > 0 && !strcasecmp(argv[0], "allow");
+            if ((how != SYSTEM_EXEC && allowcmd) ||
+                (how == SYSTEM_EXEC && !allowcmd)) {
+              /*
+               * Disable any context so that warnings are given to everyone,
+               * including syslog.
+               */
+              op = log_PromptContext;
+              log_PromptContext = NULL;
+	      command_Run(bundle, argc, (char const *const *)argv, prompt,
+                          name, cx);
+              log_PromptContext = op;
+            }
+          }
+        }
+
+	fclose(fp);  /* everything read - get out */
+	return 0;
+      }
+      break;
+    }
+  }
+  fclose(fp);
+  return -1;
+}
+
+const char *
+system_IsValid(const char *name, struct prompt *prompt, int mode)
+{
+  /*
+   * Note:  The ReadSystem() calls only result in calls to the Allow*
+   * functions.  arg->bundle will be set to NULL for these commands !
+   */
+  int def, how, rs;
+  int defuserok;
+
+  def = !strcmp(name, "default");
+  how = ID0realuid() == 0 ? SYSTEM_EXISTS : SYSTEM_VALIDATE;
+  userok = -1;
+  modeok = 1;
+  modereq = mode;
+
+  rs = ReadSystem(NULL, "default", CONFFILE, prompt, NULL, how);
+
+  defuserok = userok;
+  userok = -1;
+
+  if (!def) {
+    if (rs == -1)
+      rs = 0;		/* we don't care that ``default'' doesn't exist */
+
+    if (rs == 0)
+      rs = ReadSystem(NULL, name, CONFFILE, prompt, NULL, how);
+
+    if (rs == -1)
+      return "Configuration label not found";
+
+    if (rs == -2)
+      return PPP_CONFDIR "/" CONFFILE " : File not found";
+  }
+
+  if (userok == -1)
+    userok = defuserok;
+
+  if (how == SYSTEM_EXISTS)
+    userok = modeok = 1;
+
+  if (!userok)
+    return "User access denied";
+
+  if (!modeok)
+    return "Mode denied for this label";
+
+  return NULL;
+}
+
+int
+system_Select(struct bundle *bundle, const char *name, const char *file,
+             struct prompt *prompt, struct datalink *cx)
+{
+  userok = modeok = 1;
+  modereq = PHYS_ALL;
+  return ReadSystem(bundle, name, file, prompt, cx, SYSTEM_EXEC);
+}
diff --git a/src/systems.h b/src/systems.h
new file mode 100644
index 0000000..53d734f
--- /dev/null
+++ b/src/systems.h
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/systems.h,v 1.17.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct prompt;
+struct datalink;
+struct bundle;
+struct cmdargs;
+
+extern int system_Select(struct bundle *bundle, const char *, const char *,
+                        struct prompt *, struct datalink *);
+extern const char *system_IsValid(const char *, struct prompt *, int);
+extern FILE *OpenSecret(const char *);
+extern void CloseSecret(FILE *);
+extern int AllowUsers(struct cmdargs const *);
+extern int AllowModes(struct cmdargs const *);
+extern const char *InterpretArg(const char *, char *);
diff --git a/src/tcp.c b/src/tcp.c
new file mode 100644
index 0000000..5c42e54
--- /dev/null
+++ b/src/tcp.c
@@ -0,0 +1,212 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/tcp.c,v 1.19.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "tcp.h"
+
+static int
+tcp_OpenConnection(const char *name, char *host, char *port)
+{
+  struct sockaddr_in dest;
+  int sock;
+  struct servent *sp;
+
+  dest.sin_family = AF_INET;
+  dest.sin_addr = GetIpAddr(host);
+  if (dest.sin_addr.s_addr == INADDR_NONE) {
+    log_Printf(LogWARN, "%s: %s: unknown host\n", name, host);
+    return -2;
+  }
+  dest.sin_port = htons(atoi(port));
+  if (dest.sin_port == 0) {
+    sp = getservbyname(port, "tcp");
+    if (sp)
+      dest.sin_port = sp->s_port;
+    else {
+      log_Printf(LogWARN, "%s: %s: unknown service\n", name, port);
+      return -2;
+    }
+  }
+  log_Printf(LogPHASE, "%s: Connecting to %s:%s/tcp\n", name, host, port);
+
+  sock = socket(PF_INET, SOCK_STREAM, 0);
+  if (sock < 0)
+    return -2;
+
+  if (connect(sock, (struct sockaddr *)&dest, sizeof dest) < 0) {
+    log_Printf(LogWARN, "%s: connect: %s\n", name, strerror(errno));
+    close(sock);
+    return -2;
+  }
+
+  return sock;
+}
+
+static struct device tcpdevice = {
+  TCP_DEVICE,
+  "tcp",
+  0,
+  { CD_NOTREQUIRED, 0 },
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL
+};
+
+struct device *
+tcp_iov2device(int type, struct physical *p, struct iovec *iov,
+               int *niov, int maxiov __unused, int *auxfd __unused,
+	       int *nauxfd __unused)
+{
+  if (type == TCP_DEVICE) {
+    free(iov[(*niov)++].iov_base);
+    physical_SetupStack(p, tcpdevice.name, PHYSICAL_FORCE_ASYNC);
+    return &tcpdevice;
+  }
+
+  return NULL;
+}
+
+struct device *
+tcp_Create(struct physical *p)
+{
+  char *cp, *host, *port, *svc;
+
+  if (p->fd < 0) {
+    if ((cp = strchr(p->name.full, ':')) != NULL && !strchr(cp + 1, ':')) {
+      *cp = '\0';
+      host = p->name.full;
+      port = cp + 1;
+      svc = strchr(port, '/');
+      if (svc && strcasecmp(svc, "/tcp")) {
+        *cp = ':';
+        return 0;
+      }
+      if (svc) {
+        p->fd--;     /* We own the device but maybe can't use it - change fd */
+        *svc = '\0';
+      }
+      if (*host && *port) {
+        p->fd = tcp_OpenConnection(p->link.name, host, port);
+        *cp = ':';
+        if (svc)
+          *svc = '/';
+        if (p->fd >= 0)
+          log_Printf(LogDEBUG, "%s: Opened tcp socket %s\n", p->link.name,
+                     p->name.full);
+      } else {
+        if (svc)
+          *svc = '/';
+        *cp = ':';
+      }
+    }
+  }
+
+  if (p->fd >= 0) {
+    /* See if we're a tcp socket */
+    struct stat st;
+
+    if (fstat(p->fd, &st) != -1 && (st.st_mode & S_IFSOCK)) {
+      int type, sz;
+
+      sz = sizeof type;
+      if (getsockopt(p->fd, SOL_SOCKET, SO_TYPE, &type, &sz) == -1) {
+        log_Printf(LogPHASE, "%s: Link is a closed socket !\n", p->link.name);
+        close(p->fd);
+        p->fd = -1;
+        return NULL;
+      }
+
+      if (sz == sizeof type && type == SOCK_STREAM) {
+        struct sockaddr_in sock;
+        struct sockaddr *sockp = (struct sockaddr *)&sock;
+
+        if (*p->name.full == '\0') {
+          sz = sizeof sock;
+          if (getpeername(p->fd, sockp, &sz) != 0 ||
+              sz != sizeof(struct sockaddr_in) || sock.sin_family != AF_INET) {
+            log_Printf(LogDEBUG, "%s: Link is SOCK_STREAM, but not inet\n",
+                       p->link.name);
+            return NULL;
+          }
+
+          log_Printf(LogPHASE, "%s: Link is a tcp socket\n", p->link.name);
+
+          snprintf(p->name.full, sizeof p->name.full, "%s:%d/tcp",
+                   inet_ntoa(sock.sin_addr), ntohs(sock.sin_port));
+          p->name.base = p->name.full;
+        }
+        physical_SetupStack(p, tcpdevice.name, PHYSICAL_FORCE_ASYNC);
+        if (p->cfg.cd.necessity != CD_DEFAULT)
+          log_Printf(LogWARN, "Carrier settings ignored\n");
+        return &tcpdevice;
+      }
+    }
+  }
+
+  return NULL;
+}
diff --git a/src/tcp.h b/src/tcp.h
new file mode 100644
index 0000000..c02e1fb
--- /dev/null
+++ b/src/tcp.h
@@ -0,0 +1,34 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/tcp.h,v 1.5.62.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct physical;
+
+extern struct device *tcp_Create(struct physical *);
+extern struct device *tcp_iov2device(int, struct physical *,
+                                     struct iovec *, int *, int, int *, int *);
+#define tcp_DeviceSize physical_DeviceSize
diff --git a/src/tcpmss.c b/src/tcpmss.c
new file mode 100644
index 0000000..faa76da
--- /dev/null
+++ b/src/tcpmss.c
@@ -0,0 +1,185 @@
+/*-
+ * Copyright (c) 2000 Ruslan Ermilov and Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/tcpmss.c,v 1.8.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+
+#include <sys/socket.h>
+#include <net/route.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <sys/un.h>
+
+#include <termios.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "mbuf.h"
+#include "throughput.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "mp.h"
+#include "iface.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+
+
+/*-
+ * We are in a liberal position about MSS
+ * (RFC 879, section 7).
+ */
+#define MAXMSS(mtu) ((mtu) - sizeof(struct ip) - sizeof(struct tcphdr) - 12)
+
+
+/*-
+ * The following macro is used to update an
+ * internet checksum.  "acc" is a 32-bit
+ * accumulation of all the changes to the
+ * checksum (adding in old 16-bit words and
+ * subtracting out new words), and "cksum"
+ * is the checksum value to be updated.
+ */
+#define ADJUST_CHECKSUM(acc, cksum) { \
+  acc += cksum; \
+  if (acc < 0) { \
+    acc = -acc; \
+    acc = (acc >> 16) + (acc & 0xffff); \
+    acc += acc >> 16; \
+    cksum = (u_short) ~acc; \
+  } else { \
+    acc = (acc >> 16) + (acc & 0xffff); \
+    acc += acc >> 16; \
+    cksum = (u_short) acc; \
+  } \
+}
+
+static void
+MSSFixup(struct tcphdr *tc, size_t pktlen, u_int16_t maxmss)
+{
+  size_t hlen, olen, optlen;
+  u_char *opt;
+  u_int16_t *mss;
+  int accumulate;
+
+  hlen = tc->th_off << 2;
+
+  /* Invalid header length or header without options. */
+  if (hlen <= sizeof(struct tcphdr) || hlen > pktlen)
+    return;
+
+  /* MSS option only allowed within SYN packets. */
+  if (!(tc->th_flags & TH_SYN))
+    return;
+
+  for (olen = hlen - sizeof(struct tcphdr), opt = (u_char *)(tc + 1);
+       olen > 0; olen -= optlen, opt += optlen) {
+    if (*opt == TCPOPT_EOL)
+      break;
+    else if (*opt == TCPOPT_NOP)
+      optlen = 1;
+    else {
+      optlen = *(opt + 1);
+      if (optlen <= 0 || optlen > olen)
+        break;
+      if (*opt == TCPOPT_MAXSEG) {
+        if (optlen != TCPOLEN_MAXSEG)
+          continue;
+        mss = (u_int16_t *)(opt + 2);
+        if (ntohs(*mss) > maxmss) {
+          log_Printf(LogDEBUG, "MSS: %u -> %u\n",
+               ntohs(*mss), maxmss);
+          accumulate = *mss;
+          *mss = htons(maxmss);
+          accumulate -= *mss;
+          ADJUST_CHECKSUM(accumulate, tc->th_sum);
+        }
+      }
+    }
+  }
+}
+
+static struct mbuf *
+tcpmss_Check(struct bundle *bundle, struct mbuf *bp)
+{
+  struct ip *pip;
+  size_t hlen, plen;
+
+  if (!Enabled(bundle, OPT_TCPMSSFIXUP))
+    return bp;
+
+  bp = m_pullup(bp);
+  plen = m_length(bp);
+  pip = (struct ip *)MBUF_CTOP(bp);
+  hlen = pip->ip_hl << 2;
+
+  /*
+   * Check for MSS option only for TCP packets with zero fragment offsets
+   * and correct total and header lengths.
+   */
+  if (pip->ip_p == IPPROTO_TCP && (ntohs(pip->ip_off) & IP_OFFMASK) == 0 &&
+      ntohs(pip->ip_len) == plen && hlen <= plen &&
+      plen >= sizeof(struct tcphdr) + hlen)
+    MSSFixup((struct tcphdr *)(MBUF_CTOP(bp) + hlen), plen - hlen,
+             MAXMSS(bundle->iface->mtu));
+
+  return bp;
+}
+
+static struct mbuf *
+tcpmss_LayerPush(struct bundle *bundle, struct link *l __unused,
+		 struct mbuf *bp, int pri __unused, u_short *proto __unused)
+{
+	return tcpmss_Check(bundle, bp);
+}
+
+static struct mbuf *
+tcpmss_LayerPull(struct bundle *bundle, struct link *l __unused,
+		 struct mbuf *bp, u_short *proto __unused)
+{
+	return tcpmss_Check(bundle, bp);
+}
+
+struct layer tcpmsslayer =
+  { LAYER_PROTO, "tcpmss", tcpmss_LayerPush, tcpmss_LayerPull };
diff --git a/src/tcpmss.h b/src/tcpmss.h
new file mode 100644
index 0000000..203a067
--- /dev/null
+++ b/src/tcpmss.h
@@ -0,0 +1,29 @@
+/*-
+ * Copyright (c) 2000 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/tcpmss.h,v 1.1.44.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+extern struct layer tcpmsslayer;
diff --git a/src/throughput.c b/src/throughput.c
new file mode 100644
index 0000000..24a50d8
--- /dev/null
+++ b/src/throughput.c
@@ -0,0 +1,302 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/throughput.c,v 1.18.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <time.h>
+
+#include "log.h"
+#include "timer.h"
+#include "throughput.h"
+#include "descriptor.h"
+#include "prompt.h"
+
+
+void
+throughput_init(struct pppThroughput *t, int period)
+{
+  t->OctetsIn = t->OctetsOut = t->PacketsIn = t->PacketsOut = 0;
+  t->SamplePeriod = period;
+  t->in.SampleOctets = (long long *)
+    calloc(period, sizeof *t->in.SampleOctets);
+  t->in.OctetsPerSecond = 0;
+  t->out.SampleOctets = (long long *)
+    calloc(period, sizeof *t->out.SampleOctets);
+  t->out.OctetsPerSecond = 0;
+  t->BestOctetsPerSecond = 0;
+  t->nSample = 0;
+  time(&t->BestOctetsPerSecondTime);
+  memset(&t->Timer, '\0', sizeof t->Timer);
+  t->Timer.name = "throughput";
+  t->uptime = 0;
+  t->downtime = 0;
+  t->rolling = 0;
+  t->callback.data = NULL;
+  t->callback.fn = NULL;
+  throughput_stop(t);
+}
+
+void
+throughput_destroy(struct pppThroughput *t)
+{
+  if (t && t->in.SampleOctets) {
+    throughput_stop(t);
+    free(t->in.SampleOctets);
+    free(t->out.SampleOctets);
+    t->in.SampleOctets = NULL;
+    t->out.SampleOctets = NULL;
+  }
+}
+
+int
+throughput_uptime(struct pppThroughput *t)
+{
+  time_t downat;
+
+  downat = t->downtime ? t->downtime : time(NULL);
+  if (t->uptime && downat < t->uptime) {
+    /* Euch !  The clock's gone back ! */
+    int i;
+
+    for (i = 0; i < t->SamplePeriod; i++)
+      t->in.SampleOctets[i] = t->out.SampleOctets[i] = 0;
+    t->nSample = 0;
+    t->uptime = downat;
+  }
+  return t->uptime ? downat - t->uptime : 0;
+}
+
+void
+throughput_disp(struct pppThroughput *t, struct prompt *prompt)
+{
+  int secs_up, divisor;
+
+  secs_up = throughput_uptime(t);
+  prompt_Printf(prompt, "Connect time: %d:%02d:%02d", secs_up / 3600,
+                (secs_up / 60) % 60, secs_up % 60);
+  if (t->downtime)
+    prompt_Printf(prompt, " - down at %s", ctime(&t->downtime));
+  else
+    prompt_Printf(prompt, "\n");
+
+  divisor = secs_up ? secs_up : 1;
+  prompt_Printf(prompt, "%llu octets in, %llu octets out\n",
+                t->OctetsIn, t->OctetsOut);
+  prompt_Printf(prompt, "%llu packets in, %llu packets out\n",
+                t->PacketsIn, t->PacketsOut);
+  if (t->rolling) {
+    prompt_Printf(prompt, "  overall   %6qu bytes/sec\n",
+                  (t->OctetsIn + t->OctetsOut) / divisor);
+    prompt_Printf(prompt, "  %s %6qu bytes/sec in, %6qu bytes/sec out "
+                  "(over the last %d secs)\n",
+                  t->downtime ? "average  " : "currently",
+                  t->in.OctetsPerSecond, t->out.OctetsPerSecond,
+                  secs_up > t->SamplePeriod ? t->SamplePeriod : secs_up);
+    prompt_Printf(prompt, "  peak      %6qu bytes/sec on %s",
+                  t->BestOctetsPerSecond, ctime(&t->BestOctetsPerSecondTime));
+  } else
+    prompt_Printf(prompt, "Overall %llu bytes/sec\n",
+                  (t->OctetsIn + t->OctetsOut) / divisor);
+}
+
+
+void
+throughput_log(struct pppThroughput *t, int level, const char *title)
+{
+  if (t->uptime) {
+    int secs_up;
+
+    secs_up = throughput_uptime(t);
+    if (title == NULL)
+      title = "";
+    log_Printf(level, "%s%sConnect time: %d secs: %llu octets in, %llu octets"
+               " out\n", title, *title ? ": " : "", secs_up, t->OctetsIn,
+               t->OctetsOut);
+    log_Printf(level, "%s%s%llu packets in, %llu packets out\n",
+               title, *title ? ": " : "",  t->PacketsIn, t->PacketsOut);
+    if (secs_up == 0)
+      secs_up = 1;
+    if (t->rolling)
+      log_Printf(level, " total %llu bytes/sec, peak %llu bytes/sec on %s",
+                 (t->OctetsIn + t->OctetsOut) / secs_up, t->BestOctetsPerSecond,
+                 ctime(&t->BestOctetsPerSecondTime));
+    else
+      log_Printf(level, " total %llu bytes/sec\n",
+                 (t->OctetsIn + t->OctetsOut) / secs_up);
+  }
+}
+
+static void
+throughput_sampler(void *v)
+{
+  struct pppThroughput *t = (struct pppThroughput *)v;
+  unsigned long long old;
+  int uptime, divisor;
+  unsigned long long octets;
+
+  timer_Stop(&t->Timer);
+
+  uptime = throughput_uptime(t);
+  divisor = uptime < t->SamplePeriod ? uptime + 1 : t->SamplePeriod;
+
+  old = t->in.SampleOctets[t->nSample];
+  t->in.SampleOctets[t->nSample] = t->OctetsIn;
+  t->in.OctetsPerSecond = (t->in.SampleOctets[t->nSample] - old) / divisor;
+
+  old = t->out.SampleOctets[t->nSample];
+  t->out.SampleOctets[t->nSample] = t->OctetsOut;
+  t->out.OctetsPerSecond = (t->out.SampleOctets[t->nSample] - old) / divisor;
+
+  octets = t->in.OctetsPerSecond + t->out.OctetsPerSecond;
+  if (t->BestOctetsPerSecond < octets) {
+    t->BestOctetsPerSecond = octets;
+    time(&t->BestOctetsPerSecondTime);
+  }
+
+  if (++t->nSample == t->SamplePeriod)
+    t->nSample = 0;
+
+  if (t->callback.fn != NULL && uptime >= t->SamplePeriod)
+    (*t->callback.fn)(t->callback.data);
+
+  timer_Start(&t->Timer);
+}
+
+void
+throughput_start(struct pppThroughput *t, const char *name, int rolling)
+{
+  int i;
+  timer_Stop(&t->Timer);
+
+  for (i = 0; i < t->SamplePeriod; i++)
+    t->in.SampleOctets[i] = t->out.SampleOctets[i] = 0;
+  t->nSample = 0;
+  t->OctetsIn = t->OctetsOut = 0;
+  t->in.OctetsPerSecond = t->out.OctetsPerSecond = t->BestOctetsPerSecond = 0;
+  time(&t->BestOctetsPerSecondTime);
+  t->downtime = 0;
+  time(&t->uptime);
+  throughput_restart(t, name, rolling);
+}
+
+void
+throughput_restart(struct pppThroughput *t, const char *name, int rolling)
+{
+  timer_Stop(&t->Timer);
+  t->rolling = rolling ? 1 : 0;
+  if (t->rolling) {
+    t->Timer.load = SECTICKS;
+    t->Timer.func = throughput_sampler;
+    t->Timer.name = name;
+    t->Timer.arg = t;
+    timer_Start(&t->Timer);
+  } else {
+    t->Timer.load = 0;
+    t->Timer.func = NULL;
+    t->Timer.name = NULL;
+    t->Timer.arg = NULL;
+  }
+}
+
+void
+throughput_stop(struct pppThroughput *t)
+{
+  if (t->Timer.state != TIMER_STOPPED)
+    time(&t->downtime);
+  timer_Stop(&t->Timer);
+}
+
+void
+throughput_addin(struct pppThroughput *t, long long n)
+{
+  t->OctetsIn += n;
+  t->PacketsIn++;
+}
+
+void
+throughput_addout(struct pppThroughput *t, long long n)
+{
+  t->OctetsOut += n;
+  t->PacketsOut++;
+}
+
+void
+throughput_clear(struct pppThroughput *t, int clear_type, struct prompt *prompt)
+{
+  if (clear_type & (THROUGHPUT_OVERALL|THROUGHPUT_CURRENT)) {
+    int i;
+
+    for (i = 0; i < t->SamplePeriod; i++)
+      t->in.SampleOctets[i] = t->out.SampleOctets[i] = 0;
+    t->nSample = 0;
+  }
+
+  if (clear_type & THROUGHPUT_OVERALL) {
+    int divisor;
+
+    if ((divisor = throughput_uptime(t)) == 0)
+      divisor = 1;
+    prompt_Printf(prompt, "overall cleared (was %6qu bytes/sec)\n",
+                  (t->OctetsIn + t->OctetsOut) / divisor);
+    t->OctetsIn = t->OctetsOut = 0;
+    t->downtime = 0;
+    time(&t->uptime);
+  }
+
+  if (clear_type & THROUGHPUT_CURRENT) {
+    prompt_Printf(prompt, "current cleared (was %6qu bytes/sec in,"
+                  " %6qu bytes/sec out)\n",
+                  t->in.OctetsPerSecond, t->out.OctetsPerSecond);
+    t->in.OctetsPerSecond = t->out.OctetsPerSecond = 0;
+  }
+
+  if (clear_type & THROUGHPUT_PEAK) {
+    char *time_buf, *last;
+
+    time_buf = ctime(&t->BestOctetsPerSecondTime);
+    last = time_buf + strlen(time_buf);
+    if (last > time_buf && *--last == '\n')
+      *last = '\0';
+    prompt_Printf(prompt, "peak    cleared (was %6qu bytes/sec on %s)\n",
+                  t->BestOctetsPerSecond, time_buf);
+    t->BestOctetsPerSecond = 0;
+    time(&t->BestOctetsPerSecondTime);
+  }
+}
+
+void
+throughput_callback(struct pppThroughput *t, void (*fn)(void *), void *data)
+{
+  t->callback.fn = fn;
+  t->callback.data = data;
+}
diff --git a/src/throughput.h b/src/throughput.h
new file mode 100644
index 0000000..e23af27
--- /dev/null
+++ b/src/throughput.h
@@ -0,0 +1,70 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/throughput.h,v 1.10.42.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define SAMPLE_PERIOD 5		/* Default sample period */
+
+#define THROUGHPUT_OVERALL 0x0001
+#define THROUGHPUT_CURRENT 0x0002
+#define THROUGHPUT_PEAK    0x0004
+#define THROUGHPUT_ALL     0x0007
+
+struct pppThroughput {
+  time_t uptime, downtime;
+  unsigned long long OctetsIn;
+  unsigned long long OctetsOut;
+  unsigned long long PacketsIn;
+  unsigned long long PacketsOut;
+  int SamplePeriod;
+  struct {
+    unsigned long long *SampleOctets;
+    unsigned long long OctetsPerSecond;
+  } in, out;
+  unsigned long long BestOctetsPerSecond;
+  time_t BestOctetsPerSecondTime;
+  int nSample;
+  unsigned rolling : 1;
+  struct pppTimer Timer;
+  struct {
+    void *data;
+    void (*fn)(void *v);
+  } callback;
+};
+
+extern void throughput_init(struct pppThroughput *, int);
+extern void throughput_destroy(struct pppThroughput *);
+extern void throughput_disp(struct pppThroughput *, struct prompt *);
+extern void throughput_log(struct pppThroughput *, int, const char *);
+extern void throughput_start(struct pppThroughput *, const char *, int);
+extern void throughput_restart(struct pppThroughput *, const char *, int);
+extern void throughput_stop(struct pppThroughput *);
+extern void throughput_addin(struct pppThroughput *, long long);
+extern void throughput_addout(struct pppThroughput *, long long);
+extern void throughput_clear(struct pppThroughput *, int, struct prompt *);
+extern void throughput_callback(struct pppThroughput *, void (*)(void *),
+                                void *);
+extern int throughput_uptime(struct pppThroughput *);
diff --git a/src/timer.c b/src/timer.c
new file mode 100644
index 0000000..d1b1d03
--- /dev/null
+++ b/src/timer.c
@@ -0,0 +1,302 @@
+/*-
+ * Copyright (c) 1996 - 2001, 2009 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/timer.c,v 1.47.24.1.4.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+#include <termios.h>
+
+#include "log.h"
+#include "sig.h"
+#include "timer.h"
+#include "descriptor.h"
+#include "prompt.h"
+
+
+#define RESTVAL(t) \
+    ((t).it_value.tv_sec * SECTICKS + (t).it_value.tv_usec / TICKUNIT + \
+     ((((t).it_value.tv_usec % TICKUNIT) >= (TICKUNIT >> 1)) ? 1 : 0))
+
+static struct pppTimer *TimerList = NULL, *ExpiredList = NULL;
+
+static void StopTimerNoBlock(struct pppTimer *);
+
+static const char *
+tState2Nam(u_int state)
+{
+  static const char * const StateNames[] = { "stopped", "running", "expired" };
+
+  if (state >= sizeof StateNames / sizeof StateNames[0])
+    return "unknown";
+  return StateNames[state];
+}
+
+void
+timer_Stop(struct pppTimer *tp)
+{
+  sigset_t mask, omask;
+
+  sigemptyset(&mask);
+  sigaddset(&mask, SIGALRM);
+  sigprocmask(SIG_BLOCK, &mask, &omask);
+  StopTimerNoBlock(tp);
+  sigprocmask(SIG_SETMASK, &omask, NULL);
+}
+
+void
+timer_Start(struct pppTimer *tp)
+{
+  struct itimerval itimer;
+  struct pppTimer *t, *pt;
+  u_long ticks = 0;
+  sigset_t mask, omask;
+
+  sigemptyset(&mask);
+  sigaddset(&mask, SIGALRM);
+  sigprocmask(SIG_BLOCK, &mask, &omask);
+
+  if (tp->state != TIMER_STOPPED)
+    StopTimerNoBlock(tp);
+
+  if (tp->load == 0) {
+    log_Printf(LogTIMER, "%s timer[%p] has 0 load!\n", tp->name, tp);
+    sigprocmask(SIG_SETMASK, &omask, NULL);
+    return;
+  }
+
+  /*
+   * We just need to insert tp in the correct relative place.  We don't
+   * need to adjust TimerList->rest (yet).
+   */
+  if (TimerList && getitimer(ITIMER_REAL, &itimer) == 0)
+    ticks = RESTVAL(itimer) - TimerList->rest;
+
+  pt = NULL;
+  for (t = TimerList; t; t = t->next) {
+    if (ticks + t->rest >= tp->load)
+      break;
+    ticks += t->rest;
+    pt = t;
+  }
+
+  tp->state = TIMER_RUNNING;
+  tp->rest = tp->load - ticks;
+
+  if (t)
+    log_Printf(LogTIMER, "timer_Start: Inserting %s timer[%p] before %s "
+              "timer[%p], delta = %ld\n", tp->name, tp, t->name, t, tp->rest);
+  else
+    log_Printf(LogTIMER, "timer_Start: Inserting %s timer[%p]\n", tp->name, tp);
+
+  /* Insert given *tp just before *t */
+  tp->next = t;
+  if (pt) {
+    pt->next = tp;
+  } else {
+    TimerList = tp;
+    timer_InitService(t != NULL);	/* [re]Start the Timer Service */
+  }
+  if (t)
+    t->rest -= tp->rest;
+
+  sigprocmask(SIG_SETMASK, &omask, NULL);
+}
+
+static void
+StopTimerNoBlock(struct pppTimer *tp)
+{
+  struct itimerval itimer;
+  struct pppTimer *t, *pt;
+
+  /*
+   * A RUNNING timer must be removed from TimerList (->next list).
+   * A STOPPED timer isn't in any list, but may have a bogus [e]next field.
+   * An EXPIRED timer is in the ->enext list.
+   */
+
+  if (tp->state == TIMER_STOPPED)
+    return;
+
+  pt = NULL;
+  for (t = TimerList; t != tp && t != NULL; t = t->next)
+    pt = t;
+
+  if (t) {
+    if (pt)
+      pt->next = t->next;
+    else {
+      TimerList = t->next;
+      if (TimerList == NULL)	/* Last one ? */
+	timer_TermService();	/* Terminate Timer Service */
+    }
+    if (t->next) {
+      if (!pt && getitimer(ITIMER_REAL, &itimer) == 0)
+        t->next->rest += RESTVAL(itimer); /* t (tp) was the first in the list */
+      else
+        t->next->rest += t->rest;
+      if (!pt && t->next->rest > 0)   /* t->next is now the first in the list */
+        timer_InitService(1);
+    }
+  } else {
+    /* Search for any pending expired timers */
+    pt = NULL;
+    for (t = ExpiredList; t != tp && t != NULL; t = t->enext)
+      pt = t;
+
+    if (t) {
+      if (pt)
+        pt->enext = t->enext;
+      else
+        ExpiredList = t->enext;
+    } else if (tp->state == TIMER_RUNNING)
+      log_Printf(LogERROR, "Oops, %s timer not found!!\n", tp->name);
+  }
+
+  tp->next = tp->enext = NULL;
+  tp->state = TIMER_STOPPED;
+}
+
+static void
+TimerService(void)
+{
+  struct pppTimer *tp, *exp, *next;
+
+  if (log_IsKept(LogTIMER)) {
+    static time_t t;		/* Only show timers globally every second */
+    time_t n = time(NULL);
+
+    if (n > t)
+      timer_Show(LogTIMER, NULL);
+    t = n;
+  }
+
+  tp = TimerList;
+  if (tp) {
+    tp->rest = 0;
+
+    /* Multiple timers might expire at once. Create a list of expired timers */
+    exp = NULL;
+    do {
+      tp->state = TIMER_EXPIRED;
+      next = tp->next;
+      tp->enext = exp;
+      exp = tp;
+      tp = next;
+    } while (tp && tp->rest == 0);
+
+    TimerList = tp;
+    if (TimerList != NULL)	/* Any timers remaining ? */
+      timer_InitService(1);	/* Restart the Timer Service */
+    else
+      timer_TermService();	/* Stop the Timer Service */
+
+    /* Process all expired timers */
+    while (exp) {
+      ExpiredList = exp->enext;
+      exp->enext = NULL;
+      if (exp->func)
+        (*exp->func)(exp->arg);
+      exp = ExpiredList;
+    }
+  }
+}
+
+void
+timer_Show(int LogLevel, struct prompt *prompt)
+{
+  struct itimerval itimer;
+  struct pppTimer *pt;
+  long rest;
+
+  /*
+   * Adjust the base time so that the deltas reflect what's really
+   * happening.  Changing TimerList->rest might cause it to become zero
+   * (if getitimer() returns a value close to zero), and the
+   * timer_InitService() call will call setitimer() with zero it_value,
+   * stopping the itimer... so be careful!
+   */
+  if (TimerList && getitimer(ITIMER_REAL, &itimer) == 0)
+    rest = RESTVAL(itimer) - TimerList->rest;
+  else
+    rest = 0;
+
+#define SECS(val)	((val) / SECTICKS)
+#define HSECS(val)	(((val) % SECTICKS) * 100 / SECTICKS)
+#define DISP								\
+  "%s timer[%p]: freq = %ld.%02lds, next = %lu.%02lus, state = %s\n",	\
+  pt->name, pt, SECS(pt->load), HSECS(pt->load), SECS(rest),		\
+  HSECS(rest), tState2Nam(pt->state)
+
+  if (!prompt)
+    log_Printf(LogLevel, "---- Begin of Timer Service List---\n");
+
+  for (pt = TimerList; pt; pt = pt->next) {
+    rest += pt->rest;
+    if (prompt)
+      prompt_Printf(prompt, DISP);
+    else
+      log_Printf(LogLevel, DISP);
+  }
+
+  if (!prompt)
+    log_Printf(LogLevel, "---- End of Timer Service List ---\n");
+}
+
+void
+timer_InitService(int restart)
+{
+  struct itimerval itimer;
+
+  if (TimerList) {
+    if (!restart)
+      sig_signal(SIGALRM, (void (*)(int))TimerService);
+    itimer.it_interval.tv_sec = 0;
+    itimer.it_interval.tv_usec = 0;
+    itimer.it_value.tv_sec = TimerList->rest / SECTICKS;
+    itimer.it_value.tv_usec = (TimerList->rest % SECTICKS) * TICKUNIT;
+    if (setitimer(ITIMER_REAL, &itimer, NULL) == -1)
+      log_Printf(LogERROR, "Unable to set itimer (%s)\n", strerror(errno));
+  }
+}
+
+void
+timer_TermService(void)
+{
+  struct itimerval itimer;
+
+  itimer.it_interval.tv_usec = itimer.it_interval.tv_sec = 0;
+  itimer.it_value.tv_usec = itimer.it_value.tv_sec = 0;
+  if (setitimer(ITIMER_REAL, &itimer, NULL) == -1)
+    log_Printf(LogERROR, "Unable to set itimer (%s)\n", strerror(errno));
+  sig_signal(SIGALRM, SIG_IGN);
+}
diff --git a/src/timer.h b/src/timer.h
new file mode 100644
index 0000000..65cffeb
--- /dev/null
+++ b/src/timer.h
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/timer.h,v 1.10.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#define	TICKUNIT	100000			/* usec's per Unit */
+#define	SECTICKS	(1000000/TICKUNIT)	/* Units per second */
+
+struct pppTimer {
+  int state;
+  const char *name;
+  u_long rest;			/* Ticks to expire */
+  u_long load;			/* Initial load value */
+  void (*func)(void *);		/* Function called when timer is expired */
+  void *arg;			/* Argument passed to timeout function */
+  struct pppTimer *next;	/* Link to next timer */
+  struct pppTimer *enext;	/* Link to next expired timer */
+};
+
+#define	TIMER_STOPPED	0
+#define	TIMER_RUNNING	1
+#define	TIMER_EXPIRED	2
+
+struct prompt;
+
+extern void timer_Start(struct pppTimer *);
+extern void timer_Stop(struct pppTimer *);
+extern void timer_InitService(int);
+extern void timer_TermService(void);
+extern void timer_Show(int LogLevel, struct prompt *);
diff --git a/src/tty.c b/src/tty.c
new file mode 100644
index 0000000..ee0bb0e
--- /dev/null
+++ b/src/tty.c
@@ -0,0 +1,770 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/tty.c,v 1.32.10.1.4.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <sys/un.h>
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+#include <sys/ioctl.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <ttyent.h>
+#include <unistd.h>
+#ifndef NONETGRAPH
+#include <netgraph.h>
+#include <netgraph/ng_async.h>
+#include <netgraph/ng_message.h>
+#include <netgraph/ng_ppp.h>
+#include <netgraph/ng_tty.h>
+#endif
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "mp.h"
+#include "chat.h"
+#include "auth.h"
+#include "chap.h"
+#include "cbcp.h"
+#include "datalink.h"
+#include "main.h"
+#include "id.h"
+#include "tty.h"
+
+#if defined(__mac68k__) || defined(__macppc__)
+#undef	CRTS_IFLOW
+#undef	CCTS_OFLOW
+#define	CRTS_IFLOW	CDTRCTS
+#define	CCTS_OFLOW	CDTRCTS
+#endif
+
+#define	Online(dev)	((dev)->mbits & TIOCM_CD)
+
+struct ttydevice {
+  struct device dev;		/* What struct physical knows about */
+  struct pppTimer Timer;	/* CD checks */
+  int mbits;			/* Current DCD status */
+  int carrier_seconds;		/* seconds before CD is *required* */
+#ifndef NONETGRAPH
+  struct {
+    unsigned speed;		/* Pre-line-discipline speed */
+    int fd;			/* Pre-line-discipline fd */
+    int disc;			/* Old line-discipline */
+  } real;
+  char hook[sizeof NG_ASYNC_HOOK_SYNC]; /* our ng_socket hook */
+  int cs;			/* A netgraph control socket (maybe) */
+#endif
+  struct termios ios;		/* To be able to reset from raw mode */
+};
+
+#define device2tty(d) ((d)->type == TTY_DEVICE ? (struct ttydevice *)d : NULL)
+
+unsigned
+tty_DeviceSize(void)
+{
+  return sizeof(struct ttydevice);
+}
+
+/*
+ * tty_Timeout() watches the DCD signal and mentions it if it's status
+ * changes.
+ */
+static void
+tty_Timeout(void *data)
+{
+  struct physical *p = data;
+  struct ttydevice *dev = device2tty(p->handler);
+  int ombits, change;
+
+  timer_Stop(&dev->Timer);
+  dev->Timer.load = SECTICKS;		/* Once a second please */
+  timer_Start(&dev->Timer);
+  ombits = dev->mbits;
+
+  if (p->fd >= 0) {
+    if (ioctl(p->fd, TIOCMGET, &dev->mbits) < 0) {
+      /* we must be a pty ? */
+      if (p->cfg.cd.necessity != CD_DEFAULT)
+        log_Printf(LogWARN, "%s: Carrier ioctl not supported, "
+                   "using ``set cd off''\n", p->link.name);
+      timer_Stop(&dev->Timer);
+      dev->mbits = TIOCM_CD;
+      return;
+    }
+  } else
+    dev->mbits = 0;
+
+  if (ombits == -1) {
+    /* First time looking for carrier */
+    if (Online(dev))
+      log_Printf(LogPHASE, "%s: %s: CD detected\n", p->link.name, p->name.full);
+    else if (++dev->carrier_seconds >= dev->dev.cd.delay) {
+      if (dev->dev.cd.necessity == CD_REQUIRED)
+        log_Printf(LogPHASE, "%s: %s: Required CD not detected\n",
+                   p->link.name, p->name.full);
+      else {
+        log_Printf(LogPHASE, "%s: %s doesn't support CD\n",
+                   p->link.name, p->name.full);
+        dev->mbits = TIOCM_CD;		/* Dodgy null-modem cable ? */
+      }
+      timer_Stop(&dev->Timer);
+      /* tty_AwaitCarrier() will notice */
+    } else {
+      /* Keep waiting */
+      log_Printf(LogDEBUG, "%s: %s: Still no carrier (%d/%d)\n",
+                 p->link.name, p->name.full, dev->carrier_seconds,
+                 dev->dev.cd.delay);
+      dev->mbits = -1;
+    }
+  } else {
+    change = ombits ^ dev->mbits;
+    if (change & TIOCM_CD) {
+      if (dev->mbits & TIOCM_CD)
+        log_Printf(LogDEBUG, "%s: offline -> online\n", p->link.name);
+      else {
+        log_Printf(LogDEBUG, "%s: online -> offline\n", p->link.name);
+        log_Printf(LogPHASE, "%s: Carrier lost\n", p->link.name);
+        datalink_Down(p->dl, CLOSE_NORMAL);
+        timer_Stop(&dev->Timer);
+      }
+    } else
+      log_Printf(LogDEBUG, "%s: Still %sline\n", p->link.name,
+                 Online(dev) ? "on" : "off");
+  }
+}
+
+static void
+tty_StartTimer(struct physical *p)
+{
+  struct ttydevice *dev = device2tty(p->handler);
+
+  timer_Stop(&dev->Timer);
+  dev->Timer.load = SECTICKS;
+  dev->Timer.func = tty_Timeout;
+  dev->Timer.name = "tty CD";
+  dev->Timer.arg = p;
+  log_Printf(LogDEBUG, "%s: Using tty_Timeout [%p]\n",
+             p->link.name, tty_Timeout);
+  timer_Start(&dev->Timer);
+}
+
+static int
+tty_AwaitCarrier(struct physical *p)
+{
+  struct ttydevice *dev = device2tty(p->handler);
+
+  if (dev->dev.cd.necessity == CD_NOTREQUIRED || physical_IsSync(p))
+    return CARRIER_OK;
+
+  if (dev->mbits == -1) {
+    if (dev->Timer.state == TIMER_STOPPED) {
+      dev->carrier_seconds = 0;
+      tty_StartTimer(p);
+    }
+    return CARRIER_PENDING;			/* Not yet ! */
+  }
+
+  return Online(dev) ? CARRIER_OK : CARRIER_LOST;
+}
+
+#ifdef NONETGRAPH
+#define tty_SetAsyncParams	NULL
+#define tty_Write		NULL
+#define tty_Read		NULL
+#else
+
+static int
+isngtty(struct ttydevice *dev)
+{
+  return dev->real.fd != -1;
+}
+
+static void
+tty_SetAsyncParams(struct physical *p, u_int32_t mymap, u_int32_t hismap)
+{
+  struct ttydevice *dev = device2tty(p->handler);
+  char asyncpath[NG_PATHSIZ];
+  struct ng_async_cfg cfg;
+
+  if (isngtty(dev)) {
+    /* Configure the async converter node */
+
+    snprintf(asyncpath, sizeof asyncpath, ".:%s", dev->hook);
+    memset(&cfg, 0, sizeof cfg);
+    cfg.enabled = 1;
+    cfg.accm = mymap | hismap;
+    cfg.amru = MAX_MTU;
+    cfg.smru = MAX_MRU;
+    log_Printf(LogDEBUG, "Configure async node at %s\n", asyncpath);
+    if (NgSendMsg(dev->cs, asyncpath, NGM_ASYNC_COOKIE,
+                  NGM_ASYNC_CMD_SET_CONFIG, &cfg, sizeof cfg) < 0)
+      log_Printf(LogWARN, "%s: Can't configure async node at %s\n",
+                 p->link.name, asyncpath);
+  } else
+    /* No netgraph node, just config the async layer */
+    async_SetLinkParams(&p->async, mymap, hismap);
+}
+
+static int
+LoadLineDiscipline(struct physical *p)
+{
+  struct ttydevice *dev = device2tty(p->handler);
+  u_char rbuf[sizeof(struct ng_mesg) + sizeof(struct nodeinfo)];
+  struct ng_mesg *reply;
+  struct nodeinfo *info;
+  char ttypath[NG_NODESIZ];
+  struct ngm_mkpeer ngm;
+  struct ngm_connect ngc;
+  int ldisc, cs, ds, hot;
+  unsigned speed;
+
+  /*
+   * Don't use the netgraph line discipline for now.  Using it works, but
+   * carrier cannot be detected via TIOCMGET and the device doesn't become
+   * selectable with 0 bytes to read when carrier is lost :(
+   */
+  return 0;
+
+  reply = (struct ng_mesg *)rbuf;
+  info = (struct nodeinfo *)reply->data;
+
+  loadmodules(LOAD_VERBOSLY, "netgraph", "ng_tty", "ng_async", "ng_socket",
+              NULL);
+
+  /* Get the speed before loading the line discipline */
+  speed = physical_GetSpeed(p);
+
+  if (ioctl(p->fd, TIOCGETD, &dev->real.disc) < 0) {
+    log_Printf(LogDEBUG, "%s: Couldn't get tty line discipline\n",
+               p->link.name);
+    return 0;
+  }
+  ldisc = NETGRAPHDISC;
+  if (ID0ioctl(p->fd, TIOCSETD, &ldisc) < 0) {
+    log_Printf(LogDEBUG, "%s: Couldn't set NETGRAPHDISC line discipline\n",
+               p->link.name);
+    return 0;
+  }
+
+  /* Get the name of the tty node */
+  if (ioctl(p->fd, NGIOCGINFO, info) < 0) {
+    log_Printf(LogWARN, "%s: ioctl(NGIOCGINFO): %s\n", p->link.name,
+               strerror(errno));
+    ID0ioctl(p->fd, TIOCSETD, &dev->real.disc);
+    return 0;
+  }
+  snprintf(ttypath, sizeof ttypath, "%s:", info->name);
+
+  /* Create a socket node for our endpoint (and to send messages via) */
+  if (ID0NgMkSockNode(NULL, &cs, &ds) == -1) {
+    log_Printf(LogWARN, "%s: NgMkSockNode: %s\n", p->link.name,
+               strerror(errno));
+    ID0ioctl(p->fd, TIOCSETD, &dev->real.disc);
+    return 0;
+  }
+
+  /* Set the ``hot char'' on the TTY node */
+  hot = HDLC_SYN;
+  log_Printf(LogDEBUG, "%s: Set tty hotchar to 0x%02x\n", p->link.name, hot);
+  if (NgSendMsg(cs, ttypath, NGM_TTY_COOKIE,
+      NGM_TTY_SET_HOTCHAR, &hot, sizeof hot) < 0) {
+    log_Printf(LogWARN, "%s: Can't set hot char\n", p->link.name);
+    goto failed;
+  }
+
+  /* Attach an async converter node */
+  snprintf(ngm.type, sizeof ngm.type, "%s", NG_ASYNC_NODE_TYPE);
+  snprintf(ngm.ourhook, sizeof ngm.ourhook, "%s", NG_TTY_HOOK);
+  snprintf(ngm.peerhook, sizeof ngm.peerhook, "%s", NG_ASYNC_HOOK_ASYNC);
+  log_Printf(LogDEBUG, "%s: Send mkpeer async:%s to %s:%s\n", p->link.name,
+             ngm.peerhook, ttypath, ngm.ourhook);
+  if (NgSendMsg(cs, ttypath, NGM_GENERIC_COOKIE,
+      NGM_MKPEER, &ngm, sizeof ngm) < 0) {
+    log_Printf(LogWARN, "%s: Can't create %s node\n", p->link.name,
+               NG_ASYNC_NODE_TYPE);
+    goto failed;
+  }
+
+  /* Connect the async node to our socket */
+  snprintf(ngc.path, sizeof ngc.path, "%s%s", ttypath, NG_TTY_HOOK);
+  snprintf(ngc.peerhook, sizeof ngc.peerhook, "%s", NG_ASYNC_HOOK_SYNC);
+  memcpy(ngc.ourhook, ngc.peerhook, sizeof ngc.ourhook);
+  log_Printf(LogDEBUG, "%s: Send connect %s:%s to .:%s\n", p->link.name,
+             ngc.path, ngc.peerhook, ngc.ourhook);
+  if (NgSendMsg(cs, ".:", NGM_GENERIC_COOKIE, NGM_CONNECT,
+      &ngc, sizeof ngc) < 0) {
+    log_Printf(LogWARN, "%s: Can't connect .:%s -> %s.%s: %s\n",
+               p->link.name, ngc.ourhook, ngc.path, ngc.peerhook,
+               strerror(errno));
+    goto failed;
+  }
+
+  /* Get the async node id */
+  if (NgSendMsg(cs, ngc.path, NGM_GENERIC_COOKIE, NGM_NODEINFO, NULL, 0) < 0) {
+    log_Printf(LogWARN, "%s: Can't request async node info at %s: %s\n",
+               p->link.name, ngc.path, strerror(errno));
+    goto failed;
+  }
+  if (NgRecvMsg(cs, reply, sizeof rbuf, NULL) < 0) {
+    log_Printf(LogWARN, "%s: Can't obtain async node info at %s: %s\n",
+               p->link.name, ngc.path, strerror(errno));
+    goto failed;
+  }
+
+  /* All done, set up our device state */
+  snprintf(dev->hook, sizeof dev->hook, "%s", ngc.ourhook);
+  dev->cs = cs;
+  dev->real.fd = p->fd;
+  p->fd = ds;
+  dev->real.speed = speed;
+  physical_SetSync(p);
+
+  tty_SetAsyncParams(p, 0xffffffff, 0xffffffff);
+  physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
+  log_Printf(LogPHASE, "%s: Loaded netgraph tty line discipline\n",
+             p->link.name);
+
+  return 1;
+
+failed:
+  ID0ioctl(p->fd, TIOCSETD, &dev->real.disc);
+  close(ds);
+  close(cs);
+
+  return 0;
+}
+
+static void
+UnloadLineDiscipline(struct physical *p)
+{
+  struct ttydevice *dev = device2tty(p->handler);
+
+  if (isngtty(dev)) {
+    if (!physical_SetSpeed(p, dev->real.speed))
+      log_Printf(LogWARN, "Couldn't reset tty speed to %d\n", dev->real.speed);
+    dev->real.speed = 0;
+    close(p->fd);
+    p->fd = dev->real.fd;
+    dev->real.fd = -1;
+    close(dev->cs);
+    dev->cs = -1;
+    *dev->hook = '\0';
+    if (ID0ioctl(p->fd, TIOCSETD, &dev->real.disc) == 0) {
+      physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
+      log_Printf(LogPHASE, "%s: Unloaded netgraph tty line discipline\n",
+                 p->link.name);
+    } else
+      log_Printf(LogWARN, "%s: Failed to unload netgraph tty line discipline\n",
+                 p->link.name);
+  }
+}
+
+static ssize_t
+tty_Write(struct physical *p, const void *v, size_t n)
+{
+  struct ttydevice *dev = device2tty(p->handler);
+
+  if (isngtty(dev))
+    return NgSendData(p->fd, dev->hook, v, n) == -1 ? -1 : (ssize_t)n;
+  else
+    return write(p->fd, v, n);
+}
+
+static ssize_t
+tty_Read(struct physical *p, void *v, size_t n)
+{
+  struct ttydevice *dev = device2tty(p->handler);
+  char hook[sizeof NG_ASYNC_HOOK_SYNC];
+
+  if (isngtty(dev))
+    return NgRecvData(p->fd, v, n, hook);
+  else
+    return read(p->fd, v, n);
+}
+
+#endif /* NETGRAPH */
+
+static int
+tty_Raw(struct physical *p)
+{
+  struct ttydevice *dev = device2tty(p->handler);
+  struct termios ios;
+  int oldflag;
+
+  log_Printf(LogDEBUG, "%s: Entering tty_Raw\n", p->link.name);
+
+  if (p->type != PHYS_DIRECT && p->fd >= 0 && !Online(dev))
+    log_Printf(LogDEBUG, "%s: Raw: descriptor = %d, mbits = %x\n",
+              p->link.name, p->fd, dev->mbits);
+
+  if (!physical_IsSync(p)) {
+#ifndef NONETGRAPH
+    if (!LoadLineDiscipline(p))
+#endif
+    {
+      tcgetattr(p->fd, &ios);
+      cfmakeraw(&ios);
+      if (p->cfg.rts_cts)
+        ios.c_cflag |= CLOCAL | CCTS_OFLOW | CRTS_IFLOW;
+      else
+        ios.c_cflag |= CLOCAL;
+
+      if (p->type != PHYS_DEDICATED)
+        ios.c_cflag |= HUPCL;
+
+      if (tcsetattr(p->fd, TCSANOW, &ios) == -1)
+        log_Printf(LogWARN, "%s: tcsetattr: Failed configuring device\n",
+                   p->link.name);
+    }
+  }
+
+  oldflag = fcntl(p->fd, F_GETFL, 0);
+  if (oldflag < 0)
+    return 0;
+  fcntl(p->fd, F_SETFL, oldflag | O_NONBLOCK);
+
+  return 1;
+}
+
+static void
+tty_Offline(struct physical *p)
+{
+  struct ttydevice *dev = device2tty(p->handler);
+
+  if (p->fd >= 0) {
+    timer_Stop(&dev->Timer);
+    dev->mbits &= ~TIOCM_DTR;	/* XXX: Hmm, what's this supposed to do ? */
+    if (Online(dev)) {
+      struct termios tio;
+
+      tcgetattr(p->fd, &tio);
+      if (cfsetspeed(&tio, B0) == -1 || tcsetattr(p->fd, TCSANOW, &tio) == -1)
+        log_Printf(LogWARN, "%s: Unable to set physical to speed 0\n",
+                   p->link.name);
+    }
+  }
+}
+
+static void
+tty_Cooked(struct physical *p)
+{
+  struct ttydevice *dev = device2tty(p->handler);
+  int oldflag;
+
+  tty_Offline(p);	/* In case of emergency close()s */
+
+  tcflush(p->fd, TCIOFLUSH);
+
+  if (!physical_IsSync(p) && tcsetattr(p->fd, TCSAFLUSH, &dev->ios) == -1)
+    log_Printf(LogWARN, "%s: tcsetattr: Unable to restore device settings\n",
+               p->link.name);
+
+#ifndef NONETGRAPH
+  UnloadLineDiscipline(p);
+#endif
+
+  if ((oldflag = fcntl(p->fd, F_GETFL, 0)) != -1)
+    fcntl(p->fd, F_SETFL, oldflag & ~O_NONBLOCK);
+}
+
+static void
+tty_StopTimer(struct physical *p)
+{
+  struct ttydevice *dev = device2tty(p->handler);
+
+  timer_Stop(&dev->Timer);
+}
+
+static void
+tty_Free(struct physical *p)
+{
+  struct ttydevice *dev = device2tty(p->handler);
+
+  tty_Offline(p);	/* In case of emergency close()s */
+  free(dev);
+}
+
+static unsigned
+tty_Speed(struct physical *p)
+{
+  struct termios ios;
+
+  if (tcgetattr(p->fd, &ios) == -1)
+    return 0;
+
+  return SpeedToUnsigned(cfgetispeed(&ios));
+}
+
+static const char *
+tty_OpenInfo(struct physical *p)
+{
+  struct ttydevice *dev = device2tty(p->handler);
+  static char buf[13];
+
+  if (Online(dev))
+    strcpy(buf, "with");
+  else
+    strcpy(buf, "no");
+  strcat(buf, " carrier");
+
+  return buf;
+}
+
+static int
+tty_Slot(struct physical *p)
+{
+  struct ttyent *ttyp;
+  int slot;
+
+  setttyent();
+  for (slot = 1; (ttyp = getttyent()); ++slot)
+    if (!strcmp(ttyp->ty_name, p->name.base)) {
+      endttyent();
+      return slot;
+    }
+
+  endttyent();
+  return -1;
+}
+
+static void
+tty_device2iov(struct device *d, struct iovec *iov, int *niov,
+               int maxiov __unused,
+#ifndef NONETGRAPH
+               int *auxfd, int *nauxfd
+#else
+               int *auxfd __unused, int *nauxfd __unused
+#endif
+               )
+{
+  struct ttydevice *dev;
+  int sz = physical_MaxDeviceSize();
+
+  iov[*niov].iov_base = d = realloc(d, sz);
+  if (d == NULL) {
+    log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
+    AbortProgram(EX_OSERR);
+  }
+  iov[*niov].iov_len = sz;
+  (*niov)++;
+
+  dev = device2tty(d);
+
+#ifndef NONETGRAPH
+  if (dev->cs >= 0) {
+    *auxfd = dev->cs;
+    (*nauxfd)++;
+  }
+#endif
+
+  if (dev->Timer.state != TIMER_STOPPED) {
+    timer_Stop(&dev->Timer);
+    dev->Timer.state = TIMER_RUNNING;
+  }
+}
+
+static struct device basettydevice = {
+  TTY_DEVICE,
+  "tty",
+  0,
+  { CD_VARIABLE, DEF_TTYCDDELAY },
+  tty_AwaitCarrier,
+  NULL,
+  tty_Raw,
+  tty_Offline,
+  tty_Cooked,
+  tty_SetAsyncParams,
+  tty_StopTimer,
+  tty_Free,
+  tty_Read,
+  tty_Write,
+  tty_device2iov,
+  tty_Speed,
+  tty_OpenInfo,
+  tty_Slot
+};
+
+struct device *
+tty_iov2device(int type, struct physical *p, struct iovec *iov, int *niov,
+               int maxiov __unused,
+#ifndef NONETGRAPH
+               int *auxfd, int *nauxfd
+#else
+               int *auxfd __unused, int *nauxfd __unused
+#endif
+               )
+{
+  if (type == TTY_DEVICE) {
+    struct ttydevice *dev = (struct ttydevice *)iov[(*niov)++].iov_base;
+
+    dev = realloc(dev, sizeof *dev);	/* Reduce to the correct size */
+    if (dev == NULL) {
+      log_Printf(LogALERT, "Failed to allocate memory: %d\n",
+                 (int)(sizeof *dev));
+      AbortProgram(EX_OSERR);
+    }
+
+#ifndef NONETGRAPH
+    if (*nauxfd) {
+      dev->cs = *auxfd;
+      (*nauxfd)--;
+    } else
+      dev->cs = -1;
+#endif
+
+    /* Refresh function pointers etc */
+    memcpy(&dev->dev, &basettydevice, sizeof dev->dev);
+
+    physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
+    if (dev->Timer.state != TIMER_STOPPED) {
+      dev->Timer.state = TIMER_STOPPED;
+      p->handler = &dev->dev;		/* For the benefit of StartTimer */
+      tty_StartTimer(p);
+    }
+    return &dev->dev;
+  }
+
+  return NULL;
+}
+
+struct device *
+tty_Create(struct physical *p)
+{
+  struct ttydevice *dev;
+  struct termios ios;
+  int oldflag;
+
+  if (p->fd < 0 || !isatty(p->fd))
+    /* Don't want this */
+    return NULL;
+
+  if (*p->name.full == '\0') {
+    physical_SetDevice(p, ttyname(p->fd));
+    log_Printf(LogDEBUG, "%s: Input is a tty (%s)\n",
+               p->link.name, p->name.full);
+  } else
+    log_Printf(LogDEBUG, "%s: Opened %s\n", p->link.name, p->name.full);
+
+  /* We're gonna return a ttydevice (unless something goes horribly wrong) */
+
+  if ((dev = malloc(sizeof *dev)) == NULL) {
+    /* Complete failure - parent doesn't continue trying to ``create'' */
+    close(p->fd);
+    p->fd = -1;
+    return NULL;
+  }
+
+  memcpy(&dev->dev, &basettydevice, sizeof dev->dev);
+  memset(&dev->Timer, '\0', sizeof dev->Timer);
+  dev->mbits = -1;
+#ifndef NONETGRAPH
+  dev->real.speed = 0;
+  dev->real.fd = -1;
+  dev->real.disc = -1;
+  *dev->hook = '\0';
+#endif
+  tcgetattr(p->fd, &ios);
+  dev->ios = ios;
+
+  if (p->cfg.cd.necessity != CD_DEFAULT)
+    /* Any override is ok for the tty device */
+    dev->dev.cd = p->cfg.cd;
+
+  log_Printf(LogDEBUG, "%s: tty_Create: physical (get): fd = %d,"
+             " iflag = %lx, oflag = %lx, cflag = %lx\n", p->link.name, p->fd,
+             (u_long)ios.c_iflag, (u_long)ios.c_oflag, (u_long)ios.c_cflag);
+
+  cfmakeraw(&ios);
+  if (p->cfg.rts_cts)
+    ios.c_cflag |= CLOCAL | CCTS_OFLOW | CRTS_IFLOW;
+  else {
+    ios.c_cflag |= CLOCAL;
+    ios.c_iflag |= IXOFF;
+  }
+  ios.c_iflag |= IXON;
+  if (p->type != PHYS_DEDICATED)
+    ios.c_cflag |= HUPCL;
+
+  if (p->type != PHYS_DIRECT) {
+      /* Change tty speed when we're not in -direct mode */
+      ios.c_cflag &= ~(CSIZE | PARODD | PARENB);
+      ios.c_cflag |= p->cfg.parity;
+      if (cfsetspeed(&ios, UnsignedToSpeed(p->cfg.speed)) == -1)
+	log_Printf(LogWARN, "%s: %s: Unable to set speed to %d\n",
+		  p->link.name, p->name.full, p->cfg.speed);
+  }
+
+  if (tcsetattr(p->fd, TCSADRAIN, &ios) == -1) {
+    log_Printf(LogWARN, "%s: tcsetattr: Failed configuring device\n",
+               p->link.name);
+    if (p->type != PHYS_DIRECT && p->cfg.speed > 115200)
+      log_Printf(LogWARN, "%.*s             Perhaps the speed is unsupported\n",
+                 (int)strlen(p->link.name), "");
+  }
+
+  log_Printf(LogDEBUG, "%s: physical (put): iflag = %lx, oflag = %lx, "
+            "cflag = %lx\n", p->link.name, (u_long)ios.c_iflag,
+            (u_long)ios.c_oflag, (u_long)ios.c_cflag);
+
+  oldflag = fcntl(p->fd, F_GETFL, 0);
+  if (oldflag < 0) {
+    /* Complete failure - parent doesn't continue trying to ``create'' */
+
+    log_Printf(LogWARN, "%s: Open: Cannot get physical flags: %s\n",
+               p->link.name, strerror(errno));
+    tty_Cooked(p);
+    close(p->fd);
+    p->fd = -1;
+    free(dev);
+    return NULL;
+  } else
+    fcntl(p->fd, F_SETFL, oldflag & ~O_NONBLOCK);
+
+  physical_SetupStack(p, dev->dev.name, PHYSICAL_NOFORCE);
+
+  return &dev->dev;
+}
diff --git a/src/tty.h b/src/tty.h
new file mode 100644
index 0000000..999b830
--- /dev/null
+++ b/src/tty.h
@@ -0,0 +1,37 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/tty.h,v 1.7.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct physical;
+struct device;
+
+#define DEF_TTYCDDELAY	1		/* Default ``set cd'' value */
+
+extern struct device *tty_Create(struct physical *);
+extern struct device *tty_iov2device(int, struct physical *,
+                                     struct iovec *, int *, int, int *, int *);
+extern unsigned tty_DeviceSize(void);
diff --git a/src/tun.c b/src/tun.c
new file mode 100644
index 0000000..5022156
--- /dev/null
+++ b/src/tun.c
@@ -0,0 +1,119 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/tun.c,v 1.28.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+
+#include <sys/socket.h>		/* For IFF_ defines */
+#ifndef __FreeBSD__
+#include <net/if.h>		/* For IFF_ defines */
+#endif
+#include <net/route.h>
+#include <netinet/in.h>
+#include <net/if_types.h>
+#include <net/if_tun.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <string.h>
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+#include <sys/ioctl.h>
+#endif
+#include <stdio.h>
+#include <termios.h>
+#ifdef __NetBSD__
+#include <unistd.h>
+#endif
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "id.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "defs.h"
+#include "fsm.h"
+#include "throughput.h"
+#include "iplist.h"
+#include "slcompress.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "mp.h"
+#include "iface.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "tun.h"
+
+void
+tun_configure(struct bundle *bundle)
+{
+#ifdef __NetBSD__
+  struct ifreq ifr;
+  int s;
+
+  s = socket(PF_INET, SOCK_DGRAM, 0);
+
+  if (s < 0) {
+    log_Printf(LogERROR, "tun_configure: socket(): %s\n", strerror(errno));
+    return;
+  }
+
+  sprintf(ifr.ifr_name, "tun%d", bundle->unit);
+  ifr.ifr_mtu = bundle->iface->mtu;
+  if (ioctl(s, SIOCSIFMTU, &ifr) < 0)
+      log_Printf(LogERROR, "tun_configure: ioctl(SIOCSIFMTU): %s\n",
+             strerror(errno));
+
+  close(s);
+#else
+  struct tuninfo info;
+
+  memset(&info, '\0', sizeof info);
+  info.type = IFT_PPP;
+  info.mtu = bundle->iface->mtu;
+
+  info.baudrate = bundle->bandwidth;
+#ifdef __OpenBSD__
+  info.flags = IFF_UP|IFF_POINTOPOINT|IFF_MULTICAST;
+#endif
+  if (ID0ioctl(bundle->dev.fd, TUNSIFINFO, &info) < 0)
+    log_Printf(LogERROR, "tun_configure: ioctl(TUNSIFINFO): %s\n",
+	      strerror(errno));
+#endif
+}
diff --git a/src/tun.h b/src/tun.h
new file mode 100644
index 0000000..b982f2b
--- /dev/null
+++ b/src/tun.h
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/tun.h,v 1.10.40.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct tun_data {
+  union {
+    u_int32_t family;
+    u_int32_t timeout;
+  } header;
+  u_char data[MAX_MRU];
+};
+
+struct bundle;
+
+extern void tun_configure(struct bundle *);
diff --git a/src/ua.h b/src/ua.h
new file mode 100644
index 0000000..e4e8346
--- /dev/null
+++ b/src/ua.h
@@ -0,0 +1,74 @@
+/*-
+ * Copyright (c) 1998 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/ua.h,v 1.3.62.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#ifdef __i386__		/* Do any other archs not care about alignment ? */
+
+#  define ua_htonl(src, tgt) (*(u_int32_t *)(tgt) = htonl(*(u_int32_t *)(src)))
+#  define ua_ntohl(src, tgt) (*(u_int32_t *)(tgt) = ntohl(*(u_int32_t *)(src)))
+#  define ua_htons(src, tgt) (*(u_int16_t *)(tgt) = htons(*(u_int16_t *)(src)))
+#  define ua_ntohs(src, tgt) (*(u_int16_t *)(tgt) = ntohs(*(u_int16_t *)(src)))
+
+#else	/* We care about alignment (or else drop a core !) */
+
+#  define ua_htonl(src, tgt)				\
+    do {						\
+      u_int32_t __oh;					\
+      memcpy(&__oh, (src), sizeof __oh);		\
+      *(u_char *)(tgt) = __oh >> 24;			\
+      *((u_char *)(tgt) + 1) = (__oh >> 16) & 0xff;	\
+      *((u_char *)(tgt) + 2) = (__oh >> 8) & 0xff;	\
+      *((u_char *)(tgt) + 3) = __oh & 0xff;		\
+    } while (0)
+
+#  define ua_ntohl(src, tgt)				\
+    do {						\
+      u_int32_t __nh;					\
+      __nh = ((u_int32_t)*(u_char *)(src) << 24) |	\
+          ((u_int32_t)*((u_char *)(src) + 1) << 16) |	\
+          ((u_int32_t)*((u_char *)(src) + 2) << 8) |	\
+          (u_int32_t)*((u_char *)(src) + 3);		\
+      memcpy((tgt), &__nh, sizeof __nh);		\
+    } while (0)
+
+#  define ua_htons(src, tgt)				\
+    do {						\
+      u_int16_t __oh;					\
+      memcpy(&__oh, (src), sizeof __oh);		\
+      *(u_char *)(tgt) = __oh >> 8;			\
+      *((u_char *)(tgt) + 1) = __oh & 0xff;		\
+    } while (0)
+
+#  define ua_ntohs(src, tgt)				\
+    do {						\
+      u_int16_t __nh;					\
+      __nh = ((u_int16_t)*(u_char *)(src) << 8) |	\
+          (u_int16_t)*((u_char *)(src) + 1);		\
+      memcpy((tgt), &__nh, sizeof __nh);		\
+    } while (0)
+
+#endif
diff --git a/src/udp.c b/src/udp.c
new file mode 100644
index 0000000..075f6b2
--- /dev/null
+++ b/src/udp.c
@@ -0,0 +1,335 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/udp.c,v 1.20.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "layer.h"
+#include "defs.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "throughput.h"
+#include "fsm.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "async.h"
+#include "descriptor.h"
+#include "physical.h"
+#include "main.h"
+#include "udp.h"
+
+
+#define UDP_CONNECTED		1
+#define UDP_UNCONNECTED		2
+#define UDP_MAYBEUNCONNECTED	3
+
+struct udpdevice {
+  struct device dev;		/* What struct physical knows about */
+  struct sockaddr_in sock;	/* peer address */
+  unsigned connected : 2;	/* Have we connect()d ? */
+};
+
+#define device2udp(d) ((d)->type == UDP_DEVICE ? (struct udpdevice *)d : NULL)
+
+unsigned
+udp_DeviceSize(void)
+{
+  return sizeof(struct udpdevice);
+}
+
+static ssize_t
+udp_Sendto(struct physical *p, const void *v, size_t n)
+{
+  struct udpdevice *dev = device2udp(p->handler);
+  int ret;
+
+  switch (dev->connected) {
+    case UDP_CONNECTED:
+      ret = write(p->fd, v, n);
+      break;
+
+    case UDP_UNCONNECTED:
+    default:
+      ret = sendto(p->fd, v, n, 0, (struct sockaddr *)&dev->sock,
+                   sizeof dev->sock);
+      break;
+  }
+  if (dev->connected == UDP_MAYBEUNCONNECTED) {
+    if (ret == -1 && errno == EISCONN) {
+      dev->connected = UDP_CONNECTED;
+      ret = write(p->fd, v, n);
+    } else
+      dev->connected = UDP_UNCONNECTED;
+  }
+
+  return ret;
+}
+
+static ssize_t
+udp_Recvfrom(struct physical *p, void *v, size_t n)
+{
+  struct udpdevice *dev = device2udp(p->handler);
+  int sz, ret;
+
+  if (dev->connected == UDP_CONNECTED)
+    return read(p->fd, v, n);
+
+  sz = sizeof dev->sock;
+  ret = recvfrom(p->fd, v, n, 0, (struct sockaddr *)&dev->sock, &sz);
+
+  if (*p->name.full == '\0') {
+    snprintf(p->name.full, sizeof p->name.full, "%s:%d/udp",
+             inet_ntoa(dev->sock.sin_addr), ntohs(dev->sock.sin_port));
+    p->name.base = p->name.full;
+  }
+
+  return ret;
+}
+
+static void
+udp_Free(struct physical *p)
+{
+  struct udpdevice *dev = device2udp(p->handler);
+
+  free(dev);
+}
+
+static void
+udp_device2iov(struct device *d, struct iovec *iov, int *niov,
+               int maxiov __unused, int *auxfd __unused, int *nauxfd __unused)
+{
+  int sz = physical_MaxDeviceSize();
+
+  iov[*niov].iov_base = realloc(d, sz);
+  if (iov[*niov].iov_base == NULL) {
+    log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz);
+    AbortProgram(EX_OSERR);
+  }
+  iov[*niov].iov_len = sz;
+  (*niov)++;
+}
+
+static const struct device baseudpdevice = {
+  UDP_DEVICE,
+  "udp",
+  0,
+  { CD_NOTREQUIRED, 0 },
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  udp_Free,
+  udp_Recvfrom,
+  udp_Sendto,
+  udp_device2iov,
+  NULL,
+  NULL,
+  NULL
+};
+
+struct device *
+udp_iov2device(int type, struct physical *p, struct iovec *iov, int *niov,
+               int maxiov __unused, int *auxfd __unused, int *nauxfd __unused)
+{
+  if (type == UDP_DEVICE) {
+    struct udpdevice *dev = (struct udpdevice *)iov[(*niov)++].iov_base;
+
+    dev = realloc(dev, sizeof *dev);	/* Reduce to the correct size */
+    if (dev == NULL) {
+      log_Printf(LogALERT, "Failed to allocate memory: %d\n",
+                 (int)(sizeof *dev));
+      AbortProgram(EX_OSERR);
+    }
+
+    /* Refresh function pointers etc */
+    memcpy(&dev->dev, &baseudpdevice, sizeof dev->dev);
+
+    physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNC);
+    return &dev->dev;
+  }
+
+  return NULL;
+}
+
+static struct udpdevice *
+udp_CreateDevice(struct physical *p, char *host, char *port)
+{
+  struct udpdevice *dev;
+  struct servent *sp;
+
+  if ((dev = malloc(sizeof *dev)) == NULL) {
+    log_Printf(LogWARN, "%s: Cannot allocate a udp device: %s\n",
+               p->link.name, strerror(errno));
+    return NULL;
+  }
+
+  dev->sock.sin_family = AF_INET;
+  dev->sock.sin_addr = GetIpAddr(host);
+  if (dev->sock.sin_addr.s_addr == INADDR_NONE) {
+    log_Printf(LogWARN, "%s: %s: unknown host\n", p->link.name, host);
+    free(dev);
+    return NULL;
+  }
+  dev->sock.sin_port = htons(atoi(port));
+  if (dev->sock.sin_port == 0) {
+    sp = getservbyname(port, "udp");
+    if (sp)
+      dev->sock.sin_port = sp->s_port;
+    else {
+      log_Printf(LogWARN, "%s: %s: unknown service\n", p->link.name, port);
+      free(dev);
+      return NULL;
+    }
+  }
+
+  log_Printf(LogPHASE, "%s: Connecting to %s:%s/udp\n", p->link.name,
+             host, port);
+
+  p->fd = socket(PF_INET, SOCK_DGRAM, 0);
+  if (p->fd >= 0) {
+    log_Printf(LogDEBUG, "%s: Opened udp socket %s\n", p->link.name,
+               p->name.full);
+    if (connect(p->fd, (struct sockaddr *)&dev->sock, sizeof dev->sock) == 0) {
+      dev->connected = UDP_CONNECTED;
+      return dev;
+    } else
+      log_Printf(LogWARN, "%s: connect: %s\n", p->name.full, strerror(errno));
+  } else
+    log_Printf(LogWARN, "%s: socket: %s\n", p->name.full, strerror(errno));
+
+  close(p->fd);
+  p->fd = -1;
+  free(dev);
+
+  return NULL;
+}
+
+struct device *
+udp_Create(struct physical *p)
+{
+  char *cp, *host, *port, *svc;
+  struct udpdevice *dev;
+
+  dev = NULL;
+  if (p->fd < 0) {
+    if ((cp = strchr(p->name.full, ':')) != NULL && !strchr(cp + 1, ':')) {
+      *cp = '\0';
+      host = p->name.full;
+      port = cp + 1;
+      svc = strchr(port, '/');
+      if (svc && strcasecmp(svc, "/udp")) {
+        *cp = ':';
+        return NULL;
+      }
+      if (svc) {
+        p->fd--;     /* We own the device but maybe can't use it - change fd */
+        *svc = '\0';
+      }
+
+      if (*host && *port)
+        dev = udp_CreateDevice(p, host, port);
+
+      *cp = ':';
+      if (svc)
+        *svc = '/';
+    }
+  } else {
+    /* See if we're a connected udp socket */
+    struct stat st;
+
+    if (fstat(p->fd, &st) != -1 && (st.st_mode & S_IFSOCK)) {
+      int type, sz;
+
+      sz = sizeof type;
+      if (getsockopt(p->fd, SOL_SOCKET, SO_TYPE, &type, &sz) == -1) {
+        log_Printf(LogPHASE, "%s: Link is a closed socket !\n", p->link.name);
+        close(p->fd);
+        p->fd = -1;
+        return NULL;
+      }
+
+      if (sz == sizeof type && type == SOCK_DGRAM) {
+        struct sockaddr_in sock;
+        struct sockaddr *sockp = (struct sockaddr *)&sock;
+
+        if ((dev = malloc(sizeof *dev)) == NULL) {
+          log_Printf(LogWARN, "%s: Cannot allocate a udp device: %s\n",
+                     p->link.name, strerror(errno));
+          return NULL;
+        }
+
+        if (getpeername(p->fd, sockp, &sz) == 0) {
+          log_Printf(LogPHASE, "%s: Link is a connected udp socket\n",
+                     p->link.name);
+          dev->connected = UDP_CONNECTED;
+	} else {
+          log_Printf(LogPHASE, "%s: Link is a disconnected udp socket\n",
+                     p->link.name);
+
+          dev->connected = UDP_MAYBEUNCONNECTED;
+
+          if (p->link.lcp.cfg.openmode != OPEN_PASSIVE) {
+            log_Printf(LogPHASE, "%s:   Changing openmode to PASSIVE\n",
+                       p->link.name);
+            p->link.lcp.cfg.openmode = OPEN_PASSIVE;
+          }
+        }
+      }
+    }
+  }
+
+  if (dev) {
+    memcpy(&dev->dev, &baseudpdevice, sizeof dev->dev);
+    physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNC);
+    if (p->cfg.cd.necessity != CD_DEFAULT)
+      log_Printf(LogWARN, "Carrier settings ignored\n");
+    return &dev->dev;
+  }
+
+  return NULL;
+}
diff --git a/src/udp.h b/src/udp.h
new file mode 100644
index 0000000..7e11b36
--- /dev/null
+++ b/src/udp.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 1999 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/udp.h,v 1.5.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct physical;
+struct device;
+
+extern struct device *udp_Create(struct physical *);
+extern struct device *udp_iov2device(int, struct physical *,
+                                     struct iovec *, int *, int, int *, int *);
+extern unsigned udp_DeviceSize(void);
diff --git a/src/vjcomp.c b/src/vjcomp.c
new file mode 100644
index 0000000..db8fe2c
--- /dev/null
+++ b/src/vjcomp.c
@@ -0,0 +1,200 @@
+/*-
+ * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org>
+ *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp>
+ *                           Internet Initiative Japan, Inc (IIJ)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/vjcomp.c,v 1.40.26.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <stdio.h>
+#include <string.h>		/* strlen/memcpy */
+#include <termios.h>
+
+#include "layer.h"
+#include "mbuf.h"
+#include "log.h"
+#include "timer.h"
+#include "fsm.h"
+#include "proto.h"
+#include "slcompress.h"
+#include "lqr.h"
+#include "hdlc.h"
+#include "defs.h"
+#include "iplist.h"
+#include "throughput.h"
+#include "ncpaddr.h"
+#include "ipcp.h"
+#include "lcp.h"
+#include "ccp.h"
+#include "link.h"
+#include "filter.h"
+#include "descriptor.h"
+#include "mp.h"
+#ifndef NORADIUS
+#include "radius.h"
+#endif
+#include "ipv6cp.h"
+#include "ncp.h"
+#include "bundle.h"
+#include "vjcomp.h"
+
+#define MAX_VJHEADER 16		/* Maximum size of compressed header */
+
+static struct mbuf *
+vj_LayerPush(struct bundle *bundle, struct link *l __unused, struct mbuf *bp,
+	     int pri __unused, u_short *proto)
+{
+  int type;
+  struct ip *pip;
+  u_short cproto = bundle->ncp.ipcp.peer_compproto >> 16;
+
+  bp = m_pullup(bp);
+  pip = (struct ip *)MBUF_CTOP(bp);
+  if (*proto == PROTO_IP && pip->ip_p == IPPROTO_TCP &&
+      cproto == PROTO_VJCOMP) {
+    type = sl_compress_tcp(bp, pip, &bundle->ncp.ipcp.vj.cslc,
+                           &bundle->ncp.ipcp.vj.slstat,
+                           bundle->ncp.ipcp.peer_compproto & 0xff);
+    log_Printf(LogDEBUG, "vj_LayerWrite: type = %x\n", type);
+    switch (type) {
+    case TYPE_IP:
+      break;
+
+    case TYPE_UNCOMPRESSED_TCP:
+      *proto = PROTO_VJUNCOMP;
+      log_Printf(LogDEBUG, "vj_LayerPush: PROTO_IP -> PROTO_VJUNCOMP\n");
+      m_settype(bp, MB_VJOUT);
+      break;
+
+    case TYPE_COMPRESSED_TCP:
+      *proto = PROTO_VJCOMP;
+      log_Printf(LogDEBUG, "vj_LayerPush: PROTO_IP -> PROTO_VJUNCOMP\n");
+      m_settype(bp, MB_VJOUT);
+      break;
+
+    default:
+      log_Printf(LogERROR, "vj_LayerPush: Unknown frame type %x\n", type);
+      m_freem(bp);
+      return NULL;
+    }
+  }
+
+  return bp;
+}
+
+static struct mbuf *
+VjUncompressTcp(struct ipcp *ipcp, struct mbuf *bp, u_char type)
+{
+  u_char *bufp;
+  int len, olen, rlen;
+  u_char work[MAX_HDR + MAX_VJHEADER];	/* enough to hold TCP/IP header */
+
+  bp = m_pullup(bp);
+  olen = len = m_length(bp);
+  if (type == TYPE_UNCOMPRESSED_TCP) {
+    /*
+     * Uncompressed packet does NOT change its size, so that we can use mbuf
+     * space for uncompression job.
+     */
+    bufp = MBUF_CTOP(bp);
+    len = sl_uncompress_tcp(&bufp, len, type, &ipcp->vj.cslc, &ipcp->vj.slstat,
+                            (ipcp->my_compproto >> 8) & 255);
+    if (len <= 0) {
+      m_freem(bp);
+      bp = NULL;
+    } else
+      m_settype(bp, MB_VJIN);
+    return bp;
+  }
+
+  /*
+   * Handle compressed packet. 1) Read upto MAX_VJHEADER bytes into work
+   * space. 2) Try to uncompress it. 3) Compute amount of necessary space. 4)
+   * Copy unread data info there.
+   */
+  if (len > MAX_VJHEADER)
+    len = MAX_VJHEADER;
+  rlen = len;
+  bufp = work + MAX_HDR;
+  bp = mbuf_Read(bp, bufp, rlen);
+  len = sl_uncompress_tcp(&bufp, olen, type, &ipcp->vj.cslc, &ipcp->vj.slstat,
+                          (ipcp->my_compproto >> 8) & 255);
+  if (len <= 0) {
+    m_freem(bp);
+    return NULL;
+  }
+  len -= olen;
+  len += rlen;
+
+  bp = m_prepend(bp, bufp, len, 0);
+  m_settype(bp, MB_VJIN);
+
+  return bp;
+}
+
+static struct mbuf *
+vj_LayerPull(struct bundle *bundle, struct link *l __unused, struct mbuf *bp,
+             u_short *proto)
+{
+  u_char type;
+
+  switch (*proto) {
+  case PROTO_VJCOMP:
+    type = TYPE_COMPRESSED_TCP;
+    log_Printf(LogDEBUG, "vj_LayerPull: PROTO_VJCOMP -> PROTO_IP\n");
+    break;
+  case PROTO_VJUNCOMP:
+    type = TYPE_UNCOMPRESSED_TCP;
+    log_Printf(LogDEBUG, "vj_LayerPull: PROTO_VJUNCOMP -> PROTO_IP\n");
+    break;
+  default:
+    return bp;
+  }
+
+  *proto = PROTO_IP;
+  return VjUncompressTcp(&bundle->ncp.ipcp, bp, type);
+}
+
+const char *
+vj2asc(u_int32_t val)
+{
+  static char asc[50];		/* The return value is used immediately */
+
+  if (val)
+    snprintf(asc, sizeof asc, "%d VJ slots with%s slot compression",
+            (int)((val>>8)&15)+1, val & 1 ?  "" : "out");
+  else
+    strcpy(asc, "VJ disabled");
+  return asc;
+}
+
+struct layer vjlayer = { LAYER_VJ, "vj", vj_LayerPush, vj_LayerPull };
diff --git a/src/vjcomp.h b/src/vjcomp.h
new file mode 100644
index 0000000..66369e6
--- /dev/null
+++ b/src/vjcomp.h
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 1997 Brian Somers <brian@Awfulhak.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD: src/usr.sbin/ppp/vjcomp.h,v 1.8.62.1 2010/12/21 17:10:29 kensmith Exp $
+ */
+
+struct mbuf;
+struct link;
+struct ipcp;
+struct bundle;
+
+extern const char *vj2asc(u_int32_t);
+
+extern struct layer vjlayer;