From 706e567fc5ff6b79738a5f470e5aa7b2cae76459 Mon Sep 17 00:00:00 2001 From: Chia-chi Yeh Date: Thu, 2 Jun 2011 14:26:57 -0700 Subject: libppp: import user space PPP implementation from FreeBSD 7.4-RELEASE. Change-Id: I78d2eb0fa010078b4cd131cadc39bf32cbc93986 --- src/Makefile | 129 ++ src/README.changes | 140 ++ src/README.nat | 378 ++++ src/acf.c | 116 + src/acf.h | 33 + src/arp.c | 317 +++ src/arp.h | 36 + src/async.c | 220 ++ src/async.h | 53 + src/atm.c | 237 ++ src/atm.h | 35 + src/auth.c | 479 ++++ src/auth.h | 68 + src/bundle.c | 2019 +++++++++++++++++ src/bundle.h | 216 ++ src/cbcp.c | 763 +++++++ src/cbcp.h | 65 + src/ccp.c | 826 +++++++ src/ccp.h | 165 ++ src/chap.c | 972 +++++++++ src/chap.h | 75 + src/chap_ms.c | 415 ++++ src/chap_ms.h | 52 + src/chat.c | 797 +++++++ src/chat.h | 82 + src/command.c | 3310 ++++++++++++++++++++++++++++ src/command.h | 75 + src/datalink.c | 1478 +++++++++++++ src/datalink.h | 156 ++ src/deflate.c | 601 +++++ src/deflate.h | 30 + src/defs.c | 450 ++++ src/defs.h | 142 ++ src/descriptor.h | 53 + src/ether.c | 737 +++++++ src/ether.h | 37 + src/exec.c | 410 ++++ src/exec.h | 35 + src/filter.c | 604 ++++++ src/filter.h | 101 + src/fsm.c | 1213 +++++++++++ src/fsm.h | 201 ++ src/hdlc.c | 438 ++++ src/hdlc.h | 116 + src/i4b.c | 446 ++++ src/i4b.h | 37 + src/id.c | 303 +++ src/id.h | 93 + src/iface.c | 729 +++++++ src/iface.h | 65 + src/ip.c | 993 +++++++++ src/ip.h | 44 + src/ipcp.c | 1477 +++++++++++++ src/ipcp.h | 132 ++ src/iplist.c | 225 ++ src/iplist.h | 51 + src/ipv6cp.c | 786 +++++++ src/ipv6cp.h | 83 + src/layer.h | 52 + src/lcp.c | 1305 +++++++++++ src/lcp.h | 143 ++ src/link.c | 412 ++++ src/link.h | 81 + src/log.c | 521 +++++ src/log.h | 105 + src/lqr.c | 532 +++++ src/lqr.h | 82 + src/main.c | 678 ++++++ src/main.h | 32 + src/mbuf.c | 440 ++++ src/mbuf.h | 119 + src/mp.c | 1209 +++++++++++ src/mp.h | 146 ++ src/mppe.c | 817 +++++++ src/mppe.h | 33 + src/nat_cmd.c | 600 +++++ src/nat_cmd.h | 42 + src/ncp.c | 547 +++++ src/ncp.h | 101 + src/ncpaddr.c | 1010 +++++++++ src/ncpaddr.h | 109 + src/netgraph.c | 743 +++++++ src/netgraph.h | 37 + src/pap.c | 303 +++ src/pap.h | 40 + src/physical.c | 1151 ++++++++++ src/physical.h | 176 ++ src/ppp.8.m4 | 6134 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/pred.c | 345 +++ src/pred.h | 31 + src/probe.c | 78 + src/probe.h | 38 + src/prompt.c | 574 +++++ src/prompt.h | 96 + src/proto.c | 115 + src/proto.h | 64 + src/radius.c | 1361 ++++++++++++ src/radius.h | 133 ++ src/route.c | 929 ++++++++ src/route.h | 73 + src/server.c | 422 ++++ src/server.h | 61 + src/sig.c | 119 + src/sig.h | 35 + src/slcompress.c | 605 ++++++ src/slcompress.h | 161 ++ src/sync.c | 84 + src/sync.h | 29 + src/systems.c | 483 +++++ src/systems.h | 43 + src/tcp.c | 212 ++ src/tcp.h | 34 + src/tcpmss.c | 185 ++ src/tcpmss.h | 29 + src/throughput.c | 302 +++ src/throughput.h | 70 + src/timer.c | 302 +++ src/timer.h | 55 + src/tty.c | 770 +++++++ src/tty.h | 37 + src/tun.c | 119 + src/tun.h | 39 + src/ua.h | 74 + src/udp.c | 335 +++ src/udp.h | 35 + src/vjcomp.c | 200 ++ src/vjcomp.h | 36 + 127 files changed, 50752 insertions(+) create mode 100644 src/Makefile create mode 100644 src/README.changes create mode 100644 src/README.nat create mode 100644 src/acf.c create mode 100644 src/acf.h create mode 100644 src/arp.c create mode 100644 src/arp.h create mode 100644 src/async.c create mode 100644 src/async.h create mode 100644 src/atm.c create mode 100644 src/atm.h create mode 100644 src/auth.c create mode 100644 src/auth.h create mode 100644 src/bundle.c create mode 100644 src/bundle.h create mode 100644 src/cbcp.c create mode 100644 src/cbcp.h create mode 100644 src/ccp.c create mode 100644 src/ccp.h create mode 100644 src/chap.c create mode 100644 src/chap.h create mode 100644 src/chap_ms.c create mode 100644 src/chap_ms.h create mode 100644 src/chat.c create mode 100644 src/chat.h create mode 100644 src/command.c create mode 100644 src/command.h create mode 100644 src/datalink.c create mode 100644 src/datalink.h create mode 100644 src/deflate.c create mode 100644 src/deflate.h create mode 100644 src/defs.c create mode 100644 src/defs.h create mode 100644 src/descriptor.h create mode 100644 src/ether.c create mode 100644 src/ether.h create mode 100644 src/exec.c create mode 100644 src/exec.h create mode 100644 src/filter.c create mode 100644 src/filter.h create mode 100644 src/fsm.c create mode 100644 src/fsm.h create mode 100644 src/hdlc.c create mode 100644 src/hdlc.h create mode 100644 src/i4b.c create mode 100644 src/i4b.h create mode 100644 src/id.c create mode 100644 src/id.h create mode 100644 src/iface.c create mode 100644 src/iface.h create mode 100644 src/ip.c create mode 100644 src/ip.h create mode 100644 src/ipcp.c create mode 100644 src/ipcp.h create mode 100644 src/iplist.c create mode 100644 src/iplist.h create mode 100644 src/ipv6cp.c create mode 100644 src/ipv6cp.h create mode 100644 src/layer.h create mode 100644 src/lcp.c create mode 100644 src/lcp.h create mode 100644 src/link.c create mode 100644 src/link.h create mode 100644 src/log.c create mode 100644 src/log.h create mode 100644 src/lqr.c create mode 100644 src/lqr.h create mode 100644 src/main.c create mode 100644 src/main.h create mode 100644 src/mbuf.c create mode 100644 src/mbuf.h create mode 100644 src/mp.c create mode 100644 src/mp.h create mode 100644 src/mppe.c create mode 100644 src/mppe.h create mode 100644 src/nat_cmd.c create mode 100644 src/nat_cmd.h create mode 100644 src/ncp.c create mode 100644 src/ncp.h create mode 100644 src/ncpaddr.c create mode 100644 src/ncpaddr.h create mode 100644 src/netgraph.c create mode 100644 src/netgraph.h create mode 100644 src/pap.c create mode 100644 src/pap.h create mode 100644 src/physical.c create mode 100644 src/physical.h create mode 100644 src/ppp.8.m4 create mode 100644 src/pred.c create mode 100644 src/pred.h create mode 100644 src/probe.c create mode 100644 src/probe.h create mode 100644 src/prompt.c create mode 100644 src/prompt.h create mode 100644 src/proto.c create mode 100644 src/proto.h create mode 100644 src/radius.c create mode 100644 src/radius.h create mode 100644 src/route.c create mode 100644 src/route.h create mode 100644 src/server.c create mode 100644 src/server.h create mode 100644 src/sig.c create mode 100644 src/sig.h create mode 100644 src/slcompress.c create mode 100644 src/slcompress.h create mode 100644 src/sync.c create mode 100644 src/sync.h create mode 100644 src/systems.c create mode 100644 src/systems.h create mode 100644 src/tcp.c create mode 100644 src/tcp.h create mode 100644 src/tcpmss.c create mode 100644 src/tcpmss.h create mode 100644 src/throughput.c create mode 100644 src/throughput.h create mode 100644 src/timer.c create mode 100644 src/timer.h create mode 100644 src/tty.c create mode 100644 src/tty.h create mode 100644 src/tun.c create mode 100644 src/tun.h create mode 100644 src/ua.h create mode 100644 src/udp.c create mode 100644 src/udp.h create mode 100644 src/vjcomp.c create mode 100644 src/vjcomp.h diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..68a882f --- /dev/null +++ b/src/Makefile @@ -0,0 +1,129 @@ +# $FreeBSD: src/usr.sbin/ppp/Makefile,v 1.110.12.1 2010/12/21 17:10:29 kensmith Exp $ + +.include + +PROG= ppp +MAN= ppp.8 +SRCS= acf.c arp.c async.c auth.c bundle.c cbcp.c ccp.c chap.c chat.c \ + command.c datalink.c deflate.c defs.c exec.c filter.c fsm.c hdlc.c \ + iface.c ip.c ipcp.c ipv6cp.c iplist.c lcp.c link.c log.c lqr.c main.c \ + mbuf.c mp.c ncp.c ncpaddr.c pap.c physical.c pred.c probe.c prompt.c \ + proto.c route.c server.c sig.c slcompress.c sync.c systems.c tcp.c \ + tcpmss.c throughput.c timer.c tty.c tun.c udp.c vjcomp.c +WARNS?= 3 +.if defined(RELEASE_CRUNCH) +CFLAGS+=-DRELEASE_CRUNCH +PPP_NO_ATM= +PPP_NO_DES= +PPP_NO_I4B= +PPP_NO_KLDLOAD= +PPP_NO_NAT= +PPP_NO_PAM= +PPP_NO_RADIUS= +PPP_NO_SUID= +.endif + +.if ${MK_ATM} == "no" +PPP_NO_ATM= +.endif +.if ${MK_I4B} == "no" +PPP_NO_I4B= +.endif +.if ${MK_PAM_SUPPORT} == "no" +PPP_NO_PAM= +.endif + +.if defined(PPP_NO_SUID) +BINMODE=550 +.else +BINMODE=4550 +BINOWN= root +.endif +BINGRP= network +M4FLAGS= + +LDADD= -lcrypt -lmd -lutil -lz +DPADD= ${LIBCRYPT} ${LIBMD} ${LIBUTIL} ${LIBZ} + +.SUFFIXES: .8 .8.m4 + +.8.m4.8: + m4 ${M4FLAGS} ${.IMPSRC} >${.TARGET} + +CLEANFILES= ppp.8 + +.if defined(PPP_CONFDIR) && !empty(PPP_CONFDIR) +CFLAGS+=-DPPP_CONFDIR=\"${PPP_CONFDIR}\" +.endif + +.if defined(PPP_NO_KLDLOAD) +CFLAGS+=-DNOKLDLOAD +.endif + +.if ${MK_INET6_SUPPORT} == "no" +CFLAGS+=-DNOINET6 +.endif + +.if defined(PPP_NO_NAT) +CFLAGS+=-DNONAT +.else +SRCS+= nat_cmd.c +LDADD+= -lalias +DPADD+= ${LIBALIAS} +.endif + +.if defined(PPP_NO_ATM) +CFLAGS+=-DNOATM +.else +SRCS+= atm.c +.endif + +.if defined(PPP_NO_SUID) +CFLAGS+=-DNOSUID +.else +SRCS+= id.c +.endif + +.if defined(RELEASE_CRUNCH) || ${MK_OPENSSL} == "no" || \ + defined(PPP_NO_DES) +CFLAGS+=-DNODES +.else +SRCS+= chap_ms.c mppe.c +LDADD+= -lcrypto +DPADD+= ${LIBCRYPTO} +.endif + +.if defined(PPP_NO_RADIUS) +CFLAGS+=-DNORADIUS +.else +SRCS+= radius.c +LDADD+= -lradius +DPADD+= ${LIBRADIUS} +.endif + +.if defined(PPP_NO_I4B) || ${MACHINE_ARCH} != "i386" +CFLAGS+=-DNOI4B +.else +SRCS+= i4b.c +.endif + +.if defined(PPP_NO_NETGRAPH) +CFLAGS+=-DNONETGRAPH +.else +SRCS+= ether.c +LDADD+= -lnetgraph +DPADD+= ${LIBNETGRAPH} +.if defined(EXPERIMENTAL_NETGRAPH) +CFLAGS+=-DEXPERIMENTAL_NETGRAPH +SRCS+= netgraph.c +.endif +.endif + +.if defined(PPP_NO_PAM) +CFLAGS+=-DNOPAM +.else +LDADD+= ${MINUSLPAM} +DPADD+= ${LIBPAM} +.endif + +.include diff --git a/src/README.changes b/src/README.changes new file mode 100644 index 0000000..49acd04 --- /dev/null +++ b/src/README.changes @@ -0,0 +1,140 @@ +Copyright (c) 2001 Brian Somers + based on work by Eivind Eklund , +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +$FreeBSD: src/usr.sbin/ppp/README.changes,v 1.24.26.1 2010/12/21 17:10:29 kensmith Exp $ + +This file summarises changes made to ppp that effect +its configuration. + +It does not describe new features, rather it attempts +to answer any `this used to work, why doesn't it now?' +questions. + +o The `set debug' command was replaced with `set log'. +o The `set log LCP' command was split into LCP, IPCP and CCP logs. +o Syslogd is used for logging. /etc/syslog.conf must be updated. +o LQR is disabled by default. +o Openmode is active by default. +o Users must be a member of group `network' for ppp access. Furthermore, + they must be `allow'ed to run ppp via the `allow' command in the + configuration file. + For a brief period, ppp could only be run as root. +o No diagnostic socket is created by default. The `set server' command + must be used. +o The diagnostic socket password must be specified *only* on the `set + server' command line. +o When `set server' is used to re-select a diagnostic port, all existing + diagnostic connections are dropped. +o pppd-deflate is now called deflate24. +o Filter IPs of 0.0.0.0 have a default width of 0, not 32. +o Errors in `add' and `delete' are logged as warnings rather than being + written to the TCP/IP log. +o Any number of diagnostic prompts are allowed, and they are allowed in + interactive mode. +o The default `device' is cuad1, then cuad0 +o A password of "*" in ppp.secret causes a passwd database lookup in + pap mode. +o The value of the CONNECT environment variable is logged in the + utmp host field in -direct mode. +o Out-of-sequence FSM packets (IPCP/LCP/CCP) are dropped by default. +o Reconnect values are used after an LQR timeout. +o ^C works on the parent in -background mode. +o The dial/call/open command works asynchronously. As a result, prompts + do not lose control while dialing. +o The `display' command has been removed. All information is available + with the appropriate `show' command. +o Msext does not need to be enabled/disabled. Setting the NBNS (set nbns) + will auto enable it. The DNS side may be enabled/disabled, and if + enabled without a `set dns' (was `set ns') will use values from + /etc/resolv.conf. +o Filters are now called `allow', `dial', `in' and `out'. `set + ifilter ...' becomes `set filter in ...' etc. +o Authname and Authkey may only be `set' in phase DEAD. +o Set encrypt is no longer necessary. Ppp will respond to M$CHAP + servers correctly if it's built with DES. +o Throughput statistics are enabled by default. +o `Set stopped' only has two parameters. It's no longer possible to + have an IPCP stopped timer. +o `Set timeout' only has one or two parameters. Use `set lqrperiod' and + `set {lcp,ccp,ipcp,chap,pap}retry' for the other timers. These timeout + values can be seen using the relevant show commands. +o `set loopback' is now `enable/disable loopback'. +o `show auto', `show loopback' and `show mtu' are all part of `show bundle'. +o `show mru' is part of `show lcp' +o `show msext' and `show vj' are part of `show ipcp' +o `show reconnect' and `show redial' are part of `show link' +o A signal 15 (TERM) will now shut down the link gracefully. +o A signal 2 (HUP) will drop all links immediately. +o Signal 30 (USR1) is now ignored. +o Add & delete commands are not necessary in ppp.linkup if they are + `sticky routes' (ie, contain MYADDR or HISADDR). +o LINK and CARRIER logging are no longer available. +o Timer based DEBUG messages are now logged in the new TIMER log. +o Ppp can use tun devices > tun255. +o Protocol-compressed packets are accepted even if they were denied + at LCP negotiation time. +o Passwords aren't logged when logging the ``set server'' line. +o Command line options only need enough characters to uniquely identify + them. -a == -auto, -dd == -ddial etc. -interactive is also allowed. +o If you don't like seeing additional interface aliases when running in + -auto -alias mode, add ``iface clear'' to your ppp.linkdown file - + check the sample file. +o Ppp waits for 1 second before checking whether the device supports + carrier. This is controllable with ``set cd''. +o Random dial timeouts are now between 1 and 30 seconds inclusive rather + than between 0 and 29. +o Ppp now accepts M$CHAP (as well as normal CHAP) by default. If this + is not required, you must ``deny chap05 chap80''. +o The ``set device'' command now expects each device to be specified as an + argument rather than concatentating all arguments and splitting based + on commas and spaces. +o The ``show modem'' command is deprecated and has been changed to + ``show physical''. +o The words ``host'' and ``port'' are no longer accepted by the ``set filter'' + command. Removing them should yield the same results as before. +o The ``set weight'' command has been deprecated. The ``set bandwidth'' + command should now be used instead. +o The ``set autoload'' command syntax and implementation have changed as the + old implementation was mis-designed and dysfunctional. +o Ppp now waits either the full ``set cd'' time or until carrier is detected + before running the login script (whichever comes first). +o The -alias flag has been deprecated. The -nat flag should be used instead. +o Unbalanced quotes in commands are now warned about and the entire command + is ignored. +o It is now only necessary to escape the `-' character in chat scripts twice. + See the example files for details. +o Environment variables and ~ are expanded on in commands +o ``nat pptp'' is no longer necessary as this is now done transparently +o The ``!'' at the start of chat scripts and authkey can be made literal + (rather than meaning execute) by doubling it to ``!!''. +o MP autoload throughput measurements are now based on the maximum of input + and output averages rather than on the total. +o When only one link is open in MP mode, MP link level compression is not + open and the peer MRU >= the peer MRRU, ppp sends outbound traffic as + PROTO_IP traffic rather than PROTO_MP. +o MSCHAPv2 is now accepted by default. If you don't wish to negotiate + this, you must explicitly deny it. +o MPPE is enabled and accepted by default (although deflate and predictor1 + are preferred. diff --git a/src/README.nat b/src/README.nat new file mode 100644 index 0000000..01b572f --- /dev/null +++ b/src/README.nat @@ -0,0 +1,378 @@ +Copyright (c) 2001 Charles Mott +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +$FreeBSD: src/usr.sbin/ppp/README.nat,v 1.7.40.1 2010/12/21 17:10:29 kensmith Exp $ + +User PPP NAT (Packet Aliasing) + + + +0. Contents + 1. Background + 2. Setup + 3. New commands in ppp + 4. Future Work + 5. Authors / Acknowledgements + 6. Revision History for Aliasing Code + + + +1. Background + +User mode ppp has embedded NAT (Network Address Translation) code. +Enabling this, either by the "-nat" command line option or the +"nat enable yes" command in a ppp.conf file, makes the ppp host +automatically NAT IP packets forwarded from a local network, making +them appear to come from the ppp host machine. Incoming packets +from the outside world are then appropriately de-NAT'd. + +The process of NAT'ing involves both the IP address and the TCP or UDP +port numbers. ICMP echo and timestamp packets are natted by their id +numbers. ICMP error messages can be properly directed by examining the +fragment of the offending packet which is contained in the body of the +message. + +This software was specifically meant to support users who have +unregistered, private address IP networks (e.g. 192.168.0.x or 10.0.0.x +addresses). The ppp host can act as a gateway for these networks, and +computers on the local area net will have some degree of Internet access +without the need for a registered IP address. Additionally, there will +be no need for an Internet service provider to maintain routing tables +for the local area network. + +A disadvantage of NAT is that machines on the local network, +behind the ppp host, are not visible from the outside world. They can +establish TCP connections and make UDP inquiries (such as domain name +service requests) but the connections seem to come from the ppp host +itself. There is, in effect, a partial firewall. Of course, if this is +what you want, the disadvantage becomes an advantage. + +A second disadvantage is that "IP encoding" protocols, which send IP +address or port information within the data stream, are not supported +for the cases where exception code exists. This implementation has +workarounds for FTP and IRC DCC, the most well known of the IP encoding +protocols. This frees users from depending on using the ftp passive +mode and avoiding IRC DCC sends, as is sometimes the case with other +masquerading solutions. + +The implementation supports all standard, non-encoding TCP and UDP protocols. +Examples of these protocols are http, gopher and telnet. The standard UDP +mode of Real-Audio is not presently supported, but the TCP mode does work +correctly. + +The NAT code also handles many ICMP messages. In particular, +ping and traceroute are supported. + + + +2. Packet Aliasing Setup + +It is recommended that users first verify correct ppp operation without +NAT enabled. This will confirm that the ppp.conf file is +properly set up and that there are no ppp problems. Then start ppp with +the "-nat" option on the command line. The user should verify that +the ppp host can correctly connect to the Internet in NAT +mode. Finally, check that machines on the private network can access +the Internet. + +The NAT software handles all packets, whether they come from +the host or another computer on the local area network. Thus, a correctly +operating ppp host indicates that the software should work properly for +other computers on the private network. + +If the ppp host can access the Internet, but other computers on the local +network cannot, check that IP forwarding is enabled on the ppp host. Also, +verify that the other computers use this machine as a gateway. Of course, +you should also verify that machines within the local area network +communicate properly. A common error is inconsistent subnet addresses +and masks. + + + +3. New commands in ppp + +In order to control NAT behaviour in a simple manner (no need for +recompilation), a new command has been added to ppp: nat. This +is in addition to the -nat command line option. System managers and +more experienced users may prefer to use the ppp command syntax +within the ppp.conf file. The nat command also allows NAT +behaviour to be more precisely specified. + +The decision to add a command instead of extending 'set' or 'option' was +to make obvious that these options only work when NAT is enabled. + +The syntax for 'nat' is + + ppp> nat option [yes|no] + +where option is given by one of the following templates. + + + - nat enable [yes|no] (default no) + +Enable NAT functionality. If disabled, no other NAT +options will have any effect. You should usually enable NAT +before routing any packets over the link; good points are in the +initial script or right before adding a route. If you do not always +want NAT, consider using the -nat option to ppp instead of this +command. + + + - nat deny_incoming [yes|no] (default yes) + +Set to "yes" to disable all incoming connections. This just drops +connections to, for example, ftp, telnet or web servers. The NAT +mechanism prevents these connections. Technically, this option denies +all incoming TCP and UDP requests, making the NAT software a +fairly efficient one-way firewall. The default is no, which will allow +all incoming connections to telnetd, ftpd, etc. + + + - nat log [yes|no] + +Controls logging of NAT link creation to "/var/log/alias.log" - this +is usually only useful if debugging a setup, to see if the bug is in +the PPP NATing. The debugging information is fairly limited, listing +the number of NAT links open for different protocols. + + + - nat same_ports [yes|no] (default yes) + +When a connection is being established going through the NAT +routines, it will normally have its port number changed to allow the +NAT code to track it. If same_ports is enabled, the NAT +software attempts to keep the connection's source port unchanged. +This will allow rsh, RPC and other specialised protocols to work +_most of the time_, at least on the host machine. Please, do not +report this being unstable as a bug - it is a result of the way +NAT has to work. TCP/IP was intended to have one IP address +per machine. + + + - nat use_sockets [yes|no] (default yes) + +This is a fairly obscure option. For the most part, the NAT +software does not have to allocate system sockets when it chooses a +NAT port number. Under very specific circumstances, FTP data +connections (which don't know the remote port number, though it is +usually 20) and IRC DCC send (which doesn't know either the address or +the port from which the connection will come), there can potentially be +some interference with an open server socket having the same port number +on the ppp host machine. This possibility for interference only exists +until the TCP connection has been acknowledged on both sides. The safe +option is yes, though fewer system resources are consumed by specifying +no. + + + - nat unregistered_only [yes|no] (default no) + +NAT normally remaps all packets coming from the local area +network to the ppp host machine address. Set this option to only map +addresses from the following standard ranges for private, unregistered +addresses: + + 10.0.0.0 -> 10.255.255.255 + 172.16.0.0 -> 172.31.255.255 + 192.168.0.0 -> 192.168.255.255 */ + +In the instance that there is a subnet of public addresses and another +subnet of private addresses being routed by the ppp host, then only the +packets on the private subnet will be NAT'd. + + +- nat port : + +This command allows incoming traffic to 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 + +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) +Eivind Eklund (perhaps@yes.no) + +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 ) + - 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 ) + - Added support for IRC DCC (ee) + - Changed the aliasing routines to use ANSI style throughout - + minor API changes for integration with other programs than PPP (ee) + - Changed the build process, making all options switchable + from the Makefile (ee) + - Fixed minor security hole in alias_ftp.c for other applications + of the aliasing software. Hole could _not_ manifest in + PPP+pktAlias, but could potentially manifest in other + applications of the aliasing. (ee) + - Connections initiated from packet aliasing host machine will + not have their port number aliased unless it conflicts with + an aliasing port already being used. (There is an option to + disable this for debugging) (cjm) + - Sockets will be allocated in cases where there might be + port interference with the host machine. This can be disabled + in cases where the ppp host will be acting purely as a + masquerading router and not generate any traffic of its own. + (cjm) + +Version 2.0: March, 1997 (cjm) + - Incoming packets which are not recognised by the packet + aliasing engine are now completely dropped in ip.c. + - Aliasing links are cleared when a host interface address + changes (due to re-dial and dynamic address allocation). + - PacketAliasPermanentLink() API added. + - Option for only aliasing private, unregistered IP addresses + added. + - Substantial rework to the aliasing lookup engine. + +Version 2.1: May, 1997 (cjm) + - Continuing rework to the aliasing lookup engine to support + multiple incoming addresses and static NAT. + - Now supports outgoing as well as incoming ICMP error messages/ + - PPP commands to support address and port redirection. + diff --git a/src/acf.c b/src/acf.c new file mode 100644 index 0000000..6975f11 --- /dev/null +++ b/src/acf.c @@ -0,0 +1,116 @@ +/*- + * Copyright (c) 1999 Brian Somers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/acf.c,v 1.7.26.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#include + +#include +#include + +#include "defs.h" +#include "layer.h" +#include "timer.h" +#include "fsm.h" +#include "log.h" +#include "mbuf.h" +#include "acf.h" +#include "proto.h" +#include "throughput.h" +#include "lqr.h" +#include "hdlc.h" +#include "lcp.h" +#include "ccp.h" +#include "link.h" +#include "descriptor.h" +#include "async.h" +#include "physical.h" + +int +acf_WrapperOctets(struct lcp *lcp, u_short proto) +{ + return (proto == PROTO_LCP || lcp->his_acfcomp == 0) ? 2 : 0; +} + +static struct mbuf * +acf_LayerPush(struct bundle *b __unused, struct link *l, struct mbuf *bp, + int pri __unused, u_short *proto) +{ + const u_char cp[2] = { HDLC_ADDR, HDLC_UI }; + + if (*proto == PROTO_LCP || l->lcp.his_acfcomp == 0) { + bp = m_prepend(bp, cp, 2, 0); + m_settype(bp, MB_ACFOUT); + } + + return bp; +} + +static struct mbuf * +acf_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp, + u_short *proto __unused) +{ + struct physical *p = link2physical(l); + u_char cp[2]; + + if (!p) { + log_Printf(LogERROR, "Can't Pull an acf packet from a logical link\n"); + return bp; + } + + if (mbuf_View(bp, cp, 2) == 2) { + if (!p->link.lcp.want_acfcomp) { + /* We expect the packet not to be compressed */ + bp = mbuf_Read(bp, cp, 2); + if (cp[0] != HDLC_ADDR) { + p->hdlc.lqm.ifInErrors++; + p->hdlc.stats.badaddr++; + log_Printf(LogDEBUG, "acf_LayerPull: addr 0x%02x\n", cp[0]); + m_freem(bp); + return NULL; + } + if (cp[1] != HDLC_UI) { + p->hdlc.lqm.ifInErrors++; + p->hdlc.stats.badcommand++; + log_Printf(LogDEBUG, "acf_LayerPull: control 0x%02x\n", cp[1]); + m_freem(bp); + return NULL; + } + m_settype(bp, MB_ACFIN); + } else if (cp[0] == HDLC_ADDR && cp[1] == HDLC_UI) { + /* + * We can receive compressed packets, but the peer still sends + * uncompressed packets (or maybe this is a PROTO_LCP packet) ! + */ + bp = mbuf_Read(bp, cp, 2); + m_settype(bp, MB_ACFIN); + } + } + + return bp; +} + +struct layer acflayer = { LAYER_ACF, "acf", acf_LayerPush, acf_LayerPull }; diff --git a/src/acf.h b/src/acf.h new file mode 100644 index 0000000..4a53500 --- /dev/null +++ b/src/acf.h @@ -0,0 +1,33 @@ +/*- + * Copyright (c) 1999 Brian Somers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/acf.h,v 1.2.62.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +struct lcp; + +extern int acf_WrapperOctets(struct lcp *, u_short); + +extern struct layer acflayer; diff --git a/src/arp.c b/src/arp.c new file mode 100644 index 0000000..6d7c312 --- /dev/null +++ b/src/arp.c @@ -0,0 +1,317 @@ +/* + * sys-bsd.c - System-dependent procedures for setting up + * PPP interfaces on bsd-4.4-ish systems (including 386BSD, NetBSD, etc.) + * + * Copyright (c) 1989 Carnegie Mellon University. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that the above copyright notice and this paragraph are + * duplicated in all such forms and that any documentation, + * advertising materials, and other materials related to such + * distribution and use acknowledge that the software was developed + * by Carnegie Mellon University. The name of the + * University may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * $FreeBSD: src/usr.sbin/ppp/arp.c,v 1.46.26.1 2010/12/21 17:10:29 kensmith Exp $ + * + */ + +/* + * TODO: + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "layer.h" +#include "mbuf.h" +#include "log.h" +#include "id.h" +#include "timer.h" +#include "fsm.h" +#include "defs.h" +#include "iplist.h" +#include "throughput.h" +#include "slcompress.h" +#include "lqr.h" +#include "hdlc.h" +#include "ncpaddr.h" +#include "ipcp.h" +#include "ipv6cp.h" +#include "descriptor.h" +#include "lcp.h" +#include "ccp.h" +#include "link.h" +#include "mp.h" +#include "ncp.h" +#include "filter.h" +#ifndef NORADIUS +#include "radius.h" +#endif +#include "bundle.h" +#include "iface.h" +#include "arp.h" + +/* + * SET_SA_FAMILY - set the sa_family field of a struct sockaddr, + * if it exists. + */ +#define SET_SA_FAMILY(addr, family) \ + memset((char *) &(addr), '\0', sizeof(addr)); \ + addr.sa_family = (family); \ + addr.sa_len = sizeof(addr); + + +#if RTM_VERSION >= 3 + +/* + * arp_SetProxy - Make a proxy ARP entry for the peer. + */ +static struct { + struct rt_msghdr hdr; + struct sockaddr_inarp dst; + struct sockaddr_dl hwa; + char extra[128]; +} arpmsg; + +static int +arp_ProxySub(struct bundle *bundle, struct in_addr addr, int add) +{ + int routes; + + /* + * Get the hardware address of an interface on the same subnet as our local + * address. + */ + + memset(&arpmsg, 0, sizeof arpmsg); + if (!arp_EtherAddr(addr, &arpmsg.hwa, 0)) { + log_Printf(LogWARN, "%s: Cannot determine ethernet address for proxy ARP\n", + inet_ntoa(addr)); + return 0; + } + routes = ID0socket(PF_ROUTE, SOCK_RAW, AF_INET); + if (routes < 0) { + log_Printf(LogERROR, "arp_SetProxy: opening routing socket: %s\n", + strerror(errno)); + return 0; + } + arpmsg.hdr.rtm_type = add ? RTM_ADD : RTM_DELETE; + arpmsg.hdr.rtm_flags = RTF_ANNOUNCE | RTF_HOST | RTF_STATIC; + arpmsg.hdr.rtm_version = RTM_VERSION; + arpmsg.hdr.rtm_seq = ++bundle->routing_seq; + arpmsg.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY; + arpmsg.hdr.rtm_inits = RTV_EXPIRE; + arpmsg.dst.sin_len = sizeof(struct sockaddr_inarp); + arpmsg.dst.sin_family = AF_INET; + arpmsg.dst.sin_addr.s_addr = addr.s_addr; + arpmsg.dst.sin_other = SIN_PROXY; + + arpmsg.hdr.rtm_msglen = (char *) &arpmsg.hwa - (char *) &arpmsg + + arpmsg.hwa.sdl_len; + + + if (ID0write(routes, &arpmsg, arpmsg.hdr.rtm_msglen) < 0 && + !(!add && errno == ESRCH)) { + log_Printf(LogERROR, "%s proxy arp entry %s: %s\n", + add ? "Add" : "Delete", inet_ntoa(addr), strerror(errno)); + close(routes); + return 0; + } + close(routes); + return 1; +} + +int +arp_SetProxy(struct bundle *bundle, struct in_addr addr) +{ + return (arp_ProxySub(bundle, addr, 1)); +} + +/* + * arp_ClearProxy - Delete the proxy ARP entry for the peer. + */ +int +arp_ClearProxy(struct bundle *bundle, struct in_addr addr) +{ + return (arp_ProxySub(bundle, addr, 0)); +} + +#else /* RTM_VERSION */ + +/* + * arp_SetProxy - Make a proxy ARP entry for the peer. + */ +int +arp_SetProxy(struct bundle *bundle, struct in_addr addr, int s) +{ + struct arpreq arpreq; + struct { + struct sockaddr_dl sdl; + char space[128]; + } dls; + + memset(&arpreq, '\0', sizeof arpreq); + + /* + * Get the hardware address of an interface on the same subnet as our local + * address. + */ + if (!arp_EtherAddr(addr, &dls.sdl, 1)) { + log_Printf(LOG_PHASE_BIT, "Cannot determine ethernet address for " + "proxy ARP\n"); + return 0; + } + arpreq.arp_ha.sa_len = sizeof(struct sockaddr); + arpreq.arp_ha.sa_family = AF_UNSPEC; + memcpy(arpreq.arp_ha.sa_data, LLADDR(&dls.sdl), dls.sdl.sdl_alen); + SET_SA_FAMILY(arpreq.arp_pa, AF_INET); + ((struct sockaddr_in *)&arpreq.arp_pa)->sin_addr.s_addr = addr.s_addr; + arpreq.arp_flags = ATF_PERM | ATF_PUBL; + if (ID0ioctl(s, SIOCSARP, (caddr_t) & arpreq) < 0) { + log_Printf(LogERROR, "arp_SetProxy: ioctl(SIOCSARP): %s\n", + strerror(errno)); + return 0; + } + return 1; +} + +/* + * arp_ClearProxy - Delete the proxy ARP entry for the peer. + */ +int +arp_ClearProxy(struct bundle *bundle, struct in_addr addr, int s) +{ + struct arpreq arpreq; + + memset(&arpreq, '\0', sizeof arpreq); + SET_SA_FAMILY(arpreq.arp_pa, AF_INET); + ((struct sockaddr_in *)&arpreq.arp_pa)->sin_addr.s_addr = addr.s_addr; + if (ID0ioctl(s, SIOCDARP, (caddr_t) & arpreq) < 0) { + log_Printf(LogERROR, "arp_ClearProxy: ioctl(SIOCDARP): %s\n", + strerror(errno)); + return 0; + } + return 1; +} + +#endif /* RTM_VERSION */ + + +/* + * arp_EtherAddr - get the hardware address of an interface on the + * the same subnet as ipaddr. + */ + +int +arp_EtherAddr(struct in_addr ipaddr, struct sockaddr_dl *hwaddr, + int verbose) +{ + int mib[6], skip; + size_t needed; + char *buf, *ptr, *end; + struct if_msghdr *ifm; + struct ifa_msghdr *ifam; + struct sockaddr_dl *dl; + struct sockaddr *sa[RTAX_MAX]; + + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = 0; + mib[4] = NET_RT_IFLIST; + mib[5] = 0; + + if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) { + log_Printf(LogERROR, "arp_EtherAddr: sysctl: estimate: %s\n", + strerror(errno)); + return 0; + } + + if ((buf = malloc(needed)) == NULL) + return 0; + + if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) { + free(buf); + return 0; + } + end = buf + needed; + + ptr = buf; + while (ptr < end) { + ifm = (struct if_msghdr *)ptr; /* On if_msghdr */ + if (ifm->ifm_type != RTM_IFINFO) + break; + dl = (struct sockaddr_dl *)(ifm + 1); /* Single _dl at end */ + skip = (ifm->ifm_flags & (IFF_UP | IFF_BROADCAST | IFF_POINTOPOINT | + IFF_NOARP | IFF_LOOPBACK)) != (IFF_UP | IFF_BROADCAST); + ptr += ifm->ifm_msglen; /* First ifa_msghdr */ + while (ptr < end) { + ifam = (struct ifa_msghdr *)ptr; /* Next ifa_msghdr (alias) */ + if (ifam->ifam_type != RTM_NEWADDR) /* finished ? */ + break; + ptr += ifam->ifam_msglen; + if (skip || (ifam->ifam_addrs & (RTA_NETMASK|RTA_IFA)) != + (RTA_NETMASK|RTA_IFA)) + continue; + /* Found a candidate. Do the addresses match ? */ + if (log_IsKept(LogDEBUG) && + ptr == (char *)ifm + ifm->ifm_msglen + ifam->ifam_msglen) + log_Printf(LogDEBUG, "%.*s interface is a candidate for proxy\n", + dl->sdl_nlen, dl->sdl_data); + + iface_ParseHdr(ifam, sa); + + if (sa[RTAX_IFA]->sa_family == AF_INET) { + struct sockaddr_in *ifa, *netmask; + + ifa = (struct sockaddr_in *)sa[RTAX_IFA]; + netmask = (struct sockaddr_in *)sa[RTAX_NETMASK]; + + if (log_IsKept(LogDEBUG)) { + char a[16]; + + strncpy(a, inet_ntoa(netmask->sin_addr), sizeof a - 1); + a[sizeof a - 1] = '\0'; + log_Printf(LogDEBUG, "Check addr %s, mask %s\n", + inet_ntoa(ifa->sin_addr), a); + } + + if ((ifa->sin_addr.s_addr & netmask->sin_addr.s_addr) == + (ipaddr.s_addr & netmask->sin_addr.s_addr)) { + log_Printf(verbose ? LogPHASE : LogDEBUG, + "Found interface %.*s for %s\n", dl->sdl_nlen, + dl->sdl_data, inet_ntoa(ipaddr)); + memcpy(hwaddr, dl, dl->sdl_len); + free(buf); + return 1; + } + } + } + } + free(buf); + + return 0; +} diff --git a/src/arp.h b/src/arp.h new file mode 100644 index 0000000..f00620c --- /dev/null +++ b/src/arp.h @@ -0,0 +1,36 @@ +/*- + * Copyright (c) 1996 - 2001 Brian Somers + * based on work by Toshiharu OHNO + * Internet Initiative Japan, Inc (IIJ) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/arp.h,v 1.12.26.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +struct sockaddr_dl; +struct bundle; + +extern int arp_ClearProxy(struct bundle *, struct in_addr); +extern int arp_SetProxy(struct bundle *, struct in_addr); +extern int arp_EtherAddr(struct in_addr, struct sockaddr_dl *, int); diff --git a/src/async.c b/src/async.c new file mode 100644 index 0000000..745c52f --- /dev/null +++ b/src/async.c @@ -0,0 +1,220 @@ +/*- + * Copyright (c) 1996 - 2001 Brian Somers + * based on work by Toshiharu OHNO + * 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 +__FBSDID("$FreeBSD: src/usr.sbin/ppp/async.c,v 1.29.26.1 2010/12/21 17:10:29 kensmith Exp $"); + +#include + +#include +#include + +#include "layer.h" +#include "mbuf.h" +#include "log.h" +#include "defs.h" +#include "timer.h" +#include "fsm.h" +#include "lqr.h" +#include "hdlc.h" +#include "lcp.h" +#include "proto.h" +#include "async.h" +#include "throughput.h" +#include "ccp.h" +#include "link.h" +#include "descriptor.h" +#include "physical.h" + +#define MODE_HUNT 0x01 +#define MODE_ESC 0x02 + +void +async_Init(struct async *async) +{ + async_Setup(async); + memset(async->cfg.EscMap, '\0', sizeof async->cfg.EscMap); +} + +void +async_Setup(struct async *async) +{ + async->mode = MODE_HUNT; + async->length = 0; + async->my_accmap = async->his_accmap = 0xffffffff; +} + +void +async_SetLinkParams(struct async *async, u_int32_t mymap, u_int32_t hismap) +{ + async->my_accmap = mymap; + async->his_accmap = hismap | mymap; +} + +/* + * Encode into async HDLC byte code + */ +static void +async_Encode(struct async *async, u_char **cp, u_char c, int proto) +{ + u_char *wp; + + wp = *cp; + if ((c < 0x20 && (proto == PROTO_LCP || (async->his_accmap & (1 << c)))) + || (c == HDLC_ESC) || (c == HDLC_SYN)) { + *wp++ = HDLC_ESC; + c ^= HDLC_XOR; + } + if (async->cfg.EscMap[32] && async->cfg.EscMap[c >> 3] & (1 << (c & 7))) { + *wp++ = HDLC_ESC; + c ^= HDLC_XOR; + } + *wp++ = c; + *cp = wp; +} + +static struct mbuf * +async_LayerPush(struct bundle *b __unused, struct link *l, struct mbuf *bp, + int pri __unused, u_short *proto) +{ + struct physical *p = link2physical(l); + u_char *cp, *sp, *ep; + struct mbuf *wp; + size_t oldcnt; + size_t cnt; + + if (!p || m_length(bp) > HDLCSIZE) { + m_freem(bp); + return NULL; + } + + oldcnt = m_length(bp); + + cp = p->async.xbuff; + ep = cp + HDLCSIZE - 10; + wp = bp; + *cp++ = HDLC_SYN; + while (wp) { + sp = MBUF_CTOP(wp); + for (cnt = wp->m_len; cnt > 0; cnt--) { + async_Encode(&p->async, &cp, *sp++, *proto); + if (cp >= ep) { + m_freem(bp); + return NULL; + } + } + wp = wp->m_next; + } + *cp++ = HDLC_SYN; + + cnt = cp - p->async.xbuff; + m_freem(bp); + bp = m_get(cnt, MB_ASYNCOUT); + memcpy(MBUF_CTOP(bp), p->async.xbuff, cnt); + bp->priv = cnt - oldcnt; + log_DumpBp(LogASYNC, "Write", bp); + + return bp; +} + +static struct mbuf * +async_Decode(struct async *async, u_char c) +{ + struct mbuf *bp; + + if ((async->mode & MODE_HUNT) && c != HDLC_SYN) + return NULL; + + switch (c) { + case HDLC_SYN: + async->mode &= ~MODE_HUNT; + if (async->length) { /* packet is ready. */ + bp = m_get(async->length, MB_ASYNCIN); + mbuf_Write(bp, async->hbuff, async->length); + async->length = 0; + return bp; + } + break; + case HDLC_ESC: + if (!(async->mode & MODE_ESC)) { + async->mode |= MODE_ESC; + break; + } + /* FALLTHROUGH */ + default: + if (async->length >= HDLCSIZE) { + /* packet is too large, discard it */ + log_Printf(LogWARN, "Packet too large (%d), discarding.\n", + async->length); + async->length = 0; + async->mode = MODE_HUNT; + break; + } + if (async->mode & MODE_ESC) { + c ^= HDLC_XOR; + async->mode &= ~MODE_ESC; + } + async->hbuff[async->length++] = c; + break; + } + return NULL; +} + +static struct mbuf * +async_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp, + u_short *proto __unused) +{ + struct mbuf *nbp, **last; + struct physical *p = link2physical(l); + u_char *ch; + size_t cnt; + + if (!p) { + log_Printf(LogERROR, "Can't Pull an async packet from a logical link\n"); + return bp; + } + + last = &nbp; + + log_DumpBp(LogASYNC, "Read", bp); + while (bp) { + ch = MBUF_CTOP(bp); + for (cnt = bp->m_len; cnt; cnt--) { + *last = async_Decode(&p->async, *ch++); + if (*last != NULL) + last = &(*last)->m_nextpkt; + } + bp = m_free(bp); + } + + return nbp; +} + +struct layer asynclayer = + { LAYER_ASYNC, "async", async_LayerPush, async_LayerPull }; diff --git a/src/async.h b/src/async.h new file mode 100644 index 0000000..c0f5d14 --- /dev/null +++ b/src/async.h @@ -0,0 +1,53 @@ +/*- + * Copyright (c) 1997 Brian Somers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/async.h,v 1.8.40.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#define HDLCSIZE (MAX_MRU*2+6) + +struct async { + int mode; + int length; + u_char hbuff[HDLCSIZE]; /* recv buffer */ + u_char xbuff[HDLCSIZE]; /* xmit buffer */ + u_int32_t my_accmap; + u_int32_t his_accmap; + + struct { + u_char EscMap[33]; + } cfg; +}; + +struct lcp; +struct mbuf; +struct physical; +struct bundle; + +extern void async_Init(struct async *); +extern void async_Setup(struct async *); +extern void async_SetLinkParams(struct async *, u_int32_t, u_int32_t); + +extern struct layer asynclayer; diff --git a/src/atm.c b/src/atm.c new file mode 100644 index 0000000..6477bc1 --- /dev/null +++ b/src/atm.c @@ -0,0 +1,237 @@ +/*- + * Copyright (c) 2000 Jakob Stoklund Olesen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/atm.c,v 1.10.26.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "layer.h" +#include "defs.h" +#include "mbuf.h" +#include "log.h" +#include "timer.h" +#include "lqr.h" +#include "hdlc.h" +#include "throughput.h" +#include "fsm.h" +#include "lcp.h" +#include "ccp.h" +#include "link.h" +#include "async.h" +#include "descriptor.h" +#include "physical.h" +#include "main.h" +#include "atm.h" + +/* String identifying PPPoA */ +#define PPPOA "PPPoA" +#define PPPOA_LEN (sizeof(PPPOA) - 1) + +struct atmdevice { + struct device dev; /* What struct physical knows about */ +}; + +#define device2atm(d) ((d)->type == ATM_DEVICE ? (struct atmdevice *)d : NULL) + +unsigned +atm_DeviceSize(void) +{ + return sizeof(struct atmdevice); +} + +static ssize_t +atm_Sendto(struct physical *p, const void *v, size_t n) +{ + ssize_t ret = write(p->fd, v, n); + if (ret < 0) { + log_Printf(LogDEBUG, "atm_Sendto(%ld): %s\n", (long)n, strerror(errno)); + return ret; + } + return ret; +} + +static ssize_t +atm_Recvfrom(struct physical *p, void *v, size_t n) +{ + ssize_t ret = read(p->fd, (char*)v, n); + if (ret < 0) { + log_Printf(LogDEBUG, "atm_Recvfrom(%ld): %s\n", (long)n, strerror(errno)); + return ret; + } + return ret; +} + +static void +atm_Free(struct physical *p) +{ + struct atmdevice *dev = device2atm(p->handler); + + free(dev); +} + +static void +atm_device2iov(struct device *d, struct iovec *iov, int *niov, + int maxiov __unused, int *auxfd __unused, int *nauxfd __unused) +{ + int sz = physical_MaxDeviceSize(); + + iov[*niov].iov_base = realloc(d, sz); + if (iov[*niov].iov_base == NULL) { + log_Printf(LogALERT, "Failed to allocate memory: %d\n", sz); + AbortProgram(EX_OSERR); + } + iov[*niov].iov_len = sz; + (*niov)++; +} + +static const struct device baseatmdevice = { + ATM_DEVICE, + "atm", + 0, + { CD_NOTREQUIRED, 0 }, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + atm_Free, + atm_Recvfrom, + atm_Sendto, + atm_device2iov, + NULL, + NULL, + NULL +}; + +struct device * +atm_iov2device(int type, struct physical *p, struct iovec *iov, int *niov, + int maxiov __unused, int *auxfd __unused, int *nauxfd __unused) +{ + if (type == ATM_DEVICE) { + struct atmdevice *dev = (struct atmdevice *)iov[(*niov)++].iov_base; + + dev = realloc(dev, sizeof *dev); /* Reduce to the correct size */ + if (dev == NULL) { + log_Printf(LogALERT, "Failed to allocate memory: %d\n", + (int)(sizeof *dev)); + AbortProgram(EX_OSERR); + } + + /* Refresh function pointers etc */ + memcpy(&dev->dev, &baseatmdevice, sizeof dev->dev); + + physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF); + return &dev->dev; + } + + return NULL; +} + +static struct atmdevice * +atm_CreateDevice(struct physical *p, const char *iface, unsigned vpi, + unsigned vci) +{ + struct atmdevice *dev; + struct sockaddr_natm sock; + + if ((dev = calloc(1, sizeof *dev)) == NULL) { + log_Printf(LogWARN, "%s: Cannot allocate an atm device: %s\n", + p->link.name, strerror(errno)); + return NULL; + } + + sock.snatm_len = sizeof sock; + sock.snatm_family = AF_NATM; + strncpy(sock.snatm_if, iface, IFNAMSIZ); + sock.snatm_vpi = vpi; + sock.snatm_vci = vci; + + log_Printf(LogPHASE, "%s: Connecting to %s:%u.%u\n", p->link.name, + iface, vpi, vci); + + p->fd = socket(PF_NATM, SOCK_DGRAM, PROTO_NATMAAL5); + if (p->fd >= 0) { + log_Printf(LogDEBUG, "%s: Opened atm socket %s\n", p->link.name, + p->name.full); + if (connect(p->fd, (struct sockaddr *)&sock, sizeof sock) == 0) + return dev; + else + log_Printf(LogWARN, "%s: connect: %s\n", p->name.full, strerror(errno)); + } else + log_Printf(LogWARN, "%s: socket: %s\n", p->name.full, strerror(errno)); + + close(p->fd); + p->fd = -1; + free(dev); + + return NULL; +} + +struct device * +atm_Create(struct physical *p) +{ + struct atmdevice *dev; + + dev = NULL; + if (p->fd < 0 && !strncasecmp(p->name.full, PPPOA, PPPOA_LEN) + && p->name.full[PPPOA_LEN] == ':') { + char iface[25]; + unsigned vci, vpi; + + if (sscanf(p->name.full + PPPOA_LEN + 1, "%25[A-Za-z0-9]:%u.%u", iface, + &vpi, &vci) != 3) { + log_Printf(LogWARN, "Malformed ATM device name \'%s\', " + "PPPoA:if:vpi.vci expected\n", p->name.full); + return NULL; + } + + dev = atm_CreateDevice(p, iface, vpi, vci); + } + + if (dev) { + memcpy(&dev->dev, &baseatmdevice, sizeof dev->dev); + physical_SetupStack(p, dev->dev.name, PHYSICAL_FORCE_SYNCNOACF); + if (p->cfg.cd.necessity != CD_DEFAULT) + log_Printf(LogWARN, "Carrier settings ignored\n"); + return &dev->dev; + } + + return NULL; +} diff --git a/src/atm.h b/src/atm.h new file mode 100644 index 0000000..f91e826 --- /dev/null +++ b/src/atm.h @@ -0,0 +1,35 @@ +/*- + * Copyright (c) 2000 Jakob Stoklund Olesen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/atm.h,v 1.2.26.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +struct physical; +struct device; + +extern struct device *atm_Create(struct physical *); +extern struct device *atm_iov2device(int, struct physical *, + struct iovec *, int *, int, int *, int *); +extern unsigned atm_DeviceSize(void); diff --git a/src/auth.c b/src/auth.c new file mode 100644 index 0000000..1b441bc --- /dev/null +++ b/src/auth.c @@ -0,0 +1,479 @@ +/*- + * Copyright (c) 1996 - 2001 Brian Somers + * based on work by Toshiharu OHNO + * Internet Initiative Japan, Inc (IIJ) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/auth.c,v 1.58.10.1.8.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifndef NOPAM +#include +#ifdef OPENPAM +#include +#endif +#endif /* !NOPAM */ + +#include "layer.h" +#include "mbuf.h" +#include "defs.h" +#include "log.h" +#include "timer.h" +#include "fsm.h" +#include "iplist.h" +#include "throughput.h" +#include "slcompress.h" +#include "lqr.h" +#include "hdlc.h" +#include "ncpaddr.h" +#include "ipcp.h" +#include "auth.h" +#include "systems.h" +#include "lcp.h" +#include "ccp.h" +#include "link.h" +#include "descriptor.h" +#include "chat.h" +#include "proto.h" +#include "filter.h" +#include "mp.h" +#ifndef NORADIUS +#include "radius.h" +#endif +#include "cbcp.h" +#include "chap.h" +#include "async.h" +#include "physical.h" +#include "datalink.h" +#include "ipv6cp.h" +#include "ncp.h" +#include "bundle.h" + +const char * +Auth2Nam(u_short auth, u_char type) +{ + static char chap[10]; + + switch (auth) { + case PROTO_PAP: + return "PAP"; + case PROTO_CHAP: + snprintf(chap, sizeof chap, "CHAP 0x%02x", type); + return chap; + case 0: + return "none"; + } + return "unknown"; +} + +#if !defined(NOPAM) && !defined(OPENPAM) +static int +pam_conv(int n, const struct pam_message **msg, struct pam_response **resp, + void *data) +{ + + if (n != 1 || msg[0]->msg_style != PAM_PROMPT_ECHO_OFF) + return (PAM_CONV_ERR); + if ((*resp = malloc(sizeof(struct pam_response))) == NULL) + return (PAM_CONV_ERR); + (*resp)[0].resp = strdup((const char *)data); + (*resp)[0].resp_retcode = 0; + + return ((*resp)[0].resp != NULL ? PAM_SUCCESS : PAM_CONV_ERR); +} +#endif /* !defined(NOPAM) && !defined(OPENPAM) */ + +static int +auth_CheckPasswd(const char *name, const char *data, const char *key) +{ + if (!strcmp(data, "*")) { +#ifdef NOPAM + /* Then look up the real password database */ + struct passwd *pw; + int result; + + result = (pw = getpwnam(name)) && + !strcmp(crypt(key, pw->pw_passwd), pw->pw_passwd); + endpwent(); + return result; +#else /* !NOPAM */ + /* Then consult with PAM. */ + pam_handle_t *pamh; + int status; + + struct pam_conv pamc = { +#ifdef OPENPAM + &openpam_nullconv, NULL +#else + &pam_conv, key +#endif + }; + + if (pam_start("ppp", name, &pamc, &pamh) != PAM_SUCCESS) + return (0); +#ifdef OPENPAM + if ((status = pam_set_item(pamh, PAM_AUTHTOK, key)) == PAM_SUCCESS) +#endif + status = pam_authenticate(pamh, 0); + pam_end(pamh, status); + return (status == PAM_SUCCESS); +#endif /* !NOPAM */ + } + + return !strcmp(data, key); +} + +int +auth_SetPhoneList(const char *name, char *phone, int phonelen) +{ + FILE *fp; + int n, lineno; + char *vector[6], buff[LINE_LEN]; + const char *slash; + + fp = OpenSecret(SECRETFILE); + if (fp != NULL) { +again: + lineno = 0; + while (fgets(buff, sizeof buff, fp)) { + lineno++; + if (buff[0] == '#') + continue; + buff[strlen(buff) - 1] = '\0'; + memset(vector, '\0', sizeof vector); + if ((n = MakeArgs(buff, vector, VECSIZE(vector), PARSE_REDUCE)) < 0) + log_Printf(LogWARN, "%s: %d: Invalid line\n", SECRETFILE, lineno); + if (n < 5) + continue; + if (strcmp(vector[0], name) == 0) { + CloseSecret(fp); + if (*vector[4] == '\0') + return 0; + strncpy(phone, vector[4], phonelen - 1); + phone[phonelen - 1] = '\0'; + return 1; /* Valid */ + } + } + + if ((slash = strrchr(name, '\\')) != NULL && slash[1]) { + /* Look for the name without the leading domain */ + name = slash + 1; + rewind(fp); + goto again; + } + + CloseSecret(fp); + } + *phone = '\0'; + return 0; +} + +int +auth_Select(struct bundle *bundle, const char *name) +{ + FILE *fp; + int n, lineno; + char *vector[5], buff[LINE_LEN]; + const char *slash; + + if (*name == '\0') { + ipcp_Setup(&bundle->ncp.ipcp, INADDR_NONE); + return 1; + } + +#ifndef NORADIUS + if (bundle->radius.valid && bundle->radius.ip.s_addr != INADDR_NONE && + bundle->radius.ip.s_addr != RADIUS_INADDR_POOL) { + /* We've got a radius IP - it overrides everything */ + if (!ipcp_UseHisIPaddr(bundle, bundle->radius.ip)) + return 0; + ipcp_Setup(&bundle->ncp.ipcp, bundle->radius.mask.s_addr); + /* Continue with ppp.secret in case we've got a new label */ + } +#endif + + fp = OpenSecret(SECRETFILE); + if (fp != NULL) { +again: + lineno = 0; + while (fgets(buff, sizeof buff, fp)) { + lineno++; + if (buff[0] == '#') + continue; + buff[strlen(buff) - 1] = '\0'; + memset(vector, '\0', sizeof vector); + if ((n = MakeArgs(buff, vector, VECSIZE(vector), PARSE_REDUCE)) < 0) + log_Printf(LogWARN, "%s: %d: Invalid line\n", SECRETFILE, lineno); + if (n < 2) + continue; + if (strcmp(vector[0], name) == 0) { + CloseSecret(fp); +#ifndef NORADIUS + if (!bundle->radius.valid || bundle->radius.ip.s_addr == INADDR_NONE) { +#endif + if (n > 2 && *vector[2] && strcmp(vector[2], "*") && + !ipcp_UseHisaddr(bundle, vector[2], 1)) + return 0; + ipcp_Setup(&bundle->ncp.ipcp, INADDR_NONE); +#ifndef NORADIUS + } +#endif + if (n > 3 && *vector[3] && strcmp(vector[3], "*")) + bundle_SetLabel(bundle, vector[3]); + return 1; /* Valid */ + } + } + + if ((slash = strrchr(name, '\\')) != NULL && slash[1]) { + /* Look for the name without the leading domain */ + name = slash + 1; + rewind(fp); + goto again; + } + + CloseSecret(fp); + } + +#ifndef NOPASSWDAUTH + /* Let 'em in anyway - they must have been in the passwd file */ + ipcp_Setup(&bundle->ncp.ipcp, INADDR_NONE); + return 1; +#else +#ifndef NORADIUS + if (bundle->radius.valid) + return 1; +#endif + + /* Disappeared from ppp.secret ??? */ + return 0; +#endif +} + +int +auth_Validate(struct bundle *bundle, const char *name, const char *key) +{ + /* Used by PAP routines */ + + FILE *fp; + int n, lineno; + char *vector[5], buff[LINE_LEN]; + const char *slash; + + fp = OpenSecret(SECRETFILE); +again: + lineno = 0; + if (fp != NULL) { + while (fgets(buff, sizeof buff, fp)) { + lineno++; + if (buff[0] == '#') + continue; + buff[strlen(buff) - 1] = 0; + memset(vector, '\0', sizeof vector); + if ((n = MakeArgs(buff, vector, VECSIZE(vector), PARSE_REDUCE)) < 0) + log_Printf(LogWARN, "%s: %d: Invalid line\n", SECRETFILE, lineno); + if (n < 2) + continue; + if (strcmp(vector[0], name) == 0) { + CloseSecret(fp); + return auth_CheckPasswd(name, vector[1], key); + } + } + } + + if ((slash = strrchr(name, '\\')) != NULL && slash[1]) { + /* Look for the name without the leading domain */ + name = slash + 1; + if (fp != NULL) { + rewind(fp); + goto again; + } + } + + if (fp != NULL) + CloseSecret(fp); + +#ifndef NOPASSWDAUTH + if (Enabled(bundle, OPT_PASSWDAUTH)) + return auth_CheckPasswd(name, "*", key); +#endif + + return 0; /* Invalid */ +} + +char * +auth_GetSecret(const char *name, size_t len) +{ + /* Used by CHAP routines */ + + FILE *fp; + int n, lineno; + char *vector[5]; + const char *slash; + static char buff[LINE_LEN]; /* vector[] will point here when returned */ + + fp = OpenSecret(SECRETFILE); + if (fp == NULL) + return (NULL); + +again: + lineno = 0; + while (fgets(buff, sizeof buff, fp)) { + lineno++; + if (buff[0] == '#') + continue; + n = strlen(buff) - 1; + if (buff[n] == '\n') + buff[n] = '\0'; /* Trim the '\n' */ + memset(vector, '\0', sizeof vector); + if ((n = MakeArgs(buff, vector, VECSIZE(vector), PARSE_REDUCE)) < 0) + log_Printf(LogWARN, "%s: %d: Invalid line\n", SECRETFILE, lineno); + if (n < 2) + continue; + if (strlen(vector[0]) == len && strncmp(vector[0], name, len) == 0) { + CloseSecret(fp); + return vector[1]; + } + } + + if ((slash = strrchr(name, '\\')) != NULL && slash[1]) { + /* Go back and look for the name without the leading domain */ + len -= slash - name + 1; + name = slash + 1; + rewind(fp); + goto again; + } + + CloseSecret(fp); + return (NULL); /* Invalid */ +} + +static void +AuthTimeout(void *vauthp) +{ + struct authinfo *authp = (struct authinfo *)vauthp; + + timer_Stop(&authp->authtimer); + if (--authp->retry > 0) { + authp->id++; + (*authp->fn.req)(authp); + timer_Start(&authp->authtimer); + } else { + log_Printf(LogPHASE, "Auth: No response from server\n"); + datalink_AuthNotOk(authp->physical->dl); + } +} + +void +auth_Init(struct authinfo *authp, struct physical *p, auth_func req, + auth_func success, auth_func failure) +{ + memset(authp, '\0', sizeof(struct authinfo)); + authp->cfg.fsm.timeout = DEF_FSMRETRY; + authp->cfg.fsm.maxreq = DEF_FSMAUTHTRIES; + authp->cfg.fsm.maxtrm = 0; /* not used */ + authp->fn.req = req; + authp->fn.success = success; + authp->fn.failure = failure; + authp->physical = p; +} + +void +auth_StartReq(struct authinfo *authp) +{ + timer_Stop(&authp->authtimer); + authp->authtimer.func = AuthTimeout; + authp->authtimer.name = "auth"; + authp->authtimer.load = authp->cfg.fsm.timeout * SECTICKS; + authp->authtimer.arg = (void *)authp; + authp->retry = authp->cfg.fsm.maxreq; + authp->id = 1; + (*authp->fn.req)(authp); + timer_Start(&authp->authtimer); +} + +void +auth_StopTimer(struct authinfo *authp) +{ + timer_Stop(&authp->authtimer); +} + +struct mbuf * +auth_ReadHeader(struct authinfo *authp, struct mbuf *bp) +{ + size_t len; + + len = m_length(bp); + if (len >= sizeof authp->in.hdr) { + bp = mbuf_Read(bp, (u_char *)&authp->in.hdr, sizeof authp->in.hdr); + if (len >= ntohs(authp->in.hdr.length)) + return bp; + authp->in.hdr.length = htons(0); + log_Printf(LogWARN, "auth_ReadHeader: Short packet (%u > %zu) !\n", + ntohs(authp->in.hdr.length), len); + } else { + authp->in.hdr.length = htons(0); + log_Printf(LogWARN, "auth_ReadHeader: Short packet header (%u > %zu) !\n", + (int)(sizeof authp->in.hdr), len); + } + + m_freem(bp); + return NULL; +} + +struct mbuf * +auth_ReadName(struct authinfo *authp, struct mbuf *bp, size_t len) +{ + if (len > sizeof authp->in.name - 1) + log_Printf(LogWARN, "auth_ReadName: Name too long (%zu) !\n", len); + else { + size_t mlen = m_length(bp); + + if (len > mlen) + log_Printf(LogWARN, "auth_ReadName: Short packet (%zu > %zu) !\n", + len, mlen); + else { + bp = mbuf_Read(bp, (u_char *)authp->in.name, len); + authp->in.name[len] = '\0'; + return bp; + } + } + + *authp->in.name = '\0'; + m_freem(bp); + return NULL; +} diff --git a/src/auth.h b/src/auth.h new file mode 100644 index 0000000..c08565b --- /dev/null +++ b/src/auth.h @@ -0,0 +1,68 @@ +/*- + * Copyright (c) 1996 - 2001 Brian Somers + * based on work by Toshiharu OHNO + * Internet Initiative Japan, Inc (IIJ) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/auth.h,v 1.21.26.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +struct physical; +struct bundle; +struct authinfo; +typedef void (*auth_func)(struct authinfo *); + +struct authinfo { + struct { + auth_func req; + auth_func success; + auth_func failure; + } fn; + struct { + struct fsmheader hdr; + char name[AUTHLEN]; + } in; + struct pppTimer authtimer; + int retry; + int id; + struct physical *physical; + struct { + struct fsm_retry fsm; /* How often/frequently to resend requests */ + } cfg; +}; + +#define auth_Failure(a) (*(a)->fn.failure)(a) +#define auth_Success(a) (*(a)->fn.success)(a) + +extern const char *Auth2Nam(u_short, u_char); +extern void auth_Init(struct authinfo *, struct physical *, + auth_func, auth_func, auth_func); +extern void auth_StopTimer(struct authinfo *); +extern void auth_StartReq(struct authinfo *); +extern int auth_Validate(struct bundle *, const char *, const char *); +extern char *auth_GetSecret(const char *, size_t); +extern int auth_SetPhoneList(const char *, char *, int); +extern int auth_Select(struct bundle *, const char *); +extern struct mbuf *auth_ReadHeader(struct authinfo *, struct mbuf *); +extern struct mbuf *auth_ReadName(struct authinfo *, struct mbuf *, size_t); diff --git a/src/bundle.c b/src/bundle.c new file mode 100644 index 0000000..d5fe108 --- /dev/null +++ b/src/bundle.c @@ -0,0 +1,2019 @@ +/*- + * Copyright (c) 1998 Brian Somers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/bundle.c,v 1.136.26.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#include +#include +#include +#include +#include /* For TUNS* ioctls */ +#include +#include +#include +#include + +#include +#include +#ifdef __OpenBSD__ +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "layer.h" +#include "defs.h" +#include "command.h" +#include "mbuf.h" +#include "log.h" +#include "id.h" +#include "timer.h" +#include "fsm.h" +#include "iplist.h" +#include "lqr.h" +#include "hdlc.h" +#include "throughput.h" +#include "slcompress.h" +#include "ncpaddr.h" +#include "ip.h" +#include "ipcp.h" +#include "filter.h" +#include "descriptor.h" +#include "route.h" +#include "lcp.h" +#include "ccp.h" +#include "link.h" +#include "mp.h" +#ifndef NORADIUS +#include "radius.h" +#endif +#include "ipv6cp.h" +#include "ncp.h" +#include "bundle.h" +#include "async.h" +#include "physical.h" +#include "auth.h" +#include "proto.h" +#include "chap.h" +#include "tun.h" +#include "prompt.h" +#include "chat.h" +#include "cbcp.h" +#include "datalink.h" +#include "iface.h" +#include "server.h" +#include "probe.h" +#ifndef NODES +#include "mppe.h" +#endif + +#define SCATTER_SEGMENTS 7 /* version, datalink, name, physical, + throughput, throughput, device */ + +#define SEND_MAXFD 3 /* Max file descriptors passed through + the local domain socket */ + +static int bundle_RemainingIdleTime(struct bundle *); + +static const char * const PhaseNames[] = { + "Dead", "Establish", "Authenticate", "Network", "Terminate" +}; + +const char * +bundle_PhaseName(struct bundle *bundle) +{ + return bundle->phase <= PHASE_TERMINATE ? + PhaseNames[bundle->phase] : "unknown"; +} + +void +bundle_NewPhase(struct bundle *bundle, u_int new) +{ + if (new == bundle->phase) + return; + + if (new <= PHASE_TERMINATE) + log_Printf(LogPHASE, "bundle: %s\n", PhaseNames[new]); + + switch (new) { + case PHASE_DEAD: + bundle->phase = new; +#ifndef NODES + MPPE_MasterKeyValid = 0; +#endif + log_DisplayPrompts(); + break; + + case PHASE_ESTABLISH: + bundle->phase = new; + break; + + case PHASE_AUTHENTICATE: + bundle->phase = new; + log_DisplayPrompts(); + break; + + case PHASE_NETWORK: + if (ncp_fsmStart(&bundle->ncp, bundle)) { + bundle->phase = new; + log_DisplayPrompts(); + } else { + log_Printf(LogPHASE, "bundle: All NCPs are disabled\n"); + bundle_Close(bundle, NULL, CLOSE_STAYDOWN); + } + break; + + case PHASE_TERMINATE: + bundle->phase = new; + mp_Down(&bundle->ncp.mp); + log_DisplayPrompts(); + break; + } +} + +static void +bundle_LayerStart(void *v __unused, struct fsm *fp __unused) +{ + /* The given FSM is about to start up ! */ +} + + +void +bundle_Notify(struct bundle *bundle, char c) +{ + if (bundle->notify.fd != -1) { + int ret; + + ret = write(bundle->notify.fd, &c, 1); + if (c != EX_REDIAL && c != EX_RECONNECT) { + if (ret == 1) + log_Printf(LogCHAT, "Parent notified of %s\n", + c == EX_NORMAL ? "success" : "failure"); + else + log_Printf(LogERROR, "Failed to notify parent of success\n"); + close(bundle->notify.fd); + bundle->notify.fd = -1; + } else if (ret == 1) + log_Printf(LogCHAT, "Parent notified of %s\n", ex_desc(c)); + else + log_Printf(LogERROR, "Failed to notify parent of %s\n", ex_desc(c)); + } +} + +static void +bundle_ClearQueues(void *v) +{ + struct bundle *bundle = (struct bundle *)v; + struct datalink *dl; + + log_Printf(LogPHASE, "Clearing choked output queue\n"); + timer_Stop(&bundle->choked.timer); + + /* + * Emergency time: + * + * We've had a full queue for PACKET_DEL_SECS seconds without being + * able to get rid of any of the packets. We've probably given up + * on the redials at this point, and the queued data has almost + * definitely been timed out by the layer above. As this is preventing + * us from reading the TUN_NAME device (we don't want to buffer stuff + * indefinitely), we may as well nuke this data and start with a clean + * slate ! + * + * Unfortunately, this has the side effect of shafting any compression + * dictionaries in use (causing the relevant RESET_REQ/RESET_ACK). + */ + + ncp_DeleteQueues(&bundle->ncp); + for (dl = bundle->links; dl; dl = dl->next) + physical_DeleteQueue(dl->physical); +} + +static void +bundle_LinkAdded(struct bundle *bundle, struct datalink *dl) +{ + bundle->phys_type.all |= dl->physical->type; + if (dl->state == DATALINK_OPEN) + bundle->phys_type.open |= dl->physical->type; + +#ifndef NORADIUS + if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL)) + != bundle->phys_type.open && bundle->session.timer.state == TIMER_STOPPED) + if (bundle->radius.sessiontime) + bundle_StartSessionTimer(bundle, 0); +#endif + + if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL)) + != bundle->phys_type.open && bundle->idle.timer.state == TIMER_STOPPED) + /* We may need to start our idle timer */ + bundle_StartIdleTimer(bundle, 0); +} + +void +bundle_LinksRemoved(struct bundle *bundle) +{ + struct datalink *dl; + + bundle->phys_type.all = bundle->phys_type.open = 0; + for (dl = bundle->links; dl; dl = dl->next) + bundle_LinkAdded(bundle, dl); + + bundle_CalculateBandwidth(bundle); + mp_CheckAutoloadTimer(&bundle->ncp.mp); + + if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL)) + == bundle->phys_type.open) { +#ifndef NORADIUS + if (bundle->radius.sessiontime) + bundle_StopSessionTimer(bundle); +#endif + bundle_StopIdleTimer(bundle); + } +} + +static void +bundle_LayerUp(void *v, struct fsm *fp) +{ + /* + * The given fsm is now up + * If it's an LCP, adjust our phys_mode.open value and check the + * autoload timer. + * If it's the first NCP, calculate our bandwidth + * If it's the first NCP, set our ``upat'' time + * If it's the first NCP, start the idle timer. + * If it's an NCP, tell our -background parent to go away. + * If it's the first NCP, start the autoload timer + */ + struct bundle *bundle = (struct bundle *)v; + + if (fp->proto == PROTO_LCP) { + struct physical *p = link2physical(fp->link); + + bundle_LinkAdded(bundle, p->dl); + mp_CheckAutoloadTimer(&bundle->ncp.mp); + } else if (isncp(fp->proto)) { + if (ncp_LayersOpen(&fp->bundle->ncp) == 1) { + bundle_CalculateBandwidth(fp->bundle); + time(&bundle->upat); +#ifndef NORADIUS + if (bundle->radius.sessiontime) + bundle_StartSessionTimer(bundle, 0); +#endif + bundle_StartIdleTimer(bundle, 0); + mp_CheckAutoloadTimer(&fp->bundle->ncp.mp); + } + bundle_Notify(bundle, EX_NORMAL); + } else if (fp->proto == PROTO_CCP) + bundle_CalculateBandwidth(fp->bundle); /* Against ccp_MTUOverhead */ +} + +static void +bundle_LayerDown(void *v, struct fsm *fp) +{ + /* + * The given FSM has been told to come down. + * If it's our last NCP, stop the idle timer. + * If it's our last NCP, clear our ``upat'' value. + * If it's our last NCP, stop the autoload timer + * If it's an LCP, adjust our phys_type.open value and any timers. + * If it's an LCP and we're in multilink mode, adjust our tun + * If it's the last LCP, down all NCPs + * speed and make sure our minimum sequence number is adjusted. + */ + + struct bundle *bundle = (struct bundle *)v; + + if (isncp(fp->proto)) { + if (ncp_LayersOpen(&fp->bundle->ncp) == 0) { +#ifndef NORADIUS + if (bundle->radius.sessiontime) + bundle_StopSessionTimer(bundle); +#endif + bundle_StopIdleTimer(bundle); + bundle->upat = 0; + mp_StopAutoloadTimer(&bundle->ncp.mp); + } + } else if (fp->proto == PROTO_LCP) { + struct datalink *dl; + struct datalink *lost; + int others_active; + + bundle_LinksRemoved(bundle); /* adjust timers & phys_type values */ + + lost = NULL; + others_active = 0; + for (dl = bundle->links; dl; dl = dl->next) { + if (fp == &dl->physical->link.lcp.fsm) + lost = dl; + else if (dl->state != DATALINK_CLOSED && dl->state != DATALINK_HANGUP) + others_active++; + } + + if (bundle->ncp.mp.active) { + bundle_CalculateBandwidth(bundle); + + if (lost) + mp_LinkLost(&bundle->ncp.mp, lost); + else + log_Printf(LogALERT, "Oops, lost an unrecognised datalink (%s) !\n", + fp->link->name); + } + + if (!others_active) { + /* Down the NCPs. We don't expect to get fsm_Close()d ourself ! */ + ncp2initial(&bundle->ncp); + mp_Down(&bundle->ncp.mp); + } + } +} + +static void +bundle_LayerFinish(void *v, struct fsm *fp) +{ + /* The given fsm is now down (fp cannot be NULL) + * + * If it's the last NCP, fsm_Close all LCPs + * If it's the last NCP, bring any MP layer down + */ + + struct bundle *bundle = (struct bundle *)v; + struct datalink *dl; + + if (isncp(fp->proto) && !ncp_LayersUnfinished(&bundle->ncp)) { + if (bundle_Phase(bundle) != PHASE_DEAD) + bundle_NewPhase(bundle, PHASE_TERMINATE); + for (dl = bundle->links; dl; dl = dl->next) + if (dl->state == DATALINK_OPEN) + datalink_Close(dl, CLOSE_STAYDOWN); + fsm2initial(fp); + mp_Down(&bundle->ncp.mp); + } +} + +void +bundle_Close(struct bundle *bundle, const char *name, int how) +{ + /* + * Please close the given datalink. + * If name == NULL or name is the last datalink, fsm_Close all NCPs + * (except our MP) + * If it isn't the last datalink, just Close that datalink. + */ + + struct datalink *dl, *this_dl; + int others_active; + + others_active = 0; + this_dl = NULL; + + for (dl = bundle->links; dl; dl = dl->next) { + if (name && !strcasecmp(name, dl->name)) + this_dl = dl; + if (name == NULL || this_dl == dl) { + switch (how) { + case CLOSE_LCP: + datalink_DontHangup(dl); + break; + case CLOSE_STAYDOWN: + datalink_StayDown(dl); + break; + } + } else if (dl->state != DATALINK_CLOSED && dl->state != DATALINK_HANGUP) + others_active++; + } + + if (name && this_dl == NULL) { + log_Printf(LogWARN, "%s: Invalid datalink name\n", name); + return; + } + + if (!others_active) { +#ifndef NORADIUS + if (bundle->radius.sessiontime) + bundle_StopSessionTimer(bundle); +#endif + bundle_StopIdleTimer(bundle); + if (ncp_LayersUnfinished(&bundle->ncp)) + ncp_Close(&bundle->ncp); + else { + ncp2initial(&bundle->ncp); + mp_Down(&bundle->ncp.mp); + for (dl = bundle->links; dl; dl = dl->next) + datalink_Close(dl, how); + } + } else if (this_dl && this_dl->state != DATALINK_CLOSED && + this_dl->state != DATALINK_HANGUP) + datalink_Close(this_dl, how); +} + +void +bundle_Down(struct bundle *bundle, int how) +{ + struct datalink *dl; + + for (dl = bundle->links; dl; dl = dl->next) + datalink_Down(dl, how); +} + +static int +bundle_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n) +{ + struct bundle *bundle = descriptor2bundle(d); + struct datalink *dl; + int result, nlinks; + u_short ifqueue; + size_t queued; + + result = 0; + + /* If there are aren't many packets queued, look for some more. */ + for (nlinks = 0, dl = bundle->links; dl; dl = dl->next) + nlinks++; + + if (nlinks) { + queued = r ? ncp_FillPhysicalQueues(&bundle->ncp, bundle) : + ncp_QueueLen(&bundle->ncp); + + if (r && (bundle->phase == PHASE_NETWORK || + bundle->phys_type.all & PHYS_AUTO)) { + /* enough surplus so that we can tell if we're getting swamped */ + ifqueue = nlinks > bundle->cfg.ifqueue ? nlinks : bundle->cfg.ifqueue; + if (queued < ifqueue) { + /* Not enough - select() for more */ + if (bundle->choked.timer.state == TIMER_RUNNING) + timer_Stop(&bundle->choked.timer); /* Not needed any more */ + FD_SET(bundle->dev.fd, r); + if (*n < bundle->dev.fd + 1) + *n = bundle->dev.fd + 1; + log_Printf(LogTIMER, "%s: fdset(r) %d\n", TUN_NAME, bundle->dev.fd); + result++; + } else if (bundle->choked.timer.state == TIMER_STOPPED) { + bundle->choked.timer.func = bundle_ClearQueues; + bundle->choked.timer.name = "output choke"; + bundle->choked.timer.load = bundle->cfg.choked.timeout * SECTICKS; + bundle->choked.timer.arg = bundle; + timer_Start(&bundle->choked.timer); + } + } + } + +#ifndef NORADIUS + result += descriptor_UpdateSet(&bundle->radius.desc, r, w, e, n); +#endif + + /* Which links need a select() ? */ + for (dl = bundle->links; dl; dl = dl->next) + result += descriptor_UpdateSet(&dl->desc, r, w, e, n); + + /* + * This *MUST* be called after the datalink UpdateSet()s as it + * might be ``holding'' one of the datalinks (death-row) and + * wants to be able to de-select() it from the descriptor set. + */ + result += descriptor_UpdateSet(&bundle->ncp.mp.server.desc, r, w, e, n); + + return result; +} + +static int +bundle_IsSet(struct fdescriptor *d, const fd_set *fdset) +{ + struct bundle *bundle = descriptor2bundle(d); + struct datalink *dl; + + for (dl = bundle->links; dl; dl = dl->next) + if (descriptor_IsSet(&dl->desc, fdset)) + return 1; + +#ifndef NORADIUS + if (descriptor_IsSet(&bundle->radius.desc, fdset)) + return 1; +#endif + + if (descriptor_IsSet(&bundle->ncp.mp.server.desc, fdset)) + return 1; + + return FD_ISSET(bundle->dev.fd, fdset); +} + +static void +bundle_DescriptorRead(struct fdescriptor *d __unused, struct bundle *bundle, + const fd_set *fdset) +{ + struct datalink *dl; + unsigned secs; + u_int32_t af; + + if (descriptor_IsSet(&bundle->ncp.mp.server.desc, fdset)) + descriptor_Read(&bundle->ncp.mp.server.desc, bundle, fdset); + + for (dl = bundle->links; dl; dl = dl->next) + if (descriptor_IsSet(&dl->desc, fdset)) + descriptor_Read(&dl->desc, bundle, fdset); + +#ifndef NORADIUS + if (descriptor_IsSet(&bundle->radius.desc, fdset)) + descriptor_Read(&bundle->radius.desc, bundle, fdset); +#endif + + if (FD_ISSET(bundle->dev.fd, fdset)) { + struct tun_data tun; + int n, pri; + u_char *data; + size_t sz; + + if (bundle->dev.header) { + data = (u_char *)&tun; + sz = sizeof tun; + } else { + data = tun.data; + sz = sizeof tun.data; + } + + /* something to read from tun */ + + n = read(bundle->dev.fd, data, sz); + if (n < 0) { + log_Printf(LogWARN, "%s: read: %s\n", bundle->dev.Name, strerror(errno)); + return; + } + + if (bundle->dev.header) { + n -= sz - sizeof tun.data; + if (n <= 0) { + log_Printf(LogERROR, "%s: read: Got only %d bytes of data !\n", + bundle->dev.Name, n); + return; + } + af = ntohl(tun.header.family); +#ifndef NOINET6 + if (af != AF_INET && af != AF_INET6) +#else + if (af != AF_INET) +#endif + /* XXX: Should be maintaining drop/family counts ! */ + return; + } else + af = AF_INET; + + if (af == AF_INET && ((struct ip *)tun.data)->ip_dst.s_addr == + bundle->ncp.ipcp.my_ip.s_addr) { + /* we've been asked to send something addressed *to* us :( */ + if (Enabled(bundle, OPT_LOOPBACK)) { + pri = PacketCheck(bundle, af, tun.data, n, &bundle->filter.in, + NULL, NULL); + if (pri >= 0) { + n += sz - sizeof tun.data; + write(bundle->dev.fd, data, n); + log_Printf(LogDEBUG, "Looped back packet addressed to myself\n"); + } + return; + } else + log_Printf(LogDEBUG, "Oops - forwarding packet addressed to myself\n"); + } + + /* + * Process on-demand dialup. Output packets are queued within the tunnel + * device until the appropriate NCP is opened. + */ + + if (bundle_Phase(bundle) == PHASE_DEAD) { + /* + * Note, we must be in AUTO mode :-/ otherwise our interface should + * *not* be UP and we can't receive data + */ + pri = PacketCheck(bundle, af, tun.data, n, &bundle->filter.dial, + NULL, NULL); + if (pri >= 0) + bundle_Open(bundle, NULL, PHYS_AUTO, 0); + else + /* + * Drop the packet. If we were to queue it, we'd just end up with + * a pile of timed-out data in our output queue by the time we get + * around to actually dialing. We'd also prematurely reach the + * threshold at which we stop select()ing to read() the tun + * device - breaking auto-dial. + */ + return; + } + + secs = 0; + pri = PacketCheck(bundle, af, tun.data, n, &bundle->filter.out, + NULL, &secs); + if (pri >= 0) { + /* Prepend the number of seconds timeout given in the filter */ + tun.header.timeout = secs; + ncp_Enqueue(&bundle->ncp, af, pri, (char *)&tun, n + sizeof tun.header); + } + } +} + +static int +bundle_DescriptorWrite(struct fdescriptor *d __unused, struct bundle *bundle, + const fd_set *fdset) +{ + struct datalink *dl; + int result = 0; + + /* This is not actually necessary as struct mpserver doesn't Write() */ + if (descriptor_IsSet(&bundle->ncp.mp.server.desc, fdset)) + if (descriptor_Write(&bundle->ncp.mp.server.desc, bundle, fdset) == 1) + result++; + + for (dl = bundle->links; dl; dl = dl->next) + if (descriptor_IsSet(&dl->desc, fdset)) + switch (descriptor_Write(&dl->desc, bundle, fdset)) { + case -1: + datalink_ComeDown(dl, CLOSE_NORMAL); + break; + case 1: + result++; + } + + return result; +} + +void +bundle_LockTun(struct bundle *bundle) +{ + FILE *lockfile; + char pidfile[PATH_MAX]; + + snprintf(pidfile, sizeof pidfile, "%stun%d.pid", _PATH_VARRUN, bundle->unit); + lockfile = ID0fopen(pidfile, "w"); + if (lockfile != NULL) { + fprintf(lockfile, "%d\n", (int)getpid()); + fclose(lockfile); + } +#ifndef RELEASE_CRUNCH + else + log_Printf(LogERROR, "Warning: Can't create %s: %s\n", + pidfile, strerror(errno)); +#endif +} + +static void +bundle_UnlockTun(struct bundle *bundle) +{ + char pidfile[PATH_MAX]; + + snprintf(pidfile, sizeof pidfile, "%stun%d.pid", _PATH_VARRUN, bundle->unit); + ID0unlink(pidfile); +} + +struct bundle * +bundle_Create(const char *prefix, int type, int unit) +{ + static struct bundle bundle; /* there can be only one */ + int enoentcount, err, minunit, maxunit; + const char *ifname; +#if defined(__FreeBSD__) && !defined(NOKLDLOAD) + int kldtried; +#endif +#if defined(TUNSIFMODE) || defined(TUNSLMODE) || defined(TUNSIFHEAD) + int iff; +#endif + + if (bundle.iface != NULL) { /* Already allocated ! */ + log_Printf(LogALERT, "bundle_Create: There's only one BUNDLE !\n"); + return NULL; + } + + if (unit == -1) { + minunit = 0; + maxunit = -1; + } else { + minunit = unit; + maxunit = unit + 1; + } + err = ENOENT; + enoentcount = 0; +#if defined(__FreeBSD__) && !defined(NOKLDLOAD) + kldtried = 0; +#endif + for (bundle.unit = minunit; bundle.unit != maxunit; bundle.unit++) { + snprintf(bundle.dev.Name, sizeof bundle.dev.Name, "%s%d", + prefix, bundle.unit); + bundle.dev.fd = ID0open(bundle.dev.Name, O_RDWR); + if (bundle.dev.fd >= 0) + break; + else if (errno == ENXIO || errno == ENOENT) { +#if defined(__FreeBSD__) && !defined(NOKLDLOAD) + if (bundle.unit == minunit && !kldtried++) { + /* + * Attempt to load the tunnel interface KLD if it isn't loaded + * already. + */ + if (loadmodules(LOAD_VERBOSLY, "if_tun", NULL)) + bundle.unit--; + continue; + } +#endif + if (errno != ENOENT || ++enoentcount > 2) { + err = errno; + break; + } + } else + err = errno; + } + + if (bundle.dev.fd < 0) { + if (unit == -1) + log_Printf(LogWARN, "No available tunnel devices found (%s)\n", + strerror(err)); + else + log_Printf(LogWARN, "%s%d: %s\n", prefix, unit, strerror(err)); + return NULL; + } + + log_SetTun(bundle.unit); + + ifname = strrchr(bundle.dev.Name, '/'); + if (ifname == NULL) + ifname = bundle.dev.Name; + else + ifname++; + + bundle.iface = iface_Create(ifname); + if (bundle.iface == NULL) { + close(bundle.dev.fd); + return NULL; + } + +#ifdef TUNSIFMODE + /* Make sure we're POINTOPOINT & IFF_MULTICAST */ + iff = IFF_POINTOPOINT | IFF_MULTICAST; + if (ID0ioctl(bundle.dev.fd, TUNSIFMODE, &iff) < 0) + log_Printf(LogERROR, "bundle_Create: ioctl(TUNSIFMODE): %s\n", + strerror(errno)); +#endif + +#ifdef TUNSLMODE + /* Make sure we're not prepending sockaddrs */ + iff = 0; + if (ID0ioctl(bundle.dev.fd, TUNSLMODE, &iff) < 0) + log_Printf(LogERROR, "bundle_Create: ioctl(TUNSLMODE): %s\n", + strerror(errno)); +#endif + +#ifdef TUNSIFHEAD + /* We want the address family please ! */ + iff = 1; + if (ID0ioctl(bundle.dev.fd, TUNSIFHEAD, &iff) < 0) { + log_Printf(LogERROR, "bundle_Create: ioctl(TUNSIFHEAD): %s\n", + strerror(errno)); + bundle.dev.header = 0; + } else + bundle.dev.header = 1; +#else +#ifdef __OpenBSD__ + /* Always present for OpenBSD */ + bundle.dev.header = 1; +#else + /* + * If TUNSIFHEAD isn't available and we're not OpenBSD, assume + * everything's AF_INET (hopefully the tun device won't pass us + * anything else !). + */ + bundle.dev.header = 0; +#endif +#endif + + log_Printf(LogPHASE, "Using interface: %s\n", ifname); + + bundle.bandwidth = 0; + bundle.routing_seq = 0; + bundle.phase = PHASE_DEAD; + bundle.CleaningUp = 0; + bundle.NatEnabled = 0; + + bundle.fsm.LayerStart = bundle_LayerStart; + bundle.fsm.LayerUp = bundle_LayerUp; + bundle.fsm.LayerDown = bundle_LayerDown; + bundle.fsm.LayerFinish = bundle_LayerFinish; + bundle.fsm.object = &bundle; + + bundle.cfg.idle.timeout = NCP_IDLE_TIMEOUT; + bundle.cfg.idle.min_timeout = 0; + *bundle.cfg.auth.name = '\0'; + *bundle.cfg.auth.key = '\0'; + bundle.cfg.optmask = (1ull << OPT_IDCHECK) | (1ull << OPT_LOOPBACK) | + (1ull << OPT_SROUTES) | (1ull << OPT_TCPMSSFIXUP) | + (1ull << OPT_THROUGHPUT) | (1ull << OPT_UTMP) | + (1ull << OPT_NAS_IP_ADDRESS) | + (1ull << OPT_NAS_IDENTIFIER); +#ifndef NOINET6 + opt_enable(&bundle, OPT_IPCP); + if (probe.ipv6_available) + opt_enable(&bundle, OPT_IPV6CP); +#endif + *bundle.cfg.label = '\0'; + bundle.cfg.ifqueue = DEF_IFQUEUE; + bundle.cfg.choked.timeout = CHOKED_TIMEOUT; + bundle.phys_type.all = type; + bundle.phys_type.open = 0; + bundle.upat = 0; + + bundle.links = datalink_Create("deflink", &bundle, type); + if (bundle.links == NULL) { + log_Printf(LogALERT, "Cannot create data link: %s\n", strerror(errno)); + iface_Destroy(bundle.iface); + bundle.iface = NULL; + close(bundle.dev.fd); + return NULL; + } + + bundle.desc.type = BUNDLE_DESCRIPTOR; + bundle.desc.UpdateSet = bundle_UpdateSet; + bundle.desc.IsSet = bundle_IsSet; + bundle.desc.Read = bundle_DescriptorRead; + bundle.desc.Write = bundle_DescriptorWrite; + + ncp_Init(&bundle.ncp, &bundle); + + memset(&bundle.filter, '\0', sizeof bundle.filter); + bundle.filter.in.fragok = bundle.filter.in.logok = 1; + bundle.filter.in.name = "IN"; + bundle.filter.out.fragok = bundle.filter.out.logok = 1; + bundle.filter.out.name = "OUT"; + bundle.filter.dial.name = "DIAL"; + bundle.filter.dial.logok = 1; + bundle.filter.alive.name = "ALIVE"; + bundle.filter.alive.logok = 1; + { + int i; + for (i = 0; i < MAXFILTERS; i++) { + bundle.filter.in.rule[i].f_action = A_NONE; + bundle.filter.out.rule[i].f_action = A_NONE; + bundle.filter.dial.rule[i].f_action = A_NONE; + bundle.filter.alive.rule[i].f_action = A_NONE; + } + } + memset(&bundle.idle.timer, '\0', sizeof bundle.idle.timer); + bundle.idle.done = 0; + bundle.notify.fd = -1; + memset(&bundle.choked.timer, '\0', sizeof bundle.choked.timer); +#ifndef NORADIUS + radius_Init(&bundle.radius); +#endif + + /* Clean out any leftover crud */ + iface_Clear(bundle.iface, &bundle.ncp, 0, IFACE_CLEAR_ALL); + + bundle_LockTun(&bundle); + + return &bundle; +} + +static void +bundle_DownInterface(struct bundle *bundle) +{ + route_IfDelete(bundle, 1); + iface_ClearFlags(bundle->iface->name, IFF_UP); +} + +void +bundle_Destroy(struct bundle *bundle) +{ + struct datalink *dl; + + /* + * Clean up the interface. We don't really need to do the timer_Stop()s, + * mp_Down(), iface_Clear() and bundle_DownInterface() unless we're getting + * out under exceptional conditions such as a descriptor exception. + */ + timer_Stop(&bundle->idle.timer); + timer_Stop(&bundle->choked.timer); + mp_Down(&bundle->ncp.mp); + iface_Clear(bundle->iface, &bundle->ncp, 0, IFACE_CLEAR_ALL); + bundle_DownInterface(bundle); + +#ifndef NORADIUS + /* Tell the radius server the bad news */ + radius_Destroy(&bundle->radius); +#endif + + /* Again, these are all DATALINK_CLOSED unless we're abending */ + dl = bundle->links; + while (dl) + dl = datalink_Destroy(dl); + + ncp_Destroy(&bundle->ncp); + + close(bundle->dev.fd); + bundle_UnlockTun(bundle); + + /* In case we never made PHASE_NETWORK */ + bundle_Notify(bundle, EX_ERRDEAD); + + iface_Destroy(bundle->iface); + bundle->iface = NULL; +} + +void +bundle_LinkClosed(struct bundle *bundle, struct datalink *dl) +{ + /* + * Our datalink has closed. + * CleanDatalinks() (called from DoLoop()) will remove closed + * BACKGROUND, FOREGROUND and DIRECT links. + * If it's the last data link, enter phase DEAD. + * + * NOTE: dl may not be in our list (bundle_SendDatalink()) ! + */ + + struct datalink *odl; + int other_links; + + log_SetTtyCommandMode(dl); + + other_links = 0; + for (odl = bundle->links; odl; odl = odl->next) + if (odl != dl && odl->state != DATALINK_CLOSED) + other_links++; + + if (!other_links) { + if (dl->physical->type != PHYS_AUTO) /* Not in -auto mode */ + bundle_DownInterface(bundle); + ncp2initial(&bundle->ncp); + mp_Down(&bundle->ncp.mp); + bundle_NewPhase(bundle, PHASE_DEAD); +#ifndef NORADIUS + if (bundle->radius.sessiontime) + bundle_StopSessionTimer(bundle); +#endif + bundle_StopIdleTimer(bundle); + } +} + +void +bundle_Open(struct bundle *bundle, const char *name, int mask, int force) +{ + /* + * Please open the given datalink, or all if name == NULL + */ + struct datalink *dl; + + for (dl = bundle->links; dl; dl = dl->next) + if (name == NULL || !strcasecmp(dl->name, name)) { + if ((mask & dl->physical->type) && + (dl->state == DATALINK_CLOSED || + (force && dl->state == DATALINK_OPENING && + dl->dial.timer.state == TIMER_RUNNING) || + dl->state == DATALINK_READY)) { + timer_Stop(&dl->dial.timer); /* We're finished with this */ + datalink_Up(dl, 1, 1); + if (mask & PHYS_AUTO) + break; /* Only one AUTO link at a time */ + } + if (name != NULL) + break; + } +} + +struct datalink * +bundle2datalink(struct bundle *bundle, const char *name) +{ + struct datalink *dl; + + if (name != NULL) { + for (dl = bundle->links; dl; dl = dl->next) + if (!strcasecmp(dl->name, name)) + return dl; + } else if (bundle->links && !bundle->links->next) + return bundle->links; + + return NULL; +} + +int +bundle_ShowLinks(struct cmdargs const *arg) +{ + struct datalink *dl; + struct pppThroughput *t; + unsigned long long octets; + int secs; + + for (dl = arg->bundle->links; dl; dl = dl->next) { + octets = MAX(dl->physical->link.stats.total.in.OctetsPerSecond, + dl->physical->link.stats.total.out.OctetsPerSecond); + + prompt_Printf(arg->prompt, "Name: %s [%s, %s]", + dl->name, mode2Nam(dl->physical->type), datalink_State(dl)); + if (dl->physical->link.stats.total.rolling && dl->state == DATALINK_OPEN) + prompt_Printf(arg->prompt, " bandwidth %d, %llu bps (%llu bytes/sec)", + dl->mp.bandwidth ? dl->mp.bandwidth : + physical_GetSpeed(dl->physical), + octets * 8, octets); + prompt_Printf(arg->prompt, "\n"); + } + + t = &arg->bundle->ncp.mp.link.stats.total; + octets = MAX(t->in.OctetsPerSecond, t->out.OctetsPerSecond); + secs = t->downtime ? 0 : throughput_uptime(t); + if (secs > t->SamplePeriod) + secs = t->SamplePeriod; + if (secs) + prompt_Printf(arg->prompt, "Currently averaging %llu bps (%llu bytes/sec)" + " over the last %d secs\n", octets * 8, octets, secs); + + return 0; +} + +static const char * +optval(struct bundle *bundle, int opt) +{ + return Enabled(bundle, opt) ? "enabled" : "disabled"; +} + +int +bundle_ShowStatus(struct cmdargs const *arg) +{ + int remaining; + + prompt_Printf(arg->prompt, "Phase %s\n", bundle_PhaseName(arg->bundle)); + prompt_Printf(arg->prompt, " Device: %s\n", arg->bundle->dev.Name); + prompt_Printf(arg->prompt, " Interface: %s @ %lubps", + arg->bundle->iface->name, arg->bundle->bandwidth); + + if (arg->bundle->upat) { + int secs = bundle_Uptime(arg->bundle); + + prompt_Printf(arg->prompt, ", up time %d:%02d:%02d", secs / 3600, + (secs / 60) % 60, secs % 60); + } + prompt_Printf(arg->prompt, "\n Queued: %lu of %u\n", + (unsigned long)ncp_QueueLen(&arg->bundle->ncp), + arg->bundle->cfg.ifqueue); + + prompt_Printf(arg->prompt, "\nDefaults:\n"); + prompt_Printf(arg->prompt, " Label: %s\n", + arg->bundle->cfg.label); + prompt_Printf(arg->prompt, " Auth name: %s\n", + arg->bundle->cfg.auth.name); + prompt_Printf(arg->prompt, " Diagnostic socket: "); + if (*server.cfg.sockname != '\0') { + prompt_Printf(arg->prompt, "%s", server.cfg.sockname); + if (server.cfg.mask != (mode_t)-1) + prompt_Printf(arg->prompt, ", mask 0%03o", (int)server.cfg.mask); + prompt_Printf(arg->prompt, "%s\n", server.fd == -1 ? " (not open)" : ""); + } else if (server.cfg.port != 0) + prompt_Printf(arg->prompt, "TCP port %d%s\n", server.cfg.port, + server.fd == -1 ? " (not open)" : ""); + else + prompt_Printf(arg->prompt, "none\n"); + + prompt_Printf(arg->prompt, " Choked Timer: %us\n", + arg->bundle->cfg.choked.timeout); + +#ifndef NORADIUS + radius_Show(&arg->bundle->radius, arg->prompt); +#endif + + prompt_Printf(arg->prompt, " Idle Timer: "); + if (arg->bundle->cfg.idle.timeout) { + prompt_Printf(arg->prompt, "%us", arg->bundle->cfg.idle.timeout); + if (arg->bundle->cfg.idle.min_timeout) + prompt_Printf(arg->prompt, ", min %us", + arg->bundle->cfg.idle.min_timeout); + remaining = bundle_RemainingIdleTime(arg->bundle); + if (remaining != -1) + prompt_Printf(arg->prompt, " (%ds remaining)", remaining); + prompt_Printf(arg->prompt, "\n"); + } else + prompt_Printf(arg->prompt, "disabled\n"); + + prompt_Printf(arg->prompt, " Filter Decap: %-20.20s", + optval(arg->bundle, OPT_FILTERDECAP)); + prompt_Printf(arg->prompt, " ID check: %s\n", + optval(arg->bundle, OPT_IDCHECK)); + prompt_Printf(arg->prompt, " Iface-Alias: %-20.20s", + optval(arg->bundle, OPT_IFACEALIAS)); +#ifndef NOINET6 + prompt_Printf(arg->prompt, " IPCP: %s\n", + optval(arg->bundle, OPT_IPCP)); + prompt_Printf(arg->prompt, " IPV6CP: %-20.20s", + optval(arg->bundle, OPT_IPV6CP)); +#endif + prompt_Printf(arg->prompt, " Keep-Session: %s\n", + optval(arg->bundle, OPT_KEEPSESSION)); + prompt_Printf(arg->prompt, " Loopback: %-20.20s", + optval(arg->bundle, OPT_LOOPBACK)); + prompt_Printf(arg->prompt, " PasswdAuth: %s\n", + optval(arg->bundle, OPT_PASSWDAUTH)); + prompt_Printf(arg->prompt, " Proxy: %-20.20s", + optval(arg->bundle, OPT_PROXY)); + prompt_Printf(arg->prompt, " Proxyall: %s\n", + optval(arg->bundle, OPT_PROXYALL)); + prompt_Printf(arg->prompt, " Sticky Routes: %-20.20s", + optval(arg->bundle, OPT_SROUTES)); + prompt_Printf(arg->prompt, " TCPMSS Fixup: %s\n", + optval(arg->bundle, OPT_TCPMSSFIXUP)); + prompt_Printf(arg->prompt, " Throughput: %-20.20s", + optval(arg->bundle, OPT_THROUGHPUT)); + prompt_Printf(arg->prompt, " Utmp Logging: %s\n", + optval(arg->bundle, OPT_UTMP)); + prompt_Printf(arg->prompt, " NAS-IP-Address: %-20.20s", + optval(arg->bundle, OPT_NAS_IP_ADDRESS)); + prompt_Printf(arg->prompt, " NAS-Identifier: %s\n", + optval(arg->bundle, OPT_NAS_IDENTIFIER)); + + return 0; +} + +static void +bundle_IdleTimeout(void *v) +{ + struct bundle *bundle = (struct bundle *)v; + + log_Printf(LogPHASE, "Idle timer expired\n"); + bundle_StopIdleTimer(bundle); + bundle_Close(bundle, NULL, CLOSE_STAYDOWN); +} + +/* + * Start Idle timer. If timeout is reached, we call bundle_Close() to + * close LCP and link. + */ +void +bundle_StartIdleTimer(struct bundle *bundle, unsigned secs) +{ + timer_Stop(&bundle->idle.timer); + if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL)) != + bundle->phys_type.open && bundle->cfg.idle.timeout) { + time_t now = time(NULL); + + if (secs == 0) + secs = bundle->cfg.idle.timeout; + + /* We want at least `secs' */ + if (bundle->cfg.idle.min_timeout > secs && bundle->upat) { + unsigned up = now - bundle->upat; + + if (bundle->cfg.idle.min_timeout > up && + bundle->cfg.idle.min_timeout - up > (long long)secs) + /* Only increase from the current `remaining' value */ + secs = bundle->cfg.idle.min_timeout - up; + } + bundle->idle.timer.func = bundle_IdleTimeout; + bundle->idle.timer.name = "idle"; + bundle->idle.timer.load = secs * SECTICKS; + bundle->idle.timer.arg = bundle; + timer_Start(&bundle->idle.timer); + bundle->idle.done = now + secs; + } +} + +void +bundle_SetIdleTimer(struct bundle *bundle, unsigned timeout, + unsigned min_timeout) +{ + bundle->cfg.idle.timeout = timeout; + bundle->cfg.idle.min_timeout = min_timeout; + if (ncp_LayersOpen(&bundle->ncp)) + bundle_StartIdleTimer(bundle, 0); +} + +void +bundle_StopIdleTimer(struct bundle *bundle) +{ + timer_Stop(&bundle->idle.timer); + bundle->idle.done = 0; +} + +static int +bundle_RemainingIdleTime(struct bundle *bundle) +{ + if (bundle->idle.done) + return bundle->idle.done - time(NULL); + return -1; +} + +#ifndef NORADIUS + +static void +bundle_SessionTimeout(void *v) +{ + struct bundle *bundle = (struct bundle *)v; + + log_Printf(LogPHASE, "Session-Timeout timer expired\n"); + bundle_StopSessionTimer(bundle); + bundle_Close(bundle, NULL, CLOSE_STAYDOWN); +} + +void +bundle_StartSessionTimer(struct bundle *bundle, unsigned secs) +{ + timer_Stop(&bundle->session.timer); + if ((bundle->phys_type.open & (PHYS_DEDICATED|PHYS_DDIAL)) != + bundle->phys_type.open && bundle->radius.sessiontime) { + time_t now = time(NULL); + + if (secs == 0) + secs = bundle->radius.sessiontime; + + bundle->session.timer.func = bundle_SessionTimeout; + bundle->session.timer.name = "session"; + bundle->session.timer.load = secs * SECTICKS; + bundle->session.timer.arg = bundle; + timer_Start(&bundle->session.timer); + bundle->session.done = now + secs; + } +} + +void +bundle_StopSessionTimer(struct bundle *bundle) +{ + timer_Stop(&bundle->session.timer); + bundle->session.done = 0; +} + +#endif + +int +bundle_IsDead(struct bundle *bundle) +{ + return !bundle->links || (bundle->phase == PHASE_DEAD && bundle->CleaningUp); +} + +static struct datalink * +bundle_DatalinkLinkout(struct bundle *bundle, struct datalink *dl) +{ + struct datalink **dlp; + + for (dlp = &bundle->links; *dlp; dlp = &(*dlp)->next) + if (*dlp == dl) { + *dlp = dl->next; + dl->next = NULL; + bundle_LinksRemoved(bundle); + return dl; + } + + return NULL; +} + +static void +bundle_DatalinkLinkin(struct bundle *bundle, struct datalink *dl) +{ + struct datalink **dlp = &bundle->links; + + while (*dlp) + dlp = &(*dlp)->next; + + *dlp = dl; + dl->next = NULL; + + bundle_LinkAdded(bundle, dl); + mp_CheckAutoloadTimer(&bundle->ncp.mp); +} + +void +bundle_CleanDatalinks(struct bundle *bundle) +{ + struct datalink **dlp = &bundle->links; + int found = 0; + + while (*dlp) + if ((*dlp)->state == DATALINK_CLOSED && + (*dlp)->physical->type & + (PHYS_DIRECT|PHYS_BACKGROUND|PHYS_FOREGROUND)) { + *dlp = datalink_Destroy(*dlp); + found++; + } else + dlp = &(*dlp)->next; + + if (found) + bundle_LinksRemoved(bundle); +} + +int +bundle_DatalinkClone(struct bundle *bundle, struct datalink *dl, + const char *name) +{ + if (bundle2datalink(bundle, name)) { + log_Printf(LogWARN, "Clone: %s: name already exists\n", name); + return 0; + } + + bundle_DatalinkLinkin(bundle, datalink_Clone(dl, name)); + return 1; +} + +void +bundle_DatalinkRemove(struct bundle *bundle, struct datalink *dl) +{ + dl = bundle_DatalinkLinkout(bundle, dl); + if (dl) + datalink_Destroy(dl); +} + +void +bundle_SetLabel(struct bundle *bundle, const char *label) +{ + if (label) + strncpy(bundle->cfg.label, label, sizeof bundle->cfg.label - 1); + else + *bundle->cfg.label = '\0'; +} + +const char * +bundle_GetLabel(struct bundle *bundle) +{ + return *bundle->cfg.label ? bundle->cfg.label : NULL; +} + +int +bundle_LinkSize() +{ + struct iovec iov[SCATTER_SEGMENTS]; + int niov, expect, f; + + iov[0].iov_len = strlen(Version) + 1; + iov[0].iov_base = NULL; + niov = 1; + if (datalink2iov(NULL, iov, &niov, SCATTER_SEGMENTS, NULL, NULL) == -1) { + log_Printf(LogERROR, "Cannot determine space required for link\n"); + return 0; + } + + for (f = expect = 0; f < niov; f++) + expect += iov[f].iov_len; + + return expect; +} + +void +bundle_ReceiveDatalink(struct bundle *bundle, int s) +{ + char cmsgbuf[sizeof(struct cmsghdr) + sizeof(int) * SEND_MAXFD]; + int niov, expect, f, *fd, nfd, onfd; + ssize_t got; + struct iovec iov[SCATTER_SEGMENTS]; + struct cmsghdr *cmsg; + struct msghdr msg; + struct datalink *dl; + pid_t pid; + + log_Printf(LogPHASE, "Receiving datalink\n"); + + /* + * Create our scatter/gather array - passing NULL gets the space + * allocation requirement rather than actually flattening the + * structures. + */ + iov[0].iov_len = strlen(Version) + 1; + iov[0].iov_base = NULL; + niov = 1; + if (datalink2iov(NULL, iov, &niov, SCATTER_SEGMENTS, NULL, NULL) == -1) { + log_Printf(LogERROR, "Cannot determine space required for link\n"); + return; + } + + /* Allocate the scatter/gather array for recvmsg() */ + for (f = expect = 0; f < niov; f++) { + if ((iov[f].iov_base = malloc(iov[f].iov_len)) == NULL) { + log_Printf(LogERROR, "Cannot allocate space to receive link\n"); + return; + } + if (f) + expect += iov[f].iov_len; + } + + /* Set up our message */ + cmsg = (struct cmsghdr *)cmsgbuf; + cmsg->cmsg_len = sizeof cmsgbuf; + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = 0; + + memset(&msg, '\0', sizeof msg); + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = iov; + msg.msg_iovlen = 1; /* Only send the version at the first pass */ + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof cmsgbuf; + + log_Printf(LogDEBUG, "Expecting %u scatter/gather bytes\n", + (unsigned)iov[0].iov_len); + + if ((got = recvmsg(s, &msg, MSG_WAITALL)) != (ssize_t)iov[0].iov_len) { + if (got == -1) + log_Printf(LogERROR, "Failed recvmsg: %s\n", strerror(errno)); + else + log_Printf(LogERROR, "Failed recvmsg: Got %zd, not %u\n", + got, (unsigned)iov[0].iov_len); + while (niov--) + free(iov[niov].iov_base); + return; + } + + if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) { + log_Printf(LogERROR, "Recvmsg: no descriptors received !\n"); + while (niov--) + free(iov[niov].iov_base); + return; + } + + fd = (int *)CMSG_DATA(cmsg); + nfd = ((caddr_t)cmsg + cmsg->cmsg_len - (caddr_t)fd) / sizeof(int); + + if (nfd < 2) { + log_Printf(LogERROR, "Recvmsg: %d descriptor%s received (too few) !\n", + nfd, nfd == 1 ? "" : "s"); + while (nfd--) + close(fd[nfd]); + while (niov--) + free(iov[niov].iov_base); + return; + } + + /* + * We've successfully received two or more open file descriptors + * through our socket, plus a version string. Make sure it's the + * correct version, and drop the connection if it's not. + */ + if (strncmp(Version, iov[0].iov_base, iov[0].iov_len)) { + log_Printf(LogWARN, "Cannot receive datalink, incorrect version" + " (\"%.*s\", not \"%s\")\n", (int)iov[0].iov_len, + (char *)iov[0].iov_base, Version); + while (nfd--) + close(fd[nfd]); + while (niov--) + free(iov[niov].iov_base); + return; + } + + /* + * Everything looks good. Send the other side our process id so that + * they can transfer lock ownership, and wait for them to send the + * actual link data. + */ + pid = getpid(); + if ((got = write(fd[1], &pid, sizeof pid)) != sizeof pid) { + if (got == -1) + log_Printf(LogERROR, "Failed write: %s\n", strerror(errno)); + else + log_Printf(LogERROR, "Failed write: Got %zd, not %d\n", got, + (int)(sizeof pid)); + while (nfd--) + close(fd[nfd]); + while (niov--) + free(iov[niov].iov_base); + return; + } + + if ((got = readv(fd[1], iov + 1, niov - 1)) != expect) { + if (got == -1) + log_Printf(LogERROR, "Failed write: %s\n", strerror(errno)); + else + log_Printf(LogERROR, "Failed write: Got %zd, not %d\n", got, expect); + while (nfd--) + close(fd[nfd]); + while (niov--) + free(iov[niov].iov_base); + return; + } + close(fd[1]); + + onfd = nfd; /* We've got this many in our array */ + nfd -= 2; /* Don't include p->fd and our reply descriptor */ + niov = 1; /* Skip the version id */ + dl = iov2datalink(bundle, iov, &niov, sizeof iov / sizeof *iov, fd[0], + fd + 2, &nfd); + if (dl) { + + if (nfd) { + log_Printf(LogERROR, "bundle_ReceiveDatalink: Failed to handle %d " + "auxiliary file descriptors (%d remain)\n", onfd, nfd); + datalink_Destroy(dl); + while (nfd--) + close(fd[onfd--]); + close(fd[0]); + } else { + bundle_DatalinkLinkin(bundle, dl); + datalink_AuthOk(dl); + bundle_CalculateBandwidth(dl->bundle); + } + } else { + while (nfd--) + close(fd[onfd--]); + close(fd[0]); + close(fd[1]); + } + + free(iov[0].iov_base); +} + +void +bundle_SendDatalink(struct datalink *dl, int s, struct sockaddr_un *sun) +{ + char cmsgbuf[CMSG_SPACE(sizeof(int) * SEND_MAXFD)]; + const char *constlock; + char *lock; + struct cmsghdr *cmsg; + struct msghdr msg; + struct iovec iov[SCATTER_SEGMENTS]; + int niov, f, expect, newsid, fd[SEND_MAXFD], nfd, reply[2]; + ssize_t got; + pid_t newpid; + + log_Printf(LogPHASE, "Transmitting datalink %s\n", dl->name); + + /* Record the base device name for a lock transfer later */ + constlock = physical_LockedDevice(dl->physical); + if (constlock) { + lock = alloca(strlen(constlock) + 1); + strcpy(lock, constlock); + } else + lock = NULL; + + bundle_LinkClosed(dl->bundle, dl); + bundle_DatalinkLinkout(dl->bundle, dl); + + /* Build our scatter/gather array */ + iov[0].iov_len = strlen(Version) + 1; + iov[0].iov_base = strdup(Version); + niov = 1; + nfd = 0; + + fd[0] = datalink2iov(dl, iov, &niov, SCATTER_SEGMENTS, fd + 2, &nfd); + + if (fd[0] != -1 && socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, reply) != -1) { + /* + * fd[1] is used to get the peer process id back, then to confirm that + * we've transferred any device locks to that process id. + */ + fd[1] = reply[1]; + + nfd += 2; /* Include fd[0] and fd[1] */ + memset(&msg, '\0', sizeof msg); + + msg.msg_name = NULL; + msg.msg_namelen = 0; + /* + * Only send the version to start... We used to send the whole lot, but + * this caused problems with our RECVBUF size as a single link is about + * 22k ! This way, we should bump into no limits. + */ + msg.msg_iovlen = 1; + msg.msg_iov = iov; + msg.msg_control = cmsgbuf; + msg.msg_controllen = CMSG_SPACE(sizeof(int) * nfd); + msg.msg_flags = 0; + + cmsg = (struct cmsghdr *)cmsgbuf; + cmsg->cmsg_len = msg.msg_controllen; + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + + for (f = 0; f < nfd; f++) + *((int *)CMSG_DATA(cmsg) + f) = fd[f]; + + for (f = 1, expect = 0; f < niov; f++) + expect += iov[f].iov_len; + + if (setsockopt(reply[0], SOL_SOCKET, SO_SNDBUF, &expect, sizeof(int)) == -1) + log_Printf(LogERROR, "setsockopt(SO_RCVBUF, %d): %s\n", expect, + strerror(errno)); + if (setsockopt(reply[1], SOL_SOCKET, SO_RCVBUF, &expect, sizeof(int)) == -1) + log_Printf(LogERROR, "setsockopt(SO_RCVBUF, %d): %s\n", expect, + strerror(errno)); + + log_Printf(LogDEBUG, "Sending %d descriptor%s and %u bytes in scatter" + "/gather array\n", nfd, nfd == 1 ? "" : "s", + (unsigned)iov[0].iov_len); + + if ((got = sendmsg(s, &msg, 0)) == -1) + log_Printf(LogERROR, "Failed sendmsg: %s: %s\n", + sun->sun_path, strerror(errno)); + else if (got != (ssize_t)iov[0].iov_len) + log_Printf(LogERROR, "%s: Failed initial sendmsg: Only sent %zd of %u\n", + sun->sun_path, got, (unsigned)iov[0].iov_len); + else { + /* We must get the ACK before closing the descriptor ! */ + int res; + + if ((got = read(reply[0], &newpid, sizeof newpid)) == sizeof newpid) { + log_Printf(LogDEBUG, "Received confirmation from pid %ld\n", + (long)newpid); + if (lock && (res = ID0uu_lock_txfr(lock, newpid)) != UU_LOCK_OK) + log_Printf(LogERROR, "uu_lock_txfr: %s\n", uu_lockerr(res)); + + log_Printf(LogDEBUG, "Transmitting link (%d bytes)\n", expect); + if ((got = writev(reply[0], iov + 1, niov - 1)) != expect) { + if (got == -1) + log_Printf(LogERROR, "%s: Failed writev: %s\n", + sun->sun_path, strerror(errno)); + else + log_Printf(LogERROR, "%s: Failed writev: Wrote %zd of %d\n", + sun->sun_path, got, expect); + } + } else if (got == -1) + log_Printf(LogERROR, "%s: Failed socketpair read: %s\n", + sun->sun_path, strerror(errno)); + else + log_Printf(LogERROR, "%s: Failed socketpair read: Got %zd of %d\n", + sun->sun_path, got, (int)(sizeof newpid)); + } + + close(reply[0]); + close(reply[1]); + + newsid = Enabled(dl->bundle, OPT_KEEPSESSION) || + tcgetpgrp(fd[0]) == getpgrp(); + while (nfd) + close(fd[--nfd]); + if (newsid) + bundle_setsid(dl->bundle, got != -1); + } + close(s); + + while (niov--) + free(iov[niov].iov_base); +} + +int +bundle_RenameDatalink(struct bundle *bundle, struct datalink *ndl, + const char *name) +{ + struct datalink *dl; + + if (!strcasecmp(ndl->name, name)) + return 1; + + for (dl = bundle->links; dl; dl = dl->next) + if (!strcasecmp(dl->name, name)) + return 0; + + datalink_Rename(ndl, name); + return 1; +} + +int +bundle_SetMode(struct bundle *bundle, struct datalink *dl, int mode) +{ + int omode; + + omode = dl->physical->type; + if (omode == mode) + return 1; + + if (mode == PHYS_AUTO && !(bundle->phys_type.all & PHYS_AUTO)) + /* First auto link */ + if (bundle->ncp.ipcp.peer_ip.s_addr == INADDR_ANY) { + log_Printf(LogWARN, "You must `set ifaddr' or `open' before" + " changing mode to %s\n", mode2Nam(mode)); + return 0; + } + + if (!datalink_SetMode(dl, mode)) + return 0; + + if (mode == PHYS_AUTO && !(bundle->phys_type.all & PHYS_AUTO) && + bundle->phase != PHASE_NETWORK) + /* First auto link, we need an interface */ + ipcp_InterfaceUp(&bundle->ncp.ipcp); + + /* Regenerate phys_type and adjust idle timer */ + bundle_LinksRemoved(bundle); + + return 1; +} + +void +bundle_setsid(struct bundle *bundle, int holdsession) +{ + /* + * Lose the current session. This means getting rid of our pid + * too so that the tty device will really go away, and any getty + * etc will be allowed to restart. + */ + pid_t pid, orig; + int fds[2]; + char done; + struct datalink *dl; + + if (!holdsession && bundle_IsDead(bundle)) { + /* + * No need to lose our session after all... we're going away anyway + * + * We should really stop the timer and pause if holdsession is set and + * the bundle's dead, but that leaves other resources lying about :-( + */ + return; + } + + orig = getpid(); + if (pipe(fds) == -1) { + log_Printf(LogERROR, "pipe: %s\n", strerror(errno)); + return; + } + switch ((pid = fork())) { + case -1: + log_Printf(LogERROR, "fork: %s\n", strerror(errno)); + close(fds[0]); + close(fds[1]); + return; + case 0: + close(fds[1]); + read(fds[0], &done, 1); /* uu_locks are mine ! */ + close(fds[0]); + if (pipe(fds) == -1) { + log_Printf(LogERROR, "pipe(2): %s\n", strerror(errno)); + return; + } + switch ((pid = fork())) { + case -1: + log_Printf(LogERROR, "fork(2): %s\n", strerror(errno)); + close(fds[0]); + close(fds[1]); + return; + case 0: + close(fds[1]); + bundle_LockTun(bundle); /* update pid */ + read(fds[0], &done, 1); /* uu_locks are mine ! */ + close(fds[0]); + setsid(); + bundle_ChangedPID(bundle); + log_Printf(LogDEBUG, "%ld -> %ld: %s session control\n", + (long)orig, (long)getpid(), + holdsession ? "Passed" : "Dropped"); + timer_InitService(0); /* Start the Timer Service */ + break; + default: + close(fds[0]); + /* Give away all our physical locks (to the final process) */ + for (dl = bundle->links; dl; dl = dl->next) + if (dl->state != DATALINK_CLOSED) + physical_ChangedPid(dl->physical, pid); + write(fds[1], "!", 1); /* done */ + close(fds[1]); + _exit(0); + break; + } + break; + default: + close(fds[0]); + /* Give away all our physical locks (to the intermediate process) */ + for (dl = bundle->links; dl; dl = dl->next) + if (dl->state != DATALINK_CLOSED) + physical_ChangedPid(dl->physical, pid); + write(fds[1], "!", 1); /* done */ + close(fds[1]); + if (holdsession) { + int fd, status; + + timer_TermService(); + signal(SIGPIPE, SIG_DFL); + signal(SIGALRM, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + for (fd = getdtablesize(); fd >= 0; fd--) + close(fd); + /* + * Reap the intermediate process. As we're not exiting but the + * intermediate is, we don't want it to become defunct. + */ + waitpid(pid, &status, 0); + /* Tweak our process arguments.... */ + SetTitle("session owner"); +#ifndef NOSUID + setuid(ID0realuid()); +#endif + /* + * Hang around for a HUP. This should happen as soon as the + * ppp that we passed our ctty descriptor to closes it. + * NOTE: If this process dies, the passed descriptor becomes + * invalid and will give a select() error by setting one + * of the error fds, aborting the other ppp. We don't + * want that to happen ! + */ + pause(); + } + _exit(0); + break; + } +} + +unsigned +bundle_HighestState(struct bundle *bundle) +{ + struct datalink *dl; + unsigned result = DATALINK_CLOSED; + + for (dl = bundle->links; dl; dl = dl->next) + if (result < dl->state) + result = dl->state; + + return result; +} + +int +bundle_Exception(struct bundle *bundle, int fd) +{ + struct datalink *dl; + + for (dl = bundle->links; dl; dl = dl->next) + if (dl->physical->fd == fd) { + datalink_Down(dl, CLOSE_NORMAL); + return 1; + } + + return 0; +} + +void +bundle_AdjustFilters(struct bundle *bundle, struct ncpaddr *local, + struct ncpaddr *remote) +{ + filter_AdjustAddr(&bundle->filter.in, local, remote, NULL); + filter_AdjustAddr(&bundle->filter.out, local, remote, NULL); + filter_AdjustAddr(&bundle->filter.dial, local, remote, NULL); + filter_AdjustAddr(&bundle->filter.alive, local, remote, NULL); +} + +void +bundle_AdjustDNS(struct bundle *bundle) +{ + struct in_addr *dns = bundle->ncp.ipcp.ns.dns; + + filter_AdjustAddr(&bundle->filter.in, NULL, NULL, dns); + filter_AdjustAddr(&bundle->filter.out, NULL, NULL, dns); + filter_AdjustAddr(&bundle->filter.dial, NULL, NULL, dns); + filter_AdjustAddr(&bundle->filter.alive, NULL, NULL, dns); +} + +void +bundle_CalculateBandwidth(struct bundle *bundle) +{ + struct datalink *dl; + int sp, overhead, maxoverhead; + + bundle->bandwidth = 0; + bundle->iface->mtu = 0; + maxoverhead = 0; + + for (dl = bundle->links; dl; dl = dl->next) { + overhead = ccp_MTUOverhead(&dl->physical->link.ccp); + if (maxoverhead < overhead) + maxoverhead = overhead; + if (dl->state == DATALINK_OPEN) { + if ((sp = dl->mp.bandwidth) == 0 && + (sp = physical_GetSpeed(dl->physical)) == 0) + log_Printf(LogDEBUG, "%s: %s: Cannot determine bandwidth\n", + dl->name, dl->physical->name.full); + else + bundle->bandwidth += sp; + if (!bundle->ncp.mp.active) { + bundle->iface->mtu = dl->physical->link.lcp.his_mru; + break; + } + } + } + + if (bundle->bandwidth == 0) + bundle->bandwidth = 115200; /* Shrug */ + + if (bundle->ncp.mp.active) { + bundle->iface->mtu = bundle->ncp.mp.peer_mrru; + overhead = ccp_MTUOverhead(&bundle->ncp.mp.link.ccp); + if (maxoverhead < overhead) + maxoverhead = overhead; + } else if (!bundle->iface->mtu) + bundle->iface->mtu = DEF_MRU; + +#ifndef NORADIUS + if (bundle->radius.valid && bundle->radius.mtu && + bundle->radius.mtu < bundle->iface->mtu) { + log_Printf(LogLCP, "Reducing MTU to radius value %lu\n", + bundle->radius.mtu); + bundle->iface->mtu = bundle->radius.mtu; + } +#endif + + if (maxoverhead) { + log_Printf(LogLCP, "Reducing MTU from %lu to %lu (CCP requirement)\n", + bundle->iface->mtu, bundle->iface->mtu - maxoverhead); + bundle->iface->mtu -= maxoverhead; + } + + tun_configure(bundle); + + route_UpdateMTU(bundle); +} + +void +bundle_AutoAdjust(struct bundle *bundle, int percent, int what) +{ + struct datalink *dl, *choice, *otherlinkup; + + choice = otherlinkup = NULL; + for (dl = bundle->links; dl; dl = dl->next) + if (dl->physical->type == PHYS_AUTO) { + if (dl->state == DATALINK_OPEN) { + if (what == AUTO_DOWN) { + if (choice) + otherlinkup = choice; + choice = dl; + } + } else if (dl->state == DATALINK_CLOSED) { + if (what == AUTO_UP) { + choice = dl; + break; + } + } else { + /* An auto link in an intermediate state - forget it for the moment */ + choice = NULL; + break; + } + } else if (dl->state == DATALINK_OPEN && what == AUTO_DOWN) + otherlinkup = dl; + + if (choice) { + if (what == AUTO_UP) { + log_Printf(LogPHASE, "%d%% saturation -> Opening link ``%s''\n", + percent, choice->name); + datalink_Up(choice, 1, 1); + mp_CheckAutoloadTimer(&bundle->ncp.mp); + } else if (otherlinkup) { /* Only bring the second-last link down */ + log_Printf(LogPHASE, "%d%% saturation -> Closing link ``%s''\n", + percent, choice->name); + datalink_Close(choice, CLOSE_STAYDOWN); + mp_CheckAutoloadTimer(&bundle->ncp.mp); + } + } +} + +int +bundle_WantAutoloadTimer(struct bundle *bundle) +{ + struct datalink *dl; + int autolink, opened; + + if (bundle->phase == PHASE_NETWORK) { + for (autolink = opened = 0, dl = bundle->links; dl; dl = dl->next) + if (dl->physical->type == PHYS_AUTO) { + if (++autolink == 2 || (autolink == 1 && opened)) + /* Two auto links or one auto and one open in NETWORK phase */ + return 1; + } else if (dl->state == DATALINK_OPEN) { + opened++; + if (autolink) + /* One auto and one open link in NETWORK phase */ + return 1; + } + } + + return 0; +} + +void +bundle_ChangedPID(struct bundle *bundle) +{ +#ifdef TUNSIFPID + ioctl(bundle->dev.fd, TUNSIFPID, 0); +#endif +} + +int +bundle_Uptime(struct bundle *bundle) +{ + if (bundle->upat) + return time(NULL) - bundle->upat; + + return 0; +} diff --git a/src/bundle.h b/src/bundle.h new file mode 100644 index 0000000..67ad907 --- /dev/null +++ b/src/bundle.h @@ -0,0 +1,216 @@ +/*- + * Copyright (c) 1998 Brian Somers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/bundle.h,v 1.52.26.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#define PHASE_DEAD 0 /* Link is dead */ +#define PHASE_ESTABLISH 1 /* Establishing link */ +#define PHASE_AUTHENTICATE 2 /* Being authenticated */ +#define PHASE_NETWORK 3 /* We're alive ! */ +#define PHASE_TERMINATE 4 /* Terminating link */ + +/* cfg.opt bit settings */ +#define OPT_FILTERDECAP 1 +#define OPT_FORCE_SCRIPTS 2 /* force chat scripts */ +#define OPT_IDCHECK 3 +#define OPT_IFACEALIAS 4 +#ifndef NOINET6 +#define OPT_IPCP 5 +#define OPT_IPV6CP 6 +#endif +#define OPT_KEEPSESSION 7 +#define OPT_LOOPBACK 8 +#define OPT_NAS_IP_ADDRESS 9 +#define OPT_NAS_IDENTIFIER 10 +#define OPT_PASSWDAUTH 11 +#define OPT_PROXY 12 +#define OPT_PROXYALL 13 +#define OPT_SROUTES 14 +#define OPT_TCPMSSFIXUP 15 +#define OPT_THROUGHPUT 16 +#define OPT_UTMP 17 +#define OPT_MAX 17 + +#define MAX_ENDDISC_CLASS 5 + +#define Enabled(b, o) ((b)->cfg.optmask & (1ull << (o))) +#define opt_enable(b, o) ((b)->cfg.optmask |= (1ull << (o))) +#define opt_disable(b, o) ((b)->cfg.optmask &= ~(1ull << (o))) + +/* AutoAdjust() values */ +#define AUTO_UP 1 +#define AUTO_DOWN 2 + +struct sockaddr_un; +struct datalink; +struct physical; +struct link; +struct server; +struct prompt; +struct iface; + +struct bundle { + struct fdescriptor desc; /* really all our datalinks */ + int unit; /* The device/interface unit number */ + + struct { + char Name[20]; /* The /dev/XXXX name */ + int fd; /* The /dev/XXXX descriptor */ + unsigned header : 1; /* Family header sent & received ? */ + } dev; + + u_long bandwidth; /* struct tuninfo speed */ + struct iface *iface; /* Interface information */ + + int routing_seq; /* The current routing sequence number */ + u_int phase; /* Curent phase */ + + struct { + int all; /* Union of all physical::type's */ + int open; /* Union of all open physical::type's */ + } phys_type; + + unsigned CleaningUp : 1; /* Going to exit.... */ + unsigned NatEnabled : 1; /* Are we using libalias ? */ + + struct fsm_parent fsm; /* Our callback functions */ + struct datalink *links; /* Our data links */ + + time_t upat; /* When the link came up */ + + struct { + struct { + unsigned timeout; /* NCP Idle timeout value */ + unsigned min_timeout; /* Don't idle out before this */ + } idle; + struct { + char name[AUTHLEN]; /* PAP/CHAP system name */ + char key[AUTHLEN]; /* PAP/CHAP key */ + } auth; + unsigned long long optmask; /* Uses OPT_ bits from above */ + char label[50]; /* last thing `load'ed */ + u_short ifqueue; /* Interface queue size */ + + struct { + unsigned timeout; /* How long to leave the output queue choked */ + } choked; + } cfg; + + struct ncp ncp; + + struct { + struct filter in; /* incoming packet filter */ + struct filter out; /* outgoing packet filter */ + struct filter dial; /* dial-out packet filter */ + struct filter alive; /* keep-alive packet filter */ + } filter; + + struct { + struct pppTimer timer; /* timeout after cfg.idle_timeout */ + time_t done; + } idle; + +#ifndef NORADIUS + struct { + struct pppTimer timer; + time_t done; + } session; +#endif + + struct { + int fd; /* write status here */ + } notify; + + struct { + struct pppTimer timer; /* choked output queue timer */ + } choked; + +#ifndef NORADIUS + struct radius radius; /* Info retrieved from radius server */ + struct radacct radacct; +#ifndef NOINET6 + struct radacct radacct6; +#endif +#endif +}; + +#define descriptor2bundle(d) \ + ((d)->type == BUNDLE_DESCRIPTOR ? (struct bundle *)(d) : NULL) + +extern struct bundle *bundle_Create(const char *, int, int); +extern void bundle_Destroy(struct bundle *); +extern const char *bundle_PhaseName(struct bundle *); +#define bundle_Phase(b) ((b)->phase) +extern void bundle_NewPhase(struct bundle *, u_int); +extern void bundle_LinksRemoved(struct bundle *); +extern void bundle_Close(struct bundle *, const char *, int); +extern void bundle_Down(struct bundle *, int); +extern void bundle_Open(struct bundle *, const char *, int, int); +extern void bundle_LinkClosed(struct bundle *, struct datalink *); + +extern int bundle_ShowLinks(struct cmdargs const *); +extern int bundle_ShowStatus(struct cmdargs const *); +extern void bundle_StartIdleTimer(struct bundle *, unsigned secs); +extern void bundle_SetIdleTimer(struct bundle *, unsigned, unsigned); +extern void bundle_StopIdleTimer(struct bundle *); +extern int bundle_IsDead(struct bundle *); +extern struct datalink *bundle2datalink(struct bundle *, const char *); + +#ifndef NORADIUS +extern void bundle_StartSessionTimer(struct bundle *, unsigned secs); +extern void bundle_StopSessionTimer(struct bundle *); +#endif + +extern void bundle_RegisterDescriptor(struct bundle *, struct fdescriptor *); +extern void bundle_UnRegisterDescriptor(struct bundle *, struct fdescriptor *); + +extern void bundle_SetTtyCommandMode(struct bundle *, struct datalink *); + +extern int bundle_DatalinkClone(struct bundle *, struct datalink *, + const char *); +extern void bundle_DatalinkRemove(struct bundle *, struct datalink *); +extern void bundle_CleanDatalinks(struct bundle *); +extern void bundle_SetLabel(struct bundle *, const char *); +extern const char *bundle_GetLabel(struct bundle *); +extern void bundle_SendDatalink(struct datalink *, int, struct sockaddr_un *); +extern int bundle_LinkSize(void); +extern void bundle_ReceiveDatalink(struct bundle *, int); +extern int bundle_SetMode(struct bundle *, struct datalink *, int); +extern int bundle_RenameDatalink(struct bundle *, struct datalink *, + const char *); +extern void bundle_setsid(struct bundle *, int); +extern void bundle_LockTun(struct bundle *); +extern unsigned bundle_HighestState(struct bundle *); +extern int bundle_Exception(struct bundle *, int); +extern void bundle_AdjustFilters(struct bundle *, struct ncpaddr *, + struct ncpaddr *); +extern void bundle_AdjustDNS(struct bundle *); +extern void bundle_CalculateBandwidth(struct bundle *); +extern void bundle_AutoAdjust(struct bundle *, int, int); +extern int bundle_WantAutoloadTimer(struct bundle *); +extern void bundle_ChangedPID(struct bundle *); +extern void bundle_Notify(struct bundle *, char); +extern int bundle_Uptime(struct bundle *); diff --git a/src/cbcp.c b/src/cbcp.c new file mode 100644 index 0000000..53cebc8 --- /dev/null +++ b/src/cbcp.c @@ -0,0 +1,763 @@ +/*- + * Copyright (c) 1998 Brian Somers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/cbcp.c,v 1.26.26.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#include + +#ifdef __FreeBSD__ +#include +#endif +#include + +#include +#include + +#include "layer.h" +#include "defs.h" +#include "log.h" +#include "timer.h" +#include "descriptor.h" +#include "lqr.h" +#include "mbuf.h" +#include "fsm.h" +#include "throughput.h" +#include "hdlc.h" +#include "lcp.h" +#include "ccp.h" +#include "link.h" +#include "async.h" +#include "physical.h" +#include "proto.h" +#include "cbcp.h" +#include "mp.h" +#include "chat.h" +#include "auth.h" +#include "chap.h" +#include "datalink.h" + +void +cbcp_Init(struct cbcp *cbcp, struct physical *p) +{ + cbcp->required = 0; + cbcp->fsm.state = CBCP_CLOSED; + cbcp->fsm.id = 0; + cbcp->fsm.delay = 0; + *cbcp->fsm.phone = '\0'; + memset(&cbcp->fsm.timer, '\0', sizeof cbcp->fsm.timer); + cbcp->p = p; +} + +static void cbcp_SendReq(struct cbcp *); +static void cbcp_SendResponse(struct cbcp *); +static void cbcp_SendAck(struct cbcp *); + +static void +cbcp_Timeout(void *v) +{ + struct cbcp *cbcp = (struct cbcp *)v; + + timer_Stop(&cbcp->fsm.timer); + if (cbcp->fsm.restart) { + switch (cbcp->fsm.state) { + case CBCP_CLOSED: + case CBCP_STOPPED: + log_Printf(LogCBCP, "%s: Urk - unexpected CBCP timeout !\n", + cbcp->p->dl->name); + break; + + case CBCP_REQSENT: + cbcp_SendReq(cbcp); + break; + case CBCP_RESPSENT: + cbcp_SendResponse(cbcp); + break; + case CBCP_ACKSENT: + cbcp_SendAck(cbcp); + break; + } + } else { + const char *missed; + + switch (cbcp->fsm.state) { + case CBCP_STOPPED: + missed = "REQ"; + break; + case CBCP_REQSENT: + missed = "RESPONSE"; + break; + case CBCP_RESPSENT: + missed = "ACK"; + break; + case CBCP_ACKSENT: + missed = "Terminate REQ"; + break; + default: + log_Printf(LogCBCP, "%s: Urk - unexpected CBCP timeout !\n", + cbcp->p->dl->name); + missed = NULL; + break; + } + if (missed) + log_Printf(LogCBCP, "%s: Timeout waiting for peer %s\n", + cbcp->p->dl->name, missed); + datalink_CBCPFailed(cbcp->p->dl); + } +} + +static void +cbcp_StartTimer(struct cbcp *cbcp, int timeout) +{ + timer_Stop(&cbcp->fsm.timer); + cbcp->fsm.timer.func = cbcp_Timeout; + cbcp->fsm.timer.name = "cbcp"; + cbcp->fsm.timer.load = timeout * SECTICKS; + cbcp->fsm.timer.arg = cbcp; + timer_Start(&cbcp->fsm.timer); +} + +#define CBCP_CLOSED (0) /* Not in use */ +#define CBCP_STOPPED (1) /* Waiting for a REQ */ +#define CBCP_REQSENT (2) /* Waiting for a RESP */ +#define CBCP_RESPSENT (3) /* Waiting for an ACK */ +#define CBCP_ACKSENT (4) /* Waiting for an LCP Term REQ */ + +static const char * const cbcpname[] = { + "closed", "stopped", "req-sent", "resp-sent", "ack-sent" +}; + +static const char * +cbcpstate(unsigned s) +{ + if (s < sizeof cbcpname / sizeof cbcpname[0]) + return cbcpname[s]; + return HexStr(s, NULL, 0); +} + +static void +cbcp_NewPhase(struct cbcp *cbcp, int new) +{ + if (cbcp->fsm.state != new) { + log_Printf(LogCBCP, "%s: State change %s --> %s\n", cbcp->p->dl->name, + cbcpstate(cbcp->fsm.state), cbcpstate(new)); + cbcp->fsm.state = new; + } +} + +struct cbcp_header { + u_char code; + u_char id; + u_int16_t length; /* Network byte order */ +}; + + +/* cbcp_header::code values */ +#define CBCP_REQ (1) +#define CBCP_RESPONSE (2) +#define CBCP_ACK (3) + +struct cbcp_data { + u_char type; + u_char length; + u_char delay; + char addr_start[253]; /* max cbcp_data length 255 + 1 for NULL */ +}; + +/* cbcp_data::type values */ +#define CBCP_NONUM (1) +#define CBCP_CLIENTNUM (2) +#define CBCP_SERVERNUM (3) +#define CBCP_LISTNUM (4) + +static void +cbcp_Output(struct cbcp *cbcp, u_char code, struct cbcp_data *data) +{ + struct cbcp_header *head; + struct mbuf *bp; + + bp = m_get(sizeof *head + data->length, MB_CBCPOUT); + head = (struct cbcp_header *)MBUF_CTOP(bp); + head->code = code; + head->id = cbcp->fsm.id; + head->length = htons(sizeof *head + data->length); + memcpy(MBUF_CTOP(bp) + sizeof *head, data, data->length); + log_DumpBp(LogDEBUG, "cbcp_Output", bp); + link_PushPacket(&cbcp->p->link, bp, cbcp->p->dl->bundle, + LINK_QUEUES(&cbcp->p->link) - 1, PROTO_CBCP); +} + +static const char * +cbcp_data_Type(unsigned type) +{ + static const char * const types[] = { + "No callback", "User-spec", "Server-spec", "list" + }; + + if (type < 1 || type > sizeof types / sizeof types[0]) + return HexStr(type, NULL, 0); + return types[type-1]; +} + +struct cbcp_addr { + u_char type; + char addr[sizeof ((struct cbcp_data *)0)->addr_start - 1]; /* ASCIIZ */ +}; + +/* cbcp_data::type values */ +#define CBCP_ADDR_PSTN (1) + +static void +cbcp_data_Show(struct cbcp_data *data) +{ + struct cbcp_addr *addr; + char *end; + + addr = (struct cbcp_addr *)data->addr_start; + end = (char *)data + data->length; + *end = '\0'; + + log_Printf(LogCBCP, " TYPE %s\n", cbcp_data_Type(data->type)); + if ((char *)&data->delay < end) { + log_Printf(LogCBCP, " DELAY %d\n", data->delay); + while (addr->addr < end) { + if (addr->type == CBCP_ADDR_PSTN) + log_Printf(LogCBCP, " ADDR %s\n", addr->addr); + else + log_Printf(LogCBCP, " ADDR type %d ??\n", (int)addr->type); + addr = (struct cbcp_addr *)(addr->addr + strlen(addr->addr) + 1); + } + } +} + +static void +cbcp_SendReq(struct cbcp *cbcp) +{ + struct cbcp_data data; + struct cbcp_addr *addr; + char list[sizeof cbcp->fsm.phone], *next; + int len, max; + + /* Only callees send REQs */ + + log_Printf(LogCBCP, "%s: SendReq(%d) state = %s\n", cbcp->p->dl->name, + cbcp->fsm.id, cbcpstate(cbcp->fsm.state)); + data.type = cbcp->fsm.type; + data.delay = 0; + strncpy(list, cbcp->fsm.phone, sizeof list - 1); + list[sizeof list - 1] = '\0'; + + switch (data.type) { + case CBCP_CLIENTNUM: + addr = (struct cbcp_addr *)data.addr_start; + addr->type = CBCP_ADDR_PSTN; + *addr->addr = '\0'; + data.length = addr->addr - (char *)&data; + break; + + case CBCP_LISTNUM: + addr = (struct cbcp_addr *)data.addr_start; + for (next = strtok(list, ","); next; next = strtok(NULL, ",")) { + len = strlen(next); + max = data.addr_start + sizeof data.addr_start - addr->addr - 1; + if (len <= max) { + addr->type = CBCP_ADDR_PSTN; + strncpy(addr->addr, next, sizeof addr->addr - 1); + addr->addr[sizeof addr->addr - 1] = '\0'; + addr = (struct cbcp_addr *)((char *)addr + len + 2); + } else + log_Printf(LogWARN, "CBCP ADDR \"%s\" skipped - packet too large\n", + next); + } + data.length = (char *)addr - (char *)&data; + break; + + case CBCP_SERVERNUM: + data.length = data.addr_start - (char *)&data; + break; + + default: + data.length = (char *)&data.delay - (char *)&data; + break; + } + + cbcp_data_Show(&data); + cbcp_Output(cbcp, CBCP_REQ, &data); + cbcp->fsm.restart--; + cbcp_StartTimer(cbcp, cbcp->fsm.delay); + cbcp_NewPhase(cbcp, CBCP_REQSENT); /* Wait for a RESPONSE */ +} + +void +cbcp_Up(struct cbcp *cbcp) +{ + struct lcp *lcp = &cbcp->p->link.lcp; + + cbcp->fsm.delay = cbcp->p->dl->cfg.cbcp.delay; + if (*cbcp->p->dl->peer.authname == '\0' || + !auth_SetPhoneList(cbcp->p->dl->peer.authname, cbcp->fsm.phone, + sizeof cbcp->fsm.phone)) { + strncpy(cbcp->fsm.phone, cbcp->p->dl->cfg.cbcp.phone, + sizeof cbcp->fsm.phone - 1); + cbcp->fsm.phone[sizeof cbcp->fsm.phone - 1] = '\0'; + } + + if (lcp->want_callback.opmask) { + if (*cbcp->fsm.phone == '\0') + cbcp->fsm.type = CBCP_NONUM; + else if (!strcmp(cbcp->fsm.phone, "*")) { + cbcp->fsm.type = CBCP_SERVERNUM; + *cbcp->fsm.phone = '\0'; + } else + cbcp->fsm.type = CBCP_CLIENTNUM; + cbcp_NewPhase(cbcp, CBCP_STOPPED); /* Wait for a REQ */ + cbcp_StartTimer(cbcp, cbcp->fsm.delay * DEF_FSMTRIES); + } else { + if (*cbcp->fsm.phone == '\0') + cbcp->fsm.type = CBCP_NONUM; + else if (!strcmp(cbcp->fsm.phone, "*")) { + cbcp->fsm.type = CBCP_CLIENTNUM; + *cbcp->fsm.phone = '\0'; + } else if (strchr(cbcp->fsm.phone, ',')) + cbcp->fsm.type = CBCP_LISTNUM; + else + cbcp->fsm.type = CBCP_SERVERNUM; + cbcp->fsm.restart = DEF_FSMTRIES; + cbcp_SendReq(cbcp); + } +} + +static int +cbcp_AdjustResponse(struct cbcp *cbcp, struct cbcp_data *data) +{ + /* + * We've received a REQ (data). Adjust our reponse (cbcp->fsm.*) + * so that we (hopefully) agree with the peer + */ + struct cbcp_addr *addr; + + switch (data->type) { + case CBCP_NONUM: + if (cbcp->p->dl->cfg.callback.opmask & CALLBACK_BIT(CALLBACK_NONE)) + /* + * if ``none'' is a configured callback possibility + * (ie, ``set callback cbcp none''), go along with the callees + * request + */ + cbcp->fsm.type = CBCP_NONUM; + + /* + * Otherwise, we send our desired response anyway. This seems to be + * what Win95 does - although I can't find this behaviour documented + * in the CBCP spec.... + */ + + return 1; + + case CBCP_CLIENTNUM: + if (cbcp->fsm.type == CBCP_CLIENTNUM) { + char *ptr; + + if (data->length > data->addr_start - (char *)data) { + /* + * The peer has given us an address type spec - make sure we + * understand ! + */ + addr = (struct cbcp_addr *)data->addr_start; + if (addr->type != CBCP_ADDR_PSTN) { + log_Printf(LogPHASE, "CBCP: Unrecognised address type %d !\n", + (int)addr->type); + return 0; + } + } + /* we accept the REQ even if the peer didn't specify an addr->type */ + ptr = strchr(cbcp->fsm.phone, ','); + if (ptr) + *ptr = '\0'; /* Just use the first number in our list */ + return 1; + } + log_Printf(LogPHASE, "CBCP: no number to pass to the peer !\n"); + return 0; + + case CBCP_SERVERNUM: + if (cbcp->fsm.type == CBCP_SERVERNUM) { + *cbcp->fsm.phone = '\0'; + return 1; + } + if (data->length > data->addr_start - (char *)data) { + /* + * This violates the spec, but if the peer has told us the + * number it wants to call back, take advantage of this fact + * and allow things to proceed if we've specified the same + * number + */ + addr = (struct cbcp_addr *)data->addr_start; + if (addr->type != CBCP_ADDR_PSTN) { + log_Printf(LogPHASE, "CBCP: Unrecognised address type %d !\n", + (int)addr->type); + return 0; + } else if (cbcp->fsm.type == CBCP_CLIENTNUM) { + /* + * If the peer's insisting on deciding the number, make sure + * it's one of the ones in our list. If it is, let the peer + * think it's in control :-) + */ + char list[sizeof cbcp->fsm.phone], *next; + + strncpy(list, cbcp->fsm.phone, sizeof list - 1); + list[sizeof list - 1] = '\0'; + for (next = strtok(list, ","); next; next = strtok(NULL, ",")) + if (!strcmp(next, addr->addr)) { + cbcp->fsm.type = CBCP_SERVERNUM; + strcpy(cbcp->fsm.phone, next); + return 1; + } + } + } + log_Printf(LogPHASE, "CBCP: Peer won't allow local decision !\n"); + return 0; + + case CBCP_LISTNUM: + if (cbcp->fsm.type == CBCP_CLIENTNUM || cbcp->fsm.type == CBCP_LISTNUM) { + /* + * Search through ``data''s addresses and see if cbcp->fsm.phone + * contains any of them + */ + char list[sizeof cbcp->fsm.phone], *next, *end; + + addr = (struct cbcp_addr *)data->addr_start; + end = (char *)data + data->length; + + while (addr->addr < end) { + if (addr->type == CBCP_ADDR_PSTN) { + strncpy(list, cbcp->fsm.phone, sizeof list - 1); + list[sizeof list - 1] = '\0'; + for (next = strtok(list, ","); next; next = strtok(NULL, ",")) + if (!strcmp(next, addr->addr)) { + cbcp->fsm.type = CBCP_LISTNUM; + strcpy(cbcp->fsm.phone, next); + return 1; + } + } else + log_Printf(LogCBCP, "Warning: Unrecognised address type %d !\n", + (int)addr->type); + addr = (struct cbcp_addr *)(addr->addr + strlen(addr->addr) + 1); + } + } + log_Printf(LogPHASE, "CBCP: no good number to pass to the peer !\n"); + return 0; + } + + log_Printf(LogCBCP, "Unrecognised REQ type %d !\n", (int)data->type); + return 0; +} + +static void +cbcp_SendResponse(struct cbcp *cbcp) +{ + struct cbcp_data data; + struct cbcp_addr *addr; + + /* Only callers send RESPONSEs */ + + log_Printf(LogCBCP, "%s: SendResponse(%d) state = %s\n", cbcp->p->dl->name, + cbcp->fsm.id, cbcpstate(cbcp->fsm.state)); + + data.type = cbcp->fsm.type; + data.delay = cbcp->fsm.delay; + addr = (struct cbcp_addr *)data.addr_start; + if (data.type == CBCP_NONUM) + data.length = (char *)&data.delay - (char *)&data; + else if (*cbcp->fsm.phone) { + addr->type = CBCP_ADDR_PSTN; + strncpy(addr->addr, cbcp->fsm.phone, sizeof addr->addr - 1); + addr->addr[sizeof addr->addr - 1] = '\0'; + data.length = (addr->addr + strlen(addr->addr) + 1) - (char *)&data; + } else + data.length = data.addr_start - (char *)&data; + + cbcp_data_Show(&data); + cbcp_Output(cbcp, CBCP_RESPONSE, &data); + cbcp->fsm.restart--; + cbcp_StartTimer(cbcp, cbcp->fsm.delay); + cbcp_NewPhase(cbcp, CBCP_RESPSENT); /* Wait for an ACK */ +} + +/* What to do after checking an incoming response */ +#define CBCP_ACTION_DOWN (0) +#define CBCP_ACTION_REQ (1) +#define CBCP_ACTION_ACK (2) + +static int +cbcp_CheckResponse(struct cbcp *cbcp, struct cbcp_data *data) +{ + /* + * We've received a RESPONSE (data). Check if it agrees with + * our REQ (cbcp->fsm) + */ + struct cbcp_addr *addr; + + addr = (struct cbcp_addr *)data->addr_start; + + if (data->type == cbcp->fsm.type) { + switch (cbcp->fsm.type) { + case CBCP_NONUM: + return CBCP_ACTION_ACK; + + case CBCP_CLIENTNUM: + if ((char *)data + data->length <= addr->addr) + log_Printf(LogPHASE, "CBCP: peer didn't respond with a number !\n"); + else if (addr->type != CBCP_ADDR_PSTN) + log_Printf(LogPHASE, "CBCP: Unrecognised address type %d !\n", + addr->type); + else { + strncpy(cbcp->fsm.phone, addr->addr, sizeof cbcp->fsm.phone - 1); + cbcp->fsm.phone[sizeof cbcp->fsm.phone - 1] = '\0'; + cbcp->fsm.delay = data->delay; + return CBCP_ACTION_ACK; + } + return CBCP_ACTION_DOWN; + + case CBCP_SERVERNUM: + cbcp->fsm.delay = data->delay; + return CBCP_ACTION_ACK; + + case CBCP_LISTNUM: + if ((char *)data + data->length <= addr->addr) + log_Printf(LogPHASE, "CBCP: peer didn't respond with a number !\n"); + else if (addr->type != CBCP_ADDR_PSTN) + log_Printf(LogPHASE, "CBCP: Unrecognised address type %d !\n", + addr->type); + else { + char list[sizeof cbcp->fsm.phone], *next; + + strncpy(list, cbcp->fsm.phone, sizeof list - 1); + list[sizeof list - 1] = '\0'; + for (next = strtok(list, ","); next; next = strtok(NULL, ",")) + if (!strcmp(addr->addr, next)) { + strcpy(cbcp->fsm.phone, next); + cbcp->fsm.delay = data->delay; + return CBCP_ACTION_ACK; + } + log_Printf(LogPHASE, "CBCP: peer didn't respond with a " + "valid number !\n"); + } + return CBCP_ACTION_DOWN; + } + log_Printf(LogPHASE, "Internal CBCP error - agreed on %d !\n", + (int)cbcp->fsm.type); + return CBCP_ACTION_DOWN; + } else if (data->type == CBCP_NONUM && cbcp->fsm.type == CBCP_CLIENTNUM) { + /* + * Client doesn't want CBCP after all.... + * We only allow this when ``set cbcp *'' has been specified. + */ + cbcp->fsm.type = CBCP_NONUM; + return CBCP_ACTION_ACK; + } + log_Printf(LogCBCP, "Invalid peer RESPONSE\n"); + return CBCP_ACTION_REQ; +} + +static void +cbcp_SendAck(struct cbcp *cbcp) +{ + struct cbcp_data data; + struct cbcp_addr *addr; + + /* Only callees send ACKs */ + + log_Printf(LogCBCP, "%s: SendAck(%d) state = %s\n", cbcp->p->dl->name, + cbcp->fsm.id, cbcpstate(cbcp->fsm.state)); + + data.type = cbcp->fsm.type; + switch (data.type) { + case CBCP_NONUM: + data.length = (char *)&data.delay - (char *)&data; + break; + case CBCP_CLIENTNUM: + addr = (struct cbcp_addr *)data.addr_start; + addr->type = CBCP_ADDR_PSTN; + strncpy(addr->addr, cbcp->fsm.phone, sizeof addr->addr - 1); + addr->addr[sizeof addr->addr - 1] = '\0'; + data.delay = cbcp->fsm.delay; + data.length = addr->addr + strlen(addr->addr) + 1 - (char *)&data; + break; + default: + data.delay = cbcp->fsm.delay; + data.length = data.addr_start - (char *)&data; + break; + } + + cbcp_data_Show(&data); + cbcp_Output(cbcp, CBCP_ACK, &data); + cbcp->fsm.restart--; + cbcp_StartTimer(cbcp, cbcp->fsm.delay); + cbcp_NewPhase(cbcp, CBCP_ACKSENT); /* Wait for an ACK */ +} + +extern struct mbuf * +cbcp_Input(struct bundle *bundle __unused, struct link *l, struct mbuf *bp) +{ + struct physical *p = link2physical(l); + struct cbcp_header *head; + struct cbcp_data *data; + struct cbcp *cbcp = &p->dl->cbcp; + size_t len; + + if (p == NULL) { + log_Printf(LogERROR, "cbcp_Input: Not a physical link - dropped\n"); + m_freem(bp); + return NULL; + } + + bp = m_pullup(bp); + len = m_length(bp); + if (len < sizeof(struct cbcp_header)) { + m_freem(bp); + return NULL; + } + head = (struct cbcp_header *)MBUF_CTOP(bp); + if (ntohs(head->length) != len) { + log_Printf(LogWARN, "Corrupt CBCP packet (code %d, length %u not %zu)" + " - ignored\n", head->code, ntohs(head->length), len); + m_freem(bp); + return NULL; + } + m_settype(bp, MB_CBCPIN); + + /* XXX check the id */ + + bp->m_offset += sizeof(struct cbcp_header); + bp->m_len -= sizeof(struct cbcp_header); + data = (struct cbcp_data *)MBUF_CTOP(bp); + + switch (head->code) { + case CBCP_REQ: + log_Printf(LogCBCP, "%s: RecvReq(%d) state = %s\n", + p->dl->name, head->id, cbcpstate(cbcp->fsm.state)); + cbcp_data_Show(data); + if (cbcp->fsm.state == CBCP_STOPPED || cbcp->fsm.state == CBCP_RESPSENT) { + timer_Stop(&cbcp->fsm.timer); + if (cbcp_AdjustResponse(cbcp, data)) { + cbcp->fsm.restart = DEF_FSMTRIES; + cbcp->fsm.id = head->id; + cbcp_SendResponse(cbcp); + } else + datalink_CBCPFailed(cbcp->p->dl); + } else + log_Printf(LogCBCP, "%s: unexpected REQ dropped\n", p->dl->name); + break; + + case CBCP_RESPONSE: + log_Printf(LogCBCP, "%s: RecvResponse(%d) state = %s\n", + p->dl->name, head->id, cbcpstate(cbcp->fsm.state)); + cbcp_data_Show(data); + if (cbcp->fsm.id != head->id) { + log_Printf(LogCBCP, "Warning: Expected id was %d, not %d\n", + cbcp->fsm.id, head->id); + cbcp->fsm.id = head->id; + } + if (cbcp->fsm.state == CBCP_REQSENT || cbcp->fsm.state == CBCP_ACKSENT) { + timer_Stop(&cbcp->fsm.timer); + switch (cbcp_CheckResponse(cbcp, data)) { + case CBCP_ACTION_REQ: + cbcp_SendReq(cbcp); + break; + + case CBCP_ACTION_ACK: + cbcp->fsm.restart = DEF_FSMTRIES; + cbcp_SendAck(cbcp); + if (cbcp->fsm.type == CBCP_NONUM) { + /* + * Don't change state in case the peer doesn't get our ACK, + * just bring the layer up. + */ + timer_Stop(&cbcp->fsm.timer); + datalink_NCPUp(cbcp->p->dl); + } + break; + + default: + datalink_CBCPFailed(cbcp->p->dl); + break; + } + } else + log_Printf(LogCBCP, "%s: unexpected RESPONSE dropped\n", p->dl->name); + break; + + case CBCP_ACK: + log_Printf(LogCBCP, "%s: RecvAck(%d) state = %s\n", + p->dl->name, head->id, cbcpstate(cbcp->fsm.state)); + cbcp_data_Show(data); + if (cbcp->fsm.id != head->id) { + log_Printf(LogCBCP, "Warning: Expected id was %d, not %d\n", + cbcp->fsm.id, head->id); + cbcp->fsm.id = head->id; + } + if (cbcp->fsm.type == CBCP_NONUM) { + /* + * Don't change state in case the peer doesn't get our ACK, + * just bring the layer up. + */ + timer_Stop(&cbcp->fsm.timer); + datalink_NCPUp(cbcp->p->dl); + } else if (cbcp->fsm.state == CBCP_RESPSENT) { + timer_Stop(&cbcp->fsm.timer); + datalink_CBCPComplete(cbcp->p->dl); + log_Printf(LogPHASE, "%s: CBCP: Peer will dial back\n", p->dl->name); + } else + log_Printf(LogCBCP, "%s: unexpected ACK dropped\n", p->dl->name); + break; + + default: + log_Printf(LogWARN, "Unrecognised CBCP packet (code %d, length %zd)\n", + head->code, len); + break; + } + + m_freem(bp); + return NULL; +} + +void +cbcp_Down(struct cbcp *cbcp) +{ + timer_Stop(&cbcp->fsm.timer); + cbcp_NewPhase(cbcp, CBCP_CLOSED); + cbcp->required = 0; +} + +void +cbcp_ReceiveTerminateReq(struct physical *p) +{ + if (p->dl->cbcp.fsm.state == CBCP_ACKSENT) { + /* Don't change our state in case the peer doesn't get the ACK */ + p->dl->cbcp.required = 1; + log_Printf(LogPHASE, "%s: CBCP: Will dial back on %s\n", p->dl->name, + p->dl->cbcp.fsm.phone); + } else + cbcp_NewPhase(&p->dl->cbcp, CBCP_CLOSED); +} diff --git a/src/cbcp.h b/src/cbcp.h new file mode 100644 index 0000000..a18942a --- /dev/null +++ b/src/cbcp.h @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 1998 Brian Somers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/cbcp.h,v 1.3.62.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +struct mbuf; +struct physical; +struct datalink; + +/* fsm states */ +#define CBCP_CLOSED (0) /* Not in use */ +#define CBCP_STOPPED (1) /* Waiting for a REQ */ +#define CBCP_REQSENT (2) /* Waiting for a RESP */ +#define CBCP_RESPSENT (3) /* Waiting for an ACK */ +#define CBCP_ACKSENT (4) /* Waiting for an LCP Term REQ */ + +struct cbcpcfg { + u_char delay; + char phone[SCRIPT_LEN]; + long fsmretry; +}; + +struct cbcp { + unsigned required : 1; /* Are we gonna call back ? */ + struct physical *p; /* On this physical link */ + struct { + u_char type; /* cbcp_data::type (none/me/him/list) */ + u_char delay; /* How long to delay */ + char phone[SCRIPT_LEN]; /* What to dial */ + + int state; /* Our FSM state */ + u_char id; /* Our FSM ID */ + u_char restart; /* FSM Send again ? */ + struct pppTimer timer; /* Resend last option */ + } fsm; +}; + +extern void cbcp_Init(struct cbcp *, struct physical *); +extern void cbcp_Up(struct cbcp *); +extern struct mbuf *cbcp_Input(struct bundle *, struct link *, struct mbuf *); +extern void cbcp_Down(struct cbcp *); +extern void cbcp_ReceiveTerminateReq(struct physical *); diff --git a/src/ccp.c b/src/ccp.c new file mode 100644 index 0000000..c8c94e2 --- /dev/null +++ b/src/ccp.c @@ -0,0 +1,826 @@ +/*- + * Copyright (c) 1996 - 2001 Brian Somers + * based on work by Toshiharu OHNO + * Internet Initiative Japan, Inc (IIJ) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/ccp.c,v 1.78.26.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include /* memcpy() on some archs */ +#include + +#include "layer.h" +#include "defs.h" +#include "command.h" +#include "mbuf.h" +#include "log.h" +#include "timer.h" +#include "fsm.h" +#include "proto.h" +#include "pred.h" +#include "deflate.h" +#include "throughput.h" +#include "iplist.h" +#include "slcompress.h" +#include "lqr.h" +#include "hdlc.h" +#include "lcp.h" +#include "ccp.h" +#include "ncpaddr.h" +#include "ipcp.h" +#include "filter.h" +#include "descriptor.h" +#include "prompt.h" +#include "link.h" +#include "mp.h" +#include "async.h" +#include "physical.h" +#ifndef NORADIUS +#include "radius.h" +#endif +#ifndef NODES +#include "mppe.h" +#endif +#include "ipv6cp.h" +#include "ncp.h" +#include "bundle.h" + +static void CcpSendConfigReq(struct fsm *); +static void CcpSentTerminateReq(struct fsm *); +static void CcpSendTerminateAck(struct fsm *, u_char); +static void CcpDecodeConfig(struct fsm *, u_char *, u_char *, int, + struct fsm_decode *); +static void CcpLayerStart(struct fsm *); +static void CcpLayerFinish(struct fsm *); +static int CcpLayerUp(struct fsm *); +static void CcpLayerDown(struct fsm *); +static void CcpInitRestartCounter(struct fsm *, int); +static int CcpRecvResetReq(struct fsm *); +static void CcpRecvResetAck(struct fsm *, u_char); + +static struct fsm_callbacks ccp_Callbacks = { + CcpLayerUp, + CcpLayerDown, + CcpLayerStart, + CcpLayerFinish, + CcpInitRestartCounter, + CcpSendConfigReq, + CcpSentTerminateReq, + CcpSendTerminateAck, + CcpDecodeConfig, + CcpRecvResetReq, + CcpRecvResetAck +}; + +static const char * const ccp_TimerNames[] = + {"CCP restart", "CCP openmode", "CCP stopped"}; + +static const char * +protoname(int proto) +{ + static char const * const cftypes[] = { + /* Check out the latest ``Compression Control Protocol'' rfc (1962) */ + "OUI", /* 0: OUI */ + "PRED1", /* 1: Predictor type 1 */ + "PRED2", /* 2: Predictor type 2 */ + "PUDDLE", /* 3: Puddle Jumber */ + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + "HWPPC", /* 16: Hewlett-Packard PPC */ + "STAC", /* 17: Stac Electronics LZS (rfc1974) */ + "MPPE", /* 18: Microsoft PPC (rfc2118) and */ + /* Microsoft PPE (draft-ietf-pppext-mppe) */ + "GAND", /* 19: Gandalf FZA (rfc1993) */ + "V42BIS", /* 20: ARG->DATA.42bis compression */ + "BSD", /* 21: BSD LZW Compress */ + NULL, + "LZS-DCP", /* 23: LZS-DCP Compression Protocol (rfc1967) */ + "MAGNALINK/DEFLATE",/* 24: Magnalink Variable Resource (rfc1975) */ + /* 24: Deflate (according to pppd-2.3.*) */ + "DCE", /* 25: Data Circuit-Terminating Equip (rfc1976) */ + "DEFLATE", /* 26: Deflate (rfc1979) */ + }; + + if (proto < 0 || (unsigned)proto > sizeof cftypes / sizeof *cftypes || + cftypes[proto] == NULL) { + if (proto == -1) + return "none"; + return HexStr(proto, NULL, 0); + } + + return cftypes[proto]; +} + +/* We support these algorithms, and Req them in the given order */ +static const struct ccp_algorithm * const algorithm[] = { + &DeflateAlgorithm, + &Pred1Algorithm, + &PppdDeflateAlgorithm +#ifndef NODES + , &MPPEAlgorithm +#endif +}; + +#define NALGORITHMS (sizeof algorithm/sizeof algorithm[0]) + +int +ccp_ReportStatus(struct cmdargs const *arg) +{ + struct ccp_opt **o; + struct link *l; + struct ccp *ccp; + int f; + + l = command_ChooseLink(arg); + ccp = &l->ccp; + + prompt_Printf(arg->prompt, "%s: %s [%s]\n", l->name, ccp->fsm.name, + State2Nam(ccp->fsm.state)); + if (ccp->fsm.state == ST_OPENED) { + prompt_Printf(arg->prompt, " My protocol = %s, His protocol = %s\n", + protoname(ccp->my_proto), protoname(ccp->his_proto)); + prompt_Printf(arg->prompt, " Output: %ld --> %ld, Input: %ld --> %ld\n", + ccp->uncompout, ccp->compout, + ccp->compin, ccp->uncompin); + } + + if (ccp->in.algorithm != -1) + prompt_Printf(arg->prompt, "\n Input Options: %s\n", + (*algorithm[ccp->in.algorithm]->Disp)(&ccp->in.opt)); + + if (ccp->out.algorithm != -1) { + o = &ccp->out.opt; + for (f = 0; f < ccp->out.algorithm; f++) + if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg])) + o = &(*o)->next; + prompt_Printf(arg->prompt, " Output Options: %s\n", + (*algorithm[ccp->out.algorithm]->Disp)(&(*o)->val)); + } + + prompt_Printf(arg->prompt, "\n Defaults: "); + prompt_Printf(arg->prompt, "FSM retry = %us, max %u Config" + " REQ%s, %u Term REQ%s\n", ccp->cfg.fsm.timeout, + ccp->cfg.fsm.maxreq, ccp->cfg.fsm.maxreq == 1 ? "" : "s", + ccp->cfg.fsm.maxtrm, ccp->cfg.fsm.maxtrm == 1 ? "" : "s"); + prompt_Printf(arg->prompt, " deflate windows: "); + prompt_Printf(arg->prompt, "incoming = %d, ", ccp->cfg.deflate.in.winsize); + prompt_Printf(arg->prompt, "outgoing = %d\n", ccp->cfg.deflate.out.winsize); +#ifndef NODES + prompt_Printf(arg->prompt, " MPPE: "); + if (ccp->cfg.mppe.keybits) + prompt_Printf(arg->prompt, "%d bits, ", ccp->cfg.mppe.keybits); + else + prompt_Printf(arg->prompt, "any bits, "); + switch (ccp->cfg.mppe.state) { + case MPPE_STATEFUL: + prompt_Printf(arg->prompt, "stateful"); + break; + case MPPE_STATELESS: + prompt_Printf(arg->prompt, "stateless"); + break; + case MPPE_ANYSTATE: + prompt_Printf(arg->prompt, "any state"); + break; + } + prompt_Printf(arg->prompt, "%s\n", + ccp->cfg.mppe.required ? ", required" : ""); +#endif + + prompt_Printf(arg->prompt, "\n DEFLATE: %s\n", + command_ShowNegval(ccp->cfg.neg[CCP_NEG_DEFLATE])); + prompt_Printf(arg->prompt, " PREDICTOR1: %s\n", + command_ShowNegval(ccp->cfg.neg[CCP_NEG_PRED1])); + prompt_Printf(arg->prompt, " DEFLATE24: %s\n", + command_ShowNegval(ccp->cfg.neg[CCP_NEG_DEFLATE24])); +#ifndef NODES + prompt_Printf(arg->prompt, " MPPE: %s\n", + command_ShowNegval(ccp->cfg.neg[CCP_NEG_MPPE])); +#endif + return 0; +} + +void +ccp_SetupCallbacks(struct ccp *ccp) +{ + ccp->fsm.fn = &ccp_Callbacks; + ccp->fsm.FsmTimer.name = ccp_TimerNames[0]; + ccp->fsm.OpenTimer.name = ccp_TimerNames[1]; + ccp->fsm.StoppedTimer.name = ccp_TimerNames[2]; +} + +void +ccp_Init(struct ccp *ccp, struct bundle *bundle, struct link *l, + const struct fsm_parent *parent) +{ + /* Initialise ourselves */ + + fsm_Init(&ccp->fsm, "CCP", PROTO_CCP, 1, CCP_MAXCODE, LogCCP, + bundle, l, parent, &ccp_Callbacks, ccp_TimerNames); + + ccp->cfg.deflate.in.winsize = 0; + ccp->cfg.deflate.out.winsize = 15; + ccp->cfg.fsm.timeout = DEF_FSMRETRY; + ccp->cfg.fsm.maxreq = DEF_FSMTRIES; + ccp->cfg.fsm.maxtrm = DEF_FSMTRIES; + ccp->cfg.neg[CCP_NEG_DEFLATE] = NEG_ENABLED|NEG_ACCEPTED; + ccp->cfg.neg[CCP_NEG_PRED1] = NEG_ENABLED|NEG_ACCEPTED; + ccp->cfg.neg[CCP_NEG_DEFLATE24] = 0; +#ifndef NODES + ccp->cfg.mppe.keybits = 0; + ccp->cfg.mppe.state = MPPE_ANYSTATE; + ccp->cfg.mppe.required = 0; + ccp->cfg.neg[CCP_NEG_MPPE] = NEG_ENABLED|NEG_ACCEPTED; +#endif + + ccp_Setup(ccp); +} + +void +ccp_Setup(struct ccp *ccp) +{ + /* Set ourselves up for a startup */ + ccp->fsm.open_mode = 0; + ccp->his_proto = ccp->my_proto = -1; + ccp->reset_sent = ccp->last_reset = -1; + ccp->in.algorithm = ccp->out.algorithm = -1; + ccp->in.state = ccp->out.state = NULL; + ccp->in.opt.hdr.id = -1; + ccp->out.opt = NULL; + ccp->his_reject = ccp->my_reject = 0; + ccp->uncompout = ccp->compout = 0; + ccp->uncompin = ccp->compin = 0; +} + +/* + * Is ccp *REQUIRED* ? + * We ask each of the configured ccp protocols if they're required and + * return TRUE if they are. + * + * It's not possible for the peer to reject a required ccp protocol + * without our state machine bringing the supporting lcp layer down. + * + * If ccp is required but not open, the NCP layer should not push + * any data into the link. + */ +int +ccp_Required(struct ccp *ccp) +{ + unsigned f; + + for (f = 0; f < NALGORITHMS; f++) + if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]) && + (*algorithm[f]->Required)(&ccp->fsm)) + return 1; + + return 0; +} + +/* + * Report whether it's possible to increase a packet's size after + * compression (and by how much). + */ +int +ccp_MTUOverhead(struct ccp *ccp) +{ + if (ccp->fsm.state == ST_OPENED && ccp->out.algorithm >= 0) + return algorithm[ccp->out.algorithm]->o.MTUOverhead; + + return 0; +} + +static void +CcpInitRestartCounter(struct fsm *fp, int what) +{ + /* Set fsm timer load */ + struct ccp *ccp = fsm2ccp(fp); + + fp->FsmTimer.load = ccp->cfg.fsm.timeout * SECTICKS; + switch (what) { + case FSM_REQ_TIMER: + fp->restart = ccp->cfg.fsm.maxreq; + break; + case FSM_TRM_TIMER: + fp->restart = ccp->cfg.fsm.maxtrm; + break; + default: + fp->restart = 1; + break; + } +} + +static void +CcpSendConfigReq(struct fsm *fp) +{ + /* Send config REQ please */ + struct ccp *ccp = fsm2ccp(fp); + struct ccp_opt **o; + u_char *cp, buff[100]; + unsigned f; + int alloc; + + cp = buff; + o = &ccp->out.opt; + alloc = ccp->his_reject == 0 && ccp->out.opt == NULL; + ccp->my_proto = -1; + ccp->out.algorithm = -1; + for (f = 0; f < NALGORITHMS; f++) + if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]) && + !REJECTED(ccp, algorithm[f]->id) && + (*algorithm[f]->Usable)(fp)) { + + if (!alloc) + for (o = &ccp->out.opt; *o != NULL; o = &(*o)->next) + if ((*o)->val.hdr.id == algorithm[f]->id && (*o)->algorithm == (int)f) + break; + + if (alloc || *o == NULL) { + if ((*o = (struct ccp_opt *)malloc(sizeof(struct ccp_opt))) == NULL) { + log_Printf(LogERROR, "%s: Not enough memory for CCP REQ !\n", + fp->link->name); + break; + } + (*o)->val.hdr.id = algorithm[f]->id; + (*o)->val.hdr.len = 2; + (*o)->next = NULL; + (*o)->algorithm = f; + (*algorithm[f]->o.OptInit)(fp->bundle, &(*o)->val, &ccp->cfg); + } + + if (cp + (*o)->val.hdr.len > buff + sizeof buff) { + log_Printf(LogERROR, "%s: CCP REQ buffer overrun !\n", fp->link->name); + break; + } + memcpy(cp, &(*o)->val, (*o)->val.hdr.len); + cp += (*o)->val.hdr.len; + + ccp->my_proto = (*o)->val.hdr.id; + ccp->out.algorithm = f; + + if (alloc) + o = &(*o)->next; + } + + fsm_Output(fp, CODE_CONFIGREQ, fp->reqid, buff, cp - buff, MB_CCPOUT); +} + +void +ccp_SendResetReq(struct fsm *fp) +{ + /* We can't read our input - ask peer to reset */ + struct ccp *ccp = fsm2ccp(fp); + + ccp->reset_sent = fp->reqid; + ccp->last_reset = -1; + fsm_Output(fp, CODE_RESETREQ, fp->reqid, NULL, 0, MB_CCPOUT); +} + +static void +CcpSentTerminateReq(struct fsm *fp __unused) +{ + /* Term REQ just sent by FSM */ +} + +static void +CcpSendTerminateAck(struct fsm *fp, u_char id) +{ + /* Send Term ACK please */ + fsm_Output(fp, CODE_TERMACK, id, NULL, 0, MB_CCPOUT); +} + +static int +CcpRecvResetReq(struct fsm *fp) +{ + /* Got a reset REQ, reset outgoing dictionary */ + struct ccp *ccp = fsm2ccp(fp); + if (ccp->out.state == NULL) + return 1; + return (*algorithm[ccp->out.algorithm]->o.Reset)(ccp->out.state); +} + +static void +CcpLayerStart(struct fsm *fp) +{ + /* We're about to start up ! */ + struct ccp *ccp = fsm2ccp(fp); + + log_Printf(LogCCP, "%s: LayerStart.\n", fp->link->name); + fp->more.reqs = fp->more.naks = fp->more.rejs = ccp->cfg.fsm.maxreq * 3; +} + +static void +CcpLayerDown(struct fsm *fp) +{ + /* About to come down */ + struct ccp *ccp = fsm2ccp(fp); + struct ccp_opt *next; + + log_Printf(LogCCP, "%s: LayerDown.\n", fp->link->name); + if (ccp->in.state != NULL) { + (*algorithm[ccp->in.algorithm]->i.Term)(ccp->in.state); + ccp->in.state = NULL; + ccp->in.algorithm = -1; + } + if (ccp->out.state != NULL) { + (*algorithm[ccp->out.algorithm]->o.Term)(ccp->out.state); + ccp->out.state = NULL; + ccp->out.algorithm = -1; + } + ccp->his_reject = ccp->my_reject = 0; + + while (ccp->out.opt) { + next = ccp->out.opt->next; + free(ccp->out.opt); + ccp->out.opt = next; + } + ccp_Setup(ccp); +} + +static void +CcpLayerFinish(struct fsm *fp) +{ + /* We're now down */ + struct ccp *ccp = fsm2ccp(fp); + struct ccp_opt *next; + + log_Printf(LogCCP, "%s: LayerFinish.\n", fp->link->name); + + /* + * Nuke options that may be left over from sending a REQ but never + * coming up. + */ + while (ccp->out.opt) { + next = ccp->out.opt->next; + free(ccp->out.opt); + ccp->out.opt = next; + } + + if (ccp_Required(ccp)) { + if (fp->link->lcp.fsm.state == ST_OPENED) + log_Printf(LogLCP, "%s: Closing due to CCP completion\n", fp->link->name); + fsm_Close(&fp->link->lcp.fsm); + } +} + +/* Called when CCP has reached the OPEN state */ +static int +CcpLayerUp(struct fsm *fp) +{ + /* We're now up */ + struct ccp *ccp = fsm2ccp(fp); + struct ccp_opt **o; + unsigned f, fail; + + for (f = fail = 0; f < NALGORITHMS; f++) + if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg]) && + (*algorithm[f]->Required)(&ccp->fsm) && + (ccp->in.algorithm != (int)f || ccp->out.algorithm != (int)f)) { + /* Blow it all away - we haven't negotiated a required algorithm */ + log_Printf(LogWARN, "%s: Failed to negotiate (required) %s\n", + fp->link->name, protoname(algorithm[f]->id)); + fail = 1; + } + + if (fail) { + ccp->his_proto = ccp->my_proto = -1; + fsm_Close(fp); + fsm_Close(&fp->link->lcp.fsm); + return 0; + } + + log_Printf(LogCCP, "%s: LayerUp.\n", fp->link->name); + + if (ccp->in.state == NULL && ccp->in.algorithm >= 0 && + ccp->in.algorithm < (int)NALGORITHMS) { + ccp->in.state = (*algorithm[ccp->in.algorithm]->i.Init) + (fp->bundle, &ccp->in.opt); + if (ccp->in.state == NULL) { + log_Printf(LogERROR, "%s: %s (in) initialisation failure\n", + fp->link->name, protoname(ccp->his_proto)); + ccp->his_proto = ccp->my_proto = -1; + fsm_Close(fp); + return 0; + } + } + + o = &ccp->out.opt; + if (ccp->out.algorithm > 0) + for (f = 0; f < (unsigned)ccp->out.algorithm; f++) + if (IsEnabled(ccp->cfg.neg[algorithm[f]->Neg])) + o = &(*o)->next; + + if (ccp->out.state == NULL && ccp->out.algorithm >= 0 && + ccp->out.algorithm < (int)NALGORITHMS) { + ccp->out.state = (*algorithm[ccp->out.algorithm]->o.Init) + (fp->bundle, &(*o)->val); + if (ccp->out.state == NULL) { + log_Printf(LogERROR, "%s: %s (out) initialisation failure\n", + fp->link->name, protoname(ccp->my_proto)); + ccp->his_proto = ccp->my_proto = -1; + fsm_Close(fp); + return 0; + } + } + + fp->more.reqs = fp->more.naks = fp->more.rejs = ccp->cfg.fsm.maxreq * 3; + + log_Printf(LogCCP, "%s: Out = %s[%d], In = %s[%d]\n", + fp->link->name, protoname(ccp->my_proto), ccp->my_proto, + protoname(ccp->his_proto), ccp->his_proto); + + return 1; +} + +static void +CcpDecodeConfig(struct fsm *fp, u_char *cp, u_char *end, int mode_type, + struct fsm_decode *dec) +{ + /* Deal with incoming data */ + struct ccp *ccp = fsm2ccp(fp); + int f; + const char *disp; + struct fsm_opt *opt; + + if (mode_type == MODE_REQ) + ccp->in.algorithm = -1; /* In case we've received two REQs in a row */ + + while (end >= cp + sizeof(opt->hdr)) { + if ((opt = fsm_readopt(&cp)) == NULL) + break; + + for (f = NALGORITHMS-1; f > -1; f--) + if (algorithm[f]->id == opt->hdr.id) + break; + + disp = f == -1 ? "" : (*algorithm[f]->Disp)(opt); + if (disp == NULL) + disp = ""; + + log_Printf(LogCCP, " %s[%d] %s\n", protoname(opt->hdr.id), + opt->hdr.len, disp); + + if (f == -1) { + /* Don't understand that :-( */ + if (mode_type == MODE_REQ) { + ccp->my_reject |= (1 << opt->hdr.id); + fsm_rej(dec, opt); + } + } else { + struct ccp_opt *o; + + switch (mode_type) { + case MODE_REQ: + if (IsAccepted(ccp->cfg.neg[algorithm[f]->Neg]) && + (*algorithm[f]->Usable)(fp) && + ccp->in.algorithm == -1) { + memcpy(&ccp->in.opt, opt, opt->hdr.len); + switch ((*algorithm[f]->i.Set)(fp->bundle, &ccp->in.opt, &ccp->cfg)) { + case MODE_REJ: + fsm_rej(dec, &ccp->in.opt); + break; + case MODE_NAK: + fsm_nak(dec, &ccp->in.opt); + break; + case MODE_ACK: + fsm_ack(dec, &ccp->in.opt); + ccp->his_proto = opt->hdr.id; + ccp->in.algorithm = (int)f; /* This one'll do :-) */ + break; + } + } else { + fsm_rej(dec, opt); + } + break; + case MODE_NAK: + for (o = ccp->out.opt; o != NULL; o = o->next) + if (o->val.hdr.id == opt->hdr.id) + break; + if (o == NULL) + log_Printf(LogCCP, "%s: Warning: Ignoring peer NAK of unsent" + " option\n", fp->link->name); + else { + memcpy(&o->val, opt, opt->hdr.len); + if ((*algorithm[f]->o.Set)(fp->bundle, &o->val, &ccp->cfg) == + MODE_ACK) + ccp->my_proto = algorithm[f]->id; + else { + ccp->his_reject |= (1 << opt->hdr.id); + ccp->my_proto = -1; + if (algorithm[f]->Required(fp)) { + log_Printf(LogWARN, "%s: Cannot understand peers (required)" + " %s negotiation\n", fp->link->name, + protoname(algorithm[f]->id)); + fsm_Close(&fp->link->lcp.fsm); + } + } + } + break; + case MODE_REJ: + ccp->his_reject |= (1 << opt->hdr.id); + ccp->my_proto = -1; + if (algorithm[f]->Required(fp)) { + log_Printf(LogWARN, "%s: Peer rejected (required) %s negotiation\n", + fp->link->name, protoname(algorithm[f]->id)); + fsm_Close(&fp->link->lcp.fsm); + } + break; + } + } + } + + if (mode_type != MODE_NOP) { + fsm_opt_normalise(dec); + if (dec->rejend != dec->rej || dec->nakend != dec->nak) { + if (ccp->in.state == NULL) { + ccp->his_proto = -1; + ccp->in.algorithm = -1; + } + } + } +} + +extern struct mbuf * +ccp_Input(struct bundle *bundle, struct link *l, struct mbuf *bp) +{ + /* Got PROTO_CCP from link */ + m_settype(bp, MB_CCPIN); + if (bundle_Phase(bundle) == PHASE_NETWORK) + fsm_Input(&l->ccp.fsm, bp); + else { + if (bundle_Phase(bundle) < PHASE_NETWORK) + log_Printf(LogCCP, "%s: Error: Unexpected CCP in phase %s (ignored)\n", + l->ccp.fsm.link->name, bundle_PhaseName(bundle)); + m_freem(bp); + } + return NULL; +} + +static void +CcpRecvResetAck(struct fsm *fp, u_char id) +{ + /* Got a reset ACK, reset incoming dictionary */ + struct ccp *ccp = fsm2ccp(fp); + + if (ccp->reset_sent != -1) { + if (id != ccp->reset_sent) { + log_Printf(LogCCP, "%s: Incorrect ResetAck (id %d, not %d)" + " ignored\n", fp->link->name, id, ccp->reset_sent); + return; + } + /* Whaddaya know - a correct reset ack */ + } else if (id == ccp->last_reset) + log_Printf(LogCCP, "%s: Duplicate ResetAck (resetting again)\n", + fp->link->name); + else { + log_Printf(LogCCP, "%s: Unexpected ResetAck (id %d) ignored\n", + fp->link->name, id); + return; + } + + ccp->last_reset = ccp->reset_sent; + ccp->reset_sent = -1; + if (ccp->in.state != NULL) + (*algorithm[ccp->in.algorithm]->i.Reset)(ccp->in.state); +} + +static struct mbuf * +ccp_LayerPush(struct bundle *b __unused, struct link *l, struct mbuf *bp, + int pri, u_short *proto) +{ + if (PROTO_COMPRESSIBLE(*proto)) { + if (l->ccp.fsm.state != ST_OPENED) { + if (ccp_Required(&l->ccp)) { + /* The NCP layer shouldn't have let this happen ! */ + log_Printf(LogERROR, "%s: Unexpected attempt to use an unopened and" + " required CCP layer\n", l->name); + m_freem(bp); + bp = NULL; + } + } else if (l->ccp.out.state != NULL) { + bp = (*algorithm[l->ccp.out.algorithm]->o.Write) + (l->ccp.out.state, &l->ccp, l, pri, proto, bp); + switch (*proto) { + case PROTO_ICOMPD: + m_settype(bp, MB_ICOMPDOUT); + break; + case PROTO_COMPD: + m_settype(bp, MB_COMPDOUT); + break; + } + } + } + + return bp; +} + +static struct mbuf * +ccp_LayerPull(struct bundle *b __unused, struct link *l, struct mbuf *bp, + u_short *proto) +{ + /* + * If proto isn't PROTO_[I]COMPD, we still want to pass it to the + * decompression routines so that the dictionary's updated + */ + if (l->ccp.fsm.state == ST_OPENED) { + if (*proto == PROTO_COMPD || *proto == PROTO_ICOMPD) { + /* Decompress incoming data */ + if (l->ccp.reset_sent != -1) + /* Send another REQ and put the packet in the bit bucket */ + fsm_Output(&l->ccp.fsm, CODE_RESETREQ, l->ccp.reset_sent, NULL, 0, + MB_CCPOUT); + else if (l->ccp.in.state != NULL) { + bp = (*algorithm[l->ccp.in.algorithm]->i.Read) + (l->ccp.in.state, &l->ccp, proto, bp); + switch (*proto) { + case PROTO_ICOMPD: + m_settype(bp, MB_ICOMPDIN); + break; + case PROTO_COMPD: + m_settype(bp, MB_COMPDIN); + break; + } + return bp; + } + m_freem(bp); + bp = NULL; + } else if (PROTO_COMPRESSIBLE(*proto) && l->ccp.in.state != NULL) { + /* Add incoming Network Layer traffic to our dictionary */ + (*algorithm[l->ccp.in.algorithm]->i.DictSetup) + (l->ccp.in.state, &l->ccp, *proto, bp); + } + } + + return bp; +} + +u_short +ccp_Proto(struct ccp *ccp) +{ + return !link2physical(ccp->fsm.link) || !ccp->fsm.bundle->ncp.mp.active ? + PROTO_COMPD : PROTO_ICOMPD; +} + +int +ccp_SetOpenMode(struct ccp *ccp) +{ + int f; + + for (f = 0; f < CCP_NEG_TOTAL; f++) + if (IsEnabled(ccp->cfg.neg[f])) { + ccp->fsm.open_mode = 0; + return 1; + } + + ccp->fsm.open_mode = OPEN_PASSIVE; /* Go straight to ST_STOPPED ? */ + + for (f = 0; f < CCP_NEG_TOTAL; f++) + if (IsAccepted(ccp->cfg.neg[f])) + return 1; + + return 0; /* No CCP at all */ +} + +int +ccp_DefaultUsable(struct fsm *fp __unused) +{ + return 1; +} + +int +ccp_DefaultRequired(struct fsm *fp __unused) +{ + return 0; +} + +struct layer ccplayer = { LAYER_CCP, "ccp", ccp_LayerPush, ccp_LayerPull }; diff --git a/src/ccp.h b/src/ccp.h new file mode 100644 index 0000000..442936e --- /dev/null +++ b/src/ccp.h @@ -0,0 +1,165 @@ +/*- + * Copyright (c) 1996 - 2001 Brian Somers + * based on work by Toshiharu OHNO + * Internet Initiative Japan, Inc (IIJ) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/ccp.h,v 1.31.40.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#define CCP_MAXCODE CODE_RESETACK + +#define TY_OUI 0 /* OUI */ +#define TY_PRED1 1 /* Predictor type 1 */ +#define TY_PRED2 2 /* Predictor type 2 */ +#define TY_PUDDLE 3 /* Puddle Jumper */ +#define TY_HWPPC 16 /* Hewlett-Packard PPC */ +#define TY_STAC 17 /* Stac Electronics LZS */ +#define TY_MSPPC 18 /* Microsoft PPC */ +#define TY_MPPE 18 /* Microsoft PPE */ +#define TY_GAND 19 /* Gandalf FZA */ +#define TY_V42BIS 20 /* V.42bis compression */ +#define TY_BSD 21 /* BSD LZW Compress */ +#define TY_PPPD_DEFLATE 24 /* Deflate (gzip) - (mis) numbered by pppd */ +#define TY_DEFLATE 26 /* Deflate (gzip) - rfc 1979 */ + +#define CCP_NEG_DEFLATE 0 +#define CCP_NEG_PRED1 1 +#define CCP_NEG_DEFLATE24 2 +#ifndef NODES +#define CCP_NEG_MPPE 3 +#define CCP_NEG_TOTAL 4 +#else +#define CCP_NEG_TOTAL 3 +#endif + +#ifndef NODES +enum mppe_negstate { + MPPE_ANYSTATE, + MPPE_STATELESS, + MPPE_STATEFUL +}; +#endif + +struct mbuf; +struct link; + +struct ccp_config { + struct { + struct { + int winsize; + } in, out; + } deflate; +#ifndef NODES + struct { + int keybits; + enum mppe_negstate state; + unsigned required : 1; + } mppe; +#endif + struct fsm_retry fsm; /* How often/frequently to resend requests */ + unsigned neg[CCP_NEG_TOTAL]; +}; + +struct ccp_opt { + struct ccp_opt *next; + int algorithm; + struct fsm_opt val; +}; + +struct ccp { + struct fsm fsm; /* The finite state machine */ + + int his_proto; /* peer's compression protocol */ + int my_proto; /* our compression protocol */ + + int reset_sent; /* If != -1, ignore compressed 'till ack */ + int last_reset; /* We can receive more (dups) w/ this id */ + + struct { + int algorithm; /* Algorithm in use */ + void *state; /* Returned by implementations Init() */ + struct fsm_opt opt; /* Set by implementation's OptInit() */ + } in; + + struct { + int algorithm; /* Algorithm in use */ + void *state; /* Returned by implementations Init() */ + struct ccp_opt *opt; /* Set by implementation's OptInit() */ + } out; + + u_int32_t his_reject; /* Request codes rejected by peer */ + u_int32_t my_reject; /* Request codes I have rejected */ + + u_long uncompout, compout; /* Outgoing bytes before/after compression */ + u_long uncompin, compin; /* Incoming bytes after/before decompression */ + + struct ccp_config cfg; +}; + +#define fsm2ccp(fp) (fp->proto == PROTO_CCP ? (struct ccp *)fp : NULL) + +struct ccp_algorithm { + int id; + int Neg; /* ccp_config neg array item */ + const char *(*Disp)(struct fsm_opt *); /* Use result immediately ! */ + int (*Usable)(struct fsm *); /* Ok to negotiate ? */ + int (*Required)(struct fsm *); /* Must negotiate ? */ + struct { + int (*Set)(struct bundle *, struct fsm_opt *, const struct ccp_config *); + void *(*Init)(struct bundle *, struct fsm_opt *); + void (*Term)(void *); + void (*Reset)(void *); + struct mbuf *(*Read)(void *, struct ccp *, u_short *, struct mbuf *); + void (*DictSetup)(void *, struct ccp *, u_short, struct mbuf *); + } i; + struct { + int MTUOverhead; + void (*OptInit)(struct bundle *, struct fsm_opt *, + const struct ccp_config *); + int (*Set)(struct bundle *, struct fsm_opt *, const struct ccp_config *); + void *(*Init)(struct bundle *, struct fsm_opt *); + void (*Term)(void *); + int (*Reset)(void *); + struct mbuf *(*Write)(void *, struct ccp *, struct link *, int, u_short *, + struct mbuf *); + } o; +}; + +extern void ccp_Init(struct ccp *, struct bundle *, struct link *, + const struct fsm_parent *); +extern void ccp_Setup(struct ccp *); +extern int ccp_Required(struct ccp *); +extern int ccp_MTUOverhead(struct ccp *); + +extern void ccp_SendResetReq(struct fsm *); +extern struct mbuf *ccp_Input(struct bundle *, struct link *, struct mbuf *); +extern int ccp_ReportStatus(struct cmdargs const *); +extern u_short ccp_Proto(struct ccp *); +extern void ccp_SetupCallbacks(struct ccp *); +extern int ccp_SetOpenMode(struct ccp *); +extern int ccp_DefaultUsable(struct fsm *); +extern int ccp_DefaultRequired(struct fsm *); + +extern struct layer ccplayer; diff --git a/src/chap.c b/src/chap.c new file mode 100644 index 0000000..cf78dd0 --- /dev/null +++ b/src/chap.c @@ -0,0 +1,972 @@ +/*- + * Copyright (c) 1996 - 2001 Brian Somers + * based on work by Toshiharu OHNO + * Internet Initiative Japan, Inc (IIJ) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/chap.c,v 1.86.26.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#ifndef NODES +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "layer.h" +#include "mbuf.h" +#include "log.h" +#include "defs.h" +#include "timer.h" +#include "fsm.h" +#include "proto.h" +#include "lqr.h" +#include "hdlc.h" +#include "lcp.h" +#include "auth.h" +#include "async.h" +#include "throughput.h" +#include "descriptor.h" +#include "chap.h" +#include "iplist.h" +#include "slcompress.h" +#include "ncpaddr.h" +#include "ipcp.h" +#include "filter.h" +#include "ccp.h" +#include "link.h" +#include "physical.h" +#include "mp.h" +#ifndef NORADIUS +#include "radius.h" +#endif +#include "ipv6cp.h" +#include "ncp.h" +#include "bundle.h" +#include "chat.h" +#include "cbcp.h" +#include "command.h" +#include "datalink.h" +#ifndef NODES +#include "chap_ms.h" +#include "mppe.h" +#endif +#include "id.h" + +static const char * const chapcodes[] = { + "???", "CHALLENGE", "RESPONSE", "SUCCESS", "FAILURE" +}; +#define MAXCHAPCODE (sizeof chapcodes / sizeof chapcodes[0] - 1) + +static void +ChapOutput(struct physical *physical, u_int code, u_int id, + const u_char *ptr, int count, const char *text) +{ + int plen; + struct fsmheader lh; + struct mbuf *bp; + + plen = sizeof(struct fsmheader) + count; + lh.code = code; + lh.id = id; + lh.length = htons(plen); + bp = m_get(plen, MB_CHAPOUT); + memcpy(MBUF_CTOP(bp), &lh, sizeof(struct fsmheader)); + if (count) + memcpy(MBUF_CTOP(bp) + sizeof(struct fsmheader), ptr, count); + log_DumpBp(LogDEBUG, "ChapOutput", bp); + if (text == NULL) + log_Printf(LogPHASE, "Chap Output: %s\n", chapcodes[code]); + else + log_Printf(LogPHASE, "Chap Output: %s (%s)\n", chapcodes[code], text); + link_PushPacket(&physical->link, bp, physical->dl->bundle, + LINK_QUEUES(&physical->link) - 1, PROTO_CHAP); +} + +static char * +chap_BuildAnswer(char *name, char *key, u_char id, char *challenge +#ifndef NODES + , u_char type, char *peerchallenge, char *authresponse, + int lanman +#endif + ) +{ + char *result, *digest; + size_t nlen, klen; + + nlen = strlen(name); + klen = strlen(key); + +#ifndef NODES + if (type == 0x80) { + char expkey[AUTHLEN << 2]; + MD4_CTX MD4context; + size_t f; + + if ((result = malloc(1 + nlen + MS_CHAP_RESPONSE_LEN)) == NULL) + return result; + + digest = result; /* the response */ + *digest++ = MS_CHAP_RESPONSE_LEN; /* 49 */ + memcpy(digest + MS_CHAP_RESPONSE_LEN, name, nlen); + if (lanman) { + memset(digest + 24, '\0', 25); + mschap_LANMan(digest, challenge + 1, key); /* LANMan response */ + } else { + memset(digest, '\0', 25); + digest += 24; + + for (f = 0; f < klen; f++) { + expkey[2*f] = key[f]; + expkey[2*f+1] = '\0'; + } + /* + * ----------- + * expkey = | k\0e\0y\0 | + * ----------- + */ + MD4Init(&MD4context); + MD4Update(&MD4context, expkey, klen << 1); + MD4Final(digest, &MD4context); + + /* + * ---- -------- ---------------- ------- ------ + * result = | 49 | LANMan | 16 byte digest | 9 * ? | name | + * ---- -------- ---------------- ------- ------ + */ + mschap_NT(digest, challenge + 1); + } + /* + * ---- -------- ------------- ----- ------ + * | | struct MS_ChapResponse24 | | + * result = | 49 | LANMan | NT digest | 0/1 | name | + * ---- -------- ------------- ----- ------ + * where only one of LANMan & NT digest are set. + */ + } else if (type == 0x81) { + char expkey[AUTHLEN << 2]; + char pwdhash[CHAP81_HASH_LEN]; + char pwdhashhash[CHAP81_HASH_LEN]; + char *ntresponse; + size_t f; + + if ((result = malloc(1 + nlen + CHAP81_RESPONSE_LEN)) == NULL) + return result; + + memset(result, 0, 1 + nlen + CHAP81_RESPONSE_LEN); + + digest = result; + *digest++ = CHAP81_RESPONSE_LEN; /* value size */ + + /* Copy our challenge */ + memcpy(digest, peerchallenge + 1, CHAP81_CHALLENGE_LEN); + + /* Expand password to Unicode XXX */ + for (f = 0; f < klen; f++) { + expkey[2*f] = key[f]; + expkey[2*f+1] = '\0'; + } + + ntresponse = digest + CHAP81_NTRESPONSE_OFF; + + /* Get some needed hashes */ + NtPasswordHash(expkey, klen * 2, pwdhash); + HashNtPasswordHash(pwdhash, pwdhashhash); + + /* Generate NTRESPONSE to respond on challenge call */ + GenerateNTResponse(challenge + 1, peerchallenge + 1, name, + expkey, klen * 2, ntresponse); + + /* Generate MPPE MASTERKEY */ + GetMasterKey(pwdhashhash, ntresponse, MPPE_MasterKey); /* XXX Global ! */ + + /* Generate AUTHRESPONSE to verify on auth success */ + GenerateAuthenticatorResponse(expkey, klen * 2, ntresponse, + peerchallenge + 1, challenge + 1, name, + authresponse); + + authresponse[CHAP81_AUTHRESPONSE_LEN] = 0; + + memcpy(digest + CHAP81_RESPONSE_LEN, name, nlen); + } else +#endif + if ((result = malloc(nlen + 17)) != NULL) { + /* Normal MD5 stuff */ + MD5_CTX MD5context; + + digest = result; + *digest++ = 16; /* value size */ + + MD5Init(&MD5context); + MD5Update(&MD5context, &id, 1); + MD5Update(&MD5context, key, klen); + MD5Update(&MD5context, challenge + 1, *challenge); + MD5Final(digest, &MD5context); + + memcpy(digest + 16, name, nlen); + /* + * ---- -------- ------ + * result = | 16 | digest | name | + * ---- -------- ------ + */ + } + + return result; +} + +static void +chap_StartChild(struct chap *chap, char *prog, const char *name) +{ + char *argv[MAXARGS], *nargv[MAXARGS]; + int argc, fd; + int in[2], out[2]; + pid_t pid; + + if (chap->child.fd != -1) { + log_Printf(LogWARN, "Chap: %s: Program already running\n", prog); + return; + } + + if (pipe(in) == -1) { + log_Printf(LogERROR, "Chap: pipe: %s\n", strerror(errno)); + return; + } + + if (pipe(out) == -1) { + log_Printf(LogERROR, "Chap: pipe: %s\n", strerror(errno)); + close(in[0]); + close(in[1]); + return; + } + + pid = getpid(); + switch ((chap->child.pid = fork())) { + case -1: + log_Printf(LogERROR, "Chap: fork: %s\n", strerror(errno)); + close(in[0]); + close(in[1]); + close(out[0]); + close(out[1]); + chap->child.pid = 0; + return; + + case 0: + timer_TermService(); + + if ((argc = command_Interpret(prog, strlen(prog), argv)) <= 0) { + if (argc < 0) { + log_Printf(LogWARN, "CHAP: Invalid command syntax\n"); + _exit(255); + } + _exit(0); + } + + close(in[1]); + close(out[0]); + if (out[1] == STDIN_FILENO) + out[1] = dup(out[1]); + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + close(STDERR_FILENO); + if (open(_PATH_DEVNULL, O_RDWR) != STDERR_FILENO) { + log_Printf(LogALERT, "Chap: Failed to open %s: %s\n", + _PATH_DEVNULL, strerror(errno)); + exit(1); + } + for (fd = getdtablesize(); fd > STDERR_FILENO; fd--) + fcntl(fd, F_SETFD, 1); +#ifndef NOSUID + setuid(ID0realuid()); +#endif + command_Expand(nargv, argc, (char const *const *)argv, + chap->auth.physical->dl->bundle, 0, pid); + execvp(nargv[0], nargv); + printf("exec() of %s failed: %s\n", nargv[0], strerror(errno)); + _exit(255); + + default: + close(in[0]); + close(out[1]); + chap->child.fd = out[0]; + chap->child.buf.len = 0; + write(in[1], chap->auth.in.name, strlen(chap->auth.in.name)); + write(in[1], "\n", 1); + write(in[1], chap->challenge.peer + 1, *chap->challenge.peer); + write(in[1], "\n", 1); + write(in[1], name, strlen(name)); + write(in[1], "\n", 1); + close(in[1]); + break; + } +} + +static void +chap_Cleanup(struct chap *chap, int sig) +{ + if (chap->child.pid) { + int status; + + close(chap->child.fd); + chap->child.fd = -1; + if (sig) + kill(chap->child.pid, SIGTERM); + chap->child.pid = 0; + chap->child.buf.len = 0; + + if (wait(&status) == -1) + log_Printf(LogERROR, "Chap: wait: %s\n", strerror(errno)); + else if (WIFSIGNALED(status)) + log_Printf(LogWARN, "Chap: Child received signal %d\n", WTERMSIG(status)); + else if (WIFEXITED(status) && WEXITSTATUS(status)) + log_Printf(LogERROR, "Chap: Child exited %d\n", WEXITSTATUS(status)); + } + *chap->challenge.local = *chap->challenge.peer = '\0'; +#ifndef NODES + chap->peertries = 0; +#endif +} + +static void +chap_Respond(struct chap *chap, char *name, char *key +#ifndef NODES + , u_char type, int lm +#endif + ) +{ + u_char *ans; + + ans = chap_BuildAnswer(name, key, chap->auth.id, chap->challenge.peer +#ifndef NODES + , type, chap->challenge.local, chap->authresponse, lm +#endif + ); + + if (ans) { + ChapOutput(chap->auth.physical, CHAP_RESPONSE, chap->auth.id, + ans, *ans + 1 + strlen(name), name); +#ifndef NODES + chap->NTRespSent = !lm; + MPPE_IsServer = 0; /* XXX Global ! */ +#endif + free(ans); + } else + ChapOutput(chap->auth.physical, CHAP_FAILURE, chap->auth.id, + "Out of memory!", 14, NULL); +} + +static int +chap_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w __unused, + fd_set *e __unused, int *n) +{ + struct chap *chap = descriptor2chap(d); + + if (r && chap && chap->child.fd != -1) { + FD_SET(chap->child.fd, r); + if (*n < chap->child.fd + 1) + *n = chap->child.fd + 1; + log_Printf(LogTIMER, "Chap: fdset(r) %d\n", chap->child.fd); + return 1; + } + + return 0; +} + +static int +chap_IsSet(struct fdescriptor *d, const fd_set *fdset) +{ + struct chap *chap = descriptor2chap(d); + + return chap && chap->child.fd != -1 && FD_ISSET(chap->child.fd, fdset); +} + +static void +chap_Read(struct fdescriptor *d, struct bundle *bundle __unused, + const fd_set *fdset __unused) +{ + struct chap *chap = descriptor2chap(d); + int got; + + got = read(chap->child.fd, chap->child.buf.ptr + chap->child.buf.len, + sizeof chap->child.buf.ptr - chap->child.buf.len - 1); + if (got == -1) { + log_Printf(LogERROR, "Chap: Read: %s\n", strerror(errno)); + chap_Cleanup(chap, SIGTERM); + } else if (got == 0) { + log_Printf(LogWARN, "Chap: Read: Child terminated connection\n"); + chap_Cleanup(chap, SIGTERM); + } else { + char *name, *key, *end; + + chap->child.buf.len += got; + chap->child.buf.ptr[chap->child.buf.len] = '\0'; + name = chap->child.buf.ptr; + name += strspn(name, " \t"); + if ((key = strchr(name, '\n')) == NULL) + end = NULL; + else + end = strchr(++key, '\n'); + + if (end == NULL) { + if (chap->child.buf.len == sizeof chap->child.buf.ptr - 1) { + log_Printf(LogWARN, "Chap: Read: Input buffer overflow\n"); + chap_Cleanup(chap, SIGTERM); + } + } else { +#ifndef NODES + int lanman = chap->auth.physical->link.lcp.his_authtype == 0x80 && + ((chap->NTRespSent && + IsAccepted(chap->auth.physical->link.lcp.cfg.chap80lm)) || + !IsAccepted(chap->auth.physical->link.lcp.cfg.chap80nt)); +#endif + + while (end >= name && strchr(" \t\r\n", *end)) + *end-- = '\0'; + end = key - 1; + while (end >= name && strchr(" \t\r\n", *end)) + *end-- = '\0'; + key += strspn(key, " \t"); + + chap_Respond(chap, name, key +#ifndef NODES + , chap->auth.physical->link.lcp.his_authtype, lanman +#endif + ); + chap_Cleanup(chap, 0); + } + } +} + +static int +chap_Write(struct fdescriptor *d __unused, struct bundle *bundle __unused, + const fd_set *fdset __unused) +{ + /* We never want to write here ! */ + log_Printf(LogALERT, "chap_Write: Internal error: Bad call !\n"); + return 0; +} + +static void +chap_ChallengeInit(struct authinfo *authp) +{ + struct chap *chap = auth2chap(authp); + int len, i; + char *cp; + + len = strlen(authp->physical->dl->bundle->cfg.auth.name); + + if (!*chap->challenge.local) { + randinit(); + cp = chap->challenge.local; + +#ifndef NORADIUS + if (*authp->physical->dl->bundle->radius.cfg.file) { + /* For radius, our challenge is 16 readable NUL terminated bytes :*/ + *cp++ = 16; + for (i = 0; i < 16; i++) + *cp++ = (random() % 10) + '0'; + } else +#endif + { +#ifndef NODES + if (authp->physical->link.lcp.want_authtype == 0x80) + *cp++ = 8; /* MS does 8 byte callenges :-/ */ + else if (authp->physical->link.lcp.want_authtype == 0x81) + *cp++ = 16; /* MS-CHAP-V2 does 16 bytes challenges */ + else +#endif + *cp++ = random() % (CHAPCHALLENGELEN-16) + 16; + for (i = 0; i < *chap->challenge.local; i++) + *cp++ = random() & 0xff; + } + memcpy(cp, authp->physical->dl->bundle->cfg.auth.name, len); + } +} + +static void +chap_Challenge(struct authinfo *authp) +{ + struct chap *chap = auth2chap(authp); + int len; + + log_Printf(LogDEBUG, "CHAP%02X: Challenge\n", + authp->physical->link.lcp.want_authtype); + + len = strlen(authp->physical->dl->bundle->cfg.auth.name); + + /* Generate new local challenge value */ + if (!*chap->challenge.local) + chap_ChallengeInit(authp); + +#ifndef NODES + if (authp->physical->link.lcp.want_authtype == 0x81) + ChapOutput(authp->physical, CHAP_CHALLENGE, authp->id, + chap->challenge.local, 1 + *chap->challenge.local, NULL); + else +#endif + ChapOutput(authp->physical, CHAP_CHALLENGE, authp->id, + chap->challenge.local, 1 + *chap->challenge.local + len, NULL); +} + +static void +chap_Success(struct authinfo *authp) +{ + struct bundle *bundle = authp->physical->dl->bundle; + const char *msg; + + datalink_GotAuthname(authp->physical->dl, authp->in.name); +#ifndef NODES + if (authp->physical->link.lcp.want_authtype == 0x81) { +#ifndef NORADIUS + if (*bundle->radius.cfg.file && bundle->radius.msrepstr) + msg = bundle->radius.msrepstr; + else +#endif + msg = auth2chap(authp)->authresponse; + MPPE_MasterKeyValid = 1; /* XXX Global ! */ + } else +#endif +#ifndef NORADIUS + if (*bundle->radius.cfg.file && bundle->radius.repstr) + msg = bundle->radius.repstr; + else +#endif + msg = "Welcome!!"; + + ChapOutput(authp->physical, CHAP_SUCCESS, authp->id, msg, strlen(msg), + NULL); + + authp->physical->link.lcp.auth_ineed = 0; + if (Enabled(bundle, OPT_UTMP)) + physical_Login(authp->physical, authp->in.name); + + if (authp->physical->link.lcp.auth_iwait == 0) + /* + * Either I didn't need to authenticate, or I've already been + * told that I got the answer right. + */ + datalink_AuthOk(authp->physical->dl); +} + +static void +chap_Failure(struct authinfo *authp) +{ +#ifndef NODES + char buf[1024], *ptr; +#endif + const char *msg; + +#ifndef NORADIUS + struct bundle *bundle = authp->physical->link.lcp.fsm.bundle; + if (*bundle->radius.cfg.file && bundle->radius.errstr) + msg = bundle->radius.errstr; + else +#endif +#ifndef NODES + if (authp->physical->link.lcp.want_authtype == 0x80) { + sprintf(buf, "E=691 R=1 M=Invalid!"); + msg = buf; + } else if (authp->physical->link.lcp.want_authtype == 0x81) { + int i; + + ptr = buf; + ptr += sprintf(buf, "E=691 R=0 C="); + for (i=0; i<16; i++) + ptr += sprintf(ptr, "%02X", *(auth2chap(authp)->challenge.local+1+i)); + + sprintf(ptr, " V=3 M=Invalid!"); + msg = buf; + } else +#endif + msg = "Invalid!!"; + + ChapOutput(authp->physical, CHAP_FAILURE, authp->id, msg, strlen(msg) + 1, + NULL); + datalink_AuthNotOk(authp->physical->dl); +} + +static int +chap_Cmp(char *myans, int mylen, char *hisans, int hislen +#ifndef NODES + , u_char type, int lm +#endif + ) +{ + int off; + + if (mylen != hislen) + return 0; + + off = 0; + +#ifndef NODES + if (type == 0x80) { + off = lm ? 0 : 24; + mylen = 24; + } +#endif + + for (; mylen; off++, mylen--) + if (toupper(myans[off]) != toupper(hisans[off])) + return 0; + + return 1; +} + +#ifndef NODES +static int +chap_HaveAnotherGo(struct chap *chap) +{ + if (++chap->peertries < 3) { + /* Give the peer another shot */ + *chap->challenge.local = '\0'; + chap_Challenge(&chap->auth); + return 1; + } + + return 0; +} +#endif + +void +chap_Init(struct chap *chap, struct physical *p) +{ + chap->desc.type = CHAP_DESCRIPTOR; + chap->desc.UpdateSet = chap_UpdateSet; + chap->desc.IsSet = chap_IsSet; + chap->desc.Read = chap_Read; + chap->desc.Write = chap_Write; + chap->child.pid = 0; + chap->child.fd = -1; + auth_Init(&chap->auth, p, chap_Challenge, chap_Success, chap_Failure); + *chap->challenge.local = *chap->challenge.peer = '\0'; +#ifndef NODES + chap->NTRespSent = 0; + chap->peertries = 0; +#endif +} + +void +chap_ReInit(struct chap *chap) +{ + chap_Cleanup(chap, SIGTERM); +} + +struct mbuf * +chap_Input(struct bundle *bundle, struct link *l, struct mbuf *bp) +{ + struct physical *p = link2physical(l); + struct chap *chap = &p->dl->chap; + char *name, *key, *ans; + int len; + size_t nlen; + u_char alen; +#ifndef NODES + int lanman; +#endif + + if (p == NULL) { + log_Printf(LogERROR, "chap_Input: Not a physical link - dropped\n"); + m_freem(bp); + return NULL; + } + + if (bundle_Phase(bundle) != PHASE_NETWORK && + bundle_Phase(bundle) != PHASE_AUTHENTICATE) { + log_Printf(LogPHASE, "Unexpected chap input - dropped !\n"); + m_freem(bp); + return NULL; + } + + m_settype(bp, MB_CHAPIN); + if ((bp = auth_ReadHeader(&chap->auth, bp)) == NULL && + ntohs(chap->auth.in.hdr.length) == 0) + log_Printf(LogWARN, "Chap Input: Truncated header !\n"); + else if (chap->auth.in.hdr.code == 0 || chap->auth.in.hdr.code > MAXCHAPCODE) + log_Printf(LogPHASE, "Chap Input: %d: Bad CHAP code !\n", + chap->auth.in.hdr.code); + else { + len = m_length(bp); + ans = NULL; + + if (chap->auth.in.hdr.code != CHAP_CHALLENGE && + chap->auth.id != chap->auth.in.hdr.id && + Enabled(bundle, OPT_IDCHECK)) { + /* Wrong conversation dude ! */ + log_Printf(LogPHASE, "Chap Input: %s dropped (got id %d, not %d)\n", + chapcodes[chap->auth.in.hdr.code], chap->auth.in.hdr.id, + chap->auth.id); + m_freem(bp); + return NULL; + } + chap->auth.id = chap->auth.in.hdr.id; /* We respond with this id */ + +#ifndef NODES + lanman = 0; +#endif + switch (chap->auth.in.hdr.code) { + case CHAP_CHALLENGE: + bp = mbuf_Read(bp, &alen, 1); + len -= alen + 1; + if (len < 0) { + log_Printf(LogERROR, "Chap Input: Truncated challenge !\n"); + m_freem(bp); + return NULL; + } + *chap->challenge.peer = alen; + bp = mbuf_Read(bp, chap->challenge.peer + 1, alen); + bp = auth_ReadName(&chap->auth, bp, len); +#ifndef NODES + lanman = p->link.lcp.his_authtype == 0x80 && + ((chap->NTRespSent && IsAccepted(p->link.lcp.cfg.chap80lm)) || + !IsAccepted(p->link.lcp.cfg.chap80nt)); + + /* Generate local challenge value */ + chap_ChallengeInit(&chap->auth); +#endif + break; + + case CHAP_RESPONSE: + auth_StopTimer(&chap->auth); + bp = mbuf_Read(bp, &alen, 1); + len -= alen + 1; + if (len < 0) { + log_Printf(LogERROR, "Chap Input: Truncated response !\n"); + m_freem(bp); + return NULL; + } + if ((ans = malloc(alen + 1)) == NULL) { + log_Printf(LogERROR, "Chap Input: Out of memory !\n"); + m_freem(bp); + return NULL; + } + *ans = chap->auth.id; + bp = mbuf_Read(bp, ans + 1, alen); + bp = auth_ReadName(&chap->auth, bp, len); +#ifndef NODES + lanman = p->link.lcp.want_authtype == 0x80 && + alen == 49 && ans[alen] == 0; +#endif + break; + + case CHAP_SUCCESS: + case CHAP_FAILURE: + /* chap->auth.in.name is already set up at CHALLENGE time */ + if ((ans = malloc(len + 1)) == NULL) { + log_Printf(LogERROR, "Chap Input: Out of memory !\n"); + m_freem(bp); + return NULL; + } + bp = mbuf_Read(bp, ans, len); + ans[len] = '\0'; + break; + } + + switch (chap->auth.in.hdr.code) { + case CHAP_CHALLENGE: + case CHAP_RESPONSE: + if (*chap->auth.in.name) + log_Printf(LogPHASE, "Chap Input: %s (%d bytes from %s%s)\n", + chapcodes[chap->auth.in.hdr.code], alen, + chap->auth.in.name, +#ifndef NODES + lanman && chap->auth.in.hdr.code == CHAP_RESPONSE ? + " - lanman" : +#endif + ""); + else + log_Printf(LogPHASE, "Chap Input: %s (%d bytes%s)\n", + chapcodes[chap->auth.in.hdr.code], alen, +#ifndef NODES + lanman && chap->auth.in.hdr.code == CHAP_RESPONSE ? + " - lanman" : +#endif + ""); + break; + + case CHAP_SUCCESS: + case CHAP_FAILURE: + if (*ans) + log_Printf(LogPHASE, "Chap Input: %s (%s)\n", + chapcodes[chap->auth.in.hdr.code], ans); + else + log_Printf(LogPHASE, "Chap Input: %s\n", + chapcodes[chap->auth.in.hdr.code]); + break; + } + + switch (chap->auth.in.hdr.code) { + case CHAP_CHALLENGE: + if (*bundle->cfg.auth.key == '!' && bundle->cfg.auth.key[1] != '!') + chap_StartChild(chap, bundle->cfg.auth.key + 1, + bundle->cfg.auth.name); + else + chap_Respond(chap, bundle->cfg.auth.name, bundle->cfg.auth.key + + (*bundle->cfg.auth.key == '!' ? 1 : 0) + +#ifndef NODES + , p->link.lcp.his_authtype, lanman +#endif + ); + break; + + case CHAP_RESPONSE: + name = chap->auth.in.name; + nlen = strlen(name); +#ifndef NODES + if (p->link.lcp.want_authtype == 0x81) { + struct MSCHAPv2_resp *resp = (struct MSCHAPv2_resp *)(ans + 1); + + chap->challenge.peer[0] = sizeof resp->PeerChallenge; + memcpy(chap->challenge.peer + 1, resp->PeerChallenge, + sizeof resp->PeerChallenge); + } +#endif + +#ifndef NORADIUS + if (*bundle->radius.cfg.file) { + if (!radius_Authenticate(&bundle->radius, &chap->auth, + chap->auth.in.name, ans, alen + 1, + chap->challenge.local + 1, + *chap->challenge.local)) + chap_Failure(&chap->auth); + } else +#endif + { + if (p->link.lcp.want_authtype == 0x81 && ans[alen] != '\0' && + alen == sizeof(struct MSCHAPv2_resp)) { + struct MSCHAPv2_resp *resp = (struct MSCHAPv2_resp *)(ans + 1); + + log_Printf(LogWARN, "%s: Compensating for corrupt (Win98/WinME?) " + "CHAP81 RESPONSE\n", l->name); + resp->Flags = '\0'; /* rfc2759 says it *MUST* be zero */ + } + key = auth_GetSecret(name, nlen); + if (key) { +#ifndef NODES + if (p->link.lcp.want_authtype == 0x80 && + lanman && !IsEnabled(p->link.lcp.cfg.chap80lm)) { + log_Printf(LogPHASE, "Auth failure: LANMan not enabled\n"); + if (chap_HaveAnotherGo(chap)) + break; + key = NULL; + } else if (p->link.lcp.want_authtype == 0x80 && + !lanman && !IsEnabled(p->link.lcp.cfg.chap80nt)) { + log_Printf(LogPHASE, "Auth failure: mschap not enabled\n"); + if (chap_HaveAnotherGo(chap)) + break; + key = NULL; + } else if (p->link.lcp.want_authtype == 0x81 && + !IsEnabled(p->link.lcp.cfg.chap81)) { + log_Printf(LogPHASE, "Auth failure: CHAP81 not enabled\n"); + key = NULL; + } else +#endif + { + char *myans = chap_BuildAnswer(name, key, chap->auth.id, + chap->challenge.local +#ifndef NODES + , p->link.lcp.want_authtype, + chap->challenge.peer, + chap->authresponse, lanman); + MPPE_IsServer = 1; /* XXX Global ! */ +#else + ); +#endif + if (myans == NULL) + key = NULL; + else { + if (!chap_Cmp(myans + 1, *myans, ans + 1, alen +#ifndef NODES + , p->link.lcp.want_authtype, lanman +#endif + )) + key = NULL; + free(myans); + } + } + } + + if (key) + chap_Success(&chap->auth); + else + chap_Failure(&chap->auth); + } + + break; + + case CHAP_SUCCESS: + if (p->link.lcp.auth_iwait == PROTO_CHAP) { + p->link.lcp.auth_iwait = 0; + if (p->link.lcp.auth_ineed == 0) { +#ifndef NODES + if (p->link.lcp.his_authtype == 0x81) { + if (strncasecmp(ans, chap->authresponse, 42)) { + datalink_AuthNotOk(p->dl); + log_Printf(LogWARN, "CHAP81: AuthenticatorResponse: (%.42s)" + " != ans: (%.42s)\n", chap->authresponse, ans); + + } else { + /* Successful login */ + MPPE_MasterKeyValid = 1; /* XXX Global ! */ + datalink_AuthOk(p->dl); + } + } else +#endif + /* + * We've succeeded in our ``login'' + * If we're not expecting the peer to authenticate (or he already + * has), proceed to network phase. + */ + datalink_AuthOk(p->dl); + } + } + break; + + case CHAP_FAILURE: + datalink_AuthNotOk(p->dl); + break; + } + free(ans); + } + + m_freem(bp); + return NULL; +} diff --git a/src/chap.h b/src/chap.h new file mode 100644 index 0000000..e9936e4 --- /dev/null +++ b/src/chap.h @@ -0,0 +1,75 @@ +/*- + * Copyright (c) 1996 - 2001 Brian Somers + * based on work by Toshiharu OHNO + * Internet Initiative Japan, Inc (IIJ) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/chap.h,v 1.22.38.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +struct mbuf; +struct physical; + +#define CHAP_CHALLENGE 1 +#define CHAP_RESPONSE 2 +#define CHAP_SUCCESS 3 +#define CHAP_FAILURE 4 + +struct chap { + struct fdescriptor desc; + struct { + pid_t pid; + int fd; + struct { + char ptr[AUTHLEN * 2 + 3]; /* Allow for \r\n at the end (- NUL) */ + int len; + } buf; + } child; + struct authinfo auth; + struct { + u_char local[CHAPCHALLENGELEN + AUTHLEN]; /* I invented this one */ + u_char peer[CHAPCHALLENGELEN + AUTHLEN]; /* Peer gave us this one */ + } challenge; +#ifndef NODES + unsigned NTRespSent : 1; /* Our last response */ + int peertries; + u_char authresponse[CHAPAUTHRESPONSELEN]; /* CHAP 81 response */ +#endif +}; + +#define descriptor2chap(d) \ + ((d)->type == CHAP_DESCRIPTOR ? (struct chap *)(d) : NULL) +#define auth2chap(a) \ + ((struct chap *)((char *)a - (int)&((struct chap *)0)->auth)) + +struct MSCHAPv2_resp { /* rfc2759 */ + char PeerChallenge[16]; + char Reserved[8]; + char NTResponse[24]; + char Flags; +}; + +extern void chap_Init(struct chap *, struct physical *); +extern void chap_ReInit(struct chap *); +extern struct mbuf *chap_Input(struct bundle *, struct link *, struct mbuf *); diff --git a/src/chap_ms.c b/src/chap_ms.c new file mode 100644 index 0000000..a2a06a8 --- /dev/null +++ b/src/chap_ms.c @@ -0,0 +1,415 @@ +/*- + * Copyright (c) 1997 Gabor Kincses + * 1997 - 2001 Brian Somers + * based on work by Eric Rosenquist + * Strata Software Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/chap_ms.c,v 1.20.26.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#include +#ifdef __FreeBSD__ +#include +#include +#else +#include +#include +#ifdef __NetBSD__ +#include +#else +#include +#endif +#include +#endif +#include +#include + +#include "chap_ms.h" + +/* + * Documentation & specifications: + * + * MS-CHAP (CHAP80) rfc2433 + * MS-CHAP-V2 (CHAP81) rfc2759 + * MPPE key management draft-ietf-pppext-mppe-keys-02.txt + */ + +static char SHA1_Pad1[40] = + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static char SHA1_Pad2[40] = + {0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, + 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, + 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, + 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2}; + +/* unused, for documentation only */ +/* only NTResp is filled in for FreeBSD */ +struct MS_ChapResponse { + u_char LANManResp[24]; + u_char NTResp[24]; + u_char UseNT; /* If 1, ignore the LANMan response field */ +}; + +static u_char +Get7Bits(u_char *input, int startBit) +{ + register unsigned int word; + + word = (unsigned)input[startBit / 8] << 8; + word |= (unsigned)input[startBit / 8 + 1]; + + word >>= 15 - (startBit % 8 + 7); + + return word & 0xFE; +} + +/* IN 56 bit DES key missing parity bits + OUT 64 bit DES key with parity bits added */ +static void +MakeKey(u_char *key, u_char *des_key) +{ + des_key[0] = Get7Bits(key, 0); + des_key[1] = Get7Bits(key, 7); + des_key[2] = Get7Bits(key, 14); + des_key[3] = Get7Bits(key, 21); + des_key[4] = Get7Bits(key, 28); + des_key[5] = Get7Bits(key, 35); + des_key[6] = Get7Bits(key, 42); + des_key[7] = Get7Bits(key, 49); + + des_set_odd_parity((des_cblock *)des_key); +} + +static void /* IN 8 octets IN 7 octest OUT 8 octets */ +DesEncrypt(u_char *clear, u_char *key, u_char *cipher) +{ + des_cblock des_key; + des_key_schedule key_schedule; + + MakeKey(key, des_key); + des_set_key(&des_key, key_schedule); + des_ecb_encrypt((des_cblock *)clear, (des_cblock *)cipher, key_schedule, 1); +} + +static void /* IN 8 octets IN 16 octets OUT 24 octets */ +ChallengeResponse(u_char *challenge, u_char *pwHash, u_char *response) +{ + char ZPasswordHash[21]; + + memset(ZPasswordHash, '\0', sizeof ZPasswordHash); + memcpy(ZPasswordHash, pwHash, 16); + + DesEncrypt(challenge, ZPasswordHash + 0, response + 0); + DesEncrypt(challenge, ZPasswordHash + 7, response + 8); + DesEncrypt(challenge, ZPasswordHash + 14, response + 16); +} + +void +NtPasswordHash(char *key, int keylen, char *hash) +{ + MD4_CTX MD4context; + + MD4Init(&MD4context); + MD4Update(&MD4context, key, keylen); + MD4Final(hash, &MD4context); +} + +void +HashNtPasswordHash(char *hash, char *hashhash) +{ + MD4_CTX MD4context; + + MD4Init(&MD4context); + MD4Update(&MD4context, hash, 16); + MD4Final(hashhash, &MD4context); +} + +static void +ChallengeHash(char *PeerChallenge, char *AuthenticatorChallenge, + char *UserName, char *Challenge) +{ + SHA_CTX Context; + char Digest[SHA_DIGEST_LENGTH]; + char *Name; + + Name = strrchr(UserName, '\\'); + if(NULL == Name) + Name = UserName; + else + Name++; + + SHA1_Init(&Context); + + SHA1_Update(&Context, PeerChallenge, 16); + SHA1_Update(&Context, AuthenticatorChallenge, 16); + SHA1_Update(&Context, Name, strlen(Name)); + + SHA1_Final(Digest, &Context); + memcpy(Challenge, Digest, 8); +} + +void +GenerateNTResponse(char *AuthenticatorChallenge, char *PeerChallenge, + char *UserName, char *Password, + int PasswordLen, char *Response) +{ + char Challenge[8]; + char PasswordHash[16]; + + ChallengeHash(PeerChallenge, AuthenticatorChallenge, UserName, Challenge); + NtPasswordHash(Password, PasswordLen, PasswordHash); + ChallengeResponse(Challenge, PasswordHash, Response); +} + +#ifndef __FreeBSD__ +#define LENGTH 20 +static char * +SHA1_End(SHA_CTX *ctx, char *buf) +{ + int i; + unsigned char digest[LENGTH]; + static const char hex[]="0123456789abcdef"; + + if (!buf) + buf = malloc(2*LENGTH + 1); + if (!buf) + return 0; + SHA1_Final(digest, ctx); + for (i = 0; i < LENGTH; i++) { + buf[i+i] = hex[digest[i] >> 4]; + buf[i+i+1] = hex[digest[i] & 0x0f]; + } + buf[i+i] = '\0'; + return buf; +} +#endif + +void +GenerateAuthenticatorResponse(char *Password, int PasswordLen, + char *NTResponse, char *PeerChallenge, + char *AuthenticatorChallenge, char *UserName, + char *AuthenticatorResponse) +{ + SHA_CTX Context; + char PasswordHash[16]; + char PasswordHashHash[16]; + char Challenge[8]; + u_char Digest[SHA_DIGEST_LENGTH]; + int i; + + /* + * "Magic" constants used in response generation + */ + char Magic1[39] = + {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65, + 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67, + 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74}; + + + char Magic2[41] = + {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B, + 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F, + 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E, + 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F, + 0x6E}; + /* + * Hash the password with MD4 + */ + NtPasswordHash(Password, PasswordLen, PasswordHash); + /* + * Now hash the hash + */ + HashNtPasswordHash(PasswordHash, PasswordHashHash); + + SHA1_Init(&Context); + SHA1_Update(&Context, PasswordHashHash, 16); + SHA1_Update(&Context, NTResponse, 24); + SHA1_Update(&Context, Magic1, 39); + SHA1_Final(Digest, &Context); + ChallengeHash(PeerChallenge, AuthenticatorChallenge, UserName, Challenge); + SHA1_Init(&Context); + SHA1_Update(&Context, Digest, 20); + SHA1_Update(&Context, Challenge, 8); + SHA1_Update(&Context, Magic2, 41); + + /* + * Encode the value of 'Digest' as "S=" followed by + * 40 ASCII hexadecimal digits and return it in + * AuthenticatorResponse. + * For example, + * "S=0123456789ABCDEF0123456789ABCDEF01234567" + */ + AuthenticatorResponse[0] = 'S'; + AuthenticatorResponse[1] = '='; + SHA1_End(&Context, AuthenticatorResponse + 2); + for (i=2; i<42; i++) + AuthenticatorResponse[i] = toupper(AuthenticatorResponse[i]); + +} + +void +GetMasterKey(char *PasswordHashHash, char *NTResponse, char *MasterKey) +{ + char Digest[SHA_DIGEST_LENGTH]; + SHA_CTX Context; + static char Magic1[27] = + {0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d, + 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79}; + + SHA1_Init(&Context); + SHA1_Update(&Context, PasswordHashHash, 16); + SHA1_Update(&Context, NTResponse, 24); + SHA1_Update(&Context, Magic1, 27); + SHA1_Final(Digest, &Context); + memcpy(MasterKey, Digest, 16); +} + +void +GetAsymetricStartKey(char *MasterKey, char *SessionKey, int SessionKeyLength, + int IsSend, int IsServer) +{ + char Digest[SHA_DIGEST_LENGTH]; + SHA_CTX Context; + char *s; + + static char Magic2[84] = + {0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79, + 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65, + 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, + 0x6b, 0x65, 0x79, 0x2e}; + + static char Magic3[84] = + {0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, + 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, + 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, + 0x6b, 0x65, 0x79, 0x2e}; + + if (IsSend) { + if (IsServer) { + s = Magic3; + } else { + s = Magic2; + } + } else { + if (IsServer) { + s = Magic2; + } else { + s = Magic3; + } + } + + SHA1_Init(&Context); + SHA1_Update(&Context, MasterKey, 16); + SHA1_Update(&Context, SHA1_Pad1, 40); + SHA1_Update(&Context, s, 84); + SHA1_Update(&Context, SHA1_Pad2, 40); + SHA1_Final(Digest, &Context); + + memcpy(SessionKey, Digest, SessionKeyLength); +} + +void +GetNewKeyFromSHA(char *StartKey, char *SessionKey, long SessionKeyLength, + char *InterimKey) +{ + SHA_CTX Context; + char Digest[SHA_DIGEST_LENGTH]; + + SHA1_Init(&Context); + SHA1_Update(&Context, StartKey, SessionKeyLength); + SHA1_Update(&Context, SHA1_Pad1, 40); + SHA1_Update(&Context, SessionKey, SessionKeyLength); + SHA1_Update(&Context, SHA1_Pad2, 40); + SHA1_Final(Digest, &Context); + + memcpy(InterimKey, Digest, SessionKeyLength); +} + +#if 0 +static void +Get_Key(char *InitialSessionKey, char *CurrentSessionKey, + int LengthOfDesiredKey) +{ + SHA_CTX Context; + char Digest[SHA_DIGEST_LENGTH]; + + SHA1_Init(&Context); + SHA1_Update(&Context, InitialSessionKey, LengthOfDesiredKey); + SHA1_Update(&Context, SHA1_Pad1, 40); + SHA1_Update(&Context, CurrentSessionKey, LengthOfDesiredKey); + SHA1_Update(&Context, SHA1_Pad2, 40); + SHA1_Final(Digest, &Context); + + memcpy(CurrentSessionKey, Digest, LengthOfDesiredKey); +} +#endif + +/* passwordHash 16-bytes MD4 hashed password + challenge 8-bytes peer CHAP challenge + since passwordHash is in a 24-byte buffer, response is written in there */ +void +mschap_NT(char *passwordHash, char *challenge) +{ + u_char response[24]; + + ChallengeResponse(challenge, passwordHash, response); + memcpy(passwordHash, response, 24); + passwordHash[24] = 1; /* NT-style response */ +} + +void +mschap_LANMan(char *digest, char *challenge, char *secret) +{ + static u_char salt[] = "KGS!@#$%"; /* RASAPI32.dll */ + char SECRET[14], *ptr, *end; + u_char hash[16]; + + end = SECRET + sizeof SECRET; + for (ptr = SECRET; *secret && ptr < end; ptr++, secret++) + *ptr = toupper(*secret); + if (ptr < end) + memset(ptr, '\0', end - ptr); + + DesEncrypt(salt, SECRET, hash); + DesEncrypt(salt, SECRET + 7, hash + 8); + + ChallengeResponse(challenge, hash, digest); +} diff --git a/src/chap_ms.h b/src/chap_ms.h new file mode 100644 index 0000000..14793f0 --- /dev/null +++ b/src/chap_ms.h @@ -0,0 +1,52 @@ +/*- + * Copyright (c) 1997 Gabor Kincses + * 1997 - 2001 Brian Somers + * based on work by Eric Rosenquist + * Strata Software Limited. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/chap_ms.h,v 1.9.26.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +/* Max # of (Unicode) chars in an NT password */ +#define MAX_NT_PASSWORD 256 + +/* Don't rely on sizeof(MS_ChapResponse) in case of struct padding */ +#define MS_CHAP_RESPONSE_LEN 49 +#define CHAP81_RESPONSE_LEN 49 +#define CHAP81_NTRESPONSE_LEN 24 +#define CHAP81_NTRESPONSE_OFF 24 +#define CHAP81_HASH_LEN 16 +#define CHAP81_AUTHRESPONSE_LEN 42 +#define CHAP81_CHALLENGE_LEN 16 + +extern void mschap_NT(char *, char *); +extern void mschap_LANMan(char *, char *, char *); +extern void GenerateNTResponse(char *, char *, char *, char *, int , char *); +extern void HashNtPasswordHash(char *, char *); +extern void NtPasswordHash(char *, int, char *); +extern void GenerateAuthenticatorResponse(char *, int, char *, char *, char *, char *, char *); +extern void GetAsymetricStartKey(char *, char *, int, int, int); +extern void GetMasterKey(char *, char *, char *); +extern void GetNewKeyFromSHA(char *, char *, long, char *); diff --git a/src/chat.c b/src/chat.c new file mode 100644 index 0000000..bd635d5 --- /dev/null +++ b/src/chat.c @@ -0,0 +1,797 @@ +/*- + * Copyright (c) 1998 Brian Somers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/chat.c,v 1.80.26.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "layer.h" +#include "mbuf.h" +#include "log.h" +#include "defs.h" +#include "timer.h" +#include "lqr.h" +#include "hdlc.h" +#include "throughput.h" +#include "fsm.h" +#include "lcp.h" +#include "ccp.h" +#include "link.h" +#include "async.h" +#include "descriptor.h" +#include "physical.h" +#include "chat.h" +#include "mp.h" +#include "auth.h" +#include "chap.h" +#include "slcompress.h" +#include "iplist.h" +#include "ncpaddr.h" +#include "ipcp.h" +#include "filter.h" +#include "cbcp.h" +#include "command.h" +#include "datalink.h" +#ifndef NORADIUS +#include "radius.h" +#endif +#include "ipv6cp.h" +#include "ncp.h" +#include "bundle.h" +#include "id.h" + +#define BUFLEFT(c) (sizeof (c)->buf - ((c)->bufend - (c)->buf)) + +static void ExecStr(struct physical *, char *, char *, int); +static char *ExpandString(struct chat *, const char *, char *, int, int); + +static void +chat_PauseTimer(void *v) +{ + struct chat *c = (struct chat *)v; + timer_Stop(&c->pause); + c->pause.load = 0; +} + +static void +chat_Pause(struct chat *c, u_long load) +{ + timer_Stop(&c->pause); + c->pause.load += load; + c->pause.func = chat_PauseTimer; + c->pause.name = "chat pause"; + c->pause.arg = c; + timer_Start(&c->pause); +} + +static void +chat_TimeoutTimer(void *v) +{ + struct chat *c = (struct chat *)v; + timer_Stop(&c->timeout); + c->TimedOut = 1; +} + +static void +chat_SetTimeout(struct chat *c) +{ + timer_Stop(&c->timeout); + if (c->TimeoutSec > 0) { + c->timeout.load = SECTICKS * c->TimeoutSec; + c->timeout.func = chat_TimeoutTimer; + c->timeout.name = "chat timeout"; + c->timeout.arg = c; + timer_Start(&c->timeout); + } +} + +static char * +chat_NextChar(char *ptr, char ch) +{ + for (; *ptr; ptr++) + if (*ptr == ch) + return ptr; + else if (*ptr == '\\') + if (*++ptr == '\0') + return NULL; + + return NULL; +} + +static int +chat_UpdateSet(struct fdescriptor *d, fd_set *r, fd_set *w, fd_set *e, int *n) +{ + struct chat *c = descriptor2chat(d); + int special, gotabort, gottimeout, needcr; + int TimedOut = c->TimedOut; + static char arg_term; /* An empty string */ + + if (c->pause.state == TIMER_RUNNING) + return 0; + + if (TimedOut) { + log_Printf(LogCHAT, "Expect timeout\n"); + if (c->nargptr == NULL) + c->state = CHAT_FAILED; + else { + /* c->state = CHAT_EXPECT; */ + c->argptr = &arg_term; + } + c->TimedOut = 0; + } + + if (c->state != CHAT_EXPECT && c->state != CHAT_SEND) + return 0; + + gottimeout = gotabort = 0; + + if (c->arg < c->argc && (c->arg < 0 || *c->argptr == '\0')) { + /* Go get the next string */ + if (c->arg < 0 || c->state == CHAT_SEND) + c->state = CHAT_EXPECT; + else + c->state = CHAT_SEND; + + special = 1; + while (special && (c->nargptr || c->arg < c->argc - 1)) { + if (c->arg < 0 || (!TimedOut && c->state == CHAT_SEND)) + c->nargptr = NULL; + + if (c->nargptr != NULL) { + /* We're doing expect-send-expect.... */ + c->argptr = c->nargptr; + /* Put the '-' back in case we ever want to rerun our script */ + c->nargptr[-1] = '-'; + c->nargptr = chat_NextChar(c->nargptr, '-'); + if (c->nargptr != NULL) + *c->nargptr++ = '\0'; + } else { + int minus; + + if ((c->argptr = c->argv[++c->arg]) == NULL) { + /* End of script - all ok */ + c->state = CHAT_DONE; + return 0; + } + + if (c->state == CHAT_EXPECT) { + /* Look for expect-send-expect sequence */ + c->nargptr = c->argptr; + minus = 0; + while ((c->nargptr = chat_NextChar(c->nargptr, '-'))) { + c->nargptr++; + minus++; + } + + if (minus % 2) + log_Printf(LogWARN, "chat_UpdateSet: \"%s\": Uneven number of" + " '-' chars, all ignored\n", c->argptr); + else if (minus) { + c->nargptr = chat_NextChar(c->argptr, '-'); + *c->nargptr++ = '\0'; + } + } + } + + /* + * c->argptr now temporarily points into c->script (via c->argv) + * If it's an expect-send-expect sequence, we've just got the correct + * portion of that sequence. + */ + + needcr = c->state == CHAT_SEND && + (*c->argptr != '!' || c->argptr[1] == '!'); + + /* We leave room for a potential HDLC header in the target string */ + ExpandString(c, c->argptr, c->exp + 2, sizeof c->exp - 2, needcr); + + /* + * Now read our string. If it's not a special string, we unset + * ``special'' to break out of the loop. + */ + if (gotabort) { + if (c->abort.num < MAXABORTS) { + int len, i; + + len = strlen(c->exp+2); + for (i = 0; i < c->abort.num; i++) + if (len > c->abort.string[i].len) { + int last; + + for (last = c->abort.num; last > i; last--) { + c->abort.string[last].data = c->abort.string[last-1].data; + c->abort.string[last].len = c->abort.string[last-1].len; + } + break; + } + c->abort.string[i].len = len; + if ((c->abort.string[i].data = (char *)malloc(len+1)) != NULL) { + memcpy(c->abort.string[i].data, c->exp+2, len+1); + c->abort.num++; + } + } else + log_Printf(LogERROR, "chat_UpdateSet: too many abort strings\n"); + gotabort = 0; + } else if (gottimeout) { + c->TimeoutSec = atoi(c->exp + 2); + if (c->TimeoutSec <= 0) + c->TimeoutSec = 30; + gottimeout = 0; + } else if (c->nargptr == NULL && !strcmp(c->exp+2, "ABORT")) + gotabort = 1; + else if (c->nargptr == NULL && !strcmp(c->exp+2, "TIMEOUT")) + gottimeout = 1; + else { + if (c->exp[2] == '!' && c->exp[3] != '!') + ExecStr(c->physical, c->exp + 3, c->exp + 3, sizeof c->exp - 3); + + if (c->exp[2] == '\0') { + /* Empty string, reparse (this may be better as a `goto start') */ + c->argptr = &arg_term; + return chat_UpdateSet(d, r, w, e, n); + } + + special = 0; + } + } + + if (special) { + if (gottimeout) + log_Printf(LogWARN, "chat_UpdateSet: TIMEOUT: Argument expected\n"); + else if (gotabort) + log_Printf(LogWARN, "chat_UpdateSet: ABORT: Argument expected\n"); + + /* End of script - all ok */ + c->state = CHAT_DONE; + return 0; + } + + /* set c->argptr to point in the right place */ + c->argptr = c->exp + (c->exp[2] == '!' ? 3 : 2); + c->arglen = strlen(c->argptr); + + if (c->state == CHAT_EXPECT) { + /* We must check to see if the string's already been found ! */ + char *begin, *end; + + end = c->bufend - c->arglen + 1; + if (end < c->bufstart) + end = c->bufstart; + for (begin = c->bufstart; begin < end; begin++) + if (!strncmp(begin, c->argptr, c->arglen)) { + c->bufstart = begin + c->arglen; + c->argptr += c->arglen; + c->arglen = 0; + /* Continue - we've already read our expect string */ + return chat_UpdateSet(d, r, w, e, n); + } + + log_Printf(LogCHAT, "Expect(%d): %s\n", c->TimeoutSec, c->argptr); + chat_SetTimeout(c); + } + } + + /* + * We now have c->argptr pointing at what we want to expect/send and + * c->state saying what we want to do... we now know what to put in + * the fd_set :-) + */ + + if (c->state == CHAT_EXPECT) + return physical_doUpdateSet(&c->physical->desc, r, NULL, e, n, 1); + else + return physical_doUpdateSet(&c->physical->desc, NULL, w, e, n, 1); +} + +static int +chat_IsSet(struct fdescriptor *d, const fd_set *fdset) +{ + struct chat *c = descriptor2chat(d); + return c->argptr && physical_IsSet(&c->physical->desc, fdset); +} + +static void +chat_UpdateLog(struct chat *c, int in) +{ + if (log_IsKept(LogCHAT) || log_IsKept(LogCONNECT)) { + /* + * If a linefeed appears in the last `in' characters of `c's input + * buffer, output from there, all the way back to the last linefeed. + * This is called for every read of `in' bytes. + */ + char *ptr, *end, *stop, ch; + int level; + + level = log_IsKept(LogCHAT) ? LogCHAT : LogCONNECT; + if (in == -1) + end = ptr = c->bufend; + else { + ptr = c->bufend - in; + for (end = c->bufend - 1; end >= ptr; end--) + if (*end == '\n') + break; + } + + if (end >= ptr) { + for (ptr = c->bufend - (in == -1 ? 1 : in + 1); ptr >= c->bufstart; ptr--) + if (*ptr == '\n') + break; + ptr++; + stop = NULL; + while (stop < end) { + if ((stop = memchr(ptr, '\n', end - ptr)) == NULL) + stop = end; + ch = *stop; + *stop = '\0'; + if (level == LogCHAT || strstr(ptr, "CONNECT")) + log_Printf(level, "Received: %s\n", ptr); + *stop = ch; + ptr = stop + 1; + } + } + } +} + +static void +chat_Read(struct fdescriptor *d, struct bundle *bundle __unused, + const fd_set *fdset __unused) +{ + struct chat *c = descriptor2chat(d); + + if (c->state == CHAT_EXPECT) { + ssize_t in; + char *abegin, *ebegin, *begin, *aend, *eend, *end; + int n; + + /* + * XXX - should this read only 1 byte to guarantee that we don't + * swallow any ppp talk from the peer ? + */ + in = BUFLEFT(c); + if (in > (ssize_t)sizeof c->buf / 2) + in = sizeof c->buf / 2; + + in = physical_Read(c->physical, c->bufend, in); + if (in <= 0) + return; + + /* `begin' and `end' delimit where we're going to strncmp() from */ + ebegin = c->bufend - c->arglen + 1; + eend = ebegin + in; + if (ebegin < c->bufstart) + ebegin = c->bufstart; + + if (c->abort.num) { + abegin = c->bufend - c->abort.string[0].len + 1; + aend = c->bufend - c->abort.string[c->abort.num-1].len + in + 1; + if (abegin < c->bufstart) + abegin = c->bufstart; + } else { + abegin = ebegin; + aend = eend; + } + begin = abegin < ebegin ? abegin : ebegin; + end = aend < eend ? eend : aend; + + c->bufend += in; + + chat_UpdateLog(c, in); + + if (c->bufend > c->buf + sizeof c->buf / 2) { + /* Shuffle our receive buffer back a bit */ + int chop; + + for (chop = begin - c->buf; chop; chop--) + if (c->buf[chop] == '\n') + /* found some already-logged garbage to remove :-) */ + break; + + if (!chop) + chop = begin - c->buf; + + if (chop) { + char *from, *to; + + to = c->buf; + from = to + chop; + while (from < c->bufend) + *to++ = *from++; + c->bufstart -= chop; + c->bufend -= chop; + begin -= chop; + end -= chop; + abegin -= chop; + aend -= chop; + ebegin -= chop; + eend -= chop; + } + } + + for (; begin < end; begin++) + if (begin >= ebegin && begin < eend && + !strncmp(begin, c->argptr, c->arglen)) { + /* Got it ! */ + timer_Stop(&c->timeout); + if (memchr(begin + c->arglen - 1, '\n', + c->bufend - begin - c->arglen + 1) == NULL) { + /* force it into the log */ + end = c->bufend; + c->bufend = begin + c->arglen; + chat_UpdateLog(c, -1); + c->bufend = end; + } + c->bufstart = begin + c->arglen; + c->argptr += c->arglen; + c->arglen = 0; + break; + } else if (begin >= abegin && begin < aend) { + for (n = c->abort.num - 1; n >= 0; n--) { + if (begin + c->abort.string[n].len > c->bufend) + break; + if (!strncmp(begin, c->abort.string[n].data, + c->abort.string[n].len)) { + if (memchr(begin + c->abort.string[n].len - 1, '\n', + c->bufend - begin - c->abort.string[n].len + 1) == NULL) { + /* force it into the log */ + end = c->bufend; + c->bufend = begin + c->abort.string[n].len; + chat_UpdateLog(c, -1); + c->bufend = end; + } + c->bufstart = begin + c->abort.string[n].len; + c->state = CHAT_FAILED; + return; + } + } + } + } +} + +static int +chat_Write(struct fdescriptor *d, struct bundle *bundle __unused, + const fd_set *fdset __unused) +{ + struct chat *c = descriptor2chat(d); + int result = 0; + + if (c->state == CHAT_SEND) { + int wrote; + + if (strstr(c->argv[c->arg], "\\P")) /* Don't log the password */ + log_Printf(LogCHAT, "Send: %s\n", c->argv[c->arg]); + else { + int sz; + + sz = c->arglen - 1; + while (sz >= 0 && c->argptr[sz] == '\n') + sz--; + log_Printf(LogCHAT, "Send: %.*s\n", sz + 1, c->argptr); + } + + if (physical_IsSync(c->physical)) { + /* + * XXX: Fix me + * This data should be stuffed down through the link layers + */ + /* There's always room for the HDLC header */ + c->argptr -= 2; + c->arglen += 2; + memcpy(c->argptr, "\377\003", 2); /* Prepend HDLC header */ + } + + wrote = physical_Write(c->physical, c->argptr, c->arglen); + result = wrote > 0 ? 1 : 0; + if (wrote == -1) { + if (errno != EINTR) { + log_Printf(LogWARN, "chat_Write: %s\n", strerror(errno)); + result = -1; + } + if (physical_IsSync(c->physical)) { + c->argptr += 2; + c->arglen -= 2; + } + } else if (wrote < 2 && physical_IsSync(c->physical)) { + /* Oops - didn't even write our HDLC header ! */ + c->argptr += 2; + c->arglen -= 2; + } else { + c->argptr += wrote; + c->arglen -= wrote; + } + } + + return result; +} + +void +chat_Init(struct chat *c, struct physical *p) +{ + c->desc.type = CHAT_DESCRIPTOR; + c->desc.UpdateSet = chat_UpdateSet; + c->desc.IsSet = chat_IsSet; + c->desc.Read = chat_Read; + c->desc.Write = chat_Write; + c->physical = p; + *c->script = '\0'; + c->argc = 0; + c->arg = -1; + c->argptr = NULL; + c->nargptr = NULL; + c->bufstart = c->bufend = c->buf; + + memset(&c->pause, '\0', sizeof c->pause); + memset(&c->timeout, '\0', sizeof c->timeout); +} + +int +chat_Setup(struct chat *c, const char *data, const char *phone) +{ + c->state = CHAT_EXPECT; + + if (data == NULL) { + *c->script = '\0'; + c->argc = 0; + } else { + strncpy(c->script, data, sizeof c->script - 1); + c->script[sizeof c->script - 1] = '\0'; + c->argc = MakeArgs(c->script, c->argv, VECSIZE(c->argv), PARSE_NOHASH); + } + + c->arg = -1; + c->argptr = NULL; + c->nargptr = NULL; + + c->TimeoutSec = 30; + c->TimedOut = 0; + c->phone = phone; + c->abort.num = 0; + + timer_Stop(&c->pause); + timer_Stop(&c->timeout); + + return c->argc >= 0; +} + +void +chat_Finish(struct chat *c) +{ + timer_Stop(&c->pause); + timer_Stop(&c->timeout); + while (c->abort.num) + free(c->abort.string[--c->abort.num].data); + c->abort.num = 0; +} + +void +chat_Destroy(struct chat *c) +{ + chat_Finish(c); +} + +/* + * \c don't add a cr + * \d Sleep a little (delay 2 seconds + * \n Line feed character + * \P Auth Key password + * \p pause 0.25 sec + * \r Carrige return character + * \s Space character + * \T Telephone number(s) (defined via `set phone') + * \t Tab character + * \U Auth User + */ +static char * +ExpandString(struct chat *c, const char *str, char *result, int reslen, int cr) +{ + int len; + + result[--reslen] = '\0'; + while (*str && reslen > 0) { + switch (*str) { + case '\\': + str++; + switch (*str) { + case 'c': + cr = 0; + break; + case 'd': /* Delay 2 seconds */ + chat_Pause(c, 2 * SECTICKS); + break; + case 'p': + chat_Pause(c, SECTICKS / 4); + break; /* Delay 0.25 seconds */ + case 'n': + *result++ = '\n'; + reslen--; + break; + case 'r': + *result++ = '\r'; + reslen--; + break; + case 's': + *result++ = ' '; + reslen--; + break; + case 't': + *result++ = '\t'; + reslen--; + break; + case 'P': + strncpy(result, c->physical->dl->bundle->cfg.auth.key, reslen); + len = strlen(result); + reslen -= len; + result += len; + break; + case 'T': + if (c->phone) { + strncpy(result, c->phone, reslen); + len = strlen(result); + reslen -= len; + result += len; + } + break; + case 'U': + strncpy(result, c->physical->dl->bundle->cfg.auth.name, reslen); + len = strlen(result); + reslen -= len; + result += len; + break; + default: + reslen--; + *result++ = *str; + break; + } + if (*str) + str++; + break; + case '^': + str++; + if (*str) { + *result++ = *str++ & 0x1f; + reslen--; + } + break; + default: + *result++ = *str++; + reslen--; + break; + } + } + if (--reslen > 0) { + if (cr) + *result++ = '\r'; + } + if (--reslen > 0) + *result++ = '\0'; + return (result); +} + +static void +ExecStr(struct physical *physical, char *command, char *out, int olen) +{ + pid_t pid; + int fids[2]; + char *argv[MAXARGS], *vector[MAXARGS], *startout, *endout; + int stat, nb, argc, i; + + log_Printf(LogCHAT, "Exec: %s\n", command); + if ((argc = MakeArgs(command, vector, VECSIZE(vector), + PARSE_REDUCE|PARSE_NOHASH)) <= 0) { + if (argc < 0) + log_Printf(LogWARN, "Syntax error in exec command\n"); + *out = '\0'; + return; + } + + if (pipe(fids) < 0) { + log_Printf(LogCHAT, "Unable to create pipe in ExecStr: %s\n", + strerror(errno)); + *out = '\0'; + return; + } + if ((pid = fork()) == 0) { + command_Expand(argv, argc, (char const *const *)vector, + physical->dl->bundle, 0, getpid()); + close(fids[0]); + timer_TermService(); + if (fids[1] == STDIN_FILENO) + fids[1] = dup(fids[1]); + dup2(physical->fd, STDIN_FILENO); + dup2(fids[1], STDERR_FILENO); + dup2(STDIN_FILENO, STDOUT_FILENO); + close(3); + if (open(_PATH_TTY, O_RDWR) != 3) + open(_PATH_DEVNULL, O_RDWR); /* Leave it closed if it fails... */ + for (i = getdtablesize(); i > 3; i--) + fcntl(i, F_SETFD, 1); +#ifndef NOSUID + setuid(ID0realuid()); +#endif + execvp(argv[0], argv); + fprintf(stderr, "execvp: %s: %s\n", argv[0], strerror(errno)); + _exit(127); + } else { + char *name = strdup(vector[0]); + + close(fids[1]); + endout = out + olen - 1; + startout = out; + while (out < endout) { + nb = read(fids[0], out, 1); + if (nb <= 0) + break; + out++; + } + *out = '\0'; + close(fids[0]); + close(fids[1]); + waitpid(pid, &stat, WNOHANG); + if (WIFSIGNALED(stat)) { + log_Printf(LogWARN, "%s: signal %d\n", name, WTERMSIG(stat)); + free(name); + *out = '\0'; + return; + } else if (WIFEXITED(stat)) { + switch (WEXITSTATUS(stat)) { + case 0: + free(name); + break; + case 127: + log_Printf(LogWARN, "%s: %s\n", name, startout); + free(name); + *out = '\0'; + return; + break; + default: + log_Printf(LogWARN, "%s: exit %d\n", name, WEXITSTATUS(stat)); + free(name); + *out = '\0'; + return; + break; + } + } else { + log_Printf(LogWARN, "%s: Unexpected exit result\n", name); + free(name); + *out = '\0'; + return; + } + } +} diff --git a/src/chat.h b/src/chat.h new file mode 100644 index 0000000..1afbabe --- /dev/null +++ b/src/chat.h @@ -0,0 +1,82 @@ +/*- + * Copyright (c) 1998 Brian Somers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/chat.h,v 1.16.42.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#define CHAT_EXPECT 0 +#define CHAT_SEND 1 +#define CHAT_DONE 2 +#define CHAT_FAILED 3 + +#define MAXABORTS 50 + +struct physical; + +struct chat { + struct fdescriptor desc; + struct physical *physical; + + int state; /* Our CHAT_* status */ + + char script[LINE_LEN]; /* Our arg buffer */ + char *argv[MAXARGS]; /* Our arguments (pointing to script) */ + int argc; /* Number of argv's */ + + int arg; /* Our current arg number */ + char exp[LINE_LEN]; /* Our translated current argument */ + char *argptr; /* Our current arg pointer */ + int arglen; /* The length of argptr */ + char *nargptr; /* Our next for expect-send-expect */ + + char buf[LINE_LEN*2]; /* Our input */ + char *bufstart; /* start of relevent data */ + char *bufend; /* end of relevent data */ + + int TimeoutSec; /* Expect timeout value */ + int TimedOut; /* We timed out */ + + const char *phone; /* Our phone number */ + + struct { + struct { + char *data; /* Abort the dial if we get one */ + int len; + } string[MAXABORTS]; + int num; /* How many AbortStrings */ + } abort; + + struct pppTimer pause; /* Inactivity timer */ + struct pppTimer timeout; /* TimeoutSec timer */ +}; + +#define descriptor2chat(d) \ + ((d)->type == CHAT_DESCRIPTOR ? (struct chat *)(d) : NULL) +#define VECSIZE(v) (sizeof(v) / sizeof(v[0])) + +extern void chat_Init(struct chat *, struct physical *); +extern int chat_Setup(struct chat *, const char *, const char *); +extern void chat_Finish(struct chat *); +extern void chat_Destroy(struct chat *); diff --git a/src/command.c b/src/command.c new file mode 100644 index 0000000..0d280da --- /dev/null +++ b/src/command.c @@ -0,0 +1,3310 @@ +/*- + * Copyright (c) 1996 - 2001 Brian Somers + * based on work by Toshiharu OHNO + * Internet Initiative Japan, Inc (IIJ) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/usr.sbin/ppp/command.c,v 1.307.12.1 2010/12/21 17:10:29 kensmith Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NONAT +#ifdef LOCALNAT +#include "alias.h" +#else +#include +#endif +#endif + +#include "layer.h" +#include "defs.h" +#include "command.h" +#include "mbuf.h" +#include "log.h" +#include "timer.h" +#include "fsm.h" +#include "iplist.h" +#include "throughput.h" +#include "slcompress.h" +#include "lqr.h" +#include "hdlc.h" +#include "lcp.h" +#include "ncpaddr.h" +#include "ipcp.h" +#ifndef NONAT +#include "nat_cmd.h" +#endif +#include "systems.h" +#include "filter.h" +#include "descriptor.h" +#include "main.h" +#include "route.h" +#include "ccp.h" +#include "auth.h" +#include "async.h" +#include "link.h" +#include "physical.h" +#include "mp.h" +#ifndef NORADIUS +#include "radius.h" +#endif +#include "ipv6cp.h" +#include "ncp.h" +#include "bundle.h" +#include "server.h" +#include "prompt.h" +#include "chat.h" +#include "chap.h" +#include "cbcp.h" +#include "datalink.h" +#include "iface.h" +#include "id.h" +#include "probe.h" + +/* ``set'' values */ +#define VAR_AUTHKEY 0 +#define VAR_DIAL 1 +#define VAR_LOGIN 2 +#define VAR_AUTHNAME 3 +#define VAR_AUTOLOAD 4 +#define VAR_WINSIZE 5 +#define VAR_DEVICE 6 +#define VAR_ACCMAP 7 +#define VAR_MRRU 8 +#define VAR_MRU 9 +#define VAR_MTU 10 +#define VAR_OPENMODE 11 +#define VAR_PHONE 12 +#define VAR_HANGUP 13 +#define VAR_IDLETIMEOUT 14 +#define VAR_LQRPERIOD 15 +#define VAR_LCPRETRY 16 +#define VAR_CHAPRETRY 17 +#define VAR_PAPRETRY 18 +#define VAR_CCPRETRY 19 +#define VAR_IPCPRETRY 20 +#define VAR_DNS 21 +#define VAR_NBNS 22 +#define VAR_MODE 23 +#define VAR_CALLBACK 24 +#define VAR_CBCP 25 +#define VAR_CHOKED 26 +#define VAR_SENDPIPE 27 +#define VAR_RECVPIPE 28 +#define VAR_RADIUS 29 +#define VAR_CD 30 +#define VAR_PARITY 31 +#define VAR_CRTSCTS 32 +#define VAR_URGENTPORTS 33 +#define VAR_LOGOUT 34 +#define VAR_IFQUEUE 35 +#define VAR_MPPE 36 +#define VAR_IPV6CPRETRY 37 +#define VAR_RAD_ALIVE 38 +#define VAR_PPPOE 39 +#define VAR_PORT_ID 40 + +/* ``accept|deny|disable|enable'' masks */ +#define NEG_HISMASK (1) +#define NEG_MYMASK (2) + +/* ``accept|deny|disable|enable'' values */ +#define NEG_ACFCOMP 40 +#define NEG_CHAP05 41 +#define NEG_CHAP80 42 +#define NEG_CHAP80LM 43 +#define NEG_DEFLATE 44 +#define NEG_DNS 45 +#define NEG_ECHO 46 +#define NEG_ENDDISC 47 +#define NEG_LQR 48 +#define NEG_PAP 49 +#define NEG_PPPDDEFLATE 50 +#define NEG_PRED1 51 +#define NEG_PROTOCOMP 52 +#define NEG_SHORTSEQ 53 +#define NEG_VJCOMP 54 +#define NEG_MPPE 55 +#define NEG_CHAP81 56 + +const char Version[] = "3.4.2"; + +static int ShowCommand(struct cmdargs const *); +static int TerminalCommand(struct cmdargs const *); +static int QuitCommand(struct cmdargs const *); +static int OpenCommand(struct cmdargs const *); +static int CloseCommand(struct cmdargs const *); +static int DownCommand(struct cmdargs const *); +static int SetCommand(struct cmdargs const *); +static int LinkCommand(struct cmdargs const *); +static int AddCommand(struct cmdargs const *); +static int DeleteCommand(struct cmdargs const *); +static int NegotiateCommand(struct cmdargs const *); +static int ClearCommand(struct cmdargs const *); +static int RunListCommand(struct cmdargs const *); +static int IfaceAddCommand(struct cmdargs const *); +static int IfaceDeleteCommand(struct cmdargs const *); +static int IfaceClearCommand(struct cmdargs const *); +static int SetProcTitle(struct cmdargs const *); +#ifndef NONAT +static int NatEnable(struct cmdargs const *); +static int NatOption(struct cmdargs const *); +#endif + +static const char * +showcx(struct cmdtab const *cmd) +{ + if (cmd->lauth & LOCAL_CX) + return "(c)"; + else if (cmd->lauth & LOCAL_CX_OPT) + return "(o)"; + + return ""; +} + +static int +HelpCommand(struct cmdargs const *arg) +{ + struct cmdtab const *cmd; + int n, cmax, dmax, cols, cxlen; + const char *cx; + + if (!arg->prompt) { + log_Printf(LogWARN, "help: Cannot help without a prompt\n"); + return 0; + } + + if (arg->argc > arg->argn) { + for (cmd = arg->cmdtab; cmd->name || cmd->alias; cmd++) + if ((cmd->lauth & arg->prompt->auth) && + ((cmd->name && !strcasecmp(cmd->name, arg->argv[arg->argn])) || + (cmd->alias && !strcasecmp(cmd->alias, arg->argv[arg->argn])))) { + prompt_Printf(arg->prompt, "%s %s\n", cmd->syntax, showcx(cmd)); + return 0; + } + return -1; + } + + cmax = dmax = 0; + for (cmd = arg->cmdtab; cmd->func; cmd++) + if (cmd->name && (cmd->lauth & arg->prompt->auth)) { + if ((n = strlen(cmd->name) + strlen(showcx(cmd))) > cmax) + cmax = n; + if ((n = strlen(cmd->helpmes)) > dmax) + dmax = n; + } + + cols = 80 / (dmax + cmax + 3); + n = 0; + prompt_Printf(arg->prompt, "(o) = Optional context," + " (c) = Context required\n"); + for (cmd = arg->cmdtab; cmd->func; cmd++) + if (cmd->name && (cmd->lauth & arg->prompt->auth)) { + cx = showcx(cmd); + cxlen = cmax - strlen(cmd->name); + if (n % cols != 0) + prompt_Printf(arg->prompt, " "); + prompt_Printf(arg->prompt, "%s%-*.*s: %-*.*s", + cmd->name, cxlen, cxlen, cx, dmax, dmax, cmd->helpmes); + if (++n % cols == 0) + prompt_Printf(arg->prompt, "\n"); + } + if (n % cols != 0) + prompt_Printf(arg->prompt, "\n"); + + return 0; +} + +static int +IdentCommand(struct cmdargs const *arg) +{ + Concatinate(arg->cx->physical->link.lcp.cfg.ident, + sizeof arg->cx->physical->link.lcp.cfg.ident, + arg->argc - arg->argn, arg->argv + arg->argn); + return 0; +} + +static int +SendIdentification(struct cmdargs const *arg) +{ + if (arg->cx->state < DATALINK_LCP) { + log_Printf(LogWARN, "sendident: link has not reached LCP\n"); + return 2; + } + return lcp_SendIdentification(&arg->cx->physical->link.lcp) ? 0 : 1; +} + +static int +CloneCommand(struct cmdargs const *arg) +{ + char namelist[LINE_LEN]; + char *name; + int f; + + if (arg->argc == arg->argn) + return -1; + + namelist[sizeof namelist - 1] = '\0'; + for (f = arg->argn; f < arg->argc; f++) { + strncpy(namelist, arg->argv[f], sizeof namelist - 1); + for(name = strtok(namelist, ", "); name; name = strtok(NULL,", ")) + bundle_DatalinkClone(arg->bundle, arg->cx, name); + } + + return 0; +} + +static int +RemoveCommand(struct cmdargs const *arg) +{ + if (arg->argc != arg->argn) + return -1; + + if (arg->cx->state != DATALINK_CLOSED) { + log_Printf(LogWARN, "remove: Cannot delete links that aren't closed\n"); + return 2; + } + + bundle_DatalinkRemove(arg->bundle, arg->cx); + return 0; +} + +static int +RenameCommand(struct cmdargs const *arg) +{ + if (arg->argc != arg->argn + 1) + return -1; + + if (bundle_RenameDatalink(arg->bundle, arg->cx, arg->argv[arg->argn])) + return 0; + + log_Printf(LogWARN, "%s -> %s: target name already exists\n", + arg->cx->name, arg->argv[arg->argn]); + return 1; +} + +static int +LoadCommand(struct cmdargs const *arg) +{ + const char *err; + int n, mode; + + mode = arg->bundle->phys_type.all; + + if (arg->argn < arg->argc) { + for (n = arg->argn; n < arg->argc; n++) + if ((err = system_IsValid(arg->argv[n], arg->prompt, mode)) != NULL) { + log_Printf(LogWARN, "%s: %s\n", arg->argv[n], err); + return 1; + } + + for (n = arg->argn; n < arg->argc; n++) { + bundle_SetLabel(arg->bundle, arg->argv[arg->argc - 1]); + system_Select(arg->bundle, arg->argv[n], CONFFILE, arg->prompt, arg->cx); + } + bundle_SetLabel(arg->bundle, arg->argv[arg->argc - 1]); + } else if ((err = system_IsValid("default", arg->prompt, mode)) != NULL) { + log_Printf(LogWARN, "default: %s\n", err); + return 1; + } else { + bundle_SetLabel(arg->bundle, "default"); + system_Select(arg->bundle, "default", CONFFILE, arg->prompt, arg->cx); + bundle_SetLabel(arg->bundle, "default"); + } + + return 0; +} + +static int +LogCommand(struct cmdargs const *arg) +{ + char buf[LINE_LEN]; + + if (arg->argn < arg->argc) { + char *argv[MAXARGS]; + int argc = arg->argc - arg->argn; + + if (argc >= (int)(sizeof argv / sizeof argv[0])) { + argc = sizeof argv / sizeof argv[0] - 1; + log_Printf(LogWARN, "Truncating log command to %d args\n", argc); + } + command_Expand(argv, argc, arg->argv + arg->argn, arg->bundle, 1, getpid()); + Concatinate(buf, sizeof buf, argc, (const char *const *)argv); + log_Printf(LogLOG, "%s\n", buf); + command_Free(argc, argv); + return 0; + } + + return -1; +} + +static int +SaveCommand(struct cmdargs const *arg __unused) +{ + log_Printf(LogWARN, "save command is not yet implemented.\n"); + return 1; +} + +static int +DialCommand(struct cmdargs const *arg) +{ + int res; + + if ((arg->cx && !(arg->cx->physical->type & (PHYS_INTERACTIVE|PHYS_AUTO))) + || (!arg->cx && + (arg->bundle->phys_type.all & ~(PHYS_INTERACTIVE|PHYS_AUTO)))) { + log_Printf(LogWARN, "Manual dial is only available for auto and" + " interactive links\n"); + return 1; + } + + if (arg->argc > arg->argn && (res = LoadCommand(arg)) != 0) + return res; + + bundle_Open(arg->bundle, arg->cx ? arg->cx->name : NULL, PHYS_ALL, 1); + + return 0; +} + +#define isinword(ch) (isalnum(ch) || (ch) == '_') + +static char * +strstrword(char *big, const char *little) +{ + /* Get the first occurance of the word ``little'' in ``big'' */ + char *pos; + int len; + + pos = big; + len = strlen(little); + + while ((pos = strstr(pos, little)) != NULL) + if ((pos != big && isinword(pos[-1])) || isinword(pos[len])) + pos++; + else if (pos != big && pos[-1] == '\\') + memmove(pos - 1, pos, strlen(pos) + 1); + else + break; + + return pos; +} + +static char * +subst(char *tgt, const char *oldstr, const char *newstr) +{ + /* tgt is a malloc()d area... realloc() as necessary */ + char *word, *ntgt; + int ltgt, loldstr, lnewstr, pos; + + if ((word = strstrword(tgt, oldstr)) == NULL) + return tgt; + + ltgt = strlen(tgt) + 1; + loldstr = strlen(oldstr); + lnewstr = strlen(newstr); + do { + pos = word - tgt; + if (loldstr > lnewstr) + bcopy(word + loldstr, word + lnewstr, ltgt - pos - loldstr); + if (loldstr != lnewstr) { + ntgt = realloc(tgt, ltgt += lnewstr - loldstr); + if (ntgt == NULL) + break; /* Oh wonderful ! */ + word = ntgt + pos; + tgt = ntgt; + } + if (lnewstr > loldstr) + bcopy(word + loldstr, word + lnewstr, ltgt - pos - lnewstr); + bcopy(newstr, word, lnewstr); + } while ((word = strstrword(word, oldstr))); + + return tgt; +} + +static char * +substip(char *tgt, const char *oldstr, struct in_addr ip) +{ + return subst(tgt, oldstr, inet_ntoa(ip)); +} + +static char * +substlong(char *tgt, const char *oldstr, long l) +{ + char buf[23]; + + snprintf(buf, sizeof buf, "%ld", l); + + return subst(tgt, oldstr, buf); +} + +static char * +substull(char *tgt, const char *oldstr, unsigned long long ull) +{ + char buf[21]; + + snprintf(buf, sizeof buf, "%llu", ull); + + return subst(tgt, oldstr, buf); +} + + +#ifndef NOINET6 +static char * +substipv6(char *tgt, const char *oldstr, const struct ncpaddr *ip) +{ + return subst(tgt, oldstr, ncpaddr_ntoa(ip)); +} + +#ifndef NORADIUS +static char * +substipv6prefix(char *tgt, const char *oldstr, const uint8_t *ipv6prefix) +{ + uint8_t ipv6addr[INET6_ADDRSTRLEN]; + uint8_t prefix[INET6_ADDRSTRLEN + sizeof("/128") - 1]; + + if (ipv6prefix) { + inet_ntop(AF_INET6, &ipv6prefix[2], ipv6addr, sizeof(ipv6addr)); + snprintf(prefix, sizeof(prefix), "%s/%d", ipv6addr, ipv6prefix[1]); + } else + prefix[0] = '\0'; + return subst(tgt, oldstr, prefix); +} +#endif +#endif + +void +command_Expand(char **nargv, int argc, char const *const *oargv, + struct bundle *bundle, int inc0, pid_t pid) +{ + int arg, secs; + char uptime[20]; + unsigned long long oin, oout, pin, pout; + + if (inc0) + arg = 0; /* Start at arg 0 */ + else { + nargv[0] = strdup(oargv[0]); + arg = 1; + } + + secs = bundle_Uptime(bundle); + snprintf(uptime, sizeof uptime, "%d:%02d:%02d", + secs / 3600, (secs / 60) % 60, secs % 60); + oin = bundle->ncp.ipcp.throughput.OctetsIn; + oout = bundle->ncp.ipcp.throughput.OctetsOut; + pin = bundle->ncp.ipcp.throughput.PacketsIn; + pout = bundle->ncp.ipcp.throughput.PacketsOut; +#ifndef NOINET6 + oin += bundle->ncp.ipv6cp.throughput.OctetsIn; + oout += bundle->ncp.ipv6cp.throughput.OctetsOut; + pin += bundle->ncp.ipv6cp.throughput.PacketsIn; + pout += bundle->ncp.ipv6cp.throughput.PacketsOut; +#endif + + for (; arg < argc; arg++) { + nargv[arg] = strdup(oargv[arg]); + nargv[arg] = subst(nargv[arg], "AUTHNAME", bundle->cfg.auth.name); + nargv[arg] = substip(nargv[arg], "DNS0", bundle->ncp.ipcp.ns.dns[0]); + nargv[arg] = substip(nargv[arg], "DNS1", bundle->ncp.ipcp.ns.dns[1]); + nargv[arg] = subst(nargv[arg], "ENDDISC", + mp_Enddisc(bundle->ncp.mp.cfg.enddisc.class, + bundle->ncp.mp.cfg.enddisc.address, + bundle->ncp.mp.cfg.enddisc.len)); + nargv[arg] = substip(nargv[arg], "HISADDR", bundle->ncp.ipcp.peer_ip); +#ifndef NOINET6 + nargv[arg] = substipv6(nargv[arg], "HISADDR6", &bundle->ncp.ipv6cp.hisaddr); +#endif + nargv[arg] = subst(nargv[arg], "INTERFACE", bundle->iface->name); + nargv[arg] = substull(nargv[arg], "IPOCTETSIN", + bundle->ncp.ipcp.throughput.OctetsIn); + nargv[arg] = substull(nargv[arg], "IPOCTETSOUT", + bundle->ncp.ipcp.throughput.OctetsOut); + nargv[arg] = substull(nargv[arg], "IPPACKETSIN", + bundle->ncp.ipcp.throughput.PacketsIn); + nargv[arg] = substull(nargv[arg], "IPPACKETSOUT", + bundle->ncp.ipcp.throughput.PacketsOut); +#ifndef NOINET6 + nargv[arg] = substull(nargv[arg], "IPV6OCTETSIN", + bundle->ncp.ipv6cp.throughput.OctetsIn); + nargv[arg] = substull(nargv[arg], "IPV6OCTETSOUT", + bundle->ncp.ipv6cp.throughput.OctetsOut); + nargv[arg] = substull(nargv[arg], "IPV6PACKETSIN", + bundle->ncp.ipv6cp.throughput.PacketsIn); + nargv[arg] = substull(nargv[arg], "IPV6PACKETSOUT", + bundle->ncp.ipv6cp.throughput.PacketsOut); +#endif + nargv[arg] = subst(nargv[arg], "LABEL", bundle_GetLabel(bundle)); + nargv[arg] = substip(nargv[arg], "MYADDR", bundle->ncp.ipcp.my_ip); +#ifndef NOINET6 + nargv[arg] = substipv6(nargv[arg], "MYADDR6", &bundle->ncp.ipv6cp.myaddr); +#ifndef NORADIUS + nargv[arg] = substipv6prefix(nargv[arg], "IPV6PREFIX", + bundle->radius.ipv6prefix); +#endif +#endif + nargv[arg] = substull(nargv[arg], "OCTETSIN", oin); + nargv[arg] = substull(nargv[arg], "OCTETSOUT", oout); + nargv[arg] = substull(nargv[arg], "PACKETSIN", pin); + nargv[arg] = substull(nargv[arg], "PACKETSOUT", pout); + nargv[arg] = subst(nargv[arg], "PEER_ENDDISC", + mp_Enddisc(bundle->ncp.mp.peer.enddisc.class, + bundle->ncp.mp.peer.enddisc.address, + bundle->ncp.mp.peer.enddisc.len)); + nargv[arg] = substlong(nargv[arg], "PROCESSID", pid); + if (server.cfg.port) + nargv[arg] = substlong(nargv[arg], "SOCKNAME", server.cfg.port); + else + nargv[arg] = subst(nargv[arg], "SOCKNAME", server.cfg.sockname); + nargv[arg] = subst(nargv[arg], "UPTIME", uptime); + nargv[arg] = subst(nargv[arg], "USER", bundle->ncp.mp.peer.authname); + nargv[arg] = subst(nargv[arg], "VERSION", Version); + } + nargv[arg] = NULL; +} + +void +command_Free(int argc, char **argv) +{ + while (argc) { + free(*argv); + argc--; + argv++; + } +} + +static int +ShellCommand(struct cmdargs const *arg, int bg) +{ + const char *shell; + pid_t shpid, pid; + +#ifdef SHELL_ONLY_INTERACTIVELY + /* we're only allowed to shell when we run ppp interactively */ + if (arg->prompt && arg->prompt->owner) { + log_Printf(LogWARN, "Can't start a shell from a network connection\n"); + return 1; + } +#endif + + if (arg->argc == arg->argn) { + if (!arg->prompt) { + log_Printf(LogWARN, "Can't start an interactive shell from" + " a config file\n"); + return 1; + } else if (arg->prompt->owner) { + log_Printf(LogWARN, "Can't start an interactive shell from" + " a socket connection\n"); + return 1; + } else if (bg) { + log_Printf(LogWARN, "Can only start an interactive shell in" + " the foreground mode\n"); + return 1; + } + } + + pid = getpid(); + if ((shpid = fork()) == 0) { + int i, fd; + + if ((shell = getenv("SHELL")) == 0) + shell = _PATH_BSHELL; + + timer_TermService(); + + if (arg->prompt) + fd = arg->prompt->fd_out; + else if ((fd = open(_PATH_DEVNULL, O_RDWR)) == -1) { + log_Printf(LogALERT, "Failed to open %s: %s\n", + _PATH_DEVNULL, strerror(errno)); + exit(1); + } + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + for (i = getdtablesize(); i > STDERR_FILENO; i--) + fcntl(i, F_SETFD, 1); + +#ifndef NOSUID + setuid(ID0realuid()); +#endif + if (arg->argc > arg->argn) { + /* substitute pseudo args */ + char *argv[MAXARGS]; + int argc = arg->argc - arg->argn; + + if (argc >= (int)(sizeof argv / sizeof argv[0])) { + argc = sizeof argv / sizeof argv[0] - 1; + log_Printf(LogWARN, "Truncating shell command to %d args\n", argc); + } + command_Expand(argv, argc, arg->argv + arg->argn, arg->bundle, 0, pid); + if (bg) { + pid_t p; + + p = getpid(); + if (daemon(1, 1) == -1) { + log_Printf(LogERROR, "%ld: daemon: %s\n", (long)p, strerror(errno)); + exit(1); + } + } else if (arg->prompt) + printf("ppp: Pausing until %s finishes\n", arg->argv[arg->argn]); + execvp(argv[0], argv); + } else { + if (arg->prompt) + printf("ppp: Pausing until %s finishes\n", shell); + prompt_TtyOldMode(arg->prompt); + execl(shell, shell, (char *)NULL); + } + + log_Printf(LogWARN, "exec() of %s failed: %s\n", + arg->argc > arg->argn ? arg->argv[arg->argn] : shell, + strerror(errno)); + _exit(255); + } + + if (shpid == (pid_t)-1) + log_Printf(LogERROR, "Fork failed: %s\n", strerror(errno)); + else { + int status; + waitpid(shpid, &status, 0); + } + + if (arg->prompt && !arg->prompt->owner) + prompt_TtyCommandMode(arg->prompt); + + return 0; +} + +static int +BgShellCommand(struct cmdargs const *arg) +{ + if (arg->argc == arg->argn) + return -1; + return ShellCommand(arg, 1); +} + +static int +FgShellCommand(struct cmdargs const *arg) +{ + return ShellCommand(arg, 0); +} + +static int +ResolvCommand(struct cmdargs const *arg) +{ + if (arg->argc == arg->argn + 1) { + if (!strcasecmp(arg->argv[arg->argn], "reload")) + ipcp_LoadDNS(&arg->bundle->ncp.ipcp); + else if (!strcasecmp(arg->argv[arg->argn], "restore")) + ipcp_RestoreDNS(&arg->bundle->ncp.ipcp); + else if (!strcasecmp(arg->argv[arg->argn], "rewrite")) + ipcp_WriteDNS(&arg->bundle->ncp.ipcp); + else if (!strcasecmp(arg->argv[arg->argn], "readonly")) + arg->bundle->ncp.ipcp.ns.writable = 0; + else if (!strcasecmp(arg->argv[arg->argn], "writable")) + arg->bundle->ncp.ipcp.ns.writable = 1; + else + return -1; + + return 0; + } + + return -1; +} + +#ifndef NONAT +static struct cmdtab const NatCommands[] = +{ + {"addr", NULL, nat_RedirectAddr, LOCAL_AUTH, + "static address translation", "nat addr [addr_local addr_alias]", NULL}, + {"deny_incoming", NULL, NatOption, LOCAL_AUTH, + "stop incoming connections", "nat deny_incoming yes|no", + (const void *) PKT_ALIAS_DENY_INCOMING}, + {"enable", NULL, NatEnable, LOCAL_AUTH, + "enable NAT", "nat enable yes|no", NULL}, + {"log", NULL, NatOption, LOCAL_AUTH, + "log NAT link creation", "nat log yes|no", + (const void *) PKT_ALIAS_LOG}, + {"port", NULL, nat_RedirectPort, LOCAL_AUTH, "port redirection", + "nat port proto localaddr:port[-port] aliasport[-aliasport]", NULL}, + {"proto", NULL, nat_RedirectProto, LOCAL_AUTH, "protocol redirection", + "nat proto proto localIP [publicIP [remoteIP]]", NULL}, + {"proxy", NULL, nat_ProxyRule, LOCAL_AUTH, + "proxy control", "nat proxy server host[:port] ...", NULL}, +#ifndef NO_FW_PUNCH + {"punch_fw", NULL, nat_PunchFW, LOCAL_AUTH, + "firewall control", "nat punch_fw [base count]", NULL}, +#endif + {"skinny_port", NULL, nat_SkinnyPort, LOCAL_AUTH, + "TCP port used by Skinny Station protocol", "nat skinny_port [port]", NULL}, + {"same_ports", NULL, NatOption, LOCAL_AUTH, + "try to leave port numbers unchanged", "nat same_ports yes|no", + (const void *) PKT_ALIAS_SAME_PORTS}, + {"target", NULL, nat_SetTarget, LOCAL_AUTH, + "Default address for incoming connections", "nat target addr", NULL}, + {"unregistered_only", NULL, NatOption, LOCAL_AUTH, + "translate unregistered (private) IP address space only", + "nat unregistered_only yes|no", + (const void *) PKT_ALIAS_UNREGISTERED_ONLY}, + {"use_sockets", NULL, NatOption, LOCAL_AUTH, + "allocate host sockets", "nat use_sockets yes|no", + (const void *) PKT_ALIAS_USE_SOCKETS}, + {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH, + "Display this message", "nat help|? [command]", NatCommands}, + {NULL, NULL, NULL, 0, NULL, NULL, NULL}, +}; +#endif + +static struct cmdtab const AllowCommands[] = { + {"modes", "mode", AllowModes, LOCAL_AUTH, + "Only allow certain ppp modes", "allow modes mode...", NULL}, + {"users", "user", AllowUsers, LOCAL_AUTH, + "Only allow ppp access to certain users", "allow users logname...", NULL}, + {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH, + "Display this message", "allow help|? [command]", AllowCommands}, + {NULL, NULL, NULL, 0, NULL, NULL, NULL}, +}; + +static struct cmdtab const IfaceCommands[] = +{ + {"add", NULL, IfaceAddCommand, LOCAL_AUTH, + "Add iface address", "iface add addr[/bits| mask] peer", NULL}, + {NULL, "add!", IfaceAddCommand, LOCAL_AUTH, + "Add or change an iface address", "iface add! addr[/bits| mask] peer", + (void *)1}, + {"clear", NULL, IfaceClearCommand, LOCAL_AUTH, + "Clear iface address(es)", "iface clear [INET | INET6]", NULL}, + {"delete", "rm", IfaceDeleteCommand, LOCAL_AUTH, + "Delete iface address", "iface delete addr", NULL}, + {NULL, "rm!", IfaceDeleteCommand, LOCAL_AUTH, + "Delete iface address", "iface delete addr", (void *)1}, + {NULL, "delete!", IfaceDeleteCommand, LOCAL_AUTH, + "Delete iface address", "iface delete addr", (void *)1}, + {"show", NULL, iface_Show, LOCAL_AUTH, + "Show iface address(es)", "iface show", NULL}, + {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH, + "Display this message", "nat help|? [command]", IfaceCommands}, + {NULL, NULL, NULL, 0, NULL, NULL, NULL}, +}; + +static struct cmdtab const Commands[] = { + {"accept", NULL, NegotiateCommand, LOCAL_AUTH | LOCAL_CX_OPT, + "accept option request", "accept option ..", NULL}, + {"add", NULL, AddCommand, LOCAL_AUTH, + "add route", "add dest mask gateway", NULL}, + {NULL, "add!", AddCommand, LOCAL_AUTH, + "add or change route", "add! dest mask gateway", (void *)1}, + {"allow", "auth", RunListCommand, LOCAL_AUTH, + "Allow ppp access", "allow users|modes ....", AllowCommands}, + {"bg", "!bg", BgShellCommand, LOCAL_AUTH, + "Run a background command", "[!]bg command", NULL}, + {"clear", NULL, ClearCommand, LOCAL_AUTH | LOCAL_CX_OPT, + "Clear throughput statistics", + "clear ipcp|ipv6cp|physical [current|overall|peak]...", NULL}, + {"clone", NULL, CloneCommand, LOCAL_AUTH | LOCAL_CX, + "Clone a link", "clone newname...", NULL}, + {"close", NULL, CloseCommand, LOCAL_AUTH | LOCAL_CX_OPT, + "Close an FSM", "close [lcp|ccp]", NULL}, + {"delete", NULL, DeleteCommand, LOCAL_AUTH, + "delete route", "delete dest", NULL}, + {NULL, "delete!", DeleteCommand, LOCAL_AUTH, + "delete a route if it exists", "delete! dest", (void *)1}, + {"deny", NULL, NegotiateCommand, LOCAL_AUTH | LOCAL_CX_OPT, + "Deny option request", "deny option ..", NULL}, + {"dial", "call", DialCommand, LOCAL_AUTH | LOCAL_CX_OPT, + "Dial and login", "dial|call [system ...]", NULL}, + {"disable", NULL, NegotiateCommand, LOCAL_AUTH | LOCAL_CX_OPT, + "Disable option", "disable option ..", NULL}, + {"down", NULL, DownCommand, LOCAL_AUTH | LOCAL_CX_OPT, + "Generate a down event", "down [ccp|lcp]", NULL}, + {"enable", NULL, NegotiateCommand, LOCAL_AUTH | LOCAL_CX_OPT, + "Enable option", "enable option ..", NULL}, + {"ident", NULL, IdentCommand, LOCAL_AUTH | LOCAL_CX, + "Set the link identity", "ident text...", NULL}, + {"iface", "interface", RunListCommand, LOCAL_AUTH, + "interface control", "iface option ...", IfaceCommands}, + {"link", "datalink", LinkCommand, LOCAL_AUTH, + "Link specific commands", "link name command ...", NULL}, + {"load", NULL, LoadCommand, LOCAL_AUTH | LOCAL_CX_OPT, + "Load settings", "load [system ...]", NULL}, + {"log", NULL, LogCommand, LOCAL_AUTH | LOCAL_CX_OPT, + "log information", "log word ...", NULL}, +#ifndef NONAT + {"nat", "alias", RunListCommand, LOCAL_AUTH, + "NAT control", "nat option yes|no", NatCommands}, +#endif + {"open", NULL, OpenCommand, LOCAL_AUTH | LOCAL_CX_OPT, + "Open an FSM", "open! [lcp|ccp|ipcp]", (void *)1}, + {"passwd", NULL, PasswdCommand, LOCAL_NO_AUTH, + "Password for manipulation", "passwd LocalPassword", NULL}, + {"quit", "bye", QuitCommand, LOCAL_AUTH | LOCAL_NO_AUTH, + "Quit PPP program", "quit|bye [all]", NULL}, + {"remove", "rm", RemoveCommand, LOCAL_AUTH | LOCAL_CX, + "Remove a link", "remove", NULL}, + {"rename", "mv", RenameCommand, LOCAL_AUTH | LOCAL_CX, + "Rename a link", "rename name", NULL}, + {"resolv", NULL, ResolvCommand, LOCAL_AUTH, + "Manipulate resolv.conf", "resolv readonly|reload|restore|rewrite|writable", + NULL}, + {"save", NULL, SaveCommand, LOCAL_AUTH, + "Save settings", "save", NULL}, + {"sendident", NULL, SendIdentification, LOCAL_AUTH | LOCAL_CX, + "Transmit the link identity", "sendident", NULL}, + {"set", "setup", SetCommand, LOCAL_AUTH | LOCAL_CX_OPT, + "Set parameters", "set[up] var value", NULL}, + {"shell", "!", FgShellCommand, LOCAL_AUTH, + "Run a subshell", "shell|! [sh command]", NULL}, + {"show", NULL, ShowCommand, LOCAL_AUTH | LOCAL_CX_OPT, + "Show status and stats", "show var", NULL}, + {"term", NULL, TerminalCommand, LOCAL_AUTH | LOCAL_CX, + "Enter terminal mode", "term", NULL}, + {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH, + "Display this message", "help|? [command]", Commands}, + {NULL, NULL, NULL, 0, NULL, NULL, NULL}, +}; + +static int +ShowEscape(struct cmdargs const *arg) +{ + if (arg->cx->physical->async.cfg.EscMap[32]) { + int code, bit; + const char *sep = ""; + + for (code = 0; code < 32; code++) + if (arg->cx->physical->async.cfg.EscMap[code]) + for (bit = 0; bit < 8; bit++) + if (arg->cx->physical->async.cfg.EscMap[code] & (1 << bit)) { + prompt_Printf(arg->prompt, "%s0x%02x", sep, (code << 3) + bit); + sep = ", "; + } + prompt_Printf(arg->prompt, "\n"); + } + return 0; +} + +static int +ShowTimerList(struct cmdargs const *arg) +{ + timer_Show(0, arg->prompt); + return 0; +} + +static int +ShowStopped(struct cmdargs const *arg) +{ + prompt_Printf(arg->prompt, " Stopped Timer: LCP: "); + if (!arg->cx->physical->link.lcp.fsm.StoppedTimer.load) + prompt_Printf(arg->prompt, "Disabled"); + else + prompt_Printf(arg->prompt, "%ld secs", + arg->cx->physical->link.lcp.fsm.StoppedTimer.load / SECTICKS); + + prompt_Printf(arg->prompt, ", CCP: "); + if (!arg->cx->physical->link.ccp.fsm.StoppedTimer.load) + prompt_Printf(arg->prompt, "Disabled"); + else + prompt_Printf(arg->prompt, "%ld secs", + arg->cx->physical->link.ccp.fsm.StoppedTimer.load / SECTICKS); + + prompt_Printf(arg->prompt, "\n"); + + return 0; +} + +static int +ShowVersion(struct cmdargs const *arg) +{ + prompt_Printf(arg->prompt, "PPP Version %s\n", Version); + return 0; +} + +static int +ShowProtocolStats(struct cmdargs const *arg) +{ + struct link *l = command_ChooseLink(arg); + + prompt_Printf(arg->prompt, "%s:\n", l->name); + link_ReportProtocolStatus(l, arg->prompt); + return 0; +} + +static struct cmdtab const ShowCommands[] = { + {"bundle", NULL, bundle_ShowStatus, LOCAL_AUTH, + "bundle details", "show bundle", NULL}, + {"ccp", NULL, ccp_ReportStatus, LOCAL_AUTH | LOCAL_CX_OPT, + "CCP status", "show cpp", NULL}, + {"compress", NULL, sl_Show, LOCAL_AUTH, + "VJ compression stats", "show compress", NULL}, + {"escape", NULL, ShowEscape, LOCAL_AUTH | LOCAL_CX, + "escape characters", "show escape", NULL}, + {"filter", NULL, filter_Show, LOCAL_AUTH, + "packet filters", "show filter [in|out|dial|alive]", NULL}, + {"hdlc", NULL, hdlc_ReportStatus, LOCAL_AUTH | LOCAL_CX, + "HDLC errors", "show hdlc", NULL}, + {"iface", "interface", iface_Show, LOCAL_AUTH, + "Interface status", "show iface", NULL}, + {"ipcp", NULL, ipcp_Show, LOCAL_AUTH, + "IPCP status", "show ipcp", NULL}, +#ifndef NOINET6 + {"ipv6cp", NULL, ipv6cp_Show, LOCAL_AUTH, + "IPV6CP status", "show ipv6cp", NULL}, +#endif + {"layers", NULL, link_ShowLayers, LOCAL_AUTH | LOCAL_CX_OPT, + "Protocol layers", "show layers", NULL}, + {"lcp", NULL, lcp_ReportStatus, LOCAL_AUTH | LOCAL_CX, + "LCP status", "show lcp", NULL}, + {"link", "datalink", datalink_Show, LOCAL_AUTH | LOCAL_CX, + "(high-level) link info", "show link", NULL}, + {"links", NULL, bundle_ShowLinks, LOCAL_AUTH, + "available link names", "show links", NULL}, + {"log", NULL, log_ShowLevel, LOCAL_AUTH, + "log levels", "show log", NULL}, + {"mem", NULL, mbuf_Show, LOCAL_AUTH, + "mbuf allocations", "show mem", NULL}, + {"ncp", NULL, ncp_Show, LOCAL_AUTH, + "NCP status", "show ncp", NULL}, + {"physical", NULL, physical_ShowStatus, LOCAL_AUTH | LOCAL_CX, + "(low-level) link info", "show physical", NULL}, + {"mp", "multilink", mp_ShowStatus, LOCAL_AUTH, + "multilink setup", "show mp", NULL}, + {"proto", NULL, ShowProtocolStats, LOCAL_AUTH | LOCAL_CX_OPT, + "protocol summary", "show proto", NULL}, + {"route", NULL, route_Show, LOCAL_AUTH, + "routing table", "show route", NULL}, + {"stopped", NULL, ShowStopped, LOCAL_AUTH | LOCAL_CX, + "STOPPED timeout", "show stopped", NULL}, + {"timers", NULL, ShowTimerList, LOCAL_AUTH, + "alarm timers", "show timers", NULL}, + {"version", NULL, ShowVersion, LOCAL_NO_AUTH | LOCAL_AUTH, + "version string", "show version", NULL}, + {"who", NULL, log_ShowWho, LOCAL_AUTH, + "client list", "show who", NULL}, + {"help", "?", HelpCommand, LOCAL_NO_AUTH | LOCAL_AUTH, + "Display this message", "show help|? [command]", ShowCommands}, + {NULL, NULL, NULL, 0, NULL, NULL, NULL}, +}; + +static struct cmdtab const * +FindCommand(struct cmdtab const *cmds, const char *str, int *pmatch) +{ + int nmatch; + int len; + struct cmdtab const *found; + + found = NULL; + len = strlen(str); + nmatch = 0; + while (cmds->func) { + if (cmds->name && strncasecmp(str, cmds->name, len) == 0) { + if (cmds->name[len] == '\0') { + *pmatch = 1; + return cmds; + } + nmatch++; + found = cmds; + } else if (cmds->alias && strncasecmp(str, cmds->alias, len) == 0) { + if (cmds->alias[len] == '\0') { + *pmatch = 1; + return cmds; + } + nmatch++; + found = cmds; + } + cmds++; + } + *pmatch = nmatch; + return found; +} + +static const char * +mkPrefix(int argc, char const *const *argv, char *tgt, int sz) +{ + int f, tlen, len; + + tlen = 0; + for (f = 0; f < argc && tlen < sz - 2; f++) { + if (f) + tgt[tlen++] = ' '; + len = strlen(argv[f]); + if (len > sz - tlen - 1) + len = sz - tlen - 1; + strncpy(tgt+tlen, argv[f], len); + tlen += len; + } + tgt[tlen] = '\0'; + return tgt; +} + +static int +FindExec(struct bundle *bundle, struct cmdtab const *cmds, int argc, int argn, + char const *const *argv, struct prompt *prompt, struct datalink *cx) +{ + struct cmdtab const *cmd; + int val = 1; + int nmatch; + struct cmdargs arg; + char prefix[100]; + + cmd = FindCommand(cmds, argv[argn], &nmatch); + if (nmatch > 1) + log_Printf(LogWARN, "%s: Ambiguous command\n", + mkPrefix(argn+1, argv, prefix, sizeof prefix)); + else if (cmd && (!prompt || (cmd->lauth & prompt->auth))) { + if ((cmd->lauth & LOCAL_CX) && !cx) + /* We've got no context, but we require it */ + cx = bundle2datalink(bundle, NULL); + + if ((cmd->lauth & LOCAL_CX) && !cx) + log_Printf(LogWARN, "%s: No context (use the `link' command)\n", + mkPrefix(argn+1, argv, prefix, sizeof prefix)); + else { + if (cx && !(cmd->lauth & (LOCAL_CX|LOCAL_CX_OPT))) { + log_Printf(LogWARN, "%s: Redundant context (%s) ignored\n", + mkPrefix(argn+1, argv, prefix, sizeof prefix), cx->name); + cx = NULL; + } + arg.cmdtab = cmds; + arg.cmd = cmd; + arg.argc = argc; + arg.argn = argn+1; + arg.argv = argv; + arg.bundle = bundle; + arg.cx = cx; + arg.prompt = prompt; + val = (*cmd->func) (&arg); + } + } else + log_Printf(LogWARN, "%s: Invalid command\n", + mkPrefix(argn+1, argv, prefix, sizeof prefix)); + + if (val == -1) + log_Printf(LogWARN, "usage: %s\n", cmd->syntax); + else if (val) + log_Printf(LogWARN, "%s: Failed %d\n", + mkPrefix(argn+1, argv, prefix, sizeof prefix), val); + + return val; +} + +int +command_Expand_Interpret(char *buff, int nb, char *argv[MAXARGS], int offset) +{ + char buff2[LINE_LEN-offset]; + + InterpretArg(buff, buff2); + strncpy(buff, buff2, LINE_LEN - offset - 1); + buff[LINE_LEN - offset - 1] = '\0'; + + return command_Interpret(buff, nb, argv); +} + +int +command_Interpret(char *buff, int nb, char *argv[MAXARGS]) +{ + char *cp; + + if (nb > 0) { + cp = buff + strcspn(buff, "\r\n"); + if (cp) + *cp = '\0'; + return MakeArgs(buff, argv, MAXARGS, PARSE_REDUCE); + } + return 0; +} + +static int +arghidden(char const *const *argv, int n) +{ + /* Is arg n of the given command to be hidden from the log ? */ + + /* set authkey xxxxx */ + /* set key xxxxx */ + if (n == 2 && !strncasecmp(argv[0], "se", 2) && + (!strncasecmp(argv[1], "authk", 5) || !strncasecmp(argv[1], "ke", 2))) + return 1; + + /* passwd xxxxx */ + if (n == 1 && !strncasecmp(argv[0], "p", 1)) + return 1; + + /* set server port xxxxx .... */ + if (n == 3 && !strncasecmp(argv[0], "se", 2) && + !strncasecmp(argv[1], "se", 2)) + return 1; + + return 0; +} + +void +command_Run(struct bundle *bundle, int argc, char const *const *argv, + struct prompt *prompt, const char *label, struct datalink *cx) +{ + if (argc > 0) { + if (log_IsKept(LogCOMMAND)) { + char buf[LINE_LEN]; + int f; + size_t n; + + if (label) { + strncpy(buf, label, sizeof buf - 3); + buf[sizeof buf - 3] = '\0'; + strcat(buf, ": "); + n = strlen(buf); + } else { + *buf = '\0'; + n = 0; + } + buf[sizeof buf - 1] = '\0'; /* In case we run out of room in buf */ + + for (f = 0; f < argc; f++) { + if (n < sizeof buf - 1 && f) + buf[n++] = ' '; + if (arghidden(argv, f)) + strncpy(buf+n, "********", sizeof buf - n - 1); + else + strncpy(buf+n, argv[f], sizeof buf - n - 1); + n += strlen(buf+n); + } + log_Printf(LogCOMMAND, "%s\n", buf); + } + FindExec(bundle, Commands, argc, 0, argv, prompt, cx); + } +} + +int +command_Decode(struct bundle *bundle, char *buff, int nb, struct prompt *prompt, + const char *label) +{ + int argc; + char *argv[MAXARGS]; + + if ((argc = command_Expand_Interpret(buff, nb, argv, 0)) < 0) + return 0; + + command_Run(bundle, argc, (char const *const *)argv, prompt, label, NULL); + return 1; +} + +static int +ShowCommand(struct cmdargs const *arg) +{ + if (!arg->prompt) + log_Printf(LogWARN, "show: Cannot show without a prompt\n"); + else if (arg->argc > arg->argn) + FindExec(arg->bundle, ShowCommands, arg->argc, arg->argn, arg->argv, + arg->prompt, arg->cx); + else + prompt_Printf(arg->prompt, "Use ``show ?'' to get a list.\n"); + + return 0; +} + +static int +TerminalCommand(struct cmdargs const *arg) +{ + if (!arg->prompt) { + log_Printf(LogWARN, "term: Need a prompt\n"); + return 1; + } + + if (arg->cx->physical->link.lcp.fsm.state > ST_CLOSED) { + prompt_Printf(arg->prompt, "LCP state is [%s]\n", + State2Nam(arg->cx->physical->link.lcp.fsm.state)); + return 1; + } + + datalink_Up(arg->cx, 0, 0); + prompt_TtyTermMode(arg->prompt, arg->cx); + return 0; +} + +static int +QuitCommand(struct cmdargs const *arg) +{ + if (!arg->prompt || prompt_IsController(arg->prompt) || + (arg->argc > arg->argn && !strcasecmp(arg->argv[arg->argn], "all") && + (arg->prompt->auth & LOCAL_AUTH))) + Cleanup(); + if (arg->prompt) + prompt_Destroy(arg->prompt, 1); + + return 0; +} + +static int +OpenCommand(struct cmdargs const *arg) +{ + if (arg->argc == arg->argn) + bundle_Open(arg->bundle, arg->cx ? arg->cx->name : NULL, PHYS_ALL, 1); + else if (arg->argc == arg->argn + 1) { + if (!strcasecmp(arg->argv[arg->argn], "lcp")) { + struct datalink *cx = arg->cx ? + arg->cx : bundle2datalink(arg->bundle, NULL); + if (cx) { + if (cx->physical->link.lcp.fsm.state == ST_OPENED) + fsm_Reopen(&cx->physical->link.lcp.fsm); + else + bundle_Open(arg->bundle, cx->name, PHYS_ALL, 1); + } else + log_Printf(LogWARN, "open lcp: You must specify a link\n"); + } else if (!strcasecmp(arg->argv[arg->argn], "ccp")) { + struct fsm *fp; + + fp = &command_ChooseLink(arg)->ccp.fsm; + if (fp->link->lcp.fsm.state != ST_OPENED) + log_Printf(LogWARN, "open: LCP must be open before opening CCP\n"); + else if (fp->state == ST_OPENED) + fsm_Reopen(fp); + else { + fp->open_mode = 0; /* Not passive any more */ + if (fp->state == ST_STOPPED) { + fsm_Down(fp); + fsm_Up(fp); + } else { + fsm_Up(fp); + fsm_Open(fp); + } + } + } else if (!strcasecmp(arg->argv[arg->argn], "ipcp")) { + if (arg->cx) + log_Printf(LogWARN, "open ipcp: You need not specify a link\n"); + if (arg->bundle->ncp.ipcp.fsm.state == ST_OPENED) + fsm_Reopen(&arg->bundle->ncp.ipcp.fsm); + else + bundle_Open(arg->bundle, NULL, PHYS_ALL, 1); + } else + return -1; + } else + return -1; + + return 0; +} + +static int +CloseCommand(struct cmdargs const *arg) +{ + if (arg->argc == arg->argn) + bundle_Close(arg->bundle, arg->cx ? arg->cx->name : NULL, CLOSE_STAYDOWN); + else if (arg->argc == arg->argn + 1) { + if (!strcasecmp(arg->argv[arg->argn], "lcp")) + bundle_Close(arg->bundle, arg->cx ? arg->cx->name : NULL, CLOSE_LCP); + else if (!strcasecmp(arg->argv[arg->argn], "ccp") || + !strcasecmp(arg->argv[arg->argn], "ccp!")) { + struct fsm *fp; + + fp = &command_ChooseLink(arg)->ccp.fsm; + if (fp->state == ST_OPENED) { + fsm_Close(fp); + if (arg->argv[arg->argn][3] == '!') + fp->open_mode = 0; /* Stay ST_CLOSED */ + else + fp->open_mode = OPEN_PASSIVE; /* Wait for the peer to start */ + } + } else + return -1; + } else + return -1; + + return 0; +} + +static int +DownCommand(struct cmdargs const *arg) +{ + if (arg->argc == arg->argn) { + if (arg->cx) + datalink_Down(arg->cx, CLOSE_STAYDOWN); + else + bundle_Down(arg->bundle, CLOSE_STAYDOWN); + } else if (arg->argc == arg->argn + 1) { + if (!strcasecmp(arg->argv[arg->argn], "lcp")) { + if (arg->cx) + datalink_Down(arg->cx, CLOSE_LCP); + else + bundle_Down(arg->bundle, CLOSE_LCP); + } else if (!strcasecmp(arg->argv[arg->argn], "ccp")) { + struct fsm *fp = arg->cx ? &arg->cx->physical->link.ccp.fsm : + &arg->bundle->ncp.mp.link.ccp.fsm; + fsm2initial(fp); + } else + return -1; + } else + return -1; + + return 0; +} + +static int +SetModemSpeed(struct cmdargs const *arg) +{ + long speed; + char *end; + + if (arg->argc > arg->argn && *arg->argv[arg->argn]) { + if (arg->argc > arg->argn+1) { + log_Printf(LogWARN, "SetModemSpeed: Too many arguments\n"); + return -1; + } + if (strcasecmp(arg->argv[arg->argn], "sync") == 0) { + physical_SetSync(arg->cx->physical); + return 0; + } + end = NULL; + speed = strtol(arg->argv[arg->argn], &end, 10); + if (*end || speed < 0) { + log_Printf(LogWARN, "SetModemSpeed: Bad argument \"%s\"", + arg->argv[arg->argn]); + return -1; + } + if (physical_SetSpeed(arg->cx->physical, speed)) + return 0; + log_Printf(LogWARN, "%s: Invalid speed\n", arg->argv[arg->argn]); + } else + log_Printf(LogWARN, "SetModemSpeed: No speed specified\n"); + + return -1; +} + +static int +SetStoppedTimeout(struct cmdargs const *arg) +{ + struct link *l = &arg->cx->physical->link; + + l->lcp.fsm.StoppedTimer.load = 0; + l->ccp.fsm.StoppedTimer.load = 0; + if (arg->argc <= arg->argn+2) { + if (arg->argc > arg->argn) { + l->lcp.fsm.StoppedTimer.load = atoi(arg->argv[arg->argn]) * SECTICKS; + if (arg->argc > arg->argn+1) + l->ccp.fsm.StoppedTimer.load = atoi(arg->argv[arg->argn+1]) * SECTICKS; + } + return 0; + } + return -1; +} + +static int +SetServer(struct cmdargs const *arg) +{ + int res = -1; + + if (arg->argc > arg->argn && arg->argc < arg->argn+4) { + const char *port, *passwd, *mask; + size_t mlen; + + /* What's what ? */ + port = arg->argv[arg->argn]; + if (arg->argc == arg->argn + 2) { + passwd = arg->argv[arg->argn+1]; + mask = NULL; + } else if (arg->argc == arg->argn + 3) { + passwd = arg->argv[arg->argn+1]; + mask = arg->argv[arg->argn+2]; + mlen = strlen(mask); + if (mlen == 0 || mlen > 4 || strspn(mask, "01234567") != mlen || + (mlen == 4 && *mask != '0')) { + log_Printf(LogWARN, "%s %s: %s: Invalid mask\n", + arg->argv[arg->argn - 2], arg->argv[arg->argn - 1], mask); + return -1; + } + } else if (arg->argc != arg->argn + 1) + return -1; + else if (strcasecmp(port, "none") == 0) { + if (server_Clear(arg->bundle)) + log_Printf(LogPHASE, "Disabled server socket\n"); + return 0; + } else if (strcasecmp(port, "open") == 0) { + switch (server_Reopen(arg->bundle)) { + case SERVER_OK: + return 0; + case SERVER_FAILED: + log_Printf(LogWARN, "Failed to reopen server port\n"); + return 1; + case SERVER_UNSET: + log_Printf(LogWARN, "Cannot reopen unset server socket\n"); + return 1; + default: + break; + } + return -1; + } else if (strcasecmp(port, "closed") == 0) { + if (server_Close(arg->bundle)) + log_Printf(LogPHASE, "Closed server socket\n"); + else + log_Printf(LogWARN, "Server socket not open\n"); + + return 0; + } else + return -1; + + strncpy(server.cfg.passwd, passwd, sizeof server.cfg.passwd - 1); + server.cfg.passwd[sizeof server.cfg.passwd - 1] = '\0'; + + if (*port == '/') { + mode_t imask; + char *ptr, name[LINE_LEN + 12]; + + if (mask == NULL) + imask = (mode_t)-1; + else for (imask = mlen = 0; mask[mlen]; mlen++) + imask = (imask * 8) + mask[mlen] - '0'; + + ptr = strstr(port, "%d"); + if (ptr) { + snprintf(name, sizeof name, "%.*s%d%s", + (int)(ptr - port), port, arg->bundle->unit, ptr + 2); + port = name; + } + res = server_LocalOpen(arg->bundle, port, imask); + } else { + int iport, add = 0; + + if (mask != NULL) + return -1; + + if (*port == '+') { + port++; + add = 1; + } + if (strspn(port, "0123456789") != strlen(port)) { + struct servent *s; + + if ((s = getservbyname(port, "tcp")) == NULL) { + iport = 0; + log_Printf(LogWARN, "%s: Invalid port or service\n", port); + } else + iport = ntohs(s->s_port); + } else + iport = atoi(port); + + if (iport) { + if (add) + iport += arg->bundle->unit; + res = server_TcpOpen(arg->bundle, iport); + } else + res = -1; + } + } + + return res; +} + +static int +SetEscape(struct cmdargs const *arg) +{ + int code; + int argc = arg->argc - arg->argn; + char const *const *argv = arg->argv + arg->argn; + + for (code = 0; code < 33; code++) + arg->cx->physical->async.cfg.EscMap[code] = 0; + + while (argc-- > 0) { + sscanf(*argv++, "%x", &code); + code &= 0xff; + arg->cx->physical->async.cfg.EscMap[code >> 3] |= (1 << (code & 7)); + arg->cx->physical->async.cfg.EscMap[32] = 1; + } + return 0; +} + +static int +SetInterfaceAddr(struct cmdargs const *arg) +{ + struct ncp *ncp = &arg->bundle->ncp; + struct ncpaddr ncpaddr; + const char *hisaddr; + + if (arg->argc > arg->argn + 4) + return -1; + + hisaddr = NULL; + memset(&ncp->ipcp.cfg.my_range, '\0', sizeof ncp->ipcp.cfg.my_range); + memset(&ncp->ipcp.cfg.peer_range, '\0', sizeof ncp->ipcp.cfg.peer_range); + ncp->ipcp.cfg.HaveTriggerAddress = 0; + ncp->ipcp.cfg.netmask.s_addr = INADDR_ANY; + iplist_reset(&ncp->ipcp.cfg.peer_list); + + if (arg->argc > arg->argn) { + if (!ncprange_aton(&ncp->ipcp.cfg.my_range, ncp, arg->argv[arg->argn])) + return 1; + if (arg->argc > arg->argn+1) { + hisaddr = arg->argv[arg->argn+1]; + if (arg->argc > arg->argn+2) { + ncp->ipcp.ifmask = ncp->ipcp.cfg.netmask = + GetIpAddr(arg->argv[arg->argn+2]); + if (arg->argc > arg->argn+3) { + ncp->ipcp.cfg.TriggerAddress = GetIpAddr(arg->argv[arg->argn+3]); + ncp->ipcp.cfg.HaveTriggerAddress = 1; + } + } + } + } + + /* 0.0.0.0 means any address (0 bits) */ + ncprange_getaddr(&ncp->ipcp.cfg.my_range, &ncpaddr); + ncpaddr_getip4(&ncpaddr, &ncp->ipcp.my_ip); + if (ncp->ipcp.my_ip.s_addr == INADDR_ANY) + ncprange_setwidth(&ncp->ipcp.cfg.my_range, 0); + bundle_AdjustFilters(arg->bundle, &ncpaddr, NULL); + + if (hisaddr && !ipcp_UseHisaddr(arg->bundle, hisaddr, + arg->bundle->phys_type.all & PHYS_AUTO)) + return 4; + + return 0; +} + +static int +SetRetry(int argc, char const *const *argv, u_int *timeout, u_int *maxreq, + u_int *maxtrm, int def) +{ + if (argc == 0) { + *timeout = DEF_FSMRETRY; + *maxreq = def; + if (maxtrm != NULL) + *maxtrm = def; + } else { + long l = atol(argv[0]); + + if (l < MIN_FSMRETRY) { + log_Printf(LogWARN, "%ld: Invalid FSM retry period - min %d\n", + l, MIN_FSMRETRY); + return 1; + } else + *timeout = l; + + if (argc > 1) { + l = atol(argv[1]); + if (l < 1) { + log_Printf(LogWARN, "%ld: Invalid FSM REQ tries - changed to 1\n", l); + l = 1; + } + *maxreq = l; + + if (argc > 2 && maxtrm != NULL) { + l = atol(argv[2]); + if (l < 1) { + log_Printf(LogWARN, "%ld: Invalid FSM TRM tries - changed to 1\n", l); + l = 1; + } + *maxtrm = l; + } + } + } + + return 0; +} + +static int +SetVariable(struct cmdargs const *arg) +{ + long long_val, param = (long)arg->cmd->args; + int mode, dummyint, f, first, res; + u_short *change; + const char *argp; + struct datalink *cx = arg->cx; /* LOCAL_CX uses this */ + struct link *l = command_ChooseLink(arg); /* LOCAL_CX_OPT uses this */ + struct in_addr *ipaddr; + struct ncpaddr ncpaddr[2]; + + if (arg->argc > arg->argn) + argp = arg->argv[arg->argn]; + else + argp = ""; + + res = 0; + + if ((arg->cmd->lauth & LOCAL_CX) && !cx) { + log_Printf(LogWARN, "set %s: No context (use the `link' command)\n", + arg->cmd->name); + return 1; + } else if (cx && !(arg->cmd->lauth & (LOCAL_CX|LOCAL_CX_OPT))) { + log_Printf(LogWARN, "set %s: Redundant context (%s) ignored\n", + arg->cmd->name, cx->name); + cx = NULL; + } + + switch (param) { + case VAR_AUTHKEY: + strncpy(arg->bundle->cfg.auth.key, argp, + sizeof arg->bundle->cfg.auth.key - 1); + arg->bundle->cfg.auth.key[sizeof arg->bundle->cfg.auth.key - 1] = '\0'; + break; + + case VAR_AUTHNAME: + switch (bundle_Phase(arg->bundle)) { + default: + log_Printf(LogWARN, "Altering authname while at phase %s\n", + bundle_PhaseName(arg->bundle)); + /* drop through */ + case PHASE_DEAD: + case PHASE_ESTABLISH: + strncpy(arg->bundle->cfg.auth.name, argp, + sizeof arg->bundle->cfg.auth.name - 1); + arg->bundle->cfg.auth.name[sizeof arg->bundle->cfg.auth.name-1] = '\0'; + break; + } + break; + + case VAR_AUTOLOAD: + if (arg->argc == arg->argn + 3) { + int v1, v2, v3; + char *end; + + v1 = strtol(arg->argv[arg->argn], &end, 0); + if (v1 < 0 || *end) { + log_Printf(LogWARN, "autoload: %s: Invalid min percentage\n", + arg->argv[arg->argn]); + res = 1; + break; + } + + v2 = strtol(arg->argv[arg->argn + 1], &end, 0); + if (v2 < 0 || *end) { + log_Printf(LogWARN, "autoload: %s: Invalid max percentage\n", + arg->argv[arg->argn + 1]); + res = 1; + break; + } + if (v2 < v1) { + v3 = v1; + v1 = v2; + v2 = v3; + } + + v3 = strtol(arg->argv[arg->argn + 2], &end, 0); + if (v3 <= 0 || *end) { + log_Printf(LogWARN, "autoload: %s: Invalid throughput period\n", + arg->argv[arg->argn + 2]); + res = 1; + break; + } + + arg->bundle->ncp.mp.cfg.autoload.min = v1; + arg->bundle->ncp.mp.cfg.autoload.max = v2; + arg->bundle->ncp.mp.cfg.autoload.period = v3; + mp_RestartAutoloadTimer(&arg->bundle->ncp.mp); + } else { + log_Printf(LogWARN, "Set autoload requires three arguments\n"); + res = 1; + } + break; + + case VAR_DIAL: + strncpy(cx->cfg.script.dial, argp, sizeof cx->cfg.script.dial - 1); + cx->cfg.script.dial[sizeof cx->cfg.script.dial - 1] = '\0'; + break; + + case VAR_LOGIN: + strncpy(cx->cfg.script.login, argp, sizeof cx->cfg.script.login - 1); + cx->cfg.script.login[sizeof cx->cfg.script.login - 1] = '\0'; + break; + + case VAR_WINSIZE: + if (arg->argc > arg->argn) { + l->ccp.cfg.deflate.out.winsize = atoi(arg->argv[arg->argn]); + if (l->ccp.cfg.deflate.out.winsize < 8 || + l->ccp.cfg.deflate.out.winsize > 15) { + log_Printf(LogWARN, "%d: Invalid outgoing window size\n", + l->ccp.cfg.deflate.out.winsize); + l->ccp.cfg.deflate.out.winsize = 15; + } + if (arg->argc > arg->argn+1) { + l->ccp.cfg.deflate.in.winsize = atoi(arg->argv[arg->argn+1]); + if (l->ccp.cfg.deflate.in.winsize < 8 || + l->ccp.cfg.deflate.in.winsize > 15) { + log_Printf(LogWARN, "%d: Invalid incoming window size\n", + l->ccp.cfg.deflate.in.winsize); + l->ccp.cfg.deflate.in.winsize = 15; + } + } else + l->ccp.cfg.deflate.in.winsize = 0; + } else { + log_Printf(LogWARN, "No window size specified\n"); + res = 1; + } + break; + +#ifndef NODES + case VAR_MPPE: + if (arg->argc > arg->argn + 2) { + res = -1; + break; + } + + if (arg->argc == arg->argn) { + l->ccp.cfg.mppe.keybits = 0; + l->ccp.cfg.mppe.state = MPPE_ANYSTATE; + l->ccp.cfg.mppe.required = 0; + break; + } + + if (!strcmp(argp, "*")) + long_val = 0; + else { + long_val = atol(argp); + if (long_val != 40 && long_val != 56 && long_val != 128) { + log_Printf(LogWARN, "%s: Invalid bits value\n", argp); + res = -1; + break; + } + } + + if (arg->argc == arg->argn + 2) { + if (!strcmp(arg->argv[arg->argn + 1], "*")) + l->ccp.cfg.mppe.state = MPPE_ANYSTATE; + else if (!strcasecmp(arg->argv[arg->argn + 1], "stateless")) + l->ccp.cfg.mppe.state = MPPE_STATELESS; + else if (!strcasecmp(arg->argv[arg->argn + 1], "stateful")) + l->ccp.cfg.mppe.state = MPPE_STATEFUL; + else { + log_Printf(LogWARN, "%s: Invalid state value\n", + arg->argv[arg->argn + 1]); + res = -1; + break; + } + } else + l->ccp.cfg.mppe.state = MPPE_ANYSTATE; + l->ccp.cfg.mppe.keybits = long_val; + l->ccp.cfg.mppe.required = 1; + break; +#endif + + case VAR_DEVICE: + physical_SetDeviceList(cx->physical, arg->argc - arg->argn, + arg->argv + arg->argn); + break; + + case VAR_ACCMAP: + if (arg->argc > arg->argn) { + u_long ulong_val; + sscanf(argp, "%lx", &ulong_val); + cx->physical->link.lcp.cfg.accmap = (u_int32_t)ulong_val; + } else { + log_Printf(LogWARN, "No accmap specified\n"); + res = 1; + } + break; + + case VAR_MODE: + mode = Nam2mode(argp); + if (mode == PHYS_NONE || mode == PHYS_ALL) { + log_Printf(LogWARN, "%s: Invalid mode\n", argp); + res = -1; + break; + } + bundle_SetMode(arg->bundle, cx, mode); + break; + + case VAR_MRRU: + switch (bundle_Phase(arg->bundle)) { + case PHASE_DEAD: + break; + case PHASE_ESTABLISH: + /* Make sure none of our links are DATALINK_LCP or greater */ + if (bundle_HighestState(arg->bundle) >= DATALINK_LCP) { + log_Printf(LogWARN, "mrru: Only changable before LCP negotiations\n"); + res = 1; + break; + } + break; + default: + log_Printf(LogWARN, "mrru: Only changable at phase DEAD/ESTABLISH\n"); + res = 1; + break; + } + if (res != 0) + break; + long_val = atol(argp); + if (long_val && long_val < MIN_MRU) { + log_Printf(LogWARN, "MRRU %ld: too small - min %d\n", long_val, MIN_MRU); + res = 1; + break; + } else if (long_val > MAX_MRU) { + log_Printf(LogWARN, "MRRU %ld: too big - max %d\n", long_val, MAX_MRU); + res = 1; + break; + } else + arg->bundle->ncp.mp.cfg.mrru = long_val; + break; + + case VAR_MRU: + long_val = 0; /* silence gcc */ + change = NULL; /* silence gcc */ + switch(arg->argc - arg->argn) { + case 1: + if (argp[strspn(argp, "0123456789")] != '\0') { + res = -1; + break; + } + /*FALLTHRU*/ + case 0: + long_val = atol(argp); + change = &l->lcp.cfg.mru; + if (long_val > l->lcp.cfg.max_mru) { + log_Printf(LogWARN, "MRU %ld: too large - max set to %d\n", long_val, + l->lcp.cfg.max_mru); + res = 1; + break; + } + break; + case 2: + if (strcasecmp(argp, "max") && strcasecmp(argp, "maximum")) { + res = -1; + break; + } + long_val = atol(arg->argv[arg->argn + 1]); + change = &l->lcp.cfg.max_mru; + if (long_val > MAX_MRU) { + log_Printf(LogWARN, "MRU %ld: too large - maximum is %d\n", long_val, + MAX_MRU); + res = 1; + break; + } + break; + default: + res = -1; + break; + } + if (res != 0) + break; + + if (long_val == 0) + *change = 0; + else if (long_val < MIN_MRU) { + log_Printf(LogWARN, "MRU %ld: too small - min %d\n", long_val, MIN_MRU); + res = 1; + break; + } else if (long_val > MAX_MRU) { + log_Printf(LogWARN, "MRU %ld: too big - max %d\n", long_val, MAX_MRU); + res = 1; + break; + } else + *change = long_val; + if (l->lcp.cfg.mru > *change) + l->lcp.cfg.mru = *change; + break; + + case VAR_MTU: + long_val = 0; /* silence gcc */ + change = NULL; /* silence gcc */ + switch(arg->argc - arg->argn) { + case 1: + if (argp[strspn(argp, "0123456789")] != '\0') { + res = -1; + break; + } + /*FALLTHRU*/ + case 0: + long_val = atol(argp); + change = &l->lcp.cfg.mtu; + if (long_val > l->lcp.cfg.max_mtu) { + log_Printf(LogWARN, "MTU %ld: too large - max set to %d\n", long_val, + l->lcp.cfg.max_mtu); + res = 1; + break; + } + break; + case 2: + if (strcasecmp(argp, "max") && strcasecmp(argp, "maximum")) { + res = -1; + break; + } + long_val = atol(arg->argv[arg->argn + 1]); + change = &l->lcp.cfg.max_mtu; + if (long_val > MAX_MTU) { + log_Printf(LogWARN, "MTU %ld: too large - maximum is %d\n", long_val, + MAX_MTU); + res = 1; + break; + } + break; + default: + res = -1; + break; + } + + if (res != 0) + break; + + if (long_val && long_val < MIN_MTU) { + log_Printf(LogWARN, "MTU %ld: too small - min %d\n", long_val, MIN_MTU); + res = 1; + break; + } else if (long_val > MAX_MTU) { + log_Printf(LogWARN, "MTU %ld: too big - max %d\n", long_val, MAX_MTU); + res = 1; + break; + } else + *change = long_val; + if (l->lcp.cfg.mtu > *change) + l->lcp.cfg.mtu = *change; + break; + + case VAR_OPENMODE: + if (strcasecmp(argp, "active") == 0) + cx->physical->link.lcp.cfg.openmode = arg->argc > arg->argn+1 ? + atoi(arg->argv[arg->argn+1]) : 1; + else if (strcasecmp(argp, "passive") == 0) + cx->physical->link.lcp.cfg.openmode = OPEN_PASSIVE; + else { + log_Printf(LogWARN, "%s: Invalid openmode\n", argp); + res = 1; + } + break; + + case VAR_PHONE: + strncpy(cx->cfg.phone.list, argp, sizeof cx->cfg.phone.list - 1); + cx->cfg.phone.list[sizeof cx->cfg.phone.list - 1] = '\0'; + cx->phone.alt = cx->phone.next = NULL; + break; + + case VAR_HANGUP: + strncpy(cx->cfg.script.hangup, argp, sizeof cx->cfg.script.hangup - 1); + cx->cfg.script.hangup[sizeof cx->cfg.script.hangup - 1] = '\0'; + break; + + case VAR_IFQUEUE: + long_val = atol(argp); + arg->bundle->cfg.ifqueue = long_val < 0 ? 0 : long_val; + break; + + case VAR_LOGOUT: + strncpy(cx->cfg.script.logout, argp, sizeof cx->cfg.script.logout - 1); + cx->cfg.script.logout[sizeof cx->cfg.script.logout - 1] = '\0'; + break; + + case VAR_IDLETIMEOUT: + if (arg->argc > arg->argn+2) { + log_Printf(LogWARN, "Too many idle timeout values\n"); + res = 1; + } else if (arg->argc == arg->argn) { + log_Printf(LogWARN, "Too few idle timeout values\n"); + res = 1; + } else { + unsigned long timeout, min; + + timeout = strtoul(argp, NULL, 10); + min = arg->bundle->cfg.idle.min_timeout; + if (arg->argc == arg->argn + 2) + min = strtoul(arg->argv[arg->argn + 1], NULL, 10); + bundle_SetIdleTimer(arg->bundle, timeout, min); + } + break; + +#ifndef NORADIUS + case VAR_RAD_ALIVE: + if (arg->argc > arg->argn + 2) { + log_Printf(LogWARN, "Too many RADIUS alive interval values\n"); + res = 1; + } else if (arg->argc == arg->argn) { + log_Printf(LogWARN, "Too few RADIUS alive interval values\n"); + res = 1; + } else { + arg->bundle->radius.alive.interval = atoi(argp); + if (arg->bundle->radius.alive.interval && !arg->bundle->radius.cfg.file) { + log_Printf(LogWARN, "rad_alive requires radius to be configured\n"); + res = 1; + } else if (arg->bundle->ncp.ipcp.fsm.state == ST_OPENED) { + if (arg->bundle->radius.alive.interval) + radius_StartTimer(arg->bundle); + else + radius_StopTimer(&arg->bundle->radius); + } + } + break; +#endif + + case VAR_LQRPERIOD: + long_val = atol(argp); + if (long_val < MIN_LQRPERIOD) { + log_Printf(LogWARN, "%ld: Invalid lqr period - min %d\n", + long_val, MIN_LQRPERIOD); + res = 1; + } else + l->lcp.cfg.lqrperiod = long_val; + break; + + case VAR_LCPRETRY: + res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn, + &cx->physical->link.lcp.cfg.fsm.timeout, + &cx->physical->link.lcp.cfg.fsm.maxreq, + &cx->physical->link.lcp.cfg.fsm.maxtrm, DEF_FSMTRIES); + break; + + case VAR_CHAPRETRY: + res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn, + &cx->chap.auth.cfg.fsm.timeout, + &cx->chap.auth.cfg.fsm.maxreq, NULL, DEF_FSMAUTHTRIES); + break; + + case VAR_PAPRETRY: + res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn, + &cx->pap.cfg.fsm.timeout, &cx->pap.cfg.fsm.maxreq, + NULL, DEF_FSMAUTHTRIES); + break; + + case VAR_CCPRETRY: + res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn, + &l->ccp.cfg.fsm.timeout, &l->ccp.cfg.fsm.maxreq, + &l->ccp.cfg.fsm.maxtrm, DEF_FSMTRIES); + break; + + case VAR_IPCPRETRY: + res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn, + &arg->bundle->ncp.ipcp.cfg.fsm.timeout, + &arg->bundle->ncp.ipcp.cfg.fsm.maxreq, + &arg->bundle->ncp.ipcp.cfg.fsm.maxtrm, DEF_FSMTRIES); + break; + +#ifndef NOINET6 + case VAR_IPV6CPRETRY: + res = SetRetry(arg->argc - arg->argn, arg->argv + arg->argn, + &arg->bundle->ncp.ipv6cp.cfg.fsm.timeout, + &arg->bundle->ncp.ipv6cp.cfg.fsm.maxreq, + &arg->bundle->ncp.ipv6cp.cfg.fsm.maxtrm, DEF_FSMTRIES); + break; +#endif + + case VAR_NBNS: + case VAR_DNS: + if (param == VAR_DNS) { + ipaddr = arg->bundle->ncp.ipcp.cfg.ns.dns; + ipaddr[0].s_addr = ipaddr[1].s_addr = INADDR_NONE; + } else { + ipaddr = arg->bundle->ncp.ipcp.cfg.ns.nbns; + ipaddr[0].s_addr = ipaddr[1].s_addr = INADDR_ANY; + } + + if (arg->argc > arg->argn) { + ncpaddr_aton(ncpaddr, &arg->bundle->ncp, arg->argv[arg->argn]); + if (!ncpaddr_getip4(ncpaddr, ipaddr)) + return -1; + if (arg->argc > arg->argn+1) { + ncpaddr_aton(ncpaddr + 1, &arg->bundle->ncp, arg->argv[arg->argn + 1]); + if (!ncpaddr_getip4(ncpaddr + 1, ipaddr + 1)) + return -1; + } + + if (ipaddr[0].s_addr == INADDR_ANY) { + ipaddr[0] = ipaddr[1]; + ipaddr[1].s_addr = INADDR_ANY; + } + if (ipaddr[0].s_addr == INADDR_NONE) { + ipaddr[0] = ipaddr[1]; + ipaddr[1].s_addr = INADDR_NONE; + } + } + break; + + case VAR_CALLBACK: + cx->cfg.callback.opmask = 0; + for (dummyint = arg->argn; dummyint < arg->argc; dummyint++) { + if (!strcasecmp(arg->argv[dummyint], "auth")) + cx->cfg.callback.opmask |= CALLBACK_BIT(CALLBACK_AUTH); + else if (!strcasecmp(arg->argv[dummyint], "cbcp")) + cx->cfg.callback.opmask |= CALLBACK_BIT(CALLBACK_CBCP); + else if (!strcasecmp(arg->argv[dummyint], "e.164")) { + if (dummyint == arg->argc - 1) + log_Printf(LogWARN, "No E.164 arg (E.164 ignored) !\n"); + else { + cx->cfg.callback.opmask |= CALLBACK_BIT(CALLBACK_E164); + strncpy(cx->cfg.callback.msg, arg->argv[++dummyint], + sizeof cx->cfg.callback.msg - 1); + cx->cfg.callback.msg[sizeof cx->cfg.callback.msg - 1] = '\0'; + } + } else if (!strcasecmp(arg->argv[dummyint], "none")) + cx->cfg.callback.opmask |= CALLBACK_BIT(CALLBACK_NONE); + else { + res = -1; + break; + } + } + if (cx->cfg.callback.opmask == CALLBACK_BIT(CALLBACK_NONE)) + cx->cfg.callback.opmask = 0; + break; + + case VAR_CBCP: + cx->cfg.cbcp.delay = 0; + *cx->cfg.cbcp.phone = '\0'; + cx->cfg.cbcp.fsmretry = DEF_FSMRETRY; + if (arg->argc > arg->argn) { + strncpy(cx->cfg.cbcp.phone, arg->argv[arg->argn], + sizeof cx->cfg.cbcp.phone - 1); + cx->cfg.cbcp.phone[sizeof cx->cfg.cbcp.phone - 1] = '\0'; + if (arg->argc > arg->argn + 1) { + cx->cfg.cbcp.delay = atoi(arg->argv[arg->argn + 1]); + if (arg->argc > arg->argn + 2) { + long_val = atol(arg->argv[arg->argn + 2]); + if (long_val < MIN_FSMRETRY) + log_Printf(LogWARN, "%ld: Invalid CBCP FSM retry period - min %d\n", + long_val, MIN_FSMRETRY); + else + cx->cfg.cbcp.fsmretry = long_val; + } + } + } + break; + + case VAR_CHOKED: + arg->bundle->cfg.choked.timeout = atoi(argp); + if (arg->bundle->cfg.choked.timeout <= 0) + arg->bundle->cfg.choked.timeout = CHOKED_TIMEOUT; + break; + + case VAR_SENDPIPE: + long_val = atol(argp); + arg->bundle->ncp.cfg.sendpipe = long_val; + break; + + case VAR_RECVPIPE: + long_val = atol(argp); + arg->bundle->ncp.cfg.recvpipe = long_val; + break; + +#ifndef NORADIUS + case VAR_RADIUS: + if (!*argp) + *arg->bundle->radius.cfg.file = '\0'; + else if (access(argp, R_OK)) { + log_Printf(LogWARN, "%s: %s\n", argp, strerror(errno)); + res = 1; + break; + } else { + strncpy(arg->bundle->radius.cfg.file, argp, + sizeof arg->bundle->radius.cfg.file - 1); + arg->bundle->radius.cfg.file + [sizeof arg->bundle->radius.cfg.file - 1] = '\0'; + } + break; +#endif + + case VAR_CD: + if (*argp) { + if (strcasecmp(argp, "off")) { + long_val = atol(argp); + if (long_val < 0) + long_val = 0; + cx->physical->cfg.cd.delay = long_val; + cx->physical->cfg.cd.necessity = argp[strlen(argp)-1] == '!' ? + CD_REQUIRED : CD_VARIABLE; + } else + cx->physical->cfg.cd.necessity = CD_NOTREQUIRED; + } else { + cx->physical->cfg.cd.delay = 0; + cx->physical->cfg.cd.necessity = CD_DEFAULT; + } + break; + + case VAR_PARITY: + if (arg->argc == arg->argn + 1) + res = physical_SetParity(arg->cx->physical, argp); + else { + log_Printf(LogWARN, "Parity value must be odd, even or none\n"); + res = 1; + } + break; + + case VAR_CRTSCTS: + if (strcasecmp(argp, "on") == 0) + physical_SetRtsCts(arg->cx->physical, 1); + else if (strcasecmp(argp, "off") == 0) + physical_SetRtsCts(arg->cx->physical, 0); + else { + log_Printf(LogWARN, "RTS/CTS value must be on or off\n"); + res = 1; + } + break; + + case VAR_URGENTPORTS: + if (arg->argn == arg->argc) { + ncp_SetUrgentTOS(&arg->bundle->ncp); + ncp_ClearUrgentTcpPorts(&arg->bundle->ncp); + ncp_ClearUrgentUdpPorts(&arg->bundle->ncp); + } else if (!strcasecmp(arg->argv[arg->argn], "udp")) { + ncp_SetUrgentTOS(&arg->bundle->ncp); + if (arg->argn == arg->argc - 1) + ncp_ClearUrgentUdpPorts(&arg->bundle->ncp); + else for (f = arg->argn + 1; f < arg->argc; f++) + if (*arg->argv[f] == '+') + ncp_AddUrgentUdpPort(&arg->bundle->ncp, atoi(arg->argv[f] + 1)); + else if (*arg->argv[f] == '-') + ncp_RemoveUrgentUdpPort(&arg->bundle->ncp, atoi(arg->argv[f] + 1)); + else { + if (f == arg->argn) + ncp_ClearUrgentUdpPorts(&arg->bundle->ncp); + ncp_AddUrgentUdpPort(&arg->bundle->ncp, atoi(arg->argv[f])); + } + } else if (arg->argn == arg->argc - 1 && + !strcasecmp(arg->argv[arg->argn], "none")) { + ncp_ClearUrgentTcpPorts(&arg->bundle->ncp); + ncp_ClearUrgentUdpPorts(&arg->bundle->ncp); + ncp_ClearUrgentTOS(&arg->bundle->ncp); + } else { + ncp_SetUrgentTOS(&arg->bundle->ncp); + first = arg->argn; + if (!strcasecmp(arg->argv[first], "tcp") && ++first == arg->argc) + ncp_ClearUrgentTcpPorts(&arg->bundle->ncp); + + for (f = first; f < arg->argc; f++) + if (*arg->argv[f] == '+') + ncp_AddUrgentTcpPort(&arg->bundle->ncp, atoi(arg->argv[f] + 1)); + else if (*arg->argv[f] == '-') + ncp_RemoveUrgentTcpPort(&arg->bundle->ncp, atoi(arg->argv[f] + 1)); + else { + if (f == first) + ncp_ClearUrgentTcpPorts(&arg->bundle->ncp); + ncp_AddUrgentTcpPort(&arg->bundle->ncp, atoi(arg->argv[f])); + } + } + break; + + case VAR_PPPOE: + if (strcasecmp(argp, "3Com") == 0) + physical_SetPPPoEnonstandard(arg->cx->physical, 1); + else if (strcasecmp(argp, "standard") == 0) + physical_SetPPPoEnonstandard(arg->cx->physical, 0); + else { + log_Printf(LogWARN, "PPPoE standard value must be \"standard\" or \"3Com\"\n"); + res = 1; + } + break; + +#ifndef NORADIUS + case VAR_PORT_ID: + if (strcasecmp(argp, "default") == 0) + arg->bundle->radius.port_id_type = RPI_DEFAULT; + else if (strcasecmp(argp, "pid") == 0) + arg->bundle->radius.port_id_type = RPI_PID; + else if (strcasecmp(argp, "ifnum") == 0) + arg->bundle->radius.port_id_type = RPI_IFNUM; + else if (strcasecmp(argp, "tunnum") == 0) + arg->bundle->radius.port_id_type = RPI_TUNNUM; + else { + log_Printf(LogWARN, + "RADIUS port id must be one of \"default\", \"pid\", \"ifnum\" or \"tunnum\"\n"); + res = 1; + } + + if (arg->bundle->radius.port_id_type && !arg->bundle->radius.cfg.file) { + log_Printf(LogWARN, "rad_port_id requires radius to be configured\n"); + res = 1; + } + + break; +#endif + } + + return res; +} + +static struct cmdtab const SetCommands[] = { + {"accmap", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, + "accmap value", "set accmap hex-value", (const void *)VAR_ACCMAP}, + {"authkey", "key", SetVariable, LOCAL_AUTH, + "authentication key", "set authkey|key key", (const void *)VAR_AUTHKEY}, + {"authname", NULL, SetVariable, LOCAL_AUTH, + "authentication name", "set authname name", (const void *)VAR_AUTHNAME}, + {"autoload", NULL, SetVariable, LOCAL_AUTH, + "auto link [de]activation", "set autoload maxtime maxload mintime minload", + (const void *)VAR_AUTOLOAD}, + {"bandwidth", NULL, mp_SetDatalinkBandwidth, LOCAL_AUTH | LOCAL_CX, + "datalink bandwidth", "set bandwidth value", NULL}, + {"callback", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, + "callback control", "set callback [none|auth|cbcp|" + "E.164 *|number[,number]...]...", (const void *)VAR_CALLBACK}, + {"cbcp", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, + "CBCP control", "set cbcp [*|phone[,phone...] [delay [timeout]]]", + (const void *)VAR_CBCP}, + {"ccpretry", "ccpretries", SetVariable, LOCAL_AUTH | LOCAL_CX_OPT, + "CCP retries", "set ccpretry value [attempts]", (const void *)VAR_CCPRETRY}, + {"cd", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "Carrier delay requirement", + "set cd value[!]", (const void *)VAR_CD}, + {"chapretry", "chapretries", SetVariable, LOCAL_AUTH | LOCAL_CX, + "CHAP retries", "set chapretry value [attempts]", + (const void *)VAR_CHAPRETRY}, + {"choked", NULL, SetVariable, LOCAL_AUTH, + "choked timeout", "set choked [secs]", (const void *)VAR_CHOKED}, + {"ctsrts", "crtscts", SetVariable, LOCAL_AUTH | LOCAL_CX, + "Use hardware flow control", "set ctsrts [on|off]", + (const char *)VAR_CRTSCTS}, + {"deflate", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX_OPT, + "deflate window sizes", "set deflate out-winsize in-winsize", + (const void *) VAR_WINSIZE}, +#ifndef NODES + {"mppe", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX_OPT, + "MPPE key size and state", "set mppe [40|56|128|* [stateful|stateless|*]]", + (const void *) VAR_MPPE}, +#endif + {"device", "line", SetVariable, LOCAL_AUTH | LOCAL_CX, + "physical device name", "set device|line device-name[,device-name]", + (const void *) VAR_DEVICE}, + {"dial", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, + "dialing script", "set dial chat-script", (const void *) VAR_DIAL}, + {"dns", NULL, SetVariable, LOCAL_AUTH, "Domain Name Server", + "set dns pri-addr [sec-addr]", (const void *)VAR_DNS}, + {"enddisc", NULL, mp_SetEnddisc, LOCAL_AUTH, + "Endpoint Discriminator", "set enddisc [IP|magic|label|psn value]", NULL}, + {"escape", NULL, SetEscape, LOCAL_AUTH | LOCAL_CX, + "escape characters", "set escape hex-digit ...", NULL}, + {"filter", NULL, filter_Set, LOCAL_AUTH, + "packet filters", "set filter alive|dial|in|out rule-no permit|deny " + "[src_addr[/width]] [dst_addr[/width]] [proto " + "[src [lt|eq|gt port]] [dst [lt|eq|gt port]] [estab] [syn] [finrst]]", NULL}, + {"hangup", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, + "hangup script", "set hangup chat-script", (const void *) VAR_HANGUP}, + {"ifaddr", NULL, SetInterfaceAddr, LOCAL_AUTH, "destination address", + "set ifaddr [src-addr [dst-addr [netmask [trg-addr]]]]", NULL}, + {"ifqueue", NULL, SetVariable, LOCAL_AUTH, "interface queue", + "set ifqueue packets", (const void *)VAR_IFQUEUE}, + {"ipcpretry", "ipcpretries", SetVariable, LOCAL_AUTH, "IPCP retries", + "set ipcpretry value [attempts]", (const void *)VAR_IPCPRETRY}, + {"ipv6cpretry", "ipv6cpretries", SetVariable, LOCAL_AUTH, "IPV6CP retries", + "set ipv6cpretry value [attempts]", (const void *)VAR_IPV6CPRETRY}, + {"lcpretry", "lcpretries", SetVariable, LOCAL_AUTH | LOCAL_CX, "LCP retries", + "set lcpretry value [attempts]", (const void *)VAR_LCPRETRY}, + {"log", NULL, log_SetLevel, LOCAL_AUTH, "log level", + "set log [local] [+|-]all|async|cbcp|ccp|chat|command|connect|debug|dns|hdlc|" + "id0|ipcp|lcp|lqm|phase|physical|radius|sync|tcp/ip|timer|tun...", NULL}, + {"login", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, + "login script", "set login chat-script", (const void *) VAR_LOGIN}, + {"logout", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, + "logout script", "set logout chat-script", (const void *) VAR_LOGOUT}, + {"lqrperiod", "echoperiod", SetVariable, LOCAL_AUTH | LOCAL_CX_OPT, + "LQR period", "set lqr/echo period value", (const void *)VAR_LQRPERIOD}, + {"mode", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "mode value", + "set mode interactive|auto|ddial|background", (const void *)VAR_MODE}, + {"mrru", NULL, SetVariable, LOCAL_AUTH, "MRRU value", + "set mrru value", (const void *)VAR_MRRU}, + {"mru", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, + "MRU value", "set mru [max[imum]] [value]", (const void *)VAR_MRU}, + {"mtu", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, + "interface MTU value", "set mtu [max[imum]] [value]", (const void *)VAR_MTU}, + {"nbns", NULL, SetVariable, LOCAL_AUTH, "NetBIOS Name Server", + "set nbns pri-addr [sec-addr]", (const void *)VAR_NBNS}, + {"openmode", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "open mode", + "set openmode active|passive [secs]", (const void *)VAR_OPENMODE}, + {"papretry", "papretries", SetVariable, LOCAL_AUTH | LOCAL_CX, "PAP retries", + "set papretry value [attempts]", (const void *)VAR_PAPRETRY}, + {"parity", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "serial parity", + "set parity [odd|even|none]", (const void *)VAR_PARITY}, + {"phone", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, "telephone number(s)", + "set phone phone1[:phone2[...]]", (const void *)VAR_PHONE}, + {"proctitle", "title", SetProcTitle, LOCAL_AUTH, + "Process title", "set proctitle [value]", NULL}, +#ifndef NORADIUS + {"radius", NULL, SetVariable, LOCAL_AUTH, + "RADIUS Config", "set radius cfgfile", (const void *)VAR_RADIUS}, + {"rad_alive", NULL, SetVariable, LOCAL_AUTH, + "Raduis alive interval", "set rad_alive value", + (const void *)VAR_RAD_ALIVE}, + {"rad_port_id", NULL, SetVariable, LOCAL_AUTH, + "NAS-Port-Id", "set rad_port_id [default|pid|ifnum|tunnum]", (const void *)VAR_PORT_ID}, +#endif + {"reconnect", NULL, datalink_SetReconnect, LOCAL_AUTH | LOCAL_CX, + "Reconnect timeout", "set reconnect value ntries", NULL}, + {"recvpipe", NULL, SetVariable, LOCAL_AUTH, + "RECVPIPE value", "set recvpipe value", (const void *)VAR_RECVPIPE}, + {"redial", NULL, datalink_SetRedial, LOCAL_AUTH | LOCAL_CX, + "Redial timeout", "set redial secs[+inc[-incmax]][.next] [attempts]", NULL}, + {"sendpipe", NULL, SetVariable, LOCAL_AUTH, + "SENDPIPE value", "set sendpipe value", (const void *)VAR_SENDPIPE}, + {"server", "socket", SetServer, LOCAL_AUTH, "diagnostic port", + "set server|socket TcpPort|LocalName|none|open|closed [password [mask]]", + NULL}, + {"speed", NULL, SetModemSpeed, LOCAL_AUTH | LOCAL_CX, + "physical speed", "set speed value|sync", NULL}, + {"stopped", NULL, SetStoppedTimeout, LOCAL_AUTH | LOCAL_CX, + "STOPPED timeouts", "set stopped [LCPseconds [CCPseconds]]", NULL}, + {"timeout", NULL, SetVariable, LOCAL_AUTH, "Idle timeout", + "set timeout idletime", (const void *)VAR_IDLETIMEOUT}, + {"urgent", NULL, SetVariable, LOCAL_AUTH, "urgent ports", + "set urgent [tcp|udp] [+|-]port...", (const void *)VAR_URGENTPORTS}, + {"vj", NULL, ipcp_vjset, LOCAL_AUTH, + "vj values", "set vj slots|slotcomp [value]", NULL}, + {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH, + "Display this message", "set help|? [command]", SetCommands}, + {"pppoe", NULL, SetVariable, LOCAL_AUTH | LOCAL_CX, + "Connect using standard/3Com mode", "set pppoe [standard|3Com]", + (const char *)VAR_PPPOE}, + {NULL, NULL, NULL, 0, NULL, NULL, NULL}, +}; + +static int +SetCommand(struct cmdargs const *arg) +{ + if (arg->argc > arg->argn) + FindExec(arg->bundle, SetCommands, arg->argc, arg->argn, arg->argv, + arg->prompt, arg->cx); + else if (arg->prompt) + prompt_Printf(arg->prompt, "Use `set ?' to get a list or `set ? ' for" + " syntax help.\n"); + else + log_Printf(LogWARN, "set command must have arguments\n"); + + return 0; +} + +static int +AddCommand(struct cmdargs const *arg) +{ + struct ncpaddr gw; + struct ncprange dest; + struct in_addr host; +#ifndef NOINET6 + struct in6_addr host6; +#endif + int dest_default, gw_arg, addrs; + + if (arg->argc != arg->argn+3 && arg->argc != arg->argn+2) + return -1; + + addrs = 0; + dest_default = 0; + if (arg->argc == arg->argn + 2) { + if (!strcasecmp(arg->argv[arg->argn], "default")) + dest_default = 1; + else { + if (!ncprange_aton(&dest, &arg->bundle->ncp, arg->argv[arg->argn])) + return -1; + if (!strncasecmp(arg->argv[arg->argn], "MYADDR", 6)) + addrs = ROUTE_DSTMYADDR; + else if (!strncasecmp(arg->argv[arg->argn], "MYADDR6", 7)) + addrs = ROUTE_DSTMYADDR6; + else if (!strncasecmp(arg->argv[arg->argn], "HISADDR", 7)) + addrs = ROUTE_DSTHISADDR; + else if (!strncasecmp(arg->argv[arg->argn], "HISADDR6", 8)) + addrs = ROUTE_DSTHISADDR6; + else if (!strncasecmp(arg->argv[arg->argn], "DNS0", 4)) + addrs = ROUTE_DSTDNS0; + else if (!strncasecmp(arg->argv[arg->argn], "DNS1", 4)) + addrs = ROUTE_DSTDNS1; + } + gw_arg = 1; + } else { + if (strcasecmp(arg->argv[arg->argn], "MYADDR") == 0) { + addrs = ROUTE_DSTMYADDR; + host = arg->bundle->ncp.ipcp.my_ip; + } else if (strcasecmp(arg->argv[arg->argn], "HISADDR") == 0) { + addrs = ROUTE_DSTHISADDR; + host = arg->bundle->ncp.ipcp.peer_ip; + } else if (strcasecmp(arg->argv[arg->argn], "DNS0") == 0) { + addrs = ROUTE_DSTDNS0; + host = arg->bundle->ncp.ipcp.ns.dns[0]; + } else if (strcasecmp(arg->argv[arg->argn], "DNS1") == 0) { + addrs = ROUTE_DSTDNS1; + host = arg->bundle->ncp.ipcp.ns.dns[1]; + } else { + host = GetIpAddr(arg->argv[arg->argn]); + if (host.s_addr == INADDR_NONE) { + log_Printf(LogWARN, "%s: Invalid destination address\n", + arg->argv[arg->argn]); + return -1; + } + } + ncprange_setip4(&dest, host, GetIpAddr(arg->argv[arg->argn + 1])); + gw_arg = 2; + } + + if (strcasecmp(arg->argv[arg->argn + gw_arg], "HISADDR") == 0) { + ncpaddr_setip4(&gw, arg->bundle->ncp.ipcp.peer_ip); + addrs |= ROUTE_GWHISADDR; +#ifndef NOINET6 + } else if (strcasecmp(arg->argv[arg->argn + gw_arg], "HISADDR6") == 0) { + if (!ncpaddr_getip6(&arg->bundle->ncp.ipv6cp.hisaddr, &host6)) + memset(&host6, '\0', sizeof host6); + ncpaddr_setip6(&gw, &host6); + addrs |= ROUTE_GWHISADDR6; +#endif + } else { + if (!ncpaddr_aton(&gw, &arg->bundle->ncp, arg->argv[arg->argn + gw_arg])) { + log_Printf(LogWARN, "%s: Invalid gateway address\n", + arg->argv[arg->argn + gw_arg]); + return -1; + } + } + + if (dest_default) + ncprange_setdefault(&dest, ncpaddr_family(&gw)); + + if (rt_Set(arg->bundle, RTM_ADD, &dest, &gw, arg->cmd->args ? 1 : 0, + ((addrs & ROUTE_GWHISADDR) || (addrs & ROUTE_GWHISADDR6)) ? 1 : 0) + && addrs != ROUTE_STATIC) + route_Add(&arg->bundle->ncp.route, addrs, &dest, &gw); + + return 0; +} + +static int +DeleteCommand(struct cmdargs const *arg) +{ + struct ncprange dest; + int addrs; + + if (arg->argc == arg->argn+1) { + if(strcasecmp(arg->argv[arg->argn], "all") == 0) { + route_IfDelete(arg->bundle, 0); + route_DeleteAll(&arg->bundle->ncp.route); + } else { + addrs = 0; + if (strcasecmp(arg->argv[arg->argn], "MYADDR") == 0) { + ncprange_setip4host(&dest, arg->bundle->ncp.ipcp.my_ip); + addrs = ROUTE_DSTMYADDR; +#ifndef NOINET6 + } else if (strcasecmp(arg->argv[arg->argn], "MYADDR6") == 0) { + ncprange_sethost(&dest, &arg->bundle->ncp.ipv6cp.myaddr); + addrs = ROUTE_DSTMYADDR6; +#endif + } else if (strcasecmp(arg->argv[arg->argn], "HISADDR") == 0) { + ncprange_setip4host(&dest, arg->bundle->ncp.ipcp.peer_ip); + addrs = ROUTE_DSTHISADDR; +#ifndef NOINET6 + } else if (strcasecmp(arg->argv[arg->argn], "HISADDR6") == 0) { + ncprange_sethost(&dest, &arg->bundle->ncp.ipv6cp.hisaddr); + addrs = ROUTE_DSTHISADDR6; +#endif + } else if (strcasecmp(arg->argv[arg->argn], "DNS0") == 0) { + ncprange_setip4host(&dest, arg->bundle->ncp.ipcp.ns.dns[0]); + addrs = ROUTE_DSTDNS0; + } else if (strcasecmp(arg->argv[arg->argn], "DNS1") == 0) { + ncprange_setip4host(&dest, arg->bundle->ncp.ipcp.ns.dns[1]); + addrs = ROUTE_DSTDNS1; + } else { + ncprange_aton(&dest, &arg->bundle->ncp, arg->argv[arg->argn]); + addrs = ROUTE_STATIC; + } + rt_Set(arg->bundle, RTM_DELETE, &dest, NULL, arg->cmd->args ? 1 : 0, 0); + route_Delete(&arg->bundle->ncp.route, addrs, &dest); + } + } else + return -1; + + return 0; +} + +#ifndef NONAT +static int +NatEnable(struct cmdargs const *arg) +{ + if (arg->argc == arg->argn+1) { + if (strcasecmp(arg->argv[arg->argn], "yes") == 0) { + if (!arg->bundle->NatEnabled) { + if (arg->bundle->ncp.ipcp.fsm.state == ST_OPENED) + PacketAliasSetAddress(arg->bundle->ncp.ipcp.my_ip); + arg->bundle->NatEnabled = 1; + } + return 0; + } else if (strcasecmp(arg->argv[arg->argn], "no") == 0) { + arg->bundle->NatEnabled = 0; + opt_disable(arg->bundle, OPT_IFACEALIAS); + /* Don't iface_Clear() - there may be manually configured addresses */ + return 0; + } + } + + return -1; +} + + +static int +NatOption(struct cmdargs const *arg) +{ + long param = (long)arg->cmd->args; + + if (arg->argc == arg->argn+1) { + if (strcasecmp(arg->argv[arg->argn], "yes") == 0) { + if (arg->bundle->NatEnabled) { + PacketAliasSetMode(param, param); + return 0; + } + log_Printf(LogWARN, "nat not enabled\n"); + } else if (strcmp(arg->argv[arg->argn], "no") == 0) { + if (arg->bundle->NatEnabled) { + PacketAliasSetMode(0, param); + return 0; + } + log_Printf(LogWARN, "nat not enabled\n"); + } + } + return -1; +} +#endif /* #ifndef NONAT */ + +static int +LinkCommand(struct cmdargs const *arg) +{ + if (arg->argc > arg->argn+1) { + char namelist[LINE_LEN]; + struct datalink *cx; + char *name; + int result = 0; + + if (!strcmp(arg->argv[arg->argn], "*")) { + struct datalink *dl; + + cx = arg->bundle->links; + while (cx) { + /* Watch it, the command could be a ``remove'' */ + dl = cx->next; + FindExec(arg->bundle, Commands, arg->argc, arg->argn+1, arg->argv, + arg->prompt, cx); + for (cx = arg->bundle->links; cx; cx = cx->next) + if (cx == dl) + break; /* Pointer's still valid ! */ + } + } else { + strncpy(namelist, arg->argv[arg->argn], sizeof namelist - 1); + namelist[sizeof namelist - 1] = '\0'; + for(name = strtok(namelist, ", "); name; name = strtok(NULL,", ")) + if (!bundle2datalink(arg->bundle, name)) { + log_Printf(LogWARN, "link: %s: Invalid link name\n", name); + return 1; + } + + strncpy(namelist, arg->argv[arg->argn], sizeof namelist - 1); + namelist[sizeof namelist - 1] = '\0'; + for(name = strtok(namelist, ", "); name; name = strtok(NULL,", ")) { + cx = bundle2datalink(arg->bundle, name); + if (cx) + FindExec(arg->bundle, Commands, arg->argc, arg->argn+1, arg->argv, + arg->prompt, cx); + else { + log_Printf(LogWARN, "link: %s: Invalidated link name !\n", name); + result++; + } + } + } + return result; + } + + log_Printf(LogWARN, "usage: %s\n", arg->cmd->syntax); + return 2; +} + +struct link * +command_ChooseLink(struct cmdargs const *arg) +{ + if (arg->cx) + return &arg->cx->physical->link; + else if (!arg->bundle->ncp.mp.cfg.mrru) { + struct datalink *dl = bundle2datalink(arg->bundle, NULL); + if (dl) + return &dl->physical->link; + } + return &arg->bundle->ncp.mp.link; +} + +static const char * +ident_cmd(const char *cmd, unsigned *keep, unsigned *add) +{ + const char *result; + + switch (*cmd) { + case 'A': + case 'a': + result = "accept"; + *keep = NEG_MYMASK; + *add = NEG_ACCEPTED; + break; + case 'D': + case 'd': + switch (cmd[1]) { + case 'E': + case 'e': + result = "deny"; + *keep = NEG_MYMASK; + *add = 0; + break; + case 'I': + case 'i': + result = "disable"; + *keep = NEG_HISMASK; + *add = 0; + break; + default: + return NULL; + } + break; + case 'E': + case 'e': + result = "enable"; + *keep = NEG_HISMASK; + *add = NEG_ENABLED; + break; + default: + return NULL; + } + + return result; +} + +static int +OptSet(struct cmdargs const *arg) +{ + int opt = (int)(long)arg->cmd->args; + unsigned keep; /* Keep this opt */ + unsigned add; /* Add this opt */ + + if (ident_cmd(arg->argv[arg->argn - 2], &keep, &add) == NULL) + return 1; + +#ifndef NOINET6 + if (add == NEG_ENABLED && opt == OPT_IPV6CP && !probe.ipv6_available) { + log_Printf(LogWARN, "IPv6 is not available on this machine\n"); + return 1; + } +#endif + if (!add && ((opt == OPT_NAS_IP_ADDRESS && + !Enabled(arg->bundle, OPT_NAS_IDENTIFIER)) || + (opt == OPT_NAS_IDENTIFIER && + !Enabled(arg->bundle, OPT_NAS_IP_ADDRESS)))) { + log_Printf(LogWARN, + "Cannot disable both NAS-IP-Address and NAS-Identifier\n"); + return 1; + } + + if (add) + opt_enable(arg->bundle, opt); + else + opt_disable(arg->bundle, opt); + + return 0; +} + +static int +IfaceAliasOptSet(struct cmdargs const *arg) +{ + unsigned long long save = arg->bundle->cfg.optmask; + int result = OptSet(arg); + + if (result == 0) + if (Enabled(arg->bundle, OPT_IFACEALIAS) && !arg->bundle->NatEnabled) { + arg->bundle->cfg.optmask = save; + log_Printf(LogWARN, "Cannot enable iface-alias without NAT\n"); + result = 2; + } + + return result; +} + +static int +NegotiateSet(struct cmdargs const *arg) +{ + long param = (long)arg->cmd->args; + struct link *l = command_ChooseLink(arg); /* LOCAL_CX_OPT uses this */ + struct datalink *cx = arg->cx; /* LOCAL_CX uses this */ + const char *cmd; + unsigned keep; /* Keep these bits */ + unsigned add; /* Add these bits */ + + if ((cmd = ident_cmd(arg->argv[arg->argn-2], &keep, &add)) == NULL) + return 1; + + if ((arg->cmd->lauth & LOCAL_CX) && !cx) { + log_Printf(LogWARN, "%s %s: No context (use the `link' command)\n", + cmd, arg->cmd->name); + return 2; + } else if (cx && !(arg->cmd->lauth & (LOCAL_CX|LOCAL_CX_OPT))) { + log_Printf(LogWARN, "%s %s: Redundant context (%s) ignored\n", + cmd, arg->cmd->name, cx->name); + cx = NULL; + } + + switch (param) { + case NEG_ACFCOMP: + cx->physical->link.lcp.cfg.acfcomp &= keep; + cx->physical->link.lcp.cfg.acfcomp |= add; + break; + case NEG_CHAP05: + cx->physical->link.lcp.cfg.chap05 &= keep; + cx->physical->link.lcp.cfg.chap05 |= add; + break; +#ifndef NODES + case NEG_CHAP80: + cx->physical->link.lcp.cfg.chap80nt &= keep; + cx->physical->link.lcp.cfg.chap80nt |= add; + break; + case NEG_CHAP80LM: + cx->physical->link.lcp.cfg.chap80lm &= keep; + cx->physical->link.lcp.cfg.chap80lm |= add; + break; + case NEG_CHAP81: + cx->physical->link.lcp.cfg.chap81 &= keep; + cx->physical->link.lcp.cfg.chap81 |= add; + break; + case NEG_MPPE: + l->ccp.cfg.neg[CCP_NEG_MPPE] &= keep; + l->ccp.cfg.neg[CCP_NEG_MPPE] |= add; + break; +#endif + case NEG_DEFLATE: + l->ccp.cfg.neg[CCP_NEG_DEFLATE] &= keep; + l->ccp.cfg.neg[CCP_NEG_DEFLATE] |= add; + break; + case NEG_DNS: + arg->bundle->ncp.ipcp.cfg.ns.dns_neg &= keep; + arg->bundle->ncp.ipcp.cfg.ns.dns_neg |= add; + break; + case NEG_ECHO: /* probably misplaced in this function ! */ + if (cx->physical->link.lcp.cfg.echo && !add) { + cx->physical->link.lcp.cfg.echo = 0; + cx->physical->hdlc.lqm.method &= ~LQM_ECHO; + if (cx->physical->hdlc.lqm.method & LQM_ECHO && + !cx->physical->link.lcp.want_lqrperiod && + cx->physical->hdlc.lqm.timer.load) { + cx->physical->hdlc.lqm.timer.load = 0; + lqr_StopTimer(cx->physical); + } + } else if (!cx->physical->link.lcp.cfg.echo && add) { + cx->physical->link.lcp.cfg.echo = 1; + cx->physical->hdlc.lqm.method |= LQM_ECHO; + cx->physical->hdlc.lqm.timer.load = + cx->physical->link.lcp.cfg.lqrperiod * SECTICKS; + if (cx->physical->link.lcp.fsm.state == ST_OPENED) + (*cx->physical->hdlc.lqm.timer.func)(&cx->physical->link.lcp); + } + break; + case NEG_ENDDISC: + arg->bundle->ncp.mp.cfg.negenddisc &= keep; + arg->bundle->ncp.mp.cfg.negenddisc |= add; + break; + case NEG_LQR: + cx->physical->link.lcp.cfg.lqr &= keep; + cx->physical->link.lcp.cfg.lqr |= add; + break; + case NEG_PAP: + cx->physical->link.lcp.cfg.pap &= keep; + cx->physical->link.lcp.cfg.pap |= add; + break; + case NEG_PPPDDEFLATE: + l->ccp.cfg.neg[CCP_NEG_DEFLATE24] &= keep; + l->ccp.cfg.neg[CCP_NEG_DEFLATE24] |= add; + break; + case NEG_PRED1: + l->ccp.cfg.neg[CCP_NEG_PRED1] &= keep; + l->ccp.cfg.neg[CCP_NEG_PRED1] |= add; + break; + case NEG_PROTOCOMP: + cx->physical->link.lcp.cfg.protocomp &= keep; + cx->physical->link.lcp.cfg.protocomp |= add; + break; + case NEG_SHORTSEQ: + switch (bundle_Phase(arg->bundle)) { + case PHASE_DEAD: + break; + case PHASE_ESTABLISH: + /* Make sure none of our links are DATALINK_LCP or greater */ + if (bundle_HighestState(arg->bundle) >= DATALINK_LCP) { + log_Printf(LogWARN, "shortseq: Only changable before" + " LCP negotiations\n"); + return 1; + } + break; + default: + log_Printf(LogWARN, "shortseq: Only changable at phase" + " DEAD/ESTABLISH\n"); + return 1; + } + arg->bundle->ncp.mp.cfg.shortseq &= keep; + arg->bundle->ncp.mp.cfg.shortseq |= add; + break; + case NEG_VJCOMP: + arg->bundle->ncp.ipcp.cfg.vj.neg &= keep; + arg->bundle->ncp.ipcp.cfg.vj.neg |= add; + break; + } + + return 0; +} + +static struct cmdtab const NegotiateCommands[] = { + {"echo", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX, "Send echo requests", + "disable|enable", (const void *)NEG_ECHO}, + {"filter-decapsulation", NULL, OptSet, LOCAL_AUTH, + "filter on PPPoUDP payloads", "disable|enable", + (const void *)OPT_FILTERDECAP}, + {"force-scripts", NULL, OptSet, LOCAL_AUTH, + "Force execution of the configured chat scripts", "disable|enable", + (const void *)OPT_FORCE_SCRIPTS}, + {"idcheck", NULL, OptSet, LOCAL_AUTH, "Check FSM reply ids", + "disable|enable", (const void *)OPT_IDCHECK}, + {"iface-alias", NULL, IfaceAliasOptSet, LOCAL_AUTH, + "retain interface addresses", "disable|enable", + (const void *)OPT_IFACEALIAS}, +#ifndef NOINET6 + {"ipcp", NULL, OptSet, LOCAL_AUTH, "IP Network Control Protocol", + "disable|enable", (const void *)OPT_IPCP}, + {"ipv6cp", NULL, OptSet, LOCAL_AUTH, "IPv6 Network Control Protocol", + "disable|enable", (const void *)OPT_IPV6CP}, +#endif + {"keep-session", NULL, OptSet, LOCAL_AUTH, "Retain device session leader", + "disable|enable", (const void *)OPT_KEEPSESSION}, + {"loopback", NULL, OptSet, LOCAL_AUTH, "Loop packets for local iface", + "disable|enable", (const void *)OPT_LOOPBACK}, + {"nas-ip-address", NULL, OptSet, LOCAL_AUTH, "Send NAS-IP-Address to RADIUS", + "disable|enable", (const void *)OPT_NAS_IP_ADDRESS}, + {"nas-identifier", NULL, OptSet, LOCAL_AUTH, "Send NAS-Identifier to RADIUS", + "disable|enable", (const void *)OPT_NAS_IDENTIFIER}, + {"passwdauth", NULL, OptSet, LOCAL_AUTH, "Use passwd file", + "disable|enable", (const void *)OPT_PASSWDAUTH}, + {"proxy", NULL, OptSet, LOCAL_AUTH, "Create a proxy ARP entry", + "disable|enable", (const void *)OPT_PROXY}, + {"proxyall", NULL, OptSet, LOCAL_AUTH, "Proxy ARP for all remote hosts", + "disable|enable", (const void *)OPT_PROXYALL}, + {"sroutes", NULL, OptSet, LOCAL_AUTH, "Use sticky routes", + "disable|enable", (const void *)OPT_SROUTES}, + {"tcpmssfixup", "mssfixup", OptSet, LOCAL_AUTH, "Modify MSS options", + "disable|enable", (const void *)OPT_TCPMSSFIXUP}, + {"throughput", NULL, OptSet, LOCAL_AUTH, "Rolling throughput", + "disable|enable", (const void *)OPT_THROUGHPUT}, + {"utmp", NULL, OptSet, LOCAL_AUTH, "Log connections in utmp", + "disable|enable", (const void *)OPT_UTMP}, + +#ifndef NOINET6 +#define NEG_OPT_MAX 17 /* accept/deny allowed below and not above */ +#else +#define NEG_OPT_MAX 15 +#endif + + {"acfcomp", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX, + "Address & Control field compression", "accept|deny|disable|enable", + (const void *)NEG_ACFCOMP}, + {"chap", "chap05", NegotiateSet, LOCAL_AUTH | LOCAL_CX, + "Challenge Handshake Authentication Protocol", "accept|deny|disable|enable", + (const void *)NEG_CHAP05}, +#ifndef NODES + {"mschap", "chap80nt", NegotiateSet, LOCAL_AUTH | LOCAL_CX, + "Microsoft (NT) CHAP", "accept|deny|disable|enable", + (const void *)NEG_CHAP80}, + {"LANMan", "chap80lm", NegotiateSet, LOCAL_AUTH | LOCAL_CX, + "Microsoft (NT) CHAP", "accept|deny|disable|enable", + (const void *)NEG_CHAP80LM}, + {"mschapv2", "chap81", NegotiateSet, LOCAL_AUTH | LOCAL_CX, + "Microsoft CHAP v2", "accept|deny|disable|enable", + (const void *)NEG_CHAP81}, + {"mppe", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX_OPT, + "MPPE encryption", "accept|deny|disable|enable", + (const void *)NEG_MPPE}, +#endif + {"deflate", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX_OPT, + "Deflate compression", "accept|deny|disable|enable", + (const void *)NEG_DEFLATE}, + {"deflate24", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX_OPT, + "Deflate (type 24) compression", "accept|deny|disable|enable", + (const void *)NEG_PPPDDEFLATE}, + {"dns", NULL, NegotiateSet, LOCAL_AUTH, + "DNS specification", "accept|deny|disable|enable", (const void *)NEG_DNS}, + {"enddisc", NULL, NegotiateSet, LOCAL_AUTH, "ENDDISC negotiation", + "accept|deny|disable|enable", (const void *)NEG_ENDDISC}, + {"lqr", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX, + "Link Quality Reports", "accept|deny|disable|enable", + (const void *)NEG_LQR}, + {"pap", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX, + "Password Authentication protocol", "accept|deny|disable|enable", + (const void *)NEG_PAP}, + {"pred1", "predictor1", NegotiateSet, LOCAL_AUTH | LOCAL_CX_OPT, + "Predictor 1 compression", "accept|deny|disable|enable", + (const void *)NEG_PRED1}, + {"protocomp", NULL, NegotiateSet, LOCAL_AUTH | LOCAL_CX, + "Protocol field compression", "accept|deny|disable|enable", + (const void *)NEG_PROTOCOMP}, + {"shortseq", NULL, NegotiateSet, LOCAL_AUTH, + "MP Short Sequence Numbers", "accept|deny|disable|enable", + (const void *)NEG_SHORTSEQ}, + {"vjcomp", NULL, NegotiateSet, LOCAL_AUTH, + "Van Jacobson header compression", "accept|deny|disable|enable", + (const void *)NEG_VJCOMP}, + {"help", "?", HelpCommand, LOCAL_AUTH | LOCAL_NO_AUTH, + "Display this message", "accept|deny|disable|enable help|? [value]", + NegotiateCommands}, + {NULL, NULL, NULL, 0, NULL, NULL, NULL}, +}; + +static int +NegotiateCommand(struct cmdargs const *arg) +{ + if (arg->argc > arg->argn) { + char const *argv[3]; + unsigned keep, add; + int n; + + if ((argv[0] = ident_cmd(arg->argv[arg->argn-1], &keep, &add)) == NULL) + return -1; + argv[2] = NULL; + + for (n = arg->argn; n < arg->argc; n++) { + argv[1] = arg->argv[n]; + FindExec(arg->bundle, NegotiateCommands + (keep == NEG_HISMASK ? + 0 : NEG_OPT_MAX), 2, 1, argv, arg->prompt, arg->cx); + } + } else if (arg->prompt) + prompt_Printf(arg->prompt, "Use `%s ?' to get a list.\n", + arg->argv[arg->argn-1]); + else + log_Printf(LogWARN, "%s command must have arguments\n", + arg->argv[arg->argn] ); + + return 0; +} + +const char * +command_ShowNegval(unsigned val) +{ + switch (val&3) { + case 1: return "disabled & accepted"; + case 2: return "enabled & denied"; + case 3: return "enabled & accepted"; + } + return "disabled & denied"; +} + +static int +ClearCommand(struct cmdargs const *arg) +{ + struct pppThroughput *t; + struct datalink *cx; + int i, clear_type; + + if (arg->argc < arg->argn + 1) + return -1; + + if (strcasecmp(arg->argv[arg->argn], "physical") == 0) { + cx = arg->cx; + if (!cx) + cx = bundle2datalink(arg->bundle, NULL); + if (!cx) { + log_Printf(LogWARN, "A link must be specified for ``clear physical''\n"); + return 1; + } + t = &cx->physical->link.stats.total; + } else if (strcasecmp(arg->argv[arg->argn], "ipcp") == 0) + t = &arg->bundle->ncp.ipcp.throughput; +#ifndef NOINET6 + else if (strcasecmp(arg->argv[arg->argn], "ipv6cp") == 0) + t = &arg->bundle->ncp.ipv6cp.throughput; +#endif + else + return -1; + + if (arg->argc > arg->argn + 1) { + clear_type = 0; + for (i = arg->argn + 1; i < arg->argc; i++) + if (strcasecmp(arg->argv[i], "overall") == 0) + clear_type |= THROUGHPUT_OVERALL; + else if (strcasecmp(arg->argv[i], "current") == 0) + clear_type |= THROUGHPUT_CURRENT; + else if (strcasecmp(arg->argv[i], "peak") == 0) + clear_type |= THROUGHPUT_PEAK; + else + return -1; + } else + clear_type = THROUGHPUT_ALL; + + throughput_clear(t, clear_type, arg->prompt); + return 0; +} + +static int +RunListCommand(struct cmdargs const *arg) +{ + const char *cmd = arg->argc ? arg->argv[arg->argc - 1] : "???"; + +#ifndef NONAT + if (arg->cmd->args == NatCommands && + tolower(*arg->argv[arg->argn - 1]) == 'a') { + if (arg->prompt) + prompt_Printf(arg->prompt, "The alias command is deprecated\n"); + else + log_Printf(LogWARN, "The alias command is deprecated\n"); + } +#endif + + if (arg->argc > arg->argn) + FindExec(arg->bundle, arg->cmd->args, arg->argc, arg->argn, arg->argv, + arg->prompt, arg->cx); + else if (arg->prompt) + prompt_Printf(arg->prompt, "Use `%s help' to get a list or `%s help" + "