# # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # cmd/cmd-inet/usr.bin/pppd/Makefile # # Copyright (c) 2019, Joyent, Inc. include ../../../Makefile.cmd include Makefile.def PROG= pppd SUBDIRS= plugins OBJS= auth.o ccp.o chap.o demand.o fsm.o ipcp.o ipv6cp.o \ lcp.o magic.o main.o options.o sys-solaris.o upap.o utils.o \ multilink.o cbcp.o # Object tdb.o used only for Multilink; not supported yet. all: TARGET= all install: TARGET= install clean: TARGET= clean clobber: TARGET= clobber lint: TARGET= lint LDLIBS += -lpam -lmd -lsocket -lnsl -ldlpi # # We need absolute path to /etc/ppp/plugins and /usr/lib/inet/ppp, not # that of the proto area # LDFLAGS += -R$(ETCPPPPLUGINDIR_ABS) -R$(LIBPPPPLUGINDIR_ABS) CPPFLAGS += -DPLUGIN -DSVR4 -DSOL2 -DINET6 CPPFLAGS += -D_PATH_VARRUN='"/var/run/"' CPPFLAGS += -DNEGOTIATE_FCS -DCBCP_SUPPORT -DALLOW_PAM -DHAS_SHADOW CPPFLAGS += -DHAVE_MMAP -DCOMP_TUNE -DMUX_FRAME $(NOT_RELEASE_BUILD)CPPFLAGS += -DDEBUG .KEEP_STATE: .PARALLEL: $(SUBDIRS) all: $(PROG) $(SUBDIRS) # MS-CHAP support CPPFLAGS += -DHAVE_CRYPT_H -DUSE_CRYPT -DHAVE_LIBMD CPPFLAGS += -DCHAPMS -DMSLANMAN CPPFLAGS += -DCHAPMSV2 OBJS += chap_ms.o EXOBJS += mschap_test.o CLOBBERFILES += mschap_test CERRWARN += $(CNOWARN_UNINIT) # Hammerhead: Suppress pointer/int cast warnings in legacy pppd code CERRWARN += -Wno-pointer-to-int-cast CERRWARN += -Wno-int-to-pointer-cast # main() is too hairy for smatch SMATCH=off # This is used *only* for testing the portability of the libraries # required for MS-CHAPv1. It is not needed in any normal system and # is not built by default. mschap_test: mschap_test.o chap_ms.o $(LINK.c) -o mschap_test mschap_test.o chap_ms.o $(LDFLAGS) -lmd @echo "Run with 'mschap_test 00000000000000000000000000000000 hello'" @echo @echo "Output should be:" @echo @echo " MS-CHAPv1 with LAN Manager -- 49 bytes:" @echo " C9 CA EE 9B 1C A7 87 04" @echo " 79 36 8C 55 AB 88 EC 5A" @echo " 57 E9 A1 B7 95 40 C3 74" @echo " F4 D9 9D AF 82 64 DC 3C" @echo " 53 F9 BC 92 14 B5 5D 9E" @echo " 78 C4 21 48 9D B7 A8 B4" @echo " 01" @echo " MS-CHAPv2 -- 49 bytes:" @echo " xx xx xx xx xx xx xx xx" @echo " xx xx xx xx xx xx xx xx" @echo " 00 00 00 00 00 00 00 00" @echo " xx xx xx xx xx xx xx xx" @echo " xx xx xx xx xx xx xx xx" @echo " xx xx xx xx xx xx xx xx" @echo " 00" .PARALLEL: $(OBJS) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) $(ROOTPROG): FILEMODE = 04555 ASPPP2PPPD= $(ROOTUSRSBIN)/asppp2pppd $(ASPPP2PPPD): FILEMODE = 0550 install: $(PROG) .WAIT $(SUBDIRS) $(ROOTPROG) $(ETCPPPDIR) $(ASPPP2PPPD) $(ETCPPPDIR)/%: % $(INS.file) $(ETCPPPDIR): $(INS.dir) $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) FRC: clean: $(SUBDIRS) $(RM) $(OBJS) $(EXOBJS) clobber: $(SUBDIRS) include ../../../Makefile.targ # # Copyright (c) 2000 by Sun Microsystems, Inc. # All rights reserved. # # cmd/cmd-inet/usr.bin/pppd/Makefile.def # ETCPPPDIR= $(ROOTETC)/ppp ETCPPPPLUGINDIR_ABS= /etc/ppp/plugins LIBPPPPLUGINDIR= $(ROOT)/usr/lib/inet/ppp LIBPPPPLUGINDIR_ABS= /usr/lib/inet/ppp * Copyright (c) 1993, 1999 The Australian National 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 the Australian National 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. * Copyright (c) 1994 The Australian National University. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. This software is provided without any * warranty, express or implied. The Australian National University * makes no representations about the suitability of this software for * any purpose. * * IN NO EVENT SHALL THE AUSTRALIAN NATIONAL UNIVERSITY BE LIABLE TO ANY * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF * THE AUSTRALIAN NATIONAL UNIVERSITY HAVE BEEN ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * THE AUSTRALIAN NATIONAL UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE AUSTRALIAN NATIONAL UNIVERSITY HAS NO * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, * OR MODIFICATIONS. * 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. * Copyright (c) 1995 Pedro Roque Marques * 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 Pedro Roque Marques. The name of the author 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. * Copyright (c) 1991 Gregory M. Christy. * 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 Gregory M. Christy. The name of the author 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. * Copyright (c) 1995 Eric Rosenquist, Strata Software Limited. * http://www.strataware.com/ * * 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 Eric Rosenquist. The name of the author 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. ipv6cp.h - PPP IPV6 Control Protocol. Copyright (C) 1999 Tommi Komulainen 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. The name of the author 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. */ /* Original version, based on RFC2023 : Copyright (c) 1995, 1996, 1997 Francis.Dupont@inria.fr, INRIA Rocquencourt, Alain.Durand@imag.fr, IMAG, Jean-Luc.Richier@imag.fr, IMAG-LSR. Copyright (c) 1998, 1999 Francis.Dupont@inria.fr, GIE DYADE, Alain.Durand@imag.fr, IMAG, Jean-Luc.Richier@imag.fr, IMAG-LSR. Ce travail a \351t\351 fait au sein du GIE DYADE (Groupement d'Int\351r\352t \311conomique ayant pour membres BULL S.A. et l'INRIA). Ce logiciel informatique est disponible aux conditions usuelles dans la recherche, c'est-\340-dire qu'il peut \352tre utilis\351, copi\351, modifi\351, distribu\351 \340 l'unique condition que ce texte soit conserv\351 afin que l'origine de ce logiciel soit reconnue. Le nom de l'Institut National de Recherche en Informatique et en Automatique (INRIA), de l'IMAG, ou d'une personne morale ou physique ayant particip\351 \340 l'\351laboration de ce logiciel ne peut \352tre utilis\351 sans son accord pr\351alable explicite. Ce logiciel est fourni tel quel sans aucune garantie, support ou responsabilit\351 d'aucune sorte. Ce logiciel est d\351riv\351 de sources d'origine "University of California at Berkeley" et "Digital Equipment Corporation" couvertes par des copyrights. L'Institut d'Informatique et de Math\351matiques Appliqu\351es de Grenoble (IMAG) est une f\351d\351ration d'unit\351s mixtes de recherche du CNRS, de l'Institut National Polytechnique de Grenoble et de l'Universit\351 Joseph Fourier regroupant sept laboratoires dont le laboratoire Logiciels, Syst\350mes, R\351seaux (LSR). This work has been done in the context of GIE DYADE (joint R & D venture between BULL S.A. and INRIA). This software is available with usual "research" terms with the aim of retain credits of the software. Permission to use, copy, modify and distribute this software for any purpose and without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies, and the name of INRIA, IMAG, or any contributor not be used in advertising or publicity pertaining to this material without the prior explicit permission. The software is provided "as is" without any warranties, support or liabilities of any kind. This software is derived from source code from "University of California at Berkeley" and "Digital Equipment Corporation" protected by copyrights. Grenoble's Institute of Computer Science and Applied Mathematics (IMAG) is a federation of seven research units funded by the CNRS, National Polytechnic Institute of Grenoble and University Joseph Fourier. The research unit in Software, Systems, Networks (LSR) is member of IMAG. */ * Copyright (c) 2000 Paul Mackerras. * 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. The name of the author 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. * The basic framework for this code came from the reference * implementation for MD5. That implementation is Copyright (C) * 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. * * License to copy and use this software is granted provided that it * is identified as the "RSA Data Security, Inc. MD5 Message-Digest * Algorithm" in all material mentioning or referencing this software * or this function. * * License is also granted to make and use derivative works provided * that such works are identified as "derived from the RSA Data * Security, Inc. MD5 Message-Digest Algorithm" in all material * mentioning or referencing the derived work. * * RSA Data Security, Inc. makes no representations concerning either * the merchantability of this software or the suitability of this * software for any particular purpose. It is provided "as is" * without express or implied warranty of any kind. * * These notices must be retained in any copies of any part of this * documentation and/or software. * * NOTE: Cleaned-up and optimized, version of SHA1, based on the FIPS 180-1 * standard, available at http://www.itl.nist.gov/div897/pubs/fip180-1.htm * Not as fast as one would like -- further optimizations are encouraged * and appreciated. Copyright (c) 1985, 1986 The Regents of the University of California. All rights reserved. This code is derived from software contributed to Berkeley by James A. Woods, derived from original work by Spencer Thomas and Joseph Orost. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the University of California, Berkeley and its contributors. 4. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright (c) 1989 Regents of the University of California. 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 the University of California, Berkeley. 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. PPP SOFTWARE #!/usr/bin/perl # This utility translates from aspppd configuration to Solaris PPP 4.0 # (or ANU ppp-2.4.0; aka pppd). It can also revert to previous aspppd # configuration (discarding the pppd configuration), but does not # translate new configuration files into old. # # This script provides only a suggested translation for your existing # aspppd configuration. You will need to evaluate for yourself # whether the translation is appropriate for your operating # environment. # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Steps in translation: # - parse /etc/asppp.cf # - check for aspppls in /etc/passwd (or NIS) # - read in current /etc/ppp/options configuration file # - read list of configured serial ports from pmadm # - read in UUCP configuration files # - create translated configuration # - write files back out # Known issues: # - translation with point-to-multipoint is incomplete use Getopt::Std; use Fcntl; use POSIX qw(tmpnam ENOSYS); use Sys::Hostname; # Secure the path if we're running under RBAC. $ENV{PATH} = ( "/bin", "/sbin", "/usr/bin", "/usr/sbin", "/usr/ucb" ) if $< != $>; # General path names that can be configured. local($rootetc) = "/etc/"; local($passwd) = $rootetc . "passwd"; local($passwdlck) = $rootetc . ".pwd.lock"; local($asfile) = $rootetc . "asppp.cf"; local($astemp) = $rootetc . "asppp.temp.cf"; local($asmoved) = $rootetc . "asppp.saved.cf"; local($uucpdir) = $rootetc . "uucp/"; local($Devices) = $uucpdir . "Devices"; local($Devconfig) = $uucpdir . "Devconfig"; local($Dialers) = $uucpdir . "Dialers"; local($Dialcodes) = $uucpdir . "Dialcodes"; local($Limits) = $uucpdir . "Limits"; local($Sysfiles) = $uucpdir . "Sysfiles"; local($Systems) = $uucpdir . "Systems"; local($pppdir) = $rootetc . "ppp/"; local($options) = $pppdir . "options"; local($ttyprefix) = $pppdir . "options."; local($peersdir) = $pppdir . "peers/"; local($initd) = $rootetc . "init.d/"; local($asctl) = $initd . "asppp"; local($pppdctl) = $initd . "pppd"; local($sedpasswd) = "/tmp/sed-passwd"; # Fake asppp keyword used to keep track of dial-in paths. local($isdialin) = "-is-dial-in"; # Limits and Sysfiles are keyed on "service=". # Devconfig is keyed on "service=" and "device=". # Dialcodes, Dialers, Systems, and Devices are single keyword files. # Devices alone may have multiple entries for a given key. # Internal data structures local(@sysfiles,@limits,@devconfig); local(@sysdefault) = ( "systems=" . $Systems, "dialers=" . $Dialers, "devices=" . $Devices ); local(@limitdefault) = ( "max=-1" ); local(@devdefault) = ( "pop=", "push=", "connecttime=-1", "expecttime=-1", "msgtime=-1" ); # List of keywords for which ifconfig takes an additional parameter. local($ifconfigtakes) = ( addif => 1, removeif => 1, auth_algs => 1, encr_algs => 1, encr_auth_algs => 1, broadcast => 1, destination => 1, index => 1, metric => 1, modinsert => 1, modremove => 1, mtu => 1, netmask => 1, set => 1, subnet => 1, tdst => 1, tsrc => 1, wait => 1, # These are keywords, but do not take an additional parameter. ether => 0, inet => 0, inet6 => 0, arp => 0, -arp => 0, auto-revarp => 0, modlist => 0, plumb => 0, unplumb => 0, private => 0, -private => 0, nud => 0, -nud => 0, trailers => 0, -trailers => 0, up => 0, down => 0, xmit => 0, -xmit => 0, auto-dhcp => 0, dhcp => 0, primary => 0, drop => 0, extend => 0, inform => 0, ping => 0, release => 0, start => 0, status => 0 ); # print number of something in English. sub nof { local($num, $item, @rest) = @_; print "No ", $item, "s", @rest if $num == 0; print "1 ", $item, @rest if $num == 1; print $num, " ", $item, "s", @rest if $num > 1; } # ask a yes or no question. sub yesno { local ($query, $default) = @_; local ($ans, $defans); return $default unless (-t STDIN) && (-t STDOUT) && !$opt_n; $defans = $default ? "Yn" : "yN"; while (1) { print $query, " [", $defans, "]? "; chomp($ans = ); return $default unless $ans; return 1 if $ans =~ /^[Yy1Tt]/; return 0 if $ans =~ /^[Nn0Ff]/; print "Please enter 'y' or 'n'.\n"; } } # Put quotes around a string, if necessary. # The tests here aren't perfect -- they think that \\\' isn't an # escaped quote -- but they're good enough. sub requote { local($_) = @_; if (/^$/) { "\"\""; } elsif (/^'/ || /[^\\]'/ || /\\\\'/) { # Has unescaped quotes; must use " or redo quoting. if (/^"/ || /[^\\]"/ || /\\\\"/) { # Both kinds of quotes; redo the quoting. s/^"/\\"/; s/([^\\]|\\\\)"/$1\\"/g; } "\"" . $_ . "\""; } elsif (/^"/ || /[^\\]"/ || /\\\\"/) { "'" . $_ . "'"; } elsif (/\s/) { "\"" . $_ . "\""; } else { $_; } } # Get a single line from a UUCP configuration file and return as a # reference to an array of words. Removes comments and escapes. # (This is a modified version of the standard Perl shellwords function # that understands C escape sequences and continuation lines.) # Optionally returns lead-in, source text, and trailing component # for editing. sub uucpline { local($input, $file, $triplet) = @_; local(@words,$snippet,$field,$havefield,$cont,@triparray,$maytrail); $cont = ""; $maytrail = ""; while (<$input>) { # remove leading whitespace if (s/^(\s+)//) { $maytrail .= $1; } if ($cont eq "") { if (s/^(\#(.|\n)*)$//) { $triparray[0] .= $maytrail . $1; $maytrail = ""; next; } if (s/^(\\?\n?)$//) { $triparray[0] .= $maytrail . $1; $maytrail = ""; next; } $triparray[0] .= $maytrail; $maytrail = ""; } $snippet = $_; if (s/^(([^\#\\]|\\.)*)\\\n$//) { $maytrail .= $snippet; $cont .= $1; next; } if (/^(([^\\\#]|\\[^\#])*)(\#?(.|\n)*)$/) { $_ = $maytrail . $1; $maytrail = $3; if (s/((\s|\n)*)$//) { $maytrail = $1 . $maytrail; } $triparray[1] = $_; } $_ = $cont . $snippet; $cont = ""; s/\n$//; while ($_ ne '') { for (;;) { if (s/^#.*//) { last; } elsif (s/^"(([^"\\]|\\.)*)"//) { $snippet = $1; } elsif (s/^"//) { warn "Unmatched double quote in $file: \"$_\n"; } elsif (s/^'(([^'\\]|\\.)*)'//) { $snippet = $1; } elsif (s/^'//) { warn "Unmatched single quote in $file: '$_\n"; } elsif (s/^\\s//) { # \s works in chat, but not in the pppd option files $snippet = " "; } elsif (s/^(\\.)//) { $snippet = $1; } elsif (s/^([^\s\\'"#]+)//) { $snippet = $1; } else { s/^\s+//; last; } $havefield = 1; $field .= $snippet; } push(@words, $field) if $havefield; $havefield = 0; $field = ''; } last; } $triparray[2] .= $maytrail; @$triplet = @triparray; warn "Bad continuation line in $file: $cont\n" if $cont ne ''; \@words; } # Given a logical UUCP file name, return a list of all of the files # that should be read. sub uucpfiles { local ($file) = @_; local (@flist, $value) = (); for $value (@sysfiles, @sysdefault) { if ($value =~ /^$file=/i) { $value =~ s/^$file=//i; for $file (split /:/, $value) { $file = $uucpdir . $file if $file !~ /^\//; push @flist, $file; } last; } } @flist; } # Given a file name and some key words, parse the contents of the file # and return a reference to a hash constructed from this. All keys # except the last must match exactly. The last is used to order the # hash. sub uucpkeyfile { local($file,@keylist) = @_; local($lastkey,$keyval,$words,$i,$flag,%byservice); open(SVCFILE, '<' . $file) || return undef; $lastkey = pop @keylist; while (@{$words = uucpline(SVCFILE, $file)}) { $flag = 1; foreach $keyval (@keylist) { $flag = 0; $i = 0; while ($i < @$words) { if ($$words[$i] eq $keyval) { splice @$words, $i, 1; $flag = 1; last; } $i++; } last unless $flag; } next unless $flag; foreach $i (0 .. @{$words}) { if (@{$words}[$i] =~ /^$lastkey(.*)/i) { splice @{$words}, $i, 1; $byservice{$1} = $words; last; } } } close SVCFILE; \%byservice; } # This reads a UUCP file that is keyed on the first token on each # line. Duplicates are not permitted; the first encountered is used # and the rest are silently discarded. A hash indexed on the first # token and containing an array of tokens in each bucket is returned. # Used for Dialcodes, Dialers, and Systems. sub uucpposfiles { local(@files) = @_; local($keyval,$words,%keyeddata); foreach $file (@files) { if (!open(POSFILE, '<' . $file)) { warn "$file: $!\n"; next; } while (@{$words = uucpline(POSFILE, $file)}) { $keyval = shift @{$words}; next if $keyeddata{$keyval}; $keyeddata{$keyval} = $words; } close POSFILE; } \%keyeddata; } # This reads a UUCP file that is keyed on the first token on each line # and may have duplicate entries. Each entry of the hash contains an # array. Each entry of that array points to an array of tokens # representing one parsed line. Used for the Devices file. sub uucpdevices { local(@files) = @_; local($keyval,$words,%keyeddata); foreach $file (@files) { if (!open(POSFILE, '<' . $file)) { warn "$file: $!\n"; next; } while (@{$words = uucpline(POSFILE, $file)}) { $keyval = shift @{$words}; push @{$keyeddata{$keyval}}, $words; } close POSFILE; } \%keyeddata; } # For a path defined in asppp.cf, copy over defaults, validate the # required options, and save in the hash to be returned. sub savepath { local($paths, $options, $defref) = @_; local($peer,$intf); return if $options == $defref; foreach $key (keys %$defref) { $$options{$key} = $$defref{$key} unless defined($$options{$key}); } $peer = $$options{"peer_system_name"}; warn("Discarded path with no peer system name.\n"), return unless defined($peer); $intf = $$options{"interface"}; warn("Missing interface on path to peer \"$peer\".\n"), return unless defined($intf); warn("Bad interface $intf on path to peer \"$peer\".\n"), return unless $intf =~ /^ipd([0-9]+|ptp[0-9]+|ptp\*)$/; warn("Missing peer IP address for point-to-multipoint path to \"", $peer, "\".\n"), return if $intf =~ /^ipd[0-9]+$/ && !defined($$options{"peer_ip_address"}); warn "Multiple definitions of path to peer \"$peer\".\n", if defined($paths{$peer}); warn "Odd version number ", $$options{"version"}, " encountered in path to peer \"", $peer, "\" (ignored).\n" if defined($$options{"version"}) && $$options{"version"} != 1; $paths{$peer} = $options; } # Parse through asppp.cf. Unlike the UUCP files, lines don't matter # here. The parsing is modal, with "path" introducing a PPP session # description and "defaults" reverting back to global definitions. sub readaspppcf { local($aspppcf) = @_; local(%aspppdkey) = ( chap_name => 1, chap_peer_secret => 1, chap_peer_name => 1, chap_secret => 1, debug_level => 1, default_route => 0, ifconfig => -1, inactivity_timeout => 1, interface => 1, # sic; aspppd is seriously confused! ACCM isn't in IPCP. ipcp_async_map => 1, ipcp_compression => 1, lcp_compression => 1, lcp_mru => 1, negotiate_address => 1, pap_id => 1, pap_password => 1, pap_peer_id => 1, pap_peer_password => 1, peer_ip_address => 1, peer_system_name => 1, require_authentication => 2, version => 1, will_do_authentication => 2 ); local($words,$word,$prevword,$i,$errors,%defaults,%ifconfig,%paths); local($options); open ASPPPD, "<" . $aspppcf || die "$aspppcf: $!\n"; print "Reading configuration from $aspppcf\n" if $opt_v; $defaults{inactivity_timeout} = 120; $defaults{ipcp_compression} = "vj"; $defaults{lcp_compression} = "on"; $options = \%defaults; while (@{$words = uucpline(ASPPPD, $aspppcf)}) { if ($$words[0] =~ /^ifconfig$/i) { warn "$prevword with missing argument ignored.\n" if defined($prevword); undef $prevword; shift @$words; # discard 'ifconfig' keyword $word = shift @$words; warn("Bad interface on ifconfig $word.\n"), next unless $word =~ /^ipd([0-9]+|ptp[0-9]+)$/; $ifconfig{$word} = \@$words; next; } unshift @{$words}, $prevword if defined($prevword); undef $prevword; while ($word = lc(shift @{$words})) { $_ = $word; if (/^defaults$/i) { savepath(\%paths, $options, \%defaults); $options = \%defaults; next ; } if (/^path$/i) { local(%pathopts); savepath(\%paths, $options, \%defaults); $options = \%pathopts; next; } if (!defined($i = $aspppdkey{$word})) { die "Too many errors in $aspppcf; aborting.\n" if ++$errors > 5; warn "Ignoring unknown keyword $word in $aspppcf\n"; next; } warn("$_ unexpected; remainder of line ignored.\n"), last if $i == -1; warn("Duplicate $_ in path ignored.\n"), next if $options != \%defaults && defined($$options{$_}); $$options{$_} = 1 if $i == 0; next if $i == 0; $prevword = $_, last unless defined($word = shift @{$words}); $$options{$_} = $word if $i == 1; if ($i == 2) { undef $$options{$_}, next if $word =~ /^off$/; $$options{$_} = $word; if ($word = shift @{$words}) { if ($word =~ /^(p|ch)ap$/) { $$options{$_} .= " " . $word; } else { unshift @{$words}, $word; } } } } } warn "Odd trailing keyword \"$prevword\" ignored in $aspppcf\n" if $prevword; savepath(\%paths, $options, \%defaults); die "No paths defined for aspppd.\n" if 0+(keys %paths) == 0; die "No interfaces defined for aspppd.\n" if 0+(keys %ifconfig) == 0; if ($opt_v) { nof 0+(keys %paths), "path", " and "; nof 0+(keys %ifconfig), "interface", " defined for aspppd.\n"; } close ASPPPD; ( \%ifconfig, \%paths ); } # Read /etc/passwd (or NIS) and return hash of users for whom # the default shell is aspppls. Each hash entry contains the user's # home directory path. sub readpasswd { local(%users,@pwe); setpwent(); while (@pwe = getpwent()) { $users{$pwe[0]} = $pwe[7] if $pwe[8] =~ /\/aspppls$/; } endpwent(); nof 0+(keys %users), "aspppd dial in user", " found.\n" if $opt_v; \%users; } # Parse through pmadm output to find enabled serial ports. # Field 9 has 'I' (modem dial-out only or initialize only), 'b' # (bidirectional) or is blank (modem dial-in only or terminal-hardwired). # For that latter case, field 18 (software-carrier) has 'y'. # Field 3 has 'x' if disabled. sub getserialports { local(%dialin, %dialout); open PMADM, "pmadm -L|" || (warn "pmadm: $!\n", return undef); while () { split /:/; if ($_[3] !~ /x/) { $dialin{$_[2]} = $_[8] if $_[9] ne "I"; $dialout{$_[2]} = $_[8] if $_[9] ne ""; } } close PMADM; ( \%dialin, \%dialout ); } # Convert an ifconfig statement into a local and remote address pair. sub ifconf_addr { local($ifconf) = @_; local($arg, $narg, $lcladdr, $remaddr); shift @$ifconf; # lose the interface name while (@$ifconf) { local($arg, $narg); $arg = shift @$ifconf; $narg = shift @$ifconf if $ifconfigtakes{$arg}; if (exists($ifconfigtakes{$arg})) { if ($arg eq "set") { $lcladdr = $narg; } elsif ($arg eq "destination") { $remaddr = $narg; } } elsif (!defined($lcladdr)) { $lcladdr = $arg; } elsif (!defined($remaddr)) { $remaddr = $arg; } } ( $lcladdr, $remaddr ); } # Convert a hash of aspppd options into an array of pppd options. The # third argument ($chatpath) is undef for dial-in or a path to the # chat script file otherwise. sub convert_options { local ($pppdargs, $opts, $chatpath, $user) = @_; # Do the pppd option conversions. push(@$pppdargs, "defaultroute") if $$opts{default_route}; push(@$pppdargs, "idle " . $$opts{inactivity_timeout}) if $$opts{inactivity_timeout} != 0; push(@$pppdargs, "asyncmap " . $$opts{ipcp_async_map}) if $$opts{ipcp_async_map}; push(@$pppdargs, "novj") if !$$opts{ipcp_compression}; local($peer); if ($$opts{require_authentication}) { local (@authopts); if ($$opts{require_authentication} =~ /chap/) { push(@authopts, "require-chap"); $peer = $$opts{chap_peer_name}; } else { push(@authopts, "require-pap"); $peer = $$opts{pap_peer_id}; } push(@authopts, "remotename " . requote($peer)) if $peer; push(@authopts, "auth"); if ($chatpath) { push(@$pppdargs, @authopts); } elsif ($dialin_auth == 3) { # mixed authentication; must use wrapper script. local($sfile) = $pppdir . "dial-in." . $user; $scriptfiles{$sfile} = "#!/bin/sh\n"; $scriptfiles{$sfile} .= "exec /usr/bin/pppd @authopts\n"; $dialinshell{$user} = $sfile; } } elsif ($dialin_auth < 2) { push(@$pppdargs, "noauth"); } push(@$pppdargs, "noaccomp nopcomp") if !$$opts{lcp_compression}; push(@$pppdargs, "mru " . $$opts{lcp_mru}) if $$opts{lcp_mru}; push(@$pppdargs, "noipdefault ipcp-accept-local") if $$opts{negotiate_address}; local($myname,$csecret,$psecret,$passopt); if ($$opts{will_do_authentication} =~ /chap/i) { $myname = $$opts{chap_name}; $csecret = $$opts{chap_secret}; } $myname = "" if !$myname; if ($$opts{will_do_authentication} =~ /pap/i) { if ($myname ne "" && $$opts{pap_id} ne "" && $myname ne $$opts{pap_id}) { warn "pppd cannot have separate local names for PAP and CHAP; using CHAP name:\n"; warn "\t\"$myname\"\n"; } else { $myname = $$opts{pap_id} if $$opts{pap_id} ne ""; } $psecret = $$opts{pap_password}; } if ($$opts{will_do_authentication}) { if ($chatpath && ($$opts{will_do_authentication} !~ /chap/i || $$opts{will_do_authentication} !~ /pap/i || $csecret eq $psecret)) { push(@$pppdargs, "password " . requote($csecret ? $csecret : $psecret)); $passopt = 1; } push @$pppdargs, "user " . requote($myname); push(@$pppdargs, "remotename " . requote($peer)) if $peer && !$$opts{require_authentication}; } else { $myname = "NeverAuthenticate"; } push(@$pppdargs, "debug") if $$opts{debug_level} >= 8; push(@$pppdargs, "kdebug 3") if $$opts{debug_level} >= 9; local($lcladdr, $remaddr) = ifconf_addr($$ifconfig{$$opts{interface}}); if ($chatpath) { if ($$opts{debug_level} >= 4) { push(@pppdargs, "connect \"/usr/bin/chat -vf $chatpath\""); } else { push(@pppdargs, "connect \"/usr/bin/chat -f $chatpath\""); } push(@$pppdargs, "user " . requote($myname)); local($str) = $remaddr; $str = $$opts{peer_ip_address} if $$opts{peer_ip_address}; push(@$pppdargs, $lcladdr . ":" . $str) if !$opt_s && ($lcladdr || $str); } else { push(@$pppdargs, "user " . requote($myname)) if $myname eq "NeverAuthenticate"; } if ($$opts{interface} && $opt_s) { if ($$opts{interface} =~ /^ipd[0-9]+$/) { push(@$pppdargs, $lcladdr . ":") if $lcladdr; } elsif ($$opts{interface} =~ /^ipd(|ptp)([0-9]+)$/) { push(@pppdargs, "unit " . $2); } else { push(@pppdargs, "plumbed"); } } # Convert the secrets if ($chatpath) { $addsecret = ""; } elsif ($$opts{peer_ip_address}) { $addsecret = " " . $$opts{peer_ip_address}; } else { $addsecret = " *"; } if ($$opts{require_authentication}) { local($lclname, $secret, $authf); $lclname = $$opts{will_do_authentication} ? $myname : hostname(); if ($$opts{require_authentication} =~ /chap/) { $secret = $$opts{chap_peer_secret}; $authf = \%chapsecrets; } else { $secret = $$opts{pap_peer_password}; $authf = \%papsecrets; } ${$$authf{$peer}}{$lclname} = requote($secret) . $addsecret; } if ($$opts{will_do_authentication} && !$passopt) { ${$chapsecrets{$myname}}{$peer} = requote($csecret) if $csecret; ${$papsecrets{$myname}}{$peer} = requote($psecret) if $psecret; } } # Translate options for a dial-in user. sub translatedialin { local($peer) = @_; $optname = $$dialinusers{$peer}; $optname .= "/" if $optname !~ /\/$/; $optname .= ".ppprc"; if (exists($optfiles{$optname})) { warn "Home directories of $peer and $optuser{$optname} are the same ($optfiles{$optname}\n"; warn "Ignoring configuration for $peer.\n"; return; } $optuser{$optname} = $peer; $dialinshell{$peer} = "/usr/bin/pppd"; local (@pppdargs); convert_options(\@pppdargs, $$paths{$peer}, undef, $peer); push @pppdargs, "nologfd"; $optfiles{$optname} = \@pppdargs; } # Translate ifconfig entries in asppp.cf into either an ifconfig # script for strict translation or a set of per-port IP addresses # for normal translation. Also translate ipdX interfaces into # Ethernet aliases to make routing daemon happy. sub translateifconfig { local (@ifconfiglist,$cstr,$etherif,$intf,$ifconf,$addstr,$port); open IFLIST, "/sbin/ifconfig -au4|" || die "cannot run ifconfig: $!\n"; while () { $etherif = $1 if !$etherif && /^([^:]*).*UP.*BROADCAST/; } close IFLIST; $etherif = $1 if !$etherif && glob("/etc/hostname.*") =~ /[^\.]*.(.*)/; $etherif = $1 if !$etherif && glob("/etc/dhcp.*") =~ /[^\.]*.(.*)/; $etherif = "hme0" if !$etherif; $cstr = ""; foreach $intf (keys %$ifconfig) { $ifconf = $$ifconfig{$intf}; if ($intf =~ /ipd[0-9]+/) { shift @$ifconf; $cstr .= "ifconfig $etherif addif @$ifconf\n"; } } $ndialin = 0+(keys %$dialin); $addstr = ""; foreach $intf (keys %downif) { $ifconf = $downif{$intf}; if ($intf =~ /ipdptp([0-9]+)/ && --$ndialin >= 0) { push @ifconfiglist, $ifconf; $addstr .= "ifconfig sppp" . $1 . " @$ifconf\n"; } } if ($ndialin > 0) { if (@ifconfiglist) { print "Fewer ifconfigs (", $#ifconfiglist+1, ") than dial-in lines (", $ndialin+$#ifconfiglist+1, ")\n"; } else { print "No ifconfigs for "; nof $ndialin, "dial-in port", ".\n"; } } elsif ($ndialin < 0) { if (@ifconfiglist) { print "Ignoring ", -$ndialin, " of "; nof $#ifconfiglist-$ndialin+1, "ifconfig", " (too few dial-in ports)\n"; } else { print "Ignoring all "; nof -$ndialin, "ifconfig line", " (no dial-in ports)\n"; } } if ($opt_s) { # Strict translation uses pre-plumbed interfaces. $cstr .= $addstr; } else { foreach $port (values %$dialin) { last if !@ifconfiglist; $port =~ s+/dev/++; $port =~ s+/+.+g; $optfile = $ttyprefix . $port; $ifconf = pop @ifconfiglist; local ($lcladdr, $remaddr) = ifconf_addr($ifconf); next if !defined($lcladdr) || !defined($remaddr); local (@pppdargs) = $lcladdr . ":" . $remaddr; $optfiles{$optfile} = \@pppdargs; } } $scriptfiles{$pppdir . "ifconfig"} = $cstr if $cstr; } # Attempt to modify global passwd file using sed script stored in # the $sedpasswd temporary file. sub rewrite_passwd { print "Updating local passwd file (if any).\n" if $opt_v; if (!sysopen(PWDLCK, $passwdlck, O_WRONLY|O_CREAT, 0600)) { warn "Unable to lock password file: $!\n"; } else { $lockstr = pack "ssLLiiLLLL", F_WRLCK, 0, 0, 0, 0, 0, 0, 0, 0, 0; eval { local $SIG{ARLM} = sub { die "alarm while locking password file\n" }; alarm 15; fcntl PWDLCK, F_SETLKW, $lockstr || die "cannot lock password file: $!\n"; alarm 0; }; if ($@) { warn $@; } else { warn "Password update failed.\n" if (system("sed -f $sedpasswd < $passwd > ${passwd}.new") || !(rename "${passwd}.new", $passwd)); } $lockstr = pack "ssLLiiLLLL", F_UNLCK, 0, 0, 0, 0, 0, 0, 0, 0, 0; fcntl PWDLCK, F_SETLK, $lockstr; close PWDLCK; } if (($ypmaster = `/usr/bin/ypwhich 2>/dev/null`) && $? == 0) { $ypmaster =~ /(.*)\n/; ($ypmaster) = gethostbyname($1); ($thishost) = gethostbyname(hostname); if ($ypmaster eq $thishost) { system("cd /var/yp && make") if yesno("Rebuild NIS/YP maps", $opt_y); } else { warn "Not running on NIS/YP master $1; unable to update user shells\n"; print "Use 'sed -f $sedpasswd <$passwd >${passwd}.new' on the master\n"; print "and then remake the NIS/YP database.\n"; undef $sedpasswd; } } unlink $sedpasswd if $sedpasswd; } # Show usage message. sub usage { print "Usage:\n\n"; print "\t$0 [-rsvy]\n\n"; print " -n - non-interactive mode.\n"; print " -r - revert back to aspppd configuration.\n"; print " -s - use strict translation.\n"; print " -v - print more detail of the operations performed.\n"; print " -y - assume 'yes' as default answer where reasonable.\n"; exit; } # Correct an environment variable so that it points at either a useful # executable program, or nothing at all. sub fixpath { local ($prog, $deflt) = @_; $prog = $deflt if $prog eq ""; if ($prog !~ /^(\.\/|\/)/) { local ($_) = $ENV{PATH}; $_ = "/bin:/usr/bin:/sbin:/usr/sbin" if $_ eq ""; split /:/; foreach (@_) { $prog = $_ . "/" . $prog, last if -x $_ . "/" . $prog; } } $prog = "" if !(-x $prog); $prog; } getopts('nrsvy') || usage; die "Need permission to modify system files.\n" unless ($> == 0 || yesno "This script should be run as root. Continue"); if ($opt_r) { local ($intemp); # Revert to previous configuration. Just rename the aspppd file back # and undo changes to the passwd file. die "No saved aspppd configuration exists.\n" unless -f $asmoved; if (-e $astemp) { die "$astemp is not a file\n" unless -f $asfile; unlink $astemp || die "Cannot remove temporary $astemp: $!\n"; } $intemp = 0; if (-e $asfile) { die "$asfile is not a file\n" unless -f $asfile; die "Not modifying configuration.\n" unless yesno "Remove existing $asfile", $opt_y; rename $asfile, $astemp || die "Cannot rename existing $asfile: $!\n"; $intemp = 1; } if (rename $asmoved, $asfile) { unlink $astemp || warn "$astemp: $!\n" if $intemp; } else { $failure = "Cannot rename $asmoved to $asfile: $!\n"; rename $astemp, $asfile || die "$failure\nand cannot recover: $!\n" . "Saved current asppp.cf in $astemp\n" if $intemp; die $failure; } $( = $); $< = $>; system($pppdctl, "stop") if -x $pppdctl; # remove pppd autostart files. unlink $pppdir . "ifconfig"; unlink $pppdir . "demand"; system($asctl, "start") if -x $asctl; open SEDFILE, ">$sedpasswd" || die "Cannot write $sedpasswd: $!\n"; local ($escdir) = $pppdir; $escdir =~ s+/+\\/+g; print SEDFILE "/${escdir}dial-in\\./s+[^:]*\$+/usr/sbin/aspppls+\n"; print SEDFILE "/\\/usr\\/bin\\/pppd/s+[^:]*\$+/usr/sbin/aspppls+\n"; close SEDFILE; rewrite_passwd; exit 0; } $aspppcf = $asfile; if (!(-f $asfile)) { die "No aspppd configuration exists; nothing to convert.\n" unless -f $asmoved; die "No changes made.\n" unless yesno "Already converted; rerun anyway"; $aspppcf = $asmoved; } print "This script provides only a suggested translation for your existing aspppd\n"; print "configuration. You will need to evaluate for yourself whether the translation\n"; print "is appropriate for your operating environment.\n"; die "No changes made.\n" unless yesno "Continue", 1; # Read in the asppp.cf file first; there's no reason to continue on to # the UUCP files if this file isn't readable or has no paths defined. local($ifconfig, $paths) = readaspppcf($aspppcf); # Loop over the ifconfigs and build a list of the down ones. foreach $intf (keys %$ifconfig) { local(@words) = @{$$ifconfig{$intf}}; while ($word = shift @words) { shift @words if $ifconfigtakes{$word}; if ($word =~ /^down$/) { warn("Why is $intf declared down?\n"), last if $intf =~ /^ipd[0-9]+$/; $downif{$intf} = $$ifconfig{$intf}; delete $$ifconfig{$intf}; last; } } } # Read /etc/passwd for dial-in users configured for aspppd. local($dialinusers) = readpasswd; # Read in existing pppd configuration. All we really care about # is the setting of the "auth" option. undef $authoption; if (open(OPTIONS,"<" . $options)) { while (@{$words = uucpline(OPTIONS, $options)}) { while ($_ = pop @$words) { $authoption = $_ if /auth/i; } } close OPTIONS; $authoption = "unknown" if !defined($authoption); } $dialin_auth = 0; if ($authoption =~ /^auth$/i) { $dialin_auth = 1; } elsif ($authoption =~ /^noauth$/i) { $dialin_auth = 2; } elsif (defined($authoption)) { $dialin_auth = 3; } # Check that there's a path for each dial in user foreach $user (keys %$dialinusers) { if (!defined($$paths{$user})) { warn "Dial-in user ", $user, " does not have a corresponding dial-in path.\n"; delete $$dialinusers{$user}; next; } $intf = ${$$paths{$user}}{"interface"}; if ($intf eq "ipdptp*") { if (0+keys(%downif) == 0) { warn "Dial-in user $path has no available \"down\" interfaces.\n"; delete $$dialinusers{$user}; next; } } else { if (!defined($downif{$intf}) && !defined($$ifconfig{$intf})) { warn "Dial-in path $user has undefined $intf; deleted.\n"; delete $$dialinusers{$user}; next; } } ${$$paths{$user}}{$isdialin} = 1; # 0 - no info (no options file, "noauth" on call) # 1 - all auth ("auth" in options, "noauth" on call) # 2 - all noauth ("noauth" in options) # 3 - mixed; use auth ("noauth" in options, wrapper script for "auth") if (${$$paths{$user}}{require_authentication}) { if ($dialin_auth == 2) { $dialin_auth = 3; } elsif ($dialin_auth == 0) { $dialin_auth = 1; } } else { if ($dialin_auth == 1) { $dialin_auth = 3; } elsif ($dialin_auth == 0) { $dialin_auth = 2; } } } # Get lists of usable dial-in and dial-out ports. local($dialin,$dialout) = getserialports; # Read and parse the UUCP Sysfiles, Devconfig, and Limits files. # These are keyed with the "service=" string. The Sysfiles file can # augment or override the list of files read for a given service. print "Reading UUCP configuration.\n" if $opt_v; @sysfiles = @{${uucpkeyfile($Sysfiles,"service=")}{"ppp"}}; @limits = @{${uucpkeyfile($Limits,"service=")}{"ppp"}}; %devconfig = %{uucpkeyfile($Devconfig,"service=ppp","device=")}; # Now read in the UUCP files corresponding to this service. $systems = uucpposfiles(uucpfiles("systems")); $dialers = uucpposfiles(uucpfiles("dialers")); $dialcodes = uucpposfiles($Dialcodes); $devices = uucpdevices(uucpfiles("devices")); # just to make sure $$dialcodes{""} = (); # Loop over paths. Dial-out only paths are translated into demand-dial # configurations. Dial-in only paths are translated into appropriate # log-in entries. local (@bidirectional); foreach $peer (keys %$paths) { if (exists($$systems{$peer})) { $sline = $$systems{$peer}; if ($$sline[0] eq "Never") { if (${$$paths{$peer}}{$isdialin}) { translatedialin($peer); } else { print "We never call $peer, and he never calls us.\n" if $opt_v; } delete $$paths{$peer}; next; } push @bidirectional, $peer if ${$$paths{$peer}}{$isdialin}; print "Ignoring time restriction on $peer\n" if $$sline[0] ne "Any"; $dlist = $$devices{$$sline[1]}; $class = $$sline[2]; $i = 0; while ($i < @$dlist) { local($dev) = $$dlist[$i]; if ($$dev[1] ne "-") { print "Ignoring device $$dev[0]; 801-type not supported.\n"; splice @$dlist, $i, 1; next; } $i++; # Make sure that classes match. next if $$dev[2] ne "Any" && $class ne "Any" && $$dev[2] ne $class; # Prepend "/dev/" if it's not present in the device name. if (exists($$dialout{$$dev[0]})) { # This just seems odd. $dname = $$dialout{$$dev[0]}; $dname =~ s+/dev/term/+/dev/cua/+; } else { $dname = ($$dev[0] =~ m+^/+ ? $$dev[0] : ("/dev/" . $$dev[0])); } # Skip devices that aren't supposed to be used for dial-out. next if $dname =~ m+^/dev/term/+; next if $dname =~ m+^/dev/tty[a-z]$+; # Make sure this is a character device and we have access to it. next unless -w $dname && -c $dname; warn "Dialer for $$dev[3] is missing.\n" unless exists($warned{$$dev[3]}) || exists($$dialers{$$dev[3]}); $warned{$$dev[3]} = 1; # Expand keywords from Dialcodes file. Should have \T or \D. $phone = $$sline[3]; $xphone = ($$dev[4] eq "\\T" && $phone =~ /^([A-Za-z]*)(.*)$/ ? "@{$$dialcodes{$1}}" . $2 : $phone); # Make a copy of the dialing script. local(@dials) = @{$$dialers{$$dev[3]}}; # Translate dial tone and wait characters from Dialers file. $_ = shift @dials; s[(.)(.)]{ local($from,$to) = ($1,$2); $phone =~ s+(^|[^\\])$from+$1$to+gx; $xphone =~ s+(^|[^\\])$from+$1$to+gx; }ge; # Translate escapes in dial specification. Chat has a \T, # but uses \U instead of \D. local($needt, $needu, $isexpect, @chats) = ("", "", 1); foreach $str (@dials) { push(@chats, "") if $str eq ""; local ($ostr) = ""; if ($isexpect) { while ($str =~ s/([^\\]*)\\(.)//) { local($lead, $_) = ($1, $2); /[Mm]/ ? ($ostr .= $lead) : /[Ee]/ ? ($sorrye = 1, $ostr .= $lead) : ($ostr .= $lead . "\\" . $_); } } else { while ($str =~ s/([^\\]*)\\(.)//) { local($lead, $_) = ($1, $2); /T/ ? ($needt = " -T '$xphone'", $ostr .= $lead . "\\T") : /D/ ? ($needu = " -U '$phone'", $ostr .= $lead . "\\U") : /M/ ? ($ostr .= $lead, ($ostr ne "" ? push(@chats, $ostr, "\\c"):0), push(@chats, "HANGUP", "OFF"), $ostr = "") : /m/ ? ($ostr .= $lead, ($ostr ne "" ? push(@chats, $ostr, "\\c"):0), push(@chats, "HANGUP", "ON"), $ostr = "") : /[Ee]/ ? ($sorrye = 1, $ostr .= $lead) : /[dp]/ ? ($ostr .= $lead . "\\" . $_ . "\\" . $_) : /c/ ? ($str eq "" ? ($ostr .= $lead . "\\c") : 0) : ($ostr .= $lead . "\\" . $_); } } $ostr .= $str; push @chats, $ostr if $ostr ne ""; $isexpect = !$isexpect; } # Pad out dial list if we're missing a "send" string and tack # on the chat list from the Systems file. if (defined $$sline[4]) { push @chats, "\\c" if !$isexpect; push @chats, (splice @$sline, 4); } $chatfile = $pppdir . "chat.$peer.$$dev[3]"; if (-e $chatfile) { print "$chatfile already exists.\n"; if (!yesno("Should it be overwritten",$opt_y)) { if (yesno("Should it be used as-is")) { warn "Using $chatfile as-is; it may not be correct.\n"; } else { for ($n = 0; ; $n++) { last if !(-e $chatfile . "." . $n); } $chatfile .= "." . $n; print "Using $chatfile instead.\n"; $chatfiles{$chatfile} = \@chats; } } else { $overwrite{$chatfile} = 1; $chatfiles{$chatfile} = \@chats; } } else { $chatfiles{$chatfile} = \@chats; } push @pppdargs, $dname; push @pppdargs, $class if $class =~ /^[0-9]+$/; push @pppdargs, "demand"; convert_options(\@pppdargs,$$paths{$peer}, $chatfile . $needt . $needu, undef); $optname = $peersdir . $peer; if (-e $optname) { print "$optname already exists.\n"; if (!yesno("Should it be overwritten", $opt_y)) { if (yesno("Should it be used as-is")) { warn "Using $optname as-is; it may not be correct.\n"; } else { for ($n = 0; ; $n++) { last if !(-e $optname . "." . $n); } $optname .= "." . $n; print "Using $optname instead.\n"; $optfiles{$optname} = \@pppdargs; } } else { $overwrite{$optname} = 1; $optfiles{$optname} = \@pppdargs; } } else { $optfiles{$optname} = \@pppdargs; } $scriptfiles{$pppdir . "demand"} .= "/usr/bin/pppd file $optname\n"; last; } } elsif (${$$paths{$peer}}{$isdialin}) { translatedialin($peer); } else { warn "Path $peer has no dial-in user nor Systems file entry.\n"; delete $$paths{$peer}; } } warn "Chat cannot do echo checking; requests for this removed.\n" if $sorrye; if (@bidirectional) { print "\nWarning: The following paths are bidirectional:\n"; print "\t@bidirectional\n\n"; print "Bidirectional paths (with entries in both Systems and passwd) do not translate\n"; print "into Solaris PPP 4.0 semantics in an exact manner. The dial-out portion will\n"; print "use the designated interface, but the dial-in portion will use any available\n"; print "interface.\n"; while ($peer = pop @bidirectional) { delete $ {$$paths{$peer}}{interface}; translatedialin($peer); } } translateifconfig; # Create an /etc/ppp/options if we need to. if (!defined($authoption) && $dialin_auth > 0) { local (@pppdopts); push @pppdopts, "lock"; push @pppdopts, "auth" if $dialin_auth == 1; push @pppdopts, "noauth" if $dialin_auth > 1; $optfiles{$options} = \@pppdopts; } # Translate option files to plain text. foreach $file (keys %optfiles) { local ($opts) = $optfiles{$file}; local ($cstr) = ""; $cstr .= shift(@$opts) . "\n" while @$opts; $optfiles{$file} = $cstr; } # Change "auth" to "noauth" or add "noauth" to /etc/ppp/options. if (defined($authoption) && $authoption ne "noauth" && $dialin_auth == 3) { local(@triplet, $cstr); if ($authoption eq "unknown") { warn "Adding 'noauth' to $options\n"; } else { warn "Changing 'auth' in $options to 'noauth'\n"; } open(OPTIONS,"<" . $options) || die "$options disappeared: $!\n"; while (@{$words = uucpline(OPTIONS, $options, \@triplet)}) { $cstr .= $triplet[0]; if (grep(/auth/, @$words)) { local(@newwords) = map { $_ = "noauth" if /auth/; $_ } @$words; $cstr .= "@newwords"; } else { $cstr .= $triplet[1]; } while (pop @$words) { $authoption = $_ if /auth/i; } $cstr .= $triplet[2]; } $cstr .= $triplet[0] . $triplet[2]; close OPTIONS; $cstr .= "\n" if $cstr !~ /\n$/; $cstr .= "noauth\n" if $authoption eq "unknown"; $optfiles{$options} = $cstr; } # Create a sed script to fix the users' shell paths. if (0+(keys %dialinshell) != 0) { $cstr = ""; foreach $peer (keys %dialinshell) { $cstr .= "/^$peer:/s+[^:]*/aspppls\$+$dialinshell{$peer}+\n"; } $scriptfiles{$sedpasswd} = $cstr; } print "\nPreparing to write out translated configuration:\n"; # Enumerate the files we'll write. $nfiles = 0; if (0+(keys %chatfiles) != 0) { print " "; nof 0+(keys %chatfiles), "chat file", ":\n"; foreach $file (keys %chatfiles) { $nfiles++; print "\t$nfiles. $file\n"; local ($chats) = $chatfiles{$file}; local ($cstr) = ""; while (@$chats) { $cstr .= requote(shift(@$chats)); $cstr .= " " . requote(shift(@$chats)) if @$chats; $cstr .= "\n"; } local (@filerec) = ( $file, $cstr ); push @allfiles, \@filerec; } } if (0+(keys %optfiles) != 0) { print " "; nof 0+(keys %optfiles), "option file", ":\n"; foreach $file (keys %optfiles) { $nfiles++; print "\t$nfiles. $file\n"; local (@filerec) = ( $file, $optfiles{$file} ); push @allfiles, \@filerec; } } if (0+(keys %scriptfiles) != 0) { print " "; nof 0+(keys %scriptfiles), "script file", ":\n"; foreach $file (keys %scriptfiles) { $nfiles++; print "\t$nfiles. $file\n"; local (@filerec) = ( $file, $scriptfiles{$file} ); push @allfiles, \@filerec; } } # Merge new secrets needed with existing ones, if any. sub merge_secrets { local ($addsecrets, $fname) = @_; local ($file, $cstr, @triplet, $newsecret); $nfiles++; $file = $pppdir . $fname; print "\t$nfiles. $file\n"; if (open(SECRETS, '<' . $pppdir . $fname)) { while (@{$words = uucpline(SECRETS, $pppdir . $fname, \@triplet)}) { $cstr .= $triplet[0]; $newsecret = $ {$$addsecrets{$$words[0]}}{$$words[1]}; if (defined $newsecret) { $cstr .= requote($$words[0]) . " " . requote($$words[1]) . " " . $newsecret; delete $ {$$addsecrets{$$words[0]}}{$$words[1]}; } else { $cstr .= $triplet[1]; } $cstr .= $triplet[2]; } close SECRETS; $cstr .= $triplet[0] . $triplet[2]; } foreach $key1 (keys (%$addsecrets)) { foreach $key2 (keys (%{$$addsecrets{$key1}})) { $cstr .= requote($key1) . " " . requote($key2) . " " . $ {$$addsecrets{$key1}}{$key2} . "\n"; } } local (@filerec) = ( $file, $cstr ); push @allfiles, \@filerec; } $nchap = 0+(keys %chapsecrets) != 0; $npap = 0+(keys %papsecrets) != 0; if ($nchap != 0 || $npap != 0) { print " "; nof $nchap + $npap, "secrets file", ":\n"; merge_secrets(\%chapsecrets, "chap-secrets") if $nchap != 0; merge_secrets(\%papsecrets, "pap-secrets") if $npap != 0; } die "Nothing to write back; I'm done.\n" if $nfiles == 0; $PAGER = fixpath($ENV{PAGER}, "/usr/bin/less"); $EDITOR = fixpath($ENV{EDITOR}, "/usr/bin/vi"); $SHELL = fixpath($ENV{SHELL}, "/usr/bin/ksh"); END { if ($tempname) { unlink($tempname) or die "Cannot remove temporary file $tempname: $!\n"; } } sub show_file_options { print "\nEnter option number:\n"; print "\t1 - view contents of file on standard output\n"; print "\t2 - view contents of file using $PAGER\n" if $PAGER ne ""; print "\t3 - edit contents of file using $EDITOR\n" if $EDITOR ne ""; print "\t4 - delete/undelete file from list\n"; print "\t5 - rename file in list\n"; print "\t6 - show file list again\n"; print "\t7 - escape to shell (or \"!cmd\")\n"; print "\t8 - abort without saving anything\n"; print "\t9 - save all files and exit (default)\n"; } # If interactive, then allow user to view and modify converted data. if ((-t STDIN) && (-t STDOUT) && !$opt_n) { show_file_options(); while (1) { print "Option: "; chomp($ans = ); if ($ans eq "?" || $ans =~ /^h/i) { show_file_options(); next; } if ($ans eq "") { last if yesno "Saving all files. Are you sure"; next; } last if $ans == 9; print("Aborted.\n"), exit if $ans == 8; if ($ans =~ /^!/ || $ans == 7) { if ($ans =~ /^!(.+)/) { system($1); } else { print("Interactive shell access not permitted here.\n"), next if $< != $>; system($SHELL); } } elsif ($ans == 6) { for ($i = 0; $i < $nfiles; $i++) { print "\t", $i+1, ". $allfiles[$i][0]", ($deleted[$i] ? " (deleted)" : ""), "\n"; } } elsif ($ans > 0 && $ans < 6) { $fnum = 0; if ($nfiles > 1) { print "File number (1 .. $nfiles): "; chomp($fnum = ); if ($fnum < 1 || $fnum > $nfiles) { print "Unknown file (must be 1 to $nfiles).\n"; next; } $fnum--; } if ($ans == 5) { print "Current name is $allfiles[$fnum][0]\n"; print "New name: "; chomp($fname = ); print("Unchanged\n"), next if $fname eq ""; $allfiles[$fnum][0] = $fname; } if ($deleted[$fnum]) { if (yesno("File " . $fnum+1 . " ($allfiles[$fnum][0]) is deleted; undelete",1)) { undef $deleted[$fnum]; } next; } if ($ans == 1) { print $allfiles[$fnum][1]; } elsif ($ans == 2 && $PAGER ne "") { $i = 0; do { if (++$i > 5) { warn "Unable to open temporary file: $!"; undef $tempname; last; } $tempname = tmpnam(); } until sysopen(FH, $tempname, O_RDWR|O_CREAT|O_EXCL); next if !$tempname; print FH $allfiles[$fnum][1]; close FH; system($PAGER, $tempname); unlink($tempname) || warn "Trouble removing temporary file: $!"; undef $tempname; } elsif ($ans == 3 && $EDITOR ne "") { $i = 0; do { if (++$i > 5) { warn "Unable to open temporary file: $!"; undef $tempname; last; } $tempname = tmpnam(); } until sysopen(FH, $tempname, O_RDWR|O_CREAT|O_EXCL); next if !$tempname; chown $<, $(, $tempname; print FH $allfiles[$fnum][1]; close FH; $i = system($EDITOR, $tempname); if ($i == 0) { if (open FH, "<" . $tempname) { read FH, $allfiles[$fnum][1], (-s $tempname); close FH; } } else { print "Editor dropped core.\n" if $? & 128; print "Editor terminated on signal ", $? & 127, "\n" if $? & 127; print "Editor returned error ", $? >> 8, "\n" if $? >> 8; } unlink($tempname) || warn "Trouble removing temporary file: $!"; undef $tempname; } elsif ($ans == 4) { $deleted[$fnum] = 1; } } } } print "\n"; # Interactive part is over. Become real. $( = $); $< = $>; print "Stopping aspppd\n" if $opt_v; system($asctl, "stop") if -x $asctl; print "Saving all files\n" if $opt_v; for ($i = 0; $i < $nfiles; $i++) { $filerec = $allfiles[$i]; if ($deleted[$i]) { delete $scriptfiles{$$filerec[0]}; next; } print "Saving $$filerec[0]\n" if $opt_v; $$filerec[0] =~ m+(.*)/+; if ($1 eq "") { # this is ok; just a top level file } elsif (!(-d $1)) { local ($exdir) = $1; while ($exdir && !(-d $exdir)) { $exdir =~ m+(.*)/+; $exdir = $1; } if ($exdir) { local ($dir) = $1; $dir =~ m+$exdir/([^/]*)(.*)+; local ($tomake, $rest) = ($1, $2); mkdir $exdir . "/" . $tomake, 0775; if ($! == ENOSYS) { warn "Unable to make directory $exdir/$tomake; automount point.\n"; next; } if ($! != 0) { warn "Unable to make directory $exdir/$tomake: $!\n"; next; } if (system("mkdir", "-p", $dir) != 0) { warn "Failed to make $dir\n"; next; } } else { warn "$1 doesn't appear to have a useful path.\n"; next; } } undef $fileerr; local ($fname) = $$filerec[0]; if (-e $fname && !$overwrite{$chatfile}) { print "$fname already exists.\n" if (-t STDIN) && (-t STDOUT) && !$opt_n; if (!yesno("Should it be overwritten",$opt_y)) { warn "Using $fname as-is; it may not be correct.\n"; next; } } if (sysopen(OUTFILE, $$filerec[0], O_WRONLY|O_CREAT|O_TRUNC, 0600)) { print OUTFILE $$filerec[1] || ($fileerr = $!); close OUTFILE || ($fileerr = $!); } else { $fileerr = $!; } warn "Unable to write $$filerec[0]: $fileerr\n" if $fileerr; } local(@scripts) = keys %scriptfiles; if (@scripts) { print "Making scripts executable\n" if $opt_v; system("chmod", "u+x", @scripts); } rewrite_passwd if exists($scriptfiles{$sedpasswd}); # clean up after a previous translation. unlink $pppdir . "ifconfig" if !$scriptfiles{$pppdir . "ifconfig"}; unlink $pppdir . "demand" if !$scriptfiles{$pppdir . "demand"}; (rename($asfile, $asmoved) || warn "Cannot move $asfile: $!\n") if $aspppcf ne $astemp; system($pppdctl, "start") if -x $pppdctl; # use Dumpvalue; # my $dumper = new Dumpvalue; # $dumper->set(globPrint => 1); # $dumper->dumpValue($ifconfig); /* * auth.c - PPP authentication and phase control. * * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright (c) 1993 The Australian National 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 the Australian National 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. * * 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. */ /* * Copyright 2023 OmniOS Community Edition (OmniOSce) Association. */ /* Pull in crypt() definition. */ #define __EXTENSIONS__ #include #include #include #include #include #include #include #include #include #include #include #include #if defined(_PATH_LASTLOG) && (defined(_linux_) || defined(__linux__)) #include #endif #if defined(_linux_) || defined(__linux__) #include #endif #include #include #include /* Backward compatibility with old Makefiles */ #if defined(USE_PAM) && !defined(ALLOW_PAM) #define ALLOW_PAM #endif #ifdef ALLOW_PAM #include #endif #ifdef HAS_SHADOW #include #ifndef PW_PPP #define PW_PPP PW_LOGIN #endif #endif #include "pppd.h" #include "fsm.h" #include "lcp.h" #include "ipcp.h" #include "upap.h" #include "chap.h" #ifdef CBCP_SUPPORT #include "cbcp.h" #endif #include "pathnames.h" /* Bits in scan_authfile return value */ #define NONWILD_SERVER 1 #define NONWILD_CLIENT 2 #define ISWILD(word) (word[0] == '*' && word[1] == '\0') /* The name by which the peer authenticated itself to us. */ char peer_authname[MAXNAMELEN]; /* Records which authentication operations haven't completed yet. */ static int auth_pending[NUM_PPP]; /* Set if we have successfully called plogin() */ static int logged_in; /* List of addresses which the peer may use. */ static struct permitted_ip *addresses[NUM_PPP]; /* Wordlist giving addresses which the peer may use without authenticating itself. */ static struct wordlist *noauth_addrs; /* Extra options to apply, from the secrets file entry for the peer. */ static struct wordlist *extra_options; /* Source of those extra options. */ static const char *extra_opt_filename; static int extra_opt_line; /* Number of network protocols which we have opened. */ static int num_np_open; /* Number of network protocols which have come up. */ static int num_np_up; /* Set if we got the contents of passwd[] from the pap-secrets file. */ static int passwd_from_file; /* Set if we require authentication only because we have a default route. */ static bool default_auth; /* Hook to enable a plugin to control the idle time limit */ int (*idle_time_hook) __P((struct ppp_idle *)) = NULL; /* Hook for a plugin to say whether we can possibly authenticate any peer */ int (*pap_check_hook) __P((void)) = NULL; /* Hook for a plugin to check the PAP user and password */ int (*pap_auth_hook) __P((char *user, char *passwd, char **msgp, struct wordlist **paddrs, struct wordlist **popts)) = NULL; /* Hook for a plugin to know about the PAP user logout */ void (*pap_logout_hook) __P((void)) = NULL; /* Hook for a plugin to get the PAP password for authenticating us */ int (*pap_passwd_hook) __P((char *user, char *passwd)) = NULL; /* * This is used to ensure that we don't start an auth-up/down * script while one is already running. */ enum script_state { s_down, s_up }; static enum script_state auth_state = s_down; static enum script_state auth_script_state = s_down; static pid_t auth_script_pid = 0; /* * This is set by scan_authfile if a client matches, but server doesn't * (possible configuration error). */ static char scan_server_match_failed[MAXWORDLEN]; /* * Option variables. */ bool uselogin = 0; /* Use /etc/passwd for checking PAP */ bool cryptpap = 0; /* Passwords in pap-secrets are encrypted */ bool refuse_pap = 0; /* Don't wanna auth. ourselves with PAP */ bool refuse_chap = 0; /* Don't wanna auth. ourselves with CHAP */ bool usehostname = 0; /* Use hostname for our_name */ bool auth_required = 0; /* Always require authentication from peer */ bool allow_any_ip = 0; /* Allow peer to use any IP address */ bool explicit_remote = 0; /* User specified explicit remote name */ char remote_name[MAXNAMELEN]; /* Peer's name for authentication */ #ifdef CHAPMS bool refuse_mschap = 0; /* Don't wanna auth. ourself with MS-CHAPv1 */ #else bool refuse_mschap = 1; /* Never auth. ourself with MS-CHAPv1 */ #endif #ifdef CHAPMSV2 bool refuse_mschapv2 = 0; /* Don't wanna auth. ourself with MS-CHAPv2 */ #else bool refuse_mschapv2 = 1; /* Never auth. ourself with MS-CHAPv2 */ #endif #ifdef USE_PAM bool use_pam = 1; /* Enable use of PAM by default */ #else bool use_pam = 0; /* Disable use of PAM by default */ #endif /* Bits in auth_pending[] */ #define PAP_WITHPEER 1 #define PAP_PEER 2 #define CHAP_WITHPEER 4 #define CHAP_PEER 8 /* Prototypes for procedures local to this file. */ static void network_phase __P((int)); static void check_idle __P((void *)); static void connect_time_expired __P((void *)); static int plogin __P((char *, char *, char **)); static void plogout __P((void)); static int null_login __P((int)); static int get_pap_passwd __P((char *)); static int have_pap_secret __P((int *)); static int have_chap_secret __P((char *, char *, int, int *)); static int ip_addr_check __P((u_int32_t, struct permitted_ip *)); static int scan_authfile __P((FILE *, char *, char *, char *, struct wordlist **, struct wordlist **, char *)); static void free_wordlist __P((struct wordlist *)); static void auth_script __P((char *)); static void auth_script_done __P((void *, int)); static void set_allowed_addrs __P((int, struct wordlist *, struct wordlist *)); static int some_ip_ok __P((struct wordlist *)); static int setupapfile __P((char **, option_t *)); static int privgroup __P((char **, option_t *)); static int set_noauth_addr __P((char **, option_t *)); static void check_access __P((FILE *, char *)); /* * Authentication-related options. */ option_t auth_options[] = { { "require-pap", o_bool, &lcp_wantoptions[0].neg_upap, "Require PAP authentication from peer", 1, &auth_required }, { "+pap", o_bool, &lcp_wantoptions[0].neg_upap, "Require PAP authentication from peer", 1, &auth_required }, { "refuse-pap", o_bool, &refuse_pap, "Don't agree to auth to peer with PAP", 1 }, { "-pap", o_bool, &refuse_pap, "Don't allow PAP authentication with peer", 1 }, { "require-chap", o_bool, &lcp_wantoptions[0].neg_chap, "Require CHAP authentication from peer", 1, &auth_required }, { "+chap", o_bool, &lcp_wantoptions[0].neg_chap, "Require CHAP authentication from peer", 1, &auth_required }, { "refuse-chap", o_bool, &refuse_chap, "Don't agree to auth to peer with CHAP", 1 }, { "-chap", o_bool, &refuse_chap, "Don't allow CHAP authentication with peer", 1 }, { "name", o_string, our_name, "Set local name for authentication", OPT_PRIV|OPT_STATIC, NULL, MAXNAMELEN }, { "user", o_string, user, "Set name for auth with peer", OPT_STATIC, NULL, MAXNAMELEN }, { "usehostname", o_bool, &usehostname, "Must use hostname for authentication", 1 }, { "remotename", o_string, remote_name, "Set remote name for authentication", OPT_STATIC, &explicit_remote, MAXNAMELEN }, { "auth", o_bool, &auth_required, "Require authentication from peer", 1 }, { "noauth", o_bool, &auth_required, "Don't require peer to authenticate", OPT_PRIV, &allow_any_ip }, { "login", o_bool, &uselogin, "Use system password database for PAP", 1 }, { "papcrypt", o_bool, &cryptpap, "PAP passwords are encrypted", 1 }, { "+ua", o_special, (void *)setupapfile, "Get PAP user and password from file" }, { "password", o_string, passwd, "Password for authenticating us to the peer", OPT_STATIC, NULL, MAXSECRETLEN }, { "privgroup", o_special, (void *)privgroup, "Allow group members to use privileged options", OPT_PRIV }, { "allow-ip", o_special, (void *)set_noauth_addr, "Set peer IP address(es) usable without authentication", OPT_PRIV }, #ifdef CHAPMS { "require-mschap", o_bool, &lcp_wantoptions[0].neg_mschap, "Require MS-CHAPv1 authentication from peer", 1, &auth_required }, { "refuse-mschap", o_bool, &refuse_mschap, "Don't agree to authenticate to peer with MS-CHAPv1", 1 }, #endif #ifdef CHAPMSV2 { "require-mschapv2", o_bool, &lcp_wantoptions[0].neg_mschapv2, "Require MS-CHAPv2 authentication from peer", 1, &auth_required }, { "refuse-mschapv2", o_bool, &refuse_mschapv2, "Don't agree to authenticate to peer with MS-CHAPv2", 1 }, #endif #ifdef ALLOW_PAM { "pam", o_bool, &use_pam, "Enable use of Pluggable Authentication Modules", OPT_PRIV|1 }, { "nopam", o_bool, &use_pam, "Disable use of Pluggable Authentication Modules", OPT_PRIV|0 }, #endif { NULL } }; /* * setupapfile - specifies UPAP info for authenticating with peer. */ /*ARGSUSED*/ static int setupapfile(argv, opt) char **argv; option_t *opt; { FILE * ufile; int l; lcp_allowoptions[0].neg_upap = 1; /* open user info file */ (void) seteuid(getuid()); ufile = fopen(*argv, "r"); (void) seteuid(0); if (ufile == NULL) { option_error("unable to open user login data file %s", *argv); return 0; } check_access(ufile, *argv); /* get username */ if (fgets(user, MAXNAMELEN - 1, ufile) == NULL || fgets(passwd, MAXSECRETLEN - 1, ufile) == NULL){ option_error("unable to read user login data file %s", *argv); return 0; } (void) fclose(ufile); /* get rid of newlines */ l = strlen(user); if (l > 0 && user[l-1] == '\n') user[l-1] = '\0'; l = strlen(passwd); if (l > 0 && passwd[l-1] == '\n') passwd[l-1] = '\0'; return (1); } /* * privgroup - allow members of the group to have privileged access. */ /*ARGSUSED*/ static int privgroup(argv, opt) char **argv; option_t *opt; { struct group *g; int i; g = getgrnam(*argv); if (g == NULL) { option_error("group %s is unknown", *argv); return 0; } for (i = 0; i < ngroups; ++i) { if (groups[i] == g->gr_gid) { privileged = 1; break; } } return 1; } /* * set_noauth_addr - set address(es) that can be used without authentication. * Equivalent to specifying an entry like `"" * "" addr' in pap-secrets. */ /*ARGSUSED*/ static int set_noauth_addr(argv, opt) char **argv; option_t *opt; { char *addr = *argv; int l = strlen(addr); struct wordlist *wp; wp = (struct wordlist *) malloc(sizeof(struct wordlist) + l + 1); if (wp == NULL) novm("allow-ip argument"); wp->word = (char *) (wp + 1); wp->next = noauth_addrs; (void) strcpy(wp->word, addr); noauth_addrs = wp; return 1; } /* * An Open on LCP has requested a change from Dead to Establish phase. * Do what's necessary to bring the physical layer up. */ /*ARGSUSED*/ void link_required(unit) int unit; { } /* * LCP has terminated the link; go to the Dead phase and take the * physical layer down. */ /*ARGSUSED*/ void link_terminated(unit) int unit; { const char *pn1, *pn2; if (phase == PHASE_DEAD) return; if (pap_logout_hook != NULL) { (*pap_logout_hook)(); } else { if (logged_in) plogout(); } new_phase(PHASE_DEAD); if (peer_nak_auth) { if ((pn1 = protocol_name(nak_auth_orig)) == NULL) pn1 = "?"; if ((pn2 = protocol_name(nak_auth_proto)) == NULL) pn2 = "?"; warn("Peer sent Configure-Nak for 0x%x (%s) to suggest 0x%x (%s)", nak_auth_orig, pn1, nak_auth_proto, pn2); } if (unsolicited_nak_auth) { if ((pn1 = protocol_name(unsolicit_auth_proto)) == NULL) pn1 = "?"; warn("Peer unexpectedly asked us to authenticate with 0x%x (%s)", unsolicit_auth_proto, pn1); } if (peer_reject_auth) { if ((pn1 = protocol_name(reject_auth_proto)) == NULL) pn1 = "?"; warn("Peer rejected our demand for 0x%x (%s)", reject_auth_proto, pn1); } if (naked_peers_auth) { if ((pn1 = protocol_name(naked_auth_orig)) == NULL) pn1 = "?"; if ((pn2 = protocol_name(naked_auth_proto)) == NULL) pn2 = "?"; warn("We set Configure-Nak for 0x%x (%s) to suggest 0x%x (%s)", naked_auth_orig, pn1, naked_auth_proto, pn2); } if (rejected_peers_auth) { if ((pn1 = protocol_name(rejected_auth_proto)) == NULL) pn1 = "?"; warn("We rejected the peer's demand for 0x%x (%s)", rejected_auth_proto, pn1); } peer_nak_auth = unsolicited_nak_auth = peer_reject_auth = rejected_peers_auth = naked_peers_auth = 0; nak_auth_proto = nak_auth_orig = unsolicit_auth_proto = reject_auth_proto = rejected_auth_proto = naked_auth_orig = naked_auth_proto = 0; notice("Connection terminated."); } /* * LCP has gone down; it will either die or try to re-establish. */ void link_down(unit) int unit; { int i; struct protent *protp; auth_state = s_down; if (auth_script_state == s_up && auth_script_pid == 0) { update_link_stats(unit); auth_script_state = s_down; auth_script(_PATH_AUTHDOWN); } for (i = 0; (protp = protocols[i]) != NULL; ++i) { if (!protp->enabled_flag) continue; if (protp->protocol != PPP_LCP && protp->lowerdown != NULL) (*protp->lowerdown)(unit); if (protp->protocol < 0xC000 && protp->close != NULL) (*protp->close)(unit, "LCP down"); } num_np_open = 0; num_np_up = 0; if (phase != PHASE_DEAD) new_phase(PHASE_TERMINATE); } /* * The link is established. * Proceed to the Dead, Authenticate or Network phase as appropriate. */ void link_established(unit) int unit; { int auth; lcp_options *wo = &lcp_wantoptions[unit]; lcp_options *go = &lcp_gotoptions[unit]; lcp_options *ho = &lcp_hisoptions[unit]; int i; struct protent *protp; /* * Tell higher-level protocols that LCP is up. */ for (i = 0; (protp = protocols[i]) != NULL; ++i) if (protp->protocol != PPP_LCP && protp->enabled_flag && protp->lowerup != NULL) (*protp->lowerup)(unit); if (auth_required && !(go->neg_chap || go->neg_mschap || go->neg_mschapv2 || go->neg_upap)) { /* * We wanted the peer to authenticate itself, and it refused: * if we have some address(es) it can use without auth, fine, * otherwise treat it as though it authenticated with PAP using * a username * of "" and a password of "". If that's not OK, * boot it out. */ if (noauth_addrs != NULL) { set_allowed_addrs(unit, noauth_addrs, NULL); } else if (!wo->neg_upap || !null_login(unit)) { warn("peer refused to authenticate: terminating link"); lcp_close(unit, "peer refused to authenticate"); status = EXIT_PEER_AUTH_FAILED; return; } } new_phase(PHASE_AUTHENTICATE); auth = 0; if (go->neg_chap || go->neg_mschap || go->neg_mschapv2) { if (go->neg_chap) { if (debug) dbglog("Authenticating peer with standard CHAP"); go->chap_mdtype = CHAP_DIGEST_MD5; } else if (go->neg_mschap) { if (debug) dbglog("Authenticating peer with MS-CHAPv1"); go->chap_mdtype = CHAP_MICROSOFT; } else { if (debug) dbglog("Authenticating peer with MS-CHAPv2"); go->chap_mdtype = CHAP_MICROSOFT_V2; } ChapAuthPeer(unit, our_name, go->chap_mdtype); auth |= CHAP_PEER; } else if (go->neg_upap) { if (debug) dbglog("Authenticating peer with PAP"); upap_authpeer(unit); auth |= PAP_PEER; } if (ho->neg_chap || ho->neg_mschap || ho->neg_mschapv2) { switch (ho->chap_mdtype) { case CHAP_DIGEST_MD5: if (debug) dbglog("Authenticating to peer with standard CHAP"); break; case CHAP_MICROSOFT: if (debug) dbglog("Authenticating to peer with MS-CHAPv1"); break; case CHAP_MICROSOFT_V2: if (debug) dbglog("Authenticating to peer with MS-CHAPv2"); break; default: if (debug) dbglog("Authenticating to peer with CHAP 0x%x", ho->chap_mdtype); break; } ChapAuthWithPeer(unit, user, ho->chap_mdtype); auth |= CHAP_WITHPEER; } else if (ho->neg_upap) { if (passwd[0] == '\0') { passwd_from_file = 1; if (!get_pap_passwd(passwd)) error("No secret found for PAP login"); } if (debug) dbglog("Authenticating to peer with PAP"); upap_authwithpeer(unit, user, passwd); auth |= PAP_WITHPEER; } auth_pending[unit] = auth; if (!auth) network_phase(unit); } /* * Proceed to the network phase. */ static void network_phase(unit) int unit; { lcp_options *go = &lcp_gotoptions[unit]; /* * If the peer had to authenticate, run the auth-up script now. */ if (go->neg_chap || go->neg_mschap || go->neg_mschapv2 || go->neg_upap) { auth_state = s_up; if (auth_script_state == s_down && auth_script_pid == 0) { auth_script_state = s_up; auth_script(_PATH_AUTHUP); } } /* * Process extra options from the secrets file */ if (extra_options != NULL) { option_source = (char *)extra_opt_filename; option_line = extra_opt_line; (void) options_from_list(extra_options, 1); free_wordlist(extra_options); extra_options = NULL; } #ifdef CBCP_SUPPORT /* * If we negotiated callback, do it now. */ if (go->neg_cbcp) { new_phase(PHASE_CALLBACK); (*cbcp_protent.open)(unit); return; } #endif start_networks(); } void start_networks() { int i; struct protent *protp; new_phase(PHASE_NETWORK); #ifdef HAVE_MULTILINK if (multilink) { if (mp_join_bundle()) { if (updetach && !nodetach) detach(); return; } } #endif /* HAVE_MULTILINK */ #if 0 if (!demand) set_filters(&pass_filter, &active_filter); #endif for (i = 0; (protp = protocols[i]) != NULL; ++i) if (protp->protocol < 0xC000 && protp->enabled_flag && protp->open != NULL) { (*protp->open)(0); if (protp->protocol != PPP_CCP) ++num_np_open; } if (num_np_open == 0) /* nothing to do */ lcp_close(0, "No network protocols running"); } /* * The peer has failed to authenticate himself using `protocol'. */ /*ARGSUSED*/ void auth_peer_fail(unit, protocol) int unit, protocol; { /* * Authentication failure: take the link down */ lcp_close(unit, "Authentication failed"); status = EXIT_PEER_AUTH_FAILED; } /* * The peer has been successfully authenticated using `protocol'. */ void auth_peer_success(unit, protocol, name, namelen) int unit, protocol; char *name; int namelen; { int bit; switch (protocol) { case PPP_CHAP: bit = CHAP_PEER; break; case PPP_PAP: bit = PAP_PEER; break; default: warn("auth_peer_success: unknown protocol %x", protocol); return; } /* * Save the authenticated name of the peer for later. */ if (namelen > sizeof(peer_authname) - 1) namelen = sizeof(peer_authname) - 1; BCOPY(name, peer_authname, namelen); peer_authname[namelen] = '\0'; script_setenv("PEERNAME", peer_authname, 0); /* * If there is no more authentication still to be done, * proceed to the network (or callback) phase. */ if ((auth_pending[unit] &= ~bit) == 0) network_phase(unit); } /* * We have failed to authenticate ourselves to the peer using `protocol'. */ /*ARGSUSED*/ void auth_withpeer_fail(unit, protocol) int unit, protocol; { if (passwd_from_file) BZERO(passwd, MAXSECRETLEN); /* * We've failed to authenticate ourselves to our peer. * Some servers keep sending CHAP challenges, but there * is no point in persisting without any way to get updated * authentication secrets. */ lcp_close(unit, "Failed to authenticate ourselves to peer"); status = EXIT_AUTH_TOPEER_FAILED; } /* * We have successfully authenticated ourselves with the peer using `protocol'. */ void auth_withpeer_success(unit, protocol) int unit, protocol; { int bit; switch (protocol) { case PPP_CHAP: bit = CHAP_WITHPEER; break; case PPP_PAP: if (passwd_from_file) BZERO(passwd, MAXSECRETLEN); bit = PAP_WITHPEER; break; default: warn("auth_withpeer_success: unknown protocol %x", protocol); bit = 0; } /* * If there is no more authentication still being done, * proceed to the network (or callback) phase. */ if ((auth_pending[unit] &= ~bit) == 0) network_phase(unit); } /* * np_up - a network protocol has come up. */ /*ARGSUSED*/ void np_up(unit, proto) int unit, proto; { int tlim; if (num_np_up == 0) { /* * At this point we consider that the link has come up successfully. */ status = EXIT_OK; unsuccess = 0; peer_nak_auth = unsolicited_nak_auth = peer_reject_auth = rejected_peers_auth = naked_peers_auth = 0; nak_auth_proto = nak_auth_orig = unsolicit_auth_proto = reject_auth_proto = rejected_auth_proto = naked_auth_orig = naked_auth_proto = 0; new_phase(PHASE_RUNNING); if (idle_time_hook != NULL) tlim = (*idle_time_hook)(NULL); else tlim = idle_time_limit; if (tlim > 0) TIMEOUT(check_idle, NULL, tlim); /* * Set a timeout to close the connection once the maximum * connect time has expired. */ if (maxconnect > 0) { TIMEOUT(connect_time_expired, &lcp_fsm[unit], maxconnect); /* * Tell LCP to send Time-Remaining packets. One should be * sent out now, at maxconnect-300, at maxconnect-120, and * again at maxconnect-30. */ lcp_settimeremaining(unit, maxconnect, maxconnect); if (maxconnect > 300) lcp_settimeremaining(unit, maxconnect, 300); if (maxconnect > 120) lcp_settimeremaining(unit, maxconnect, 120); if (maxconnect > 30) lcp_settimeremaining(unit, maxconnect, 30); } /* * Detach now, if the updetach option was given. */ if (updetach && !nodetach) detach(); } ++num_np_up; } /* * np_down - a network protocol has gone down. */ /*ARGSUSED*/ void np_down(unit, proto) int unit, proto; { if (--num_np_up == 0) { UNTIMEOUT(check_idle, NULL); new_phase(PHASE_NETWORK); } } /* * np_finished - a network protocol has finished using the link. */ /*ARGSUSED*/ void np_finished(unit, proto) int unit, proto; { if (--num_np_open <= 0) { /* no further use for the link: shut up shop. */ lcp_close(0, "No network protocols running"); } } /* * check_idle - check whether the link has been idle for long * enough that we can shut it down. */ /*ARGSUSED*/ static void check_idle(arg) void *arg; { struct ppp_idle idle; time_t itime; int tlim; if (!get_idle_time(0, &idle)) return; if (idle_time_hook != NULL) { tlim = (*idle_time_hook)(&idle); } else { itime = MIN(idle.xmit_idle, idle.recv_idle); tlim = idle_time_limit - itime; } if (tlim <= 0) { /* link is idle: shut it down. */ notice("Terminating connection due to lack of activity."); lcp_close(0, "Link inactive"); need_holdoff = 0; status = EXIT_IDLE_TIMEOUT; } else { TIMEOUT(check_idle, NULL, tlim); } } /* * connect_time_expired - log a message and close the connection. */ /*ARGSUSED*/ static void connect_time_expired(arg) void *arg; { fsm *f = (fsm *)arg; info("Connect time expired"); lcp_close(f->unit, "Connect time expired"); /* Close connection */ status = EXIT_CONNECT_TIME; } /* * auth_check_options - called to check authentication options. */ void auth_check_options() { lcp_options *wo = &lcp_wantoptions[0]; int can_auth; int lacks_ip; /* Default our_name to hostname, and user to our_name */ if (our_name[0] == '\0' || usehostname) (void) strlcpy(our_name, hostname, sizeof(our_name)); if (user[0] == '\0') (void) strlcpy(user, our_name, sizeof(user)); /* * If we have a default route, require the peer to authenticate * unless the noauth option was given or the real user is root. */ if (!auth_required && !allow_any_ip && have_route_to(0) && !privileged) { auth_required = 1; default_auth = 1; } /* If authentication is required, ask peer for CHAP or PAP. */ if (auth_required) { if (!wo->neg_chap && !wo->neg_mschap && !wo->neg_mschapv2 && !wo->neg_upap) { wo->neg_chap = 1; #ifdef CHAPMS wo->neg_mschap = 1; #endif #ifdef CHAPMSV2 wo->neg_mschapv2 = 1; #endif wo->chap_mdtype = CHAP_DIGEST_MD5; wo->neg_upap = 1; } } else { wo->neg_chap = 0; wo->neg_mschap = 0; wo->neg_mschapv2 = 0; wo->neg_upap = 0; } /* * Check whether we have appropriate secrets to use * to authenticate the peer. */ lacks_ip = 0; can_auth = wo->neg_upap && (uselogin || have_pap_secret(&lacks_ip)); if (!can_auth && (wo->neg_chap || wo->neg_mschap || wo->neg_mschapv2)) { can_auth = have_chap_secret((explicit_remote? remote_name: NULL), our_name, 1, &lacks_ip); } if (auth_required && !can_auth && noauth_addrs == NULL) { if (default_auth) { option_error( "By default the remote system is required to authenticate itself"); option_error( "(because this system has a default route to the Internet)"); } else if (explicit_remote) option_error( "The remote system (%s) is required to authenticate itself", remote_name); else option_error( "The remote system is required to authenticate itself"); option_error( "but I couldn't find any suitable secret (password) for it to use to do so."); if (lacks_ip) option_error( "(None of the available passwords would let it use an IP address.)"); exit(1); } } /* * auth_reset - called when LCP is starting negotiations to recheck * authentication options, i.e. whether we have appropriate secrets * to use for authenticating ourselves and/or the peer. */ void auth_reset(unit) int unit; { lcp_options *go = &lcp_gotoptions[unit]; lcp_options *ao = &lcp_allowoptions[unit]; int havesecret; ao->neg_upap = !refuse_pap && (passwd[0] != '\0' || get_pap_passwd(NULL)); havesecret = passwd[0] != '\0' || have_chap_secret(user, (explicit_remote? remote_name: NULL), 0, NULL); ao->neg_chap = !refuse_chap && havesecret; ao->neg_mschap = !refuse_mschap && havesecret; ao->neg_mschapv2 = !refuse_mschapv2 && havesecret; if (ao->neg_chap) ao->chap_mdtype = CHAP_DIGEST_MD5; else if (ao->neg_mschap) ao->chap_mdtype = CHAP_MICROSOFT; else ao->chap_mdtype = CHAP_MICROSOFT_V2; if (go->neg_upap && !uselogin && !have_pap_secret(NULL)) go->neg_upap = 0; if (go->neg_chap || go->neg_mschap || go->neg_mschapv2) { havesecret = have_chap_secret((explicit_remote? remote_name: NULL), our_name, 1, NULL); if (!havesecret) go->neg_chap = go->neg_mschap = go->neg_mschapv2 = 0; else if (go->neg_chap) go->chap_mdtype = CHAP_DIGEST_MD5; else if (go->neg_mschap) go->chap_mdtype = CHAP_MICROSOFT; else go->chap_mdtype = CHAP_MICROSOFT_V2; } } /* * check_passwd - Check the user name and passwd against the PAP secrets * file. If requested, also check against the system password database, * and login the user if OK. * * returns: * UPAP_AUTHNAK: Authentication failed. * UPAP_AUTHACK: Authentication succeeded. * In either case, msg points to an appropriate message. */ int check_passwd(unit, auser, userlen, apasswd, passwdlen, msg) int unit; char *auser; int userlen; char *apasswd; int passwdlen; char **msg; { int ret; char *filename; FILE *f; struct wordlist *addrs = NULL, *opts = NULL; char passwd[256], user[256]; char secret[MAXWORDLEN]; static int attempts = 0; /* * Make copies of apasswd and auser, then null-terminate them. * If there are unprintable characters in the password, make * them visible. */ (void) slprintf(passwd, sizeof(passwd), "%.*v", passwdlen, apasswd); (void) slprintf(user, sizeof(user), "%.*v", userlen, auser); *msg = ""; /* * Check if a plugin wants to handle this. */ if (pap_auth_hook != NULL) { /* Set a default and allow the plug-in to change it. */ extra_opt_filename = "plugin"; extra_opt_line = 0; ret = (*pap_auth_hook)(user, passwd, msg, &addrs, &opts); if (ret >= 0) { if (ret > 0) set_allowed_addrs(unit, addrs, opts); BZERO(passwd, sizeof(passwd)); if (addrs != NULL) free_wordlist(addrs); return ret? UPAP_AUTHACK: UPAP_AUTHNAK; } } /* * Open the file of pap secrets and scan for a suitable secret * for authenticating this user. */ filename = _PATH_UPAPFILE; addrs = opts = NULL; ret = UPAP_AUTHNAK; f = fopen(filename, "r"); if (f == NULL) { error("Can't open PAP password file %s: %m", filename); } else { check_access(f, filename); if (scan_authfile(f, user, our_name, secret, &addrs, &opts, filename) < 0) { warn("no PAP secret found for %s", user); if (scan_server_match_failed[0] != '\0') warn("possible configuration error: local name is %q, but " "found %q instead", our_name, scan_server_match_failed); } else if (secret[0] != '\0') { /* password given in pap-secrets - must match */ if ((!cryptpap && strcmp(passwd, secret) == 0) || strcmp(crypt(passwd, secret), secret) == 0) ret = UPAP_AUTHACK; else warn("PAP authentication failure for %s", user); } else if (uselogin) { /* empty password in pap-secrets and login option */ ret = plogin(user, passwd, msg); if (ret == UPAP_AUTHNAK) warn("PAP login failure for %s", user); } else { /* empty password in pap-secrets and login option not used */ ret = UPAP_AUTHACK; } (void) fclose(f); } if (ret == UPAP_AUTHNAK) { if (**msg == '\0') *msg = "Login incorrect"; /* * Frustrate passwd stealer programs. * Allow 10 tries, but start backing off after 3 (stolen from login). * On 10'th, drop the connection. */ if (attempts++ >= 10) { warn("%d LOGIN FAILURES ON %s, %s", attempts, devnam, user); lcp_close(unit, "login failed"); } if (attempts > 3) (void) sleep((u_int) (attempts - 3) * 5); if (opts != NULL) free_wordlist(opts); } else { attempts = 0; /* Reset count */ if (**msg == '\0') *msg = "Login ok"; set_allowed_addrs(unit, addrs, opts); } if (addrs != NULL) free_wordlist(addrs); BZERO(passwd, sizeof(passwd)); BZERO(secret, sizeof(secret)); return ret; } /* * This function is needed for PAM. */ #ifdef ALLOW_PAM /* Static variables used to communicate between the conversation function * and the server_login function */ static char *PAM_username; static char *PAM_password; static int PAM_error = 0; static pam_handle_t *pamh = NULL; /* PAM conversation function * Here we assume (for now, at least) that echo on means login name, and * echo off means password. */ static int PAM_conv (int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { int replies = 0; struct pam_response *reply = NULL; #define COPY_STRING(s) (s) ? strdup(s) : NULL reply = malloc(sizeof(struct pam_response) * num_msg); if (reply == NULL) return PAM_CONV_ERR; for (replies = 0; replies < num_msg; replies++) { switch (msg[replies]->msg_style) { case PAM_PROMPT_ECHO_ON: reply[replies].resp_retcode = PAM_SUCCESS; reply[replies].resp = COPY_STRING(PAM_username); /* PAM frees resp */ break; case PAM_PROMPT_ECHO_OFF: reply[replies].resp_retcode = PAM_SUCCESS; reply[replies].resp = COPY_STRING(PAM_password); /* PAM frees resp */ break; case PAM_TEXT_INFO: /* fall through */ case PAM_ERROR_MSG: /* ignore it, but pam still wants a NULL response... */ reply[replies].resp_retcode = PAM_SUCCESS; reply[replies].resp = NULL; break; default: /* Must be an error of some sort... */ free (reply); PAM_error = 1; return PAM_CONV_ERR; } } *resp = reply; return PAM_SUCCESS; } static struct pam_conv PAM_conversation = { PAM_conv, NULL }; #endif /* ALLOW_PAM */ /* * plogin - Check the user name and password against the system * password database, and login the user if OK. * * returns: * UPAP_AUTHNAK: Login failed. * UPAP_AUTHACK: Login succeeded. * In either case, msg points to an appropriate message. */ static int plogin(user, passwd, msg) char *user; char *passwd; char **msg; { char *tty; #ifdef HAS_SHADOW struct spwd *spwd; #endif struct passwd *pw = NULL; #ifdef ALLOW_PAM int pam_error; if (use_pam) { if (debug) dbglog("using PAM for user authentication"); pam_error = pam_start ("ppp", user, &PAM_conversation, &pamh); if (pam_error != PAM_SUCCESS) { *msg = (char *) pam_strerror (pamh, pam_error); reopen_log(); return UPAP_AUTHNAK; } /* * Define the fields for the credential validation */ PAM_username = user; PAM_password = passwd; PAM_error = 0; /* this might be useful to some modules; required for Solaris */ tty = devnam; if (*tty == '\0') tty = ppp_devnam; (void) pam_set_item(pamh, PAM_TTY, tty); #ifdef PAM_RHOST (void) pam_set_item(pamh, PAM_RHOST, ""); #endif /* * Validate the user */ pam_error = pam_authenticate (pamh, PAM_SILENT); if (pam_error == PAM_SUCCESS && !PAM_error) { pam_error = pam_acct_mgmt (pamh, PAM_SILENT); if (pam_error == PAM_SUCCESS) (void) pam_open_session (pamh, PAM_SILENT); } *msg = (char *) pam_strerror (pamh, pam_error); /* * Clean up the mess */ reopen_log(); /* apparently the PAM stuff does closelog() */ PAM_username = NULL; PAM_password = NULL; if (pam_error != PAM_SUCCESS) return UPAP_AUTHNAK; } else #endif /* ALLOW_PAM */ { if (debug) { #ifdef HAS_SHADOW dbglog("using passwd/shadow for user authentication"); #else dbglog("using passwd for user authentication"); #endif } /* * Use the non-PAM methods directly */ pw = getpwnam(user); endpwent(); if (pw == NULL) return (UPAP_AUTHNAK); #ifdef HAS_SHADOW spwd = getspnam(user); endspent(); if (spwd != NULL) { /* check the age of the password entry */ long now = time(NULL) / 86400L; if ((spwd->sp_expire > 0 && now >= spwd->sp_expire) || ((spwd->sp_max >= 0 && spwd->sp_max < 10000) && spwd->sp_lstchg >= 0 && now >= spwd->sp_lstchg + spwd->sp_max)) { warn("Password for %s has expired", user); return (UPAP_AUTHNAK); } pw->pw_passwd = spwd->sp_pwdp; } #endif /* * If no passwd, don't let them login. */ if (pw->pw_passwd == NULL || strlen(pw->pw_passwd) < 2 || strcmp(crypt(passwd, pw->pw_passwd), pw->pw_passwd) != 0) return (UPAP_AUTHNAK); } /* * Write a wtmp entry for this user. */ tty = devnam; if (strncmp(tty, "/dev/", 5) == 0) tty += 5; logwtmp(tty, user, remote_name); /* Add wtmp login entry */ #ifdef _PATH_LASTLOG if (!use_pam && pw != (struct passwd *)NULL) { struct lastlog ll; int fd; if ((fd = open(_PATH_LASTLOG, O_RDWR, 0)) >= 0) { (void)lseek(fd, (off_t)(pw->pw_uid * sizeof(ll)), SEEK_SET); BZERO((void *)&ll, sizeof(ll)); (void)time(&ll.ll_time); (void)strncpy(ll.ll_line, tty, sizeof(ll.ll_line)); (void)write(fd, (char *)&ll, sizeof(ll)); (void)close(fd); } } #endif /* _PATH_LASTLOG */ info("user %s logged in", user); logged_in = 1; return (UPAP_AUTHACK); } /* * plogout - Logout the user. */ static void plogout() { char *tty; #ifdef ALLOW_PAM int pam_error; if (use_pam) { if (pamh != NULL) { pam_error = pam_close_session (pamh, PAM_SILENT); (void) pam_end (pamh, pam_error); pamh = NULL; } /* Apparently the pam stuff does closelog(). */ reopen_log(); } else #endif /* ALLOW_PAM */ { tty = devnam; if (strncmp(tty, "/dev/", 5) == 0) tty += 5; /* Wipe out utmp logout entry */ logwtmp(tty, "", ""); } logged_in = 0; } /* * null_login - Check if a username of "" and a password of "" are * acceptable, and iff so, set the list of acceptable IP addresses * and return 1. */ static int null_login(unit) int unit; { char *filename; FILE *f; int i, ret; struct wordlist *addrs, *opts; char secret[MAXWORDLEN]; /* * Open the file of pap secrets and scan for a suitable secret. */ filename = _PATH_UPAPFILE; addrs = NULL; f = fopen(filename, "r"); if (f == NULL) return 0; check_access(f, filename); i = scan_authfile(f, "", our_name, secret, &addrs, &opts, filename); ret = i >= 0 && secret[0] == '\0'; BZERO(secret, sizeof(secret)); if (ret) set_allowed_addrs(unit, addrs, opts); else if (opts != NULL) free_wordlist(opts); if (addrs != NULL) free_wordlist(addrs); (void) fclose(f); return ret; } /* * get_pap_passwd - get a password for authenticating ourselves with * our peer using PAP. Returns 1 on success, 0 if no suitable password * could be found. * Assumes passwd points to MAXSECRETLEN bytes of space (if non-null). */ static int get_pap_passwd(passwd) char *passwd; { char *filename; FILE *f; int ret; char secret[MAXWORDLEN]; /* * Check whether a plugin wants to supply this. */ if (pap_passwd_hook != NULL) { ret = (*pap_passwd_hook)(user, passwd); if (ret >= 0) return ret; } filename = _PATH_UPAPFILE; f = fopen(filename, "r"); if (f == NULL) return 0; check_access(f, filename); ret = scan_authfile(f, user, (remote_name[0] != '\0' ? remote_name: NULL), secret, NULL, NULL, filename); (void) fclose(f); if (ret < 0) return 0; if (passwd != NULL) (void) strlcpy(passwd, secret, MAXSECRETLEN); BZERO(secret, sizeof(secret)); return 1; } /* * have_pap_secret - check whether we have a PAP file with any * secrets that we could possibly use for authenticating the peer. */ static int have_pap_secret(lacks_ipp) int *lacks_ipp; { FILE *f; int ret; char *filename; struct wordlist *addrs; /* let the plugin decide, if there is one */ if (pap_check_hook != NULL) { ret = (*pap_check_hook)(); if (ret >= 0) return ret; } filename = _PATH_UPAPFILE; f = fopen(filename, "r"); if (f == NULL) return 0; ret = scan_authfile(f, (explicit_remote? remote_name: NULL), our_name, NULL, &addrs, NULL, filename); (void) fclose(f); if (ret >= 0 && !some_ip_ok(addrs)) { if (lacks_ipp != NULL) *lacks_ipp = 1; ret = -1; } if (addrs != NULL) free_wordlist(addrs); return ret >= 0; } /* * have_chap_secret - check whether we have a CHAP file with a secret * that we could possibly use for authenticating `client' on `server'. * Either or both can be the null string, meaning we don't know the * identity yet. */ static int have_chap_secret(client, server, need_ip, lacks_ipp) char *client; char *server; int need_ip; int *lacks_ipp; { FILE *f; int ret; char *filename; struct wordlist *addrs; filename = _PATH_CHAPFILE; f = fopen(filename, "r"); if (f == NULL) return 0; if (client != NULL && client[0] == '\0') client = NULL; if (server != NULL && server[0] == '\0') server = NULL; ret = scan_authfile(f, client, server, NULL, &addrs, NULL, filename); (void) fclose(f); if (ret >= 0 && need_ip && !some_ip_ok(addrs)) { if (lacks_ipp != NULL) *lacks_ipp = 1; ret = -1; } if (addrs != NULL) free_wordlist(addrs); return ret >= 0; } /* * get_secret - open the CHAP secret file and return the secret * for authenticating the given client on the given server. * * "am_server" means that we're the authenticator (demanding * identity from the peer). * * "!am_server" means that we're the authenticatee (supplying * identity to the peer). */ int get_secret(unit, client, server, secret, secret_len, am_server) int unit; char *client; char *server; char *secret; int *secret_len; int am_server; { FILE *f; int ret, len; char *filename; struct wordlist *addrs, *opts; char secbuf[MAXWORDLEN]; /* * Support the 'password' option on authenticatee only in order to * avoid obvious security problem (authenticator and authenticatee * within a given implementation must never share secrets). */ if (!am_server && passwd[0] != '\0') { (void) strlcpy(secbuf, passwd, sizeof(secbuf)); } else { filename = _PATH_CHAPFILE; addrs = NULL; secbuf[0] = '\0'; f = fopen(filename, "r"); if (f == NULL) { error("Can't open chap secret file %s: %m", filename); return 0; } check_access(f, filename); ret = scan_authfile(f, client, server, secbuf, &addrs, &opts, filename); (void) fclose(f); if (ret < 0) { if (scan_server_match_failed[0] != '\0') warn("possible configuration error: local name is %q, but " "found %q instead", our_name, scan_server_match_failed); return 0; } /* Only the authenticator cares about limiting peer addresses. */ if (am_server) set_allowed_addrs(unit, addrs, opts); else if (opts != NULL) free_wordlist(opts); if (addrs != NULL) free_wordlist(addrs); } len = strlen(secbuf); if (len > MAXSECRETLEN) { error("Secret for %s on %s is too long", client, server); len = MAXSECRETLEN; } /* Do not leave a temporary copy of the secret on the stack. */ BCOPY(secbuf, secret, len); BZERO(secbuf, sizeof(secbuf)); *secret_len = len; return 1; } /* * set_allowed_addrs() - set the list of allowed addresses. * The caller must also look for `--' indicating options to apply for * this peer and leaves the following words in extra_options. */ static void set_allowed_addrs(unit, addrs, opts) int unit; struct wordlist *addrs; struct wordlist *opts; { int n; struct wordlist *ap, **pap; struct permitted_ip *ip; char *ptr_word, *ptr_mask; struct hostent *hp; struct netent *np; u_int32_t a, mask, newmask, ah, offset; struct ipcp_options *wo = &ipcp_wantoptions[unit]; u_int32_t suggested_ip = 0; int err_num; if (addresses[unit] != NULL) free(addresses[unit]); addresses[unit] = NULL; if (extra_options != NULL) free_wordlist(extra_options); extra_options = opts; /* * Count the number of IP addresses given. */ for (n = 0, pap = &addrs; (ap = *pap) != NULL; pap = &ap->next) ++n; if (n == 0) return; ip = (struct permitted_ip *) malloc((n + 1) * sizeof(struct permitted_ip)); if (ip == NULL) return; n = 0; for (ap = addrs; ap != NULL; ap = ap->next) { /* "-" means no addresses authorized, "*" means any address allowed */ ptr_word = ap->word; if (strcmp(ptr_word, "-") == 0) break; if (strcmp(ptr_word, "*") == 0) { ip[n].permit = 1; ip[n].base = ip[n].mask = 0; ++n; break; } ip[n].permit = 1; if (*ptr_word == '!') { ip[n].permit = 0; ++ptr_word; } mask = ~ (u_int32_t) 0; offset = 0; ptr_mask = strchr (ptr_word, '/'); if (ptr_mask != NULL) { int bit_count; char *endp; bit_count = (int) strtol (ptr_mask+1, &endp, 10); if (bit_count <= 0 || bit_count > 32) { warn("invalid address length %v in authorized address list", ptr_mask+1); continue; } bit_count = 32 - bit_count; /* # bits in host part */ if (*endp == '+') { offset = ifunit + 1; ++endp; } if (*endp != '\0') { warn("invalid address length syntax: %v", ptr_mask+1); continue; } *ptr_mask = '\0'; mask <<= bit_count; } /* Try to interpret value as host name or numeric address first */ hp = getipnodebyname(ptr_word, AF_INET, 0, &err_num); if (hp != NULL) { (void) memcpy(&a, hp->h_addr, sizeof(a)); freehostent(hp); } else { char *cp = ptr_word + strlen(ptr_word); if (cp > ptr_word) cp--; if (*cp == '+') { offset = ifunit + 1; *cp = '\0'; } np = getnetbyname (ptr_word); if (np != NULL && np->n_addrtype == AF_INET) { ah = np->n_net; newmask = (u_int32_t)~0; if ((ah & 0xff000000ul) == 0) ah <<= 8, newmask <<= 8; if ((ah & 0xff000000ul) == 0) ah <<= 8, newmask <<= 8; if ((ah & 0xff000000ul) == 0) ah <<= 8, newmask <<= 8; if (ptr_mask == NULL) mask = newmask; a = htonl(ah); } } if (ptr_mask != NULL) *ptr_mask = '/'; if (a == (u_int32_t)-1L) { warn("unknown host %s in auth. address list", ap->word); continue; } if (offset != 0) { if (offset >= ~mask) { warn("interface unit %d too large for subnet %v", ifunit, ptr_word); continue; } a = htonl((ntohl(a) & mask) + offset); mask = ~(u_int32_t)0; } ip[n].mask = htonl(mask); ip[n].base = a & ip[n].mask; ++n; if (~mask == 0 && suggested_ip == 0) suggested_ip = a; } /* Sentinel value at end of list */ ip[n].permit = 0; /* make the last entry forbid all addresses */ ip[n].base = 0; /* to terminate the list */ ip[n].mask = 0; addresses[unit] = ip; /* * If the address given for the peer isn't authorized, or if * the user hasn't given one, AND there is an authorized address * which is a single host, then use that if we find one. */ if (suggested_ip != 0 && (wo->hisaddr == 0 || !auth_ip_addr(unit, wo->hisaddr))) wo->hisaddr = suggested_ip; } /* * auth_ip_addr - check whether the peer is authorized to use * a given IP address. Returns 1 if authorized, 0 otherwise. */ int auth_ip_addr(unit, addr) int unit; u_int32_t addr; { int ok; /* don't allow loopback or multicast address */ if (bad_ip_adrs(addr)) return 0; if (addresses[unit] != NULL) { ok = ip_addr_check(addr, addresses[unit]); if (ok >= 0) return ok; } if (auth_required) return 0; /* no addresses authorized */ return allow_any_ip || privileged || !have_route_to(addr); } static int ip_addr_check(addr, addrs) u_int32_t addr; struct permitted_ip *addrs; { /* This loop is safe because of the sentinel value in set_allowed_addrs */ for (; ; ++addrs) if ((addr & addrs->mask) == addrs->base) return addrs->permit; } /* * bad_ip_adrs - return 1 if the IP address is one we don't want * to use, such as an address in the loopback net or a multicast address. * addr is in network byte order. */ int bad_ip_adrs(addr) u_int32_t addr; { addr = ntohl(addr); return #ifndef ALLOW_127_NET (addr >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET || #endif #ifndef ALLOW_0_NET ((addr >> IN_CLASSA_NSHIFT) == 0 && addr != 0) || #endif IN_MULTICAST(addr); } /* * some_ip_ok - check a wordlist to see if it authorizes any * IP address(es). */ static int some_ip_ok(addrs) struct wordlist *addrs; { for (; addrs != NULL; addrs = addrs->next) { if (addrs->word[0] == '-') break; if (addrs->word[0] != '!') return 1; /* some IP address is allowed */ } return 0; } /* * check_access - complain if a secret file has too-liberal permissions. */ static void check_access(f, filename) FILE *f; char *filename; { struct stat sbuf; if (fstat(fileno(f), &sbuf) < 0) { warn("cannot stat secret file %s: %m", filename); } else if ((sbuf.st_mode & (S_IRWXG | S_IRWXO)) != 0) { warn("Warning - secret file %s has world and/or group access", filename); } } /* * scan_authfile - Scan an authorization file for a secret suitable * for authenticating `client' on `server'. The return value is -1 if * no secret is found, otherwise >= 0. The return value has * NONWILD_CLIENT set if the secret didn't have "*" for the client, * and NONWILD_SERVER set if the secret didn't have "*" for the * server. * Any following words on the line up to a "--" (i.e. address * authorization info) are placed in a wordlist and returned in * *addrs. Any following words (extra options) are placed in a * wordlist and returned in *opts. If opts is NULL, these are just * discarded. Otherwise, the extra_opt_* variables are set to * indicate the source of the options. * * We assume secret is NULL or points to MAXWORDLEN bytes of space. */ static int scan_authfile(f, client, server, secret, addrs, opts, filename) FILE *f; char *client; char *server; char *secret; struct wordlist **addrs; struct wordlist **opts; char *filename; { int newline, xxx, sline; int got_flag, best_flag; FILE *sf; struct wordlist *ap, *addr_list, *alist, **app; char word[MAXWORDLEN]; char atfile[MAXWORDLEN]; char lsecret[MAXWORDLEN]; scan_server_match_failed[0] = '\0'; if (addrs != NULL) *addrs = NULL; if (opts != NULL) *opts = NULL; addr_list = NULL; option_line = 0; if (!getword(f, word, &newline, filename)) { if (debug) dbglog("%s is apparently empty", filename); return -1; /* file is empty??? */ } newline = 1; best_flag = -1; for (;;) { /* * Skip until we find a word at the start of a line. */ while (!newline && getword(f, word, &newline, filename)) ; if (!newline) break; /* got to end of file */ sline = option_line; /* * Got a client - check if it's a match or a wildcard. */ got_flag = 0; if (client != NULL && strcmp(word, client) != 0 && !ISWILD(word)) { newline = 0; continue; } if (!ISWILD(word)) got_flag = NONWILD_CLIENT; /* * Now get a server and check if it matches. */ if (!getword(f, word, &newline, filename)) break; if (newline) continue; if (!ISWILD(word)) { if (server != NULL && strcmp(word, server) != 0) { (void) strcpy(scan_server_match_failed, word); continue; } got_flag |= NONWILD_SERVER; } /* * Got some sort of a match - see if it's better than what * we have already. */ if (got_flag <= best_flag) continue; /* * Get the secret. */ if (!getword(f, word, &newline, filename)) break; if (newline) continue; /* * Special syntax: @filename means read secret from file. * Because the secrets files are modifiable only by root, * it's safe to open this file as root. One small addition -- * if open fails, we try as the regular user; just in case * it's over NFS and not root-equivalent. */ if (word[0] == '@') { (void) strlcpy(atfile, word+1, sizeof(atfile)); if ((sf = fopen(atfile, "r")) == NULL) { (void) seteuid(getuid()); sf = fopen(atfile, "r"); (void) seteuid(0); } if (sf == NULL) { warn("can't open indirect secret file %s: %m", atfile); continue; } check_access(sf, atfile); if (!getword(sf, word, &xxx, atfile)) { warn("no secret in indirect secret file %s", atfile); (void) fclose(sf); continue; } (void) fclose(sf); } if (secret != NULL) (void) strlcpy(lsecret, word, sizeof(lsecret)); /* * Now read address authorization info and make a wordlist. */ app = &alist; for (;;) { if (!getword(f, word, &newline, filename) || newline) break; ap = (struct wordlist *) malloc(sizeof(struct wordlist)); if (ap == NULL) novm("authorized addresses"); ap->word = strdup(word); if (ap->word == NULL) novm("authorized addresses"); *app = ap; app = &ap->next; } *app = NULL; /* * This is the best so far; remember it. */ best_flag = got_flag; if (addr_list != NULL) free_wordlist(addr_list); addr_list = alist; if (secret != NULL) (void) strlcpy(secret, lsecret, MAXWORDLEN); if (opts != NULL) { extra_opt_filename = filename; extra_opt_line = sline; } if (!newline) break; } /* scan for a -- word indicating the start of options */ for (app = &addr_list; (ap = *app) != NULL; app = &ap->next) if (strcmp(ap->word, "--") == 0) break; /* ap = start of options */ if (ap != NULL) { ap = ap->next; /* first option */ free(*app); /* free the "--" word */ *app = NULL; /* terminate addr list */ } if (opts != NULL) *opts = ap; else if (ap != NULL) free_wordlist(ap); if (addrs != NULL) *addrs = addr_list; else if (addr_list != NULL) free_wordlist(addr_list); return best_flag; } /* * free_wordlist - release memory allocated for a wordlist. */ static void free_wordlist(wp) struct wordlist *wp; { struct wordlist *next; while (wp != NULL) { next = wp->next; free(wp); wp = next; } } /* * auth_script_done - called when the auth-up or auth-down script * has finished. */ /*ARGSUSED*/ static void auth_script_done(arg, status) void *arg; int status; { auth_script_pid = 0; switch (auth_script_state) { case s_up: if (auth_state == s_down) { auth_script_state = s_down; auth_script(_PATH_AUTHDOWN); } break; case s_down: if (auth_state == s_up) { auth_script_state = s_up; auth_script(_PATH_AUTHUP); } break; } } /* * auth_script - execute a script with arguments * interface-name peer-name real-user tty speed */ static void auth_script(script) char *script; { char strspeed[32]; struct passwd *pw; char struid[32]; char *user_name; char *argv[8]; if ((pw = getpwuid(getuid())) != NULL && pw->pw_name != NULL) user_name = pw->pw_name; else { (void) slprintf(struid, sizeof(struid), "%d", getuid()); user_name = struid; } (void) slprintf(strspeed, sizeof(strspeed), "%d", baud_rate); argv[0] = script; argv[1] = ifname; argv[2] = peer_authname; argv[3] = user_name; argv[4] = devnam; argv[5] = strspeed; argv[6] = NULL; auth_script_pid = run_program(script, argv, 0, auth_script_done, NULL); } /* * cbcp - Call Back Configuration Protocol. * * Copyright (c) 2000 by Sun Microsystems, Inc. * All rights reserved. * * Copyright (c) 1995 Pedro Roque Marques * 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 Pedro Roque Marques. The name of the author 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. */ #include #include #include #include #include "pppd.h" #include "cbcp.h" #include "fsm.h" #include "lcp.h" /* * Options. */ static int setcbcp __P((char **, option_t *)); static option_t cbcp_option_list[] = { { "callback", o_special, (void *)setcbcp, "Ask for callback" }, { NULL } }; /* * Protocol entry points. */ static void cbcp_init __P((int unit)); static void cbcp_lowerup __P((int unit)); static void cbcp_input __P((int unit, u_char *pkt, int len)); static void cbcp_protrej __P((int unit)); static int cbcp_printpkt __P((u_char *pkt, int len, void (*printer) __P((void *, const char *, ...)), void *arg)); struct protent cbcp_protent = { PPP_CBCP, /* PPP protocol number */ cbcp_init, /* Initialization procedure */ cbcp_input, /* Process a received packet */ cbcp_protrej, /* Process a received protocol-reject */ cbcp_lowerup, /* Lower layer has come up */ NULL, /* Lower layer has gone down */ NULL, /* Open the protocol */ NULL, /* Close the protocol */ cbcp_printpkt, /* Print a packet in readable form */ NULL, /* Process a received data packet */ 0, /* 0 iff protocol is disabled */ "CBCP", /* Text name of protocol */ NULL, /* Text name of corresponding data protocol */ cbcp_option_list, /* List of command-line options */ NULL, /* Check requested options, assign defaults */ NULL, /* Configure interface for demand-dial */ NULL /* Say whether to bring up link for this pkt */ }; /* Not static'd for plug-ins */ cbcp_state cbcp[NUM_PPP]; /* internal prototypes */ static void cbcp_recvreq __P((cbcp_state *us, u_char *pckt, int len)); static void cbcp_recvack __P((cbcp_state *us, u_char *pckt, int len)); static void cbcp_send __P((cbcp_state *us, int code, u_char *buf, int len)); /* option processing */ /*ARGSUSED*/ static int setcbcp(argv, opt) char **argv; option_t *opt; { lcp_wantoptions[0].neg_cbcp = 1; cbcp_protent.enabled_flag = 1; cbcp[0].us_number = strdup(*argv); if (cbcp[0].us_number == NULL) novm("callback number"); cbcp[0].us_type |= (1 << CB_CONF_USER); cbcp[0].us_type |= (1 << CB_CONF_ADMIN); return (1); } /* init state */ static void cbcp_init(unit) int unit; { cbcp_state *us; us = &cbcp[unit]; BZERO(us, sizeof(cbcp_state)); us->us_unit = unit; us->us_type |= (1 << CB_CONF_NO); } /* lower layer is up */ static void cbcp_lowerup(unit) int unit; { cbcp_state *us = &cbcp[unit]; if (debug) { dbglog("cbcp_lowerup: want: %d", us->us_type); if (us->us_type == CB_CONF_USER) dbglog("phone no: %s", us->us_number); } } /* process an incoming packet */ static void cbcp_input(unit, inpacket, pktlen) int unit; u_char *inpacket; int pktlen; { u_char *inp; u_char code, id; u_short len; cbcp_state *us = &cbcp[unit]; inp = inpacket; if (pktlen < CBCP_MINLEN) { error("CBCP packet is too small (%d < %d)", pktlen, CBCP_MINLEN); return; } GETCHAR(code, inp); GETCHAR(id, inp); GETSHORT(len, inp); if (len > pktlen) { error("CBCP packet: invalid length (%d > %d)", len, pktlen); return; } len -= CBCP_MINLEN; switch (code) { case CBCP_REQ: us->us_id = id; cbcp_recvreq(us, inp, len); break; case CBCP_RESP: if (debug) dbglog("CBCP Response received; no request sent"); break; case CBCP_ACK: if (id != us->us_id) { if (debug) dbglog("CBCP Ack ID %d doesn't match expected %d", id, us->us_id); break; } cbcp_recvack(us, inp, len); break; default: if (debug) dbglog("Unknown CBCP code number %d", code); break; } } /* protocol was rejected by foe */ /*ARGSUSED*/ static void cbcp_protrej(int unit) { start_networks(); } static char *cbcp_codenames[] = { "Request", "Response", "Ack" }; static char *cbcp_optionnames[] = { "NoCallback", "UserDefined", "AdminDefined", "List" }; /* * Pretty print a packet. Return value is number of bytes parsed out * of the packet and printed in some way. Caller (in util.c) will * print the remainder of the packet. */ static int cbcp_printpkt(p, plen, printer, arg) u_char *p; int plen; void (*printer) __P((void *, const char *, ...)); void *arg; { int code, id, len, olen, alen; u_char *pstart, cichar; if (plen < HEADERLEN) { printer(arg, "too short (%d<%d)", plen, HEADERLEN); return (0); } pstart = p; GETCHAR(code, p); GETCHAR(id, p); GETSHORT(len, p); if (code >= 1 && code <= Dim(cbcp_codenames)) printer(arg, " %s", cbcp_codenames[code-1]); else printer(arg, " code=0x%x", code); printer(arg, " id=0x%x", id); if (len < HEADERLEN) { printer(arg, " header length %d<%d", len, HEADERLEN); return (HEADERLEN); } if (len > plen) { printer(arg, " truncated (%d>%d)", len, plen); len = plen; } len -= HEADERLEN; switch (code) { case CBCP_REQ: case CBCP_RESP: case CBCP_ACK: while (len >= 2) { GETCHAR(cichar, p); GETCHAR(olen, p); if (olen < 2) break; printer(arg, " <"); if (olen > len) { printer(arg, "trunc[%d>%d] ", olen, len); olen = len; } len -= olen; olen -= 2; if (cichar >= 1 && cichar <= Dim(cbcp_optionnames)) printer(arg, " %s", cbcp_optionnames[cichar-1]); else printer(arg, " option=0x%x", cichar); if (olen > 0) { GETCHAR(cichar, p); olen--; printer(arg, " delay=%d", cichar); } while (olen > 0) { GETCHAR(cichar, p); olen--; if (cichar != 1) printer(arg, " (type %d?)", cichar); alen = strllen((const char *)p, olen); if (olen > 0 && alen > 0) printer(arg, " '%.*s'", alen, p); else printer(arg, " null"); p += alen + 1; olen -= alen + 1; } printer(arg, ">"); } default: break; } if (len > 0) { if (len > 8) printer(arg, "%8B ...", p); else printer(arg, "%.*B", len, p); } p += len; return p - pstart; } /* * received CBCP request. * No reason to print packet contents in detail here, since enabling * debug mode will cause the print routine above to be invoked. */ static void cbcp_recvreq(us, pckt, pcktlen) cbcp_state *us; u_char *pckt; int pcktlen; { u_char type, opt_len; int len = pcktlen; u_char cb_type; u_char buf[256]; u_char *bufp = buf; us->us_allowed = 0; while (len > 0) { GETCHAR(type, pckt); GETCHAR(opt_len, pckt); if (opt_len > 2) { pckt++; /* ignore the delay time */ } len -= opt_len; /* * Careful; don't use left-shift operator on numbers that are * too big. */ if (type > CB_CONF_LIST) { if (debug) dbglog("CBCP: ignoring unknown type %d", type); continue; } us->us_allowed |= (1 << type); switch (type) { case CB_CONF_NO: if (debug) dbglog("CBCP: operation without callback allowed"); break; case CB_CONF_USER: if (debug) dbglog("callback to user-specified number allowed"); break; case CB_CONF_ADMIN: if (debug) dbglog("CBCP: callback to admin-defined address allowed"); break; case CB_CONF_LIST: if (debug) dbglog("CBCP: callback to one out of list allowed"); break; } } /* Now generate the response */ len = 0; cb_type = us->us_allowed & us->us_type; if (cb_type & ( 1 << CB_CONF_USER ) ) { if (debug) dbglog("CBCP Response: selecting user-specified number"); PUTCHAR(CB_CONF_USER, bufp); len = 3 + 1 + strlen(us->us_number) + 1; PUTCHAR(len , bufp); PUTCHAR(5, bufp); /* delay */ PUTCHAR(1, bufp); BCOPY(us->us_number, bufp, strlen(us->us_number) + 1); cbcp_send(us, CBCP_RESP, buf, len); return; } if (cb_type & ( 1 << CB_CONF_ADMIN ) ) { if (debug) dbglog("CBCP Response: selecting admin-specified number"); PUTCHAR(CB_CONF_ADMIN, bufp); len = 3; PUTCHAR(len, bufp); PUTCHAR(5, bufp); /* delay */ cbcp_send(us, CBCP_RESP, buf, len); return; } if (cb_type & ( 1 << CB_CONF_NO ) ) { if (debug) dbglog("CBCP Response: selecting no-callback mode"); PUTCHAR(CB_CONF_NO, bufp); len = 3; PUTCHAR(len , bufp); PUTCHAR(0, bufp); cbcp_send(us, CBCP_RESP, buf, len); start_networks(); return; } if (debug) dbglog("CBCP: no callback types in common"); lcp_close(us->us_unit, "No CBCP callback options available"); } static void cbcp_send(us, code, buf, len) cbcp_state *us; int code; u_char *buf; int len; { u_char *outp; int outlen; outp = outpacket_buf; outlen = 4 + len; MAKEHEADER(outp, PPP_CBCP); PUTCHAR(code, outp); PUTCHAR(us->us_id, outp); PUTSHORT(outlen, outp); if (len > 0) BCOPY(buf, outp, len); output(us->us_unit, outpacket_buf, outlen + PPP_HDRLEN); } /* * Received CBCP Acknowledgment message. */ static void cbcp_recvack(us, pckt, len) cbcp_state *us; u_char *pckt; int len; { u_char type, addr_type; int opt_len; if (len > 0) { GETCHAR(type, pckt); GETCHAR(opt_len, pckt); if (type == CB_CONF_NO) { if (debug) dbglog("CBCP: proceeding without callback"); return; } /* just ignore the delay time */ pckt++; if (opt_len > 4) { GETCHAR(addr_type, pckt); if (addr_type != 1) warn("CBCP: unknown callback address type %d", addr_type); } if (debug && opt_len > 5) dbglog("CBCP: peer will call %.*s", pckt, opt_len - 4); } persist = 0; lcp_close(us->us_unit, "Call me back, please"); status = EXIT_CALLBACK; } /* * cbcp - Call Back Configuration Protocol. * * Copyright (c) 2000 by Sun Microsystems, Inc. * All rights reserved. * * Copyright (c) 1995 Pedro Roque Marques * All rights reserved. */ #ifndef CBCP_H #define CBCP_H typedef struct cbcp_state { int us_unit; /* Interface unit number */ u_char us_id; /* Current id */ u_char us_allowed; int us_type; char *us_number; /* Telefone Number */ } cbcp_state; extern cbcp_state cbcp[]; extern struct protent cbcp_protent; #define CBCP_MINLEN 4 #define CBCP_REQ 1 #define CBCP_RESP 2 #define CBCP_ACK 3 #define CB_CONF_NO 1 #define CB_CONF_USER 2 #define CB_CONF_ADMIN 3 #define CB_CONF_LIST 4 #endif /* * ccp.c - PPP Compression Control Protocol. * * Copyright (c) 2000 by Sun Microsystems, Inc. * All rights reserved. * * Copyright (c) 1994 The Australian National University. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. This software is provided without any * warranty, express or implied. The Australian National University * makes no representations about the suitability of this software for * any purpose. * * IN NO EVENT SHALL THE AUSTRALIAN NATIONAL UNIVERSITY BE LIABLE TO ANY * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF * THE AUSTRALIAN NATIONAL UNIVERSITY HAVE BEEN ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * THE AUSTRALIAN NATIONAL UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE AUSTRALIAN NATIONAL UNIVERSITY HAS NO * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, * OR MODIFICATIONS. */ /* * Copyright (c) 2016 by Delphix. All rights reserved. */ #include #include #include "pppd.h" #include "fsm.h" #include "ccp.h" #include /* * Command-line options. */ static int setbsdcomp __P((char **, option_t *)); static int setdeflate __P((char **, option_t *)); static option_t ccp_option_list[] = { { "noccp", o_bool, &ccp_protent.enabled_flag, "Disable CCP negotiation" }, { "-ccp", o_bool, &ccp_protent.enabled_flag, "Disable CCP negotiation" }, { "bsdcomp", o_special, (void *)setbsdcomp, "Request BSD-Compress packet compression" }, { "nobsdcomp", o_bool, &ccp_wantoptions[0].bsd_compress, "don't allow BSD-Compress", OPT_A2COPY, &ccp_allowoptions[0].bsd_compress }, { "-bsdcomp", o_bool, &ccp_wantoptions[0].bsd_compress, "don't allow BSD-Compress", OPT_A2COPY, &ccp_allowoptions[0].bsd_compress }, { "deflate", o_special, (void *)setdeflate, "request Deflate compression" }, { "nodeflate", o_bool, &ccp_wantoptions[0].deflate, "don't allow Deflate compression", OPT_A2COPY, &ccp_allowoptions[0].deflate }, { "-deflate", o_bool, &ccp_wantoptions[0].deflate, "don't allow Deflate compression", OPT_A2COPY, &ccp_allowoptions[0].deflate }, { "nodeflatedraft", o_bool, &ccp_wantoptions[0].deflate_draft, "don't use draft deflate #", OPT_A2COPY, &ccp_allowoptions[0].deflate_draft }, { "predictor1", o_bool, &ccp_wantoptions[0].predictor_1, "request Predictor-1", 1, &ccp_allowoptions[0].predictor_1 }, { "nopredictor1", o_bool, &ccp_wantoptions[0].predictor_1, "don't allow Predictor-1", OPT_A2COPY, &ccp_allowoptions[0].predictor_1 }, { "-predictor1", o_bool, &ccp_wantoptions[0].predictor_1, "don't allow Predictor-1", OPT_A2COPY, &ccp_allowoptions[0].predictor_1 }, { NULL } }; /* * Protocol entry points from main code. */ static void ccp_init __P((int unit)); static void ccp_open __P((int unit)); static void ccp_close __P((int unit, char *)); static void ccp_lowerup __P((int unit)); static void ccp_lowerdown __P((int)); static void ccp_input __P((int unit, u_char *pkt, int len)); static void ccp_protrej __P((int unit)); static int ccp_printpkt __P((u_char *pkt, int len, void (*printer) __P((void *, const char *, ...)), void *arg)); static void ccp_datainput __P((int unit, u_char *pkt, int len)); struct protent ccp_protent = { PPP_CCP, ccp_init, ccp_input, ccp_protrej, ccp_lowerup, ccp_lowerdown, ccp_open, ccp_close, ccp_printpkt, ccp_datainput, 1, "CCP", "Compressed", ccp_option_list, NULL, NULL, NULL }; fsm ccp_fsm[NUM_PPP]; ccp_options ccp_wantoptions[NUM_PPP]; /* what to request the peer to use */ ccp_options ccp_gotoptions[NUM_PPP]; /* what the peer agreed to do */ ccp_options ccp_allowoptions[NUM_PPP]; /* what we'll agree to do */ ccp_options ccp_hisoptions[NUM_PPP]; /* what we agreed to do */ /* * Callbacks for fsm code. */ static void ccp_resetci __P((fsm *)); static int ccp_cilen __P((fsm *)); static void ccp_addci __P((fsm *, u_char *, int *)); static int ccp_ackci __P((fsm *, u_char *, int)); static int ccp_nakci __P((fsm *, u_char *, int)); static int ccp_rejci __P((fsm *, u_char *, int)); static int ccp_reqci __P((fsm *, u_char *, int *, int)); static void ccp_up __P((fsm *)); static void ccp_down __P((fsm *)); static int ccp_extcode __P((fsm *, int, int, u_char *, int)); static int ccp_codereject __P((fsm *p, int code, int id, u_char *inp, int len)); static fsm_callbacks ccp_callbacks = { ccp_resetci, /* Reset our Configuration Information */ ccp_cilen, /* Length of our Configuration Information */ ccp_addci, /* Add our Configuration Information */ ccp_ackci, /* ACK our Configuration Information */ ccp_nakci, /* NAK our Configuration Information */ ccp_rejci, /* Reject our Configuration Information */ ccp_reqci, /* Request peer's Configuration Information */ ccp_up, /* Called when fsm reaches OPENED state */ ccp_down, /* Called when fsm leaves OPENED state */ NULL, /* Called when we want the lower layer up */ NULL, /* Called when we want the lower layer down */ NULL, /* Retransmission is necessary */ ccp_extcode, /* Called to handle LCP-specific codes */ "CCP", /* String name of protocol */ ccp_codereject, /* Peer rejected a code number */ }; /* * Local statics. */ static void ccp_rack_timeout __P((void *)); static char * method_name __P((ccp_options *, ccp_options *)); /* * Do we want / did we get any compression? */ #define ANY_COMPRESS(opt) ((opt).deflate || (opt).bsd_compress \ || (opt).predictor_1 || (opt).predictor_2) /* * Local state (mainly for handling reset-reqs and reset-acks). */ static int ccp_localstate[NUM_PPP]; #define RACK_PENDING 0x0001 /* waiting for reset-ack */ #define RREQ_REPEAT 0x0002 /* send another reset-req if no reset-ack */ #define RREQ_REJECTED 0x0004 /* peer code-rejected reset-request */ #define RACK_REJECTED 0x0008 /* peer code-rejected reset-ack */ #define RREQ_IGNORED 0x0010 /* peer just ignored reset-request */ #define RACKTIMEOUT 1 /* time in seconds between Reset-Requests */ static int all_rejected[NUM_PPP]; /* we rejected all peer's options */ #ifdef COMP_TUNE static int deflate_tune = -1; /* compression effort level for deflate */ #endif static int deflate_rmax = DEFLATE_MAX_SIZE; /* max rbits */ static int deflate_amax = DEFLATE_MAX_SIZE; /* max abits */ /* * Option parsing. */ /*ARGSUSED*/ static int setbsdcomp(argv, opt) char **argv; option_t *opt; { int rbits, abits; char *str, *endp; str = *argv; abits = rbits = strtol(str, &endp, 0); if (endp != str && *endp == ',') { str = endp + 1; abits = strtol(str, &endp, 0); } if (*endp != '\0' || endp == str) { option_error("invalid parameter '%s' for bsdcomp option", *argv); return 0; } if ((rbits != 0 && (rbits < BSD_MIN_BITS || rbits > BSD_MAX_BITS)) || (abits != 0 && (abits < BSD_MIN_BITS || abits > BSD_MAX_BITS))) { option_error("bsdcomp option values must be 0 or %d .. %d", BSD_MIN_BITS, BSD_MAX_BITS); return 0; } if (rbits > 0) { ccp_wantoptions[0].bsd_compress = 1; ccp_wantoptions[0].bsd_bits = rbits; } else ccp_wantoptions[0].bsd_compress = 0; if (abits > 0) { ccp_allowoptions[0].bsd_compress = 1; ccp_allowoptions[0].bsd_bits = abits; } else ccp_allowoptions[0].bsd_compress = 0; return 1; } /*ARGSUSED*/ static int setdeflate(argv, opt) char **argv; option_t *opt; { int rbits, abits, def_rmax, def_amax; char *str, *endp; str = endp = *argv; if (*str == ',') abits = rbits = -1; else abits = rbits = strtol(str, &endp, 0); if (*endp == ',') { str = ++endp; if (*str == ',') abits = rbits; else abits = strtol(str, &endp, 0); } #ifdef COMP_TUNE if (*endp == ',' && privileged_option) { str = ++endp; deflate_tune = strtol(str, &endp, 0); } #endif if (*endp != '\0' || endp == str) { option_error("invalid parameter '%s' for deflate option", *argv); return 0; } if (privileged_option) { def_rmax = def_amax = DEFLATE_MAX_SIZE; } else { def_rmax = deflate_rmax; def_amax = deflate_amax; } if (rbits < 0) rbits = def_rmax; if (abits < 0) abits = def_amax; if ((rbits != 0 && (rbits <= DEFLATE_MIN_SIZE || rbits > def_rmax)) || (abits != 0 && (abits <= DEFLATE_MIN_SIZE || abits > def_amax))) { option_error("deflate option values must be 0 or {%d,%d} .. {%d,%d}", DEFLATE_MIN_SIZE+1, DEFLATE_MIN_SIZE+1, def_rmax, def_amax); return 0; } if (privileged_option) { deflate_rmax = rbits; deflate_amax = abits; } if (rbits > 0) { ccp_wantoptions[0].deflate = 1; ccp_wantoptions[0].deflate_size = rbits; } else ccp_wantoptions[0].deflate = 0; if (abits > 0) { ccp_allowoptions[0].deflate = 1; ccp_allowoptions[0].deflate_size = abits; } else ccp_allowoptions[0].deflate = 0; return 1; } /* * ccp_init - initialize CCP. */ static void ccp_init(unit) int unit; { fsm *f = &ccp_fsm[unit]; f->unit = unit; f->protocol = PPP_CCP; f->callbacks = &ccp_callbacks; fsm_init(f); f->flags |= OPT_RESTART; BZERO(&ccp_wantoptions[unit], sizeof(ccp_options)); BZERO(&ccp_gotoptions[unit], sizeof(ccp_options)); BZERO(&ccp_allowoptions[unit], sizeof(ccp_options)); BZERO(&ccp_hisoptions[unit], sizeof(ccp_options)); ccp_wantoptions[0].deflate = 1; ccp_wantoptions[0].deflate_size = DEFLATE_MAX_SIZE; ccp_wantoptions[0].deflate_correct = 1; ccp_wantoptions[0].deflate_draft = 1; ccp_allowoptions[0].deflate = 1; ccp_allowoptions[0].deflate_size = DEFLATE_MAX_SIZE; ccp_allowoptions[0].deflate_correct = 1; ccp_allowoptions[0].deflate_draft = 1; ccp_wantoptions[0].bsd_compress = 1; ccp_wantoptions[0].bsd_bits = BSD_MAX_BITS; ccp_allowoptions[0].bsd_compress = 1; ccp_allowoptions[0].bsd_bits = BSD_MAX_BITS; ccp_allowoptions[0].predictor_1 = 1; } /* * ccp_open - CCP is allowed to come up. */ static void ccp_open(unit) int unit; { fsm *f = &ccp_fsm[unit]; /* * If we haven't gone open yet (first time through), then go open * but not up. Otherwise, skip this to allow reopen to reset the * compressor. */ if (f->state != OPENED) ccp_flags_set(unit, 1, 0); /* * Find out which compressors the kernel supports before * deciding whether to open in silent mode. */ ccp_resetci(f); if (!ANY_COMPRESS(ccp_gotoptions[unit])) f->flags |= OPT_SILENT; fsm_open(f); } /* * ccp_close - Terminate CCP. */ static void ccp_close(unit, reason) int unit; char *reason; { ccp_flags_set(unit, 0, 0); fsm_close(&ccp_fsm[unit], reason); } /* * ccp_lowerup - we may now transmit CCP packets. */ static void ccp_lowerup(unit) int unit; { fsm_lowerup(&ccp_fsm[unit]); } /* * ccp_lowerdown - we may not transmit CCP packets. */ static void ccp_lowerdown(unit) int unit; { fsm_lowerdown(&ccp_fsm[unit]); } /* * ccp_input - process a received CCP packet. */ static void ccp_input(unit, p, len) int unit; u_char *p; int len; { fsm *f = &ccp_fsm[unit]; int oldstate; /* * Check for a terminate-request so we can print a message. */ oldstate = f->state; fsm_input(f, p, len); if (oldstate == OPENED && p[0] == CODE_TERMREQ && f->state != OPENED) notice("Compression disabled by peer."); /* * If we get a terminate-ack and we're not asking for compression, * close CCP. (Terminate-Request is handled by fsm_input() above.) */ if (oldstate == REQSENT && p[0] == CODE_TERMACK && !ANY_COMPRESS(ccp_gotoptions[unit])) ccp_close(unit, "No compression negotiated"); } /* * Handle a CCP-specific code. */ static int ccp_extcode(f, code, id, p, len) fsm *f; int code, id; u_char *p; int len; { switch (code) { case CCP_RESETREQ: /* If not open, then silently ignore. */ if (f->state != OPENED) break; /* send a reset-ack, which our transmitter module will see and reset its compression state. */ fsm_sdata(f, CCP_RESETACK, id, p, len); break; case CCP_RESETACK: /* * Note that the compression module isn't picky about ID * numbers and such. */ ccp_localstate[f->unit] &= ~RREQ_IGNORED & ~RREQ_REJECTED; if ((ccp_localstate[f->unit] & RACK_PENDING) && id == f->reqid) { ccp_localstate[f->unit] &= ~RACK_PENDING & ~RREQ_REPEAT; UNTIMEOUT(ccp_rack_timeout, f); } break; default: /* Tell fsm to send code reject */ return (0); } return (1); } /* * Handle Code-Reject for one of our extended codes by dropping back to * reopen as mechanism to restart compression. */ /*ARGSUSED*/ static int ccp_codereject(f, code, id, inp, len) fsm *f; int code, id; u_char *inp; int len; { switch (code) { case CCP_RESETREQ: if (!(ccp_localstate[f->unit] & RREQ_REJECTED)) { info("peer has rejected CCP Reset-Request; falling back on Open"); if (f->state == OPENED) ccp_open(f->unit); } ccp_localstate[f->unit] |= RREQ_REJECTED; return (0); case CCP_RESETACK: /* * Peer must have sent us CCP Reset-Request but then code-rejected when * we sent CCP Reset-Ack. It seems to have changed its mind, and we * have to obey its wishes. */ ccp_localstate[f->unit] |= RACK_REJECTED; notice("peer has erroneously rejected CCP Reset-Ack"); f->term_reason = "peer sent Code-Reject for CCP Reset-Ack"; f->term_reason_len = strlen(f->term_reason); break; default: f->term_reason = "peer sent invalid Code-Reject"; break; } f->term_reason_len = strlen(f->term_reason); return (1); } /* * ccp_protrej - peer doesn't talk CCP. */ static void ccp_protrej(unit) int unit; { /* Neither open nor up. */ ccp_flags_set(unit, 0, 0); fsm_lowerdown(&ccp_fsm[unit]); } /* * ccp_resetci - initialize at start of negotiation. */ static void ccp_resetci(f) fsm *f; { ccp_options *go = &ccp_gotoptions[f->unit]; u_char opt_buf[16]; *go = ccp_wantoptions[f->unit]; all_rejected[f->unit] = 0; /* * Check whether the kernel knows about the various * decompression methods we might request. */ if (go->bsd_compress) { opt_buf[0] = CI_BSD_COMPRESS; opt_buf[1] = CILEN_BSD_COMPRESS; opt_buf[2] = BSD_MAKE_OPT(BSD_CURRENT_VERSION, BSD_MIN_BITS); if (ccp_test(f->unit, opt_buf, CILEN_BSD_COMPRESS, 0) <= 0) go->bsd_compress = 0; } if (go->deflate) { if (go->deflate_correct) { opt_buf[0] = CI_DEFLATE; opt_buf[1] = CILEN_DEFLATE; opt_buf[2] = DEFLATE_MAKE_OPT(DEFLATE_MIN_SIZE+1); opt_buf[3] = DEFLATE_CHK_SEQUENCE; if (ccp_test(f->unit, opt_buf, CILEN_DEFLATE, 0) <= 0) go->deflate_correct = 0; } if (go->deflate_draft) { opt_buf[0] = CI_DEFLATE_DRAFT; opt_buf[1] = CILEN_DEFLATE; opt_buf[2] = DEFLATE_MAKE_OPT(DEFLATE_MIN_SIZE+1); opt_buf[3] = DEFLATE_CHK_SEQUENCE; if (ccp_test(f->unit, opt_buf, CILEN_DEFLATE, 0) <= 0) go->deflate_draft = 0; } if (!go->deflate_correct && !go->deflate_draft) go->deflate = 0; } if (go->predictor_1) { opt_buf[0] = CI_PREDICTOR_1; opt_buf[1] = CILEN_PREDICTOR_1; if (ccp_test(f->unit, opt_buf, CILEN_PREDICTOR_1, 0) <= 0) go->predictor_1 = 0; } if (go->predictor_2) { opt_buf[0] = CI_PREDICTOR_2; opt_buf[1] = CILEN_PREDICTOR_2; if (ccp_test(f->unit, opt_buf, CILEN_PREDICTOR_2, 0) <= 0) go->predictor_2 = 0; } } /* * ccp_cilen - Return total length of our configuration info. */ static int ccp_cilen(f) fsm *f; { ccp_options *go = &ccp_gotoptions[f->unit]; return (go->bsd_compress? CILEN_BSD_COMPRESS: 0) + (go->deflate && go->deflate_correct ? CILEN_DEFLATE : 0) + (go->deflate && go->deflate_draft ? CILEN_DEFLATE : 0) + (go->predictor_1? CILEN_PREDICTOR_1: 0) + (go->predictor_2? CILEN_PREDICTOR_2: 0); } /* * ccp_addci - put our requests in a packet. */ static void ccp_addci(f, p, lenp) fsm *f; u_char *p; int *lenp; { int res; ccp_options *go = &ccp_gotoptions[f->unit]; u_char *p0 = p; /* * Add the compression types that we can receive, in decreasing * preference order. Get the kernel to allocate the first one * in case it gets Acked. */ if (go->deflate) { p[0] = go->deflate_correct? CI_DEFLATE: CI_DEFLATE_DRAFT; p[1] = CILEN_DEFLATE; p[2] = DEFLATE_MAKE_OPT(go->deflate_size); p[3] = DEFLATE_CHK_SEQUENCE; for (;;) { res = ccp_test(f->unit, p, CILEN_DEFLATE, 0); if (res > 0) { p += CILEN_DEFLATE; break; } if (res < 0 || go->deflate_size <= DEFLATE_MIN_SIZE+1) { go->deflate = 0; break; } --go->deflate_size; p[2] = DEFLATE_MAKE_OPT(go->deflate_size); } /* If we're offering both, then this is second. */ if (p != p0 && go->deflate_correct && go->deflate_draft) { p[0] = CI_DEFLATE_DRAFT; p[1] = CILEN_DEFLATE; p[2] = p[2 - CILEN_DEFLATE]; p[3] = DEFLATE_CHK_SEQUENCE; p += CILEN_DEFLATE; } } if (go->bsd_compress) { p[0] = CI_BSD_COMPRESS; p[1] = CILEN_BSD_COMPRESS; p[2] = BSD_MAKE_OPT(BSD_CURRENT_VERSION, go->bsd_bits); if (p != p0) { p += CILEN_BSD_COMPRESS; /* not the first option */ } else { for (;;) { res = ccp_test(f->unit, p, CILEN_BSD_COMPRESS, 0); if (res > 0) { p += CILEN_BSD_COMPRESS; break; } if (res < 0 || go->bsd_bits <= BSD_MIN_BITS) { go->bsd_compress = 0; break; } --go->bsd_bits; p[2] = BSD_MAKE_OPT(BSD_CURRENT_VERSION, go->bsd_bits); } } } /* * Prefer Predictor-1 over Predictor-2. (The latter requires the use * of LAP-B and has no known implementations.) */ if (go->predictor_1) { p[0] = CI_PREDICTOR_1; p[1] = CILEN_PREDICTOR_1; if (p == p0 && ccp_test(f->unit, p, CILEN_PREDICTOR_1, 0) <= 0) { go->predictor_1 = 0; } else { p += CILEN_PREDICTOR_1; } } if (go->predictor_2) { p[0] = CI_PREDICTOR_2; p[1] = CILEN_PREDICTOR_2; if (p == p0 && ccp_test(f->unit, p, CILEN_PREDICTOR_2, 0) <= 0) { go->predictor_2 = 0; } else { p += CILEN_PREDICTOR_2; } } go->method = (p > p0)? p0[0]: -1; *lenp = p - p0; } /* * ccp_ackci - process a received configure-ack, and return * 1 iff the packet was OK. */ static int ccp_ackci(f, p, len) fsm *f; u_char *p; int len; { ccp_options *go = &ccp_gotoptions[f->unit]; u_char *p0 = p; if (go->deflate && go->deflate_correct) { if (len < CILEN_DEFLATE || p[0] != CI_DEFLATE || p[1] != CILEN_DEFLATE || p[2] != DEFLATE_MAKE_OPT(go->deflate_size) || p[3] != DEFLATE_CHK_SEQUENCE) return 0; /* Cope with non-standard first/fast ack */ if (p == p0 && len == 0) return 1; p += CILEN_DEFLATE; len -= CILEN_DEFLATE; } if (go->deflate && go->deflate_draft) { if (len < CILEN_DEFLATE || p[0] != CI_DEFLATE_DRAFT || p[1] != CILEN_DEFLATE || p[2] != DEFLATE_MAKE_OPT(go->deflate_size) || p[3] != DEFLATE_CHK_SEQUENCE) return 0; /* Cope with non-standard first/fast ack */ if (p == p0 && len == 0) return 1; p += CILEN_DEFLATE; len -= CILEN_DEFLATE; } if (go->bsd_compress) { if (len < CILEN_BSD_COMPRESS || p[0] != CI_BSD_COMPRESS || p[1] != CILEN_BSD_COMPRESS || p[2] != BSD_MAKE_OPT(BSD_CURRENT_VERSION, go->bsd_bits)) return 0; /* Cope with non-standard first/fast ack */ if (p == p0 && len == 0) return 1; p += CILEN_BSD_COMPRESS; len -= CILEN_BSD_COMPRESS; } if (go->predictor_1) { if (len < CILEN_PREDICTOR_1 || p[0] != CI_PREDICTOR_1 || p[1] != CILEN_PREDICTOR_1) return 0; /* Cope with non-standard first/fast ack */ if (p == p0 && len == 0) return 1; p += CILEN_PREDICTOR_1; len -= CILEN_PREDICTOR_1; } if (go->predictor_2) { if (len < CILEN_PREDICTOR_2 || p[0] != CI_PREDICTOR_2 || p[1] != CILEN_PREDICTOR_2) return 0; /* Cope with non-standard first/fast ack */ if (p == p0 && len == 0) return 1; p += CILEN_PREDICTOR_2; len -= CILEN_PREDICTOR_2; } /* Peer cannot ack something that wasn't sent. */ if (len != 0) return 0; return 1; } /* * ccp_nakci - process received configure-nak. * Returns 1 iff the nak was OK. */ static int ccp_nakci(f, p, len) fsm *f; u_char *p; int len; { ccp_options *go = &ccp_gotoptions[f->unit]; ccp_options no; /* options we've seen already */ ccp_options try; /* options to ask for next time */ BZERO(&no, sizeof(no)); try = *go; if (go->deflate && go->deflate_correct && len >= CILEN_DEFLATE && p[0] == CI_DEFLATE) { no.deflate = 1; /* * Peer wants us to use a different code size or something. * Stop asking for Deflate if we don't understand its suggestion. */ if (p[1] != CILEN_DEFLATE || DEFLATE_METHOD(p[2]) != DEFLATE_METHOD_VAL || DEFLATE_SIZE(p[2]) <= DEFLATE_MIN_SIZE || p[3] != DEFLATE_CHK_SEQUENCE) try.deflate_correct = 0; else if (DEFLATE_SIZE(p[2]) < go->deflate_size) try.deflate_size = DEFLATE_SIZE(p[2]); len -= p[1]; p += p[1]; } if (go->deflate && go->deflate_draft && len >= CILEN_DEFLATE && p[0] == CI_DEFLATE_DRAFT) { no.deflate = 1; /* * Peer wants us to use a different code size or something. * Stop asking for Deflate using the old algorithm number if * we don't understand its suggestion. (Note that this will * happen if the peer is running Magnalink instead of * old-style Deflate.) */ if (p[1] != CILEN_DEFLATE || DEFLATE_METHOD(p[2]) != DEFLATE_METHOD_VAL || DEFLATE_SIZE(p[2]) <= DEFLATE_MIN_SIZE || p[3] != DEFLATE_CHK_SEQUENCE) try.deflate_draft = 0; else if (DEFLATE_SIZE(p[2]) < go->deflate_size) try.deflate_size = DEFLATE_SIZE(p[2]); len -= p[1]; p += p[1]; } if (!try.deflate_correct && !try.deflate_draft) try.deflate = 0; if (go->bsd_compress && len >= CILEN_BSD_COMPRESS && p[0] == CI_BSD_COMPRESS) { no.bsd_compress = 1; /* * Peer wants us to use a different number of bits * or a different version. */ if (p[1] != CILEN_BSD_COMPRESS || BSD_VERSION(p[2]) != BSD_CURRENT_VERSION) try.bsd_compress = 0; else if (BSD_NBITS(p[2]) < go->bsd_bits) try.bsd_bits = BSD_NBITS(p[2]); len -= p[1]; p += p[1]; } /* * Predictor-1 and 2 have no options, so they can't be Naked. * * There may be remaining options but we ignore them. */ if (f->state != OPENED) *go = try; return 1; } /* * ccp_rejci - peer rejects some of our suggested compression methods. */ static int ccp_rejci(f, p, len) fsm *f; u_char *p; int len; { ccp_options *go = &ccp_gotoptions[f->unit]; ccp_options try; /* options to request next time */ try = *go; /* * Cope with empty configure-rejects by ceasing to send * configure-requests. */ if (len == 0 && all_rejected[f->unit]) return -1; if (go->deflate && go->deflate_correct && len >= CILEN_DEFLATE && p[0] == CI_DEFLATE && p[1] == CILEN_DEFLATE) { if (p[2] != DEFLATE_MAKE_OPT(go->deflate_size) || p[3] != DEFLATE_CHK_SEQUENCE) return 0; /* Rej is bad */ try.deflate_correct = 0; p += CILEN_DEFLATE; len -= CILEN_DEFLATE; } if (go->deflate && go->deflate_draft && len >= CILEN_DEFLATE && p[0] == CI_DEFLATE_DRAFT && p[1] == CILEN_DEFLATE) { if (p[2] != DEFLATE_MAKE_OPT(go->deflate_size) || p[3] != DEFLATE_CHK_SEQUENCE) return 0; /* Rej is bad */ try.deflate_draft = 0; p += CILEN_DEFLATE; len -= CILEN_DEFLATE; } if (!try.deflate_correct && !try.deflate_draft) try.deflate = 0; if (go->bsd_compress && len >= CILEN_BSD_COMPRESS && p[0] == CI_BSD_COMPRESS && p[1] == CILEN_BSD_COMPRESS) { if (p[2] != BSD_MAKE_OPT(BSD_CURRENT_VERSION, go->bsd_bits)) return 0; try.bsd_compress = 0; p += CILEN_BSD_COMPRESS; len -= CILEN_BSD_COMPRESS; } if (go->predictor_1 && len >= CILEN_PREDICTOR_1 && p[0] == CI_PREDICTOR_1 && p[1] == CILEN_PREDICTOR_1) { try.predictor_1 = 0; p += CILEN_PREDICTOR_1; len -= CILEN_PREDICTOR_1; } if (go->predictor_2 && len >= CILEN_PREDICTOR_2 && p[0] == CI_PREDICTOR_2 && p[1] == CILEN_PREDICTOR_2) { try.predictor_2 = 0; p += CILEN_PREDICTOR_2; len -= CILEN_PREDICTOR_2; } if (len != 0) return 0; if (f->state != OPENED) *go = try; return 1; } /* * ccp_reqci - process a received configure-request. * * Returns CODE_CONFACK, CODE_CONFNAK or CODE_CONFREJ and the packet * is modified appropriately. */ static int ccp_reqci(f, p, lenp, dont_nak) fsm *f; u_char *p; int *lenp; int dont_nak; { int ret, newret, res; u_char *p0, *nakp, *rejp, *pv; int len, clen, type, nb; ccp_options *ho = &ccp_hisoptions[f->unit]; ccp_options *ao = &ccp_allowoptions[f->unit]; ret = CODE_CONFACK; rejp = p0 = p; nakp = nak_buffer; len = *lenp; BZERO(ho, sizeof(ccp_options)); ho->method = (len > 0)? p[0]: -1; for (; len > 0; len -= clen, p += clen) { newret = CODE_CONFACK; if (len < 2 || p[1] > len) { /* * RFC 1661 page 40 -- if the option extends beyond the * packet, then discard the entire packet. */ return (0); } type = p[0]; clen = p[1]; pv = p; switch (type) { case CI_DEFLATE: case CI_DEFLATE_DRAFT: if (!ao->deflate || (!ao->deflate_correct && type == CI_DEFLATE) || (!ao->deflate_draft && type == CI_DEFLATE_DRAFT)) { newret = CODE_CONFREJ; break; } ho->deflate = 1; nb = clen < CILEN_DEFLATE ? ao->deflate_size : DEFLATE_SIZE(p[2]); ho->deflate_size = nb; if (clen != CILEN_DEFLATE || DEFLATE_METHOD(p[2]) != DEFLATE_METHOD_VAL || p[3] != DEFLATE_CHK_SEQUENCE || nb > ao->deflate_size || nb <= DEFLATE_MIN_SIZE) { newret = CODE_CONFNAK; if (dont_nak) break; if (nb > ao->deflate_size) nb = ao->deflate_size; else if (nb <= DEFLATE_MIN_SIZE) nb = DEFLATE_MIN_SIZE+1; pv = nakp; PUTCHAR(type, nakp); PUTCHAR(CILEN_DEFLATE, nakp); PUTCHAR(DEFLATE_MAKE_OPT(nb), nakp); PUTCHAR(DEFLATE_CHK_SEQUENCE, nakp); } /* * Check whether we can do Deflate with the window * size they want. If the window is too big, reduce * it until the kernel can cope and nak with that. * We only check this for the first option. */ if (p == p0) { for (;;) { res = ccp_test(f->unit, pv, CILEN_DEFLATE, 1); if (res > 0) break; /* it's OK now */ if (res < 0 || nb <= DEFLATE_MIN_SIZE+1 || dont_nak) { newret = CODE_CONFREJ; break; } if (newret == CODE_CONFACK) { BCOPY(pv, nakp, CILEN_DEFLATE); pv = nakp; nakp += CILEN_DEFLATE; newret = CODE_CONFNAK; } --nb; pv[2] = DEFLATE_MAKE_OPT(nb); } #ifdef COMP_TUNE /* Tune Deflate compression effort. */ if (newret == CODE_CONFACK) ccp_tune(f->unit, deflate_tune); #endif } break; case CI_BSD_COMPRESS: if (!ao->bsd_compress) { newret = CODE_CONFREJ; break; } ho->bsd_compress = 1; nb = clen < CILEN_BSD_COMPRESS ? ao->bsd_bits : BSD_NBITS(p[2]); ho->bsd_bits = nb; if (clen != CILEN_BSD_COMPRESS || BSD_VERSION(p[2]) != BSD_CURRENT_VERSION || nb > ao->bsd_bits || nb < BSD_MIN_BITS) { newret = CODE_CONFNAK; if (dont_nak) break; if (nb > ao->bsd_bits) nb = ao->bsd_bits; else if (nb < BSD_MIN_BITS) nb = BSD_MIN_BITS; pv = nakp; PUTCHAR(type, nakp); PUTCHAR(CILEN_BSD_COMPRESS, nakp); PUTCHAR(BSD_MAKE_OPT(BSD_CURRENT_VERSION, nb), nakp); } /* * Check whether we can do BSD-Compress with the code * size they want. If the code size is too big, reduce * it until the kernel can cope and nak with that. * We only check this for the first option. */ if (p == p0) { for (;;) { res = ccp_test(f->unit, pv, CILEN_BSD_COMPRESS, 1); if (res > 0) break; if (res < 0 || nb == BSD_MIN_BITS || dont_nak) { newret = CODE_CONFREJ; break; } if (newret == CODE_CONFACK) { BCOPY(pv, nakp, CILEN_BSD_COMPRESS); pv = nakp; nakp += CILEN_BSD_COMPRESS; newret = CODE_CONFNAK; } --nb; pv[2] = BSD_MAKE_OPT(BSD_CURRENT_VERSION, nb); } } break; case CI_PREDICTOR_1: if (!ao->predictor_1) { newret = CODE_CONFREJ; break; } ho->predictor_1 = 1; if (clen != CILEN_PREDICTOR_1) { newret = CODE_CONFNAK; if (dont_nak) break; pv = nakp; PUTCHAR(type, nakp); PUTCHAR(CILEN_PREDICTOR_1, nakp); } if (p == p0 && ccp_test(f->unit, pv, CILEN_PREDICTOR_1, 1) <= 0) { newret = CODE_CONFREJ; } break; case CI_PREDICTOR_2: if (!ao->predictor_2) { newret = CODE_CONFREJ; break; } ho->predictor_2 = 1; if (clen != CILEN_PREDICTOR_2) { newret = CODE_CONFNAK; if (dont_nak) break; pv = nakp; PUTCHAR(type, nakp); PUTCHAR(CILEN_PREDICTOR_2, nakp); } if (p == p0 && ccp_test(f->unit, p, CILEN_PREDICTOR_2, 1) <= 0) { newret = CODE_CONFREJ; } break; default: newret = CODE_CONFREJ; break; } /* Cope with confused peers. */ if (clen < 2) clen = 2; if (newret == CODE_CONFACK && ret != CODE_CONFACK) continue; if (newret == CODE_CONFNAK) { if (dont_nak) { newret = CODE_CONFREJ; } else { /* Ignore subsequent nakable things if rejecting. */ if (ret == CODE_CONFREJ) continue; ret = CODE_CONFNAK; } } if (newret == CODE_CONFREJ) { ret = CODE_CONFREJ; if (p != rejp) BCOPY(p, rejp, clen); rejp += clen; } } switch (ret) { case CODE_CONFACK: *lenp = p - p0; break; case CODE_CONFNAK: *lenp = nakp - nak_buffer; BCOPY(nak_buffer, p0, *lenp); break; case CODE_CONFREJ: *lenp = rejp - p0; break; } return ret; } /* * Make a string name for a compression method (or 2). */ static char * method_name(opt, opt2) ccp_options *opt, *opt2; { static char result[64]; if (!ANY_COMPRESS(*opt)) return "(none)"; switch (opt->method) { case CI_DEFLATE: case CI_DEFLATE_DRAFT: if (opt2 != NULL && opt2->deflate_size != opt->deflate_size) (void) slprintf(result, sizeof(result), "Deflate%s (%d/%d)", (opt->method == CI_DEFLATE_DRAFT? "(old#)": ""), opt->deflate_size, opt2->deflate_size); else (void) slprintf(result, sizeof(result), "Deflate%s (%d)", (opt->method == CI_DEFLATE_DRAFT? "(old#)": ""), opt->deflate_size); break; case CI_BSD_COMPRESS: if (opt2 != NULL && opt2->bsd_bits != opt->bsd_bits) (void) slprintf(result, sizeof(result), "BSD-Compress (%d/%d)", opt->bsd_bits, opt2->bsd_bits); else (void) slprintf(result, sizeof(result), "BSD-Compress (%d)", opt->bsd_bits); break; case CI_PREDICTOR_1: return "Predictor 1"; case CI_PREDICTOR_2: return "Predictor 2"; #ifdef CI_STAC case CI_STAC: return "Stac"; #endif #ifdef CI_MPPC case CI_MPPC: return "MS-PPC"; #endif default: (void) slprintf(result, sizeof(result), "Method %d", opt->method); } return result; } /* * CCP has come up - inform the kernel driver and log a message. */ static void ccp_up(f) fsm *f; { ccp_options *go = &ccp_gotoptions[f->unit]; ccp_options *ho = &ccp_hisoptions[f->unit]; char method1[64]; /* * We're now open and up (running). */ ccp_flags_set(f->unit, 1, 1); if (ANY_COMPRESS(*go)) { if (ANY_COMPRESS(*ho)) { if (go->method == ho->method) { notice("%s compression enabled", method_name(go, ho)); } else { (void) strlcpy(method1, method_name(go, NULL), sizeof(method1)); notice("%s / %s compression enabled", method1, method_name(ho, NULL)); } } else notice("%s receive decompression enabled", method_name(go, NULL)); } else if (ANY_COMPRESS(*ho)) notice("%s transmit compression enabled", method_name(ho, NULL)); } /* * CCP has gone down - inform the kernel driver. */ static void ccp_down(f) fsm *f; { if (ccp_localstate[f->unit] & RACK_PENDING) UNTIMEOUT(ccp_rack_timeout, f); /* Don't forget about peer's code rejects or ignoring of requests. */ ccp_localstate[f->unit] &= ~RACK_PENDING & ~RREQ_REPEAT; /* We're still open, but no longer up. */ ccp_flags_set(f->unit, 1, 0); } static int ccp_printpkt(p, plen, printer, arg) u_char *p; int plen; void (*printer) __P((void *, const char *, ...)); void *arg; { u_char *p0, *optend, cichar; int code, id, len; int optlen, clen; u_short cishort; #ifdef CI_MPPC u_int32_t cilong; #endif p0 = p; if (plen < HEADERLEN) { printer(arg, "too short (%d<%d)", plen, HEADERLEN); return (0); } GETCHAR(code, p); GETCHAR(id, p); GETSHORT(len, p); printer(arg, " %s id=0x%x", code_name(code, 1), id); if (len < HEADERLEN) { printer(arg, " header length %d<%d", len, HEADERLEN); return (HEADERLEN); } if (len > plen) { printer(arg, " truncated (%d>%d)", len, plen); len = plen; } len -= HEADERLEN; switch (code) { case CODE_CONFREQ: case CODE_CONFACK: case CODE_CONFNAK: case CODE_CONFREJ: /* print list of possible compression methods */ while (len >= 2) { GETCHAR(code, p); GETCHAR(clen, p); optlen = clen; printer(arg, " <"); if (optlen > len) optlen = len; if (optlen < 2) optlen = 2; len -= optlen; optend = p + optlen - 2; switch (code) { case CI_DEFLATE: case CI_DEFLATE_DRAFT: printer(arg, "deflate%s", (code == CI_DEFLATE_DRAFT? "(old#)": "")); if (clen != CILEN_DEFLATE) printer(arg, " length %d", clen); if (optlen >= CILEN_DEFLATE) { GETCHAR(cichar, p); printer(arg, " %d", DEFLATE_SIZE(cichar)); if (DEFLATE_METHOD(cichar) != DEFLATE_METHOD_VAL) printer(arg, " method %d", DEFLATE_METHOD(cichar)); GETCHAR(cichar, p); if (cichar != DEFLATE_CHK_SEQUENCE) printer(arg, " check %d", cichar); } break; case CI_BSD_COMPRESS: printer(arg, "bsd"); if (clen != CILEN_BSD_COMPRESS) printer(arg, " length %d", clen); if (optlen >= CILEN_BSD_COMPRESS) { GETCHAR(cichar, p); printer(arg, " v%d %d", BSD_VERSION(cichar), BSD_NBITS(cichar)); } break; case CI_PREDICTOR_1: printer(arg, "predictor-1"); if (clen != CILEN_PREDICTOR_1) printer(arg, " length %d", clen); break; case CI_PREDICTOR_2: printer(arg, "predictor-2"); if (clen != CILEN_PREDICTOR_2) printer(arg, " length %d", clen); break; #ifdef CI_STAC case CI_STAC: printer(arg, "Stac"); if (clen != CILEN_STAC) printer(arg, " length %d", clen); if (optlen >= CILEN_STAC) { GETSHORT(cishort, p); GETCHAR(cichar, p); printer(arg, " h%d/m%d", cishort, cichar); } break; #endif #ifdef CI_MPPC case CI_MPPC: /* There appears to be no good generic name for this one. */ if (optlen >= CILEN_MPPC) { GETLONG(cilong, p); if (!(cilong & MPPC_COMP)) { if (cilong & MPPC_MPPE) printer(arg, "MPPE"); else printer(arg, "MS-PPC?"); } else { if (cilong & MPPC_MPPE) printer(arg, "MPPC+MPPE"); else printer(arg, "MPPC"); } } else { printer(arg, "MS-?"); } if (clen != CILEN_STAC) printer(arg, " length %d", clen); break; #endif default: printer(arg, "typ%d len%d ", code, clen); break; } if (p < optend) { if (p+8 < optend) printer(arg, " %.8B ...", p); else printer(arg, " %.*B", optend-p, p); p = optend; } printer(arg, ">"); } break; case CODE_TERMACK: case CODE_TERMREQ: if (len > 0) { if (len == 2) { GETSHORT(cishort, p); printer(arg, " history %d", cishort); len = 0; } else if (*p >= ' ' && *p < 0x7f) { printer(arg, " "); print_string((char *)p, len, printer, arg); p += len; len = 0; } } break; } /* dump out the rest of the packet in hex */ if (len > 0) { if (len > 8) printer(arg, " %.8B ...", p); else printer(arg, " %.*B", len, p); p += len; } return p - p0; } /* * We have received a packet that the decompressor failed to * decompress. Here we would expect to issue a reset-request, but * Motorola has a patent on resetting the compressor as a result of * detecting an error in the decompressed data after decompression. * (See US patent 5,130,993; international patent publication number * WO 91/10289; Australian patent 73296/91.) * * So we ask the kernel whether the error was detected after * decompression; if it was, we take CCP down, thus disabling * compression :-(, otherwise we issue the reset-request. */ /*ARGSUSED*/ static void ccp_datainput(unit, pkt, len) int unit; u_char *pkt; int len; { fsm *f; f = &ccp_fsm[unit]; if (f->state == OPENED) { if (ccp_fatal_error(unit)) { /* * Disable compression by taking CCP down. */ error("Lost compression sync: disabling compression"); ccp_close(unit, "Lost compression sync"); } else { /* * Send a reset-request to reset the peer's compressor, if * possible. We don't do anything if we are still waiting * for an acknowledgement to a previous reset-request (to * avoid flooding the peer). We reopen CCP if the peer * doesn't like hearing about CCP Reset-Request (Cisco * sends CCP Code-Reject for Reset-Request). (Reopen * automatically clears the flags and cancels the * timeout.) */ if (ccp_localstate[f->unit] & RREQ_REJECTED) { dbglog("reopening CCP to reset peer's compressor"); ccp_open(f->unit); } else if (ccp_localstate[f->unit] & RACK_PENDING) { /* Send another reset request; we're out of sequence. */ ccp_localstate[f->unit] |= RREQ_REPEAT; } else { dbglog("sending CCP Reset-Request to reset peer's compressor"); fsm_sdata(f, CCP_RESETREQ, f->reqid = ++f->id, NULL, 0); TIMEOUT(ccp_rack_timeout, f, RACKTIMEOUT); ccp_localstate[f->unit] |= RACK_PENDING; } } } } /* * Timeout waiting for reset-ack. */ static void ccp_rack_timeout(arg) void *arg; { fsm *f = arg; /* Timeout; no longer pending. */ ccp_localstate[f->unit] &= ~RACK_PENDING; /* Frankly, it's a coding flaw if this occurs. */ if (f->state != OPENED) return; if (ccp_localstate[f->unit] & RREQ_IGNORED) { info("peer ignored our CCP Reset-Request twice; reopen instead"); ccp_localstate[f->unit] = (ccp_localstate[f->unit] & ~RREQ_IGNORED) | RREQ_REJECTED; ccp_open(f->unit); } else if (ccp_localstate[f->unit] & RREQ_REPEAT) { dbglog("sending another CCP Reset-Request on timeout"); fsm_sdata(f, CCP_RESETREQ, f->reqid, NULL, 0); TIMEOUT(ccp_rack_timeout, f, RACKTIMEOUT); ccp_localstate[f->unit] = (ccp_localstate[f->unit] & ~RREQ_REPEAT) | RREQ_IGNORED | RACK_PENDING; } else { dbglog("timeout waiting for CCP Reset-Ack; hope for the best"); ccp_localstate[f->unit] |= RREQ_IGNORED; } } /* * ccp.h - Definitions for PPP Compression Control Protocol. * * Copyright (c) 1994 The Australian National University. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. This software is provided without any * warranty, express or implied. The Australian National University * makes no representations about the suitability of this software for * any purpose. * * IN NO EVENT SHALL THE AUSTRALIAN NATIONAL UNIVERSITY BE LIABLE TO ANY * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF * THE AUSTRALIAN NATIONAL UNIVERSITY HAVE BEEN ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * THE AUSTRALIAN NATIONAL UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE AUSTRALIAN NATIONAL UNIVERSITY HAS NO * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, * OR MODIFICATIONS. * * $Id: ccp.h,v 1.9 1998/11/07 06:59:26 paulus Exp $ */ typedef struct ccp_options { bool bsd_compress; /* do BSD Compress? */ bool deflate; /* do Deflate? */ bool predictor_1; /* do Predictor-1? */ bool predictor_2; /* do Predictor-2? */ bool deflate_correct; /* use correct code for deflate? */ bool deflate_draft; /* use draft RFC code for deflate? */ u_short bsd_bits; /* # bits/code for BSD Compress */ u_short deflate_size; /* lg(window size) for Deflate */ short method; /* code for chosen compression method */ } ccp_options; extern fsm ccp_fsm[]; extern ccp_options ccp_wantoptions[]; extern ccp_options ccp_gotoptions[]; extern ccp_options ccp_allowoptions[]; extern ccp_options ccp_hisoptions[]; extern struct protent ccp_protent; /* * chap.c - Challenge Handshake Authentication Protocol. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright (c) 1993 The Australian National 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 the Australian National 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. * * Copyright (c) 1991 Gregory M. Christy. * 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 Gregory M. Christy. The name of the author 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. */ #include #include #include #include #include #include "pppd.h" #include "chap.h" #include "md5.h" #if defined(CHAPMS) || defined(CHAPMSV2) #include "chap_ms.h" #endif /* * Command-line options. */ static option_t chap_option_list[] = { { "chap-restart", o_int, &chap[0].timeouttime, "Set CHAP Challenge retry interval" }, { "chap-max-challenge", o_int, &chap[0].max_transmits, "Max number of Challenges sent without a valid response" }, { "chap-interval", o_int, &chap[0].chal_interval, "Set interval for rechallenge" }, #if defined(CHAPMS) && defined(MSLANMAN) { "ms-lanman", o_bool, &ms_lanman, "Use obsolete LAN Manager password when using MS-CHAP", 1 }, #endif { NULL } }; /* * Protocol entry points. */ static void ChapInit __P((int)); static void ChapLowerUp __P((int)); static void ChapLowerDown __P((int)); static void ChapInput __P((int, u_char *, int)); static void ChapProtocolReject __P((int)); static int ChapPrintPkt __P((u_char *, int, void (*) __P((void *, const char *, ...)), void *)); struct protent chap_protent = { PPP_CHAP, /* PPP protocol number */ ChapInit, /* Initialization procedure */ ChapInput, /* Process a received packet */ ChapProtocolReject, /* Process a received protocol-reject */ ChapLowerUp, /* Lower layer has come up */ ChapLowerDown, /* Lower layer has gone down */ NULL, /* Open the protocol */ NULL, /* Close the protocol */ ChapPrintPkt, /* Print a packet in readable form */ NULL, /* Process a received data packet */ 1, /* 0 iff protocol is disabled */ "CHAP", /* Text name of protocol */ NULL, /* Text name of corresponding data protocol */ chap_option_list, /* List of command-line options */ NULL, /* Check requested options, assign defaults */ NULL, /* Configure interface for demand-dial */ NULL /* Say whether to bring up link for this pkt */ }; /* Not static'd for plug-ins */ chap_state chap[NUM_PPP]; /* CHAP state; one for each unit */ static void ChapChallengeTimeout __P((void *)); static void ChapResponseTimeout __P((void *)); static void ChapReceiveChallenge __P((chap_state *, u_char *, int, int)); static void ChapRechallenge __P((void *)); static void ChapReceiveResponse __P((chap_state *, u_char *, int, int)); static void ChapReceiveSuccess __P((chap_state *, u_char *, int, int)); static void ChapReceiveFailure __P((chap_state *, u_char *, int, int)); static void ChapSendStatus __P((chap_state *, int)); static void ChapSendChallenge __P((chap_state *)); static void ChapSendResponse __P((chap_state *)); static void ChapGenChallenge __P((chap_state *)); static const char * chap_cstate(int clientstate) { static const char *cstate[] = { CHAPCS__LIST }; static char buf[32]; if (clientstate < 0 || clientstate >= Dim(cstate)) { (void) slprintf(buf, sizeof(buf), "#%d", clientstate); return (buf); } return cstate[clientstate]; } static const char * chap_sstate(int serverstate) { static const char *sstate[] = { CHAPSS__LIST }; static char buf[32]; if (serverstate < 0 || serverstate >= Dim(sstate)) { (void) slprintf(buf, sizeof(buf), "#%d", serverstate); return (buf); } return sstate[serverstate]; } /* * ChapInit - Initialize a CHAP unit. */ static void ChapInit(unit) int unit; { chap_state *cstate = &chap[unit]; BZERO(cstate, sizeof(*cstate)); cstate->unit = unit; cstate->clientstate = CHAPCS_INITIAL; cstate->serverstate = CHAPSS_INITIAL; cstate->timeouttime = CHAP_DEFTIMEOUT; cstate->max_transmits = CHAP_DEFTRANSMITS; /* XXX save get_first_hwaddr() here. */ /* random number generator is initialized in magic_init */ } /* * ChapAuthWithPeer - Authenticate us with our peer (start client). * */ void ChapAuthWithPeer(unit, our_name, digest) int unit; char *our_name; int digest; { chap_state *cstate = &chap[unit]; cstate->resp_name = our_name; cstate->resp_type = digest; if (cstate->clientstate == CHAPCS_INITIAL || cstate->clientstate == CHAPCS_PENDING) { /* lower layer isn't up - wait until later */ cstate->clientstate = CHAPCS_PENDING; return; } /* * We get here as a result of LCP coming up. * So even if CHAP was open before, we will * have to re-authenticate ourselves. */ cstate->clientstate = CHAPCS_LISTEN; } /* * ChapAuthPeer - Authenticate our peer (start server). */ void ChapAuthPeer(unit, our_name, digest) int unit; char *our_name; int digest; { chap_state *cstate = &chap[unit]; cstate->chal_name = our_name; cstate->chal_type = digest; if (cstate->serverstate == CHAPSS_INITIAL || cstate->serverstate == CHAPSS_PENDING) { /* lower layer isn't up - wait until later */ cstate->serverstate = CHAPSS_PENDING; return; } ChapGenChallenge(cstate); ChapSendChallenge(cstate); /* crank it up dude! */ cstate->serverstate = CHAPSS_INITIAL_CHAL; } /* * ChapChallengeTimeout - Timeout expired on sending challenge. */ static void ChapChallengeTimeout(arg) void *arg; { chap_state *cstate = (chap_state *) arg; /* if we aren't sending challenges, don't worry. then again we */ /* probably shouldn't be here either */ if (cstate->serverstate != CHAPSS_INITIAL_CHAL && cstate->serverstate != CHAPSS_RECHALLENGE) return; if (cstate->chal_transmits >= cstate->max_transmits) { /* give up on peer */ error("Peer failed to respond to CHAP challenge"); cstate->serverstate = CHAPSS_BADAUTH; auth_peer_fail(cstate->unit, PPP_CHAP); return; } ChapSendChallenge(cstate); /* Re-send challenge */ } /* * ChapResponseTimeout - Timeout expired on sending response. */ static void ChapResponseTimeout(arg) void *arg; { chap_state *cstate = (chap_state *) arg; /* if we aren't sending a response, don't worry. */ if (cstate->clientstate != CHAPCS_RESPONSE) return; ChapSendResponse(cstate); /* re-send response */ } /* * ChapRechallenge - Time to challenge the peer again. */ static void ChapRechallenge(arg) void *arg; { chap_state *cstate = (chap_state *) arg; /* if we aren't sending a response, don't worry. */ if (cstate->serverstate != CHAPSS_OPEN) return; ChapGenChallenge(cstate); ChapSendChallenge(cstate); cstate->serverstate = CHAPSS_RECHALLENGE; } /* * ChapLowerUp - The lower layer is up. * * Start up if we have pending requests. */ static void ChapLowerUp(unit) int unit; { chap_state *cstate = &chap[unit]; if (cstate->clientstate == CHAPCS_INITIAL) cstate->clientstate = CHAPCS_CLOSED; else if (cstate->clientstate == CHAPCS_PENDING) cstate->clientstate = CHAPCS_LISTEN; if (cstate->serverstate == CHAPSS_INITIAL) cstate->serverstate = CHAPSS_CLOSED; else if (cstate->serverstate == CHAPSS_PENDING) { ChapGenChallenge(cstate); ChapSendChallenge(cstate); cstate->serverstate = CHAPSS_INITIAL_CHAL; } } /* * ChapLowerDown - The lower layer is down. * * Cancel all timeouts. */ static void ChapLowerDown(unit) int unit; { chap_state *cstate = &chap[unit]; /* Timeout(s) pending? Cancel if so. */ if (cstate->serverstate == CHAPSS_INITIAL_CHAL || cstate->serverstate == CHAPSS_RECHALLENGE) UNTIMEOUT(ChapChallengeTimeout, cstate); else if (cstate->serverstate == CHAPSS_OPEN && cstate->chal_interval != 0) UNTIMEOUT(ChapRechallenge, cstate); if (cstate->clientstate == CHAPCS_RESPONSE) UNTIMEOUT(ChapResponseTimeout, cstate); cstate->clientstate = CHAPCS_INITIAL; cstate->serverstate = CHAPSS_INITIAL; } /* * ChapProtocolReject - Peer doesn't grok CHAP. */ static void ChapProtocolReject(unit) int unit; { chap_state *cstate = &chap[unit]; if (cstate->serverstate != CHAPSS_INITIAL && cstate->serverstate != CHAPSS_CLOSED) auth_peer_fail(unit, PPP_CHAP); if (cstate->clientstate != CHAPCS_INITIAL && cstate->clientstate != CHAPCS_CLOSED) auth_withpeer_fail(unit, PPP_CHAP); ChapLowerDown(unit); /* shutdown chap */ } /* * ChapInput - Input CHAP packet. */ static void ChapInput(unit, inpacket, packet_len) int unit; u_char *inpacket; int packet_len; { chap_state *cstate = &chap[unit]; u_char *inp; u_char code, id; int len; /* * Parse header (code, id and length). * If packet too short, drop it. */ inp = inpacket; if (packet_len < CHAP_HEADERLEN) { error("CHAP: packet is too small (%d < %d)", packet_len, CHAP_HEADERLEN); return; } GETCHAR(code, inp); GETCHAR(id, inp); GETSHORT(len, inp); if (len < CHAP_HEADERLEN || len > packet_len) { error("CHAP: packet has illegal length %d (%d..%d)", len, CHAP_HEADERLEN, packet_len); return; } len -= CHAP_HEADERLEN; /* * Action depends on code (as in fact it usually does :-). */ switch (code) { case CHAP_CHALLENGE: ChapReceiveChallenge(cstate, inp, id, len); break; case CHAP_RESPONSE: ChapReceiveResponse(cstate, inp, id, len); break; case CHAP_FAILURE: ChapReceiveFailure(cstate, inp, id, len); break; case CHAP_SUCCESS: ChapReceiveSuccess(cstate, inp, id, len); break; default: /* CHAP does not define a code reject. */ warn("Unknown CHAP code (%d) received.", code); break; } } /* * ChapReceiveChallenge - Receive Challenge and send Response. */ static void ChapReceiveChallenge(cstate, inp, id, len) chap_state *cstate; u_char *inp; int id; int len; { int rchallenge_len; u_char *rchallenge; int secret_len; char rhostname[MAXNAMELEN]; char secret[MAXSECRETLEN]; MD5_CTX mdContext; u_char hash[MD5_SIGNATURE_SIZE]; int fake_response = 0; if (cstate->clientstate == CHAPCS_CLOSED || cstate->clientstate == CHAPCS_PENDING) { if (debug) dbglog("CHAP Challenge unexpectedly received in state %s", chap_cstate(cstate->clientstate)); return; } if (len < 2) { error("CHAP: Challenge message length %d", len); return; } GETCHAR(rchallenge_len, inp); len -= sizeof (u_char) + rchallenge_len; /* now name field length */ if (len < 0) { error("CHAP: Challenge truncated"); return; } rchallenge = inp; INCPTR(rchallenge_len, inp); if (len >= sizeof(rhostname)) len = sizeof(rhostname) - 1; if (len > 0) { BCOPY(inp, rhostname, len); } rhostname[len] = '\0'; #ifdef CHECK_CHALLENGE_LENGTH if (rchallenge_len < CHECK_CHALLENGE_LENGTH) { warn("CHAP: Challenge from %s unreasonably short (%d octets)", rhostname, rchallenge_len); fake_response = 1; } #endif /* XXX compare against saved get_first_hwaddr() here. */ /* Microsoft NT doesn't send a name in the CHAP Challenge message */ if (explicit_remote || (remote_name[0] != '\0' && rhostname[0] == '\0')) { (void) strlcpy(rhostname, remote_name, sizeof(rhostname)); if (debug) dbglog("CHAP: Peer gave no name; using '%q' as remote name", rhostname); } if (cstate->peercname[0] == '\0') { (void) strlcpy(cstate->peercname, rhostname, sizeof (cstate->peercname)); } else if (strcmp(rhostname, cstate->peercname) != 0) { if (++cstate->rename_count == 1) { info("CHAP: peer challenge name changed from '%q' to '%q'", cstate->peercname, rhostname); (void) strlcpy(cstate->peercname, rhostname, sizeof (cstate->peercname)); } else { fake_response = 1; if (cstate->rename_count == 2) warn("CHAP: peer challenge name changed again to '%q'", rhostname); } } /* get secret for authenticating ourselves with the specified host */ if (!get_secret(cstate->unit, cstate->resp_name, rhostname, secret, &secret_len, 0)) { secret_len = 0; /* assume null secret if can't find one */ warn("No CHAP secret found for authenticating us (%q) to %q", cstate->resp_name, rhostname); } /* cancel response send timeout if necessary */ if (cstate->clientstate == CHAPCS_RESPONSE) UNTIMEOUT(ChapResponseTimeout, cstate); cstate->resp_id = id; cstate->resp_transmits = 0; /* generate MD based on negotiated type */ switch (cstate->resp_type) { case CHAP_DIGEST_MD5: MD5Init(&mdContext); MD5Update(&mdContext, &cstate->resp_id, 1); MD5Update(&mdContext, (u_char *)secret, (unsigned)secret_len); MD5Update(&mdContext, rchallenge, rchallenge_len); MD5Final(hash, &mdContext); if (fake_response) { for (len = 0; len < MD5_SIGNATURE_SIZE; len++) cstate->response[len] = (char) (drand48() * 0xff); } else { BCOPY(hash, cstate->response, MD5_SIGNATURE_SIZE); } cstate->resp_length = MD5_SIGNATURE_SIZE; break; #ifdef CHAPMS case CHAP_MICROSOFT: ChapMS(cstate, rchallenge, rchallenge_len, secret, secret_len); break; #endif #ifdef CHAPMSV2 case CHAP_MICROSOFT_V2: ChapMSv2(cstate, rchallenge, rchallenge_len, secret, secret_len); break; #endif default: error("CHAP: unknown digest type %d", cstate->resp_type); return; } BZERO(secret, sizeof(secret)); ChapSendResponse(cstate); } /* * ChapReceiveResponse - Receive and process response. */ static void ChapReceiveResponse(cstate, inp, id, len) chap_state *cstate; u_char *inp; int id; int len; { u_char *remmd, remmd_len; int secret_len, old_state; int code; char rhostname[MAXNAMELEN], *rhn; MD5_CTX mdContext; char secret[MAXSECRETLEN]; u_char hash[MD5_SIGNATURE_SIZE]; if (cstate->serverstate == CHAPSS_CLOSED || cstate->serverstate == CHAPSS_PENDING) { if (debug) dbglog("CHAP Response unexpectedly received in state %s", chap_sstate(cstate->serverstate)); return; } if (id != cstate->chal_id) { if (debug) dbglog("CHAP: discard response %d; expecting %d", id, cstate->chal_id); return; /* doesn't match ID of last challenge */ } /* * If we have received a duplicate or bogus Response, * we have to send the same answer (Success/Failure) * as we did for the first Response we saw. */ if (cstate->serverstate == CHAPSS_OPEN) { if (debug) dbglog("CHAP ignoring response and resending success message"); ChapSendStatus(cstate, CHAP_SUCCESS); return; } if (cstate->serverstate == CHAPSS_BADAUTH) { if (debug) dbglog("CHAP ignoring response and resending failure message"); ChapSendStatus(cstate, CHAP_FAILURE); return; } if (len < 2) { error("CHAP: Response message length %d", len); return; } GETCHAR(remmd_len, inp); /* get length of MD */ remmd = inp; /* get pointer to MD */ INCPTR(remmd_len, inp); len -= sizeof (u_char) + remmd_len; if (len < 0) { error("CHAP: Response truncated"); return; } UNTIMEOUT(ChapChallengeTimeout, cstate); if (len >= sizeof(rhostname)) len = sizeof(rhostname) - 1; BCOPY(inp, rhostname, len); rhostname[len] = '\0'; /* * Get secret for authenticating them with us, * do the hash ourselves, and compare the result. */ code = CHAP_FAILURE; rhn = (explicit_remote? remote_name: rhostname); if (cstate->serverstate == CHAPSS_RECHALLENGE && strcmp(rhostname, peer_authname) != 0) { warn("Peer changed his name from '%q' to '%q' on rechallenge", peer_authname, rhostname); } else if (!get_secret(cstate->unit, rhn, cstate->chal_name, secret, &secret_len, 1)) { warn("No CHAP secret found for authenticating %q to us (%q)", rhn, cstate->chal_name); } else { /* generate MD based on negotiated type */ switch (cstate->chal_type) { case CHAP_DIGEST_MD5: /* only MD5 is defined for now */ if (remmd_len != MD5_SIGNATURE_SIZE) break; /* it's not even the right length */ MD5Init(&mdContext); MD5Update(&mdContext, &cstate->chal_id, 1); MD5Update(&mdContext, (u_char *)secret, secret_len); MD5Update(&mdContext, cstate->challenge, cstate->chal_len); MD5Final(hash, &mdContext); /* compare local and remote MDs and send the appropriate status */ if (memcmp (hash, remmd, MD5_SIGNATURE_SIZE) == 0) code = CHAP_SUCCESS; /* they are the same! */ break; #ifdef CHAPMS case CHAP_MICROSOFT: if (ChapMSValidate(cstate, remmd, remmd_len, secret, secret_len)) code = CHAP_SUCCESS; break; #endif #ifdef CHAPMSV2 case CHAP_MICROSOFT_V2: if (ChapMSv2Validate(cstate, rhostname, remmd, remmd_len, secret, secret_len)) code = CHAP_SUCCESS; break; #endif default: error("CHAP: unknown digest type %d", cstate->chal_type); } } BZERO(secret, sizeof(secret)); ChapSendStatus(cstate, code); if (code == CHAP_SUCCESS) { old_state = cstate->serverstate; cstate->serverstate = CHAPSS_OPEN; if (old_state == CHAPSS_INITIAL_CHAL) { auth_peer_success(cstate->unit, PPP_CHAP, rhostname, len); } if (cstate->chal_interval != 0) TIMEOUT(ChapRechallenge, cstate, cstate->chal_interval); if (old_state == CHAPSS_INITIAL_CHAL) notice("CHAP peer authentication succeeded for %q", rhostname); else if (debug) dbglog("CHAP peer reauthentication succeeded for %q", rhostname); } else { error("CHAP peer %sauthentication failed for remote host %q", (cstate->serverstate == CHAPSS_INITIAL_CHAL ? "" : "re"), rhostname); cstate->serverstate = CHAPSS_BADAUTH; auth_peer_fail(cstate->unit, PPP_CHAP); } } /* * ChapReceiveSuccess - Receive Success */ /*ARGSUSED*/ static void ChapReceiveSuccess(cstate, inp, id, len) chap_state *cstate; u_char *inp; u_char id; int len; { if (cstate->clientstate == CHAPCS_OPEN) { /* presumably an answer to a duplicate response */ if (debug) dbglog("CHAP duplicate Success message ignored"); return; } if (cstate->clientstate != CHAPCS_RESPONSE) { /* don't know what this is */ if (debug) dbglog("CHAP Success unexpectedly received in state %s", chap_cstate(cstate->clientstate)); return; } UNTIMEOUT(ChapResponseTimeout, cstate); /* * Print message. */ if (len > 0) PRINTMSG(inp, len); cstate->clientstate = CHAPCS_OPEN; auth_withpeer_success(cstate->unit, PPP_CHAP); } /* * ChapReceiveFailure - Receive failure. */ /*ARGSUSED*/ static void ChapReceiveFailure(cstate, inp, id, len) chap_state *cstate; u_char *inp; u_char id; int len; { if (cstate->clientstate != CHAPCS_RESPONSE) { /* don't know what this is */ if (debug) dbglog("CHAP Failure unexpectedly received in state %s", chap_cstate(cstate->clientstate)); return; } UNTIMEOUT(ChapResponseTimeout, cstate); /* * Print message. */ if (len > 0) PRINTMSG(inp, len); error("CHAP authentication failed"); auth_withpeer_fail(cstate->unit, PPP_CHAP); } /* * ChapSendChallenge - Send an Authenticate challenge. */ static void ChapSendChallenge(cstate) chap_state *cstate; { u_char *outp; int chal_len, name_len; int outlen; chal_len = cstate->chal_len; name_len = strlen(cstate->chal_name); outlen = CHAP_HEADERLEN + sizeof (u_char) + chal_len + name_len; outp = outpacket_buf; MAKEHEADER(outp, PPP_CHAP); /* paste in a CHAP header */ PUTCHAR(CHAP_CHALLENGE, outp); PUTCHAR(cstate->chal_id, outp); PUTSHORT(outlen, outp); PUTCHAR(chal_len, outp); /* put length of challenge */ BCOPY(cstate->challenge, outp, chal_len); INCPTR(chal_len, outp); BCOPY(cstate->chal_name, outp, name_len); /* append hostname */ output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN); TIMEOUT(ChapChallengeTimeout, cstate, cstate->timeouttime); ++cstate->chal_transmits; } /* * ChapSendStatus - Send a status response (ack or nak). */ static void ChapSendStatus(cstate, code) chap_state *cstate; int code; { u_char *outp; int outlen, msglen; char msg[256], *msgp; if ((msgp = cstate->stat_message) != NULL) { msglen = cstate->stat_length; } else { if (code == CHAP_SUCCESS) (void) slprintf(msg, sizeof(msg), "Welcome to %s.", hostname); else (void) slprintf(msg, sizeof(msg), "I don't like you. Go 'way."); msglen = strlen(msg); msgp = msg; } outlen = CHAP_HEADERLEN + msglen; outp = outpacket_buf; MAKEHEADER(outp, PPP_CHAP); /* paste in a header */ PUTCHAR(code, outp); PUTCHAR(cstate->chal_id, outp); PUTSHORT(outlen, outp); BCOPY(msgp, outp, msglen); output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN); } /* * ChapGenChallenge is used to generate a pseudo-random challenge string of * a pseudo-random length between min_len and max_len. The challenge * string and its length are stored in *cstate, and various other fields of * *cstate are initialized. */ static void ChapGenChallenge(cstate) chap_state *cstate; { int chal_len; u_char *ptr = cstate->challenge; int i; /* pick a random challenge length between MIN_CHALLENGE_LENGTH and MAX_CHALLENGE_LENGTH */ chal_len = (unsigned) ((drand48() * (MAX_CHALLENGE_LENGTH - MIN_CHALLENGE_LENGTH)) + MIN_CHALLENGE_LENGTH); cstate->chal_len = chal_len; cstate->chal_id = ++cstate->id; cstate->chal_transmits = 0; /* XXX tack on saved get_first_hwaddr() here. */ /* generate a random string */ for (i = 0; i < chal_len; i++) *ptr++ = (char) (drand48() * 0xff); } /* * ChapSendResponse - send a response packet with values as specified * in *cstate. */ /* ARGSUSED */ static void ChapSendResponse(cstate) chap_state *cstate; { u_char *outp; int outlen, md_len, name_len; md_len = cstate->resp_length; name_len = strlen(cstate->resp_name); outlen = CHAP_HEADERLEN + sizeof (u_char) + md_len + name_len; outp = outpacket_buf; MAKEHEADER(outp, PPP_CHAP); PUTCHAR(CHAP_RESPONSE, outp); /* we are a response */ PUTCHAR(cstate->resp_id, outp); /* copy id from challenge packet */ PUTSHORT(outlen, outp); /* packet length */ PUTCHAR(md_len, outp); /* length of MD */ BCOPY(cstate->response, outp, md_len); /* copy MD to buffer */ INCPTR(md_len, outp); BCOPY(cstate->resp_name, outp, name_len); /* append our name */ /* send the packet */ output(cstate->unit, outpacket_buf, outlen + PPP_HDRLEN); cstate->clientstate = CHAPCS_RESPONSE; TIMEOUT(ChapResponseTimeout, cstate, cstate->timeouttime); ++cstate->resp_transmits; } /* * ChapPrintPkt - print the contents of a CHAP packet. */ static char *ChapCodenames[] = { "Challenge", "Response", "Success", "Failure" }; static int ChapPrintPkt(p, plen, printer, arg) u_char *p; int plen; void (*printer) __P((void *, const char *, ...)); void *arg; { int code, id, len; int clen, nlen; u_char x; if (plen < CHAP_HEADERLEN) return 0; GETCHAR(code, p); GETCHAR(id, p); GETSHORT(len, p); if (len < CHAP_HEADERLEN || len > plen) return 0; if (code >= 1 && code <= sizeof(ChapCodenames) / sizeof(char *)) printer(arg, " %s", ChapCodenames[code-1]); else printer(arg, " code=0x%x", code); printer(arg, " id=0x%x", id); len -= CHAP_HEADERLEN; switch (code) { case CHAP_CHALLENGE: case CHAP_RESPONSE: if (len < 1) break; clen = p[0]; if (len < clen + 1) break; ++p; nlen = len - clen - 1; printer(arg, " <"); for (; clen > 0; --clen) { GETCHAR(x, p); printer(arg, "%.2x", x); } printer(arg, ">, name = "); print_string((char *)p, nlen, printer, arg); break; case CHAP_FAILURE: case CHAP_SUCCESS: printer(arg, " "); print_string((char *)p, len, printer, arg); break; default: for (clen = len; clen > 0; --clen) { GETCHAR(x, p); printer(arg, " %.2x", x); } } return len + CHAP_HEADERLEN; } /* * chap.h - Challenge Handshake Authentication Protocol definitions. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright (c) 1993 The Australian National 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 the Australian National 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. * * Copyright (c) 1991 Gregory M. Christy * 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 the author. * * 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. * * $Id: chap.h,v 1.8 1999/11/15 01:44:41 paulus Exp $ */ #ifndef __CHAP_INCLUDE__ #define __CHAP_INCLUDE__ /* Code + ID + length */ #define CHAP_HEADERLEN 4 #define CHAP_DIGEST_MD5 5 /* use MD5 algorithm */ #define MD5_SIGNATURE_SIZE 16 /* 16 bytes in a MD5 message digest */ #define CHAP_MICROSOFT 0x80 /* use Microsoft-compatible alg. */ #define CHAP_MICROSOFT_V2 0x81 /* use MS-CHAPv2 */ #define CHECK_CHALLENGE_LENGTH 8 /* Minimum acceptable challenge */ /* * CHAP message code numbers. */ #define CHAP_CHALLENGE 1 #define CHAP_RESPONSE 2 #define CHAP_SUCCESS 3 #define CHAP_FAILURE 4 /* * Challenge lengths (for challenges we send) and other limits. */ #define MIN_CHALLENGE_LENGTH 16 #define MAX_CHALLENGE_LENGTH 24 #define MAX_RESPONSE_LENGTH 64 /* sufficient for MD5 or MS-CHAP */ /* These are here to remind people of the buffer limits */ #define MS_CHAP_RESPONSE_LEN 49 /* Response length for MS-CHAP */ #define MS_CHAPV2_RESPONSE_LEN 49 /* Response length for MS-CHAPv2 */ /* * Each interface is described by a chap structure. */ typedef struct chap_state { int unit; /* Interface unit number */ int clientstate; /* Client state */ int serverstate; /* Server state */ char peercname[MAXNAMELEN]; /* unauthenticated peer name in challenge */ u_char challenge[MAX_CHALLENGE_LENGTH]; /* last challenge string sent */ u_char chal_len; /* challenge length */ u_char chal_id; /* ID of last challenge */ u_char chal_type; /* hash algorithm for challenges */ u_char id; /* Current id */ char *chal_name; /* Our name to use with challenge */ int chal_interval; /* Time until we challenge peer again */ int timeouttime; /* Timeout time in seconds */ int max_transmits; /* Maximum # of challenge transmissions */ int chal_transmits; /* Number of transmissions of challenge */ int resp_transmits; /* Number of transmissions of response */ u_char response[MAX_RESPONSE_LENGTH]; /* Response to send */ u_char resp_length; /* length of response */ u_char resp_id; /* ID for response messages */ u_char resp_type; /* hash algorithm for responses */ u_char stat_length; /* Length of status message (MS-CHAP) */ char *resp_name; /* Our name to send with response */ char *stat_message; /* per-algorithm status message (MS-CHAP) */ int rename_count; /* number of peer renames seen */ } chap_state; /* * Client (authenticatee) states. */ #define CHAPCS_INITIAL 0 /* Lower layer down, not opened */ #define CHAPCS_CLOSED 1 /* Lower layer up, not opened */ #define CHAPCS_PENDING 2 /* Auth us to peer when lower up */ #define CHAPCS_LISTEN 3 /* Listening for a challenge */ #define CHAPCS_RESPONSE 4 /* Sent response, waiting for status */ #define CHAPCS_OPEN 5 /* We've received Success */ #define CHAPCS__LIST \ "Initial", "Closed", "Pending", "Listen", \ "Response", "Open" /* * Server (authenticator) states. */ #define CHAPSS_INITIAL 0 /* Lower layer down, not opened */ #define CHAPSS_CLOSED 1 /* Lower layer up, not opened */ #define CHAPSS_PENDING 2 /* Auth peer when lower up */ #define CHAPSS_INITIAL_CHAL 3 /* We've sent the first challenge */ #define CHAPSS_OPEN 4 /* We've sent a Success msg */ #define CHAPSS_RECHALLENGE 5 /* We've sent another challenge */ #define CHAPSS_BADAUTH 6 /* We've sent a Failure msg */ #define CHAPSS__LIST \ "Initial", "Closed", "Pending", "InitialChal", \ "Open", "Rechallenge", "BadAuth" /* * Timeouts. */ #define CHAP_DEFTIMEOUT 3 /* Timeout time in seconds */ #define CHAP_DEFTRANSMITS 10 /* max # times to send challenge */ extern chap_state chap[]; void ChapAuthWithPeer __P((int, char *, int)); void ChapAuthPeer __P((int, char *, int)); extern struct protent chap_protent; #endif /* __CHAP_INCLUDE__ */ /* * chap_ms.c - Microsoft MS-CHAP compatible implementation. * * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright (c) 1995 Eric Rosenquist, Strata Software Limited. * http://www.strataware.com/ * * 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 Eric Rosenquist. The name of the author 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. */ /* * This module implements MS-CHAPv1 (RFC 2433) and MS-CHAPv2 (RFC 2759). * * Modifications by Lauri Pesonen / lpesonen@clinet.fi, april 1997 * * Implemented LANManager type password response to MS-CHAP challenges. * Now pppd provides both NT style and LANMan style blocks, and the * prefered is set by option "ms-lanman". Default is to use NT. * The hash text (StdText) was taken from Win95 RASAPI32.DLL. * * You should also use DOMAIN\\USERNAME as described in README.MSCHAP80 * * Modifications by James Carlson / james.d.carlson@sun.com, June 1st, 2000. * * Added MS-CHAPv2 support. */ #if defined(CHAPMS) || defined(CHAPMSV2) #include #include #include #include #include #include #include #ifdef HAVE_CRYPT_H #include #endif #ifdef CHAPMSV2 #include "sha1.h" #endif #ifndef USE_CRYPT #include #endif #include "pppd.h" #include "chap.h" #include "chap_ms.h" #include "md4.h" typedef struct { u_char LANManResp[24]; u_char NTResp[24]; u_char UseNT; /* If 1, ignore the LANMan response field */ } MS_ChapResponse; /* We use MS_CHAP_RESPONSE_LEN, rather than sizeof(MS_ChapResponse), in case this struct gets padded. */ typedef struct { u_char PeerChallenge[16]; u_char MustBeZero[8]; u_char NTResp[24]; u_char Flags; /* Should be zero (Win98 sends 04) */ } MS_Chapv2Response; /* We use MS_CHAPV2_RESPONSE_LEN, rather than sizeof(MS_Chapv2Response), in case this struct gets padded. */ static void ChallengeResponse __P((u_char *, u_char *, u_char *)); static void DesEncrypt __P((u_char *, u_char *, u_char *)); static void MakeKey __P((u_char *, u_char *)); static u_char Get7Bits __P((u_char *, int)); #ifdef CHAPMS static void ChapMS_NT __P((u_char *, char *, int, MS_ChapResponse *)); #ifdef MSLANMAN static void ChapMS_LANMan __P((u_char *, char *, int, MS_ChapResponse *)); #endif #endif #ifdef CHAPMSV2 static void ChapMSv2_NT __P((char *, u_char *, char *, int, MS_Chapv2Response *)); #endif #ifdef USE_CRYPT static void Expand __P((u_char *, char *)); static void Collapse __P((char *, u_char *)); #endif #if defined(MSLANMAN) && defined(CHAPMS) bool ms_lanman = 0; /* Use LanMan password instead of NT */ /* Has meaning only with MS-CHAP challenges */ #endif #ifdef CHAPMSV2 /* Specially-formatted Microsoft CHAP response message. */ static char status_message[256]; #endif static void ChallengeResponse(challenge, pwHash, response) u_char *challenge; /* IN 8 octets */ u_char *pwHash; /* IN 16 octets */ u_char *response; /* OUT 24 octets */ { u_char ZPasswordHash[21]; BZERO(ZPasswordHash, sizeof(ZPasswordHash)); BCOPY(pwHash, ZPasswordHash, MD4_SIGNATURE_SIZE); #if 0 dbglog("ChallengeResponse - ZPasswordHash %.*B", sizeof(ZPasswordHash), ZPasswordHash); #endif DesEncrypt(challenge, ZPasswordHash + 0, response + 0); DesEncrypt(challenge, ZPasswordHash + 7, response + 8); DesEncrypt(challenge, ZPasswordHash + 14, response + 16); #if 0 dbglog("ChallengeResponse - response %.24B", response); #endif } #ifdef USE_CRYPT static void DesEncrypt(clear, key, cipher) u_char *clear; /* IN 8 octets */ u_char *key; /* IN 7 octets */ u_char *cipher; /* OUT 8 octets */ { u_char des_key[8]; char crypt_key[66]; char des_input[66]; MakeKey(key, des_key); Expand(des_key, crypt_key); setkey(crypt_key); #if 0 CHAPDEBUG((LOG_INFO, "DesEncrypt: 8 octet input : %.8B", clear)); #endif Expand(clear, des_input); encrypt(des_input, 0); Collapse(des_input, cipher); #if 0 CHAPDEBUG((LOG_INFO, "DesEncrypt: 8 octet output: %.8B", cipher)); #endif } #else /* USE_CRYPT */ static void DesEncrypt(clear, key, cipher) u_char *clear; /* IN 8 octets */ u_char *key; /* IN 7 octets */ u_char *cipher; /* OUT 8 octets */ { des_cblock des_key; des_key_schedule key_schedule; MakeKey(key, des_key); des_set_key(&des_key, key_schedule); #if 0 CHAPDEBUG((LOG_INFO, "DesEncrypt: 8 octet input : %.8B", clear)); #endif des_ecb_encrypt((des_cblock *)clear, (des_cblock *)cipher, key_schedule, 1); #if 0 CHAPDEBUG((LOG_INFO, "DesEncrypt: 8 octet output: %.8B", cipher)); #endif } #endif /* USE_CRYPT */ static u_char Get7Bits(input, startBit) 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; } #ifdef USE_CRYPT /* in == 8-byte string (expanded version of the 56-bit key) * out == 64-byte string where each byte is either 1 or 0 * Note that the low-order "bit" is always ignored by by setkey() */ static void Expand(in, out) u_char *in; char *out; { int j, c; int i; for(i = 0; i < 64; in++){ c = *in; for(j = 7; j >= 0; j--) *out++ = (c >> j) & 01; i += 8; } } /* The inverse of Expand */ static void Collapse(in, out) char *in; u_char *out; { int j; int i; unsigned int c; for (i = 0; i < 64; i += 8, out++) { c = 0; for (j = 7; j >= 0; j--, in++) c |= *(u_char *)in << j; *out = c & 0xff; } } #endif static void MakeKey(key, des_key) u_char *key; /* IN 56 bit DES key missing parity bits */ u_char *des_key; /* OUT 64 bit DES key with parity bits added */ { 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); #ifndef USE_CRYPT des_set_odd_parity((des_cblock *)des_key); #endif #if 0 CHAPDEBUG((LOG_INFO, "MakeKey: 56-bit input : %.7B", key)); CHAPDEBUG((LOG_INFO, "MakeKey: 64-bit output: %.8B", des_key)); #endif } #ifdef CHAPMS static void ChapMS_NT(rchallenge, secret, secret_len, response) u_char *rchallenge; char *secret; int secret_len; MS_ChapResponse *response; { int i; #if defined(__NetBSD__) || defined(HAVE_LIBMD) /* NetBSD uses the libc md4 routines which take bytes instead of bits */ int mdlen = secret_len * 2; #else int mdlen = secret_len * 2 * 8; #endif MD4_CTX md4Context; u_char hash[MD4_SIGNATURE_SIZE]; u_char unicodePassword[MAX_NT_PASSWORD * 2]; /* Initialize the Unicode version of the secret (== password). */ /* This implicitly supports 8-bit ISO8859/1 characters. */ BZERO(unicodePassword, sizeof(unicodePassword)); for (i = 0; i < secret_len; i++) unicodePassword[i * 2] = (u_char)secret[i]; MD4Init(&md4Context); MD4Update(&md4Context, unicodePassword, mdlen); MD4Final(hash, &md4Context); /* Tell MD4 we're done */ ChallengeResponse(rchallenge, hash, response->NTResp); } #ifdef MSLANMAN static u_char *StdText = (u_char *)"KGS!@#$%"; /* key from rasapi32.dll */ static void ChapMS_LANMan(rchallenge, secret, secret_len, response) u_char *rchallenge; char *secret; int secret_len; MS_ChapResponse *response; { int i; u_char UcasePassword[MAX_NT_PASSWORD]; /* max is actually 14 */ u_char PasswordHash[MD4_SIGNATURE_SIZE]; /* LANMan password is case insensitive */ BZERO(UcasePassword, sizeof(UcasePassword)); for (i = 0; i < secret_len; i++) UcasePassword[i] = (u_char)( islower(secret[i]) ? toupper(secret[i]) : secret[i]); DesEncrypt( StdText, UcasePassword + 0, PasswordHash + 0 ); DesEncrypt( StdText, UcasePassword + 7, PasswordHash + 8 ); ChallengeResponse(rchallenge, PasswordHash, response->LANManResp); } #endif void ChapMS(cstate, rchallenge, rchallenge_len, secret, secret_len) chap_state *cstate; u_char *rchallenge; int rchallenge_len; char *secret; int secret_len; { MS_ChapResponse response; if (rchallenge_len < 8) { cstate->resp_length = 0; return; } #if 0 CHAPDEBUG((LOG_INFO, "ChapMS: secret is '%.*s'", secret_len, secret)); #endif BZERO(&response, sizeof(response)); /* Calculate both always */ ChapMS_NT(rchallenge, secret, secret_len, &response); #ifdef MSLANMAN ChapMS_LANMan(rchallenge, secret, secret_len, &response); /* prefered method is set by option */ response.UseNT = !ms_lanman; #else response.UseNT = 1; #endif BCOPY(&response, cstate->response, MS_CHAP_RESPONSE_LEN); cstate->resp_length = MS_CHAP_RESPONSE_LEN; } static int ChapMSStatus(cstate, flag) chap_state *cstate; int flag; { if (flag != 0) { cstate->stat_message = NULL; cstate->stat_length = 0; } else { cstate->stat_message = "E=691 R=0 M=\"Authentication failed\""; cstate->stat_length = strlen(cstate->stat_message); } return (flag); } int ChapMSValidate(cstate, response, response_len, secret, secret_len) chap_state *cstate; u_char *response; int response_len; char *secret; int secret_len; { MS_ChapResponse ckresp; if (response_len < MS_CHAP_RESPONSE_LEN || cstate->chal_len < 8) return (0); BZERO(&ckresp, sizeof(ckresp)); if (response[MS_CHAP_RESPONSE_LEN-1]) { ChapMS_NT(cstate->challenge, secret, secret_len, &ckresp); return (ChapMSStatus(cstate, memcmp(ckresp.NTResp, response+24, 24) == 0)); } #ifdef MSLANMAN ChapMS_LANMan(cstate->challenge, secret, secret_len, &ckresp); return (ChapMSStatus(cstate, memcmp(ckresp.LANManResp, response, 24) == 0)); #else return (ChapMSStatus(cstate, 0)); #endif } #endif /* CHAPMS */ #ifdef CHAPMSV2 static void ChallengeHash(peerchallenge, authenticatorchallenge, username, challenge) u_char *peerchallenge, *authenticatorchallenge, *challenge; char *username; { uint8_t digest[20]; SHA1_CTX sha1Context; char *cp; SHA1Init(&sha1Context); SHA1Update(&sha1Context, peerchallenge, 16); SHA1Update(&sha1Context, authenticatorchallenge, 16); /* * Only the user name (as presented by the peer and * excluding any prepended domain name) * is used as input to SHAUpdate(). */ if ((cp = strchr(username,'\\')) != NULL) username = cp; SHA1Update(&sha1Context, (uint8_t *)username, strlen(username)); SHA1Final(digest, &sha1Context); BCOPY(digest, challenge, 8); } static void ChapMSv2_NT(username, rchallenge, secret, secret_len, response) char *username; u_char *rchallenge; char *secret; int secret_len; MS_Chapv2Response *response; { int i; #if defined(__NetBSD__) || defined(HAVE_LIBMD) /* NetBSD uses the libc md4 routines that take bytes instead of bits */ int mdlen = secret_len * 2; #else int mdlen = secret_len * 2 * 8; #endif MD4_CTX md4Context; u_char hash[MD4_SIGNATURE_SIZE]; u_char challenge[8]; u_char unicodePassword[MAX_NT_PASSWORD * 2]; /* Initialize the Unicode version of the secret (== password). */ /* This implicitly supports 8-bit ISO8859/1 characters. */ BZERO(unicodePassword, sizeof(unicodePassword)); for (i = 0; i < secret_len && i < MAX_NT_PASSWORD; i++) if ((unicodePassword[i * 2] = (u_char)secret[i]) == '\0') break; ChallengeHash(response->PeerChallenge, rchallenge, username, challenge); MD4Init(&md4Context); MD4Update(&md4Context, unicodePassword, mdlen); MD4Final(hash, &md4Context); /* Tell MD4 we're done */ ChallengeResponse(challenge, hash, response->NTResp); } void ChapMSv2(cstate, rchallenge, rchallenge_len, secret, secret_len) chap_state *cstate; u_char *rchallenge; int rchallenge_len; char *secret; int secret_len; { MS_Chapv2Response response; u_char *ptr; int i; if (rchallenge_len < 8) { cstate->resp_length = 0; return; } BZERO(&response, sizeof(response)); ptr = response.PeerChallenge; for (i = 0; i < 16; i++) *ptr++ = (u_char) (drand48() * 0xff); ChapMSv2_NT(cstate->resp_name, rchallenge, secret, secret_len, &response); BCOPY(&response, cstate->response, MS_CHAPV2_RESPONSE_LEN); cstate->resp_length = MS_CHAPV2_RESPONSE_LEN; } static void ChapMSv2Success(cstate, msresp, authchall, rhostname, secret, secret_len) chap_state *cstate; MS_Chapv2Response *msresp; u_char *authchall; char *rhostname, *secret; int secret_len; { static const u_char Magic1[39] __nonstring = "Magic server to client signing constant"; static const u_char Magic2[41] __nonstring = "Pad to make it do more than one iteration"; #if defined(__NetBSD__) || defined(HAVE_LIBMD) /* NetBSD uses the libc md4 routines that take bytes instead of bits */ int mdlen = 1; #else int mdlen = 8; #endif u_char unicodePassword[MAX_NT_PASSWORD * 2]; MD4_CTX md4Context; u_char hash[MD4_SIGNATURE_SIZE]; u_char hashhash[MD4_SIGNATURE_SIZE]; SHA1_CTX sha1Context; uint8_t digest[20]; u_char challenge[8]; char *cp; static const char hexdig[] = "0123456789ABCDEF"; int i; /* Initialize the Unicode version of the secret (== password). */ /* This implicitly supports 8-bit ISO8859/1 characters. */ BZERO(unicodePassword, sizeof(unicodePassword)); for (i = 0; i < secret_len && i < MAX_NT_PASSWORD; i++) if ((unicodePassword[i * 2] = (u_char)secret[i]) == '\0') break; /* Hash the password with MD4 */ MD4Init(&md4Context); MD4Update(&md4Context, unicodePassword, secret_len * 2 * mdlen); MD4Final(hash, &md4Context); /* Now hash the hash */ MD4Init(&md4Context); MD4Update(&md4Context, hash, MD4_SIGNATURE_SIZE * mdlen); MD4Final(hashhash, &md4Context); SHA1Init(&sha1Context); SHA1Update(&sha1Context, hashhash, MD4_SIGNATURE_SIZE); SHA1Update(&sha1Context, msresp->NTResp, sizeof (msresp->NTResp)); SHA1Update(&sha1Context, Magic1, 39); SHA1Final(digest, &sha1Context); ChallengeHash(msresp->PeerChallenge, authchall, rhostname, challenge); SHA1Init(&sha1Context); SHA1Update(&sha1Context, digest, 20); SHA1Update(&sha1Context, challenge, 8); SHA1Update(&sha1Context, Magic2, 41); SHA1Final(digest, &sha1Context); cp = status_message; *cp++ = 'S'; *cp++ = '='; for (i = 0; i < 20; i++) { *cp++ = hexdig[digest[i]>>4]; *cp++ = hexdig[digest[i]&15]; } /* * RFC 2759 says that a M= greeting message is possible * here. It lies. Any such greeting causes Windoze-98 to give * error number 742, "Dial-Up Networking was unable to complete * the connection. The computer you're dialing in to does not * support the data encryption requirements specified. Please * check your encryption settings in the properties of the * connection. If this problem persists, contact your network * administrator." */ *cp = '\0'; #if 0 slprintf(cp, sizeof (status_message) - (cp-status_message), "M=\"Welcome to %s.\"", hostname); #endif cstate->stat_message = status_message; cstate->stat_length = strlen(status_message); } int ChapMSv2Validate(cstate, rhostname, response, response_len, secret, secret_len) chap_state *cstate; char *rhostname; u_char *response; int response_len; char *secret; int secret_len; { MS_Chapv2Response ckresp; if (response_len < MS_CHAPV2_RESPONSE_LEN || /* response[MS_CHAPV2_RESPONSE_LEN-1] != 0 || */cstate->chal_len < 8) { cstate->stat_message = NULL; cstate->stat_length = 0; return 0; } BZERO(&ckresp, sizeof(ckresp)); BCOPY(response, ckresp.PeerChallenge, 16); ChapMSv2_NT(rhostname, cstate->challenge, secret, secret_len, &ckresp); if (memcmp(ckresp.NTResp, response+24, 24) != 0) { cstate->stat_message = "E=691 R=0 C=11111111111111111111111111111111 V=3 M=\"Authentication failed\""; cstate->stat_length = strlen(cstate->stat_message); return (0); } ChapMSv2Success(cstate, (MS_Chapv2Response *)response, cstate->challenge, rhostname, secret, secret_len); return (1); } #endif /* CHAPMSV2 */ #endif /* CHAPMS or CHAPMSV2 */ /* * chap_ms.h - Microsoft CHAP definitions. * * Copyright (c) 2000 by Sun Microsystems, Inc. * All rights reserved. * * Copyright (c) 1995 Eric Rosenquist, Strata Software Limited. * http://www.strataware.com/ * * 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 Eric Rosenquist. The name of the author 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. * * $Id: chap_ms.h,v 1.2 1997/11/27 06:08:10 paulus Exp $ */ #ifndef __CHAPMS_INCLUDE__ #define MD4_SIGNATURE_SIZE 16 /* 16 bytes in a MD4 message digest */ #define MAX_NT_PASSWORD 256 /* Maximum number of (Unicode) chars in an NT password */ void ChapMS __P((chap_state *, u_char *, int, char *, int)); void ChapMSv2 __P((chap_state *, u_char *, int, char *, int)); int ChapMSValidate __P((chap_state *cstate, u_char *response, int response_len, char *secret, int secret_len)); int ChapMSv2Validate __P((chap_state *cstate, char *rhostname, u_char *response, int response_len, char *secret, int secret_len)); #define __CHAPMS_INCLUDE__ #endif /* __CHAPMS_INCLUDE__ */ /* * demand.c - Support routines for demand-dialling. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright (c) 1993 The Australian National 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 the Australian National 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PPP_FILTER #include #include #include #endif #include "pppd.h" #include "fsm.h" #include "lcp.h" static char *frame; static int framelen; static int framemax; static int escape_flag; static int flush_flag; static int fcs; struct packet { int length; struct packet *next; unsigned char data[1]; }; static struct packet *pend_q; static struct packet *pend_qtail; static int active_packet __P((unsigned char *, int)); /* * demand_conf - configure the interface for doing dial-on-demand. */ void demand_conf() { int i; struct protent *protp; int mtu; framemax = lcp_wantoptions[0].mru; if (framemax < PPP_MRU) framemax = PPP_MRU; framemax += PPP_HDRLEN + PPP_FCSLEN; frame = malloc(framemax); if (frame == NULL) novm("demand frame"); framelen = 0; pend_q = NULL; escape_flag = 0; flush_flag = 0; fcs = PPP_INITFCS; if ((mtu = lcp_allowoptions[0].mru) == 0) mtu = PPP_MTU; ppp_send_config(0, mtu, (u_int32_t) 0, 0, 0); ppp_recv_config(0, framemax, (u_int32_t) 0, 0, 0); #ifdef PPP_FILTER set_filters(&pass_filter, &active_filter); #endif /* * Call the demand_conf procedure for each protocol that's got one. */ for (i = 0; (protp = protocols[i]) != NULL; ++i) if (protp->enabled_flag && protp->demand_conf != NULL) if (!((*protp->demand_conf)(0))) fatal("unable to set demand configuration on %s", protp->name); } /* * demand_block - set each network protocol to block further packets. */ void demand_block() { int i; struct protent *protp; for (i = 0; (protp = protocols[i]) != NULL; ++i) if (protp->enabled_flag && protp->demand_conf != NULL && !sifnpmode(0, protp->protocol & ~0x8000, NPMODE_QUEUE)) fatal("unable to enable queuing for %s", protp->name); /* Intentionally discard return value; we're on our way up now. */ (void) get_loop_output(); } /* * demand_discard - set each network protocol to discard packets * with an error. */ void demand_discard() { struct packet *pkt, *nextpkt; int i; struct protent *protp; for (i = 0; (protp = protocols[i]) != NULL; ++i) if (protp->enabled_flag && protp->demand_conf != NULL && !sifnpmode(0, protp->protocol & ~0x8000, NPMODE_DROP)) fatal("unable to disable %s", protp->name); /* Intentionally discard return value; we're on our way down now. */ (void) get_loop_output(); /* discard all saved packets */ for (pkt = pend_q; pkt != NULL; pkt = nextpkt) { nextpkt = pkt->next; free(pkt); } pend_q = NULL; framelen = 0; flush_flag = 0; escape_flag = 0; fcs = PPP_INITFCS; } /* * demand_unblock - set each enabled network protocol to pass packets. */ void demand_unblock() { int i; struct protent *protp; for (i = 0; (protp = protocols[i]) != NULL; ++i) if (protp->enabled_flag && protp->demand_conf != NULL && !sifnpmode(0, protp->protocol & ~0x8000, NPMODE_PASS)) fatal("unable to enable %s", protp->name); } /* * FCS lookup table as calculated by genfcstab. */ static u_short fcstab[256] = { 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 }; /* * loop_chars - process characters received from the loopback. * Calls loop_frame when a complete frame has been accumulated. * Return value is 1 if we need to bring up the link, 0 otherwise. */ int loop_chars(p, n) unsigned char *p; int n; { int c, rv; rv = 0; for (; n > 0; --n) { c = *p++; if (c == PPP_FLAG) { if (!escape_flag && !flush_flag && framelen > 2 && fcs == PPP_GOODFCS) { framelen -= 2; if (loop_frame((unsigned char *)frame, framelen)) rv = 1; } framelen = 0; flush_flag = 0; escape_flag = 0; fcs = PPP_INITFCS; continue; } if (flush_flag) continue; if (escape_flag) { c ^= PPP_TRANS; escape_flag = 0; } else if (c == PPP_ESCAPE) { escape_flag = 1; continue; } if (framelen >= framemax) { flush_flag = 1; continue; } frame[framelen++] = c; fcs = PPP_FCS(fcs, c); } return rv; } /* * loop_frame - given a frame obtained from the loopback, * decide whether to bring up the link or not, and, if we want * to transmit this frame later, put it on the pending queue. * Return value is 1 if we need to bring up the link, 0 otherwise. * We assume that the kernel driver has already applied the * pass_filter, so we won't get packets it rejected. * We apply the active_filter to see if we want this packet to * bring up the link. */ int loop_frame(frame, len) unsigned char *frame; int len; { struct packet *pkt; if (len < PPP_HDRLEN) return 0; if ((PPP_PROTOCOL(frame) & 0x8000) != 0) return 0; /* shouldn't get any of these anyway */ /* Note - once we have pending packets, we don't drop any more. */ if (pend_q == NULL && !active_packet(frame, len)) return 0; pkt = (struct packet *) malloc(sizeof(struct packet) + len); if (pkt != NULL) { pkt->length = len; pkt->next = NULL; (void) memcpy(pkt->data, frame, len); if (pend_q == NULL) pend_q = pkt; else pend_qtail->next = pkt; pend_qtail = pkt; } return 1; } /* * demand_rexmit - Resend all those frames that we got via the * loopback, now that the real serial link is up. */ void demand_rexmit(proto) int proto; { struct packet *pkt, *prev, *nextpkt; prev = NULL; pkt = pend_q; pend_q = NULL; for (; pkt != NULL; pkt = nextpkt) { nextpkt = pkt->next; if (PPP_PROTOCOL(pkt->data) == proto) { output(0, pkt->data, pkt->length); free(pkt); } else { if (prev == NULL) pend_q = pkt; else prev->next = pkt; prev = pkt; } } pend_qtail = prev; if (prev != NULL) prev->next = NULL; } /* * Scan a packet to decide whether it is an "active" packet, * that is, whether it is worth bringing up the link for. */ static int active_packet(p, len) unsigned char *p; int len; { int proto, i; struct protent *protp; const char *cp; char pbuf[32]; if (len < PPP_HDRLEN) return 0; #ifdef PPP_FILTER if (active_filter.bf_len != 0 && bpf_filter(active_filter.bf_insns, frame, len, len) == 0) { dbglog("BPF identified packet as not worth bringing up the link."); return 0; } #endif proto = PPP_PROTOCOL(p); for (i = 0; (protp = protocols[i]) != NULL; ++i) { if (protp->protocol < 0xC000 && (protp->protocol & ~0x8000) == proto) { if (!protp->enabled_flag) { dbglog("%s: not enabled; not bringing up link", protp->name); return 0; } if (protp->active_pkt == NULL) { notice("%s: no active test; bringing up link", protp->name); return 1; } i = (*protp->active_pkt)(p, len); if (i != 0) notice("%s: active test; bringing up link", protp->name); else dbglog("%s: active test; not bringing up link", protp->name); return i; } } if ((cp = protocol_name(proto)) == NULL) { (void) slprintf(pbuf, sizeof (pbuf), "0x#X", proto); cp = (const char *)pbuf; } dbglog("%s: unknown protocol; not bringing up link", cp); return 0; /* not a supported protocol !!?? */ } /* eui64.h - EUI64 routines for IPv6CP. Copyright (C) 1999 Tommi Komulainen 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 Tommi Komulainen. The name of the author 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. $Id: eui64.h,v 1.3 1999/09/30 19:56:37 masputra Exp $ */ #ifndef __EUI64_H__ #define __EUI64_H__ #if !defined(INET6) #error "this file should only be included when INET6 is defined" #endif /* not defined(INET6) */ #if defined(SOL2) #include typedef union { uint8_t e8[8]; /* lower 64-bit IPv6 address */ uint32_t e32[2]; /* lower 64-bit IPv6 address */ } eui64_t; /* * Declare the two below, since in.h only defines them when _KERNEL * is declared - which shouldn't be true when dealing with user-land programs */ #define s6_addr8 _S6_un._S6_u8 #define s6_addr32 _S6_un._S6_u32 #else /* else if not defined(SOL2) */ /* * TODO: * * Maybe this should be done by processing struct in6_addr directly... */ typedef union { u_int8_t e8[8]; u_int16_t e16[4]; u_int32_t e32[2]; } eui64_t; #endif /* defined(SOL2) */ #define eui64_iszero(e) (((e).e32[0] | (e).e32[1]) == 0) #define eui64_equals(e, o) (((e).e32[0] == (o).e32[0]) && \ ((e).e32[1] == (o).e32[1])) #define eui64_zero(e) ((e).e32[0] = (e).e32[1] = 0) #define eui64_copy(s, d) (void) memcpy(&(d), &(s), sizeof(eui64_t)) #define eui64_magic(e) ( \ (e).e32[0] = magic(), \ (e).e32[1] = magic(), \ (e).e8[0] &= ~2 \ ) #define eui64_magic_nz(x) do { \ eui64_magic(x); \ } while (eui64_iszero(x)) #define eui64_magic_ne(x, y) do { \ eui64_magic(x); \ } while (eui64_equals(x, y)) #define eui64_get(ll, cp) ( \ eui64_copy((*cp), (ll)), \ (cp) += sizeof(eui64_t) \ ) #define eui64_put(ll, cp) ( \ eui64_copy((ll), (*cp)), \ (cp) += sizeof(eui64_t) \ ) #define eui64_set32(e, l) ( \ (e).e32[0] = 0, \ (e).e32[1] = htonl(l) \ ) #define eui64_setlo32(e, l) eui64_set32(e, l) /* * Returns ascii representation of ID. This is at most 20 bytes long; * 19 characters plus one NUL. */ char *eui64_ntoa __P((eui64_t)); #endif /* __EUI64_H__ */ /* * fsm.c - {Link, IP} Control Protocol Finite State Machine. * * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * 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. */ /* * TODO: * Randomize fsm id on link/init. * Deal with variable outgoing MTU. */ #include #include #include #ifndef NO_DRAND48 #include #endif /* NO_DRAND48 */ #include "pppd.h" #include "fsm.h" static void fsm_timeout __P((void *)); static void fsm_rconfreq __P((fsm *, int, u_char *, int)); static void fsm_rconfack __P((fsm *, int, u_char *, int)); static void fsm_rconfnakrej __P((fsm *, int, int, u_char *, int)); static void fsm_rtermreq __P((fsm *, int, u_char *, int)); static void fsm_rtermack __P((fsm *)); static void fsm_rcoderej __P((fsm *, u_char *, int)); static void fsm_sconfreq __P((fsm *, int)); #define PROTO_NAME(f) ((f)->callbacks->proto_name) static int peer_mru[NUM_PPP]; const char * fsm_state(int statenum) { static const char *fsm_states[] = { FSM__STATES }; static char buf[32]; if (statenum < 0 || statenum >= Dim(fsm_states)) { (void) slprintf(buf, sizeof (buf), "unknown#%d", statenum); return buf; } return fsm_states[statenum]; } /* * fsm_init - Initialize fsm. * * Initialize fsm state. */ void fsm_init(f) fsm *f; { f->state = INITIAL; f->flags = 0; f->id = (uchar_t)(drand48() * 0xFF); /* Start with random id */ f->timeouttime = DEFTIMEOUT; f->maxconfreqtransmits = DEFMAXCONFREQS; f->maxtermtransmits = DEFMAXTERMREQS; f->maxnakloops = DEFMAXNAKLOOPS; f->term_reason_len = 0; } /* * fsm_lowerup - The lower layer is up. */ void fsm_lowerup(f) fsm *f; { switch( f->state ){ case INITIAL: f->state = CLOSED; break; case STARTING: if( f->flags & OPT_SILENT ) f->state = STOPPED; else { /* Send an initial configure-request */ fsm_sconfreq(f, 0); f->state = REQSENT; } break; default: error("%s: Up event in state %s", PROTO_NAME(f), fsm_state(f->state)); } } /* * fsm_lowerdown - The lower layer is down. * * Cancel all timeouts and inform upper layers. */ void fsm_lowerdown(f) fsm *f; { switch( f->state ){ case CLOSED: f->state = INITIAL; break; case STOPPED: f->state = STARTING; if (f->callbacks->starting != NULL) (*f->callbacks->starting)(f); break; case CLOSING: f->state = INITIAL; UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ break; case STOPPING: case REQSENT: case ACKRCVD: case ACKSENT: f->state = STARTING; UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ break; case OPENED: f->state = STARTING; if (f->callbacks->down != NULL) (*f->callbacks->down)(f); break; default: dbglog("%s: Down event in state %s", PROTO_NAME(f), fsm_state(f->state)); } } /* * fsm_open - Link is allowed to come up. */ void fsm_open(f) fsm *f; { switch( f->state ){ case INITIAL: f->state = STARTING; if (f->callbacks->starting != NULL) (*f->callbacks->starting)(f); break; case CLOSED: if( f->flags & OPT_SILENT ) f->state = STOPPED; else { /* Send an initial configure-request */ fsm_sconfreq(f, 0); f->state = REQSENT; } break; case CLOSING: f->state = STOPPING; /*FALLTHROUGH*/ case STOPPING: case STOPPED: case OPENED: if( f->flags & OPT_RESTART ){ fsm_lowerdown(f); fsm_lowerup(f); } break; case STARTING: case REQSENT: case ACKRCVD: case ACKSENT: /* explicitly do nothing here. */ break; } } /* * fsm_close - Start closing connection. * * Cancel timeouts and either initiate close or possibly go directly to * the CLOSED state. */ void fsm_close(f, reason) fsm *f; char *reason; { int prevstate = f->state; f->term_reason = reason; f->term_reason_len = (reason == NULL? 0: strlen(reason)); switch( f->state ){ case STARTING: f->state = INITIAL; if (f->callbacks->finished != NULL) (*f->callbacks->finished)(f); break; case STOPPED: f->state = CLOSED; break; case STOPPING: f->state = CLOSING; break; case REQSENT: case ACKRCVD: case ACKSENT: case OPENED: f->state = CLOSING; if (prevstate != OPENED ) UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ else if (f->callbacks->down != NULL) (*f->callbacks->down)(f); /* Inform upper layers we're down */ /* * Note that this-layer-down means "stop transmitting." * This-layer-finished means "stop everything." */ /* Init restart counter, send Terminate-Request */ f->retransmits = f->maxtermtransmits; fsm_sdata(f, CODE_TERMREQ, f->reqid = ++f->id, (u_char *) f->term_reason, f->term_reason_len); TIMEOUT(fsm_timeout, f, f->timeouttime); --f->retransmits; break; case INITIAL: case CLOSED: case CLOSING: /* explicitly do nothing here. */ break; } } /* * fsm_timeout - Timeout expired. */ static void fsm_timeout(arg) void *arg; { fsm *f = (fsm *) arg; switch (f->state) { case CLOSING: case STOPPING: if( f->retransmits <= 0 ){ /* * We've waited for an ack long enough. Peer probably heard us. */ f->state = (f->state == CLOSING)? CLOSED: STOPPED; if (f->callbacks->finished != NULL) (*f->callbacks->finished)(f); } else { /* Send Terminate-Request */ fsm_sdata(f, CODE_TERMREQ, f->reqid = ++f->id, (u_char *) f->term_reason, f->term_reason_len); TIMEOUT(fsm_timeout, f, f->timeouttime); --f->retransmits; } break; case REQSENT: case ACKRCVD: case ACKSENT: if (f->retransmits <= 0) { warn("%s: timeout sending Config-Requests\n", PROTO_NAME(f)); f->state = STOPPED; if (!(f->flags & OPT_PASSIVE) && f->callbacks->finished != NULL) (*f->callbacks->finished)(f); } else { /* Retransmit the configure-request */ if (f->callbacks->retransmit != NULL) (*f->callbacks->retransmit)(f); fsm_sconfreq(f, 1); /* Re-send Configure-Request */ if( f->state == ACKRCVD ) f->state = REQSENT; } break; default: fatal("%s: Timeout event in state %s!", PROTO_NAME(f), fsm_state(f->state)); } } /* * fsm_input - Input packet. */ void fsm_input(f, inpacket, l) fsm *f; u_char *inpacket; int l; { u_char *inp; u_char code, id; int len; /* * Parse header (code, id and length). * If packet too short, drop it. */ inp = inpacket; if (l < HEADERLEN) { error("%s packet: discard; too small (%d < %d)", PROTO_NAME(f), l, HEADERLEN); return; } GETCHAR(code, inp); GETCHAR(id, inp); GETSHORT(len, inp); if (len < HEADERLEN) { error("%s packet: discard; invalid length (%d < %d)", PROTO_NAME(f), len, HEADERLEN); return; } if (len > l) { error("%s packet: discard; truncated (%d > %d)", PROTO_NAME(f), len, l); return; } len -= HEADERLEN; /* subtract header length */ if (f->state == INITIAL || f->state == STARTING) { dbglog("%s: discarded packet in state %s", PROTO_NAME(f), fsm_state(f->state)); return; } /* * Action depends on code. */ switch (code) { case CODE_CONFREQ: fsm_rconfreq(f, id, inp, len); break; case CODE_CONFACK: fsm_rconfack(f, id, inp, len); break; case CODE_CONFNAK: case CODE_CONFREJ: fsm_rconfnakrej(f, code, id, inp, len); break; case CODE_TERMREQ: fsm_rtermreq(f, id, inp, len); break; case CODE_TERMACK: fsm_rtermack(f); break; case CODE_CODEREJ: fsm_rcoderej(f, inp, len); break; default: if (f->callbacks->extcode == NULL || !(*f->callbacks->extcode)(f, code, id, inp, len)) fsm_sdata(f, CODE_CODEREJ, ++f->id, inpacket, len + HEADERLEN); break; } } /* * fsm_rconfreq - Receive Configure-Request. */ static void fsm_rconfreq(f, id, inp, len) fsm *f; u_char id; u_char *inp; int len; { int code, reject_if_disagree; switch( f->state ){ case CLOSED: /* Go away, we're closed */ fsm_sdata(f, CODE_TERMACK, id, NULL, 0); return; case CLOSING: case STOPPING: dbglog("%s: discarded Configure-Request in state %s", PROTO_NAME(f), fsm_state(f->state)); return; case OPENED: /* Go down and restart negotiation */ if (f->callbacks->down != NULL) (*f->callbacks->down)(f); /* Inform upper layers */ break; } #ifdef DEBUG if (inp >= outpacket_buf && inp < outpacket_buf+PPP_MRU+PPP_HDRLEN) fatal("bad pointer"); #endif /* * Pass the requested configuration options * to protocol-specific code for checking. */ if (f->callbacks->reqci != NULL) { /* Check CI */ reject_if_disagree = (f->nakloops >= f->maxnakloops); code = (*f->callbacks->reqci)(f, inp, &len, reject_if_disagree); } else if (len > 0) code = CODE_CONFREJ; /* Reject all CI */ else code = CODE_CONFACK; /* Allow NCP to do fancy footwork, such as reinitializing. */ if (code <= 0) return; if (f->state == OPENED || f->state == STOPPED) fsm_sconfreq(f, 0); /* Send initial Configure-Request */ /* send the Ack, Nak or Rej to the peer */ fsm_sdata(f, code, id, inp, len); if (code == CODE_CONFACK) { /* RFC 1661 event RCR+ */ if (f->state == ACKRCVD) { UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ f->state = OPENED; if (f->callbacks->up != NULL) (*f->callbacks->up)(f); /* Inform upper layers */ } else f->state = ACKSENT; f->nakloops = 0; } else { /* RFC 1661 event RCR- */ /* (we sent CODE_CONFNAK or CODE_CONFREJ) */ if (f->state != ACKRCVD) f->state = REQSENT; if( code == CODE_CONFNAK ) ++f->nakloops; } } /* * fsm_rconfack - Receive Configure-Ack. */ static void fsm_rconfack(f, id, inp, len) fsm *f; int id; u_char *inp; int len; { if (id != f->reqid || f->seen_ack) /* Expected id? */ return; /* Nope, toss... */ if( !(f->callbacks->ackci != NULL ? (*f->callbacks->ackci)(f, inp, len): (len == 0)) ){ /* Ack is bad - ignore it */ error("Received bad configure-ack: %P", inp, len); return; } f->seen_ack = 1; switch (f->state) { case CLOSED: case STOPPED: fsm_sdata(f, CODE_TERMACK, id, NULL, 0); break; case REQSENT: f->state = ACKRCVD; f->retransmits = f->maxconfreqtransmits; break; case ACKRCVD: /* Huh? an extra valid Ack? oh well... */ UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ fsm_sconfreq(f, 0); f->state = REQSENT; break; case ACKSENT: UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ f->state = OPENED; f->retransmits = f->maxconfreqtransmits; if (f->callbacks->up != NULL) (*f->callbacks->up)(f); /* Inform upper layers */ break; case OPENED: /* Go down and restart negotiation */ fsm_sconfreq(f, 0); /* Send initial Configure-Request */ f->state = REQSENT; if (f->callbacks->down != NULL) (*f->callbacks->down)(f); /* Inform upper layers */ break; } } /* * fsm_rconfnakrej - Receive Configure-Nak or Configure-Reject. */ static void fsm_rconfnakrej(f, code, id, inp, len) fsm *f; int code, id; u_char *inp; int len; { int (*proc) __P((fsm *, u_char *, int)); int ret; if (id != f->reqid || f->seen_ack) /* Expected id? */ return; /* Nope, toss... */ proc = (code == CODE_CONFNAK)? f->callbacks->nakci: f->callbacks->rejci; if (proc == NULL || !(ret = proc(f, inp, len))) { /* Nak/reject is bad - ignore it */ error("Received bad configure-nak/rej: %P", inp, len); return; } f->seen_ack = 1; switch (f->state) { case CLOSED: case STOPPED: fsm_sdata(f, CODE_TERMACK, id, NULL, 0); break; case REQSENT: case ACKSENT: /* They didn't agree to what we wanted - try another request */ UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ if (ret < 0) f->state = STOPPED; /* kludge for stopping CCP */ else fsm_sconfreq(f, 0); /* Send Configure-Request */ break; case ACKRCVD: /* Got a Nak/reject when we had already had an Ack?? oh well... */ UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ fsm_sconfreq(f, 0); f->state = REQSENT; break; case OPENED: /* Go down and restart negotiation */ fsm_sconfreq(f, 0); /* Send initial Configure-Request */ f->state = REQSENT; if (f->callbacks->down != NULL) (*f->callbacks->down)(f); /* Inform upper layers */ break; } } /* * fsm_rtermreq - Receive Terminate-Req. */ static void fsm_rtermreq(f, id, p, len) fsm *f; int id; u_char *p; int len; { switch (f->state) { case ACKRCVD: case ACKSENT: f->state = REQSENT; /* Start over but keep trying */ break; case OPENED: if (len > 0) { info("%s terminated by peer (%0.*v)", PROTO_NAME(f), len, p); } else { info("%s terminated by peer", PROTO_NAME(f)); } f->state = STOPPING; if (f->callbacks->down != NULL) (*f->callbacks->down)(f); /* Inform upper layers */ f->retransmits = 0; TIMEOUT(fsm_timeout, f, f->timeouttime); break; } fsm_sdata(f, CODE_TERMACK, id, NULL, 0); } /* * fsm_rtermack - Receive Terminate-Ack. */ static void fsm_rtermack(f) fsm *f; { switch (f->state) { case CLOSING: UNTIMEOUT(fsm_timeout, f); f->state = CLOSED; if (f->callbacks->finished != NULL) (*f->callbacks->finished)(f); break; case STOPPING: UNTIMEOUT(fsm_timeout, f); f->state = STOPPED; if (f->callbacks->finished != NULL) (*f->callbacks->finished)(f); break; case ACKRCVD: f->state = REQSENT; break; case OPENED: fsm_sconfreq(f, 0); f->state = REQSENT; if (f->callbacks->down != NULL) (*f->callbacks->down)(f); /* Inform upper layers */ break; } } /* * fsm_rcoderej - Receive a Code-Reject. */ static void fsm_rcoderej(f, inp, len) fsm *f; u_char *inp; int len; { u_char code, id; int seriouserr; if (len < HEADERLEN) { error("%s: Code-Reject too short (%d < %d)", PROTO_NAME(f), len, HEADERLEN); return; } GETCHAR(code, inp); GETCHAR(id, inp); len -= 2; warn("%s: Rcvd Code-Reject for %s id %d", PROTO_NAME(f), code_name(code,0), id); setbit(f->codemask, code); /* Let the protocol know what happened. */ if (f->callbacks->codereject != NULL) { seriouserr = (*f->callbacks->codereject)(f,code,id,inp,len); } else { /* * By default, it's RXJ- for well-known codes and RXJ+ for * unknown ones. */ seriouserr = (code >= CODE_CONFREQ && code <= CODE_CODEREJ); } if (seriouserr) { /* RXJ- -- shut down the protocol. */ switch (f->state) { case CLOSING: UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ /*FALLTHROUGH*/ case CLOSED: f->state = CLOSED; if (f->callbacks->finished != NULL) (*f->callbacks->finished)(f); break; case STOPPING: case REQSENT: case ACKRCVD: case ACKSENT: UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ f->state = STOPPED; /*FALLTHROUGH*/ case STOPPED: if (f->callbacks->finished != NULL) (*f->callbacks->finished)(f); break; case OPENED: f->state = STOPPING; if (f->callbacks->down != NULL) (*f->callbacks->down)(f); if (f->term_reason == NULL) { f->term_reason = "unacceptable Code-Reject received"; f->term_reason_len = strlen(f->term_reason); } /* Init restart counter, send Terminate-Request */ f->retransmits = f->maxtermtransmits; fsm_sdata(f, CODE_TERMREQ, f->reqid = ++f->id, (u_char *) f->term_reason, f->term_reason_len); TIMEOUT(fsm_timeout, f, f->timeouttime); --f->retransmits; break; default: fatal("state error"); } } else { /* RXJ+ -- just back up from Ack-Rcvd to Req-Sent. */ if (f->state == ACKRCVD) f->state = REQSENT; } } /* * fsm_protreject - Peer doesn't speak this protocol. * * Treat this as a catastrophic error (RXJ-). */ void fsm_protreject(f) fsm *f; { switch( f->state ){ case CLOSING: UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ /*FALLTHROUGH*/ case CLOSED: f->state = CLOSED; if (f->callbacks->finished != NULL) (*f->callbacks->finished)(f); break; case STOPPING: case REQSENT: case ACKRCVD: case ACKSENT: UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ /*FALLTHROUGH*/ case STOPPED: f->state = STOPPED; if (f->callbacks->finished != NULL) (*f->callbacks->finished)(f); break; case OPENED: f->state = STOPPING; if (f->callbacks->down != NULL) (*f->callbacks->down)(f); /* Init restart counter, send Terminate-Request */ f->retransmits = f->maxtermtransmits; fsm_sdata(f, CODE_TERMREQ, f->reqid = ++f->id, (u_char *) f->term_reason, f->term_reason_len); TIMEOUT(fsm_timeout, f, f->timeouttime); --f->retransmits; break; default: dbglog("%s: Protocol-Reject in state %s", PROTO_NAME(f), fsm_state(f->state)); } } /* * fsm_sconfreq - Send a Configure-Request. */ static void fsm_sconfreq(f, retransmit) fsm *f; int retransmit; { u_char *outp; int cilen; if( f->state != REQSENT && f->state != ACKRCVD && f->state != ACKSENT ){ /* Not currently negotiating - reset options */ if (f->callbacks->resetci != NULL) (*f->callbacks->resetci)(f); f->nakloops = 0; } if( !retransmit ){ /* New request - reset retransmission counter, use new ID */ f->retransmits = f->maxconfreqtransmits; f->reqid = ++f->id; } f->seen_ack = 0; /* * Make up the request packet */ outp = outpacket_buf + PPP_HDRLEN + HEADERLEN; if (f->callbacks->cilen != NULL) { cilen = (*f->callbacks->cilen)(f); if (cilen > peer_mru[f->unit] - HEADERLEN) cilen = peer_mru[f->unit] - HEADERLEN; } else { cilen = peer_mru[f->unit] - HEADERLEN; } if (f->callbacks->addci != NULL) (*f->callbacks->addci)(f, outp, &cilen); else cilen = 0; /* send the request to our peer */ fsm_sdata(f, CODE_CONFREQ, f->reqid, outp, cilen); /* start the retransmit timer */ --f->retransmits; TIMEOUT(fsm_timeout, f, f->timeouttime); } /* * fsm_sdata - Send some data. * * Used for all packets sent to our peer by this module. */ void fsm_sdata(f, code, id, data, datalen) fsm *f; u_char code, id; u_char *data; int datalen; { u_char *outp; int outlen; if (isset(f->codemask,code)) { dbglog("%s: Peer has rejected %s; not sending another", PROTO_NAME(f), code_name(code,0)); return; } /* Adjust length to be smaller than MTU */ outp = outpacket_buf; if (datalen > peer_mru[f->unit] - HEADERLEN) datalen = peer_mru[f->unit] - HEADERLEN; if (datalen && data != outp + PPP_HDRLEN + HEADERLEN) BCOPY(data, outp + PPP_HDRLEN + HEADERLEN, datalen); outlen = datalen + HEADERLEN; MAKEHEADER(outp, f->protocol); PUTCHAR(code, outp); PUTCHAR(id, outp); PUTSHORT(outlen, outp); output(f->unit, outpacket_buf, outlen + PPP_HDRLEN); } /* * fsm_setpeermru - Set our idea of the peer's mru * * Used by routines in lcp.c which negotiate this value. */ void fsm_setpeermru(unit, mru) int unit; int mru; { if (unit >= NUM_PPP) { dbglog("fsm_setpeermru: unit out of bounds"); } else { peer_mru[unit] = mru; } } /* * fsm.h - {Link, IP} Control Protocol Finite State Machine definitions. * * Copyright (c) 2000 by Sun Microsystems, Inc. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. * * SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES * * 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. * * $Id: fsm.h,v 1.8 1999/11/15 01:51:50 paulus Exp $ */ #ifndef __FSM_H__ #define __FSM_H__ #ifdef __cplusplus extern "C" { #endif /* * Packet header = Code, id, length. */ #define HEADERLEN 4 /* * Control Protocol (LCP, IPCP, etc.) message code numbers. * (More in lcp.h and net/ppp-comp.h.) */ #define CODE_CONFREQ 1 /* Configuration Request */ #define CODE_CONFACK 2 /* Configuration Ack */ #define CODE_CONFNAK 3 /* Configuration Nak */ #define CODE_CONFREJ 4 /* Configuration Reject */ #define CODE_TERMREQ 5 /* Termination Request */ #define CODE_TERMACK 6 /* Termination Ack */ #define CODE_CODEREJ 7 /* Code Reject */ /* * Each FSM is described by an fsm structure and fsm callbacks. */ typedef struct fsm { int unit; /* Interface unit number */ int protocol; /* Data Link Layer Protocol field value */ int state; /* State */ int flags; /* Contains option bits */ u_char id; /* Current id */ u_char reqid; /* Current request id */ u_char seen_ack; /* Have received valid Ack/Nak/Rej to Req */ int timeouttime; /* Timeout time in milliseconds */ int maxconfreqtransmits; /* Maximum Configure-Request transmissions */ int retransmits; /* Number of retransmissions left */ int maxtermtransmits; /* Maximum Terminate-Request transmissions */ int nakloops; /* Number of nak loops since last ack */ int maxnakloops; /* Maximum number of nak loops tolerated */ struct fsm_callbacks *callbacks; /* Callback routines */ char *term_reason; /* Reason for closing protocol */ int term_reason_len; /* Length of term_reason */ u_int32_t codemask[8]; /* Codes rejected by peer */ } fsm; /* * Function RFC 1661 * ----- ------------------------------ * ackci RCA * nakci RCN * rejci RCN * reqci RCR+ or RCR- (by return value) * up tlu (this-layer-up) * down tld (this-layer-down) * starting tls (this-layer-starting) * finished tlf (this-layer-finished) * codereject RXJ+ or RXJ- (by return value) * * Note that this-layer-down means "stop transmitting." * This-layer-finished means "stop everything." */ typedef struct fsm_callbacks { void (*resetci) /* Reset our Configuration Information */ __P((fsm *)); int (*cilen) /* Length of our Configuration Information */ __P((fsm *)); void (*addci) /* Add our Configuration Information */ __P((fsm *, u_char *, int *)); int (*ackci) /* ACK our Configuration Information */ __P((fsm *, u_char *, int)); int (*nakci) /* NAK our Configuration Information */ __P((fsm *, u_char *, int)); int (*rejci) /* Reject our Configuration Information */ __P((fsm *, u_char *, int)); int (*reqci) /* Request peer's Configuration Information */ __P((fsm *, u_char *, int *, int)); void (*up) /* Called when fsm reaches OPENED state */ __P((fsm *)); void (*down) /* Called when fsm leaves OPENED state */ __P((fsm *)); void (*starting) /* Called when we want the lower layer */ __P((fsm *)); void (*finished) /* Called when we don't want the lower layer */ __P((fsm *)); void (*retransmit) /* Retransmission is necessary */ __P((fsm *)); int (*extcode) /* Called when unknown code received */ __P((fsm *, int, int, u_char *, int)); char *proto_name; /* String name for protocol (for messages) */ int (*codereject) /* Called when Code-Reject received */ __P((fsm *p, int code, int id, u_char *inp, int len)); } fsm_callbacks; /* * RFC 1661 NCP states. */ #define INITIAL 0 /* Down, hasn't been opened */ #define STARTING 1 /* Down, been opened */ #define CLOSED 2 /* Up, hasn't been opened */ #define STOPPED 3 /* Open, waiting for down event */ #define CLOSING 4 /* Terminating the connection, not open */ #define STOPPING 5 /* Terminating, but open */ #define REQSENT 6 /* We've sent a Config Request */ #define ACKRCVD 7 /* We've received a Config Ack */ #define ACKSENT 8 /* We've sent a Config Ack */ #define OPENED 9 /* Connection available */ #define FSM__STATES \ "Initial", "Starting", "Closed", "Stopped", "Closing", "Stopping", \ "ReqSent", "AckRcvd", "AckSent", "Opened" /* * Flags - indicate options controlling FSM operation */ #define OPT_PASSIVE 1 /* Don't die if we don't get a response */ #define OPT_RESTART 2 /* Treat 2nd OPEN as DOWN, UP */ #define OPT_SILENT 4 /* Wait for peer to speak first */ /* * Timeouts. */ #define DEFTIMEOUT 3 /* Timeout time in seconds */ #define DEFMAXTERMREQS 2 /* Maximum Terminate-Request transmissions */ #define DEFMAXCONFREQS 10 /* Maximum Configure-Request transmissions */ #define DEFMAXNAKLOOPS 5 /* Maximum number of nak loops */ /* * Prototypes */ void fsm_init __P((fsm *)); void fsm_lowerup __P((fsm *)); void fsm_lowerdown __P((fsm *)); void fsm_open __P((fsm *)); void fsm_close __P((fsm *, char *)); void fsm_input __P((fsm *, u_char *, int)); void fsm_protreject __P((fsm *)); void fsm_sdata __P((fsm *, int, int, u_char *, int)); void fsm_setpeermru __P((int, int)); const char *fsm_state __P((int statenum)); #ifdef __cplusplus } #endif #endif /* __FSM_H__ */ /* * ipcp.c - PPP IP Control Protocol. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * 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. */ /* * Copyright (c) 2016 by Delphix. All rights reserved. */ #include #include #include #include #include #include #if defined(_linux_) || defined(__linux__) #define __FAVOR_BSD #endif #include #include #include #include "pppd.h" #include "fsm.h" #include "ipcp.h" #include "pathnames.h" /* global vars */ ipcp_options ipcp_wantoptions[NUM_PPP]; /* Options that we want to request */ ipcp_options ipcp_gotoptions[NUM_PPP]; /* Options that peer ack'd */ ipcp_options ipcp_allowoptions[NUM_PPP]; /* Options we allow peer to request */ ipcp_options ipcp_hisoptions[NUM_PPP]; /* Options that we ack'd */ bool ipcp_from_hostname = 0; /* Local IP address is from hostname lookup */ /* Hook for a plugin to know when IP protocol has come up */ void (*ip_up_hook) __P((void)) = NULL; /* Hook for a plugin to know when IP protocol has come down */ void (*ip_down_hook) __P((void)) = NULL; /* local vars */ static bool default_route_set[NUM_PPP]; /* Have set up a default route */ static bool proxy_arp_set[NUM_PPP]; /* Have created proxy arp entry */ static bool ipcp_is_up[NUM_PPP]; /* have called np_up() */ static bool proxy_arp_quiet[NUM_PPP]; /* We should be quiet on error */ static bool disable_defaultip = 0; /* Don't use hostname for IP addr */ /* * Callbacks for fsm code. (CI = Configuration Information) */ static void ipcp_resetci __P((fsm *)); /* Reset our CI */ static int ipcp_cilen __P((fsm *)); /* Return length of our CI */ static void ipcp_addci __P((fsm *, u_char *, int *)); /* Add our CI */ static int ipcp_ackci __P((fsm *, u_char *, int)); /* Peer ack'd our CI */ static int ipcp_nakci __P((fsm *, u_char *, int)); /* Peer nak'd our CI */ static int ipcp_rejci __P((fsm *, u_char *, int)); /* Peer rej'd our CI */ static int ipcp_reqci __P((fsm *, u_char *, int *, int)); /* Rcv CI */ static void ipcp_up __P((fsm *)); /* We're UP */ static void ipcp_down __P((fsm *)); /* We're DOWN */ static void ipcp_finished __P((fsm *)); /* Don't need lower layer */ static int setmsservaddr __P((char *, u_int32_t *)); fsm ipcp_fsm[NUM_PPP]; /* IPCP fsm structure */ static fsm_callbacks ipcp_callbacks = { /* IPCP callback routines */ ipcp_resetci, /* Reset our Configuration Information */ ipcp_cilen, /* Length of our Configuration Information */ ipcp_addci, /* Add our Configuration Information */ ipcp_ackci, /* ACK our Configuration Information */ ipcp_nakci, /* NAK our Configuration Information */ ipcp_rejci, /* Reject our Configuration Information */ ipcp_reqci, /* Request peer's Configuration Information */ ipcp_up, /* Called when fsm reaches OPENED state */ ipcp_down, /* Called when fsm leaves OPENED state */ NULL, /* Called when we want the lower layer up */ ipcp_finished, /* Called when we want the lower layer down */ NULL, /* Retransmission is necessary */ NULL, /* Called to handle protocol-specific codes */ "IPCP", /* String name of protocol */ NULL /* Peer rejected a code number */ }; /* * Command-line options. */ static int setvjslots __P((char **)); static int setdnsaddr __P((char **)); static int setwinsaddr __P((char **)); static int autoproxyarp __P((char **)); static option_t ipcp_option_list[] = { { "noip", o_bool, &ipcp_protent.enabled_flag, "Disable IP and IPCP" }, { "-ip", o_bool, &ipcp_protent.enabled_flag, "Disable IP and IPCP" }, { "novj", o_bool, &ipcp_wantoptions[0].neg_vj, "Disable VJ compression", OPT_A2COPY, &ipcp_allowoptions[0].neg_vj }, { "-vj", o_bool, &ipcp_wantoptions[0].neg_vj, "Disable VJ compression", OPT_A2COPY, &ipcp_allowoptions[0].neg_vj }, { "novjccomp", o_bool, &ipcp_wantoptions[0].cflag, "Disable VJ connection-ID compression", OPT_A2COPY, &ipcp_allowoptions[0].cflag }, { "-vjccomp", o_bool, &ipcp_wantoptions[0].cflag, "Disable VJ connection-ID compression", OPT_A2COPY, &ipcp_allowoptions[0].cflag }, { "vj-max-slots", o_special, (void *)setvjslots, "Set maximum VJ header slots" }, { "ipcp-accept-local", o_bool, &ipcp_wantoptions[0].accept_local, "Accept peer's address for us", 1 }, { "ipcp-accept-remote", o_bool, &ipcp_wantoptions[0].accept_remote, "Accept peer's address for it", 1 }, { "ipparam", o_string, &ipparam, "Set ip script parameter" }, { "noipdefault", o_bool, &disable_defaultip, "Don't use name for default IP adrs", 1 }, { "ms-dns", o_special, (void *)setdnsaddr, "DNS address for the peer's use" }, { "ms-wins", o_special, (void *)setwinsaddr, "Nameserver for SMB over TCP/IP for peer" }, { "ipcp-restart", o_int, &ipcp_fsm[0].timeouttime, "Set timeout for IPCP" }, { "ipcp-max-terminate", o_int, &ipcp_fsm[0].maxtermtransmits, "Set max #xmits for term-reqs" }, { "ipcp-max-configure", o_int, &ipcp_fsm[0].maxconfreqtransmits, "Set max #xmits for conf-reqs" }, { "ipcp-max-failure", o_int, &ipcp_fsm[0].maxnakloops, "Set max #conf-naks for IPCP" }, { "defaultroute", o_bool, &ipcp_wantoptions[0].default_route, "Add default route", OPT_ENABLE|1, &ipcp_allowoptions[0].default_route }, { "nodefaultroute", o_bool, &ipcp_allowoptions[0].default_route, "disable defaultroute option", OPT_A2COPY, &ipcp_wantoptions[0].default_route }, { "-defaultroute", o_bool, &ipcp_allowoptions[0].default_route, "disable defaultroute option", OPT_A2COPY, &ipcp_wantoptions[0].default_route }, { "proxyarp", o_bool, &ipcp_wantoptions[0].proxy_arp, "Add proxy ARP entry", OPT_ENABLE|1, &ipcp_allowoptions[0].proxy_arp }, { "autoproxyarp", o_special_noarg, (void *)autoproxyarp, "Add proxy ARP entry if needed", OPT_ENABLE, &ipcp_allowoptions[0].proxy_arp }, { "noproxyarp", o_bool, &ipcp_allowoptions[0].proxy_arp, "disable proxyarp option", OPT_A2COPY, &ipcp_wantoptions[0].proxy_arp }, { "-proxyarp", o_bool, &ipcp_allowoptions[0].proxy_arp, "disable proxyarp option", OPT_A2COPY, &ipcp_wantoptions[0].proxy_arp }, { "usepeerdns", o_bool, &ipcp_wantoptions[0].req_dns1, "Ask peer for DNS address(es)", OPT_A2COPY|1, &ipcp_wantoptions[0].req_dns2 }, { NULL } }; /* * Protocol entry points from main code. */ static void ipcp_init __P((int)); static void ipcp_open __P((int)); static void ipcp_close __P((int, char *)); static void ipcp_lowerup __P((int)); static void ipcp_lowerdown __P((int)); static void ipcp_input __P((int, u_char *, int)); static void ipcp_protrej __P((int)); static int ipcp_printpkt __P((u_char *, int, void (*) __P((void *, const char *, ...)), void *)); static void ip_check_options __P((void)); static int ip_demand_conf __P((int)); static int ip_active_pkt __P((u_char *, int)); static void ipcp_print_stat __P((int, FILE *)); static void create_resolv __P((u_int32_t, u_int32_t)); struct protent ipcp_protent = { PPP_IPCP, ipcp_init, ipcp_input, ipcp_protrej, ipcp_lowerup, ipcp_lowerdown, ipcp_open, ipcp_close, ipcp_printpkt, NULL, 1, "IPCP", "IP", ipcp_option_list, ip_check_options, ip_demand_conf, ip_active_pkt, ipcp_print_stat }; static void ipcp_clear_addrs __P((int, u_int32_t, u_int32_t)); static void ipcp_script __P((char *)); /* Run an up/down script */ static void ipcp_script_done __P((void *, int)); /* * Lengths of configuration options. */ #define CILEN_VOID 2 #define CILEN_COMPRESS 4 /* min length for compression protocol opt. */ #define CILEN_VJ 6 /* length for RFC1332 Van-Jacobson opt. */ #define CILEN_ADDR 6 /* new-style single address option */ #define CILEN_ADDRS 10 /* old-style dual address option */ /* * This state variable is used to ensure that we don't * run an ipcp-up/down script while one is already running. */ static enum script_state { s_down, s_up } ipcp_script_state; static pid_t ipcp_script_pid; /* * Make a string representation of a network IP address. */ char * ip_ntoa(ipaddr) u_int32_t ipaddr; { static char b[64]; (void) slprintf(b, sizeof(b), "%I", ipaddr); return b; } /* * Option parsing. */ /* * setvjslots - set maximum number of connection slots for VJ compression */ static int setvjslots(argv) char **argv; { int value; if (!int_option(*argv, &value)) return 0; if (value < 2 || value > 16) { option_error("vj-max-slots value must be between 2 and 16"); return 0; } ipcp_wantoptions [0].maxslotindex = ipcp_allowoptions[0].maxslotindex = value - 1; return 1; } /* * setmsservaddr - Set the primary and secondary server addresses in the * array. setdnsaddr() and setwinsaddr() call this function with either * dnsaddr[] or winsaddr[] as the serverarray argument. */ static int setmsservaddr(servname, serverarray) char *servname; u_int32_t *serverarray; { u_int32_t addr; struct hostent *hp = NULL; addr = inet_addr(servname); if (addr == (u_int32_t) -1) { if ((hp = gethostbyname(servname)) == NULL) return 0; BCOPY(hp->h_addr, &addr, sizeof (u_int32_t)); } /* * If there is no primary then this is the first instance of the * option, we must set the primary. In that case, try to set the * secondary to h_addr_list[1]. If the primary is already set, then * this is the second instance of the option, and we must set * the secondary. */ if (serverarray[0] == 0) { serverarray[0] = addr; if (hp != NULL && hp->h_addr_list[1] != NULL) BCOPY(hp->h_addr_list[1], &serverarray[1], sizeof (u_int32_t)); else serverarray[1] = addr; } else { serverarray[1] = addr; } return (1); } /* * setdnsaddr - set the dns address(es) */ static int setdnsaddr(argv) char **argv; { if (setmsservaddr(*argv, &(ipcp_allowoptions[0].dnsaddr[0])) == 0) { option_error("invalid address parameter '%s' for ms-dns option", *argv); return (0); } return (1); } /* * setwinsaddr - set the wins address(es) * This is primrarly used with the Samba package under UNIX or for pointing * the caller to the existing WINS server on a Windows NT platform. */ static int setwinsaddr(argv) char **argv; { if (setmsservaddr(*argv, &(ipcp_allowoptions[0].winsaddr[0])) == 0) { option_error("invalid address parameter '%s' for ms-wins option", *argv); return (0); } return (1); } /* * autoproxyarp -- enable proxy ARP but don't emit error messages if * it's not actually needed. */ /*ARGSUSED*/ static int autoproxyarp(argv) char **argv; { ipcp_wantoptions[0].proxy_arp = 1; proxy_arp_quiet[0] = 1; return (1); } /* * ipcp_init - Initialize IPCP. */ static void ipcp_init(unit) int unit; { fsm *f = &ipcp_fsm[unit]; ipcp_options *wo = &ipcp_wantoptions[unit]; ipcp_options *ao = &ipcp_allowoptions[unit]; f->unit = unit; f->protocol = PPP_IPCP; f->callbacks = &ipcp_callbacks; fsm_init(&ipcp_fsm[unit]); BZERO(wo, sizeof(*wo)); BZERO(ao, sizeof(*ao)); wo->neg_addr = wo->old_addrs = 1; wo->neg_vj = 1; wo->vj_protocol = IPCP_VJ_COMP; wo->maxslotindex = MAX_STATES - 1; /* really max index */ wo->cflag = 1; ao->neg_addr = ao->old_addrs = 1; ao->neg_vj = 1; ao->maxslotindex = MAX_STATES - 1; ao->cflag = 1; /* * These aren't actually negotiated. Instead, they control * whether the user may use the proxyarp and defaultroute options. */ ao->proxy_arp = 1; ao->default_route = 1; proxy_arp_quiet[unit] = 0; } /* * ipcp_open - IPCP is allowed to come up. */ static void ipcp_open(unit) int unit; { fsm_open(&ipcp_fsm[unit]); } /* * ipcp_close - Take IPCP down. */ static void ipcp_close(unit, reason) int unit; char *reason; { fsm_close(&ipcp_fsm[unit], reason); } /* * ipcp_lowerup - The lower layer is up. */ static void ipcp_lowerup(unit) int unit; { fsm_lowerup(&ipcp_fsm[unit]); } /* * ipcp_lowerdown - The lower layer is down. */ static void ipcp_lowerdown(unit) int unit; { fsm_lowerdown(&ipcp_fsm[unit]); } /* * ipcp_input - Input IPCP packet. */ static void ipcp_input(unit, p, len) int unit; u_char *p; int len; { fsm_input(&ipcp_fsm[unit], p, len); } /* * ipcp_protrej - A Protocol-Reject was received for IPCP. */ static void ipcp_protrej(unit) int unit; { fsm_protreject(&ipcp_fsm[unit]); } /* * ipcp_resetci - Reset our CI. * Called by fsm_sconfreq, Send Configure Request. */ static void ipcp_resetci(f) fsm *f; { ipcp_options *wo = &ipcp_wantoptions[f->unit]; ipcp_options *go = &ipcp_gotoptions[f->unit]; ipcp_options *ao = &ipcp_allowoptions[f->unit]; wo->req_addr = (wo->neg_addr || wo->old_addrs) && (ao->neg_addr || ao->old_addrs); if (wo->ouraddr == 0 || disable_defaultip) wo->accept_local = 1; if (wo->hisaddr == 0) wo->accept_remote = 1; *go = *wo; if (disable_defaultip) go->ouraddr = 0; } /* * ipcp_cilen - Return length of our CI. * Called by fsm_sconfreq, Send Configure Request. */ static int ipcp_cilen(f) fsm *f; { ipcp_options *go = &ipcp_gotoptions[f->unit]; ipcp_options *wo = &ipcp_wantoptions[f->unit]; ipcp_options *ho = &ipcp_hisoptions[f->unit]; #define LENCIADDRS(neg) (neg ? CILEN_ADDRS : 0) #define LENCIVJ(neg, old) (neg ? (old? CILEN_COMPRESS : CILEN_VJ) : 0) #define LENCIADDR(neg) (neg ? (CILEN_ADDR) : 0) /* * First see if we want to change our options to the old * forms because we have received old forms from the peer. */ if (go->neg_addr && go->old_addrs && !ho->neg_addr && ho->old_addrs) /* use the old style of address negotiation */ go->neg_addr = 0; if (wo->neg_vj && !go->neg_vj && !go->old_vj) { /* try an older style of VJ negotiation */ /* use the old style only if the peer did */ if (ho->neg_vj && ho->old_vj) { go->neg_vj = 1; go->old_vj = 1; go->vj_protocol = ho->vj_protocol; } } return (LENCIADDRS(!go->neg_addr && go->old_addrs) + LENCIVJ(go->neg_vj, go->old_vj) + LENCIADDR(go->neg_addr) + LENCIADDR(go->req_dns1) + LENCIADDR(go->req_dns2)) ; } /* * ipcp_addci - Add our desired CIs to a packet. * Called by fsm_sconfreq, Send Configure Request. */ static void ipcp_addci(f, ucp, lenp) fsm *f; u_char *ucp; int *lenp; { ipcp_options *go = &ipcp_gotoptions[f->unit]; int len = *lenp; #define ADDCIADDRS(opt, neg, val1, val2) \ if (neg) { \ if (len >= CILEN_ADDRS) { \ PUTCHAR(opt, ucp); \ PUTCHAR(CILEN_ADDRS, ucp); \ PUTNLONG(val1, ucp); \ PUTNLONG(val2, ucp); \ len -= CILEN_ADDRS; \ } else \ go->old_addrs = 0; \ } #define ADDCIVJ(opt, neg, val, old, maxslotindex, cflag) \ if (neg) { \ int vjlen = old? CILEN_COMPRESS : CILEN_VJ; \ if (len >= vjlen) { \ PUTCHAR(opt, ucp); \ PUTCHAR(vjlen, ucp); \ PUTSHORT(val, ucp); \ if (!old) { \ PUTCHAR(maxslotindex, ucp); \ PUTCHAR(cflag, ucp); \ } \ len -= vjlen; \ } else \ neg = 0; \ } #define ADDCIADDR(opt, neg, val) \ if (neg) { \ if (len >= CILEN_ADDR) { \ PUTCHAR(opt, ucp); \ PUTCHAR(CILEN_ADDR, ucp); \ PUTNLONG(val, ucp); \ len -= CILEN_ADDR; \ } else \ neg = 0; \ } ADDCIADDRS(CI_ADDRS, !go->neg_addr && go->old_addrs, go->ouraddr, go->hisaddr); ADDCIVJ(CI_COMPRESSTYPE, go->neg_vj, go->vj_protocol, go->old_vj, go->maxslotindex, go->cflag); ADDCIADDR(CI_ADDR, go->neg_addr, go->ouraddr); ADDCIADDR(CI_MS_DNS1, go->req_dns1, go->dnsaddr[0]); ADDCIADDR(CI_MS_DNS2, go->req_dns2, go->dnsaddr[1]); *lenp -= len; } /* * ipcp_ackci - Ack our CIs. * Called by fsm_rconfack, Receive Configure ACK. * * Returns: * 0 - Ack was bad. * 1 - Ack was good. */ static int ipcp_ackci(f, p, len) fsm *f; u_char *p; int len; { ipcp_options *go = &ipcp_gotoptions[f->unit]; u_short cilen, citype, cishort; u_int32_t cilong; u_char cimaxslotindex, cicflag; /* * CIs must be in exactly the same order that we sent... * Check packet length and CI length at each step. * If we find any deviations, then this packet is bad. */ #define ACKCHECK(opt, olen) \ if ((len -= olen) < 0) \ goto bad; \ GETCHAR(citype, p); \ GETCHAR(cilen, p); \ if (cilen != olen || \ citype != opt) \ goto bad; #define ACKCIADDRS(opt, neg, val1, val2) \ if (neg) { \ ACKCHECK(opt, CILEN_ADDRS) \ GETNLONG(cilong, p); \ if (val1 != cilong) \ goto bad; \ GETNLONG(cilong, p); \ if (val2 != cilong) \ goto bad; \ } #define ACKCIVJ(opt, neg, val, old, maxslotindex, cflag) \ if (neg) { \ int vjlen = old? CILEN_COMPRESS : CILEN_VJ; \ ACKCHECK(opt, vjlen) \ GETSHORT(cishort, p); \ if (cishort != val) \ goto bad; \ if (!old) { \ GETCHAR(cimaxslotindex, p); \ if (cimaxslotindex != maxslotindex) \ goto bad; \ GETCHAR(cicflag, p); \ if (cicflag != cflag) \ goto bad; \ } \ } #define ACKCIADDR(opt, neg, val) \ if (neg) { \ ACKCHECK(opt, CILEN_ADDR) \ GETNLONG(cilong, p); \ if (val != cilong) \ goto bad; \ } ACKCIADDRS(CI_ADDRS, !go->neg_addr && go->old_addrs, go->ouraddr, go->hisaddr); ACKCIVJ(CI_COMPRESSTYPE, go->neg_vj, go->vj_protocol, go->old_vj, go->maxslotindex, go->cflag); ACKCIADDR(CI_ADDR, go->neg_addr, go->ouraddr); ACKCIADDR(CI_MS_DNS1, go->req_dns1, go->dnsaddr[0]); ACKCIADDR(CI_MS_DNS2, go->req_dns2, go->dnsaddr[1]); /* * If there are any remaining CIs, then this packet is bad. */ if (len != 0) goto bad; return (1); bad: IPCPDEBUG(("ipcp_ackci: received bad Ack!")); return (0); } /* * ipcp_nakci - Peer has sent a NAK for some of our CIs. * This should not modify any state if the Nak is bad * or if IPCP is in the OPENED state. * Calback from fsm_rconfnakrej - Receive Configure-Nak or Configure-Reject. * * Returns: * 0 - Nak was bad. * 1 - Nak was good. */ static int ipcp_nakci(f, p, len) fsm *f; u_char *p; int len; { ipcp_options *go = &ipcp_gotoptions[f->unit]; u_char cimaxslotindex, cicflag; u_char citype, cilen, *next; u_short cishort; u_int32_t ciaddr1, ciaddr2; ipcp_options no; /* options we've seen Naks for */ ipcp_options try; /* options to request next time */ BZERO(&no, sizeof(no)); try = *go; /* * Any Nak'd CIs must be in exactly the same order that we sent. * Check packet length and CI length at each step. * If we find any deviations, then this packet is bad. */ #define NAKCIADDRS(opt, neg, code) \ if ((neg) && \ (cilen = p[1]) == CILEN_ADDRS && \ len >= cilen && \ p[0] == opt) { \ len -= cilen; \ INCPTR(2, p); \ GETNLONG(ciaddr1, p); \ GETNLONG(ciaddr2, p); \ no.old_addrs = 1; \ code \ } #define NAKCIVJ(opt, neg, code) \ if (go->neg && \ ((cilen = p[1]) == CILEN_COMPRESS || cilen == CILEN_VJ) && \ len >= cilen && \ p[0] == opt) { \ len -= cilen; \ INCPTR(2, p); \ GETSHORT(cishort, p); \ no.neg = 1; \ code \ } #define NAKCIADDR(opt, neg, code) \ if (go->neg && \ (cilen = p[1]) == CILEN_ADDR && \ len >= cilen && \ p[0] == opt) { \ len -= cilen; \ INCPTR(2, p); \ GETNLONG(ciaddr1, p); \ no.neg = 1; \ code \ } /* * Accept the peer's idea of {our,its} address, if different * from our idea, only if the accept_{local,remote} flag is set. */ NAKCIADDRS(CI_ADDRS, !go->neg_addr && go->old_addrs, if (go->accept_local && ciaddr1) { /* Do we know our address? */ try.ouraddr = ciaddr1; } if (go->accept_remote && ciaddr2) { /* Does it know its? */ try.hisaddr = ciaddr2; } ); /* * Accept the peer's value of maxslotindex provided that it * is less than what we asked for. Turn off slot-ID compression * if the peer wants. Send old-style compress-type option if * the peer wants. */ NAKCIVJ(CI_COMPRESSTYPE, neg_vj, if (cilen == CILEN_VJ) { GETCHAR(cimaxslotindex, p); GETCHAR(cicflag, p); if (cishort == IPCP_VJ_COMP) { try.old_vj = 0; if (cimaxslotindex < go->maxslotindex) try.maxslotindex = cimaxslotindex; if (!cicflag) try.cflag = 0; } else { try.neg_vj = 0; } } else { if (cishort == IPCP_VJ_COMP || cishort == IPCP_VJ_COMP_OLD) { try.old_vj = 1; try.vj_protocol = cishort; } else { try.neg_vj = 0; } } ); NAKCIADDR(CI_ADDR, neg_addr, if (go->accept_local && ciaddr1) { /* Do we know our address? */ try.ouraddr = ciaddr1; } ); NAKCIADDR(CI_MS_DNS1, req_dns1, try.dnsaddr[0] = ciaddr1; ); NAKCIADDR(CI_MS_DNS2, req_dns2, try.dnsaddr[1] = ciaddr1; ); /* * There may be remaining CIs, if the peer is requesting negotiation * on an option that we didn't include in our request packet. * If they want to negotiate about IP addresses, we comply. * If they want us to ask for compression, we refuse. */ while (len > CILEN_VOID) { GETCHAR(citype, p); GETCHAR(cilen, p); if( (len -= cilen) < 0 ) goto bad; next = p + cilen - 2; switch (citype) { case CI_COMPRESSTYPE: if (go->neg_vj || no.neg_vj || (cilen != CILEN_VJ && cilen != CILEN_COMPRESS)) goto bad; no.neg_vj = 1; break; case CI_ADDRS: if ((!go->neg_addr && go->old_addrs) || no.old_addrs || cilen != CILEN_ADDRS) goto bad; try.neg_addr = 1; try.old_addrs = 1; GETNLONG(ciaddr1, p); if (ciaddr1 && go->accept_local) try.ouraddr = ciaddr1; GETNLONG(ciaddr2, p); if (ciaddr2 && go->accept_remote) try.hisaddr = ciaddr2; no.old_addrs = 1; break; case CI_ADDR: if (go->neg_addr || no.neg_addr || cilen != CILEN_ADDR) goto bad; try.old_addrs = 0; GETNLONG(ciaddr1, p); if (ciaddr1 && go->accept_local) try.ouraddr = ciaddr1; if (try.ouraddr != 0) try.neg_addr = 1; no.neg_addr = 1; break; } p = next; } /* * OK, the Nak is good. Now we can update state. * If there are any remaining options, we ignore them. */ if (f->state != OPENED) *go = try; return 1; bad: IPCPDEBUG(("ipcp_nakci: received bad Nak!")); return 0; } /* * ipcp_rejci - Reject some of our CIs. * Callback from fsm_rconfnakrej. */ static int ipcp_rejci(f, p, len) fsm *f; u_char *p; int len; { ipcp_options *go = &ipcp_gotoptions[f->unit]; u_char cimaxslotindex, ciflag, cilen; u_short cishort; u_int32_t cilong; ipcp_options try; /* options to request next time */ try = *go; /* * Any Rejected CIs must be in exactly the same order that we sent. * Check packet length and CI length at each step. * If we find any deviations, then this packet is bad. */ #define REJCIADDRS(opt, neg, val1, val2) \ if ((neg) && \ (cilen = p[1]) == CILEN_ADDRS && \ len >= cilen && \ p[0] == opt) { \ len -= cilen; \ INCPTR(2, p); \ GETNLONG(cilong, p); \ /* Check rejected value. */ \ if (cilong != val1) \ goto bad; \ GETNLONG(cilong, p); \ /* Check rejected value. */ \ if (cilong != val2) \ goto bad; \ try.old_addrs = 0; \ } #define REJCIVJ(opt, neg, val, old, maxslot, cflag) \ if (go->neg && \ p[1] == (old? CILEN_COMPRESS : CILEN_VJ) && \ len >= p[1] && \ p[0] == opt) { \ len -= p[1]; \ INCPTR(2, p); \ GETSHORT(cishort, p); \ /* Check rejected value. */ \ if (cishort != val) \ goto bad; \ if (!old) { \ GETCHAR(cimaxslotindex, p); \ if (cimaxslotindex != maxslot) \ goto bad; \ GETCHAR(ciflag, p); \ if (ciflag != cflag) \ goto bad; \ } \ try.neg = 0; \ } #define REJCIADDR(opt, neg, addr) \ if (go->neg && \ ((cilen = p[1]) == CILEN_ADDR) && \ len >= cilen && \ p[0] == opt) { \ len -= cilen; \ INCPTR(2, p); \ GETNLONG(cilong, p); \ /* Check rejected value. */ \ if (cilong != addr) \ goto bad; \ try.neg = 0; \ } REJCIADDRS(CI_ADDRS, !go->neg_addr && go->old_addrs, go->ouraddr, go->hisaddr); REJCIVJ(CI_COMPRESSTYPE, neg_vj, go->vj_protocol, go->old_vj, go->maxslotindex, go->cflag); REJCIADDR(CI_ADDR, neg_addr, go->ouraddr); REJCIADDR(CI_MS_DNS1, req_dns1, go->dnsaddr[0]); REJCIADDR(CI_MS_DNS2, req_dns2, go->dnsaddr[1]); /* * If there are any remaining CIs, then this packet is bad. */ if (len != 0) goto bad; /* * Now we can update state. */ if (f->state != OPENED) *go = try; return 1; bad: IPCPDEBUG(("ipcp_rejci: received bad Reject!")); return 0; } /* * ipcp_reqci - Check the peer's requested CIs and send appropriate response. * Callback from fsm_rconfreq, Receive Configure Request * * Returns: CODE_CONFACK, CODE_CONFNAK or CODE_CONFREJ and input * packet modified appropriately. If reject_if_disagree is non-zero, * doesn't return CODE_CONFNAK; returns CODE_CONFREJ if it can't * return CODE_CONFACK. */ static int ipcp_reqci(f, p, lenp, dont_nak) fsm *f; u_char *p; /* Requested CIs */ int *lenp; /* Length of requested CIs */ bool dont_nak; { ipcp_options *wo = &ipcp_wantoptions[f->unit]; ipcp_options *ho = &ipcp_hisoptions[f->unit]; ipcp_options *ao = &ipcp_allowoptions[f->unit]; ipcp_options *go = &ipcp_gotoptions[f->unit]; int ret, newret; u_char *p0, *nakp, *rejp, *prev; u_short cishort; int len, cilen, type; u_int32_t tl, ciaddr1, ciaddr2; /* Parsed address values */ u_char maxslotindex, cflag; int d; ret = CODE_CONFACK; rejp = p0 = p; nakp = nak_buffer; /* * Reset all its options. */ BZERO(ho, sizeof(*ho)); /* * Process all its options. */ for (len = *lenp; len > 0; len -= cilen, p = prev + cilen) { if ((len < 2) || p[1] > len) { /* * RFC 1661 page 40 -- if the option extends beyond the * packet, then discard the entire packet. */ return (0); } newret = CODE_CONFACK; prev = p; GETCHAR(type, p); GETCHAR(cilen, p); switch (type) { /* Check CI type */ case CI_ADDRS: if (!ao->old_addrs || ho->neg_addr) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_ADDRS) { /* * rfc1661, page 40 -- a recongnized option with an * invalid length should be Nak'ed. */ newret = CODE_CONFNAK; ciaddr1 = wo->hisaddr; ciaddr2 = wo->ouraddr; } else { /* * If it has no address, or if we both have its * address but disagree about it, then NAK it with our * idea. In particular, if we don't know its address, * but it does, then accept it. */ GETNLONG(ciaddr1, p); if (ciaddr1 != wo->hisaddr && (ciaddr1 == 0 || !wo->accept_remote)) { newret = CODE_CONFNAK; ciaddr1 = wo->hisaddr; } else if (ciaddr1 == 0 && wo->hisaddr == 0) { /* * If neither we nor he knows his address, reject * the option. */ newret = CODE_CONFREJ; wo->req_addr = 0; /* don't NAK with 0.0.0.0 later */ break; } else if (ciaddr1 != 0) { go->hisaddr = ciaddr1; } /* * If he doesn't know our address, or if we both have * our address * but disagree about it, then NAK it * with our idea. */ GETNLONG(ciaddr2, p); if (ciaddr2 != wo->ouraddr) { if (ciaddr2 == 0 || !wo->accept_local) { newret = CODE_CONFNAK; ciaddr2 = wo->ouraddr; } else { go->ouraddr = ciaddr2; /* accept peer's idea */ } } } if (newret == CODE_CONFNAK) { PUTCHAR(type, nakp); PUTCHAR(CILEN_ADDRS, nakp); PUTNLONG(ciaddr1, nakp); PUTNLONG(ciaddr2, nakp); } ho->old_addrs = 1; ho->hisaddr = ciaddr1; ho->ouraddr = ciaddr2; break; case CI_ADDR: if (!ao->neg_addr || ho->old_addrs) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_ADDR) { /* * rfc1661, page 40 -- a recongnized option with an * invalid length should be Nak'ed. */ newret = CODE_CONFNAK; ciaddr1 = wo->hisaddr; } else { /* * If he has no address, or if we both have his * address but disagree about it, then NAK it with our * idea. In particular, if we don't know his address, * but he does, then accept it. */ GETNLONG(ciaddr1, p); if (ciaddr1 != wo->hisaddr && (ciaddr1 == 0 || !wo->accept_remote)) { newret = CODE_CONFNAK; ciaddr1 = wo->hisaddr; } else if (ciaddr1 == 0 && wo->hisaddr == 0 && wo->default_route != 0) { newret = CODE_CONFNAK; /* * If this is a dialup line (default_route is * set), and neither side knows about its address, * suggest an arbitrary rfc1918 address. */ ciaddr1 = htonl(0xc0a80101 + ifunit); dbglog("Peer address unknown; suggesting %I", ciaddr1); } else if (ciaddr1 == 0 && wo->hisaddr == 0) { /* * If this is not a dialup line, don't ACK an * address of 0.0.0.0 - reject it instead. */ newret = CODE_CONFREJ; wo->req_addr = 0; /* don't NAK with 0.0.0.0 later */ break; } } if (newret == CODE_CONFNAK) { PUTCHAR(type, nakp); PUTCHAR(CILEN_ADDR, nakp); PUTNLONG(ciaddr1, nakp); } ho->neg_addr = 1; ho->hisaddr = ciaddr1; break; case CI_MS_DNS1: case CI_MS_DNS2: /* Warning -- these options work backwards. */ /* Microsoft primary or secondary DNS request */ d = (type == CI_MS_DNS2 ? 1 : 0); if (ao->dnsaddr[d] == 0) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_ADDR) { newret = CODE_CONFNAK; } else { GETNLONG(tl, p); if (tl != ao->dnsaddr[d]) { newret = CODE_CONFNAK; } } if (newret == CODE_CONFNAK) { PUTCHAR(type, nakp); PUTCHAR(CILEN_ADDR, nakp); PUTNLONG(ao->dnsaddr[d], nakp); } break; case CI_MS_WINS1: case CI_MS_WINS2: /* Warning -- these options work backwards. */ /* Microsoft primary or secondary WINS request */ d = (type == CI_MS_WINS2 ? 1 : 0); if (ao->winsaddr[d] == 0) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_ADDR) { newret = CODE_CONFNAK; } else { GETNLONG(tl, p); if (tl != ao->winsaddr[d]) { newret = CODE_CONFNAK; } } if (newret == CODE_CONFNAK) { PUTCHAR(type, nakp); PUTCHAR(CILEN_ADDR, nakp); PUTNLONG(ao->winsaddr[d], nakp); } break; case CI_COMPRESSTYPE: if (!ao->neg_vj) { newret = CODE_CONFREJ; break; } maxslotindex = ao->maxslotindex; cflag = ao->cflag; if (cilen != CILEN_VJ && cilen != CILEN_COMPRESS) { newret = CODE_CONFNAK; cishort = IPCP_VJ_COMP; } else { GETSHORT(cishort, p); if (cishort != IPCP_VJ_COMP && (cishort != IPCP_VJ_COMP_OLD || cilen != CILEN_COMPRESS)) { newret = CODE_CONFNAK; cishort = IPCP_VJ_COMP; } else if (cilen == CILEN_VJ) { GETCHAR(maxslotindex, p); if (maxslotindex > ao->maxslotindex) { newret = CODE_CONFNAK; maxslotindex = ao->maxslotindex; } GETCHAR(cflag, p); if (cflag != 0 && ao->cflag == 0) { newret = CODE_CONFNAK; cflag = 0; } } else { ho->old_vj = 1; maxslotindex = MAX_STATES - 1; cflag = 1; } } if (newret == CODE_CONFNAK) { PUTCHAR(type, nakp); if (cishort == IPCP_VJ_COMP) { PUTCHAR(CILEN_VJ, nakp); PUTSHORT(cishort, nakp); PUTCHAR(maxslotindex, nakp); PUTCHAR(cflag, nakp); } else { PUTCHAR(CILEN_COMPRESS, nakp); PUTSHORT(cishort, nakp); } } ho->neg_vj = 1; ho->vj_protocol = cishort; ho->maxslotindex = maxslotindex; ho->cflag = cflag; break; default: newret = CODE_CONFREJ; break; } /* Cope with confused peers. */ if (cilen < 2) cilen = 2; /* * If this is an Ack'able CI, but we're sending back a Nak, * don't include this CI. */ if (newret == CODE_CONFACK && ret != CODE_CONFACK) continue; if (newret == CODE_CONFNAK) { if (dont_nak) { newret = CODE_CONFREJ; } else { /* Ignore subsequent Nak'able things if rejecting. */ if (ret == CODE_CONFREJ) continue; ret = CODE_CONFNAK; } } if (newret == CODE_CONFREJ) { ret = CODE_CONFREJ; if (prev != rejp) BCOPY(prev, rejp, cilen); rejp += cilen; } } /* * If we aren't rejecting this packet, and we want to negotiate * their address, and they didn't send their address, then we * send a NAK with a CI_ADDR option appended. We assume the * input buffer is long enough that we can append the extra * option safely. */ if (ret != CODE_CONFREJ && !ho->neg_addr && !ho->old_addrs && wo->req_addr && !dont_nak) { if (ret == CODE_CONFACK) wo->req_addr = 0; /* don't ask again */ ret = CODE_CONFNAK; PUTCHAR(CI_ADDR, nakp); PUTCHAR(CILEN_ADDR, nakp); PUTNLONG(wo->hisaddr, nakp); } switch (ret) { case CODE_CONFACK: *lenp = p - p0; sys_block_proto(PPP_IP); break; case CODE_CONFNAK: *lenp = nakp - nak_buffer; BCOPY(nak_buffer, p0, *lenp); break; case CODE_CONFREJ: *lenp = rejp - p0; break; } return (ret); /* Return final code */ } /* * ip_check_options - check that any IP-related options are OK, * and assign appropriate defaults. */ static void ip_check_options() { struct hostent *hp; u_int32_t local; ipcp_options *wo = &ipcp_wantoptions[0]; /* * Default our local IP address based on our hostname. * If local IP address already given, don't bother. */ if (wo->ouraddr == 0) { /* * Look up our hostname (possibly with domain name appended) * and take the first IP address as our local IP address. * If there isn't an IP address for our hostname, too bad. */ wo->accept_local = 1; /* don't insist on this default value */ if ((hp = gethostbyname(hostname)) != NULL) { BCOPY(hp->h_addr, &local, sizeof (hp->h_addr)); if (local != 0 && !bad_ip_adrs(local)) { wo->ouraddr = local; ipcp_from_hostname = 1; } } } } /* * ip_demand_conf - configure the interface as though * IPCP were up, for use with dial-on-demand. */ static int ip_demand_conf(u) int u; { ipcp_options *wo = &ipcp_wantoptions[u]; if (wo->hisaddr == 0) { /* make up an arbitrary address for the peer */ wo->hisaddr = htonl(0x0a707070 + ifunit); wo->accept_remote = 1; } if (wo->ouraddr == 0) { /* make up an arbitrary address for us */ wo->ouraddr = htonl(0x0a404040 + ifunit); wo->accept_local = 1; disable_defaultip = 1; /* don't tell the peer this address */ } if (!sifaddr(u, wo->ouraddr, wo->hisaddr, GetMask(wo->ouraddr))) return 0; if (!sifup(u)) return 0; if (!sifnpmode(u, PPP_IP, NPMODE_QUEUE)) return 0; if (wo->default_route && sifdefaultroute(u, wo->ouraddr, wo->hisaddr)) default_route_set[u] = 1; if (wo->proxy_arp && sifproxyarp(u, wo->hisaddr, proxy_arp_quiet[u])) proxy_arp_set[u] = 1; notice("local IP address %I", wo->ouraddr); notice("remote IP address %I", wo->hisaddr); return 1; } /* * ipcp_up - IPCP has come UP. * * Configure the IP network interface appropriately and bring it up. */ static void ipcp_up(f) fsm *f; { u_int32_t mask; ipcp_options *ho = &ipcp_hisoptions[f->unit]; ipcp_options *go = &ipcp_gotoptions[f->unit]; ipcp_options *wo = &ipcp_wantoptions[f->unit]; IPCPDEBUG(("ipcp: up")); /* * We must have a non-zero IP address for both ends of the link. */ if (ho->hisaddr == 0) ho->hisaddr = wo->hisaddr; if (ho->hisaddr == 0) { if (wo->accept_remote) { /* Pick some rfc1918 address. */ ho->hisaddr = htonl(0xc0a80101 + ifunit); dbglog("Peer refused to provide his address; assuming %I", ho->hisaddr); } else { error("Could not determine remote IP address"); ipcp_close(f->unit, "Could not determine remote IP address"); return; } } if (go->ouraddr == 0) { error("Could not determine local IP address"); ipcp_close(f->unit, "Could not determine local IP address"); return; } script_setenv("IPLOCAL", ip_ntoa(go->ouraddr), 0); script_setenv("IPREMOTE", ip_ntoa(ho->hisaddr), 1); /* * Check that the peer is allowed to use the IP address it wants. */ if (!auth_ip_addr(f->unit, ho->hisaddr)) { error("Peer is not authorized to use remote address %I", ho->hisaddr); ipcp_close(f->unit, "Unauthorized remote IP address"); return; } if ((go->req_dns1 && go->dnsaddr[0] != 0) || (go->req_dns2 && go->dnsaddr[1] != 0)) { script_setenv("USEPEERDNS", "1", 0); if (go->dnsaddr[0] != 0) script_setenv("DNS1", ip_ntoa(go->dnsaddr[0]), 0); if (go->dnsaddr[1] != 0) script_setenv("DNS2", ip_ntoa(go->dnsaddr[1]), 0); create_resolv(go->dnsaddr[0], go->dnsaddr[1]); } /* set tcp compression */ if (sifvjcomp(f->unit, ho->neg_vj, ho->cflag, ho->maxslotindex) != 1) { ipcp_close(f->unit, "Could not enable VJ TCP header compression"); return; } /* * If we are doing dial-on-demand, the interface is already * configured, so we put out any saved-up packets, then set the * interface to pass IP packets. */ if (demand) { if (go->ouraddr != wo->ouraddr || ho->hisaddr != wo->hisaddr) { ipcp_clear_addrs(f->unit, wo->ouraddr, wo->hisaddr); if (go->ouraddr != wo->ouraddr) { warn("Local IP address changed to %I", go->ouraddr); script_setenv("OLDIPLOCAL", ip_ntoa(wo->ouraddr), 0); wo->ouraddr = go->ouraddr; } else script_unsetenv("OLDIPLOCAL"); if (ho->hisaddr != wo->hisaddr) { warn("Remote IP address changed to %I", ho->hisaddr); script_setenv("OLDIPREMOTE", ip_ntoa(wo->hisaddr), 0); wo->hisaddr = ho->hisaddr; } else script_unsetenv("OLDIPREMOTE"); /* Set the interface to the new addresses */ mask = GetMask(go->ouraddr); if (!sifaddr(f->unit, go->ouraddr, ho->hisaddr, mask)) { warn("Interface configuration failed"); ipcp_close(f->unit, "Interface configuration failed"); return; } /* assign a default route through the interface if required */ if (wo->default_route) if (sifdefaultroute(f->unit, go->ouraddr, ho->hisaddr)) default_route_set[f->unit] = 1; /* Make a proxy ARP entry if requested. */ if (wo->proxy_arp && sifproxyarp(f->unit, ho->hisaddr, proxy_arp_quiet[f->unit])) proxy_arp_set[f->unit] = 1; } demand_rexmit(PPP_IP); if (sifnpmode(f->unit, PPP_IP, NPMODE_PASS) != 1) { ipcp_close(f->unit, "Interface configuration failed."); return; } } else { /* * Set IP addresses and (if specified) netmask. */ mask = GetMask(go->ouraddr); #if SIFUPFIRST /* bring the interface up for IP */ if (!sifup(f->unit)) { warn("Interface failed to come up"); ipcp_close(f->unit, "Interface configuration failed"); return; } #endif if (!sifaddr(f->unit, go->ouraddr, ho->hisaddr, mask)) { warn("Interface configuration failed"); ipcp_close(f->unit, "Interface configuration failed"); return; } #if !SIFUPFIRST /* bring the interface up for IP */ if (!sifup(f->unit)) { warn("Interface failed to come up"); ipcp_close(f->unit, "Interface configuration failed"); return; } #endif if (sifnpmode(f->unit, PPP_IP, NPMODE_PASS) != 1) { ipcp_close(f->unit, "Interface configuration failed."); return; } /* assign a default route through the interface if required */ if (wo->default_route) if (sifdefaultroute(f->unit, go->ouraddr, ho->hisaddr)) default_route_set[f->unit] = 1; /* Make a proxy ARP entry if requested. */ if (wo->proxy_arp && sifproxyarp(f->unit, ho->hisaddr, proxy_arp_quiet[f->unit])) proxy_arp_set[f->unit] = 1; wo->ouraddr = go->ouraddr; notice("local IP address %I", go->ouraddr); notice("remote IP address %I", ho->hisaddr); if (go->dnsaddr[0] != 0) notice("primary DNS address %I", go->dnsaddr[0]); if (go->dnsaddr[1] != 0) notice("secondary DNS address %I", go->dnsaddr[1]); } np_up(f->unit, PPP_IP); ipcp_is_up[f->unit] = 1; if (ip_up_hook != NULL) (*ip_up_hook)(); /* * Execute the ip-up script, like this: * /etc/ppp/ip-up interface tty speed local-IP remote-IP */ if (ipcp_script_state == s_down && ipcp_script_pid == 0) { ipcp_script_state = s_up; ipcp_script(_PATH_IPUP); } sys_unblock_proto(PPP_IP); } /* * ipcp_down - IPCP has gone DOWN. * * Take the IP network interface down, clear its addresses * and delete routes through it. */ static void ipcp_down(f) fsm *f; { IPCPDEBUG(("ipcp: down")); /* XXX a bit IPv4-centric here, we only need to get the stats * before the interface is marked down. */ update_link_stats(f->unit); if (ip_down_hook != NULL) (*ip_down_hook)(); if (ipcp_is_up[f->unit]) { ipcp_is_up[f->unit] = 0; np_down(f->unit, PPP_IP); } if (sifvjcomp(f->unit, 0, 0, 0) != 1) { if (debug) warn("Failed to disable VJ TCP header compression."); } /* * If we are doing dial-on-demand, set the interface * to queue up outgoing packets (for now). */ if (demand) { if (sifnpmode(f->unit, PPP_IP, NPMODE_QUEUE) != 1) { if (debug) warn("Failed to enable Queueing on outgoing packets."); } } else { if (sifnpmode(f->unit, PPP_IP, NPMODE_ERROR) != 1) { if (debug) warn("Could not set interface to drop packets."); } if (sifdown(f->unit) != 1) warn("Could not bring interface down."); ipcp_clear_addrs(f->unit, ipcp_gotoptions[f->unit].ouraddr, ipcp_hisoptions[f->unit].hisaddr); } /* Execute the ip-down script */ if (ipcp_script_state == s_up && ipcp_script_pid == 0) { ipcp_script_state = s_down; ipcp_script(_PATH_IPDOWN); } } /* * ipcp_clear_addrs() - clear the interface addresses, routes, * proxy arp entries, etc. */ static void ipcp_clear_addrs(unit, ouraddr, hisaddr) int unit; u_int32_t ouraddr; /* local address */ u_int32_t hisaddr; /* remote address */ { if (proxy_arp_set[unit]) { (void) cifproxyarp(unit, hisaddr); proxy_arp_set[unit] = 0; } if (default_route_set[unit]) { (void) cifdefaultroute(unit, ouraddr, hisaddr); default_route_set[unit] = 0; } if (cifaddr(unit, ouraddr, hisaddr) != 1) warn("Could not clear addresses"); } /* * ipcp_finished - possibly shut down the lower layers. */ static void ipcp_finished(f) fsm *f; { np_finished(f->unit, PPP_IP); } /* * ipcp_script_done - called when the ip-up or ip-down script * has finished. */ /*ARGSUSED*/ static void ipcp_script_done(arg, status) void *arg; int status; { ipcp_script_pid = 0; switch (ipcp_script_state) { case s_up: if (ipcp_fsm[0].state != OPENED) { ipcp_script_state = s_down; ipcp_script(_PATH_IPDOWN); } break; case s_down: if (ipcp_fsm[0].state == OPENED) { ipcp_script_state = s_up; ipcp_script(_PATH_IPUP); } break; } } /* * ipcp_script - Execute a script with arguments * interface-name tty-name speed local-IP remote-IP. */ static void ipcp_script(script) char *script; { char strspeed[32], strlocal[32], strremote[32]; char *argv[8]; (void) slprintf(strspeed, sizeof(strspeed), "%d", baud_rate); (void) slprintf(strlocal, sizeof(strlocal), "%I", ipcp_gotoptions[0].ouraddr); (void) slprintf(strremote, sizeof(strremote), "%I", ipcp_hisoptions[0].hisaddr); argv[0] = script; argv[1] = ifname; argv[2] = devnam; argv[3] = strspeed; argv[4] = strlocal; argv[5] = strremote; argv[6] = ipparam; argv[7] = NULL; ipcp_script_pid = run_program(script, argv, 0, ipcp_script_done, NULL); } /* * create_resolv - create the replacement resolv.conf file */ static void create_resolv(peerdns1, peerdns2) u_int32_t peerdns1, peerdns2; { FILE *f; f = fopen(_PATH_RESOLV, "w"); if (f == NULL) { error("Failed to create %s: %m", _PATH_RESOLV); return; } if (peerdns1) if (fprintf(f, "nameserver %s\n", ip_ntoa(peerdns1)) <= 0) error("Write failed to %s: %m", _PATH_RESOLV); if (peerdns2) if (fprintf(f, "nameserver %s\n", ip_ntoa(peerdns2)) <= 0) error("Write failed to %s: %m", _PATH_RESOLV); if (fclose(f) != 0) error("Failed to close %s: %m", _PATH_RESOLV); } /* * ipcp_printpkt - print the contents of an IPCP packet. */ static int ipcp_printpkt(p, plen, printer, arg) u_char *p; int plen; void (*printer) __P((void *, const char *, ...)); void *arg; { int code, id, len, olen; u_char *pstart, *optend; u_short cishort; u_int32_t cilong; if (plen < HEADERLEN) return 0; pstart = p; GETCHAR(code, p); GETCHAR(id, p); GETSHORT(len, p); if (len < HEADERLEN || len > plen) return 0; printer(arg, " %s id=0x%x", code_name(code, 1), id); len -= HEADERLEN; switch (code) { case CODE_CONFREQ: case CODE_CONFACK: case CODE_CONFNAK: case CODE_CONFREJ: /* print option list */ while (len >= 2) { GETCHAR(code, p); GETCHAR(olen, p); p -= 2; if (olen < 2 || olen > len) { break; } printer(arg, " <"); len -= olen; optend = p + olen; switch (code) { case CI_ADDRS: if (olen == CILEN_ADDRS) { p += 2; GETNLONG(cilong, p); printer(arg, "addrs %I", cilong); GETNLONG(cilong, p); printer(arg, " %I", cilong); } break; case CI_COMPRESSTYPE: if (olen >= CILEN_COMPRESS) { p += 2; GETSHORT(cishort, p); printer(arg, "compress "); switch (cishort) { case IPCP_VJ_COMP: printer(arg, "VJ"); break; case IPCP_VJ_COMP_OLD: printer(arg, "old-VJ"); break; default: printer(arg, "0x%x", cishort); } } break; case CI_ADDR: if (olen == CILEN_ADDR) { p += 2; GETNLONG(cilong, p); printer(arg, "addr %I", cilong); } break; case CI_MS_DNS1: case CI_MS_DNS2: p += 2; GETNLONG(cilong, p); printer(arg, "ms-dns%d %I", (code == CI_MS_DNS1 ? 1 : 2), cilong); break; case CI_MS_WINS1: case CI_MS_WINS2: p += 2; GETNLONG(cilong, p); printer(arg, "ms-wins%d %I", (code == CI_MS_WINS1 ? 1 : 2), cilong); break; case CI_SUBNET: p += 2; GETNLONG(cilong, p); printer(arg, "subnet %I", cilong); break; } while (p < optend) { GETCHAR(code, p); printer(arg, " %.2x", code); } printer(arg, ">"); } break; case CODE_TERMACK: case CODE_TERMREQ: if (len > 0 && *p >= ' ' && *p < 0x7f) { printer(arg, " "); print_string((char *)p, len, printer, arg); p += len; len = 0; } break; } /* print the rest of the bytes in the packet */ for (; len > 0; --len) { GETCHAR(code, p); printer(arg, " %.2x", code); } return p - pstart; } char * tcp_flag_decode(val) int val; { static char buf[32]; char *cp = buf; if (val & TH_URG) *cp++ = 'U'; if (val & TH_ACK) *cp++ = 'A'; if (val & TH_PUSH) *cp++ = 'P'; if (val & TH_RST) *cp++ = 'R'; if (val & TH_SYN) *cp++ = 'S'; if (val & TH_FIN) *cp++ = 'F'; if (cp != buf) *cp++ = ' '; *cp = '\0'; return buf; } /* * ip_active_pkt - see if this IP packet is worth bringing the link up for. * We don't bring the link up for IP fragments or for TCP FIN packets * with no data. */ static int ip_active_pkt(pkt, len) u_char *pkt; int len; { u_char *tcp; struct protoent *pep; int val; int hlen; char buf[32], *cp; u_int32_t src, dst; len -= PPP_HDRLEN; pkt += PPP_HDRLEN; if (len < IP_HDRLEN) { dbglog("IP packet of length %d is not activity", len); return 0; } src = get_ipsrc(pkt); dst = get_ipdst(pkt); if ((get_ipoff(pkt) & IP_OFFMASK) != 0) { dbglog("IP fragment from %I->%I is not activity", src, dst); return 0; } val = get_ipproto(pkt); if (val != IPPROTO_TCP) { if (debug) { if ((pep = getprotobynumber(val)) != NULL) { cp = pep->p_name; } else { (void) slprintf(buf, sizeof (buf), "IP proto %d", val); cp = buf; } info("%s from %I->%I is activity", cp, src, dst); } return 1; } hlen = get_iphl(pkt) * 4; if (len < hlen + TCP_HDRLEN) { dbglog("Bad TCP length %d<%d+%d %I->%I is not activity", len, hlen, TCP_HDRLEN, src, dst); return 0; } tcp = pkt + hlen; val = get_tcpflags(tcp); hlen += get_tcpoff(tcp) * 4; if ((val & TH_FIN) != 0 && len == hlen) { dbglog("Empty TCP FIN %I->%I is not activity", src, dst); return 0; } info("TCP %d data %s%I->%I is activity", len - hlen, tcp_flag_decode(get_tcpflags(tcp)), src, dst); return 1; } static void ipcp_print_stat(unit, strptr) int unit; FILE *strptr; { ipcp_options *go = &ipcp_gotoptions[unit]; ipcp_options *ho = &ipcp_hisoptions[unit]; char *proto_name = ipcp_protent.name; if (!ipcp_protent.enabled_flag) { (void) flprintf(strptr, "%s disabled\n", proto_name); return; } (void) flprintf(strptr, "%s state: %s", proto_name, fsm_state(ipcp_fsm[unit].state)); (void) flprintf(strptr, "%s local %I remote %I", proto_name, go->ouraddr, ho->ouraddr); } /* * ipcp.h - IP Control Protocol definitions. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * 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. * * $Id: ipcp.h,v 1.13 1999/03/02 05:35:09 paulus Exp $ */ /* * Options. */ #define CI_ADDRS 1 /* IP Addresses */ #define CI_COMPRESSTYPE 2 /* Compression Type */ #define CI_ADDR 3 #define CI_MS_DNS1 129 /* Primary DNS value */ #define CI_MS_WINS1 130 /* Primary WINS value */ #define CI_MS_DNS2 131 /* Secondary DNS value */ #define CI_MS_WINS2 132 /* Secondary WINS value */ #define CI_SUBNET 144 /* Bogus subnet option */ #define MAX_STATES 16 /* from slcompress.h */ #define IPCP_VJMODE_OLD 1 /* "old" mode (option # = 0x0037) */ #define IPCP_VJMODE_RFC1172 2 /* "old-rfc"mode (option # = 0x002d) */ #define IPCP_VJMODE_RFC1332 3 /* "new-rfc"mode (option # = 0x002d, */ /* maxslot and slot number compression) */ #define IPCP_VJ_COMP 0x002d /* current value for VJ compression option*/ #define IPCP_VJ_COMP_OLD 0x0037 /* "old" (i.e, broken) value for VJ */ /* compression option*/ typedef struct ipcp_options { bool neg_addr; /* Negotiate IP Address? */ bool old_addrs; /* Use old (IP-Addresses) option? */ bool req_addr; /* Ask peer to send IP address? */ bool default_route; /* Assign default route through interface? */ bool proxy_arp; /* Make proxy ARP entry for peer? */ bool neg_vj; /* Van Jacobson Compression? */ bool old_vj; /* use old (short) form of VJ option? */ bool accept_local; /* accept peer's value for ouraddr */ bool accept_remote; /* accept peer's value for hisaddr */ bool req_dns1; /* Ask peer to send primary DNS address? */ bool req_dns2; /* Ask peer to send secondary DNS address? */ bool cflag; /* VJ slot compression flag */ int vj_protocol; /* protocol value to use in VJ option */ int maxslotindex; /* values for RFC1332 VJ compression neg. */ u_int32_t ouraddr, hisaddr; /* Addresses in NETWORK BYTE ORDER */ u_int32_t dnsaddr[2]; /* Primary and secondary MS DNS entries */ u_int32_t winsaddr[2]; /* Primary and secondary MS WINS entries */ } ipcp_options; extern fsm ipcp_fsm[]; extern ipcp_options ipcp_wantoptions[]; extern ipcp_options ipcp_gotoptions[]; extern ipcp_options ipcp_allowoptions[]; extern ipcp_options ipcp_hisoptions[]; extern char *ip_ntoa __P((u_int32_t)); extern char *tcp_flag_decode __P((int)); extern struct protent ipcp_protent; extern bool ipcp_from_hostname; /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. * * ipv6cp.c - PPP IPV6 Control Protocol. Copyright (C) 1999 Tommi Komulainen 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. The name of the author 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. */ /* Original version, based on RFC2023 : Copyright (c) 1995, 1996, 1997 Francis.Dupont@inria.fr, INRIA Rocquencourt, Alain.Durand@imag.fr, IMAG, Jean-Luc.Richier@imag.fr, IMAG-LSR. Copyright (c) 1998, 1999 Francis.Dupont@inria.fr, GIE DYADE, Alain.Durand@imag.fr, IMAG, Jean-Luc.Richier@imag.fr, IMAG-LSR. Ce travail a été fait au sein du GIE DYADE (Groupement d'Intérêt Économique ayant pour membres BULL S.A. et l'INRIA). Ce logiciel informatique est disponible aux conditions usuelles dans la recherche, c'est-à-dire qu'il peut être utilisé, copié, modifié, distribué à l'unique condition que ce texte soit conservé afin que l'origine de ce logiciel soit reconnue. Le nom de l'Institut National de Recherche en Informatique et en Automatique (INRIA), de l'IMAG, ou d'une personne morale ou physique ayant participé à l'élaboration de ce logiciel ne peut être utilisé sans son accord préalable explicite. Ce logiciel est fourni tel quel sans aucune garantie, support ou responsabilité d'aucune sorte. Ce logiciel est dérivé de sources d'origine "University of California at Berkeley" et "Digital Equipment Corporation" couvertes par des copyrights. L'Institut d'Informatique et de Mathématiques Appliquées de Grenoble (IMAG) est une fédération d'unités mixtes de recherche du CNRS, de l'Institut National Polytechnique de Grenoble et de l'Université Joseph Fourier regroupant sept laboratoires dont le laboratoire Logiciels, Systèmes, Réseaux (LSR). This work has been done in the context of GIE DYADE (joint R & D venture between BULL S.A. and INRIA). This software is available with usual "research" terms with the aim of retain credits of the software. Permission to use, copy, modify and distribute this software for any purpose and without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies, and the name of INRIA, IMAG, or any contributor not be used in advertising or publicity pertaining to this material without the prior explicit permission. The software is provided "as is" without any warranties, support or liabilities of any kind. This software is derived from source code from "University of California at Berkeley" and "Digital Equipment Corporation" protected by copyrights. Grenoble's Institute of Computer Science and Applied Mathematics (IMAG) is a federation of seven research units funded by the CNRS, National Polytechnic Institute of Grenoble and University Joseph Fourier. The research unit in Software, Systems, Networks (LSR) is member of IMAG. */ /* * Derived from : * * * ipcp.c - PPP IP Control Protocol. * * 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. * * $Id: ipv6cp.c,v 1.9 2000/04/15 01:27:11 masputra Exp $ */ /* * TODO: * * Proxy Neighbour Discovery. * * Better defines for selecting the ordering of * interface up / set address. (currently checks for __linux__, * since SVR4 && (SNI || __USLC__) didn't work properly) */ #include #include #include #include #include #include #include #include #include #include #include "pppd.h" #include "eui64.h" #include "fsm.h" #include "ipcp.h" #include "ipv6cp.h" #include "magic.h" #include "pathnames.h" /* global vars */ ipv6cp_options ipv6cp_wantoptions[NUM_PPP]; /* Options that we want to request */ ipv6cp_options ipv6cp_gotoptions[NUM_PPP]; /* Options that peer ack'd */ ipv6cp_options ipv6cp_allowoptions[NUM_PPP]; /* Options we allow peer to request */ ipv6cp_options ipv6cp_hisoptions[NUM_PPP]; /* Options that we ack'd */ int no_ifaceid_neg = 0; /* local vars */ static bool ipv6cp_is_up; /* * Callbacks for fsm code. (CI = Configuration Information) */ static void ipv6cp_resetci __P((fsm *)); /* Reset our CI */ static int ipv6cp_cilen __P((fsm *)); /* Return length of our CI */ static void ipv6cp_addci __P((fsm *, u_char *, int *)); /* Add our CI */ static int ipv6cp_ackci __P((fsm *, u_char *, int)); /* Peer ack'd our CI */ static int ipv6cp_nakci __P((fsm *, u_char *, int)); /* Peer nak'd our CI */ static int ipv6cp_rejci __P((fsm *, u_char *, int)); /* Peer rej'd our CI */ static int ipv6cp_reqci __P((fsm *, u_char *, int *, int)); /* Rcv CI */ static void ipv6cp_up __P((fsm *)); /* We're UP */ static void ipv6cp_down __P((fsm *)); /* We're DOWN */ static void ipv6cp_finished __P((fsm *)); /* Don't need lower layer */ fsm ipv6cp_fsm[NUM_PPP]; /* IPV6CP fsm structure */ static fsm_callbacks ipv6cp_callbacks = { /* IPV6CP callback routines */ ipv6cp_resetci, /* Reset our Configuration Information */ ipv6cp_cilen, /* Length of our Configuration Information */ ipv6cp_addci, /* Add our Configuration Information */ ipv6cp_ackci, /* ACK our Configuration Information */ ipv6cp_nakci, /* NAK our Configuration Information */ ipv6cp_rejci, /* Reject our Configuration Information */ ipv6cp_reqci, /* Request peer's Configuration Information */ ipv6cp_up, /* Called when fsm reaches OPENED state */ ipv6cp_down, /* Called when fsm leaves OPENED state */ NULL, /* Called when we want the lower layer up */ ipv6cp_finished, /* Called when we want the lower layer down */ NULL, /* Retransmission is necessary */ NULL, /* Called to handle protocol-specific codes */ "IPV6CP", /* String name of protocol */ NULL /* Peer rejected a code number */ }; static int setifaceid __P((char **arg, option_t *)); /* * Command-line options. */ static option_t ipv6cp_option_list[] = { { "ipv6", o_special, (void *)setifaceid, "Set interface identifiers for IPV6" }, { "noipv6", o_bool, &ipv6cp_protent.enabled_flag, "Disable IPv6 and IPv6CP" }, { "-ipv6", o_bool, &ipv6cp_protent.enabled_flag, "Disable IPv6 and IPv6CP" }, { "+ipv6", o_bool, &ipv6cp_protent.enabled_flag, "Enable IPv6 and IPv6CP", 1 }, { "ipv6cp-accept-local", o_bool, &ipv6cp_wantoptions[0].accept_local, "Accept peer's interface identifier for us", 1 }, { "ipv6cp-use-ipaddr", o_bool, &ipv6cp_wantoptions[0].use_ip, "Use (default) IPv4 address as interface identifier", 1 }, #if defined(SOL2) { "ipv6cp-use-persistent", o_bool, &ipv6cp_wantoptions[0].use_persistent, "Use unique persistent value for link local address", 1 }, #endif /* defined(SOL2) */ { "ipv6cp-restart", o_int, &ipv6cp_fsm[0].timeouttime, "Set timeout for IPv6CP" }, { "ipv6cp-max-terminate", o_int, &ipv6cp_fsm[0].maxtermtransmits, "Maximum number of IPV6CP Terminate-Request" }, { "ipv6cp-max-configure", o_int, &ipv6cp_fsm[0].maxconfreqtransmits, "Maximum number of IPV6CP Configure-Request" }, { "ipv6cp-max-failure", o_int, &ipv6cp_fsm[0].maxnakloops, "Maximum number of IPV6CP Configure-Nak" }, { NULL } }; /* * Protocol entry points from main code. */ static void ipv6cp_init __P((int)); static void ipv6cp_open __P((int)); static void ipv6cp_close __P((int, char *)); static void ipv6cp_lowerup __P((int)); static void ipv6cp_lowerdown __P((int)); static void ipv6cp_input __P((int, u_char *, int)); static void ipv6cp_protrej __P((int)); static int ipv6cp_printpkt __P((u_char *, int, void (*) __P((void *, const char *, ...)), void *)); static void ipv6_check_options __P((void)); static int ipv6_demand_conf __P((int)); static int ipv6_active_pkt __P((u_char *, int)); struct protent ipv6cp_protent = { PPP_IPV6CP, /* Protocol Number for IPV6CP */ ipv6cp_init, /* Initializes IPV6CP */ ipv6cp_input, /* Processes a received IPV6CP packet */ ipv6cp_protrej, /* Process a received Protocol-reject */ ipv6cp_lowerup, /* Called when LCP is brought up */ ipv6cp_lowerdown, /* Called when LCP has gone down */ ipv6cp_open, /* Called when link is established */ ipv6cp_close, /* Called when link has gone down */ ipv6cp_printpkt, /* Print a packet in human readable form */ NULL, /* Process a received data packet */ 0, /* IPV6CP is disabled by default */ "IPV6CP", /* Name of the protocol */ "IPV6", /* Name of the corresponding data protocol */ ipv6cp_option_list, /* List of IPV6CP command-line options */ ipv6_check_options, /* Assigns default values for options */ ipv6_demand_conf, /* Configures demand-dial */ ipv6_active_pkt /* Bring up the link for this packet? */ }; /* * Local forward function declarations. */ static void ipv6cp_clear_addrs __P((int, eui64_t, eui64_t)); static void ipv6cp_script __P((char *)); static void ipv6cp_script_done __P((void *, int)); /* * Lengths of configuration options. */ #define CILEN_VOID 2 #define CILEN_COMPRESS 4 /* length for RFC2023 compress opt. */ #define CILEN_IFACEID 10 /* RFC2472, interface identifier */ #define CODENAME(x) ((x) == CODE_CONFACK ? "ACK" : \ (x) == CODE_CONFNAK ? "NAK" : "REJ") /* * This state variable is used to ensure that we don't * run an ipcp-up/down script while one is already running. */ static enum script_state { s_down, s_up } ipv6cp_script_state; static pid_t ipv6cp_script_pid; /* * setifaceid - set the interface identifiers manually */ /*ARGSUSED*/ static int setifaceid(argv, opt) char **argv; option_t *opt; { char *comma, *arg; ipv6cp_options *wo = &ipv6cp_wantoptions[0]; struct in6_addr addr; #define VALIDID(a) ( (((a).s6_addr32[0] == 0) && ((a).s6_addr32[1] == 0)) && \ (((a).s6_addr32[2] != 0) || ((a).s6_addr32[3] != 0)) ) arg = *argv; comma = strchr(arg, ','); /* * If comma first character, then no local identifier */ if (comma != arg) { if (comma != NULL) *comma = '\0'; if (inet_pton(AF_INET6, arg, &addr) != 1 || !VALIDID(addr)) { option_error("Illegal interface identifier (local): %s", arg); return 0; } eui64_copy(addr.s6_addr32[2], wo->ourid); wo->opt_local = 1; } /* * If comma last character, then no remote identifier */ if (comma != NULL && *++comma != '\0') { if (inet_pton(AF_INET6, comma, &addr) != 1 || !VALIDID(addr)) { option_error("Illegal interface identifier (remote): %s", comma); return 0; } eui64_copy(addr.s6_addr32[2], wo->hisid); wo->opt_remote = 1; } ipv6cp_protent.enabled_flag = 1; return 1; } /* * Given an interface identifier, return a string representation of the * link local address associated with that identifier. * string will be at most 26 characters (including null terminator). */ static char * llv6_ntoa(ifaceid) eui64_t ifaceid; { struct in6_addr addr; static char addrstr[26]; BZERO(&addr, sizeof (addr)); addr.s6_addr[0] = 0xfe; addr.s6_addr[1] = 0x80; eui64_copy(ifaceid, addr.s6_addr[8]); (void) inet_ntop(AF_INET6, &addr, addrstr, 26); return addrstr; } /* * ipv6cp_init - Initialize IPV6CP. */ static void ipv6cp_init(unit) int unit; { fsm *f = &ipv6cp_fsm[unit]; ipv6cp_options *wo = &ipv6cp_wantoptions[unit]; ipv6cp_options *ao = &ipv6cp_allowoptions[unit]; f->unit = unit; f->protocol = PPP_IPV6CP; f->callbacks = &ipv6cp_callbacks; fsm_init(&ipv6cp_fsm[unit]); BZERO(wo, sizeof(*wo)); BZERO(ao, sizeof(*ao)); wo->neg_ifaceid = 1; ao->neg_ifaceid = 1; #ifdef IPV6CP_COMP wo->neg_vj = 1; ao->neg_vj = 1; wo->vj_protocol = IPV6CP_COMP; #endif } /* * ipv6cp_open - IPV6CP is allowed to come up. */ static void ipv6cp_open(unit) int unit; { fsm_open(&ipv6cp_fsm[unit]); } /* * ipv6cp_close - Take IPV6CP down. */ static void ipv6cp_close(unit, reason) int unit; char *reason; { fsm_close(&ipv6cp_fsm[unit], reason); } /* * ipv6cp_lowerup - The lower layer is up. */ static void ipv6cp_lowerup(unit) int unit; { fsm_lowerup(&ipv6cp_fsm[unit]); } /* * ipv6cp_lowerdown - The lower layer is down. */ static void ipv6cp_lowerdown(unit) int unit; { fsm_lowerdown(&ipv6cp_fsm[unit]); } /* * ipv6cp_input - Input IPV6CP packet. */ static void ipv6cp_input(unit, p, len) int unit; u_char *p; int len; { fsm_input(&ipv6cp_fsm[unit], p, len); } /* * ipv6cp_protrej - A Protocol-Reject was received for IPV6CP. */ static void ipv6cp_protrej(unit) int unit; { fsm_protreject(&ipv6cp_fsm[unit]); } /* * ipv6cp_resetci - Reset our CI. */ static void ipv6cp_resetci(f) fsm *f; { ipv6cp_options *wo = &ipv6cp_wantoptions[f->unit]; ipv6cp_options *go = &ipv6cp_gotoptions[f->unit]; wo->req_ifaceid = wo->neg_ifaceid && ipv6cp_allowoptions[f->unit].neg_ifaceid; if (!wo->opt_local) { eui64_magic_nz(wo->ourid); } *go = *wo; eui64_zero(go->hisid); /* last proposed interface identifier */ } /* * ipv6cp_cilen - Return length of our CI. */ static int ipv6cp_cilen(f) fsm *f; { ipv6cp_options *go = &ipv6cp_gotoptions[f->unit]; #define LENCIVJ(neg) (neg ? CILEN_COMPRESS : 0) #define LENCIIFACEID(neg) (neg ? CILEN_IFACEID : 0) return (LENCIIFACEID(go->neg_ifaceid) + LENCIVJ(go->neg_vj)); } /* * ipv6cp_addci - Add our desired CIs to a packet. */ static void ipv6cp_addci(f, ucp, lenp) fsm *f; u_char *ucp; int *lenp; { ipv6cp_options *go = &ipv6cp_gotoptions[f->unit]; int len = *lenp; #define ADDCIVJ(opt, neg, val) \ if (neg) { \ int vjlen = CILEN_COMPRESS; \ if (len >= vjlen) { \ PUTCHAR(opt, ucp); \ PUTCHAR(vjlen, ucp); \ PUTSHORT(val, ucp); \ len -= vjlen; \ } else \ neg = 0; \ } #define ADDCIIFACEID(opt, neg, val1) \ if (neg) { \ int idlen = CILEN_IFACEID; \ if (len >= idlen) { \ PUTCHAR(opt, ucp); \ PUTCHAR(idlen, ucp); \ eui64_put(val1, ucp); \ len -= idlen; \ } else \ neg = 0; \ } ADDCIIFACEID(CI_IFACEID, go->neg_ifaceid, go->ourid); ADDCIVJ(CI_COMPRESSTYPE, go->neg_vj, go->vj_protocol); *lenp -= len; } /* * ipv6cp_ackci - Ack our CIs. * * Returns: * 0 - Ack was bad. * 1 - Ack was good. */ static int ipv6cp_ackci(f, p, len) fsm *f; u_char *p; int len; { ipv6cp_options *go = &ipv6cp_gotoptions[f->unit]; u_short cilen, citype, cishort; eui64_t ifaceid; /* * CIs must be in exactly the same order that we sent... * Check packet length and CI length at each step. * If we find any deviations, then this packet is bad. */ #define ACKCIVJ(opt, neg, val) \ if (neg) { \ int vjlen = CILEN_COMPRESS; \ if ((len -= vjlen) < 0) \ goto bad; \ GETCHAR(citype, p); \ GETCHAR(cilen, p); \ if (cilen != vjlen || \ citype != opt) \ goto bad; \ GETSHORT(cishort, p); \ if (cishort != val) \ goto bad; \ } #define ACKCIIFACEID(opt, neg, val1) \ if (neg) { \ int idlen = CILEN_IFACEID; \ if ((len -= idlen) < 0) \ goto bad; \ GETCHAR(citype, p); \ GETCHAR(cilen, p); \ if (cilen != idlen || \ citype != opt) \ goto bad; \ eui64_get(ifaceid, p); \ if (! eui64_equals(val1, ifaceid)) \ goto bad; \ } ACKCIIFACEID(CI_IFACEID, go->neg_ifaceid, go->ourid); ACKCIVJ(CI_COMPRESSTYPE, go->neg_vj, go->vj_protocol); /* * If there are any remaining CIs, then this packet is bad. */ if (len != 0) goto bad; return (1); bad: IPV6CPDEBUG(("ipv6cp_ackci: received bad Ack!")); return (0); } /* * ipv6cp_nakci - Peer has sent a NAK for some of our CIs. * This should not modify any state if the Nak is bad * or if IPV6CP is in the OPENED state. * * Returns: * 0 - Nak was bad. * 1 - Nak was good. */ static int ipv6cp_nakci(f, p, len) fsm *f; u_char *p; int len; { ipv6cp_options *go = &ipv6cp_gotoptions[f->unit]; u_char citype, cilen, *next; u_short cishort; eui64_t ifaceid; ipv6cp_options no; /* options we've seen Naks for */ ipv6cp_options try; /* options to request next time */ BZERO(&no, sizeof(no)); try = *go; /* * Any Nak'd CIs must be in exactly the same order that we sent. * Check packet length and CI length at each step. * If we find any deviations, then this packet is bad. */ #define NAKCIIFACEID(opt, neg, code) \ if (go->neg && \ len >= (cilen = CILEN_IFACEID) && \ p[1] == cilen && \ p[0] == opt) { \ len -= cilen; \ INCPTR(2, p); \ eui64_get(ifaceid, p); \ no.neg = 1; \ code \ } #define NAKCIVJ(opt, neg, code) \ if (go->neg && \ ((cilen = p[1]) == CILEN_COMPRESS) && \ len >= cilen && \ p[0] == opt) { \ len -= cilen; \ INCPTR(2, p); \ GETSHORT(cishort, p); \ no.neg = 1; \ code \ } /* * Accept the peer's idea of {our,its} interface identifier, if different * from our idea, only if the accept_{local,remote} flag is set. */ NAKCIIFACEID(CI_IFACEID, neg_ifaceid, if (go->accept_local) { while (eui64_iszero(ifaceid) || eui64_equals(ifaceid, go->hisid)) /* bad luck */ eui64_magic(ifaceid); try.ourid = ifaceid; IPV6CPDEBUG(("local LL address %s", llv6_ntoa(ifaceid))); } ); #ifdef IPV6CP_COMP NAKCIVJ(CI_COMPRESSTYPE, neg_vj, { if (cishort == IPV6CP_COMP) { try.vj_protocol = cishort; } else { try.neg_vj = 0; } } ); #else NAKCIVJ(CI_COMPRESSTYPE, neg_vj, { try.neg_vj = 0; } ); #endif /* * There may be remaining CIs, if the peer is requesting negotiation * on an option that we didn't include in our request packet. * If they want to negotiate about interface identifier, we comply. * If they want us to ask for compression, we refuse. */ while (len > CILEN_VOID) { GETCHAR(citype, p); GETCHAR(cilen, p); if( (len -= cilen) < 0 ) goto bad; next = p + cilen - 2; switch (citype) { case CI_COMPRESSTYPE: if (go->neg_vj || no.neg_vj || (cilen != CILEN_COMPRESS)) goto bad; no.neg_vj = 1; break; case CI_IFACEID: if (go->neg_ifaceid || no.neg_ifaceid || cilen != CILEN_IFACEID) goto bad; try.neg_ifaceid = 1; eui64_get(ifaceid, p); if (go->accept_local) { while (eui64_iszero(ifaceid) || eui64_equals(ifaceid, go->hisid)) /* bad luck */ eui64_magic(ifaceid); try.ourid = ifaceid; } no.neg_ifaceid = 1; break; } p = next; } /* If there is still anything left, this packet is bad. */ if (len != 0) goto bad; /* * OK, the Nak is good. Now we can update state. */ if (f->state != OPENED) *go = try; return 1; bad: IPV6CPDEBUG(("ipv6cp_nakci: received bad Nak!")); return 0; } /* * ipv6cp_rejci - Reject some of our CIs. */ static int ipv6cp_rejci(f, p, len) fsm *f; u_char *p; int len; { ipv6cp_options *go = &ipv6cp_gotoptions[f->unit]; u_char cilen; u_short cishort; eui64_t ifaceid; ipv6cp_options try; /* options to request next time */ try = *go; /* * Any Rejected CIs must be in exactly the same order that we sent. * Check packet length and CI length at each step. * If we find any deviations, then this packet is bad. */ #define REJCIIFACEID(opt, neg, val1) \ if (go->neg && \ len >= (cilen = CILEN_IFACEID) && \ p[1] == cilen && \ p[0] == opt) { \ len -= cilen; \ INCPTR(2, p); \ eui64_get(ifaceid, p); \ /* Check rejected value. */ \ if (! eui64_equals(ifaceid, val1)) \ goto bad; \ try.neg = 0; \ } #define REJCIVJ(opt, neg, val) \ if (go->neg && \ p[1] == CILEN_COMPRESS && \ len >= p[1] && \ p[0] == opt) { \ len -= p[1]; \ INCPTR(2, p); \ GETSHORT(cishort, p); \ /* Check rejected value. */ \ if (cishort != val) \ goto bad; \ try.neg = 0; \ } REJCIIFACEID(CI_IFACEID, neg_ifaceid, go->ourid); REJCIVJ(CI_COMPRESSTYPE, neg_vj, go->vj_protocol); /* * If there are any remaining CIs, then this packet is bad. */ if (len != 0) goto bad; /* * Now we can update state. */ if (f->state != OPENED) *go = try; return 1; bad: IPV6CPDEBUG(("ipv6cp_rejci: received bad Reject!")); return 0; } /* * ipv6cp_reqci - Check the peer's requested CIs and send appropriate response. * * Returns: CODE_CONFACK, CODE_CONFNAK or CODE_CONFREJ and input packet modified * appropriately. If reject_if_disagree is non-zero, doesn't return * CODE_CONFNAK; returns CODE_CONFREJ if it can't return CODE_CONFACK. */ static int ipv6cp_reqci(f, p, lenp, dont_nak) fsm *f; u_char *p; /* Requested CIs */ int *lenp; /* Length of requested CIs */ int dont_nak; { ipv6cp_options *wo = &ipv6cp_wantoptions[f->unit]; ipv6cp_options *ho = &ipv6cp_hisoptions[f->unit]; ipv6cp_options *ao = &ipv6cp_allowoptions[f->unit]; ipv6cp_options *go = &ipv6cp_gotoptions[f->unit]; u_char *p0, *nakp, *rejp, *prev; int ret, newret; int len, cilen, type; eui64_t ifaceid; u_short cishort; ret = CODE_CONFACK; rejp = p0 = p; nakp = nak_buffer; /* * Reset all its options. */ BZERO(ho, sizeof(*ho)); /* * Process all its options. */ for (len = *lenp; len > 0; len -= cilen, p = prev + cilen) { newret = CODE_CONFACK; if ((len < 2) || p[1] > len) { /* * RFC 1661 page 40 -- if the option extends beyond the * packet, then discard the entire packet. */ return (0); } prev = p; GETCHAR(type, p); GETCHAR(cilen, p); switch (type) { /* Check CI type */ case CI_IFACEID: IPV6CPDEBUG(("ipv6cp: received interface identifier ")); if (!ao->neg_ifaceid) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_IFACEID) { /* * rfc1661, page 40 -- a recongnized option with an * invalid length should be Nak'ed. */ newret = CODE_CONFNAK; eui64_copy(wo->hisid, ifaceid); } else { /* * If it has no interface identifier, or if we both * have same identifier then NAK it with new idea. In * particular, if we don't know its identifier, but it * does, then accept that identifier. */ eui64_get(ifaceid, p); IPV6CPDEBUG(("(%s)", llv6_ntoa(ifaceid))); if (eui64_iszero(ifaceid) && eui64_iszero(go->ourid)) { newret = CODE_CONFREJ; /* Reject CI */ break; } /* If we don't like its ID, then nak that ID. */ if (!eui64_iszero(wo->hisid) && !eui64_equals(ifaceid, wo->hisid) && eui64_iszero(go->hisid)) { newret = CODE_CONFNAK; eui64_copy(wo->hisid, ifaceid); } else if (eui64_iszero(ifaceid) || eui64_equals(ifaceid, go->ourid)) { newret = CODE_CONFNAK; /* first time, try option */ if (eui64_iszero(go->hisid)) eui64_copy(wo->hisid, ifaceid); while (eui64_iszero(ifaceid) || eui64_equals(ifaceid, go->ourid)) /* bad luck */ eui64_magic(ifaceid); } } if (newret == CODE_CONFNAK) { PUTCHAR(type, nakp); PUTCHAR(CILEN_IFACEID, nakp); eui64_put(ifaceid, nakp); } ho->neg_ifaceid = 1; eui64_copy(ifaceid, ho->hisid); break; case CI_COMPRESSTYPE: IPV6CPDEBUG(("ipv6cp: received COMPRESSTYPE ")); if (!ao->neg_vj) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_COMPRESS) { newret = CODE_CONFNAK; cishort = ao->vj_protocol; } else { GETSHORT(cishort, p); IPV6CPDEBUG(("(%d)", cishort)); #ifdef IPV6CP_COMP if (cishort != IPV6CP_COMP) { newret = CODE_CONFNAK; cishort = IPV6CP_COMP; } #else newret = CODE_CONFREJ; break; #endif } ho->neg_vj = 1; ho->vj_protocol = cishort; break; default: newret = CODE_CONFREJ; break; } IPV6CPDEBUG((" (%s)\n", CODENAME(newret))); /* Cope with confused peers. */ if (cilen < 2) cilen = 2; /* * If this is an Ack'able CI, but we're sending back a Nak, * don't include this CI. */ if (newret == CODE_CONFACK && ret != CODE_CONFACK) continue; if (newret == CODE_CONFNAK) { if (dont_nak) { newret = CODE_CONFREJ; } else { /* Ignore subsequent Nak'able things if rejecting. */ if (ret == CODE_CONFREJ) continue; ret = CODE_CONFNAK; } } if (newret == CODE_CONFREJ) { ret = CODE_CONFREJ; if (prev != rejp) (void) BCOPY(prev, rejp, cilen); rejp += cilen; } } /* * If we aren't rejecting this packet, and we want to negotiate * their identifier and they didn't send their identifier, then we * send a NAK with a CI_IFACEID option appended. We assume the * input buffer is long enough that we can append the extra * option safely. */ if (ret != CODE_CONFREJ && !ho->neg_ifaceid && wo->req_ifaceid && !dont_nak) { if (ret == CODE_CONFACK) wo->req_ifaceid = 0; ret = CODE_CONFNAK; PUTCHAR(CI_IFACEID, nakp); PUTCHAR(CILEN_IFACEID, nakp); eui64_put(wo->hisid, nakp); } switch (ret) { case CODE_CONFACK: *lenp = p - p0; sys_block_proto(PPP_IPV6); break; case CODE_CONFNAK: *lenp = nakp - nak_buffer; (void) BCOPY(nak_buffer, p0, *lenp); break; case CODE_CONFREJ: *lenp = rejp - p0; break; } IPV6CPDEBUG(("ipv6cp: returning Configure-%s", CODENAME(ret))); return (ret); /* Return final code */ } /* * ipv6_check_options - check that any IP-related options are OK, * and assign appropriate defaults. */ static void ipv6_check_options() { ipv6cp_options *wo = &ipv6cp_wantoptions[0]; #if defined(SOL2) /* * Persistent link-local id is only used when user has not explicitly * configure/hard-code the id */ if ((wo->use_persistent) && (!wo->opt_local) && (!wo->opt_remote)) { /* * On systems where there are no Ethernet interfaces used, there * may be other ways to obtain a persistent id. Right now, it * will fall back to using magic [see eui64_magic] below when * an EUI-48 from MAC address can't be obtained. Other possibilities * include obtaining EEPROM serial numbers, or some other unique * yet persistent number. On Sparc platforms, this is possible, * but too bad there's no standards yet for x86 machines. */ if (ether_to_eui64(&wo->ourid)) { wo->opt_local = 1; } } #endif /* * If ipv6cp-use-ipaddr is used, then both local and remote IPv4 * addresses should be specified as options. Otherwise, since * ipcp has yet to negotiate the IPv4 addresses, the interface * identifiers will be based on meaningless values. */ if (wo->use_ip) { if ((ipcp_wantoptions[0].accept_local || ipcp_wantoptions[0].ouraddr == 0) && eui64_iszero(wo->ourid)) { warn("either IPv4 or IPv6 local address should be non-zero for ipv6cp-use-ipaddr"); } if ((ipcp_wantoptions[0].accept_remote || ipcp_wantoptions[0].hisaddr == 0) && eui64_iszero(wo->hisid)) { warn("either IPv4 or IPv6 remote address should be non-zero for ipv6cp-use-ipaddr"); } } if (!wo->opt_local) { /* init interface identifier */ if (wo->use_ip && eui64_iszero(wo->ourid)) { eui64_setlo32(wo->ourid, ntohl(ipcp_wantoptions[0].ouraddr)); if (!eui64_iszero(wo->ourid)) wo->opt_local = 1; } while (eui64_iszero(wo->ourid)) eui64_magic(wo->ourid); } if (!wo->opt_remote) { if (wo->use_ip && eui64_iszero(wo->hisid)) { eui64_setlo32(wo->hisid, ntohl(ipcp_wantoptions[0].hisaddr)); if (!eui64_iszero(wo->hisid)) wo->opt_remote = 1; } } if (demand && (eui64_iszero(wo->ourid) || eui64_iszero(wo->hisid))) { fatal("local/remote LL address required for demand-dialling\n"); } } /* * ipv6_demand_conf - configure the interface as though * IPV6CP were up, for use with dial-on-demand. */ static int ipv6_demand_conf(u) int u; { ipv6cp_options *wo = &ipv6cp_wantoptions[u]; #if SIF6UPFIRST if (!sif6up(u)) return 0; #endif if (!sif6addr(u, wo->ourid, wo->hisid)) return 0; #if !SIF6UPFIRST if (!sif6up(u)) return 0; #endif if (!sifnpmode(u, PPP_IPV6, NPMODE_QUEUE)) return 0; notice("local LL address %s", llv6_ntoa(wo->ourid)); notice("remote LL address %s", llv6_ntoa(wo->hisid)); return 1; } /* * ipv6cp_up - IPV6CP has come UP. * * Configure the IPv6 network interface appropriately and bring it up. */ static void ipv6cp_up(f) fsm *f; { ipv6cp_options *ho = &ipv6cp_hisoptions[f->unit]; ipv6cp_options *go = &ipv6cp_gotoptions[f->unit]; ipv6cp_options *wo = &ipv6cp_wantoptions[f->unit]; IPV6CPDEBUG(("ipv6cp: up")); /* * We must have a non-zero LL address for both ends of the link. */ if (!ho->neg_ifaceid) ho->hisid = wo->hisid; if(!no_ifaceid_neg) { if (eui64_iszero(ho->hisid)) { error("Could not determine remote LL address"); ipv6cp_close(f->unit, "Could not determine remote LL address"); return; } if (eui64_iszero(go->ourid)) { error("Could not determine local LL address"); ipv6cp_close(f->unit, "Could not determine local LL address"); return; } if (eui64_equals(go->ourid, ho->hisid)) { error("local and remote LL addresses are equal"); ipv6cp_close(f->unit, "local and remote LL addresses are equal"); return; } } #ifdef IPV6CP_COMP /* set tcp compression */ if (sif6comp(f->unit, ho->neg_vj) != 1) { ipv6cp_close(f->unit, "Could not enable TCP compression"); return; } #endif /* * If we are doing dial-on-demand, the interface is already * configured, so we put out any saved-up packets, then set the * interface to pass IPv6 packets. */ if (demand) { if (! eui64_equals(go->ourid, wo->ourid) || ! eui64_equals(ho->hisid, wo->hisid)) { if (! eui64_equals(go->ourid, wo->ourid)) warn("Local LL address changed to %s", llv6_ntoa(go->ourid)); if (! eui64_equals(ho->hisid, wo->hisid)) warn("Remote LL address changed to %s", llv6_ntoa(ho->hisid)); ipv6cp_clear_addrs(f->unit, go->ourid, ho->hisid); /* Set the interface to the new addresses */ if (!sif6addr(f->unit, go->ourid, ho->hisid)) { if (debug) warn("sif6addr failed"); ipv6cp_close(f->unit, "Interface configuration failed"); return; } } demand_rexmit(PPP_IPV6); if (sifnpmode(f->unit, PPP_IPV6, NPMODE_PASS) != 1) { ipv6cp_close(f->unit, "Interface configuration failed"); return; } } else { /* * Set LL addresses */ #if !SIF6UPFIRST if (!sif6addr(f->unit, go->ourid, ho->hisid)) { if (debug) warn("sif6addr failed"); ipv6cp_close(f->unit, "Interface configuration failed"); return; } #endif #if defined(SOL2) /* bring the interface up for IPv6 */ if (!sif6up(f->unit)) { if (debug) warn("sifup failed (IPV6)"); ipv6cp_close(f->unit, "Interface configuration failed"); return; } #else if (!sifup(f->unit)) { if (debug) warn("sifup failed (IPV6)"); ipv6cp_close(f->unit, "Interface configuration failed"); return; } #endif #if SIF6UPFIRST if (!sif6addr(f->unit, go->ourid, ho->hisid)) { if (debug) warn("sif6addr failed"); ipv6cp_close(f->unit, "Interface configuration failed"); return; } #endif if (sifnpmode(f->unit, PPP_IPV6, NPMODE_PASS) != 1) { ipv6cp_close(f->unit, "Interface configuration failed"); return; } notice("local LL address %s", llv6_ntoa(go->ourid)); notice("remote LL address %s", llv6_ntoa(ho->hisid)); } np_up(f->unit, PPP_IPV6); ipv6cp_is_up = 1; /* * Execute the ipv6-up script, like this: * /etc/ppp/ipv6-up interface tty speed local-LL remote-LL */ script_setenv("LLLOCAL", llv6_ntoa(go->ourid), 0); script_setenv("LLREMOTE", llv6_ntoa(ho->hisid), 0); if (ipv6cp_script_state == s_down && ipv6cp_script_pid == 0) { ipv6cp_script_state = s_up; ipv6cp_script(_PATH_IPV6UP); } sys_unblock_proto(PPP_IPV6); } /* * ipv6cp_down - IPV6CP has gone DOWN. * * Take the IPv6 network interface down, clear its addresses * and delete routes through it. */ static void ipv6cp_down(f) fsm *f; { IPV6CPDEBUG(("ipv6cp: down")); update_link_stats(f->unit); if (ipv6cp_is_up) { ipv6cp_is_up = 0; np_down(f->unit, PPP_IPV6); } #ifdef IPV6CP_COMP if (sif6comp(f->unit, 0) != 1) { if (debug) warn("Failed to disable TCP compression."); } #endif /* * If we are doing dial-on-demand, set the interface * to queue up outgoing packets (for now). */ if (demand) { if (sifnpmode(f->unit, PPP_IPV6, NPMODE_QUEUE) != 1) { if (debug) warn("Failed to enable queueing on outgoing packets."); } } else { if (sifnpmode(f->unit, PPP_IPV6, NPMODE_ERROR) != 1) { if (debug) warn("Could not set interface to drop packets."); } #if !defined(__linux__) && !(defined(SVR4) && (defined(SNI) || defined(__USLC))) #if defined(SOL2) if (sif6down(f->unit) != 1) warn("Couldn not bring interface down."); #else if (sifdown(f->unit) != 1) warn("Could not bring interface down."); #endif /* defined(SOL2) */ #endif ipv6cp_clear_addrs(f->unit, ipv6cp_gotoptions[f->unit].ourid, ipv6cp_hisoptions[f->unit].hisid); #if defined(__linux__) || (defined(SVR4) && (defined(SNI) || defined(__USLC))) if (sifdown(f->unit) != 1) warn("Could not bring interface down."); #endif } /* Execute the ipv6-down script */ if (ipv6cp_script_state == s_up && ipv6cp_script_pid == 0) { ipv6cp_script_state = s_down; ipv6cp_script(_PATH_IPV6DOWN); } } /* * ipv6cp_clear_addrs() - clear the interface addresses, routes, * proxy neighbour discovery entries, etc. */ static void ipv6cp_clear_addrs(unit, ourid, hisid) int unit; eui64_t ourid; eui64_t hisid; { if (cif6addr(unit, ourid, hisid) != 1) warn("Could not clear addresses"); } /* * ipv6cp_finished - possibly shut down the lower layers. */ static void ipv6cp_finished(f) fsm *f; { np_finished(f->unit, PPP_IPV6); } /* * ipv6cp_script_done - called when the ipv6-up or ipv6-down script * has finished. */ /*ARGSUSED*/ static void ipv6cp_script_done(arg, status) void *arg; int status; { ipv6cp_script_pid = 0; switch (ipv6cp_script_state) { case s_up: if (ipv6cp_fsm[0].state != OPENED) { ipv6cp_script_state = s_down; ipv6cp_script(_PATH_IPV6DOWN); } break; case s_down: if (ipv6cp_fsm[0].state == OPENED) { ipv6cp_script_state = s_up; ipv6cp_script(_PATH_IPV6UP); } break; } } /* * ipv6cp_script - Execute a script with arguments * interface-name tty-name speed local-LL remote-LL. */ static void ipv6cp_script(script) char *script; { char strspeed[32], strlocal[26], strremote[26]; char *argv[8]; (void) slprintf(strspeed, sizeof (strspeed), "%d", baud_rate); (void) strlcpy(strlocal, llv6_ntoa(ipv6cp_gotoptions[0].ourid), sizeof (strlocal)); (void) strlcpy(strremote, llv6_ntoa(ipv6cp_hisoptions[0].hisid), sizeof (strremote)); argv[0] = script; argv[1] = ifname; argv[2] = devnam; argv[3] = strspeed; argv[4] = strlocal; argv[5] = strremote; argv[6] = ipparam; argv[7] = NULL; ipv6cp_script_pid = run_program(script, argv, 0, ipv6cp_script_done, NULL); } static int ipv6cp_printpkt(p, plen, printer, arg) u_char *p; int plen; void (*printer) __P((void *, const char *, ...)); void *arg; { int code, id, len, olen; u_char *pstart, *optend; u_short cishort; eui64_t ifaceid; if (plen < HEADERLEN) return 0; pstart = p; GETCHAR(code, p); GETCHAR(id, p); GETSHORT(len, p); if (len < HEADERLEN || len > plen) return 0; printer(arg, " %s id=0x%x", code_name(code, 1), id); len -= HEADERLEN; switch (code) { case CODE_CONFREQ: case CODE_CONFACK: case CODE_CONFNAK: case CODE_CONFREJ: /* print option list */ while (len >= 2) { GETCHAR(code, p); GETCHAR(olen, p); p -= 2; if (olen < 2 || olen > len) { break; } printer(arg, " <"); len -= olen; optend = p + olen; switch (code) { case CI_COMPRESSTYPE: if (olen >= CILEN_COMPRESS) { p += 2; GETSHORT(cishort, p); printer(arg, "compress 0x%x", cishort); } break; case CI_IFACEID: if (olen == CILEN_IFACEID) { p += 2; eui64_get(ifaceid, p); printer(arg, "addr %s", llv6_ntoa(ifaceid)); } break; } printer(arg, "%8.*B>", optend-p, p); p = optend; } break; case CODE_TERMACK: case CODE_TERMREQ: if (len > 0 && *p >= ' ' && *p < 0x7f) { printer(arg, " "); print_string((char *)p, len, printer, arg); p += len; len = 0; } break; } /* print the rest of the bytes in the packet */ printer(arg, " %32.*B", len, p); return p - pstart; } /* * ipv6_active_pkt - see if this IP packet is worth bringing the link up for. * We don't bring the link up for IP fragments or for TCP FIN packets * with no data. */ #define TCP_HDRLEN 20 #define TH_FIN 0x01 static int ipv6_active_pkt(pkt, len) u_char *pkt; int len; { u_char *tcp; struct in6_addr addr; char fromstr[26]; char tostr[26]; len -= PPP_HDRLEN; pkt += PPP_HDRLEN; if (len < IP6_HDRLEN) { dbglog("IPv6 packet of length %d is not activity", len); return 0; } (void) BCOPY(get_ip6src(pkt), &addr, sizeof (addr)); (void) inet_ntop(AF_INET6, &addr, fromstr, 26); (void) BCOPY(get_ip6dst(pkt), &addr, sizeof (addr)); (void) inet_ntop(AF_INET6, &addr, tostr, 26); if (get_ip6nh(pkt) == IPPROTO_FRAGMENT) { dbglog("IPv6 fragment from %s->%s is not activity", fromstr, tostr); return 0; } if (get_ip6nh(pkt) != IPPROTO_TCP) { info("IPv6 proto %d from %s->%s is activity", get_ip6nh(pkt), fromstr, tostr); return 1; } if (len < IP6_HDRLEN + TCP_HDRLEN) { dbglog("Bad TCP length %d<%d+%d %s->%s is not activity", len, IP6_HDRLEN, TCP_HDRLEN, fromstr, tostr); return 0; } tcp = pkt + IP6_HDRLEN; if ((get_tcpflags(tcp) & TH_FIN) != 0 && len == IP6_HDRLEN + get_tcpoff(tcp) * 4) { dbglog("Empty TCP FIN %s->%s is not activity", fromstr, tostr); return 0; } info("TCP %d data %s%s->%s is activity", len - IP6_HDRLEN - TCP_HDRLEN, tcp_flag_decode(get_tcpflags(tcp)), fromstr, tostr); return 1; } /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * ipv6cp.h - PPP IPV6 Control Protocol. Copyright (C) 1999 Tommi Komulainen 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. The name of the author 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. */ /* Original version, based on RFC2023 : Copyright (c) 1995, 1996, 1997 Francis.Dupont@inria.fr, INRIA Rocquencourt, Alain.Durand@imag.fr, IMAG, Jean-Luc.Richier@imag.fr, IMAG-LSR. Copyright (c) 1998, 1999 Francis.Dupont@inria.fr, GIE DYADE, Alain.Durand@imag.fr, IMAG, Jean-Luc.Richier@imag.fr, IMAG-LSR. Ce travail a été fait au sein du GIE DYADE (Groupement d'Intérêt Économique ayant pour membres BULL S.A. et l'INRIA). Ce logiciel informatique est disponible aux conditions usuelles dans la recherche, c'est-à-dire qu'il peut être utilisé, copié, modifié, distribué à l'unique condition que ce texte soit conservé afin que l'origine de ce logiciel soit reconnue. Le nom de l'Institut National de Recherche en Informatique et en Automatique (INRIA), de l'IMAG, ou d'une personne morale ou physique ayant participé à l'élaboration de ce logiciel ne peut être utilisé sans son accord préalable explicite. Ce logiciel est fourni tel quel sans aucune garantie, support ou responsabilité d'aucune sorte. Ce logiciel est dérivé de sources d'origine "University of California at Berkeley" et "Digital Equipment Corporation" couvertes par des copyrights. L'Institut d'Informatique et de Mathématiques Appliquées de Grenoble (IMAG) est une fédération d'unités mixtes de recherche du CNRS, de l'Institut National Polytechnique de Grenoble et de l'Université Joseph Fourier regroupant sept laboratoires dont le laboratoire Logiciels, Systèmes, Réseaux (LSR). This work has been done in the context of GIE DYADE (joint R & D venture between BULL S.A. and INRIA). This software is available with usual "research" terms with the aim of retain credits of the software. Permission to use, copy, modify and distribute this software for any purpose and without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies, and the name of INRIA, IMAG, or any contributor not be used in advertising or publicity pertaining to this material without the prior explicit permission. The software is provided "as is" without any warranties, support or liabilities of any kind. This software is derived from source code from "University of California at Berkeley" and "Digital Equipment Corporation" protected by copyrights. Grenoble's Institute of Computer Science and Applied Mathematics (IMAG) is a federation of seven research units funded by the CNRS, National Polytechnic Institute of Grenoble and University Joseph Fourier. The research unit in Software, Systems, Networks (LSR) is member of IMAG. */ /* * Derived from : * * * ipcp.h - IP Control Protocol definitions. * * 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. * * $Id: ipv6cp.h,v 1.3 1999/09/30 19:57:45 masputra Exp $ */ #include /* * Options. */ #define CI_IFACEID 1 /* Interface Identifier */ #define CI_COMPRESSTYPE 2 /* Compression Type */ /* *#define IPV6CP_COMP 0x0061 */ typedef struct ipv6cp_options { bool neg_ifaceid; /* Negotiate interface identifier? */ bool req_ifaceid; /* Ask peer to send interface identifier? */ bool accept_local; /* accept peer's value for iface id? */ bool opt_local; /* ourtoken set by option */ bool opt_remote; /* histoken set by option */ bool use_ip; /* use IP as interface identifier */ #if defined(SOL2) bool use_persistent; /* use uniquely persistent value for address */ #endif /* defined(SOL2) */ bool neg_vj; /* Van Jacobson Compression? */ u_short vj_protocol; /* protocol value to use in VJ option */ eui64_t ourid, hisid; /* Interface identifiers */ } ipv6cp_options; extern fsm ipv6cp_fsm[]; extern ipv6cp_options ipv6cp_wantoptions[]; extern ipv6cp_options ipv6cp_gotoptions[]; extern ipv6cp_options ipv6cp_allowoptions[]; extern ipv6cp_options ipv6cp_hisoptions[]; extern struct protent ipv6cp_protent; #define IP6_HDRLEN (sizeof (struct ip6_hdr)) /* bytes */ /* * lcp.c - PPP Link Control Protocol. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright (c) 1989 Carnegie Mellon University. * All rights reserved. * * Copyright (c) 2016 by Delphix. 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. */ #include #include #include #include #if defined(CHAPMS) || defined(CHAPMSV2) #ifdef HAVE_CRYPT_H #include #endif #ifndef USE_CRYPT #include #endif #ifdef SOL2 #include #endif #endif #include "pppd.h" #include "fsm.h" #include "lcp.h" #include "chap.h" #include "magic.h" #include "patchlevel.h" /* * Special failure codes for logging link failure reasons. */ bool peer_nak_auth; /* Peer sent nak for our auth request */ u_short nak_auth_orig; /* Auth proto peer naked */ u_short nak_auth_proto; /* Auth proto peer suggested instead */ bool unsolicited_nak_auth; /* Peer asked us to authenticate */ u_short unsolicit_auth_proto; /* Auth proto peer wants */ bool peer_reject_auth; /* Peer sent reject for auth */ u_short reject_auth_proto; /* Protocol that peer rejected */ bool rejected_peers_auth; /* We sent a reject to the peer */ u_short rejected_auth_proto; /* Protocol that peer wanted to use */ bool naked_peers_auth; /* We sent a nak to the peer */ u_short naked_auth_orig; /* Protocol that we wanted to use */ u_short naked_auth_proto; /* Protocol that peer wants us to use */ /* * LCP-related command-line options. */ int lcp_echo_interval = 0; /* Interval between LCP echo-requests */ int lcp_echo_fails = 0; /* Tolerance to unanswered echo-requests */ bool lax_recv = 0; /* accept control chars in asyncmap */ static int use_accm_test = 2; /* use big echo-requests to check ACCM */ #define ACCM_TEST_FAILS 5 #define _tostr2(x) #x #define _tostr(x) _tostr2(x) static char identstr[256] = /* Identification string */ "ppp-" VERSION "." _tostr(PATCHLEVEL) IMPLEMENTATION; static int noident = 0; /* 1 to disable; 2 to reject */ static int sentident = 0; /* counts the # of ident codes sent */ /* set if we're allowed to send an unsolicited Configure-Nak for MRU. */ static bool unsolicit_mru; static int setescape __P((char **, option_t *)); static bool do_msft_workaround = 1; static int setasyncmap __P((char **, option_t *)); bool noendpoint = 0; /* don't send/accept endpoint discriminator */ static int setendpoint __P((char **, option_t *)); static char *callback_strings[] = { "auth", "dialstring", "location", "E.164", "X.500", "", "CBCP", NULL }; /* This is used in packet printing even if NEGOTIATE_FCS isn't enabled */ static char *fcsalt_strings[] = { "null", "crc16", "crc32", NULL }; #ifdef NEGOTIATE_FCS static int setfcsallow __P((char **, option_t *)); static int setfcswant __P((char **, option_t *)); #endif /* Backward compatibility for Linux */ #ifndef PPP_MAXMRU #define PPP_MTU 1500 /* Default MTU (size of Info field) */ #define PPP_MAXMTU 65535 - (PPP_HDRLEN + PPP_FCSLEN) #define PPP_MINMTU 64 #define PPP_MAXMRU 65000 /* Largest MRU we allow */ #define PPP_MINMRU 128 #endif static option_t lcp_option_list[] = { /* LCP options */ { "noaccomp", o_bool, &lcp_wantoptions[0].neg_accompression, "Disable address/control compression", OPT_A2COPY, &lcp_allowoptions[0].neg_accompression }, { "-ac", o_bool, &lcp_wantoptions[0].neg_accompression, "Disable address/control compression", OPT_A2COPY, &lcp_allowoptions[0].neg_accompression }, { "default-asyncmap", o_bool, &lcp_wantoptions[0].neg_asyncmap, "Disable asyncmap negotiation", OPT_A2COPY, &lcp_allowoptions[0].neg_asyncmap }, { "-am", o_bool, &lcp_wantoptions[0].neg_asyncmap, "Disable asyncmap negotiation", OPT_A2COPY, &lcp_allowoptions[0].neg_asyncmap }, { "asyncmap", o_special, (void *)setasyncmap, "Set asyncmap (for received packets)" }, { "-as", o_special, (void *)setasyncmap, "Set asyncmap (for received packets)" }, { "nomagic", o_bool, &lcp_wantoptions[0].neg_magicnumber, "Disable magic number option (looped-back line detect)", OPT_A2COPY, &lcp_allowoptions[0].neg_magicnumber }, { "-mn", o_bool, &lcp_wantoptions[0].neg_magicnumber, "Disable magic number option (looped-back line detect)", OPT_A2COPY, &lcp_allowoptions[0].neg_magicnumber }, { "default-mru", o_bool, &lcp_wantoptions[0].neg_mru, "Disable MRU negotiation (use default 1500)", OPT_A2COPY, &lcp_allowoptions[0].neg_mru }, { "-mru", o_bool, &lcp_wantoptions[0].neg_mru, "Disable MRU negotiation (use default 1500)", OPT_A2COPY, &lcp_allowoptions[0].neg_mru }, { "mru", o_int, &lcp_wantoptions[0].mru, "Set MRU (maximum received packet size) for negotiation", OPT_LIMITS, &lcp_wantoptions[0].neg_mru, PPP_MAXMRU, PPP_MINMRU }, { "mtu", o_int, &lcp_allowoptions[0].mru, "Set our MTU", OPT_LIMITS|OPT_A2COPY, &lcp_allowoptions[0].mrru, PPP_MAXMTU, PPP_MINMTU }, { "nopcomp", o_bool, &lcp_wantoptions[0].neg_pcompression, "Disable protocol field compression", OPT_A2COPY, &lcp_allowoptions[0].neg_pcompression }, { "-pc", o_bool, &lcp_wantoptions[0].neg_pcompression, "Disable protocol field compression", OPT_A2COPY, &lcp_allowoptions[0].neg_pcompression }, { "-p", o_bool, &lcp_wantoptions[0].passive, "Set passive mode", 1 }, { "passive", o_bool, &lcp_wantoptions[0].passive, "Set passive mode", 1 }, { "silent", o_bool, &lcp_wantoptions[0].silent, "Set silent mode", 1 }, { "escape", o_special, (void *)setescape, "List of character codes to escape on transmission" }, { "lcp-echo-failure", o_int, &lcp_echo_fails, "Number of consecutive echo failures for link failure" }, { "lcp-echo-interval", o_int, &lcp_echo_interval, "Set time in seconds between LCP echo requests" }, { "no-accm-test", o_int, &use_accm_test, "Disable use of LCP Echo-Request asyncmap checking", OPT_NOARG|OPT_VAL(0) }, { "small-accm-test", o_int, &use_accm_test, "Use only small Echo-Requests for asyncmap checking", OPT_NOARG|OPT_VAL(1) }, { "lcp-restart", o_int, &lcp_fsm[0].timeouttime, "Set time in seconds between LCP retransmissions" }, { "lcp-max-terminate", o_int, &lcp_fsm[0].maxtermtransmits, "Maximum number of LCP terminate-request transmissions" }, { "lcp-max-configure", o_int, &lcp_fsm[0].maxconfreqtransmits, "Maximum number of LCP configure-request transmissions" }, { "lcp-max-failure", o_int, &lcp_fsm[0].maxnakloops, "Set limit on number of LCP configure-naks" }, { "receive-all", o_bool, &lax_recv, "Accept all received control characters", 1 }, #ifdef HAVE_MULTILINK { "mrru", o_int, &lcp_wantoptions[0].mrru, "Maximum received packet size for multilink bundle", OPT_LIMITS, &lcp_wantoptions[0].neg_mrru, PPP_MAXMRU, PPP_MINMRU }, { "mpshortseq", o_bool, &lcp_wantoptions[0].neg_ssnhf, "Use short sequence numbers in multilink headers", OPT_A2COPY | 1, &lcp_allowoptions[0].neg_ssnhf }, { "nompshortseq", o_bool, &lcp_wantoptions[0].neg_ssnhf, "Don't use short sequence numbers in multilink headers", OPT_A2COPY, &lcp_allowoptions[0].neg_ssnhf }, #endif /* HAVE_MULTILINK */ { "endpoint", o_special, (void *)setendpoint, "Endpoint discriminator for multilink", }, { "noendpoint", o_bool, &noendpoint, "Don't send or accept multilink endpoint discriminator", 1 }, { "ident", o_string, identstr, "LCP Identification string", OPT_STATIC, NULL, sizeof(identstr) }, { "noident", o_int, &noident, "Disable use of LCP Identification", OPT_INC|OPT_NOARG|1 }, #ifdef NEGOTIATE_FCS { "default-fcs", o_bool, &lcp_wantoptions[0].neg_fcs, "Disable FCS Alternatives option (use default CRC-16)", OPT_A2COPY, &lcp_allowoptions[0].neg_fcs }, { "allow-fcs", o_special, (void *)setfcsallow, "Set allowable FCS types; crc16, crc32, null, or number" }, { "fcs", o_special, (void *)setfcswant, "Set FCS type(s) desired; crc16, crc32, null, or number" }, #endif #ifdef MUX_FRAME /* * if pppmux option is turned on, then the parameter to this * is time value in microseconds */ { "pppmux", o_int, &lcp_wantoptions[0].pppmux, "Set PPP Multiplexing option timer", OPT_LLIMIT | OPT_A2COPY, &lcp_allowoptions[0].pppmux, 0, 0 }, #endif {NULL} }; /* global vars */ fsm lcp_fsm[NUM_PPP]; /* LCP fsm structure (global)*/ lcp_options lcp_wantoptions[NUM_PPP]; /* Options that we want to request */ lcp_options lcp_gotoptions[NUM_PPP]; /* Options that peer ack'd */ lcp_options lcp_allowoptions[NUM_PPP]; /* Options we allow peer to request */ lcp_options lcp_hisoptions[NUM_PPP]; /* Options that we ack'd */ u_int32_t xmit_accm[NUM_PPP][8]; /* extended transmit ACCM */ /* * These variables allow a plugin to assert limits on the maximum * MRU/MTU values that can be negotiated. */ int absmax_mru = PPP_MAXMRU; int absmax_mtu = PPP_MAXMTU; static int lcp_echos_pending = 0; /* Number of outstanding echo msgs */ static int lcp_echo_number = 0; /* ID number of next echo frame */ static int lcp_echo_timer_running = 0; /* set if a timer is running */ static bool lcp_echo_accm_test = 0; /* flag if still testing ACCM */ static int lcp_echo_badreplies = 0; /* number of bad replies from peer */ /* * The maximum number of bad replies we tolerate before bringing the * link down. */ #define LCP_ECHO_MAX_BADREPLIES 10 /* * Callbacks for fsm code. (CI = Configuration Information) */ static void lcp_resetci __P((fsm *)); /* Reset our CI */ static int lcp_cilen __P((fsm *)); /* Return length of our CI */ static void lcp_addci __P((fsm *, u_char *, int *)); /* Add our CI to pkt */ static int lcp_ackci __P((fsm *, u_char *, int)); /* Peer ack'd our CI */ static int lcp_nakci __P((fsm *, u_char *, int)); /* Peer nak'd our CI */ static int lcp_rejci __P((fsm *, u_char *, int)); /* Peer rej'd our CI */ static int lcp_reqci __P((fsm *, u_char *, int *, int)); /* Rcv peer CI */ static void lcp_up __P((fsm *)); /* We're UP */ static void lcp_down __P((fsm *)); /* We're DOWN */ static void lcp_starting __P((fsm *)); /* We need lower layer up */ static void lcp_finished __P((fsm *)); /* We need lower layer down */ static int lcp_extcode __P((fsm *, int, int, u_char *, int)); static void lcp_rprotrej __P((fsm *, u_char *, int)); static int lcp_coderej __P((fsm *f, int code, int id, u_char *inp, int len)); /* * routines to send LCP echos to peer */ static void lcp_echo_lowerup __P((int)); static void lcp_echo_lowerdown __P((int)); static void LcpEchoTimeout __P((void *)); static int lcp_received_echo_reply __P((fsm *, int, u_char *, int)); static void LcpSendEchoRequest __P((fsm *)); static void LcpLinkFailure __P((fsm *)); static void LcpEchoCheck __P((fsm *)); /* * routines to send and receive additional LCP packets described in * section 1 of rfc1570. */ static void LcpSendIdentification __P((fsm *)); static void lcp_received_identification __P((fsm *, int, u_char *, int)); static void LcpSendTimeRemaining __P((fsm *, u_int32_t)); static void lcp_timeremaining __P((void *)); static void lcp_received_timeremain __P((fsm *, int, u_char *, int)); static fsm_callbacks lcp_callbacks = { /* LCP callback routines */ lcp_resetci, /* Reset our Configuration Information */ lcp_cilen, /* Length of our Configuration Information */ lcp_addci, /* Add our Configuration Information */ lcp_ackci, /* ACK our Configuration Information */ lcp_nakci, /* NAK our Configuration Information */ lcp_rejci, /* Reject our Configuration Information */ lcp_reqci, /* Request peer's Configuration Information */ lcp_up, /* Called when fsm reaches OPENED state */ lcp_down, /* Called when fsm leaves OPENED state */ lcp_starting, /* Called when we want the lower layer up */ lcp_finished, /* Called when we want the lower layer down */ NULL, /* Retransmission is necessary */ lcp_extcode, /* Called to handle LCP-specific codes */ "LCP", /* String name of protocol */ lcp_coderej, /* Peer rejected a code number */ }; /* * Protocol entry points. * Some of these are called directly. */ static void lcp_init __P((int)); static void lcp_input __P((int, u_char *, int)); static void lcp_protrej __P((int)); static int lcp_printpkt __P((u_char *, int, void (*) __P((void *, const char *, ...)), void *)); struct protent lcp_protent = { PPP_LCP, /* Protocol Number for LCP */ lcp_init, /* Initializes LCP */ lcp_input, /* Processes a received LCP packet */ lcp_protrej, /* Process a received Protocol-reject */ lcp_lowerup, /* Called after the serial device has been set up */ lcp_lowerdown, /* Called when the link is brought down */ lcp_open, /* Called after lcp_lowerup when bringing up the link */ lcp_close, /* Called when the link goes down */ lcp_printpkt, /* Print a packet in human readable form */ NULL, /* Process a received data packet */ 1, /* LCP is enabled by default */ "LCP", /* Name of the protocol */ NULL, /* Name of the corresponding data protocol */ lcp_option_list, /* List of LCP command-line options */ NULL, /* Assigns default values for options */ NULL, /* Configures demand-dial */ NULL /* Bring up the link for this packet? */ }; int lcp_loopbackfail = DEFLOOPBACKFAIL; /* * Length of each type of configuration option (in octets) */ #define CILEN_VOID 2 #define CILEN_CHAR 3 #define CILEN_SHORT 4 /* CILEN_VOID + 2 */ #define CILEN_CHAP 5 /* CILEN_VOID + 2 + 1 */ #define CILEN_LONG 6 /* CILEN_VOID + 4 */ #define CILEN_LQR 8 /* CILEN_VOID + 2 + 4 */ #define CILEN_CBCP 3 /* * setescape - add chars to the set we escape on transmission. */ /*ARGSUSED*/ static int setescape(argv, opt) char **argv; option_t *opt; { int n, ret; char *p, *endp; p = *argv; ret = 1; while (*p != '\0') { n = strtol(p, &endp, 16); if (p == endp) { option_error("escape parameter contains invalid hex number '%s'", p); return 0; } p = endp; if (n < 0 || n == 0x5E || n > 0xFF) { option_error("can't escape character 0x%x", n); ret = 0; } else xmit_accm[0][n >> 5] |= 1 << (n & 0x1F); while (*p == ',' || *p == ' ') ++p; } return ret; } /* * setasyncmap - set async map negotiated */ /*ARGSUSED*/ static int setasyncmap(argv, opt) char **argv; option_t *opt; { u_int32_t val; char *endp; val = strtoul(*argv, &endp, 16); if (*argv == endp) { option_error("invalid numeric parameter '%s' for 'asyncmap' option", *argv); return 0; } lcp_wantoptions[0].asyncmap |= val; lcp_wantoptions[0].neg_asyncmap = (~lcp_wantoptions[0].asyncmap != 0); do_msft_workaround = 0; return 1; } /*ARGSUSED*/ static int setendpoint(argv, opt) char **argv; option_t *opt; { if (str_to_epdisc(&lcp_wantoptions[0].endpoint, *argv)) { lcp_wantoptions[0].neg_endpoint = 1; return 1; } option_error("Can't parse '%s' as an endpoint discriminator", *argv); return 0; } #ifdef NEGOTIATE_FCS static int str_to_fcstype(opt,arg) lcp_options *opt; char *arg; { char **cpp, *cp; int val, len; if (*arg != '\0') { val = 0; while (*arg != '\0') { len = 0; if (isdigit(*arg)) { len = strtol(arg, &cp, 0); if (len < 0 || len > 255 || arg == cp || (*cp != '\0' && *cp != ',')) break; val |= len; len = cp - arg; } else { for (cpp = fcsalt_strings; *cpp != NULL; cpp++) { len = strlen(*cpp); if (strncasecmp(arg, *cpp, len) == 0 && (arg[len] == '\0' || arg[len] == ',')) break; } if (*cpp == NULL) break; val |= 1<<(cpp-fcsalt_strings); } if (arg[len] == '\0') { opt->neg_fcs = 1; opt->fcs_type = val; return (1); } arg += len+1; } } option_error("Can't parse '%s' as an FCS type", arg); return (0); } /*ARGSUSED*/ static int setfcsallow(argv, opt) char **argv; option_t *opt; { return str_to_fcstype(&lcp_allowoptions[0], *argv); } /*ARGSUSED*/ static int setfcswant(argv, opt) char **argv; option_t *opt; { return str_to_fcstype(&lcp_wantoptions[0], *argv); } #endif /* * lcp_init - Initialize LCP. */ static void lcp_init(unit) int unit; { fsm *f = &lcp_fsm[unit]; lcp_options *wo = &lcp_wantoptions[unit]; lcp_options *ao = &lcp_allowoptions[unit]; f->unit = unit; f->protocol = PPP_LCP; f->callbacks = &lcp_callbacks; fsm_init(f); BZERO(wo, sizeof(*wo)); wo->neg_mru = 1; wo->mru = PPP_MRU; wo->neg_asyncmap = 1; wo->chap_mdtype = CHAP_DIGEST_MD5; wo->neg_magicnumber = 1; wo->neg_pcompression = 1; wo->neg_accompression = 1; /* * Leave allowed MRU (MTU) at zero; configuration option sets it * non-zero if we should nak for something else. */ BZERO(ao, sizeof(*ao)); ao->neg_mru = 1; ao->neg_asyncmap = 1; ao->neg_chap = 1; #if defined(CHAPMS) || defined(CHAPMSV2) #ifdef SOL2 /* Check if DES wasn't exported */ errno = 0; setkey("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); if (errno == 0) #endif { #ifdef CHAPMS ao->neg_mschap = 1; #endif #ifdef CHAPMSV2 ao->neg_mschapv2 = 1; #endif } #endif ao->chap_mdtype = CHAP_DIGEST_MD5; ao->neg_upap = 1; ao->neg_magicnumber = 1; ao->neg_pcompression = 1; ao->neg_accompression = 1; #ifdef CBCP_SUPPORT ao->neg_cbcp = 1; #endif ao->neg_endpoint = 1; #ifdef NEGOTIATE_FCS ao->neg_fcs = 1; ao->fcs_type = FCSALT_NULL|FCSALT_16|FCSALT_32; #endif BZERO(xmit_accm[unit], sizeof(xmit_accm[0])); xmit_accm[unit][3] = 0x60000000; } /* * lcp_open - LCP is allowed to come up. */ void lcp_open(unit) int unit; { fsm *f = &lcp_fsm[unit]; lcp_options *wo = &lcp_wantoptions[unit]; f->flags = 0; if (wo->passive) f->flags |= OPT_PASSIVE; if (wo->silent) f->flags |= OPT_SILENT; fsm_open(f); } /* * lcp_close - Take LCP down. */ void lcp_close(unit, reason) int unit; char *reason; { fsm *f = &lcp_fsm[unit]; if (phase != PHASE_DEAD) new_phase(PHASE_TERMINATE); if (f->state == STOPPED && (f->flags & (OPT_PASSIVE|OPT_SILENT))) { /* * This action is not strictly according to the FSM in RFC1548, * but it does mean that the program terminates if you do a * lcp_close() in passive/silent mode when a connection hasn't * been established. */ f->state = CLOSED; lcp_finished(f); } else fsm_close(&lcp_fsm[unit], reason); } /* * lcp_lowerup - The lower layer is up. */ void lcp_lowerup(unit) int unit; { lcp_options *wo = &lcp_wantoptions[unit]; int mru, mtu; mru = PPP_MRU > absmax_mru ? absmax_mru : PPP_MRU; mtu = PPP_MTU > absmax_mtu ? absmax_mtu : PPP_MTU; /* * Don't use A/C or protocol compression on transmission, * but accept A/C and protocol compressed packets * if we are going to ask for A/C and protocol compression. */ ppp_set_xaccm(unit, xmit_accm[unit]); ppp_send_config(unit, mtu, 0xffffffff, 0, 0); ppp_recv_config(unit, mru, (lax_recv? 0: 0xffffffff), wo->neg_pcompression, wo->neg_accompression); #ifdef NEGOTIATE_FCS ppp_send_fcs(unit, FCSALT_16); ppp_recv_fcs(unit, FCSALT_16); #endif fsm_setpeermru(unit, mtu); lcp_allowoptions[unit].asyncmap = xmit_accm[unit][0]; fsm_lowerup(&lcp_fsm[unit]); } /* * lcp_lowerdown - The lower layer is down. */ void lcp_lowerdown(unit) int unit; { fsm_lowerdown(&lcp_fsm[unit]); } /* * lcp_input - Input LCP packet. */ static void lcp_input(unit, p, len) int unit; u_char *p; int len; { fsm *f = &lcp_fsm[unit]; fsm_input(f, p, len); } /* * lcp_extcode - Handle a LCP-specific code. */ static int lcp_extcode(f, code, id, inp, len) fsm *f; int code, id; u_char *inp; int len; { u_char *magp; switch( code ){ case CODE_PROTREJ: lcp_rprotrej(f, inp, len); break; case CODE_ECHOREQ: if (f->state != OPENED) break; magp = inp; PUTLONG(lcp_gotoptions[f->unit].magicnumber, magp); fsm_sdata(f, CODE_ECHOREP, id, inp, len); break; case CODE_ECHOREP: if (!lcp_received_echo_reply(f, id, inp, len)) { lcp_echo_badreplies++; if (lcp_echo_badreplies > LCP_ECHO_MAX_BADREPLIES) { LcpLinkFailure(f); lcp_echos_pending = 0; lcp_echo_badreplies = 0; } } break; case CODE_DISCREQ: break; case CODE_IDENT: /* More than one 'noident' tells us to reject the code number. */ if (noident > 1) return 0; lcp_received_identification(f, id, inp, len); break; case CODE_TIMEREMAIN: lcp_received_timeremain(f, id, inp, len); break; default: return 0; } return 1; } /* * lcp_rprotrej - Receive an Protocol-Reject. * * Figure out which protocol is rejected and inform it. */ static void lcp_rprotrej(f, inp, len) fsm *f; u_char *inp; int len; { int i; struct protent *protp; u_short prot; if (len < 2) { dbglog("lcp_rprotrej: Rcvd short Protocol-Reject packet!"); return; } GETSHORT(prot, inp); /* * Protocol-Reject packets received in any state other than the LCP * OPENED state SHOULD be silently discarded. */ if( f->state != OPENED ){ dbglog("Protocol-Reject discarded: LCP in state %s", fsm_state(f->state)); return; } /* * Upcall the proper Protocol-Reject routine. */ for (i = 0; (protp = protocols[i]) != NULL; ++i) if (protp->protocol == prot && protp->enabled_flag) { (*protp->protrej)(f->unit); return; } warn("Protocol-Reject for unsupported protocol 0x%x", prot); } /* * lcp_protrej - A Protocol-Reject was received. */ /*ARGSUSED*/ static void lcp_protrej(unit) int unit; { /* * Can't reject LCP! */ error("Received Protocol-Reject for LCP!"); } /* * lcp_coderej - A Code-Reject was received. */ /*ARGSUSED*/ static int lcp_coderej(f, code, id, inp, len) fsm *f; int code; int id; u_char *inp; int len; { /* The peer cannot reject these code numbers. */ if (code >= CODE_CONFREQ && code <= CODE_PROTREJ) return 1; switch (code) { case CODE_ECHOREQ: /* * If the peer rejects an Echo-Request, then stop doing that. */ if (lcp_echo_timer_running != 0) { UNTIMEOUT (LcpEchoTimeout, f); lcp_echo_timer_running = 0; lcp_echo_interval = 0; } break; } return 0; } /* * lcp_sprotrej - Send a Protocol-Reject for some protocol. */ void lcp_sprotrej(unit, p, len) int unit; u_char *p; int len; { /* * Send back the protocol and the information field of the * rejected packet. We only get here if LCP is in the OPENED state. */ p += 2; len -= 2; fsm_sdata(&lcp_fsm[unit], CODE_PROTREJ, ++lcp_fsm[unit].id, p, len); } /* * lcp_resetci - Reset our CI. */ static void lcp_resetci(f) fsm *f; { lcp_options *wo = &lcp_wantoptions[f->unit]; lcp_options *go = &lcp_gotoptions[f->unit]; lcp_options *ao = &lcp_allowoptions[f->unit]; wo->magicnumber = magic(); wo->numloops = 0; sentident = 0; *go = *wo; if (!multilink) { go->neg_mrru = 0; go->neg_ssnhf = 0; } if (noendpoint) ao->neg_endpoint = 0; if (go->mru > absmax_mru) go->mru = absmax_mru; if (ao->mru > absmax_mtu) ao->mru = absmax_mtu; unsolicit_mru = 1; fsm_setpeermru(f->unit, PPP_MTU > absmax_mtu ? absmax_mtu : PPP_MTU); auth_reset(f->unit); } /* * lcp_cilen - Return length of our CI. */ static int lcp_cilen(f) fsm *f; { lcp_options *go = &lcp_gotoptions[f->unit]; #define LENCIVOID(neg) ((neg) ? CILEN_VOID : 0) #define LENCICHAP(neg) ((neg) ? CILEN_CHAP : 0) #define LENCICHAR(neg) ((neg) ? CILEN_CHAR : 0) #define LENCISHORT(neg) ((neg) ? CILEN_SHORT : 0) #define LENCILONG(neg) ((neg) ? CILEN_LONG : 0) #define LENCILQR(neg) ((neg) ? CILEN_LQR: 0) #define LENCICBCP(neg) ((neg) ? CILEN_CBCP: 0) /* * NB: we only ask for one of CHAP and UPAP, even if we will * accept either. */ return (LENCISHORT(go->neg_mru && go->mru != PPP_MRU) + LENCILONG(go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF) + LENCICHAP(go->neg_chap || go->neg_mschap || go->neg_mschapv2) + LENCISHORT(!go->neg_chap && go->neg_upap && !go->neg_mschap && !go->neg_mschapv2) + LENCILQR(go->neg_lqr) + LENCICBCP(go->neg_cbcp) + LENCILONG(go->neg_magicnumber) + LENCIVOID(go->neg_pcompression) + LENCIVOID(go->neg_accompression) + LENCICHAR(go->neg_fcs) + LENCISHORT(go->neg_mrru) + LENCIVOID(go->neg_ssnhf) + #ifdef MUX_FRAME LENCIVOID(go->pppmux) + #endif (go->neg_endpoint? CILEN_CHAR + go->endpoint.length: 0)); } /* * lcp_addci - Add our desired CIs to a packet. */ static void lcp_addci(f, ucp, lenp) fsm *f; u_char *ucp; int *lenp; { lcp_options *go = &lcp_gotoptions[f->unit]; lcp_options *ho = &lcp_hisoptions[f->unit]; u_char *start_ucp = ucp; #define ADDCIVOID(opt, neg) \ if (neg) { \ PUTCHAR(opt, ucp); \ PUTCHAR(CILEN_VOID, ucp); \ } #define ADDCISHORT(opt, neg, val) \ if (neg) { \ PUTCHAR(opt, ucp); \ PUTCHAR(CILEN_SHORT, ucp); \ PUTSHORT(val, ucp); \ } #define ADDCICHAP(opt, neg, val, digest) \ if (neg) { \ PUTCHAR(opt, ucp); \ PUTCHAR(CILEN_CHAP, ucp); \ PUTSHORT(val, ucp); \ PUTCHAR(digest, ucp); \ } #define ADDCILONG(opt, neg, val) \ if (neg) { \ PUTCHAR(opt, ucp); \ PUTCHAR(CILEN_LONG, ucp); \ PUTLONG(val, ucp); \ } #define ADDCILQR(opt, neg, val) \ if (neg) { \ PUTCHAR(opt, ucp); \ PUTCHAR(CILEN_LQR, ucp); \ PUTSHORT(PPP_LQR, ucp); \ PUTLONG(val, ucp); \ } #define ADDCICHAR(opt, neg, val) \ if (neg) { \ PUTCHAR(opt, ucp); \ PUTCHAR(CILEN_CHAR, ucp); \ PUTCHAR(val, ucp); \ } #define ADDCIENDP(opt, neg, class, val, len) \ if (neg) { \ int i; \ PUTCHAR(opt, ucp); \ PUTCHAR(CILEN_CHAR + len, ucp); \ PUTCHAR(class, ucp); \ for (i = 0; i < len; ++i) \ PUTCHAR(val[i], ucp); \ } ADDCISHORT(CI_MRU, go->neg_mru && go->mru != PPP_MRU, go->mru); ADDCILONG(CI_ASYNCMAP, go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF, go->asyncmap); /* go->chap_mdtype always points to a useful value */ ADDCICHAP(CI_AUTHTYPE, go->neg_chap || go->neg_mschap || go->neg_mschapv2, PPP_CHAP, go->chap_mdtype); ADDCISHORT(CI_AUTHTYPE, !(go->neg_chap || go->neg_mschap || go->neg_mschapv2) && go->neg_upap, PPP_PAP); /* We can't both say zero for LQR period. */ if (f->state == ACKSENT && go->neg_lqr && go->lqr_period == 0 && ho->neg_lqr && ho->lqr_period == 0) go->lqr_period = 500; ADDCILQR(CI_QUALITY, go->neg_lqr, go->lqr_period); ADDCICHAR(CI_CALLBACK, go->neg_cbcp, CBOP_CBCP); ADDCILONG(CI_MAGICNUMBER, go->neg_magicnumber, go->magicnumber); ADDCIVOID(CI_PCOMPRESSION, go->neg_pcompression); ADDCIVOID(CI_ACCOMPRESSION, go->neg_accompression); ADDCICHAR(CI_FCSALTERN, (go->neg_fcs && go->fcs_type != 0), go->fcs_type); ADDCIENDP(CI_EPDISC, go->neg_endpoint, go->endpoint.class, go->endpoint.value, go->endpoint.length); #ifdef MUX_FRAME ADDCIVOID(CI_MUXING, go->pppmux); #endif ADDCISHORT(CI_MRRU, go->neg_mrru, go->mrru); ADDCIVOID(CI_SSNHF, go->neg_ssnhf); if (ucp - start_ucp != *lenp) { /* this should never happen, because peer_mtu should be 1500 */ error("Bug in lcp_addci: wrong length"); } } /* * lcp_ackci - Ack our CIs. * This should not modify any state if the Ack is bad. * * Returns: * 0 - Ack was bad. * 1 - Ack was good. */ static int lcp_ackci(f, p, len) fsm *f; u_char *p; int len; { lcp_options *go = &lcp_gotoptions[f->unit]; #ifdef MUX_FRAME lcp_options *ao = &lcp_allowoptions[f->unit]; #endif u_char cilen, citype, cichar; u_short cishort; u_int32_t cilong; /* * CIs must be in exactly the same order that we sent. * Check packet length and CI length at each step. * If we find any deviations, then this packet is bad. */ #define ACKCIVOID(opt, neg) \ if (neg) { \ if ((len -= CILEN_VOID) < 0) \ goto bad; \ GETCHAR(citype, p); \ GETCHAR(cilen, p); \ if (cilen != CILEN_VOID || \ citype != opt) \ goto bad; \ } #define ACKCISHORT(opt, neg, val) \ if (neg) { \ if ((len -= CILEN_SHORT) < 0) \ goto bad; \ GETCHAR(citype, p); \ GETCHAR(cilen, p); \ if (cilen != CILEN_SHORT || \ citype != opt) \ goto bad; \ GETSHORT(cishort, p); \ if (cishort != val) \ goto bad; \ } #define ACKCIAUTH(opt, neg, val) \ if (neg) { \ if ((len -= CILEN_SHORT) < 0) \ goto bad; \ GETCHAR(citype, p); \ GETCHAR(cilen, p); \ if (cilen != CILEN_SHORT || \ citype != opt) \ goto bad; \ GETSHORT(cishort, p); \ if (cishort != val) \ goto bad; \ peer_nak_auth = 0; \ peer_reject_auth = 0; \ } #define ACKCICHAR(opt, neg, val) \ if (neg) { \ if ((len -= CILEN_CHAR) < 0) \ goto bad; \ GETCHAR(citype, p); \ GETCHAR(cilen, p); \ if (cilen != CILEN_CHAR || \ citype != opt) \ goto bad; \ GETCHAR(cichar, p); \ if (cichar != val) \ goto bad; \ } #define ACKCICHAP(opt, neg, val, digest) \ if (neg) { \ if ((len -= CILEN_CHAP) < 0) \ goto bad; \ GETCHAR(citype, p); \ GETCHAR(cilen, p); \ if (cilen != CILEN_CHAP || \ citype != opt) \ goto bad; \ GETSHORT(cishort, p); \ if (cishort != val) \ goto bad; \ GETCHAR(cichar, p); \ if (cichar != digest) \ goto bad; \ peer_nak_auth = 0; \ peer_reject_auth = 0; \ } #define ACKCILONG(opt, neg, val) \ if (neg) { \ if ((len -= CILEN_LONG) < 0) \ goto bad; \ GETCHAR(citype, p); \ GETCHAR(cilen, p); \ if (cilen != CILEN_LONG || \ citype != opt) \ goto bad; \ GETLONG(cilong, p); \ if (cilong != val) \ goto bad; \ } #define ACKCILQR(opt, neg, val) \ if (neg) { \ if ((len -= CILEN_LQR) < 0) \ goto bad; \ GETCHAR(citype, p); \ GETCHAR(cilen, p); \ if (cilen != CILEN_LQR || \ citype != opt) \ goto bad; \ GETSHORT(cishort, p); \ if (cishort != PPP_LQR) \ goto bad; \ GETLONG(cilong, p); \ if (cilong != val) \ goto bad; \ } #define ACKCIENDP(opt, neg, class, val, vlen) \ if (neg) { \ int i; \ if ((len -= CILEN_CHAR + vlen) < 0) \ goto bad; \ GETCHAR(citype, p); \ GETCHAR(cilen, p); \ if (cilen != CILEN_CHAR + vlen || \ citype != opt) \ goto bad; \ GETCHAR(cichar, p); \ if (cichar != class) \ goto bad; \ for (i = 0; i < vlen; ++i) { \ GETCHAR(cichar, p); \ if (cichar != val[i]) \ goto bad; \ } \ } ACKCISHORT(CI_MRU, go->neg_mru && go->mru != PPP_MRU, go->mru); ACKCILONG(CI_ASYNCMAP, go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF, go->asyncmap); /* go->chap_mdtype always points to a useful value */ ACKCICHAP(CI_AUTHTYPE, go->neg_chap || go->neg_mschap || go->neg_mschapv2, PPP_CHAP, go->chap_mdtype); ACKCIAUTH(CI_AUTHTYPE, !(go->neg_chap || go->neg_mschap || go->neg_mschapv2) && go->neg_upap, PPP_PAP); ACKCILQR(CI_QUALITY, go->neg_lqr, go->lqr_period); ACKCICHAR(CI_CALLBACK, go->neg_cbcp, CBOP_CBCP); ACKCILONG(CI_MAGICNUMBER, go->neg_magicnumber, go->magicnumber); ACKCIVOID(CI_PCOMPRESSION, go->neg_pcompression); ACKCIVOID(CI_ACCOMPRESSION, go->neg_accompression); ACKCICHAR(CI_FCSALTERN, go->neg_fcs, go->fcs_type); ACKCIENDP(CI_EPDISC, go->neg_endpoint, go->endpoint.class, go->endpoint.value, go->endpoint.length); #ifdef MUX_FRAME ACKCIVOID(CI_MUXING, go->pppmux); if (go->pppmux) go->pppmux = ao->pppmux; #endif ACKCISHORT(CI_MRRU, go->neg_mrru, go->mrru); ACKCIVOID(CI_SSNHF, go->neg_ssnhf); /* * If there are any remaining CIs, then this packet is bad. */ if (len != 0) goto bad; return (1); bad: dbglog("lcp_acki: received bad Ack!"); return (0); } /* * lcp_nakci - Peer has sent a NAK for some of our CIs. * This should not modify any state if the Nak is bad * or if LCP is in the OPENED state. * * Returns: * 0 - Nak was bad. * 1 - Nak was good. */ static int lcp_nakci(f, p, len) fsm *f; u_char *p; int len; { lcp_options *go = &lcp_gotoptions[f->unit]; lcp_options *wo = &lcp_wantoptions[f->unit]; u_char citype, cichar, *next; u_short cishort; u_int32_t cilong; lcp_options no; /* options we've seen Naks for */ lcp_options try; /* options to request next time */ int looped_back = 0; int cilen; BZERO(&no, sizeof(no)); try = *go; /* * Any Nak'd CIs must be in exactly the same order that we sent. * Check packet length and CI length at each step. * If we find any deviations, then this packet is bad. */ #define NAKCIVOID(opt, neg) \ if (go->neg && \ len >= CILEN_VOID && \ p[1] == CILEN_VOID && \ p[0] == opt) { \ len -= CILEN_VOID; \ INCPTR(CILEN_VOID, p); \ no.neg = 1; \ try.neg = 0; \ } #define NAKCICHAR(opt, neg, code) \ if (go->neg && \ len >= CILEN_CHAR && \ p[1] == CILEN_CHAR && \ p[0] == opt) { \ len -= CILEN_CHAR; \ INCPTR(2, p); \ GETCHAR(cichar, p); \ no.neg = 1; \ code \ } #define NAKCISHORT(opt, neg, code) \ if (go->neg && \ len >= CILEN_SHORT && \ p[1] == CILEN_SHORT && \ p[0] == opt) { \ len -= CILEN_SHORT; \ INCPTR(2, p); \ GETSHORT(cishort, p); \ no.neg = 1; \ code \ } #define NAKCILONG(opt, neg, code) \ if (go->neg && \ len >= CILEN_LONG && \ p[1] == CILEN_LONG && \ p[0] == opt) { \ len -= CILEN_LONG; \ INCPTR(2, p); \ GETLONG(cilong, p); \ no.neg = 1; \ code \ } #define NAKCILQR(opt, neg, code) \ if (go->neg && \ len >= CILEN_LQR && \ p[1] == CILEN_LQR && \ p[0] == opt) { \ len -= CILEN_LQR; \ INCPTR(2, p); \ GETSHORT(cishort, p); \ GETLONG(cilong, p); \ no.neg = 1; \ code \ } #define NAKCIENDP(opt, neg) \ if (go->neg && \ len >= CILEN_CHAR && \ p[0] == opt && \ p[1] >= CILEN_CHAR && \ p[1] <= len) { \ len -= p[1]; \ INCPTR(p[1], p); \ no.neg = 1; \ try.neg = 0; \ } /* * We don't care if they want to send us smaller packets than * we want. Therefore, accept any MRU less than what we asked for, * but then ignore the new value when setting the MRU in the kernel. * If they send us a bigger MRU than what we asked, accept it, up to * the limit of the default MRU we'd get if we didn't negotiate. */ if (go->neg_mru && go->mru != PPP_MRU) { NAKCISHORT(CI_MRU, neg_mru, if (cishort <= wo->mru || (cishort <= PPP_MRU && cishort <= absmax_mru)) try.mru = cishort; ); } /* * Add any characters they want to our (receive-side) asyncmap. */ if (go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF) { NAKCILONG(CI_ASYNCMAP, neg_asyncmap, try.asyncmap = go->asyncmap | cilong; ); } /* * If they've nak'd our authentication-protocol, check whether * they are proposing a different protocol, or a different * hash algorithm for CHAP. */ if ((go->neg_chap || go->neg_mschap || go->neg_mschapv2 || go->neg_upap) && len >= CILEN_SHORT && p[0] == CI_AUTHTYPE && p[1] >= CILEN_SHORT && p[1] <= len) { cilen = p[1]; len -= cilen; INCPTR(2, p); GETSHORT(cishort, p); peer_nak_auth = 1; nak_auth_orig = (go->neg_chap || go->neg_mschap || go->neg_mschapv2) ? PPP_CHAP : PPP_PAP; nak_auth_proto = cishort; if (cishort == PPP_PAP && cilen == CILEN_SHORT) { no.neg_upap = go->neg_upap; /* * If we were asking for CHAP, they obviously don't want to do it. * If we weren't asking for CHAP, then we were asking for PAP, * in which case this Nak is bad. */ if (!go->neg_chap && !go->neg_mschap && !go->neg_mschapv2) goto bad; try.neg_chap = 0; try.neg_mschap = 0; try.neg_mschapv2 = 0; } else if (cishort == PPP_CHAP && cilen >= CILEN_CHAP) { /* stop asking for that type */ switch (go->chap_mdtype) { case CHAP_DIGEST_MD5: no.neg_chap = go->neg_chap; try.neg_chap = 0; break; case CHAP_MICROSOFT: no.neg_mschap = go->neg_mschap; try.neg_mschap = 0; break; case CHAP_MICROSOFT_V2: no.neg_mschapv2 = go->neg_mschapv2; try.neg_mschapv2 = 0; break; } GETCHAR(cichar, p); /* Allow >= on length here for broken and silly peers. */ p += cilen - CILEN_CHAP; try.neg_upap = 0; if ((cichar == CHAP_DIGEST_MD5 && wo->neg_chap) || (cichar == CHAP_MICROSOFT && wo->neg_mschap) || (cichar == CHAP_MICROSOFT_V2 && wo->neg_mschapv2)) { /* Try its requested algorithm. */ try.chap_mdtype = cichar; } else { goto try_another; } } else { /* * We don't recognize what they're suggesting. * Stop asking for what we were asking for. */ try_another: if (go->neg_chap || go->neg_mschap || go->neg_mschapv2) { switch (go->chap_mdtype) { case CHAP_DIGEST_MD5: try.neg_chap = 0; if (wo->neg_mschap) { try.chap_mdtype = CHAP_MICROSOFT; break; } /*FALLTHROUGH*/ case CHAP_MICROSOFT: try.neg_mschap = 0; if (wo->neg_mschapv2) { try.chap_mdtype = CHAP_MICROSOFT_V2; break; } /*FALLTHROUGH*/ case CHAP_MICROSOFT_V2: try.neg_mschapv2 = 0; break; } } else try.neg_upap = 0; p += cilen - CILEN_SHORT; } } /* * If they can't cope with our link quality protocol, we'll have * to stop asking for LQR. We haven't got any other protocol. If * they Nak the reporting period, then the following logic * applies: * If it suggests zero and go->neg_fcs is true and * ao->lqr_period isn't zero, then take its suggestion. If it * suggests zero otherwise, ignore it. If it suggests a nonzero * value and wo->lqr_period is zero, then take its suggestion. If * it suggests a nonzero value otherwise that's less than * wo->lqr_period, then ignore it. */ NAKCILQR(CI_QUALITY, neg_lqr, if (cishort != PPP_LQR) try.neg_lqr = 0; else if (cilong == 0 && go->neg_fcs && wo->lqr_period != 0) try.lqr_period = cilong; else if (cilong != 0 && (wo->lqr_period == 0 || cilong > wo->lqr_period)) try.lqr_period = cilong; ); /* * Only implementing CBCP...not the rest of the callback options */ NAKCICHAR(CI_CALLBACK, neg_cbcp, try.neg_cbcp = 0; ); /* * Check for a looped-back line. */ NAKCILONG(CI_MAGICNUMBER, neg_magicnumber, try.magicnumber = magic(); looped_back = 1; ); /* * Peer shouldn't send Nak for protocol compression or * address/control compression requests; they should send * a Reject instead. If they send a Nak, treat it as a Reject. */ NAKCIVOID(CI_PCOMPRESSION, neg_pcompression); NAKCIVOID(CI_ACCOMPRESSION, neg_accompression); /* * Remove any FCS types it doesn't like from our (receive-side) * FCS list. */ NAKCICHAR(CI_FCSALTERN, neg_fcs, try.fcs_type = go->fcs_type & cichar;); #ifdef MUX_FRAME /* Nacked MUX option */ NAKCIVOID(CI_MUXING, pppmux); #endif /* * Nak of the endpoint discriminator option is not permitted, * treat it like a reject. */ NAKCIENDP(CI_EPDISC, neg_endpoint); /* * Nak for MRRU option - accept their value if it is smaller * than the one we want. */ if (go->neg_mrru) { NAKCISHORT(CI_MRRU, neg_mrru, if (cishort <= wo->mrru) try.mrru = cishort; ); } /* * Nak for short sequence numbers shouldn't be sent, treat it * like a reject. */ NAKCIVOID(CI_SSNHF, neg_ssnhf); /* * There may be remaining CIs, if the peer is requesting negotiation * on an option that we didn't include in our request packet. * If we see an option that we requested, or one we've already seen * in this packet, then this packet is bad. * If we wanted to respond by starting to negotiate on the requested * option(s), we could, but we don't, because except for the * authentication type and quality protocol, if we are not negotiating * an option, it is because we were told not to. * For the authentication type, the Nak from the peer means * `let me authenticate myself with you' which is a bit pointless. * For the quality protocol, the Nak means `ask me to send you quality * reports', but if we didn't ask for them, we don't want them. * An option we don't recognize represents the peer asking to * negotiate some option we don't support, so ignore it. */ while (len > CILEN_VOID) { GETCHAR(citype, p); GETCHAR(cilen, p); if (cilen < CILEN_VOID || (len -= cilen) < 0) goto bad; next = p + cilen - 2; switch (citype) { case CI_MRU: if ((go->neg_mru && go->mru != PPP_MRU) || no.neg_mru || cilen != CILEN_SHORT) goto bad; GETSHORT(cishort, p); if (cishort < PPP_MRU && cishort < absmax_mru) { try.neg_mru = 1; try.mru = cishort; notice("Peer sent unsolicited Nak for MRU less than default."); } break; case CI_ASYNCMAP: if ((go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF) || no.neg_asyncmap || cilen != CILEN_LONG) goto bad; break; case CI_AUTHTYPE: unsolicited_nak_auth = 1; if (cilen >= CILEN_SHORT) { GETSHORT(unsolicit_auth_proto, p); } else { unsolicit_auth_proto = 0; } if (go->neg_chap || no.neg_chap || go->neg_mschap || no.neg_mschap || go->neg_mschapv2 || no.neg_mschapv2 || go->neg_upap || no.neg_upap) goto bad; break; case CI_MAGICNUMBER: if (go->neg_magicnumber || no.neg_magicnumber || cilen != CILEN_LONG) goto bad; break; case CI_PCOMPRESSION: if (go->neg_pcompression || no.neg_pcompression || cilen != CILEN_VOID) goto bad; break; case CI_ACCOMPRESSION: if (go->neg_accompression || no.neg_accompression || cilen != CILEN_VOID) goto bad; break; case CI_QUALITY: if (go->neg_lqr || no.neg_lqr || cilen != CILEN_LQR) goto bad; break; case CI_MRRU: if (go->neg_mrru || no.neg_mrru || cilen != CILEN_SHORT) goto bad; break; case CI_SSNHF: if (go->neg_ssnhf || no.neg_ssnhf || cilen != CILEN_VOID) goto bad; try.neg_ssnhf = 1; break; case CI_EPDISC: if (go->neg_endpoint || no.neg_endpoint || cilen < CILEN_CHAR) goto bad; break; case CI_FCSALTERN: if (go->neg_fcs || no.neg_fcs || cilen < CILEN_CHAR) goto bad; break; #ifdef MUX_FRAME case CI_MUXING: if (go->pppmux || no.pppmux || cilen < CILEN_VOID) goto bad; break; #endif } p = next; } /* * OK, the Nak is good. Now we can update state. * If there are any options left we ignore them. */ if (f->state != OPENED) { /* * Note: the code once reset try.numloops to zero here if * looped_back wasn't set. This is wrong because a mixture of * looped-back and peer data (possible if half-duplex is used) * will allow the link to come up, and it shouldn't. */ if (looped_back) { if (++try.numloops >= lcp_loopbackfail) { notice("Serial line is looped back."); lcp_close(f->unit, "Loopback detected"); status = EXIT_LOOPBACK; } } *go = try; } return 1; bad: dbglog("lcp_nakci: received bad Nak!"); return 0; } /* * lcp_rejci - Peer has Rejected some of our CIs. * This should not modify any state if the Reject is bad * or if LCP is in the OPENED state. * * Returns: * 0 - Reject was bad. * 1 - Reject was good. */ static int lcp_rejci(f, p, len) fsm *f; u_char *p; int len; { lcp_options *go = &lcp_gotoptions[f->unit]; u_char cichar; u_short cishort; u_int32_t cilong; lcp_options try; /* options to request next time */ try = *go; /* * Any Rejected CIs must be in exactly the same order that we sent. * Check packet length and CI length at each step. * If we find any deviations, then this packet is bad. */ #define REJCIVOID(opt, neg) \ if (go->neg && \ len >= CILEN_VOID && \ p[1] == CILEN_VOID && \ p[0] == opt) { \ len -= CILEN_VOID; \ INCPTR(CILEN_VOID, p); \ try.neg = 0; \ } #define REJCICHAR(opt, neg, val) \ if (go->neg && \ len >= CILEN_CHAR && \ p[1] == CILEN_CHAR && \ p[0] == opt) { \ len -= CILEN_CHAR; \ INCPTR(2, p); \ GETCHAR(cichar, p); \ /* Check rejected value. */ \ if (cichar != val) \ goto bad; \ try.neg = 0; \ } #define REJCISHORT(opt, neg, val) \ if (go->neg && \ len >= CILEN_SHORT && \ p[1] == CILEN_SHORT && \ p[0] == opt) { \ len -= CILEN_SHORT; \ INCPTR(2, p); \ GETSHORT(cishort, p); \ /* Check rejected value. */ \ if (cishort != val) \ goto bad; \ try.neg = 0; \ } #define REJCIAUTH(opt, neg, val) \ if (go->neg && \ len >= CILEN_SHORT && \ p[1] == CILEN_SHORT && \ p[0] == opt) { \ len -= CILEN_SHORT; \ INCPTR(2, p); \ GETSHORT(cishort, p); \ /* Check rejected value. */ \ peer_reject_auth = 1; \ reject_auth_proto = cishort; \ if (cishort != val) \ goto bad; \ try.neg = 0; \ } #define REJCILONG(opt, neg, val) \ if (go->neg && \ len >= CILEN_LONG && \ p[1] == CILEN_LONG && \ p[0] == opt) { \ len -= CILEN_LONG; \ INCPTR(2, p); \ GETLONG(cilong, p); \ /* Check rejected value. */ \ if (cilong != val) \ goto bad; \ try.neg = 0; \ } #define REJCILQR(opt, neg, val) \ if (go->neg && \ len >= CILEN_LQR && \ p[1] == CILEN_LQR && \ p[0] == opt) { \ len -= CILEN_LQR; \ INCPTR(2, p); \ GETSHORT(cishort, p); \ GETLONG(cilong, p); \ /* Check rejected value. */ \ if (cishort != PPP_LQR || cilong != val) \ goto bad; \ try.neg = 0; \ } #define REJCICBCP(opt, neg, val) \ if (go->neg && \ len >= CILEN_CBCP && \ p[1] == CILEN_CBCP && \ p[0] == opt) { \ len -= CILEN_CBCP; \ INCPTR(2, p); \ GETCHAR(cichar, p); \ /* Check rejected value. */ \ if (cichar != val) \ goto bad; \ try.neg = 0; \ } #define REJCIENDP(opt, neg, class, val, vlen) \ if (go->neg && \ len >= CILEN_CHAR + vlen && \ p[0] == opt && \ p[1] == CILEN_CHAR + vlen) { \ int i; \ len -= CILEN_CHAR + vlen; \ INCPTR(2, p); \ GETCHAR(cichar, p); \ if (cichar != class) \ goto bad; \ for (i = 0; i < vlen; ++i) { \ GETCHAR(cichar, p); \ if (cichar != val[i]) \ goto bad; \ } \ try.neg = 0; \ } /* Received a Configure-Reject, try to send Identification now. */ if (!noident && sentident < 3) { LcpSendIdentification(f); sentident++; } REJCISHORT(CI_MRU, neg_mru, go->mru); REJCILONG(CI_ASYNCMAP, neg_asyncmap, go->asyncmap); /* * There are broken peers (such as unbundled Solaris PPP) that * send Configure-Reject for authentication when they really * intend Configure-Nak. This code works around this problem. */ if ((go->neg_chap || go->neg_mschap || go->neg_mschapv2) && len >= CILEN_CHAP && p[1] == CILEN_CHAP && p[0] == CI_AUTHTYPE) { len -= CILEN_CHAP; INCPTR(2, p); GETSHORT(cishort, p); GETCHAR(cichar, p); peer_reject_auth = 1; reject_auth_proto = cishort; /* Check rejected value. */ if (cishort != PPP_CHAP || cichar != go->chap_mdtype) goto bad; /* Disable the one that it rejected */ switch (cichar) { case CHAP_DIGEST_MD5: try.neg_chap = 0; break; case CHAP_MICROSOFT: try.neg_mschap = 0; break; case CHAP_MICROSOFT_V2: try.neg_mschapv2 = 0; break; } /* Try another, if we can. */ if (try.neg_chap) try.chap_mdtype = CHAP_DIGEST_MD5; else if (try.neg_mschap) try.chap_mdtype = CHAP_MICROSOFT; else try.chap_mdtype = CHAP_MICROSOFT_V2; } if (!go->neg_chap && !go->neg_mschap && !go->neg_mschapv2) { REJCIAUTH(CI_AUTHTYPE, neg_upap, PPP_PAP); } REJCILQR(CI_QUALITY, neg_lqr, go->lqr_period); REJCICBCP(CI_CALLBACK, neg_cbcp, CBOP_CBCP); REJCILONG(CI_MAGICNUMBER, neg_magicnumber, go->magicnumber); REJCIVOID(CI_PCOMPRESSION, neg_pcompression); REJCIVOID(CI_ACCOMPRESSION, neg_accompression); REJCICHAR(CI_FCSALTERN, neg_fcs, go->fcs_type); #ifdef MUX_FRAME REJCIVOID(CI_MUXING,pppmux); #endif REJCIENDP(CI_EPDISC, neg_endpoint, go->endpoint.class, go->endpoint.value, go->endpoint.length); REJCISHORT(CI_MRRU, neg_mrru, go->mrru); REJCIVOID(CI_SSNHF, neg_ssnhf); /* * If there are any remaining CIs, then this packet is bad. */ if (len != 0) goto bad; /* * Now we can update state. */ if (f->state != OPENED) *go = try; return 1; bad: dbglog("lcp_rejci: received bad Reject!"); return 0; } /* * lcp_reqci - Check the peer's requested CIs and send appropriate response. * * Returns: CODE_CONFACK, CODE_CONFNAK or CODE_CONFREJ and input * packet modified appropriately. If reject_if_disagree is non-zero, * doesn't return CODE_CONFNAK; returns CODE_CONFREJ if it can't * return CODE_CONFACK. */ static int lcp_reqci(f, p, lenp, dont_nak) fsm *f; u_char *p; /* Requested CIs */ int *lenp; /* Length of requested CIs */ int dont_nak; { lcp_options *wo = &lcp_wantoptions[f->unit]; lcp_options *go = &lcp_gotoptions[f->unit]; lcp_options *ho = &lcp_hisoptions[f->unit]; lcp_options *ao = &lcp_allowoptions[f->unit]; int cilen, citype, cichar; /* Parsed len, type, char value */ u_short cishort; /* Parsed short value */ u_int32_t cilong; /* Parse long value */ int ret, newret; u_char *p0, *nakp, *rejp, *prev; int len; /* * Loop through options once to find out if peer is offering * Multilink, and repair values as needed. */ ao->mru = ao->mrru; p0 = p; for (len = *lenp; len > 0; len -= cilen, p = prev + cilen) { if (len < 2 || p[1] > len) { /* * RFC 1661 page 40 -- if the option extends beyond the * packet, then discard the entire packet. */ dbglog("discarding LCP Configure-Request due to truncated option"); return (0); } prev = p; GETCHAR(citype, p); GETCHAR(cilen, p); if (citype == CI_MRRU) { if (ao->mrru != 0) { if (ao->mrru+6 > PPP_MTU) ao->mru = PPP_MTU; else ao->mru = ao->mrru + 6; } } if (cilen < 2) cilen = 2; } if (ao->mru > absmax_mtu) ao->mru = absmax_mtu; ret = CODE_CONFACK; rejp = p = p0; nakp = nak_buffer; /* * Reset all its options. */ BZERO(ho, sizeof(*ho)); /* * Process all its options. */ for (len = *lenp; len > 0; len -= cilen, p = prev + cilen) { newret = CODE_CONFACK; /* Assume success */ prev = p; GETCHAR(citype, p); GETCHAR(cilen, p); switch (citype) { /* Check CI type */ case CI_MRU: if (!ao->neg_mru) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_SHORT) { /* Check CI length */ newret = CODE_CONFNAK; cishort = ao->mru; } else { /* extract the MRU from the option */ GETSHORT(cishort, p); /* * If the offered MRU is less than our desired MTU, we * should nak. This is especially helpful if we're * doing demand-dial, since those queued up packets * might be discarded otherwise. */ if (cishort < ao->mru) { newret = CODE_CONFNAK; cishort = ao->mru; } } /* * If we're going to send a nak with something less than * or equal to the default PPP MTU, then just reject instead. */ if (newret == CODE_CONFNAK && cishort <= PPP_MTU) newret = CODE_CONFREJ; if (newret == CODE_CONFNAK) { PUTCHAR(CI_MRU, nakp); PUTCHAR(CILEN_SHORT, nakp); PUTSHORT(cishort, nakp); /* Give it a hint */ } ho->neg_mru = 1; /* Remember that it sent MRU */ ho->mru = cishort; /* And remember value */ break; case CI_ASYNCMAP: if (!ao->neg_asyncmap) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_LONG) { newret = CODE_CONFNAK; cilong = 0; } else { GETLONG(cilong, p); /* * Asyncmap must have set at least the bits * which are set in lcp_allowoptions[unit].asyncmap. */ if ((ao->asyncmap & ~cilong) != 0) newret = CODE_CONFNAK; } /* * Workaround for common broken Microsoft software -- if * the peer is sending us a nonzero ACCM, then it *needs* * us to send the same to it. Adjust our Configure- * Request message and restart LCP. */ if (do_msft_workaround && (cilong & ~wo->asyncmap)) { dbglog("adjusted requested asyncmap from %X to %X", wo->asyncmap, wo->asyncmap | cilong); do_msft_workaround = 0; wo->neg_asyncmap = 1; wo->asyncmap |= cilong; f->flags &= ~OPT_SILENT; info("possibly broken peer detected; restarting LCP"); fsm_lowerdown(f); fsm_lowerup(f); return (0); } if (newret == CODE_CONFNAK) { PUTCHAR(CI_ASYNCMAP, nakp); PUTCHAR(CILEN_LONG, nakp); PUTLONG(ao->asyncmap | cilong, nakp); } ho->neg_asyncmap = 1; ho->asyncmap = cilong; break; case CI_AUTHTYPE: if (!(ao->neg_upap || ao->neg_chap || ao->neg_mschap || ao->neg_mschapv2)) { rejected_peers_auth = 1; if (cilen >= CILEN_SHORT) { GETSHORT(rejected_auth_proto, p); } else { rejected_auth_proto = 0; } /* * Reject the option if we're not willing to authenticate. */ newret = CODE_CONFREJ; break; } rejected_peers_auth = 0; naked_peers_auth = 0; if (cilen >= CILEN_SHORT) { /* Extract the authentication protocol from the option */ GETSHORT(cishort, p); if (ho->neg_upap || ho->neg_chap || ho->neg_mschap || ho->neg_mschapv2) { dbglog("Rejecting extra authentication protocol option"); newret = CODE_CONFREJ; break; } /* * Authtype must be PAP or CHAP. * * Note: if both ao->neg_upap and ao->neg_*chap* are * set, and the peer sends a Configure-Request with * two authenticate-protocol requests, one for CHAP * and one for UPAP, then we will reject the second * request. Whether we end up doing CHAP or UPAP * depends then on the ordering of the CIs in the * peer's Configure-Request. * * We're supposed to list all of the protocols we can * possibly use in the returned Configure-Nak. This * part of RFC 1661 (section 5.3) is in conflict with * the section that says the options shouldn't be * reordered, so it's often ignored. */ if (cishort == PPP_PAP) { if (ao->neg_upap) { if (cilen != CILEN_SHORT) goto try_pap_anyway; ho->neg_upap = 1; break; } } else if (cishort == PPP_CHAP) { /* Test >= here to allow for broken peers. */ if (cilen >= CILEN_CHAP && (ao->neg_chap || ao->neg_mschap || ao->neg_mschapv2)) { GETCHAR(cichar, p); if (cichar == CHAP_DIGEST_MD5 && ao->neg_chap) ho->neg_chap = 1; else if (cichar == CHAP_MICROSOFT && ao->neg_mschap) ho->neg_mschap = 1; else if (cichar == CHAP_MICROSOFT_V2 && ao->neg_mschapv2) ho->neg_mschap = 1; if (ho->neg_chap || ho->neg_mschap || ho->neg_mschapv2) { ho->chap_mdtype = cichar; /* save md type */ break; } } } } /* * We don't recognize the protocol they're asking for. * Nak it with something we're willing to do. * (At this point we know ao->neg_upap || ao->neg_chap.) */ PUTCHAR(CI_AUTHTYPE, nakp); if (ao->neg_chap || ao->neg_mschap || ao->neg_mschapv2) { PUTCHAR(CILEN_CHAP, nakp); PUTSHORT(PPP_CHAP, nakp); PUTCHAR(ao->chap_mdtype, nakp); naked_auth_proto = PPP_CHAP; } else { try_pap_anyway: PUTCHAR(CILEN_SHORT, nakp); PUTSHORT(PPP_PAP, nakp); naked_auth_proto = PPP_PAP; } naked_peers_auth = 1; naked_auth_orig = cishort; newret = CODE_CONFNAK; break; case CI_QUALITY: if (!ao->neg_lqr) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_LQR) { newret = CODE_CONFNAK; cilong = ao->lqr_period; } else { GETSHORT(cishort, p); GETLONG(cilong, p); /* Check the LQM protocol */ if (cishort != PPP_LQR) { newret = CODE_CONFNAK; } /* Check the reporting period; we can't both send zero */ if ((cilong == 0 && go->lqr_period == 0) || cilong < ao->lqr_period) { newret = CODE_CONFNAK; if ((cilong = ao->lqr_period) == 0) cilong = 500; } } if (newret == CODE_CONFNAK) { PUTCHAR(CI_QUALITY, nakp); PUTCHAR(CILEN_LQR, nakp); PUTSHORT(PPP_LQR, nakp); PUTLONG(cilong, nakp); } ho->neg_lqr = 1; ho->lqr_period = cilong; break; case CI_MAGICNUMBER: if (!(ao->neg_magicnumber || go->neg_magicnumber)) { newret = CODE_CONFREJ; break; } ho->neg_magicnumber = 1; if (cilen < CILEN_LONG) { /* * If we send Magic-Number, then we must not reject it * when the peer sends it to us, even if its version * looks odd to us. Ack if the cilent is wrong in this * case. If we're not sending Magic-Number, then we don't * much care what its value is anyway. */ break; } GETLONG(cilong, p); ho->magicnumber = cilong; if (cilen > CILEN_LONG) break; /* * It must have a different magic number. Make sure we * give it a good one to use. */ while (go->neg_magicnumber && cilong == go->magicnumber) { newret = CODE_CONFNAK; cilong = magic(); } if (newret == CODE_CONFNAK) { PUTCHAR(CI_MAGICNUMBER, nakp); PUTCHAR(CILEN_LONG, nakp); PUTLONG(cilong, nakp); /* * We don't need to bump the numloops counter here * since it's already done upon reception of a nak. */ } break; case CI_PCOMPRESSION: if (!ao->neg_pcompression) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_VOID) { newret = CODE_CONFNAK; PUTCHAR(CI_PCOMPRESSION, nakp); PUTCHAR(CILEN_VOID, nakp); } ho->neg_pcompression = 1; break; case CI_ACCOMPRESSION: if (!ao->neg_accompression) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_VOID) { newret = CODE_CONFNAK; PUTCHAR(CI_ACCOMPRESSION, nakp); PUTCHAR(CILEN_VOID, nakp); } ho->neg_accompression = 1; break; case CI_FCSALTERN: if (!ao->neg_fcs) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_CHAR) { newret = CODE_CONFNAK; cichar = ao->fcs_type; } else { GETCHAR(cichar, p); /* If it has bits we don't like, tell it to stop. */ if (cichar & ~ao->fcs_type) { if ((cichar &= ao->fcs_type) == 0) { newret = CODE_CONFREJ; break; } newret = CODE_CONFNAK; } } if (newret == CODE_CONFNAK) { PUTCHAR(CI_FCSALTERN, nakp); PUTCHAR(CILEN_CHAR, nakp); PUTCHAR(cichar, nakp); } ho->neg_fcs = 1; ho->fcs_type = cichar; break; case CI_MRRU: if (!ao->neg_mrru || !multilink) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_SHORT) { newret = CODE_CONFNAK; cishort = ao->mrru; } else { GETSHORT(cishort, p); if (cishort < ao->mrru) { newret = CODE_CONFNAK; cishort = ao->mrru; } } if (cishort < PPP_MINMTU) { newret = CODE_CONFNAK; cishort = PPP_MINMTU; } if (newret == CODE_CONFNAK) { PUTCHAR(CI_MRRU, nakp); PUTCHAR(CILEN_SHORT, nakp); PUTSHORT(cishort, nakp); } ho->neg_mrru = 1; ho->mrru = cishort; break; case CI_SSNHF: if (!ao->neg_ssnhf || !multilink) { newret = CODE_CONFREJ; break; } if (cilen != CILEN_VOID) { newret = CODE_CONFNAK; PUTCHAR(CI_SSNHF, nakp); PUTCHAR(CILEN_VOID, nakp); } ho->neg_ssnhf = 1; break; case CI_EPDISC: if (!ao->neg_endpoint) { newret = CODE_CONFREJ; break; } if (cilen < CILEN_CHAR || cilen > CILEN_CHAR + MAX_ENDP_LEN) { int i; newret = CODE_CONFNAK; PUTCHAR(CI_EPDISC, nakp); PUTCHAR(CILEN_CHAR + ao->endpoint.length, nakp); PUTCHAR(ao->endpoint.class, nakp); for (i = 0; i < ao->endpoint.length; i++) PUTCHAR(ao->endpoint.value[i], nakp); break; } GETCHAR(cichar, p); ho->neg_endpoint = 1; ho->endpoint.class = cichar; ho->endpoint.length = cilen - 3; BCOPY(p, ho->endpoint.value, cilen - 3); break; #ifdef MUX_FRAME case CI_MUXING: if (ao->pppmux == 0 || cilen != CILEN_VOID) { newret = CODE_CONFREJ; break; } /* remember its option */ ho->pppmux = ao->pppmux; break; #endif default: dbglog("LCP: rejecting unknown option %d", citype); newret = CODE_CONFREJ; break; } /* Cope with confused peers. */ if (cilen < 2) cilen = 2; /* * If this is an Ack'able CI, but we're sending back a Nak, * don't include this CI. */ if (newret == CODE_CONFACK && ret != CODE_CONFACK) continue; if (newret == CODE_CONFNAK) { /* * Continue naking the Magic Number option until the cows come * home -- rejecting it is wrong. */ if (dont_nak && citype != CI_MAGICNUMBER) { newret = CODE_CONFREJ; } else { /* Ignore subsequent Nak'able things if rejecting. */ if (ret == CODE_CONFREJ) continue; ret = CODE_CONFNAK; } } if (newret == CODE_CONFREJ) { ret = CODE_CONFREJ; if (prev != rejp) BCOPY(prev, rejp, cilen); rejp += cilen; } } /* * If the peer hasn't negotiated its MRU, and we'd like an MTU * that's larger than the default, try sending an unsolicited * Nak for what we want. */ if (ret != CODE_CONFREJ && !ho->neg_mru && ao->mru > PPP_MTU && !dont_nak && unsolicit_mru) { unsolicit_mru = 0; /* don't ask again */ ret = CODE_CONFNAK; PUTCHAR(CI_MRU, nakp); PUTCHAR(CILEN_SHORT, nakp); PUTSHORT(ao->mru, nakp); } switch (ret) { case CODE_CONFACK: *lenp = p - p0; break; case CODE_CONFNAK: /* * Copy the Nak'd options from the nak_buffer to the caller's buffer. */ *lenp = nakp - nak_buffer; BCOPY(nak_buffer, p0, *lenp); break; case CODE_CONFREJ: *lenp = rejp - p0; /* We're about to send Configure-Reject; send Identification */ if (!noident && sentident < 3) { LcpSendIdentification(f); sentident++; } break; } LCPDEBUG(("lcp_reqci: returning %s.", code_name(ret, 1))); return (ret); /* Return final code */ } /* * lcp_up - LCP has come UP. */ static void lcp_up(f) fsm *f; { lcp_options *wo = &lcp_wantoptions[f->unit]; lcp_options *ho = &lcp_hisoptions[f->unit]; lcp_options *go = &lcp_gotoptions[f->unit]; lcp_options *ao = &lcp_allowoptions[f->unit]; int mru, mtu; if (!go->neg_magicnumber) go->magicnumber = 0; if (!ho->neg_magicnumber) ho->magicnumber = 0; /* * Set our MTU to the smaller of the MTU we wanted and * the MRU our peer wanted. If we negotiated an MRU, * set our MRU to the larger of value we wanted and * the value we got in the negotiation. */ if (ao->mru != 0 && ho->mru > ao->mru) ho->mru = ao->mru; mtu = (ho->neg_mru ? ho->mru: PPP_MRU); if (mtu > absmax_mtu) mtu = absmax_mtu; ppp_send_config(f->unit, mtu, (ho->neg_asyncmap? ho->asyncmap: 0xffffffff), ho->neg_pcompression, ho->neg_accompression); fsm_setpeermru(f->unit, mtu); mru = (go->neg_mru? MAX(wo->mru, go->mru): PPP_MRU); if (mru > absmax_mru) mru = absmax_mru; ppp_recv_config(f->unit, mru, (lax_recv? 0: go->neg_asyncmap? go->asyncmap: 0xffffffff), go->neg_pcompression, go->neg_accompression); #ifdef NEGOTIATE_FCS ppp_send_fcs(f->unit, ho->neg_fcs ? ho->fcs_type : FCSALT_16); ppp_recv_fcs(f->unit, go->neg_fcs ? go->fcs_type : FCSALT_16); #endif #ifdef MUX_FRAME ppp_send_muxoption(f->unit, ho->pppmux); ppp_recv_muxoption(f->unit, go->pppmux); #endif lcp_echo_lowerup(f->unit); /* Enable echo messages */ /* LCP is Up; send Identification */ if (!noident) { LcpSendIdentification(f); sentident++; } link_established(f->unit); } /* * lcp_down - LCP has gone DOWN. * * Alert other protocols. */ static void lcp_down(f) fsm *f; { int mtu; lcp_options *go = &lcp_gotoptions[f->unit]; lcp_echo_lowerdown(f->unit); link_down(f->unit); mtu = PPP_MTU > absmax_mtu ? absmax_mtu : PPP_MTU; ppp_send_config(f->unit, mtu, 0xffffffff, 0, 0); ppp_recv_config(f->unit, (PPP_MRU > absmax_mru ? absmax_mru : PPP_MRU), (go->neg_asyncmap? go->asyncmap: 0xffffffff), go->neg_pcompression, go->neg_accompression); #ifdef NEGOTIATE_FCS ppp_send_fcs(f->unit, FCSALT_16); ppp_recv_fcs(f->unit, FCSALT_16); #endif fsm_setpeermru(f->unit, mtu); } /* * lcp_starting - LCP needs the lower layer up. */ static void lcp_starting(f) fsm *f; { link_required(f->unit); } /* * lcp_finished - LCP has finished with the lower layer. */ static void lcp_finished(f) fsm *f; { link_terminated(f->unit); } /* * lcp_printpkt - print the contents of an LCP packet. */ static int lcp_printpkt(p, plen, printer, arg) u_char *p; int plen; void (*printer) __P((void *, const char *, ...)); void *arg; { int code, id, len, olen, i; u_char *pstart, *optend, cichar; u_short cishort; u_int32_t cilong; if (plen < HEADERLEN) return 0; pstart = p; GETCHAR(code, p); GETCHAR(id, p); GETSHORT(len, p); if (len < HEADERLEN || len > plen) return 0; printer(arg, " %s id=0x%x", code_name(code,1), id); len -= HEADERLEN; switch (code) { case CODE_CONFREQ: case CODE_CONFACK: case CODE_CONFNAK: case CODE_CONFREJ: /* print option list */ while (len >= 2) { GETCHAR(code, p); GETCHAR(olen, p); p -= 2; if (olen < 2 || olen > len) { break; } printer(arg, " <"); len -= olen; optend = p + olen; switch (code) { case CI_MRU: if (olen >= CILEN_SHORT) { p += 2; GETSHORT(cishort, p); printer(arg, "mru %d", cishort); } break; case CI_ASYNCMAP: if (olen >= CILEN_LONG) { p += 2; GETLONG(cilong, p); printer(arg, "asyncmap 0x%x", cilong); } break; case CI_AUTHTYPE: if (olen >= CILEN_SHORT) { p += 2; printer(arg, "auth "); GETSHORT(cishort, p); switch (cishort) { case PPP_PAP: printer(arg, "pap"); break; case PPP_CHAP: printer(arg, "chap"); if (p < optend) { switch (*p) { case CHAP_DIGEST_MD5: printer(arg, " MD5"); ++p; break; case CHAP_MICROSOFT: printer(arg, " m$oft"); ++p; break; case CHAP_MICROSOFT_V2: printer(arg, " m$oft-v2"); ++p; break; } } break; #ifdef PPP_EAP case PPP_EAP: printer(arg, "eap"); break; #endif case 0xC027: printer(arg, "spap"); break; case 0xC123: printer(arg, "old-spap"); break; default: printer(arg, "0x%x", cishort); } } break; case CI_QUALITY: if (olen >= CILEN_SHORT) { p += 2; printer(arg, "quality "); GETSHORT(cishort, p); switch (cishort) { case PPP_LQR: printer(arg, "lqr"); break; default: printer(arg, "0x%x", cishort); } } break; case CI_CALLBACK: if (olen >= CILEN_CHAR) { p += 2; printer(arg, "callback "); GETCHAR(cichar, p); if (cichar <= 6 && *callback_strings[(int)cichar] != '\0') { printer(arg, "%s", callback_strings[(int)cichar]); } else { printer(arg, "0x%x", cichar); } } break; case CI_MAGICNUMBER: if (olen >= CILEN_LONG) { p += 2; GETLONG(cilong, p); printer(arg, "magic 0x%x", cilong); } break; case CI_PCOMPRESSION: if (olen >= CILEN_VOID) { p += 2; printer(arg, "pcomp"); } break; case CI_ACCOMPRESSION: if (olen >= CILEN_VOID) { p += 2; printer(arg, "accomp"); } break; case CI_FCSALTERN: if (olen >= CILEN_CHAR) { char **cpp; int needcomma = 0; p += 2; GETCHAR(cichar, p); for (cpp = fcsalt_strings; *cpp != NULL; cpp++) if (cichar & 1<<(cpp-fcsalt_strings)) { cichar &= ~(1<<(cpp-fcsalt_strings)); printer(arg, (needcomma ? ",%s" : "fcs %s"), *cpp); needcomma = 1; } if (cichar != 0 || !needcomma) printer(arg, (needcomma ? ",0x%x" : "fcs 0x%x"), cichar); } break; case CI_NUMBERED: if (olen >= CILEN_SHORT) { p += 2; GETCHAR(cichar, p); printer(arg, "numb win %d", cichar); GETCHAR(cichar, p); printer(arg, " addr %d", cichar); } break; case CI_MRRU: if (olen >= CILEN_SHORT) { p += 2; GETSHORT(cishort, p); printer(arg, "mrru %d", cishort); } break; case CI_SSNHF: if (olen >= CILEN_VOID) { p += 2; printer(arg, "ssnhf"); } break; case CI_EPDISC: if (olen >= CILEN_CHAR) { struct epdisc epd; p += 2; GETCHAR(epd.class, p); epd.length = olen - CILEN_CHAR; if (epd.length > MAX_ENDP_LEN) epd.length = MAX_ENDP_LEN; if (epd.length > 0) { BCOPY(p, epd.value, epd.length); p += epd.length; } printer(arg, "endpoint [%s]", epdisc_to_str(&epd)); } break; case CI_LINKDISC: if (olen >= CILEN_SHORT) { p += 2; GETSHORT(cishort, p); printer(arg, "linkdisc %d", cishort); } break; case CI_COBS: if (olen >= CILEN_CHAR) { p += 2; GETCHAR(cichar, p); printer(arg, "cobs 0x%x", cichar); } break; case CI_PFXELISION: if (olen >= CILEN_CHAR) { p += 2; printer(arg, "pfx"); } break; case CI_MPHDRFMT: if (olen >= CILEN_SHORT) { p += 2; printer(arg, "mphdr "); GETCHAR(cichar, p); switch (cichar) { case 2: printer(arg, "long"); break; case 6: printer(arg, "short"); break; default: printer(arg, "0x%x", cichar); break; } GETCHAR(cichar, p); printer(arg, " #cl %d", cichar); } break; case CI_I18N: if (olen >= CILEN_LONG) { p += 2; GETLONG(cilong, p); printer(arg, "i18n charset 0x%x", cilong); if (olen > CILEN_LONG) { printer(arg, " lang "); print_string((char *)p, olen-CILEN_LONG, printer, arg); p = optend; } } break; case CI_SDL: if (olen >= CILEN_VOID) { p += 2; printer(arg, "sdl"); } break; case CI_MUXING: if (olen >= CILEN_VOID) { p += 2; printer(arg, "mux"); } break; } while (p < optend) { GETCHAR(code, p); printer(arg, " %.2x", code); } printer(arg, ">"); } break; case CODE_TERMACK: case CODE_TERMREQ: if (len > 0 && *p >= ' ' && *p < 0x7f) { printer(arg, " "); print_string((char *)p, len, printer, arg); p += len; len = 0; } break; case CODE_ECHOREQ: case CODE_ECHOREP: case CODE_DISCREQ: if (len >= 4) { GETLONG(cilong, p); printer(arg, " magic=0x%x", cilong); len -= 4; } break; case CODE_IDENT: if (len >= 4) { GETLONG(cilong, p); printer(arg, " magic=0x%x", cilong); len -= 4; } else break; if (len > 0 && (len > 1 || *p != '\0')) { printer(arg, " "); print_string((char *)p, len, printer, arg); p += len; len = 0; } break; case CODE_TIMEREMAIN: if (len >= 4) { GETLONG(cilong, p); printer(arg, " magic=0x%x", cilong); len -= 4; } else break; if (len >= 4) { GETLONG(cilong, p); printer(arg, " seconds=%d", cilong); len -= 4; } else break; if (len > 0 && (len > 1 || *p != '\0')) { printer(arg, " "); print_string((char *)p, len, printer, arg); p += len; len = 0; } break; } /* print the rest of the bytes in the packet */ for (i = 0; i < len && i < 32; ++i) { GETCHAR(code, p); printer(arg, " %.2x", code); } if (i < len) { printer(arg, " ..."); p += len - i; } return p - pstart; } /* * Time to shut down the link because there is nothing out there. */ static void LcpLinkFailure (f) fsm *f; { char *close_message; if (f->state == OPENED) { if (lcp_echo_badreplies > LCP_ECHO_MAX_BADREPLIES) { info("Received %d bad echo-replies", lcp_echo_badreplies); close_message = "Receiving malformed Echo-Replies"; } else if (lcp_echo_accm_test) { /* * If this is an asynchronous line and we've missed all of * the initial echo requests, then this is probably due to * a bad ACCM. */ notice("Peer not responding to initial Echo-Requests."); notice("Negotiated asyncmap may be incorrect for this link."); close_message = "Peer not responding; perhaps bad asyncmap"; } else { info("No response to %d echo-requests", lcp_echos_pending); notice("Serial link appears to be disconnected."); close_message = "Peer not responding"; } lcp_close(f->unit, close_message); status = EXIT_PEER_DEAD; } } /* * Timer expired for the LCP echo requests from this process. */ static void LcpEchoCheck (f) fsm *f; { if (f->state != OPENED || lcp_echo_interval == 0) return; LcpSendEchoRequest (f); /* * Start the timer for the next interval. */ if (lcp_echo_timer_running) warn("assertion lcp_echo_timer_running==0 failed"); TIMEOUT (LcpEchoTimeout, f, lcp_echo_interval); lcp_echo_timer_running = 1; } /* * LcpEchoTimeout - Timer expired on the LCP echo */ static void LcpEchoTimeout (arg) void *arg; { if (lcp_echo_timer_running != 0) { lcp_echo_timer_running = 0; LcpEchoCheck ((fsm *) arg); } } /* * LcpEchoReply - LCP has received a reply to the echo */ /*ARGSUSED*/ static int lcp_received_echo_reply (f, id, inp, len) fsm *f; int id; u_char *inp; int len; { u_int32_t magic; /* Check the magic number - don't count replies from ourselves. */ if (len < 4) { dbglog("lcp: received short Echo-Reply, length %d", len); return (0); } GETLONG(magic, inp); if (lcp_gotoptions[f->unit].neg_magicnumber && magic == lcp_gotoptions[f->unit].magicnumber) { warn("appear to have received our own echo-reply!"); return (0); } /* Reset the number of outstanding echo frames */ lcp_echos_pending = 0; if (lcp_echo_accm_test) { dbglog("lcp: validated asyncmap setting"); lcp_echo_accm_test = 0; if (lcp_echo_fails == 0) lcp_echo_interval = 0; } return (1); } /* * LcpSendEchoRequest - Send an echo request frame to the peer */ static void LcpSendEchoRequest (f) fsm *f; { u_int32_t lcp_magic; u_char pkt[4+256], *pktp; int i; /* * Detect the failure of the peer at this point. If we're not currently * performing the ACCM test, then we just check for the user's echo-failure * point. If we are performing the ACCM test, then use ACCM_TEST_FAILS if * the user hasn't specified a different failure point. */ i = lcp_echo_fails; if (i == 0) i = ACCM_TEST_FAILS; if ((!lcp_echo_accm_test && lcp_echo_fails != 0 && lcp_echos_pending >= lcp_echo_fails) || (lcp_echo_accm_test && lcp_echos_pending >= i)) { LcpLinkFailure(f); lcp_echos_pending = 0; lcp_echo_badreplies = 0; } /* * Make and send the echo request frame. */ if (f->state == OPENED) { lcp_magic = lcp_gotoptions[f->unit].magicnumber; pktp = pkt; PUTLONG(lcp_magic, pktp); /* Send some test packets so we can fail the link early. */ if (lcp_echo_accm_test) { switch (use_accm_test) { case 1: /* Only the characters covered by negotiated ACCM */ for (i = 0; i < 32; i++) *pktp++ = i; break; case 2: /* All characters */ for (i = 0; i < 256; i++) *pktp++ = i; break; } } fsm_sdata(f, CODE_ECHOREQ, lcp_echo_number++ & 0xFF, pkt, pktp - pkt); ++lcp_echos_pending; } } /* * lcp_echo_lowerup - Start the timer for the LCP frame */ static void lcp_echo_lowerup (unit) int unit; { fsm *f = &lcp_fsm[unit]; /* Clear the parameters for generating echo frames */ lcp_echos_pending = 0; lcp_echo_number = 0; lcp_echo_timer_running = 0; lcp_echo_accm_test = !sync_serial && use_accm_test; /* If a timeout interval is specified then start the timer */ LcpEchoCheck(f); } /* * lcp_echo_lowerdown - Stop the timer for the LCP frame */ static void lcp_echo_lowerdown (unit) int unit; { fsm *f = &lcp_fsm[unit]; if (lcp_echo_timer_running != 0) { UNTIMEOUT (LcpEchoTimeout, f); lcp_echo_timer_running = 0; } } /* * LcpSendIdentification - Send LCP Identification string to peer. */ static void LcpSendIdentification (f) fsm *f; { u_int32_t lcp_magic; u_char pkt[4 + sizeof(identstr)], *pktp; int idlen; /* * Make and send the Identification frame. */ if (f->state == OPENED) lcp_magic = lcp_gotoptions[f->unit].magicnumber; else lcp_magic = 0; pktp = pkt; PUTLONG(lcp_magic, pktp); idlen = strlen(identstr); BCOPY(identstr, pktp, idlen); INCPTR(idlen, pktp); fsm_sdata(f, CODE_IDENT, ++f->id, pkt, pktp - pkt); } /*ARGSUSED*/ static void lcp_received_identification (f, id, inp, len) fsm *f; int id; u_char *inp; int len; { u_int32_t magic; /* Check the magic number - don't count replies from ourselves. */ if (len < 4) { dbglog("%s: received short Identification; %d < 4", len); return; } GETLONG(magic, inp); len -= 4; if (lcp_gotoptions[f->unit].neg_magicnumber && f->state == OPENED && magic == lcp_gotoptions[f->unit].magicnumber) { warn("appear to have received our own Identification!"); return; } if (len > 0 && (len > 1 || *inp != '\0')) notice("Peer Identification: %0.*v", len, inp); } /* * Send a Time-Remaining LCP packet. We don't include a message. */ static void LcpSendTimeRemaining(f, time_remaining) fsm *f; u_int32_t time_remaining; { u_int32_t lcp_magic; u_char pkt[8]; u_char *pktp; if (f->state != OPENED) return; lcp_magic = lcp_gotoptions[f->unit].magicnumber; pktp = pkt; PUTLONG(lcp_magic, pktp); PUTLONG(time_remaining, pktp); fsm_sdata(f, CODE_TIMEREMAIN, ++f->id, pkt, pktp - pkt); } /*ARGSUSED*/ static void lcp_received_timeremain(f, id, inp, len) fsm *f; int id; u_char *inp; int len; { u_int32_t magic; u_int32_t time_remaining; /* Check the magic number - don't count replies from ourselves. */ if (len < 8) { dbglog("%s: received short Time-Remain; %d < 8", len); return; } GETLONG(magic, inp); if (lcp_gotoptions[f->unit].neg_magicnumber && f->state == OPENED && magic == lcp_gotoptions[f->unit].magicnumber) { warn("appear to have received our own Time-Remain!"); return; } GETLONG(time_remaining, inp); if (len > 8) { notice("%d seconds remain: \"%.*s\"", time_remaining, len-8, inp); } else { notice("Time Remaining: %d seconds", time_remaining); } } /* * lcp_timeremaining - timeout handler which sends LCP Time-Remaining * packet. */ static void lcp_timeremaining(arg) void *arg; { struct lcp_timer *lt = (struct lcp_timer *)arg; u_int32_t time_remaining; int unit; unit = lt->unit; time_remaining = lt->tr; LcpSendTimeRemaining(&lcp_fsm[unit], time_remaining); free(lt); } /* * lcp_settimeremaining - set a timeout to send an LCP Time-Remaining * packet. The first argument, connecttime, is the time remaining * at the time this function is called. The second argument is the * desired time remaining when the packet should be sent out. */ void lcp_settimeremaining(unit, connecttime, time_remaining) int unit; u_int32_t connecttime; u_int32_t time_remaining; { struct lcp_timer *lt; if (connecttime == time_remaining) { LcpSendTimeRemaining(&lcp_fsm[unit], time_remaining); } else { lt = (struct lcp_timer *)malloc(sizeof (struct lcp_timer)); lt->unit = unit; lt->tr = time_remaining; TIMEOUT(lcp_timeremaining, (void *)lt, connecttime - time_remaining); } } /* * lcp.h - Link Control Protocol definitions. * * Copyright (c) 2000 by Sun Microsystems, Inc. * All rights reserved. * * 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. * * $Id: lcp.h,v 1.15 2000/04/04 07:06:51 paulus Exp $ */ #ifndef __LCP_H_ #define __LCP_H_ #ifdef __cplusplus extern "C" { #endif /* * Options. */ #define CI_MRU 1 /* Maximum Receive Unit */ #define CI_ASYNCMAP 2 /* Async Control Character Map */ #define CI_AUTHTYPE 3 /* Authentication Type */ #define CI_QUALITY 4 /* Quality Protocol */ #define CI_MAGICNUMBER 5 /* Magic Number */ #define CI_PCOMPRESSION 7 /* Protocol Field Compression */ #define CI_ACCOMPRESSION 8 /* Address/Control Field Compression */ #define CI_FCSALTERN 9 /* FCS Alternatives */ #define CI_NUMBERED 11 /* Numbered Mode */ #define CI_CALLBACK 13 /* callback */ #define CI_MRRU 17 /* max reconstructed receive unit; multilink */ #define CI_SSNHF 18 /* short sequence numbers for multilink */ #define CI_EPDISC 19 /* endpoint discriminator */ #define CI_LINKDISC 23 /* Link Discriminator (BACP) */ #define CI_COBS 25 /* Consistent Overhead Byte Stuffing */ #define CI_PFXELISION 26 /* Prefix Elision */ #define CI_MPHDRFMT 27 /* Multilink Header Format */ #define CI_I18N 28 /* Internationalization */ #define CI_SDL 29 /* Simple Data Link */ #define CI_MUXING 30 /* PPP Muxing */ /* * LCP-specific packet types. */ #define CODE_PROTREJ 8 /* Protocol Reject */ #define CODE_ECHOREQ 9 /* Echo Request */ #define CODE_ECHOREP 10 /* Echo Reply */ #define CODE_DISCREQ 11 /* Discard Request */ #define CODE_IDENT 12 /* Identification */ #define CODE_TIMEREMAIN 13 /* Time Remaining */ /* * Callback operation field values */ #define CBOP_AUTH 0 /* Location determined by user auth */ #define CBOP_DIALSTR 1 /* Dialing string */ #define CBOP_LOCATION 2 /* Location identifier */ #define CBOP_E164 3 /* E.164 number */ #define CBOP_X500 4 /* X.500 distinguished name */ #define CBOP_CBCP 6 /* Use callback control protocol */ /* FCS-Alternatives bits (RFC 1570) */ #define FCSALT_NULL 1 /* None for network data; default otherwise */ #define FCSALT_16 2 /* CRC-16 */ #define FCSALT_32 4 /* CRC-32 */ /* An endpoint discriminator, used with multilink. */ #define MAX_ENDP_LEN 20 /* maximum length of discriminator value */ struct epdisc { unsigned char class; unsigned char length; unsigned char value[MAX_ENDP_LEN]; }; /* values for epdisc.class */ #define EPD_NULL 0 /* null discriminator, no data */ #define EPD_LOCAL 1 #define EPD_IP 2 #define EPD_MAC 3 #define EPD_MAGIC 4 #define EPD_PHONENUM 5 /* * The state of options is described by an lcp_options structure. * * We encode CHAP/MS-CHAP/MS-CHAPv2 options as Booleans. This is done * so that we can represent the choices of requiring or refusing each * separately. The chap_mdtype value can't do that. */ typedef struct lcp_options { bool passive; /* Don't die if we don't get a response */ bool silent; /* Wait for the other end to start first */ bool restart; /* Restart vs. exit after close */ bool neg_mru; /* Negotiate the MRU? */ bool neg_asyncmap; /* Negotiate the async map? */ bool neg_upap; /* Ask for UPAP authentication? */ bool neg_chap; /* Ask for CHAP authentication? */ bool neg_mschap; /* Ask for MS-CHAPv1 authentication? */ bool neg_mschapv2; /* Ask for MS-CHAPv2 authentication? */ bool neg_magicnumber; /* Ask for magic number? */ bool neg_pcompression; /* HDLC Protocol Field Compression? */ bool neg_accompression; /* HDLC Address/Control Field Compression? */ bool neg_lqr; /* Negotiate use of Link Quality Reports */ bool neg_cbcp; /* Negotiate use of CBCP */ bool neg_mrru; /* negotiate multilink MRRU */ #ifdef MUX_FRAME u_int32_t pppmux; /* Negotiate for PPP Multiplexing option */ #endif bool neg_ssnhf; /* negotiate short sequence numbers */ bool neg_endpoint; /* negotiate endpoint discriminator */ bool neg_fcs; /* negotiate FCS alternatives */ int mru; /* Value of MRU */ int mrru; /* Value of MRRU, and multilink enable */ u_char chap_mdtype; /* which MD type (hashing algorithm) */ u_char fcs_type; /* selected FCS type(s) */ u_int32_t asyncmap; /* Value of async map */ u_int32_t magicnumber; int numloops; /* Number of loops during magic number neg. */ u_int32_t lqr_period; /* Reporting period for LQR 1/100ths second */ struct epdisc endpoint; /* endpoint discriminator */ } lcp_options; /* * The structure passed to lcp_settimeremaining(), holds the unit * number of the link being timed, and the time remaining for that * connection. */ struct lcp_timer { int unit; u_int32_t tr; }; extern fsm lcp_fsm[]; extern lcp_options lcp_wantoptions[]; extern lcp_options lcp_gotoptions[]; extern lcp_options lcp_allowoptions[]; extern lcp_options lcp_hisoptions[]; extern u_int32_t xmit_accm[][8]; void lcp_open __P((int)); void lcp_close __P((int, char *)); void lcp_lowerup __P((int)); void lcp_lowerdown __P((int)); void lcp_sprotrej __P((int, u_char *, int)); /* send protocol reject */ void lcp_settimeremaining __P((int, u_int32_t, u_int32_t)); /* * Procedures exported from multilink.c */ extern char *epdisc_to_str __P((struct epdisc *)); /* string from endpoint discriminator */ extern int str_to_epdisc __P((struct epdisc *, char *)); /* endpt discriminator from str */ extern struct protent lcp_protent; /* Default number of times we receive our magic number from the peer before deciding the link is looped-back. */ #define DEFLOOPBACKFAIL 10 #ifdef __cplusplus } #endif #endif /* __LCP_H_ */ /* * magic.c - PPP Magic Number routines. * * 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. */ #include #include #include #include #include #include "pppd.h" #include "magic.h" #ifdef NO_DRAND48 long mrand48 __P((void)); void srand48 __P((long)); #endif /* * magic_init - Initialize the magic number generator. * * Attempts to compute a random number seed which will not repeat. * The current method uses the current hostid, current process ID * and current time, currently. */ void magic_init() { long seed; struct timeval t; (void) gettimeofday(&t, NULL); seed = get_host_seed() ^ t.tv_sec ^ t.tv_usec ^ getpid(); srand48(seed); } /* * magic - Returns the next magic number. */ u_int32_t magic() { return (u_int32_t) mrand48(); } #ifdef NO_DRAND48 /* * Substitute procedures for those systems which don't have * drand48 et al. */ double drand48() { return (double)random() / (double)0x7fffffffL; /* 2**31-1 */ } long mrand48() { return random(); } void srand48(seedval) long seedval; { srandom((int)seedval); } #endif /* * magic.h - PPP Magic Number definitions. * * 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. * * $Id: magic.h,v 1.3 1994/09/21 06:47:37 paulus Exp $ */ void magic_init __P((void)); /* Initialize the magic number generator */ u_int32_t magic __P((void)); /* Returns the next magic number */ /* * main.c - Point-to-Point Protocol main module * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. * * SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES * * 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. */ /* * Copyright (c) 2016 by Delphix. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pppd.h" #include "magic.h" #include "fsm.h" #include "lcp.h" #include "ipcp.h" #ifdef INET6 #include "ipv6cp.h" #endif #include "upap.h" #include "chap.h" #include "ccp.h" #include "pathnames.h" #include "patchlevel.h" #ifdef HAVE_MULTILINK #include "tdb.h" #endif #ifdef CBCP_SUPPORT #include "cbcp.h" #endif #ifdef IPX_CHANGE #include "ipxcp.h" #endif /* IPX_CHANGE */ #ifdef AT_CHANGE #include "atcp.h" #endif /* interface vars */ char ifname[32]; /* Interface name */ int ifunit = -1; /* Interface unit number */ char *progname; /* Name of this program */ char hostname[MAXHOSTNAMELEN+1]; /* Our hostname */ static char pidfilename[MAXPATHLEN]; /* name of pid file */ static char linkpidfile[MAXPATHLEN]; /* name of linkname pid file */ char ppp_devnam[MAXPATHLEN]; /* name of PPP tty (maybe ttypx) */ static uid_t uid; /* Our real user-id */ static int conn_running; /* we have a [dis]connector running */ int ttyfd; /* Serial port file descriptor */ mode_t tty_mode = (mode_t)-1; /* Original access permissions to tty */ int baud_rate; /* Actual bits/second for serial device */ bool hungup; /* terminal has been hung up */ bool privileged; /* we're running as real uid root */ bool need_holdoff; /* need holdoff period before restarting */ bool detached; /* have detached from terminal */ struct stat devstat; /* result of stat() on devnam */ bool prepass = 0; /* doing prepass to find device name */ int devnam_fixed; /* set while in options.ttyxx file */ volatile int status; /* exit status for pppd */ int unsuccess; /* # unsuccessful connection attempts */ int do_callback; /* != 0 if we should do callback next */ int doing_callback; /* != 0 if we are doing callback */ char *callback_script; /* script for doing callback */ #ifdef HAVE_MULTILINK TDB_CONTEXT *pppdb; /* database for storing status etc. */ char db_key[32]; #endif /* * For plug-in usage: * * holdoff_hook - Can be used to change the demand-dial hold-off * time dynamically. This is normally set by the * "holdoff" option, and is 30 seconds by default. * * new_phase_hook - This is called for each change in the PPP * phase (per RFC 1661). This can be used to log * progress. * * check_options_hook - This is called before doing sys_init() * and allows the plugin to verify the selected options. * * updown_script_hook - This is called with the proposed * command-line arguments for any of the * /etc/ppp/{ip,ipv6,ipx,auth}-{up,down} scripts before * fork/exec. It can be used to add or change arguments. * * device_pipe_hook - If this is set, then an extra fd (3) is * passed to the connect/disconnect script. This extra * fd is the write side of a pipe, and the read side is * passed to this routine. This can be used to pass * arbitrary data from the script back to pppd. */ int (*holdoff_hook) __P((void)) = NULL; int (*new_phase_hook) __P((int new, int old)) = NULL; int (*check_options_hook) __P((uid_t uid)) = NULL; int (*updown_script_hook) __P((const char ***argsp)) = NULL; void (*device_pipe_hook) __P((int pipefd)) = NULL; static int fd_ppp = -1; /* fd for talking PPP */ static int fd_loop; /* fd for getting demand-dial packets */ static int pty_master; /* fd for master side of pty */ int pty_slave = -1; /* fd for slave side of pty */ static int real_ttyfd; /* fd for actual serial port (not pty) */ int phase; /* where the link is at */ int kill_link; int open_ccp_flag; static int waiting; /* for input from peer or timer expiration */ static sigjmp_buf sigjmp; char **script_env; /* Env. variable values for scripts */ int s_env_nalloc; /* # words avail at script_env */ u_char outpacket_buf[PPP_MRU+PPP_HDRLEN]; /* buffer for outgoing packet */ u_char inpacket_buf[PPP_MRU+PPP_HDRLEN]; /* buffer for incoming packet */ u_char nak_buffer[PPP_MRU]; /* where we construct a nak packet */ static int n_children; /* # child processes still running */ static bool got_sigchld; /* set if we have received a SIGCHLD */ static sigset_t main_sigmask; /* signals blocked while dispatching */ static bool locked; /* lock() has succeeded */ static bool privopen; /* don't lock, open device as root */ char *no_ppp_msg = "Sorry - this system lacks PPP kernel support\n"; GIDSET_TYPE groups[NGROUPS_MAX];/* groups the user is in */ int ngroups; /* How many groups valid in groups */ static struct timeval start_time; /* Time when link was started. */ struct pppd_stats link_stats; int link_connect_time; bool link_stats_valid; static pid_t charshunt_pid; /* Process ID for charshunt */ extern option_t general_options[]; extern option_t auth_options[]; /* * We maintain a list of child process pids and * functions to call when they exit. */ struct subprocess { pid_t pid; char *prog; void (*done) __P((void *, int)); void *arg; struct subprocess *next; }; static struct subprocess *children; /* Prototypes for procedures local to this file. */ static void setup_signals __P((void)); static void create_pidfile __P((void)); static void create_linkpidfile __P((void)); static void cleanup __P((void)); static void close_tty __P((void)); static void get_input __P((void)); static void calltimeout __P((void)); static struct timeval *timeleft __P((struct timeval *)); static void kill_my_pg __P((int)); static void hup __P((int)); static void term __P((int)); static void chld __P((int)); static void toggle_debug __P((int)); static void open_ccp __P((int)); static void bad_signal __P((int)); static void holdoff_end __P((void *)); static int device_script __P((char *, int, int, int, char *)); static int reap_kids __P((int waitfor)); static void record_child __P((pid_t, char *, void (*) (void *, int), void *)); static int open_socket __P((char *)); static int start_charshunt __P((int, int)); static void charshunt_done __P((void *, int)); static void charshunt __P((int, int, char *)); static int record_write __P((FILE *, int code, u_char *buf, int nb, struct timeval *)); static void final_reap __P((void)); #ifdef HAVE_MULTILINK static void update_db_entry __P((void)); static void add_db_key __P((const char *)); static void delete_db_key __P((const char *)); static void cleanup_db __P((void)); #endif int main __P((int, char *[])); #ifdef ultrix #undef O_NONBLOCK #define O_NONBLOCK O_NDELAY #endif #ifdef ULTRIX #define setlogmask(x) 0 #endif /* Backward compatibility for Linux */ #ifndef RECMARK_TIMESTART #define RECMARK_STARTSEND 1 #define RECMARK_STARTRECV 2 #define RECMARK_ENDSEND 3 #define RECMARK_ENDRECV 4 #define RECMARK_TIMEDELTA32 5 #define RECMARK_TIMEDELTA8 6 #define RECMARK_TIMESTART 7 #endif /* * PPP Data Link Layer "protocol" table. * One entry per supported protocol. * The last entry must be NULL. */ struct protent *protocols[] = { &lcp_protent, &pap_protent, &chap_protent, #ifdef CBCP_SUPPORT &cbcp_protent, #endif &ipcp_protent, #ifdef INET6 &ipv6cp_protent, #endif &ccp_protent, #ifdef IPX_CHANGE &ipxcp_protent, #endif #ifdef AT_CHANGE &atcp_protent, #endif NULL }; int main(argc, argv) int argc; char *argv[]; { int i, fdflags, t; char *p, *connector; struct passwd *pw; struct timeval timo; struct protent *protp; struct stat statbuf; char numbuf[16]; ifname[0] = '\0'; new_phase(PHASE_INITIALIZE); /* * Ensure that fds 0, 1, 2 are open, to /dev/null if nowhere else. * This way we can close 0, 1, 2 in detach() without clobbering * a fd that we are using. */ if ((i = open(_PATH_DEVNULL, O_RDWR)) >= 0) { while (0 <= i && i <= 2) i = dup(i); if (i >= 0) (void) close(i); } script_env = NULL; /* Initialize syslog facilities */ reopen_log(); if (gethostname(hostname, MAXHOSTNAMELEN+1) < 0 ) { option_error("Couldn't get hostname: %m"); exit(1); } hostname[MAXHOSTNAMELEN] = '\0'; /* make sure we don't create world or group writable files. */ (void) umask(umask(0777) | 022); uid = getuid(); privileged = (uid == 0); (void) slprintf(numbuf, sizeof(numbuf), "%d", uid); script_setenv("ORIG_UID", numbuf, 0); ngroups = getgroups(NGROUPS_MAX, groups); /* * Initialize magic number generator now so that protocols may * use magic numbers in initialization. */ magic_init(); progname = *argv; prepass = 0; /* * Initialize to the standard option set, then parse, in order, the * system options file, the user's options file, the tty's options file, * and the command line arguments. At last, install the options declared * by each protocol into the extra_option list. */ for (i = 0; (protp = protocols[i]) != NULL; ++i) { (*protp->init)(0); if (protp->options != NULL) { add_options(protp->options); } } /* * Install "generic" options into the extra_options list. */ add_options(auth_options); add_options(general_options); /* Install any system-specific options (or remove unusable ones) */ sys_options(); if (!options_from_file(_PATH_SYSOPTIONS, !privileged, 0, 1) || !options_from_user()) exit(EXIT_OPTION_ERROR); /* scan command line and options files to find device name */ prepass = 1; (void) parse_args(argc-1, argv+1); prepass = 0; /* * Work out the device name, if it hasn't already been specified. */ using_pty = notty || ptycommand != NULL || pty_socket != NULL; if (!using_pty && default_device && !direct_tty) { char *p; if (!isatty(0) || (p = ttyname(0)) == NULL) { option_error("no device specified and stdin is not a tty"); exit(EXIT_OPTION_ERROR); } (void) strlcpy(devnam, p, sizeof(devnam)); if (stat(devnam, &devstat) < 0) fatal("Couldn't stat default device %s: %m", devnam); } /* * Parse the tty options file and the command line. * The per-tty options file should not change * ptycommand, pty_socket, notty or devnam. */ devnam_fixed = 1; if (!using_pty && !direct_tty) { if (!options_for_tty()) exit(EXIT_OPTION_ERROR); } devnam_fixed = 0; if (!parse_args(argc-1, argv+1)) exit(EXIT_OPTION_ERROR); /* * Check that we are running as root. */ if (geteuid() != 0) { option_error("must be root to run %s, since it is not setuid-root", argv[0]); exit(EXIT_NOT_ROOT); } if (!ppp_available()) { option_error(no_ppp_msg); exit(EXIT_NO_KERNEL_SUPPORT); } /* * Check that the options given are valid and consistent. */ if (!sys_check_options()) exit(EXIT_OPTION_ERROR); auth_check_options(); #ifdef HAVE_MULTILINK mp_check_options(); #endif for (i = 0; (protp = protocols[i]) != NULL; ++i) if (protp->enabled_flag && protp->check_options != NULL) (*protp->check_options)(); if (demand && (connect_script == NULL)) { option_error("connect script is required for demand-dialling\n"); exit(EXIT_OPTION_ERROR); } if (updetach && (nodetach || demand)) { option_error("updetach cannot be used with %s", nodetach ? "nodetach" : "demand"); exit(EXIT_OPTION_ERROR); } /* default holdoff to 0 if no connect script has been given */ if ((connect_script == NULL) && !holdoff_specified) holdoff = 0; if (using_pty || direct_tty) { if (!default_device) { option_error("%s option precludes specifying device name", notty? "notty": "pty"); exit(EXIT_OPTION_ERROR); } if (ptycommand != NULL && (notty || direct_tty)) { option_error("pty option is incompatible with notty option"); exit(EXIT_OPTION_ERROR); } if (pty_socket != NULL && (ptycommand != NULL || notty || direct_tty)) { option_error("socket option is incompatible with pty and notty"); exit(EXIT_OPTION_ERROR); } default_device = notty || direct_tty; lockflag = 0; modem = 0; if (default_device && log_to_fd <= 1) log_to_fd = -1; } else { /* * If the user has specified a device which is the same as * the one on stdin, pretend they didn't specify any. * If the device is already open read/write on stdin, * we assume we don't need to lock it, and we can open it as root. */ if (fstat(0, &statbuf) >= 0 && S_ISCHR(statbuf.st_mode) && statbuf.st_rdev == devstat.st_rdev) { default_device = 1; fdflags = fcntl(0, F_GETFL); if (fdflags != -1 && (fdflags & O_ACCMODE) == O_RDWR) privopen = 1; } } if (default_device) nodetach = 1; /* * Don't send log messages to the serial port, it tends to * confuse the peer. :-) */ if (log_to_fd >= 0 && fstat(log_to_fd, &statbuf) >= 0 && S_ISCHR(statbuf.st_mode) && statbuf.st_rdev == devstat.st_rdev) log_to_fd = -1; early_log = 0; if (debug) (void) setlogmask(LOG_UPTO(LOG_DEBUG)); /* * Initialize system-dependent stuff. */ if (check_options_hook != NULL && (*check_options_hook)(uid) == -1) { exit(EXIT_OPTION_ERROR); } sys_init(!devnam_info.priv && !privopen); #ifdef HAVE_MULTILINK pppdb = tdb_open(_PATH_PPPDB, 0, 0, O_RDWR|O_CREAT, 0644); if (pppdb != NULL) { (void) slprintf(db_key, sizeof(db_key), "pppd%d", getpid()); update_db_entry(); } else { warn("Warning: couldn't open ppp database %s", _PATH_PPPDB); if (multilink) { warn("Warning: disabling multilink"); multilink = 0; } } #endif /* * Detach ourselves from the terminal, if required, and identify * who is running us. Printing to stderr stops here unless * nodetach or updetach is set. */ if (!nodetach && !updetach) detach(); p = getlogin(); if (p == NULL) { pw = getpwuid(uid); if (pw != NULL && pw->pw_name != NULL) p = pw->pw_name; else p = "(unknown)"; } syslog(LOG_NOTICE, "pppd %s.%d%s started by %s, uid %d", VERSION, PATCHLEVEL, IMPLEMENTATION, p, uid); script_setenv("PPPLOGNAME", p, 0); if (devnam[0] != '\0') script_setenv("DEVICE", devnam, 1); (void) slprintf(numbuf, sizeof(numbuf), "%d", getpid()); script_setenv("PPPD_PID", numbuf, 1); setup_signals(); waiting = 0; create_linkpidfile(); /* * If we're doing dial-on-demand, set up the interface now. */ if (demand) { /* * Open the loopback channel and set it up to be the ppp interface. */ #ifdef HAVE_MULTILINK (void) tdb_writelock(pppdb); #endif set_ifunit(1); fd_loop = open_ppp_loopback(); #ifdef HAVE_MULTILINK (void) tdb_writeunlock(pppdb); #endif /* * Configure the interface and mark it up, etc. */ demand_conf(); } new_phase(PHASE_INITIALIZED); do_callback = 0; for (;;) { need_holdoff = 1; ttyfd = -1; real_ttyfd = -1; status = EXIT_OK; ++unsuccess; doing_callback = do_callback; do_callback = 0; if (demand && !doing_callback) { /* * Don't do anything until we see some activity. */ kill_link = 0; new_phase(PHASE_DORMANT); demand_unblock(); add_fd(fd_loop); for (;;) { if (sigsetjmp(sigjmp, 1) == 0) { (void) sigprocmask(SIG_BLOCK, &main_sigmask, NULL); if (kill_link || got_sigchld) { (void) sigprocmask(SIG_UNBLOCK, &main_sigmask, NULL); } else { waiting = 1; (void) sigprocmask(SIG_UNBLOCK, &main_sigmask, NULL); wait_input(timeleft(&timo)); } } waiting = 0; calltimeout(); if (kill_link) { if (!persist) break; kill_link = 0; } if (get_loop_output()) break; if (got_sigchld) (void) reap_kids(0); } remove_fd(fd_loop); if (kill_link && !persist) break; /* * Now we want to bring up the link. */ demand_block(); info("Starting link"); } new_phase(doing_callback ? PHASE_CALLINGBACK : PHASE_SERIALCONN); /* * Get a pty master/slave pair if the pty, notty, socket, * or record options were specified. */ (void) strlcpy(ppp_devnam, devnam, sizeof(ppp_devnam)); pty_master = -1; pty_slave = -1; if (using_pty || record_file != NULL) { if (!get_pty(&pty_master, &pty_slave, ppp_devnam, uid)) { error("Couldn't allocate pseudo-tty"); status = EXIT_FATAL_ERROR; goto fail; } set_up_tty(pty_slave, 1); } /* * Lock the device if we've been asked to. */ status = EXIT_LOCK_FAILED; if (lockflag && !privopen && !direct_tty) { if (lock(devnam) < 0) goto fail; locked = 1; } /* * Open the serial device and set it up to be the ppp interface. * First we open it in non-blocking mode so we can set the * various termios flags appropriately. If we aren't dialling * out and we want to use the modem lines, we reopen it later * in order to wait for the carrier detect signal from the modem. */ hungup = 0; kill_link = 0; connector = doing_callback? callback_script: connect_script; if (direct_tty) { ttyfd = 0; } else if (devnam[0] != '\0') { for (;;) { /* If the user specified the device name, become the user before opening it. */ int err; if (!devnam_info.priv && !privopen) (void) seteuid(uid); if ((ttyfd = sys_extra_fd()) < 0) ttyfd = open(devnam, O_NONBLOCK | O_RDWR); err = errno; if (!devnam_info.priv && !privopen) (void) seteuid(0); if (ttyfd >= 0) break; errno = err; if (err != EINTR) { error("Failed to open %s: %m", devnam); status = EXIT_OPEN_FAILED; } if (!persist || err != EINTR) goto fail; } if ((fdflags = fcntl(ttyfd, F_GETFL)) == -1 || fcntl(ttyfd, F_SETFL, fdflags & ~O_NONBLOCK) < 0) warn("Couldn't reset non-blocking mode on device: %m"); /* * Do the equivalent of `mesg n' to stop broadcast messages. */ if (fstat(ttyfd, &statbuf) < 0 || fchmod(ttyfd, statbuf.st_mode & ~(S_IWGRP | S_IWOTH)) < 0) { warn("Couldn't restrict write permissions to %s: %m", devnam); } else tty_mode = statbuf.st_mode; /* * Set line speed, flow control, etc. * If we have a non-null connection or initializer script, * on most systems we set CLOCAL for now so that we can talk * to the modem before carrier comes up. But this has the * side effect that we might miss it if CD drops before we * get to clear CLOCAL below. On systems where we can talk * successfully to the modem with CLOCAL clear and CD down, * we could clear CLOCAL at this point. */ set_up_tty(ttyfd, ((connector != NULL && connector[0] != '\0') || initializer != NULL)); real_ttyfd = ttyfd; } /* * If the pty, socket, notty and/or record option was specified, * start up the character shunt now. */ status = EXIT_PTYCMD_FAILED; if (ptycommand != NULL) { if (record_file != NULL) { int ipipe[2], opipe[2], ok; if (pipe(ipipe) < 0 || pipe(opipe) < 0) fatal("Couldn't create pipes for record option: %m"); dbglog("starting charshunt for pty option"); ok = device_script(ptycommand, opipe[0], ipipe[1], 1, "record") == 0 && start_charshunt(ipipe[0], opipe[1]); (void) close(ipipe[0]); (void) close(ipipe[1]); (void) close(opipe[0]); (void) close(opipe[1]); if (!ok) goto fail; } else { if (device_script(ptycommand, pty_master, pty_master, 1, "pty") < 0) goto fail; ttyfd = pty_slave; (void) close(pty_master); pty_master = -1; } } else if (pty_socket != NULL) { int fd = open_socket(pty_socket); if (fd < 0) goto fail; dbglog("starting charshunt for socket option"); if (!start_charshunt(fd, fd)) goto fail; } else if (notty) { dbglog("starting charshunt for notty option"); if (!start_charshunt(0, 1)) goto fail; } else if (record_file != NULL) { dbglog("starting charshunt for record option"); if (!start_charshunt(ttyfd, ttyfd)) goto fail; } /* run connection script */ if (((connector != NULL) && (connector[0] != '\0')) || initializer) { if (real_ttyfd != -1) { /* XXX do this if doing_callback == CALLBACK_DIALIN? */ if (!default_device && modem && !direct_tty) { setdtr(real_ttyfd, 0); /* in case modem is off hook */ (void) sleep(1); setdtr(real_ttyfd, 1); } } if ((initializer != NULL) && (initializer[0] != '\0')) { if (device_script(initializer, ttyfd, ttyfd, 0, "init") < 0) { error("Initializer script failed"); status = EXIT_INIT_FAILED; goto fail; } if (kill_link) goto disconnect; info("Serial port initialized."); } if ((connector != NULL) && (connector[0] != '\0')) { if (device_script(connector, ttyfd, ttyfd, 0, "connect") < 0) { error("Connect script failed"); status = EXIT_CONNECT_FAILED; goto fail; } if (kill_link) goto disconnect; info("Serial connection established."); } /* * Clear CLOCAL if modem option -- we now have carrier * established, and we should respect loss of carrier. */ if (real_ttyfd != -1) set_up_tty(real_ttyfd, 0); if (doing_callback == CALLBACK_DIALIN) connector = NULL; } /* reopen tty if necessary to wait for carrier */ if (connector == NULL && modem && devnam[0] != '\0' && !direct_tty) { for (;;) { if ((i = open(devnam, O_RDWR)) >= 0) break; if (errno != EINTR) { error("Failed to reopen %s: %m", devnam); status = EXIT_OPEN_FAILED; } if (!persist || errno != EINTR || hungup || kill_link) goto fail; } (void) close(i); } (void) slprintf(numbuf, sizeof(numbuf), "%d", baud_rate); script_setenv("SPEED", numbuf, 0); /* run welcome script, if any */ if ((welcomer != NULL) && (welcomer[0] != '\0')) { if (device_script(welcomer, ttyfd, ttyfd, 0, "welcome") < 0) warn("Welcome script failed"); } /* set up the serial device as a ppp interface */ #ifdef HAVE_MULTILINK (void) tdb_writelock(pppdb); #endif fd_ppp = establish_ppp(ttyfd); if (fd_ppp < 0) { #ifdef HAVE_MULTILINK (void) tdb_writeunlock(pppdb); #endif status = EXIT_FATAL_ERROR; goto disconnect; } if (!demand && ifunit >= 0) set_ifunit(1); #ifdef HAVE_MULTILINK (void) tdb_writeunlock(pppdb); #endif /* * Start opening the connection and wait for * incoming events (reply, timeout, etc.). */ notice("Connect: %s <--> %s", ifname, ppp_devnam); (void) gettimeofday(&start_time, NULL); link_stats_valid = 0; script_unsetenv("CONNECT_TIME"); script_unsetenv("BYTES_SENT"); script_unsetenv("BYTES_RCVD"); lcp_lowerup(0); /* Mostly for accounting purposes */ new_phase(PHASE_CONNECTED); /* * If we are initiating this connection, wait for a short * time for something from the peer. This can avoid bouncing * our packets off its tty before it has set up the tty. */ add_fd(fd_ppp); if (connect_delay != 0 && (connector != NULL || ptycommand != NULL)) { struct timeval t; t.tv_sec = connect_delay / 1000; t.tv_usec = connect_delay % 1000; wait_input(&t); } lcp_open(0); /* Start protocol */ open_ccp_flag = 0; status = EXIT_NEGOTIATION_FAILED; new_phase(PHASE_ESTABLISH); while (phase != PHASE_DEAD) { if (sigsetjmp(sigjmp, 1) == 0) { (void) sigprocmask(SIG_BLOCK, &main_sigmask, NULL); if (kill_link || open_ccp_flag || got_sigchld) { (void) sigprocmask(SIG_UNBLOCK, &main_sigmask, NULL); } else { waiting = 1; (void) sigprocmask(SIG_UNBLOCK, &main_sigmask, NULL); wait_input(timeleft(&timo)); } } waiting = 0; calltimeout(); get_input(); if (kill_link) { lcp_close(0, "User request"); kill_link = 0; } if (open_ccp_flag) { if (phase == PHASE_NETWORK || phase == PHASE_RUNNING) { /* Uncloak ourselves. */ ccp_fsm[0].flags &= ~OPT_SILENT; (*ccp_protent.open)(0); } open_ccp_flag = 0; } if (got_sigchld) (void) reap_kids(0); /* Don't leave dead kids lying around */ } /* * Print connect time and statistics. */ if (link_stats_valid) { int t = (link_connect_time + 5) / 6; /* 1/10ths of minutes */ info("Connect time %d.%d minutes.", t/10, t%10); info("Sent %" PPP_COUNTER_F " bytes (%" PPP_COUNTER_F " packets), received %" PPP_COUNTER_F " bytes (%" PPP_COUNTER_F " packets).", link_stats.bytes_out, link_stats.pkts_out, link_stats.bytes_in, link_stats.pkts_in); } /* * Delete pid file before disestablishing ppp. Otherwise it * can happen that another pppd gets the same unit and then * we delete its pid file. */ if (!demand) { if (pidfilename[0] != '\0' && unlink(pidfilename) < 0 && errno != ENOENT) warn("unable to delete pid file %s: %m", pidfilename); pidfilename[0] = '\0'; } /* * If we may want to bring the link up again, transfer * the ppp unit back to the loopback. Set the * real serial device back to its normal mode of operation. */ remove_fd(fd_ppp); clean_check(); if (demand) restore_loop(); disestablish_ppp(ttyfd); fd_ppp = -1; if (!hungup) lcp_lowerdown(0); if (!demand) script_unsetenv("IFNAME"); /* * Run disconnector script, if requested. * XXX we may not be able to do this if the line has hung up! */ disconnect: if ((disconnect_script != NULL) && (disconnect_script[0] != '\0') && !hungup) { new_phase(PHASE_DISCONNECT); if (real_ttyfd >= 0) set_up_tty(real_ttyfd, 1); if (device_script(disconnect_script, ttyfd, ttyfd, 0, "disconnect") < 0) { warn("disconnect script failed"); } else { info("Serial link disconnected."); } } fail: if (pty_master >= 0) (void) close(pty_master); if (pty_slave >= 0) { (void) close(pty_slave); pty_slave = -1; } if (real_ttyfd >= 0) close_tty(); if (locked) { locked = 0; unlock(); } if (!demand) { if (pidfilename[0] != '\0' && unlink(pidfilename) < 0 && errno != ENOENT) warn("unable to delete pid file %s: %m", pidfilename); pidfilename[0] = '\0'; } if (!persist || (maxfail > 0 && unsuccess >= maxfail)) break; kill_link = 0; if (demand) demand_discard(); t = need_holdoff? holdoff: 0; if (holdoff_hook != NULL) t = (*holdoff_hook)(); if (t > 0) { new_phase(PHASE_HOLDOFF); TIMEOUT(holdoff_end, NULL, t); do { if (sigsetjmp(sigjmp, 1) == 0) { (void) sigprocmask(SIG_BLOCK, &main_sigmask, NULL); if (kill_link || got_sigchld) { (void) sigprocmask(SIG_UNBLOCK, &main_sigmask, NULL); } else { waiting = 1; (void) sigprocmask(SIG_UNBLOCK, &main_sigmask, NULL); wait_input(timeleft(&timo)); } } waiting = 0; calltimeout(); if (kill_link) { kill_link = 0; new_phase(PHASE_DORMANT); /* allow signal to end holdoff */ } if (got_sigchld) (void) reap_kids(0); } while (phase == PHASE_HOLDOFF); if (!persist) break; } } /* Wait for scripts to finish */ final_reap(); die(status); return (0); } /* * setup_signals - initialize signal handling. */ static void setup_signals() { struct sigaction sa; /* * Compute mask of all interesting signals and install signal handlers * for each. Only one signal handler may be active at a time. Therefore, * all other signals should be masked when any handler is executing. */ (void) sigemptyset(&main_sigmask); (void) sigaddset(&main_sigmask, SIGHUP); (void) sigaddset(&main_sigmask, SIGINT); (void) sigaddset(&main_sigmask, SIGTERM); (void) sigaddset(&main_sigmask, SIGCHLD); (void) sigaddset(&main_sigmask, SIGUSR2); #define SIGNAL(s, handler) if (1) { \ sa.sa_handler = handler; \ if (sigaction(s, &sa, NULL) < 0) \ fatal("Couldn't establish signal handler (%d): %m", s); \ } else ((void)0) sa.sa_mask = main_sigmask; sa.sa_flags = 0; /*CONSTANTCONDITION*/ SIGNAL(SIGHUP, hup); /* Hangup */ /*CONSTANTCONDITION*/ SIGNAL(SIGINT, term); /* Interrupt */ /*CONSTANTCONDITION*/ SIGNAL(SIGTERM, term); /* Terminate */ /*CONSTANTCONDITION*/ SIGNAL(SIGCHLD, chld); /*CONSTANTCONDITION*/ SIGNAL(SIGUSR1, toggle_debug); /* Toggle debug flag */ /*CONSTANTCONDITION*/ SIGNAL(SIGUSR2, open_ccp); /* Reopen CCP */ /* * Install a handler for other signals which would otherwise * cause pppd to exit without cleaning up. */ /*CONSTANTCONDITION*/ SIGNAL(SIGALRM, bad_signal); /*CONSTANTCONDITION*/ SIGNAL(SIGQUIT, bad_signal); /* Do not hook any of these signals on Solaris; allow core dump instead */ #ifndef SOL2 /*CONSTANTCONDITION*/ SIGNAL(SIGABRT, bad_signal); /*CONSTANTCONDITION*/ SIGNAL(SIGFPE, bad_signal); /*CONSTANTCONDITION*/ SIGNAL(SIGILL, bad_signal); #ifndef DEBUG /*CONSTANTCONDITION*/ SIGNAL(SIGSEGV, bad_signal); #endif #ifdef SIGBUS /*CONSTANTCONDITION*/ SIGNAL(SIGBUS, bad_signal); #endif #ifdef SIGEMT /*CONSTANTCONDITION*/ SIGNAL(SIGEMT, bad_signal); #endif #ifdef SIGPOLL /*CONSTANTCONDITION*/ SIGNAL(SIGPOLL, bad_signal); #endif #ifdef SIGPROF /*CONSTANTCONDITION*/ SIGNAL(SIGPROF, bad_signal); #endif #ifdef SIGSYS /*CONSTANTCONDITION*/ SIGNAL(SIGSYS, bad_signal); #endif #ifdef SIGTRAP /*CONSTANTCONDITION*/ SIGNAL(SIGTRAP, bad_signal); #endif #ifdef SIGVTALRM /*CONSTANTCONDITION*/ SIGNAL(SIGVTALRM, bad_signal); #endif #ifdef SIGXCPU /*CONSTANTCONDITION*/ SIGNAL(SIGXCPU, bad_signal); #endif #ifdef SIGXFSZ /*CONSTANTCONDITION*/ SIGNAL(SIGXFSZ, bad_signal); #endif #endif /* * Apparently we can get a SIGPIPE when we call syslog, if * syslogd has died and been restarted. Ignoring it seems * be sufficient. */ (void) signal(SIGPIPE, SIG_IGN); } /* * set_ifunit - do things we need to do once we know which ppp * unit we are using. */ void set_ifunit(iskey) int iskey; { sys_ifname(); info("Using interface %s", ifname); script_setenv("IFNAME", ifname, iskey); if (iskey) { create_pidfile(); /* write pid to file */ create_linkpidfile(); } } /* * detach - detach us from the controlling terminal. */ void detach() { pid_t pid; char numbuf[16]; if (detached) return; if ((pid = fork()) == (pid_t)-1) { error("Couldn't detach (fork failed: %m)"); die(1); /* or just return? */ } if (pid != (pid_t)0) { /* parent */ if (locked) (void) relock(pid); exit(0); /* parent dies */ } (void) setsid(); /* * Fork again to relinquish session leadership. This is needed * to prevent the daemon from acquiring controlling terminal. */ if ((pid = fork()) == (pid_t)-1) { error("Couldn't detach (second fork failed: %m)"); die(1); /* or just return? */ } if (pid != (pid_t)0) { /* parent */ if (locked) (void) relock(pid); exit(0); /* parent dies */ } (void) chdir("/"); (void) close(0); (void) close(1); (void) close(2); detached = 1; if (!log_to_file && !log_to_specific_fd) log_to_fd = -1; /* update pid files if they have been written already */ if (pidfilename[0] != '\0') create_pidfile(); if (linkpidfile[0] != '\0') create_linkpidfile(); (void) slprintf(numbuf, sizeof(numbuf), "%d", getpid()); script_setenv("PPPD_PID", numbuf, 1); } /* * reopen_log - (re)open our connection to syslog. */ void reopen_log() { #ifdef ULTRIX openlog("pppd", LOG_PID); #else openlog("pppd", LOG_PID | LOG_NDELAY, LOG_PPP); (void) setlogmask(LOG_UPTO(LOG_INFO)); #endif } /* * Create a file containing our process ID. */ static void create_pidfile() { FILE *pidfile; (void) slprintf(pidfilename, sizeof(pidfilename), "%s%s.pid", _PATH_VARRUN, ifname); if ((pidfile = fopen(pidfilename, "w")) != NULL) { (void) fprintf(pidfile, "%u\n", (unsigned)getpid()); (void) fclose(pidfile); } else { error("Failed to create pid file %s: %m", pidfilename); pidfilename[0] = '\0'; } } static void create_linkpidfile() { FILE *pidfile; if (linkname[0] == '\0') return; script_setenv("LINKNAME", linkname, 1); (void) slprintf(linkpidfile, sizeof(linkpidfile), "%sppp-%s.pid", _PATH_VARRUN, linkname); if ((pidfile = fopen(linkpidfile, "w")) != NULL) { (void) fprintf(pidfile, "%u\n", (unsigned)getpid()); if (ifname[0] != '\0') (void) fprintf(pidfile, "%s\n", ifname); (void) fclose(pidfile); } else { error("Failed to create pid file %s: %m", linkpidfile); linkpidfile[0] = '\0'; } } /* * holdoff_end - called via a timeout when the holdoff period ends. */ /*ARGSUSED*/ static void holdoff_end(arg) void *arg; { new_phase(PHASE_DORMANT); } /* List of protocol names, to make our messages a little more informative. */ struct protocol_list { u_short proto; const char *name; } protocol_list[] = { { 0x21, "IP" }, { 0x23, "OSI Network Layer" }, { 0x25, "Xerox NS IDP" }, { 0x27, "DECnet Phase IV" }, { 0x29, "Appletalk" }, { 0x2b, "Novell IPX" }, { 0x2d, "VJ compressed TCP/IP" }, { 0x2f, "VJ uncompressed TCP/IP" }, { 0x31, "Bridging PDU" }, { 0x33, "Stream Protocol ST-II" }, { 0x35, "Banyan Vines" }, { 0x37, "Old VJ compressed TCP/IP" }, { 0x39, "AppleTalk EDDP" }, { 0x3b, "AppleTalk SmartBuffered" }, { 0x3d, "Multilink" }, { 0x3f, "NetBIOS Frame" }, { 0x41, "Cisco LAN Extension" }, { 0x43, "Ascom Timeplex" }, { 0x45, "Fujitsu Link Backup and Load Balancing (LBLB)" }, { 0x47, "DCA Remote Lan" }, { 0x49, "Serial Data Transport Protocol (PPP-SDTP)" }, { 0x4b, "SNA over 802.2" }, { 0x4d, "SNA" }, { 0x4f, "IP6 Header Compression" }, { 0x51, "KNX Bridging" }, { 0x53, "Encrypted" }, { 0x55, "per-link encrypted" }, { 0x57, "IPv6" }, { 0x59, "PPP Muxing" }, { 0x6f, "Stampede Bridging" }, { 0x73, "MP+" }, { 0xc1, "STMF" }, { 0xfb, "per-link compressed" }, { 0xfd, "compressed datagram" }, { 0x0201, "802.1d Hello Packets" }, { 0x0203, "IBM Source Routing BPDU" }, { 0x0205, "DEC LANBridge100 Spanning Tree" }, { 0x0207, "Cisco Discovery Protocol" }, { 0x0231, "Luxcom" }, { 0x0233, "Sigma Network Systems" }, { 0x0235, "Apple Client Server Protocol" }, { 0x0281, "MPLS Unicast" }, { 0x0283, "MPLS Multicast" }, { 0x0285, "IEEE p1284.4" }, { 0x0287, "ETSI TETRA TNP1" }, { 0x4021, "Stacker LZS" }, { 0x8021, "Internet Protocol Control Protocol" }, { 0x8023, "OSI Network Layer Control Protocol" }, { 0x8025, "Xerox NS IDP Control Protocol" }, { 0x8027, "DECnet Phase IV Control Protocol" }, { 0x8029, "Appletalk Control Protocol" }, { 0x802b, "Novell IPX Control Protocol" }, { 0x8031, "Bridging Control Protocol" }, { 0x8033, "Stream Protocol Control Protocol" }, { 0x8035, "Banyan Vines Control Protocol" }, { 0x803f, "NetBIOS Frames Control Protocol" }, { 0x8041, "Cisco LAN Extension Control Protocol" }, { 0x8043, "Ascom Timeplex Control Protocol" }, { 0x8045, "Fujitsu LBLB Control Protocol" }, { 0x8047, "DCA Remote Lan Network Control Protocol (RLNCP)" }, { 0x8049, "Serial Data Control Protocol (PPP-SDCP)" }, { 0x804b, "SNA over 802.2 Control Protocol" }, { 0x804d, "SNA Control Protocol" }, { 0x8051, "KNX Bridging Control Protocol" }, { 0x8053, "Encryption Control Protocol" }, { 0x8055, "Per-link Encryption Control Protocol" }, { 0x8057, "IPv6 Control Protocol" }, { 0x806f, "Stampede Bridging Control Protocol" }, { 0x80c1, "STMF Control Protocol" }, { 0x80fb, "Per-link Compression Control Protocol" }, { 0x80fd, "Compression Control Protocol" }, { 0x8207, "Cisco Discovery Control Protocol" }, { 0x8235, "Apple Client Server Control Protocol" }, { 0x8281, "MPLS Control Protocol" }, { 0x8287, "ETSI TETRA TNP1 Control Protocol" }, { 0xc021, "Link Control Protocol" }, { 0xc023, "Password Authentication Protocol" }, { 0xc025, "Link Quality Report" }, { 0xc027, "Shiva Password Authentication Protocol" }, { 0xc029, "CallBack Control Protocol (CBCP)" }, { 0xc02b, "Bandwidth Allocation Control Protocol" }, { 0xc02d, "BAP" }, { 0xc081, "Container Control Protocol" }, { 0xc223, "Challenge Handshake Authentication Protocol" }, { 0xc227, "Extensible Authentication Protocol" }, { 0xc281, "Funk Proprietary Authentication Protocol" }, { 0, NULL }, }; /* * protocol_name - find a name for a PPP protocol. */ const char * protocol_name(proto) int proto; { struct protocol_list *lp; for (lp = protocol_list; lp->proto != 0; ++lp) if (proto == lp->proto) return (lp->name); return (NULL); } static const char *phase_names[] = { PHASE__NAMES }; const char * phase_name(pval) int pval; { static char buf[32]; if (pval < 0 || pval >= Dim(phase_names)) { (void) slprintf(buf, sizeof (buf), "unknown %d", pval); return ((const char *)buf); } return (phase_names[pval]); } /* * get_input - called when incoming data is available. */ static void get_input() { int len, i; u_char *p; u_short protocol; struct protent *protp; const char *pname; p = inpacket_buf; /* point to beginning of packet buffer */ len = read_packet(inpacket_buf); if (len < 0) return; if (len == 0) { notice("Modem hangup"); hungup = 1; status = EXIT_HANGUP; lcp_lowerdown(0); /* serial link is no longer available */ link_terminated(0); return; } if (debug /*&& (debugflags & DBG_INPACKET)*/) dbglog("rcvd %P", p, len); if (len < PPP_HDRLEN) { dbglog("Discarded short packet (%d < %d)", len, PPP_HDRLEN); return; } p += 2; /* Skip address and control */ GETSHORT(protocol, p); len -= PPP_HDRLEN; pname = debug ? NULL : protocol_name(protocol); /* * Toss all non-LCP packets unless LCP is in Opened state and * discard non-authentication protocols if we're not yet * authenticated. */ if ((protocol != PPP_LCP && (phase < PHASE_AUTHENTICATE || phase > PHASE_RUNNING)) || (phase <= PHASE_AUTHENTICATE && !(protocol == PPP_LCP || protocol == PPP_LQR || protocol == PPP_PAP || protocol == PPP_CHAP))) { if (pname == NULL) dbglog("Discarded proto 0x%x in %s phase", protocol, phase_name(phase)); else dbglog("Discarded %s (0x%x) in %s phase", pname, protocol, phase_name(phase)); return; } /* * Upcall the proper protocol input routine. */ for (i = 0; (protp = protocols[i]) != NULL; ++i) { if (protp->protocol == protocol && protp->enabled_flag) { (*protp->input)(0, p, len); return; } if (protocol == (protp->protocol & ~0x8000) && protp->enabled_flag && protp->datainput != NULL) { (*protp->datainput)(0, p, len); return; } } if (debug) { if (pname != NULL) warn("Unsupported protocol '%s' (0x%x) received", pname, protocol); else warn("Unsupported protocol 0x%x received", protocol); } lcp_sprotrej(0, p - PPP_HDRLEN, len + PPP_HDRLEN); } /* * new_phase - signal the start of a new phase of pppd's operation. */ void new_phase(p) int p; { if (new_phase_hook != NULL) (*new_phase_hook)(p, phase); phase = p; } /* * die - clean up state and exit with the specified status. */ void die(status) int status; { cleanup(); if (phase != PHASE_EXIT) { syslog(LOG_INFO, "Exit."); new_phase(PHASE_EXIT); } exit(status); } /* * cleanup - restore anything which needs to be restored before we exit */ static void cleanup() { sys_cleanup(); /* XXX: Need to check if this is okay after close_tty */ if (fd_ppp >= 0) { fd_ppp = -1; disestablish_ppp(ttyfd); } if (real_ttyfd >= 0) close_tty(); if (pidfilename[0] != '\0' && unlink(pidfilename) < 0 && errno != ENOENT) warn("unable to delete pid file %s: %m", pidfilename); pidfilename[0] = '\0'; if (linkpidfile[0] != '\0' && unlink(linkpidfile) < 0 && errno != ENOENT) warn("unable to delete pid file %s: %m", linkpidfile); linkpidfile[0] = '\0'; if (locked) { locked = 0; unlock(); } #ifdef HAVE_MULTILINK if (pppdb != NULL) { cleanup_db(); pppdb = NULL; } #endif } /* * close_tty - restore the terminal device and close it. */ static void close_tty() { int fd = real_ttyfd; real_ttyfd = -1; /* drop dtr to hang up */ if (!default_device && modem) { setdtr(fd, 0); /* * This sleep is in case the serial port has CLOCAL set by default, * and consequently will reassert DTR when we close the device. */ (void) sleep(1); } restore_tty(fd); if (tty_mode != (mode_t) -1) { if (fchmod(fd, tty_mode) != 0) { /* XXX if devnam is a symlink, this will change the link */ if (chmod(devnam, tty_mode) != 0) { error("Unable to chmod file %s: %m", devnam); } } } (void) close(fd); } /* * update_link_stats - get stats at link termination. */ void update_link_stats(u) int u; { struct timeval now; char numbuf[32]; if (gettimeofday(&now, NULL) >= 0) { link_connect_time = now.tv_sec - start_time.tv_sec; (void) slprintf(numbuf, sizeof(numbuf), "%d", link_connect_time); script_setenv("CONNECT_TIME", numbuf, 0); } else { link_connect_time = 0; } if (get_ppp_stats(u, &link_stats)) { (void) slprintf(numbuf, sizeof(numbuf), "%" PPP_COUNTER_F, link_stats.bytes_out); script_setenv("BYTES_SENT", numbuf, 0); (void) slprintf(numbuf, sizeof(numbuf), "%" PPP_COUNTER_F, link_stats.bytes_in); script_setenv("BYTES_RCVD", numbuf, 0); (void) slprintf(numbuf, sizeof(numbuf), "%" PPP_COUNTER_F, link_stats.pkts_in); script_setenv("PKTS_RCVD", numbuf, 0); (void) slprintf(numbuf, sizeof(numbuf), "%" PPP_COUNTER_F, link_stats.pkts_out); script_setenv("PKTS_SENT", numbuf, 0); link_stats_valid = 1; } } struct callout { struct timeval c_time; /* time at which to call routine */ void *c_arg; /* argument to routine */ void (*c_func) __P((void *)); /* routine */ struct callout *c_next; }; static struct callout *callout = NULL; /* Callout list */ static struct timeval timenow; /* Current time */ /* * timeout - Schedule a timeout. * * Note that this timeout takes the number of seconds, NOT hz (as in * the kernel). */ void timeout(func, arg, time) void (*func) __P((void *)); void *arg; int time; { struct callout *newp, *p, **pp; MAINDEBUG(("Timeout %p:%p in %d seconds.", func, arg, time)); /* * Allocate timeout. */ if ((newp = (struct callout *) malloc(sizeof(struct callout))) == NULL) novm("callout structure for timeout."); newp->c_arg = arg; newp->c_func = func; (void) gettimeofday(&timenow, NULL); newp->c_time.tv_sec = timenow.tv_sec + time; newp->c_time.tv_usec = timenow.tv_usec; /* * Find correct place and link it in. */ for (pp = &callout; (p = *pp) != NULL; pp = &p->c_next) if (newp->c_time.tv_sec < p->c_time.tv_sec || (newp->c_time.tv_sec == p->c_time.tv_sec && newp->c_time.tv_usec < p->c_time.tv_usec)) break; newp->c_next = p; *pp = newp; } /* * untimeout - Unschedule a timeout. */ void untimeout(func, arg) void (*func) __P((void *)); void *arg; { struct callout **copp, *freep; MAINDEBUG(("Untimeout %p:%p.", func, arg)); /* * Find first matching timeout and remove it from the list. */ for (copp = &callout; (freep = *copp) != NULL; copp = &freep->c_next) if (freep->c_func == func && freep->c_arg == arg) { *copp = freep->c_next; free((char *) freep); break; } } /* * calltimeout - Call any timeout routines which are now due. */ static void calltimeout() { struct callout *p; while (callout != NULL) { p = callout; if (gettimeofday(&timenow, NULL) < 0) fatal("Failed to get time of day: %m"); if (!(p->c_time.tv_sec < timenow.tv_sec || (p->c_time.tv_sec == timenow.tv_sec && p->c_time.tv_usec <= timenow.tv_usec))) break; /* no, it's not time yet */ callout = p->c_next; (*p->c_func)(p->c_arg); free((char *) p); } } /* * timeleft - return the length of time until the next timeout is due. */ static struct timeval * timeleft(tvp) struct timeval *tvp; { if (callout == NULL) return (NULL); (void) gettimeofday(&timenow, NULL); tvp->tv_sec = callout->c_time.tv_sec - timenow.tv_sec; tvp->tv_usec = callout->c_time.tv_usec - timenow.tv_usec; if (tvp->tv_usec < 0) { tvp->tv_usec += 1000000; tvp->tv_sec -= 1; } if (tvp->tv_sec < 0) tvp->tv_sec = tvp->tv_usec = 0; return (tvp); } /* * kill_my_pg - send a signal to our process group, and ignore it ourselves. */ static void kill_my_pg(sig) int sig; { struct sigaction act, oldact; sigset_t mask; BZERO(&act, sizeof (act)); act.sa_handler = SIG_IGN; (void) sigemptyset(&mask); (void) sigaddset(&mask, sig); /* * Ignore signal 'sig' temporarily, before finally re-activating the * original handler. We need to do it in the following sequence, since * otherwise the signal handler for 'sig' will be called forever. */ if (sigaction(sig, &act, &oldact) < 0) { fatal("kill_my_pg: couldn't establish signal handler (%d): %m", sig); } (void) sigprocmask(SIG_UNBLOCK, &mask, NULL); /* * Send signal 'sig' to all processes whose process group ID is equal * to the process group ID of the sender. */ (void) kill(0, sig); if (sigaction(sig, &oldact, NULL) < 0) { fatal("kill_my_pg: couldn't establish signal handler (%d): %m", sig); } } /* * hup - Catch SIGHUP signal. * * Indicates that the physical layer has been disconnected. * We don't rely on this indication; if the user has sent this * signal, we just take the link down. */ static void hup(sig) int sig; { info("Hangup (SIGHUP)"); kill_link = 1; if (status != EXIT_HANGUP) status = EXIT_USER_REQUEST; if (conn_running > 0) /* Send the signal to the [dis]connector process(es) also */ kill_my_pg(sig); if (charshunt_pid) (void) kill(charshunt_pid, sig); if (waiting) siglongjmp(sigjmp, 1); } /* * term - Catch SIGTERM signal and SIGINT signal (^C/del). * * Indicates that we should initiate a graceful disconnect and exit. */ /*ARGSUSED*/ static void term(sig) int sig; { info("Terminating on signal %d.", sig); persist = 0; /* don't try to restart */ kill_link = 1; status = EXIT_USER_REQUEST; if (conn_running > 0) /* Send the signal to the [dis]connector process(es) also */ kill_my_pg(sig); if (charshunt_pid) (void) kill(charshunt_pid, sig); if (waiting) siglongjmp(sigjmp, 1); } /* * chld - Catch SIGCHLD signal. * Sets a flag so we will call reap_kids in the mainline. */ /*ARGSUSED*/ static void chld(sig) int sig; { got_sigchld = 1; if (waiting) siglongjmp(sigjmp, 1); } /* * toggle_debug - Catch SIGUSR1 signal. * * Toggle debug flag. */ /*ARGSUSED*/ static void toggle_debug(sig) int sig; { if (debug) { print_ncpstate(0, NULL); dbglog("debug logging disabled"); (void) setlogmask(LOG_UPTO(LOG_WARNING)); debug = 0; } else { (void) setlogmask(LOG_UPTO(LOG_DEBUG)); dbglog("debug logging enabled"); print_ncpstate(0, NULL); debug = 1; } } /* * open_ccp - Catch SIGUSR2 signal. * * Try to (re)negotiate compression. */ /*ARGSUSED*/ static void open_ccp(sig) int sig; { open_ccp_flag = 1; if (waiting) siglongjmp(sigjmp, 1); } /* * bad_signal - We've caught a fatal signal. Clean up state and exit. */ static void bad_signal(sig) int sig; { static int crashed = 0; if (crashed) _exit(127); crashed = 1; error("Fatal signal %d", sig); if (conn_running > 0) kill_my_pg(SIGTERM); if (charshunt_pid) (void) kill(charshunt_pid, SIGTERM); die(127); } /* * device_script - run a program to talk to the serial device * (e.g. to run the connector or disconnector script). */ static int device_script(program, in, out, dont_wait, optname) char *program; int in, out; int dont_wait; char *optname; { pid_t pid; int status = -1; int errfd; int envpipe[2]; envpipe[0] = envpipe[1] = -1; if (!dont_wait && device_pipe_hook != NULL && pipe(envpipe) == -1) { error("Cannot create pipe for child: %m"); return (-1); } ++conn_running; pid = fork(); if (pid == (pid_t)-1) { --conn_running; error("Failed to create child process: %m"); return (-1); } if (pid == (pid_t)0) { sys_close(); closelog(); if (envpipe[0] >= 0) { if (envpipe[1] <= 2) envpipe[1] = dup(envpipe[1]); (void) close(envpipe[0]); } if (in == 2) { /* aargh!!! */ int newin = dup(in); if (in == out) out = newin; in = newin; } else if (out == 2) { out = dup(out); } if (log_to_fd >= 0) { if (log_to_fd != 2) { if (dup2(log_to_fd, 2) < 0) error("dup2(log_to_fd, STDERR) failed: %m"); } } else { (void) close(2); errfd = open(_PATH_CONNERRS, O_WRONLY | O_APPEND | O_CREAT, 0600); if (errfd >= 0 && errfd != 2) { if (dup2(errfd, 2) < 0) error("dup2(errfd, STDERR) failed: %m"); (void) close(errfd); } } if (in != 0) { if (out == 0) out = dup(out); if (dup2(in, 0) < 0) error("dup2(in, STDIN) failed: %m"); } if (out != 1) { if (dup2(out, 1) < 0) error("dup2(out, STDOUT) failed: %m"); } if (envpipe[0] >= 0 && dup2(envpipe[1], 3) < 0) error("dup2(pipe, pipeout) failed: %m"); if (real_ttyfd > 2) (void) close(real_ttyfd); if (pty_master > 2) (void) close(pty_master); if (pty_slave > 2) { (void) close(pty_slave); pty_slave = -1; } (void) setuid(uid); if (getuid() != uid) { error("setuid failed"); exit(1); } (void) setgid(getgid()); if (script_env != NULL) { while (*script_env != NULL) { if (putenv(*script_env) == -1) warn("unable to set %s for %s: %m", *script_env, program); script_env++; } } (void) execl("/bin/sh", "sh", "-c", program, (char *)0); error("could not exec /bin/sh: %m"); exit(99); /* NOTREACHED */ } if (debug) dbglog("%s option: '%s' started (pid %d)", optname, program, pid); if (dont_wait) { record_child(pid, program, NULL, NULL); status = 0; } else { if (envpipe[0] >= 0) { (void) close(envpipe[1]); (*device_pipe_hook)(envpipe[0]); } while (waitpid(pid, &status, 0) < 0) { if (errno == EINTR) continue; fatal("error waiting for (dis)connection process: %m"); } if (envpipe[0] >= 0) (void) close(envpipe[0]); --conn_running; } return (status == 0 ? 0 : -1); } /* * run-program - execute a program with given arguments, * but don't wait for it. * If the program can't be executed, logs an error unless * must_exist is 0 and the program file doesn't exist. * Returns -1 if it couldn't fork, 0 if the file doesn't exist * or isn't an executable plain file, or the process ID of the child. * If done != NULL, (*done)(arg, int) will be called later (within * reap_kids) if this routine returns value > 0. */ pid_t run_program(prog, args, must_exist, done, arg) char *prog; char **args; int must_exist; void (*done) __P((void *arg, int status)); void *arg; { pid_t pid; struct stat sbuf; int retv; /* * First check if the file exists and is executable. * We don't use access() because that would use the * real user-id, which might not be root, and the script * might be accessible only to root. */ errno = EINVAL; if (stat(prog, &sbuf) < 0 || !S_ISREG(sbuf.st_mode) || (sbuf.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0) { if (must_exist || errno != ENOENT) warn("Can't execute %s: %m", prog); return (0); } if (updown_script_hook != NULL) { retv = (*updown_script_hook)((const char ***)&args); if (retv == -1) { return (-1); } } pid = fork(); if (pid == (pid_t)-1) { error("Failed to create child process for %s: %m", prog); return (-1); } if (pid == (pid_t)0) { int new_fd; /* Leave the current location */ (void) setsid(); /* No controlling tty. */ (void) umask (S_IRWXG|S_IRWXO); (void) chdir ("/"); /* no current directory. */ (void) setuid(0); /* set real UID = root */ (void) setgid(getegid()); /* Ensure that nothing of our device environment is inherited. */ sys_close(); closelog(); (void) close(0); (void) close(1); (void) close(2); (void) close(ttyfd); /* tty interface to the ppp device */ if (real_ttyfd >= 0) (void) close(real_ttyfd); /* Don't pass handles to the PPP device, even by accident. */ new_fd = open (_PATH_DEVNULL, O_RDWR); if (new_fd >= 0) { if (new_fd != 0) { if (dup2(new_fd, 0) < 0) /* stdin <- /dev/null */ error("dup2(/dev/null, STDIN) failed: %m"); (void) close(new_fd); } if (dup2(0, 1) < 0) /* stdout -> /dev/null */ error("dup2(/dev/null, STDOUT) failed: %m"); if (dup2(0, 2) < 0) /* stderr -> /dev/null */ error("dup2(/dev/null, STDERR) failed: %m"); } #ifdef BSD /* Force the priority back to zero if pppd is running higher. */ if (setpriority (PRIO_PROCESS, 0, 0) < 0) warn("can't reset priority to 0: %m"); #endif /* SysV recommends a second fork at this point. */ /* run the program */ (void) execve(prog, args, script_env); if (must_exist || errno != ENOENT) { /* have to reopen the log, there's nowhere else for the message to go. */ reopen_log(); syslog(LOG_ERR, "Can't execute %s: %m", prog); closelog(); } _exit(-1); } if (debug) dbglog("Script %s started (pid %d)", prog, pid); record_child(pid, prog, done, arg); return (pid); } /* * record_child - add a child process to the list for reap_kids * to use. */ static void record_child(pid, prog, done, arg) pid_t pid; char *prog; void (*done) __P((void *, int)); void *arg; { struct subprocess *chp; ++n_children; chp = (struct subprocess *) malloc(sizeof(struct subprocess)); if (chp == NULL) { warn("losing track of %s process", prog); } else { chp->pid = pid; chp->prog = prog; chp->done = done; chp->arg = arg; chp->next = children; children = chp; } } /* * reap_kids - get status from any dead child processes, * and log a message for abnormal terminations. */ static int reap_kids(waitfor) int waitfor; { pid_t pid; int status, i; struct subprocess *chp, **prevp; got_sigchld = 0; if (n_children == 0) return (0); /*CONSTANTCONDITION*/ while (1) { pid = waitpid(-1, &status, (waitfor ? 0 : WNOHANG)); if (pid == 0) { break; /* return 0 */ } else if (pid == -1) { if (errno == EINTR) continue; if (errno != ECHILD) error("Error waiting for child process: %m"); return (-1); } else { for (prevp = &children; (chp = *prevp) != NULL; prevp = &chp->next) { if (chp->pid == pid) { --n_children; *prevp = chp->next; break; } } if (WIFSIGNALED(status) || WIFSTOPPED(status)) { i = WIFSIGNALED(status) ? WTERMSIG(status) : WSTOPSIG(status); warn("Child process %s (pid %d) %s with signal %d (%s)", (chp != NULL ? chp->prog : "??"), pid, (WIFSIGNALED(status) ? "terminated" : "stopped"), i, signal_name(i)); } else if (debug) { dbglog("Child process %s finished (pid %d), status = %d", (chp != NULL ? chp->prog: "??"), pid, WEXITSTATUS(status)); } if ((chp != NULL) && (chp->done != NULL)) (*chp->done)(chp->arg, status); if (chp != NULL) free(chp); } } return (0); } /* * infanticide - timeout while waiting for child process. */ /*ARGSUSED*/ static void infanticide(sig) int sig; { struct subprocess *chp; static int runcount = 0; if (runcount < 2) { for (chp = children; chp != NULL; chp = chp->next) (void) kill(chp->pid, runcount == 0 ? SIGTERM : SIGKILL); } else { kill_my_pg(SIGTERM); /* Quit and hope for the best. */ n_children = 0; } runcount++; } /* * Perform final wait before exiting. */ static void final_reap() { struct sigaction sa; struct subprocess *chp; if (n_children > 0 && debug) { dbglog("Waiting for %d child processes...", n_children); for (chp = children; chp != NULL; chp = chp->next) dbglog(" pid %d: %s", chp->pid, chp->prog); } BZERO(&sa, sizeof (sa)); /*CONSTANTCONDITION*/ SIGNAL(SIGALRM, infanticide); while (n_children > 0) { (void) alarm(7); if (reap_kids(1) < 0) break; } (void) alarm(0); } /* * novm - log an error message saying we ran out of memory, and die. */ void novm(msg) char *msg; { fatal("Virtual memory exhausted allocating %s\n", msg); } /* * script_setenv - set an environment variable value to be used * for scripts that we run (e.g. ip-up, auth-up, etc.) */ void script_setenv(var, value, iskey) const char *var; const char *value; int iskey; { size_t varl = strlen(var); size_t vl = varl + strlen(value) + 2; int i; char *p, *newstring; /* * XXX: Can we assert that a tdb write lock is held here ? It appears that * Linux's use of tdb is not safe. */ newstring = (char *) malloc(vl+1); if (newstring == NULL) { novm("script environment string"); return; } *newstring++ = iskey; (void) slprintf(newstring, vl, "%s=%s", var, value); /* check if this variable is already set */ if (script_env != NULL) { for (i = 0; (p = script_env[i]) != NULL; ++i) { if (strncmp(p, var, varl) == 0 && p[varl] == '=') { #ifdef HAVE_MULTILINK if (p[-1] != '\0' && pppdb != NULL) delete_db_key(p); #endif free(p-1); script_env[i] = newstring; #ifdef HAVE_MULTILINK if (iskey && pppdb != NULL) add_db_key(newstring); update_db_entry(); #endif return; } } } else { /* no space allocated for script env. ptrs. yet */ i = 0; script_env = (char **) malloc(16 * sizeof(char *)); if (script_env == NULL) { novm("script environment variable."); return; } s_env_nalloc = 16; } /* reallocate script_env with more space if needed */ if (i + 1 >= s_env_nalloc) { int new_n = i + 17; char **newenv = (char **) realloc((void *)script_env, new_n * sizeof(char *)); if (newenv == NULL) { novm("expanded script environment variable."); return; } script_env = newenv; s_env_nalloc = new_n; } script_env[i] = newstring; script_env[i+1] = NULL; #ifdef HAVE_MULTILINK if (pppdb != NULL) { if (iskey) add_db_key(newstring); update_db_entry(); } #endif } /* * script_unsetenv - remove a variable from the environment * for scripts. */ void script_unsetenv(var) const char *var; { int vl = strlen(var); int i; char *p; /* * XXX: Can we assert that a tdb write lock is held here ? It appears that * Linux's use of tdb is not safe. */ if (script_env == NULL) return; for (i = 0; (p = script_env[i]) != NULL; ++i) { if (strncmp(p, var, vl) == 0 && p[vl] == '=') { #ifdef HAVE_MULTILINK if (p[-1] != '\0' && pppdb != NULL) delete_db_key(p); #endif free(p-1); while ((script_env[i] = script_env[i+1]) != NULL) ++i; break; } } #ifdef HAVE_MULTILINK if ((pppdb != NULL) && (p != NULL)) update_db_entry(); #endif } /* * script_getenv - find a variable in the script environment. */ const char * script_getenv(var) const char *var; { int vl = strlen(var); int i; char *p; if (script_env == NULL) return (NULL); for (i = 0; (p = script_env[i]) != NULL; ++i) { if (strncmp(p, var, vl) == 0 && p[vl] == '=') return ((const char *)p+vl+1); } return (NULL); } #ifdef HAVE_MULTILINK /* * update_db_entry - update our entry in the database. */ static void update_db_entry() { TDB_DATA key, dbuf; int vlen, i; char *p, *q, *vbuf; if (script_env == NULL) return; /* * vlen needs to be initialized as 1, or otherwise, the last string * is truncated by slprintf. */ vlen = 1; for (i = 0; (p = script_env[i]) != NULL; ++i) vlen += strlen(p) + 1; vbuf = malloc(vlen); if (vbuf == NULL) novm("database entry"); q = vbuf; for (i = 0; (p = script_env[i]) != NULL; ++i) q += slprintf(q, vbuf + vlen - q, "%s;", p); key.dptr = db_key; key.dsize = strlen(db_key); dbuf.dptr = vbuf; dbuf.dsize = vlen; if (tdb_store(pppdb, key, dbuf, TDB_REPLACE)) error("tdb_store failed: %s", tdb_error(pppdb)); } /* * add_db_key - add a key that we can use to look up our database entry. */ static void add_db_key(str) const char *str; { TDB_DATA key, dbuf; key.dptr = (char *) str; key.dsize = strlen(str); dbuf.dptr = db_key; dbuf.dsize = strlen(db_key); if (tdb_store(pppdb, key, dbuf, TDB_REPLACE)) error("tdb_store key failed: %s", tdb_error(pppdb)); } /* * delete_db_key - delete a key for looking up our database entry. */ static void delete_db_key(str) const char *str; { TDB_DATA key; key.dptr = (char *) str; key.dsize = strlen(str); (void) tdb_delete(pppdb, key); } /* * cleanup_db - delete all the entries we put in the database. */ static void cleanup_db() { TDB_DATA key; int i; char *p; key.dptr = db_key; key.dsize = strlen(db_key); (void) tdb_delete(pppdb, key); for (i = 0; (p = script_env[i]) != NULL; ++i) if (p[-1] != '\0') delete_db_key(p); } #endif /* HAVE_MULTILINK */ /* * open_socket - establish a stream socket connection to the nominated * host and port. * XXX: Need IPv6 support for those systems that support it (use getaddrinfo), * but requires portability changes. */ static int open_socket(dest) char *dest; { char *sep, *endp = NULL; int sock; int port = -1; u_int32_t host; struct hostent *hent = NULL; struct sockaddr_in sad; struct servent *se; /* parse host:port and resolve host to an IP address */ sep = strchr(dest, ':'); if (sep != NULL) { se = getservbyname((const char *)sep+1, "tcp"); if (se != NULL) { port = ntohs(se->s_port); } else { port = strtol(sep+1, &endp, 10); if (endp == sep+1 || *endp != '\0') { error("Can't parse host:port for socket destination"); return (-1); } } } if (port < 0 || port > 65535 || sep == dest) { error("Can't parse host:port for socket destination"); return (-1); } *sep = '\0'; host = inet_addr(dest); if (host == (u_int32_t) -1) { hent = gethostbyname(dest); if (hent == NULL) { error("%s: unknown host in socket option", dest); *sep = ':'; return (-1); } BCOPY(hent->h_addr_list[0], &host, sizeof(host)); hent->h_addr_list++; } *sep = ':'; for (;;) { /* get a socket and connect it to the other end */ sock = socket(PF_INET, SOCK_STREAM, 0); if (sock < 0) { error("Can't create socket: %m"); return (-1); } BZERO(&sad, sizeof(sad)); sad.sin_family = AF_INET; sad.sin_port = htons(port); sad.sin_addr.s_addr = host; if (connect(sock, (struct sockaddr *)&sad, sizeof(sad)) >= 0) { break; /* return sock file descriptor */ } if ((hent != NULL) && (hent->h_addr_list != NULL)) { BCOPY(hent->h_addr_list[0], &host, sizeof(host)); hent->h_addr_list++; (void) close(sock); continue; } error("Can't connect to %s: %m", dest); (void) close(sock); return (-1); } return (sock); } /* * print_ncpstate - prints out current NCP state. * * We're normally called from SIGUSR1 here, but this is safe because * these signals are blocked unless we're idle waiting for events. * There's no need to otherwise lock the data structures referenced. */ void print_ncpstate(unit, strptr) int unit; FILE *strptr; { struct protent *protp; int i; (void) flprintf(strptr, "In %s phase\n", phase_name(phase)); for (i = 0; (protp = protocols[i]) != NULL; ++i) { if (protp->print_stat != NULL) (*protp->print_stat)(unit, strptr); } sys_print_state(strptr); } /* * start_charshunt - create a child process to run the character shunt. */ static int start_charshunt(ifd, ofd) int ifd, ofd; { pid_t cpid; cpid = fork(); if (cpid == (pid_t)-1) { error("Can't fork process for character shunt: %m"); return (0); } if (cpid == (pid_t)0) { /* child */ (void) close(pty_slave); pty_slave = -1; (void) setgid(getgid()); (void) setuid(uid); if (getuid() != uid) fatal("setuid failed"); if (!nodetach) log_to_fd = -1; charshunt(ifd, ofd, record_file); exit(0); } charshunt_pid = cpid; (void) close(pty_master); pty_master = -1; ttyfd = pty_slave; record_child(cpid, "pppd (charshunt)", charshunt_done, NULL); return (1); } /*ARGSUSED*/ static void charshunt_done(arg, status) void *arg; int status; { charshunt_pid = (pid_t)0; } static void reportme(int signo) { dbglog("charshunt taking signal %d", signo); exit(1); } /* * charshunt - the character shunt, which passes characters between * the pty master side and the serial port (or stdin/stdout). * This runs as the user (not as root). * (We assume ofd >= ifd which is true the way this gets called. :-). */ static void charshunt(ifd, ofd, record_file) int ifd, ofd; char *record_file; { int n, nfds; fd_set ready, writey; u_char *ibufp, *obufp; int nibuf, nobuf; int flags; struct timeval lasttime; FILE *recordf = NULL; int ilevel, olevel, max_level; struct timeval levelt, tout, *top; /* * Reset signal handlers. */ (void) signal(SIGHUP, SIG_IGN); /* Hangup */ (void) signal(SIGINT, reportme); /* Interrupt */ (void) signal(SIGTERM, reportme); /* Terminate */ (void) signal(SIGCHLD, reportme); (void) signal(SIGUSR1, reportme); (void) signal(SIGUSR2, reportme); (void) signal(SIGABRT, reportme); (void) signal(SIGALRM, reportme); (void) signal(SIGFPE, reportme); (void) signal(SIGILL, reportme); (void) signal(SIGPIPE, reportme); (void) signal(SIGQUIT, reportme); #ifndef DEBUG (void) signal(SIGSEGV, reportme); #endif #ifdef SIGBUS (void) signal(SIGBUS, reportme); #endif #ifdef SIGEMT (void) signal(SIGEMT, reportme); #endif #ifdef SIGPOLL (void) signal(SIGPOLL, reportme); #endif #ifdef SIGPROF (void) signal(SIGPROF, reportme); #endif #ifdef SIGSYS (void) signal(SIGSYS, reportme); #endif #ifdef SIGTRAP (void) signal(SIGTRAP, reportme); #endif #ifdef SIGVTALRM (void) signal(SIGVTALRM, reportme); #endif #ifdef SIGXCPU (void) signal(SIGXCPU, reportme); #endif #ifdef SIGXFSZ (void) signal(SIGXFSZ, reportme); #endif /* * Open the record file if required. */ if (record_file != NULL) { recordf = fopen(record_file, "a"); if (recordf == NULL) error("Couldn't create record file %s: %m", record_file); } /* set all the fds to non-blocking mode */ flags = fcntl(pty_master, F_GETFL); if (flags == -1 || fcntl(pty_master, F_SETFL, flags | O_NONBLOCK) == -1) warn("couldn't set pty master to nonblock: %m"); flags = fcntl(ifd, F_GETFL); if (flags == -1 || fcntl(ifd, F_SETFL, flags | O_NONBLOCK) == -1) warn("couldn't set %s to nonblock: %m", (ifd==0? "stdin": "tty")); if (ofd != ifd) { flags = fcntl(ofd, F_GETFL); if (flags == -1 || fcntl(ofd, F_SETFL, flags | O_NONBLOCK) == -1) warn("couldn't set stdout to nonblock: %m"); } nibuf = nobuf = 0; ibufp = obufp = NULL; ilevel = olevel = 0; (void) gettimeofday(&levelt, NULL); if (max_data_rate) { max_level = max_data_rate / 10; if (max_level < MAXLEVELMINSIZE) max_level = MAXLEVELMINSIZE; } else max_level = sizeof(inpacket_buf) + 1; nfds = (ofd > pty_master? ofd: pty_master) + 1; if (recordf != NULL) { (void) gettimeofday(&lasttime, NULL); (void) putc(RECMARK_TIMESTART, recordf); /* put start marker */ (void) putc(lasttime.tv_sec >> 24, recordf); (void) putc(lasttime.tv_sec >> 16, recordf); (void) putc(lasttime.tv_sec >> 8, recordf); (void) putc(lasttime.tv_sec, recordf); lasttime.tv_usec = 0; } while (nibuf != 0 || nobuf != 0 || ofd >= 0 || pty_master >= 0) { top = 0; tout.tv_sec = 0; tout.tv_usec = 10000; FD_ZERO(&ready); FD_ZERO(&writey); if (nibuf != 0) { if (ilevel >= max_level) top = &tout; else if (pty_master >= 0) FD_SET(pty_master, &writey); } else if (ifd >= 0) FD_SET(ifd, &ready); if (nobuf != 0) { if (olevel >= max_level) top = &tout; else if (ofd >= 0) FD_SET(ofd, &writey); } else { /* Don't read from pty if it's gone or it has closed. */ if (pty_master >= 0 && ofd >= 0) FD_SET(pty_master, &ready); } if (select(nfds, &ready, &writey, NULL, top) < 0) { if (errno != EINTR) fatal("select"); continue; } if (max_data_rate) { double dt; int nbt; struct timeval now; (void) gettimeofday(&now, NULL); dt = (now.tv_sec - levelt.tv_sec + (now.tv_usec - levelt.tv_usec) / 1e6); nbt = (int)(dt * max_data_rate); ilevel = (nbt < 0 || nbt > ilevel)? 0: ilevel - nbt; olevel = (nbt < 0 || nbt > olevel)? 0: olevel - nbt; levelt = now; } else ilevel = olevel = 0; if (FD_ISSET(ifd, &ready)) { ibufp = inpacket_buf; nibuf = read(ifd, ibufp, sizeof(inpacket_buf)); if (nibuf < 0 && errno == EIO) nibuf = 0; if (nibuf < 0 || pty_master == -1) { if (errno != EINTR && errno != EAGAIN) { error("Error reading standard input: %m"); break; } nibuf = 0; } else if (nibuf == 0) { /* end of file from stdin */ (void) close(pty_master); pty_master = -1; (void) close(ifd); ifd = -1; if (recordf) if (!record_write(recordf, RECMARK_ENDRECV, NULL, 0, &lasttime)) recordf = NULL; } else { FD_SET(pty_master, &writey); if (recordf) if (!record_write(recordf, RECMARK_STARTRECV, ibufp, nibuf, &lasttime)) recordf = NULL; } } if (ofd >= 0 && pty_master >= 0 && FD_ISSET(pty_master, &ready)) { obufp = outpacket_buf; nobuf = read(pty_master, obufp, sizeof(outpacket_buf)); if (nobuf < 0 && errno == EIO) nobuf = 0; if (nobuf < 0 || ofd == -1) { if (!(errno == EINTR || errno == EAGAIN)) { error("Error reading pseudo-tty master: %m"); break; } nobuf = 0; } else if (nobuf == 0) { /* end of file from the pty - slave side has closed */ nibuf = 0; (void) close(ofd); ofd = -1; if (recordf) if (!record_write(recordf, RECMARK_ENDSEND, NULL, 0, &lasttime)) recordf = NULL; } else { FD_SET(ofd, &writey); if (recordf) if (!record_write(recordf, RECMARK_STARTSEND, obufp, nobuf, &lasttime)) recordf = NULL; } } if (ofd == -1) nobuf = 0; else if (FD_ISSET(ofd, &writey)) { n = nobuf; if (olevel + n > max_level) n = max_level - olevel; n = write(ofd, obufp, n); if (n < 0) { if (errno == EIO) { (void) close(ofd); ofd = -1; nobuf = 0; } else if (errno != EAGAIN && errno != EINTR) { error("Error writing standard output: %m"); break; } } else { obufp += n; nobuf -= n; olevel += n; } } if (pty_master == -1) nibuf = 0; else if (FD_ISSET(pty_master, &writey)) { n = nibuf; if (ilevel + n > max_level) n = max_level - ilevel; n = write(pty_master, ibufp, n); if (n < 0) { if (errno == EAGAIN || errno == EINTR) continue; if (errno != EIO) { error("Error writing pseudo-tty master: %m"); break; } (void) close(pty_master); pty_master = -1; nibuf = 0; } else { ibufp += n; nibuf -= n; ilevel += n; } } } exit(0); } static int record_write(f, code, buf, nb, tp) FILE *f; int code; u_char *buf; int nb; struct timeval *tp; { struct timeval now; int diff; (void) gettimeofday(&now, NULL); now.tv_usec /= 100000; /* actually 1/10 s, not usec now */ diff = (now.tv_sec - tp->tv_sec) * 10 + (now.tv_usec - tp->tv_usec); if (diff > 0) { if (diff > 255) { (void) putc(RECMARK_TIMEDELTA32, f); (void) putc(diff >> 24, f); (void) putc(diff >> 16, f); (void) putc(diff >> 8, f); (void) putc(diff, f); } else { (void) putc(RECMARK_TIMEDELTA8, f); (void) putc(diff, f); } *tp = now; } (void) putc(code, f); if (buf != NULL) { (void) putc(nb >> 8, f); (void) putc(nb, f); (void) fwrite(buf, nb, 1, f); } (void) fflush(f); if (ferror(f)) { error("Error writing record file: %m"); return (0); } return (1); } /* * Test MS-CHAPv1 library code. * * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Originally from the README.MSCHAP80 file written by: * Eric Rosenquist rosenqui@strataware.com * (updated by Paul Mackerras) * (updated by Al Longyear) * (updated by Farrell Woods) */ #include #include "pppd.h" #include "chap.h" #include "chap_ms.h" static void show_response(chap_state *cstate, const char *str) { int i; printf("%s -- %d bytes:", str, cstate->resp_length); for (i = 0; i < cstate->resp_length; i++) { if (i % 8 == 0) putchar('\n'); printf("%02X ", (unsigned int)cstate->response[i]); } putchar('\n'); } int main(argc, argv) int argc; char *argv[]; { u_char challenge[8]; int challengeInt[sizeof(challenge)]; chap_state cstate; int i; if (argc != 3) { fprintf(stderr, "Usage: %s <16-hexchar challenge> \n", argv[0]); exit(1); } sscanf(argv[1], "%2x%2x%2x%2x%2x%2x%2x%2x", challengeInt + 0, challengeInt + 1, challengeInt + 2, challengeInt + 3, challengeInt + 4, challengeInt + 5, challengeInt + 6, challengeInt + 7); for (i = 0; i < sizeof(challenge); i++) challenge[i] = (u_char)challengeInt[i]; BZERO(&cstate, sizeof(cstate)); ChapMS(&cstate, challenge, sizeof(challenge), argv[2], strlen(argv[2])); #ifdef MSLANMAN show_response(&cstate, "MS-CHAPv1 with LAN Manager"); #else show_response(&cstate, "MS-CHAPv1"); #endif cstate.chal_len = sizeof(challenge); BCOPY(challenge, cstate.challenge, cstate.chal_len); if (!ChapMSValidate(&cstate, cstate.response, cstate.resp_length, argv[2], strlen(argv[2]))) printf("Cannot validate own MS-CHAPv1 response.\n"); #ifdef MSLANMAN cstate.response[MS_CHAP_RESPONSE_LEN-1] = '\0'; if (!ChapMSValidate(&cstate, cstate.response, cstate.resp_length, argv[2], strlen(argv[2]))) printf("Cannot validate own LAN Manager response.\n"); #endif #ifdef CHAPMSV2 cstate.resp_name = "joe user"; ChapMSv2(&cstate, cstate.challenge, 16, argv[2], strlen(argv[2])); show_response(&cstate, "MS-CHAPv2"); if (!ChapMSv2Validate(&cstate, cstate.resp_name, cstate.response, cstate.resp_length, argv[2], strlen(argv[2]))) printf("Cannot validate own MS-CHAPv2 response.\n"); #endif return (0); } /* * multilink.c - support routines for multilink. * * Copyright (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. * * Copyright (c) 2000 Paul Mackerras. * 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. The name of the author 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. */ #include #include #include #include #include #include #include #include "pppd.h" #include "fsm.h" #include "lcp.h" #ifdef HAVE_MULTILINK #include "tdb.h" #endif #define set_ip_epdisc(ep, addr) ( \ ep->length = 4, \ ep->value[0] = addr >> 24, \ ep->value[1] = addr >> 16, \ ep->value[2] = addr >> 8, \ ep->value[3] = addr \ ) #ifdef HAVE_MULTILINK bool endpoint_specified; /* user gave explicit endpoint discriminator */ char *bundle_id; /* identifier for our bundle */ static int get_default_epdisc __P((struct epdisc *)); static int parse_num __P((char *str, const char *key, int *valp)); static int owns_unit __P((TDB_DATA pid, int unit)); #define process_exists(n) (kill(0, (n)) == 0 || errno != ESRCH) void mp_check_options() { lcp_options *wo = &lcp_wantoptions[0]; lcp_options *ao = &lcp_allowoptions[0]; if (!multilink) return; /* if we're doing multilink, we have to negotiate MRRU */ if (!wo->neg_mrru) { /* mrru not specified, default to mru */ wo->mrru = wo->mru; wo->neg_mrru = 1; } ao->mrru = ao->mru; ao->neg_mrru = 1; if (!wo->neg_endpoint && !noendpoint) { /* get a default endpoint value */ wo->neg_endpoint = get_default_epdisc(&wo->endpoint); if (wo->neg_endpoint) dbglog("using default endpoint %s", epdisc_to_str(&wo->endpoint)); } } /* * Make a new bundle or join us to an existing bundle * if we are doing multilink. */ int mp_join_bundle() { lcp_options *go = &lcp_gotoptions[0]; lcp_options *ho = &lcp_hisoptions[0]; int unit, pppd_pid; int l; char *p; TDB_DATA key, pid, rec; if (!go->neg_mrru || !ho->neg_mrru) { /* not doing multilink */ if (go->neg_mrru) notice("oops, multilink negotiated only for receive"); multilink = 0; if (demand) { /* already have a bundle */ cfg_bundle(0, 0, 0, 0); return 0; } make_new_bundle(0, 0, 0, 0); set_ifunit(1); return 0; } /* * Find the appropriate bundle or join a new one. * First we make up a name for the bundle. * The length estimate is worst-case assuming every * character has to be quoted. * * Note - RFC 1990 requires that an unnegotiated endpoint * discriminator value be equivalent to negotiating with class * zero. Do not test ho->neg_endpoint here. */ l = 4 * strlen(peer_authname) + 10; l += 3 * ho->endpoint.length + 8; if (bundle_name) l += 3 * strlen(bundle_name) + 2; bundle_id = malloc(l); if (bundle_id == NULL) novm("bundle identifier"); p = bundle_id; p += slprintf(p, l-1, "BUNDLE=\"%q\"", peer_authname); *p++ = '/'; p += slprintf(p, bundle_id+l-p, "%s", epdisc_to_str(&ho->endpoint)); if (bundle_name) p += slprintf(p, bundle_id+l-p, "/%v", bundle_name); dbglog("bundle_id = %s", bundle_id+7); /* * For demand mode, we only need to configure the bundle * and attach the link. */ if (demand) { cfg_bundle(go->mrru, ho->mrru, go->neg_ssnhf, ho->neg_ssnhf); script_setenv("BUNDLE", bundle_id + 7, 1); return 0; } /* * Check if the bundle ID is already in the database. */ unit = -1; tdb_writelock(pppdb); key.dptr = bundle_id; key.dsize = p - bundle_id; pid = tdb_fetch(pppdb, key); if (pid.dptr != NULL) { /* bundle ID exists, see if the pppd record exists */ rec = tdb_fetch(pppdb, pid); if (rec.dptr != NULL) { /* it does, parse the interface number */ parse_num(rec.dptr, "IFNAME=ppp", &unit); /* check the pid value */ if (!parse_num(rec.dptr, "PPPD_PID=", &pppd_pid) || !process_exists(pppd_pid) || !owns_unit(pid, unit)) unit = -1; free(rec.dptr); } free(pid.dptr); } if (unit >= 0) { /* attach to existing unit */ if (bundle_attach(unit)) { set_ifunit(0); script_setenv("BUNDLE", bundle_id + 7, 0); tdb_writeunlock(pppdb); info("Link attached to %s", ifname); return 1; } /* attach failed because bundle doesn't exist */ } /* we have to make a new bundle */ make_new_bundle(go->mrru, ho->mrru, go->neg_ssnhf, ho->neg_ssnhf); set_ifunit(1); script_setenv("BUNDLE", bundle_id + 7, 1); tdb_writeunlock(pppdb); info("New bundle %s created", ifname); return 0; } static int parse_num(str, key, valp) char *str; const char *key; int *valp; { char *p, *endp; int i; p = strstr(str, key); if (p != 0) { p += strlen(key); i = strtol(p, &endp, 10); if (endp != p && (*endp == 0 || *endp == ';')) { *valp = i; return 1; } } return 0; } /* * Check whether the pppd identified by `key' still owns ppp unit `unit'. */ static int owns_unit(key, unit) TDB_DATA key; int unit; { char ifkey[32]; TDB_DATA kd, vd; int ret = 0; (void) slprintf(ifkey, sizeof(ifkey), "IFNAME=ppp%d", unit); kd.dptr = ifkey; kd.dsize = strlen(ifkey); vd = tdb_fetch(pppdb, kd); if (vd.dptr != NULL) { ret = vd.dsize == key.dsize && memcmp(vd.dptr, key.dptr, vd.dsize) == 0; free(vd.dptr); } return ret; } static int get_default_epdisc(ep) struct epdisc *ep; { struct hostent *hp; u_int32_t addr; if (get_first_hwaddr(ep->value, sizeof(ep->value))) { ep->class = EPD_MAC; ep->length = 6; return 1; } /* see if our hostname corresponds to a reasonable IP address */ hp = gethostbyname(hostname); if (hp != NULL) { addr = *(u_int32_t *)hp->h_addr; if (!bad_ip_adrs(addr)) { addr = ntohl(addr); if (!LOCAL_IP_ADDR(addr)) { ep->class = EPD_IP; set_ip_epdisc(ep, addr); return 1; } } } return 0; } #endif /* HAVE_MULTILINK */ /* * epdisc_to_str - make a printable string from an endpoint discriminator. */ static char *endp_class_names[] = { "null", "local", "IP", "MAC", "magic", "phone" }; char * epdisc_to_str(ep) struct epdisc *ep; { static char str[MAX_ENDP_LEN*3+8]; u_char *p = ep->value; int i, mask = 0; char *q, c, c2; if (ep->class == EPD_NULL && ep->length == 0) return "null"; if (ep->class == EPD_IP && ep->length == 4) { u_int32_t addr; GETLONG(addr, p); (void) slprintf(str, sizeof(str), "IP:%I", htonl(addr)); return str; } c = ':'; c2 = '.'; if (ep->class == EPD_MAC && ep->length == 6) c2 = ':'; else if (ep->class == EPD_MAGIC && (ep->length % 4) == 0) mask = 3; q = str; if (ep->class <= EPD_PHONENUM) q += slprintf(q, sizeof(str)-1, "%s", endp_class_names[ep->class]); else q += slprintf(q, sizeof(str)-1, "%d", ep->class); c = ':'; for (i = 0; i < ep->length && i < MAX_ENDP_LEN; ++i) { if ((i & mask) == 0) { *q++ = c; c = c2; } q += slprintf(q, str + sizeof(str) - q, "%.2x", ep->value[i]); } return str; } static int hexc_val(int c) { if (c >= 'a') return c - 'a' + 10; if (c >= 'A') return c - 'A' + 10; return c - '0'; } int str_to_epdisc(ep, str) struct epdisc *ep; char *str; { int i, l; char *p, *endp; for (i = EPD_NULL; i <= EPD_PHONENUM; ++i) { int sl = strlen(endp_class_names[i]); if (strncasecmp(str, endp_class_names[i], sl) == 0) { str += sl; break; } } if (i > EPD_PHONENUM) { /* not a class name, try a decimal class number */ i = strtol(str, &endp, 10); if (endp == str) { option_error("cannot parse endpoint class in \"%s\"", str); return 0; /* can't parse class number */ } str = endp; } ep->class = i; if (*str == 0) { ep->length = 0; return 1; } if (*str != ':' && *str != '.') { option_error("invalid class/value separator '%c'", *str); return 0; } ++str; if (i == EPD_IP) { u_int32_t addr; i = parse_dotted_ip(str, &addr); if (i == 0 || str[i] != 0) return 0; set_ip_epdisc(ep, addr); dbglog("str_to_epdisc -> %s", epdisc_to_str(ep)); return 1; } if (i == EPD_MAC && get_if_hwaddr(ep->value, sizeof(ep->value), str) >= 0) { ep->length = 6; dbglog("str_to_epdisc -> %s", epdisc_to_str(ep)); return 1; } p = str; for (l = 0; l < MAX_ENDP_LEN; ++l) { if (*str == 0) break; if (p <= str) for (p = str; isxdigit(*p); ++p) ; i = p - str; if (i == 0) { option_error("no valid hex digits in \"%s\"", str); return 0; } ep->value[l] = hexc_val(*str++); if ((i & 1) == 0) ep->value[l] = (ep->value[l] << 4) + hexc_val(*str++); if (*str == ':' || *str == '.') ++str; } if (*str != 0) { option_error("too many bytes in value; max is %d", MAX_ENDP_LEN); return 0; } if (ep->class == EPD_MAC && l != 6) { option_error("bad endpoint; MAC address must have 6 bytes"); return 0; } ep->length = l; dbglog("str_to_epdisc -> %s", epdisc_to_str(ep)); return 1; } /* * options.c - handles option processing for PPP. * * Copyright (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. * * SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PLUGIN #include #endif /* PLUGIN */ #ifdef PPP_FILTER #include #include /* XXX: To get struct pcap */ #endif /* PPP_FILTER */ #include "pppd.h" #include "pathnames.h" #include "patchlevel.h" #include "fsm.h" #include "lcp.h" #include "ipcp.h" #if defined(ultrix) || defined(NeXT) char *strdup __P((char *)); #endif /* * Option variables and default values. */ #ifdef PPP_FILTER int dflag = 0; /* Tell libpcap we want debugging */ #endif /* PPP_FILTER */ int debug = 0; /* Debug flag */ int kdebugflag = 0; /* Tell kernel to print debug messages */ int default_device = 1; /* Using /dev/tty or equivalent */ char devnam[MAXPATHLEN]; /* Device name */ int crtscts = 0; /* Use hardware flow control */ bool modem = 1; /* Use modem control lines */ int inspeed = 0; /* Input/Output speed requested */ u_int32_t netmask = 0; /* IP netmask to set on interface */ bool lockflag = 0; /* Create lock file to lock the serial dev */ bool nodetach = 0; /* Don't detach from controlling tty */ bool updetach = 0; /* Detach once link is up */ char *initializer = NULL; /* Script to initialize physical link */ char *connect_script = NULL; /* Script to establish physical link */ char *disconnect_script = NULL; /* Script to disestablish physical link */ char *welcomer = NULL; /* Script to run after phys link estab. */ char *ptycommand = NULL; /* Command to run on other side of pty */ int maxconnect = 0; /* Maximum connect time */ char user[MAXNAMELEN]; /* Username for PAP */ char passwd[MAXSECRETLEN]; /* Password for PAP */ bool persist = 0; /* Reopen link after it goes down */ char our_name[MAXNAMELEN]; /* Our name for authentication purposes */ bool demand = 0; /* do dial-on-demand */ char *ipparam = NULL; /* Extra parameter for ip up/down scripts */ int idle_time_limit = 0; /* Disconnect if idle for this many seconds */ int holdoff = 30; /* # seconds to pause before reconnecting */ bool holdoff_specified; /* true if a holdoff value has been given */ bool notty = 0; /* Stdin/out is not a tty */ char *pty_socket = NULL; /* Socket to connect to pty */ char *record_file = NULL; /* File to record chars sent/received */ int using_pty = 0; bool sync_serial = 0; /* Device is synchronous serial device */ int log_to_fd = 1; /* send log messages to this fd too */ int maxfail = 10; /* max # of unsuccessful connection attempts */ char linkname[MAXPATHLEN]; /* logical name for link */ bool tune_kernel; /* may alter kernel settings */ int connect_delay = 1000; /* wait this many ms after connect script */ int max_data_rate; /* max bytes/sec through charshunt */ int req_unit = -1; /* requested interface unit */ bool multilink = 0; /* Enable multilink operation */ char *bundle_name = NULL; /* bundle name for multilink */ bool direct_tty = 0; /* use standard input directly; not a tty */ /* Maximum depth of include files; prevents looping. */ #define MAXFILENESTING 10 struct option_info initializer_info; struct option_info connect_script_info; struct option_info disconnect_script_info; struct option_info welcomer_info; struct option_info devnam_info; struct option_info ptycommand_info; struct option_info ipsrc_info; struct option_info ipdst_info; struct option_info speed_info; #ifdef PPP_FILTER struct bpf_program pass_filter;/* Filter program for packets to pass */ struct bpf_program active_filter; /* Filter program for link-active pkts */ pcap_t pc; /* Fake struct pcap so we can compile expr */ #endif /* PPP_FILTER */ char *current_option; /* the name of the option being parsed */ bool privileged_option; /* set iff the current option came from root */ char *option_source = NULL; /* string saying where the option came from */ int option_line = 0; /* line number in file */ bool log_to_file; /* log_to_fd is a file opened by us */ bool log_to_specific_fd; /* log_to_fd was specified by user option */ /* * Prototypes. */ static int setdevname __P((char *)); static int setipaddr __P((char *)); static int setspeed __P((char *)); static int noopt __P((char **, option_t *)); static int setdomain __P((char **, option_t *)); static int setnetmask __P((char **, option_t *)); static int setxonxoff __P((char **, option_t *)); static int readfile __P((char **, option_t *)); static int callfile __P((char **, option_t *)); static int showversion __P((char **, option_t *)); static int showhelp __P((char **, option_t *)); static int showalloptions __P((char **, option_t *)); static void usage __P((void)); static int setlogfile __P((char **, option_t *)); #ifdef PLUGIN static int loadplugin __P((char **, option_t *)); #endif #ifdef PPP_FILTER static int setpassfilter __P((char **, option_t *)); static int setactivefilter __P((char **, option_t *)); #endif /* PPP_FILTER */ static option_t *find_option __P((char *name)); static int process_option __P((option_t *opt, char **argv, int sline)); static int n_arguments __P((option_t *opt)); static int number_option __P((char *str, u_int32_t *valp, int base)); static u_int32_t opt_hash __P((const void *key)); static int opt_compare __P((const void *p1, const void *p2)); typedef struct _opt_t { option_t *p; } opt_t; typedef struct _hashentry_t { struct _hashentry_t *next; opt_t opt; } hashentry_t; /* * A prime number describing the size of hash table. */ #define OPTHASH_TBLSIZE 101 /* * Chained hash table containing pointers to available options. */ static hashentry_t *hash_tbl[OPTHASH_TBLSIZE] = { NULL }; /* * Total number of entries in the hash table. */ int hash_tblcnt = 0; /* * Valid arguments. */ option_t general_options[] = { { "debug", o_int, &debug, "Increase debugging level", OPT_INC|OPT_NOARG|1 }, { "-d", o_int, &debug, "Increase debugging level", OPT_INC|OPT_NOARG|1 }, { "kdebug", o_int, &kdebugflag, "Set kernel driver debug level" }, { "nodetach", o_bool, &nodetach, "Don't detach from controlling tty", 1 }, { "-detach", o_bool, &nodetach, "Don't detach from controlling tty", 1 }, { "updetach", o_bool, &updetach, "Detach from controlling tty once link is up", 1 }, { "holdoff", o_int, &holdoff, "Set time in seconds before retrying connection" }, { "idle", o_int, &idle_time_limit, "Set time in seconds before disconnecting idle link" }, { "lock", o_bool, &lockflag, "Lock serial device with UUCP-style lock file", 1 }, { "-all", o_special_noarg, (void *)noopt, "Don't request/allow any LCP or IPCP options (useless)" }, { "init", o_string, &initializer, "A program to initialize the device", OPT_A2INFO | OPT_PRIVFIX, &initializer_info }, { "connect", o_string, &connect_script, "A program to set up a connection", OPT_A2INFO | OPT_PRIVFIX, &connect_script_info }, { "disconnect", o_string, &disconnect_script, "Program to disconnect serial device", OPT_A2INFO | OPT_PRIVFIX, &disconnect_script_info }, { "welcome", o_string, &welcomer, "Script to welcome client", OPT_A2INFO | OPT_PRIVFIX, &welcomer_info }, { "pty", o_string, &ptycommand, "Script to run on pseudo-tty master side", OPT_A2INFO | OPT_PRIVFIX | OPT_DEVNAM, &ptycommand_info }, { "notty", o_bool, ¬ty, "Input/output is not a tty", OPT_DEVNAM | 1 }, { "directtty", o_bool, &direct_tty, "Use standard input as tty without checking", OPT_DEVNAM | 1 }, { "socket", o_string, &pty_socket, "Send and receive over socket, arg is host:port", OPT_DEVNAM }, { "record", o_string, &record_file, "Record characters sent/received to file" }, { "maxconnect", o_int, &maxconnect, "Set connection time limit", OPT_LLIMIT|OPT_NOINCR|OPT_ZEROINF }, { "crtscts", o_int, &crtscts, "Set hardware (RTS/CTS) flow control", OPT_NOARG|OPT_VAL(1) }, { "nocrtscts", o_int, &crtscts, "Disable hardware flow control", OPT_NOARG|OPT_VAL(-1) }, { "-crtscts", o_int, &crtscts, "Disable hardware flow control", OPT_NOARG|OPT_VAL(-1) }, { "cdtrcts", o_int, &crtscts, "Set alternate hardware (DTR/CTS) flow control", OPT_NOARG|OPT_VAL(2) }, { "nocdtrcts", o_int, &crtscts, "Disable hardware flow control", OPT_NOARG|OPT_VAL(-1) }, { "xonxoff", o_special_noarg, (void *)setxonxoff, "Set software (XON/XOFF) flow control" }, { "domain", o_special, (void *)setdomain, "Add given domain name to hostname" }, { "netmask", o_special, (void *)setnetmask, "set netmask" }, { "modem", o_bool, &modem, "Use modem control lines", 1 }, { "local", o_bool, &modem, "Don't use modem control lines" }, { "file", o_special, (void *)readfile, "Take options from a file", OPT_PREPASS }, { "call", o_special, (void *)callfile, "Take options from a privileged file", OPT_PREPASS }, { "persist", o_bool, &persist, "Keep on reopening connection after close", 1 }, { "nopersist", o_bool, &persist, "Turn off persist option" }, { "demand", o_bool, &demand, "Dial on demand", OPT_INITONLY | 1, &persist }, { "--version", o_special_noarg, (void *)showversion, "Show version number" }, { "--help", o_special_noarg, (void *)showhelp, "Show brief listing of options" }, { "-h", o_special_noarg, (void *)showhelp, "Show brief listing of options" }, { "options", o_special_noarg, (void *)showalloptions, "Show full listing of options" }, { "sync", o_bool, &sync_serial, "Use synchronous HDLC serial encoding", 1 }, { "logfd", o_int, &log_to_fd, "Send log messages to this file descriptor", 0, &log_to_specific_fd }, { "logfile", o_special, (void *)setlogfile, "Append log messages to this file" }, { "nolog", o_int, &log_to_fd, "Don't send log messages to any file", OPT_NOARG | OPT_VAL(-1) }, { "nologfd", o_int, &log_to_fd, "Don't send log messages to any file descriptor", OPT_NOARG | OPT_VAL(-1) }, { "linkname", o_string, linkname, "Set logical name for link", OPT_PRIV|OPT_STATIC, NULL, MAXPATHLEN }, { "maxfail", o_int, &maxfail, "Number of unsuccessful connection attempts to allow" }, { "ktune", o_bool, &tune_kernel, "Alter kernel settings as necessary", 1 }, { "noktune", o_bool, &tune_kernel, "Don't alter kernel settings", 0 }, { "connect-delay", o_int, &connect_delay, "Maximum wait time (msec) after connect script finishes" }, { "datarate", o_int, &max_data_rate, "Max data rate in bytes/sec for pty, notty, or record" }, { "unit", o_int, &req_unit, "PPP interface unit number to use if possible", OPT_LLIMIT, 0, 0 }, #ifdef HAVE_MULTILINK { "multilink", o_bool, &multilink, "Enable multilink operation", 1 }, { "nomultilink", o_bool, &multilink, "Disable multilink operation", 0 }, { "mp", o_bool, &multilink, "Enable multilink operation", 1 }, { "nomp", o_bool, &multilink, "Disable multilink operation", 0 }, { "bundle", o_string, &bundle_name, "Bundle name for multilink" }, #endif /* HAVE_MULTILINK */ #ifdef PLUGIN { "plugin", o_special, (void *)loadplugin, "Load a plug-in module into pppd", OPT_PRIV }, #endif /* PLUGIN */ #ifdef PPP_FILTER { "pdebug", o_int, &dflag, "libpcap debugging" }, { "pass-filter", o_special, setpassfilter, "set filter for packets to pass" }, { "active-filter", o_special, setactivefilter, "set filter for active pkts" }, #endif /* PPP_FILTER */ { NULL } }; /* * This string gets printed out when "options" is given on the command * line. Following this string, all of the available options and * their descriptions are printed out as well. Certain options which * are not available as part of the option_t structure are placed in * the "dummy" option structure. */ static const char pre_allopt_string[] = "\ pppd version %s.%d%s\n\ Usage: %s [ options ], where options are:\n\n\ "; /* Do not call add_options() on this structure */ static option_t dummy_options[] = { { "", o_special_noarg, NULL, "Communicate over the named device" }, { "", o_special_noarg, NULL, "Set the baud rate to " }, { "[]:[]", o_special_noarg, NULL, "Set the local and/or remote interface IP addresses" }, { NULL } }; static const char post_allopt_string[] = "\ \n\ Notes:\ \t\tinteger type argument\n\ \t\tstring type argument\n\ \t\tspecial type argument\n\ \t(!)\tprivileged option available only when pppd is executed by root\n\ \t\tor when found in the privileged option files (/etc/ppp/options,\n\ \t\t/etc/ppp/options.ttyname, /etc/ppp/peers/name, or following\n\ \t\t\"--\" in /etc/ppp/pap-secrets or /etc/ppp/chap-secrets).\n\ \t(#)\tdisabled option\n\ \n\ Please see the pppd man page for details.\n"; /* * parse_args - parse a string of arguments from the command line. If prepass * is true, we are scanning for the device name and only processing a few * options, so error messages are suppressed. Returns 1 upon successful * processing of options, and 0 otherwise. */ int parse_args(argc, argv) int argc; char **argv; { char *arg; option_t *opt; int ret; privileged_option = privileged; option_source = "command line"; option_line = 0; while (argc > 0) { arg = *argv++; --argc; /* * First check to see if it's a known option name. If so, parse the * argument(s) and set the option. */ opt = find_option(arg); if (opt != NULL) { int n = n_arguments(opt); if (argc < n) { option_error("too few parameters for option '%s'", arg); return (0); } current_option = arg; if (!process_option(opt, argv, 0)) return (0); argc -= n; argv += n; continue; } /* * Maybe a tty name, speed or IP address ? */ if (((ret = setdevname(arg)) == 0) && ((ret = setspeed(arg)) == 0) && ((ret = setipaddr(arg)) == 0) && !prepass) { option_error("unrecognized option '%s'", arg); usage(); return (0); } if (ret < 0) /* error */ return (0); } return (1); } /* * options_from_file - read a string of options from a file, and * interpret them. Returns 1 upon successful processing of options, * and 0 otherwise. */ int options_from_file #ifdef __STDC__ (char *filename, bool must_exist, bool check_prot, bool priv) #else (filename, must_exist, check_prot, priv) char *filename; bool must_exist; bool check_prot; bool priv; #endif { FILE *f; int i, newline, ret, err; option_t *opt; bool oldpriv; int oldline, sline; char *oldsource; char *argv[MAXARGS]; char args[MAXARGS][MAXWORDLEN]; char cmd[MAXWORDLEN]; static bool firsterr = 1; static int nestlevel = 0; if (nestlevel >= MAXFILENESTING) { option_error("file nesting too deep"); return (0); } if (check_prot) (void) seteuid(getuid()); errno = 0; f = fopen(filename, "r"); err = errno; if (check_prot) (void) seteuid(0); if (f == NULL) { if (!must_exist && err == ENOENT) return (1); errno = err; option_error("Can't open options file %s: %m", filename); return (0); } nestlevel++; oldpriv = privileged_option; privileged_option = priv; oldsource = option_source; /* * strdup() is used here because the pointer might refer to the * caller's automatic (stack) storage, and the option_info array * records the source file name. */ option_source = strdup(filename); oldline = option_line; option_line = 1; if (option_source == NULL) option_source = "file"; ret = 0; while (getword(f, cmd, &newline, filename)) { sline = option_line; /* * First see if it's a command. */ opt = find_option(cmd); if (opt != NULL) { int n = n_arguments(opt); for (i = 0; i < n; ++i) { if (!getword(f, args[i], &newline, filename)) { option_error("too few parameters for option '%s'", cmd); goto err; } argv[i] = args[i]; } current_option = cmd; if ((opt->flags & OPT_DEVEQUIV) && devnam_fixed) { option_error("the '%s' option may not be used here", cmd); goto err; } if (!process_option(opt, argv, sline)) goto err; continue; } /* * Maybe a tty name, speed or IP address ? */ if (((i = setdevname(cmd)) == 0) && ((i = setspeed(cmd)) == 0) && ((i = setipaddr(cmd)) == 0)) { option_error("unrecognized option '%s'", cmd); goto err; } if (i < 0) /* error */ goto err; } ret = 1; err: (void) fclose(f); /* We assume here that we abort all processing on the first error. */ if (firsterr) firsterr = 0; else if (!prepass && !ret) option_error("error in included file"); /* * Cannot free option_source because it might be referenced in one * or more option_info structures now. */ privileged_option = oldpriv; option_source = oldsource; option_line = oldline; nestlevel--; return (ret); } /* * options_from_user - see if the user has a ~/.ppprc file, and if so, * interpret options from it. Returns 1 upon successful processing of * options, and 0 otherwise. */ int options_from_user() { char *user, *path, *file; int ret; struct passwd *pw; size_t pl; pw = getpwuid(getuid()); if (pw == NULL || (user = pw->pw_dir) == NULL || user[0] == '\0') return (1); file = _PATH_USEROPT; pl = strlen(user) + strlen(file) + 2; path = malloc(pl); if (path == NULL) novm("init file name"); (void) slprintf(path, pl, "%s/%s", user, file); ret = options_from_file(path, 0, 1, privileged); free(path); return (ret); } /* * options_for_tty - see if an options file exists for the serial device, and * if so, interpret options from it. Returns 1 upon successful processing of * options, and 0 otherwise. */ int options_for_tty() { char *dev, *path, *p; int ret; size_t pl; dev = devnam; if (strncmp(dev, "/dev/", 5) == 0) dev += 5; if (dev[0] == '\0' || strcmp(dev, "tty") == 0) return (1); /* don't look for /etc/ppp/options.tty */ pl = strlen(_PATH_TTYOPT) + strlen(dev) + 1; path = malloc(pl); if (path == NULL) novm("tty init file name"); (void) slprintf(path, pl, "%s%s", _PATH_TTYOPT, dev); /* Turn slashes into dots, for Solaris case (e.g. /dev/term/a) */ for (p = path + strlen(_PATH_TTYOPT); *p != '\0'; ++p) if (*p == '/') *p = '.'; ret = options_from_file(path, 0, 0, 1); free(path); return (ret); } /* * options_from_list - process a string of options in a wordlist. Returns 1 * upon successful processing of options, and 0 otherwise. */ int options_from_list #ifdef __STDC__ (struct wordlist *w, bool priv) #else (w, priv) struct wordlist *w; bool priv; #endif { char *argv[MAXARGS]; option_t *opt; int i, ret = 0; privileged_option = priv; /* Caller is expected to set option_source and option_line. */ while (w != NULL) { /* * First see if it's a command. */ opt = find_option(w->word); if (opt != NULL) { int n = n_arguments(opt); struct wordlist *w0 = w; for (i = 0; i < n; ++i) { w = w->next; if (w == NULL) { option_error("too few parameters for option '%s'", w0->word); goto err; } argv[i] = w->word; } current_option = w0->word; if (!process_option(opt, argv, option_line)) goto err; continue; } /* * Options from the {p,ch}ap-secrets files can't change the device * name nor the speed. Therefore, calls to setdevname() and * setspeed() were removed. */ if ((i = setipaddr(w->word)) == 0) { option_error("unrecognized option '%s'", w->word); goto err; } if (i < 0) /* error */ goto err; } ret = 1; err: return (ret); } /* * find_option - scan the option lists for the various protocols looking for an * entry with the given name. Returns a pointer to the matching option_t * structure upon successful processing of options, and NULL otherwise. */ static option_t * find_option(name) char *name; { hashentry_t *bucket; bucket = hash_tbl[opt_hash(name)]; for (; bucket != NULL; bucket = bucket->next) { if (bucket->opt.p->name != NULL) { if ((strcmp(bucket->opt.p->name, name) == 0) && !(bucket->opt.p->flags & OPT_DISABLE)) { return (bucket->opt.p); } } } return (NULL); } /* * process_option - process one new-style option (something other than a * port name, bit rate, or IP address). Returns 1 upon successful * processing of options, and 0 otherwise. */ static int process_option(opt, argv, sline) option_t *opt; char **argv; int sline; { u_int32_t v; int iv, a; char *sv; int (*parser) __P((char **, option_t *)); if ((opt->flags & OPT_PREPASS) == 0 && prepass) return (1); if ((opt->flags & OPT_INITONLY) && phase != PHASE_INITIALIZE) { option_error("it's too late to use the '%s' option", opt->name); return (0); } if ((opt->flags & OPT_PRIV) && !privileged_option) { option_error("using the '%s' option requires root privilege", opt->name); return (0); } if ((opt->flags & OPT_ENABLE) && !privileged_option && *(bool *)(opt->addr2) == 0) { option_error("'%s' option is disabled", opt->name); return (0); } if ((opt->flags & OPT_PRIVFIX) && !privileged_option) { struct option_info *ip = (struct option_info *) opt->addr2; if ((ip != NULL) && ip->priv) { option_error("'%s' option cannot be overridden", opt->name); return (0); } } switch (opt->type) { case o_bool: v = opt->flags & OPT_VALUE; *(bool *)(opt->addr) = (v != 0); if ((opt->addr2 != NULL) && (opt->flags & OPT_A2COPY)) *(bool *)(opt->addr2) = (v != 0); break; case o_int: iv = 0; if ((opt->flags & OPT_NOARG) == 0) { if (!int_option(*argv, &iv)) return (0); if ((((opt->flags & OPT_LLIMIT) && (iv < opt->lower_limit)) || ((opt->flags & OPT_ULIMIT) && (iv > opt->upper_limit))) && !((opt->flags & OPT_ZEROOK) && (iv == 0))) { char *zok = (opt->flags & OPT_ZEROOK) ? " zero or" : ""; switch (opt->flags & OPT_LIMITS) { case OPT_LLIMIT: option_error("%s value must be%s >= %d", opt->name, zok, opt->lower_limit); break; case OPT_ULIMIT: option_error("%s value must be%s <= %d", opt->name, zok, opt->upper_limit); break; case OPT_LIMITS: option_error("%s value must be%s between %d and %d", opt->name, zok, opt->lower_limit, opt->upper_limit); break; } return (0); } } a = opt->flags & OPT_VALUE; if (a >= 128) a -= 256; /* sign extend */ iv += a; if (opt->flags & OPT_INC) iv += *(int *)(opt->addr); if ((opt->flags & OPT_NOINCR) && !privileged_option) { int oldv = *(int *)(opt->addr); if ((opt->flags & OPT_ZEROINF) && (iv == 0)) { if (oldv > 0) { option_error("%s value cannot be set to infinity; limited to %d", opt->name, oldv); return (0); } } else if (iv > oldv) { option_error("%s value cannot be increased beyond %d", opt->name, oldv); return (0); } } *(int *)(opt->addr) = iv; if ((opt->addr2 != NULL) && (opt->flags & OPT_A2COPY)) *(int *)(opt->addr2) = iv; break; case o_uint32: if (opt->flags & OPT_NOARG) { v = opt->flags & OPT_VALUE; } else if (!number_option(*argv, &v, 16)) return (0); if (opt->flags & OPT_OR) v |= *(u_int32_t *)(opt->addr); *(u_int32_t *)(opt->addr) = v; if ((opt->addr2 != NULL) && (opt->flags & OPT_A2COPY)) *(u_int32_t *)(opt->addr2) = v; break; case o_string: if (opt->flags & OPT_STATIC) { (void) strlcpy((char *)(opt->addr), *argv, opt->upper_limit); if ((opt->addr2 != NULL) && (opt->flags & OPT_A2COPY)) { (void) strlcpy((char *)(opt->addr2), *argv, opt->upper_limit); } } else { sv = strdup(*argv); if (sv == NULL) novm("option argument"); *(char **)(opt->addr) = sv; if (opt->addr2 != NULL && (opt->flags & OPT_A2COPY)) *(char **)(opt->addr2) = sv; } break; case o_special_noarg: case o_special: parser = (int (*) __P((char **, option_t *))) opt->addr; if (!(*parser)(argv, opt)) return (0); break; } if (opt->addr2 != NULL) { if (opt->flags & OPT_A2INFO) { struct option_info *ip = (struct option_info *) opt->addr2; ip->priv = privileged_option; ip->source = option_source; ip->line = sline; } else if ((opt->flags & (OPT_A2COPY|OPT_ENABLE)) == 0) *(bool *)(opt->addr2) = 1; } return (1); } /* * n_arguments - tell how many arguments an option takes. Returns 1 upon * successful processing of options, and 0 otherwise. */ static int n_arguments(opt) option_t *opt; { return ((opt->type == o_bool || opt->type == o_special_noarg || (opt->flags & OPT_NOARG)) ? 0 : 1); } /* * opt_hash - a hash function that works quite well for strings. Returns * the hash key of the supplied string. */ static u_int32_t opt_hash(key) const void *key; { register const char *ptr; register u_int32_t val; val = 0; ptr = key; while (*ptr != '\0') { int tmp; val = (val << 4) + (*ptr); tmp = val & 0xf0000000; if (tmp) { val ^= (tmp >> 24); val ^= tmp; } ptr++; } return (val % OPTHASH_TBLSIZE); } /* * add_options - add a list of options to the chained hash table. * Also detect duplicate options, and if found, disable the older * definition and log it as an error. */ void add_options(opt) option_t *opt; { register option_t *sopt; register hashentry_t *bucket; register u_int32_t loc; hashentry_t *he; /* fill hash-table */ for (sopt = opt; sopt->name != NULL; ++sopt, hash_tblcnt++) { /* first, allocate a hash entry */ he = (hashentry_t *)malloc(sizeof(*he)); if (he == NULL) { novm("option hash table entry"); } he->opt.p = sopt; he->next = NULL; /* * fill the chained hash table and take care of any collisions or * duplicate items. */ loc = opt_hash(sopt->name); bucket = hash_tbl[loc]; if (bucket != NULL) { for (;;) { if (!(bucket->opt.p->flags & OPT_DISABLE) && strcmp(sopt->name, bucket->opt.p->name) == 0) { info("option '%s' redefined; old definition disabled", sopt->name); bucket->opt.p->flags |= OPT_DISABLE; } if (bucket->next == NULL) break; bucket = bucket->next; } bucket->next = he; } else { hash_tbl[loc] = he; } } } /* * remove_option - disable an option. Returns the option_t structure * of the disabled option, or NULL if the option name is invalid or if * the option has already been disabled. */ option_t * remove_option(name) char *name; { option_t *opt; if ((opt = find_option(name)) != NULL) { opt->flags |= OPT_DISABLE; } return (opt); } /* * opt_compare - a compare function supplied to the quicksort routine. * Returns an integer less than, equal to, or greater than zero to indicate * if the first argument is considered less than, equal to, or greater * than the second argument. */ static int opt_compare(p1, p2) const void *p1; const void *p2; { opt_t *o1 = (opt_t *)p1; opt_t *o2 = (opt_t *)p2; return (strcmp(o1->p->name, o2->p->name)); } /*ARGSUSED*/ static int showalloptions(argv, topt) char **argv; option_t *topt; { #define MAXOPTSTRLEN 257 #define PRINTOPTIONS() { \ (void) slprintf(opt_str, sizeof(opt_str), "%s", opt->name); \ if ((opt->type == o_int || opt->type == o_uint32) && \ !(opt->flags & OPT_NOARG)) { \ (void) strlcat(opt_str, " ", sizeof(opt_str)); \ } else if (opt->type == o_string) { \ (void) strlcat(opt_str, " ", sizeof(opt_str)); \ } else if (opt->type == o_special) { \ (void) strlcat(opt_str, " ", sizeof(opt_str)); \ } \ if (opt->flags & OPT_PRIV) { \ (void) strlcat(opt_str, " (!)", sizeof(opt_str)); \ } else if (opt->flags & OPT_DISABLE) { \ (void) strlcat(opt_str, " (#)", sizeof(opt_str)); \ } \ (void) printf("%-26s%s\n", opt_str, opt->description); \ } char opt_str[MAXOPTSTRLEN]; option_t *opt; hashentry_t *bucket; int i, sofar; opt_t *sopt; if (phase != PHASE_INITIALIZE) { return (0); } (void) printf(pre_allopt_string, VERSION, PATCHLEVEL, IMPLEMENTATION, progname); for (opt = dummy_options; opt->name != NULL; ++opt) { PRINTOPTIONS(); } sopt = malloc(sizeof(*sopt) * hash_tblcnt); if (sopt == NULL) { novm("sorted option table"); } sofar = 0; for (i = 0; i < OPTHASH_TBLSIZE; i++) { for (bucket = hash_tbl[i]; bucket != NULL; bucket = bucket->next) { if (sofar >= hash_tblcnt) { fatal("options hash table corrupted; size mismatch"); } sopt[sofar++].p = bucket->opt.p; } } qsort((void *)sopt, sofar, sizeof(sopt[0]), opt_compare); for (i = 0; i < sofar; i++) { opt = sopt[i].p; PRINTOPTIONS(); } (void) printf(post_allopt_string); (void) free(sopt); #undef MAXOPTSTRLEN #undef PRINTOPTIONS return (0); } /* * usage - print out a message telling how to use the program. * This string gets printed out when either "--help" or an invalid option * is specified. */ static void usage() { static const char usage_string[] = "\ pppd version %s.%d%s\n\ Usage: %s [ options ], where options are:\n\ \t\tCommunicate over the named device\n\ \t\t\tSet the baud rate to \n\ \t:\tSet the local and/or remote interface IP\n\ \t\t\taddresses. Either one may be omitted.\n\ \tnoauth\t\tDon't require authentication from peer\n\ \tconnect

\tInvoke shell command

to set up the serial line\n\ \tdefaultroute\tAdd default route through interface\n\ Use \"%s options\" or \"man pppd\" for more options.\n\ "; if (phase == PHASE_INITIALIZE) (void) fprintf(stderr, usage_string, VERSION, PATCHLEVEL, IMPLEMENTATION, progname, progname); } /* * showhelp - print out usage message and exit program upon success, or * return 0 otherwise. */ /*ARGSUSED*/ static int showhelp(argv, opt) char **argv; option_t *opt; { if (phase == PHASE_INITIALIZE) { usage(); exit(0); } return (0); } /* * showversion - print out the version number and exit program upon success, * or return 0 otherwise. */ /*ARGSUSED*/ static int showversion(argv, opt) char **argv; option_t *opt; { if (phase == PHASE_INITIALIZE) { (void) fprintf(stderr, "pppd version %s.%d%s\n", VERSION, PATCHLEVEL, IMPLEMENTATION); exit(0); } return (0); } /* * option_error - print a message about an error in an option. The message is * logged, and also sent to stderr if phase == PHASE_INITIALIZE. */ void option_error __V((char *fmt, ...)) { va_list args; char buf[256]; int i, err; #if defined(__STDC__) va_start(args, fmt); #else char *fmt; va_start(args); fmt = va_arg(args, char *); #endif if (prepass) { va_end(args); return; } err = errno; if (option_source == NULL) { i = 0; } else if (option_line <= 0) { (void) strlcpy(buf, option_source, sizeof (buf)); i = strlen(buf); } else { i = slprintf(buf, sizeof(buf), "%s:%d", option_source, option_line); } if (i != 0) { (void) strlcat(buf, ": ", sizeof (buf)); i += 2; } errno = err; (void) vslprintf(buf + i, sizeof (buf) - i, fmt, args); va_end(args); if ((phase == PHASE_INITIALIZE) && !detached) (void) fprintf(stderr, "%s: %s\n", progname, buf); syslog(LOG_ERR, "%s", buf); } /* * getword - read a word from a file. Words are delimited by white-space or by * quotes (" or '). Quotes, white-space and \ may be escaped with \. * \ is ignored. Returns 1 upon successful processing of options, * and 0 otherwise. */ int getword(f, word, newlinep, filename) FILE *f; char *word; int *newlinep; char *filename; { int c, len, escape; int quoted, comment; int value, digit, got, n; #define isoctal(c) ((c) >= '0' && (c) < '8') *newlinep = 0; len = 0; escape = 0; comment = 0; /* * First skip white-space and comments. */ for (;;) { c = getc(f); if (c == EOF) break; /* * A newline means the end of a comment; backslash-newline * is ignored. Note that we cannot have escape && comment. */ if (c == '\n') { option_line++; if (!escape) { *newlinep = 1; comment = 0; } else escape = 0; continue; } /* * Ignore characters other than newline in a comment. */ if (comment) continue; /* * If this character is escaped, we have a word start. */ if (escape) break; /* * If this is the escape character, look at the next character. */ if (c == '\\') { escape = 1; continue; } /* * If this is the start of a comment, ignore the rest of the line. */ if (c == '#') { comment = 1; continue; } /* * A non-whitespace character is the start of a word. */ if (!isspace(c)) break; } /* * Save the delimiter for quoted strings. */ if (!escape && (c == '"' || c == '\'')) { quoted = c; c = getc(f); } else quoted = 0; /* * Process characters until the end of the word. */ while (c != EOF) { if (escape) { /* * This character is escaped: backslash-newline is ignored, * various other characters indicate particular values * as for C backslash-escapes. */ escape = 0; if (c == '\n') { c = getc(f); continue; } got = 0; switch (c) { case 'a': value = '\a'; break; case 'b': value = '\b'; break; case 'f': value = '\f'; break; case 'n': value = '\n'; break; case 'r': value = '\r'; break; case 's': value = ' '; break; case 't': value = '\t'; break; default: if (isoctal(c)) { /* * \ddd octal sequence */ value = 0; for (n = 0; n < 3 && isoctal(c); ++n) { value = (value << 3) + (c & 07); c = getc(f); } got = 1; break; } if (c == 'x') { /* * \x sequence */ value = 0; c = getc(f); for (n = 0; n < 2 && isxdigit(c); ++n) { digit = (islower(c) ? toupper(c) : c) - '0'; if (digit > 10 || digit < 0) /* allow non-ASCII */ digit += '0' + 10 - 'A'; value = (value << 4) + digit; c = getc (f); } got = 1; break; } /* * Otherwise the character stands for itself. */ value = c; break; } /* * Store the resulting character for the escape sequence. */ if (len < MAXWORDLEN) { word[len] = value; ++len; } if (!got) c = getc(f); continue; } /* * Not escaped: see if we've reached the end of the word. */ if (quoted) { if (c == quoted) break; } else { if (isspace(c) || c == '#') { (void) ungetc (c, f); break; } } /* * Backslash starts an escape sequence. */ if (c == '\\') { escape = 1; c = getc(f); continue; } /* * An ordinary character: store it in the word and get another. */ if (len < MAXWORDLEN) { word[len] = c; ++len; } c = getc(f); } /* * End of the word: check for errors. */ if (c == EOF) { if (ferror(f)) { if (errno == 0) errno = EIO; option_error("Error reading %s: %m", filename); die(1); } /* * If len is zero, then we didn't find a word before the * end of the file. */ if (len == 0) return (0); } /* * Warn if the word was too long, and append a terminating null. */ if (len >= MAXWORDLEN) { option_error("warning: word in file %s too long (%.20s...)", filename, word); len = MAXWORDLEN - 1; } word[len] = '\0'; return (1); #undef isoctal } /* * number_option - parse an unsigned numeric parameter for an option. * Returns 1 upon successful processing of options, and 0 otherwise. */ static int number_option(str, valp, base) char *str; u_int32_t *valp; int base; { char *ptr; *valp = strtoul(str, &ptr, base); if (ptr == str || *ptr != '\0') { option_error("invalid numeric parameter '%s' for '%s' option", str, current_option); return (0); } return (1); } /* * save_source - store option source, line, and privilege into an * option_info structure. */ void save_source(info) struct option_info *info; { info->priv = privileged_option; info->source = option_source; info->line = option_line; } /* * set_source - set option source, line, and privilege from an * option_info structure. */ void set_source(info) struct option_info *info; { privileged_option = info->priv; option_source = info->source; option_line = info->line; } /* * name_source - return string containing option source and line. Can * be used as part of an option_error call. */ const char * name_source(info) struct option_info *info; { static char buf[MAXPATHLEN]; if (info->source == NULL) return "none"; if (info->line <= 0) return info->source; (void) slprintf(buf, sizeof (buf), "%s:%d", info->source, info->line); return (const char *)buf; } /* * int_option - like number_option, but valp is int *, the base is assumed to * be 0, and *valp is not changed if there is an error. Returns 1 upon * successful processing of options, and 0 otherwise. */ int int_option(str, valp) char *str; int *valp; { u_int32_t v; if (!number_option(str, &v, 0)) return (0); *valp = (int) v; return (1); } /* * The following procedures parse options. */ /* * readfile - take commands from a file. */ /*ARGSUSED*/ static int readfile(argv, opt) char **argv; option_t *opt; { return (options_from_file(*argv, 1, 1, privileged_option)); } /* * callfile - take commands from /etc/ppp/peers/. Name may not contain * /../, start with / or ../, or end in /. Returns 1 upon successful * processing of options, and 0 otherwise. */ /*ARGSUSED*/ static int callfile(argv, opt) char **argv; option_t *opt; { char *fname, *arg, *p; int l, ok; arg = *argv; ok = 1; if (arg[0] == '/' || arg[0] == '\0') ok = 0; else { for (p = arg; *p != '\0'; ) { if (p[0] == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) { ok = 0; break; } while (*p != '/' && *p != '\0') ++p; if (*p == '/') ++p; } } if (!ok) { option_error("call option value may not contain .. or start with /"); return (0); } l = strlen(arg) + strlen(_PATH_PEERFILES) + 1; if ((fname = (char *) malloc(l)) == NULL) novm("call file name"); (void) slprintf(fname, l, "%s%s", _PATH_PEERFILES, arg); ok = options_from_file(fname, 1, 1, 1); free(fname); return (ok); } #ifdef PPP_FILTER /* * setpdebug - set libpcap debugging level. Returns 1 upon successful * processing of options, and 0 otherwise. */ static int setpdebug(argv) char **argv; { return (int_option(*argv, &dflag)); } /* * setpassfilter - set the pass filter for packets. Returns 1 upon successful * processing of options, and 0 otherwise. */ /*ARGSUSED*/ static int setpassfilter(argv, opt) char **argv; option_t *opt; { pc.linktype = DLT_PPP; pc.snapshot = PPP_HDRLEN; if (pcap_compile(&pc, &pass_filter, *argv, 1, netmask) == 0) return (1); option_error("error in pass-filter expression: %s\n", pcap_geterr(&pc)); return (0); } /* * setactivefilter - set the active filter for packets. Returns 1 upon * successful processing of options, and 0 otherwise. */ /*ARGSUSED*/ static int setactivefilter(argv, opt) char **argv; option_t *opt; { pc.linktype = DLT_PPP; pc.snapshot = PPP_HDRLEN; if (pcap_compile(&pc, &active_filter, *argv, 1, netmask) == 0) return (1); option_error("error in active-filter expression: %s\n", pcap_geterr(&pc)); return (0); } #endif /* PPP_FILTER */ /* * noopt - disable all options. Returns 1 upon successful processing of * options, and 0 otherwise. */ /*ARGSUSED*/ static int noopt(argv, opt) char **argv; option_t *opt; { BZERO((char *) &lcp_wantoptions[0], sizeof (struct lcp_options)); BZERO((char *) &lcp_allowoptions[0], sizeof (struct lcp_options)); BZERO((char *) &ipcp_wantoptions[0], sizeof (struct ipcp_options)); BZERO((char *) &ipcp_allowoptions[0], sizeof (struct ipcp_options)); return (1); } /* * setdomain - set domain name to append to hostname. Returns 1 upon * successful processing of options, and 0 otherwise. */ /*ARGSUSED*/ static int setdomain(argv, opt) char **argv; option_t *opt; { if (!privileged_option) { option_error("using the domain option requires root privilege"); return (0); } (void) gethostname(hostname, MAXHOSTNAMELEN+1); if (**argv != '\0') { if (**argv != '.') (void) strncat(hostname, ".", MAXHOSTNAMELEN - strlen(hostname)); (void) strncat(hostname, *argv, MAXHOSTNAMELEN - strlen(hostname)); } hostname[MAXHOSTNAMELEN] = '\0'; return (1); } /* * setspeed - set the speed. Returns 1 upon successful processing of options, * and 0 otherwise. */ static int setspeed(arg) char *arg; { char *ptr; int spd; if (prepass) return (1); spd = strtol(arg, &ptr, 0); if (ptr == arg || *ptr != '\0' || spd <= 0) return (0); inspeed = spd; save_source(&speed_info); return (1); } /* * setdevname - set the device name. Returns 1 upon successful processing of * options, 0 when the device does not exist, and -1 when an error is * encountered. */ static int setdevname(cp) char *cp; { struct stat statbuf; char dev[MAXPATHLEN]; if (*cp == '\0') return (0); if (strncmp("/dev/", cp, 5) != 0) { (void) strlcpy(dev, "/dev/", sizeof(dev)); (void) strlcat(dev, cp, sizeof(dev)); cp = dev; } /* * Check if there is a character device by this name. */ if (stat(cp, &statbuf) < 0) { if (errno == ENOENT) { return (0); } option_error("Couldn't stat '%s': %m", cp); return (-1); } if (!S_ISCHR(statbuf.st_mode)) { option_error("'%s' is not a character device", cp); return (-1); } if (phase != PHASE_INITIALIZE) { option_error("device name cannot be changed after initialization"); return (-1); } else if (devnam_fixed) { option_error("per-tty options file may not specify device name"); return (-1); } if (devnam_info.priv && !privileged_option) { option_error("device name %s from %s cannot be overridden", devnam, name_source(&devnam_info)); return (-1); } (void) strlcpy(devnam, cp, sizeof(devnam)); devstat = statbuf; default_device = 0; save_source(&devnam_info); return (1); } /* * setipaddr - set the IP address. Returns 1 upon successful processing of * options, 0 when the argument does not contain a `:', and -1 for error. */ static int setipaddr(arg) char *arg; { struct hostent *hp; char *colon; u_int32_t local, remote; ipcp_options *wo = &ipcp_wantoptions[0]; /* * IP address pair separated by ":". */ if ((colon = strchr(arg, ':')) == NULL) return (0); if (prepass) return (1); /* * If colon first character, then no local addr. */ if (colon != arg) { *colon = '\0'; if ((local = inet_addr(arg)) == (u_int32_t) -1) { if ((hp = gethostbyname(arg)) == NULL) { option_error("unknown host: %s", arg); return (-1); } else { BCOPY(hp->h_addr, &local, sizeof(local)); } } if (bad_ip_adrs(local)) { option_error("bad local IP address %I", local); return (-1); } if (local != 0) { save_source(&ipsrc_info); wo->ouraddr = local; } *colon = ':'; } /* * If colon last character, then no remote addr. */ if (*++colon != '\0') { if ((remote = inet_addr(colon)) == (u_int32_t) -1) { if ((hp = gethostbyname(colon)) == NULL) { option_error("unknown host: %s", colon); return (-1); } else { BCOPY(hp->h_addr, &remote, sizeof(remote)); if (remote_name[0] == '\0') (void) strlcpy(remote_name, colon, sizeof(remote_name)); } } if (bad_ip_adrs(remote)) { option_error("bad remote IP address %I", remote); return (-1); } if (remote != 0) { save_source(&ipdst_info); wo->hisaddr = remote; } } return (1); } /* * setnetmask - set the netmask to be used on the interface. Returns 1 upon * successful processing of options, and 0 otherwise. */ /*ARGSUSED*/ static int setnetmask(argv, opt) char **argv; option_t *opt; { u_int32_t mask; int n; char *p; /* * Unfortunately, if we use inet_addr, we can't tell whether * a result of all 1s is an error or a valid 255.255.255.255. */ p = *argv; n = parse_dotted_ip(p, &mask); mask = htonl(mask); if (n == 0 || p[n] != 0 || (netmask & ~mask) != 0) { option_error("invalid netmask value '%s'", *argv); return (0); } netmask = mask; return (1); } /* * parse_dotted_ip - parse and convert the IP address string to make * sure it conforms to the dotted notation. Returns the length of * processed characters upon success, and 0 otherwise. If successful, * the converted IP address number is stored in vp, in the host byte * order. */ int parse_dotted_ip(cp, vp) register char *cp; u_int32_t *vp; { register u_int32_t val, base, n; register char c; char *cp0 = cp; u_char parts[3], *pp = parts; if ((*cp == '\0') || (vp == NULL)) return (0); /* disallow null string in cp */ *vp = 0; again: /* * Collect number up to ``.''. Values are specified as for C: * 0x=hex, 0=octal, other=decimal. */ val = 0; base = 10; if (*cp == '0') { if (*++cp == 'x' || *cp == 'X') base = 16, cp++; else base = 8; } while ((c = *cp) != '\0') { if (isdigit(c)) { if ((c - '0') >= base) break; val = (val * base) + (c - '0'); cp++; continue; } if (base == 16 && isxdigit(c)) { val = (val << 4) + (c + 10 - (islower(c) ? 'a' : 'A')); cp++; continue; } break; } if (*cp == '.') { /* * Internet format: * a.b.c.d * a.b.c (with c treated as 16-bits) * a.b (with b treated as 24 bits) */ if ((pp >= parts + 3) || (val > 0xff)) { return (0); } *pp++ = (u_char)val; cp++; goto again; } /* * Check for trailing characters. */ if (*cp != '\0' && !isspace(*cp)) { return (0); } /* * Concoct the address according to the number of parts specified. */ n = pp - parts; switch (n) { case 0: /* a -- 32 bits */ break; case 1: /* a.b -- 8.24 bits */ if (val > 0xffffff) return (0); val |= parts[0] << 24; break; case 2: /* a.b.c -- 8.8.16 bits */ if (val > 0xffff) return (0); val |= (parts[0] << 24) | (parts[1] << 16); break; case 3: /* a.b.c.d -- 8.8.8.8 bits */ if (val > 0xff) return (0); val |= (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8); break; default: return (0); } *vp = val; return (cp - cp0); } /* * setxonxoff - modify the asyncmap to include escaping XON and XOFF * characters used for software flow control. Returns 1 upon successful * processing of options, and 0 otherwise. */ /*ARGSUSED*/ static int setxonxoff(argv, opt) char **argv; option_t *opt; { int xonxoff = 0x000A0000; lcp_wantoptions[0].neg_asyncmap = 1; lcp_wantoptions[0].asyncmap |= xonxoff; /* escape ^S and ^Q */ lcp_allowoptions[0].asyncmap |= xonxoff; xmit_accm[0][0] |= xonxoff; xmit_accm[0][4] |= xonxoff; /* escape 0x91 and 0x93 as well */ crtscts = -2; return (1); } /* * setlogfile - open (or create) a file used for logging purposes. Returns 1 * upon success, and 0 otherwise. */ /*ARGSUSED*/ static int setlogfile(argv, opt) char **argv; option_t *opt; { int fd, err; if (!privileged_option) (void) seteuid(getuid()); fd = open(*argv, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0644); if (fd < 0 && errno == EEXIST) fd = open(*argv, O_WRONLY | O_APPEND); err = errno; if (!privileged_option) (void) seteuid(0); if (fd < 0) { errno = err; option_error("Can't open log file %s: %m", *argv); return (0); } if (log_to_file && log_to_fd >= 0) (void) close(log_to_fd); log_to_fd = fd; log_to_file = 1; early_log = 0; return (1); } #ifdef PLUGIN /* * loadplugin - load and initialize the plugin. Returns 1 upon successful * processing of the plugin, and 0 otherwise. */ /*ARGSUSED*/ static int loadplugin(argv, opt) char **argv; option_t *opt; { char *arg = *argv; void *handle; const char *err; void (*init) __P((void)); handle = dlopen(arg, RTLD_GLOBAL | RTLD_NOW); if (handle == NULL) { err = dlerror(); if (err != NULL) option_error("%s", err); option_error("Couldn't load plugin %s", arg); return (0); } init = (void (*)(void))dlsym(handle, "plugin_init"); if (init == NULL) { option_error("%s has no initialization entry point", arg); (void) dlclose(handle); return (0); } info("Plugin %s loaded.", arg); (*init)(); return (1); } #endif /* PLUGIN */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _PATCH_LEVEL_H #define _PATCH_LEVEL_H #ifdef __cplusplus extern "C" { #endif #define PATCHLEVEL 0 #define VERSION "2.4" #define IMPLEMENTATION "b1 (Sun Microsystems, Inc.)" #ifdef __cplusplus } #endif #endif /* _PATCH_LEVEL_H */ /* * pathnames.h - define path names used by pppd. * * Copyright (c) 2000 by Sun Microsystems, Inc. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. * * SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES * * Copyright (c) 1993 The Australian National 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 the Australian National 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. * * $Id: pathnames.h,v 1.13 2000/04/04 07:06:52 paulus Exp $ */ #ifndef __PATHNAMES_H__ #define __PATHNAMES_H__ #ifdef HAVE_PATHS_H #include #else /* HAVE_PATHS_H */ #ifdef __cplusplus extern "C" { #endif #ifndef _PATH_VARRUN #define _PATH_VARRUN "/etc/ppp/" #endif /* _PATH_VARRUN */ #define _PATH_DEVNULL "/dev/null" #endif /* HAVE_PATHS_H */ #ifndef _ROOT_PATH #define _ROOT_PATH #endif /* _ROOT_PATH */ /* * Duplication of /etc/ppp allows default compilation on non-ANSI compilers. */ #define _PATH_UPAPFILE _ROOT_PATH "/etc/ppp/pap-secrets" #define _PATH_CHAPFILE _ROOT_PATH "/etc/ppp/chap-secrets" #define _PATH_SYSOPTIONS _ROOT_PATH "/etc/ppp/options" #define _PATH_IPUP _ROOT_PATH "/etc/ppp/ip-up" #define _PATH_IPDOWN _ROOT_PATH "/etc/ppp/ip-down" #define _PATH_IPV6UP _ROOT_PATH "/etc/ppp/ipv6-up" #define _PATH_IPV6DOWN _ROOT_PATH "/etc/ppp/ipv6-down" #define _PATH_IPXUP _ROOT_PATH "/etc/ppp/ipx-up" #define _PATH_IPXDOWN _ROOT_PATH "/etc/ppp/ipx-down" #define _PATH_AUTHUP _ROOT_PATH "/etc/ppp/auth-up" #define _PATH_AUTHDOWN _ROOT_PATH "/etc/ppp/auth-down" #define _PATH_TTYOPT _ROOT_PATH "/etc/ppp/options." #define _PATH_CONNERRS _ROOT_PATH "/etc/ppp/connect-errors" #define _PATH_PEERFILES _ROOT_PATH "/etc/ppp/peers/" #define _PATH_RESOLV _ROOT_PATH "/etc/ppp/resolv.conf" #define _PATH_USEROPT ".ppprc" #ifdef __STDC__ #define _PATH_PPPDB _ROOT_PATH _PATH_VARRUN "pppd.tdb" #else /* __STDC__ */ #ifdef HAVE_PATHS_H #define _PATH_PPPDB "/var/run/pppd.tdb" #else /* HAVE_PATHS_H */ #define _PATH_PPPDB "/etc/ppp/pppd.tdb" #endif /* HAVE_PATHS_H */ #endif /* __STDC__ */ #ifdef __cplusplus } #endif #endif /* __PATHNAMES_H__ */ # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2011 Nexenta Systems, Inc. All rights reserved. # MINCONN = minconn.so PASSPROMPT = passprompt.so PPPOE = pppoe.so LIBRARIES = minconn.so passprompt.so pppoe.so OBJECTS = minconn.o passprompt.o pppoe.o # The rest of the sources are GPL LINTSRCS = pppoe.c include $(SRC)/lib/Makefile.lib include ../Makefile.def # Express objects in terms of "pics" thus triggering the appropriate CPPFLAGS, # CFLAGS and DYNFLAGS settings from lib/Makefile.lib to build shared objects. $(MINCONN): PICS = pics/minconn.o $(PASSPROMPT): PICS = pics/passprompt.o $(PPPOE): PICS = pics/pppoe.o # Suppress -h setting from DYNFLAGS as these libraries aren't linked against. # Establish external references through mapfiles and dependencies to allow # use of -zdefs. # Hammerhead: pppd plugins are dlopen'd at runtime - symbols come from pppd ZDEFS = HSONAME= $(MINCONN): MAPFILES = mapfile-minconn $(PASSPROMPT): MAPFILES = mapfile-passprompt $(PPPOE): MAPFILES = mapfile-pppoe $(PASSPROMPT): LDLIBS += -lc $(PPPOE): LDLIBS += -lc CPPFLAGS += -I.. -I$(SRC)/uts/common # XX64 -- this should not be needed -- fix me DYNFLAGS += -nostdlib CLOBBERFILES += $(LIBRARIES) LIBPPPPLUGIN= $(LIBRARIES:%=$(LIBPPPPLUGINDIR)/%) $(LIBPPPPLUGIN): FILEMODE = 0544 $(LIBPPPPLUGINDIR): FILEMODE = 0755 .KEEP_STATE: all: $(LIBRARIES) .SECONDEXPANSION: $(MINCONN): pics .WAIT $$(PICS) $(BUILD.SO) $(POST_PROCESS_SO) $(PASSPROMPT): pics .WAIT $$(PICS) $(BUILD.SO) $(POST_PROCESS_SO) $(PPPOE): pics .WAIT $$(PICS) $(BUILD.SO) $(POST_PROCESS_SO) install: all $(LIBPPPPLUGINDIR) $(LIBPPPPLUGIN) $(LIBPPPPLUGINDIR): $(INS.dir) $(LIBPPPPLUGINDIR)/%: % $(INS.file) lint: $(LINT.c) $(LINTSRCS) $(LDLIBS) include $(SRC)/lib/Makefile.targ * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. PPPD PLUGIN TO IMPLEMENT A 'MINCONNECT' OPTION * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. PPPD PLUGIN TO INVOKE AN EXTERNAL PAP PASSWORD PROMPTER # # Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. # # External declarations - used to suppress -zdefs errors. # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # $mapfile_version 2 SYMBOL_SCOPE { add_options { TYPE = FUNCTION; FLAGS = extern }; # From pppd idle_time_hook { TYPE = DATA; FLAGS = extern }; # " " idle_time_limit { TYPE = DATA; FLAGS = extern }; # " " info { TYPE = FUNCTION; FLAGS = extern }; # " " }; # # Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. # # External declarations - used to suppress -zdefs errors. # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # $mapfile_version 2 SYMBOL_SCOPE { add_options { TYPE = FUNCTION; FLAGS = extern }; # From pppd detached { TYPE = DATA; FLAGS = extern }; # " " error { TYPE = FUNCTION; FLAGS = extern }; # " " pap_passwd_hook { TYPE = DATA; FLAGS = extern }; # " " remote_name { TYPE = DATA; FLAGS = extern }; # " " slprintf { TYPE = FUNCTION; FLAGS = extern }; # " " sys_close { TYPE = FUNCTION; FLAGS = extern }; # " " warn { TYPE = FUNCTION; FLAGS = extern }; # " " }; # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. # # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # # External declarations - used to suppress -zdefs errors. $mapfile_version 2 SYMBOL_SCOPE { script_setenv { TYPE = FUNCTION; FLAGS = extern }; # From pppd script_getenv { TYPE = FUNCTION; FLAGS = extern }; # " " slprintf { TYPE = FUNCTION; FLAGS = extern }; # " " option_error { TYPE = FUNCTION; FLAGS = extern }; # " " warn { TYPE = FUNCTION; FLAGS = extern }; # " " fatal { TYPE = FUNCTION; FLAGS = extern }; # " " dbglog { TYPE = FUNCTION; FLAGS = extern }; # " " absmax_mtu { TYPE = DATA; FLAGS = extern }; # " " absmax_mru { TYPE = DATA; FLAGS = extern }; # " " updown_script_hook { TYPE = DATA; FLAGS = extern }; # " " devnam_info { TYPE = DATA; FLAGS = extern }; # " " check_options_hook { TYPE = DATA; FLAGS = extern }; # " " device_pipe_hook { TYPE = DATA; FLAGS = extern }; # " " already_ppp { TYPE = DATA; FLAGS = extern }; # " " devnam { TYPE = DATA; FLAGS = extern }; # " " sys_read_packet_hook { TYPE = DATA; FLAGS = extern }; # " " ifname { TYPE = DATA; FLAGS = extern }; # " " fclose { TYPE = FUNCTION; FLAGS = extern }; # " " ioctl { TYPE = FUNCTION; FLAGS = extern }; # " " fdopen { TYPE = FUNCTION; FLAGS = extern }; # " " close { TYPE = FUNCTION; FLAGS = extern }; # " " open { TYPE = FUNCTION; FLAGS = extern }; # " " strcmp { TYPE = FUNCTION; FLAGS = extern }; # " " fgets { TYPE = FUNCTION; FLAGS = extern }; # " " strlen { TYPE = FUNCTION; FLAGS = extern }; # " " seteuid { TYPE = FUNCTION; FLAGS = extern }; # " " ether_ntoa { TYPE = FUNCTION; FLAGS = extern }; # " " ___errno { TYPE = FUNCTION; FLAGS = extern }; # " " strdup { TYPE = FUNCTION; FLAGS = extern }; # " " run_program { TYPE = FUNCTION; FLAGS = extern }; # " " malloc { TYPE = FUNCTION; FLAGS = extern }; # " " strrchr { TYPE = FUNCTION; FLAGS = extern }; # " " strchr { TYPE = FUNCTION; FLAGS = extern }; # " " error { TYPE = FUNCTION; FLAGS = extern }; # " " free { TYPE = FUNCTION; FLAGS = extern }; # " " strcpy { TYPE = FUNCTION; FLAGS = extern }; # " " memcpy { TYPE = FUNCTION; FLAGS = extern }; # " " script_unsetenv { TYPE = FUNCTION; FLAGS = extern }; # " " ntohs { TYPE = FUNCTION; FLAGS = extern }; # " " }; /* * minconn.c - pppd plugin to implement a `minconnect' option. * * Copyright 1999 Paul Mackerras. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #include #include #include "pppd.h" static int minconnect = 0; static option_t my_options[] = { { "minconnect", o_int, &minconnect, "Set minimum connect time before idle timeout applies" }, { NULL } }; static int my_get_idle(struct ppp_idle *idle) { time_t t; if (idle == NULL) return minconnect? minconnect: idle_time_limit; t = idle->xmit_idle; if (idle->recv_idle < t) t = idle->recv_idle; return idle_time_limit - t; } void plugin_init(void) { info("plugin_init"); add_options(my_options); idle_time_hook = my_get_idle; } /* * passprompt.c - pppd plugin to invoke an external PAP password prompter * * Copyright 1999 Paul Mackerras, Alan Curry. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ /* * Copyright (c) 2018, Joyent, Inc. */ #include #include #include #include #include #include "pppd.h" static char promptprog[PATH_MAX+1]; static option_t options[] = { { "promptprog", o_string, promptprog, "External PAP password prompting program", OPT_STATIC, NULL, PATH_MAX }, { NULL } }; static int promptpass(char *user, char *passwd) { int p[2]; pid_t kid; int readgood, wstat; int red; if (promptprog[0] == 0 || access(promptprog, X_OK) < 0) return -1; /* sorry, can't help */ /* This occurs when we're probed for the ability to supply a password */ if (user != NULL && passwd == NULL) return 1; if (pipe(p)) { warn("Can't make a pipe for %s", promptprog); return 0; } if ((kid = fork()) == (pid_t) -1) { warn("Can't fork to run %s", promptprog); (void) close(p[0]); (void) close(p[1]); return 0; } if (kid == (pid_t)0) { /* we are the child, exec the program */ char *argv[5], fdstr[32]; sys_close(); closelog(); if (detached && p[1] <= 2) { (void) dup2(p[1], 3); p[1] = 3; } (void) close(p[0]); if (detached) { red = open("/etc/ppp/prompt-errors", O_WRONLY | O_APPEND | O_CREAT, 0600); (void) dup2(red, 1); (void) dup2(red, 2); } (void) seteuid(getuid()); (void) setegid(getgid()); argv[0] = promptprog; argv[1] = user == NULL ? "" : user; argv[2] = remote_name; (void) slprintf(fdstr, sizeof (fdstr), "%d", p[1]); argv[3] = fdstr; argv[4] = NULL; (void) execv(*argv, argv); _exit(127); } /* we are the parent, read the password from the pipe */ (void) close(p[1]); readgood = 0; do { red = read(p[0], passwd + readgood, MAXSECRETLEN-1 - readgood); if (red == 0) break; if (red < 0) { if (errno == EINTR) continue; error("Can't read secret from %s: %m", promptprog); readgood = -1; break; } readgood += red; } while (readgood < MAXSECRETLEN - 1); passwd[readgood] = 0; (void) close(p[0]); /* now wait for child to exit */ while (waitpid(kid, &wstat, 0) < 0) { if (errno != EINTR) { warn("error waiting for %s: %m", promptprog); break; } } if (readgood < 0) return 0; if (readgood > 0 && passwd[--readgood] == '\n') passwd[readgood] = '\0'; if (!WIFEXITED(wstat)) warn("%s terminated abnormally", promptprog); if (WEXITSTATUS(wstat) != 0) warn("%s exited with code %d", promptprog, WEXITSTATUS(wstat)); return 1; } void plugin_init(void) { add_options(options); pap_passwd_hook = promptpass; } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * pppoe.c - pppd plugin to handle PPPoE operation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "pppd.h" #include "pathnames.h" /* Saved hook pointers */ static int (*old_check_options)(uid_t uid); static int (*old_updown_script)(const char ***argsp); static int (*old_sys_read_packet)(int retv, struct strbuf *ctrl, struct strbuf *data, int flags); /* Room for 3 IPv4 addresses and metric */ #define RTE_MSG_LEN (3*16 + 10 + 1) /* Environment string for routes */ #define RTE_STR "ROUTE_%d" /* * strioctl() * * wrapper for STREAMS I_STR ioctl. */ static int strioctl(int fd, int cmd, void *ptr, int ilen, int olen) { struct strioctl str; str.ic_cmd = cmd; str.ic_timout = 0; /* Use default timer; 15 seconds */ str.ic_len = ilen; str.ic_dp = ptr; if (ioctl(fd, I_STR, &str) == -1) { return (-1); } if (str.ic_len != olen) { return (-1); } return (0); } /* * If the user named the tunneling device, check that it is * reasonable; otherwise check that standard input is the tunnel. */ static int pppoe_check_options(uid_t uid) { int tstfd; /* fd for device being checked */ int err; /* saved errno value */ int retv; /* return value */ int intv; /* integer return value (from ioctl) */ union ppptun_name ptn; if (devnam[0] != '\0') { /* * Open as real user so that modes on device can be * used to limit access. */ if (!devnam_info.priv) (void) seteuid(uid); tstfd = open(devnam, O_NONBLOCK | O_RDWR, 0); err = errno; if (!devnam_info.priv) (void) seteuid(0); if (tstfd == -1) { errno = err; option_error("unable to open %s: %m", devnam); return (-1); } retv = strioctl(tstfd, PPPTUN_GDATA, &ptn, 0, sizeof (ptn)); (void) close(tstfd); if (retv == -1) { option_error("device %s is not a PPP tunneling device", devnam); return (-1); } } else { retv = strioctl(0, PPPIO_GTYPE, &intv, 0, sizeof (intv)); if (retv == -1) { option_error("standard input is not a PPP device"); return (-1); } retv = strioctl(0, PPPTUN_GDATA, &ptn, 0, sizeof (ptn)); if (retv == -1) { option_error("standard input is not a PPP tunnel"); return (-1); } if (strcmp(ptn.ptn_name + strlen(ptn.ptn_name) - 6, ":pppoe") != 0) { option_error("standard input not connected to PPPoE"); return (-1); } } if (old_check_options != NULL && old_check_options != pppoe_check_options) return ((*old_check_options)(uid)); return (0); } /* * When we're about to call one of the up or down scripts, change the * second argument to contain the interface name and selected PPPoE * service. */ static int pppoe_updown_script(const char ***argsp) { const char *cp; if ((*argsp)[2] == devnam && (cp = script_getenv("IF_AND_SERVICE")) != NULL) (*argsp)[2] = cp; if (old_updown_script != NULL && old_updown_script != pppoe_updown_script) return ((*old_updown_script)(argsp)); return (0); } /* * Concatenate and save strings from command line into environment * variable. */ static void cat_save_env(char **argv, char idchar, const char *envname) { char **argp; int totlen; char *str; char *cp; totlen = 0; for (argp = argv; argp[0] != NULL; argp += 2) if (*argp[0] == idchar) totlen += strlen(argp[1]) + 1; if ((str = malloc(totlen + 1)) == NULL) { error("cannot malloc PPPoE environment for %s", envname); return; } cp = str; for (argp = argv; argp[0] != NULL; argp += 2) if (*argp[0] == idchar) { (void) strcpy(cp, argp[1]); cp += strlen(cp); *cp++ = '\n'; } *cp = '\0'; script_setenv(envname, str, 0); } /* * Convert Message Of The Moment (MOTM) and Host Uniform Resource * Locator (HURL) strings into environment variables and command-line * arguments for script. */ static void handle_motm_hurl(char **argv, int argc, const uint8_t *tagp, int pktlen) { int ttype; int tlen; char *str; char **oargv = argv; /* Must have room for two strings and NULL terminator. */ while (argc >= 3) { str = NULL; while (pktlen >= POET_HDRLEN) { ttype = POET_GET_TYPE(tagp); if (ttype == POETT_END) break; tlen = POET_GET_LENG(tagp); if (tlen > pktlen - POET_HDRLEN) break; if (ttype == POETT_HURL || ttype == POETT_MOTM) { if ((str = malloc(tlen + 1)) == NULL) { error("cannot malloc PPPoE message"); break; } (void) memcpy(str, POET_DATA(tagp), tlen); str[tlen] = '\0'; } pktlen -= POET_HDRLEN + tlen; tagp += POET_HDRLEN + tlen; if (str != NULL) break; } if (str == NULL) break; *argv++ = ttype == POETT_HURL ? "hurl" : "motm"; *argv++ = str; argc -= 2; } *argv = NULL; cat_save_env(oargv, 'h', "HURL"); cat_save_env(oargv, 'm', "MOTM"); } /* * Convert IP Route Add structures into environment variables and * command-line arguments for script. */ static void handle_ip_route_add(char **argv, int argc, const uint8_t *tagp, int pktlen) { int ttype; int tlen; char *str; poer_t poer; int idx; char envname[sizeof (RTE_STR) + 10]; idx = 0; /* Must have room for four strings and NULL terminator. */ while (argc >= 5) { str = NULL; while (pktlen >= POET_HDRLEN) { ttype = POET_GET_TYPE(tagp); if (ttype == POETT_END) break; tlen = POET_GET_LENG(tagp); if (tlen > pktlen - POET_HDRLEN) break; if (ttype == POETT_RTEADD && tlen >= sizeof (poer) && (str = malloc(RTE_MSG_LEN)) == NULL) { error("cannot malloc PPPoE route"); break; } pktlen -= POET_HDRLEN + tlen; tagp += POET_HDRLEN + tlen; if (str != NULL) break; } if (str == NULL) break; /* No alignment restrictions on source; copy to local. */ (void) memcpy(&poer, POET_DATA(tagp), sizeof (poer)); (void) slprintf(str, RTE_MSG_LEN, "%I %I %I %d", poer.poer_dest_network, poer.poer_subnet_mask, poer.poer_gateway, (int)poer.poer_metric); /* Save off the environment variable version of this. */ (void) slprintf(envname, sizeof (envname), RTE_STR, ++idx); script_setenv(envname, str, 0); *argv++ = str; /* Destination */ str = strchr(str, ' '); *str++ = '\0'; *argv++ = str; /* Subnet mask */ str = strchr(str, ' '); *str++ = '\0'; *argv++ = str; /* Gateway */ str = strchr(str, ' '); *str++ = '\0'; *argv++ = str; /* Metric */ argc -= 4; } *argv = NULL; } /* * If we get here, then the driver has already validated the sender, * the PPPoE version, the message length, and session ID. The code * number is known not to be zero. */ static int handle_pppoe_input(const ppptun_atype *pma, struct strbuf *ctrl, struct strbuf *data) { const poep_t *poep; struct ppp_ls *plp; const char *mname; const char *cstr; char *str; char *cp; char *argv[64]; pid_t rpid; char **argp; int idx; char envname[sizeof (RTE_STR) + 10]; const uint8_t *tagp; int pktlen; /* * Warning: the data->buf pointer here is not necessarily properly * aligned for access to the poep_session_id or poep_length members. */ /* LINTED: alignment */ poep = (const poep_t *)data->buf; tagp = (const uint8_t *)poep + offsetof(poep_t, poep_length); pktlen = (tagp[0] << 8) + tagp[1]; tagp = (const uint8_t *)(poep + 1); switch (poep->poep_code) { case POECODE_PADT: dbglog("received PPPoE PADT; connection has been closed"); /* LINTED: alignment */ plp = (struct ppp_ls *)ctrl->buf; plp->magic = PPPLSMAGIC; plp->ppp_message = PPP_LINKSTAT_HANGUP; ctrl->len = sizeof (*plp); return (0); /* Active Discovery Message and Network extensions */ case POECODE_PADM: case POECODE_PADN: if (poep->poep_code == POECODE_PADM) { argv[0] = _ROOT_PATH "/etc/ppp/pppoe-msg"; mname = "PADM"; handle_motm_hurl(argv + 4, Dim(argv) - 4, tagp, pktlen); } else { argv[0] = _ROOT_PATH "/etc/ppp/pppoe-network"; mname = "PADN"; handle_ip_route_add(argv + 4, Dim(argv) - 4, tagp, pktlen); } argv[1] = ifname; /* Note: strdup doesn't handle NULL input. */ str = NULL; if ((cstr = script_getenv("IF_AND_SERVICE")) == NULL || (str = strdup(cstr)) == NULL) { argv[2] = argv[3] = ""; } else { if ((cp = strrchr(str, ':')) == NULL) cp = str + strlen(str); else *cp++ = '\0'; argv[2] = str; argv[3] = cp; } rpid = run_program(argv[0], argv, 0, NULL, NULL); if (rpid == (pid_t)0) dbglog("ignored PPPoE %s; no %s script", mname, argv[0]); else if (rpid != (pid_t)-1) dbglog("PPPoE %s: started PID %d", mname, rpid); if (str != NULL) free(str); /* Free storage allocated by handle_{motm_hurl,ip_route_add} */ idx = 0; for (argp = argv + 4; *argp != NULL; ) { if (poep->poep_code == POECODE_PADM) { free(argp[1]); argp += 2; } else { free(argp[0]); argp += 4; (void) slprintf(envname, sizeof (envname), RTE_STR, ++idx); script_unsetenv(envname); } } if (poep->poep_code == POECODE_PADM) { script_unsetenv("HURL"); script_unsetenv("MOTM"); } break; default: warn("unexpected PPPoE code %d from %s", poep->poep_code, ether_ntoa(&pma->pta_pppoe.ptma_mac_ether_addr)); break; } return (-1); } /* * Handle an action code passed up from the driver. */ static int handle_action(struct ppptun_control *ptc, struct strbuf *ctrl, struct strbuf *data) { switch (ptc->ptc_action) { case PTCA_CONTROL: return (handle_pppoe_input(&ptc->ptc_address, ctrl, data)); case PTCA_BADCTRL: warn("bad control message; session %u on %s", ptc->ptc_rsessid, ptc->ptc_name); return (0); } return (-1); } /* * sys-solaris has just read in a packet; grovel through it and see if * it's something we need to handle ourselves. */ static int pppoe_sys_read_packet(int retv, struct strbuf *ctrl, struct strbuf *data, int flags) { struct ppptun_control *ptc; if (retv >= 0 && !(retv & MORECTL) && ctrl->len >= sizeof (uint32_t)) { /* LINTED: alignment */ ptc = (struct ppptun_control *)ctrl->buf; /* ptc_discrim is the first uint32_t of the structure. */ if (ptc->ptc_discrim == PPPOE_DISCRIM) { retv = -1; if (ctrl->len == sizeof (*ptc)) retv = handle_action(ptc, ctrl, data); if (retv < 0) errno = EAGAIN; return (retv); } } /* Forward along to other plug-ins */ if (old_sys_read_packet != NULL && old_sys_read_packet != pppoe_sys_read_packet) return ((*old_sys_read_packet)(retv, ctrl, data, flags)); return (retv); } /* * Get an environment variable from the chat script. */ static int saveenv(FILE *fd, const char *envname) { char envstr[1024]; int len; if (fgets(envstr, sizeof (envstr), fd) == NULL) return (-1); len = strlen(envstr); if (len <= 1) return (0); envstr[len-1] = '\0'; script_setenv(envname, envstr, 0); return (1); } /* * Read environment variables exported by chat script. */ static void pppoe_device_pipe(int pipefd) { FILE *fd; int i; char envname[32]; fd = fdopen(pipefd, "r"); if (fd == NULL) fatal("unable to open environment file: %m"); (void) saveenv(fd, "IF_AND_SERVICE"); (void) saveenv(fd, "SERVICE_NAME"); (void) saveenv(fd, "AC_NAME"); (void) saveenv(fd, "AC_MAC"); (void) saveenv(fd, "SESSION_ID"); for (i = 1; ; i++) { (void) slprintf(envname, sizeof (envname), "VENDOR_SPECIFIC_%d", i); if (saveenv(fd, envname) <= 0) break; } (void) fclose(fd); } void plugin_init(void) { if (absmax_mtu > 1492) absmax_mtu = 1492; if (absmax_mru > 1492) absmax_mru = 1492; old_check_options = check_options_hook; check_options_hook = pppoe_check_options; old_updown_script = updown_script_hook; updown_script_hook = pppoe_updown_script; old_sys_read_packet = sys_read_packet_hook; sys_read_packet_hook = pppoe_sys_read_packet; device_pipe_hook = pppoe_device_pipe; already_ppp = 1; } /* * pppd.h - PPP daemon global declarations. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. * * SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES * * 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. * * $Id: pppd.h,v 1.54 2000/04/15 10:10:25 paulus Exp $ */ #ifndef __PPPD_H__ #define __PPPD_H__ #include /* for FILE */ #include /* for NGROUPS_MAX */ #include /* for MAXPATHLEN and BSD4_4, if defined */ #include /* for u_int32_t, if defined */ #include /* for struct timeval */ #include #if defined(__STDC__) #include #define __V(x) x #else #include #define __V(x) (va_alist) va_dcl #define const #define volatile #endif /* __STDC__ */ #ifdef INET6 #include "eui64.h" #endif /* INET6 */ #ifdef HAVE_MULTILINK #include "tdb.h" #endif #ifdef __cplusplus extern "C" { #endif /* * Limits. */ #define NUM_PPP 1 /* One PPP interface supported (per process) */ #define MAXWORDLEN 1024 /* max length of word in file (incl null) */ #define MAXARGS 1 /* max # args to a command */ #define MAXNAMELEN 256 /* max length of name for auth */ #define MAXSECRETLEN 256 /* max length of password or secret */ #ifndef MAXHOSTNAMELEN #define MAXHOSTNAMELEN MAXNAMELEN /* max length of hostname */ #endif /* MAXHOSTNAMELEN */ /* * If this evaluates non-zero, then sifup() must be called before * sifaddr(). */ #if (defined(SVR4) && (defined(SNI) || defined(__USLC__))) #define SIFUPFIRST (1) #else #define SIFUPFIRST (0) #endif /* * If this evaluates non-zero, then sif6up() must be called before * sif6addr(). */ #if (defined(__linux__) || \ (defined(SVR4) && (defined(SNI) || defined(__USLC__)))) #define SIF6UPFIRST (1) #else #define SIF6UPFIRST (0) #endif /* * Option descriptor structure. */ typedef unsigned char bool; enum opt_type { o_special_noarg = 0, o_special = 1, o_bool, o_int, o_uint32, o_string }; typedef struct { char *name; /* name of the option */ enum opt_type type; void *addr; char *description; int flags; void *addr2; int upper_limit; int lower_limit; } option_t; /* * Values for flags. */ #define OPT_VALUE 0xff /* mask for presupplied value */ #define OPT_HEX 0x100 /* int option is in hex */ #define OPT_NOARG 0x200 /* option doesn't take argument */ #define OPT_OR 0x400 /* OR in argument to value */ #define OPT_INC 0x800 /* increment value */ #define OPT_PRIV 0x1000 /* privileged option */ #define OPT_STATIC 0x2000 /* string option goes into static array */ #define OPT_LLIMIT 0x4000 /* check value against lower limit */ #define OPT_ULIMIT 0x8000 /* check value against upper limit */ #define OPT_LIMITS (OPT_LLIMIT|OPT_ULIMIT) #define OPT_ZEROOK 0x10000 /* 0 value is OK even if not within limits */ #define OPT_NOINCR 0x20000 /* value mustn't be increased */ #define OPT_ZEROINF 0x40000 /* with OPT_NOINCR, 0 == infinity */ #define OPT_DISABLE 0x80000 /* ignore option */ #define OPT_A2INFO 0x100000 /* addr2 -> option_info to update */ #define OPT_A2COPY 0x200000 /* addr2 -> second location to rcv value */ #define OPT_ENABLE 0x400000 /* use *addr2 as enable for option */ #define OPT_PRIVFIX 0x800000 /* can't be overridden if noauth */ #define OPT_PREPASS 0x1000000 /* do this opt in pre-pass to find device */ #define OPT_INITONLY 0x2000000 /* option can only be set in init phase */ #define OPT_DEVEQUIV 0x4000000 /* equiv to device name */ #define OPT_DEVNAM (OPT_PREPASS | OPT_INITONLY | OPT_DEVEQUIV) #define OPT_VAL(x) ((x) & OPT_VALUE) #ifndef GIDSET_TYPE #define GIDSET_TYPE gid_t #endif /* GIDSET_TYPE */ /* * Structure representing a list of permitted IP addresses. */ struct permitted_ip { int permit; /* 1 = permit, 0 = forbid */ u_int32_t base; /* match if (addr & mask) == base */ u_int32_t mask; /* base and mask are in network byte order */ }; /* * Unfortunately, the linux kernel driver uses a different structure * for statistics from the rest of the ports. * This structure serves as a common representation for the bits * pppd needs. */ struct pppd_stats { ppp_counter_t bytes_in; ppp_counter_t bytes_out; ppp_counter_t pkts_in; ppp_counter_t pkts_out; }; /* * Used for storing a sequence of words. Usually malloced. */ struct wordlist { struct wordlist *next; char *word; }; /* * Global variables. */ extern bool hungup; /* Physical layer has disconnected */ extern int ifunit; /* Interface unit number */ extern char ifname[32]; /* Interface name */ extern int ttyfd; /* Serial device file descriptor */ extern char hostname[]; /* Our hostname */ extern u_char outpacket_buf[]; /* Buffer for outgoing packets */ extern int phase; /* Current state of link - see values below */ extern int baud_rate; /* Current link speed in bits/sec */ extern char *progname; /* Name of this program */ extern int redirect_stderr;/* Connector's stderr should go to file */ extern char peer_authname[];/* Authenticated name of peer */ extern bool privileged; /* We were run by real-uid root */ extern bool need_holdoff; /* Need holdoff period after link terminates */ extern char **script_env; /* Environment variables for scripts */ extern bool detached; /* Have detached from controlling tty */ extern GIDSET_TYPE groups[NGROUPS_MAX]; /* groups the user is in */ extern int ngroups; /* How many groups valid in groups */ extern struct pppd_stats link_stats; /* byte/packet counts etc. for link */ extern bool link_stats_valid; /* set if link_stats is valid */ extern int link_connect_time; /* time the link was up for */ extern int using_pty; /* using pty as device (notty or pty opt.) */ extern int log_to_fd; /* logging to this fd as well as syslog */ extern bool log_to_file; /* log_to_fd is a file */ extern bool log_to_specific_fd; /* log_to_fd was specified by user */ extern char *no_ppp_msg; /* message to print if ppp not in kernel */ extern volatile int status; /* exit status for pppd */ extern int devnam_fixed; /* can no longer change devnam */ extern int unsuccess; /* # unsuccessful connection attempts */ extern int do_callback; /* set if we want to do callback next */ extern int doing_callback; /* set if this is a callback */ extern u_char nak_buffer[]; /* where we construct a nak packet */ extern u_char inpacket_buf[]; /* buffer for incoming packet */ extern bool direct_tty; /* use standard input directly; not a tty */ extern int absmax_mru; /* absolute maximum link (not i/f) MRU */ extern int absmax_mtu; /* absolute maximum link (not i/f) MTU */ extern int pty_slave; /* slave side of PTY, if any */ extern bool early_log; /* avoid logging to stdout */ /* * Values for do_callback and doing_callback. */ #define CALLBACK_DIALIN 1 /* we are expecting the call back */ #define CALLBACK_DIALOUT 2 /* we are dialling out to call back */ /* * Variables set by command-line options. */ extern int debug; /* Debug flag */ extern int kdebugflag; /* Tell kernel to print debug messages */ extern int default_device; /* Using /dev/tty or equivalent */ extern char devnam[MAXPATHLEN]; /* Device name */ extern char ppp_devnam[MAXPATHLEN]; /* Device name (might be pty) */ extern int crtscts; /* Use hardware flow control */ extern bool modem; /* Use modem control lines */ extern int inspeed; /* Input/Output speed requested */ extern u_int32_t netmask; /* IP netmask to set on interface */ extern bool lockflag; /* Create lock file to lock the serial dev */ extern bool nodetach; /* Don't detach from controlling tty */ extern bool updetach; /* Detach from controlling tty when link up */ extern char *initializer; /* Script to initialize physical link */ extern char *connect_script; /* Script to establish physical link */ extern char *disconnect_script; /* Script to disestablish physical link */ extern char *welcomer; /* Script to welcome client after connection */ extern char *ptycommand; /* Command to run on other side of pty */ extern int maxconnect; /* Maximum connect time (seconds) */ extern char user[MAXNAMELEN];/* Our name for authenticating ourselves */ extern char passwd[MAXSECRETLEN]; /* Password for PAP or CHAP */ extern bool auth_required; /* Peer is required to authenticate */ extern bool persist; /* Reopen link after it goes down */ extern bool uselogin; /* Use /etc/passwd for checking PAP */ extern char our_name[MAXNAMELEN];/* Our name for authentication purposes */ extern char remote_name[MAXNAMELEN]; /* Peer's name for authentication */ extern bool explicit_remote;/* remote_name specified with remotename opt */ extern bool demand; /* Do dial-on-demand */ extern char *ipparam; /* Extra parameter for ip up/down scripts */ extern bool cryptpap; /* Others' PAP passwords are encrypted */ extern int idle_time_limit;/* Shut down link if idle for this long */ extern int holdoff; /* Dead time before restarting */ extern bool holdoff_specified; /* true if user gave a holdoff value */ extern bool notty; /* Stdin/out is not a tty */ extern char *pty_socket; /* Socket to connect to pty */ extern char *record_file; /* File to record chars sent/received */ extern bool sync_serial; /* Device is synchronous serial device */ extern int maxfail; /* Max # of unsuccessful connection attempts */ extern char linkname[MAXPATHLEN]; /* logical name for link */ extern bool tune_kernel; /* May alter kernel settings as necessary */ extern int connect_delay; /* Time to delay after connect script */ extern int max_data_rate; /* max bytes/sec through charshunt */ extern int req_unit; /* interface unit number to use */ extern bool multilink; /* enable multilink operation */ extern bool noendpoint; /* don't send or accept endpt. discrim. */ extern char *bundle_name; /* bundle name for multilink */ #ifdef HAVE_MULTILINK extern TDB_CONTEXT *pppdb; /* handle to multilink database context */ extern char db_key[]; /* multilink database key */ #endif #ifdef PPP_FILTER extern struct bpf_program pass_filter; /* Filter for pkts to pass */ extern struct bpf_program active_filter; /* Filter for link-active pkts */ #endif /* PPP_FILTER */ #ifdef MSLANMAN extern bool ms_lanman; /* Use LanMan password instead of NT */ /* Has meaning only with MS-CHAP challenges */ #endif /* MXLANMAN */ extern char *current_option; /* the name of the option being parsed */ extern bool privileged_option; /* set iff the current option came from root */ extern char *option_source; /* string saying where the option came from */ extern int option_line; /* and from which line in the file */ extern bool already_ppp; /* device is already in PPP mode */ extern bool prepass; /* Doing pre-pass to find device name */ extern struct stat devstat; /* Result of stat() on device */ extern bool peer_nak_auth; /* Peer sent nak for our auth request */ extern u_short nak_auth_orig; /* Auth proto peer naked */ extern u_short nak_auth_proto; /* Auth proto peer suggested instead */ extern bool unsolicited_nak_auth; /* Peer asked us to authenticate */ extern u_short unsolicit_auth_proto; /* Auth proto peer wants */ extern bool peer_reject_auth; /* Peer sent reject for auth */ extern u_short reject_auth_proto; /* Protocol that peer rejected */ extern bool rejected_peers_auth; /* We sent a reject to the peer */ extern u_short rejected_auth_proto; /* Protocol that peer wanted to use */ extern bool naked_peers_auth; /* We sent a nak to the peer */ extern u_short naked_auth_orig; /* Protocol that we wanted to use */ extern u_short naked_auth_proto; /* Protocol that peer wants us to use */ /* * Values for phase. */ #define PHASE_DEAD 0 /* RFC 1661; link terminated */ #define PHASE_INITIALIZE 1 /* execution begins */ #define PHASE_INITIALIZED 2 /* options ok; entering main loop */ #define PHASE_SERIALCONN 3 /* connecting to peer */ #define PHASE_CONNECTED 4 /* connecting to peer */ #define PHASE_DORMANT 5 /* waiting for demand-dial trigger */ #define PHASE_ESTABLISH 6 /* RFC 1661; LCP negotiation begins */ #define PHASE_AUTHENTICATE 7 /* RFC 1661; authentication begins */ #define PHASE_CALLBACK 8 /* negotiating for callback */ #define PHASE_NETWORK 9 /* RFC 1661; NCP negotiation begins */ #define PHASE_RUNNING 10 /* first NCP went to Opened state */ #define PHASE_TERMINATE 11 /* RFC 1661; LCP left Opened state */ #define PHASE_DISCONNECT 12 /* running disconnect script */ #define PHASE_HOLDOFF 13 /* waiting before restart */ #define PHASE_CALLINGBACK 14 /* calling back */ #define PHASE_EXIT 15 /* execution ends */ #define PHASE__NAMES \ "Dead", "Initialize", "Initialized", "Serialconn", "Connected", \ "Dormant", "Establish", "Authenticate", "Callback", "Network", \ "Running", "Terminate", "Disconnect", "Holdoff", "Callingback", \ "Exit" /* * The following struct gives the addresses of procedures to call * for a particular protocol. */ struct protent { u_short protocol; /* PPP protocol number */ /* Initialization procedure */ void (*init) __P((int unit)); /* Process a received packet */ void (*input) __P((int unit, u_char *pkt, int len)); /* Process a received protocol-reject */ void (*protrej) __P((int unit)); /* Lower layer has come up */ void (*lowerup) __P((int unit)); /* Lower layer has gone down */ void (*lowerdown) __P((int unit)); /* Open the protocol */ void (*open) __P((int unit)); /* Close the protocol */ void (*close) __P((int unit, char *reason)); /* Print a packet in readable form */ int (*printpkt) __P((u_char *pkt, int len, void (*printer) __P((void *, const char *, ...)), void *arg)); /* Process a received data packet */ void (*datainput) __P((int unit, u_char *pkt, int len)); bool enabled_flag; /* 0 iff protocol is disabled */ char *name; /* Text name of protocol */ char *data_name; /* Text name of corresponding data protocol */ option_t *options; /* List of command-line options */ /* Check requested options, assign defaults */ void (*check_options) __P((void)); /* Configure interface for demand-dial */ int (*demand_conf) __P((int unit)); /* Say whether to bring up link for this pkt */ int (*active_pkt) __P((u_char *pkt, int len)); /* Print current status to file or syslog (if strptr == NULL) */ void (*print_stat) __P((int unit, FILE *strptr)); }; /* * This structure is used to store information about certain * options, such as where the option value came from (/etc/ppp/options, * command line, etc.) and whether it came from a privileged source. */ struct option_info { bool priv; /* was value set by sysadmin? */ char *source; /* where option came from */ int line; /* line number where the option came from */ }; extern struct option_info devnam_info; extern struct option_info initializer_info; extern struct option_info connect_script_info; extern struct option_info disconnect_script_info; extern struct option_info welcomer_info; extern struct option_info ptycommand_info; extern struct option_info ipsrc_info; extern struct option_info ipdst_info; extern struct option_info speed_info; /* * Table of pointers to supported protocols. */ extern struct protent *protocols[]; /* * Prototypes. */ /* * Procedures exported from main.c. */ extern void set_ifunit __P((int)); /* set stuff that depends on ifunit */ extern void detach __P((void)); /* Detach from controlling tty */ extern void die __P((int)); /* Cleanup and exit */ extern void quit __P((void)); /* like die(1) */ extern void novm __P((char *)); /* Say we ran out of memory, and die */ extern void timeout __P((void (*func)(void *), void *arg, int t)); /* Call func(arg) after t seconds */ extern void untimeout __P((void (*func)(void *), void *arg)); /* Cancel call to func(arg) */ extern pid_t run_program __P((char *prog, char **args, int must_exist, void (*done)(void *, int), void *arg)); /* Run program prog with args in child */ extern void reopen_log __P((void)); /* (re)open the connection to syslog */ extern void update_link_stats __P((int)); /* Get stats at link termination */ /* set script env var */ extern void script_setenv __P((const char *, const char *, int)); extern void script_unsetenv __P((const char *)); /* unset script env var */ extern const char *script_getenv __P((const char *var)); extern void new_phase __P((int)); /* signal start of new phase */ extern void print_ncpstate __P((int, FILE *)); /* prints NCP state */ extern const char *protocol_name __P((int proto)); /* canonical name */ extern const char *phase_name __P((int phaseval)); /* * Procedures exported from utils.c. */ extern void log_packet __P((u_char *, int, const char *, int)); /* Format a packet and log it with syslog */ extern void print_string __P((char *, int, void (*)(void *, const char *, ...), void *)); /* Format a string for output */ extern int slprintf __P((char *, int, const char *, ...)); /* sprintf++ */ extern int vslprintf __P((char *, int, const char *, va_list));/* vsprintf++ */ extern size_t strlcpy __P((char *, const char *, size_t)); /* safe strcpy */ extern size_t strlcat __P((char *, const char *, size_t)); /* safe strncpy */ extern void dbglog __P((const char *, ...));/* log a debug message */ extern void info __P((const char *, ...)); /* log an informational message */ extern void notice __P((const char *, ...));/* log a notice-level message */ extern void warn __P((const char *, ...)); /* log a warning message */ extern void error __P((const char *, ...)); /* log an error message */ extern void fatal __P((const char *, ...)); /* log an error message and die(1) */ extern const char *code_name __P((int code, int shortflag)); /* Code to string */ extern int flprintf __P((FILE *, const char *, ...)); /* fprintf++ */ extern size_t strllen __P((const char *, size_t)); /* safe strlen */ extern const char *signal_name __P((int signum)); /* * Procedures exported from auth.c */ extern void link_required __P((int)); /* we are starting to use the link */ extern void link_terminated __P((int)); /* we are finished with the link */ extern void link_down __P((int)); /* the LCP layer has left the Opened state */ extern void link_established __P((int)); /* the link is up; authenticate now */ extern void start_networks __P((void)); /* start all the network control protos */ extern void np_up __P((int, int)); /* a network protocol has come up */ extern void np_down __P((int, int)); /* a network protocol has gone down */ extern void np_finished __P((int, int)); /* a network protocol no longer needs link */ extern void auth_peer_fail __P((int, int)); /* peer failed to authenticate itself */ extern void auth_peer_success __P((int, int, char *, int)); /* peer successfully authenticated itself */ extern void auth_withpeer_fail __P((int, int)); /* we failed to authenticate ourselves */ extern void auth_withpeer_success __P((int, int)); /* we successfully authenticated ourselves */ extern void auth_check_options __P((void)); /* check authentication options supplied */ extern void auth_reset __P((int)); /* check what secrets we have */ extern int check_passwd __P((int, char *, int, char *, int, char **)); /* Check peer-supplied username/password */ extern int get_secret __P((int, char *, char *, char *, int *, int)); /* get "secret" for chap */ extern int auth_ip_addr __P((int, u_int32_t)); /* check if IP address is authorized */ extern int bad_ip_adrs __P((u_int32_t)); /* check if IP address is unreasonable */ /* * Procedures exported from demand.c */ extern void demand_conf __P((void)); /* config interface(s) for demand-dial */ extern void demand_block __P((void)); /* set all NPs to queue up packets */ extern void demand_unblock __P((void)); /* set all NPs to pass packets */ extern void demand_discard __P((void)); /* set all NPs to discard packets */ extern void demand_rexmit __P((int)); /* retransmit saved frames for an NP */ extern int loop_chars __P((unsigned char *, int)); /* process chars from loopback */ extern int loop_frame __P((unsigned char *, int)); /* should we bring link up? */ /* * Procedures exported from multilink.c */ extern void mp_check_options __P((void)); /* Check multilink-related options */ extern int mp_join_bundle __P((void)); /* join our link to an appropriate bundle */ /* * Procedures exported from sys-*.c */ extern void sys_init __P((bool)); /* Do system-dependent initialization */ extern void sys_cleanup __P((void)); /* Restore system state before exiting */ extern int sys_check_options __P((void)); /* Check options specified */ extern void sys_options __P((void)); /* add or remove system options */ extern void sys_close __P((void)); /* Clean up in a child before execing */ extern int ppp_available __P((void)); /* Test whether ppp kernel support exists */ extern int get_pty __P((int *, int *, char *, int)); /* Get pty master/slave */ extern int open_ppp_loopback __P((void)); /* Open loopback for demand-dialling */ extern int establish_ppp __P((int)); /* Turn serial port into a ppp interface */ extern void restore_loop __P((void)); /* Transfer ppp unit back to loopback */ extern void disestablish_ppp __P((int)); /* Restore port to normal operation */ extern void make_new_bundle __P((int, int, int, int)); /* Create new bundle */ extern int bundle_attach __P((int)); /* Attach link to existing bundle */ extern void cfg_bundle __P((int, int, int, int)); /* Configure existing bundle */ extern void clean_check __P((void)); /* Check if line was 8-bit clean */ extern void set_up_tty __P((int, int)); /* Set up port's speed, parameters, etc. */ extern void restore_tty __P((int)); /* Restore port's original parameters */ extern void setdtr __P((int, int)); /* Raise or lower port's DTR line */ extern void output __P((int, u_char *, int)); /* Output a PPP packet */ extern void wait_input __P((struct timeval *)); /* Wait for input, with timeout */ extern void add_fd __P((int)); /* Add fd to set to wait for */ extern void remove_fd __P((int)); /* Remove fd from set to wait for */ extern int read_packet __P((u_char *)); /* Read PPP packet */ extern int get_loop_output __P((void)); /* Read pkts from loopback */ extern void ppp_send_config __P((int, int, u_int32_t, int, int)); /* Configure i/f transmit parameters */ extern void ppp_set_xaccm __P((int, ext_accm)); /* Set extended transmit ACCM */ extern void ppp_recv_config __P((int, int, u_int32_t, int, int)); /* Configure i/f receive parameters */ #ifdef NEGOTIATE_FCS extern void ppp_send_fcs __P((int unit, int fcstype)); extern void ppp_recv_fcs __P((int unit, int fcstype)); #endif /* NEGOTIATE_FCS */ #ifdef MUX_FRAME extern void ppp_send_muxoption __P((int ,u_int32_t)); extern void ppp_recv_muxoption __P((int ,u_int32_t)); #endif /* MUX_FRAME */ extern int ccp_test __P((int, u_char *, int, int)); /* Test support for compression scheme */ #ifdef COMP_TUNE extern void ccp_tune __P((int, int)); /* Tune compression effort level */ #endif /* COMP_TUNE */ extern void ccp_flags_set __P((int, int, int)); /* Set kernel CCP state */ extern int ccp_fatal_error __P((int)); /* Test for fatal decomp error in kernel */ extern int get_idle_time __P((int, struct ppp_idle *)); /* Find out how long link has been idle */ extern int get_ppp_stats __P((int, struct pppd_stats *)); /* Return link statistics */ extern int sifvjcomp __P((int, int, int, int)); /* Configure VJ TCP header compression */ extern int sifup __P((int)); /* Configure i/f up for one protocol */ extern int sifnpmode __P((int u, int proto, enum NPmode mode)); /* Set mode for handling packets for proto */ extern int sifdown __P((int)); /* Configure i/f down for one protocol */ extern int sifaddr __P((int, u_int32_t, u_int32_t, u_int32_t)); /* Configure IPv4 addresses for i/f */ extern int cifaddr __P((int, u_int32_t, u_int32_t)); /* Reset i/f IP addresses */ extern void sys_block_proto __P((uint16_t)); extern void sys_unblock_proto __P((uint16_t)); #ifdef INET6 extern int sif6addr __P((int, eui64_t, eui64_t)); /* Configure IPv6 addresses for i/f */ extern int cif6addr __P((int, eui64_t, eui64_t)); /* Remove an IPv6 address from i/f */ #endif /* INET6 */ extern int sifdefaultroute __P((int, u_int32_t, u_int32_t)); /* Create default route through i/f */ extern int cifdefaultroute __P((int, u_int32_t, u_int32_t)); /* Delete default route through i/f */ extern int sifproxyarp __P((int unit, u_int32_t addr, int flag)); /* Add proxy ARP entry for peer */ extern int cifproxyarp __P((int unit, u_int32_t addr)); /* Delete proxy ARP entry for peer */ extern u_int32_t GetMask __P((u_int32_t)); /* Get appropriate netmask for address */ extern int lock __P((char *)); /* Create lock file for device */ extern int relock __P((int)); /* Rewrite lock file with new pid */ extern void unlock __P((void)); /* Delete previously-created lock file */ extern void logwtmp __P((const char *, const char *, const char *)); /* Write entry to wtmp file */ extern int get_host_seed __P((void)); /* Get host-dependent random number seed */ extern int have_route_to __P((u_int32_t)); /* Check if route to addr exists */ #ifdef PPP_FILTER extern int set_filters __P((struct bpf_program *pass, struct bpf_program *active)); /* Set filter programs in kernel */ #endif /* PPP_FILTER */ #ifdef IPX_CHANGE extern int sipxfaddr __P((int, unsigned long, unsigned char *)); extern int cipxfaddr __P((int)); #endif /* IPX_CHANGE */ extern int get_if_hwaddr __P((u_char *addr, int msize, char *name)); extern int get_first_hwaddr __P((u_char *addr, int msize)); #if defined(INET6) && defined(SOL2) extern int ether_to_eui64 __P((eui64_t *p_eui64)); extern int sif6up __P((int unit)); extern int sif6down __P((int unit)); extern int sif6mtu __P((int mtu)); extern int sif6flags __P((u_int32_t flags, int set)); #endif /* INET6 && SOL2*/ #if defined(INET6) && !defined(SOL2) #define sif6up sifup #endif extern int sifmtu __P((int mtu)); extern int siflags __P((u_int32_t flags, int set)); extern void sys_ifname __P((void)); extern void sys_print_state __P((FILE *strptr)); extern int sys_extra_fd __P((void)); /* * Procedures exported from options.c */ extern int parse_args __P((int argc, char **argv)); /* Parse options from arguments given */ extern int options_from_file __P((char *filename, bool must_exist, bool check_prot, bool privileged)); /* Parse options from an options file */ extern int options_from_user __P((void)); /* Parse options from user's .ppprc */ extern int options_for_tty __P((void)); /* Parse options from /etc/ppp/options.tty */ extern int options_from_list __P((struct wordlist *, bool privileged)); /* Parse options from a wordlist */ extern int getword __P((FILE *f, char *word, int *newlinep, char *filename)); /* Read a word from a file */ extern void option_error __P((char *fmt, ...)); /* Print an error message about an option */ extern int int_option __P((char *, int *)); /* Simplified number_option for decimal ints */ extern void add_options __P((option_t *)); /* Add extra options */ extern int parse_dotted_ip __P((char *, u_int32_t *)); /* Parse dotted IP notation */ extern option_t *remove_option __P((char *)); /* Remove (disable) an option */ extern void save_source __P((struct option_info *)); /* Save the source information (where an option comes from) */ extern void set_source __P((struct option_info *)); /* Set the source (for logging option errors detected after parsing) */ extern const char *name_source __P((struct option_info *)); /* Return a string containing the option source and line number */ /* * Hooks to enable plugins to change various things. */ extern int (*new_phase_hook) __P((int new, int old)); extern int (*idle_time_hook) __P((struct ppp_idle *)); extern int (*holdoff_hook) __P((void)); extern int (*pap_check_hook) __P((void)); extern int (*pap_auth_hook) __P((char *user, char *passwd, char **msgp, struct wordlist **paddrs, struct wordlist **popts)); extern void (*pap_logout_hook) __P((void)); extern int (*pap_passwd_hook) __P((char *user, char *passwd)); extern void (*ip_up_hook) __P((void)); extern void (*ip_down_hook) __P((void)); extern int (*check_options_hook) __P((uid_t uid)); /* extern int (*attach_device_hook) __P((uid_t uid, char *devnam)); */ extern int (*updown_script_hook) __P((const char ***argsp)); struct strbuf; /* forward declaration */ extern int (*sys_read_packet_hook) __P((int retv, struct strbuf *ctrl, struct strbuf *data, int flags)); extern void (*device_pipe_hook) __P((int pipefd)); /* * Inline versions of get/put char/short/long. * Pointer is advanced; we assume that both arguments * are lvalues and will already be in registers. * cp MUST be u_char *. */ #define GETCHAR(c, cp) { \ (c) = *(cp)++; \ } #define PUTCHAR(c, cp) { \ *(cp)++ = (u_char) (c); \ } #define GETSHORT(s, cp) { \ (s) = *(cp)++ << 8; \ (s) |= *(cp)++; \ } #define PUTSHORT(s, cp) { \ *(cp)++ = (u_char) ((s) >> 8); \ *(cp)++ = (u_char) (s); \ } #define GETLONG(l, cp) { \ (l) = *(cp)++ << 8; \ (l) |= *(cp)++; (l) <<= 8; \ (l) |= *(cp)++; (l) <<= 8; \ (l) |= *(cp)++; \ } #define PUTLONG(l, cp) { \ *(cp)++ = (u_char) ((l) >> 24); \ *(cp)++ = (u_char) ((l) >> 16); \ *(cp)++ = (u_char) ((l) >> 8); \ *(cp)++ = (u_char) (l); \ } /* * For values that are kept internally in network byte order. */ #define GETNLONG(l, cp) { \ u_int32_t getnlong_val; \ getnlong_val = *(cp)++ << 8; \ getnlong_val |= *(cp)++; getnlong_val <<= 8; \ getnlong_val |= *(cp)++; getnlong_val <<= 8; \ getnlong_val |= *(cp)++; \ (l) = htonl(getnlong_val); \ } #define PUTNLONG(l, cp) { \ u_int32_t putnlong_val = ntohl(l); \ *(cp)++ = (u_char) (putnlong_val >> 24); \ *(cp)++ = (u_char) (putnlong_val >> 16); \ *(cp)++ = (u_char) (putnlong_val >> 8); \ *(cp)++ = (u_char) putnlong_val; \ } #define INCPTR(n, cp) ((cp) += (n)) #define DECPTR(n, cp) ((cp) -= (n)) /* * System dependent definitions for user-level 4.3BSD UNIX implementation. */ #define TIMEOUT(r, f, t) timeout((r), (f), (t)) #define UNTIMEOUT(r, f) untimeout((r), (f)) #ifndef SOL2 #define BCOPY(s, d, l) memcpy(d, s, l) #define BZERO(s, n) memset(s, 0, n) #else #include #define BCOPY bcopy #define BZERO bzero #endif #define PRINTMSG(m, l) { info("Remote message: %0.*v", l, m); } /* * MAKEHEADER - Add Header fields to a packet. */ #define MAKEHEADER(p, t) { \ PUTCHAR(PPP_ALLSTATIONS, p); \ PUTCHAR(PPP_UI, p); \ PUTSHORT(t, p); } /* * Exit status values. */ #define EXIT_OK 0 #define EXIT_FATAL_ERROR 1 #define EXIT_OPTION_ERROR 2 #define EXIT_NOT_ROOT 3 #define EXIT_NO_KERNEL_SUPPORT 4 #define EXIT_USER_REQUEST 5 #define EXIT_LOCK_FAILED 6 #define EXIT_OPEN_FAILED 7 #define EXIT_CONNECT_FAILED 8 #define EXIT_PTYCMD_FAILED 9 #define EXIT_NEGOTIATION_FAILED 10 #define EXIT_PEER_AUTH_FAILED 11 #define EXIT_IDLE_TIMEOUT 12 #define EXIT_CONNECT_TIME 13 #define EXIT_CALLBACK 14 #define EXIT_PEER_DEAD 15 #define EXIT_HANGUP 16 #define EXIT_LOOPBACK 17 #define EXIT_INIT_FAILED 18 #define EXIT_AUTH_TOPEER_FAILED 19 /* * Character shunt constants. */ #define MAXLEVELMINSIZE 100 /* * Debug macros. Slightly useful for finding bugs in pppd, not particularly * useful for finding out why your connection isn't being established. */ #ifdef DEBUGALL #define DEBUGMAIN 1 #define DEBUGSYS 1 #define DEBUGLCP 1 #define DEBUGIPCP 1 #define DEBUGIPV6CP 1 #define DEBUGCHAP 1 #define DEBUGIPXCP 1 #define LOG_PPP LOG_LOCAL2 /* Log here when debugging all */ #endif /* DEBUGALL */ #ifndef LOG_PPP /* we use LOG_DAEMON for syslog by default */ #define LOG_PPP LOG_DAEMON #endif /* LOG_PPP */ #ifdef DEBUGMAIN #define MAINDEBUG(x) if (debug) dbglog x #else #define MAINDEBUG(x) ((void) 0) #endif /* DEBUGMAIN */ #ifdef DEBUGSYS #define SYSDEBUG(x) if (debug) dbglog x #else #define SYSDEBUG(x) ((void) 0) #endif /* DEBUGSYS */ #ifdef DEBUGLCP #define LCPDEBUG(x) if (debug) dbglog x #else #define LCPDEBUG(x) ((void) 0) #endif /* DEBUGLCP */ #ifdef DEBUGIPCP #define IPCPDEBUG(x) if (debug) dbglog x #else #define IPCPDEBUG(x) ((void) 0) #endif /* DEBUGIPCP */ #ifdef DEBUGIPV6CP #define IPV6CPDEBUG(x) if (debug) dbglog x #else #define IPV6CPDEBUG(x) ((void) 0) #endif /* DEBUGIPV6CP */ #ifdef DEBUGCHAP #define CHAPDEBUG(x) if (debug) dbglog x #else #define CHAPDEBUG(x) ((void) 0) #endif /* DEBUGCHAP */ #ifdef DEBUGIPXCP #define IPXCPDEBUG(x) if (debug) dbglog x #else #define IPXCPDEBUG(x) ((void) 0) #endif /* DEBUGIPXCP */ #ifndef SIGTYPE #if defined(sun) || defined(SYSV) || defined(POSIX_SOURCE) #define SIGTYPE void #else #define SIGTYPE int #endif /* defined(sun) || defined(SYSV) || defined(POSIX_SOURCE) */ #endif /* SIGTYPE */ #ifndef MIN #define MIN(a, b) ((a) < (b)? (a): (b)) #endif /* MIN */ #ifndef MAX #define MAX(a, b) ((a) > (b)? (a): (b)) #endif /* MAX */ #ifndef Dim #define Dim(x) (sizeof (x) / sizeof (*(x))) #endif /* Dim */ #ifndef NBBY #define NBBY 8 #endif /* NBBY */ #ifndef isset #define isset(arr, val) (((u_char *)(arr))[(val)/NBBY] & (1<<((val)%NBBY))) #endif /* isset */ #ifndef setbit #define setbit(arr, val) (((u_char *)(arr))[(val)/NBBY] |= (1<<((val)%NBBY))) #endif /* setbit */ #define IP_HDRLEN 20 /* bytes */ #define IP_OFFMASK 0x1fff #define TCP_HDRLEN 20 /* * We use these macros because the IP header may be at an odd address, * and some compilers might use word loads to get th_off or ip_hl. */ #define net_short(x) (((x)[0] << 8) + (x)[1]) #define native_long(x) (htonl((net_short(x) << 16) + \ net_short((unsigned char *)(x) + 2))) #define get_ipv(x) ((((unsigned char *)(x))[0] >> 4) & 0xF) #define get_iphl(x) (((unsigned char *)(x))[0] & 0xF) #define get_iplen(x) net_short((unsigned char *)(x) + 2) #define get_ipoff(x) net_short((unsigned char *)(x) + 6) #define get_ipproto(x) (((unsigned char *)(x))[9]) #define get_ipsrc(x) native_long((unsigned char *)(x) + 12) #define get_ipdst(x) native_long((unsigned char *)(x) + 16) #define get_ip6nh(x) (((unsigned char *)(x))[6]) #define get_ip6src(x) (((unsigned char *)(x))+8) #define get_ip6dst(x) (((unsigned char *)(x))+24) /* Ports for both UDP and TCP are first */ #define get_sport(x) net_short(x) #define get_dport(x) net_short((unsigned char *)(x) + 2) #define get_tcpoff(x) (((unsigned char *)(x))[12] >> 4) #define get_tcpflags(x) (((unsigned char *)(x))[13]) /* Check for RFC 1918 (local use) addresses */ #define LOCAL_IP_ADDR(addr) \ (((addr) & 0xff000000) == 0x0a000000 || /* 10.x.x.x */ \ ((addr) & 0xfff00000) == 0xac100000 || /* 172.16.x.x */ \ ((addr) & 0xffff0000) == 0xc0a80000) /* 192.168.x.x */ #ifdef __cplusplus } #endif #endif /* __PPPD_H__ */ #!/bin/sh # Copyright (c) 2001 by Sun Microsystems, Inc. # All rights reserved. echo_file usr/src/lib/Makefile.lib echo_file usr/src/lib/Makefile.targ /* * System-dependent procedures for pppd under Solaris 2.x (SunOS 5.x). * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. * * SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES * * Copyright (c) 1994 The Australian National University. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. This software is provided without any * warranty, express or implied. The Australian National University * makes no representations about the suitability of this software for * any purpose. * * IN NO EVENT SHALL THE AUSTRALIAN NATIONAL UNIVERSITY BE LIABLE TO ANY * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF * THE AUSTRALIAN NATIONAL UNIVERSITY HAVE BEEN ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * THE AUSTRALIAN NATIONAL UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE AUSTRALIAN NATIONAL UNIVERSITY HAS NO * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, * OR MODIFICATIONS. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pppd.h" #include "fsm.h" #include "lcp.h" #include "ipcp.h" #ifdef INET6 #include "ipv6cp.h" #endif /* INET6 */ #include "ccp.h" #define PPPSTRTIMOUT 1 /* Timeout in seconds for ioctl */ #define MAX_POLLFDS 32 #define NMODULES 32 #ifndef MAXIFS #define MAXIFS 256 #endif /* MAXIFS */ #ifdef INET6 #define _IN6_LLX_FROM_EUI64(l, s, eui64, as, len) \ (s->sin6_addr.s6_addr32[0] = htonl(as), \ eui64_copy(eui64, s->sin6_addr.s6_addr32[2]), \ s->sin6_family = AF_INET6, \ l.lifr_addr.ss_family = AF_INET6, \ l.lifr_addrlen = len, \ l.lifr_addr = laddr) /* * Generate a link-local address with an interface-id based on the given * EUI64 identifier. Note that the len field is unused by SIOCSLIFADDR. */ #define IN6_LLADDR_FROM_EUI64(l, s, eui64) \ _IN6_LLX_FROM_EUI64(l, s, eui64, 0xfe800000, 0) /* * Generate an EUI64 based interface-id for use by stateless address * autoconfiguration. These are required to be 64 bits long as defined in * the "Interface Identifiers" section of the IPv6 Addressing Architecture * (RFC3513). */ #define IN6_LLTOKEN_FROM_EUI64(l, s, eui64) \ _IN6_LLX_FROM_EUI64(l, s, eui64, 0, 64) #endif /* INET6 */ #define IPCP_ENABLED ipcp_protent.enabled_flag #ifdef INET6 #define IPV6CP_ENABLED ipv6cp_protent.enabled_flag #endif /* INET6 */ /* For plug-in usage. */ int (*sys_read_packet_hook) __P((int retv, struct strbuf *ctrl, struct strbuf *data, int flags)) = NULL; bool already_ppp = 0; /* Already in PPP mode */ static int pppfd = -1; /* ppp driver fd */ static int fdmuxid = -1; /* driver mux fd */ static int ipfd = -1; /* IPv4 fd */ static int ipmuxid = -1; /* IPv4 mux fd */ static int ip6fd = -1; /* IPv6 fd */ static int ip6muxid = -1; /* IPv6 mux fd */ static bool if6_is_up = 0; /* IPv6 if marked as up */ static bool if_is_up = 0; /* IPv4 if marked as up */ static bool restore_term = 0; /* Restore TTY after closing link */ static struct termios inittermios; /* TTY settings */ static struct winsize wsinfo; /* Initial window size info */ static pid_t tty_sid; /* original sess ID for term */ static struct pollfd pollfds[MAX_POLLFDS]; /* array of polled fd */ static int n_pollfds = 0; /* total count of polled fd */ static int link_mtu; /* link Maximum Transmit Unit */ static int tty_nmodules; /* total count of TTY modules used */ static char tty_modules[NMODULES][FMNAMESZ+1]; /* array of TTY modules used */ static int tty_npushed; /* total count of pushed PPP modules */ static u_int32_t remote_addr; /* IP address of peer */ static u_int32_t default_route_gateway; /* Gateway for default route */ static u_int32_t proxy_arp_addr; /* Addr for proxy arp entry */ static u_int32_t lastlink_status; /* Last link status info */ static bool use_plink = 0; /* Use I_LINK by default */ static bool plumbed = 0; /* Use existing interface */ /* Default is to use /dev/sppp as driver. */ static const char *drvnam = PPP_DEV_NAME; static bool integrated_driver = 0; static int extra_dev_fd = -1; /* keep open until ready */ static option_t solaris_option_list[] = { { "plink", o_bool, &use_plink, "Use I_PLINK instead of I_LINK", OPT_PRIV|1 }, { "noplink", o_bool, &use_plink, "Use I_LINK instead of I_PLINK", OPT_PRIV|0 }, { "plumbed", o_bool, &plumbed, "Use pre-plumbed interface", OPT_PRIV|1 }, { NULL } }; /* * Prototypes for procedures local to this file. */ static int translate_speed __P((int)); static int baud_rate_of __P((int)); static int get_ether_addr __P((u_int32_t, struct sockaddr_dl *, int)); static int strioctl __P((int, int, void *, int, int)); static int plumb_ipif __P((int)); static int unplumb_ipif __P((int)); #ifdef INET6 static int plumb_ip6if __P((int)); static int unplumb_ip6if __P((int)); static int open_ip6fd(void); #endif /* INET6 */ static int open_ipfd(void); static int sifroute __P((int, u_int32_t, u_int32_t, int, const char *)); static int giflags __P((u_int32_t, bool *)); static void handle_unbind __P((u_int32_t)); static void handle_bind __P((u_int32_t)); /* * Wrapper for regular ioctl; masks out EINTR. */ static int myioctl(int fd, int cmd, void *arg) { int retv; errno = 0; while ((retv = ioctl(fd, cmd, arg)) == -1) { if (errno != EINTR) break; } return (retv); } /* * sys_check_options() * * Check the options that the user specified. */ int sys_check_options(void) { if (plumbed) { if (req_unit == -1) req_unit = -2; ipmuxid = 0; ip6muxid = 0; } return (1); } /* * sys_options() * * Add or remove system-specific options. */ void sys_options(void) { (void) remove_option("ktune"); (void) remove_option("noktune"); add_options(solaris_option_list); } /* * sys_ifname() * * Set ifname[] to contain name of IP interface for this unit. */ void sys_ifname(void) { const char *cp; if ((cp = strrchr(drvnam, '/')) == NULL) cp = drvnam; else cp++; (void) slprintf(ifname, sizeof (ifname), "%s%d", cp, ifunit); } /* * ppp_available() * * Check whether the system has any ppp interfaces. */ int ppp_available(void) { struct stat buf; int fd; uint32_t typ; if (stat(PPP_DEV_NAME, &buf) >= 0) return (1); /* * Simple check for system using Apollo POS without SUNWpppd * (/dev/sppp) installed. This is intentionally not kept open * here, since the user may not have the same privileges (as * determined later). If Apollo were just shipped with the * full complement of packages, this wouldn't be an issue. */ if (devnam[0] == '\0' && (fd = open(devnam, O_RDWR | O_NONBLOCK | O_NOCTTY)) >= 0) { if (strioctl(fd, PPPIO_GTYPE, &typ, 0, sizeof (typ)) >= 0 && typ == PPPTYP_MUX) { (void) close(fd); return (1); } (void) close(fd); } return (0); } static int open_ipfd(void) { ipfd = open(IP_DEV_NAME, O_RDWR | O_NONBLOCK, 0); if (ipfd < 0) { error("Couldn't open IP device (%s): %m", IP_DEV_NAME); } return (ipfd); } static int read_ip_interface(int unit) { struct ifreq ifr; struct sockaddr_in sin; if (ipfd == -1 && open_ipfd() == -1) return (0); BZERO(&ifr, sizeof (ifr)); (void) strlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name)); /* Get the existing MTU */ if (myioctl(ipfd, SIOCGIFMTU, &ifr) < 0) { warn("Couldn't get IP MTU on %s: %m", ifr.ifr_name); return (0); } dbglog("got MTU %d from interface", ifr.ifr_metric); if (ifr.ifr_metric != 0 && (lcp_allowoptions[unit].mru == 0 || lcp_allowoptions[unit].mru > ifr.ifr_metric)) lcp_allowoptions[unit].mru = ifr.ifr_metric; /* Get the local IP address */ if (ipcp_wantoptions[unit].ouraddr == 0 || ipcp_from_hostname) { if (myioctl(ipfd, SIOCGIFADDR, &ifr) < 0) { warn("Couldn't get local IP address (%s): %m", ifr.ifr_name); return (0); } BCOPY(&ifr.ifr_addr, &sin, sizeof (struct sockaddr_in)); ipcp_wantoptions[unit].ouraddr = sin.sin_addr.s_addr; dbglog("got local address %I from interface", ipcp_wantoptions[unit].ouraddr); } /* Get the remote IP address */ if (ipcp_wantoptions[unit].hisaddr == 0) { if (myioctl(ipfd, SIOCGIFDSTADDR, &ifr) < 0) { warn("Couldn't get remote IP address (%s): %m", ifr.ifr_name); return (0); } BCOPY(&ifr.ifr_dstaddr, &sin, sizeof (struct sockaddr_in)); ipcp_wantoptions[unit].hisaddr = sin.sin_addr.s_addr; dbglog("got remote address %I from interface", ipcp_wantoptions[unit].hisaddr); } return (1); } #ifdef INET6 static int open_ip6fd(void) { ip6fd = open(IP6_DEV_NAME, O_RDWR | O_NONBLOCK, 0); if (ip6fd < 0) { error("Couldn't open IPv6 device (%s): %m", IP6_DEV_NAME); } return (ip6fd); } static int read_ipv6_interface(int unit) { struct lifreq lifr; struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; if (ip6fd == -1 && open_ip6fd() == -1) return (0); BZERO(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); /* Get the existing MTU */ if (myioctl(ip6fd, SIOCGLIFMTU, &lifr) < 0) { warn("Couldn't get IPv6 MTU on %s: %m", lifr.lifr_name); return (0); } if (lifr.lifr_mtu != 0 && (lcp_allowoptions[unit].mru == 0 || lcp_allowoptions[unit].mru > lifr.lifr_mtu)) lcp_allowoptions[unit].mru = lifr.lifr_mtu; /* Get the local IPv6 address */ if (eui64_iszero(ipv6cp_wantoptions[unit].ourid) || (ipcp_from_hostname && ipv6cp_wantoptions[unit].use_ip)) { if (myioctl(ip6fd, SIOCGLIFADDR, &lifr) < 0) { warn("Couldn't get local IPv6 address (%s): %m", lifr.lifr_name); return (0); } eui64_copy(sin6->sin6_addr.s6_addr32[2], ipv6cp_wantoptions[unit].ourid); } /* Get the remote IP address */ if (eui64_iszero(ipv6cp_wantoptions[unit].hisid)) { if (myioctl(ip6fd, SIOCGLIFDSTADDR, &lifr) < 0) { warn("Couldn't get remote IPv6 address (%s): %m", lifr.lifr_name); return (0); } eui64_copy(sin6->sin6_addr.s6_addr32[2], ipv6cp_wantoptions[unit].hisid); } return (1); } #endif /* INET6 */ /* * Read information on existing interface(s) and configure ourselves * to negotiate appropriately. */ static void read_interface(int unit) { dbglog("reading existing interface data; %sip %sipv6", IPCP_ENABLED ? "" : "!", #ifdef INET6 IPV6CP_ENABLED ? "" : #endif "!"); if (IPCP_ENABLED && !read_ip_interface(unit)) IPCP_ENABLED = 0; #ifdef INET6 if (IPV6CP_ENABLED && !read_ipv6_interface(unit)) IPV6CP_ENABLED = 0; #endif } /* * sys_init() * * System-dependent initialization. */ void sys_init(bool open_as_user) { uint32_t x; uint32_t typ; if (pppfd != -1) { return; } if (!direct_tty && devnam[0] != '\0') { /* * Check for integrated driver-like devices (such as * POS). These identify themselves as "PPP * multiplexor" drivers. */ if (open_as_user) (void) seteuid(getuid()); pppfd = open(devnam, O_RDWR | O_NONBLOCK); if (open_as_user) (void) seteuid(0); if (pppfd >= 0 && strioctl(pppfd, PPPIO_GTYPE, &typ, 0, sizeof (typ)) >= 0 && typ == PPPTYP_MUX) { integrated_driver = 1; drvnam = devnam; } else if (demand) { (void) close(pppfd); pppfd = -1; } else { extra_dev_fd = pppfd; pppfd = -1; } } /* * Open Solaris PPP device driver. */ if (pppfd < 0) pppfd = open(drvnam, O_RDWR | O_NONBLOCK); if (pppfd < 0) { fatal("Can't open %s: %m", drvnam); } if (kdebugflag & 1) { x = PPPDBG_LOG + PPPDBG_DRIVER; if (strioctl(pppfd, PPPIO_DEBUG, &x, sizeof (x), 0) < 0) { warn("PPPIO_DEBUG ioctl for mux failed: %m"); } } /* * Assign a new PPA and get its unit number. */ x = req_unit; if (strioctl(pppfd, PPPIO_NEWPPA, &x, sizeof (x), sizeof (x)) < 0) { if (errno == ENXIO && plumbed) fatal("No idle interfaces available for use"); fatal("PPPIO_NEWPPA ioctl failed: %m"); } ifunit = x; if (req_unit >= 0 && ifunit != req_unit) { if (plumbed) fatal("unable to get requested unit %d", req_unit); else warn("unable to get requested unit %d", req_unit); } /* * Enable packet time-stamping when idle option is specified. Note * that we need to only do this on the control stream. Subsequent * streams attached to this control stream (ppa) will inherit * the time-stamp bit. */ if (idle_time_limit > 0) { if (strioctl(pppfd, PPPIO_USETIMESTAMP, NULL, 0, 0) < 0) { warn("PPPIO_USETIMESTAMP ioctl failed: %m"); } } if (plumbed) { sys_ifname(); read_interface(0); } } int sys_extra_fd(void) { int fd; fd = extra_dev_fd; extra_dev_fd = -1; return (fd); } static int open_udpfd(void) { int udpfd; udpfd = open(UDP_DEV_NAME, O_RDWR | O_NONBLOCK, 0); if (udpfd < 0) { error("Couldn't open UDP device (%s): %m", UDP_DEV_NAME); } return (udpfd); } /* * plumb_ipif() * * Perform IP interface plumbing. */ /*ARGSUSED*/ static int plumb_ipif(int unit) { int udpfd = -1, tmpfd; uint32_t x; struct ifreq ifr; if (!IPCP_ENABLED || (ifunit == -1) || (pppfd == -1)) { return (0); } if (plumbed) return (1); if (ipfd == -1 && open_ipfd() == -1) return (0); if (use_plink && (udpfd = open_udpfd()) == -1) return (0); tmpfd = open(drvnam, O_RDWR | O_NONBLOCK, 0); if (tmpfd < 0) { error("Couldn't open PPP device (%s): %m", drvnam); if (udpfd != -1) (void) close(udpfd); return (0); } if (kdebugflag & 1) { x = PPPDBG_LOG + PPPDBG_DRIVER; if (strioctl(tmpfd, PPPIO_DEBUG, &x, sizeof (x), 0) < 0) { warn("PPPIO_DEBUG ioctl for mux failed: %m"); } } if (myioctl(tmpfd, I_PUSH, IP_MOD_NAME) < 0) { error("Couldn't push IP module (%s): %m", IP_MOD_NAME); goto err_ret; } /* * Assign ppa according to the unit number returned by ppp device * after plumbing is completed above. Without setting the ppa, ip * module will return EINVAL upon setting the interface UP * (SIOCSxIFFLAGS). This is because ip module in 2.8 expects two * DLPI_INFO_REQ to be sent down to the driver (below ip) before * IFF_UP bit can be set. Plumbing the device causes one DLPI_INFO_REQ * to be sent down, and the second DLPI_INFO_REQ is sent upon receiving * IF_UNITSEL (old) or SIOCSLIFNAME (new) ioctls. Such setting of the * ppa is required because the ppp DLPI provider advertises itself as * a DLPI style 2 type, which requires a point of attachment to be * specified. The only way the user can specify a point of attachment * is via SIOCSLIFNAME or IF_UNITSEL. Such changes in the behavior of * ip module was made to meet new or evolving standards requirements. */ if (myioctl(tmpfd, IF_UNITSEL, &ifunit) < 0) { error("Couldn't set ppa for unit %d: %m", ifunit); goto err_ret; } if (use_plink) { ipmuxid = myioctl(udpfd, I_PLINK, (void *)tmpfd); if (ipmuxid < 0) { error("Can't I_PLINK PPP device to IP: %m"); goto err_ret; } } else { ipmuxid = myioctl(ipfd, I_LINK, (void *)tmpfd); if (ipmuxid < 0) { error("Can't I_LINK PPP device to IP: %m"); goto err_ret; } } BZERO(&ifr, sizeof (ifr)); (void) strlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name)); ifr.ifr_ip_muxid = ipmuxid; ifr.ifr_arp_muxid = -1; if (myioctl(ipfd, SIOCSIFMUXID, (caddr_t)&ifr) < 0) { error("Can't set mux ID SIOCSIFMUXID on %s: %m", ifname); goto err_ret; } if (udpfd != -1) (void) close(udpfd); (void) close(tmpfd); return (1); err_ret: if (udpfd != -1) (void) close(udpfd); (void) close(tmpfd); return (0); } /* * unplumb_ipif() * * Perform IP interface unplumbing. Possibly called from die(), so there * shouldn't be any call to die() or fatal() here. */ static int unplumb_ipif(int unit) { int udpfd = -1, fd = -1; int id; struct lifreq lifr; if (!IPCP_ENABLED || (ifunit == -1)) { return (0); } if (!plumbed && (ipmuxid == -1 || (ipfd == -1 && !use_plink))) return (1); id = ipmuxid; if (!plumbed && use_plink) { if ((udpfd = open_udpfd()) == -1) return (0); /* * Note: must re-get mux ID, since any intervening * ifconfigs will change this. */ BZERO(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); if (myioctl(ipfd, SIOCGLIFMUXID, (caddr_t)&lifr) < 0) { warn("Can't get mux fd: SIOCGLIFMUXID: %m"); } else { id = lifr.lifr_ip_muxid; fd = myioctl(udpfd, _I_MUXID2FD, (void *)id); if (fd < 0) { warn("Can't get mux fd: _I_MUXID2FD: %m"); } } } /* * Mark down and unlink the ip interface. */ (void) sifdown(unit); if (default_route_gateway != 0) { (void) cifdefaultroute(0, default_route_gateway, default_route_gateway); } if (proxy_arp_addr != 0) { (void) cifproxyarp(0, proxy_arp_addr); } ipmuxid = -1; if (plumbed) return (1); if (use_plink) { if (myioctl(udpfd, I_PUNLINK, (void *)id) < 0) { error("Can't I_PUNLINK PPP from IP: %m"); if (fd != -1) (void) close(fd); (void) close(udpfd); return (0); } if (fd != -1) (void) close(fd); (void) close(udpfd); } else { if (myioctl(ipfd, I_UNLINK, (void *)id) < 0) { error("Can't I_UNLINK PPP from IP: %m"); return (0); } } return (1); } /* * sys_cleanup() * * Restore any system state we modified before exiting: mark the * interface down, delete default route and/or proxy arp entry. This * should not call die() because it's called from die(). */ void sys_cleanup() { (void) unplumb_ipif(0); #ifdef INET6 (void) unplumb_ip6if(0); #endif /* INET6 */ } /* * get_first_hwaddr() * * Stores the first hardware interface address found in the system * into addr and return 1 upon success, or 0 if none is found. This * is also called from the multilink code. */ int get_first_hwaddr(addr, msize) uchar_t *addr; int msize; { struct ifconf ifc; register struct ifreq *pifreq; struct ifreq ifr; int fd, num_ifs, i; uint_t fl, req_size; char *req; boolean_t found; if (addr == NULL) { return (0); } fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { error("get_first_hwaddr: error opening IP socket: %m"); return (0); } /* * Find out how many interfaces are running */ if (myioctl(fd, SIOCGIFNUM, (caddr_t)&num_ifs) < 0) { num_ifs = MAXIFS; } req_size = num_ifs * sizeof (struct ifreq); req = malloc(req_size); if (req == NULL) { novm("interface request structure."); } /* * Get interface configuration info for all interfaces */ ifc.ifc_len = req_size; ifc.ifc_buf = req; if (myioctl(fd, SIOCGIFCONF, &ifc) < 0) { error("SIOCGIFCONF: %m"); (void) close(fd); free(req); return (0); } /* * And traverse each interface to look specifically for the first * occurence of an Ethernet interface which has been marked up */ pifreq = ifc.ifc_req; found = 0; for (i = ifc.ifc_len / sizeof (struct ifreq); i > 0; i--, pifreq++) { if (strchr(pifreq->ifr_name, ':') != NULL) { continue; } BZERO(&ifr, sizeof (ifr)); (void) strncpy(ifr.ifr_name, pifreq->ifr_name, sizeof (ifr.ifr_name)); if (myioctl(fd, SIOCGIFFLAGS, &ifr) < 0) { continue; } fl = ifr.ifr_flags; if ((fl & (IFF_UP|IFF_BROADCAST|IFF_POINTOPOINT|IFF_LOOPBACK)) != (IFF_UP | IFF_BROADCAST)) { continue; } if (get_if_hwaddr(addr, msize, ifr.ifr_name) <= 0) { continue; } found = 1; break; } free(req); (void) close(fd); return (found); } /* * get_if_hwaddr() * * Get the hardware address for the specified network interface device. * Return the length of the MAC address (in bytes) or -1 if error. */ int get_if_hwaddr(uchar_t *addrp, int msize, char *linkname) { dlpi_handle_t dh; uchar_t physaddr[DLPI_PHYSADDR_MAX]; size_t physaddrlen = sizeof (physaddr); int retv; if ((addrp == NULL) || (linkname == NULL)) return (-1); /* * Open the link and ask for hardware address. */ if ((retv = dlpi_open(linkname, &dh, 0)) != DLPI_SUCCESS) { error("Could not open %s: %s", linkname, dlpi_strerror(retv)); return (-1); } retv = dlpi_get_physaddr(dh, DL_CURR_PHYS_ADDR, physaddr, &physaddrlen); dlpi_close(dh); if (retv != DLPI_SUCCESS) { error("Could not get physical address on %s: %s", linkname, dlpi_strerror(retv)); return (-1); } /* * Check if we have enough space to copy the address to. */ if (physaddrlen > msize) return (-1); (void) memcpy(addrp, physaddr, physaddrlen); return (physaddrlen); } /* * giflags() */ static int giflags(u_int32_t flag, bool *retval) { struct ifreq ifr; int fd; *retval = 0; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { error("giflags: error opening IP socket: %m"); return (errno); } BZERO(&ifr, sizeof (ifr)); (void) strncpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name)); if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) { (void) close(fd); return (errno); } *retval = ((ifr.ifr_flags & flag) != 0); (void) close(fd); return (errno); } /* * sys_close() * * Clean up in a child process before exec-ing. */ void sys_close() { if (ipfd != -1) { (void) close(ipfd); ipfd = -1; } #ifdef INET6 if (ip6fd != -1) { (void) close(ip6fd); ip6fd = -1; } #endif /* INET6 */ if (pppfd != -1) { (void) close(pppfd); pppfd = -1; } } /* * any_compressions() * * Check if compression is enabled or not. In the STREAMS implementation of * kernel-portion pppd, the comp STREAMS module performs the ACFC, PFC, as * well CCP and VJ compressions. However, if the user has explicitly declare * to not enable them from the command line, there is no point of having the * comp module be pushed on the stream. */ static int any_compressions(void) { if ((!lcp_wantoptions[0].neg_accompression) && (!lcp_wantoptions[0].neg_pcompression) && (!ccp_protent.enabled_flag) && (!ipcp_wantoptions[0].neg_vj)) { return (0); } return (1); } /* * modpush() * * Push a module on the stream. */ static int modpush(int fd, const char *modname, const char *text) { if (myioctl(fd, I_PUSH, (void *)modname) < 0) { error("Couldn't push %s module: %m", text); return (-1); } if (++tty_npushed == 1 && !already_ppp) { if (strioctl(fd, PPPIO_LASTMOD, NULL, 0, 0) < 0) { warn("unable to set LASTMOD on %s: %m", text); } } return (0); } /* * establish_ppp() * * Turn the serial port into a ppp interface. */ int establish_ppp(fd) int fd; { int i; uint32_t x; if (default_device && !notty) { tty_sid = getsid((pid_t)0); } if (integrated_driver) return (pppfd); /* * Pop any existing modules off the tty stream */ for (i = 0; ; ++i) { if ((myioctl(fd, I_LOOK, tty_modules[i]) < 0) || (strcmp(tty_modules[i], "ptem") == 0) || (myioctl(fd, I_POP, (void *)0) < 0)) { break; } } tty_nmodules = i; /* * Push the async hdlc module and the compressor module */ tty_npushed = 0; if (!sync_serial && !already_ppp && modpush(fd, AHDLC_MOD_NAME, "PPP async HDLC") < 0) { return (-1); } /* * There's no need to push comp module if we don't intend * to compress anything */ if (any_compressions()) { (void) modpush(fd, COMP_MOD_NAME, "PPP compression"); } /* * Link the serial port under the PPP multiplexor */ if ((fdmuxid = myioctl(pppfd, I_LINK, (void *)fd)) < 0) { error("Can't link tty to PPP mux: %m"); return (-1); } if (tty_npushed == 0 && !already_ppp) { if (strioctl(pppfd, PPPIO_LASTMOD, NULL, 0, 0) < 0) { warn("unable to set LASTMOD on PPP mux: %m"); } } /* * Debug configuration must occur *after* I_LINK. */ if (kdebugflag & 4) { x = PPPDBG_LOG + PPPDBG_AHDLC; if (strioctl(pppfd, PPPIO_DEBUG, &x, sizeof (x), 0) < 0) { warn("PPPIO_DEBUG ioctl for ahdlc module failed: %m"); } } if (any_compressions() && (kdebugflag & 2)) { x = PPPDBG_LOG + PPPDBG_COMP; if (strioctl(pppfd, PPPIO_DEBUG, &x, sizeof (x), 0) < 0) { warn("PPPIO_DEBUG ioctl for comp module failed: %m"); } } return (pppfd); } /* * restore_loop() * * Reattach the ppp unit to the loopback. This doesn't need to do anything * because disestablish_ppp does it */ void restore_loop() { } /* * disestablish_ppp() * * Restore the serial port to normal operation. It attempts to reconstruct * the stream with the previously popped modules. This shouldn't call die() * because it's called from die(). Stream reconstruction is needed in case * pppd is used for dial-in on /dev/tty and there's an option error. */ void disestablish_ppp(fd) int fd; { int i; if (fdmuxid == -1 || integrated_driver) { return; } if (myioctl(pppfd, I_UNLINK, (void *)fdmuxid) < 0) { if (!hungup) { error("Can't unlink tty from PPP mux: %m"); } } fdmuxid = -1; if (!hungup) { while (tty_npushed > 0 && myioctl(fd, I_POP, (void *)0) >= 0) { --tty_npushed; } for (i = tty_nmodules - 1; i >= 0; --i) { if (myioctl(fd, I_PUSH, tty_modules[i]) < 0) { error("Couldn't restore tty module %s: %m", tty_modules[i]); } } } if (hungup && default_device && tty_sid > 0) { /* * If we have received a hangup, we need to send a * SIGHUP to the terminal's controlling process. * The reason is that the original stream head for * the terminal hasn't seen the M_HANGUP message * (it went up through the ppp driver to the stream * head for our fd to /dev/ppp). */ (void) kill(tty_sid, SIGHUP); } } /* * clean_check() * * Check whether the link seems not to be 8-bit clean */ void clean_check() { uint32_t x; char *s = NULL; /* * Skip this is synchronous link is used, since spppasyn won't * be anywhere in the stream below to handle the ioctl. */ if (sync_serial) { return; } if (strioctl(pppfd, PPPIO_GCLEAN, &x, 0, sizeof (x)) < 0) { warn("unable to obtain serial link status: %m"); return; } switch (~x) { case RCV_B7_0: s = "bit 7 set to 1"; break; case RCV_B7_1: s = "bit 7 set to 0"; break; case RCV_EVNP: s = "odd parity"; break; case RCV_ODDP: s = "even parity"; break; } if (s != NULL) { warn("Serial link is not 8-bit clean:"); warn("All received characters had %s", s); } } /* * List of valid speeds. */ struct speed { int speed_int; int speed_val; } speeds[] = { { 50, B50 }, { 75, B75 }, { 110, B110 }, { 134, B134 }, { 150, B150 }, { 200, B200 }, { 300, B300 }, { 600, B600 }, { 1200, B1200 }, { 1800, B1800 }, { 2400, B2400 }, { 4800, B4800 }, { 9600, B9600 }, { 19200, B19200 }, { 38400, B38400 }, { 57600, B57600 }, { 76800, B76800 }, { 115200, B115200 }, { 153600, B153600 }, { 230400, B230400 }, { 307200, B307200 }, { 460800, B460800 }, { 921600, B921600 }, { 1000000, B1000000 }, { 1152000, B1152000 }, { 1500000, B1500000 }, { 2000000, B2000000 }, { 2500000, B2500000 }, { 3000000, B3000000 }, { 3500000, B3500000 }, { 4000000, B4000000 }, { 0, 0 } }; /* * translate_speed() * * Translate from bits/second to a speed_t */ static int translate_speed(int bps) { struct speed *speedp; if (bps == 0) { return (0); } for (speedp = speeds; speedp->speed_int; speedp++) { if (bps == speedp->speed_int) { return (speedp->speed_val); } } set_source(&speed_info); option_error("speed %d not supported", bps); return (0); } /* * baud_rate_of() * * Translate from a speed_t to bits/second */ static int baud_rate_of(int speed) { struct speed *speedp; if (speed == 0) { return (0); } for (speedp = speeds; speedp->speed_int; speedp++) { if (speed == speedp->speed_val) { return (speedp->speed_int); } } return (0); } /* * set_up_tty() * * Set up the serial port on `fd' for 8 bits, no parity, at the requested * speed, etc. If `local' is true, set CLOCAL regardless of whether the * modem option was specified. */ void set_up_tty(fd, local) int fd, local; { int speed; struct termios tios; struct scc_mode sm; if (already_ppp) return; if (sync_serial) { restore_term = 0; speed = B0; baud_rate = 0; if (strioctl(fd, S_IOCGETMODE, &sm, sizeof (sm), sizeof (sm)) < 0) { return; } baud_rate = sm.sm_baudrate; dbglog("synchronous speed appears to be %d bps", baud_rate); } else { if (tcgetattr(fd, &tios) < 0) { fatal("tcgetattr: %m"); } if (!restore_term) { inittermios = tios; if (myioctl(fd, TIOCGWINSZ, &wsinfo) < 0) { if (errno == EINVAL) { /* * ptem returns EINVAL if all zeroes. * Strange and unfixable code. */ bzero(&wsinfo, sizeof (wsinfo)); } else { warn("unable to get TTY window " "size: %m"); } } } tios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CLOCAL); if (crtscts > 0) { tios.c_cflag |= CRTSCTS | CRTSXOFF; } else if (crtscts < 0) { tios.c_cflag &= ~CRTSCTS & ~CRTSXOFF; } tios.c_cflag |= CS8 | CREAD | HUPCL; if (local || !modem) { tios.c_cflag |= CLOCAL; } tios.c_iflag = IGNBRK | IGNPAR; tios.c_oflag = 0; tios.c_lflag = 0; tios.c_cc[VMIN] = 1; tios.c_cc[VTIME] = 0; if (crtscts == -2) { tios.c_iflag |= IXON | IXOFF; tios.c_cc[VSTOP] = 0x13; /* DC3 = XOFF = ^S */ tios.c_cc[VSTART] = 0x11; /* DC1 = XON = ^Q */ } speed = translate_speed(inspeed); if (speed) { (void) cfsetospeed(&tios, speed); (void) cfsetispeed(&tios, speed); } else { speed = cfgetospeed(&tios); /* * We can't proceed if the serial port speed is 0, * since that implies that the serial port is disabled. */ if (speed == B0) { fatal("Baud rate for %s is 0; need explicit " "baud rate", devnam); } } if (tcsetattr(fd, TCSAFLUSH, &tios) < 0) { fatal("tcsetattr: %m"); } baud_rate = baud_rate_of(speed); dbglog("%s speed set to %d bps", fd == pty_slave ? "pty" : "serial", baud_rate); restore_term = 1; } } /* * restore_tty() * * Restore the terminal to the saved settings. */ void restore_tty(fd) int fd; { if (restore_term == 0) { return; } if (!default_device) { /* * Turn off echoing, because otherwise we can get into * a loop with the tty and the modem echoing to each * other. We presume we are the sole user of this tty * device, so when we close it, it will revert to its * defaults anyway. */ inittermios.c_lflag &= ~(ECHO | ECHONL); } if (tcsetattr(fd, TCSAFLUSH, &inittermios) < 0) { if (!hungup && errno != ENXIO) { warn("tcsetattr: %m"); } } if (wsinfo.ws_row != 0 || wsinfo.ws_col != 0 || wsinfo.ws_xpixel != 0 || wsinfo.ws_ypixel != 0) { if (myioctl(fd, TIOCSWINSZ, &wsinfo) < 0) { warn("unable to set TTY window size: %m"); } } restore_term = 0; } /* * setdtr() * * Control the DTR line on the serial port. This is called from die(), so it * shouldn't call die() */ void setdtr(fd, on) int fd, on; { int modembits = TIOCM_DTR; if (!already_ppp && myioctl(fd, (on ? TIOCMBIS : TIOCMBIC), &modembits) < 0) { warn("unable to set DTR line %s: %m", (on ? "ON" : "OFF")); } } /* * open_loopback() * * Open the device we use for getting packets in demand mode. Under Solaris 2, * we use our existing fd to the ppp driver. */ int open_ppp_loopback() { /* * Plumb the interface. */ if (IPCP_ENABLED && (plumb_ipif(0) == 0)) { fatal("Unable to initialize IP interface for demand dial."); } #ifdef INET6 if (IPV6CP_ENABLED && (plumb_ip6if(0) == 0)) { fatal("Unable to initialize IPv6 interface for demand dial."); } #endif /* INET6 */ return (pppfd); } /* * output() * * Output PPP packet downstream */ /*ARGSUSED*/ void output(unit, p, len) int unit; uchar_t *p; int len; { struct strbuf data; struct pollfd pfd; int retries, n; bool sent_ok = 1; data.len = len; data.buf = (caddr_t)p; retries = 4; while (putmsg(pppfd, NULL, &data, 0) < 0) { if (errno == EINTR) continue; if (--retries < 0 || (errno != EWOULDBLOCK && errno != EAGAIN)) { if (errno != ENXIO) { error("Couldn't send packet: %m"); sent_ok = 0; } break; } pfd.fd = pppfd; pfd.events = POLLOUT; do { /* wait for up to 0.25 seconds */ n = poll(&pfd, 1, 250); } while ((n == -1) && (errno == EINTR)); } if (debug && sent_ok) { dbglog("sent %P", p, len); } } /* * wait_input() * * Wait until there is data available, for the length of time specified by * timo (indefinite if timo is NULL). */ void wait_input(timo) struct timeval *timo; { int t; t = (timo == NULL ? -1 : (timo->tv_sec * 1000 + timo->tv_usec / 1000)); if ((poll(pollfds, n_pollfds, t) < 0) && (errno != EINTR)) { fatal("poll: %m"); } } /* * add_fd() * * Add an fd to the set that wait_input waits for. */ void add_fd(fd) int fd; { int n; if (fd < 0) { return; } for (n = 0; n < n_pollfds; ++n) { if (pollfds[n].fd == fd) { return; } } if (n_pollfds < MAX_POLLFDS) { pollfds[n_pollfds].fd = fd; pollfds[n_pollfds].events = POLLIN | POLLPRI | POLLHUP; ++n_pollfds; } else { fatal("add_fd: too many inputs!"); } } /* * remove_fd() * * Remove an fd from the set that wait_input waits for. */ void remove_fd(fd) int fd; { int n; for (n = 0; n < n_pollfds; ++n) { if (pollfds[n].fd == fd) { while (++n < n_pollfds) { pollfds[n-1] = pollfds[n]; } --n_pollfds; break; } } } static void dump_packet(uchar_t *buf, int len) { uchar_t *bp; int proto, offs; const char *cp; char sbuf[32]; uint32_t src, dst; struct protoent *pep; struct in6_addr addr; char fromstr[INET6_ADDRSTRLEN]; char tostr[INET6_ADDRSTRLEN]; if (len < 4) { notice("strange link activity: %.*B", len, buf); return; } bp = buf; if (bp[0] == 0xFF && bp[1] == 0x03) bp += 2; proto = *bp++; if (!(proto & 1)) proto = (proto << 8) + *bp++; len -= bp-buf; switch (proto) { case PPP_IP: if (len < IP_HDRLEN || get_ipv(bp) != 4 || get_iphl(bp) < 5) { notice("strange IP packet activity: %16.*B", len, buf); return; } src = get_ipsrc(bp); dst = get_ipdst(bp); proto = get_ipproto(bp); if ((pep = getprotobynumber(proto)) != NULL) { cp = pep->p_name; } else { (void) slprintf(sbuf, sizeof (sbuf), "IP proto %d", proto); cp = sbuf; } if ((get_ipoff(bp) & IP_OFFMASK) != 0) { len -= get_iphl(bp) * 4; bp += get_iphl(bp) * 4; notice("%s fragment from %I->%I: %8.*B", cp, src, dst, len, bp); } else { if (len > get_iplen(bp)) len = get_iplen(bp); len -= get_iphl(bp) * 4; bp += get_iphl(bp) * 4; offs = proto == IPPROTO_TCP ? (get_tcpoff(bp)*4) : 8; if (proto == IPPROTO_TCP || proto == IPPROTO_UDP) notice("%s data:%d %s%I:%d->%I:%d: %8.*B", cp, len-offs, proto == IPPROTO_TCP ? tcp_flag_decode(get_tcpflags(bp)) : "", src, get_sport(bp), dst, get_dport(bp), len-offs, bp+offs); else notice("%s %d bytes %I->%I: %8.*B", cp, len, src, dst, len, bp); } return; case PPP_IPV6: if (len < IP6_HDRLEN) { notice("strange IPv6 activity: %16.*B", len, buf); return; } (void) BCOPY(get_ip6src(bp), &addr, sizeof (addr)); (void) inet_ntop(AF_INET6, &addr, fromstr, sizeof (fromstr)); (void) BCOPY(get_ip6dst(bp), &addr, sizeof (addr)); (void) inet_ntop(AF_INET6, &addr, tostr, sizeof (tostr)); proto = get_ip6nh(bp); if (proto == IPPROTO_FRAGMENT) { notice("IPv6 fragment from %s->%s", fromstr, tostr); return; } if ((pep = getprotobynumber(proto)) != NULL) { cp = pep->p_name; } else { (void) slprintf(sbuf, sizeof (sbuf), "IPv6 proto %d", proto); cp = sbuf; } len -= IP6_HDRLEN; bp += IP6_HDRLEN; offs = proto == IPPROTO_TCP ? (get_tcpoff(bp)*4) : 8; if (proto == IPPROTO_TCP || proto == IPPROTO_UDP) notice("%s data:%d %s[%s]%d->[%s]%d: %8.*B", cp, len-offs, proto == IPPROTO_TCP ? tcp_flag_decode(get_tcpflags(bp)) : "", fromstr, get_sport(bp), tostr, get_dport(bp), len-offs, bp+offs); else notice("%s %d bytes %s->%s: %8.*B", cp, len, fromstr, tostr, len, bp); return; } if ((cp = protocol_name(proto)) == NULL) { (void) slprintf(sbuf, sizeof (sbuf), "0x#X", proto); cp = (const char *)sbuf; } notice("link activity: %s %16.*B", cp, len, bp); } /* * handle_bind() */ static void handle_bind(u_int32_t reason) { /* * Here we might, in the future, handle DL_BIND_REQ notifications * in order to close and re-open a NCP when certain interface * parameters (addresses, etc.) are changed via external mechanisms * such as through the "ifconfig" program. */ switch (reason) { case PPP_LINKSTAT_IPV4_BOUND: break; #ifdef INET6 case PPP_LINKSTAT_IPV6_BOUND: break; #endif default: error("handle_bind: unrecognized reason"); break; } } /* * handle_unbind() */ static void handle_unbind(u_int32_t reason) { bool iff_up_isset; int rc; static const char *unplumb_str = "unplumbed"; static const char *down_str = "downed"; /* * Since the kernel driver (sppp) notifies this daemon of the * DLPI bind/unbind activities (for the purpose of bringing down * a NCP), we need to explicitly test the "actual" status of * the interface instance for which the notification is destined * from. This is because /dev/ip performs multiple DLPI attach- * bind-unbind-detach during the early life of the interface, * and when certain interface parameters change. A DL_UNBIND_REQ * coming down to the sppp driver from /dev/ip (which results in * our receiving of the PPP_LINKSTAT_*_UNBOUND link status message) * is not enough to conclude that the interface has been marked * DOWN (its IFF_UP bit is cleared) or is going away. Therefore, * we should query /dev/ip directly, upon receiving such *_UNBOUND * notification, to determine whether the interface is DOWN * for real, and only take the necessary actions when IFF_UP * bit for the interface instance is actually cleared. */ switch (reason) { case PPP_LINKSTAT_IPV4_UNBOUND: (void) sleep(1); rc = giflags(IFF_UP, &iff_up_isset); if (!iff_up_isset) { if_is_up = 0; ipmuxid = -1; info("IPv4 interface %s by administrator", ((rc < 0 && rc == ENXIO) ? unplumb_str : down_str)); fsm_close(&ipcp_fsm[0], "administratively disconnected"); } break; #ifdef INET6 case PPP_LINKSTAT_IPV6_UNBOUND: (void) sleep(1); rc = giflags(IFF_UP, &iff_up_isset); if (!iff_up_isset) { if6_is_up = 0; ip6muxid = -1; info("IPv6 interface %s by administrator", ((rc < 0 && rc == ENXIO) ? unplumb_str : down_str)); fsm_close(&ipv6cp_fsm[0], "administratively disconnected"); } break; #endif default: error("handle_unbind: unrecognized reason"); break; } } /* * read_packet() * * Get a PPP packet from the serial device. */ int read_packet(buf) uchar_t *buf; { struct strbuf ctrl; struct strbuf data; int flags; int len; int rc; struct ppp_ls *plp; uint32_t ctrlbuf[1536 / sizeof (uint32_t)]; bool flushmode; flushmode = 0; for (;;) { data.maxlen = PPP_MRU + PPP_HDRLEN; data.buf = (caddr_t)buf; ctrl.maxlen = sizeof (ctrlbuf); ctrl.buf = (caddr_t)ctrlbuf; flags = 0; rc = len = getmsg(pppfd, &ctrl, &data, &flags); if (sys_read_packet_hook != NULL) { rc = len = (*sys_read_packet_hook)(len, &ctrl, &data, flags); } if (len < 0) { if (errno == EAGAIN || errno == EINTR) { return (-1); } fatal("Error reading packet: %m"); } if ((data.len > 0) && (ctrl.len < 0)) { /* * If there's more data on stream head, keep reading * but discard, since the stream is now corrupt. */ if (rc & MOREDATA) { dbglog("More data; input packet garbled"); flushmode = 1; continue; } if (flushmode) return (-1); return (data.len); } else if (ctrl.len > 0) { /* * If there's more ctl on stream head, keep reading, * but start discarding. We can't deal with fragmented * messages at all. */ if (rc & MORECTL) { dbglog("More control; stream garbled"); flushmode = 1; continue; } if (flushmode) return (-1); if (ctrl.len < sizeof (struct ppp_ls)) { warn("read_packet: ctl.len %d < " "sizeof ppp_ls %d", ctrl.len, sizeof (struct ppp_ls)); return (-1); } plp = (struct ppp_ls *)ctrlbuf; if (plp->magic != PPPLSMAGIC) { /* Skip, as we don't understand it */ dbglog("read_packet: unrecognized control %lX", plp->magic); return (-1); } lastlink_status = plp->ppp_message; switch (plp->ppp_message) { case PPP_LINKSTAT_HANGUP: return (0); /* Hangup */ /* For use by integrated drivers. */ case PPP_LINKSTAT_UP: lcp_lowerdown(0); lcp_lowerup(0); return (0); case PPP_LINKSTAT_NEEDUP: if (data.len > 0) dump_packet(buf, data.len); return (-1); /* Demand dial */ case PPP_LINKSTAT_IPV4_UNBOUND: (void) handle_unbind(plp->ppp_message); return (-1); case PPP_LINKSTAT_IPV4_BOUND: (void) handle_bind(plp->ppp_message); return (-1); #ifdef INET6 case PPP_LINKSTAT_IPV6_UNBOUND: (void) handle_unbind(plp->ppp_message); return (-1); case PPP_LINKSTAT_IPV6_BOUND: (void) handle_bind(plp->ppp_message); return (-1); #endif default: warn("read_packet: unknown link status type!"); return (-1); } } else { /* * We get here on zero length data or control. */ return (-1); } } } /* * get_loop_output() * * Get outgoing packets from the ppp device, and detect when we want to bring * the real link up. Return value is 1 if we need to bring up the link, or 0 * otherwise. */ int get_loop_output() { int loops; /* * In the Solaris 2.x kernel-level portion implementation, packets * which are received on a demand-dial interface are immediately * discarded, and a notification message is sent up the control * stream to the pppd process. Therefore, the call to read_packet() * below is merely there to wait for such message. */ lastlink_status = 0; loops = 0; while (read_packet(inpacket_buf) > 0) { if (++loops > 10) break; } return (lastlink_status == PPP_LINKSTAT_NEEDUP); } #ifdef MUX_FRAME /*ARGSUSED*/ void ppp_send_muxoption(unit, muxflag) int unit; u_int32_t muxflag; { uint32_t cf[2]; /* * Since muxed frame feature is implemented in the async module, * don't send down the ioctl in the synchronous case. */ if (!sync_serial && fdmuxid >= 0 && pppfd != -1) { cf[0] = muxflag; cf[1] = X_MUXMASK; if (strioctl(pppfd, PPPIO_MUX, cf, sizeof (cf), 0) < 0) { error("Couldn't set mux option: %m"); } } } /*ARGSUSED*/ void ppp_recv_muxoption(unit, muxflag) int unit; u_int32_t muxflag; { uint32_t cf[2]; /* * Since muxed frame feature is implemented in the async module, * don't send down the ioctl in the synchronous case. */ if (!sync_serial && fdmuxid >= 0 && pppfd != -1) { cf[0] = muxflag; cf[1] = R_MUXMASK; if (strioctl(pppfd, PPPIO_MUX, cf, sizeof (cf), 0) < 0) { error("Couldn't set receive mux option: %m"); } } } #endif /* * ppp_send_config() * * Configure the transmit characteristics of the ppp interface. */ /*ARGSUSED*/ void ppp_send_config(unit, mtu, asyncmap, pcomp, accomp) int unit; int mtu; u_int32_t asyncmap; int pcomp; int accomp; { uint32_t cf[2]; if (pppfd == -1) { error("ppp_send_config called with invalid device handle"); return; } cf[0] = link_mtu = mtu; if (strioctl(pppfd, PPPIO_MTU, cf, sizeof (cf[0]), 0) < 0) { if (hungup && errno == ENXIO) { return; } error("Couldn't set MTU: %m"); } if (fdmuxid != -1) { if (!sync_serial) { if (strioctl(pppfd, PPPIO_XACCM, &asyncmap, sizeof (asyncmap), 0) < 0) { error("Couldn't set transmit ACCM: %m"); } } cf[0] = (pcomp? COMP_PROT: 0) + (accomp? COMP_AC: 0); cf[1] = COMP_PROT | COMP_AC; if (any_compressions() && strioctl(pppfd, PPPIO_CFLAGS, cf, sizeof (cf), sizeof (cf[0])) < 0) { error("Couldn't set prot/AC compression: %m"); } } } /* * ppp_set_xaccm() * * Set the extended transmit ACCM for the interface. */ /*ARGSUSED*/ void ppp_set_xaccm(unit, accm) int unit; ext_accm accm; { if (sync_serial) { return; } if (fdmuxid != -1 && strioctl(pppfd, PPPIO_XACCM, accm, sizeof (ext_accm), 0) < 0) { if (!hungup || errno != ENXIO) { warn("Couldn't set extended ACCM: %m"); } } } /* * ppp_recv_config() * * Configure the receive-side characteristics of the ppp interface. */ /*ARGSUSED*/ void ppp_recv_config(unit, mru, asyncmap, pcomp, accomp) int unit; int mru; u_int32_t asyncmap; int pcomp; int accomp; { uint32_t cf[2]; if (pppfd == -1) { error("ppp_recv_config called with invalid device handle"); return; } cf[0] = mru; if (strioctl(pppfd, PPPIO_MRU, cf, sizeof (cf[0]), 0) < 0) { if (hungup && errno == ENXIO) { return; } error("Couldn't set MRU: %m"); } if (fdmuxid != -1) { if (!sync_serial) { if (strioctl(pppfd, PPPIO_RACCM, &asyncmap, sizeof (asyncmap), 0) < 0) { error("Couldn't set receive ACCM: %m"); } } cf[0] = (pcomp ? DECOMP_PROT : 0) + (accomp ? DECOMP_AC : 0); cf[1] = DECOMP_PROT | DECOMP_AC; if (any_compressions() && strioctl(pppfd, PPPIO_CFLAGS, cf, sizeof (cf), sizeof (cf[0])) < 0) { error("Couldn't set prot/AC decompression: %m"); } } } #ifdef NEGOTIATE_FCS /* * ppp_send_fcs() * * Configure the sender-side FCS. */ /*ARGSUSED*/ void ppp_send_fcs(unit, fcstype) int unit, fcstype; { uint32_t fcs; if (sync_serial) { return; } if (fcstype & FCSALT_32) { fcs = PPPFCS_32; } else if (fcstype & FCSALT_NULL) { fcs = PPPFCS_NONE; } else { fcs = PPPFCS_16; } if (strioctl(pppfd, PPPIO_XFCS, &fcs, sizeof (fcs), 0) < 0) { warn("Couldn't set transmit FCS: %m"); } } /* * ppp_recv_fcs() * * Configure the receiver-side FCS. */ /*ARGSUSED*/ void ppp_recv_fcs(unit, fcstype) int unit, fcstype; { uint32_t fcs; if (sync_serial) { return; } if (fcstype & FCSALT_32) { fcs = PPPFCS_32; } else if (fcstype & FCSALT_NULL) { fcs = PPPFCS_NONE; } else { fcs = PPPFCS_16; } if (strioctl(pppfd, PPPIO_RFCS, &fcs, sizeof (fcs), 0) < 0) { warn("Couldn't set receive FCS: %m"); } } #endif /* * ccp_test() * * Ask kernel whether a given compression method is acceptable for use. */ /*ARGSUSED*/ int ccp_test(unit, opt_ptr, opt_len, for_transmit) int unit; uchar_t *opt_ptr; int opt_len; int for_transmit; { if (strioctl(pppfd, (for_transmit ? PPPIO_XCOMP : PPPIO_RCOMP), opt_ptr, opt_len, 0) >= 0) { return (1); } warn("Error in %s ioctl: %m", (for_transmit ? "PPPIO_XCOMP" : "PPPIO_RCOMP")); return ((errno == ENOSR) ? 0 : -1); } #ifdef COMP_TUNE /* * ccp_tune() * * Tune compression effort level. */ /*ARGSUSED*/ void ccp_tune(unit, effort) int unit, effort; { uint32_t x; x = effort; if (strioctl(pppfd, PPPIO_COMPLEV, &x, sizeof (x), 0) < 0) { warn("unable to set compression effort level: %m"); } } #endif /* * ccp_flags_set() * * Inform kernel about the current state of CCP. */ /*ARGSUSED*/ void ccp_flags_set(unit, isopen, isup) int unit, isopen, isup; { uint32_t cf[2]; cf[0] = (isopen ? CCP_ISOPEN : 0) + (isup ? CCP_ISUP : 0); cf[1] = CCP_ISOPEN | CCP_ISUP | CCP_ERROR | CCP_FATALERROR; if (strioctl(pppfd, PPPIO_CFLAGS, cf, sizeof (cf), sizeof (cf[0])) < 0) { if (!hungup || errno != ENXIO) { error("Couldn't set kernel CCP state: %m"); } } } /* * get_idle_time() * * Return how long the link has been idle. */ /*ARGSUSED*/ int get_idle_time(u, pids) int u; struct ppp_idle *pids; { int rc; rc = strioctl(pppfd, PPPIO_GIDLE, pids, 0, sizeof (struct ppp_idle)); if (rc < 0) { warn("unable to obtain idle time: %m"); } return ((rc == 0) ? 1 : 0); } /* * get_ppp_stats() * * Return statistics for the link. */ /*ARGSUSED*/ int get_ppp_stats(u, stats) int u; struct pppd_stats *stats; { struct ppp_stats64 s64; struct ppp_stats s; /* Try first to get these from the 64-bit interface */ if (strioctl(pppfd, PPPIO_GETSTAT64, &s64, 0, sizeof (s64)) >= 0) { stats->bytes_in = s64.p.ppp_ibytes; stats->bytes_out = s64.p.ppp_obytes; stats->pkts_in = s64.p.ppp_ipackets; stats->pkts_out = s64.p.ppp_opackets; return (1); } if (strioctl(pppfd, PPPIO_GETSTAT, &s, 0, sizeof (s)) < 0) { error("Couldn't get link statistics: %m"); return (0); } stats->bytes_in = s.p.ppp_ibytes; stats->bytes_out = s.p.ppp_obytes; stats->pkts_in = s.p.ppp_ipackets; stats->pkts_out = s.p.ppp_opackets; return (1); } #if defined(FILTER_PACKETS) /* * set_filters() * * Transfer the pass and active filters to the kernel. */ int set_filters(pass, active) struct bpf_program *pass; struct bpf_program *active; { int ret = 1; if (pass->bf_len > 0) { if (strioctl(pppfd, PPPIO_PASSFILT, pass, sizeof (struct bpf_program), 0) < 0) { error("Couldn't set pass-filter in kernel: %m"); ret = 0; } } if (active->bf_len > 0) { if (strioctl(pppfd, PPPIO_ACTIVEFILT, active, sizeof (struct bpf_program), 0) < 0) { error("Couldn't set active-filter in kernel: %m"); ret = 0; } } return (ret); } #endif /* FILTER_PACKETS */ /* * ccp_fatal_error() * * Returns 1 if decompression was disabled as a result of an error detected * after decompression of a packet, 0 otherwise. This is necessary because * of patent nonsense. */ /*ARGSUSED*/ int ccp_fatal_error(unit) int unit; { uint32_t cf[2]; cf[0] = cf[1] = 0; if (strioctl(pppfd, PPPIO_CFLAGS, cf, sizeof (cf), sizeof (cf[0])) < 0) { if (errno != ENXIO && errno != EINVAL) { error("Couldn't get compression flags: %m"); } return (0); } return (cf[0] & CCP_FATALERROR); } /* * sifvjcomp() * * Config TCP header compression. */ /*ARGSUSED*/ int sifvjcomp(u, vjcomp, xcidcomp, xmaxcid) int u, vjcomp, xcidcomp, xmaxcid; { uint32_t cf[2]; uchar_t maxcid[2]; /* * Since VJ compression code is in the comp module, there's no * point of sending down any ioctls pertaining to VJ compression * when the module isn't pushed on the stream. */ if (!any_compressions()) { return (1); } if (vjcomp) { maxcid[0] = xcidcomp; maxcid[1] = 15; /* XXX should be rmaxcid */ if (strioctl(pppfd, PPPIO_VJINIT, maxcid, sizeof (maxcid), 0) < 0) { error("Couldn't initialize VJ compression: %m"); return (0); } } cf[0] = (vjcomp ? COMP_VJC + DECOMP_VJC : 0) /* XXX this is wrong */ + (xcidcomp? COMP_VJCCID + DECOMP_VJCCID: 0); cf[1] = COMP_VJC + DECOMP_VJC + COMP_VJCCID + DECOMP_VJCCID; if (strioctl(pppfd, PPPIO_CFLAGS, cf, sizeof (cf), sizeof (cf[0])) < 0) { if (vjcomp) { error("Couldn't enable VJ compression: %m"); } else { error("Couldn't disable VJ compression: %m"); } return (0); } return (1); } /* * siflags() * * Set or clear the IP interface flags. */ int siflags(f, set) u_int32_t f; int set; { struct ifreq ifr; if (!IPCP_ENABLED || (ipmuxid == -1)) { return (0); } if (ipfd == -1 && open_ipfd() == -1) return (0); BZERO(&ifr, sizeof (ifr)); (void) strlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name)); if (myioctl(ipfd, SIOCGIFFLAGS, &ifr) < 0) { error("Couldn't get IP interface flags: %m"); return (0); } if (set) { ifr.ifr_flags |= f; } else { ifr.ifr_flags &= ~f; } if (myioctl(ipfd, SIOCSIFFLAGS, &ifr) < 0) { error("Couldn't set IP interface flags: %m"); return (0); } return (1); } /* * sifup() * * Config the interface up and enable IP packets to pass. */ /*ARGSUSED*/ int sifup(u) int u; { if (if_is_up) { return (1); } else if (!IPCP_ENABLED) { warn("sifup called when IPCP is disabled"); return (0); } else if (ipmuxid == -1) { warn("sifup called in wrong state"); return (0); } else if (!siflags(IFF_UP, 1)) { error("Unable to mark the IP interface UP"); return (0); } if_is_up = 1; return (1); } /* * sifdown() * * Config the interface down and disable IP. Possibly called from die(), * so there shouldn't be any call to die() here. */ /*ARGSUSED*/ int sifdown(u) int u; { if (!IPCP_ENABLED) { warn("sifdown called when IPCP is disabled"); return (0); } else if (!if_is_up || (ipmuxid == -1)) { return (1); } else if (!siflags(IFF_UP, 0)) { error("Unable to mark the IP interface DOWN"); return (0); } if_is_up = 0; return (1); } /* * sifnpmode() * * Set the mode for handling packets for a given NP. Not worried * about performance here since this is done only rarely. */ /*ARGSUSED*/ int sifnpmode(u, proto, mode) int u; int proto; enum NPmode mode; { uint32_t npi[2]; const char *cp; static const struct npi_entry { enum NPmode ne_value; const char *ne_name; } npi_list[] = { { NPMODE_PASS, "pass" }, { NPMODE_DROP, "drop" }, { NPMODE_ERROR, "error" }, { NPMODE_QUEUE, "queue" }, }; int i; char pname[32], mname[32]; npi[0] = proto; npi[1] = (uint32_t)mode; cp = protocol_name(proto); if (cp == NULL) (void) slprintf(pname, sizeof (pname), "NP %04X", proto); else (void) strlcpy(pname, cp, sizeof (pname)); for (i = 0; i < Dim(npi_list); i++) if (npi_list[i].ne_value == mode) break; if (i >= Dim(npi_list)) (void) slprintf(mname, sizeof (mname), "mode %d", (int)mode); else (void) strlcpy(mname, npi_list[i].ne_name, sizeof (mname)); if ((proto == PPP_IP && !if_is_up) || (proto == PPP_IPV6 && !if6_is_up)) { dbglog("ignoring request to set %s to %s", pname, mname); return (1); } if (strioctl(pppfd, PPPIO_NPMODE, npi, sizeof (npi), 0) < 0) { error("unable to set %s to %s: %m", pname, mname); return (0); } return (1); } /* * sifmtu() * * Config the interface IP MTU. */ int sifmtu(mtu) int mtu; { struct ifreq ifr; if (!IPCP_ENABLED || (ipmuxid == -1)) { return (0); } if (ipfd == -1 && open_ipfd() == -1) return (0); BZERO(&ifr, sizeof (ifr)); (void) strlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name)); ifr.ifr_metric = mtu; if (myioctl(ipfd, SIOCSIFMTU, &ifr) < 0) { error("Couldn't set IP MTU on %s to %d: %m", ifr.ifr_name, mtu); return (0); } return (1); } /* * sifaddr() * * Config the interface IP addresses and netmask. */ /*ARGSUSED*/ int sifaddr(u, o, h, m) int u; u_int32_t o; u_int32_t h; u_int32_t m; { struct ifreq ifr; struct sockaddr_in sin; if (!IPCP_ENABLED || (ipmuxid == -1 && plumb_ipif(u) == 0)) { return (0); } if (ipfd == -1 && open_ipfd() == -1) return (0); /* * Set the IP interface MTU. */ if (!sifmtu(link_mtu)) { return (0); } /* * Set the IP interface local point-to-point address. */ BZERO(&sin, sizeof (sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = o; BZERO(&ifr, sizeof (ifr)); (void) strlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name)); ifr.ifr_addr = *(struct sockaddr *)&sin; if (myioctl(ipfd, SIOCSIFADDR, &ifr) < 0) { error("Couldn't set local IP address (%s): %m", ifr.ifr_name); return (0); } /* * Set the IP interface remote point-to-point address. */ sin.sin_addr.s_addr = h; ifr.ifr_dstaddr = *(struct sockaddr *)&sin; if (myioctl(ipfd, SIOCSIFDSTADDR, &ifr) < 0) { error("Couldn't set remote IP address (%s): %m", ifr.ifr_name); return (0); } remote_addr = h; return (1); } /* * cifaddr() * * Clear the interface IP addresses. */ /*ARGSUSED*/ int cifaddr(u, o, h) int u; u_int32_t o; u_int32_t h; { if (!IPCP_ENABLED) { return (0); } /* * Most of the work is done in sifdown(). */ remote_addr = 0; return (1); } /* * sifroute() * * Add or delete a route. */ /*ARGSUSED*/ static int sifroute(int u, u_int32_t l, u_int32_t g, int add, const char *str) { struct sockaddr_in sin_dst, sin_gtw; struct rtentry rt; if (!IPCP_ENABLED || (ipmuxid == -1)) { error("Can't %s route: IP is not enabled", str); return (0); } if (ipfd == -1 && open_ipfd() == -1) return (0); BZERO(&sin_dst, sizeof (sin_dst)); sin_dst.sin_family = AF_INET; sin_dst.sin_addr.s_addr = l; BZERO(&sin_gtw, sizeof (sin_gtw)); sin_gtw.sin_family = AF_INET; sin_gtw.sin_addr.s_addr = g; BZERO(&rt, sizeof (rt)); rt.rt_dst = *(struct sockaddr *)&sin_dst; rt.rt_gateway = *(struct sockaddr *)&sin_gtw; rt.rt_flags = (RTF_GATEWAY|RTF_STATIC); if (myioctl(ipfd, (add ? SIOCADDRT : SIOCDELRT), &rt) < 0) { error("Can't %s route: %m", str); return (0); } return (1); } /* * sifdefaultroute() * * Assign a default route through the address given. */ /*ARGSUSED*/ int sifdefaultroute(u, l, g) int u; u_int32_t l; u_int32_t g; { if (!sifroute(u, 0, g, 1, "add default")) { return (0); } default_route_gateway = g; return (1); } /* * cifdefaultroute() * * Delete a default route through the address given. */ /*ARGSUSED*/ int cifdefaultroute(u, l, g) int u; u_int32_t l; u_int32_t g; { if (!sifroute(u, 0, g, 0, "delete default")) { return (0); } default_route_gateway = 0; return (1); } /* * sifproxyarp() * * Make a proxy ARP entry for the peer. */ /*ARGSUSED*/ int sifproxyarp(unit, hisaddr, quietflag) int unit; u_int32_t hisaddr; int quietflag; { struct sockaddr_in sin; struct xarpreq arpreq; const uchar_t *cp; char *str = NULL; if (!IPCP_ENABLED || (ipmuxid == -1)) { return (0); } if (ipfd == -1 && open_ipfd() == -1) return (0); BZERO(&sin, sizeof (sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = hisaddr; BZERO(&arpreq, sizeof (arpreq)); if (!get_ether_addr(hisaddr, &arpreq.xarp_ha, quietflag)) { return (0); } BCOPY(&sin, &arpreq.xarp_pa, sizeof (sin)); arpreq.xarp_flags = ATF_PERM | ATF_PUBL; arpreq.xarp_ha.sdl_family = AF_LINK; if (myioctl(ipfd, SIOCSXARP, (caddr_t)&arpreq) < 0) { if (!quietflag) error("Couldn't set proxy ARP entry: %m"); return (0); } cp = (const uchar_t *)LLADDR(&arpreq.xarp_ha); str = _link_ntoa(cp, str, arpreq.xarp_ha.sdl_alen, IFT_OTHER); if (str != NULL) { dbglog("established proxy ARP for %I using %s", hisaddr, str); free(str); } proxy_arp_addr = hisaddr; return (1); } /* * cifproxyarp() * * Delete the proxy ARP entry for the peer. */ /*ARGSUSED*/ int cifproxyarp(unit, hisaddr) int unit; u_int32_t hisaddr; { struct sockaddr_in sin; struct xarpreq arpreq; if (!IPCP_ENABLED || (ipmuxid == -1)) { return (0); } if (ipfd == -1 && open_ipfd() == -1) return (0); BZERO(&sin, sizeof (sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = hisaddr; BZERO(&arpreq, sizeof (arpreq)); BCOPY(&sin, &arpreq.xarp_pa, sizeof (sin)); arpreq.xarp_ha.sdl_family = AF_LINK; if (myioctl(ipfd, SIOCDXARP, (caddr_t)&arpreq) < 0) { error("Couldn't delete proxy ARP entry: %m"); return (0); } proxy_arp_addr = 0; return (1); } /* * get_ether_addr() * * Get the hardware address of an interface on the the same subnet as * ipaddr. This routine uses old-style interfaces for intentional * backward compatibility -- SIOCGLIF* isn't in older Solaris * releases. */ static int get_ether_addr(u_int32_t ipaddr, struct sockaddr_dl *hwaddr, int quietflag) { struct ifreq *ifr, *ifend, ifreq; int nif, s, retv; struct ifconf ifc; u_int32_t ina, mask; struct xarpreq req; struct sockaddr_in sin; if (ipfd == -1 && open_ipfd() == -1) return (0); /* * Scan through the system's network interfaces. */ if (myioctl(ipfd, SIOCGIFNUM, &nif) < 0) { nif = MAXIFS; } if (nif <= 0) return (0); ifc.ifc_len = nif * sizeof (struct ifreq); ifc.ifc_buf = (caddr_t)malloc(ifc.ifc_len); if (ifc.ifc_buf == NULL) { return (0); } if (myioctl(ipfd, SIOCGIFCONF, &ifc) < 0) { error("Couldn't get system interface list: %m"); free(ifc.ifc_buf); return (0); } /* LINTED */ ifend = (struct ifreq *)(ifc.ifc_buf + ifc.ifc_len); for (ifr = ifc.ifc_req; ifr < ifend; ++ifr) { if (ifr->ifr_addr.sa_family != AF_INET) { continue; } /* * Check that the interface is up, and not * point-to-point or loopback. */ (void) strlcpy(ifreq.ifr_name, ifr->ifr_name, sizeof (ifreq.ifr_name)); if (myioctl(ipfd, SIOCGIFFLAGS, &ifreq) < 0) { continue; } if ((ifreq.ifr_flags & (IFF_UP|IFF_BROADCAST|IFF_POINTOPOINT| IFF_LOOPBACK|IFF_NOARP)) != (IFF_UP|IFF_BROADCAST)) { continue; } /* * Get its netmask and check that it's on the right subnet. */ if (myioctl(ipfd, SIOCGIFNETMASK, &ifreq) < 0) { continue; } (void) memcpy(&sin, &ifr->ifr_addr, sizeof (sin)); ina = sin.sin_addr.s_addr; (void) memcpy(&sin, &ifreq.ifr_addr, sizeof (sin)); mask = sin.sin_addr.s_addr; if ((ipaddr & mask) == (ina & mask)) { break; } } if (ifr >= ifend) { if (!quietflag) warn("No suitable interface found for proxy ARP of %I", ipaddr); free(ifc.ifc_buf); return (0); } info("found interface %s for proxy ARP of %I", ifr->ifr_name, ipaddr); /* * New way - get the address by doing an arp request. */ s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { error("get_ether_addr: error opening IP socket: %m"); free(ifc.ifc_buf); return (0); } BZERO(&sin, sizeof (sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = ina; BZERO(&req, sizeof (req)); BCOPY(&sin, &req.xarp_pa, sizeof (sin)); req.xarp_ha.sdl_family = AF_LINK; if (myioctl(s, SIOCGXARP, &req) < 0) { error("Couldn't get ARP entry for %I: %m", ina); retv = 0; } else { (void) memcpy(hwaddr, &req.xarp_ha, sizeof (struct sockaddr_dl)); retv = 1; } (void) close(s); free(ifc.ifc_buf); return (retv); } /* * GetMask() * * Return mask (bogus, but needed for compatibility with other platforms). */ /*ARGSUSED*/ u_int32_t GetMask(addr) u_int32_t addr; { return (0xffffffffUL); } /* * logwtmp() * * Write an accounting record to the /var/adm/wtmp file. */ /*ARGSUSED*/ void logwtmp(line, name, host) const char *line; const char *name; const char *host; { static struct utmpx utmpx; if (name[0] != '\0') { /* * logging in */ (void) strncpy(utmpx.ut_user, name, sizeof (utmpx.ut_user)); (void) strncpy(utmpx.ut_id, ifname, sizeof (utmpx.ut_id)); (void) strncpy(utmpx.ut_line, line, sizeof (utmpx.ut_line)); utmpx.ut_pid = getpid(); utmpx.ut_type = USER_PROCESS; } else { utmpx.ut_type = DEAD_PROCESS; } (void) gettimeofday(&utmpx.ut_tv, NULL); updwtmpx("/var/adm/wtmpx", &utmpx); } /* * get_host_seed() * * Return the serial number of this machine. */ int get_host_seed() { char buf[32]; if (sysinfo(SI_HW_SERIAL, buf, sizeof (buf)) < 0) { error("sysinfo: %m"); return (0); } return ((int)strtoul(buf, NULL, 16)); } /* * strioctl() * * Wrapper for STREAMS I_STR ioctl. Masks out EINTR from caller. */ static int strioctl(int fd, int cmd, void *ptr, int ilen, int olen) { struct strioctl str; str.ic_cmd = cmd; str.ic_timout = PPPSTRTIMOUT; str.ic_len = ilen; str.ic_dp = ptr; if (myioctl(fd, I_STR, &str) == -1) { return (-1); } if (str.ic_len != olen) { dbglog("strioctl: expected %d bytes, got %d for cmd %x\n", olen, str.ic_len, cmd); } return (0); } /* * have_route_to() * * Determine if the system has a route to the specified IP address. * Returns 0 if not, 1 if so, -1 if we can't tell. `addr' is in network * byte order. For demand mode to work properly, we have to ignore routes * through our own interface. XXX Would be nice to use routing socket. */ int have_route_to(addr) u_int32_t addr; { int r, flags, i; struct { struct T_optmgmt_req req; struct opthdr hdr; } req; union { struct T_optmgmt_ack ack; unsigned char space[64]; } ack; struct opthdr *rh; struct strbuf cbuf, dbuf; int nroutes; mib2_ipRouteEntry_t routes[8]; mib2_ipRouteEntry_t *rp; if (ipfd == -1 && open_ipfd() == -1) return (0); req.req.PRIM_type = T_OPTMGMT_REQ; req.req.OPT_offset = (caddr_t)&req.hdr - (caddr_t)&req; req.req.OPT_length = sizeof (req.hdr); #ifdef T_CURRENT req.req.MGMT_flags = T_CURRENT; #else /* Old-style */ req.req.MGMT_flags = T_CHECK; #endif req.hdr.level = MIB2_IP; req.hdr.name = 0; req.hdr.len = 0; cbuf.buf = (caddr_t)&req; cbuf.len = sizeof (req); if (putmsg(ipfd, &cbuf, NULL, 0) == -1) { warn("have_route_to: putmsg: %m"); return (-1); } for (;;) { cbuf.buf = (caddr_t)&ack; cbuf.maxlen = sizeof (ack); dbuf.buf = (caddr_t)routes; dbuf.maxlen = sizeof (routes); flags = 0; r = getmsg(ipfd, &cbuf, &dbuf, &flags); if (r == -1) { warn("have_route_to: getmsg: %m"); return (-1); } if (cbuf.len < sizeof (struct T_optmgmt_ack) || ack.ack.PRIM_type != T_OPTMGMT_ACK || ack.ack.MGMT_flags != T_SUCCESS || ack.ack.OPT_length < sizeof (struct opthdr)) { dbglog("have_route_to: bad message len=%d prim=%d", cbuf.len, ack.ack.PRIM_type); return (-1); } /* LINTED */ rh = (struct opthdr *)((caddr_t)&ack + ack.ack.OPT_offset); if (rh->level == 0 && rh->name == 0) { break; } if (rh->level != MIB2_IP || rh->name != MIB2_IP_21) { while (r == MOREDATA) { r = getmsg(ipfd, NULL, &dbuf, &flags); } continue; } /* * Note that we have to skip routes to our own * interface in order for demand dial to work. * * XXX awful hack here. We don't know our own * ifIndex, so we can't check ipRouteIfIndex here. * Instead, we check the next hop address. */ for (;;) { nroutes = dbuf.len / sizeof (mib2_ipRouteEntry_t); for (rp = routes, i = 0; i < nroutes; ++i, ++rp) { if (rp->ipRouteNextHop != remote_addr && ((addr ^ rp->ipRouteDest) & rp->ipRouteMask) == 0) { dbglog("have route to %I/%I via %I", rp->ipRouteDest, rp->ipRouteMask, rp->ipRouteNextHop); return (1); } } if (r == 0) { break; } r = getmsg(ipfd, NULL, &dbuf, &flags); } } return (0); } /* * get_pty() * * Get a pty master/slave pair and chown the slave side to the uid given. * Assumes slave_name points to MAXPATHLEN bytes of space. */ int get_pty(master_fdp, slave_fdp, slave_name, uid) int *master_fdp; int *slave_fdp; char *slave_name; int uid; { int mfd; int sfd; char *pty_name; mfd = open("/dev/ptmx", O_NOCTTY | O_RDWR); if (mfd < 0) { error("Couldn't open pty master: %m"); return (0); } pty_name = ptsname(mfd); if (pty_name == NULL) { dbglog("Didn't get pty slave name on first try; sleeping."); /* In case "grow" operation is in progress; try again. */ (void) sleep(1); pty_name = ptsname(mfd); } if (pty_name == NULL) { error("Couldn't get name of pty slave"); (void) close(mfd); return (0); } if (chown(pty_name, uid, -1) < 0) { warn("Couldn't change owner of pty slave: %m"); } if (chmod(pty_name, S_IRUSR | S_IWUSR) < 0) { warn("Couldn't change permissions on pty slave: %m"); } if (unlockpt(mfd) < 0) { warn("Couldn't unlock pty slave: %m"); } sfd = open(pty_name, O_RDWR); if (sfd < 0) { error("Couldn't open pty slave %s: %m", pty_name); (void) close(mfd); return (0); } if (myioctl(sfd, I_PUSH, "ptem") < 0) { warn("Couldn't push ptem module on pty slave: %m"); } dbglog("Using %s; master fd %d, slave fd %d", pty_name, mfd, sfd); (void) strlcpy(slave_name, pty_name, MAXPATHLEN); *master_fdp = mfd; *slave_fdp = sfd; return (1); } #ifdef INET6 static int open_udp6fd(void) { int udp6fd; udp6fd = open(UDP6_DEV_NAME, O_RDWR | O_NONBLOCK, 0); if (udp6fd < 0) { error("Couldn't open UDPv6 device (%s): %m", UDP6_DEV_NAME); } return (udp6fd); } /* * plumb_ip6if() * * Perform IPv6 interface plumbing. */ /*ARGSUSED*/ static int plumb_ip6if(int unit) { int udp6fd = -1, tmpfd; uint32_t x; struct lifreq lifr; if (!IPV6CP_ENABLED || (ifunit == -1) || (pppfd == -1)) { return (0); } if (plumbed) return (1); if (ip6fd == -1 && open_ip6fd() == -1) return (0); if (use_plink && (udp6fd = open_udp6fd()) == -1) return (0); tmpfd = open(drvnam, O_RDWR | O_NONBLOCK, 0); if (tmpfd < 0) { error("Couldn't open PPP device (%s): %m", drvnam); if (udp6fd != -1) (void) close(udp6fd); return (0); } if (kdebugflag & 1) { x = PPPDBG_LOG + PPPDBG_DRIVER; if (strioctl(tmpfd, PPPIO_DEBUG, &x, sizeof (x), 0) < 0) { warn("PPPIO_DEBUG ioctl for mux failed: %m"); } } if (myioctl(tmpfd, I_PUSH, IP_MOD_NAME) < 0) { error("Couldn't push IP module(%s): %m", IP_MOD_NAME); goto err_ret; } /* * Sets interface ppa and flags (refer to comments in plumb_ipif for * the IF_UNITSEL ioctl). In addition, the IFF_IPV6 bit must be set in * order to declare this as an IPv6 interface. */ BZERO(&lifr, sizeof (lifr)); if (myioctl(tmpfd, SIOCGLIFFLAGS, &lifr) < 0) { error("Couldn't get IPv6 interface flags: %m"); goto err_ret; } lifr.lifr_flags |= IFF_IPV6; lifr.lifr_flags &= ~(IFF_BROADCAST | IFF_IPV4); lifr.lifr_ppa = ifunit; (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); if (myioctl(tmpfd, SIOCSLIFNAME, &lifr) < 0) { error("Can't set ifname for unit %d: %m", ifunit); goto err_ret; } if (use_plink) { ip6muxid = myioctl(udp6fd, I_PLINK, (void *)tmpfd); if (ip6muxid < 0) { error("Can't I_PLINK PPP device to IPv6: %m"); goto err_ret; } } else { ip6muxid = myioctl(ip6fd, I_LINK, (void *)tmpfd); if (ip6muxid < 0) { error("Can't I_LINK PPP device to IPv6: %m"); goto err_ret; } } lifr.lifr_ip_muxid = ip6muxid; lifr.lifr_arp_muxid = -1; if (myioctl(ip6fd, SIOCSLIFMUXID, (caddr_t)&lifr) < 0) { error("Can't set mux ID: SIOCSLIFMUXID: %m"); goto err_ret; } (void) close(tmpfd); if (udp6fd != -1) (void) close(udp6fd); return (1); err_ret: (void) close(tmpfd); if (udp6fd != -1) (void) close(udp6fd); return (0); } /* * unplumb_ip6if() * * Perform IPv6 interface unplumbing. Possibly called from die(), so there * shouldn't be any call to die() here. */ static int unplumb_ip6if(int unit) { int udp6fd = -1, fd = -1; int id; struct lifreq lifr; if (!IPV6CP_ENABLED || ifunit == -1) { return (0); } if (!plumbed && (ip6muxid == -1 || (ip6fd == -1 && !use_plink))) { return (1); } id = ip6muxid; if (!plumbed && use_plink) { if ((udp6fd = open_udp6fd()) == -1) return (0); /* * Note: must re-get mux ID, since any intervening * ifconfigs will change this. */ BZERO(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); if (myioctl(ip6fd, SIOCGLIFMUXID, (caddr_t)&lifr) < 0) { warn("Can't get mux fd: SIOCGLIFMUXID: %m"); } else { id = lifr.lifr_ip_muxid; fd = myioctl(udp6fd, _I_MUXID2FD, (void *)id); if (fd < 0) { warn("Can't get mux fd: _I_MUXID2FD: %m"); } } } /* * Mark down and unlink the IPv6 interface. */ (void) sif6down(unit); if (plumbed) return (1); ip6muxid = -1; if (use_plink) { if ((fd = myioctl(udp6fd, _I_MUXID2FD, (void *)id)) < 0) { error("Can't recapture mux fd: _I_MUXID2FD: %m"); (void) close(udp6fd); return (0); } if (myioctl(udp6fd, I_PUNLINK, (void *)id) < 0) { error("Can't I_PUNLINK PPP from IPv6: %m"); (void) close(fd); (void) close(udp6fd); return (0); } (void) close(fd); (void) close(udp6fd); } else { if (myioctl(ip6fd, I_UNLINK, (void *)id) < 0) { error("Can't I_UNLINK PPP from IPv6: %m"); return (0); } } return (1); } /* * sif6flags() * * Set or clear the IPv6 interface flags. */ int sif6flags(f, set) u_int32_t f; int set; { struct lifreq lifr; int fd; if (!IPV6CP_ENABLED || (ip6muxid == -1)) { return (0); } fd = socket(AF_INET6, SOCK_DGRAM, 0); if (fd < 0) { error("sif6flags: error opening IPv6 socket: %m"); return (0); } BZERO(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); if (myioctl(fd, SIOCGLIFFLAGS, &lifr) < 0) { error("Couldn't get IPv6 interface flags: %m"); (void) close(fd); return (0); } if (set) { lifr.lifr_flags |= f; } else { lifr.lifr_flags &= ~f; } (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); if (myioctl(fd, SIOCSLIFFLAGS, &lifr) < 0) { error("Couldn't set IPv6 interface flags: %m"); (void) close(fd); return (0); } (void) close(fd); return (1); } /* * sif6up() * * Config the IPv6 interface up and enable IPv6 packets to pass. */ /*ARGSUSED*/ int sif6up(unit) int unit; { if (if6_is_up) { return (1); } else if (!IPV6CP_ENABLED) { warn("sif6up called when IPV6CP is disabled"); return (0); } else if (ip6muxid == -1) { warn("sif6up called in wrong state"); return (0); } else if (!sif6flags(IFF_UP, 1)) { error("Unable to mark the IPv6 interface UP"); return (0); } if6_is_up = 1; return (1); } /* * sif6down() * * Config the IPv6 interface down and disable IPv6. Possibly called from * die(), so there shouldn't be any call to die() here. */ /*ARGSUSED*/ int sif6down(unit) int unit; { if (!IPV6CP_ENABLED) { warn("sif6down called when IPV6CP is disabled"); return (0); } else if (!if6_is_up || (ip6muxid == -1)) { return (1); } else if (!sif6flags(IFF_UP, 0)) { error("Unable to mark the IPv6 interface DOWN"); return (0); } if6_is_up = 0; return (1); } /* * sif6mtu() * * Config the IPv6 interface MTU. */ int sif6mtu(mtu) int mtu; { struct lifreq lifr; int s; if (!IPV6CP_ENABLED || (ip6muxid == -1)) { return (0); } s = socket(AF_INET6, SOCK_DGRAM, 0); if (s < 0) { error("sif6mtu: error opening IPv6 socket: %m"); return (0); } BZERO(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); lifr.lifr_mtu = mtu; if (myioctl(s, SIOCSLIFMTU, &lifr) < 0) { error("Couldn't set IPv6 MTU (%s): %m", lifr.lifr_name); (void) close(s); return (0); } (void) close(s); return (1); } /* * sif6addr() * * Config the interface with an IPv6 link-local address. */ /*ARGSUSED*/ int sif6addr(unit, ourid, hisid) int unit; eui64_t ourid; eui64_t hisid; { struct lifreq lifr; struct sockaddr_storage laddr; struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&laddr; int fd; if (!IPV6CP_ENABLED || (ip6muxid == -1 && plumb_ip6if(unit) == 0)) { return (0); } fd = socket(AF_INET6, SOCK_DGRAM, 0); if (fd < 0) { error("sif6addr: error opening IPv6 socket: %m"); return (0); } /* * Set the IPv6 interface MTU. */ if (!sif6mtu(link_mtu)) { (void) close(fd); return (0); } /* * Set the interface address token. Do this because /dev/ppp responds * to DL_PHYS_ADDR_REQ with zero values, hence the interface token * came to be zero too, and without this, in.ndpd will complain. */ BZERO(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); BZERO(sin6, sizeof (struct sockaddr_in6)); IN6_LLTOKEN_FROM_EUI64(lifr, sin6, ourid); if (myioctl(fd, SIOCSLIFTOKEN, &lifr) < 0) { error("Couldn't set IPv6 token (%s): %m", lifr.lifr_name); (void) close(fd); return (0); } /* * Set the IPv6 interface local point-to-point address. */ IN6_LLADDR_FROM_EUI64(lifr, sin6, ourid); if (myioctl(fd, SIOCSLIFADDR, &lifr) < 0) { error("Couldn't set local IPv6 address (%s): %m", lifr.lifr_name); (void) close(fd); return (0); } /* * Set the IPv6 interface local point-to-point address. */ BZERO(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); IN6_LLADDR_FROM_EUI64(lifr, sin6, hisid); if (myioctl(fd, SIOCSLIFDSTADDR, &lifr) < 0) { error("Couldn't set remote IPv6 address (%s): %m", lifr.lifr_name); (void) close(fd); return (0); } (void) close(fd); return (1); } /* * cif6addr() */ /*ARGSUSED*/ int cif6addr(u, o, h) int u; eui64_t o; eui64_t h; { if (!IPV6CP_ENABLED) { return (0); } /* * Do nothing here, as everything has been done in sif6down(). */ return (1); } /* * ether_to_eui64() * * Convert 48-bit Ethernet address into 64-bit EUI. Walks the list of valid * ethernet interfaces, and convert the first found 48-bit MAC address into * EUI 64. caller also assumes that the system has a properly configured * Ethernet interface for this function to return non-zero. */ int ether_to_eui64(p_eui64) eui64_t *p_eui64; { struct ether_addr eth_addr; if (p_eui64 == NULL) { return (0); } if (!get_first_hwaddr(eth_addr.ether_addr_octet, sizeof (eth_addr.ether_addr_octet))) { return (0); } /* * And convert the EUI-48 into EUI-64, per RFC 2472 [sec 4.1] */ p_eui64->e8[0] = (eth_addr.ether_addr_octet[0] & 0xFF) | 0x02; p_eui64->e8[1] = (eth_addr.ether_addr_octet[1] & 0xFF); p_eui64->e8[2] = (eth_addr.ether_addr_octet[2] & 0xFF); p_eui64->e8[3] = 0xFF; p_eui64->e8[4] = 0xFE; p_eui64->e8[5] = (eth_addr.ether_addr_octet[3] & 0xFF); p_eui64->e8[6] = (eth_addr.ether_addr_octet[4] & 0xFF); p_eui64->e8[7] = (eth_addr.ether_addr_octet[5] & 0xFF); return (1); } #endif /* INET6 */ struct bit_ent { int val; char *off, *on; }; /* see sbuf[] below if you change this list */ static struct bit_ent bit_list[] = { { TIOCM_DTR, "dtr", "DTR" }, { TIOCM_RTS, "rts", "RTS" }, { TIOCM_CTS, "cts", "CTS" }, { TIOCM_CD, "dcd", "DCD" }, { TIOCM_RI, "ri", "RI" }, { TIOCM_DSR, "dsr", "DSR" }, #if 0 { TIOCM_LE, "disabled", "ENABLED" }, { TIOCM_ST, NULL, "2nd-XMIT" }, { TIOCM_SR, NULL, "2nd-RECV" }, #endif { 0, NULL, NULL } }; static void getbits(int fd, char *name, FILE *strptr) { int nmods, i; struct str_list strlist; struct bit_ent *be; int mstate; char sbuf[50]; /* sum of string lengths in bit_list */ char *str; nmods = ioctl(fd, I_LIST, NULL); if (nmods < 0) { error("unable to get module count: %m"); } else { strlist.sl_nmods = nmods; strlist.sl_modlist = malloc(sizeof (struct str_mlist) * nmods); if (strlist.sl_modlist == NULL) novm("module list"); if (ioctl(fd, I_LIST, (caddr_t)&strlist) < 0) { error("unable to get module names: %m"); } else { for (i = 0; i < strlist.sl_nmods; i++) (void) flprintf(strptr, "%d: %s", i, strlist.sl_modlist[i].l_name); free(strlist.sl_modlist); } } if (ioctl(fd, TIOCMGET, &mstate) < 0) { error("unable to get modem state: %m"); } else { sbuf[0] = '\0'; for (be = bit_list; be->val != 0; be++) { str = (be->val & mstate) ? be->on : be->off; if (str != NULL) { if (sbuf[0] != '\0') (void) strcat(sbuf, " "); (void) strcat(sbuf, str); } } (void) flprintf(strptr, "%s: %s\n", name, sbuf); } } /* * Print state of serial link. The stream might be linked under the * /dev/sppp driver. If it is, then it's necessary to unlink it first * and relink it when done. Otherwise, it's not possible to use * ioctl() on the stream. */ void sys_print_state(FILE *strptr) { bool was_linked; if (pppfd == -1) return; if (ttyfd == -1) { (void) flprintf(strptr, "serial link is not active"); return; } was_linked = fdmuxid != -1; if (was_linked && ioctl(pppfd, I_UNLINK, fdmuxid) == -1) { error("I_UNLINK: %m"); } else { fdmuxid = -1; getbits(ttyfd, devnam, strptr); if (was_linked && (fdmuxid = ioctl(pppfd, I_LINK, (void *)ttyfd)) == -1) fatal("I_LINK: %m"); } } /* * send ioctl to driver asking it to block packets with network protocol * proto in the control queue until the queue for proto is plumbed. */ void sys_block_proto(uint16_t proto) { if (proto > 0x7fff) { warn("cannot block: not a network proto 0x%lx\n", proto); return; } if (strioctl(pppfd, PPPIO_BLOCKNP, &proto, sizeof (proto), 0) < 0) { warn("PPPIO_BLOCKNP ioctl failed %m"); } } /* * send ioctl to driver asking it to release packets with network protocol * proto from control queue to the protocol specific queue. */ void sys_unblock_proto(uint16_t proto) { if (proto > 0x7fff) { warn("cannot unblock: not a network proto 0x%lx\n", proto); return; } if (strioctl(pppfd, PPPIO_UNBLOCKNP, &proto, sizeof (proto), 0) < 0) { warn("PPPIO_UNBLOCKNP ioctl failed %m"); } } /* * upap.c - User/Password Authentication Protocol. * * Copyright (c) 2000 by Sun Microsystems, Inc. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. * * SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES * * 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. */ #include #include #include "pppd.h" #include "upap.h" static bool hide_password = 1; /* * Command-line options. */ static option_t pap_option_list[] = { { "hide-password", o_bool, &hide_password, "Don't output passwords to log", 1 }, { "show-password", o_bool, &hide_password, "Show password string in debug log messages", 0 }, { "pap-restart", o_int, &upap[0].us_timeouttime, "Set retransmit timeout for PAP" }, { "pap-max-authreq", o_int, &upap[0].us_maxtransmits, "Max number of PAP Authenticate-Request sent" }, { "pap-max-receive", o_int, &upap[0].us_maxreceives, "Max allowable PAP Authenticate-Request received" }, { "pap-timeout", o_int, &upap[0].us_reqtimeout, "Set time limit for peer PAP authentication" }, { NULL } }; /* * Protocol entry points. */ static void upap_init __P((int)); static void upap_lowerup __P((int)); static void upap_lowerdown __P((int)); static void upap_input __P((int, u_char *, int)); static void upap_protrej __P((int)); static int upap_printpkt __P((u_char *, int, void (*) __P((void *, const char *, ...)), void *)); struct protent pap_protent = { PPP_PAP, upap_init, upap_input, upap_protrej, upap_lowerup, upap_lowerdown, NULL, NULL, upap_printpkt, NULL, 1, "PAP", NULL, pap_option_list, NULL, NULL, NULL }; upap_state upap[NUM_PPP]; /* UPAP state; one for each unit */ static void upap_timeout __P((void *)); static void upap_reqtimeout __P((void *)); static void upap_rauthreq __P((upap_state *, u_char *, int, int)); static void upap_rauthack __P((upap_state *, u_char *, int, int)); static void upap_rauthnak __P((upap_state *, u_char *, int, int)); static void upap_sauthreq __P((upap_state *)); static void upap_sresp __P((upap_state *, int, int, char *, int)); static const char * pap_cstate(clientstate) int clientstate; { static const char *cstate[] = { UPAPCS__NAMES }; static char buf[32]; if (clientstate < 0 || clientstate >= Dim(cstate)) { (void) slprintf(buf, sizeof (buf), "Cli#%d", clientstate); return ((const char *)buf); } return (cstate[clientstate]); } static const char * pap_sstate(serverstate) int serverstate; { static const char *sstate[] = { UPAPSS__NAMES }; static char buf[32]; if (serverstate < 0 || serverstate >= Dim(sstate)) { (void) slprintf(buf, sizeof (buf), "Srv#%d", serverstate); return ((const char *)buf); } return (sstate[serverstate]); } /* * upap_init - Initialize a UPAP unit. */ static void upap_init(unit) int unit; { upap_state *u = &upap[unit]; u->us_unit = unit; u->us_user = NULL; u->us_userlen = 0; u->us_passwd = NULL; u->us_clientstate = UPAPCS_INITIAL; u->us_serverstate = UPAPSS_INITIAL; u->us_id = 0; u->us_timeouttime = UPAP_DEFTIMEOUT; u->us_maxtransmits = 10; u->us_reqtimeout = UPAP_DEFREQTIME; u->us_maxreceives = 3; u->us_msg = ""; u->us_msglen = 0; } /* * upap_authwithpeer - Authenticate us with our peer (start client). * * Set new state and send authenticate's. */ void upap_authwithpeer(unit, user, password) int unit; char *user, *password; { upap_state *u = &upap[unit]; /* Save the username and password we're given */ u->us_user = user; u->us_userlen = strlen(user); u->us_passwd = password; u->us_transmits = 0; /* Lower layer up yet? */ if (u->us_clientstate == UPAPCS_INITIAL || u->us_clientstate == UPAPCS_PENDING) { u->us_clientstate = UPAPCS_PENDING; return; } upap_sauthreq(u); /* Start protocol */ } /* * upap_authpeer - Authenticate our peer (start server). * * Set new state. */ void upap_authpeer(unit) int unit; { upap_state *u = &upap[unit]; /* Lower layer up yet? */ if (u->us_serverstate == UPAPSS_INITIAL || u->us_serverstate == UPAPSS_PENDING) { u->us_serverstate = UPAPSS_PENDING; return; } u->us_serverstate = UPAPSS_LISTEN; u->us_receives = 0; if (u->us_reqtimeout > 0) TIMEOUT(upap_reqtimeout, u, u->us_reqtimeout); } /* * upap_timeout - Retransmission timer for sending auth-reqs expired. */ static void upap_timeout(arg) void *arg; { upap_state *u = (upap_state *) arg; if (u->us_clientstate != UPAPCS_AUTHREQ) return; if (u->us_transmits >= u->us_maxtransmits) { /* give up in disgust */ error("No response to %d PAP Authenticate-Requests", u->us_transmits); u->us_clientstate = UPAPCS_BADAUTH; auth_withpeer_fail(u->us_unit, PPP_PAP); return; } upap_sauthreq(u); /* Send Authenticate-Request */ } /* * upap_reqtimeout - Give up waiting for the peer to send a valid auth-req. */ static void upap_reqtimeout(arg) void *arg; { upap_state *u = (upap_state *) arg; if (u->us_serverstate != UPAPSS_LISTEN) return; /* huh?? */ auth_peer_fail(u->us_unit, PPP_PAP); u->us_serverstate = UPAPSS_BADAUTH; } /* * upap_lowerup - The lower layer is up. * * Start authenticating if pending. */ static void upap_lowerup(unit) int unit; { upap_state *u = &upap[unit]; if (u->us_clientstate == UPAPCS_INITIAL) u->us_clientstate = UPAPCS_CLOSED; else if (u->us_clientstate == UPAPCS_PENDING) { upap_sauthreq(u); /* send an auth-request */ } if (u->us_serverstate == UPAPSS_INITIAL) u->us_serverstate = UPAPSS_CLOSED; else if (u->us_serverstate == UPAPSS_PENDING) { u->us_serverstate = UPAPSS_LISTEN; if (u->us_reqtimeout > 0) TIMEOUT(upap_reqtimeout, u, u->us_reqtimeout); } } /* * upap_lowerdown - The lower layer is down. * * Cancel all timeouts. */ static void upap_lowerdown(unit) int unit; { upap_state *u = &upap[unit]; /* Cancel timeouts */ if (u->us_clientstate == UPAPCS_AUTHREQ && u->us_timeouttime > 0) UNTIMEOUT(upap_timeout, u); if (u->us_serverstate == UPAPSS_LISTEN && u->us_reqtimeout > 0) UNTIMEOUT(upap_reqtimeout, u); u->us_clientstate = UPAPCS_INITIAL; u->us_serverstate = UPAPSS_INITIAL; } /* * upap_protrej - Peer doesn't speak this protocol. * * This shouldn't happen. In any case, pretend lower layer went down. */ static void upap_protrej(unit) int unit; { upap_state *u = &upap[unit]; if (u->us_clientstate == UPAPCS_AUTHREQ) { error("PAP authentication failed due to protocol-reject"); auth_withpeer_fail(unit, PPP_PAP); } if (u->us_serverstate == UPAPSS_LISTEN) { error("PAP authentication of peer failed (protocol-reject)"); auth_peer_fail(unit, PPP_PAP); } upap_lowerdown(unit); } /* * upap_input - Input UPAP packet. */ static void upap_input(unit, inpacket, l) int unit; u_char *inpacket; int l; { upap_state *u = &upap[unit]; u_char *inp; u_char code, id; int len; /* * Parse header (code, id and length). * If packet too short, drop it. */ inp = inpacket; if (l < UPAP_HEADERLEN) { error("PAP: packet is too small (%d < %d)", l, UPAP_HEADERLEN); return; } GETCHAR(code, inp); GETCHAR(id, inp); GETSHORT(len, inp); if ((len < UPAP_HEADERLEN) || (len > l)) { error("PAP: packet has illegal length %d (%d..%d)", len, UPAP_HEADERLEN, l); return; } len -= UPAP_HEADERLEN; /* * Action depends on code. */ switch (code) { case UPAP_AUTHREQ: upap_rauthreq(u, inp, id, len); break; case UPAP_AUTHACK: upap_rauthack(u, inp, id, len); break; case UPAP_AUTHNAK: upap_rauthnak(u, inp, id, len); break; default: warn("Unknown PAP code (%d) received.", code); break; } } /* * upap_rauth - Receive Authenticate. */ static void upap_rauthreq(u, inp, id, len) upap_state *u; u_char *inp; int id; int len; { u_char ruserlen, rpasswdlen; char *ruser, *rpasswd; int retcode; char *msg; int msglen; if (u->us_serverstate < UPAPSS_LISTEN) { info("PAP: discarded Authenticate-Request in state %s", pap_sstate(u->us_serverstate)); return; } /* * If we receive a duplicate authenticate-request, we are * supposed to return the same status as for the first request. */ if (u->us_serverstate == UPAPSS_OPEN) { /* return auth-ack */ upap_sresp(u, UPAP_AUTHACK, id, u->us_msg, u->us_msglen); return; } if (u->us_serverstate == UPAPSS_BADAUTH) { /* return auth-nak */ upap_sresp(u, UPAP_AUTHNAK, id, u->us_msg, u->us_msglen); return; } /* * Parse user/passwd. */ if (len < 1) { error("PAP: rcvd short packet; no data"); return; } GETCHAR(ruserlen, inp); len -= sizeof (u_char) + ruserlen + sizeof (u_char); if (len < 0) { error("PAP: rcvd short packet; peer name missing"); return; } ruser = (char *) inp; INCPTR(ruserlen, inp); GETCHAR(rpasswdlen, inp); if (len < rpasswdlen) { error("PAP: rcvd short packet; pass len %d < %d", len, rpasswdlen); return; } rpasswd = (char *) inp; /* * Check the username and password given. */ retcode = check_passwd(u->us_unit, ruser, ruserlen, rpasswd, rpasswdlen, &msg); BZERO(rpasswd, rpasswdlen); msglen = strlen(msg); if (msglen > 255) msglen = 255; u->us_msg = msg; u->us_msglen = msglen; upap_sresp(u, retcode, id, u->us_msg, u->us_msglen); if (retcode == UPAP_AUTHACK) { u->us_serverstate = UPAPSS_OPEN; auth_peer_success(u->us_unit, PPP_PAP, ruser, ruserlen); } else if (++u->us_receives >= u->us_maxreceives) { u->us_serverstate = UPAPSS_BADAUTH; auth_peer_fail(u->us_unit, PPP_PAP); } else { /* Just wait for a good one to arrive, or for time-out. */ return; } if (u->us_reqtimeout > 0) UNTIMEOUT(upap_reqtimeout, u); } /* * upap_rauthack - Receive Authenticate-Ack. */ /*ARGSUSED*/ static void upap_rauthack(u, inp, id, len) upap_state *u; u_char *inp; int id; int len; { u_char msglen; char *msg; if (u->us_clientstate != UPAPCS_AUTHREQ) { info("PAP: discarded Authenticate-Ack in state %s", pap_cstate(u->us_clientstate)); return; } if (id != u->us_id) { dbglog("PAP: discard Authenticate-Ack; ID %d != %d", id, u->us_id); return; } if (u->us_timeouttime > 0) UNTIMEOUT(upap_timeout, u); /* * Parse message. */ if (len < 1) { info("PAP: Ignoring missing ack msg-length octet"); } else { GETCHAR(msglen, inp); if (msglen > 0) { len -= sizeof (u_char); if (len < msglen) { error("PAP: Discarding short packet (%d < %d)", len, msglen); return; } msg = (char *) inp; PRINTMSG(msg, msglen); } } u->us_clientstate = UPAPCS_OPEN; auth_withpeer_success(u->us_unit, PPP_PAP); } /* * upap_rauthnak - Receive Authenticate-Nakk. */ /*ARGSUSED*/ static void upap_rauthnak(u, inp, id, len) upap_state *u; u_char *inp; int id; int len; { u_char msglen; char *msg; if (u->us_clientstate != UPAPCS_AUTHREQ) { info("PAP: discarded Authenticate-Nak in state %s", pap_cstate(u->us_clientstate)); return; } if (id != u->us_id) { dbglog("PAP: discard Authenticate-Ack; ID %d != %d", id, u->us_id); return; } if (u->us_timeouttime > 0) UNTIMEOUT(upap_timeout, u); /* * Parse message. */ if (len < 1) { error("PAP: ignoring missing nak msg-length octet"); } else { GETCHAR(msglen, inp); if (msglen > 0) { len -= sizeof (u_char); if (len < msglen) { error("PAP: Discarding short packet (%d < %d)", len, msglen); return; } msg = (char *) inp; PRINTMSG(msg, msglen); } } /* Try to get a new password from the plugin. */ if (pap_passwd_hook != NULL) { if (u->us_transmits < u->us_maxtransmits) { if ((*pap_passwd_hook)(user, passwd) >= 0) { upap_sauthreq(u); return; } } else { /* Tell plug-in that we're giving up. */ (void) (*pap_passwd_hook)(NULL, NULL); } } u->us_clientstate = UPAPCS_BADAUTH; error("PAP authentication failed"); auth_withpeer_fail(u->us_unit, PPP_PAP); } /* * upap_sauthreq - Send an Authenticate-Request. */ static void upap_sauthreq(u) upap_state *u; { u_char *outp; int pwlen; int outlen; pwlen = strllen(passwd, MAXSECRETLEN); if (pwlen > 0xFF) pwlen = 0xFF; outlen = UPAP_HEADERLEN + 2 * sizeof (u_char) + u->us_userlen + pwlen; outp = outpacket_buf; MAKEHEADER(outp, PPP_PAP); PUTCHAR(UPAP_AUTHREQ, outp); PUTCHAR(++u->us_id, outp); PUTSHORT(outlen, outp); PUTCHAR(u->us_userlen, outp); BCOPY(u->us_user, outp, u->us_userlen); INCPTR(u->us_userlen, outp); PUTCHAR(pwlen, outp); BCOPY(u->us_passwd, outp, pwlen); output(u->us_unit, outpacket_buf, outlen + PPP_HDRLEN); if (u->us_timeouttime > 0) TIMEOUT(upap_timeout, u, u->us_timeouttime); ++u->us_transmits; u->us_clientstate = UPAPCS_AUTHREQ; } /* * upap_sresp - Send a response (ack or nak). */ static void upap_sresp(u, code, id, msg, msglen) upap_state *u; u_char code, id; char *msg; int msglen; { u_char *outp; int outlen; outlen = UPAP_HEADERLEN + sizeof (u_char) + msglen; outp = outpacket_buf; MAKEHEADER(outp, PPP_PAP); PUTCHAR(code, outp); PUTCHAR(id, outp); PUTSHORT(outlen, outp); PUTCHAR(msglen, outp); BCOPY(msg, outp, msglen); output(u->us_unit, outpacket_buf, outlen + PPP_HDRLEN); } /* * upap_printpkt - print the contents of a PAP packet. */ static char *upap_codenames[] = { "AuthReq", "AuthAck", "AuthNak" }; static int upap_printpkt(p, plen, printer, arg) u_char *p; int plen; void (*printer) __P((void *, const char *, ...)); void *arg; { int code, id, len; int mlen, ulen, wlen; char *user, *pwd, *msg; u_char *pstart; if (plen < UPAP_HEADERLEN) return (0); pstart = p; GETCHAR(code, p); GETCHAR(id, p); GETSHORT(len, p); if (len < UPAP_HEADERLEN || len > plen) return (0); if (code >= 1 && code <= Dim(upap_codenames)) printer(arg, " %s", upap_codenames[code-1]); else printer(arg, " code=0x%x", code); printer(arg, " id=0x%x", id); len -= UPAP_HEADERLEN; switch (code) { case UPAP_AUTHREQ: if (len < 1) break; ulen = p[0]; if (len < ulen + 2) break; wlen = p[ulen + 1]; if (len < ulen + wlen + 2) break; user = (char *) (p + 1); pwd = (char *) (p + ulen + 2); p += ulen + wlen + 2; len -= ulen + wlen + 2; printer(arg, " user="); print_string(user, ulen, printer, arg); printer(arg, " password="); if (!hide_password) print_string(pwd, wlen, printer, arg); else printer(arg, ""); break; case UPAP_AUTHACK: case UPAP_AUTHNAK: if (len < 1) break; mlen = p[0]; if (len < mlen + 1) break; msg = (char *) (p + 1); p += mlen + 1; len -= mlen + 1; printer(arg, " "); print_string(msg, mlen, printer, arg); break; } /* print the rest of the bytes in the packet */ for (; len > 0; --len) { GETCHAR(code, p); printer(arg, " %.2x", code); } return (p - pstart); } /* * upap.h - User/Password Authentication Protocol definitions. * * Copyright (c) 2000 by Sun Microsystems, Inc. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. * * SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES * * 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. * * $Id: upap.h,v 1.7 1999/11/15 01:51:54 paulus Exp $ */ #ifndef __UPAP_H__ #define __UPAP_H__ #ifdef __cplusplus extern "C" { #endif /* * Packet header = Code, id, length. */ #define UPAP_HEADERLEN 4 /* * UPAP codes. */ #define UPAP_AUTHREQ 1 /* Authenticate-Request */ #define UPAP_AUTHACK 2 /* Authenticate-Ack */ #define UPAP_AUTHNAK 3 /* Authenticate-Nak */ /* * Each interface is described by upap structure. */ typedef struct upap_state { int us_unit; /* Interface unit number */ char *us_user; /* User */ int us_userlen; /* User length */ char *us_passwd; /* Password */ int us_clientstate; /* Client state */ int us_serverstate; /* Server state */ u_char us_id; /* Current id */ int us_timeouttime; /* Timeout (seconds) for auth-req retrans. */ int us_transmits; /* Number of auth-reqs sent */ int us_maxtransmits; /* Maximum number of auth-reqs to send */ int us_receives; /* Number of auth-reqs received */ int us_maxreceives; /* Maximum number of auth-reqs allowed */ int us_reqtimeout; /* Time to wait for auth-req from peer */ char *us_msg; /* Authentication response message */ int us_msglen; /* and its length */ } upap_state; /* * Client states. */ #define UPAPCS_INITIAL 0 /* Connection down */ #define UPAPCS_CLOSED 1 /* Connection up, haven't requested auth */ #define UPAPCS_PENDING 2 /* Connection down, have requested auth */ #define UPAPCS_AUTHREQ 3 /* We've sent an Authenticate-Request */ #define UPAPCS_OPEN 4 /* We've received an Ack */ #define UPAPCS_BADAUTH 5 /* We've received a Nak */ #define UPAPCS__NAMES \ "CliInitial", "CliClosed", "CliPending", "CliAuthReq", "CliOpen", \ "CliBadAuth" /* * Server states. */ #define UPAPSS_INITIAL 0 /* Connection down */ #define UPAPSS_CLOSED 1 /* Connection up, haven't requested auth */ #define UPAPSS_PENDING 2 /* Connection down, have requested auth */ #define UPAPSS_LISTEN 3 /* Listening for an Authenticate */ #define UPAPSS_OPEN 4 /* We've sent an Ack */ #define UPAPSS_BADAUTH 5 /* We've sent a Nak */ #define UPAPSS__NAMES \ "SrvInitial", "SrvClosed", "SrvPending", "SrvListen", "SrvOpen", \ "SrvBadAuth" /* * Timeouts. */ #define UPAP_DEFTIMEOUT 3 /* Timeout (seconds) for retransmitting req */ #define UPAP_DEFREQTIME 30 /* Time to wait for auth-req from peer */ extern upap_state upap[]; void upap_authwithpeer __P((int, char *, char *)); void upap_authpeer __P((int)); extern struct protent pap_protent; #ifdef __cplusplus } #endif #endif /* __UPAP_H__ */ /* * utils.c - various utility functions used in pppd. * * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Permission to use, copy, modify, and distribute this software and its * documentation is hereby granted, provided that the above copyright * notice appears in all copies. * * SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES * * Copyright (c) 1999 The Australian National 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 the Australian National 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. */ #ifdef __linux__ #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SVR4 #include #endif #include "pppd.h" #if defined(SUNOS4) extern char *strerror(); #endif /* Don't log to stdout until we're sure it's ok to do so. */ bool early_log = 1; static void pr_log __P((void *, const char *, ...)); static void logit __P((int, const char *, va_list)); static void vslp_printer __P((void *, const char *, ...)); static void format_packet __P((u_char *, int, void (*) (void *, const char *, ...), void *)); struct buffer_info { char *ptr; int len; }; /* * strllen - like strlen, but doesn't run past end of input. */ size_t strllen(str, len) const char *str; size_t len; { size_t ret; for (ret = 0; ret < len; ret++) if (*str++ == '\0') break; return (ret); } /* * slprintf - format a message into a buffer. Like sprintf except we * also specify the length of the output buffer, and we handle %m * (error message), %v (visible string), %q (quoted string), %t * (current time), %I (IP address), %P (PPP packet), and %B (sequence * of bytes) formats. Doesn't do floating-point formats. Returns the * number of chars put into buf. */ int slprintf __V((char *buf, int buflen, const char *fmt, ...)) { va_list args; int n; #if defined(__STDC__) va_start(args, fmt); #else char *buf; int buflen; const char *fmt; va_start(args); buf = va_arg(args, char *); buflen = va_arg(args, int); fmt = va_arg(args, const char *); #endif n = vslprintf(buf, buflen, fmt, args); va_end(args); return (n); } /* * Print to file or, if argument is NULL, to syslog at debug level. */ int flprintf __V((FILE *strptr, const char *fmt, ...)) { va_list args; int n; char buf[1024], *bp, *nlp, *ebp; #if defined(__STDC__) va_start(args, fmt); #else FILE *strptr; const char *fmt; va_start(args); strptr = va_arg(args, FILE *); fmt = va_arg(args, const char *); #endif n = vslprintf(buf, sizeof (buf), fmt, args); va_end(args); if (strptr == NULL) { bp = buf; ebp = buf + n; while (bp < ebp) { if ((nlp = strchr(bp, '\n')) == NULL) nlp = ebp; if (nlp > bp) { *nlp = '\0'; syslog(LOG_DEBUG, "%s", bp); } bp = nlp + 1; } } else { n = fwrite(buf, 1, n, strptr); } return (n); } /* * vslprintf - like slprintf, takes a va_list instead of a list of args. */ #define OUTCHAR(c) (buflen > 0? (--buflen, *buf++ = (c)): 0) int vslprintf(buf, buflen, fmt, args) char *buf; int buflen; const char *fmt; va_list args; { int c, n, longs; int width, prec, fillch; int base, len, neg, quoted; #ifdef SOL2 uint64_t val; int64_t sval; #else unsigned long val; long sval; #endif char *buf0, *mstr; const char *f, *str; unsigned char *p; char num[32]; /* 2^64 is 20 chars decimal, 22 octal */ time_t t; u_int32_t ip; static const char hexchars[] = "0123456789abcdef"; struct buffer_info bufinfo; buf0 = buf; --buflen; while (buflen > 0) { for (f = fmt; *f != '%' && *f != 0; ++f) ; if (f > fmt) { len = f - fmt; if (len > buflen) len = buflen; (void) memcpy(buf, fmt, len); buf += len; buflen -= len; fmt = f; } if (*fmt == 0) break; c = *++fmt; width = 0; prec = -1; fillch = ' '; if (c == '0') { fillch = '0'; c = *++fmt; } if (c == '*') { width = va_arg(args, int); c = *++fmt; } else { while (isdigit(c)) { width = width * 10 + c - '0'; c = *++fmt; } } if (c == '.') { c = *++fmt; if (c == '*') { prec = va_arg(args, int); c = *++fmt; } else { prec = 0; while (isdigit(c)) { prec = prec * 10 + c - '0'; c = *++fmt; } } } longs = 0; if (c == 'l') { longs++; c = *++fmt; if (c == 'l') { longs++; c = *++fmt; } } str = 0; base = 0; neg = 0; val = 0; ++fmt; switch (c) { case 'u': #ifdef SOL2 if (longs >= 2) val = va_arg(args, uint64_t); else #endif if (longs > 0) val = va_arg(args, unsigned long); else val = va_arg(args, unsigned int); base = 10; break; case 'd': #ifdef SOL2 if (longs >= 2) sval = va_arg(args, int64_t); else #endif if (longs > 0) sval = va_arg(args, long); else sval = va_arg(args, int); if (sval < 0) { neg = 1; val = -sval; } else val = sval; base = 10; break; case 'o': #ifdef SOL2 if (longs >= 2) val = va_arg(args, uint64_t); else #endif if (longs > 0) val = va_arg(args, unsigned long); else val = va_arg(args, unsigned int); base = 8; break; case 'x': case 'X': #ifdef SOL2 if (longs >= 2) val = va_arg(args, uint64_t); else #endif if (longs > 0) val = va_arg(args, unsigned long); else val = va_arg(args, unsigned int); base = 16; break; case 'p': val = (unsigned long) va_arg(args, void *); base = 16; neg = 2; break; case 's': str = va_arg(args, const char *); break; case 'c': num[0] = va_arg(args, int); num[1] = 0; str = num; break; case 'm': str = strerror(errno); break; case 'I': ip = va_arg(args, u_int32_t); ip = ntohl(ip); (void) slprintf(num, sizeof(num), "%d.%d.%d.%d", (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); str = num; break; case 't': (void) time(&t); mstr = ctime(&t); mstr += 4; /* chop off the day name */ mstr[15] = 0; /* chop off year and newline */ str = (const char *)mstr; break; case 'v': /* "visible" string */ case 'q': /* quoted string */ quoted = c == 'q'; p = va_arg(args, unsigned char *); if (fillch == '0' && prec >= 0) { n = prec; } else { n = strlen((char *)p); if (prec >= 0 && n > prec) n = prec; } while (n > 0 && buflen > 0) { c = *p++; --n; if (!quoted && c >= 0x80) { (void) OUTCHAR('M'); (void) OUTCHAR('-'); c -= 0x80; } if (quoted && (c == '"' || c == '\\')) (void) OUTCHAR('\\'); if (c < 0x20 || (0x7f <= c && c < 0xa0)) { if (quoted) { (void) OUTCHAR('\\'); switch (c) { case '\t': (void) OUTCHAR('t'); break; case '\n': (void) OUTCHAR('n'); break; case '\b': (void) OUTCHAR('b'); break; case '\f': (void) OUTCHAR('f'); break; default: (void) OUTCHAR('x'); (void) OUTCHAR(hexchars[c >> 4]); (void) OUTCHAR(hexchars[c & 0xf]); } } else { if (c == '\t') (void) OUTCHAR(c); else { (void) OUTCHAR('^'); (void) OUTCHAR(c ^ 0x40); } } } else (void) OUTCHAR(c); } continue; case 'P': /* print PPP packet */ bufinfo.ptr = buf; bufinfo.len = buflen + 1; p = va_arg(args, unsigned char *); n = va_arg(args, int); format_packet(p, n, vslp_printer, &bufinfo); buf = bufinfo.ptr; buflen = bufinfo.len - 1; continue; case 'B': p = va_arg(args, unsigned char *); if ((n = prec) > width && width > 0) n = width; /* For safety's sake */ if (n > 2000) n = 2000; while (--n >= 0) { c = *p++; if (fillch == ' ') (void) OUTCHAR(' '); (void) OUTCHAR(hexchars[(c >> 4) & 0xf]); (void) OUTCHAR(hexchars[c & 0xf]); } if (prec > width && width > 0) { (void) OUTCHAR('.'); (void) OUTCHAR('.'); (void) OUTCHAR('.'); } continue; default: *buf++ = '%'; if (c != '%') --fmt; /* so %z outputs %z etc. */ --buflen; continue; } if (base != 0) { mstr = num + sizeof(num); *--mstr = 0; while (mstr > num + neg) { *--mstr = hexchars[val % base]; val = val / base; if (--prec <= 0 && val == 0) break; } switch (neg) { case 1: *--mstr = '-'; break; case 2: *--mstr = 'x'; *--mstr = '0'; break; } len = num + sizeof(num) - 1 - mstr; str = (const char *)mstr; } else { len = strlen(str); if (prec >= 0 && len > prec) len = prec; } if (width > 0) { if (width > buflen) width = buflen; if ((n = width - len) > 0) { buflen -= n; for (; n > 0; --n) *buf++ = fillch; } } if (len > buflen) len = buflen; (void) memcpy(buf, str, len); buf += len; buflen -= len; } *buf = 0; return (buf - buf0); } /* * vslp_printer - used in processing a %P format */ static void vslp_printer __V((void *arg, const char *fmt, ...)) { int n; va_list pvar; struct buffer_info *bi; #if defined(__STDC__) va_start(pvar, fmt); #else void *arg; const char *fmt; va_start(pvar); arg = va_arg(pvar, void *); fmt = va_arg(pvar, const char *); #endif bi = (struct buffer_info *) arg; n = vslprintf(bi->ptr, bi->len, fmt, pvar); va_end(pvar); bi->ptr += n; bi->len -= n; } /* * log_packet - format a packet and log it. */ static char line[256]; /* line to be logged accumulated here */ static char *linep; void log_packet(p, len, prefix, level) u_char *p; int len; const char *prefix; int level; { (void) strlcpy(line, prefix, sizeof(line)); linep = line + strlen(line); format_packet(p, len, pr_log, (void *)level); if (linep != line) syslog(level, "%s", line); } /* * format_packet - make a readable representation of a packet, * calling `printer(arg, format, ...)' to output it. */ static void format_packet(p, len, printer, arg) u_char *p; int len; void (*printer) __P((void *, const char *, ...)); void *arg; { int i, n; u_short proto; struct protent *protp; if (len >= PPP_HDRLEN && p[0] == PPP_ALLSTATIONS && p[1] == PPP_UI) { p += 2; GETSHORT(proto, p); len -= PPP_HDRLEN; for (i = 0; (protp = protocols[i]) != NULL; ++i) if (proto == protp->protocol) break; if (protp != NULL) { printer(arg, "[%s", protp->name); n = (*protp->printpkt)(p, len, printer, arg); printer(arg, "]"); p += n; len -= n; } else { for (i = 0; (protp = protocols[i]) != NULL; ++i) if (proto == (protp->protocol & ~0x8000)) break; if (protp != NULL && protp->data_name != NULL) { printer(arg, "[%s data] %8.*B", protp->data_name, len, p); len = 0; } else printer(arg, "[proto=0x%x]", proto); } } printer(arg, "%32.*B", len, p); } static void pr_log __V((void *arg, const char *fmt, ...)) { int n; va_list pvar; char buf[256]; #if defined(__STDC__) va_start(pvar, fmt); #else void *arg; const char *fmt; va_start(pvar); arg = va_arg(pvar, void *); fmt = va_arg(pvar, const char *); #endif n = vslprintf(buf, sizeof(buf), fmt, pvar); va_end(pvar); if (linep + n + 1 > line + sizeof(line)) { syslog((int)arg, "%s", line); linep = line; } (void) strlcpy(linep, buf, line + sizeof(line) - linep); linep += n; } /* * print_string - print a readable representation of a string using * printer. */ void print_string(p, len, printer, arg) char *p; int len; void (*printer) __P((void *, const char *, ...)); void *arg; { int c; printer(arg, "\""); for (; len > 0; --len) { c = *p++; if (isprint(c)) { if (c == '\\' || c == '"') printer(arg, "\\"); printer(arg, "%c", c); } else { switch (c) { case '\n': printer(arg, "\\n"); break; case '\r': printer(arg, "\\r"); break; case '\t': printer(arg, "\\t"); break; default: printer(arg, "\\%.3o", c); } } } printer(arg, "\""); } /* * logit - does the hard work for fatal et al. */ static void logit(level, fmt, args) int level; const char *fmt; va_list args; { int n; char buf[1024]; n = vslprintf(buf, sizeof(buf), fmt, args); syslog(level, "%s", buf); if (log_to_fd >= 0 && (level != LOG_DEBUG || debug) && (!early_log || log_to_specific_fd)) { if (buf[n-1] != '\n') buf[n++] = '\n'; if (write(log_to_fd, buf, n) != n) log_to_fd = -1; } } /* * fatal - log an error message and die horribly. */ void fatal __V((const char *fmt, ...)) { va_list pvar; #if defined(__STDC__) va_start(pvar, fmt); #else const char *fmt; va_start(pvar); fmt = va_arg(pvar, const char *); #endif logit(LOG_ERR, fmt, pvar); va_end(pvar); die(1); /* as promised */ } /* * error - log an error message. */ void error __V((const char *fmt, ...)) { va_list pvar; #if defined(__STDC__) va_start(pvar, fmt); #else const char *fmt; va_start(pvar); fmt = va_arg(pvar, const char *); #endif logit(LOG_ERR, fmt, pvar); va_end(pvar); } /* * warn - log a warning message. */ void warn __V((const char *fmt, ...)) { va_list pvar; #if defined(__STDC__) va_start(pvar, fmt); #else const char *fmt; va_start(pvar); fmt = va_arg(pvar, const char *); #endif logit(LOG_WARNING, fmt, pvar); va_end(pvar); } /* * notice - log a notice-level message. */ void notice __V((const char *fmt, ...)) { va_list pvar; #if defined(__STDC__) va_start(pvar, fmt); #else const char *fmt; va_start(pvar); fmt = va_arg(pvar, const char *); #endif logit(LOG_NOTICE, fmt, pvar); va_end(pvar); } /* * info - log an informational message. */ void info __V((const char *fmt, ...)) { va_list pvar; #if defined(__STDC__) va_start(pvar, fmt); #else const char *fmt; va_start(pvar); fmt = va_arg(pvar, const char *); #endif logit(LOG_INFO, fmt, pvar); va_end(pvar); } /* * dbglog - log a debug message. */ void dbglog __V((const char *fmt, ...)) { va_list pvar; #if defined(__STDC__) va_start(pvar, fmt); #else const char *fmt; va_start(pvar); fmt = va_arg(pvar, const char *); #endif logit(LOG_DEBUG, fmt, pvar); va_end(pvar); } /* * Code names for regular PPP messages. Used by LCP and most NCPs, * not used by authentication protocols. */ const char * code_name(int code, int shortflag) { static const char *codelist[] = { "Vendor-Extension", "Configure-Request", "Configure-Ack", "Configure-Nak", "Configure-Reject", "Terminate-Request", "Terminate-Ack", "Code-Reject", "Protocol-Reject", "Echo-Request", "Echo-Reply", "Discard-Request", "Identification", "Time-Remaining", "Reset-Request", "Reset-Ack" }; static const char *shortcode[] = { "VendExt", "ConfReq", "ConfAck", "ConfNak", "ConfRej", "TermReq", "TermAck", "CodeRej", "ProtRej", "EchoReq", "EchoRep", "DiscReq", "Ident", "TimeRem", "ResetReq", "ResetAck" }; static char msgbuf[64]; if (code < 0 || code >= sizeof (codelist) / sizeof (*codelist)) { if (shortflag) (void) slprintf(msgbuf, sizeof (msgbuf), "Code#%d", code); else (void) slprintf(msgbuf, sizeof (msgbuf), "unknown code %d", code); return ((const char *)msgbuf); } return (shortflag ? shortcode[code] : codelist[code]); } /* Procedures for locking the serial device using a lock file. */ #ifndef LOCK_DIR #ifdef _linux_ #define LOCK_DIR "/var/lock" #else #ifdef SVR4 #define LOCK_DIR "/var/spool/locks" #else #define LOCK_DIR "/var/spool/lock" #endif #endif #endif /* LOCK_DIR */ static char lock_file[MAXPATHLEN]; /* * lock - create a lock file for the named device */ int lock(dev) char *dev; { #ifdef LOCKLIB int result; result = mklock (dev, (void *) 0); if (result == 0) { (void) strlcpy(lock_file, sizeof(lock_file), dev); return (0); } if (result > 0) notice("Device %s is locked by pid %d", dev, result); else error("Can't create lock file %s", lock_file); return (-1); #else /* LOCKLIB */ char lock_buffer[12]; int fd, pid, n; #ifdef SVR4 struct stat sbuf; if (stat(dev, &sbuf) < 0) { error("Can't get device number for %s: %m", dev); return (-1); } if ((sbuf.st_mode & S_IFMT) != S_IFCHR) { error("Can't lock %s: not a character device", dev); return (-1); } (void) slprintf(lock_file, sizeof(lock_file), "%s/LK.%03d.%03d.%03d", LOCK_DIR, major(sbuf.st_dev), major(sbuf.st_rdev), minor(sbuf.st_rdev)); #else char *p; if ((p = strrchr(dev, '/')) != NULL) dev = p + 1; (void) slprintf(lock_file, sizeof(lock_file), "%s/LCK..%s", LOCK_DIR, dev); #endif while ((fd = open(lock_file, O_EXCL | O_CREAT | O_RDWR, 0644)) < 0) { if (errno != EEXIST) { error("Can't create lock file %s: %m", lock_file); break; } /* Read the lock file to find out who has the device locked. */ fd = open(lock_file, O_RDONLY, 0); if (fd < 0) { if (errno == ENOENT) /* This is just a timing problem. */ continue; error("Can't open existing lock file %s: %m", lock_file); break; } #ifndef LOCK_BINARY n = read(fd, lock_buffer, 11); #else n = read(fd, &pid, sizeof(pid)); #endif /* LOCK_BINARY */ (void) close(fd); fd = -1; if (n <= 0) { error("Can't read pid from lock file %s", lock_file); break; } /* See if the process still exists. */ #ifndef LOCK_BINARY lock_buffer[n] = 0; pid = atoi(lock_buffer); #endif /* LOCK_BINARY */ if (pid == getpid()) return (1); /* somebody else locked it for us */ if (pid == 0 || (kill(pid, 0) == -1 && errno == ESRCH)) { if (unlink (lock_file) == 0) { notice("Removed stale lock on %s (pid %d)", dev, pid); continue; } warn("Couldn't remove stale lock on %s", dev); } else notice("Device %s is locked by pid %d", dev, pid); break; } if (fd < 0) { lock_file[0] = 0; return (-1); } pid = getpid(); #ifndef LOCK_BINARY (void) slprintf(lock_buffer, sizeof(lock_buffer), "%10d\n", pid); (void) write (fd, lock_buffer, 11); #else (void) write(fd, &pid, sizeof (pid)); #endif (void) close(fd); return (0); #endif } /* * relock - called to update our lockfile when we are about to detach, * thus changing our pid (we fork, the child carries on, and the parent dies). * Note that this is called by the parent, with pid equal to the pid * of the child. This avoids a potential race which would exist if * we had the child rewrite the lockfile (the parent might die first, * and another process could think the lock was stale if it checked * between when the parent died and the child rewrote the lockfile). */ int relock(pid) int pid; { #ifdef LOCKLIB /* XXX is there a way to do this? */ return (-1); #else /* LOCKLIB */ int fd; char lock_buffer[12]; if (lock_file[0] == 0) return (-1); fd = open(lock_file, O_WRONLY, 0); if (fd < 0) { error("Couldn't reopen lock file %s: %m", lock_file); lock_file[0] = 0; return (-1); } #ifndef LOCK_BINARY (void) slprintf(lock_buffer, sizeof(lock_buffer), "%10d\n", pid); (void) write (fd, lock_buffer, 11); #else (void) write(fd, &pid, sizeof(pid)); #endif /* LOCK_BINARY */ (void) close(fd); return (0); #endif /* LOCKLIB */ } /* * unlock - remove our lockfile */ void unlock() { if (lock_file[0]) { #ifdef LOCKLIB (void) rmlock(lock_file, (void *) 0); #else (void) unlink(lock_file); #endif lock_file[0] = 0; } } const char * signal_name(int signum) { #if defined(SOL2) || defined(__linux__) || defined(_linux_) const char *cp; if ((cp = strsignal(signum)) != NULL) return (cp); #else extern char *sys_siglist[]; extern int sys_nsig; if (signum >= 0 && signum < sys_nsig && sys_siglist[signum] != NULL) return (sys_siglist[signum]); #endif return ("??"); }