initial code layout
[gwvp-mini.git] / gwvpmini / gwvpmini_gitbackend.php
1 <?php
2
3 $CALL_ME_FUNCTIONS["gitcontrol"] = "gwvpmini_gitControlCallMe";
4
5 //$MENU_ITEMS["20repos"]["text"] = "Repo Admin";
6 //$MENU_ITEMS["20repos"]["link"] = "$BASE_URL/admin/repos";
7
8 // TODO: we could actually change backend interface such that is
9 // will respond to any url's that contain "repo.git" rather then
10 // having to be $BASE_URL/git/repo.git
11 function gwvpmini_gitControlCallMe()
12 {
13         if(isset($_REQUEST["q"])) {
14                 $query = $_REQUEST["q"];
15                 $qspl = explode("/", $query);
16                 if(isset($qspl[0])) {
17                         if($qspl[0] == "git") {
18                                 return "gwvpmini_gitBackendInterface";
19                         }
20                 } 
21                 else return false;
22         }
23         
24         return false;
25         
26 }
27
28
29 function gwvpmini_gitBackendInterface()
30 {
31         // and this is where i re-code the git backend interface from scratch
32         global $BASE_URL;
33         
34         $repo_base = gwvpmini_getConfigVal("repodir");
35         
36         // TODO: we need to stop passing the repo name around as "repo.git", it needs to be just "repo"
37         
38         
39         /* bizare git problem that ignores 403's or continues on with a push despite them 
40         error_log("FLAP for ".$_SERVER["REQUEST_URI"]);
41         if(isset($_REQUEST)) {
42                 $dump = print_r($_REQUEST, true);
43                 error_log("FLAP, $dump");
44         }
45         if(isset($_SERVER["PHP_AUTH_USER"])) {
46                 error_log("FLAP: donut hole");
47         }*/
48         
49
50         
51         $repo = "";
52         $repoid = false;
53         $newloc = "/";
54         if(isset($_REQUEST["q"])) {
55                 $query = $_REQUEST["q"];
56                 $qspl = explode("/", $query);
57                 // TODO do this with 
58                 $repo = preg_replace("/\.git$/", "", $qspl[1]);
59                 $repoid = gwvpmini_GetRepoId($repo);
60                 for($i=2; $i < count($qspl); $i++) {
61                         $newloc .= "/".$qspl[$i];
62                 }
63         }
64         
65         if($repoid == false) {
66                 gwvpmini_fourZeroFour();
67                 return;
68         }
69         
70         // we do an update server cause its weird and i cant figure out when it actually needs to happen
71         chdir("$repo_base/$repo.git");
72         exec("/usr/bin/git update-server-info");
73         
74         
75         // so now we have the repo
76         // next we determine if this is a read or a write
77         $write = false;
78         if(isset($_REQUEST["service"])) {
79                 if($_REQUEST["service"] == "git-receive-pack") {
80                         error_log("got write as receivepack in post");
81                         $write = true;
82                 }
83         }
84         if($_SERVER["REQUEST_METHOD"] == "POST") {
85                 $write = true;
86         }
87         // THIS MAY CAUSE ISSUES LATER ON but we do it cause the git client ignores our 403 when it uses git-receive-pack after an auth
88         // no, this isnt a solution cause auth'd read attempts will come up as writes...
89         //if(isset($_SERVER["PHP_AUTH_USER"])) {
90                 //$write = true;
91         //}
92         
93         $perms = 5;
94         
95         // if its a write, we push for authentication
96         if($write) {
97                 error_log("is write attempt, ask for login");
98                 $person = gwvpmini_checkBasicAuthLogin();
99                 if($person == false) {
100                         gwvpmini_AskForBasicAuth();
101                         return;
102                 } else {
103                         error_log("checking perms for $person against $repoid for repo $repo");
104                         // here we pass to the git backend
105                         error_log("perms are $perms and im allowed");
106                         gwvpmini_callGitBackend($person["username"], $repo);
107                 }
108                 return;
109         }
110         
111         
112         // if they're less then read, we need to then check the user auth permissions
113         if($perms < 2) {
114                 // we ask for auth
115                 $person = gwvpmini_checkBasicAuthLogin();
116                 if($person == false) {
117                         gwvpmini_AskForBasicAuth();
118                         return;
119                 } else {
120                 }
121         }
122         
123         // if we made it this far, we a read and we have permissions to do so, just search the file from the repo
124         if(file_exists("$repo_base/$repo.git/$newloc")) {
125                 error_log("would ask $repo for $repo.git/$newloc from $repo_base/$repo.git/$newloc");
126                 $fh = fopen("$repo_base/$repo.git/$newloc", "rb");
127                 
128                 error_log("pushing file");
129                 while(!feof($fh)) {
130                         echo fread($fh, 8192);
131                 }
132         } else {
133                 //echo "would ask $repo,$actual_repo_name for $repo/$newloc from $repo_base/$repo/$newloc, NE";
134                 gwvpmini_fourZeroFour();
135                 return;
136         }
137         
138 }
139
140
141 function gwvpmini_gitBackendInterface_old()
142 {
143         global $BASE_URL;
144         
145         $repo_base = gwvpmini_getConfigVal("repodir");
146         
147         $repo = "";
148         $newloc = "/";
149         if(isset($_REQUEST["q"])) {
150                 $query = $_REQUEST["q"];
151                 $qspl = explode("/", $query);
152                 $repo = $qspl[1];
153                 for($i=2; $i < count($qspl); $i++) {
154                         $newloc .= "/".$qspl[$i];
155                 }
156         }
157         
158         $actual_repo_name = preg_replace("/\.git$/", "", $repo); 
159         
160         $user = gwvpmini_checkBasicAuthLogin();
161
162         if(!$user) {
163                 error_log("User is set to false, so its anonymouse");
164         } else {
165                 error_log("user is $user");
166         }
167         
168         // must remember that $user of false is anonymous when we code gwvpmini_repoPerm'sCheck()
169         if(!gwvpmini_repoPermissionCheck($actual_repo_name, $user)) {
170                 error_log("perms check fails - start auth");
171                 if(isset($_SERVER["PHP_AUTH_USER"])) {
172                         error_log("have auth - push 403");
173                         gwvpmini_fourZeroThree();
174                 } else {
175                         error_log("push auth");
176                         gwvpmini_AskForBasicAuth();
177                         return;
178                 }
179         }
180         
181         // we need to quite a bit of parsing in here. The "repo" will always be /git/repo.git
182         // but if we get here from a browser, we need to forward back to a normal repo viewer
183         // the only way i can think of doing this is to check the useragent for the word "git"
184         
185         /*
186          * here we need to
187          * 1) figure out the repo its acessing
188          * 2) figure out the perms on the repo
189          * 3) determine if its a pull or a push
190          * - if its a pull, we just serve straight from the fs
191          * - if its a push, we go thru git-http-backend
192          * 4) if it requiers auth, we push to auth
193          * 
194          */
195         $agent = "git-unknown";
196         $isgitagent = false;
197         
198         // tested the user agent bit with jgit from eclipse and normal git... seems to work
199         if(isset($_SERVER["HTTP_USER_AGENT"])) {
200                 $agent = $_SERVER["HTTP_USER_AGENT"];
201                 error_log("in git backend with user agent $agent");
202                 if(stristr($agent, "git")!==false) {
203                         $isgitagent = true;
204                 }
205         }
206         
207         
208                 
209         /* dont need this code right now
210         if($isgitagent) echo "GIT: i am a git backened interface for a repo $repo, agent $agent";
211         else echo "NOT GIT: i am a git backened interface for a repo $repo, agent $agent";
212         */
213         
214         // now we need to rebuild the actual request or do we?
215         //$basegit = "$BASE_URL/git/something.git";
216         //$newloc = preg_replace("/^$basegit/", "", $_SERVER["REQUEST_URI"]);
217         chdir("$repo_base/$repo");
218         exec("/usr/bin/git update-server-info");
219         
220         if($_SERVER["REQUEST_METHOD"] == "POST") {
221                         gwvpmini_AskForBasicAuth();
222                         gwvpmini_callGitBackend($repo);
223                         return;
224         }
225         
226         if(isset($_REQUEST["service"])) {
227                 if($_REQUEST["service"] == "git-receive-pack") {
228                         // we are a write call - we need auth and we're going to the backend proper
229                         gwvpmini_AskForBasicAuth();
230                         gwvpmini_callGitBackend($repo);
231                         return;
232                 }
233         }
234         
235         
236         if(file_exists("$repo_base/$repo/$newloc")) {
237                 error_log("would ask $repo,$actual_repo_name for $repo/$newloc from $repo_base/$repo/$newloc");
238                 $fh = fopen("$repo_base/$repo/$newloc", "rb");
239                 
240                 error_log("pushing file");
241                 while(!feof($fh)) {
242                         echo fread($fh, 8192);
243                 }
244         } else {
245                 echo "would ask $repo,$actual_repo_name for $repo/$newloc from $repo_base/$repo/$newloc, NE";
246                 header('HTTP/1.0 404 No Such Thing');
247                 return;
248         }
249 }
250
251 function gwvpmini_canManageRepo($userid, $repoid)
252 {
253         // only the owner or an admin can do these tasks
254         error_log("Checking repoid, $repoid against userid $userid");
255         
256         if(gwvpmini_IsUserAdmin(null, null, $userid)) return true;
257         if(gwvpmini_IsRepoOwner($userid, $repoid)) return true;
258         return false;
259 }
260
261 function gwvpmini_callGitBackend($username, $repo)
262 {
263         // this is where things become a nightmare
264                 $fh   = fopen('php://input', "r");
265                 
266                 $repo_base = gwvpmini_getConfigVal("repodir");\r
267                 
268                 
269                 $ruri = $_SERVER["REQUEST_URI"];
270                 $strrem = "git/$repo.git";
271                 $euri = str_replace($strrem, "", $_REQUEST["q"]);
272                 //$euri = preg_replace("/^git\/$repo\.git/", "", $_REQUEST["q"]);
273                 
274                 
275                 
276                 $rmeth = $_SERVER["REQUEST_METHOD"];
277                 
278                 $qs = "";
279                 foreach($_REQUEST as $key => $var) {
280                         if($key != "q") {
281                                 //error_log("adding, $var from $key");
282                                 if($qs == "") $qs.="$key=$var";
283                                 else $qs.="&$key=$var";
284                         }
285                 }
286                 
287                 //sleep(2);
288                 
289                 
290                 
291                 // this is where the fun, it ends.
292                 $myoutput = "";
293                 unset($myoutput);
294                 
295                 // this be nasty!
296                 
297                 // setup env
298                 if(isset($procenv))     unset($procenv);
299                 $procenv["GATEWAY_INTERFACE"] = "CGI/1.1";
300                 $procenv["PATH_TRANSLATED"] = "/$repo_base/$repo.git/$euri";
301                 $procenv["REQUEST_METHOD"] = "$rmeth";
302                 $procenv["GIT_HTTP_EXPORT_ALL"] = "1";
303                 $procenv["QUERY_STRING"] = "$qs";
304                 $procenv["HTTP_USER_AGENT"] = "git/1.7.1";
305                 $procenv["REMOTE_USER"] = "$username";
306                 $procenv["REMOTE_ADDR"] = $_SERVER["REMOTE_ADDR"];
307                 $procenv["AUTH_TYPE"] = "Basic";
308                 
309                 if(isset($_SERVER["CONTENT_TYPE"])) { 
310                         $procenv["CONTENT_TYPE"] = $_SERVER["CONTENT_TYPE"];
311                 } else {
312                         //$procenv["CONTENT_TYPE"] = "";
313                 }
314                 if(isset($_SERVER["CONTENT_LENGTH"])) { 
315                         $procenv["CONTENT_LENGTH"] = $_SERVER["CONTENT_LENGTH"];
316                 }
317                 
318                 error_log("path trans'd is /$repo_base/$repo.git/$euri from $ruri with ".$_REQUEST["q"]." $strrem");
319                 
320                 
321                 
322
323                 $pwd = "/$repo_base/";
324                 
325                 $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);
326                 
327                 $untilblank = false;
328                 while(!$untilblank&&!feof($pipes[1])) {
329                         $lines_t = fgets($pipes[1]);
330                         $lines = trim($lines_t);
331                         error_log("got line: $lines");
332                         if($lines_t == "\r\n") {
333                                 $untilblank = true;
334                                 error_log("now blank");
335                         } else header($lines);
336                         if($lines === false) {
337                                 error_log("got an unexpexted exit...");
338                                 exit(0);
339                         }
340                         
341                 }
342                 
343
344                 $firstline = true;
345                 $continue = true;
346                 
347                 if(!stream_set_blocking($fh,0)) {
348                         error_log("cant set input non-blocking");
349                 }
350
351                 if(!stream_set_blocking($pipes[1],0)) {
352                         error_log("cant set pipe1 non-blocking");
353                 }
354                 
355                 // i was going to use stream_select, but i feel this works better like this
356                 while($continue) {
357                         // do client
358                         if(!feof($fh)) {
359                                 $from_client_data = fread($fh,8192);
360                                 if($from_client_data !== false) fwrite($pipes[0], $from_client_data);
361                                 fflush($pipes[0]);
362                                 //fwrite($fl, $from_client_data);
363                                 $client_len = strlen($from_client_data);
364                         } else {
365                                 error_log("client end");
366                                 $client_len = 0;
367                         }
368                         
369                         // do cgi
370                         // sometimes, we get a \r\n from the cgi, i do not know why she swallowed the fly,
371                         // but i do know that the fgets for the headers above should have comsued that
372                         if(!feof($pipes[1])) {
373                                 $from_cgi_data_t = fread($pipes[1],8192);
374                                 $from_cgi_data = $from_cgi_data_t;
375                                 
376                                 // i dont know if this will solve it... it coudl cause some serious issues elsewhere
377                                 // TODO: this is a hack, i need to know why the fgets above doesn consume the \r\n even tho it reads it
378                                 // i.e. why the pointer doesnt increment over it, cause the freads above then get them again.
379                                 if($firstline) {
380                                         if(strlen($from_cgi_data_t)>0) {
381                                                 // i dont get why this happens, and its very frustrating.. im not sure if its a bug in php
382                                                 // or something the git-http-backend thing is doing..
383                                                 // TODO: find out why this happens
384                                                 $from_cgi_data = preg_replace("/^\r\n/", "", $from_cgi_data_t);
385                                                 if(strlen($from_cgi_data)!=strlen($from_cgi_data_t)) {
386                                                         error_log("MOOOKS - we did trunc");
387                                                 } else {
388                                                         error_log("MOOOKS - we did not trunc");
389                                                 }
390                                                 $firstline = false;
391                                         }
392                                 }
393                                 
394                                 if($from_cgi_data !== false) {
395                                         echo $from_cgi_data;
396                                         flush();
397                                 }
398                                 $cgi_len = strlen($from_cgi_data);
399                         } else {
400                                 error_log("cgi end");
401                                 $cgi_len = 0;
402                         }
403                         
404                         if(feof($pipes[1])) $continue = false;
405                         else {
406                                 if($client_len == 0 && $cgi_len == 0) {
407                                         usleep(200000);
408                                         error_log("sleep tick");
409                                 } else {
410                                         error_log("sizes: $client_len, $cgi_len");
411                                         if($cgi_len > 0) {
412                                                 error_log("from cgi: \"$from_cgi_data\"");
413                                         }
414                                 }
415                         }
416                         
417                 }
418                 
419                 
420                 //fclose($fl);
421                 fclose($fh);
422                 fclose($pipes[1]);
423                 fclose($pipes[0]);      
424 }
425
426
427
428 function gwvpmini_repoExists($name)
429 {
430         $repo_base = gwvpmini_getConfigVal("repodir");
431         
432         if(file_exists("$repo_base/$name.git")) return true;
433         else return false;
434 }
435
436 // default perms:
437 // 0 - anyone can clone/read, only owner can write
438 // 1 - noone can clone/read, repo is visible (i.e. name), only owner can read/write repo
439 // 2 - only owner can see anything
440 function gwvpmini_createGitRepo($name, $ownerid, $desc)
441 {
442         $repo_base = gwvpmini_getConfigVal("repodir");
443         
444         // phew, this works, but i tell you this - bundles arent quite as nice as they should be
445         error_log("would create $repo_base/$name.git");
446         exec("/usr/bin/git init $repo_base/$name.git --bare > /tmp/gitlog 2>&1");
447         chdir("$repo_base/$name.git");
448         exec("/usr/bin/git update-server-info");
449
450         // gwvpmini_AddRepo($reponame, $repodesc, $repoowner, $defaultperms = 0)
451         gwvpmini_AddRepo($name, $desc, $ownerid);
452         
453         return true;
454 }
455
456
457 ?>