1 /*
2 ** Copyright (c) 2002 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the GNU General Public
6 ** License as published by the Free Software Foundation; either
7 ** version 2 of the License, or (at your option) any later version.
8 **
9 ** This program is distributed in the hope that it will be useful,
10 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 ** General Public License for more details.
13 **
14 ** You should have received a copy of the GNU General Public
15 ** License along with this library; if not, write to the
16 ** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 ** Boston, MA 02111-1307, USA.
18 **
19 ** Author contact information:
20 ** drh@hwaci.com
21 ** http://www.hwaci.com/drh/
22 **
23 *******************************************************************************
24 **
25 ** The main routine
26 */
27 #include "config.h"
28 #include "main.h"
29 #include <time.h>
30 #include <pwd.h>
31 #include <sys/types.h>
32
33 #if SQLITE_VERSION_NUMBER < 3001000
34 # error "Requires SQLite 3.1 or greater"
35 #endif
36
37 #if CVSTRAC_I18N
38 #include <langinfo.h>
39 #endif
40
41 #if INTERFACE
42
43 /*
44 ** Maximum number of distinct aux() values
45 */
46 #define MX_AUX 10
47
48 struct Scm {
49 const char *zSCM; /* Which SCM subsystem is supported (i.e. "cvs") */
50 const char *zName; /* User-readable SCM name (i.e. "Subversion") */
51 int canFilterModules; /* non-zero if the SCM can filter modules */
52
53 int (*pxHistoryUpdate)(int isReread);
54 int (*pxDiffVersions)(const char *zOldVersion, const char *zNewVersion,
55 const char *zFile);
56 int (*pxDiffChng)(int cn, int bRaw);
57 int (*pxIsFileAvailable)(const char *zFile);
58 int (*pxDumpVersion)(const char *zVers, const char *zFile, int bRaw);
59 int (*pxUserRead)();
60 int (*pxUserWrite)(const char *zOmit);
61 };
62
63 /*
64 ** All global variables are in this structure.
65 */
66 struct Global {
67 int argc; char **argv; /* Command-line arguments to the program */
68 struct Scm scm; /* SCM-specific variables, callbacks, etc */
69 const char *zName; /* Base name of the program */
70 const char *zUser; /* Name of the user */
71 const char *zHumanName; /* Human readable name of the user */
72 char *zBaseURL; /* Absolute base URL for any CVSTrac page */
73 char *zLinkURL; /* URL prefixed to all output URLs */
74 char *zPath; /* The URL for the current page */
75 char *zExtra; /* Additional path information following g.zPath */
76 int okCheckout; /* True if the user has CVS checkout permission */
77 int okCheckin; /* True if the user has CVS checkin permission */
78 int okNewTkt; /* True if the user can create new tickets */
79 int okRead; /* True if the user may view tickets */
80 int okPassword; /* True if the user may change his password */
81 int okWrite; /* True if the user can edit tickets */
82 int okAdmin; /* True if the user has administrative permission */
83 int okSetup; /* True if the user has setup permission */
84 int okRdWiki; /* True if the user can read wiki pages */
85 int okWiki; /* True if the user can write wiki pages */
86 int okDelete; /* True if able to delete wiki or tickets */
87 int okQuery; /* True if able to create new reports */
88 int isAnon; /* Anonymous user (not logged in) */
89 int isConst; /* True if the page is constant and cacheable. */
90 int okTicketLink; /* True for ticket info link titles */
91 int okCheckinLink; /* True for chng info link titles */
92 int noFollow; /* Output links with rel="nofollow" */
93 int useUTF8; /* CVSTrac running in UTF-8 locale */
94
95 /* Storage for the aux() and/or option() SQL function arguments */
96 int nAux; /* Number of distinct aux() or option() values */
97 const char *azAuxName[MX_AUX]; /* Name of each aux() or option() value */
98 char *azAuxParam[MX_AUX]; /* Param of each aux() or option() value */
99 const char *azAuxVal[MX_AUX]; /* Value of each aux() or option() value */
100 const char **azAuxOpt[MX_AUX]; /* Options of each option() value */
101 int anAuxCols[MX_AUX]; /* Number of columns for option() values */
102 };
103 #endif
104
105 Global g;
106
107 /*
108 ** The table of web pages supported by this application is generated
109 ** automatically by the "mkindex" program and written into a file
110 ** named "page_index.h". We include that file here to get access
111 ** to the table.
112 */
113 #include "page_index.h"
114
115 /*
116 ** Search for a match against the given pathname. Return TRUE on
117 ** success and FALSE if not found.
118 */
find_path(const char * zPath,void (** pxFunc)(void))119 static int find_path(
120 const char *zPath, /* The pathname we are looking for */
121 void (**pxFunc)(void) /* Write pointer to handler function here */
122 ){
123 int upr, lwr;
124 lwr = 0;
125 upr = sizeof(aSearch)/sizeof(aSearch[0])-1;
126 while( lwr<=upr ){
127 int mid, c;
128 mid = (upr+lwr)/2;
129 c = strcmp(zPath, aSearch[mid].zPath);
130 if( c==0 ){
131 *pxFunc = aSearch[mid].xFunc;
132 return 1;
133 }else if( c<0 ){
134 upr = mid - 1;
135 }else{
136 lwr = mid + 1;
137 }
138 }
139 return 0;
140 }
141
142 /*
143 ** Print a usage message and die
144 */
usage(const char * argv0)145 static void usage(const char *argv0){
146 fprintf(stderr,
147 "Usage: %s <command> ?<directory>? ?<project>?\n"
148 " Or: %s chroot <root> <user> <command> ?<directory>? ?<project>?\n"
149 " Or: %s server <port> <directory> ?<project>?\n"
150 " Or: %s chroot <root> <user> server <port> <directory> ?<project>?\n"
151 "Where:\n"
152 " <command> is one of \"cgi\", \"http\", \"init\", \"wikiinit\""
153 " or \"update\".\n"
154 " <directory> is the directory that contains the project database.\n"
155 " <project> is the name of the project.\n"
156 " <port> is a TCP port number to listen on.\n"
157 " <root> is a chroot jail directory.\n"
158 " <user> is the user to run as.\n",
159 argv0, argv0, argv0, argv0);
160 exit(1);
161 }
162
163 /* Check the database schema version. Upgrade if the database schema
164 ** if necessary.
165 */
check_schema()166 static void check_schema() {
167 const char *zSchema = db_config("schema","1.0");
168 if( strcmp(zSchema,"2.2") ){
169 if( strcmp(zSchema,"1.1")<0 ) db_upgrade_schema_1();
170 if( strcmp(zSchema,"1.2")<0 ) db_upgrade_schema_2();
171 if( strcmp(zSchema,"1.3")<0 ) db_upgrade_schema_3();
172 if( strcmp(zSchema,"1.4")<0 ) db_upgrade_schema_4();
173 if( strcmp(zSchema,"1.5")<0 ) db_upgrade_schema_5();
174 if( strcmp(zSchema,"1.6")<0 ) db_upgrade_schema_6();
175 if( strcmp(zSchema,"1.7")<0 ) db_upgrade_schema_7();
176 if( strcmp(zSchema,"1.8")<0 ) db_upgrade_schema_8();
177 if( strcmp(zSchema,"1.9")<0 ) db_upgrade_schema_9();
178 if( strcmp(zSchema,"2.0")<0 ) db_upgrade_schema_20();
179 if( strcmp(zSchema,"2.1")<0 ) db_upgrade_schema_21();
180 if( strcmp(zSchema,"2.2")<0 ) db_upgrade_schema_22();
181
182 /* Good thing to do after you move tables around... */
183 db_execute("VACUUM;");
184 }
185 }
186
187 /*
188 ** RSS feeds need to reference absolute URLs so we need to calculate
189 ** the base URL onto which we add components. This is basically
190 ** cgi_redirect() stripped down and always returning an absolute URL.
191 */
get_base_url(void)192 static char *get_base_url(void){
193 int i;
194 char *zHost = getenv("HTTP_HOST");
195 char *zMode = getenv("HTTPS");
196 char *zCur = getenv("REQUEST_URI");
197 if( zCur==0 ) zCur = "/";
198 if( zHost==0 ) zHost = "";
199
200 for(i=0; zCur[i] && zCur[i]!='?' && zCur[i]!='#'; i++){}
201 if( g.zExtra ){
202 /* Skip to start of extra stuff, then pass over any /'s that might
203 ** have separated the document root from the extra stuff. This
204 ** ensures that the redirection actually redirects the root, not
205 ** something deep down at the bottom of a URL.
206 */
207 i -= strlen(g.zExtra);
208 while( i>0 && zCur[i-1]=='/' ){ i--; }
209 }
210 while( i>0 && zCur[i-1]!='/' ){ i--; }
211 while( i>0 && zCur[i-1]=='/' ){ i--; }
212
213 if( zMode && strcmp(zMode,"on")==0 ){
214 return mprintf("https://%s%.*s", zHost, i, zCur);
215 }
216 return mprintf("http://%s%.*s", zHost, i, zCur);
217 }
218
219 /*
220 ** Run the program.
221 */
main(int argc,char ** argv)222 int main(int argc, char **argv){
223 int i, j;
224 char *zSCM;
225 char *zPath;
226 char *zDb;
227 const char *zLogFile;
228 int cmdlineProj; /* True if project specified on command line */
229 void (*xFunc)(void);
230
231 /* Determine the SCM subsystem. Need to do this before anyone messes with
232 ** argv.
233 */
234 i = strlen(argv[0]);
235 while( i>0 && argv[0][i-1]!='/' ){ i--; }
236 zSCM = mprintf("%s", &argv[0][i]);
237 zPath = strstr(zSCM,"trac");
238 if( zPath!=0 ) *zPath = 0;
239
240 if( !strcmp(zSCM,"cvs") ){
241 init_cvs();
242 }else if(!strcmp(zSCM,"svn") ){
243 init_svn();
244 }else if(!strcmp(zSCM,"git") ){
245 init_git();
246 }else{
247 fprintf(stderr,"%s: unknown SCM '%s'\n", argv[0], zSCM);
248 exit(1);
249 }
250
251 /*
252 ** Attempt to put this process in a chroot jail if requested by the
253 ** user. The program must be run as root for this to work.
254 */
255 if( argc>=5 && strcmp(argv[1],"chroot")==0 ){
256 struct passwd *pwinfo;
257 pwinfo = getpwnam(argv[3]);
258 if( pwinfo==0 ){
259 fprintf(stderr,"%s: no such user: %s\n", argv[0], argv[3]);
260 exit(1);
261 }
262 if( chdir(argv[2]) || chroot(argv[2]) ){
263 fprintf(stderr, "%s: Unable to change root directory to %s\n",
264 argv[0], argv[2]);
265 exit(1);
266 }
267 argv[3] = argv[0];
268 argv += 3;
269 argc -= 3;
270 if( argc>=3 && strcmp(argv[1],"server")==0 ){
271 cgi_http_server(atoi(argv[2]));
272 argc--;
273 argv[1] = argv[0];
274 argv++;
275 argv[1] = "http";
276 }
277 setgid(pwinfo->pw_gid);
278 setuid(pwinfo->pw_uid);
279 }else if( argc>=3 && strcmp(argv[1],"server")==0 ){
280 cgi_http_server(atoi(argv[2]));
281 argv[1] = argv[0];
282 argv++;
283 argv[1] = "http";
284 argc--;
285 }
286
287 /*
288 ** Make sure we have the right number of arguments left.
289 */
290 if( argc<2 || argc>4 ){
291 usage(argv[0]);
292 }
293
294 /*
295 ** For security, do not allow this program to be run as root.
296 */
297 if( getuid()==0 || getgid()==0 ){
298 fprintf(stderr,"%s: execution by the superuser is disallowed\n", argv[0]);
299 exit(1);
300 }
301
302 /* Change into the project directory. */
303 if( argc>=3 && chdir(argv[2]) ){
304 fprintf(stderr,"%s: unable to change directories to %s\n", argv[0],argv[2]);
305 exit(1);
306 }
307
308 #if CVSTRAC_I18N
309 /* Set the appropriate locale */
310 setlocale(LC_ALL, "");
311 g.useUTF8 = (strcmp(nl_langinfo(CODESET), "UTF-8") == 0);
312 #endif
313
314 /* Set up global variable g
315 */
316 g.argc = argc;
317 g.argv = argv;
318 if( argc>=4 ){
319 /* The project name is specified on the command-line */
320 g.zName = argv[3];
321 cmdlineProj = 1;
322 }else{
323 /* No project name on the command line. Get the project name from
324 ** either the URL or the HTTP_HOST parameter of the request.
325 */
326 i = strlen(argv[0]);
327 while( i>0 && argv[0][i-1]!='/' ){ i--; }
328 g.zName = mprintf("%s", &argv[0][i]);
329 cmdlineProj = 0;
330 }
331
332 /* Figure out our behavior based on command line parameters and
333 ** the environment.
334 */
335 if( strcmp(argv[1],"cgi")==0 /* || getenv("GATEWAY_INTERFACE")!=0 */ ){
336 cgi_init();
337 }else if( strcmp(argv[1],"http")==0 ){
338 cgi_handle_http_request();
339 }else if( strcmp(argv[1],"init")==0 ){
340 if( getuid()!=geteuid() ){
341 fprintf(stderr,"Permission denied\n");
342 exit(1);
343 }
344 db_init();
345 exit(0);
346 }else if( strcmp(argv[1],"wikiinit")==0 ){
347 if( getuid()!=geteuid() ){
348 fprintf(stderr,"Permission denied\n");
349 exit(1);
350 }
351 initialize_wiki_pages();
352 exit(0);
353 }else if( strcmp(argv[1],"update")==0 ){
354 check_schema();
355 history_update(0);
356 exit(0);
357 }else if( strcmp(argv[1],"testcgi")==0 ){
358 cgi_init();
359 test_cgi_vardump();
360 cgi_reply();
361 exit(0);
362 }else{
363 usage(argv[0]);
364 }
365
366 /* Find the page that the user has requested, construct and deliver that
367 ** page.
368 */
369 zPath = getenv("PATH_INFO");
370 if( zPath==0 || zPath[0]==0 ){
371 char *zBase, *zUri;
372 zUri = getenv("REQUEST_URI");
373 if( zUri==0 ) zUri = "/";
374 for(i=0; zUri[i] && zUri[i]!='?' && zUri[i]!='#'; i++){}
375 for(j=i; j>0 && zUri[j-1]!='/'; j--){}
376 if( i==j ){
377 cgi_set_status(404,"Not Found");
378 @ <h1>Not Found</h1>
379 @ <p>Page not found: %h(zUri)</p>
380 }else{
381 zBase = mprintf("%.*s/index", i-j, &zUri[j]);
382 cgi_redirect(zBase);
383 }
384 cgi_reply();
385 return 0;
386 }
387
388 /*
389 ** Extract the project name from the front of the path if no project
390 ** was specified on the command line.
391 */
392 if( !cmdlineProj ){
393 while(zPath[0]=='/') {zPath++;} /* eat leading '/' */
394 for(i=0; zPath[i] && zPath[i]!='/'; i++){}
395 if( i>0 ){
396 g.zName = mprintf("%.*s", i, zPath);
397 zPath = &zPath[i];
398 }else{
399 cgi_set_status(404,"Not Found");
400 @ <h1>Not Found</h1>
401 @ <p>Page not found: %h(zPath)</p>
402 cgi_reply();
403 return 0;
404 }
405 }
406 while(zPath[0]=='/') {zPath++;} /* eat leading '/' */
407 g.zPath = zPath;
408 for(i=0; zPath[i] && zPath[i]!='/'; i++){}
409 if( zPath[i]=='/' ){
410 zPath[i] = 0;
411 g.zExtra = &zPath[i+1];
412
413 /* CGI parameters get this treatment elsewhere, but places like getfile
414 ** will use g.zExtra directly.
415 */
416 dehttpize(g.zExtra);
417 }else{
418 g.zExtra = 0;
419 }
420
421 g.zBaseURL = get_base_url();
422
423 /* Prevent robots from indexing this site.
424 */
425 if( strcmp(g.zPath, "robots.txt")==0 ){
426 cgi_set_content_type("text/plain");
427 @ User-agent: *
428 @ Disallow: /
429 cgi_reply();
430 exit(0);
431 }
432
433 /* Make sure the specified project really exists. Return an error
434 ** if it does not.
435 */
436 zDb = mprintf("%s.db", g.zName);
437 if( access(zDb,0) ){
438 free(zDb);
439 zDb = mprintf("%s.db", g.zPath);
440 if( !cmdlineProj && access(zDb,0)==0 ){
441 cgi_redirect( mprintf("%s/index", g.zPath) );
442 }else{
443 cgi_set_status(404,"Not Found");
444 @ <h1>Not Found</h1>
445 @ <p>Project not found: %h(g.zName)</p>
446 @ <p>Page not found: %h(g.zPath)</p>
447 }
448 cgi_reply();
449 return 0;
450 }
451 free(zDb);
452
453 check_schema();
454
455 /* Ensure the CVSTrac process doesn't live indefinitely. If it takes more
456 ** than this long, you're doing something wrong.
457 */
458 alarm(MX_CHILD_LIFETIME);
459
460 /* Make a log file entry for this access.
461 */
462 zLogFile = db_config("logfile", 0);
463 if( zLogFile ){
464 cgi_logfile(zLogFile,"*");
465 }
466
467 /* Locate the method specified by the path and execute the function
468 ** that implements that method.
469 */
470 if( !find_path(g.zPath, &xFunc) && !find_path("not_found",&xFunc) ){
471 char *atn = db_short_query("SELECT atn FROM attachment "
472 "WHERE tn=0 AND fname='%q' "
473 "ORDER BY date DESC LIMIT 1", g.zPath);
474 if( atn && *atn ){
475 attachment_output(atoi(atn));
476 free(atn);
477
478 /* it's not actually constant because the URL can point at different
479 ** attachments over time. */
480 g.isConst = 0;
481 }else{
482 cgi_set_status(404,"Not Found");
483 @ <h1>Not Found</h1>
484 @ <p>Page not found: %h(g.zPath)</p>
485 }
486 }else{
487 xFunc();
488 }
489
490 /* Return the result.
491 */
492 cgi_reply();
493 return 0;
494 }
495