├── README.txt ├── telnetbbs.conf ├── dosbox.conf.template └── telnetbbs.pl /README.txt: -------------------------------------------------------------------------------- 1 | --=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 2 | TelnetBBS Server 3 | telnetbbs.pl 4 | 5 | Nicholas DeClario 6 | nick@declario.com 7 | http://nick.declario.com 8 | 9 | December 2010 10 | $Id: README.txt,v 1.3 2010-12-20 20:51:39 nick Exp $ 11 | --=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 12 | 13 | About 14 | ----- 15 | TelnetBBS Server is a perl-based telnet server that utilizes DOSBox to run a BBS from. The goal is to make this process as seamless as possible as little time should be spent in configuring the system to answer calls as oppossed to building the actual BBS. 16 | 17 | History 18 | ------- 19 | Back in the 90's I ran a local BBS and have always regreted taking it down. I had finished college and was moving out and running a BBS was no longer an option. Especially seeing I would not longer be living in the same state that the BBS was originally in. To make matters worse it was the late 90s and the standalone BBS just couldn't compete. Who wanted LORD when The Realm was picking up speed? Who wanted upload/download ratios, and message boards limited to the few hundred local users versus online forums limited to anyone with an internet conncetion. 20 | 21 | A number of years later I moved away from DOS/Windows-based operating systems and really wanted to get my BBS up and running. I made a few somewhat weak attempts at doing so and failed. Finally I decided I am going to get this working. I originally wanted to put my old BBS back up. I tried reading up on ways to get DOS networked and things of that nature. I even tried setting WinXP up in a virtual box instance and use someone elses virtual modem and connecting software, which worked but was clunky in a VM and ate up a ton of memory. I wanted an elegant Linux solution to this, so I ended up writing this one. 22 | 23 | 24 | Quick Install 25 | ------------- 26 | The quick and dirty way to get all this running is to install DOSBox, your favorite fossil driver (I found FOSS works best) and grab your favorite BBS software. I used Cott Lang's Renegade BBS back in the day and I am continuing to do so. 27 | 28 | For a basic set up, that I don't recommend keeping but it's a good starting point, make sure you are running X and edit the include DOSBox template file, 'dosbox.conf.template'. 29 | 30 | You will need to make changes to the autoexec section to load your fossil driver and start your BBS. The '__NODE__' will be replaced the with the appropriate node when the BBS is started. What you may need to do is configure your BBS to answer the (virtual) modem on com1. A number of BBS software are configured by default for this. I give an example in the config file for Renegade BBS. 31 | 32 | At this point start the server, './telnetbbs.pl'. The server will start up and tell you what port it's running on, 3023 is the default. 33 | 34 | Now take your favorite terminal program, I _highly_ recommend SyncTERM, and point it to localhost on port 3023 and you should get a login prompt. 35 | 36 | There are many more options and configurations that can be made, please read the detailed 'Install' section below for more details. Additionally the main configuration file goes through, in detail, all of the configuration options. 37 | 38 | Install 39 | ------- 40 | Detailed install coming soon. 41 | 42 | 43 | Please report any bugs either via e-mail or my blog at nick.declario.com. 44 | -------------------------------------------------------------------------------- /telnetbbs.conf: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | ## 3 | ## This is the configuration file for telnetbbs.pl. If everything is 4 | ## commented out the defaults will be used. 5 | ## 6 | ## Nicholas DeClario 7 | ## $Id: telnetbbs.conf,v 1.5 2010-12-20 20:51:39 nick Exp $ 8 | ## December 2010 9 | ## 10 | ############################################################################### 11 | ## 12 | ## pidfile contains the pid of the parent process and also acts as a lock 13 | ## file. If you plan on running more than 1 BBS on the same system this 14 | ## needs to be different for each BBS configuration. 15 | ## 16 | ## Also note, traditionally lock/pid files are in '/var/run'; however, root 17 | ## access is required to write to this directory. It is not recommended 18 | ## running your BBS as root for security reasons. If you wish to continue 19 | ## using '/var/run' it's recommended you create a directory such as 20 | ## '/var/run/telnetbbs', change the ownership to the user/group that will 21 | ## be running the BBS and point the PID file(s) to that location. 22 | ## 23 | pidfile = /tmp/telnetbbs.pid 24 | 25 | ## 26 | ## This is the initial port for the telnet service to listen on. Telnet 27 | ## generally uses port 23 but root access is required to use any privledged 28 | ## port, which is any port 1024 or below. If running this service behind a 29 | ## router, the router can be configured to accept incoming connections on 30 | ## port 23 and forward it to a different port on the system. 31 | ## 32 | ## If running more than one BBS, you will need to run each BBS on a different 33 | ## port. 34 | ## 35 | ## Also note, this can be set at the command line with the '-p' option and 36 | ## will overwrite the setting in here. This can be usefull for testing. 37 | ## 38 | port = 3023 39 | 40 | ## 41 | ## This set of ports is entirely different than the port above. Once someone 42 | ## connects to the telnet BBS server, the server will determine if there 43 | ## are any nodes available. If there are is spawns off a child process 44 | ## and moves the connection to a new port so that it can continue allowing 45 | ## connections on the original listening port. 46 | ## 47 | ## Each node the BBS uses will be assigned the base_port + node. So if 48 | ## your BBS is configured for 10 nodes and the base_port is set to 7000, it 49 | ## will use ports 7000 - 7009 for connections. It is highly recommended 50 | ## to use non-privledged ports for this and confirm there are no 51 | ## services running on the ports you wish to use. 52 | ## 53 | base_port = 3024 54 | 55 | ## 56 | ## The telnet bbs server uses dosbox which requires an X server for display. 57 | ## The X server does not need to be local. Set the display here. If X 58 | ## is running on the machine the telnet bbs server is running this option 59 | ## does not need to be changed. 60 | ## 61 | ## If using a headless system with no access to an X server on the network 62 | ## an X server such as nxserver (http://www.nomachine.com/download.php) can 63 | ## be used. However, each time the server is rebooted or nxserver is 64 | ## restarted the port it is using may change and this will need to be updated. 65 | ## 66 | ## Multiple BBS' can share this display setting. 67 | ## 68 | display = :0.0 69 | 70 | ## 71 | ## Enter the name of your BBS here. This name gets displayed when a 72 | ## connection is first made to the telnet server, before the BBS is 73 | ## actually launched. Once a node has been allocated to the connection 74 | ## a lock file for that node is put in place, which is based on this 75 | ## name as well. 76 | ## 77 | bbs_name = My BBS 78 | 79 | ## 80 | ## The lock_path specified where the individual lock files for the BBS nodes 81 | ## will be stored. Once someone connects and a node has been allocated for 82 | ## that user a node lock file is put in place. It is only removed once that 83 | ## node has been shutdown. 84 | ## 85 | ## If the server is killed with a HUP (kill -HUP), INT (kill -2) or TERM 86 | ## (ctrl-C) these will be cleanly removed. 87 | ## 88 | ## As above, lock files are normally in '/var/run'. If running as non-root, 89 | ## which is recommended, using the lock directory created above is perfectly 90 | ## acceptable. 91 | ## 92 | lock_path = /tmp 93 | 94 | ## 95 | ## Since dosbox is being utilized, each time a connection is made a new 96 | ## dosbox configuration is generated. The '__NODE__' in the command line 97 | ## is necessary as the configuration file is passed to dosbox on a 98 | ## per node basis which determines how the BBS is started. 99 | ## 100 | dosbox_cfg = /tmp/dosbox-__NODE__.conf 101 | 102 | ## 103 | ## The configuration file above is generated from a template. If you are 104 | ## running multiple BBSes you will need different templates per BBS. This 105 | ## template contains the autoexec.bat that will launch your BBS. 106 | ## 107 | dosboxt = dosbox.conf.template 108 | 109 | ## 110 | ## This is the command that will launch dosbox and pass it the configuration 111 | ## file. You can add custom dosbox options to the command line below. 112 | ## 113 | bbs_cmd = DISPLAY=__DISPLAY__ /usr/bin/dosbox -conf 114 | 115 | ## 116 | ## This will enable logging to a file on the system. By default any system 117 | ## messages will be sent to STDOUT and STDERR. 118 | ## 119 | logging = 0 120 | 121 | ## 122 | ## If logging is enabled this will tell the server what file to write to. 123 | ## Traditionally these files are stored in '/var/log' but root access is 124 | ## required to write to this directory. As mentioned before it's not 125 | ## recommended to run as root. Creating a seperate directory in '/var/log' 126 | ## with write permissions to the telnet bbs server user is acceptable. 127 | ## 128 | log_path = /tmp/bbs.log 129 | 130 | ## 131 | ## This specifies the number of nodes the telnet BBS server will spawn. 132 | ## This starts counting from 1. EG: for a 5 node BBS, enter 5. 133 | ## 134 | nodes = 3 135 | 136 | -------------------------------------------------------------------------------- /dosbox.conf.template: -------------------------------------------------------------------------------- 1 | ## 2 | ## This is the dosbox configuration used by the telnetBBS service. 3 | ## Please refer to the README.txt for any telnetBBS dosbox related 4 | ## configuration and set up. 5 | ## 6 | 7 | # This is the configurationfile for DOSBox 0.72. 8 | # Lines starting with a # are commentlines. 9 | # They are used to (briefly) document the effect of each option. 10 | 11 | [sdl] 12 | # fullscreen -- Start dosbox directly in fullscreen. 13 | # fulldouble -- Use double buffering in fullscreen. 14 | # fullresolution -- What resolution to use for fullscreen: original or fixed size (e.g. 1024x768). 15 | # windowresolution -- Scale the window to this size IF the output device supports hardware scaling. 16 | # output -- What to use for output: surface,overlay,opengl,openglnb. 17 | # autolock -- Mouse will automatically lock, if you click on the screen. 18 | # sensitiviy -- Mouse sensitivity. 19 | # waitonerror -- Wait before closing the console if dosbox has an error. 20 | # priority -- Priority levels for dosbox: lowest,lower,normal,higher,highest,pause (when not focussed). 21 | # Second entry behind the comma is for when dosbox is not focused/minimized. 22 | # mapperfile -- File used to load/save the key/event mappings from. 23 | # usescancodes -- Avoid usage of symkeys, might not work on all operating systems. 24 | 25 | fullscreen=false 26 | fulldouble=false 27 | fullresolution=original 28 | windowresolution=original 29 | output=surface 30 | autolock=true 31 | sensitivity=100 32 | waitonerror=true 33 | priority=higher,normal 34 | mapperfile=mapper.txt 35 | usescancodes=true 36 | 37 | [dosbox] 38 | # language -- Select another language file. 39 | # memsize -- Amount of memory DOSBox has in megabytes. 40 | # machine -- The type of machine tries to emulate:hercules,cga,tandy,pcjr,vga. 41 | # captures -- Directory where things like wave,midi,screenshot get captured. 42 | 43 | language= 44 | machine=vga 45 | captures=capture 46 | memsize=4 47 | 48 | [render] 49 | # frameskip -- How many frames DOSBox skips before drawing one. 50 | # aspect -- Do aspect correction, if your output method doesn't support scaling this can slow things down!. 51 | # scaler -- Scaler used to enlarge/enhance low resolution modes. 52 | # Supported are none,normal2x,normal3x,advmame2x,advmame3x,hq2x,hq3x, 53 | # 2xsai,super2xsai,supereagle,advinterp2x,advinterp3x, 54 | # tv2x,tv3x,rgb2x,rgb3x,scan2x,scan3x. 55 | # If forced is appended (like scaler=hq2x forced), the scaler will be used 56 | # even if the result might not be desired. 57 | 58 | frameskip=100 59 | aspect=false 60 | scaler=none 61 | 62 | [cpu] 63 | # core -- CPU Core used in emulation: normal,simple,dynamic,auto. 64 | # auto switches from normal to dynamic if appropriate. 65 | # cycles -- Amount of instructions DOSBox tries to emulate each millisecond. 66 | # Setting this value too high results in sound dropouts and lags. 67 | # You can also let DOSBox guess the correct value by setting it to max. 68 | # The default setting (auto) switches to max if appropriate. 69 | # cycleup -- Amount of cycles to increase/decrease with keycombo. 70 | # cycledown Setting it lower than 100 will be a percentage. 71 | 72 | core=auto 73 | cycles=auto 74 | cycleup=500 75 | cycledown=20 76 | 77 | [mixer] 78 | # nosound -- Enable silent mode, sound is still emulated though. 79 | # rate -- Mixer sample rate, setting any devices higher than this will 80 | # probably lower their sound quality. 81 | # blocksize -- Mixer block size, larger blocks might help sound stuttering 82 | # but sound will also be more lagged. 83 | # prebuffer -- How many milliseconds of data to keep on top of the blocksize. 84 | 85 | nosound=true 86 | rate=22050 87 | blocksize=2048 88 | prebuffer=10 89 | 90 | [midi] 91 | # mpu401 -- Type of MPU-401 to emulate: none, uart or intelligent. 92 | # device -- Device that will receive the MIDI data from MPU-401. 93 | # This can be default,alsa,oss,win32,coreaudio,none. 94 | # config -- Special configuration options for the device. In Windows put 95 | # the id of the device you want to use. See README for details. 96 | 97 | mpu401=intelligent 98 | device=default 99 | config= 100 | 101 | [sblaster] 102 | # sbtype -- Type of sblaster to emulate:none,sb1,sb2,sbpro1,sbpro2,sb16. 103 | # sbbase,irq,dma,hdma -- The IO/IRQ/DMA/High DMA address of the soundblaster. 104 | # mixer -- Allow the soundblaster mixer to modify the DOSBox mixer. 105 | # oplmode -- Type of OPL emulation: auto,cms,opl2,dualopl2,opl3. 106 | # On auto the mode is determined by sblaster type. 107 | # All OPL modes are 'Adlib', except for CMS. 108 | # oplrate -- Sample rate of OPL music emulation. 109 | 110 | sbtype=false 111 | sbbase=220 112 | irq=7 113 | dma=1 114 | hdma=5 115 | mixer=true 116 | oplmode=auto 117 | oplrate=22050 118 | 119 | [gus] 120 | # gus -- Enable the Gravis Ultrasound emulation. 121 | # gusbase,irq1,irq2,dma1,dma2 -- The IO/IRQ/DMA addresses of the 122 | # Gravis Ultrasound. (Same IRQ's and DMA's are OK.) 123 | # gusrate -- Sample rate of Ultrasound emulation. 124 | # ultradir -- Path to Ultrasound directory. In this directory 125 | # there should be a MIDI directory that contains 126 | # the patch files for GUS playback. Patch sets used 127 | # with Timidity should work fine. 128 | 129 | gus=false 130 | gusrate=22050 131 | gusbase=240 132 | irq1=5 133 | irq2=5 134 | dma1=3 135 | dma2=3 136 | ultradir=C:\ULTRASND 137 | 138 | [speaker] 139 | # pcspeaker -- Enable PC-Speaker emulation. 140 | # pcrate -- Sample rate of the PC-Speaker sound generation. 141 | # tandy -- Enable Tandy Sound System emulation (off,on,auto). 142 | # For auto Tandysound emulation is present only if machine is set to tandy. 143 | # tandyrate -- Sample rate of the Tandy 3-Voice generation. 144 | # disney -- Enable Disney Sound Source emulation. Covox Voice Master and Speech Thing compatible. 145 | 146 | pcspeaker=true 147 | pcrate=22050 148 | tandy=auto 149 | tandyrate=22050 150 | disney=true 151 | 152 | [joystick] 153 | # joysticktype -- Type of joystick to emulate: auto (default), none, 154 | # 2axis (supports two joysticks, 155 | # 4axis (supports one joystick, first joystick used), 156 | # 4axis_2 (supports one joystick, second joystick used), 157 | # fcs (Thrustmaster), ch (CH Flightstick). 158 | # none disables joystick emulation. 159 | # auto chooses emulation depending on real joystick(s). 160 | # timed -- enable timed intervals for axis. (false is old style behaviour). 161 | # autofire -- continuously fires as long as you keep the button pressed. 162 | # swap34 -- swap the 3rd and the 4th axis. can be useful for certain joysticks. 163 | # buttonwrap -- enable button wrapping at the number of emulated buttons. 164 | 165 | joysticktype=auto 166 | timed=true 167 | autofire=false 168 | swap34=false 169 | buttonwrap=true 170 | 171 | [serial] 172 | # serial1-4 -- set type of device connected to com port. 173 | # Can be disabled, dummy, modem, nullmodem, directserial. 174 | # Additional parameters must be in the same line in the form of 175 | # parameter:value. Parameter for all types is irq. 176 | # for directserial: realport (required), rxdelay (optional). 177 | # for modem: listenport (optional). 178 | # for nullmodem: server, rxdelay, txdelay, telnet, usedtr, 179 | # transparent, port, inhsocket (all optional). 180 | # Example: serial1=modem listenport:5000 181 | 182 | ## 183 | ## For details regarding this configuration with telnetBBS server please 184 | ## view the README.txt for more details. 185 | ## 186 | serial1=modem listenport:__LISTEN_PORT__ 187 | serial2=dummy 188 | serial3=disabled 189 | serial4=disabled 190 | 191 | [dos] 192 | # xms -- Enable XMS support. 193 | # ems -- Enable EMS support. 194 | # umb -- Enable UMB support. 195 | # keyboardlayout -- Language code of the keyboard layout (or none). 196 | 197 | xms=true 198 | ems=true 199 | umb=true 200 | keyboardlayout=none 201 | 202 | [ipx] 203 | # ipx -- Enable ipx over UDP/IP emulation. 204 | 205 | ipx=false 206 | 207 | ## 208 | ## Please view the README.txt to configuration this properly 209 | ## for your BBS. 210 | ## 211 | [autoexec] 212 | # Lines in this section will be run at startup. 213 | mount c: /home/bbs 214 | c: 215 | PATH=c:\;c:\util 216 | cd c:\foss 217 | fci 218 | cd c:\bbs 219 | renegade /N__NODE__ -Q 220 | #renegade /N1 -Q -B115200 221 | exit 222 | -------------------------------------------------------------------------------- /telnetbbs.pl: -------------------------------------------------------------------------------- 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 8 | ## October 2009 9 | ## $Id: telnetbbs.pl,v 1.11 2010-12-20 20:51:39 nick Exp $ 10 | ## 11 | ################################################################################ 12 | BEGIN { 13 | delete @ENV{qw(IFS CDPATH ENV BASH_ENV PATH)}; 14 | $ENV{PATH} = "/bin:/usr/bin"; 15 | $|++; 16 | # $SIG{__DIE__} = sub { require Carp; Carp::confess(@_); } 17 | } 18 | 19 | use strict; 20 | use Getopt::Long; 21 | use Pod::Usage; 22 | use Data::Dumper; 23 | use POSIX qw/ mkfifo /; 24 | use IO::File; 25 | use Socket; 26 | use IO::Socket::INET; 27 | use Time::HiRes qw/ sleep setitimer ITIMER_REAL time /; 28 | use threads; 29 | use threads::shared; 30 | 31 | ## 32 | ## Fetch our command line and configuration options 33 | ## 34 | my %opts = &fetchOptions( ); 35 | my %cfg = &fetchConfig( ); 36 | my $EOL = "\015\012"; 37 | 38 | ## 39 | ## These are read in from the config file 40 | ## 41 | my $BBS_NODE = 0; 42 | my $pidFile = $cfg{'pidfile'} || "/tmp/telnetbbs.pid"; 43 | my $port = $opts{'port'} || $cfg{'port'} || 23; 44 | my $DISPLAY = $cfg{'display'} || ":0.0"; 45 | my $BBS_NAME = $cfg{'bbs_name'} || "Hell's Dominion BBS"; 46 | my $DBCONF = $cfg{'dosbox_cfg'} || "/tmp/dosbox-__NODE__.conf"; 47 | my $BBS_CMD = $cfg{'bbs_cmd'} || "DISPLAY=__DISPLAY__ /usr/bin/dosbox -conf "; 48 | my $LOGGING = $cfg{'logging'} || 0; 49 | my $LOG = $cfg{'log_path'} || "/tmp/bbs.log"; 50 | my $MAX_NODE = $cfg{'nodes'} || 1; 51 | my $DOSBOXT = $cfg{'dosboxt'} || "dosbox.conf.template"; 52 | my $BASE_PORT = $cfg{'base_port'} || 7000; 53 | my $LOCK_PATH = $cfg{'lock_path'} || "/tmp"; 54 | $BBS_CMD =~ s/__DISPLAY__/$DISPLAY/g; 55 | 56 | ## 57 | ## Check that we are 'root' 58 | ## 59 | die( "Must be root user to run this.\n" ) 60 | if ( getpwuid( $> ) ne "root" && $port < 1023 ); 61 | 62 | ## 63 | ## Check for a PID 64 | ## 65 | exit( 255 ) if ( ! &checkPID( $pidFile ) ); 66 | 67 | ## 68 | ## Lets keep an eye on the forked children our network socket will be using 69 | ## 70 | $SIG{CHLD} = 'IGNORE'; 71 | 72 | ## 73 | ## Catch any type of kill signals so that we may cleanly shutdown. These 74 | ## include 'kill', 'kill -HUP' and a 'CTRL-C' from the keyboard. 75 | ## 76 | local $SIG{HUP} = $SIG{INT} = $SIG{TERM} = \&shutdown; 77 | 78 | ## 79 | ## Open the Log 80 | ## 81 | open LOG, ">>$LOG" if ( $LOGGING ); 82 | &logmsg( "Starting telnetbbs server" ); 83 | 84 | ## 85 | ## Display running information 86 | ## 87 | &display_config_and_options( \%opts, "Options" ); 88 | &display_config_and_options( \%cfg, "Configuration" ); 89 | 90 | ## 91 | ## Start the network server 92 | ## 93 | my $netThread = threads->create( \&startNetServer( ) ); 94 | 95 | 96 | while( 1 ) { sleep 1; } 97 | 98 | ## 99 | ## If we made it here, the main loop died, lets shutdown 100 | ## 101 | &shutdown( ); 102 | 103 | ############################################################################### 104 | ############################################################################### 105 | ## 106 | ## Sub-routines begin here 107 | ## 108 | ############################################################################### 109 | 110 | 111 | ############################################################################### 112 | ## 113 | ## &logmsg( "string" ); 114 | ## 115 | ## This takes a string and prepends the process name, ID and timestamp 116 | ## to the message. It then displays it to STDOUT and logs it if enabled. 117 | ## 118 | ############################################################################### 119 | sub logmsg 120 | { 121 | my $message = "$0 $$ " . scalar( localtime( ) ) . ":@_\n"; 122 | print STDOUT $message; 123 | print LOG $message if ( $LOGGING ); 124 | } 125 | 126 | 127 | ############################################################################### 128 | ## 129 | ## &display_config_and_options( %hash ); 130 | ## 131 | ## This will display via Data::Dumper a hash that is passed to it. 132 | ## If verbose is enabled it will got to STDOUT and if logging is enabled 133 | ## it will be logged. 134 | ## 135 | ## This is called only once during startup. 136 | ## 137 | ############################################################################### 138 | sub display_config_and_options 139 | { 140 | my $hr = shift || 0; 141 | my $name = shift || "Unknown"; 142 | my $title = "Displaying $name\n"; 143 | 144 | return $hr if ( ! $hr ); 145 | 146 | print LOG $title . Dumper( $hr ) if ( $LOGGING ); 147 | print STDOUT $title . Dumper( $hr ) if ( $opts{'verbose'}); 148 | } 149 | 150 | ############################################################################### 151 | ## startNetServer( ); 152 | ## 153 | ## We want to open the next free port when an incoming connection to the 154 | ## main port is made. We need to set that up here. 155 | ## 156 | ############################################################################### 157 | sub startNetServer 158 | { 159 | my $hostConnection; 160 | my $childPID; 161 | my @nodes = ( ); 162 | 163 | my $server = IO::Socket::INET->new( 164 | LocalPort => $port, 165 | Type => SOCK_STREAM, 166 | Proto => 'tcp', 167 | Reuse => 1, 168 | Listen => 1, 169 | ) or die "Couldn't create socket on port $port: $!\n"; 170 | 171 | &logmsg( "Listening on port $port" ); 172 | 173 | ## 174 | ## We want to fork our connection when it's made so that we are ready 175 | ## to accept other incoming connections. 176 | ## 177 | REQUEST: while( $hostConnection = $server->accept( ) ) 178 | { 179 | ## 180 | ## Find the next available node 181 | ## 182 | my $node = 0; 183 | my $lock_file = ""; 184 | my $cnt = 0; 185 | while ( ! $node && $cnt < $MAX_NODE ) 186 | { 187 | $cnt++; 188 | $lock_file = $LOCK_PATH . "/" . $BBS_NAME . 189 | "_node" . $cnt . ".lock"; 190 | print "Checking for node lock: $lock_file\n"; 191 | next if ( -f $lock_file ); 192 | 193 | ## 194 | ## Create node lock file 195 | ## 196 | open LOCK, ">$lock_file"; 197 | close( LOCK ); 198 | $BBS_NODE = $node = $cnt; 199 | print "Using node: $node\n"; 200 | } 201 | 202 | ## 203 | ## Create our dosbox config 204 | ## 205 | open( DBT, "<$DOSBOXT" ); 206 | my @dbt = ; 207 | close( DBT ); 208 | 209 | my $bpn = $BASE_PORT + $BBS_NODE - 1; 210 | $DBCONF =~ s/__NODE__/$BBS_NODE/g; 211 | open( DBC, ">$DBCONF" ); 212 | foreach( @dbt ) 213 | { 214 | $_ =~ s/__NODE__/$BBS_NODE/g; 215 | $_ =~ s/__LISTEN_PORT__/$bpn/g; 216 | print DBC $_; 217 | } 218 | close( DBC ); 219 | 220 | &logmsg( "Connecting on node $BBS_NODE\n" ); 221 | 222 | my $kidpid; 223 | my $line; 224 | 225 | if( $childPID = fork( ) ) { 226 | close( $hostConnection ); 227 | next REQUEST; 228 | } 229 | defined( $childPID ) || die( "Cannot fork: $!\n" ); 230 | 231 | select $hostConnection; 232 | 233 | ## 234 | ## Default file descriptor to the client and turn on autoflush 235 | ## 236 | $hostConnection->autoflush( 1 ); 237 | 238 | print "Welcome to $BBS_NAME!" . $EOL; 239 | 240 | ## 241 | if ( ! $node ) 242 | { 243 | print "No available nodes. Try again later.".$EOL; 244 | exit; 245 | } 246 | 247 | print "Starting BBS on node $BBS_NODE...$EOL"; 248 | 249 | ## 250 | ## Launch BBS via dosbox 251 | ## 252 | my $bbsPID = fork( ); 253 | if ( $bbsPID ) 254 | { 255 | select STDOUT; 256 | my $cmd = $BBS_CMD . $DBCONF; 257 | system( $cmd ); 258 | print "Shutting down node $BBS_NODE\n"; 259 | 260 | ## 261 | ## Remove Lock 262 | ## 263 | unlink( $lock_file ); 264 | unlink( $DBCONF ); 265 | close( $hostConnection ); 266 | close( $server ); 267 | kill( "TERM" => $bbsPID ); 268 | exit; 269 | } 270 | 271 | ## 272 | ## We wait for dosbox to start and the BBS to start 273 | ## There really should be a better way to determine this 274 | ## 275 | sleep 5; 276 | 277 | ## 278 | ## Create connection to BBS 279 | ## 280 | my $bbs = IO::Socket::INET->new ( 281 | PeerAddr => 'localhost', 282 | Type => SOCK_STREAM, 283 | PeerPort => $bpn, 284 | Proto => 'tcp', 285 | ) || die "Could not open BBS socket: $!\n"; 286 | $bbs->autoflush( 1 ); 287 | die "Can't fork BBS connection: $!\n" 288 | unless defined( $kidpid = fork( ) ); 289 | 290 | if ( $kidpid ) 291 | { 292 | my $byte; 293 | while ( sysread( $bbs, $byte, 1 ) == 1 ) 294 | { 295 | print $hostConnection $byte; 296 | } 297 | $nodes[$BBS_NODE] = 0; 298 | kill( "TERM" => $kidpid ); 299 | } 300 | else 301 | { 302 | my $byte; 303 | while( sysread( $hostConnection, $byte, 1 ) == 1 ) 304 | { 305 | print $bbs $byte; 306 | } 307 | } 308 | 309 | unlink( $DBCONF ); 310 | } 311 | close( $hostConnection ); 312 | close( $server ); 313 | 314 | exit; 315 | } 316 | 317 | ############################################################################### 318 | ## 319 | ## shutdown( $signame ); 320 | ## 321 | ## Call our shutdown routine which cleanly kills all the child processes 322 | ## and shuts down. Optionally, if a kill signal was received, it will be 323 | ## displayed. 324 | ## 325 | ############################################################################### 326 | sub shutdown 327 | { 328 | my $signame = shift || 0; 329 | 330 | select STDOUT; 331 | 332 | &logmsg( "$0: Shutdown (SIG$signame) received.\n" ) 333 | if( $signame ); 334 | 335 | ## 336 | ## Close Log 337 | ## 338 | close( LOG ) if ( $LOGGING ); 339 | 340 | ## 341 | ## Remove the PID 342 | ## 343 | unlink( $pidFile ); 344 | 345 | ## 346 | ## Remove node lock files 347 | ## 348 | foreach (1 .. $MAX_NODE) 349 | { 350 | my $node_lock = $LOCK_PATH."/".$BBS_NAME."_node".$_.".lock"; 351 | unlink( $node_lock ) if ( -f $node_lock ); 352 | } 353 | 354 | ## 355 | ## Wait for the thread to shutdown 356 | ## 357 | # $netThread->detach( ); 358 | 359 | ## 360 | ## And time to exit 361 | ## 362 | &POSIX::_exit( 0 ); 363 | } 364 | 365 | ############################################################################### 366 | ## 367 | ## my $result = checkPID( $pidFile ); 368 | ## 369 | ## We need to see if there is a PID file, if so if the PID inside is still 370 | ## actually running, if not, re-create the PID file with the new PID. 371 | ## If there is no PID file to begin with, we create one. 372 | ## 373 | ## If this process is successfull a non-zero value is returned. 374 | ## 375 | ############################################################################### 376 | sub checkPID 377 | { 378 | my $pidFile = shift || return 0; 379 | my $pid = 0; 380 | 381 | ## 382 | ## If there is no PID file, create it. 383 | ## 384 | if ( ! stat( $pidFile ) ) 385 | { 386 | open PF, ">$pidFile" || return 0; 387 | print PF $$; 388 | close( PF ); 389 | 390 | return 1; 391 | } 392 | ## 393 | ## We have a PID file. If the process does not actually exist, then 394 | ## delete the PID file. 395 | ## 396 | else 397 | { 398 | open PIDFILE, "<$pidFile" || 399 | die( "Failed ot open PID file: $!\n" ); 400 | $pid = ; 401 | close( PIDFILE ); 402 | 403 | ## 404 | ## Unlink the file if the process doesn't exist 405 | ## and continue with execution 406 | ## 407 | if ( &processExists( $pid, $0 ) ) 408 | { 409 | unlink( $pidFile ); 410 | open PF, ">$pidFile" || return 0; 411 | print PF $$ . "\n"; 412 | close( PF ); 413 | 414 | return 2; 415 | } 416 | return 0; 417 | } 418 | } 419 | 420 | ############################################################################### 421 | ## 422 | ## sub processExists( ); 423 | ## 424 | ## Check the '/proc' file system for the process ID number listed in the 425 | ## PID file. '/proc/$PID/cmdline' will be compared to the running process 426 | ## name. If the process ID does exist but the name does not match we assume 427 | ## it's a dead PID file. 428 | ## 429 | ############################################################################### 430 | sub processExists 431 | { 432 | my $pid = shift || return 0; 433 | my $pname = shift || return 0; 434 | 435 | ## 436 | ## If the directory doesn't exist, there is no way the 437 | ## process is running 438 | ## 439 | return 0 if ( ! -f "/proc/$pid" ); 440 | 441 | ## 442 | ## However, if the directory does exist, we need to confirm that it is 443 | ## indeed the process we are looking. 444 | ## 445 | open CMD, "; 447 | close( CMD ); 448 | 449 | ## 450 | ## Filter out leading PATH information 451 | ## 452 | $pname =~ s/^\.\///; 453 | $cmd =~ s/.*\/(.*)/$1/; 454 | 455 | ## 456 | ## if we found the process, return 1 457 | ## 458 | return 1 if ( $cmd =~ m/$pname/ ); 459 | 460 | return 0; 461 | } 462 | 463 | ############################################################################### 464 | ## 465 | ## %config_hash = &fetchConfig( ); 466 | ## 467 | ## This reads in a file in the format of "key = value" and stores them 468 | ## in to a hash of $hash{$key} = $value. Lines starting with '#' are 469 | ## considered comments and ignored. 470 | ## 471 | ############################################################################### 472 | sub fetchConfig 473 | { 474 | my %conf = ( ); 475 | my $cf = &findConfig; 476 | 477 | if ( $cf ) 478 | { 479 | open( CONF, "<$cf" ) or die ( "Error opening $cf: $!\n" ); 480 | while( ) 481 | { 482 | next if ( $_ =~ m/^#/ ); 483 | if ( $_ =~ m/(.*?)=(.*)/ ) 484 | { 485 | my $k = $1; my $v = $2; 486 | $k =~ s/\s+//; 487 | $v =~ s/\s+//; 488 | $conf{$k} = $v; 489 | } 490 | } 491 | close( CONF ); 492 | } 493 | 494 | return %conf; 495 | } 496 | 497 | ############################################################################### 498 | ## 499 | ## my $file = &fetchConfig( ); 500 | ## 501 | ## This function will look for 'telnetbbs.conf' or whatever was specified 502 | ## on the command line. It will search the @paths below for the default 503 | ## filename if none is specifed. 504 | ## 505 | ############################################################################### 506 | sub findConfig 507 | { 508 | my $cf = 0; 509 | my @paths = qw| ./ ./.telnetbbs /etc /usr/local/etc |; 510 | 511 | return $opts{'config'} if defined $opts{'config'}; 512 | 513 | foreach ( @paths ) 514 | { 515 | my $fn = $_ . "/telnetbbs.conf"; 516 | return $fn if ( -f $fn ); 517 | } 518 | 519 | return $cf; 520 | } 521 | 522 | ############################################################################### 523 | ## 524 | ## &fetchOptions( ); 525 | ## 526 | ## Grab our command line arguments and toss them in to a hash 527 | ## 528 | ############################################################################### 529 | sub fetchOptions { 530 | my %opts; 531 | 532 | &GetOptions( 533 | "config:s" => \$opts{'config'}, 534 | "help|?" => \$opts{'help'}, 535 | "man" => \$opts{'man'}, 536 | "port:i" => \$opts{'port'}, 537 | "verbose" => \$opts{'verbose'}, 538 | ) || &pod2usage( ); 539 | &pod2usage( ) if defined $opts{'help'}; 540 | &pod2usage( { -verbose => 2, -input => \*DATA } ) if defined $opts{'man'}; 541 | 542 | return %opts; 543 | } 544 | 545 | __END__ 546 | 547 | =head1 NAME 548 | 549 | telnetbbs.pl - A telnet server designed to launch a multi-node BBS. 550 | 551 | =head1 SYNOPSIS 552 | 553 | telnetbbs.pl [options] 554 | 555 | Options: 556 | --config,c Specify the configuration file to use 557 | --help,? Display the basic help menu 558 | --man,m Display the detailed man page 559 | --port,p Port to listen on, default 23. 560 | --verbose,v Enable verbose output 561 | 562 | =head1 DESCRIPTION 563 | 564 | =head1 HISTORY 565 | 566 | =head1 AUTHOR 567 | 568 | Nicholas DeClario 569 | 570 | =head1 BUGS 571 | 572 | This is a work in progress. Please report all bugs to the author. 573 | 574 | =head1 SEE ALSO 575 | 576 | =head1 COPYRIGHT 577 | 578 | =cut 579 | --------------------------------------------------------------------------------