Subversion Repositories oidplus

Rev

Rev 652 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

#!/usr/bin/env php
<?php

/*
 * OIDplus 2.0
 * Copyright 2019 - 2021 Daniel Marschall, ViaThinkSoft
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// This script will be called at the ViaThinkSoft server side

// Generate keypair with:
//      openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:8192
//      openssl rsa -pubout -in private.pem -out public.pem

$argc = $_SERVER['argc']; // to please Eclipse for PHP
$argv = $_SERVER['argv']; // to please Eclipse for PHP

if (PHP_SAPI != 'cli') {
        die("This file can only be invoked in CLI mode.\n");
}

if ($argc != 4) {
        echo "Usage: ".$argv[0]." <targetpath> <privkey> <force(1|0)>\n";
        exit(2);
}

$output_dir = $argv[1];
$priv_key = $argv[2];
$force = $argv[3];

if (!is_dir($output_dir)) {
        echo "Path $output_dir does not exist!\n";
        exit(1);
}

if (!file_exists($priv_key)) {
        echo "Private key file $priv_key does not exist!\n";
        exit(1);
}

if (($force != '1') && ($force != '0')) {
        echo "Argument 'force' must be 0 or 1\n";
        exit(1);
}

$outscript = '';

function getDirContents_del($dir_old, $dir_new, $basepath_old=null, $basepath_new=null) {
        global $outscript;

        if (is_null($basepath_old)) $basepath_old = $dir_old;
        $basepath_old = realpath($basepath_old) . DIRECTORY_SEPARATOR;
        if ($basepath_old == '/') die('ARG');

        $dir_old = realpath($dir_old) . DIRECTORY_SEPARATOR;
        $dir_new = realpath($dir_new) . DIRECTORY_SEPARATOR;
        $files_old = scandir($dir_old);
        $files_new = scandir($dir_new);

        foreach ($files_old as $file_old) {
                if ($file_old === '.') continue;
                if ($file_old === '..') continue;
                if ($file_old === '.svn') continue;
                if ($file_old === '.git') continue;

                $path_old = realpath($dir_old . DIRECTORY_SEPARATOR . $file_old);
                $path_new = realpath($dir_new . DIRECTORY_SEPARATOR . $file_old);

                $xpath_old = substr($path_old, strlen($basepath_old));

                if (is_dir($path_old)) {
                        getDirContents_del($path_old, $path_new, $basepath_old, $basepath_new);
                }

                if (is_dir($path_old) && !is_dir($path_new)) {
                        $outscript .= "// Dir deleted: $xpath_old\n";
                        $outscript .= "@rmdir('$xpath_old');\n";
                        $outscript .= "if (is_dir('$xpath_old'))\n\twarn('Directory could not be deleted (was not empty?): $xpath_old');\n\n";

                } else if (file_exists($path_old) && !file_exists($path_new)) {
                        $outscript .= "// File deleted: $xpath_old\n";
                        $outscript .= "@unlink('$xpath_old');\n";
                        $outscript .= "if (file_exists('$xpath_old'))\n\twarn('File could not be deleted: $xpath_old');\n\n";
                }
        }
}

function getDirContents_diff($dir_old, $dir_new, $basepath_old=null, $basepath_new=null) {
        global $outscript;

        if (is_null($basepath_old)) $basepath_old = $dir_old;
        $basepath_old = realpath($basepath_old) . DIRECTORY_SEPARATOR;
        if ($basepath_old == '/') die('ARG');

        $dir_old = realpath($dir_old) . DIRECTORY_SEPARATOR;
        $dir_new = realpath($dir_new) . DIRECTORY_SEPARATOR;
        $files_old = scandir($dir_old);
        $files_new = scandir($dir_new);

        foreach ($files_old as $file_old) {
                if ($file_old === '.') continue;
                if ($file_old === '..') continue;
                if ($file_old === '.svn') continue;
                if ($file_old === '.git') continue;

                $path_old = realpath($dir_old . DIRECTORY_SEPARATOR . $file_old);
                $path_new = realpath($dir_new . DIRECTORY_SEPARATOR . $file_old);

                $xpath_old = substr($path_old, strlen($basepath_old));

                if (file_exists($path_old) && file_exists($path_new)) {
                        if (file_get_contents($path_old) != file_get_contents($path_new)) {
                                $outscript .= "// Files different: $xpath_old\n";
                                $outscript .= "if (@sha1_file('$xpath_old') !== '".sha1_file($path_old)."')\n\twarn('File was probably modified. Will overwrite the changes now: $xpath_old');\n";
                                $outscript .= "@file_put_contents('$xpath_old', base64_decode('".base64_encode(file_get_contents($path_new))."'));\n";
                                $outscript .= "if (@sha1_file('$xpath_old') !== '".sha1_file($path_new)."')\n\twarn('File cannot be written (checksum mismatch): $xpath_old');\n\n";
                        }
                        if ((fileperms($path_old) & 0777) != (fileperms($path_new) & 0777)) {
                                $outscript .= "// Different file chmod: $xpath_old\n";
                                $outscript .= "if ((DIRECTORY_SEPARATOR === '/') && !@chmod('$xpath_old', 0".sprintf('%o', fileperms($path_new) & 0777)."))\n\twarn('Could not change file permissions of ".$xpath_old."');\n\n";
                        }
                } else if (is_dir($path_old) && is_dir($path_new)) {
                        if ((fileperms($path_old) & 0777) != (fileperms($path_new) & 0777)) {
                                $outscript .= "// Different dir chmod: $xpath_old\n";
                                $outscript .= "if ((DIRECTORY_SEPARATOR === '/') && !@chmod('$xpath_old', 0".sprintf('%o', fileperms($path_new) & 0777)."))\n\twarn('Could not change dir permissions of ".$xpath_old."');\n\n";
                        }
                }

                if (is_dir($path_old)) {
                        getDirContents_diff($path_old, $path_new, $basepath_old, $basepath_new);
                }
        }
}

function getDirContents_add($dir_old, $dir_new, $basepath_old=null, $basepath_new=null) {
        global $outscript;

        if (is_null($basepath_new)) $basepath_new = $dir_new;
        $basepath_new = realpath($basepath_new) . DIRECTORY_SEPARATOR;
        if ($basepath_new == '/') die('ARG');

        $dir_old = realpath($dir_old) . DIRECTORY_SEPARATOR;
        $dir_new = realpath($dir_new) . DIRECTORY_SEPARATOR;
        $files_old = scandir($dir_old);
        $files_new = scandir($dir_new);

        foreach ($files_new as $file_new) {
                if ($file_new === '.') continue;
                if ($file_new === '..') continue;
                if ($file_new === '.svn') continue;
                if ($file_new === '.git') continue;

                $path_old = realpath($dir_old . DIRECTORY_SEPARATOR . $file_new);
                $path_new = realpath($dir_new . DIRECTORY_SEPARATOR . $file_new);

                $xpath_new = substr($path_new, strlen($basepath_new));

                if (is_dir($path_new) && !is_dir($path_old)) {
                        $outscript .= "// Dir added: $xpath_new\n";
                        $outscript .= "@mkdir('$xpath_new');\n";
                        $outscript .= "if (!is_dir('$xpath_new'))\n\twarn('Directory could not be created: $xpath_new');\n";
                        $outscript .= "else if ((DIRECTORY_SEPARATOR === '/') && !@chmod('$xpath_new', 0".sprintf('%o', fileperms($path_new) & 0777)."))\n\twarn('Could not change directory permissions of ".$xpath_new."');\n\n";
                } else if (file_exists($path_new) && !file_exists($path_old)) {
                        $outscript .= "// File added: $xpath_new\n";
                        $outscript .= "@file_put_contents('$xpath_new', base64_decode('".base64_encode(file_get_contents($path_new))."'));\n";
                        $outscript .= "if (!file_exists('$xpath_new'))\n\twarn('File cannot be created (not existing): $xpath_new');\n";
                        $outscript .= "else if (sha1_file('$xpath_new') != '".sha1_file($path_new)."')\n\twarn('File cannot be created (checksum mismatch): $xpath_new');\n";
                        $outscript .= "else if ((DIRECTORY_SEPARATOR === '/') && !@chmod('$xpath_new', 0".sprintf('%o', fileperms($path_new) & 0777)."))\n\twarn('Could not change file permissions of ".$xpath_new."');\n\n";
                }

                if (is_dir($path_new)) {
                        getDirContents_add($path_old, $path_new, $basepath_old, $basepath_new);
                }
        }
}

function getDirContents($dir_old, $dir_new) {
        getDirContents_add($dir_old, $dir_new);
        getDirContents_diff($dir_old, $dir_new);
        getDirContents_del($dir_old, $dir_new);
}


$out = array();
$ec = -1;
exec('svn info https://svn.viathinksoft.com/svn/oidplus/trunk/ | grep "Revision:" | cut -d " " -f 2', $out, $ec);
$max_svn = implode("", $out);


for ($i=2; $i<=$max_svn; $i++) {
        echo "SVN revision $i / $max_svn\r";

        $outfile = $output_dir."/update_".($i-1)."_to_$i.txt";
        if (!$force && file_exists($outfile)) continue;

        $outdir_old = "/tmp/oidplus_svntmp2_".($i-1)."/";
        if (is_dir($outdir_old)) exec("rm -rf $outdir_old", $out, $ec);
        exec("svn co https://svn.viathinksoft.com/svn/oidplus/trunk/@".($i-1)." $outdir_old", $out, $ec);

        $outdir_new = "/tmp/oidplus_svntmp2_$i/";
        if (is_dir($outdir_new)) exec("rm -rf $outdir_new", $out, $ec);
        exec("svn co https://svn.viathinksoft.com/svn/oidplus/trunk/@$i $outdir_new", $out, $ec);

        $outscript  = "<?php\n";
        $outscript .= "\n";
        $outscript .= "/*\n";
        $outscript .= " * OIDplus 2.0\n";
        $outscript .= " * Copyright 2019 - ".date('Y')." Daniel Marschall, ViaThinkSoft\n";
        $outscript .= " *\n";
        $outscript .= " * Licensed under the Apache License, Version 2.0 (the \"License\");\n";
        $outscript .= " * you may not use this file except in compliance with the License.\n";
        $outscript .= " * You may obtain a copy of the License at\n";
        $outscript .= " *\n";
        $outscript .= " *     http://www.apache.org/licenses/LICENSE-2.0\n";
        $outscript .= " *\n";
        $outscript .= " * Unless required by applicable law or agreed to in writing, software\n";
        $outscript .= " * distributed under the License is distributed on an \"AS IS\" BASIS,\n";
        $outscript .= " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n";
        $outscript .= " * See the License for the specific language governing permissions and\n";
        $outscript .= " * limitations under the License.\n";
        $outscript .= " */\n";
        $outscript .= "\n";
        $outscript .= "function info(\$str) { echo \"INFO: \$str\\n\"; }\n";
        $outscript .= "function warn(\$str) { echo \"WARNING: \$str\\n\"; }\n";
        $outscript .= "function err(\$str) { die(\"FATAL ERROR: \$str\\n\"); }\n";
        $outscript .= "\n";
        $outscript .= "@set_time_limit(0);\n";
        $outscript .= "\n";
        $outscript .= "@header('Content-Type: text/plain');\n";
        $outscript .= "\n";
        $outscript .= "chdir(__DIR__);\n";
        if ($i >= 662) {
                $outscript .= "if (trim(@file_get_contents('.version.php')) !== '<?php // Revision ".($i-1)."')\n\terr('This update can only be applied to OIDplus SVN Rev ".($i-1)."!');\n";
        } else {
                $outscript .= "if (trim(@file_get_contents('oidplus_version.txt')) !== 'Revision ".($i-1)."')\n\terr('This update can only be applied to OIDplus SVN Rev ".($i-1)."!');\n";
        }
        $outscript .= "\n";
        /*
        if ($i >= 99999) {
                ... once we require PHP 7.1, we add the requirement here
                ... also if we require fancy new PHP modules, we must add it here
                ... the checks avoid that someone breaks their OIDplus installation if they update
        } else
        */if ($i >= 2) {
                // Rev 2+ requires PHP 7.0.0
                $outscript .= "if (version_compare(PHP_VERSION, '7.0.0') < 0)\n\terr('You need PHP Version 7.0 to update to this version');\n";
        }
        $outscript .= "\n";
        $outscript .= "info('Update to SVN Rev $i...');\n";
        $outscript .= "\n";
        getDirContents($outdir_old, $outdir_new);
        $outscript .= "\n";
        if ($i >= 661) {
                $outscript .= "file_put_contents('.version.php', \"<?php // Revision $i\\n\");\n";
                $outscript .= "if (trim(@file_get_contents('.version.php')) !== '<?php // Revision $i') err('Could not write to .version.php!');\n";
                if ($i == 661) {
                        $outscript .= "@unlink('oidplus_version.txt');\n";
                        $outscript .= "if (file_exists('oidplus_version.txt')) err('Could not delete oidplus_version.txt! Please delete it manually');\n";
                }
        } else {
                $outscript .= "file_put_contents('oidplus_version.txt', \"Revision $i\\n\");\n";
                $outscript .= "if (trim(@file_get_contents('oidplus_version.txt')) !== 'Revision $i') err('Could not write to oidplus_version.txt!');\n";
        }
        $outscript .= "\n";
        $outscript .= "\n";
        $outscript .= "info('Update to SVN Rev $i done!');\n";
        $outscript .= "\n";
        $outscript .= "unlink(__FILE__);\n";
        $outscript .= "\n";

        // Now add digital signature

        if (strpos($outscript, '<?php') === false) {
                echo "Not a PHP file\n"; // Should not happen
                continue;
        }

        $naked = preg_replace('@<\?php /\* <ViaThinkSoftSignature>(.+)</ViaThinkSoftSignature> \*/ \?>\n@ismU', '', $outscript);

        $hash = hash("sha256", $naked.basename($outfile));

        $pkeyid = openssl_pkey_get_private('file://'.$priv_key);
        openssl_sign($hash, $signature, $pkeyid, OPENSSL_ALGO_SHA256);
        openssl_free_key($pkeyid);

        if (!$signature) {
                echo "ERROR: Signature failed\n";
                continue;
        }

        $sign_line = '<?php /* <ViaThinkSoftSignature>'."\n".split_equal_length(base64_encode($signature),65).'</ViaThinkSoftSignature> */ ?>';

        // We have to put the signature at the beginning, because we don't know if the end of the file lacks a PHP closing tag
        if (substr($outscript,0,2) === '#!') {
                // Preserve shebang
                $shebang_pos = strpos($naked, "\n");
                $shebang = substr($naked, 0, $shebang_pos);
                $rest = substr($naked, $shebang_pos+1);
                $outscript = $shebang."\n".$sign_line."\n".$rest;
        } else {
                $outscript = $sign_line."\n".$naked;
        }

        // Write the file

        file_put_contents($outfile, $outscript);
        file_put_contents($outfile.'.gz', gzencode($outscript));

        // Delete temp dirs

        exec("rm -rf $outdir_old", $out, $ec);
        exec("rm -rf $outdir_new", $out, $ec);
}
echo "\n";


// Now write the release messages (required by software update and vnag)


$out = array();
$ec = 0;
exec('svn log https://svn.viathinksoft.com/svn/oidplus/trunk --xml', $out, $ec);
if ($ec != 0) die();

$str = implode("\n",$out);

$xml = simplexml_load_string($str);

$out = array();

foreach ($xml as $a) {

        $out[(int)$a->attributes()->revision] = array(

                'date' => date('Y-m-d H:i:s',strtotime((string)$a->date)),
                'author' => (string)$a->author,
                'msg' => trim((string)$a->msg),

        );

}

ksort($out);

file_put_contents($output_dir.'/releases.ser', serialize($out));

# ---

function split_equal_length($data, $width=65) {
        $out = '';
        for ($i=0; $i<strlen($data); $i+=$width) {
                $out .= substr($data, $i, $width)."\n";
        }
        return $out;
}