Annotation of telnetbbs/telnetbbs.pl, revision 1.4

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

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