# Proc::Daemon or somesuch
use strict;
-use vars qw( $kids $max_kids $shutdown $log_file );
-use vars qw($ssh_pid);
+use vars qw( $Debug %kids $kids $max_kids $shutdown $log_file $ssh_pid );
use Fcntl qw(:flock);
-use POSIX qw(setsid);
-use Date::Format;
+use POSIX qw(:sys_wait_h setsid);
use IO::Handle;
+use IO::Select;
+use IO::File;
use Storable qw(nstore_fd fd_retrieve);
use Net::SSH qw(sshopen2);
use FS::UID qw(adminsuidsetup forksuidsetup);
+use FS::ClientAPI;
-use LockFile::Simple qw(lock unlock);
-
-#use Tie::RefHash;
-#use FS::Conf;
-#use FS::Record qw( qsearch qsearchs );
-#use FS::cust_main_county;
-#use FS::cust_main;
-#use FS::Msgcat qw(gettext);
+$Debug = 2; # >= 2 will log packet contents, including potentially compromising
+ # information
$shutdown = 0;
$max_kids = '10'; #?
my $machine = shift or die &usage;
my $pid_file = "/var/run/freeside-selfservice-server.$user.pid";
#my $pid_file = "/var/run/freeside-selfservice-server.$user.pid"; $FS::UID::datasrc not posible, but should include machine name at least, hmm
-my $lock_file = "/usr/local/etc/freeside/freeside-selfservice-server.$user.lock";
-unlink $lock_file;
&init($user);
my $clientd = "/usr/local/sbin/freeside-selfservice-clientd"; #better name?
-my %dispatch = (
- 'signup' => \&signup,
- #'signup_init' => 'signup_init',
- 'passwd' => \&passwd,
-
-);
-
my $warnkids=0;
while (1) {
- my($reader, $writer) = (new IO::Handle, new IO::Handle);
- warn "connecting to $machine\n";
+ my($writer,$reader,$error) = (new IO::Handle, new IO::Handle, new IO::Handle);
+ warn "connecting to $machine\n" if $Debug;
+
$ssh_pid = sshopen2($machine,$reader,$writer,$clientd);
# nstore_fd(\*writer, {'hi'=>'there'});
- warn "entering main loop\n";
+ warn "entering main loop\n" if $Debug;
my $undisp = 0;
+ my $s = IO::Select->new( $reader );
while (1) {
- warn "waiting for packet from client\n" unless $undisp;
+ &reap_kids;
+
+ warn "waiting for packet from client\n" if $Debug && !$undisp;
$undisp = 1;
- my $packet = eval {
- local $SIG{__DIE__};
- local $SIG{ALRM} = sub { local $SIG{__DIE__};die "MyAlarm\n" }; #NB: \n required
- alarm 5;
- #my $string = <$reader>;
- #die $string;
- my $p = fd_retrieve($reader);
- alarm 0;
- $p;
- };
- if ($@) {
- &shutdown if $shutdown && $@;
- die "Fatal error receiving packet from client: $@" if $@ !~ /^MyAlarm/;
- #die $@ unless $@ eq "alarm\n" || $@ eq 'Alarm'; #?????
- #timeout
- next unless $shutdown;
- &shutdown;
+ my @handles = $s->can_read(5);
+ unless ( @handles ) {
+ &shutdown if $shutdown;
+ next;
}
- warn "packet received\n".
- join('', map { " $_=>$packet->{$_}\n" } keys %$packet );
$undisp = 0;
+ warn "receiving packet from client\n" if $Debug;
+
+ my $packet = fd_retrieve($reader);
+ warn "packet received\n".
+ join('', map { " $_=>$packet->{$_}\n" } keys %$packet )
+ if $Debug > 1;
+
#prevent runaway forking
my $warnkids = 0;
while ( $kids >= $max_kids ) {
warn "WARNING: maximum $kids children reached\n" unless $warnkids++;
+ &reap_kids;
sleep 1;
}
- warn "forking child\n";
+ warn "forking child\n" if $Debug;
defined( my $pid = fork ) or die "can't fork: $!";
if ( $pid ) {
- warn "child $pid spawned\n";
$kids++;
+ $kids{$pid} = 1;
+ warn "child $pid spawned\n" if $Debug;
} else { #kid time
- #local($SIG{PIPE});
-
#get new db handle
- #$FS::UID::dbh->{InactiveDestroy} = 1;
+ $FS::UID::dbh->{InactiveDestroy} = 1;
forksuidsetup($user);
- my $sub = $dispatch{$packet->{_packet}};
- my $rv;
- if ( $sub ) {
- warn "calling $sub handler\n";
- $rv = &{$sub}($packet);
- } else {
- warn my $error = "WARNING: unknown packet type ". $packet->{_packet};
+ my $type = $packet->{_packet};
+ warn "calling $type handler\n" if $Debug;
+ my $rv = eval { FS::ClientAPI->dispatch($type, $packet); };
+ if ( $@ ) {
+ warn my $error = "WARNING: error dispatching $type: $@";
$rv = { _error => $error };
}
$rv->{_token} = $packet->{_token}; #identifier
- warn "sending response\n";
- flock($writer, LOCK_EX); #acquire write lock
- #lock($lock_file);
- nstore_fd($rv, $writer) or die "can't send response: $!";
- $writer->flush;
- #unlock($lock_file);
- flock($writer, LOCK_UN); #release write lock
-#
- warn "child exiting\n";
+ warn "sending response\n" if $Debug;
+ flock($writer, LOCK_EX) or die "FATAL: can't lock write stream: $!";
+ nstore_fd($rv, $writer) or die "FATAL: can't send response: $!";
+ $writer->flush or die "FATAL: can't flush: $!";
+ flock($writer, LOCK_UN) or die "WARNING: can't release write lock: $!";
+
+ warn "child exiting\n" if $Debug;
exit; #end-of-kid
}
}
###
-# dispatch subroutines (should live elsewhere eventually)
+# utility subroutines
###
-sub passwd {
- #sleep 3;
- use FS::Record qw(qsearchs);
- use FS::svc_acct;
- #use FS::svc_domain;
-
- my $packet = shift;
-
- #my $domain = qsearchs('svc_domain', { 'domain' => $packet->{'domain'} } )
- # or return { error => "Domain $domain not found" };
-
- my $old_password = $packet->{'old_password'};
- my $new_password = $packet->{'new_password'};
- my $new_gecos = $packet->{'new_gecos'};
- my $new_shell = $packet->{'new_shell'};
-
- my $svc_acct =
- ( length($old_password) < 13
- && qsearchs( 'svc_acct', { 'username' => $packet->{'username'},
- #'domsvc' => $svc_domain->domsvc,
- '_password' => $old_password } )
- )
- || qsearchs( 'svc_acct', { 'username' => $packet->{'username'},
- #'domsvc' => $svc_domain->domsvc,
- '_password' => $old_password } );
-
- unless ( $svc_acct ) { return { error => 'Incorrect password.' } }
-
- my %hash = $svc_acct->hash;
- my $new_svc_acct = new FS::svc_acct ( \%hash );
- $new_svc_acct->setfield('_password', $new_password )
- if $new_password && $new_password ne $old_password;
- $new_svc_acct->setfield('finger',$new_gecos) if $new_gecos;
- $new_svc_acct->setfield('shell',$new_shell) if $new_shell;
- my $error = $new_svc_acct->replace($svc_acct);
-
- return { error => $error };
-
+sub reap_kids {
+ #warn "reaping kids\n";
+ foreach my $pid ( keys %kids ) {
+ my $kid = waitpid($pid, WNOHANG);
+ if ( $kid > 0 ) {
+ $kids--;
+ delete $kids{$kid};
+ }
+ }
+ #warn "done reaping\n";
}
-###
-# utility subroutines
-###
-
sub init {
my $user = shift;
chdir "/" or die "Can't chdir to /: $!";
- open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
+ open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
defined(my $pid = fork) or die "Can't fork: $!";
if ( $pid ) {
print "freeside-selfservice-server to $machine started with pid $pid\n"; #logging to $log_file
exit;
}
- sub REAPER { my $pid = wait; $SIG{CHLD} = \&REAPER; $kids--; }
- $SIG{CHLD} = \&REAPER;
+# sub REAPER { my $pid = wait; $SIG{CHLD} = \&REAPER; $kids--; }
+# #sub REAPER { my $pid = wait; $kids--; $SIG{CHLD} = \&REAPER; }
+# $SIG{CHLD} = \&REAPER;
$shutdown = 0;
$SIG{HUP} = sub { warn "SIGHUP received; shutting down\n"; $shutdown++; };
$SIG{QUIT} = sub { warn "SIGQUIT received; shutting down\n"; $shutdown++; };
$SIG{PIPE} = sub { warn "SIGPIPE received; shutting down\n"; $shutdown++; };
- $> = $FS::UID::freeside_uid unless $>;
- $< = $>;
-
#false laziness w/freeside-queued
my $freeside_gid = scalar(getgrnam('freeside'))
or die "can't setgid to freeside group\n";
open STDOUT, '>/dev/null'
or die "Can't write to /dev/null: $!";
setsid or die "Can't start a new session: $!";
-# open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
+ open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
$SIG{__DIE__} = \&_die;
$SIG{__WARN__} = \&_logmsg;
}
sub _logmsg {
+ chomp( my $msg = shift );
+ _do_logmsg( "[server] [". scalar(localtime). "] [$$] $msg\n" );
+}
+
+sub _do_logmsg {
chomp( my $msg = shift );
my $log = new IO::File ">>$log_file";
flock($log, LOCK_EX);
seek($log, 0, 2);
- print $log "[server] [". time2str("%a %b %e %T %Y",time). "] [$$] $msg\n";
+ print $log "$msg\n";
flock($log, LOCK_UN);
close $log;
}
die "Usage:\n\n fs_signup_server user machine\n";
}
-###
-# handlers... should go in their own files eventually...
-###
-