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