a59a02b9605efa8e237863083551ef8cd5b38b2f
[gwvp.git] / gwvplib / gwvpgitcontrol.php
1 <?php
2
3 $CALL_ME_FUNCTIONS["gitcontrol"] = "gwvp_gitControlCallMe";
4
5 //$MENU_ITEMS["20repos"]["text"] = "Repo Admin";
6 //$MENU_ITEMS["20repos"]["link"] = "$BASE_URL/admin/repos";
7 $HOME_PAGE_PROVIDERS["gitlog"] = "gwvp_GitLogProvider";
8
9 function gwvp_gitControlCallMe()
10 {
11         if(isset($_REQUEST["q"])) {
12                 $query = $_REQUEST["q"];
13                 $qspl = explode("/", $query);
14                 if(isset($qspl[0])) {
15                         if($qspl[0] == "git") {
16                                 return "gwvp_gitBackendInterface";
17                         }
18                 } 
19                 else return false;
20         }
21         
22         return false;
23         
24 }
25
26 function gwvp_GitLogProvider()
27 {
28         echo "<br>gitload provider loaded on homepage<br>";
29 }
30
31 function gwvp_repoPermissionCheck($repo, $user)
32 {
33         return true;
34 }
35
36 function gwvp_gitBackendInterface()
37 {
38         // and this is where i re-code the git backend interface from scratch
39         global $BASE_URL;
40         
41         $repo_base = gwvp_getConfigVal("repodir");
42         
43         // TODO: we need to stop passing the repo name around as "repo.git", it needs to be just "repo"
44         
45         $repo = "";
46         $repoid = -1;
47         $newloc = "/";
48         if(isset($_REQUEST["q"])) {
49                 $query = $_REQUEST["q"];
50                 $qspl = explode("/", $query);
51                 $repo = $qspl[1];
52                 $repoid = gwvp_resolvRepoPerms($repo);
53                 for($i=2; $i < count($qspl); $i++) {
54                         $newloc .= "/".$qspl[$i];
55                 }
56         }
57         
58         if($repoid == -1) {
59                 gwvp_fourZeroFour();
60                 return;
61         }
62         
63         // so now we have the repo
64         // next we determine if this is a read or a write
65         $write = false;
66         if(isset($_REQUEST["service"])) {
67                 if($_REQUEST["service"] == "git-receive-pack") {
68                         $write = true;
69                 }
70         }
71         if($_SERVER["REQUEST_METHOD"] == "POST") {
72                 $write = true;
73         }
74         
75         // if its a write, we push for authentication
76         if($write) {
77                 $person = gwvp_checkBasicAuthLogin();
78                 if($person == false) {
79                         gwvp_AskForBasicAuth();
80                         return;
81                 } else {
82                         $perms = gwvp_resolvRepoPerms($person["id"], $repoid);
83                         if($perms < 3) {
84                                 gwvp_fourZeroThree();
85                                 return;
86                         } else {
87                                 // here we pass to the git backend
88                                 gwvp_callGitBackend($person["username"], $repo);
89                         }
90                 }
91                 return;
92         }
93         
94         // if not we figure out the anon permissions for a repo
95         $perms = gwvp_resolvRepoPerms(-1, $repoid);
96         
97         // if they're less then read, we need to then check the user auth permissions
98         if($perms < 2) {
99                 // we ask for auth
100                 $person = gwvp_checkBasicAuthLogin();
101                 if($person == false) {
102                         gwvp_AskForBasicAuth();
103                         return;
104                 } else {
105                         $perms = gwvp_resolvRepoPerms($person["id"], $repoid);
106                         if($perms < 3) {
107                                 gwvp_fourZeroThree();
108                                 return;
109                         }
110                 }
111         }
112         
113         // if we made it this far, we a read and we have permissions to do so, just search the file from the repo
114         if(file_exists("$repo_base/$repo/$newloc")) {
115                 error_log("would ask $repo,$actual_repo_name for $repo/$newloc from $repo_base/$repo/$newloc");
116                 $fh = fopen("$repo_base/$repo/$newloc", "rb");
117                 
118                 error_log("pushing file");
119                 while(!feof($fh)) {
120                         echo fread($fh, 8192);
121                 }
122         } else {
123                 //echo "would ask $repo,$actual_repo_name for $repo/$newloc from $repo_base/$repo/$newloc, NE";
124                 gwvp_fourZeroFour();
125                 return;
126         }
127         
128 }
129
130
131 function gwvp_gitBackendInterface_old()
132 {
133         global $BASE_URL;
134         
135         $repo_base = gwvp_getConfigVal("repodir");
136         
137         $repo = "";
138         $newloc = "/";
139         if(isset($_REQUEST["q"])) {
140                 $query = $_REQUEST["q"];
141                 $qspl = explode("/", $query);
142                 $repo = $qspl[1];
143                 for($i=2; $i < count($qspl); $i++) {
144                         $newloc .= "/".$qspl[$i];
145                 }
146         }
147         
148         $actual_repo_name = preg_replace("/\.git$/", "", $repo); 
149         
150         $user = gwvp_checkBasicAuthLogin();
151
152         if(!$user) {
153                 error_log("User is set to false, so its anonymouse");
154         } else {
155                 error_log("user is $user");
156         }
157         
158         // must remember that $user of false is anonymous when we code gwvp_repoPerm'sCheck()
159         if(!gwvp_repoPermissionCheck($actual_repo_name, $user)) {
160                 error_log("perms check fails - start auth");
161                 if(isset($_SERVER["PHP_AUTH_USER"])) {
162                         error_log("have auth - push 403");
163                         gwvp_fourZeroThree();
164                 } else {
165                         error_log("push auth");
166                         gwvp_AskForBasicAuth();
167                         return;
168                 }
169         }
170         
171         // we need to quite a bit of parsing in here. The "repo" will always be /git/repo.git
172         // but if we get here from a browser, we need to forward back to a normal repo viewer
173         // the only way i can think of doing this is to check the useragent for the word "git"
174         
175         /*
176          * here we need to
177          * 1) figure out the repo its acessing
178          * 2) figure out the perms on the repo
179          * 3) determine if its a pull or a push
180          * - if its a pull, we just serve straight from the fs
181          * - if its a push, we go thru git-http-backend
182          * 4) if it requiers auth, we push to auth
183          * 
184          */
185         $agent = "git-unknown";
186         $isgitagent = false;
187         
188         // tested the user agent bit with jgit from eclipse and normal git... seems to work
189         if(isset($_SERVER["HTTP_USER_AGENT"])) {
190                 $agent = $_SERVER["HTTP_USER_AGENT"];
191                 error_log("in git backend with user agent $agent");
192                 if(stristr($agent, "git")!==false) {
193                         $isgitagent = true;
194                 }
195         }
196         
197         
198                 
199         /* dont need this code right now
200         if($isgitagent) echo "GIT: i am a git backened interface for a repo $repo, agent $agent";
201         else echo "NOT GIT: i am a git backened interface for a repo $repo, agent $agent";
202         */
203         
204         // now we need to rebuild the actual request or do we?
205         //$basegit = "$BASE_URL/git/something.git";
206         //$newloc = preg_replace("/^$basegit/", "", $_SERVER["REQUEST_URI"]);
207         chdir("$repo_base/$repo");
208         exec("/usr/bin/git update-server-info");
209         
210         if($_SERVER["REQUEST_METHOD"] == "POST") {
211                         gwvp_AskForBasicAuth();
212                         gwvp_callGitBackend($repo);
213                         return;
214         }
215         
216         if(isset($_REQUEST["service"])) {
217                 if($_REQUEST["service"] == "git-receive-pack") {
218                         // we are a write call - we need auth and we're going to the backend proper
219                         gwvp_AskForBasicAuth();
220                         gwvp_callGitBackend($repo);
221                         return;
222                 }
223         }
224         
225         
226         if(file_exists("$repo_base/$repo/$newloc")) {
227                 error_log("would ask $repo,$actual_repo_name for $repo/$newloc from $repo_base/$repo/$newloc");
228                 $fh = fopen("$repo_base/$repo/$newloc", "rb");
229                 
230                 error_log("pushing file");
231                 while(!feof($fh)) {
232                         echo fread($fh, 8192);
233                 }
234         } else {
235                 echo "would ask $repo,$actual_repo_name for $repo/$newloc from $repo_base/$repo/$newloc, NE";
236                 header('HTTP/1.0 404 No Such Thing');
237                 return;
238         }
239 }
240
241 function gwvp_canManageRepo($userid, $repoid)
242 {
243         // only the owner or an admin can do these tasks
244         error_log("Checking repoid, $repoid against userid $userid");
245         
246         if(gwvp_IsUserAdmin(null, null, $userid)) return true;
247         if(gwvp_IsRepoOwner($userid, $repoid)) return true;
248         return false;
249 }
250
251 function gwvp_callGitBackend($username, $reponame)
252 {
253         // this is where things become a nightmare
254                 $fh   = fopen('php://input', "r");
255                 
256                 $ruri = $_SERVER["REQUEST_URI"];
257                 $strrem = "git/$repo";
258                 $euri = str_replace($strrem, "", $_REQUEST["q"]);
259                 //$euri = preg_replace("/^git\/$repo\.git/", "", $_REQUEST["q"]);
260                 
261                 
262                 
263                 $rmeth = $_SERVER["REQUEST_METHOD"];
264                 
265                 $qs = "";
266                 foreach($_REQUEST as $key => $var) {
267                         if($key != "q") {
268                                 //error_log("adding, $var from $key");
269                                 if($qs == "") $qs.="$key=$var";
270                                 else $qs.="&$key=$var";
271                         }
272                 }
273                 
274                 //sleep(2);
275                 
276                 
277                 
278                 // this is where the fun, it ends.
279                 $myoutput = "";
280                 unset($myoutput);
281                 
282                 // this be nasty!
283                 
284                 // setup env
285                 if(isset($procenv))     unset($procenv);
286                 $procenv["GATEWAY_INTERFACE"] = "CGI/1.1";
287                 $procenv["PATH_TRANSLATED"] = "/tmp/$repo/$euri";
288                 $procenv["REQUEST_METHOD"] = "$rmeth";
289                 $procenv["GIT_HTTP_EXPORT_ALL"] = "1";
290                 $procenv["QUERY_STRING"] = "$qs";
291                 $procenv["HTTP_USER_AGENT"] = "git/1.7.1";
292                 $procenv["REMOTE_USER"] = "$username";
293                 $procenv["REMOTE_ADDR"] = "1.2.3.4";
294                 $procenv["AUTH_TYPE"] = "Basic";
295                 
296                 if(isset($_SERVER["CONTENT_TYPE"])) { 
297                         $procenv["CONTENT_TYPE"] = $_SERVER["CONTENT_TYPE"];
298                 } else {
299                         //$procenv["CONTENT_TYPE"] = "";
300                 }
301                 if(isset($_SERVER["CONTENT_LENGTH"])) { 
302                         $procenv["CONTENT_LENGTH"] = $_SERVER["CONTENT_LENGTH"];
303                 }
304                 
305                 error_log("path trans'd is /tmp/$repo/$euri from $ruri with ".$_REQUEST["q"]." $strrem");
306                 
307                 
308                 
309
310                 $pwd = "/tmp/";
311                 
312                 $proc = proc_open("/usr/lib/git-core/git-http-backend", array(array("pipe","rb"),array("pipe","wb"),array("file","/tmp/err", "a")), $pipes, $pwd, $procenv);
313                 
314                 $untilblank = false;
315                 while(!$untilblank&&!feof($pipes[1])) {
316                         $lines_t = fgets($pipes[1]);
317                         $lines = trim($lines_t);
318                         error_log("got line: $lines");
319                         if($lines_t == "\r\n") {
320                                 $untilblank = true;
321                                 error_log("now blank");
322                         } else header($lines);
323                         if($lines === false) {
324                                 error_log("got an unexpexted exit...");
325                                 exit(0);
326                         }
327                         
328                 }
329                 
330
331                 $firstline = true;
332                 $continue = true;
333                 
334                 if(!stream_set_blocking($fh,0)) {
335                         error_log("cant set input non-blocking");
336                 }
337
338                 if(!stream_set_blocking($pipes[1],0)) {
339                         error_log("cant set pipe1 non-blocking");
340                 }
341                 
342                 // i was going to use stream_select, but i feel this works better like this
343                 while($continue) {
344                         // do client
345                         if(!feof($fh)) {
346                                 $from_client_data = fread($fh,8192);
347                                 if($from_client_data !== false) fwrite($pipes[0], $from_client_data);
348                                 fflush($pipes[0]);
349                                 //fwrite($fl, $from_client_data);
350                                 $client_len = strlen($from_client_data);
351                         } else {
352                                 error_log("client end");
353                                 $client_len = 0;
354                         }
355                         
356                         // do cgi
357                         // sometimes, we get a \r\n from the cgi, i do not know why she swallowed the fly,
358                         // but i do know that the fgets for the headers above should have comsued that
359                         if(!feof($pipes[1])) {
360                                 $from_cgi_data_t = fread($pipes[1],8192);
361                                 $from_cgi_data = $from_cgi_data_t;
362                                 
363                                 // i dont know if this will solve it... it coudl cause some serious issues elsewhere
364                                 // TODO: this is a hack, i need to know why the fgets above doesn consume the \r\n even tho it reads it
365                                 // i.e. why the pointer doesnt increment over it, cause the freads above then get them again.
366                                 if($firstline) {
367                                         if(strlen($from_cgi_data_t)>0) {
368                                                 // i dont get why this happens, and its very frustrating.. im not sure if its a bug in php
369                                                 // or something the git-http-backend thing is doing..
370                                                 // TODO: find out why this happens
371                                                 $from_cgi_data = preg_replace("/^\r\n/", "", $from_cgi_data_t);
372                                                 if(strlen($from_cgi_data)!=strlen($from_cgi_data_t)) {
373                                                         error_log("MOOOKS - we did trunc");
374                                                 } else {
375                                                         error_log("MOOOKS - we did not trunc");
376                                                 }
377                                                 $firstline = false;
378                                         }
379                                 }
380                                 
381                                 if($from_cgi_data !== false) {
382                                         echo $from_cgi_data;
383                                         flush();
384                                 }
385                                 $cgi_len = strlen($from_cgi_data);
386                         } else {
387                                 error_log("cgi end");
388                                 $cgi_len = 0;
389                         }
390                         
391                         if(feof($pipes[1])) $continue = false;
392                         else {
393                                 if($client_len == 0 && $cgi_len == 0) {
394                                         usleep(200000);
395                                         error_log("sleep tick");
396                                 } else {
397                                         error_log("sizes: $client_len, $cgi_len");
398                                         if($cgi_len > 0) {
399                                                 error_log("from cgi: \"$from_cgi_data\"");
400                                         }
401                                 }
402                         }
403                         
404                 }
405                 
406                 
407                 //fclose($fl);
408                 fclose($fh);
409                 fclose($pipes[1]);
410                 fclose($pipes[0]);      
411 }
412
413
414
415 function gwvp_repoExists($name)
416 {
417         $repo_base = gwvp_getConfigVal("repodir");
418         
419         if(file_exists("$repo_base/$name.git")) return true;
420         else return false;
421 }
422
423 // default perms:
424 // 0 - anyone can clone/read, only owner can write
425 // 1 - noone can clone/read, repo is visible (i.e. name), only owner can read/write repo
426 // 2 - only owner can see anything
427 function gwvp_createGitRepo($name, $ownerid, $desc, $bundle=null, $defaultperms=0)
428 {
429         $repo_base = gwvp_getConfigVal("repodir");
430         
431         // phew, this works, but i tell you this - bundles arent quite as nice as they should be
432         if($bundle == null) {
433                 error_log("would create $repo_base/$name.git");
434                 exec("/usr/bin/git init $repo_base/$name.git --bare > /tmp/gitlog 2>&1");
435                 chdir("$repo_base/$name.git");
436                 exec("/usr/bin/git update-server-info");
437         } else {
438                 error_log("create via mirror on $repo_base/$name.git");
439                 exec("/usr/bin/git clone --mirror $bundle $repo_base/$name.git > /tmp/gitlog 2>&1");
440                 chdir("$repo_base/$name.git");
441                 exec("/usr/bin/git update-server-info");
442         }
443
444         // gwvp_AddRepo($reponame, $repodesc, $repoowner, $defaultperms = 0)
445         gwvp_AddRepo($name, $desc, $ownerid, $defaultperms);
446         
447         return true;
448 }
449
450 // this funciton returns one of three things, read, visible, write, none
451 // as
452 // 0 - none
453 // 1 - visible
454 // 2 - read
455 // 3 - write
456 function gwvp_resolvRepoPerms($userid, $repoid)
457 {
458         $ownerid = gwvp_getRepoOwner($repoid);
459         $isadmin = gwvp_IsUserAdmin(null, null, $userid);
460         
461         if($isadmin) return 3;
462         
463         if($userid == $ownerid) return 3;
464         
465         // now we load the perms table and pray
466         $repoperms = gwvp_getRepoPermissions($repoid);
467         $usergroups = gwvp_getGroupsForUser(null, $userid);
468
469         $maxperm = 0;
470         if($repoperms != false) foreach($repoperms as $perm) {
471                 // need to go thru each perm, then check it agains the user we're trying to figure
472                 // the perms on
473                 switch($perm["type"]) {
474                         case "read":
475                                 $permval = 2;
476                                 break;
477                         case "visible":
478                                 $permval = 1;
479                                 break;
480                         case "write":
481                                 $permval = 3;
482                                 break;
483                         default:
484                                 $permval = 0;
485                 }
486                 
487                 // we only var if permval is greater then current
488                 if($permval > $maxperm) {
489                         //error_log("going into check for $maxperm/$permval, ".$perm["ref"]);
490                         if($perm["ref"] == "anon") {
491                                 $maxperm = $permval;
492                         } else if($perm["ref"] == "authed") {
493                                 $maxperm = $permval;
494                         } else {
495                                 // now we do splits
496                                 $spl = explode(":", $perm["ref"]);
497                                 $idtype = $spl[0];
498                                 $idval = $spl[1];
499                                 if($idtype == "group") {
500                                         // function gwvp_IsGroupMember($email, $groupname)
501                                         if(gwvp_IsGroupMemberById($userid, $idval)) $maxperm = $permval;
502                                 } else if ($idtype == "user") {
503                                         //error_log("checking $userid, $idval");
504                                         if($userid == $idval) $maxperm = $permval;
505                                 }
506                         }
507                 }
508         }
509         
510         // thats TOTALLY going to work... -_0 we should really write a unit test for this, but thats a bit
511         // hard given the db req's so for now, we'll leave it as is
512         return $maxperm;
513 }
514
515 ?>