diff options
Diffstat (limited to 'usr.sbin/ppp')
126 files changed, 50277 insertions, 0 deletions
diff --git a/usr.sbin/ppp/Makefile b/usr.sbin/ppp/Makefile new file mode 100644 index 0000000..11003d7 --- /dev/null +++ b/usr.sbin/ppp/Makefile @@ -0,0 +1,122 @@ +# $FreeBSD$ + +.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_KLDLOAD= +PPP_NO_NAT= +PPP_NO_PAM= +PPP_NO_RADIUS= +PPP_NO_SUID= +.endif + +.if ${MK_ATM} == "no" +PPP_NO_ATM= +.endif +.if ${MK_NETGRAPH} == "no" +PPP_NO_NETGRAPH= +.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_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/usr.sbin/ppp/README.changes b/usr.sbin/ppp/README.changes new file mode 100644 index 0000000..bf626e3 --- /dev/null +++ b/usr.sbin/ppp/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$ + +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/usr.sbin/ppp/README.nat b/usr.sbin/ppp/README.nat new file mode 100644 index 0000000..9ff240a9 --- /dev/null +++ b/usr.sbin/ppp/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$ + +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/usr.sbin/ppp/acf.c b/usr.sbin/ppp/acf.c new file mode 100644 index 0000000..6c3ec05 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/acf.h b/usr.sbin/ppp/acf.h new file mode 100644 index 0000000..e32adbc --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +struct lcp; + +extern int acf_WrapperOctets(struct lcp *, u_short); + +extern struct layer acflayer; diff --git a/usr.sbin/ppp/arp.c b/usr.sbin/ppp/arp.c new file mode 100644 index 0000000..e67f89b --- /dev/null +++ b/usr.sbin/ppp/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$ + * + */ + +/* + * 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 | RTF_LLDATA; + 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/usr.sbin/ppp/arp.h b/usr.sbin/ppp/arp.h new file mode 100644 index 0000000..81e3fa6 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/async.c b/usr.sbin/ppp/async.c new file mode 100644 index 0000000..a2a8e04 --- /dev/null +++ b/usr.sbin/ppp/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$"); + +#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/usr.sbin/ppp/async.h b/usr.sbin/ppp/async.h new file mode 100644 index 0000000..309f597 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/atm.c b/usr.sbin/ppp/atm.c new file mode 100644 index 0000000..6e0ce75 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/atm.h b/usr.sbin/ppp/atm.h new file mode 100644 index 0000000..ccfad8d --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/auth.c b/usr.sbin/ppp/auth.c new file mode 100644 index 0000000..66a3de7 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/auth.h b/usr.sbin/ppp/auth.h new file mode 100644 index 0000000..3e1047d --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/bundle.c b/usr.sbin/ppp/bundle.c new file mode 100644 index 0000000..912f855 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/bundle.h b/usr.sbin/ppp/bundle.h new file mode 100644 index 0000000..e2f9e7f --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/cbcp.c b/usr.sbin/ppp/cbcp.c new file mode 100644 index 0000000..be68e48 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/cbcp.h b/usr.sbin/ppp/cbcp.h new file mode 100644 index 0000000..46bf274 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/ccp.c b/usr.sbin/ppp/ccp.c new file mode 100644 index 0000000..f5bbf0c --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/ccp.h b/usr.sbin/ppp/ccp.h new file mode 100644 index 0000000..bc867b6 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/chap.c b/usr.sbin/ppp/chap.c new file mode 100644 index 0000000..75de650 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/chap.h b/usr.sbin/ppp/chap.h new file mode 100644 index 0000000..08c6865 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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 - (uintptr_t)&((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/usr.sbin/ppp/chap_ms.c b/usr.sbin/ppp/chap_ms.c new file mode 100644 index 0000000..24e959c --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/chap_ms.h b/usr.sbin/ppp/chap_ms.h new file mode 100644 index 0000000..8a69f93 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +/* 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/usr.sbin/ppp/chat.c b/usr.sbin/ppp/chat.c new file mode 100644 index 0000000..04b2679 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/chat.h b/usr.sbin/ppp/chat.h new file mode 100644 index 0000000..b8162bf --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/command.c b/usr.sbin/ppp/command.c new file mode 100644 index 0000000..cc23518 --- /dev/null +++ b/usr.sbin/ppp/command.c @@ -0,0 +1,3312 @@ +/*- + * 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$ + */ + +#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 + +extern struct libalias *la; + +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) + LibAliasSetAddress(la, 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) { + LibAliasSetMode(la, param, param); + return 0; + } + log_Printf(LogWARN, "nat not enabled\n"); + } else if (strcmp(arg->argv[arg->argn], "no") == 0) { + if (arg->bundle->NatEnabled) { + LibAliasSetMode(la, 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/usr.sbin/ppp/command.h b/usr.sbin/ppp/command.h new file mode 100644 index 0000000..8eb1e38 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/datalink.c b/usr.sbin/ppp/datalink.c new file mode 100644 index 0000000..d47b11a --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/datalink.h b/usr.sbin/ppp/datalink.h new file mode 100644 index 0000000..6fd7e9b --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/deflate.c b/usr.sbin/ppp/deflate.c new file mode 100644 index 0000000..57c9004 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/deflate.h b/usr.sbin/ppp/deflate.h new file mode 100644 index 0000000..a478b24 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +extern const struct ccp_algorithm PppdDeflateAlgorithm; +extern const struct ccp_algorithm DeflateAlgorithm; diff --git a/usr.sbin/ppp/defs.c b/usr.sbin/ppp/defs.c new file mode 100644 index 0000000..dcd9e47 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + + +#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/usr.sbin/ppp/defs.h b/usr.sbin/ppp/defs.h new file mode 100644 index 0000000..f56ee32 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +/* 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/usr.sbin/ppp/descriptor.h b/usr.sbin/ppp/descriptor.h new file mode 100644 index 0000000..a3c1b10 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/ether.c b/usr.sbin/ppp/ether.c new file mode 100644 index 0000000..a86f3bd --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/ether.h b/usr.sbin/ppp/ether.h new file mode 100644 index 0000000..94511bd --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/exec.c b/usr.sbin/ppp/exec.c new file mode 100644 index 0000000..a53db37 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/exec.h b/usr.sbin/ppp/exec.h new file mode 100644 index 0000000..32bd748 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/filter.c b/usr.sbin/ppp/filter.c new file mode 100644 index 0000000..f59848e --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/filter.h b/usr.sbin/ppp/filter.h new file mode 100644 index 0000000..ce67420 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +/* 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/usr.sbin/ppp/fsm.c b/usr.sbin/ppp/fsm.c new file mode 100644 index 0000000..78530b9 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/fsm.h b/usr.sbin/ppp/fsm.h new file mode 100644 index 0000000..d233899 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +/* + * 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/usr.sbin/ppp/hdlc.c b/usr.sbin/ppp/hdlc.c new file mode 100644 index 0000000..517c3c5 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/hdlc.h b/usr.sbin/ppp/hdlc.h new file mode 100644 index 0000000..b9211a6 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +/* + * 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/usr.sbin/ppp/i4b.h b/usr.sbin/ppp/i4b.h new file mode 100644 index 0000000..3545c84 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/id.c b/usr.sbin/ppp/id.c new file mode 100644 index 0000000..ec66574 --- /dev/null +++ b/usr.sbin/ppp/id.c @@ -0,0 +1,292 @@ +/*- + * 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$ + */ + +#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 <utmpx.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(const struct utmpx *ut) +{ + ID0set0(); + pututxline(ut); + log_Printf(LogID0, "pututxline(\"%.*s\", \"%.*s\", \"%.*s\", \"%.*s\")\n", + (int)sizeof ut->ut_id, ut->ut_id, + (int)sizeof ut->ut_user, ut->ut_user, + (int)sizeof ut->ut_line, ut->ut_line, + (int)sizeof ut->ut_host, ut->ut_host); + ID0setuser(); +} + +void +ID0logout(const struct utmpx *ut) +{ + ID0set0(); + pututxline(ut); + log_Printf(LogID0, "pututxline(\"%.*s\")\n", + (int)sizeof ut->ut_id, ut->ut_id); + 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/usr.sbin/ppp/id.h b/usr.sbin/ppp/id.h new file mode 100644 index 0000000..2357dc7 --- /dev/null +++ b/usr.sbin/ppp/id.h @@ -0,0 +1,81 @@ +/*- + * 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$ + */ + +#ifndef NOSUID +struct utmpx; +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(const struct utmpx *); +extern void ID0logout(const struct utmpx *); +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 pututxline +#define ID0logout pututxline +#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/usr.sbin/ppp/iface.c b/usr.sbin/ppp/iface.c new file mode 100644 index 0000000..32bb590 --- /dev/null +++ b/usr.sbin/ppp/iface.c @@ -0,0 +1,730 @@ +/*- + * 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$ + */ + +#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); + ncp_IfaceAddrAdded(ncp, iface->addr + n); + 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/usr.sbin/ppp/iface.h b/usr.sbin/ppp/iface.h new file mode 100644 index 0000000..1fd0a70 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/ip.c b/usr.sbin/ppp/ip.c new file mode 100644 index 0000000..5cd2272 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/ip.h b/usr.sbin/ppp/ip.h new file mode 100644 index 0000000..a4c4179 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/ipcp.c b/usr.sbin/ppp/ipcp.c new file mode 100644 index 0000000..31aa07d --- /dev/null +++ b/usr.sbin/ppp/ipcp.c @@ -0,0 +1,1479 @@ +/*- + * 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$ + */ + +#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 *); + +extern struct libalias *la; + +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, NULL, NULL); + } + + 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) + LibAliasSetAddress(la, 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/usr.sbin/ppp/ipcp.h b/usr.sbin/ppp/ipcp.h new file mode 100644 index 0000000..dc1ed9e --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/iplist.c b/usr.sbin/ppp/iplist.c new file mode 100644 index 0000000..a7fae6e --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/iplist.h b/usr.sbin/ppp/iplist.h new file mode 100644 index 0000000..5805a2c --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/ipv6cp.c b/usr.sbin/ppp/ipv6cp.c new file mode 100644 index 0000000..4557188 --- /dev/null +++ b/usr.sbin/ppp/ipv6cp.c @@ -0,0 +1,785 @@ +/*- + * 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$ + */ + +#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: + /* 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, NULL, NULL); + } + + 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/usr.sbin/ppp/ipv6cp.h b/usr.sbin/ppp/ipv6cp.h new file mode 100644 index 0000000..53f7153 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/layer.h b/usr.sbin/ppp/layer.h new file mode 100644 index 0000000..4ee59a1 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/lcp.c b/usr.sbin/ppp/lcp.c new file mode 100644 index 0000000..cf75718 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/lcp.h b/usr.sbin/ppp/lcp.h new file mode 100644 index 0000000..e0382a8 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +/* 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/usr.sbin/ppp/link.c b/usr.sbin/ppp/link.c new file mode 100644 index 0000000..dc5507d --- /dev/null +++ b/usr.sbin/ppp/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$ + * + */ + +#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/usr.sbin/ppp/link.h b/usr.sbin/ppp/link.h new file mode 100644 index 0000000..822474b --- /dev/null +++ b/usr.sbin/ppp/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$ + * + */ + + +#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/usr.sbin/ppp/log.c b/usr.sbin/ppp/log.c new file mode 100644 index 0000000..c90720b --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/log.h b/usr.sbin/ppp/log.h new file mode 100644 index 0000000..0da4b8c --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/lqr.c b/usr.sbin/ppp/lqr.c new file mode 100644 index 0000000..cb04741 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/lqr.h b/usr.sbin/ppp/lqr.h new file mode 100644 index 0000000..13d378f --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +/* + * 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/usr.sbin/ppp/main.c b/usr.sbin/ppp/main.c new file mode 100644 index 0000000..fd826d0 --- /dev/null +++ b/usr.sbin/ppp/main.c @@ -0,0 +1,679 @@ +/*- + * 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$ + */ + +#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; +struct libalias *la; + +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 + la = LibAliasInit(NULL); +#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/usr.sbin/ppp/main.h b/usr.sbin/ppp/main.h new file mode 100644 index 0000000..0c7399a --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +extern void Cleanup(void); +extern void AbortProgram(int); diff --git a/usr.sbin/ppp/mbuf.c b/usr.sbin/ppp/mbuf.c new file mode 100644 index 0000000..4773224 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/mbuf.h b/usr.sbin/ppp/mbuf.h new file mode 100644 index 0000000..980d649 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/mp.c b/usr.sbin/ppp/mp.c new file mode 100644 index 0000000..095f9bb --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/mp.h b/usr.sbin/ppp/mp.h new file mode 100644 index 0000000..7cf5a8e --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/mppe.c b/usr.sbin/ppp/mppe.c new file mode 100644 index 0000000..141574f --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/mppe.h b/usr.sbin/ppp/mppe.h new file mode 100644 index 0000000..c70a609 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/nat_cmd.c b/usr.sbin/ppp/nat_cmd.c new file mode 100644 index 0000000..48f894a --- /dev/null +++ b/usr.sbin/ppp/nat_cmd.c @@ -0,0 +1,602 @@ +/*- + * 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$ + */ + +#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 *); + +extern struct libalias *la; + +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 = LibAliasRedirectPort(la, 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 = LibAliasRedirectAddr(la, 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 = LibAliasRedirectProto(la, 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 LibAliasProxyRule(la, cmd); +} + +int +nat_SetTarget(struct cmdargs const *arg) +{ + struct in_addr addr; + + if (arg->argc == arg->argn) { + addr.s_addr = INADDR_ANY; + LibAliasSetTarget(la, addr); + return 0; + } + + if (arg->argc != arg->argn + 1) + return -1; + + if (!strcasecmp(arg->argv[arg->argn], "MYADDR")) { + addr.s_addr = INADDR_ANY; + LibAliasSetTarget(la, 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; + } + + LibAliasSetTarget(la, addr); + return 0; +} + +#ifndef NO_FW_PUNCH +int +nat_PunchFW(struct cmdargs const *arg) +{ + char *end; + long base, count; + + if (arg->argc == arg->argn) { + LibAliasSetMode(la, 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; + + LibAliasSetFWBase(la, base, count); + LibAliasSetMode(la, 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) { + LibAliasSetSkinnyPort(la, 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; + + LibAliasSetSkinnyPort(la, 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)); + LibAliasOut(la, 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 = LibAliasIn(la, 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); + LibAliasSaveFragment(la, 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 = LibAliasGetFragment(la, MBUF_CTOP(bp))) != NULL) { + nfrags++; + LibAliasFragmentIn(la, 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 (LibAliasSetMode(la, 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/usr.sbin/ppp/nat_cmd.h b/usr.sbin/ppp/nat_cmd.h new file mode 100644 index 0000000..f4c3655 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/ncp.c b/usr.sbin/ppp/ncp.c new file mode 100644 index 0000000..1c12fc5 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/ncp.h b/usr.sbin/ppp/ncp.h new file mode 100644 index 0000000..99580cf --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/ncpaddr.c b/usr.sbin/ppp/ncpaddr.c new file mode 100644 index 0000000..0b16999 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/ncpaddr.h b/usr.sbin/ppp/ncpaddr.h new file mode 100644 index 0000000..8c6b886 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +/* + * 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/usr.sbin/ppp/netgraph.c b/usr.sbin/ppp/netgraph.c new file mode 100644 index 0000000..23a575b --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/netgraph.h b/usr.sbin/ppp/netgraph.h new file mode 100644 index 0000000..a5b4082 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/pap.c b/usr.sbin/ppp/pap.c new file mode 100644 index 0000000..8eda020 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/pap.h b/usr.sbin/ppp/pap.h new file mode 100644 index 0000000..8e8d457 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/physical.c b/usr.sbin/ppp/physical.c new file mode 100644 index 0000000..4083aa9 --- /dev/null +++ b/usr.sbin/ppp/physical.c @@ -0,0 +1,1141 @@ +/* + * 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$ + * + */ + +#include <sys/param.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <sys/socket.h> +#include <sys/time.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/uio.h> +#include <sysexits.h> +#include <termios.h> +#include <time.h> +#include <unistd.h> +#include <utmpx.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 NONETGRAPH +#include "ether.h" +#include "netgraph.h" +#endif +#ifndef NOATM +#include "atm.h" +#endif +#include "tcpmss.h" + +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[] = { + { 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]; + struct utmpx ut; + + 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) { + memset(&ut, 0, sizeof ut); + ut.ut_type = DEAD_PROCESS; + gettimeofday(&ut.ut_tv, NULL); + snprintf(ut.ut_id, sizeof ut.ut_id, "%xppp", (int)getpid()); + ID0logout(&ut); + 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 utmpx ut; + const char *connstr; + char *colon; + + memset(&ut, 0, sizeof ut); + ut.ut_type = USER_PROCESS; + gettimeofday(&ut.ut_tv, NULL); + snprintf(ut.ut_id, sizeof ut.ut_id, "%xppp", (int)getpid()); + strncpy(ut.ut_user, name, sizeof ut.ut_user); + if (p->handler && (p->handler->type == TCP_DEVICE || + p->handler->type == UDP_DEVICE)) { + 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 = 1; + } +} + +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/usr.sbin/ppp/physical.h b/usr.sbin/ppp/physical.h new file mode 100644 index 0000000..cf3408e --- /dev/null +++ b/usr.sbin/ppp/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$ + * + */ + +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; + + int 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 - (uintptr_t)(&((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/usr.sbin/ppp/ppp.8.m4 b/usr.sbin/ppp/ppp.8.m4 new file mode 100644 index 0000000..a711d58 --- /dev/null +++ b/usr.sbin/ppp/ppp.8.m4 @@ -0,0 +1,6117 @@ +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$ +.\" +.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. +Sometimes, +.Em PPP +is implemented as a part of the kernel (e.g., as managed by +.Nm pppd ) +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 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 \< , +.Sq \&eq +or +.Sq \> , +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 +.Nm pppd +- 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 +.Nm pppd +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, +.Nm pppd +(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 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 serial device names are expected to begin with +.Pa /dev/ . +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 named 8 , +.Xr ping 8 , +.Xr pppctl 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/usr.sbin/ppp/pred.c b/usr.sbin/ppp/pred.c new file mode 100644 index 0000000..43d5d94 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/pred.h b/usr.sbin/ppp/pred.h new file mode 100644 index 0000000..1afb77a --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +extern const struct ccp_algorithm Pred1Algorithm; diff --git a/usr.sbin/ppp/probe.c b/usr.sbin/ppp/probe.c new file mode 100644 index 0000000..a33734c --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/probe.h b/usr.sbin/ppp/probe.h new file mode 100644 index 0000000..5a7dce9 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/prompt.c b/usr.sbin/ppp/prompt.c new file mode 100644 index 0000000..de331ef --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/prompt.h b/usr.sbin/ppp/prompt.h new file mode 100644 index 0000000..0489338 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/proto.c b/usr.sbin/ppp/proto.c new file mode 100644 index 0000000..2c2c26d --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/proto.h b/usr.sbin/ppp/proto.h new file mode 100644 index 0000000..8c93327 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +/* + * 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/usr.sbin/ppp/radius.c b/usr.sbin/ppp/radius.c new file mode 100644 index 0000000..6b1d685 --- /dev/null +++ b/usr.sbin/ppp/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$ + * + */ + +#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/usr.sbin/ppp/radius.h b/usr.sbin/ppp/radius.h new file mode 100644 index 0000000..ab144a5 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/route.c b/usr.sbin/ppp/route.c new file mode 100644 index 0000000..e7db97e --- /dev/null +++ b/usr.sbin/ppp/route.c @@ -0,0 +1,949 @@ +/*- + * 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$ + */ + +#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_XRESOLVE, 'X' }, +#ifdef RTF_CLONING + { RTF_CLONING, 'C' }, +#endif + { RTF_STATIC, 'S' }, + { RTF_PROTO1, '1' }, + { RTF_PROTO2, '2' }, + { RTF_BLACKHOLE, 'B' }, + +#ifdef RTF_LLINFO + { RTF_LLINFO, 'L' }, +#endif +#ifdef RTF_CLONING + { RTF_CLONING, 'C' }, +#endif +#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], + sa[RTAX_IFP], sa[RTAX_IFA]); + } + } + + 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, + const struct sockaddr *ifp, const struct sockaddr *ifa) +{ + 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); + } + + if (gw) { + 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); + } + + if (ifa && ifp && ifp->sa_family == AF_LINK) { + rtmes.m_rtm.rtm_addrs |= RTA_IFP; + p += memcpy_roundup(p, ifp, ifp->sa_len); + rtmes.m_rtm.rtm_addrs |= RTA_IFA; + p += memcpy_roundup(p, ifa, ifa->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/usr.sbin/ppp/route.h b/usr.sbin/ppp/route.h new file mode 100644 index 0000000..bb8bd38 --- /dev/null +++ b/usr.sbin/ppp/route.h @@ -0,0 +1,74 @@ +/*- + * 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$ + */ + +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 *, + const struct sockaddr *, const struct sockaddr *); diff --git a/usr.sbin/ppp/server.c b/usr.sbin/ppp/server.c new file mode 100644 index 0000000..864c627 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/server.h b/usr.sbin/ppp/server.h new file mode 100644 index 0000000..9fdc52c --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/sig.c b/usr.sbin/ppp/sig.c new file mode 100644 index 0000000..a42194b --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/sig.h b/usr.sbin/ppp/sig.h new file mode 100644 index 0000000..27d264c --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/slcompress.c b/usr.sbin/ppp/slcompress.c new file mode 100644 index 0000000..48928c7 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/slcompress.h b/usr.sbin/ppp/slcompress.h new file mode 100644 index 0000000..9c415cb --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/sync.c b/usr.sbin/ppp/sync.c new file mode 100644 index 0000000..712e036 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/sync.h b/usr.sbin/ppp/sync.h new file mode 100644 index 0000000..4f5bfee --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +extern struct layer synclayer; diff --git a/usr.sbin/ppp/systems.c b/usr.sbin/ppp/systems.c new file mode 100644 index 0000000..36cb4e9 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/systems.h b/usr.sbin/ppp/systems.h new file mode 100644 index 0000000..9091e73 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/tcp.c b/usr.sbin/ppp/tcp.c new file mode 100644 index 0000000..4ace882 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/tcp.h b/usr.sbin/ppp/tcp.h new file mode 100644 index 0000000..f6a8235 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/tcpmss.c b/usr.sbin/ppp/tcpmss.c new file mode 100644 index 0000000..38d2920 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/tcpmss.h b/usr.sbin/ppp/tcpmss.h new file mode 100644 index 0000000..ffd7b82 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +extern struct layer tcpmsslayer; diff --git a/usr.sbin/ppp/throughput.c b/usr.sbin/ppp/throughput.c new file mode 100644 index 0000000..f6bc9b5 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/throughput.h b/usr.sbin/ppp/throughput.h new file mode 100644 index 0000000..21e5450 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/timer.c b/usr.sbin/ppp/timer.c new file mode 100644 index 0000000..c1b589b7 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/timer.h b/usr.sbin/ppp/timer.h new file mode 100644 index 0000000..02fcdd1 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/tty.c b/usr.sbin/ppp/tty.c new file mode 100644 index 0000000..ab984cc --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/tty.h b/usr.sbin/ppp/tty.h new file mode 100644 index 0000000..0991c81 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/tun.c b/usr.sbin/ppp/tun.c new file mode 100644 index 0000000..883cd5c --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/tun.h b/usr.sbin/ppp/tun.h new file mode 100644 index 0000000..1bb0712 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/ua.h b/usr.sbin/ppp/ua.h new file mode 100644 index 0000000..dbd89e5 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/udp.c b/usr.sbin/ppp/udp.c new file mode 100644 index 0000000..d9f3252 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/udp.h b/usr.sbin/ppp/udp.h new file mode 100644 index 0000000..c89e276 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +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/usr.sbin/ppp/vjcomp.c b/usr.sbin/ppp/vjcomp.c new file mode 100644 index 0000000..f3c6b71 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +#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/usr.sbin/ppp/vjcomp.h b/usr.sbin/ppp/vjcomp.h new file mode 100644 index 0000000..2956122 --- /dev/null +++ b/usr.sbin/ppp/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$ + */ + +struct mbuf; +struct link; +struct ipcp; +struct bundle; + +extern const char *vj2asc(u_int32_t); + +extern struct layer vjlayer; |