#! /usr/local/bin/perl -w # proxy.pl Version 1.2 # Release Date 3/16/2001 # By Marc Waldman (waldman@cs.nyu.edu) # Publius Project WWW page - cs.nyu.edu/waldman/publius # Publius Project E-mail address - publius@cs.nyu.edu # Copyright 2000 - New York University # #PUBLIUS TERMS AND CONDITIONS #THIS SOFTWARE IS EXPERIMENTAL AND IS PROVIDED "AS IS." #ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT #LIMITED TO, THE IMPLIED WARRANTIES OF #MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # #IN NO EVENT SHALL AT&T OR THE AUTHORS BE LIABLE FOR ANY DIRECT, #INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR #CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT #OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; #OR BUSINESS INTERRUPTION) HOWEVER #CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, #STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) #ARISING IN ANY WAY OUT OF THE USE OF THIS #SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # #You are granted a royalty-free, non-exclusive, non-transferable right to #install and use the Publius software for non-commercial purposes during #the Publius trial. You #may not redistribute the code, with or without alterations, #or use the code for #commercial purposes without the permission of AT&T and the authors. BEGIN { push @INC, "./Lib" } use strict; use Common; use Publish; use HostTable; use URI; use Update; use Delete; use HTTP::Daemon; use HTTP::Status; use HTTP::Date; use LWP::UserAgent; use MIME::Base64; use HTTP::Request::Common; use HTTP::Response; use Digest::MD5; use Cwd; use POSIX qw(:sys_wait_h); sub debug{ print @_,"\n"; } my $proxyNonPubliusUrls="YES"; # Set to "NO" if you don't want to proxy non Publius # URLs. Any value other than "YES" (in uppercase) will indicate that #non Publius URLs will not be proxied. sub removeUrlWhitespace{ # Used only for forms that are URLencoded (Delete and Retreive). # Don't use in mime encoded forms that have attachments (Publish and Update) my $fullPath=shift @_; # strip out the + signs embedded in form that represent white space # all "user-entered" + signs are already URI escaped by the browser $fullPath=~ s/[+]//g; $fullPath=URI::Escape::uri_unescape($fullPath); $fullPath =~ s/\s//g; #remove all unescaped whitespace return $fullPath; } sub removeWhitespace{ my $str=shift @_; $str =~ s/\s//g; return $str; } sub makeRequest{ my ($command,$hostTableIndex,$directory)=@_; my ($serverIp,$serverName,$cgiLocation)=HostTable::getAllHostInfo($hostTableIndex); my $publishURL="http://".$serverName . $cgiLocation; #debug ($publishURL,"\n"); #debug ("directory=$directory\n"); my $request=HTTP::Request::Common::POST($publishURL, Content_Type => 'form-data', Content => [command => $command, directory=> $directory, ] ); my $ua=LWP::UserAgent->new(); $ua->timeout($Common::httpTimeOutValue); my $response=$ua->request($request); return $response; } sub sendDocToBrowser{ my ($connection,$plainTextRef)=@_; #add all headers except content-type. my $header=HTTP::Headers->new("expires"=>HTTP::Date::time2str(),"date"=>HTTP::Date::time2str(),"last_modified"=>HTTP::Date::time2str(),"pragma"=>"no-cache"); # determine correct Mime-type my $defaultMimeType; if (-T "$Common::workDir/dec"){ # text file $defaultMimeType="text/plain"; } else{ $defaultMimeType="octet/stream"; } my $fileExtensionLength=length($Common::emptyFileExtension); my $fileExtension=substr($$plainTextRef,0,$fileExtensionLength); # remove white space at end of extension and convert to lower case $fileExtension =~ s/^[\s]+//; $fileExtension =~ tr/A-Z/a-z/; if ($Common::MimeTypes{$fileExtension}){ $defaultMimeType=$Common::MimeTypes{$fileExtension}; } #debug("Mime-type:",$defaultMimeType); $header->push_header("content-type"=>$defaultMimeType); # when return document to browser remove file extension my $response=HTTP::Response->new(200,"OK",$header,substr($$plainTextRef,$fileExtensionLength)); # debug($response->as_string()); $response->protocol("HTTP/1.0"); $connection->send_response($response); push (@Common::filesToDelete,"$Common::workDir/dec"); push (@Common::filesToDelete,"$Common::workDir/key"); } sub trySharesOnEncryptedFile{ my ($connection,$usedIndexValuesRef,$filesRetrievedRef,$reorderedHashesRef,$hostTableIndicesRef,$elementsRef)=@_; my $testIndex=$$usedIndexValuesRef[$#$usedIndexValuesRef]; if (!($$filesRetrievedRef[$testIndex])){ my $response=makeRequest("RETRIEVE_F",$$hostTableIndicesRef[$testIndex],Common::hexStringFromHash(Common::base64DecodeHash($$reorderedHashesRef[$testIndex]))); if (($response->is_success()) && (defined($response->header("update")))){ if ($response->header("update")=="0"){ $$filesRetrievedRef[$testIndex]="F"; open(FH,">$Common::workDir/" . "F$testIndex"); print FH $response->content(); close(FH); push (@Common::filesToDelete,"$Common::workDir/F$testIndex"); } else{ #returned an update, consider an error $$filesRetrievedRef[$testIndex]="E"; return 0; } } else{ #error occured - could be a time out $$filesRetrievedRef[$testIndex]="E"; return 0; } } if ($$filesRetrievedRef[$testIndex] eq "F"){ Common::assembleShares("key", @$usedIndexValuesRef); my ($errorCode,$keyRef)=Common::readFileIntoString("$Common::workDir/key"); if ($errorCode<0){ return 0; # can't read the key - don't use this combination } Common::decryptFile("F$testIndex","dec",$$keyRef); ($errorCode, my $plainTextRef)=Common::readFileIntoString("$Common::workDir/dec"); if ($errorCode<0){ return 0; # can't read the document - don't use this combination } for (my $i=0; $i<=$#$usedIndexValuesRef; $i++){ my $digest=Digest::MD5->new(); $digest->add($$plainTextRef); $digest->add($$elementsRef[$$usedIndexValuesRef[$i]]); my $d=MIME::Base64::encode_base64(Common::xorHash($digest->digest()),""); # debug ("d=$d r=$$reorderedHashesRef[$$usedIndexValuesRef[$i]]\n"); # append "=" onto reorderedHashesRef since removed during url split if ($d ne ($$reorderedHashesRef[$$usedIndexValuesRef[$i]]."=")){ return 0; } } # if made it here then document must have verified sendDocToBrowser($connection,$plainTextRef); return 1; } else{ return 0; # requested file was not available } } sub retrieveAnonDocument{ my ($connection,$request,$fullPathRef)=@_; #debug($$fullPathRef); if (substr($$fullPathRef,0,1) eq "/"){ $$fullPathRef=substr($$fullPathRef,1); #move past initial slash } if ($$fullPathRef eq ""){ # http://!publius!/ empty file path so just display User Interface my $header=HTTP::Headers->new(-type=>"text/html"); my ($errorCode,$fileContentRef)=Common::readFileIntoString("$Common::guiDir/menu.html"); $connection->send_response(200,"OK",$header,$$fileContentRef); return; } my @hashes=Common::splitUrlIntoHashes($$fullPathRef); my @hostTableIndices; my @reorderedHashes; $#reorderedHashes=$#hashes; #set array to same size as hashes $#hostTableIndices=$#hashes; my $randomIndex; my $numOfHashes=$#hashes+1; # mix up hash ordering so retrieval order not predictable for (my $i=0; $i<$numOfHashes; $i++){ $randomIndex=rand(100) % $numOfHashes; if (!($reorderedHashes[$randomIndex])){ $reorderedHashes[$randomIndex]=$hashes[$i]; $hostTableIndices[$randomIndex]=Common::getIndexFromBase64Hash($hashes[$i]); } else{ #already something at $randomIndex location, search for free loc. for (my $j=1; $j<$numOfHashes; $j++){ if (!($reorderedHashes[($randomIndex+$j)%$numOfHashes])){ $reorderedHashes[($randomIndex+$j)%$numOfHashes]=$hashes[$i]; $hostTableIndices[($randomIndex+$j)%$numOfHashes]=Common::getIndexFromBase64Hash($hashes[$i]); last; } } } } # start to retrieve shares my @maxIndexValue; my @currentIndexValue; # setup for generating all combinations of shares for (my $i=1; $i<=$Common::numSharesToReconstruct; $i++){ $maxIndexValue[$i-1]=$numOfHashes-$Common::numSharesToReconstruct+$i; $currentIndexValue[$i-1]=$i-1; } my $moreCombinations=0; my @elementRetrieved; # "S"=Share, "U"=Update my @elements; # actual share or update my @filesRetrieved; while (1){ # test each combination my $shareCount=0; my $updateCount=0; my @usedIndexValues; # may need to undef @usedIndexValues # retrieve information corresponding to current combination for (my $i=0; $i<$Common::numSharesToReconstruct; $i++){ my $testIndex=$currentIndexValue[$i]; push(@usedIndexValues, $testIndex); if (!($elementRetrieved[$testIndex])){ my $response=makeRequest("RETRIEVE_S",$hostTableIndices[$testIndex],Common::hexStringFromHash(Common::base64DecodeHash($reorderedHashes[$testIndex]))); if (($response->is_success()) && (defined($response->header("update")))){ if ($response->header("update")=="1"){ $updateCount++; $elementRetrieved[$testIndex]="U"; #update $elements[$testIndex]=$response->content(); # check size of Url retured if (length($elements[$testIndex])>$Common::maxUpdateUrlSize){ # consider too long URL as error $elementRetrieved[$testIndex]="E"; $elements[$testIndex]="Too Large"; } } else{ #must be a share $shareCount++; $elementRetrieved[$testIndex]="S"; #share $elements[$testIndex]=$response->content(); # check size of share returned if (length($elements[$testIndex])>$Common::maxShareSize){ # consider large share as an error $elementRetrieved[$testIndex]="E"; $elements[$testIndex]="Too Large"; } else{ open(FH,">$Common::workDir/" . $testIndex); print FH $response->content(); close(FH); push (@Common::filesToDelete,"$Common::workDir/$testIndex"); } } } else{ #error - could be a time out $elementRetrieved[$testIndex]="E"; #error $elements[$testIndex]=$response->message(); } } elsif ($elementRetrieved[$testIndex] eq "U"){ $updateCount++; } elsif ($elementRetrieved[$testIndex] eq "S"){ $shareCount++; } } # check if all updates and if willing to accept updates if (($updateCount==$Common::numSharesToReconstruct) && ($Common::updateBit eq "1")){ # check if all updates are the same my $allEqual=1; for (my $j=1; $j<=$#usedIndexValues; $j++){ if ($elements[$usedIndexValues[$j]] ne $elements[$usedIndexValues[0]]){ $allEqual=0; last; } } if ($allEqual==1){ $connection->send_redirect($elements[$usedIndexValues[0]],301,"Redirecting"); return; } } elsif ($shareCount==$Common::numSharesToReconstruct){ my $result=trySharesOnEncryptedFile($connection,\@usedIndexValues,\@filesRetrieved,\@reorderedHashes,\@hostTableIndices,\@elements); if ($result==1){ return; #all done - process next request } } $moreCombinations=0; # generate next combination for (my $i=$Common::numSharesToReconstruct-1; $i>=0; $i--){ if ($currentIndexValue[$i]+1<$maxIndexValue[$i]){ $currentIndexValue[$i]++; $moreCombinations=1; for (my $j=$i+1; $j<$Common::numSharesToReconstruct; $j++){ $currentIndexValue[$j]=$currentIndexValue[$i]+($j-$i); } last; } } if ($moreCombinations==0){ $connection->send_response(200,"OK",undef,"

Unable To Reconstruct Document

"); last; } } } sub processMimeForm{ my ($request)=@_; my $boundary; my $requestString=$request->as_string; my %fieldsFound; my $prependString=""; #Used to prepend file extension to file contents #Find the boundary string that separates the fields if ($requestString=~ /Content-Type.*boundary=(.*)/i){ $boundary="--".$1 #field boundaries will start with two "-" characters #and end line with "\r\n" (RFC 2046) #last boundary ends with "--\r\n" } # find boundary for first field my $fieldStartLocation=index($requestString,$boundary); while ($fieldStartLocation>0){ #move past initial boundary $fieldStartLocation+=length($boundary)+2; #move past "\r\n" #find the field name associated with this entry my $nameIndex=index($requestString,"name=",$fieldStartLocation); $nameIndex+=6; # move onto field name my $nameEndIndex=index($requestString,'"',$nameIndex); my $fieldname=substr($requestString,$nameIndex,$nameEndIndex-$nameIndex); #save program name - located in field "filename" if ($fieldname eq "DOCUMENT"){ my $endOfLine=index($requestString,"\r\n",$nameEndIndex); # Store filename if available - Used to display fn in publish window if (substr($requestString,$nameEndIndex,$endOfLine-$nameEndIndex)=~/filename=\"([^\"]*)\"/){ $fieldsFound{$fieldname}=$1; my $periodIndex=rindex($1,"."); #find last period in file if ($periodIndex>-1){ $prependString=substr($1,$periodIndex+1,length($Common::emptyFileExtension)); # The file extension could be less than # of chars set aside for ext. if (length($prependString)$Common::maxFileSize){ $fieldsFound{$fieldname}=""; #don't save field contents to disk } else{ open (FLD,">$Common::workDir/$fieldname"); print FLD $prependString.substr($requestString,$doubleCrlfIndex,$fieldEndLocation-$doubleCrlfIndex-2); #subtract two to remove the CRLF characters preceding end boundary tag close(FLD); push (@Common::filesToDelete,"$Common::workDir/$fieldname"); } $prependString=""; $fieldStartLocation=index($requestString,$boundary."\r\n",$fieldEndLocation); #debug($fieldStartLocation,"\n"); # fieldStartLocation should be less than zero when we hit the final boundary # of the document since the boundary will have a "--\r\n" suffix, # we just check for "\r\n" suffix; } return \%fieldsFound; } sub processPubFile{ my ($connection,$request)=@_; #debug($request->as_string); my $fieldsFoundRef=processMimeForm($request); if (!($$fieldsFoundRef{"DOCUMENT"})){ $connection->send_response(200,"OK",undef,"Unable To Locate Document - May Have Exceeded Allowable Size"); return; } if (!($$fieldsFoundRef{"PASSWORD"})){ $connection->send_response(200,"OK",undef,"Unable To Locate Password - May Have Exceeded Allowable Size"); return; } if (!($$fieldsFoundRef{"PUBLISH_OPTIONS"})){ $connection->send_response(200,"OK",undef,"Unable To Locate Publish Options - May Have Exceeded Allowable Size"); return; } else{ # read PUBLISH_OPTIONS and NUM_SHARES my ($errorCode,$optionsRef)=Common::readFileIntoString($Common::workDir."/PUBLISH_OPTIONS"); if ($$optionsRef eq "NORM"){ # allow both update and delete $Common::updateBit="1"; $Common::deleteBit="1"; } elsif ($$optionsRef eq "NOUP"){ #debug("No Update Specified"); $Common::updateBit="0"; $Common::deleteBit=$Common::defaultDeleteBit; } elsif ($$optionsRef eq "NODL"){ #debug("No Delete Specified"); $Common::updateBit="0"; $Common::deleteBit="0" } else{ $connection->send_response(200,"OK",undef,"Unknown Publish Options"); return; } if (!($$fieldsFoundRef{"NUM_SHARES"})){ $Common::numSharesToReconstruct=$Common::defaultNumSharesToReconstruct; } else{ my ($errorCode,$sharesRef)=Common::readFileIntoString($Common::workDir."/NUM_SHARES"); if ((($$sharesRef+0)>0) && (($$sharesRef+0)<$Common::numSharesToSplit)){ $Common::numSharesToReconstruct=$$sharesRef+0; } else{ $connection->send_response(200,"OK",undef,"Incorrect Number of Shares Specified Options"); } } } # found password and path so attempt to publish file my $filePath=$Common::workDir."/DOCUMENT"; my ($errorCode,$passwordRef)=Common::readFileIntoString($Common::workDir."/PASSWORD"); if ($errorCode<0){ $$passwordRef=""; return; } Publish::publishFile($filePath,$$passwordRef,$$fieldsFoundRef{"DOCUMENT"}); $connection->send_response(200,"OK",undef,"".$Common::outputString.""); return; } sub processRetrieve{ my ($connection,$request)=@_; if ($request->as_string =~ /URL=([^\s&]*)/){ my $fullPath=removeUrlWhitespace($1); $fullPath=(new URI::URL($fullPath))->full_path(); retrieveAnonDocument($connection,$request,\$fullPath) } else{ $connection->send_response(200,"OK",undef,"

Cannot Find URL field

"); } } sub processUpdate{ my ($connection,$request)=@_; my $fieldsFoundRef=processMimeForm($request); if (!($$fieldsFoundRef{"DOCUMENT"})){ $connection->send_response(200,"OK",undef,"Unable To Locate Document"); return; } if (!($$fieldsFoundRef{"NEWPASSWORD"})){ $connection->send_response(200,"OK",undef,"Unable To Locate New Password -- Try publishing with command line publishing tools"); return; } if (!($$fieldsFoundRef{"OLDPASSWORD"})){ $connection->send_response(200,"OK",undef,"Unable To Locate Old Password -- Try publishing with command line publishing tools"); return; } if (!($$fieldsFoundRef{"OLDURL"})){ $connection->send_response(200,"OK",undef,"Unable To Locate Password -- Try publishing with command line publishing tools"); return; } # found all fields so read all data from files my ($errorCode,$newPasswordRef)=Common::readFileIntoString($Common::workDir."/NEWPASSWORD"); if ($errorCode<0){ $connection->send_response(200,"OK",undef,"Unable To Read New Password -- Try publishing with command line publishing tools"); } ($errorCode, my $oldPasswordRef)=Common::readFileIntoString($Common::workDir."/OLDPASSWORD"); if ($errorCode<0){ $connection->send_response(200,"OK",undef,"Unable To Read Old Password -- Try publishing with command line publishing tools"); } ($errorCode, my $oldURLRef)=Common::readFileIntoString($Common::workDir."/OLDURL"); if ($errorCode<0){ $connection->send_response(200,"OK",undef,"Unable To Read Previous URL -- Try publishing with command line publishing tools"); return; } if (Common::isUrlUpdateable(removeWhitespace($$oldURLRef))<1){ $connection->send_response(200,"OK",undef,"This URL is not updateable"); return; } $Common::outputString="

Update Operation

"; Common::addToOutput("

The result of the Publish operation performed on the update document is shown first. The result of the Update operation follows.

"); Common::addToOutput("

Output of Publish Operation

"); my $newURL=Publish::publishFile($Common::workDir."/DOCUMENT",$$newPasswordRef,$$fieldsFoundRef{"DOCUMENT"}); # found all fields so attempt update operation $Common::outputString=$Common::outputString."

Output of Update Operation


\n"; Update::update(removeWhitespace($$oldURLRef),removeWhitespace($newURL),$$oldPasswordRef); $connection->send_response(200,"OK",undef,"$Common::outputString"); return; } sub processDelete{ my ($connection,$request)=@_; my $url; my $password; if ($request->as_string =~ /URL=([^\s&]*)/){ $url=removeUrlWhitespace($1); } else{ $connection->send_response(200,"OK",undef,"

Cannot Find URL field

"); return; } if ($request->as_string =~ /PASSWORD=([^\s&]*)/){ #debug($request->as_string); #debug($1); # remove non-escaped "+" signs, they are white space $password=$1; $password=~ s/[+]/ /g; $password=URI::Escape::uri_unescape($password); } else{ $connection->send_response(200,"OK",undef,"

Cannot Find PASSWORD field

"); return; } # found all fields so attempt delete operation Delete::deleteDoc(\$url,$password); $connection->send_response(200,"OK",undef,"$Common::outputString"); } sub processAnonCommand{ my ($connection,$request)=@_; my $fullPath=$request->url->full_path; my $anonCommand=substr($request->url->host,9); if (length($anonCommand)>0){ #URL contains embedded command if ($anonCommand eq "gui_ret"){ my $header=HTTP::Headers->new(-type=>"text/html"); my ($errorCode,$fileContentRef)=Common::readFileIntoString("$Common::guiDir/retrieve.html"); $connection->send_response(200,"OK",$header,$$fileContentRef); return; } if ($anonCommand eq "gui_pub"){ my $header=HTTP::Headers->new(-type=>"text/html"); my ($errorCode,$fileContentRef)=Common::readFileIntoString("$Common::guiDir/publish.html"); $connection->send_response(200,"OK",$header,$$fileContentRef); return; } if ($anonCommand eq "gui_del"){ my $header=HTTP::Headers->new(-type=>"text/html"); my ($errorCode,$fileContentRef)=Common::readFileIntoString("$Common::guiDir/delete.html"); $connection->send_response(200,"OK",$header,$$fileContentRef); return; } if ($anonCommand eq "gui_upd"){ my $header=HTTP::Headers->new(-type=>"text/html"); my ($errorCode,$fileContentRef)=Common::readFileIntoString("$Common::guiDir/update.html"); $connection->send_response(200,"OK",$header,$$fileContentRef); return; } if ($anonCommand eq "gui_adv"){ my $header=HTTP::Headers->new(-type=>"text/html"); my ($errorCode,$fileContentRef)=Common::readFileIntoString("$Common::guiDir/advpub.html"); $connection->send_response(200,"OK",$header,$$fileContentRef); return; } # all other options require crypto Common::connectToCrypto(); if ($anonCommand eq "retrieve"){ processRetrieve($connection,$request); Common::quitCrypto(); return; } elsif ($anonCommand eq "update"){ processUpdate($connection,$request); Common::quitCrypto(); return; } elsif ($anonCommand eq "delete"){ processDelete($connection,$request); Common::quitCrypto(); return; } elsif ($anonCommand eq "pubfile"){ processPubFile($connection,$request); Common::quitCrypto(); return; } else { #unknown command $connection->send_response(200,"OK",undef,"

Unknown Command - $anonCommand

"); Common::quitCrypto(); return; } } else{ # regular publius URL if (($fullPath eq "") || ($fullPath eq "/")){ # http://!publius!/ empty file path so just display User Interface my $header=HTTP::Headers->new(-type=>"text/html"); my ($errorCode,$fileContentRef)=Common::readFileIntoString("$Common::guiDir/menu.html"); $connection->send_response(200,"OK",$header,$$fileContentRef); return; } else{ Common::connectToCrypto(); retrieveAnonDocument($connection,$request,\$fullPath); Common::quitCrypto(); } } } sub acceptConnection{ my $connection=shift; my $request; my $pid=fork; #debug("Child pid value = $pid\n"); if ($pid){ return; } $request = $connection->get_request; if (!$request){ # $request was undefined close($connection); #debug("Undefined Request\n"); close($connection); exit 0; } #debug($request->url->host."\n"); if (substr($request->url->host,0,9) eq "!publius!"){ # $found a "publius" URL #debug("Publius URL found\n"); processAnonCommand($connection,$request); Common::clearOutputString(); Common::cleanUpWorkDir(); } else { # found a regular URL # debug("regular URL found\n"); my $response; if ($proxyNonPubliusUrls eq "YES"){ my $ua=LWP::UserAgent->new(); $response=$ua->request($request); #debug($response->message()); } else{ my $header=HTTP::Headers->new(-type=>"text/html"); $response=HTTP::Response->new(200,"OK",$header,"

This proxy will not process non-Publius URLs.
Make sure the requested host's domain name is listed in the proxy exception list -- See step 4 of the Netscape instructions, or step 6 of the Internet Explorer instructions.

"); } $connection->send_response($response); } close($connection); exit 0; } sub removeDirectory{ my $dirname=shift; my $entry; #directory should be empty if Publius #operation completed, might be non-empty #if operation canceled opendir(DH,$dirname); while ($entry=readdir(DH)){ if (substr($entry,0,1) ne "."){ unlink("$dirname/$entry"); } } closedir(DH); rmdir($dirname); } sub main{ my $d = new HTTP::Daemon(Reuse => 1, LocalPort=> $Common::publiusLocalPort,Timeout=>5); if (!$d){ die "Couldn't connect to port $Common::publiusLocalPort\n"; } Common::initialize(); srand(); #for later use in retrieve function $Common::useHTML=1; #assume sending output to the browser print "Publius daemon installed at: URL:", $d->url, "\n"; my $pid; my $s; my $cd=Cwd::getcwd().$Common::workDir."/"; while (1){ $s=$d->accept(); if ($s){ acceptConnection($s); } while (1){ $pid=waitpid(-1,WNOHANG); last if $pid <1; removeDirectory($cd.$pid) if (-e $cd.$pid); } } } main();