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