add fs_selfservice
[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( $kids $max_kids $shutdown $log_file );
12 use vars qw($ssh_pid);
13 use Fcntl qw(:flock);
14 use POSIX qw(setsid);
15 use IO::Handle;
16 use Storable qw(nstore_fd fd_retrieve);
17 use Net::SSH qw(sshopen2);
18 use FS::UID qw(adminsuidsetup);
19
20 #use Tie::RefHash;
21 #use FS::Conf;
22 #use FS::Record qw( qsearch qsearchs );
23 #use FS::cust_main_county;
24 #use FS::cust_main;
25 #use FS::Msgcat qw(gettext);
26
27 $shutdown = 0;
28 $max_kids = '10'; #?
29 $kids = 0;
30
31 my $user = shift or die &usage;
32 my $machine = shift or die &usage;
33 my $pid_file = "/var/run/freeside-selfservice-server.$user.pid";
34 #my $pid_file = "/var/run/freeside-selfservice-server.$user.pid"; $FS::UID::datasrc not posible, but should include machine name at least, hmm
35
36 &init($user);
37
38 my $clientd = "/usr/local/sbin/freeside-selfservice-clientd"; #better name?
39
40 my %dispatch = (
41   'signup' => \&signup,
42   #'signup_init' => 'signup_init',
43   'passwd' => \&passwd,
44
45 );
46
47 my $warnkids=0;
48 while (1) {
49   my($reader, $writer) = (new IO::Handle, new IO::Handle);
50   warn "connecting to $machine";
51   $ssh_pid = sshopen2($machine,$reader,$writer,$clientd);
52
53   warn "entering main loop";
54   while (1) {
55
56     warn "waiting for packet from client";
57     my $packet = eval {
58       local $SIG{__DIE__};
59       local $SIG{ALRM} = sub { die "alarm\n" }; #NB: \n required
60       alarm 5;
61       my $p = fd_retrieve($reader);
62       alarm 0;
63       $p;
64     };
65     if ($@) {
66       die $@ unless $@ eq "alarm\n";
67       #timeout
68       next unless $shutdown;
69       &shutdown;
70     }
71     warn "packet received";
72
73     #prevent runaway forking
74     my $warnkids = 0;
75     while ( $kids >= $max_kids ) {
76       warn "WARNING: maximum $kids children reached" unless $warnkids++;
77       sleep 1;
78     }
79
80     warn "forking child";
81     defined( my $pid = fork ) or die "can't fork: $!";
82     if ( $pid ) {
83       warn "child $pid spawned";
84       $kids++;
85     } else { #kid time
86
87       #get new db handle
88       $FS::UID::dbh->{InactiveDestroy} = 1;
89       forksuidsetup($user);
90
91       my $sub = $dispatch{$packet->{_packet}};
92       my $rv;
93       if ( $sub ) {
94         warn "calling $sub handler"; 
95         $rv = &{$sub}($packet);
96       } else {
97         warn my $error = "WARNING: unknown packet type ". $packet->{_packet};
98         $rv = { _error => $error };
99       }
100       $rv->{_token} = $packet->{_token}; #identifier
101
102       warn "sending response";
103       flock($writer, LOCK_EX); #acquire write lock
104       nstore_fd($rv, $writer) or die "can't send response: $!";
105       $writer->flush;
106       flock($writer, LOCK_UN); #release write lock
107
108       warn "child exiting";
109       exit; #end-of-kid
110     }
111
112   }
113
114 }
115
116 ###
117 # utility subroutines
118 ###
119
120 sub init {
121   my $user = shift;
122
123   chdir "/" or die "Can't chdir to /: $!";
124   open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
125   defined(my $pid = fork) or die "Can't fork: $!";
126   if ( $pid ) {
127     print "freeside-selfservice-server to $machine started with pid $pid\n"; #logging to $log_file
128     exit unless $pid_file;
129     my $pidfh = new IO::File ">$pid_file" or exit;
130     print $pidfh "$pid\n";
131     exit;
132   }
133
134   sub REAPER { my $pid = wait; $SIG{CHLD} = \&REAPER; $kids--; }
135   $SIG{CHLD} =  \&REAPER;
136
137   $shutdown = 0;
138   $SIG{HUP} = sub { warn "SIGHUP received; shutting down\n"; $shutdown++; };
139   $SIG{INT} = sub { warn "SIGINT received; shutting down\n"; $shutdown++; };
140   $SIG{TERM} = sub { warn "SIGTERM received; shutting down\n"; $shutdown++; };
141   $SIG{QUIT} = sub { warn "SIGQUIT received; shutting down\n"; $shutdown++; };
142   $SIG{PIPE} = sub { warn "SIGPIPE received; shutting down\n"; $shutdown++; };
143
144   $> = $FS::UID::freeside_uid unless $>;
145   $< = $>;
146   $ENV{HOME} = (getpwuid($>))[7]; #for ssh
147   adminsuidsetup $user;
148
149   #$log_file = "/usr/local/etc/freeside/selfservice.". $FS::UID::datasrc; #MACHINE NAME
150   $log_file = "/usr/local/etc/freeside/selfservice.$machine.log";
151
152   open STDOUT, '>/dev/null'
153                             or die "Can't write to /dev/null: $!";
154   setsid                  or die "Can't start a new session: $!";
155   open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
156
157   $SIG{__DIE__} = \&_die;
158   $SIG{__WARN__} = \&_logmsg;
159
160   warn "freeside-selfservice-server starting\n";
161
162 }
163
164 sub shutdown {
165   my $wait = 12; #wait up to 1 minute
166   while ( $kids && $wait-- ) {
167     warn "waiting for $kids children to terminate";
168     sleep 5;
169   }
170   warn "abandoning $kids children" if $kids;
171   kill 'TERM', $ssh_pid if $ssh_pid;
172   die "exiting";
173 }
174
175 sub _die {
176   my $msg = shift;
177   unlink $pid_file if -e $pid_file;
178   _logmsg($msg);
179 }
180
181 sub _logmsg {
182   chomp( my $msg = shift );
183   my $log = new IO::File ">>$log_file";
184   flock($log, LOCK_EX);
185   seek($log, 0, 2);
186   print $log "[server] [". time2str("%a %b %e %T %Y",time). "] [$$] $msg\n";
187   flock($log, LOCK_UN);
188   close $log;
189 }
190
191 sub usage {
192   die "Usage:\n\n  fs_signup_server user machine\n";
193 }
194
195 ###
196 # handlers... should go in their own files eventually...
197 ###
198