#!/usr/bin/perl -w # # This script is invoked when the yarrg client uploads to # the chiark database. # This is part of ypp-sc-tools, a set of third-party tools for assisting # players of Yohoho Puzzle Pirates. # # Copyright (C) 2009 Ian Jackson # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Yohoho and Puzzle Pirates are probably trademarks of Three Rings and # are used without permission. This program is not endorsed or # sponsored by Three Rings. use strict (qw(vars)); use POSIX; use MIME::Entity; use Commods; $CGI::POST_MAX= 3*1024*1024; use CGI qw/:standard -private_tempfiles/; set_ctype_utf8(); our $now= time; defined $now or die $!; my $re_any= "^(.*)\$"; parse_info_serverside(); sub fail ($) { my ($msg) = @_; addlog("failing $msg"); print header(-status=>'400 Bad commodity update', -type=>'text/plain', -charset=>'us-ascii'); print "Error: $msg\n"; exit 0; } sub must_param ($$) { my ($n,$re)= @_; my $v= param($n); fail("missing form parameter $n") unless defined $v; fail("invalid form parameter $n ($re)") unless $v =~ m/$re/; return $1; } my %o; my $midtmp= "_mid-pid$$.hold"; open MIDTMP, ">$midtmp" or die "$midtmp $!"; stat MIDTMP or die $!; my $ino= (stat _)[1]; my $midino= "_mid-ino$$.hold"; rename $midtmp, $midino or die "$midtmp $midino $!"; close MIDTMP or die $!; our $hostname= `hostname -f`; $? and die $?; chomp $hostname or die; our $mid= "<$now.$$.$ino\@$hostname>"; our $pwd= `pwd`; $? and die $?; chomp($pwd); our $caller= cgi_get_caller; sub addlog ($) { print LOG "$mid $caller $_[0]\n" or die $!; flush LOG or die $!; } open LOG, ">>_upload.log" or die $!; addlog("receiving"); $o{'clientname'}= must_param('clientname',$re_any); my $clientinfo= $clients{$o{'clientname'}}; fail('unknown client') unless defined $clientinfo; my $clientfixes= must_param('clientfixes', "^([-0-9a-z ]*)\$"); my @clientfixes= sort grep { m/./ } split /\s+/, $clientfixes; $o{'clientfixes'}= "@clientfixes"; foreach my $bug (@$clientinfo) { fail("client out of date - missing bugfix \`$bug'") unless grep { $_ eq $bug } @clientfixes; } $o{'clientversion'}= must_param('clientversion', "^(\\d[-+._0-9a-zA-Z]+)\$"); if (param('requesttimestamp')) { my $now= time; defined $now or die; print header(-type=>'text/plain', -charset=>'us-ascii'), "OK $now.\n"; exit(0); } $o{'ocean'}= must_param('ocean', $re_any); $o{'island'}= must_param('island', $re_any); my $arches= $oceans{$o{'ocean'}}; fail("unknown ocean") unless $arches; my $island_found= 0; foreach my $islands (values %$arches) { my $sources= $islands->{$o{'island'}}; next unless $sources; die if $island_found; $island_found= $sources; } fail("unknown island") unless $island_found; $o{'timestamp'}= must_param('timestamp', "^([1-9]\\d{1,20})\$"); fail("clock skew") if $o{'timestamp'} >= $now; my $indatafh= upload('data'); defined $indatafh or fail("data is not a file"); my $datafile= must_param('data',"^(deduped\\.tsv\\.gz)\$"); foreach my $mid (<_mid-*.hold>) { if (!stat $mid) { $!==&ENOENT or die "$mid $!"; next; } my $age= (stat _)[9]; next if $age < 60; unlink $mid or $!==&ENOENT or die "$mid $!"; } $o{'instance'}= $ENV{'YARRG_INSTANCE'}; my $mcontent= MIME::Entity->build(To => 'yarrg-commod-updates', Subject => $ENV{'YARRG_INSTANCE'}, Type => 'multipart/mixed', Boundary => '=', 'Message-ID' => $mid, Charset => 'utf-8'); get_our_version(\%o, 'server'); foreach my $cs (qw(client server)) { $o{"${cs}spec"}= join "\t", map { $o{$cs.$_} } qw(name version fixes); } my $metadata= ''; sub ksmap ($) { my ($v) = @_; my $i=0; grep { return $i if $_ eq $v; $i++ } qw(ocean island timestamp); sprintf "z %d %s", (length $v) / 8, $v; } foreach my $vn (sort { ksmap($a) cmp ksmap($b) } keys %o) { my $val= $o{$vn}; die if $val =~ m/\n|\r/; $metadata .= "$vn\t$o{$vn}\n"; } my $mdpart= MIME::Entity->build(Top => 0, Type => 'text/plain', Charset => 'utf-8', Disposition => 'inline', Encoding => 'quoted-printable', Filename => 'metadata', Data => $metadata); $mcontent->add_part($mdpart); my $gunzchild= open(GZ, "-|"); defined $gunzchild or die $!; if (!$gunzchild) { open STDIN, "<&=", $indatafh or die $!; exec 'gunzip'; die $!; } my $dedupedtsv= pipethrough_prep(); while () { my @v= check_tsv_line($_,\&fail); print $dedupedtsv join("\t",@v),"\n" or die $!; } GZ->error and die $!; $?=0; close GZ; $? and fail("gunzip for check failed code $?"); my $launderedgz= pipethrough_run($dedupedtsv,undef,'gzip','gzip'); my $mdatafile= MIME::Entity->build(Top => 0, Type => 'application/octet-stream', Disposition => 'attachment', Encoding => 'base64', Filename => 'deduped.tsv.gz', Data => $launderedgz); $mcontent->add_part($mdatafile); open M, "|/usr/sbin/sendmail -t -oi -oee -odb" or fail("fork sendmail failed! ($!)"); $mcontent->print(\*M); M->error and fail("write sendmail failed! ($!)"); $?=0; close M; $? and fail("sendmail failed code $?"); print header(-type=>'text/plain', -charset=>'us-ascii'), "OK\nThank you for your submission to YARRG.\n" or die $!; addlog("accepted $o{'clientspec'}"); close LOG or die $!;