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