<?php
class
IDF_Plugin_SyncGit_Serve
{
public
$preg
=
'#^\'/*(?P<path>[a-zA-Z0-9][a-zA-Z0-9@._-]*(/[a-zA-Z0-9][a-zA-Z0-9@._-]*)*)\'$#'
;
public
$commands_readonly
=
array
(
'git-upload-pack'
,
'git upload-pack'
);
public
$commands_write
=
array
(
'git-receive-pack'
,
'git receive-pack'
);
/**
* Serve a git request.
*
* @param string Username.
* @param string Command to be run.
*/
public
function
serve(
$username
,
$cmd
)
{
if
(false !==
strpos
(
$cmd
,
"\n"
)) {
throw
new
Exception(
'Command may not contain newline.'
);
}
$splitted
= preg_split(
'/\s/'
,
$cmd
, 2);
if
(
count
(
$splitted
) != 2) {
throw
new
Exception(
'Unknown command denied.'
);
}
if
(
$splitted
[0] ==
'git'
) {
$sub_splitted
= preg_split(
'/\s/'
,
$splitted
[1], 2);
if
(
count
(
$sub_splitted
) != 2) {
throw
new
Exception(
'Unknown command denied.'
);
}
$verb
= sprintf(
'%s %s'
,
$splitted
[0],
$sub_splitted
[0]);
$args
=
$sub_splitted
[1];
}
else
{
$verb
=
$splitted
[0];
$args
=
$splitted
[1];
}
if
(!in_array(
$verb
,
$this
->commands_write)
and
!in_array(
$verb
,
$this
->commands_readonly)) {
throw
new
Exception(
'Unknown command denied.'
);
}
if
(!preg_match(
$this
->preg,
$args
,
$matches
)) {
throw
new
Exception(
'Arguments to command look dangerous.'
);
}
$path
=
$matches
[
'path'
];
$new_path
=
$this
->haveAccess(
$username
,
$path
,
'writable'
);
if
(
$new_path
== false) {
$new_path
=
$this
->haveAccess(
$username
,
$path
,
'readonly'
);
if
(
$new_path
== false) {
throw
new
Exception(
'Repository read access denied.'
);
}
if
(in_array(
$verb
,
$this
->commands_write)) {
throw
new
Exception(
'Repository write access denied.'
);
}
}
list(
$topdir
,
$relpath
) =
$new_path
;
$repopath
= sprintf(
'%s.git'
,
$relpath
);
$fullpath
=
$topdir
.DIRECTORY_SEPARATOR.
$repopath
;
if
(!
file_exists
(
$fullpath
)
and
in_array(
$verb
,
$this
->commands_write)) {
$p
=
explode
(DIRECTORY_SEPARATOR,
$fullpath
);
$mpath
= implode(DIRECTORY_SEPARATOR,
array_slice
(
$p
, 0, -1));
if
(!
file_exists
(
$mpath
)) {
mkdir
(
$mpath
, 0750, true);
}
$this
->initRepository(
$fullpath
);
$this
->setGitExport(
$relpath
,
$fullpath
);
}
$new_cmd
= sprintf(
"%s '%s'"
,
$verb
,
$fullpath
);
Pluf_Log::info(
array
(
'IDF_Plugin_Git_Serve::serve'
,
$username
,
$cmd
,
$new_cmd
));
return
$new_cmd
;
}
public
static
function
main(
$argv
,
$env
)
{
if
(
count
(
$argv
) != 2) {
self::fatalError(
'Missing argument USER.'
);
}
$username
=
$argv
[1];
umask(0022);
if
(!isset(
$env
[
'SSH_ORIGINAL_COMMAND'
])) {
self::fatalError(
'Need SSH_ORIGINAL_COMMAND in environment.'
);
}
$cmd
=
$env
[
'SSH_ORIGINAL_COMMAND'
];
chdir
(Pluf::f(
'idf_plugin_syncgit_git_home_dir'
,
'/home/git'
));
$serve
=
new
IDF_Plugin_SyncGit_Serve();
try
{
$new_cmd
=
$serve
->serve(
$username
,
$cmd
);
}
catch
(Exception
$e
) {
self::fatalError(
$e
->getMessage());
}
print
$new_cmd
;
exit
(0);
}
public
function
haveAccess(
$username
,
$path
,
$mode
=
'writable'
)
{
if
(
'.git'
==
substr
(
$path
, -4)) {
$path
=
substr
(
$path
, 0, -4);
}
$sql
=
new
Pluf_SQL(
'shortname=%s'
,
array
(
$path
));
$projects
= Pluf::factory(
'IDF_Project'
)->getList(
array
(
'filter'
=>
$sql
->gen()));
if
(
$projects
->
count
() != 1) {
return
false;
}
$project
=
$projects
[0];
$conf
=
new
IDF_Conf();
$conf
->setProject(
$project
);
$scm
=
$conf
->getVal(
'scm'
,
'git'
);
if
(
$scm
!=
'git'
) {
return
false;
}
$sql
=
new
Pluf_SQL(
'login=%s'
,
array
(
$username
));
$users
= Pluf::factory(
'Pluf_User'
)->getList(
array
(
'filter'
=>
$sql
->gen()));
if
(
$users
->
count
() != 1
or
!
$users
[0]->active) {
return
false;
}
$user
=
$users
[0];
$request
=
new
StdClass();
$request
->user =
$user
;
$request
->conf =
$conf
;
$request
->project =
$project
;
if
(true === IDF_Precondition::accessSource(
$request
)) {
if
(
$mode
==
'readonly'
) {
return
array
(Pluf::f(
'idf_plugin_syncgit_base_repositories'
,
'/home/git/repositories'
),
$project
->shortname);
}
if
(true === IDF_Precondition::projectMemberOrOwner(
$request
)) {
return
array
(Pluf::f(
'idf_plugin_syncgit_base_repositories'
,
'/home/git/repositories'
),
$project
->shortname);
}
}
return
false;
}
public
static
function
fatalError(
$mess
)
{
fwrite(STDERR,
$mess
.
"\n"
);
exit
(1);
}
public
function
initRepository(
$fullpath
)
{
if
(!
file_exists
(
$fullpath
)) {
mkdir
(
$fullpath
, 0750, true);
}
$out
=
array
();
$res
= 0;
exec
(sprintf(Pluf::f(
'idf_exec_cmd_prefix'
,
''
).
Pluf::f(
'git_path'
,
'git'
).
' --git-dir=%s init'
,
escapeshellarg
(
$fullpath
)),
$out
,
$res
);
if
(
$res
!= 0) {
Pluf_Log::error(
array
(
'IDF_Plugin_Git_Serve::initRepository'
,
$res
,
$fullpath
));
throw
new
Exception(sprintf(
'Init repository error, exit status %d.'
,
$res
));
}
Pluf_Log::event(
array
(
'IDF_Plugin_Git_Serve::initRepository'
,
'success'
,
$fullpath
));
$p
=
realpath
(dirname(
__FILE__
).
'/../../../../scripts/git-post-update'
);
$p
= Pluf::f(
'idf_plugin_syncgit_post_update'
,
$p
);
if
(!@unlink(
$fullpath
.
'/hooks/post-update'
)) {
Pluf_Log::warn(
array
(
'IDF_Plugin_Git_Serve::initRepository'
,
'post-update hook removal error.'
,
$fullpath
.
'/hooks/post-update'
));
return
;
}
$out
=
array
();
$res
= 0;
exec
(sprintf(Pluf::f(
'idf_exec_cmd_prefix'
,
''
).
'ln -s %s %s'
,
escapeshellarg
(
$p
),
escapeshellarg
(
$fullpath
.
'/hooks/post-update'
)),
$out
,
$res
);
if
(
$res
!= 0) {
Pluf_Log::warn(
array
(
'IDF_Plugin_Git_Serve::initRepository'
,
'post-update hook creation error.'
,
$fullpath
.
'/hooks/post-update'
));
return
;
}
Pluf_Log::debug(
array
(
'IDF_Plugin_Git_Serve::initRepository'
,
'Added post-update hook.'
,
$fullpath
));
$quotepath
= (Pluf::f(
'git_core_quotepath'
, true) == true) ?
'true'
:
'false'
;
$out
=
array
();
$res
= 0;
exec
(sprintf(Pluf::f(
'idf_exec_cmd_prefix'
,
''
).
Pluf::f(
'git_path'
,
'git'
).
' config -f %s/config --add core.quotepath %s'
,
escapeshellarg
(
$fullpath
),
escapeshellarg
(
$quotepath
)
),
$out
,
$res
);
if
(
$res
!= 0) {
Pluf_Log::warn(
array
(
'IDF_Plugin_Git_Serve::initRepository'
,
'core.quotepath configuration error.'
,
$quotepath
));
return
;
}
Pluf_Log::debug(
array
(
'IDF_Plugin_Git_Serve::initRepository'
,
'core.quotepath configured.'
,
$quotepath
));
}
public
function
setGitExport(
$relpath
,
$fullpath
)
{
$sql
=
new
Pluf_SQL(
'shortname=%s'
,
array
(
$relpath
));
$projects
= Pluf::factory(
'IDF_Project'
)->getList(
array
(
'filter'
=>
$sql
->gen()));
if
(
$projects
->
count
() != 1
and
file_exists
(
$fullpath
)) {
return
$this
->gitExportDeny(
$fullpath
);
}
$project
=
$projects
[0];
$conf
=
new
IDF_Conf();
$conf
->setProject(
$project
);
$scm
=
$conf
->getVal(
'scm'
,
'git'
);
if
(
$scm
==
'git'
and
!
file_exists
(
$fullpath
)) {
return
false;
}
if
(
$scm
!=
'git'
or
$project
->
private
) {
return
$this
->gitExportDeny(
$fullpath
);
}
if
(
'all'
==
$conf
->getVal(
'source_access_rights'
,
'all'
)) {
return
$this
->gitExportAllow(
$fullpath
);
}
return
$this
->gitExportDeny(
$fullpath
);
}
public
function
gitExportDeny(
$fullpath
)
{
if
(!
file_exists
(
$fullpath
)) {
return
;
}
@unlink(
$fullpath
.DIRECTORY_SEPARATOR.
'git-daemon-export-ok'
);
if
(
file_exists
(
$fullpath
.DIRECTORY_SEPARATOR.
'git-daemon-export-ok'
)) {
throw
new
Exception(
'Cannot remove git-daemon-export-ok file.'
);
}
return
true;
}
public
function
gitExportAllow(
$fullpath
)
{
if
(!
file_exists
(
$fullpath
)) {
return
;
}
touch(
$fullpath
.DIRECTORY_SEPARATOR.
'git-daemon-export-ok'
);
if
(!
file_exists
(
$fullpath
.DIRECTORY_SEPARATOR.
'git-daemon-export-ok'
)) {
throw
new
Exception(
'Cannot create git-daemon-export-ok file.'
);
}
return
true;
}
}