import rt 2.0.14
[freeside.git] / fs_selfservice / freeside-selfservice-server
1 #!/usr/bin/perl -w
2 #
3 # freeside-selfservice-server
4
5 # alas, much false laziness with freeside-queued and fs_signup_server.  at
6 # least it is slated to replace fs_{signup,passwd,mailadmin}_server
7 # should probably generalize the version in here, or better yet use
8 # Proc::Daemon or somesuch
9
10 use strict;
11 use vars qw( $Debug %kids $kids $max_kids $shutdown $log_file $ssh_pid );
12 use Fcntl qw(:flock);
13 use POSIX qw(:sys_wait_h setsid);
14 use IO::Handle;
15 use IO::Select;
16 use IO::File;
17 use Storable qw(nstore_fd fd_retrieve);
18 use Net::SSH qw(sshopen2);
19 use FS::UID qw(adminsuidsetup forksuidsetup);
20 use FS::ClientAPI;
21
22 $Debug = 2; # >= 2 will log packet contents, including potentially compromising
23             # information
24
25 $shutdown = 0;
26 $max_kids = '10'; #?
27 $kids = 0;
28
29 my $user = shift or die &usage;
30 my $machine = shift or die &usage;
31 my $pid_file = "/var/run/freeside-selfservice-server.$user.pid";
32 #my $pid_file = "/var/run/freeside-selfservice-server.$user.pid"; $FS::UID::datasrc not posible, but should include machine name at least, hmm
33
34 &init($user);
35
36 my $clientd = "/usr/local/sbin/freeside-selfservice-clientd"; #better name?
37
38 my $warnkids=0;
39 while (1) {
40   my($writer,$reader,$error) = (new IO::Handle, new IO::Handle, new IO::Handle);
41   warn "connecting to $machine\n" if $Debug;
42
43   $ssh_pid = sshopen2($machine,$reader,$writer,$clientd);
44
45 #  nstore_fd(\*writer, {'hi'=>'there'});
46
47   warn "entering main loop\n" if $Debug;
48   my $undisp = 0;
49   my $s = IO::Select->new( $reader );
50   while (1) {
51
52     &reap_kids;
53
54     warn "waiting for packet from client\n" if $Debug && !$undisp;
55     $undisp = 1;
56     my @handles = $s->can_read(5);
57     unless ( @handles ) {
58       &shutdown if $shutdown;
59       next;
60     }
61
62     $undisp = 0;
63
64     warn "receiving packet from client\n" if $Debug;
65
66     my $packet = fd_retrieve($reader);
67     warn "packet received\n".
68          join('', map { " $_=>$packet->{$_}\n" } keys %$packet )
69       if $Debug > 1;
70
71     #prevent runaway forking
72     my $warnkids = 0;
73     while ( $kids >= $max_kids ) {
74       warn "WARNING: maximum $kids children reached\n" unless $warnkids++;
75       &reap_kids;
76       sleep 1;
77     }
78
79     warn "forking child\n" if $Debug;
80     defined( my $pid = fork ) or die "can't fork: $!";
81     if ( $pid ) {
82       $kids++;
83       $kids{$pid} = 1;
84       warn "child $pid spawned\n" if $Debug;
85     } else { #kid time
86
87       #get new db handle
88       $FS::UID::dbh->{InactiveDestroy} = 1;
89       forksuidsetup($user);
90
91       my $type = $packet->{_packet};
92       warn "calling $type handler\n" if $Debug; 
93       my $rv = eval { FS::ClientAPI->dispatch($type, $packet); };
94       if ( $@ ) {
95         warn my $error = "WARNING: error dispatching $type: $@";
96         $rv = { _error => $error };
97       }
98       $rv->{_token} = $packet->{_token}; #identifier
99
100       warn "sending response\n" if $Debug;
101       flock($writer, LOCK_EX) or die "FATAL: can't lock write stream: $!";
102       nstore_fd($rv, $writer) or die "FATAL: can't send response: $!";
103       $writer->flush or die "FATAL: can't flush: $!";
104       flock($writer, LOCK_UN) or die "WARNING: can't release write lock: $!";
105
106       warn "child exiting\n" if $Debug;
107       exit; #end-of-kid
108     }
109
110   }
111
112 }
113
114 ###
115 # utility subroutines
116 ###
117
118 sub reap_kids {
119   #warn "reaping kids\n";
120   foreach my $pid ( keys %kids ) {
121     my $kid = waitpid($pid, WNOHANG);
122     if ( $kid > 0 ) {
123       $kids--;
124       delete $kids{$kid};
125     }
126   }
127   #warn "done reaping\n";
128 }
129
130 sub init {
131   my $user = shift;
132
133   chdir "/" or die "Can't chdir to /: $!";
134   open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
135   defined(my $pid = fork) or die "Can't fork: $!";
136   if ( $pid ) {
137     print "freeside-selfservice-server to $machine started with pid $pid\n"; #logging to $log_file
138     exit unless $pid_file;
139     my $pidfh = new IO::File ">$pid_file" or exit;
140     print $pidfh "$pid\n";
141     exit;
142   }
143
144 #  sub REAPER { my $pid = wait; $SIG{CHLD} = \&REAPER; $kids--; }
145 #  #sub REAPER { my $pid = wait; $kids--; $SIG{CHLD} = \&REAPER; }
146 #  $SIG{CHLD} =  \&REAPER;
147
148   $shutdown = 0;
149   $SIG{HUP} = sub { warn "SIGHUP received; shutting down\n"; $shutdown++; };
150   $SIG{INT} = sub { warn "SIGINT received; shutting down\n"; $shutdown++; };
151   $SIG{TERM} = sub { warn "SIGTERM received; shutting down\n"; $shutdown++; };
152   $SIG{QUIT} = sub { warn "SIGQUIT received; shutting down\n"; $shutdown++; };
153   $SIG{PIPE} = sub { warn "SIGPIPE received; shutting down\n"; $shutdown++; };
154
155   #false laziness w/freeside-queued
156   my $freeside_gid = scalar(getgrnam('freeside'))
157     or die "can't setgid to freeside group\n";
158   $) = $freeside_gid;
159   $( = $freeside_gid;
160   #if freebsd can't setuid(), presumably it can't setgid() either.  grr fleabsd
161   ($(,$)) = ($),$();
162   $) = $freeside_gid;
163
164   $> = $FS::UID::freeside_uid;
165   $< = $FS::UID::freeside_uid;
166   #freebsd is sofa king broken, won't setuid()
167   ($<,$>) = ($>,$<);
168   $> = $FS::UID::freeside_uid;
169   #eslaf
170
171   $ENV{HOME} = (getpwuid($>))[7]; #for ssh
172   adminsuidsetup $user;
173
174   #$log_file = "/usr/local/etc/freeside/selfservice.". $FS::UID::datasrc; #MACHINE NAME
175   $log_file = "/usr/local/etc/freeside/selfservice.$machine.log";
176
177   open STDOUT, '>/dev/null'
178                             or die "Can't write to /dev/null: $!";
179   setsid                  or die "Can't start a new session: $!";
180   open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
181
182   $SIG{__DIE__} = \&_die;
183   $SIG{__WARN__} = \&_logmsg;
184
185   warn "freeside-selfservice-server starting\n";
186
187 }
188
189 sub shutdown {
190   my $wait = 12; #wait up to 1 minute
191   while ( $kids > 0 && $wait-- ) {
192     warn "waiting for $kids children to terminate";
193     sleep 5;
194   }
195   warn "abandoning $kids children" if $kids;
196   kill 'TERM', $ssh_pid if $ssh_pid;
197   die "exiting";
198 }
199
200 sub _die {
201   my $msg = shift;
202   unlink $pid_file if -e $pid_file;
203   _logmsg($msg);
204 }
205
206 sub _logmsg {
207   chomp( my $msg = shift );
208   _do_logmsg( "[server] [". scalar(localtime). "] [$$] $msg\n" );
209 }
210
211 sub _do_logmsg {
212   chomp( my $msg = shift );
213   my $log = new IO::File ">>$log_file";
214   flock($log, LOCK_EX);
215   seek($log, 0, 2);
216   print $log "$msg\n";
217   flock($log, LOCK_UN);
218   close $log;
219 }
220
221 sub usage {
222   die "Usage:\n\n  fs_signup_server user machine\n";
223 }
224