Annotation of telnetbbs/telnetbbs.pl, revision 1.5

1.1       nick        1: #!/usr/bin/perl -wT
                      2: ################################################################################
                      3: ##
                      4: ## See end of script for comments and 'pod2man telnetbbs | nroff -man' to
                      5: ## view man page or pod2text telnetbbs for plain text.
                      6: ##
                      7: ##   Nicholas DeClario <nick@declario.com>
                      8: ##   October 2009
1.5     ! nick        9: ##     $Id: telnetbbs.pl,v 1.4 2010-12-10 23:28:09 nick Exp $
1.1       nick       10: ##
                     11: ################################################################################
                     12: BEGIN {
                     13:         delete @ENV{qw(IFS CDPATH ENV BASH_ENV PATH)};
                     14:         $ENV{PATH} = "/bin:/usr/bin";
                     15:         $|++;
                     16: # Flip this back on for more detailed error reporting
                     17: #        $SIG{__DIE__} = sub { require Carp; Carp::confess(@_); }
                     18:       }
                     19: 
                     20: use strict;
                     21: use Getopt::Long;
                     22: use Pod::Usage;
                     23: use Data::Dumper;
                     24: use POSIX qw/ mkfifo /;
                     25: use IO::File;
                     26: use Socket;
                     27: use IO::Socket::INET;
                     28: use Time::HiRes qw/ sleep setitimer ITIMER_REAL time /;
                     29: use threads;
                     30: use threads::shared;
                     31: 
                     32: ##
                     33: ## Fetch our command line options
                     34: ##
                     35: my %opts    = &fetchOptions( );
                     36: my $pidFile = "/var/run/telnetbbs.pid";
                     37: my $EOL     = "\015\012";
1.3       nick       38: 
                     39: ## 
                     40: ## These will be moved in to a config file
                     41: ##
1.5     ! nick       42: my $DISPLAY   = ":0.0";
        !            43: my $BBS_NAME  = "Hell's Dominion BBS";
        !            44: my $BBS_NODE  = 0;
        !            45: my $DBCONF    = "/tmp/dosbox-__NODE__.conf";
        !            46: my $BBS_CMD   = "DISPLAY=$DISPLAY /usr/bin/dosbox -conf ";
        !            47: my $LOG       = "/var/log/bbs.log";
        !            48: my $MAX_NODE  = 1;
        !            49: my $DOSBOXT   = "dosbox.conf.template";
        !            50: my $BASE_PORT = 7000;
        !            51: my $LOCK_PATH = "/tmp";
1.1       nick       52: 
                     53: ##
                     54: ## Check that we are 'root' 
                     55: ##
                     56: die( "Must be root user to run this.\n" )
                     57:        if ( getpwuid( $> ) ne "root" );
                     58: 
                     59: ##
                     60: ## Check for a PID
                     61: ##
                     62: exit( 255 ) if ( ! &checkPID( $pidFile ) );
                     63: 
                     64: ##
                     65: ## Lets keep an eye on the forked children our network socket will be using
                     66: ##
                     67: $SIG{CHLD} = 'IGNORE';
                     68: 
                     69: ##
                     70: ## Catch any type of kill signals so that we may cleanly shutdown.  These
                     71: ## include 'kill', 'kill -HUP' and a 'CTRL-C' from the keyboard.
                     72: ##
                     73: local $SIG{HUP}  = $SIG{INT} = $SIG{TERM} = \&shutdown;
                     74: 
                     75: ##
1.3       nick       76: ## Open the Log
                     77: ##
                     78: #open LOG, ">>$LOG";
                     79: &logmsg( "Starting telnetbbs server" );
                     80: 
                     81: ##
1.1       nick       82: ## Start the network server
                     83: ##
1.4       nick       84: my $netThread = threads->create( \&startNetServer( ) );
                     85: 
1.1       nick       86: 
                     87: while( 1 ) { sleep 1; }
                     88: 
                     89: ##
                     90: ## If we made it here, the main loop died, lets shutdown
                     91: ##
                     92: &shutdown( );
                     93: 
                     94: ###############################################################################
                     95: ###############################################################################
                     96: ##
                     97: ## Sub-routines begin here
                     98: ##
                     99: ###############################################################################
                    100: ###############################################################################
                    101: 
1.5     ! nick      102: sub logmsg { print STDOUT "$0 $$ ", scalar localtime, ":@_\n" }
1.1       nick      103: 
                    104: ###############################################################################
                    105: ## startNetServer( );
                    106: ##
                    107: ##  We want to open the next free port when an incoming connection to the
                    108: ##  main port is made.  We need to set that up here.
                    109: ##
                    110: ###############################################################################
                    111: sub startNetServer 
                    112: {
                    113:        my $hostConnection;
                    114:        my $childPID;
1.4       nick      115:        my $port  = $opts{'port'} || 23;
                    116:        my @nodes = ( );
1.1       nick      117: 
                    118:        my $server = IO::Socket::INET->new( 
                    119:                        LocalPort => $port,
                    120:                        Type      => SOCK_STREAM,
                    121:                        Proto     => 'tcp',
                    122:                        Reuse     => 1,
                    123:                        Listen    => 1,
                    124:                ) or die "Couldn't create socket on port $port: $!\n";
                    125:        
                    126:        &logmsg( "Listening on port $port" );
                    127: 
                    128:         ##
                    129:         ## We want to fork our connection when it's made so that we are ready
                    130:         ## to accept other incoming connections.
                    131:         ##
                    132:        REQUEST: while( $hostConnection = $server->accept( ) )
                    133:        {
1.3       nick      134:                ## 
                    135:                ## Find the next available node
                    136:                ##
                    137:                my $node = 0;
1.5     ! nick      138:                my $lock_file = "";
1.3       nick      139:                foreach (1 .. $MAX_NODE)
                    140:                {
1.5     ! nick      141: print "Searching for lock: " . $LOCK_PATH."/".$BBS_NAME."_node".$_.".lock\n";
        !           142:                        next if ( -f $LOCK_PATH."/".$BBS_NAME."_node".$_.".lock" );
        !           143: 
        !           144:                        ##
        !           145:                        ## Create node lock file
        !           146:                        ##
        !           147:                        $lock_file = $LOCK_PATH."/".$BBS_NAME."_node".$_.".lock";
        !           148:                        open LOCK, ">$lock_file";
        !           149:                        close( LOCK );
        !           150:                        $node = $BBS_NODE = $_;
1.3       nick      151:                }
1.5     ! nick      152: print "Using lock: " . $LOCK_PATH."/".$BBS_NAME."_node".$node.".lock\n";
1.4       nick      153: 
1.3       nick      154:                ##
                    155:                ## Create our dosbox config
                    156:                ##
                    157:                open( DBT, "<$DOSBOXT" );
                    158:                        my @dbt = <DBT>;
                    159:                close( DBT );
                    160:                
                    161:                my $bpn = $BASE_PORT + $BBS_NODE;
                    162:                $DBCONF =~ s/__NODE__/$BBS_NODE/g;
                    163:                open( DBC, ">$DBCONF" );
                    164:                foreach( @dbt ) 
                    165:                {
                    166:                        $_ =~ s/__NODE__/$BBS_NODE/g;
                    167:                        $_ =~ s/__LISTEN_PORT__/$bpn/g;
                    168:                        print DBC $_;
                    169:                }
                    170:                close( DBC );
                    171: 
                    172:                &logmsg( "Connecting on node $BBS_NODE\n" );
                    173: 
1.1       nick      174:                my $kidpid;
                    175:                my $line;
                    176: 
                    177:                if( $childPID = fork( ) ) {
                    178:                        close( $hostConnection );
                    179:                        next REQUEST;
                    180:                }
                    181:                defined( $childPID ) || die( "Cannot fork: $!\n" );
                    182: 
1.5     ! nick      183:                select $hostConnection;
        !           184: 
1.1       nick      185:                ##
                    186:                ## Default file descriptor to the client and turn on autoflush
                    187:                ##
                    188:                $hostConnection->autoflush( 1 );
                    189: 
1.5     ! nick      190:                print "Welcome to $BBS_NAME!" . $EOL;
1.3       nick      191: 
                    192:                ##
1.5     ! nick      193:                if ( ! $lock_file ) 
1.3       nick      194:                {
1.5     ! nick      195:                        print "No available nodes.  Try again later.".$EOL;
1.3       nick      196:                        exit;
                    197:                }
                    198: 
1.5     ! nick      199:                print "Starting BBS on node $BBS_NODE...$EOL";
1.1       nick      200: 
                    201:                ##
                    202:                ## Launch BBS via dosbox
                    203:                ##
                    204:                my $bbsPID = fork( );
                    205:                if ( $bbsPID ) 
                    206:                {
1.5     ! nick      207:                        select STDOUT;
1.3       nick      208:                        my $cmd = $BBS_CMD . $DBCONF;
1.5     ! nick      209:                        system( $cmd );
        !           210:                        print "Shutting down node $BBS_NODE\n";
        !           211:                        ##
        !           212:                        ## Remove Lock
        !           213:                        ##      
        !           214:                        unlink( $lock_file );
        !           215:                        unlink( $DBCONF );
1.1       nick      216:                        exit;
                    217:                }
1.3       nick      218: 
                    219:                ##
                    220:                ## We wait for dosbox to start and the BBS to start
                    221:                ## There really should be a better way to determine this
                    222:                ##
                    223:                sleep 5;
1.1       nick      224: 
                    225:                ##
                    226:                ## Create connection to BBS
                    227:                ##
                    228:                my $bbs = IO::Socket::INET->new (
                    229:                                PeerAddr        => 'localhost',
1.3       nick      230:                                Type            => SOCK_STREAM,
                    231:                                PeerPort        => $bpn,
1.1       nick      232:                                Proto           => 'tcp',
                    233:                        ) || die "Could not open BBS socket: $!\n";
                    234:                $bbs->autoflush( 1 );
                    235:                die "Can't fork BBS connection: $!\n" 
                    236:                        unless defined( $kidpid = fork( ) );
                    237: 
                    238:                if ( $kidpid ) 
                    239:                {
                    240:                        my $byte;
                    241:                        while ( sysread( $bbs, $byte, 1 ) == 1 ) 
                    242:                        {
                    243:                                print $hostConnection $byte;
                    244:                        }
1.4       nick      245:                        $nodes[$BBS_NODE] = 0;
                    246:                        kill( "TERM" => $kidpid );
1.1       nick      247:                }
                    248:                else 
                    249:                {
                    250:                        my $byte;
                    251:                        while( sysread( $hostConnection, $byte, 1 ) == 1 )
                    252:                        {
                    253:                                print $bbs $byte;
                    254:                        }
                    255:                }
1.3       nick      256: 
                    257:                unlink( $DBCONF );
1.1       nick      258:        }
                    259:        close( $hostConnection );
                    260:        exit;
                    261: 
1.3       nick      262: #      close( $server );
1.1       nick      263: }
                    264: 
                    265: ###############################################################################
                    266: ##
                    267: ## shutdown( $signame );
                    268: ##
                    269: ##  Call our shutdown routine which cleanly kills all the child processes 
                    270: ##  and shuts down.  Optionally, if a kill signal was received, it will be
                    271: ##  displayed.
                    272: ##
                    273: ###############################################################################
                    274: sub shutdown 
                    275: {
                    276:        my $signame = shift || 0;
                    277: 
1.5     ! nick      278:        select STDOUT;
        !           279: 
1.3       nick      280:        &logmsg( "$0: Shutdown (SIG$signame) received.\n" )
1.1       nick      281:                if( $signame );
                    282:        
1.3       nick      283:        ##
                    284:        ## Close Log
                    285:        ##
                    286:        close( LOG );
                    287: 
1.1       nick      288:        ##      
                    289:        ## Remove the PID
                    290:        ##
                    291:        unlink( $pidFile );
                    292: 
                    293:        ##
                    294:        ## Wait for the thread to shutdown
                    295:        ##
                    296: #      $netThread->detach( );  
                    297: 
                    298:        ##
                    299:        ## And time to exit
                    300:        ##
                    301:        &POSIX::_exit( 0 );
                    302: }
                    303: 
                    304: ###############################################################################
                    305: ##
                    306: ## my $result = checkPID( $pidFile );
                    307: ##
                    308: ##   We need to see if there is a PID file, if so if the PID inside is still
                    309: ## actually running, if not, re-create the PID file with the new PID.  
                    310: ## If there is no PID file to begin with, we create one.
                    311: ##
                    312: ## If this process is successfull a non-zero value is returned.
                    313: ##
                    314: ###############################################################################
                    315: sub checkPID
                    316: {
                    317:        my $pidFile = shift || return 0;
                    318:        my $pid     = 0;
                    319: 
                    320:         ##
                    321:         ## If there is no PID file, create it.
                    322:         ##
                    323:         if ( ! stat( $pidFile ) ) 
                    324:        {
                    325:             open PF, ">$pidFile" || return 0;
                    326:                 print PF $$;
                    327:             close( PF );
                    328: 
                    329:             return 1;
                    330:         }
                    331:         ##
                    332:         ## We have a PID file.  If the process does not actually exist, then
                    333:         ## delete the PID file.
                    334:         ##
                    335:         else 
                    336:        {
                    337:                open PIDFILE, "<$pidFile" || 
                    338:                        die( "Failed ot open PID file: $!\n" );
                    339:                        $pid = <PIDFILE>;
                    340:                close( PIDFILE );
                    341: 
                    342:                ##
                    343:                ## Unlink the file if the process doesn't exist 
                    344:                ## and continue with execution
                    345:                ##
                    346:                if ( &processExists( $pid, $0 ) ) 
                    347:                {
                    348:                        unlink( $pidFile );
                    349:                        open PF, ">$pidFile" || return 0;
                    350:                                print PF $$ . "\n";
                    351:                        close( PF );
                    352: 
                    353:                        return 2;
                    354:                }
                    355:                return 0;
                    356:         }
                    357: }
                    358: 
                    359: ###############################################################################
                    360: ##
                    361: ## sub processExists( );
                    362: ##
                    363: ##     Check the '/proc' file system for the process ID number listed in the
                    364: ##  PID file.  '/proc/$PID/cmdline' will be compared to the running process
                    365: ##  name.  If the process ID does exist but the name does not match we assume
                    366: ##  it's a dead PID file.
                    367: ##
                    368: ###############################################################################
                    369: sub processExists 
                    370: {
                    371:        my $pid   = shift || return 0;
                    372:        my $pname = shift || return 0;
                    373: 
                    374:        ##
                    375:        ## If the directory doesn't exist, there is no way the 
                    376:        ## process is running
                    377:        ##
                    378:        return 0 if ( ! -f "/proc/$pid" );
                    379: 
                    380:        ##
                    381:        ## However, if the directory does exist, we need to confirm that it is
                    382:        ## indeed the process we are looking.
                    383:        ##
                    384:        open CMD, "</proc/$pid/cmdline";
                    385:                my $cmd = <CMD>;
                    386:        close( CMD );
                    387: 
                    388:        ##
                    389:        ## Filter out leading PATH information
                    390:        ##
                    391:        $pname =~ s/^\.\///;
                    392:        $cmd   =~ s/.*\/(.*)/$1/;
                    393: 
                    394:        ##
                    395:        ## if we found the process, return 1
                    396:        ##
                    397:        return 1 if ( $cmd =~ m/$pname/ );
                    398: 
                    399:        return 0;
                    400: }
                    401: 
                    402: 
                    403: ###############################################################################
                    404: ##
                    405: ## &fetchOptions( );
                    406: ##
                    407: ##      Grab our command line arguments and toss them in to a hash
                    408: ##
                    409: ###############################################################################
                    410: sub fetchOptions {
                    411:         my %opts;
                    412: 
                    413:         &GetOptions(
                    414:                         "help|?"        => \$opts{'help'},
                    415:                         "man"           => \$opts{'man'},
1.2       nick      416:                        "port:i"        => \$opts{'port'},
1.1       nick      417:                    ) || &pod2usage( );
                    418:         &pod2usage( ) if defined $opts{'help'};
                    419:         &pod2usage( { -verbose => 2, -input => \*DATA } ) if defined $opts{'man'};
                    420: 
                    421:         return %opts;
                    422: }
                    423: 
                    424: __END__
                    425: 
                    426: =head1 NAME
                    427: 
1.2       nick      428: telnetbbs.pl - A telnet server designed to launch a multi-node BBS.
1.1       nick      429: 
                    430: =head1 SYNOPSIS
                    431: 
1.2       nick      432: telnetbbs.pl [options]
1.1       nick      433: 
                    434:  Options:
                    435:         --help,?        Display the basic help menu
                    436:         --man,m         Display the detailed man page
1.2       nick      437:        --port,p        Port to listen on, default 23.
1.1       nick      438: 
                    439: =head1 DESCRIPTION
                    440: 
                    441: =head1 HISTORY
                    442: 
                    443: =head1 AUTHOR
                    444: 
                    445: Nicholas DeClario <nick@declario.com>
                    446: 
                    447: =head1 BUGS
                    448: 
                    449: This is a work in progress.  Please report all bugs to the author.
                    450: 
                    451: =head1 SEE ALSO
                    452: 
                    453: =head1 COPYRIGHT
                    454: 
                    455: =cut

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>