1#!/usr/bin/perl
2
3# This utility translates from aspppd configuration to Solaris PPP 4.0
4# (or ANU ppp-2.4.0; aka pppd).  It can also revert to previous aspppd
5# configuration (discarding the pppd configuration), but does not
6# translate new configuration files into old.
7#
8# This script provides only a suggested translation for your existing
9# aspppd configuration.  You will need to evaluate for yourself
10# whether the translation is appropriate for your operating
11# environment.
12
13# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
14# Use is subject to license terms.
15#
16
17# Steps in translation:
18#	- parse /etc/asppp.cf
19#	- check for aspppls in /etc/passwd (or NIS)
20#	- read in current /etc/ppp/options configuration file
21#	- read list of configured serial ports from pmadm
22#	- read in UUCP configuration files
23#	- create translated configuration
24#	- write files back out
25
26# Known issues:
27#	- translation with point-to-multipoint is incomplete
28
29use Getopt::Std;
30use Fcntl;
31use POSIX qw(tmpnam ENOSYS);
32use Sys::Hostname;
33
34# Secure the path if we're running under RBAC.
35$ENV{PATH} = ( "/bin", "/sbin", "/usr/bin", "/usr/sbin", "/usr/ucb" )
36    if $< != $>;
37
38# General path names that can be configured.
39local($rootetc) =	"/etc/";
40local($passwd) =	$rootetc . "passwd";
41local($passwdlck) =	$rootetc . ".pwd.lock";
42local($asfile) =	$rootetc . "asppp.cf";
43local($astemp) =	$rootetc . "asppp.temp.cf";
44local($asmoved) =	$rootetc . "asppp.saved.cf";
45local($uucpdir) =	$rootetc . "uucp/";
46local($Devices) =	$uucpdir . "Devices";
47local($Devconfig) =	$uucpdir . "Devconfig";
48local($Dialers) =	$uucpdir . "Dialers";
49local($Dialcodes) =	$uucpdir . "Dialcodes";
50local($Limits) =	$uucpdir . "Limits";
51local($Sysfiles) =	$uucpdir . "Sysfiles";
52local($Systems) =	$uucpdir . "Systems";
53local($pppdir) =	$rootetc . "ppp/";
54local($options) =	$pppdir . "options";
55local($ttyprefix) =	$pppdir . "options.";
56local($peersdir) =	$pppdir . "peers/";
57local($initd) =		$rootetc . "init.d/";
58local($asctl) =		$initd . "asppp";
59local($pppdctl) =	$initd . "pppd";
60local($sedpasswd) =	"/tmp/sed-passwd";
61
62# Fake asppp keyword used to keep track of dial-in paths.
63local($isdialin) = "-is-dial-in";
64
65# Limits and Sysfiles are keyed on "service=".
66# Devconfig is keyed on "service=" and "device=".
67# Dialcodes, Dialers, Systems, and Devices are single keyword files.
68# Devices alone may have multiple entries for a given key.
69
70# Internal data structures
71local(@sysfiles,@limits,@devconfig);
72local(@sysdefault) = ( "systems=" . $Systems, "dialers=" . $Dialers,
73		       "devices=" . $Devices );
74local(@limitdefault) = ( "max=-1" );
75local(@devdefault) = ( "pop=", "push=", "connecttime=-1", "expecttime=-1",
76		       "msgtime=-1" );
77
78# List of keywords for which ifconfig takes an additional parameter.
79local($ifconfigtakes) = (
80	addif => 1,
81	removeif => 1,
82	auth_algs => 1,
83	encr_algs => 1,
84	encr_auth_algs => 1,
85	broadcast => 1,
86	destination => 1,
87	index => 1,
88	metric => 1,
89	modinsert => 1,
90	modremove => 1,
91	mtu => 1,
92	netmask => 1,
93	set => 1,
94	subnet => 1,
95	tdst => 1,
96	tsrc => 1,
97	wait => 1,
98
99# These are keywords, but do not take an additional parameter.
100	ether => 0,
101	inet => 0,
102	inet6 => 0,
103	arp => 0,
104	-arp => 0,
105	auto-revarp => 0,
106	modlist => 0,
107	plumb => 0,
108	unplumb => 0,
109	private => 0,
110	-private => 0,
111	nud => 0,
112	-nud => 0,
113	trailers => 0,
114	-trailers => 0,
115	up => 0,
116	down => 0,
117	xmit => 0,
118	-xmit => 0,
119	auto-dhcp => 0,
120	dhcp => 0,
121	primary => 0,
122	drop => 0,
123	extend => 0,
124	inform => 0,
125	ping => 0,
126	release => 0,
127	start => 0,
128	status => 0
129);
130
131# print number of something in English.
132sub nof
133{
134    local($num, $item, @rest) = @_;
135    print "No ", $item, "s", @rest if $num == 0;
136    print "1 ", $item, @rest if $num == 1;
137    print $num, " ", $item, "s", @rest if $num > 1;
138}
139
140# ask a yes or no question.
141sub yesno
142{
143    local ($query, $default) = @_;
144    local ($ans, $defans);
145
146    return $default unless (-t STDIN) && (-t STDOUT) && !$opt_n;
147    $defans = $default ? "Yn" : "yN";
148    while (1) {
149	print $query, " [", $defans, "]? ";
150	chomp($ans = <STDIN>);
151	return $default unless $ans;
152	return 1 if $ans =~ /^[Yy1Tt]/;
153	return 0 if $ans =~ /^[Nn0Ff]/;
154	print "Please enter 'y' or 'n'.\n";
155    }
156}
157
158# Put quotes around a string, if necessary.
159# The tests here aren't perfect -- they think that \\\' isn't an
160# escaped quote -- but they're good enough.
161sub requote
162{
163    local($_) = @_;
164    if (/^$/) {
165	"\"\"";
166    } elsif (/^'/ || /[^\\]'/ || /\\\\'/) {
167# Has unescaped quotes; must use " or redo quoting.
168	if (/^"/ || /[^\\]"/ || /\\\\"/) {
169# Both kinds of quotes; redo the quoting.
170	    s/^"/\\"/;
171	    s/([^\\]|\\\\)"/$1\\"/g;
172	}
173	"\"" . $_ . "\"";
174    } elsif (/^"/ || /[^\\]"/ || /\\\\"/) {
175	"'" . $_ . "'";
176    } elsif (/\s/) {
177	"\"" . $_ . "\"";
178    } else {
179	$_;
180    }
181}
182
183# Get a single line from a UUCP configuration file and return as a
184# reference to an array of words.  Removes comments and escapes.
185# (This is a modified version of the standard Perl shellwords function
186# that understands C escape sequences and continuation lines.)
187# Optionally returns lead-in, source text, and trailing component
188# for editing.
189sub uucpline
190{
191    local($input, $file, $triplet) = @_;
192    local(@words,$snippet,$field,$havefield,$cont,@triparray,$maytrail);
193
194    $cont = "";
195    $maytrail = "";
196    while (<$input>) {
197	# remove leading whitespace
198	if (s/^(\s+)//) {
199	    $maytrail .= $1;
200	}
201	if ($cont eq "") {
202	    if (s/^(\#(.|\n)*)$//) {
203		$triparray[0] .= $maytrail . $1;
204		$maytrail = "";
205		next;
206	    }
207	    if (s/^(\\?\n?)$//) {
208		$triparray[0] .= $maytrail . $1;
209		$maytrail = "";
210		next;
211	    }
212	    $triparray[0] .= $maytrail;
213	    $maytrail = "";
214	}
215	$snippet = $_;
216	if (s/^(([^\#\\]|\\.)*)\\\n$//) {
217	    $maytrail .= $snippet;
218	    $cont .= $1;
219	    next;
220	}
221	if (/^(([^\\\#]|\\[^\#])*)(\#?(.|\n)*)$/) {
222	    $_ = $maytrail . $1;
223	    $maytrail = $3;
224	    if (s/((\s|\n)*)$//) {
225		$maytrail = $1 . $maytrail;
226	    }
227	    $triparray[1] = $_;
228	}
229	$_ = $cont . $snippet;
230	$cont = "";
231	s/\n$//;
232	while ($_ ne '') {
233	    for (;;) {
234		if (s/^#.*//) {
235		    last;
236		} elsif (s/^"(([^"\\]|\\.)*)"//) {
237		    $snippet = $1;
238		} elsif (s/^"//) {
239		    warn "Unmatched double quote in $file: \"$_\n";
240		} elsif (s/^'(([^'\\]|\\.)*)'//) {
241		    $snippet = $1;
242		} elsif (s/^'//) {
243		    warn "Unmatched single quote in $file: '$_\n";
244		} elsif (s/^\\s//) {
245# \s works in chat, but not in the pppd option files
246		    $snippet = " ";
247		} elsif (s/^(\\.)//) {
248		    $snippet = $1;
249		} elsif (s/^([^\s\\'"#]+)//) {
250		    $snippet = $1;
251		} else {
252		    s/^\s+//;
253		    last;
254		}
255		$havefield = 1;
256		$field .= $snippet;
257	    }
258	    push(@words, $field) if $havefield;
259	    $havefield = 0;
260	    $field = '';
261	}
262	last;
263    }
264    $triparray[2] .= $maytrail;
265    @$triplet = @triparray;
266    warn "Bad continuation line in $file: $cont\n" if $cont ne '';
267    \@words;
268}
269
270# Given a logical UUCP file name, return a list of all of the files
271# that should be read.
272sub uucpfiles
273{
274    local ($file) = @_;
275    local (@flist, $value) = ();
276
277    for $value (@sysfiles, @sysdefault) {
278	if ($value =~ /^$file=/i) {
279	    $value =~ s/^$file=//i;
280	    for $file (split /:/, $value) {
281		$file = $uucpdir . $file if $file !~ /^\//;
282		push @flist, $file;
283	    }
284	    last;
285	}
286    }
287    @flist;
288}
289
290# Given a file name and some key words, parse the contents of the file
291# and return a reference to a hash constructed from this.  All keys
292# except the last must match exactly.  The last is used to order the
293# hash.
294sub uucpkeyfile
295{
296    local($file,@keylist) = @_;
297    local($lastkey,$keyval,$words,$i,$flag,%byservice);
298
299    open(SVCFILE, '<' . $file) || return undef;
300    $lastkey = pop @keylist;
301    while (@{$words = uucpline(SVCFILE, $file)}) {
302	$flag = 1;
303	foreach $keyval (@keylist) {
304	    $flag = 0;
305	    $i = 0;
306	    while ($i < @$words) {
307		if ($$words[$i] eq $keyval) {
308		    splice @$words, $i, 1;
309		    $flag = 1;
310		    last;
311		}
312		$i++;
313	    }
314	    last unless $flag;
315	}
316	next unless $flag;
317	foreach $i (0 .. @{$words}) {
318	    if (@{$words}[$i] =~ /^$lastkey(.*)/i) {
319		splice @{$words}, $i, 1;
320		$byservice{$1} = $words;
321		last;
322	    }
323	}
324    }
325    close SVCFILE;
326    \%byservice;
327}
328
329# This reads a UUCP file that is keyed on the first token on each
330# line.  Duplicates are not permitted; the first encountered is used
331# and the rest are silently discarded.  A hash indexed on the first
332# token and containing an array of tokens in each bucket is returned.
333# Used for Dialcodes, Dialers, and Systems.
334sub uucpposfiles
335{
336    local(@files) = @_;
337    local($keyval,$words,%keyeddata);
338
339    foreach $file (@files) {
340	if (!open(POSFILE, '<' . $file)) {
341	    warn "$file: $!\n";
342	    next;
343	}
344	while (@{$words = uucpline(POSFILE, $file)}) {
345	    $keyval = shift @{$words};
346	    next if $keyeddata{$keyval};
347	    $keyeddata{$keyval} = $words;
348	}
349	close POSFILE;
350    }
351    \%keyeddata;
352}
353
354# This reads a UUCP file that is keyed on the first token on each line
355# and may have duplicate entries.  Each entry of the hash contains an
356# array.  Each entry of that array points to an array of tokens
357# representing one parsed line.  Used for the Devices file.
358sub uucpdevices
359{
360    local(@files) = @_;
361    local($keyval,$words,%keyeddata);
362
363    foreach $file (@files) {
364	if (!open(POSFILE, '<' . $file)) {
365	    warn "$file: $!\n";
366	    next;
367	}
368	while (@{$words = uucpline(POSFILE, $file)}) {
369	    $keyval = shift @{$words};
370	    push @{$keyeddata{$keyval}}, $words;
371	}
372	close POSFILE;
373    }
374    \%keyeddata;
375}
376
377# For a path defined in asppp.cf, copy over defaults, validate the
378# required options, and save in the hash to be returned.
379sub savepath
380{
381    local($paths, $options, $defref) = @_;
382    local($peer,$intf);
383
384    return if $options == $defref;
385    foreach $key (keys %$defref) {
386	$$options{$key} = $$defref{$key} unless defined($$options{$key});
387    }
388    $peer = $$options{"peer_system_name"};
389    warn("Discarded path with no peer system name.\n"), return
390	unless defined($peer);
391    $intf = $$options{"interface"};
392    warn("Missing interface on path to peer \"$peer\".\n"), return
393	unless defined($intf);
394    warn("Bad interface $intf on path to peer \"$peer\".\n"), return
395	unless $intf =~ /^ipd([0-9]+|ptp[0-9]+|ptp\*)$/;
396    warn("Missing peer IP address for point-to-multipoint path to \"",
397	$peer, "\".\n"), return
398	if $intf =~ /^ipd[0-9]+$/ && !defined($$options{"peer_ip_address"});
399    warn "Multiple definitions of path to peer \"$peer\".\n",
400	if defined($paths{$peer});
401    warn "Odd version number ", $$options{"version"},
402	" encountered in path to peer \"", $peer, "\" (ignored).\n"
403	if defined($$options{"version"}) && $$options{"version"} != 1;
404    $paths{$peer} = $options;
405}
406
407# Parse through asppp.cf. Unlike the UUCP files, lines don't matter
408# here.  The parsing is modal, with "path" introducing a PPP session
409# description and "defaults" reverting back to global definitions.
410sub readaspppcf
411{
412    local($aspppcf) = @_;
413    local(%aspppdkey) = (
414	chap_name		=> 1,
415	chap_peer_secret	=> 1,
416	chap_peer_name		=> 1,
417	chap_secret		=> 1,
418	debug_level		=> 1,
419	default_route		=> 0,
420	ifconfig		=> -1,
421	inactivity_timeout	=> 1,
422	interface		=> 1,
423# sic; aspppd is seriously confused!  ACCM isn't in IPCP.
424	ipcp_async_map		=> 1,
425	ipcp_compression	=> 1,
426	lcp_compression		=> 1,
427	lcp_mru			=> 1,
428	negotiate_address	=> 1,
429	pap_id			=> 1,
430	pap_password		=> 1,
431	pap_peer_id		=> 1,
432	pap_peer_password	=> 1,
433	peer_ip_address		=> 1,
434	peer_system_name	=> 1,
435	require_authentication	=> 2,
436	version			=> 1,
437	will_do_authentication	=> 2
438    );
439    local($words,$word,$prevword,$i,$errors,%defaults,%ifconfig,%paths);
440    local($options);
441
442    open ASPPPD, "<" . $aspppcf || die "$aspppcf: $!\n";
443    print "Reading configuration from $aspppcf\n" if $opt_v;
444    $defaults{inactivity_timeout} = 120;
445    $defaults{ipcp_compression} = "vj";
446    $defaults{lcp_compression} = "on";
447    $options = \%defaults;
448    while (@{$words = uucpline(ASPPPD, $aspppcf)}) {
449	if ($$words[0] =~ /^ifconfig$/i) {
450	    warn "$prevword with missing argument ignored.\n"
451		if defined($prevword);
452	    undef $prevword;
453	    shift @$words;	# discard 'ifconfig' keyword
454	    $word = shift @$words;
455	    warn("Bad interface on ifconfig $word.\n"), next
456		unless $word =~ /^ipd([0-9]+|ptp[0-9]+)$/;
457	    $ifconfig{$word} = \@$words;
458	    next;
459	}
460	unshift @{$words}, $prevword if defined($prevword);
461	undef $prevword;
462	while ($word = lc(shift @{$words})) {
463	    $_ = $word;
464	    if (/^defaults$/i) {
465		savepath(\%paths, $options, \%defaults);
466		$options = \%defaults;
467		next ;
468	    }
469	    if (/^path$/i) {
470		local(%pathopts);
471		savepath(\%paths, $options, \%defaults);
472		$options = \%pathopts;
473		next;
474	    }
475	    if (!defined($i = $aspppdkey{$word})) {
476		die "Too many errors in $aspppcf; aborting.\n"
477		    if ++$errors > 5;
478		warn "Ignoring unknown keyword $word in $aspppcf\n";
479		next;
480	    }
481	    warn("$_ unexpected; remainder of line ignored.\n"),
482		last if $i == -1;
483	    warn("Duplicate $_ in path ignored.\n"), next
484		if $options != \%defaults && defined($$options{$_});
485	    $$options{$_} = 1 if $i == 0;
486	    next if $i == 0;
487	    $prevword = $_, last unless defined($word = shift @{$words});
488	    $$options{$_} = $word if $i == 1;
489	    if ($i == 2) {
490		undef $$options{$_}, next if $word =~ /^off$/;
491		$$options{$_} = $word;
492		if ($word = shift @{$words}) {
493		    if ($word =~ /^(p|ch)ap$/) {
494			$$options{$_} .= " " . $word;
495		    } else {
496			unshift @{$words}, $word;
497		    }
498		}
499	    }
500	}
501    }
502    warn "Odd trailing keyword \"$prevword\" ignored in $aspppcf\n"
503	if $prevword;
504    savepath(\%paths, $options, \%defaults);
505    die "No paths defined for aspppd.\n" if 0+(keys %paths) == 0;
506    die "No interfaces defined for aspppd.\n" if 0+(keys %ifconfig) == 0;
507    if ($opt_v) {
508	nof 0+(keys %paths), "path", " and ";
509	nof 0+(keys %ifconfig), "interface", " defined for aspppd.\n";
510    }
511    close ASPPPD;
512    ( \%ifconfig, \%paths );
513}
514
515# Read /etc/passwd (or NIS) and return hash of users for whom
516# the default shell is aspppls.  Each hash entry contains the user's
517# home directory path.
518sub readpasswd
519{
520    local(%users,@pwe);
521
522    setpwent();
523    while (@pwe = getpwent()) {
524	$users{$pwe[0]} = $pwe[7] if $pwe[8] =~ /\/aspppls$/;
525    }
526    endpwent();
527    nof 0+(keys %users), "aspppd dial in user", " found.\n"
528	if $opt_v;
529    \%users;
530}
531
532# Parse through pmadm output to find enabled serial ports.
533# Field 9 has 'I' (modem dial-out only or initialize only), 'b'
534# (bidirectional) or is blank (modem dial-in only or terminal-hardwired).
535# For that latter case, field 18 (software-carrier) has 'y'.
536# Field 3 has 'x' if disabled.
537sub getserialports
538{
539    local(%dialin, %dialout);
540
541    open PMADM, "pmadm -L|" || (warn "pmadm: $!\n", return undef);
542    while (<PMADM>) {
543	split /:/;
544	if ($_[3] !~ /x/) {
545	    $dialin{$_[2]} = $_[8] if $_[9] ne "I";
546	    $dialout{$_[2]} = $_[8] if $_[9] ne "";
547	}
548    }
549    close PMADM;
550    ( \%dialin, \%dialout );
551}
552
553# Convert an ifconfig statement into a local and remote address pair.
554sub ifconf_addr
555{
556    local($ifconf) = @_;
557    local($arg, $narg, $lcladdr, $remaddr);
558
559    shift @$ifconf;	# lose the interface name
560    while (@$ifconf) {
561	local($arg, $narg);
562	$arg = shift @$ifconf;
563	$narg = shift @$ifconf if $ifconfigtakes{$arg};
564	if (exists($ifconfigtakes{$arg})) {
565	    if ($arg eq "set") {
566		$lcladdr = $narg;
567	    } elsif ($arg eq "destination") {
568		$remaddr = $narg;
569	    }
570	} elsif (!defined($lcladdr)) {
571	    $lcladdr = $arg;
572	} elsif (!defined($remaddr)) {
573	    $remaddr = $arg;
574	}
575    }
576    ( $lcladdr, $remaddr );
577}
578
579# Convert a hash of aspppd options into an array of pppd options.  The
580# third argument ($chatpath) is undef for dial-in or a path to the
581# chat script file otherwise.
582sub convert_options
583{
584    local ($pppdargs, $opts, $chatpath, $user) = @_;
585
586# Do the pppd option conversions.
587    push(@$pppdargs, "defaultroute") if $$opts{default_route};
588    push(@$pppdargs, "idle " . $$opts{inactivity_timeout})
589	if $$opts{inactivity_timeout} != 0;
590    push(@$pppdargs, "asyncmap " . $$opts{ipcp_async_map})
591	if $$opts{ipcp_async_map};
592    push(@$pppdargs, "novj") if !$$opts{ipcp_compression};
593    local($peer);
594    if ($$opts{require_authentication}) {
595	local (@authopts);
596	if ($$opts{require_authentication} =~ /chap/) {
597	    push(@authopts, "require-chap");
598	    $peer = $$opts{chap_peer_name};
599	} else {
600	    push(@authopts, "require-pap");
601	    $peer = $$opts{pap_peer_id};
602	}
603	push(@authopts, "remotename " . requote($peer)) if $peer;
604	push(@authopts, "auth");
605	if ($chatpath) {
606	    push(@$pppdargs, @authopts);
607	} elsif ($dialin_auth == 3) {
608# mixed authentication; must use wrapper script.
609	    local($sfile) = $pppdir . "dial-in." . $user;
610	    $scriptfiles{$sfile}  = "#!/bin/sh\n";
611	    $scriptfiles{$sfile} .= "exec /usr/bin/pppd @authopts\n";
612	    $dialinshell{$user} = $sfile;
613	}
614    } elsif ($dialin_auth < 2) {
615	push(@$pppdargs, "noauth");
616    }
617    push(@$pppdargs, "noaccomp nopcomp") if !$$opts{lcp_compression};
618    push(@$pppdargs, "mru " . $$opts{lcp_mru}) if $$opts{lcp_mru};
619    push(@$pppdargs, "noipdefault ipcp-accept-local")
620	if $$opts{negotiate_address};
621    local($myname,$csecret,$psecret,$passopt);
622    if ($$opts{will_do_authentication} =~ /chap/i) {
623	$myname = $$opts{chap_name};
624	$csecret = $$opts{chap_secret};
625    }
626    $myname = "" if !$myname;
627    if ($$opts{will_do_authentication} =~ /pap/i) {
628	if ($myname ne "" && $$opts{pap_id} ne "" &&
629	    $myname ne $$opts{pap_id}) {
630	    warn "pppd cannot have separate local names for PAP and CHAP; using CHAP name:\n";
631	    warn "\t\"$myname\"\n";
632	} else {
633	    $myname = $$opts{pap_id} if $$opts{pap_id} ne "";
634	}
635	$psecret = $$opts{pap_password};
636    }
637    if ($$opts{will_do_authentication}) {
638	if ($chatpath &&
639	    ($$opts{will_do_authentication} !~ /chap/i ||
640	     $$opts{will_do_authentication} !~ /pap/i ||
641	     $csecret eq $psecret)) {
642	    push(@$pppdargs,
643		"password " . requote($csecret ? $csecret : $psecret));
644	    $passopt = 1;
645	}
646	push @$pppdargs, "user " . requote($myname);
647	push(@$pppdargs, "remotename " . requote($peer))
648	  if $peer && !$$opts{require_authentication};
649    } else {
650	$myname = "NeverAuthenticate";
651    }
652
653    push(@$pppdargs, "debug") if $$opts{debug_level} >= 8;
654    push(@$pppdargs, "kdebug 3") if $$opts{debug_level} >= 9;
655    local($lcladdr, $remaddr) = ifconf_addr($$ifconfig{$$opts{interface}});
656    if ($chatpath) {
657	if ($$opts{debug_level} >= 4) {
658	    push(@pppdargs, "connect \"/usr/bin/chat -vf $chatpath\"");
659	} else {
660	    push(@pppdargs, "connect \"/usr/bin/chat -f $chatpath\"");
661	}
662	push(@$pppdargs, "user " . requote($myname));
663	local($str) = $remaddr;
664	$str = $$opts{peer_ip_address} if $$opts{peer_ip_address};
665	push(@$pppdargs, $lcladdr . ":" . $str)
666	    if !$opt_s && ($lcladdr || $str);
667    } else {
668	push(@$pppdargs, "user " . requote($myname))
669	    if $myname eq "NeverAuthenticate";
670    }
671    if ($$opts{interface} && $opt_s) {
672	if ($$opts{interface} =~ /^ipd[0-9]+$/) {
673	    push(@$pppdargs, $lcladdr . ":") if $lcladdr;
674	} elsif ($$opts{interface} =~ /^ipd(|ptp)([0-9]+)$/) {
675	    push(@pppdargs, "unit " . $2);
676	} else {
677	    push(@pppdargs, "plumbed");
678	}
679    }
680
681# Convert the secrets
682    if ($chatpath) {
683	$addsecret = "";
684    } elsif ($$opts{peer_ip_address}) {
685	$addsecret = " " . $$opts{peer_ip_address};
686    } else {
687	$addsecret = " *";
688    }
689    if ($$opts{require_authentication}) {
690	local($lclname, $secret, $authf);
691	$lclname = $$opts{will_do_authentication} ? $myname : hostname();
692	if ($$opts{require_authentication} =~ /chap/) {
693	    $secret = $$opts{chap_peer_secret};
694	    $authf = \%chapsecrets;
695	} else {
696	    $secret = $$opts{pap_peer_password};
697	    $authf = \%papsecrets;
698	}
699	${$$authf{$peer}}{$lclname} = requote($secret) . $addsecret;
700    }
701    if ($$opts{will_do_authentication} && !$passopt) {
702	${$chapsecrets{$myname}}{$peer} = requote($csecret) if $csecret;
703	${$papsecrets{$myname}}{$peer} = requote($psecret) if $psecret;
704    }
705}
706
707# Translate options for a dial-in user.
708sub translatedialin
709{
710    local($peer) = @_;
711
712    $optname = $$dialinusers{$peer};
713    $optname .= "/" if $optname !~ /\/$/;
714    $optname .= ".ppprc";
715    if (exists($optfiles{$optname})) {
716	warn "Home directories of $peer and $optuser{$optname} are the same ($optfiles{$optname}\n";
717	warn "Ignoring configuration for $peer.\n";
718	return;
719    }
720    $optuser{$optname} = $peer;
721    $dialinshell{$peer} = "/usr/bin/pppd";
722    local (@pppdargs);
723    convert_options(\@pppdargs, $$paths{$peer}, undef, $peer);
724    push @pppdargs, "nologfd";
725    $optfiles{$optname} = \@pppdargs;
726}
727
728# Translate ifconfig entries in asppp.cf into either an ifconfig
729# script for strict translation or a set of per-port IP addresses
730# for normal translation.  Also translate ipdX interfaces into
731# Ethernet aliases to make routing daemon happy.
732sub translateifconfig
733{
734    local (@ifconfiglist,$cstr,$etherif,$intf,$ifconf,$addstr,$port);
735
736    open IFLIST, "/usr/sbin/ifconfig -au4|" || die "cannot run ifconfig: $!\n";
737    while (<IFLIST>) {
738	$etherif = $1 if !$etherif && /^([^:]*).*UP.*BROADCAST/;
739    }
740    close IFLIST;
741    $etherif = $1 if !$etherif && glob("/etc/hostname.*") =~ /[^\.]*.(.*)/;
742    $etherif = $1 if !$etherif && glob("/etc/dhcp.*") =~ /[^\.]*.(.*)/;
743    $etherif = "hme0" if !$etherif;
744
745    $cstr = "";
746    foreach $intf (keys %$ifconfig) {
747	$ifconf = $$ifconfig{$intf};
748	if ($intf =~ /ipd[0-9]+/) {
749	    shift @$ifconf;
750	    $cstr .= "ifconfig $etherif addif @$ifconf\n";
751	}
752    }
753
754    $ndialin = 0+(keys %$dialin);
755    $addstr = "";
756    foreach $intf (keys %downif) {
757	$ifconf = $downif{$intf};
758	if ($intf =~ /ipdptp([0-9]+)/ && --$ndialin >= 0) {
759	    push @ifconfiglist, $ifconf;
760	    $addstr .= "ifconfig sppp" . $1 . " @$ifconf\n";
761	}
762    }
763    if ($ndialin > 0) {
764	if (@ifconfiglist) {
765	    print "Fewer ifconfigs (", $#ifconfiglist+1,
766	    ") than dial-in lines (", $ndialin+$#ifconfiglist+1, ")\n";
767	} else {
768	    print "No ifconfigs for ";
769	    nof $ndialin, "dial-in port", ".\n";
770	}
771    } elsif ($ndialin < 0) {
772	if (@ifconfiglist) {
773	    print "Ignoring ", -$ndialin, " of ";
774	    nof $#ifconfiglist-$ndialin+1, "ifconfig",
775	    " (too few dial-in ports)\n";
776	} else {
777	    print "Ignoring all ";
778	    nof -$ndialin, "ifconfig line", " (no dial-in ports)\n";
779	}
780    }
781
782    if ($opt_s) {
783	# Strict translation uses pre-plumbed interfaces.
784	$cstr .= $addstr;
785    } else {
786	foreach $port (values %$dialin) {
787	    last if !@ifconfiglist;
788	    $port =~ s+/dev/++;
789	    $port =~ s+/+.+g;
790	    $optfile = $ttyprefix . $port;
791	    $ifconf = pop @ifconfiglist;
792	    local ($lcladdr, $remaddr) = ifconf_addr($ifconf);
793	    next if !defined($lcladdr) || !defined($remaddr);
794	    local (@pppdargs) = $lcladdr . ":" . $remaddr;
795	    $optfiles{$optfile} = \@pppdargs;
796	}
797    }
798    $scriptfiles{$pppdir . "ifconfig"} = $cstr if $cstr;
799}
800
801# Attempt to modify global passwd file using sed script stored in
802# the $sedpasswd temporary file.
803sub rewrite_passwd
804{
805    print "Updating local passwd file (if any).\n" if $opt_v;
806    if (!sysopen(PWDLCK, $passwdlck, O_WRONLY|O_CREAT, 0600)) {
807	warn "Unable to lock password file: $!\n";
808    } else {
809	$lockstr = pack "ssLLiiLLLL", F_WRLCK, 0, 0, 0, 0, 0, 0, 0, 0, 0;
810	eval {
811	    local $SIG{ARLM} = sub {
812		die "alarm while locking password file\n"
813	    };
814	    alarm 15;
815	    fcntl PWDLCK, F_SETLKW, $lockstr ||
816	      die "cannot lock password file: $!\n";
817	    alarm 0;
818	};
819	if ($@) {
820	    warn $@;
821	} else {
822	    warn "Password update failed.\n"
823	      if (system("sed -f $sedpasswd < $passwd > ${passwd}.new") ||
824		  !(rename "${passwd}.new", $passwd));
825	}
826	$lockstr = pack "ssLLiiLLLL", F_UNLCK, 0, 0, 0, 0, 0, 0, 0, 0, 0;
827	fcntl PWDLCK, F_SETLK, $lockstr;
828	close PWDLCK;
829    }
830    if (($ypmaster = `/usr/bin/ypwhich 2>/dev/null`) && $? == 0) {
831	$ypmaster =~ /(.*)\n/;
832	($ypmaster) = gethostbyname($1);
833	($thishost) = gethostbyname(hostname);
834	if ($ypmaster eq $thishost) {
835	    system("cd /var/yp && make")
836	      if yesno("Rebuild NIS/YP maps", $opt_y);
837	} else {
838	    warn "Not running on NIS/YP master $1; unable to update user shells\n";
839	    print "Use 'sed -f $sedpasswd <$passwd >${passwd}.new' on the master\n";
840	    print "and then remake the NIS/YP database.\n";
841	    undef $sedpasswd;
842	}
843    }
844    unlink $sedpasswd if $sedpasswd;
845}
846
847# Show usage message.
848sub usage
849{
850    print "Usage:\n\n";
851    print "\t$0 [-rsvy]\n\n";
852    print "    -n - non-interactive mode.\n";
853    print "    -r - revert back to aspppd configuration.\n";
854    print "    -s - use strict translation.\n";
855    print "    -v - print more detail of the operations performed.\n";
856    print "    -y - assume 'yes' as default answer where reasonable.\n";
857    exit;
858}
859
860# Correct an environment variable so that it points at either a useful
861# executable program, or nothing at all.
862sub fixpath
863{
864    local ($prog, $deflt) = @_;
865
866    $prog = $deflt if $prog eq "";
867    if ($prog !~ /^(\.\/|\/)/) {
868	local ($_) = $ENV{PATH};
869	$_ = "/bin:/usr/bin:/sbin:/usr/sbin" if $_ eq "";
870	split /:/;
871	foreach (@_) {
872	    $prog = $_ . "/" . $prog, last if -x $_ . "/" . $prog;
873	}
874    }
875    $prog = "" if !(-x $prog);
876    $prog;
877}
878
879getopts('nrsvy') || usage;
880
881die "Need permission to modify system files.\n"
882    unless ($> == 0 || yesno "This script should be run as root.  Continue");
883
884if ($opt_r) {
885    local ($intemp);
886
887# Revert to previous configuration.  Just rename the aspppd file back
888# and undo changes to the passwd file.
889
890    die "No saved aspppd configuration exists.\n" unless -f $asmoved;
891    if (-e $astemp) {
892	die "$astemp is not a file\n" unless -f $asfile;
893	unlink $astemp || die "Cannot remove temporary $astemp: $!\n";
894    }
895    $intemp = 0;
896    if (-e $asfile) {
897	die "$asfile is not a file\n" unless -f $asfile;
898	die "Not modifying configuration.\n"
899	    unless yesno "Remove existing $asfile", $opt_y;
900	rename $asfile, $astemp || die "Cannot rename existing $asfile: $!\n";
901	$intemp = 1;
902    }
903
904    if (rename $asmoved, $asfile) {
905	unlink $astemp || warn "$astemp: $!\n" if $intemp;
906    } else {
907	$failure = "Cannot rename $asmoved to $asfile: $!\n";
908	rename $astemp, $asfile ||
909	    die "$failure\nand cannot recover: $!\n" .
910		"Saved current asppp.cf in $astemp\n"
911		    if $intemp;
912	die $failure;
913    }
914
915    $( = $);
916    $< = $>;
917
918    system($pppdctl, "stop") if -x $pppdctl;
919    # remove pppd autostart files.
920    unlink $pppdir . "ifconfig";
921    unlink $pppdir . "demand";
922
923    system($asctl, "start") if -x $asctl;
924
925    open SEDFILE, ">$sedpasswd" || die "Cannot write $sedpasswd: $!\n";
926    local ($escdir) = $pppdir;
927    $escdir =~ s+/+\\/+g;
928    print SEDFILE "/${escdir}dial-in\\./s+[^:]*\$+/usr/sbin/aspppls+\n";
929    print SEDFILE "/\\/usr\\/bin\\/pppd/s+[^:]*\$+/usr/sbin/aspppls+\n";
930    close SEDFILE;
931
932    rewrite_passwd;
933
934    exit 0;
935}
936
937$aspppcf = $asfile;
938if (!(-f $asfile)) {
939    die "No aspppd configuration exists; nothing to convert.\n"
940	unless -f $asmoved;
941    die "No changes made.\n"
942	unless yesno "Already converted; rerun anyway";
943    $aspppcf = $asmoved;
944}
945
946print "This script provides only a suggested translation for your existing aspppd\n";
947print "configuration.  You will need to evaluate for yourself whether the translation\n";
948print "is appropriate for your operating environment.\n";
949die "No changes made.\n"
950  unless yesno "Continue", 1;
951
952# Read in the asppp.cf file first; there's no reason to continue on to
953# the UUCP files if this file isn't readable or has no paths defined.
954local($ifconfig, $paths) = readaspppcf($aspppcf);
955
956# Loop over the ifconfigs and build a list of the down ones.
957foreach $intf (keys %$ifconfig) {
958    local(@words) = @{$$ifconfig{$intf}};
959    while ($word = shift @words) {
960	shift @words if $ifconfigtakes{$word};
961	if ($word =~ /^down$/) {
962	    warn("Why is $intf declared down?\n"), last
963		if $intf =~ /^ipd[0-9]+$/;
964	    $downif{$intf} = $$ifconfig{$intf};
965	    delete $$ifconfig{$intf};
966	    last;
967	}
968    }
969}
970
971# Read /etc/passwd for dial-in users configured for aspppd.
972local($dialinusers) = readpasswd;
973
974# Read in existing pppd configuration.  All we really care about
975# is the setting of the "auth" option.
976undef $authoption;
977if (open(OPTIONS,"<" . $options)) {
978    while (@{$words = uucpline(OPTIONS, $options)}) {
979	while ($_ = pop @$words) {
980	    $authoption = $_ if /auth/i;
981	}
982    }
983    close OPTIONS;
984    $authoption = "unknown" if !defined($authoption);
985}
986
987$dialin_auth = 0;
988if ($authoption =~ /^auth$/i) {
989    $dialin_auth = 1;
990} elsif ($authoption =~ /^noauth$/i) {
991    $dialin_auth = 2;
992} elsif (defined($authoption)) {
993    $dialin_auth = 3;
994}
995
996# Check that there's a path for each dial in user
997foreach $user (keys %$dialinusers) {
998    if (!defined($$paths{$user})) {
999	warn "Dial-in user ", $user,
1000	    " does not have a corresponding dial-in path.\n";
1001	delete $$dialinusers{$user};
1002	next;
1003    }
1004    $intf = ${$$paths{$user}}{"interface"};
1005    if ($intf eq "ipdptp*") {
1006	if (0+keys(%downif) == 0) {
1007	    warn "Dial-in user $path has no available \"down\" interfaces.\n";
1008	    delete $$dialinusers{$user};
1009	    next;
1010	}
1011    } else {
1012	if (!defined($downif{$intf}) && !defined($$ifconfig{$intf})) {
1013	    warn "Dial-in path $user has undefined $intf; deleted.\n";
1014	    delete $$dialinusers{$user};
1015	    next;
1016	}
1017    }
1018    ${$$paths{$user}}{$isdialin} = 1;
1019# 0 - no info (no options file, "noauth" on call)
1020# 1 - all auth ("auth" in options, "noauth" on call)
1021# 2 - all noauth ("noauth" in options)
1022# 3 - mixed; use auth ("noauth" in options, wrapper script for "auth")
1023    if (${$$paths{$user}}{require_authentication}) {
1024	if ($dialin_auth == 2) {
1025	    $dialin_auth = 3;
1026	} elsif ($dialin_auth == 0) {
1027	    $dialin_auth = 1;
1028	}
1029    } else {
1030	if ($dialin_auth == 1) {
1031	    $dialin_auth = 3;
1032	} elsif ($dialin_auth == 0) {
1033	    $dialin_auth = 2;
1034	}
1035    }
1036}
1037
1038# Get lists of usable dial-in and dial-out ports.
1039local($dialin,$dialout) = getserialports;
1040
1041# Read and parse the UUCP Sysfiles, Devconfig, and Limits files.
1042# These are keyed with the "service=" string.  The Sysfiles file can
1043# augment or override the list of files read for a given service.
1044print "Reading UUCP configuration.\n" if $opt_v;
1045@sysfiles = @{${uucpkeyfile($Sysfiles,"service=")}{"ppp"}};
1046@limits = @{${uucpkeyfile($Limits,"service=")}{"ppp"}};
1047%devconfig = %{uucpkeyfile($Devconfig,"service=ppp","device=")};
1048
1049# Now read in the UUCP files corresponding to this service.
1050$systems = uucpposfiles(uucpfiles("systems"));
1051$dialers = uucpposfiles(uucpfiles("dialers"));
1052$dialcodes = uucpposfiles($Dialcodes);
1053$devices = uucpdevices(uucpfiles("devices"));
1054
1055# just to make sure
1056$$dialcodes{""} = ();
1057
1058# Loop over paths.  Dial-out only paths are translated into demand-dial
1059# configurations.  Dial-in only paths are translated into appropriate
1060# log-in entries.
1061local (@bidirectional);
1062foreach $peer (keys %$paths) {
1063    if (exists($$systems{$peer})) {
1064	$sline = $$systems{$peer};
1065	if ($$sline[0] eq "Never") {
1066	    if (${$$paths{$peer}}{$isdialin}) {
1067		translatedialin($peer);
1068	    } else {
1069		print "We never call $peer, and he never calls us.\n"
1070		    if $opt_v;
1071	    }
1072	    delete $$paths{$peer};
1073	    next;
1074	}
1075	push @bidirectional, $peer if ${$$paths{$peer}}{$isdialin};
1076	print "Ignoring time restriction on $peer\n"
1077	    if $$sline[0] ne "Any";
1078	$dlist = $$devices{$$sline[1]};
1079	$class = $$sline[2];
1080	$i = 0;
1081	while ($i < @$dlist) {
1082	    local($dev) = $$dlist[$i];
1083	    if ($$dev[1] ne "-") {
1084		print "Ignoring device $$dev[0]; 801-type not supported.\n";
1085		splice @$dlist, $i, 1;
1086		next;
1087	    }
1088	    $i++;
1089
1090	    # Make sure that classes match.
1091	    next if $$dev[2] ne "Any" && $class ne "Any" && $$dev[2] ne $class;
1092	    # Prepend "/dev/" if it's not present in the device name.
1093	    if (exists($$dialout{$$dev[0]})) {
1094		# This just seems odd.
1095		$dname = $$dialout{$$dev[0]};
1096		$dname =~ s+/dev/term/+/dev/cua/+;
1097	    } else {
1098		$dname = ($$dev[0] =~ m+^/+ ? $$dev[0] : ("/dev/" . $$dev[0]));
1099	    }
1100	    # Skip devices that aren't supposed to be used for dial-out.
1101	    next if $dname =~ m+^/dev/term/+;
1102	    next if $dname =~ m+^/dev/tty[a-z]$+;
1103	    # Make sure this is a character device and we have access to it.
1104	    next unless -w $dname && -c $dname;
1105	    warn "Dialer for $$dev[3] is missing.\n"
1106		unless exists($warned{$$dev[3]}) ||
1107		    exists($$dialers{$$dev[3]});
1108	    $warned{$$dev[3]} = 1;
1109
1110	    # Expand keywords from Dialcodes file.  Should have \T or \D.
1111	    $phone = $$sline[3];
1112	    $xphone = ($$dev[4] eq "\\T" && $phone =~ /^([A-Za-z]*)(.*)$/ ?
1113	        "@{$$dialcodes{$1}}" . $2 : $phone);
1114
1115	    # Make a copy of the dialing script.
1116	    local(@dials) = @{$$dialers{$$dev[3]}};
1117
1118	    # Translate dial tone and wait characters from Dialers file.
1119	    $_ = shift @dials;
1120	    s[(.)(.)]{
1121		local($from,$to) = ($1,$2);
1122		$phone =~ s+(^|[^\\])$from+$1$to+gx;
1123		$xphone =~ s+(^|[^\\])$from+$1$to+gx;
1124	    }ge;
1125
1126	    # Translate escapes in dial specification.  Chat has a \T,
1127	    # but uses \U instead of \D.
1128	    local($needt, $needu, $isexpect, @chats) = ("", "", 1);
1129	    foreach $str (@dials) {
1130		push(@chats, "") if $str eq "";
1131		local ($ostr) = "";
1132		if ($isexpect) {
1133		    while ($str =~ s/([^\\]*)\\(.)//) {
1134			local($lead, $_) = ($1, $2);
1135			/[Mm]/ ? ($ostr .= $lead) :
1136			/[Ee]/ ? ($sorrye = 1, $ostr .= $lead) :
1137			($ostr .= $lead . "\\" . $_);
1138		    }
1139		} else {
1140		    while ($str =~ s/([^\\]*)\\(.)//) {
1141			local($lead, $_) = ($1, $2);
1142			/T/ ? ($needt = " -T '$xphone'",
1143			       $ostr .= $lead . "\\T") :
1144			/D/ ? ($needu = " -U '$phone'",
1145			       $ostr .= $lead . "\\U") :
1146			/M/ ? ($ostr .= $lead,
1147			       ($ostr ne "" ? push(@chats, $ostr, "\\c"):0),
1148			       push(@chats, "HANGUP", "OFF"), $ostr = "") :
1149			/m/ ? ($ostr .= $lead,
1150			       ($ostr ne "" ? push(@chats, $ostr, "\\c"):0),
1151			       push(@chats, "HANGUP", "ON"), $ostr = "") :
1152			/[Ee]/ ? ($sorrye = 1, $ostr .= $lead) :
1153			/[dp]/ ? ($ostr .= $lead . "\\" . $_ . "\\" . $_) :
1154			/c/ ? ($str eq "" ? ($ostr .= $lead . "\\c") : 0) :
1155			($ostr .= $lead . "\\" . $_);
1156		    }
1157		}
1158		$ostr .= $str;
1159		push @chats, $ostr if $ostr ne "";
1160		$isexpect = !$isexpect;
1161	    }
1162
1163	    # Pad out dial list if we're missing a "send" string and tack
1164	    # on the chat list from the Systems file.
1165	    if (defined $$sline[4]) {
1166		push @chats, "\\c" if !$isexpect;
1167		push @chats, (splice @$sline, 4);
1168	    }
1169
1170	    $chatfile = $pppdir . "chat.$peer.$$dev[3]";
1171	    if (-e $chatfile) {
1172		print "$chatfile already exists.\n";
1173		if (!yesno("Should it be overwritten",$opt_y)) {
1174		    if (yesno("Should it be used as-is")) {
1175			warn "Using $chatfile as-is; it may not be correct.\n";
1176		    } else {
1177			for ($n = 0; ; $n++) {
1178			    last if !(-e $chatfile . "." . $n);
1179			}
1180			$chatfile .= "." . $n;
1181			print "Using $chatfile instead.\n";
1182			$chatfiles{$chatfile} = \@chats;
1183		    }
1184		} else {
1185		    $overwrite{$chatfile} = 1;
1186		    $chatfiles{$chatfile} = \@chats;
1187		}
1188	    } else {
1189		$chatfiles{$chatfile} = \@chats;
1190	    }
1191
1192	    push @pppdargs, $dname;
1193	    push @pppdargs, $class if $class =~ /^[0-9]+$/;
1194	    push @pppdargs, "demand";
1195	    convert_options(\@pppdargs,$$paths{$peer},
1196		$chatfile . $needt . $needu, undef);
1197
1198	    $optname = $peersdir . $peer;
1199	    if (-e $optname) {
1200		print "$optname already exists.\n";
1201		if (!yesno("Should it be overwritten", $opt_y)) {
1202		    if (yesno("Should it be used as-is")) {
1203			warn "Using $optname as-is; it may not be correct.\n";
1204		    } else {
1205			for ($n = 0; ; $n++) {
1206			    last if !(-e $optname . "." . $n);
1207			}
1208			$optname .= "." . $n;
1209			print "Using $optname instead.\n";
1210			$optfiles{$optname} = \@pppdargs;
1211		    }
1212		} else {
1213		    $overwrite{$optname} = 1;
1214		    $optfiles{$optname} = \@pppdargs;
1215		}
1216	    } else {
1217		$optfiles{$optname} = \@pppdargs;
1218	    }
1219	    $scriptfiles{$pppdir . "demand"} .= "/usr/bin/pppd file $optname\n";
1220	    last;
1221	}
1222    } elsif (${$$paths{$peer}}{$isdialin}) {
1223    	translatedialin($peer);
1224    } else {
1225	warn "Path $peer has no dial-in user nor Systems file entry.\n";
1226	delete $$paths{$peer};
1227    }
1228}
1229
1230warn "Chat cannot do echo checking; requests for this removed.\n" if $sorrye;
1231
1232if (@bidirectional) {
1233    print "\nWarning:  The following paths are bidirectional:\n";
1234    print "\t@bidirectional\n\n";
1235    print "Bidirectional paths (with entries in both Systems and passwd) do not translate\n";
1236    print "into Solaris PPP 4.0 semantics in an exact manner.  The dial-out portion will\n";
1237    print "use the designated interface, but the dial-in portion will use any available\n";
1238    print "interface.\n";
1239    while ($peer = pop @bidirectional) {
1240	delete $ {$$paths{$peer}}{interface};
1241	translatedialin($peer);
1242    }
1243}
1244
1245translateifconfig;
1246
1247# Create an /etc/ppp/options if we need to.
1248if (!defined($authoption) && $dialin_auth > 0) {
1249    local (@pppdopts);
1250    push @pppdopts, "lock";
1251    push @pppdopts, "auth" if $dialin_auth == 1;
1252    push @pppdopts, "noauth" if $dialin_auth > 1;
1253    $optfiles{$options} = \@pppdopts;
1254}
1255# Translate option files to plain text.
1256foreach $file (keys %optfiles) {
1257    local ($opts) = $optfiles{$file};
1258    local ($cstr) = "";
1259    $cstr .= shift(@$opts) . "\n" while @$opts;
1260    $optfiles{$file} = $cstr;
1261}
1262# Change "auth" to "noauth" or add "noauth" to /etc/ppp/options.
1263if (defined($authoption) && $authoption ne "noauth" && $dialin_auth == 3) {
1264    local(@triplet, $cstr);
1265    if ($authoption eq "unknown") {
1266	warn "Adding 'noauth' to $options\n";
1267    } else {
1268	warn "Changing 'auth' in $options to 'noauth'\n";
1269    }
1270    open(OPTIONS,"<" . $options) || die "$options disappeared: $!\n";
1271    while (@{$words = uucpline(OPTIONS, $options, \@triplet)}) {
1272	$cstr .= $triplet[0];
1273	if (grep(/auth/, @$words)) {
1274	    local(@newwords) = map { $_ = "noauth" if /auth/; $_ } @$words;
1275	    $cstr .= "@newwords";
1276	} else {
1277	    $cstr .= $triplet[1];
1278	}
1279	while (pop @$words) {
1280	    $authoption = $_ if /auth/i;
1281	}
1282	$cstr .= $triplet[2];
1283    }
1284    $cstr .= $triplet[0] . $triplet[2];
1285    close OPTIONS;
1286    $cstr .= "\n" if $cstr !~ /\n$/;
1287    $cstr .= "noauth\n" if $authoption eq "unknown";
1288    $optfiles{$options} = $cstr;
1289}
1290
1291# Create a sed script to fix the users' shell paths.
1292if (0+(keys %dialinshell) != 0) {
1293    $cstr = "";
1294    foreach $peer (keys %dialinshell) {
1295	$cstr .= "/^$peer:/s+[^:]*/aspppls\$+$dialinshell{$peer}+\n";
1296    }
1297    $scriptfiles{$sedpasswd} = $cstr;
1298}
1299
1300print "\nPreparing to write out translated configuration:\n";
1301
1302# Enumerate the files we'll write.
1303$nfiles = 0;
1304if (0+(keys %chatfiles) != 0) {
1305    print "    ";
1306    nof 0+(keys %chatfiles), "chat file", ":\n";
1307    foreach $file (keys %chatfiles) {
1308	$nfiles++;
1309	print "\t$nfiles.  $file\n";
1310	local ($chats) = $chatfiles{$file};
1311	local ($cstr) = "";
1312	while (@$chats) {
1313	    $cstr .= requote(shift(@$chats));
1314	    $cstr .= " " . requote(shift(@$chats)) if @$chats;
1315	    $cstr .= "\n";
1316	}
1317	local (@filerec) = ( $file, $cstr );
1318	push @allfiles, \@filerec;
1319    }
1320}
1321if (0+(keys %optfiles) != 0) {
1322    print "    ";
1323    nof 0+(keys %optfiles), "option file", ":\n";
1324    foreach $file (keys %optfiles) {
1325	$nfiles++;
1326	print "\t$nfiles.  $file\n";
1327	local (@filerec) = ( $file, $optfiles{$file} );
1328	push @allfiles, \@filerec;
1329    }
1330}
1331if (0+(keys %scriptfiles) != 0) {
1332    print "    ";
1333    nof 0+(keys %scriptfiles), "script file", ":\n";
1334    foreach $file (keys %scriptfiles) {
1335	$nfiles++;
1336	print "\t$nfiles.  $file\n";
1337	local (@filerec) = ( $file, $scriptfiles{$file} );
1338	push @allfiles, \@filerec;
1339    }
1340}
1341
1342# Merge new secrets needed with existing ones, if any.
1343sub merge_secrets
1344{
1345    local ($addsecrets, $fname) = @_;
1346    local ($file, $cstr, @triplet, $newsecret);
1347
1348    $nfiles++;
1349    $file = $pppdir . $fname;
1350    print "\t$nfiles.  $file\n";
1351    if (open(SECRETS, '<' . $pppdir . $fname)) {
1352	while (@{$words = uucpline(SECRETS, $pppdir . $fname, \@triplet)}) {
1353	    $cstr .= $triplet[0];
1354	    $newsecret = $ {$$addsecrets{$$words[0]}}{$$words[1]};
1355	    if (defined $newsecret) {
1356		$cstr .= requote($$words[0]) . " " . requote($$words[1]) .
1357		  " " . $newsecret;
1358		delete $ {$$addsecrets{$$words[0]}}{$$words[1]};
1359	    } else {
1360		$cstr .= $triplet[1];
1361	    }
1362	    $cstr .= $triplet[2];
1363	}
1364	close SECRETS;
1365	$cstr .= $triplet[0] . $triplet[2];
1366    }
1367    foreach $key1 (keys (%$addsecrets)) {
1368	foreach $key2 (keys (%{$$addsecrets{$key1}})) {
1369	    $cstr .= requote($key1) . " " . requote($key2) . " " .
1370	      $ {$$addsecrets{$key1}}{$key2} . "\n";
1371	}
1372    }
1373    local (@filerec) = ( $file, $cstr );
1374    push @allfiles, \@filerec;
1375}
1376
1377$nchap = 0+(keys %chapsecrets) != 0;
1378$npap = 0+(keys %papsecrets) != 0;
1379if ($nchap != 0 || $npap != 0) {
1380    print "    ";
1381    nof $nchap + $npap, "secrets file", ":\n";
1382    merge_secrets(\%chapsecrets, "chap-secrets") if $nchap != 0;
1383    merge_secrets(\%papsecrets, "pap-secrets") if $npap != 0;
1384}
1385
1386die "Nothing to write back; I'm done.\n" if $nfiles == 0;
1387
1388$PAGER = fixpath($ENV{PAGER}, "/usr/bin/less");
1389$EDITOR = fixpath($ENV{EDITOR}, "/usr/bin/vi");
1390$SHELL = fixpath($ENV{SHELL}, "/usr/bin/ksh");
1391
1392END {
1393    if ($tempname) {
1394	unlink($tempname) or
1395	    die "Cannot remove temporary file $tempname: $!\n";
1396    }
1397}
1398
1399sub show_file_options
1400{
1401    print "\nEnter option number:\n";
1402    print "\t1 - view contents of file on standard output\n";
1403    print "\t2 - view contents of file using $PAGER\n" if $PAGER ne "";
1404    print "\t3 - edit contents of file using $EDITOR\n" if $EDITOR ne "";
1405    print "\t4 - delete/undelete file from list\n";
1406    print "\t5 - rename file in list\n";
1407    print "\t6 - show file list again\n";
1408    print "\t7 - escape to shell (or \"!cmd\")\n";
1409    print "\t8 - abort without saving anything\n";
1410    print "\t9 - save all files and exit (default)\n";
1411}
1412
1413# If interactive, then allow user to view and modify converted data.
1414if ((-t STDIN) && (-t STDOUT) && !$opt_n) {
1415    show_file_options();
1416    while (1) {
1417	print "Option:  ";
1418	chomp($ans = <STDIN>);
1419	if ($ans eq "?" || $ans =~ /^h/i) {
1420	    show_file_options();
1421	    next;
1422	}
1423	if ($ans eq "") {
1424	    last if yesno "Saving all files.  Are you sure";
1425	    next;
1426	}
1427	last if $ans == 9;
1428	print("Aborted.\n"), exit if $ans == 8;
1429	if ($ans =~ /^!/ || $ans == 7) {
1430	    if ($ans =~ /^!(.+)/) {
1431		system($1);
1432	    } else {
1433		print("Interactive shell access not permitted here.\n"), next
1434		    if $< != $>;
1435		system($SHELL);
1436	    }
1437	} elsif ($ans == 6) {
1438	    for ($i = 0; $i < $nfiles; $i++) {
1439		print "\t", $i+1, ".  $allfiles[$i][0]",
1440		    ($deleted[$i] ? "   (deleted)" : ""), "\n";
1441	    }
1442	} elsif ($ans > 0 && $ans < 6) {
1443	    $fnum = 0;
1444	    if ($nfiles > 1) {
1445		print "File number (1 .. $nfiles):  ";
1446		chomp($fnum = <STDIN>);
1447		if ($fnum < 1 || $fnum > $nfiles) {
1448		    print "Unknown file (must be 1 to $nfiles).\n";
1449		    next;
1450		}
1451		$fnum--;
1452	    }
1453	    if ($ans == 5) {
1454		print "Current name is $allfiles[$fnum][0]\n";
1455		print "New name:  ";
1456		chomp($fname = <STDIN>);
1457		print("Unchanged\n"), next if $fname eq "";
1458		$allfiles[$fnum][0] = $fname;
1459	    }
1460	    if ($deleted[$fnum]) {
1461		if (yesno("File " . $fnum+1 .
1462		   " ($allfiles[$fnum][0]) is deleted; undelete",1)) {
1463		    undef $deleted[$fnum];
1464		}
1465		next;
1466	    }
1467	    if ($ans == 1) {
1468		print $allfiles[$fnum][1];
1469	    } elsif ($ans == 2 && $PAGER ne "") {
1470		$i = 0;
1471		do {
1472		    if (++$i > 5) {
1473			warn "Unable to open temporary file: $!";
1474			undef $tempname;
1475			last;
1476		    }
1477		    $tempname = tmpnam();
1478		} until sysopen(FH, $tempname, O_RDWR|O_CREAT|O_EXCL);
1479		next if !$tempname;
1480		print FH $allfiles[$fnum][1];
1481		close FH;
1482		system($PAGER, $tempname);
1483		unlink($tempname) ||
1484		    warn "Trouble removing temporary file: $!";
1485		undef $tempname;
1486	    } elsif ($ans == 3 && $EDITOR ne "") {
1487		$i = 0;
1488		do {
1489		    if (++$i > 5) {
1490			warn "Unable to open temporary file: $!";
1491			undef $tempname;
1492			last;
1493		    }
1494		    $tempname = tmpnam();
1495		} until sysopen(FH, $tempname, O_RDWR|O_CREAT|O_EXCL);
1496		next if !$tempname;
1497		chown $<, $(, $tempname;
1498		print FH $allfiles[$fnum][1];
1499		close FH;
1500		$i = system($EDITOR, $tempname);
1501		if ($i == 0) {
1502		    if (open FH, "<" . $tempname) {
1503			read FH, $allfiles[$fnum][1], (-s $tempname);
1504			close FH;
1505		    }
1506		} else {
1507		    print "Editor dropped core.\n" if $? & 128;
1508		    print "Editor terminated on signal ", $? & 127, "\n"
1509			if $? & 127;
1510		    print "Editor returned error ", $? >> 8, "\n"
1511			if $? >> 8;
1512		}
1513		unlink($tempname) ||
1514		    warn "Trouble removing temporary file: $!";
1515		undef $tempname;
1516	    } elsif ($ans == 4) {
1517		$deleted[$fnum] = 1;
1518	    }
1519	}
1520    }
1521}
1522
1523print "\n";
1524
1525# Interactive part is over.  Become real.
1526$( = $);
1527$< = $>;
1528
1529print "Stopping aspppd\n" if $opt_v;
1530system($asctl, "stop") if -x $asctl;
1531
1532print "Saving all files\n" if $opt_v;
1533for ($i = 0; $i < $nfiles; $i++) {
1534    $filerec = $allfiles[$i];
1535    if ($deleted[$i]) {
1536	delete $scriptfiles{$$filerec[0]};
1537	next;
1538    }
1539    print "Saving $$filerec[0]\n" if $opt_v;
1540    $$filerec[0] =~ m+(.*)/+;
1541    if ($1 eq "") {
1542	# this is ok; just a top level file
1543    } elsif (!(-d $1)) {
1544	local ($exdir) = $1;
1545	while ($exdir && !(-d $exdir)) {
1546	    $exdir =~ m+(.*)/+;
1547	    $exdir = $1;
1548	}
1549	if ($exdir) {
1550	    local ($dir) = $1;
1551	    $dir =~ m+$exdir/([^/]*)(.*)+;
1552	    local ($tomake, $rest) = ($1, $2);
1553	    mkdir $exdir . "/" . $tomake, 0775;
1554	    if ($! == ENOSYS) {
1555		warn "Unable to make directory $exdir/$tomake; automount point.\n";
1556		next;
1557	    }
1558	    if ($! != 0) {
1559		warn "Unable to make directory $exdir/$tomake: $!\n";
1560		next;
1561	    }
1562	    if (system("mkdir", "-p", $dir) != 0) {
1563		warn "Failed to make $dir\n";
1564		next;
1565	    }
1566	} else {
1567	    warn "$1 doesn't appear to have a useful path.\n";
1568	    next;
1569	}
1570    }
1571    undef $fileerr;
1572    local ($fname) = $$filerec[0];
1573    if (-e $fname && !$overwrite{$chatfile}) {
1574	print "$fname already exists.\n"
1575	  if (-t STDIN) && (-t STDOUT) && !$opt_n;
1576	if (!yesno("Should it be overwritten",$opt_y)) {
1577	    warn "Using $fname as-is; it may not be correct.\n";
1578	    next;
1579	}
1580    }
1581    if (sysopen(OUTFILE, $$filerec[0], O_WRONLY|O_CREAT|O_TRUNC, 0600)) {
1582	print OUTFILE $$filerec[1] || ($fileerr = $!);
1583	close OUTFILE || ($fileerr = $!);
1584    } else {
1585	$fileerr = $!;
1586    }
1587    warn "Unable to write $$filerec[0]: $fileerr\n" if $fileerr;
1588}
1589
1590local(@scripts) = keys %scriptfiles;
1591if (@scripts) {
1592    print "Making scripts executable\n" if $opt_v;
1593    system("chmod", "u+x", @scripts);
1594}
1595
1596rewrite_passwd if exists($scriptfiles{$sedpasswd});
1597
1598# clean up after a previous translation.
1599unlink $pppdir . "ifconfig" if !$scriptfiles{$pppdir . "ifconfig"};
1600unlink $pppdir . "demand" if !$scriptfiles{$pppdir . "demand"};
1601
1602(rename($asfile, $asmoved) || warn "Cannot move $asfile: $!\n")
1603  if $aspppcf ne $astemp;
1604
1605system($pppdctl, "start") if -x $pppdctl;
1606
1607# use Dumpvalue;
1608# my $dumper = new Dumpvalue;
1609# $dumper->set(globPrint => 1);
1610# $dumper->dumpValue($ifconfig);
1611