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