| 1 | <?php |
| 2 | /************************************************************* |
| 3 | # 076 License |
| 4 | |
| 5 | Copyright (c) テクニカル諏訪子 |
| 6 | |
| 7 | Permission is hereby granted to any person obtaining a copy of the software |
| 8 | Little Beast (the "Software") to use, modify, merge, copy, publish, distribute, |
| 9 | sublicense, and/or sell copies of the Software, subject to the following conditions: |
| 10 | |
| 11 | 1. **Origin Attribution**: |
| 12 | - You must not misrepresent the origin of the Software; you must not claim |
| 13 | you created the original Software. |
| 14 | - If the Software is used in a product, you must either: |
| 15 | a. Provide clear attribution in the product's documentation, user interface, |
| 16 | or other visible areas, **OR** |
| 17 | b. Pay the original developers a fee they specify in writing. |
| 18 | 2. **Usage Restriction**: |
| 19 | - The Software, or any derivative works, dependencies, or libraries |
| 20 | incorporating it, must not be used for censorship or to suppress freedom of |
| 21 | speech, expression, or creativity. Prohibited uses include, but are not |
| 22 | limited to: |
| 23 | - Censorship of so-called "hate speech", visuals, non-mainstream opinions, |
| 24 | ideas, or objective reality. |
| 25 | - Tools or systems designed to restrict access to information or |
| 26 | artistic works. |
| 27 | 3. **Notice Preservation**: |
| 28 | - This license and the above copyright notice must remain intact in all copies |
| 29 | of the source code. |
| 30 | |
| 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
| 32 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A |
| 33 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
| 34 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
| 35 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE |
| 36 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 37 | */ |
| 38 | |
| 39 | // Gitler(Gitのホスティング)のコマンド。SSHのgitユーザーから使う(chrootの外で動く) |
| 40 | // sshd_config: |
| 41 | // Match User git |
| 42 | // AuthorizedKeysCommand /usr/local/bin/php-8.5 /var/www/htdocs/gitler.php keys %u %t %k |
| 43 | // AuthorizedKeysCommandUser git |
| 44 | // AcceptEnv GIT_PROTOCOL |
| 45 | if (PHP_SAPI !== 'cli') { |
| 46 | http_response_code(404); |
| 47 | exit; |
| 48 | } |
| 49 | |
| 50 | require_once __DIR__.DIRECTORY_SEPARATOR.'/autoload.php'; |
| 51 | define('ROOT', realpath(__DIR__)); |
| 52 | require_once ROOT.'/config/config.php'; |
| 53 | require_once ROOT.'/util.php'; |
| 54 | mb_internal_encoding('UTF-8'); |
| 55 | |
| 56 | use Site\Controller\Gitler as GitlerController; |
| 57 | use Std\Lib\Git\Repository; |
| 58 | use Std\Lib\Git\Ssh; |
| 59 | use Std\Lib\Gitler\Gitler; |
| 60 | use Std\Lib\Gitler\Keys; |
| 61 | |
| 62 | $usage = <<<TXT |
| 63 | 使い方: php gitler.php [コマンド] |
| 64 | |
| 65 | keys <ユーザー> <種類> <鍵> sshdのAuthorizedKeysCommand(%u %t %k)。鍵の持ち主のauthorized_keysの行 |
| 66 | authorized-keys 全部の鍵のauthorized_keys(AuthorizedKeysCommandを使わない場合) |
| 67 | serve <ユーザー> [指紋] SSHの強制コマンド(SSH_ORIGINAL_COMMANDのgit-upload-pack等を処理する) |
| 68 | gc 全部のリポジトリを整理する |
| 69 | |
| 70 | TXT; |
| 71 | |
| 72 | if (!Gitler::enabled()) { |
| 73 | fwrite(STDERR, "Gitlerは無効です(config.phpのGITLER_ENABLED)。\n"); |
| 74 | exit(1); |
| 75 | } |
| 76 | |
| 77 | $args = array_slice($argv, 1); |
| 78 | switch ($args[0] ?? '') { |
| 79 | case 'keys': |
| 80 | // %u(ログインのユーザー)、%t(鍵の種類)、%k(base64の鍵) |
| 81 | [, $login, $type, $key] = $args + [null, '', '', '']; |
| 82 | $sshUser = explode('@', (string)Gitler::config()['ssh'])[0] ?: 'git'; |
| 83 | if ($login !== $sshUser) exit(0); |
| 84 | $found = Keys::find((string)$type, (string)$key); |
| 85 | if ($found !== null) { |
| 86 | $u = Gitler::user($found[0]); |
| 87 | if ($u !== null && (int)($u->role ?? 0) !== Roles::BANNED) echo Keys::authorizedKey($found[0], $found[1]), "\n"; |
| 88 | } |
| 89 | exit(0); |
| 90 | |
| 91 | case 'authorized-keys': |
| 92 | foreach (Keys::all() as $user => $keys) { |
| 93 | $u = Gitler::user($user); |
| 94 | if ($u === null || (int)($u->role ?? 0) === Roles::BANNED) continue; |
| 95 | foreach ($keys as $k) echo Keys::authorizedKey($user, $k), "\n"; |
| 96 | } |
| 97 | exit(0); |
| 98 | |
| 99 | case 'serve': |
| 100 | $name = (string)($args[1] ?? ''); |
| 101 | $user = Gitler::user($name); |
| 102 | if ($user === null || (int)($user->role ?? 0) === Roles::BANNED) { |
| 103 | fwrite(STDERR, "このユーザーは使えません。\n"); |
| 104 | exit(1); |
| 105 | } |
| 106 | if (isset($args[2])) Keys::touchKey($name, (string)$args[2]); |
| 107 | $command = (string)getenv('SSH_ORIGINAL_COMMAND'); |
| 108 | if ($command === '') { |
| 109 | fwrite(STDERR, "こんにちは、{$name}さん!Gitlerにはシェルが有りません。\n"); |
| 110 | exit(1); |
| 111 | } |
| 112 | // gitユーザーはdata/に書けないので、通知とFediverseへの配信はリポジトリに溜める(cronのnotifyが送る) |
| 113 | Gitler::$deferred = true; |
| 114 | $owner = $repoName = ''; |
| 115 | $repoAccess = Gitler::NONE; |
| 116 | exit(Ssh::handle($command, function(string $path, bool $write) use ($user, &$owner, &$repoName, &$repoAccess): ?Repository { |
| 117 | // 「所有者/名前.git」又は「所有者/名前」(前に「gitler/」が有っても良い) |
| 118 | if (!preg_match('#^(?:gitler/)?([A-Za-z0-9]+)/([A-Za-z0-9._-]+?)(?:\.git)?/?$#', $path, $m)) return null; |
| 119 | $access = Gitler::access($user, $m[1], $m[2]); |
| 120 | $meta = $access > Gitler::NONE ? Gitler::meta($m[1], $m[2]) : []; |
| 121 | // 読めてプルリクエストのロールが有ればrefs/for/(AGit)にだけプッシュ出来る |
| 122 | $agit = $write && $access === Gitler::READ && $meta['pulls'] && !$meta['archived'] && Gitler::userCan($user, 'pulls'); |
| 123 | if ($access < ($write && !$agit ? Gitler::WRITE : Gitler::READ)) return null; |
| 124 | if ($write && $meta['archived']) { |
| 125 | fwrite(STDERR, "アーカイブされたリポジトリにはプッシュ出来ません。\n"); |
| 126 | return null; |
| 127 | } |
| 128 | [$owner, $repoName, $repoAccess] = [$m[1], $m[2], $access]; |
| 129 | return Gitler::open($m[1], $m[2]); |
| 130 | }, ['agent' => 'Gitler', 'receive' => GitlerController::receiveOptions($owner, $repoName, $user, $repoAccess)])); |
| 131 | |
| 132 | case 'gc': |
| 133 | $res = Gitler::gcAll(); |
| 134 | fwrite($res->isSuccess ? STDOUT : STDERR, $res->message."\n"); |
| 135 | exit($res->isSuccess ? 0 : 1); |
| 136 | |
| 137 | default: |
| 138 | fwrite(STDERR, $usage); |
| 139 | exit(2); |
| 140 | } |