#!/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, "/usr/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);