Return to BSD News archive
Xref: sserve comp.os.386bsd.questions:15291 comp.lang.perl:35878 Path: sserve!newshost.anu.edu.au!harbinger.cc.monash.edu.au!msunews!uwm.edu!cs.utexas.edu!geraldo.cc.utexas.edu!sylvester.cc.utexas.edu!not-for-mail From: vax@sylvester.cc.utexas.edu (Vax) Newsgroups: comp.os.386bsd.questions,comp.lang.perl Subject: [NetBSD,*BSD] free slipmaint script Date: 17 Dec 1994 16:54:20 -0600 Organization: The University of Texas at Austin; Austin, Texas Lines: 610 Message-ID: <3cvq6s$ln@sylvester.cc.utexas.edu> NNTP-Posting-Host: sylvester.cc.utexas.edu I wrote this script; it's based loosely on "slipup", but it's a re-write so I changed the name. I tried to make it do everything in a modular way. It works very well under NetBSD-1.0. However, I can't get it to hang up the darned phone. It says (in debug mode) that it's hanging up the phone but it doesn't work. Try sending a HUP signal to the child (watching line..) to see what I mean. I'm stumped. Also, if you can tell me how to get a HUP signal to the child if the modem hangs up, that'd be great. PLEASE HELP! I've spent weeks on this program. ===================code follows===================----cut-here-----======== #!/usr/foreign/bin/perl # $Id: slipmaint,v 1.16 1994/12/14 10:36:19 vax Exp $ # $Log: slipmaint,v $ # Revision 1.16 1994/12/14 10:36:19 vax # fixed syntax errors, also last rev fixed hostname properly # # Revision 1.15 1994/12/14 10:35:00 vax # modem hangup stuff added, killing dummy-child used on configure # # Revision 1.14 1994/12/10 12:05:41 vax # Added ability to set and restore the hostname # # Revision 1.13 1994/12/10 11:41:48 vax # Program now kills off slattach properly if signalled. # # Revision 1.12 1994/12/10 11:25:45 vax # Fixed it so that everything will die off properly when HUP'ed # # Revision 1.11 1994/12/07 12:13:39 vax # Rearrangement of constants, implementation of signal handler, # use of indirect filehandles rather than literal names, non-buffering, etc. # # Revision 1.10 1994/12/06 12:07:07 vax # Added a lot of error handling, robustness, stty flags, etc # # Revision 1.9 1994/12/06 10:21:23 vax # Added a makefile to install slipmaint, and added some debug stuff to # the slipmaint program itself. # # Revision 1.8 1994/12/05 09:48:10 vax # First version that can start all the stuff # # Revision 1.7 1994/12/03 10:47:45 vax # This is the first version of slipmaint that really can log in. # # Revision 1.6 1994/12/03 08:24:22 vax # Added small error catching code in open call # # Revision 1.5 1994/12/03 08:15:45 vax # Changed name from $network to $remote_ip_address and added network # start code. # # Revision 1.4 1994/12/02 09:00:17 vax # Added some comments, sleep_on_open, and some lock stuff # # Revision 1.3 1994/11/30 01:47:05 vax # re-arranged so that open/configure will work # # Revision 1.2 1994/11/29 18:44:10 vax # An actual, error-free skeleton # # set up the signal handler first # NOTE: that handled signals are not preserved across an exec, # but are preserved across forks! $SIG{'HUP'} = 'signal_handler'; # CONSTANTS ########### # progname # this sets the program name to something reasonable based on command line $_ = $0; s!.*/([^/]*)!$1!; $progname = $_; $devfh = 'DEV'; # a constant holding the device filehandle name # CLEANUP (SIGNAL) CONSTANTS $clean_flags_unlock = 1; $clean_flags_ifconfig = 2; $clean_flags_route = 4; $clean_flags_online = 8; # STTY CONSTANTS $stty_baud = '38400'; # PAUSE LENGTHS $pause_for_open = 5; $pause_for_network = 5; # MODEM CONSTANTS # Input $modem_attention = 'AT'; $modem_end_line = "\r"; $modem_init_string = $modem_attention . ' Z S2=43 S12=20 M0' . $modem_end_line; $modem_dial_string = $modem_attention . 'DT'; $modem_hangup_string = $modem_attention . 'H' . $modem_end_line; # Output $modem_ok = 'OK'; $modem_success = 'CONNECT'; $modem_wait = '(' . $modem_success . '|BUSY|CARRIER)'; $modem_timeout_char = 60; # time to wait for an expected character # DIALING CONSTANTS $busy_retries = 5; # OS/SYSTEM CONFIGURATION $interface = 'sl0'; $slattach_baud = $stty_baud; # NOTE: don't interpolate here; enclose in single quotes $slattach_cmd = 'slattach -h -s $slattach_baud $device'; $slip_file_dir = "/usr/foreign/runtime/$progname"; $slip_num_file = $slip_file_dir . '/number'; $slip_name_file = $slip_file_dir . '/name'; # global variables: # config_file # The configuration file from which to read (a database) # config_entry # The entry in the configuration file # device # The I/O device to use # network # Network number of the remote host # phone # The phone number of the remote host # verbose # debug # daemon # remote_chat # Chat string to log in to remote host # @children # array of child pids to kill # @unlink_files # array of files to unlink # include required perl-files (if any) require 'getopts.pl'; # required for Getopts call to parse arguments # parse arguments # allows p,n,t,f,h options with args, vxd without. defined &Getopts ? &Getopts('f:h:t:n:p:c:vxd') : die 'No getopts'; # here are the command-line only flags - NOTE: we must set them first # (esp. debug!) $verbose = defined($opt_v) ? 1 : 0; $debug = defined($opt_x) ? 1 : 0; $daemon = defined($opt_d) ? 1 : 0; # first we parse the config file from which to read the other variables # if it is defined from Getopts, then use that # otherwise, use /etc/{program name}.hosts (default) $config_file = defined($opt_f) ? $opt_f : "/etc/$progname.hosts"; # next we parse out the specific entry in the config file # if it is defined on the command line, use it, else use default # NOTE: that this could mask some errors, but is useful $config_entry = defined($opt_h) ? $opt_h : 'default'; # now we can go and read the info from the config file. ($config_entry,$device,$remote_ip_address,$phone,$remote_chat) = &config_read($config_file,$config_entry,$debug); # next we adjust other defaults... # here is the device to use $device = defined($opt_t) ? $opt_t : defined($device) ? $device : '/dev/tty00'; # and the network number of the remote host $remote_ip_address = defined($opt_n) ? $opt_n : defined($remote_ip_address) ? $remote_ip_address : '127.0.0.1'; # and the phone number $phone = defined($opt_p) ? $opt_p : defined($phone) ? $phone : '5555555'; # the chat string to log us in $remote_chat = defined($opt_c) ? $opt_c : defined($remote_chat) ? $remote_chat : 'gin:|guest\r|ord:|guest\r|>|slip default\r'; # fork off as daemon process if necessary # this is so that it will not stay on as a "zombie" of /etc/rc or whatever. # NOTE: this works - see the perl manual, under forking a daemon process. if ($daemon) { fork && exit; setpgrp(0,$$); } require 'syslog.pl'; # required for the openlog call &openlog($progname . ' ' . (getpwuid($<))[0], 'pid,cons', 'daemon'); # check if network is already up if (`netstat -i -n | cut -c25-40 | tail +2` =~ /^$remote_ip_address/) { die "Network to $remote_ip_address is already up\n"; } if (`netstat -rn | cut -c61- | tail +5` =~ /^$interface/) { die "Network interface $interface is already up\n"; } # configure the I/O device &configure_device($device,$stty_baud,$pause_for_open); # open the correct I/O device # NOTE: to (properly) open the device, we must configure it first. &open_device($device,$devfh) && push(@closelist,$devfh); # NOTE: to do flock-style locking, you must open the device first. # lock the correct I/O device &lock_device() && ($clean_flags |= $clean_flags_unlock); # Now we can open it for write. &finish_opening_device($device,$devfh) || warn "Unable to open device for r/w\n"; &finish_configuring_device($devfh) || warn "Unable to finish configuring device\n"; warn "selected filehandle is " . select . "\n" if $debug; require 'sys/wait.ph'; # required for WIF* calls below waitpid, below. # fork and run the main loop # if we get a recoverable error, allow the child to exit and repeat for (;;) { local($childpid,$retval); unless ($childpid = fork()) { # fix signal handler - preserved across forks # fix global vars undef(@children); $cleanup_flags = 0; $0 = "$progname (child)"; exit &main($phone,$remote_ip_address,$verbose,$debug,$busy_retries, $busy_sleep,$modem_timeout_char,$remote_chat,$slattach_cmd, $device,$pause_for_network,$slip_num_file,$slip_name_file, $devfh); } push(@children,$childpid); $0 = "$progname (parent)"; $retval = waitpid($childpid,0); @children -= $childpid; last if ($retval == -1); # last if (&WIFSIGNALED($?)); } # unlock the I/O device # NOTE: flock must have a device handle to unlock it, so we have to # close after unlocking # NOTE: we unset the unlock flag, since we've unlocked it already &unlock_device($devfh) && ($clean_flags &= (~$clean_flags_unlock)); # close the I/O device &close_device($devfh) && shift(@closelist); # close log files &closelog(); ##### End of main code; subroutines follow ##### sub config_read { local($file,$entry,$debug) = @_; local(@data); warn "config file is $file, entry is $entry\n" if $debug; open(CONFIG_FILE,$file) || warn "Could not open config file $file: $!\n"; CFILE: while(<CONFIG_FILE>) { # get rid of the newline chop; # ignore blank and comment lines in config file next if (/^[ \t]*(#|)$/); # convert all occurences of spaces to tabs - ICK! # s/ +/\t/g; # squish multiple tabs into one tab s/\t[ \t]+/\t/g; warn "Current entry is $_\n" if $debug; # if we've found the info, break out if (/^$entry\t/) { # assign the tab-divided columns to the data array @data = split('\t'); warn "data is " . join(':',@data) . "\n" if $debug; last CFILE; } } # close the fdesc for the config file, to clean up close(CONFIG_FILE); # return the found data @data; } # In the configuration routine, we must hold the tty open with a (blocked) # process. Hence the fork below. sub configure_device { local($device,$stty_baud,$pause_for_open) = @_; local($childpid); unless ($childpid=fork()) { # NOTE: the fork call; must reset the signal handler. $SIG{'HUP'} = 'DEFAULT'; $0 = "$progname (holding $device open)"; # try to open the port - it won't work, but we'll hold it open so that # the parent process can run stty on it # Normally, we would block, but if clocal is set, open will complete local($ret) = open(TEMP,$device); warn "Warning-open exited with value $ret (clocal probably set on $device)\n"; # sleep forever sleep; # execution never gets here exit $ret; } push(@children,$childpid); $0 = "$progname (configuring $device)"; sleep $pause_for_open; system('stty', '-f', $device, '-parenb', 'cs8', $stty_baud, 'clocal', 'hupcl', 'raw', 'ignbrk', '-brkint', '-istrip', '-inlcr', '-icrnl', '-ixon', '-ixoff', '-opost', '-isig', '-icanon', '-iexten', '-echo', '-echoe', '-echok', '-echonl', 'noflsh', 'min', '0', 'time', '5', 'crtscts'); } sub open_device { local($device,$devfh) = @_; $0 = "$progname (opening $device)"; open($devfh,$device); } sub lock_device { local($devfh) = @_; $0 = "$progname (locking device)"; flock($devfh,2); } sub finish_opening_device { local($device,$devfh) = @_; $0 = "$progname (opening $device for write)"; open($devfh,"+<$device"); local($childpid) = pop(@children); # Now that we've opened the device, we can kill off that dumb process # we started in configure: kill 'HUP', $childpid; waitpid($childpid,0); } sub finish_configuring_device { local($devfh) = @_; warn "selecting $devfh\n" if $debug; # This selects devfh's value as the default filehandle, # makes it unbuffered, and restores default filehandle - sorry :) select((select($devfh), $| = 1)[$[]); } sub main { local($phone,$remote_ip_address,$verbose,$debug,$busy_retries,$busy_sleep, $timeout_char,$chat,$slattach_cmd,$device,$pause_for_network, $slip_num_file,$slip_name_file,$devfh) = @_; warn "Signal handler is $SIG{'HUP'}\n" if $debug; # Here we dial the modem and check for busy, etc. if (&dial_modem($phone,$modem_init_string,$modem_ok,$modem_success, $modem_wait,$modem_end_line,$busy_retries,$busy_sleep, $debug,$timeout_char,$modem_dial_string,$devfh)) { # configure the device for carrier-mode # &acquired_carrier($device); $clean_flags |= $clean_flags_online; warn "Chat string is $chat\n" if $debug; # Log on to the remote system and get our ip address. local($local_ip_address) = &chat_with_remote($chat,$modem_end_line,$debug,$timeout_char,$devfh); # Start programs to activate network stuff &start_network($local_ip_address,$remote_ip_address,$slattach_cmd, $device,$debug,$slip_num_file,$slip_name_file); # Watch the line forever and ever and return its retval &watch_line($local_ip_address); } # dialing failed: else { 0; } } # Dial the modem # returns non-zero if busy_retries > 0 (initially) and dial succeeded # returns zero if the line has stayed busy sub dial_modem { $0 = "$progname (dialing)"; # params local($phone,$init_string,$ok,$success,$wait_pattern,$end_line, $busy_retries,$busy_sleep,$debug,$timeout_char,$dial_string,$devfh) = @_; warn "dialing - devfh is $devfh\n" if $debug; # initialize the modem &tty_send($init_string,$debug,$devfh); # wait for acknowledgement &tty_wait_for($ok,$debug,$timeout_char,$devfh); # dial until some condition is fulfilled do { &tty_send($dial_string . $phone . $end_line,$debug,$devfh); } while ((&tty_wait_pat($wait_pattern,$debug,$timeout_char,$devfh) ne $success) # NOTE: don't think this is necessary && &hangup_modem($devfh,$debug) && ($busy_retries--) && (sleep $busy_sleep == $busy_sleep)); $busy_retries; } # Send a string over the device sub tty_send { local ($send,$debug,$devfh) = @_; warn "Sending: $send to $devfh\n" if $debug; # NOTE: devfh is now unbuffered. print $devfh $send; } # Wait for a string from the device sub tty_wait_for { local($pattern); local($wait,$debug,$timeout_char,$devfh) = @_; warn "Waiting for: $wait on $devfh\n" if $debug; ($pattern = $wait) =~ s/(\W)/\\$1/g; # NOTE: str is a static variable (or global) while ($str !~ /$pattern/) { $str .= &get_char($timeout_char,$debug,$devfh); } $str = $'; $&; } sub get_char { local($timeout_char,$debug,$devfh) = @_; local($rmask, $nfound, $endtime, $timeleft, $nread, $thisbuf); warn "getting characters from $devfh\n" if $debug; $endtime = time + $timeout_char; $rmask = ''; vec($rmask,fileno($devfh),1) = 1; ($nfound, $timeleft) = select($rmask, undef, undef, $endtime - time); warn "nfound=$nfound,timeleft=$timeleft,rmask=$rmask\n" if $debug; # if the number of ready filehandles "found" was not zero if ($nfound) { # NOTE: devfh is unbuffered # NOTE: !! read() will not work here, only sysread() - :-? $nread = sysread($devfh, $thisbuf, 1024); warn "read $nread bytes: \"$thisbuf\"\n" if $debug; $thisbuf; # return '' if !$nread; # eof } $thisbuf; } sub tty_wait_pat { local($wait,$debug,$timeout_char,$devfh) = @_; warn "Waiting for pattern $wait on $devfh\n" if $debug; while ($str !~ /$wait/) { warn "str is \"$str\"\n" if $debug; $str .= &get_char($timeout_char,$debug,$devfh); } # Redundant but necessary for the $&,$' below $str =~ /$wait/; warn "Found string \"$&\" in \"$str\"\n" if $debug; $str = $'; $&; } # Set local-mode OFF now that we have carrier. # TODO: do this, but make sure it's set back on in loop. # NOTE: this seems contradictory to the idea of having the parent hold # the modem line open the whole time - after all, why have -clocal if # you don't intend a hangup to cause a HUP signal? sub acquired_carrier { local($device) = @_; system('stty', '-f', $device, '-clocal'); } # log-in to the remote system sub chat_with_remote { $0 = "$progname (chatting)"; local($chat,$end_line,$debug,$timeout_char,$devfh) = @_; warn "Chat string is $chat\n" if $debug; # unless the chat string is empty, process it. unless ($chat eq '') { # split the chat string up by vertical bars. local(@chat) = split('\|', $chat); local($i) = 0; # loop through each item foreach (@chat) { warn "Considering item $_, i = $i\n" if $debug; # allow modem newlines in the chat strings s/\\r/$end_line/; if ($i & 1) { &tty_send($_,$debug,$devfh); } else { &tty_wait_for($_,$debug,$timeout_char,$devfh); } $i = !$i; } } warn "Looking for IP pattern\n" if $debug; $_ = &tty_wait_pat('Your (IP |)address is (\d+\.\d+\.\d+\.\d+)\D', $debug,$timeout_char,$devfh); /Your (IP |)address is (\d+\.\d+\.\d+\.\d+)\D/; $addr = $2; warn "Output was \'$_\', addr is $addr\n" if $debug; $2; } sub start_network { $0 = "$progname (starting network)"; local($local_ip_address,$remote_ip_address,$cmd, $device,$debug,$slip_num_file,$slip_name_file) = @_; local($childpid,$inet_hostname); # this madness allows us to interpret variable references in the command # string at run-time - see "Programming Perl", p217 $_ = $cmd; s/"/\\"/g; $cmd = eval qq/"$_"/; warn "command is $cmd\n" if $debug; # NOTE: for older NetBSD/386BSD systems, ignore hup and quit signals # during slattach. You can do this by setting a signal handler and # exec'ing slattach, I think. # TODO: slattach forks itself, leaving a zombie :( so why don't you clean # up this nasty code and do it right. unless ($childpid=fork()) { exec $cmd; } warn "slattach pid was $childpid\n" if $debug; # this is the nasty part push(@children,($childpid + 1)); # wait for the slattach program to fork waitpid($childpid,0); # ifconfig system ('ifconfig sl0 inet ' . $local_ip_address . ' ' . $remote_ip_address); $clean_flags |= $clean_flags_ifconfig; # route system ('route add default ' . $remote_ip_address); $clean_flags |= $clean_flags_route; # TODO: use named if wanted # put net addresses into files $inet_hostname = &write_net_addresses($local_ip_address,$slip_num_file, $slip_name_file,$debug); # save the old hostname $old_hostname = `hostname`; # set the new hostname system("hostname $inet_hostname"); # call utsetrhost or moral equivalent system('utsetrhost'); } # Returns the internet-style name of this system sub write_net_addresses { local($ipaddr,$numfile,$namefile,$debug) = @_; # Set the umask to make this world-readable umask(022); open(SLIPNUM,'>' . $numfile) || warn "Could not open num file $numfile: $!\n"; print SLIPNUM $ipaddr; close SLIPNUM; push(@unlink_files,$numfile); $_ = `nslookup $ipaddr | tail +4 | head -n 1`; warn "output was \"$_\"\n" if $debug; /\s(\S+)\s/; warn "name was \"$1\"\n" if $debug; open(SLIPNAME,'>' . $namefile) || warn "Could not open name file $namefile: $!\n"; print SLIPNAME $1 . "\n"; close SLIPNAME; push(@unlink_files,$namefile); $1; } # Just sleep forever (for now) # TODO: add more checks sub watch_line { $0 = "$progname (watching line)"; sleep; } sub unlock_device { local($devfh) = @_; flock($devfh,8); } sub close_device { local($devfh) = @_; close $devfh; } # NOTE: signal handler is SHARED by parent and child processes! # Hence, my bending over backwards to maintain the global vars # NOTE: (TODO) child process never gets sighdl called, prob b/c # parent sends a TERM and not a HUP. sub signal_handler { local($sig) = @_; $_ = $sig; SIGNALS: { if (/^HUP$/) { # if the parent terminates, HUP will automagically be sent to children # TODO: this appears to be incorrect - comment out the undef # c.f. Rochkind, "Advanced Unix Programming" # undef @children; exit &clean_exit("Signal SIG$sig caught, shutting down\n") }; exit &clean_exit("Unknown signal SIG$sig caught, shutting down\n"); } # Execution never gets here } sub clean_exit { local($arg) = @_; local($fh); $0 = "$progname (cleaning up: $arg)"; warn $arg; &syslog(WARNING,$arg); &hangup_modem($devfh,$debug) if ($clean_flags & $clean_flags_online); # kill any children created by this program (c.f. signal_handler!) warn "Killing children " . join(' ',@children) . "\n" if $debug; kill 'HUP', @children; # don't bother to mask off flags as we take care of them &unlock_device($devfh) if ($clean_flags & $clean_flags_unlock); # NOTE: this will remove the gateway route, if any system("ifconfig $interface delete") if ($clean_flags & $clean_flags_ifconfig); # NOTE: since ifconfig delete must always be done if route delete is, # we don't have to delete the gateway (remote host) route system("route delete default") if ($clean_flags & $clean_flags_route); warn "Unlinking files " . join(' ',@unlink_files) . "\n" if $debug; unlink @unlink_files; # restore the old hostname if needed system("hostname $old_hostname") if (defined($old_hostname)); while ($fh = shift @closelist) { close $fh; } } # NOTE: kluge ub hangup_modem { local($devfh,$debug) = @_; warn "Attempting to hang up\n"; &tty_send('+',$debug,$devfh); sleep(1); &tty_send('+',$debug,$devfh); sleep(1); &tty_send('+',$debug,$devfh); sleep(1); &tty_send($modem_hangup_string,$debug,$devfh); } -- Internet is not a medium for self-expression, but rather for self-education. VaX#n8 vax@ccwf.cc.utexas.edu - Don't believe the hype