pricecharts

track prices of consumer electronics
Log | Files | Refs | README

commit d4dba07d4472442285f5c872898d61dff141cf47
parent 27b6c938f705f32cdfb04e82c50bf165b2076bac
Author: Kyle Milz <kyle@getaddrinfo.net>
Date:   Mon, 27 Apr 2015 21:36:41 -0600

pc_fcgi: try to implement privsep properly

- require root:daemon permissions for stuff in /var/www/{db,run}
- open fcgi socket, db handle, syslog all as root
- fork, then chroot in child and serve connections from there
- parent waits to clean up everything afterwards

Diffstat:
Mopenbsd_rc.d_pc_fcgi | 2++
Mpc_fcgi | 107+++++++++++++++++++++++++++++++++++++++++++------------------------------------
2 files changed, 60 insertions(+), 49 deletions(-)

diff --git a/openbsd_rc.d_pc_fcgi b/openbsd_rc.d_pc_fcgi @@ -4,4 +4,6 @@ daemon="perl /home/kyle/src/pricechart/pc_fcgi" . /etc/rc.d/rc.subr +pexp="perl: ps_fcgi sloth" + rc_cmd $1 diff --git a/pc_fcgi b/pc_fcgi @@ -3,7 +3,7 @@ use strict; use warnings; -# because we chroot all dependencies must be explicitly listed +# all dependencies explicitly listed use Config::Grammar; use DBD::SQLite; use Encode; @@ -26,61 +26,80 @@ getopts("v", \%args); # fork into background unless verbose unless ($args{v}) { - exit if (fork()); + if (fork()) { + exit(); + } } my $cfg = get_config(); my %http_cfg = %{$cfg->{"http"}}; -# translates user/group names to id's, needs to be before chroot -my $uid_name = $http_cfg{"uid"}; -my $gid_name = $http_cfg{"gid"}; -my $uid = getpwnam($uid_name) or die "error: user $uid_name does not exist\n"; -my $gid = getgrnam($gid_name) or die "error: group $gid_name does not exist\n"; -print "info: $uid_name:$gid_name -> $uid:$gid\n" if ($args{v});; - -# chroot early -print "info: chrooting to $http_cfg{chroot}\n" if ($args{v}); -chroot($http_cfg{"chroot"}); -chdir("/"); - -# XXX: verify we have indeed dropped privileges? -$( = $) = "$gid $gid"; -$< = $> = $uid; -print "info: uid:gid set to $<:$(\n" if ($args{v}); - -print "info: opening syslog\n" if ($args{v}); -openlog("pc_fcgi", LOG_PID, LOG_DAEMON); +openlog("ps_fcgi", LOG_PID, LOG_DAEMON); if (-e $http_cfg{socket}) { - my $msg = "socket $http_cfg{socket} exists, not starting\n"; - print "error: $msg\n" if ($args{v}); + my $msg = "error: socket $http_cfg{socket} exists\n"; + print "$msg\n" if ($args{v}); syslog(LOG_ERR, $msg); exit; } -# XXX: i need to be sudo for this to work? after we've dropped privileges? -print "info: opening $http_cfg{socket}\n" if ($args{v}); my $socket = FCGI::OpenSocket($http_cfg{socket}, 1024); - -my $dbh = get_dbh($cfg->{"http"}, $http_cfg{db_dir}, $args{v}); my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket, FCGI::FAIL_ACCEPT_ON_INTR); -print "info: making template, include_path = $http_cfg{htdocs}\n" if ($args{v}); +my $dbh = get_dbh($cfg->{http}, $http_cfg{db_dir}); +my $sql = qq{select distinct manufacturer, part_num from prices where + manufacturer like ? or part_num like ?}; +my $srch_sth = $dbh->prepare($sql); + +my ($user, $group) = ($http_cfg{uid}, $http_cfg{gid}); +my $uid = getpwnam($user) or die "error: user $user does not exist\n"; +my $gid = getgrnam($group) or die "error: group $group does not exist\n"; +chown $uid, $gid, $http_cfg{socket} or die "error: chown $uid:$gid: $!"; + +if (fork()) { + # parent + $0 = "ps_fcgi [priv]"; + + # child should catch sigint and exit nicely, then we exit nicely here too + $SIG{INT} = "IGNORE"; + + print "info: parent: alive\n" if ($args{v}); + syslog(LOG_INFO, "parent: alive"); + wait(); + + print "info: parent: cleaning up\n" if ($args{v}); + syslog(LOG_INFO, "parent: shutdown"); + + $dbh->disconnect(); + FCGI::CloseSocket($socket); + unlink($http_cfg{socket}) or print "error: could not unlink $http_cfg{socket}: $!"; + closelog(); + + exit 0; +} + +# child +$0 = "ps_fcgi sloth"; + +print "info: child: chroot $http_cfg{chroot}\n" if ($args{v}); +chroot($http_cfg{"chroot"}); +chdir("/"); + +$( = $) = "$gid $gid"; +$< = $> = $uid; +print "info: child: uid:gid appears to be $<:$(\n" if ($args{v}); + my $config = { INCLUDE_PATH => "$http_cfg{htdocs}/tt" }; my $template = Template->new($config) || die $Template::ERROR . "\n"; -# try hard here not to use EVAL_PERL in the template -my $sql = "select distinct manufacturer, part_num " . - "from prices where part_num like ? or manufacturer like ?"; -my $search_sth = $dbh->prepare($sql); - # intercept signals to shut down cleanly $SIG{INT} = \&child_sig; $SIG{TERM} = \&child_sig; -syslog(LOG_INFO, "startup"); +syslog(LOG_INFO, "child: ready"); +print "info: child: ready\n" if ($args{v}); + my $requests_total = 0; while ($request->Accept() >= 0) { print "Content-Type: text/html\r\n\r\n"; @@ -90,27 +109,17 @@ while ($request->Accept() >= 0) { $input = uri_unescape($input); # fuzzy search on manufacturer and part number - $search_sth->execute("%$input%", "%$input%"); - my $products = $search_sth->fetchall_arrayref(); - - my $vars = { query => $input, results => $products }; + $srch_sth->execute("%$input%", "%$input%"); + my $vars = { query => $input, results => $srch_sth->fetchall_arrayref() }; $template->process("search.tt", $vars) || print "template: " . $template->error(); + $requests_total++; } -syslog(LOG_INFO, "shutdown, $requests_total total requests served"); -closelog(); - -$search_sth = undef; -$dbh->disconnect(); - -FCGI::CloseSocket($socket); -unlink($http_cfg{socket}) or print "error: could not unlink $http_cfg{socket}: $!"; +syslog(LOG_INFO, "child: shutdown, $requests_total requests"); sub child_sig { - my $signame = shift; - $request->LastCall(); - print "info: caught SIG$signame\n" if ($args{v}); + print "info: child: caught signal " . lc shift . "\n" if ($args{v}); }