#!/usr/bin/perl -w # # $Id$ # ############################################################################ # The following preferences may be modified to match the local environment # ############################################################################ # Directory with the users data. $TMPDIR = '/tmp/asn1c-cgi-jail/'; $SUIDHelper = './asn1c-suid-helper'; $SkeletonsDir = '/usr/local/share/asn1c'; # Will be needed only once $CompilerLocation = '/usr/local/bin/asn1c'; # asn1c binary location $HashProgramPath = 'md5'; # Program to hash the input $DM = 0750; # Directory mode for all mkdirs. $MaxHistoryItems = 5; # Number of items in History $DynamicHistory = 'yes'; # Full/Short history $safeFilename = '^[a-z0-9_-]+[.a-z0-9_-]*$'; # Safe filename $ASN1C_Page = 'http://lionet.info/asn1c'; $HelpEmail = 'asn1c@lionet.info'; $defaultUserEmail = 'your@email'; $warn = '
'; $unwarn = '
'; $OpEnvFailed = 'Failed to create the operations\' environment:'; $RandFailed = 'No source of randomness'; $SandBoxInitFailed = 'User playground initialization failed'; $myName = $ENV{SCRIPT_NAME}; # URL of this particular script (without args) $homePath = "" . "Home" . " >> asn1c" . " >> Free Online ASN.1 Compiler" . "

"; ################################################### # The code below rarely requires any modification # ################################################### use CGI qw/param cookie header upload escapeHTML/; $|=1; # Enable AutoFlush (for older versions of Perl) my $redirect = ''; # No redirection by default my $redirect_bottom = ''; # No redirection text by default my $content = ''; # Default content is empty sub IssueRedirect() { $redirect = ""; $redirect_bottom = "

This page will disappear in 5 seconds.
" } # If something goes wrong, this function is invoked to display the error message sub bark($@) { local $_ = join("
\n", @_); $content = $warn . $_ . $unwarn; goto PRINTOUT; } # Make the directory name containing session files for the given Session ID sub makeSessionDirName($$) { local $pfx = shift; # Prefix is the name of the top-level directory local $sid = shift; # Session identifier (md5) $pfx . '/sessions/' . $sid . '/'; } # Create ISO 8601 time string: "YYYY-MM-DDThh:mm:ss" my $cachedTime; sub isoTime() { return $cachedTime if $cachedTime; local @tm = localtime(time); $tm[5] += 1900; $tm[4] += 1; # Insert leading zeros for(my $i = 0; $i < 5; $i++) { $tm[$i] =~ s/^(.)$/0$1/; } $cachedTime = "$tm[5]-$tm[4]-$tm[3]T$tm[2]:$tm[1]:$tm[0]"; } # Create the necessary environment for chrooting into. sub prepareChrootEnvironment() { return 1 if(-d $TMPDIR); # Envuronment already exists mkdir $TMPDIR, $DM, or bark($OpEnvFailed, $!); # Global directory mkdir $TMPDIR . 'sessions', $DM or bark($OpEnvFailed, $!); # sessions mkdir $TMPDIR . 'bin', $DM or bark($OpEnvFailed, $!); # asn1c location mkdir $TMPDIR . 'skeletons', $DM or bark($OpEnvFailed, $!); # asn1c data if(-d '/lib') { # Merge in dynamic libc mkdir $TMPDIR . 'lib', $DM or bark($OpEnvFailed, $!); system("cd $TMPDIR/lib && " . "for i in" . " /lib/ld-linux.*" # Linux ELF loader . " /lib/libc.*" # Standard C library . " /lib/libm.*" # Math library . '; do ln $i; done'); } elsif(-d '/usr/lib') { # There's no /lib on MacOS mkdir $TMPDIR . 'usr', $DM or bark($OpEnvFailed, $!); mkdir $TMPDIR . 'usr/lib', $DM or bark($OpEnvFailed, $!); mkdir $TMPDIR . 'usr/lib/system', $DM or bark($OpEnvFailed, $!); system("cd $TMPDIR/usr/lib && " . "for i in" . " /usr/lib/libc.*" . " /usr/lib/libSystem.*" . " /usr/lib/system/libmath*" . " /usr/lib/dy*" . '; do ln $i; done'); } if(-d '/usr/libexec') { # FreeBSD ELF loader mkdir $TMPDIR . 'usr', $DM; mkdir $TMPDIR . 'usr/libexec',$DM or bark($OpEnvFailed, $!); system("cd $TMPDIR/usr/libexec && " . 'for i in /usr/libexec/ld-elf.*; do ln $i; done'); } system("cp $CompilerLocation $TMPDIR/bin 2>/dev/null") == 0 or bark($OpEnvFailed, $!); system("cp -r $SkeletonsDir/* $TMPDIR/skeletons >/dev/null 2>&1") == 0 or bark($OpEnvFailed, $!); return 1; } sub makeArchive($$) { local $TMPDIR = shift; local $sandbox = shift; local $archName = $sandbox . '/+Archive.tgz'; if(! -f $archName) { system("cd $sandbox && " . "for i in ./*.[ch]; do if [ -L \$i ]; then" . " cp $TMPDIR/skeletons/\$i \$i.-;" . " mv \$i.- \$i;" . " fi done && tar --dereference --ignore-failed-read --owner nobody --group nobody -zcf +tmp." . $$ . " *.[ch] Makefile* +Compiler.Log *.asn *.asn1" . " && rm -f ./*.[ch] ./Makefile*" . " && mv ./+tmp." . $$ . " $archName" . " || rm -f ./+tmp." . $$); undef unless -f $archName; } $archName; } my $EnvironmentSetOK = prepareChrootEnvironment(); # # Record user's email. # $userEmail = cookie('userEmail'); $userEmail = $defaultUserEmail unless defined($userEmail); $tmpEmail = param('email'); if(defined($tmpEmail)) { unless($tmpEmail =~ /^\s*([a-z0-9._+-]+@[a-z0-9.+-]+)\s*$/i) { bark("Invalid email address: " . "$tmpEmail"); } my $previousEmail = $userEmail; $userEmail = $1; if($userEmail eq $defaultUserEmail) { IssueRedirect(); bark("Please enter your own " . "email address, " . "instead of default \"$defaultUserEmail\""); } if($userEmail ne $previousEmail) { # Refresh cookie contents. local $ck = cookie(-name=>'userEmail', -value=>$userEmail, -path=>'/', -expires=>'+1d'); print "Set-Cookie: " . $ck . "\n"; } } # # Check if full history requested. # $HistoryShow = cookie('HistoryShow'); $HistoryShow = '' unless $HistoryShow; $tmpHSParam = param('history'); # Control cookie setting if (defined($tmpHSParam) && $tmpHSParam ne $HistoryShow && $tmpHSParam =~ /^(full|short)$/) { $HistoryShow = $tmpHSParam; local $ck = cookie(-name=>'HistoryShow', -value=>$HistoryShow, -path=>'/', -expires=>'+1h'); print "Set-Cookie: " . $ck . "\n"; } # # Prepare the session and create the session directory. # If session exists, perfom arguments checking and execute historic views. # $session = cookie('SessionID'); unless($session) { $session = ''; open(R, '/dev/urandom') or open(R, '/dev/random') or bark($RandFailed); read(R, $session, 16) == 16 or bark("Not enough randomness"); if($ENV{HTTP_USER_AGENT}) { $session .= $ENV{HTTP_USER_AGENT}; # Add randomness } my $pid = open(R, "-|"); if($pid == 0) { # Child open(W, "| $HashProgramPath") or die; print W $session; exit(0); } $session = ; $session =~ s/[^a-f0-9]//ig; bark("md5 program is rotten here") if(length($session) != 32); $sessionDir = makeSessionDirName($TMPDIR, $session); mkdir($sessionDir, $DM) or bark($SandBoxInitFailed); my $ck = cookie(-name=>'SessionID', -value=>$session, -path=>'/', -expires=>'+1y'); print header(-expires=>'-1y', -cookie=>$ck); $HTTPHeaderGenerated = 1; } else { $session =~ s/[^a-f0-9]//ig; bark("Nope, try again") if(length($session) != 32); # cool hacker? # Make sure the session directory exists $sessionDir = makeSessionDirName($TMPDIR, $session); mkdir($sessionDir, $DM) or bark($SandBoxInitFailed) unless(-d $sessionDir); local $t = param('time'); local $file = param('file'); local $fetch = param('fetch'); local $show = param('show'); unless(defined($t) && defined($file) && $t =~ /^[0-9TZ:+-]{14,}$/ && $file =~ /$safeFilename/i) { $fetch = ''; $show = ''; } if($fetch =~ /$safeFilename/i || $show =~ /^(log|tgz)$/) { local $sandbox = $sessionDir . '/' . $t . '--' . $file; if($show eq 'tgz') { local $tarball = makeArchive($TMPDIR, $sandbox); defined $tarball or bark("Cannot create archive [$sandbox]"); printf("Content-Type: application/x-tar\n"); printf("Content-Encoding: gzip\n\n"); exec("cat $tarball"); exit(0); } if($show eq 'log') { $sandbox .= '/+Compiler.Log'; } else { $sandbox .= '/' . $fetch; } open(I, "< " . $sandbox) or bark("Invalid or outdated request: [$sandbox] [$show] $!"); printf "Content-Type: text/plain\n\n"; while() { print; } exit(0); } } # # Check if transaction help is requested. # $transHelp = param('transHelp'); if(defined($transHelp) && $transHelp =~ /^([0-9]+)--([0-9TZ:+-]{14,})--([_.a-zA-Z0-9-]+)$/) { open(S, "| sendmail -it") or bark("Cannot perform help request, please email to the address below"); print S "From: $userEmail\n"; print S "To: $HelpEmail\n"; print S "Subject: asn1c help requested for $3 ($1)\n"; print S "\n"; print S "User $userEmail requested help with\n$session/$2--$3 ($1)\n"; print S "\n-- \nasn1c\n"; close(S); open(S, '>> ' . $sessionDir . '/' . $2 . '--' . $3 . '/+HelpReq'); print S "$userEmail\n"; close(S); $content = '
Transaction ' . "$1 ($3) is marked for manual processing.
" . "Results will be mailed to " . "$userEmail shortly." . ""; IssueRedirect(); goto PRINTOUT; } open(LOG, ">> $sessionDir/+logfile") or bark("Sandbox error: $!"); print LOG isoTime() . "\tIP=$ENV{REMOTE_ADDR}"; print LOG "\tEMAIL=$userEmail" if($userEmail ne $defaultUserEmail); @gotSafeNames = (); @gotNames = param('file'); if($#gotNames != -1 && $gotNames[0] ne "") { $gotFile = param('file'); @gotFiles = upload('file'); } else { @gotNames = (); @gotFiles = (); $gotFile = undef; } if($#gotNames == -1) { my $text = param('text'); if($text) { push(@gotNames, 'module.asn1'); } } # Make safe filenames foreach my $fname (@gotNames) { local $_ = $fname; s/.*\///g; # Strip directory components s/.*\\//g; # Strip directory components (DOS version) s/^[.-]/_/g; # Don't allow filenames starting with a dot or a dash s/[^._a-z0-9-]/_/gi; if(!length($_)) { print LOG "\n"; bark("Too strange filename: \"$fname\""); } $_ .= '.asn1' unless(/asn[1]{0,1}$/i); @gotSafeNames = (@gotSafeNames, $_); print LOG "\t" . $_; } # # Save the files and start compilation process. # if($#gotSafeNames >= 0) { $transactionDir = isoTime() . '--' . join("-", @gotSafeNames); print LOG "\tDST=$transactionDir"; my $sandbox = $sessionDir . '/' . $transactionDir; mkdir($sandbox, $DM) or bark($SandBoxInitFailed); open(O, '> ' . $sandbox . '/+Names'); print O join("\n", @gotNames); open(O, '> ' . $sandbox . '/+safeNames'); print O join("\n", @gotSafeNames); for(my $i = 0; $i <= $#gotSafeNames; $i++) { local $name = $gotSafeNames[$i]; open(O, '> ' . $sandbox . '/'. $name); if($#gotFiles == -1) { print O scalar(param('text')); } else { while(<$gotFile>) { print O; } } } close(O); my $inChDir = makeSessionDirName("/", $session) . $transactionDir; my $options = ''; my $optDebugL = param('optDebugL'); my $optE = param('optE'); my $optEF = param('optEF'); my $optNT = param('optNT'); $options .= " -Wdebug-lexer" if(defined($optDebugL) && $optDebugL eq "on"); $options .= " -E" if(defined($optE) && $optE eq "on"); $options .= " -EF" if(defined($optEF) && $optEF eq "on"); $options .= " -fnative-types" if(defined($optNT) && $optNT eq "on"); my $CompileASN = "$TMPDIR/bin/asn1c -v | sed -e 's/^/-- /'" . " > $sandbox/+Compiler.Log 2>&1" . "; $SUIDHelper $TMPDIR $inChDir $options @gotSafeNames " . " >> $sandbox/+Compiler.Log 2>&1" . "; echo \$? > $sandbox/+ExitCode"; system($CompileASN); bark("Failed to initiate compilation process: $!") if(!-r $sandbox . '/+ExitCode'); makeArchive($TMPDIR, $sandbox); } #print join("
\n", `env`); $form = "
" . "Pick the ASN.1 module file:
\n" . "
\n" . "Alternatively, enter the ASN.1 specification into the area below:
\n" . "
\n" . "

" . "" . "These options may be enabled to control the compiler's behavior:
\n" . " Debug lexer (-Wdebug-lexer)
\n" . " Just parse and dump (do not compile) (-E)
\n" . " Parse, perform semantic checks, and dump (-E -F)
\n" . " Employ native machine types (e.g. double instead of REAL_t) (-fnative-types)
\n" . "
" . "

\n" . "" . " (What is ASN.1?)" . "

"; # # Gather previous transactions to generate the history page. # The history page contains a list of several last ASN.1 files # which were uploaded for compilation into the system # by this particular browser (cookie-tracked). # opendir(SD, $sessionDir) or bark("Cannot open sandbox: $!"); my @transactions = sort { $b cmp $a } (grep {/^[0-9TZ:+-]{14,}--[_.a-zA-Z0-9-]+$/} readdir(SD)); my $CountHistoryItems = 0; foreach my $trans (sort { $b cmp $a } @transactions) { next unless($trans =~ /^([0-9TZ:+-]{14,})--([_.a-zA-Z0-9-]+)$/); local ($t, $f) = ($1, $2); local $origTime = $t; $t =~ s/T/ /; # "1999-01-02T13:53:12" => "1999-01-02 13:53:12" # Global transaction number local $tNum = 1 + $#transactions - $CountHistoryItems; # Open the list of file names under which these files are known # at the remote system. open(I, '< ' . $sessionDir . '/' . $trans . '/+Names'); local @Names = ; # Open the list of "safe" file names under which these files # are known to our file system. open(I, '< ' . $sessionDir . '/' . $trans . '/+safeNames'); local @safeNames = ; # Create a list of real file names whith appropriate links to the # "safe" file names for subsequent file fetching. local @markedNames = (); for(my $i = 0; $i <= $#Names; $i++) { local $_ = "$Names[$i]"; @markedNames = (@markedNames, $_); } local $ec = ''; open(I, '< ' . $sessionDir . '/' . $trans . '/+ExitCode') and chop($ec = ); if($ec eq "0") { $results = "" . "Compiled OK
\n"; } else { $results = "" . "Error during compilation: $ec
\n"; } $allowFetchResults = $ec eq "0" && (-f $sessionDir . '/' . $trans . '/+Archive.tgz' || -f $sessionDir . '/' . $trans . '/Makefile.am.sample'); $results .= "" . ($allowFetchResults ? '1. ' : '') . "" . "Show compiler log"; $results .= "
\n" . "2. " . "Fetch results (.tgz)" if $allowFetchResults; if($ec ne "0") { local $eml; open(H, '< ' . $sessionDir . '/' . $trans . '/+HelpReq') and chomp($eml = ); if(defined($eml)) { $results .= "

" . "Status: manual help requested
" . " by $eml,
" . "expect results in a few hours.
"; } else { $results .= '

' . "
" . "" . '' . '' ; $atLeastOneError = 1; } } $trColor = ' BGCOLOR=#f8f8f8'; $trColor = ' BGCOLOR=#d0ffe0' unless($CountHistoryItems); $tNum = '' . $tNum . '' unless($CountHistoryItems); $history .= "" . "$tNum" . "" . join(", ", @markedNames) . "" . "

" . $results . "" . "\n"; last if(++$CountHistoryItems >= $MaxHistoryItems && $HistoryShow ne 'full'); } if($DynamicHistory eq 'yes') { # [Un-]limit number of history items $HistoryItemsHidden = 1 + $#transactions - $CountHistoryItems; if($HistoryItemsHidden > 0) { # Propose to expand the list. local $item = 'item'; $HistoryItemsHidden == 1 or $item = 'items'; $history .= "" . "" . "Show full history " . "($HistoryItemsHidden hidden $item)" . "\n"; } elsif($HistoryShow eq "full" && $#transactions >= $MaxHistoryItems) { # Propose to shorten the list. local $item = 'item'; $MaxHistoryItems == 1 or $item = 'items'; $history .= "" . "" . "Short history ($MaxHistoryItems $item)" . "\n"; } } if($history) { $history = "

History

" . "
" . "\n" . "" . "" . "" . "" . "\n" . $history . "
NFiles processedResult

\n"; if($atLeastOneError) { $history .= "" . "Bottom line: ASN.1 compiler was unable to process some of the input files.
" . "This is typically caused by syntax errors in the input files.\n" . "Such errors are normally fixed by removing or adding a couple of characters in the ASN.1 module.
\n" . "
Please consider clicking on an appropriate "Help me fix it!" link above.
\n" . "An email will be sent to a person who will gladly fix the ASN.1 module for you. (The typical turn-around time is less than 24 hours.)\n" . "
This is free, and highly advisable.\n" . "Your request will help us make a better compiler!\n" . "
Thank you." . "
"; } } unless($history) { $history = "" . "[compiled results will appear here]"; $histValign = 'center'; } else { $histValign = 'top'; } $content .= "
\n" . "

ASN.1 Input

\n" . "$form" . "
$history \n" . "
" . "Privacy Note: this page is tailored " . "to your browser using a cryprographically strong cookie. " . "Other users will see their own (different) data. " . "(Read more...)" . "" . "
"; $ua = $ENV{HTTP_USER_AGENT}; $ua =~ s/\\/\\\\/; $ua =~ s/"/\\"/; print LOG "\tUA=\"$ua\""; print LOG "\n"; # Finalize logging record PRINTOUT: print header(-expires=>'-1y') unless($HTTPHeaderGenerated); # If environment has never been set up completely, remove it. if($EnvironmentSetOK != 1 && $TMPDIR ne "/") { system("rm -rf $TMPDIR/ >/dev/null 2>&1"); } print< Free Online ASN.1 Compiler $redirect $homePath $content $redirect_bottom
The ASN.1 Compiler Copyright © 2003, 2004, 2005 Lev Walkin <vlm@lionet.info>
EOM