-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathssh_serve.php
More file actions
executable file
·186 lines (156 loc) · 6.46 KB
/
ssh_serve.php
File metadata and controls
executable file
·186 lines (156 loc) · 6.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#!/usr/bin/env php
<?php
require_once(dirname(__FILE__) . '/bootstrap.php');
class SSH_Serve
{
const COMMANDS_READONLY = [
'git-upload-pack',
'git upload-pack',
'git-upload-archive',
'git upload-archive',
];
const COMMANDS_WRITE = [
'git-receive-pack',
'git receive-pack',
];
const ARG_REGEXP = "#^'/*(?P<path>[a-zA-Z0-9][a-zA-Z0-9@._-]*(/[a-zA-Z0-9][a-zA-Z0-9@._-]*)*)'$#";
protected $user = '';
protected $command = '';
protected $full_path = '';
protected $repository = '';
public function init()
{
$original_command = getenv('SSH_ORIGINAL_COMMAND');
$this->user = $_SERVER['argv'][1] ?? '';
if (empty($this->user)) {
$this->error('wrong ssh_serve script usage. Expected username as the only argument');
}
@list($command, $arguments) = explode(' ', $original_command);
if (!preg_match(self::ARG_REGEXP, $arguments, $matches) || !isset($matches['path'])) {
$this->error("command looks unsafe: {$command} {$arguments}");
}
$this->full_path = $this->repository = $matches['path'];
if (empty($command) || empty($arguments) || $this->commandIsNotSupported($command)) {
$this->error("command is not supported: {$command} {$arguments}");
}
$project_root = \GitPHP\Config::GetInstance()->getValue(\GitPHP\Config::PROJECT_ROOT);
// we force all repositories to be in the same directory
if (strpos($this->full_path, $project_root) === false) {
$this->full_path = $project_root . $this->full_path;
}
// and we also force all repositories to have .git suffix
// this is done on the project creation form
if (strpos($this->full_path, '.git') === false) {
$this->full_path .= '.git';
$this->repository .= '.git';
}
if (!file_exists($this->full_path)) {
// since we have "global" access modes for people
// we have to check if repository is in DB
if ($this->repositoryRegisteredInDatabase($this->repository)) {
// and here we can create repository
$this->initEmptyRepository($this->repository);
} else {
$this->error('repository can\'t be found');
}
}
$this->command = $command;
}
public function run()
{
$ModelGitosis = new \GitPHP\Model_Gitosis();
$global_mode = $this->getUserGlobalAccessMode($ModelGitosis, $this->user);
if ($global_mode === 'readonly' && $this->isWriteCommand($this->command)) {
// it's not enough to have readonly global mode for write commands
// so let's assume that we don't have global mode at all
$global_mode = false;
}
if (!$global_mode || $this->isRestrictedRepository($ModelGitosis, $this->repository)) {
$access = $ModelGitosis->getUserAccessToRepository($this->user, $this->repository);
} else {
$access = $global_mode;
}
if (!empty($access)) {
if ($this->isWriteCommand($this->command) && $access !== 'writable') {
$this->error('You don\' have write access to repo.');
}
if (!function_exists('pcntl_exec') && !extension_loaded('pcntl') && !dl('pcntl.so')) {
// we've seen some strange git behaviour without using pcntl_exec
// nevertheless I've saved this part for back-compatibility with old php config
trigger_error('cannot load pcntl extension');
$escaped_user = escapeshellarg($this->user);
$escaped_repo = escapeshellarg($this->repository);
$command = implode(' ', [
'GITOSIS_USER=' . $escaped_user,
'GITOSIS_REPO=' . $escaped_repo,
'git-shell -c "' . $this->command . ' ' . escapeshellarg($this->full_path) . '"'
]);
passthru(
$command
);
} else {
// we need this for hooks and back-compatibility with gitosis
putenv('GITOSIS_USER=' . $this->user);
putenv('GITOSIS_REPO=' . $this->repository);
pcntl_exec(
'/usr/bin/git-shell',
['-c', $this->command . ' ' . escapeshellarg($this->full_path)]
);
}
} else {
$this->error("You don't have rights to access the repo.");
}
}
protected function commandIsNotSupported($command)
{
return !($this->isWriteCommand($command) || $this->isReadCommand($command));
}
protected function isWriteCommand($command)
{
return in_array($command, self::COMMANDS_WRITE, true);
}
protected function isReadCommand($command)
{
return in_array($command, self::COMMANDS_READONLY, true);
}
protected function isRestrictedRepository(\GitPHP\Model_Gitosis $ModelGitosis, $repository)
{
$repository_info = $ModelGitosis->getRepositoryByProject($repository);
return $repository_info['restricted'] === 'Yes';
}
protected function getUserGlobalAccessMode(\GitPHP\Model_Gitosis $Gitosis, $username)
{
$user = $Gitosis->getUserByUsername($username);
if ($user['access_mode'] === \GitPHP\Controller\GitosisUsers::ACCESS_MODE_ALLOW_ALL) {
return 'writable';
}
if ($user['access_mode'] === \GitPHP\Controller\GitosisUsers::ACCESS_MODE_ALLOW_ALL_RO) {
return 'readonly';
}
return false;
}
protected function error($message)
{
fwrite(STDERR, '[ERROR]: ' . $message . PHP_EOL);
exit(1);
}
protected function repositoryRegisteredInDatabase($repository)
{
$Model = new \GitPHP\Model_Gitosis();
return !empty($Model->getRepositoryByProject($repository));
}
protected function initEmptyRepository($repository)
{
$root_directory = escapeshellarg(\GitPHP\Config::GetInstance()->GetValue(\GitPHP\Config::PROJECT_ROOT));
$repository = escapeshellarg($repository);
exec("git -C {$root_directory} init --bare {$repository}", $out, $retval);
if ($retval) {
$this->error("cannot create directory for repository");
}
}
}
$Application = new GitPHP\Application();
$Application->init();
$Serve = new SSH_Serve();
$Serve->init();
$Serve->run();