1 /*
2 ** Copyright (c) 2006 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7 **
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
13 **   drh@hwaci.com
14 **   http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This module codes the main() procedure that runs first when the
19 ** program is invoked.
20 */
21 #include "VERSION.h"
22 #include "config.h"
23 #if defined(_WIN32)
24 #  include <windows.h>
25 #  include <io.h>
26 #  define isatty(h) _isatty(h)
27 #  define GETPID (int)GetCurrentProcessId
28 #endif
29 #include "main.h"
30 #include <string.h>
31 #include <time.h>
32 #include <fcntl.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <stdlib.h> /* atexit() */
36 #if !defined(_WIN32)
37 #  include <errno.h> /* errno global */
38 #  include <unistd.h>
39 #  include <signal.h>
40 #  define GETPID getpid
41 #endif
42 #ifdef FOSSIL_ENABLE_SSL
43 #  include "openssl/crypto.h"
44 #endif
45 #if defined(FOSSIL_ENABLE_MINIZ)
46 #  define MINIZ_HEADER_FILE_ONLY
47 #  include "miniz.c"
48 #else
49 #  include <zlib.h>
50 #endif
51 #if INTERFACE
52 #ifdef FOSSIL_ENABLE_TCL
53 #  include "tcl.h"
54 #endif
55 #ifdef FOSSIL_ENABLE_JSON
56 #  include "cson_amalgamation.h" /* JSON API. */
57 #  include "json_detail.h"
58 #endif
59 #ifdef HAVE_BACKTRACE
60 # include <execinfo.h>
61 #endif
62 
63 /*
64 ** Default length of a timeout for serving an HTTP request.  Changable
65 ** using the "--timeout N" command-line option or via "timeout: N" in the
66 ** CGI script.
67 */
68 #ifndef FOSSIL_DEFAULT_TIMEOUT
69 # define FOSSIL_DEFAULT_TIMEOUT 600  /* 10 minutes */
70 #endif
71 
72 /*
73 ** Maximum number of auxiliary parameters on reports
74 */
75 #define MX_AUX  5
76 
77 /*
78 ** Holds flags for fossil user permissions.
79 */
80 struct FossilUserPerms {
81   char Setup;            /* s: use Setup screens on web interface */
82   char Admin;            /* a: administrative permission */
83   char Password;         /* p: change password */
84   char Query;            /* q: create new reports */
85   char Write;            /* i: xfer inbound. check-in */
86   char Read;             /* o: xfer outbound. check-out */
87   char Hyperlink;        /* h: enable the display of hyperlinks */
88   char Clone;            /* g: clone */
89   char RdWiki;           /* j: view wiki via web */
90   char NewWiki;          /* f: create new wiki via web */
91   char ApndWiki;         /* m: append to wiki via web */
92   char WrWiki;           /* k: edit wiki via web */
93   char ModWiki;          /* l: approve and publish wiki content (Moderator) */
94   char RdTkt;            /* r: view tickets via web */
95   char NewTkt;           /* n: create new tickets */
96   char ApndTkt;          /* c: append to tickets via the web */
97   char WrTkt;            /* w: make changes to tickets via web */
98   char ModTkt;           /* q: approve and publish ticket changes (Moderator) */
99   char Attach;           /* b: add attachments */
100   char TktFmt;           /* t: create new ticket report formats */
101   char RdAddr;           /* e: read email addresses or other private data */
102   char Zip;              /* z: download zipped artifact via /zip URL */
103   char Private;          /* x: can send and receive private content */
104   char WrUnver;          /* y: can push unversioned content */
105   char RdForum;          /* 2: Read forum posts */
106   char WrForum;          /* 3: Create new forum posts */
107   char WrTForum;         /* 4: Post to forums not subject to moderation */
108   char ModForum;         /* 5: Moderate (approve or reject) forum posts */
109   char AdminForum;       /* 6: Grant capability 4 to other users */
110   char EmailAlert;       /* 7: Sign up for email notifications */
111   char Announce;         /* A: Send announcements */
112   char Chat;             /* C: read or write the chatroom */
113   char Debug;            /* D: show extra Fossil debugging features */
114   /* These last two are included to block infinite recursion */
115   char XReader;          /* u: Inherit all privileges of "reader" */
116   char XDeveloper;       /* v: Inherit all privileges of "developer" */
117 };
118 
119 #ifdef FOSSIL_ENABLE_TCL
120 /*
121 ** All Tcl related context information is in this structure.  This structure
122 ** definition has been copied from and should be kept in sync with the one in
123 ** "th_tcl.c".
124 */
125 struct TclContext {
126   int argc;              /* Number of original (expanded) arguments. */
127   char **argv;           /* Full copy of the original (expanded) arguments. */
128   void *hLibrary;        /* The Tcl library module handle. */
129   void *xFindExecutable; /* See tcl_FindExecutableProc in th_tcl.c. */
130   void *xCreateInterp;   /* See tcl_CreateInterpProc in th_tcl.c. */
131   void *xDeleteInterp;   /* See tcl_DeleteInterpProc in th_tcl.c. */
132   void *xFinalize;       /* See tcl_FinalizeProc in th_tcl.c. */
133   Tcl_Interp *interp;    /* The on-demand created Tcl interpreter. */
134   int useObjProc;        /* Non-zero if an objProc can be called directly. */
135   int useTip285;         /* Non-zero if TIP #285 is available. */
136   char *setup;           /* The optional Tcl setup script. */
137   void *xPreEval;        /* Optional, called before Tcl_Eval*(). */
138   void *pPreContext;     /* Optional, provided to xPreEval(). */
139   void *xPostEval;       /* Optional, called after Tcl_Eval*(). */
140   void *pPostContext;    /* Optional, provided to xPostEval(). */
141 };
142 #endif
143 
144 struct Global {
145   int argc; char **argv;  /* Command-line arguments to the program */
146   char *nameOfExe;        /* Full path of executable. */
147   const char *zErrlog;    /* Log errors to this file, if not NULL */
148   const char *zPhase;     /* Phase of operation, for use by the error log */
149   int isConst;            /* True if the output is unchanging & cacheable */
150   const char *zVfsName;   /* The VFS to use for database connections */
151   sqlite3 *db;            /* The connection to the databases */
152   sqlite3 *dbConfig;      /* Separate connection for global_config table */
153   char *zAuxSchema;       /* Main repository aux-schema */
154   int dbIgnoreErrors;     /* Ignore database errors if true */
155   char *zConfigDbName;    /* Path of the config database. NULL if not open */
156   sqlite3_int64 now;      /* Seconds since 1970 */
157   int repositoryOpen;     /* True if the main repository database is open */
158   unsigned iRepoDataVers;  /* Initial data version for repository database */
159   char *zRepositoryOption; /* Most recent cached repository option value */
160   char *zRepositoryName;  /* Name of the repository database file */
161   char *zLocalDbName;     /* Name of the local database file */
162   char *zOpenRevision;    /* Check-in version to use during database open */
163   const char *zCmdName;   /* Name of the Fossil command currently running */
164   int localOpen;          /* True if the local database is open */
165   char *zLocalRoot;       /* The directory holding the  local database */
166   int minPrefix;          /* Number of digits needed for a distinct hash */
167   int eHashPolicy;        /* Current hash policy.  One of HPOLICY_* */
168   int fSqlTrace;          /* True if --sqltrace flag is present */
169   int fSqlStats;          /* True if --sqltrace or --sqlstats are present */
170   int fSqlPrint;          /* True if --sqlprint flag is present */
171   int fCgiTrace;          /* True if --cgitrace is enabled */
172   int fQuiet;             /* True if -quiet flag is present */
173   int fJail;              /* True if running with a chroot jail */
174   int fHttpTrace;         /* Trace outbound HTTP requests */
175   int fAnyTrace;          /* Any kind of tracing */
176   char *zHttpAuth;        /* HTTP Authorization user:pass information */
177   int fSystemTrace;       /* Trace calls to fossil_system(), --systemtrace */
178   int fSshTrace;          /* Trace the SSH setup traffic */
179   int fSshClient;         /* HTTP client flags for SSH client */
180   int fNoHttpCompress;    /* Do not compress HTTP traffic (for debugging) */
181   char *zSshCmd;          /* SSH command string */
182   int fNoSync;            /* Do not do an autosync ever.  --nosync */
183   int fIPv4;              /* Use only IPv4, not IPv6. --ipv4 */
184   char *zPath;            /* Name of webpage being served */
185   char *zExtra;           /* Extra path information past the webpage name */
186   char *zBaseURL;         /* Full text of the URL being served */
187   char *zHttpsURL;        /* zBaseURL translated to https: */
188   char *zTop;             /* Parent directory of zPath */
189   int nExtraURL;          /* Extra bytes added to SCRIPT_NAME */
190   const char *zExtRoot;   /* Document root for the /ext sub-website */
191   const char *zContentType;  /* The content type of the input HTTP request */
192   int iErrPriority;       /* Priority of current error message */
193   char *zErrMsg;          /* Text of an error message */
194   int sslNotAvailable;    /* SSL is not available.  Do not redirect to https: */
195   Blob cgiIn;             /* Input to an xfer www method */
196   int cgiOutput;          /* 0: command-line 1: CGI. 2: after CGI */
197   int xferPanic;          /* Write error messages in XFER protocol */
198   int fullHttpReply;      /* True for full HTTP reply.  False for CGI reply */
199   Th_Interp *interp;      /* The TH1 interpreter */
200   char *th1Setup;         /* The TH1 post-creation setup script, if any */
201   int th1Flags;           /* The TH1 integration state flags */
202   FILE *httpIn;           /* Accept HTTP input from here */
203   FILE *httpOut;          /* Send HTTP output here */
204   int xlinkClusterOnly;   /* Set when cloning.  Only process clusters */
205   int fTimeFormat;        /* 1 for UTC.  2 for localtime.  0 not yet selected */
206   int *aCommitFile;       /* Array of files to be committed */
207   int markPrivate;        /* All new artifacts are private if true */
208   char *ckinLockFail;     /* Check-in lock failure received from server */
209   int clockSkewSeen;      /* True if clocks on client and server out of sync */
210   int wikiFlags;          /* Wiki conversion flags applied to %W */
211   char isHTTP;            /* True if server/CGI modes, else assume CLI. */
212   char javascriptHyperlink; /* If true, set href= using script, not HTML */
213   Blob httpHeader;        /* Complete text of the HTTP request header */
214   UrlData url;            /* Information about current URL */
215   const char *zLogin;     /* Login name.  NULL or "" if not logged in. */
216   const char *zCkoutAlias;   /* doc/ uses this branch as an alias for "ckout" */
217   const char *zMainMenuFile; /* --mainmenu FILE from server/ui/cgi */
218   const char *zSSLIdentity;  /* Value of --ssl-identity option, filename of
219                              ** SSL client identity */
220 #if defined(_WIN32) && USE_SEE
221   const char *zPidKey;    /* Saved value of the --usepidkey option.  Only
222                            * applicable when using SEE on Windows. */
223 #endif
224   int useLocalauth;       /* No login required if from 127.0.0.1 */
225   int noPswd;             /* Logged in without password (on 127.0.0.1) */
226   int userUid;            /* Integer user id */
227   int isHuman;            /* True if access by a human, not a spider or bot */
228   int comFmtFlags;        /* Zero or more "COMMENT_PRINT_*" bit flags, should be
229                           ** accessed through get_comment_format(). */
230 
231   /* Information used to populate the RCVFROM table */
232   int rcvid;              /* The rcvid.  0 if not yet defined. */
233   char *zIpAddr;          /* The remote IP address */
234   char *zNonce;           /* The nonce used for login */
235 
236   /* permissions available to current user */
237   struct FossilUserPerms perm;
238 
239   /* permissions available to current user or to "anonymous".
240   ** This is the logical union of perm permissions above with
241   ** the value that perm would take if g.zLogin were "anonymous". */
242   struct FossilUserPerms anon;
243 
244 #ifdef FOSSIL_ENABLE_TCL
245   /* all Tcl related context necessary for integration */
246   struct TclContext tcl;
247 #endif
248 
249   /* For defense against Cross-site Request Forgery attacks */
250   char zCsrfToken[12];    /* Value of the anti-CSRF token */
251   int okCsrf;             /* Anti-CSRF token is present and valid */
252 
253   int parseCnt[10];       /* Counts of artifacts parsed */
254   FILE *fDebug;           /* Write debug information here, if the file exists */
255 #ifdef FOSSIL_ENABLE_TH1_HOOKS
256   int fNoThHook;          /* Disable all TH1 command/webpage hooks */
257 #endif
258   int thTrace;            /* True to enable TH1 debugging output */
259   Blob thLog;             /* Text of the TH1 debugging output */
260 
261   int isHome;             /* True if rendering the "home" page */
262 
263   /* Storage for the aux() and/or option() SQL function arguments */
264   int nAux;                    /* Number of distinct aux() or option() values */
265   const char *azAuxName[MX_AUX]; /* Name of each aux() or option() value */
266   char *azAuxParam[MX_AUX];      /* Param of each aux() or option() value */
267   const char *azAuxVal[MX_AUX];  /* Value of each aux() or option() value */
268   const char **azAuxOpt[MX_AUX]; /* Options of each option() value */
269   int anAuxCols[MX_AUX];         /* Number of columns for option() values */
270   int allowSymlinks;             /* Cached "allow-symlinks" option */
271   int mainTimerId;               /* Set to fossil_timer_start() */
272   int nPendingRequest;           /* # of HTTP requests in "fossil server" */
273   int nRequest;                  /* Total # of HTTP request */
274   int bAvoidDeltaManifests;      /* Avoid using delta manifests if true */
275 #ifdef FOSSIL_ENABLE_JSON
276   struct FossilJsonBits {
277     int isJsonMode;            /* True if running in JSON mode, else
278                                   false. This changes how errors are
279                                   reported. In JSON mode we try to
280                                   always output JSON-form error
281                                   responses and always (in CGI mode)
282                                   exit() with code 0 to avoid an HTTP
283                                   500 error.
284                                */
285     int preserveRc;            /* Do not convert error codes into 0.
286                                 * This is primarily intended for use
287                                 * by the test suite. */
288     int resultCode;            /* used for passing back specific codes
289                                ** from /json callbacks. */
290     int errorDetailParanoia;   /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
291     cson_output_opt outOpt;    /* formatting options for JSON mode. */
292     cson_value *authToken;     /* authentication token */
293     const char *jsonp;         /* Name of JSONP function wrapper. */
294     unsigned char dispatchDepth /* Tells JSON command dispatching
295                                    which argument we are currently
296                                    working on. For this purpose, arg#0
297                                    is the "json" path/CLI arg.
298                                 */;
299     struct {                   /* "garbage collector" */
300       cson_value *v;
301       cson_array *a;
302     } gc;
303     struct {                   /* JSON POST data. */
304       cson_value *v;
305       cson_array *a;
306       int offset;              /* Tells us which PATH_INFO/CLI args
307                                   part holds the "json" command, so
308                                   that we can account for sub-repos
309                                   and path prefixes.  This is handled
310                                   differently for CLI and CGI modes.
311                                */
312       const char *commandStr   /*"command" request param.*/;
313     } cmd;
314     struct {                   /* JSON POST data. */
315       cson_value *v;
316       cson_object *o;
317     } post;
318     struct {                   /* GET/COOKIE params in JSON mode. */
319       cson_value *v;
320       cson_object *o;
321     } param;
322     struct {
323       cson_value *v;
324       cson_object *o;
325     } reqPayload;              /* request payload object (if any) */
326     cson_array *warnings;      /* response warnings */
327     int timerId;               /* fetched from fossil_timer_start() */
328   } json;
329 #endif /* FOSSIL_ENABLE_JSON */
330   int diffCnt[3];         /* Counts for DIFF_NUMSTAT: files, ins, del */
331 };
332 
333 /*
334 ** Macro for debugging:
335 */
336 #define CGIDEBUG(X)  if( g.fDebug ) cgi_debug X
337 
338 #endif
339 
340 Global g;
341 
342 /*
343 ** atexit() handler which frees up "some" of the resources
344 ** used by fossil.
345 */
fossil_atexit(void)346 static void fossil_atexit(void) {
347   static int once = 0;
348   if( once++ ) return; /* Ensure that this routine only runs once */
349 #if USE_SEE
350   /*
351   ** Zero, unlock, and free the saved database encryption key now.
352   */
353   db_unsave_encryption_key();
354 #endif
355 #if defined(_WIN32) || (defined(__BIONIC__) && !defined(FOSSIL_HAVE_GETPASS))
356   /*
357   ** Free the secure getpass() buffer now.
358   */
359   freepass();
360 #endif
361 #if defined(_WIN32) && !defined(_WIN64) && defined(FOSSIL_ENABLE_TCL) && \
362     defined(USE_TCL_STUBS)
363   /*
364   ** If Tcl is compiled on Windows using the latest MinGW, Fossil can crash
365   ** when exiting while a stubs-enabled Tcl is still loaded.  This is due to
366   ** a bug in MinGW, see:
367   **
368   **     http://comments.gmane.org/gmane.comp.gnu.mingw.user/41724
369   **
370   ** The workaround is to manually unload the loaded Tcl library prior to
371   ** exiting the process.  This issue does not impact 64-bit Windows.
372   */
373   unloadTcl(g.interp, &g.tcl);
374 #endif
375 #ifdef FOSSIL_ENABLE_JSON
376   cson_value_free(g.json.gc.v);
377   memset(&g.json, 0, sizeof(g.json));
378 #endif
379   free(g.zErrMsg);
380   if(g.db){
381     db_close(0);
382   }
383   manifest_clear_cache();
384   content_clear_cache(1);
385   rebuild_clear_cache();
386   /*
387   ** FIXME: The next two lines cannot always be enabled; however, they
388   **        are very useful for tracking down TH1 memory leaks.
389   */
390   if( fossil_getenv("TH1_DELETE_INTERP")!=0 ){
391     if( g.interp ){
392       Th_DeleteInterp(g.interp); g.interp = 0;
393     }
394 #if defined(TH_MEMDEBUG)
395     if( Th_GetOutstandingMalloc()!=0 ){
396       fossil_print("Th_GetOutstandingMalloc() => %d\n",
397                    Th_GetOutstandingMalloc());
398     }
399     assert( Th_GetOutstandingMalloc()==0 );
400 #endif
401   }
402 }
403 
404 /*
405 ** Convert all arguments from mbcs (or unicode) to UTF-8. Then
406 ** search g.argv for arguments "--args FILENAME". If found, then
407 ** (1) remove the two arguments from g.argv
408 ** (2) Read the file FILENAME
409 ** (3) Use the contents of FILE to replace the two removed arguments:
410 **     (a) Ignore blank lines in the file
411 **     (b) Each non-empty line of the file is an argument, except
412 **     (c) If the line begins with "-" and contains a space, it is broken
413 **         into two arguments at the space.
414 */
expand_args_option(int argc,void * argv)415 void expand_args_option(int argc, void *argv){
416   Blob file = empty_blob;   /* Content of the file */
417   Blob line = empty_blob;   /* One line of the file */
418   unsigned int nLine;       /* Number of lines in the file*/
419   unsigned int i, j, k;     /* Loop counters */
420   int n;                    /* Number of bytes in one line */
421   unsigned int nArg;        /* Number of new arguments */
422   char *z;                  /* General use string pointer */
423   char **newArgv;           /* New expanded g.argv under construction */
424   const char *zFileName;    /* input file name */
425   FILE *inFile;             /* input FILE */
426 #if defined(_WIN32)
427   wchar_t buf[MAX_PATH];
428 #endif
429 
430   g.argc = argc;
431   g.argv = argv;
432   sqlite3_initialize();
433 #if defined(_WIN32) && defined(BROKEN_MINGW_CMDLINE)
434   for(i=0; i<g.argc; i++) g.argv[i] = fossil_mbcs_to_utf8(g.argv[i]);
435 #else
436   for(i=0; i<g.argc; i++) g.argv[i] = fossil_path_to_utf8(g.argv[i]);
437 #endif
438 #if defined(_WIN32)
439   GetModuleFileNameW(NULL, buf, MAX_PATH);
440   g.nameOfExe = fossil_path_to_utf8(buf);
441 #else
442   g.nameOfExe = g.argv[0];
443 #endif
444   for(i=1; i<g.argc-1; i++){
445     z = g.argv[i];
446     if( z[0]!='-' ) continue;
447     z++;
448     if( z[0]=='-' ) z++;
449     if( z[0]==0 ) return;   /* Stop searching at "--" */
450     if( fossil_strcmp(z, "args")==0 ) break;
451   }
452   if( i>=g.argc-1 ) return;
453 
454   zFileName = g.argv[i+1];
455   if( strcmp(zFileName,"-")==0 ){
456     inFile = stdin;
457   }else if( !file_isfile(zFileName, ExtFILE) ){
458     fossil_fatal("Not an ordinary file: \"%s\"", zFileName);
459   }else{
460     inFile = fossil_fopen(zFileName,"rb");
461     if( inFile==0 ){
462       fossil_fatal("Cannot open -args file [%s]", zFileName);
463     }
464   }
465   blob_read_from_channel(&file, inFile, -1);
466   if(stdin != inFile){
467     fclose(inFile);
468   }
469   inFile = NULL;
470   blob_to_utf8_no_bom(&file, 1);
471   z = blob_str(&file);
472   for(k=0, nLine=1; z[k]; k++) if( z[k]=='\n' ) nLine++;
473   if( nLine>100000000 ) fossil_fatal("too many command-line arguments");
474   nArg = g.argc + nLine*2;
475   newArgv = fossil_malloc( sizeof(char*)*nArg );
476   for(j=0; j<i; j++) newArgv[j] = g.argv[j];
477 
478   blob_rewind(&file);
479   while( nLine-->0 && (n = blob_line(&file, &line))>0 ){
480     /* Reminder: ^^^ nLine check avoids that embedded NUL bytes in the
481     ** --args file causes nLine to be less than blob_line() will end
482     ** up reporting, as such a miscount leads to an illegal memory
483     ** write. See forum post
484     ** https://fossil-scm.org/forum/forumpost/7b34eecc1b8c for
485     ** details */
486     if( n<1 ){
487       /* Reminder: corner-case: a line with 1 byte and no newline. */
488       continue;
489     }
490     z = blob_buffer(&line);
491     if('\n'==z[n-1]){
492       z[n-1] = 0;
493     }
494 
495     if((n>1) && ('\r'==z[n-2])){
496       if(n==2) continue /*empty line*/;
497       z[n-2] = 0;
498     }
499     if(!z[0]) continue;
500     if( j>=nArg ){
501       fossil_fatal("malformed command-line arguments");
502     }
503     newArgv[j++] = z;
504     if( z[0]=='-' ){
505       for(k=1; z[k] && !fossil_isspace(z[k]); k++){}
506       if( z[k] ){
507         z[k] = 0;
508         k++;
509         if( z[k] ) newArgv[j++] = &z[k];
510       }
511     }
512   }
513   i += 2;
514   while( i<g.argc ) newArgv[j++] = g.argv[i++];
515   newArgv[j] = 0;
516   g.argc = j;
517   g.argv = newArgv;
518 }
519 
520 #ifdef FOSSIL_ENABLE_TCL
521 /*
522 ** Make a deep copy of the provided argument array and return it.
523 */
copy_args(int argc,char ** argv)524 static char **copy_args(int argc, char **argv){
525   char **zNewArgv;
526   int i;
527   zNewArgv = fossil_malloc( sizeof(char*)*(argc+1) );
528   memset(zNewArgv, 0, sizeof(char*)*(argc+1));
529   for(i=0; i<argc; i++){
530     zNewArgv[i] = fossil_strdup(argv[i]);
531   }
532   return zNewArgv;
533 }
534 #endif
535 
536 /*
537 ** Returns a name for a SQLite return code.
538 */
fossil_sqlite_return_code_name(int rc)539 static const char *fossil_sqlite_return_code_name(int rc){
540   static char zCode[30];
541   switch( rc & 0xff ){
542     case SQLITE_OK:         return "SQLITE_OK";
543     case SQLITE_ERROR:      return "SQLITE_ERROR";
544     case SQLITE_INTERNAL:   return "SQLITE_INTERNAL";
545     case SQLITE_PERM:       return "SQLITE_PERM";
546     case SQLITE_ABORT:      return "SQLITE_ABORT";
547     case SQLITE_BUSY:       return "SQLITE_BUSY";
548     case SQLITE_LOCKED:     return "SQLITE_LOCKED";
549     case SQLITE_NOMEM:      return "SQLITE_NOMEM";
550     case SQLITE_READONLY:   return "SQLITE_READONLY";
551     case SQLITE_INTERRUPT:  return "SQLITE_INTERRUPT";
552     case SQLITE_IOERR:      return "SQLITE_IOERR";
553     case SQLITE_CORRUPT:    return "SQLITE_CORRUPT";
554     case SQLITE_NOTFOUND:   return "SQLITE_NOTFOUND";
555     case SQLITE_FULL:       return "SQLITE_FULL";
556     case SQLITE_CANTOPEN:   return "SQLITE_CANTOPEN";
557     case SQLITE_PROTOCOL:   return "SQLITE_PROTOCOL";
558     case SQLITE_EMPTY:      return "SQLITE_EMPTY";
559     case SQLITE_SCHEMA:     return "SQLITE_SCHEMA";
560     case SQLITE_TOOBIG:     return "SQLITE_TOOBIG";
561     case SQLITE_CONSTRAINT: return "SQLITE_CONSTRAINT";
562     case SQLITE_MISMATCH:   return "SQLITE_MISMATCH";
563     case SQLITE_MISUSE:     return "SQLITE_MISUSE";
564     case SQLITE_NOLFS:      return "SQLITE_NOLFS";
565     case SQLITE_AUTH:       return "SQLITE_AUTH";
566     case SQLITE_FORMAT:     return "SQLITE_FORMAT";
567     case SQLITE_RANGE:      return "SQLITE_RANGE";
568     case SQLITE_NOTADB:     return "SQLITE_NOTADB";
569     case SQLITE_NOTICE:     return "SQLITE_NOTICE";
570     case SQLITE_WARNING:    return "SQLITE_WARNING";
571     case SQLITE_ROW:        return "SQLITE_ROW";
572     case SQLITE_DONE:       return "SQLITE_DONE";
573     default: {
574       sqlite3_snprintf(sizeof(zCode), zCode, "SQLite return code %d", rc);
575     }
576   }
577   return zCode;
578 }
579 
580 /* Error logs from SQLite */
fossil_sqlite_log(void * notUsed,int iCode,const char * zErrmsg)581 static void fossil_sqlite_log(void *notUsed, int iCode, const char *zErrmsg){
582   sqlite3_stmt *p;
583   Blob msg;
584 #ifdef __APPLE__
585   /* Disable the file alias warning on apple products because Time Machine
586   ** creates lots of aliases and the warnings alarm people. */
587   if( iCode==SQLITE_WARNING ) return;
588 #endif
589 #ifndef FOSSIL_DEBUG
590   /* Disable the automatic index warning except in FOSSIL_DEBUG builds. */
591   if( iCode==SQLITE_WARNING_AUTOINDEX ) return;
592 #endif
593   if( iCode==SQLITE_SCHEMA ) return;
594   if( g.dbIgnoreErrors ) return;
595 #ifdef SQLITE_READONLY_DIRECTORY
596   if( iCode==SQLITE_READONLY_DIRECTORY ){
597     zErrmsg = "database is in a read-only directory";
598   }
599 #endif
600   blob_init(&msg, 0, 0);
601   blob_appendf(&msg, "%s(%d): %s",
602      fossil_sqlite_return_code_name(iCode), iCode, zErrmsg);
603   if( g.db ){
604     for(p=sqlite3_next_stmt(g.db, 0); p; p=sqlite3_next_stmt(g.db,p)){
605       const char *zSql;
606       if( !sqlite3_stmt_busy(p) ) continue;
607       zSql = sqlite3_sql(p);
608       if( zSql==0 ) continue;
609       blob_appendf(&msg, "\nSQL: %s", zSql);
610     }
611   }
612   fossil_warning("%s", blob_str(&msg));
613   blob_reset(&msg);
614 }
615 
616 /*
617 ** This function attempts to find command line options known to contain
618 ** bitwise flags and initializes the associated global variables.  After
619 ** this function executes, all global variables (i.e. in the "g" struct)
620 ** containing option-settable bitwise flag fields must be initialized.
621 */
fossil_init_flags_from_options(void)622 static void fossil_init_flags_from_options(void){
623   const char *zValue = find_option("comfmtflags", 0, 1);
624   if( zValue==0 ){
625     zValue = find_option("comment-format", 0, 1);
626   }
627   if( zValue ){
628     g.comFmtFlags = atoi(zValue);
629   }else{
630     g.comFmtFlags = COMMENT_PRINT_UNSET;   /* Command-line option not found. */
631   }
632 }
633 
634 /*
635 ** Check to see if the Fossil binary contains an appended repository
636 ** file using the appendvfs extension.  If so, change command-line arguments
637 ** to cause Fossil to launch with "fossil ui" on that repo.
638 */
fossilExeHasAppendedRepo(void)639 static int fossilExeHasAppendedRepo(void){
640   extern int deduceDatabaseType(const char*,int);
641   if( 2==deduceDatabaseType(g.nameOfExe,0) ){
642     static char *azAltArgv[] = { 0, "ui", 0, 0 };
643     azAltArgv[0] = g.nameOfExe;
644     azAltArgv[2] = g.nameOfExe;
645     g.argv = azAltArgv;
646     g.argc = 3;
647     return 1;
648   }else{
649     return 0;
650   }
651 }
652 
653 /*
654 ** This procedure runs first.
655 */
656 #if defined(FOSSIL_FUZZ)
657   /* Do not include a main() procedure when building for fuzz testing.
658   ** libFuzzer will supply main(). */
659 #elif defined(_WIN32) && !defined(BROKEN_MINGW_CMDLINE)
660   int _dowildcard = -1; /* This turns on command-line globbing in MinGW-w64 */
wmain(int argc,wchar_t ** argv)661   int wmain(int argc, wchar_t **argv){ return fossil_main(argc,(char**)argv); }
662 #elif defined(_WIN32)
663   int _CRT_glob = 0x0001; /* See MinGW bug #2062 */
main(int argc,char ** argv)664   int main(int argc, char **argv){ return fossil_main(argc, argv); }
665 #else
main(int argc,char ** argv)666   int main(int argc, char **argv){ return fossil_main(argc, argv); }
667 #endif
668 
669 /* All the work of main() is done by a separate procedure "fossil_main()".
670 ** We have to break this out, because fossil_main() is sometimes called
671 ** separately (by the "shell" command) but we do not want atwait() handlers
672 ** being called by separate invocations of fossil_main().
673 */
fossil_main(int argc,char ** argv)674 int fossil_main(int argc, char **argv){
675   const char *zCmdName = "unknown";
676   const CmdOrPage *pCmd = 0;
677   int rc;
678 
679   g.zPhase = "init";
680 #if !defined(_WIN32_WCE)
681   if( fossil_getenv("FOSSIL_BREAK") ){
682     if( isatty(0) && isatty(2) ){
683       fprintf(stderr,
684           "attach debugger to process %d and press any key to continue.\n",
685           GETPID());
686       fgetc(stdin);
687     }else{
688 #if defined(_WIN32) || defined(WIN32)
689       DebugBreak();
690 #elif defined(SIGTRAP)
691       raise(SIGTRAP);
692 #endif
693     }
694   }
695 #endif
696 
697   fossil_printf_selfcheck();
698   fossil_limit_memory(1);
699 
700   /* When updating the minimum SQLite version, change the number here,
701   ** and also MINIMUM_SQLITE_VERSION value set in ../auto.def.  Take
702   ** care that both places agree! */
703   if( sqlite3_libversion_number()<3035000 ){
704     fossil_panic("Unsuitable SQLite version %s, must be at least 3.35.0",
705                  sqlite3_libversion());
706   }
707 
708   sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
709   sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0);
710   memset(&g, 0, sizeof(g));
711   g.now = time(0);
712   g.httpHeader = empty_blob;
713 #ifdef FOSSIL_ENABLE_JSON
714 #if defined(NDEBUG)
715   g.json.errorDetailParanoia = 2 /* FIXME: make configurable
716                                     One problem we have here is that this
717                                     code is needed before the db is opened,
718                                     so we can't sql for it.*/;
719 #else
720   g.json.errorDetailParanoia = 0;
721 #endif
722   g.json.outOpt = cson_output_opt_empty;
723   g.json.outOpt.addNewline = 1;
724   g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */;
725 #endif /* FOSSIL_ENABLE_JSON */
726   expand_args_option(argc, argv);
727 #ifdef FOSSIL_ENABLE_TCL
728   memset(&g.tcl, 0, sizeof(TclContext));
729   g.tcl.argc = g.argc;
730   g.tcl.argv = copy_args(g.argc, g.argv); /* save full arguments */
731 #endif
732   g.mainTimerId = fossil_timer_start();
733   capture_case_sensitive_option();
734   g.zVfsName = find_option("vfs",0,1);
735   if( g.zVfsName==0 ){
736     g.zVfsName = fossil_getenv("FOSSIL_VFS");
737   }
738   if( g.zVfsName ){
739     sqlite3_vfs *pVfs = sqlite3_vfs_find(g.zVfsName);
740     if( pVfs ){
741       sqlite3_vfs_register(pVfs, 1);
742     }else{
743       fossil_fatal("no such VFS: \"%s\"", g.zVfsName);
744     }
745   }
746   if( !find_option("nocgi", 0, 0) && fossil_getenv("GATEWAY_INTERFACE")!=0){
747     zCmdName = "cgi";
748     g.isHTTP = 1;
749   }else if( g.argc<2 && !fossilExeHasAppendedRepo() ){
750     fossil_print(
751        "Usage: %s COMMAND ...\n"
752        "   or: %s help           -- for a list of common commands\n"
753        "   or: %s help COMMAND   -- for help with the named command\n",
754        g.argv[0], g.argv[0], g.argv[0]);
755     fossil_print(
756       "\nCommands and filenames may be passed on to fossil from a file\n"
757       "by using:\n"
758       "\n    %s --args FILENAME ...\n",
759       g.argv[0]
760     );
761     fossil_print(
762       "\nEach line of the file is assumed to be a filename unless it starts\n"
763       "with '-' and contains a space, in which case it is assumed to be\n"
764       "another flag and is treated as such. --args FILENAME may be used\n"
765       "in conjunction with any other flags.\n");
766     fossil_exit(1);
767   }else{
768     const char *zChdir = find_option("chdir",0,1);
769     g.isHTTP = 0;
770     g.rcvid = 0;
771     g.fQuiet = find_option("quiet", 0, 0)!=0;
772     g.fSqlTrace = find_option("sqltrace", 0, 0)!=0;
773     g.fSqlStats = find_option("sqlstats", 0, 0)!=0;
774     g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
775     g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
776     g.fCgiTrace = find_option("cgitrace", 0, 0)!=0;
777     g.fSshClient = 0;
778     g.zSshCmd = 0;
779     if( g.fSqlTrace ) g.fSqlStats = 1;
780 #ifdef FOSSIL_ENABLE_JSON
781     g.json.preserveRc = find_option("json-preserve-rc", 0, 0)!=0;
782 #endif
783     g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
784 #ifdef FOSSIL_ENABLE_TH1_HOOKS
785     g.fNoThHook = find_option("no-th-hook", 0, 0)!=0;
786 #endif
787     g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|
788                   g.fHttpTrace|g.fCgiTrace;
789     g.zHttpAuth = 0;
790     g.zLogin = find_option("user", "U", 1);
791     g.zSSLIdentity = find_option("ssl-identity", 0, 1);
792     g.zErrlog = find_option("errorlog", 0, 1);
793     fossil_init_flags_from_options();
794     if( find_option("utc",0,0) ) g.fTimeFormat = 1;
795     if( find_option("localtime",0,0) ) g.fTimeFormat = 2;
796     if( zChdir && file_chdir(zChdir, 0) ){
797       fossil_fatal("unable to change directories to %s", zChdir);
798     }
799 #if defined(_WIN32) && USE_SEE
800     {
801       g.zPidKey = find_option("usepidkey",0,1);
802       if( g.zPidKey ){
803         DWORD processId = 0;
804         LPVOID pAddress = NULL;
805         SIZE_T nSize = 0;
806         parse_pid_key_value(g.zPidKey, &processId, &pAddress, &nSize);
807         db_read_saved_encryption_key_from_process(processId, pAddress, nSize);
808       }else{
809         const char *zSeeDbConfig = find_option("seedbcfg",0,1);
810         if( !zSeeDbConfig ){
811           zSeeDbConfig = fossil_getenv("FOSSIL_SEE_DB_CONFIG");
812         }
813         if( zSeeDbConfig ){
814           db_read_saved_encryption_key_from_process_via_th1(zSeeDbConfig);
815         }
816       }
817     }
818 #endif
819     if( find_option("help",0,0)!=0 ){
820       /* If --help is found anywhere on the command line, translate the command
821        * to "fossil help cmdname" where "cmdname" is the first argument that
822        * does not begin with a "-" character.  If all arguments start with "-",
823        * translate to "fossil help argv[1] argv[2]...". */
824       int i, nNewArgc;
825       char **zNewArgv = fossil_malloc( sizeof(char*)*(g.argc+3) );
826       zNewArgv[0] = g.argv[0];
827       zNewArgv[1] = "help";
828       zNewArgv[2] = "-c";
829       for(i=1; i<g.argc; i++){
830         if( g.argv[i][0]!='-' ){
831           nNewArgc = 4;
832           zNewArgv[3] = g.argv[i];
833           zNewArgv[4] = 0;
834           break;
835         }
836       }
837       if( i==g.argc ){
838         for(i=1; i<g.argc; i++) zNewArgv[i+1] = g.argv[i];
839         nNewArgc = g.argc+1;
840         zNewArgv[i+1] = 0;
841       }
842       g.argc = nNewArgc;
843       g.argv = zNewArgv;
844 #if 0
845     }else if( g.argc==2 && file_is_repository(g.argv[1]) ){
846       char **zNewArgv = fossil_malloc( sizeof(char*)*4 );
847       zNewArgv[0] = g.argv[0];
848       zNewArgv[1] = "ui";
849       zNewArgv[2] = g.argv[1];
850       zNewArgv[3] = 0;
851       g.argc = 3;
852       g.argv = zNewArgv;
853 #endif
854     }
855     zCmdName = g.argv[1];
856   }
857 #ifndef _WIN32
858   /* There is a bug in stunnel4 in which it sometimes starts up client
859   ** processes without first opening file descriptor 2 (standard error).
860   ** If this happens, and a subsequent open() of a database returns file
861   ** descriptor 2, and then an assert() fires and writes on fd 2, that
862   ** can corrupt the data file.  To avoid this problem, make sure open()
863   ** will never return file descriptor 2 or less. */
864   if( !is_valid_fd(2) ){
865     int nTry = 0;
866     int fd = 0;
867     int x = 0;
868     do{
869       fd = open("/dev/null",O_WRONLY);
870       if( fd>=2 ) break;
871       if( fd<0 ) x = errno;
872     }while( nTry++ < 2 );
873     if( fd<2 ){
874       g.cgiOutput = 1;
875       g.httpOut = stdout;
876       g.fullHttpReply = !g.isHTTP;
877       fossil_panic("file descriptor 2 is not open. (fd=%d, errno=%d)",
878                    fd, x);
879     }
880   }
881 #endif
882   g.zCmdName = zCmdName;
883   rc = dispatch_name_search(zCmdName, CMDFLAG_COMMAND|CMDFLAG_PREFIX, &pCmd);
884   if( rc==1 && g.argc==2 && file_is_repository(g.argv[1]) ){
885     /* If the command-line is "fossil ABC" and "ABC" is no a valid command,
886     ** but "ABC" is the name of a repository file, make the command be
887     ** "fossil ui ABC" instead.
888     */
889     char **zNewArgv = fossil_malloc( sizeof(char*)*4 );
890     zNewArgv[0] = g.argv[0];
891     zNewArgv[1] = "ui";
892     zNewArgv[2] = g.argv[1];
893     zNewArgv[3] = 0;
894     g.argc = 3;
895     g.argv = zNewArgv;
896     g.zCmdName = zCmdName = "ui";
897     rc = dispatch_name_search(zCmdName, CMDFLAG_COMMAND|CMDFLAG_PREFIX, &pCmd);
898   }
899   if( rc==1 ){
900 #ifdef FOSSIL_ENABLE_TH1_HOOKS
901     if( !g.isHTTP && !g.fNoThHook ){
902       rc = Th_CommandHook(zCmdName, 0);
903     }else{
904       rc = TH_OK;
905     }
906     if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
907       if( rc==TH_OK || rc==TH_RETURN ){
908 #endif
909         fossil_fatal("%s: unknown command: %s\n"
910                      "%s: use \"help\" for more information",
911                      g.argv[0], zCmdName, g.argv[0]);
912 #ifdef FOSSIL_ENABLE_TH1_HOOKS
913       }
914       if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
915         Th_CommandNotify(zCmdName, 0);
916       }
917     }
918     fossil_exit(0);
919 #endif
920   }else if( rc==2 ){
921     Blob couldbe;
922     blob_init(&couldbe,0,0);
923     dispatch_matching_names(zCmdName, &couldbe);
924     fossil_print("%s: ambiguous command prefix: %s\n"
925                  "%s: could be any of:%s\n"
926                  "%s: use \"help\" for more information\n",
927                  g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]);
928     fossil_exit(1);
929   }
930 #ifdef FOSSIL_ENABLE_JSON
931   else if( rc==0 && strcmp("json",pCmd->zName)==0 ){
932     g.json.isJsonMode = 1;
933   }else{
934     assert(!g.json.isJsonMode && "JSON-mode misconfiguration.");
935   }
936 #endif
937   atexit( fossil_atexit );
938 #ifdef FOSSIL_ENABLE_TH1_HOOKS
939   /*
940   ** The TH1 return codes from the hook will be handled as follows:
941   **
942   ** TH_OK: The xFunc() and the TH1 notification will both be executed.
943   **
944   ** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be
945   **           skipped.  If the xFunc() is being hooked, the error message
946   **           will be emitted.
947   **
948   ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped.
949   **
950   ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be
951   **            skipped.
952   **
953   ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be
954   **              executed.
955   */
956   if( !g.isHTTP && !g.fNoThHook ){
957     rc = Th_CommandHook(pCmd->zName, pCmd->eCmdFlags);
958   }else{
959     rc = TH_OK;
960   }
961   if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
962     if( rc==TH_OK || rc==TH_RETURN ){
963 #endif
964       g.zPhase = pCmd->zName;
965       pCmd->xFunc();
966       g.zPhase = "shutdown";
967 #ifdef FOSSIL_ENABLE_TH1_HOOKS
968     }
969     if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
970       Th_CommandNotify(pCmd->zName, pCmd->eCmdFlags);
971     }
972   }
973 #endif
974   fossil_exit(0);
975   /*NOT_REACHED*/
976   return 0;
977 }
978 
979 /*
980 ** Print a usage comment and quit
981 */
usage(const char * zFormat)982 void usage(const char *zFormat){
983   fossil_fatal("Usage: %s %s %s", g.argv[0], g.argv[1], zFormat);
984 }
985 
986 /*
987 ** Remove n elements from g.argv beginning with the i-th element.
988 */
remove_from_argv(int i,int n)989 static void remove_from_argv(int i, int n){
990   int j;
991   for(j=i+n; j<g.argc; i++, j++){
992     g.argv[i] = g.argv[j];
993   }
994   g.argc = i;
995 }
996 
997 
998 /*
999 ** Look for a command-line option.  If present, remove it from the
1000 ** argument list and return a pointer to either the flag's name (if
1001 ** hasArg==0), sans leading - or --, or its value (if hasArg==1).
1002 ** Return NULL if the flag is not found.
1003 **
1004 ** zLong is the "long" form of the flag and zShort is the
1005 ** short/abbreviated form (typically a single letter, but it may be
1006 ** longer). zLong must not be NULL, but zShort may be.
1007 **
1008 ** hasArg==0 means the option is a flag.  It is either present or not.
1009 ** hasArg==1 means the option has an argument, in which case a pointer
1010 ** to the argument's value is returned. For zLong, a flag value (if
1011 ** hasValue==1) may either be in the form (--flag=value) or (--flag
1012 ** value). For zShort, only the latter form is accepted.
1013 **
1014 ** If a standalone argument of "--" is encountered in the argument
1015 ** list while searching for the given flag(s), this routine stops
1016 ** searching and NULL is returned.
1017 */
find_option(const char * zLong,const char * zShort,int hasArg)1018 const char *find_option(const char *zLong, const char *zShort, int hasArg){
1019   int i;
1020   int nLong;
1021   const char *zReturn = 0;
1022   assert( hasArg==0 || hasArg==1 );
1023   nLong = strlen(zLong);
1024   for(i=1; i<g.argc; i++){
1025     char *z;
1026     if( i+hasArg >= g.argc ) break;
1027     z = g.argv[i];
1028     if( z[0]!='-' ) continue;
1029     z++;
1030     if( z[0]=='-' ){
1031       if( z[1]==0 ){
1032         /* Stop processing at "--" without consuming it.
1033            verify_all_options() will consume this flag. */
1034         break;
1035       }
1036       z++;
1037     }
1038     if( strncmp(z,zLong,nLong)==0 ){
1039       if( hasArg && z[nLong]=='=' ){
1040         zReturn = &z[nLong+1];
1041         remove_from_argv(i, 1);
1042         break;
1043       }else if( z[nLong]==0 ){
1044         zReturn = g.argv[i+hasArg];
1045         remove_from_argv(i, 1+hasArg);
1046         break;
1047       }
1048     }else if( fossil_strcmp(z,zShort)==0 ){
1049       zReturn = g.argv[i+hasArg];
1050       remove_from_argv(i, 1+hasArg);
1051       break;
1052     }
1053   }
1054   return zReturn;
1055 }
1056 
1057 /* Return true if zOption exists in the command-line arguments,
1058 ** but do not remove it from the list or otherwise process it.
1059 */
has_option(const char * zOption)1060 int has_option(const char *zOption){
1061   int i;
1062   int n = (int)strlen(zOption);
1063   for(i=1; i<g.argc; i++){
1064     char *z = g.argv[i];
1065     if( z[0]!='-' ) continue;
1066     z++;
1067     if( z[0]=='-' ){
1068       if( z[1]==0 ){
1069         /* Stop processing at "--" */
1070         break;
1071       }
1072       z++;
1073     }
1074     if( strncmp(z,zOption,n)==0 && (z[n]==0 || z[n]=='=') ) return 1;
1075   }
1076   return 0;
1077 }
1078 
1079 /*
1080 ** Look for multiple occurrences of a command-line option with the
1081 ** corresponding argument.
1082 **
1083 ** Return a malloc allocated array of pointers to the arguments.
1084 **
1085 ** pnUsedArgs is used to store the number of matched arguments.
1086 **
1087 ** Caller is responsible for freeing allocated memory by passing the
1088 ** head of the array (not each entry) to fossil_free(). (The
1089 ** individual entries have the same lifetime as values returned from
1090 ** find_option().)
1091 */
find_repeatable_option(const char * zLong,const char * zShort,int * pnUsedArgs)1092 const char **find_repeatable_option(
1093   const char *zLong,
1094   const char *zShort,
1095   int *pnUsedArgs
1096 ){
1097   const char *zOption;
1098   const char **pzArgs = 0;
1099   int nAllocArgs = 0;
1100   int nUsedArgs = 0;
1101 
1102   while( (zOption = find_option(zLong, zShort, 1))!=0 ){
1103     if( pzArgs==0 && nAllocArgs==0 ){
1104       nAllocArgs = 1;
1105       pzArgs = fossil_malloc( nAllocArgs*sizeof(pzArgs[0]) );
1106     }else if( nAllocArgs<=nUsedArgs ){
1107       nAllocArgs = nAllocArgs*2;
1108       pzArgs = fossil_realloc( (void *)pzArgs, nAllocArgs*sizeof(pzArgs[0]) );
1109     }
1110     pzArgs[nUsedArgs++] = zOption;
1111   }
1112   *pnUsedArgs = nUsedArgs;
1113   return pzArgs;
1114 }
1115 
1116 /*
1117 ** Look for a repository command-line option.  If present, [re-]cache it in
1118 ** the global state and return the new pointer, freeing any previous value.
1119 ** If absent and there is no cached value, return NULL.
1120 */
find_repository_option()1121 const char *find_repository_option(){
1122   const char *zRepository = find_option("repository", "R", 1);
1123   if( zRepository ){
1124     if( g.zRepositoryOption ) fossil_free(g.zRepositoryOption);
1125     g.zRepositoryOption = mprintf("%s", zRepository);
1126   }
1127   return g.zRepositoryOption;
1128 }
1129 
1130 /*
1131 ** Verify that there are no unprocessed command-line options.  If
1132 ** Any remaining command-line argument begins with "-" print
1133 ** an error message and quit.
1134 **
1135 ** Exception: if "--" is encountered, it is consumed from the argument
1136 ** list and this function immediately returns. The effect is to treat
1137 ** all arguments after "--" as non-flags (conventionally used to
1138 ** enable passing-in of filenames which start with a dash).
1139 **
1140 ** This function must normally only be called one time per app
1141 ** invokation. The exception is commands which process their
1142 ** arguments, call this to confirm that there are no extraneous flags,
1143 ** then modify the arguments list for forwarding to another
1144 ** (sub)command (which itself will call this to confirm its own
1145 ** arguments).
1146 */
verify_all_options(void)1147 void verify_all_options(void){
1148   int i;
1149   for(i=1; i<g.argc; i++){
1150     const char * arg = g.argv[i];
1151     if( arg[0]=='-' ){
1152       if( arg[1]=='-' && arg[2]==0 ){
1153         /* Remove "--" from the list and treat all following
1154         ** arguments as non-flags. */
1155         remove_from_argv(i, 1);
1156         break;
1157       }else if( arg[1]!=0 ){
1158         fossil_fatal(
1159           "unrecognized command-line option or missing argument: %s",
1160           arg);
1161       }
1162     }
1163   }
1164 }
1165 
1166 /*
1167 ** This function returns a human readable version string.
1168 */
get_version()1169 const char *get_version(){
1170   static const char version[] = RELEASE_VERSION " " MANIFEST_VERSION " "
1171                                 MANIFEST_DATE " UTC";
1172   return version;
1173 }
1174 
1175 /*
1176 ** This function populates a blob with version information.  It is used by
1177 ** the "version" command and "test-version" web page.  It assumes the blob
1178 ** passed to it is uninitialized; otherwise, it will leak memory.
1179 */
fossil_version_blob(Blob * pOut,int bVerbose)1180 void fossil_version_blob(
1181   Blob *pOut,                 /* Write the manifest here */
1182   int bVerbose                /* Non-zero for full information. */
1183 ){
1184 #if defined(FOSSIL_ENABLE_TCL)
1185   int rc;
1186   const char *zRc;
1187 #endif
1188   Stmt q;
1189   size_t pageSize = 0;
1190   blob_zero(pOut);
1191   blob_appendf(pOut, "This is fossil version %s\n", get_version());
1192   if( !bVerbose ) return;
1193   blob_appendf(pOut, "Compiled on %s %s using %s (%d-bit)\n",
1194                __DATE__, __TIME__, COMPILER_NAME, sizeof(void*)*8);
1195   blob_appendf(pOut, "Schema version %s\n", AUX_SCHEMA_MAX);
1196   fossil_get_page_size(&pageSize);
1197   blob_appendf(pOut, "Detected memory page size is %lu bytes\n",
1198                (unsigned long)pageSize);
1199 #if defined(FOSSIL_ENABLE_MINIZ)
1200   blob_appendf(pOut, "miniz %s, loaded %s\n", MZ_VERSION, mz_version());
1201 #else
1202   blob_appendf(pOut, "zlib %s, loaded %s\n", ZLIB_VERSION, zlibVersion());
1203 #endif
1204 #if FOSSIL_HARDENED_SHA1
1205   blob_appendf(pOut, "hardened-SHA1 by Marc Stevens and Dan Shumow\n");
1206 #endif
1207 #if defined(FOSSIL_ENABLE_SSL)
1208   blob_appendf(pOut, "SSL (%s)\n", SSLeay_version(SSLEAY_VERSION));
1209 #endif
1210 #if defined(FOSSIL_HAVE_FUSEFS)
1211   blob_appendf(pOut, "libfuse %s, loaded %s\n", fusefs_inc_version(),
1212                fusefs_lib_version());
1213 #endif
1214 #if defined(FOSSIL_DEBUG)
1215   blob_append(pOut, "FOSSIL_DEBUG\n", -1);
1216 #endif
1217 #if defined(FOSSIL_ENABLE_DELTA_CKSUM_TEST)
1218   blob_append(pOut, "FOSSIL_ENABLE_DELTA_CKSUM_TEST\n", -1);
1219 #endif
1220   blob_append(pOut, "FOSSIL_ENABLE_LEGACY_MV_RM\n", -1);
1221 #if defined(FOSSIL_ENABLE_EXEC_REL_PATHS)
1222   blob_append(pOut, "FOSSIL_ENABLE_EXEC_REL_PATHS\n", -1);
1223 #endif
1224 #if defined(FOSSIL_ENABLE_TH1_DOCS)
1225   blob_append(pOut, "FOSSIL_ENABLE_TH1_DOCS\n", -1);
1226 #endif
1227 #if defined(FOSSIL_ENABLE_TH1_HOOKS)
1228   blob_append(pOut, "FOSSIL_ENABLE_TH1_HOOKS\n", -1);
1229 #endif
1230 #if defined(FOSSIL_ENABLE_TCL)
1231   Th_FossilInit(TH_INIT_DEFAULT | TH_INIT_FORCE_TCL);
1232   rc = Th_Eval(g.interp, 0, "tclInvoke info patchlevel", -1);
1233   zRc = Th_ReturnCodeName(rc, 0);
1234   blob_appendf(pOut, "TCL (Tcl %s, loaded %s: %s)\n",
1235     TCL_PATCH_LEVEL, zRc, Th_GetResult(g.interp, 0)
1236   );
1237 #endif
1238 #if defined(USE_TCL_STUBS)
1239   blob_append(pOut, "USE_TCL_STUBS\n", -1);
1240 #endif
1241 #if defined(FOSSIL_ENABLE_TCL_STUBS)
1242   blob_append(pOut, "FOSSIL_TCL_STUBS\n", -1);
1243 #endif
1244 #if defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS)
1245   blob_append(pOut, "FOSSIL_ENABLE_TCL_PRIVATE_STUBS\n", -1);
1246 #endif
1247 #if defined(FOSSIL_ENABLE_JSON)
1248   blob_appendf(pOut, "JSON (API %s)\n", FOSSIL_JSON_API_VERSION);
1249 #endif
1250   blob_append(pOut, "MARKDOWN\n", -1);
1251 #if defined(BROKEN_MINGW_CMDLINE)
1252   blob_append(pOut, "MBCS_COMMAND_LINE\n", -1);
1253 #else
1254   blob_append(pOut, "UNICODE_COMMAND_LINE\n", -1);
1255 #endif
1256 #if defined(FOSSIL_DYNAMIC_BUILD)
1257   blob_append(pOut, "FOSSIL_DYNAMIC_BUILD\n", -1);
1258 #else
1259   blob_append(pOut, "FOSSIL_STATIC_BUILD\n", -1);
1260 #endif
1261 #if defined(HAVE_PLEDGE)
1262   blob_append(pOut, "HAVE_PLEDGE\n", -1);
1263 #endif
1264 #if defined(USE_MMAN_H)
1265   blob_append(pOut, "USE_MMAN_H\n", -1);
1266 #endif
1267 #if defined(USE_SEE)
1268   blob_append(pOut, "USE_SEE\n", -1);
1269 #endif
1270 #if defined(FOSSIL_ALLOW_OUT_OF_ORDER_DATES)
1271   blob_append(pOut, "FOSSIL_ALLOW_OUT_OF_ORDER_DATES\n");
1272 #endif
1273   blob_appendf(pOut, "SQLite %s %.30s\n", sqlite3_libversion(),
1274                sqlite3_sourceid());
1275   if( g.db==0 ) sqlite3_open(":memory:", &g.db);
1276   db_prepare(&q,
1277      "pragma compile_options");
1278   while( db_step(&q)==SQLITE_ROW ){
1279     const char *text = db_column_text(&q, 0);
1280     if( strncmp(text, "COMPILER", 8) ){
1281       blob_appendf(pOut, "SQLITE_%s\n", text);
1282     }
1283   }
1284   db_finalize(&q);
1285 }
1286 
1287 /*
1288 ** This function returns the user-agent string for Fossil, for
1289 ** use in HTTP(S) requests.
1290 */
get_user_agent()1291 const char *get_user_agent(){
1292   static const char version[] = "Fossil/" RELEASE_VERSION " (" MANIFEST_DATE
1293                                 " " MANIFEST_VERSION ")";
1294   return version;
1295 }
1296 
1297 
1298 /*
1299 ** COMMAND: version
1300 **
1301 ** Usage: %fossil version ?-v|--verbose?
1302 **
1303 ** Print the source code version number for the fossil executable.
1304 ** If the verbose option is specified, additional details will
1305 ** be output about what optional features this binary was compiled
1306 ** with
1307 */
version_cmd(void)1308 void version_cmd(void){
1309   Blob versionInfo;
1310   int verboseFlag = find_option("verbose","v",0)!=0;
1311 
1312   /* We should be done with options.. */
1313   verify_all_options();
1314   fossil_version_blob(&versionInfo, verboseFlag);
1315   fossil_print("%s", blob_str(&versionInfo));
1316 }
1317 
1318 
1319 /*
1320 ** WEBPAGE: version
1321 **
1322 ** Show the version information for Fossil.
1323 **
1324 ** Query parameters:
1325 **
1326 **    verbose       Show details
1327 */
test_version_page(void)1328 void test_version_page(void){
1329   Blob versionInfo;
1330   int verboseFlag;
1331 
1332   login_check_credentials();
1333   if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1334   verboseFlag = PD("verbose", 0) != 0;
1335   style_header("Version Information");
1336   style_submenu_element("Stat", "stat");
1337   fossil_version_blob(&versionInfo, verboseFlag);
1338   @ <pre>
1339   @ %h(blob_str(&versionInfo))
1340   @ </pre>
1341   style_finish_page();
1342 }
1343 
1344 
1345 /*
1346 ** Set the g.zBaseURL value to the full URL for the toplevel of
1347 ** the fossil tree.  Set g.zTop to g.zBaseURL without the
1348 ** leading "http://" and the host and port.
1349 **
1350 ** The g.zBaseURL is normally set based on HTTP_HOST and SCRIPT_NAME
1351 ** environment variables.  However, if zAltBase is not NULL then it
1352 ** is the argument to the --baseurl option command-line option and
1353 ** g.zBaseURL and g.zTop is set from that instead.
1354 */
set_base_url(const char * zAltBase)1355 void set_base_url(const char *zAltBase){
1356   int i;
1357   const char *zHost;
1358   const char *zMode;
1359   const char *zCur;
1360 
1361   if( g.zBaseURL!=0 ) return;
1362   if( zAltBase ){
1363     int i, n, c;
1364     g.zTop = g.zBaseURL = mprintf("%s", zAltBase);
1365     i = (int)strlen(g.zBaseURL);
1366     while( i>3 && g.zBaseURL[i-1]=='/' ){ i--; }
1367     g.zBaseURL[i] = 0;
1368     if( strncmp(g.zTop, "http://", 7)==0 ){
1369       /* it is HTTP, replace prefix with HTTPS. */
1370       g.zHttpsURL = mprintf("https://%s", &g.zTop[7]);
1371     }else if( strncmp(g.zTop, "https://", 8)==0 ){
1372       /* it is already HTTPS, use it. */
1373       g.zHttpsURL = mprintf("%s", g.zTop);
1374     }else{
1375       fossil_fatal("argument to --baseurl should be 'http://host/path'"
1376                    " or 'https://host/path'");
1377     }
1378     for(i=n=0; (c = g.zTop[i])!=0; i++){
1379       if( c=='/' ){
1380         n++;
1381         if( n==3 ){
1382           g.zTop += i;
1383           break;
1384         }
1385       }
1386     }
1387     if( n==2 ) g.zTop = "";
1388     if( g.zTop==g.zBaseURL ){
1389       fossil_fatal("argument to --baseurl should be 'http://host/path'"
1390                    " or 'https://host/path'");
1391     }
1392     if( g.zTop[1]==0 ) g.zTop++;
1393   }else{
1394     char *z;
1395     zHost = PD("HTTP_HOST","");
1396     z = fossil_strdup(zHost);
1397     for(i=0; z[i]; i++){
1398       if( z[i]<='Z' && z[i]>='A' ) z[i] += 'a' - 'A';
1399     }
1400     if( i>3 && z[i-1]=='0' && z[i-2]=='8' && z[i-3]==':' ) i -= 3;
1401     if( i && z[i-1]=='.' ) i--;
1402     z[i] = 0;
1403     zMode = PD("HTTPS","off");
1404     zCur = PD("SCRIPT_NAME","/");
1405     i = strlen(zCur);
1406     while( i>0 && zCur[i-1]=='/' ) i--;
1407     if( fossil_stricmp(zMode,"on")==0 ){
1408       g.zBaseURL = mprintf("https://%s%.*s", z, i, zCur);
1409       g.zTop = &g.zBaseURL[8+strlen(z)];
1410       g.zHttpsURL = g.zBaseURL;
1411     }else{
1412       g.zBaseURL = mprintf("http://%s%.*s", z, i, zCur);
1413       g.zTop = &g.zBaseURL[7+strlen(z)];
1414       g.zHttpsURL = mprintf("https://%s%.*s", z, i, zCur);
1415     }
1416     fossil_free(z);
1417   }
1418 
1419   /* Try to record the base URL as a CONFIG table entry with a name
1420   ** of the form:  "baseurl:BASE".  This keeps a record of how the
1421   ** the repository is used as a server, to help in answering questions
1422   ** like "where is the CGI script that references this repository?"
1423   **
1424   ** This is just a logging hint.  So don't worry if it cannot be done.
1425   ** Don't try this if the repository database is not writable, for
1426   ** example.
1427   **
1428   ** If g.useLocalauth is set, that (probably) means that we are running
1429   ** "fossil ui" and there is no point in logging those cases either.
1430   */
1431   if( db_is_writeable("repository") && !g.useLocalauth ){
1432     int nBase = (int)strlen(g.zBaseURL);
1433     char *zBase = g.zBaseURL;
1434     if( g.nExtraURL>0 && g.nExtraURL<nBase-6 ){
1435       zBase = fossil_strndup(g.zBaseURL, nBase - g.nExtraURL);
1436     }
1437     db_unprotect(PROTECT_CONFIG);
1438     if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", zBase)){
1439       db_multi_exec("INSERT INTO config(name,value,mtime)"
1440                     "VALUES('baseurl:%q',1,now())", zBase);
1441     }else{
1442       db_optional_sql("repository",
1443            "REPLACE INTO config(name,value,mtime)"
1444            "VALUES('baseurl:%q',1,now())", zBase
1445       );
1446     }
1447     db_protect_pop();
1448     if( zBase!=g.zBaseURL ) fossil_free(zBase);
1449   }
1450 }
1451 
1452 /*
1453 ** Send an HTTP redirect back to the designated Index Page.
1454 */
fossil_redirect_home(void)1455 NORETURN void fossil_redirect_home(void){
1456   cgi_redirectf("%R%s", db_get("index-page", "/index"));
1457 }
1458 
1459 /*
1460 ** If running as root, chroot to the directory containing the
1461 ** repository zRepo and then drop root privileges.  Return the
1462 ** new repository name.
1463 **
1464 ** zRepo might be a directory itself.  In that case chroot into
1465 ** the directory zRepo.
1466 **
1467 ** Assume the user-id and group-id of the repository, or if zRepo
1468 ** is a directory, of that directory.
1469 **
1470 ** The noJail flag means that the chroot jail is not entered.  But
1471 ** privileges are still lowered to that of the user-id and group-id
1472 ** of the repository file.
1473 */
enter_chroot_jail(char * zRepo,int noJail)1474 char *enter_chroot_jail(char *zRepo, int noJail){
1475 #if !defined(_WIN32)
1476   if( getuid()==0 ){
1477     int i;
1478     struct stat sStat;
1479     Blob dir;
1480     char *zDir;
1481     if( g.db!=0 ){
1482       db_close(1);
1483     }
1484 
1485     file_canonical_name(zRepo, &dir, 0);
1486     zDir = blob_str(&dir);
1487     if( !noJail ){
1488       if( file_isdir(zDir, ExtFILE)==1 ){
1489         if( file_chdir(zDir, 1) ){
1490           fossil_panic("unable to chroot into %s", zDir);
1491         }
1492         g.fJail = 1;
1493         zRepo = "/";
1494       }else{
1495         for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
1496         if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
1497         if( i>0 ){
1498           zDir[i] = 0;
1499           if( file_chdir(zDir, 1) ){
1500             fossil_fatal("unable to chroot into %s", zDir);
1501           }
1502           zDir[i] = '/';
1503         }
1504         zRepo = &zDir[i];
1505       }
1506     }
1507     if( stat(zRepo, &sStat)!=0 ){
1508       fossil_fatal("cannot stat() repository: %s", zRepo);
1509     }
1510     i = setgid(sStat.st_gid);
1511     i = i || setuid(sStat.st_uid);
1512     if(i){
1513       fossil_fatal("setgid/uid() failed with errno %d", errno);
1514     }
1515     if( g.db==0 && file_isfile(zRepo, ExtFILE) ){
1516       db_open_repository(zRepo);
1517     }
1518   }
1519 #endif
1520   return zRepo;
1521 }
1522 
1523 /*
1524 ** Called whenever a crash is encountered while processing a webpage.
1525 */
sigsegv_handler(int x)1526 void sigsegv_handler(int x){
1527 #if HAVE_BACKTRACE
1528   void *array[20];
1529   size_t size;
1530   char **strings;
1531   size_t i;
1532   Blob out;
1533   size = backtrace(array, sizeof(array)/sizeof(array[0]));
1534   strings = backtrace_symbols(array, size);
1535   blob_init(&out, 0, 0);
1536   blob_appendf(&out, "Segfault during %s", g.zPhase);
1537   for(i=0; i<size; i++){
1538     blob_appendf(&out, "\n(%d) %s", i, strings[i]);
1539   }
1540   fossil_panic("%s", blob_str(&out));
1541 #else
1542   fossil_panic("Segfault during %s", g.zPhase);
1543 #endif
1544   exit(1);
1545 }
1546 
1547 /*
1548 ** Called if a server gets a SIGPIPE.  This often happens when a client
1549 ** webbrowser opens a connection but never sends the HTTP request
1550 */
sigpipe_handler(int x)1551 void sigpipe_handler(int x){
1552 #ifndef _WIN32
1553   if( g.fAnyTrace ){
1554     fprintf(stderr,"/***** sigpipe received by subprocess %d ****\n", getpid());
1555   }
1556 #endif
1557   g.zPhase = "sigpipe shutdown";
1558   db_panic_close();
1559   exit(1);
1560 }
1561 
1562 /*
1563 ** Return true if it is appropriate to redirect requests to HTTPS.
1564 **
1565 ** Redirect to https is appropriate if all of the above are true:
1566 **    (1) The redirect-to-https flag has a valud of iLevel or greater.
1567 **    (2) The current connection is http, not https or ssh
1568 **    (3) The sslNotAvailable flag is clear
1569 */
fossil_wants_https(int iLevel)1570 int fossil_wants_https(int iLevel){
1571   if( g.sslNotAvailable ) return 0;
1572   if( db_get_int("redirect-to-https",0)<iLevel ) return 0;
1573   if( P("HTTPS")!=0 ) return 0;
1574   return 1;
1575 }
1576 
1577 /*
1578 ** Redirect to the equivalent HTTPS request if the current connection is
1579 ** insecure and if the redirect-to-https flag greater than or equal to
1580 ** iLevel.  iLevel is 1 for /login pages and 2 for every other page.
1581 */
fossil_redirect_to_https_if_needed(int iLevel)1582 int fossil_redirect_to_https_if_needed(int iLevel){
1583   if( fossil_wants_https(iLevel) ){
1584     const char *zQS = P("QUERY_STRING");
1585     char *zURL;
1586     if( zQS==0 || zQS[0]==0 ){
1587       zURL = mprintf("%s%T", g.zHttpsURL, P("PATH_INFO"));
1588     }else if( zQS[0]!=0 ){
1589       zURL = mprintf("%s%T?%s", g.zHttpsURL, P("PATH_INFO"), zQS);
1590     }
1591     cgi_redirect_with_status(zURL, 301, "Moved Permanently");
1592     return 1;
1593   }
1594   return 0;
1595 }
1596 
1597 /*
1598 ** Preconditions:
1599 **
1600 **  * Environment variables are set up according to the CGI standard.
1601 **
1602 ** If the repository is known, it has already been opened.  If unknown,
1603 ** then g.zRepositoryName holds the directory that contains the repository
1604 ** and the actual repository is taken from the first element of PATH_INFO.
1605 **
1606 ** Process the webpage specified by the PATH_INFO or REQUEST_URI
1607 ** environment variable.
1608 **
1609 ** If the repository is not known, then a search is done through the
1610 ** file hierarchy rooted at g.zRepositoryName for a suitable repository
1611 ** with a name of $prefix.fossil, where $prefix is any prefix of PATH_INFO.
1612 ** Or, if an ordinary file named $prefix is found, and $prefix matches
1613 ** pFileGlob and $prefix does not match "*.fossil*" and the mimetype of
1614 ** $prefix can be determined from its suffix, then the file $prefix is
1615 ** returned as static text.
1616 **
1617 ** If no suitable webpage is found, try to redirect to zNotFound.
1618 */
process_one_web_page(const char * zNotFound,Glob * pFileGlob,int allowRepoList)1619 static void process_one_web_page(
1620   const char *zNotFound,      /* Redirect here on a 404 if not NULL */
1621   Glob *pFileGlob,            /* Deliver static files matching */
1622   int allowRepoList           /* Send repo list for "/" URL */
1623 ){
1624   const char *zPathInfo = PD("PATH_INFO", "");
1625   char *zPath = NULL;
1626   int i;
1627   const CmdOrPage *pCmd = 0;
1628   const char *zBase = g.zRepositoryName;
1629 
1630   g.zPhase = "process_one_web_page";
1631 #if !defined(_WIN32)
1632   signal(SIGSEGV, sigsegv_handler);
1633 #endif
1634 
1635   /* Handle universal query parameters */
1636   if( PB("utc") ){
1637     g.fTimeFormat = 1;
1638   }else if( PB("localtime") ){
1639     g.fTimeFormat = 2;
1640   }
1641 #ifdef FOSSIL_ENABLE_JSON
1642   /*
1643   ** Ensure that JSON mode is set up if we're visiting /json, to allow
1644   ** us to customize some following behaviour (error handling and only
1645   ** process JSON-mode POST data if we're actually in a /json
1646   ** page). This is normally set up before this routine is called, but
1647   ** it looks like the ssh_request_loop() approach to dispatching
1648   ** might bypass that.
1649   */
1650   if( g.json.isJsonMode==0 && json_request_is_json_api(zPathInfo)!=0 ){
1651     g.json.isJsonMode = 1;
1652     json_bootstrap_early();
1653   }
1654 #endif
1655   /* If the repository has not been opened already, then find the
1656   ** repository based on the first element of PATH_INFO and open it.
1657   */
1658   if( !g.repositoryOpen ){
1659     char *zRepo;               /* Candidate repository name */
1660     char *zToFree = 0;         /* Malloced memory that needs to be freed */
1661     const char *zCleanRepo;    /* zRepo with surplus leading "/" removed */
1662     const char *zOldScript = PD("SCRIPT_NAME", "");  /* Original SCRIPT_NAME */
1663     char *zNewScript;          /* Revised SCRIPT_NAME after processing */
1664     int j, k;                  /* Loop variables */
1665     i64 szFile;                /* File size of the candidate repository */
1666 
1667     i = zPathInfo[0]!=0;
1668     if( fossil_strcmp(g.zRepositoryName, "/")==0 ){
1669       zBase++;
1670 #if defined(_WIN32) || defined(__CYGWIN__)
1671       if( sqlite3_strglob("/[a-zA-Z]:/*", zPathInfo)==0 ) i = 4;
1672 #endif
1673     }
1674     while( 1 ){
1675       while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; }
1676 
1677       /* The candidate repository name is some prefix of the PATH_INFO
1678       ** with ".fossil" appended */
1679       zRepo = zToFree = mprintf("%s%.*s.fossil",zBase,i,zPathInfo);
1680       if( g.fHttpTrace ){
1681         @ <!-- Looking for repository named "%h(zRepo)" -->
1682         fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo);
1683       }
1684 
1685 
1686       /* For safety -- to prevent an attacker from accessing arbitrary disk
1687       ** files by sending a maliciously crafted request URI to a public
1688       ** server -- make sure the repository basename contains no
1689       ** characters other than alphanumerics, "/", "_", "-", and ".", and
1690       ** that "-" never occurs immediately after a "/" and that "." is always
1691       ** surrounded by two alphanumerics.  Any character that does not
1692       ** satisfy these constraints is converted into "_".
1693       */
1694       szFile = 0;
1695       for(j=strlen(zBase)+1, k=0; zRepo[j] && k<i-1; j++, k++){
1696         char c = zRepo[j];
1697         if( fossil_isalnum(c) ) continue;
1698 #if defined(_WIN32) || defined(__CYGWIN__)
1699         /* Allow names to begin with "/X:/" on windows */
1700         if( c==':' && j==2 && sqlite3_strglob("/[a-zA-Z]:/*", zRepo)==0 ){
1701           continue;
1702         }
1703 #endif
1704         if( c=='/' ) continue;
1705         if( c=='_' ) continue;
1706         if( c=='-' && zRepo[j-1]!='/' ) continue;
1707         if( c=='.' && fossil_isalnum(zRepo[j-1]) && fossil_isalnum(zRepo[j+1])){
1708           continue;
1709         }
1710         /* If we reach this point, it means that the request URI contains
1711         ** an illegal character or character combination.  Provoke a
1712         ** "Not Found" error. */
1713         szFile = 1;
1714         if( g.fHttpTrace ){
1715           @ <!-- Unsafe pathname rejected: "%h(zRepo)" -->
1716           fprintf(stderr, "# unsafe pathname rejected: %s\n", zRepo);
1717         }
1718         break;
1719       }
1720 
1721       /* Check to see if a file name zRepo exists.  If a file named zRepo
1722       ** does not exist, szFile will become -1.  If the file does exist,
1723       ** then szFile will become zero (for an empty file) or positive.
1724       ** Special case:  Assume any file with a basename of ".fossil" does
1725       ** not exist.
1726       */
1727       zCleanRepo = file_cleanup_fullpath(zRepo);
1728       if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
1729         szFile = file_size(zCleanRepo, ExtFILE);
1730         if( g.fHttpTrace ){
1731           char zBuf[24];
1732           sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", szFile);
1733           @ <!-- file_size(%h(zCleanRepo)) is %s(zBuf) -->
1734           fprintf(stderr, "# file_size(%s) = %s\n", zCleanRepo, zBuf);
1735         }
1736       }
1737 
1738       /* If no file named by zRepo exists, remove the added ".fossil" suffix
1739       ** and check to see if there is a file or directory with the same
1740       ** name as the raw PATH_INFO text.
1741       */
1742       if( szFile<0 && i>0 ){
1743         const char *zMimetype;
1744         assert( fossil_strcmp(&zRepo[j], ".fossil")==0 );
1745         zRepo[j] = 0;  /* Remove the ".fossil" suffix */
1746 
1747         /* The PATH_INFO prefix seen so far is a valid directory.
1748         ** Continue the loop with the next element of the PATH_INFO */
1749         if( zPathInfo[i]=='/' && file_isdir(zCleanRepo, ExtFILE)==1 ){
1750           fossil_free(zToFree);
1751           i++;
1752           continue;
1753         }
1754 
1755         /* If zRepo is the name of an ordinary file that matches the
1756         ** "--file GLOB" pattern, then the CGI reply is the text of
1757         ** of the file.
1758         **
1759         ** For safety, do not allow any file whose name contains ".fossil"
1760         ** to be returned this way, to prevent complete repositories from
1761         ** being delivered accidently.  This is not intended to be a
1762         ** general-purpose web server.  The "--file GLOB" mechanism is
1763         ** designed to allow the delivery of a few static images or HTML
1764         ** pages.
1765         */
1766         if( pFileGlob!=0
1767          && file_isfile(zCleanRepo, ExtFILE)
1768          && glob_match(pFileGlob, file_cleanup_fullpath(zRepo))
1769          && sqlite3_strglob("*.fossil*",zRepo)!=0
1770          && (zMimetype = mimetype_from_name(zRepo))!=0
1771          && strcmp(zMimetype, "application/x-fossil-artifact")!=0
1772         ){
1773           Blob content;
1774           blob_read_from_file(&content, file_cleanup_fullpath(zRepo), ExtFILE);
1775           cgi_set_content_type(zMimetype);
1776           cgi_set_content(&content);
1777           cgi_reply();
1778           return;
1779         }
1780         zRepo[j] = '.';
1781       }
1782 
1783       /* If we reach this point, it means that the search of the PATH_INFO
1784       ** string is finished.  Either zRepo contains the name of the
1785       ** repository to be used, or else no repository could be found and
1786       ** some kind of error response is required.
1787       */
1788       if( szFile<1024 ){
1789         set_base_url(0);
1790         if( (zPathInfo[0]==0 || strcmp(zPathInfo,"/")==0)
1791                   && allowRepoList
1792                   && repo_list_page() ){
1793           /* Will return a list of repositories */
1794         }else if( zNotFound ){
1795           cgi_redirect(zNotFound);
1796         }else{
1797 #ifdef FOSSIL_ENABLE_JSON
1798           if(g.json.isJsonMode){
1799             json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
1800             return;
1801           }
1802 #endif
1803           @ <html><head>
1804           @ <meta name="viewport" \
1805           @ content="width=device-width, initial-scale=1.0">
1806           @ </head><body>
1807           @ <h1>Not Found</h1>
1808           @ </body>
1809           cgi_set_status(404, "Not Found");
1810           cgi_reply();
1811         }
1812         return;
1813       }
1814       break;
1815     }
1816 
1817     /* Add the repository name (without the ".fossil" suffix) to the end
1818     ** of SCRIPT_NAME and g.zTop and g.zBaseURL and remove the repository
1819     ** name from the beginning of PATH_INFO.
1820     */
1821     zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo);
1822     if( g.zTop ) g.zTop = mprintf("%R%.*s", i, zPathInfo);
1823     if( g.zBaseURL ) g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo);
1824     cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]);
1825     zPathInfo += i;
1826     cgi_replace_parameter("SCRIPT_NAME", zNewScript);
1827     db_open_repository(file_cleanup_fullpath(zRepo));
1828     if( g.fHttpTrace ){
1829       @ <!-- repository: "%h(zRepo)" -->
1830       @ <!-- translated PATH_INFO: "%h(zPathInfo)" -->
1831       @ <!-- translated SCRIPT_NAME: "%h(zNewScript)" -->
1832       fprintf(stderr,
1833           "# repository: [%s]\n"
1834           "# translated PATH_INFO = [%s]\n"
1835           "# translated SCRIPT_NAME = [%s]\n",
1836           zRepo, zPathInfo, zNewScript);
1837       if( g.zTop ){
1838         @ <!-- translated g.zTop: "%h(g.zTop)" -->
1839         fprintf(stderr, "# translated g.zTop = [%s]\n", g.zTop);
1840       }
1841       if( g.zBaseURL ){
1842         @ <!-- translated g.zBaseURL: "%h(g.zBaseURL)" -->
1843         fprintf(stderr, "# translated g.zBaseURL = [%s]\n", g.zBaseURL);
1844       }
1845     }
1846   }
1847 
1848   /* At this point, the appropriate repository database file will have
1849   ** been opened.
1850   */
1851 
1852 
1853   /*
1854   ** Check to see if the first term of PATH_INFO specifies an
1855   ** alternative skin.  This will be the case if the first term of
1856   ** PATH_INFO begins with "draftN/" where N is an integer between 1
1857   ** and 9. If so, activate the skin associated with that draft.
1858   */
1859   if( zPathInfo && strncmp(zPathInfo,"/draft",6)==0
1860    && zPathInfo[6]>='1' && zPathInfo[6]<='9'
1861    && (zPathInfo[7]=='/' || zPathInfo[7]==0)
1862   ){
1863     int iSkin = zPathInfo[6] - '0';
1864     char *zNewScript;
1865     skin_use_draft(iSkin);
1866     zNewScript = mprintf("%T/draft%d", P("SCRIPT_NAME"), iSkin);
1867     if( g.zTop ) g.zTop = mprintf("%R/draft%d", iSkin);
1868     if( g.zBaseURL ) g.zBaseURL = mprintf("%s/draft%d", g.zBaseURL, iSkin);
1869     zPathInfo += 7;
1870     g.nExtraURL += 7;
1871     cgi_replace_parameter("PATH_INFO", zPathInfo);
1872     cgi_replace_parameter("SCRIPT_NAME", zNewScript);
1873     etag_cancel();
1874   }
1875 
1876   /* If the content type is application/x-fossil or
1877   ** application/x-fossil-debug, then a sync/push/pull/clone is
1878   ** desired, so default the PATH_INFO to /xfer
1879   */
1880   if( g.zContentType &&
1881       strncmp(g.zContentType, "application/x-fossil", 20)==0 ){
1882     /* Special case:  If the content mimetype shows that it is "fossil sync"
1883     ** payload, then pretend that the PATH_INFO is /xfer so that we always
1884     ** invoke the sync page. */
1885     zPathInfo = "/xfer";
1886   }
1887 
1888   /* Use the first element of PATH_INFO as the page name
1889   ** and deliver the appropriate page back to the user.
1890   */
1891   set_base_url(0);
1892   if( fossil_redirect_to_https_if_needed(2) ) return;
1893   if( zPathInfo==0 || zPathInfo[0]==0
1894       || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
1895     /* Second special case: If the PATH_INFO is blank, issue a redirect to
1896     ** the home page identified by the "index-page" setting in the repository
1897     ** CONFIG table, to "/index" if there no "index-page" setting. */
1898 #ifdef FOSSIL_ENABLE_JSON
1899     if(g.json.isJsonMode){
1900       json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
1901       fossil_exit(0);
1902     }
1903 #endif
1904     fossil_redirect_home() /*does not return*/;
1905   }else{
1906     zPath = mprintf("%s", zPathInfo);
1907   }
1908 
1909   /* Make g.zPath point to the first element of the path.  Make
1910   ** g.zExtra point to everything past that point.
1911   */
1912   while(1){
1913     g.zPath = &zPath[1];
1914     for(i=1; zPath[i] && zPath[i]!='/'; i++){}
1915     if( zPath[i]=='/' ){
1916       zPath[i] = 0;
1917       g.zExtra = &zPath[i+1];
1918     }else{
1919       g.zExtra = 0;
1920     }
1921     break;
1922   }
1923   if( g.zExtra ){
1924     /* CGI parameters get this treatment elsewhere, but places like getfile
1925     ** will use g.zExtra directly.
1926     ** Reminder: the login mechanism uses 'name' differently, and may
1927     ** eventually have a problem/collision with this.
1928     **
1929     ** Disabled by stephan when running in JSON mode because this
1930     ** particular parameter name is very common and i have had no end
1931     ** of grief with this handling. The JSON API never relies on the
1932     ** handling below, and by disabling it in JSON mode I can remove
1933     ** lots of special-case handling in several JSON handlers.
1934     */
1935 #ifdef FOSSIL_ENABLE_JSON
1936     if(g.json.isJsonMode==0){
1937 #endif
1938       dehttpize(g.zExtra);
1939       cgi_set_parameter_nocopy("name", g.zExtra, 1);
1940 #ifdef FOSSIL_ENABLE_JSON
1941     }
1942 #endif
1943   }
1944 
1945   /* Locate the method specified by the path and execute the function
1946   ** that implements that method.
1947   */
1948   if( dispatch_name_search(g.zPath-1, CMDFLAG_WEBPAGE, &pCmd)
1949    && dispatch_alias(g.zPath-1, &pCmd)
1950   ){
1951 #ifdef FOSSIL_ENABLE_JSON
1952     if(g.json.isJsonMode!=0){
1953       json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,0);
1954     }else
1955 #endif
1956     {
1957 #ifdef FOSSIL_ENABLE_TH1_HOOKS
1958       int rc;
1959       if( !g.fNoThHook ){
1960         rc = Th_WebpageHook(g.zPath, 0);
1961       }else{
1962         rc = TH_OK;
1963       }
1964       if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
1965         if( rc==TH_OK || rc==TH_RETURN ){
1966 #endif
1967           cgi_set_status(404,"Not Found");
1968           @ <h1>Not Found</h1>
1969           @ <p>Page not found: %h(g.zPath)</p>
1970 #ifdef FOSSIL_ENABLE_TH1_HOOKS
1971         }
1972         if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
1973           Th_WebpageNotify(g.zPath, 0);
1974         }
1975       }
1976 #endif
1977     }
1978   }else if( pCmd->xFunc!=page_xfer && db_schema_is_outofdate() ){
1979 #ifdef FOSSIL_ENABLE_JSON
1980     if(g.json.isJsonMode!=0){
1981       json_err(FSL_JSON_E_DB_NEEDS_REBUILD,NULL,0);
1982     }else
1983 #endif
1984     {
1985       @ <h1>Server Configuration Error</h1>
1986       @ <p>The database schema on the server is out-of-date.  Please ask
1987       @ the administrator to run <b>fossil rebuild</b>.</p>
1988     }
1989   }else{
1990 #ifdef FOSSIL_ENABLE_JSON
1991     static int jsonOnce = 0;
1992     if( jsonOnce==0 && g.json.isJsonMode!=0 ){
1993       assert(json_is_bootstrapped_early());
1994       json_bootstrap_late();
1995       jsonOnce = 1;
1996     }
1997 #endif
1998     if( (pCmd->eCmdFlags & CMDFLAG_RAWCONTENT)==0 ){
1999       cgi_decode_post_parameters();
2000     }
2001     if( g.fCgiTrace ){
2002       fossil_trace("######## Calling %s #########\n", pCmd->zName);
2003       cgi_print_all(1, 1);
2004     }
2005 #ifdef FOSSIL_ENABLE_TH1_HOOKS
2006     {
2007       /*
2008       ** The TH1 return codes from the hook will be handled as follows:
2009       **
2010       ** TH_OK: The xFunc() and the TH1 notification will both be executed.
2011       **
2012       ** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be
2013       **           skipped.  If the xFunc() is being hooked, the error message
2014       **           will be emitted.
2015       **
2016       ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped.
2017       **
2018       ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be
2019       **            skipped.
2020       **
2021       ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be
2022       **              executed.
2023       */
2024       int rc;
2025       if( !g.fNoThHook ){
2026         rc = Th_WebpageHook(pCmd->zName+1, pCmd->eCmdFlags);
2027       }else{
2028         rc = TH_OK;
2029       }
2030       if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
2031         if( rc==TH_OK || rc==TH_RETURN ){
2032 #endif
2033           g.zPhase = pCmd->zName;
2034           pCmd->xFunc();
2035 #ifdef FOSSIL_ENABLE_TH1_HOOKS
2036         }
2037         if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
2038           Th_WebpageNotify(pCmd->zName+1, pCmd->eCmdFlags);
2039         }
2040       }
2041     }
2042 #endif
2043   }
2044 
2045   /* Return the result.
2046   */
2047   g.zPhase = "web-page reply";
2048   cgi_reply();
2049 }
2050 
2051 /* If the CGI program contains one or more lines of the form
2052 **
2053 **    redirect:  repository-filename  http://hostname/path/%s
2054 **
2055 ** then control jumps here.  Search each repository for an artifact ID
2056 ** or ticket ID that matches the "name" query parameter.  If there is
2057 ** no "name" query parameter, use PATH_INFO instead.  If a match is
2058 ** found, redirect to the corresponding URL.  Substitute "%s" in the
2059 ** URL with the value of the name query parameter before the redirect.
2060 **
2061 ** If there is a line of the form:
2062 **
2063 **    redirect: * URL
2064 **
2065 ** Then a redirect is made to URL if no match is found.  If URL contains
2066 ** "%s" then substitute the "name" query parameter.  If REPO is "*" and
2067 ** URL does not contains "%s" and does not contain "?" then append
2068 ** PATH_INFO and QUERY_STRING to the URL prior to the redirect.
2069 **
2070 ** If no matches are found and if there is no "*" entry, then generate
2071 ** a primitive error message.
2072 **
2073 ** USE CASES:
2074 **
2075 ** (1)  Suppose you have two related projects projA and projB.  You can
2076 **      use this feature to set up an /info page that covers both
2077 **      projects.
2078 **
2079 **          redirect: /fossils/projA.fossil /proj-a/info/%s
2080 **          redirect: /fossils/projB.fossil /proj-b/info/%s
2081 **
2082 **      Then visits to the /info/HASH page will redirect to the
2083 **      first project that contains that hash.
2084 **
2085 ** (2)  Use the "*" form for to redirect legacy URLs.  On the Fossil
2086 **      website we have an CGI at http://fossil.com/index.html (note
2087 **      ".com" instead of ".org") that looks like this:
2088 **
2089 **          #!/usr/bin/fossil
2090 **          redirect: * https://fossil-scm.org/home
2091 **
2092 **      Thus requests to the .com website redirect to the .org website.
2093 */
redirect_web_page(int nRedirect,char ** azRedirect)2094 static void redirect_web_page(int nRedirect, char **azRedirect){
2095   int i;                             /* Loop counter */
2096   const char *zNotFound = 0;         /* Not found URL */
2097   const char *zName = P("name");
2098   set_base_url(0);
2099   if( zName==0 ){
2100     zName = P("PATH_INFO");
2101     if( zName && zName[0]=='/' ) zName++;
2102   }
2103   if( zName ){
2104     for(i=0; i<nRedirect; i++){
2105       if( fossil_strcmp(azRedirect[i*2],"*")==0 ){
2106         zNotFound = azRedirect[i*2+1];
2107         continue;
2108       }else if( validate16(zName, strlen(zName)) ){
2109         db_open_repository(azRedirect[i*2]);
2110         if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'", zName) ||
2111             db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'",zName) ){
2112           cgi_redirectf(azRedirect[i*2+1] /*works-like:"%s"*/, zName);
2113           return;
2114         }
2115         db_close(1);
2116       }
2117     }
2118   }
2119   if( zNotFound ){
2120     Blob to;
2121     const char *z;
2122     if( strstr(zNotFound, "%s") ){
2123       cgi_redirectf(zNotFound /*works-like:"%s"*/, zName);
2124     }
2125     if( strchr(zNotFound, '?') ){
2126       cgi_redirect(zNotFound);
2127     }
2128     blob_init(&to, zNotFound, -1);
2129     z = P("PATH_INFO");
2130     if( z && z[0]=='/' ) blob_append(&to, z, -1);
2131     z = P("QUERY_STRING");
2132     if( z && z[0]!=0 ) blob_appendf(&to, "?%s", z);
2133     cgi_redirect(blob_str(&to));
2134   }else{
2135     @ <html>
2136     @ <head><title>No Such Object</title></head>
2137     @ <body>
2138     @ <p>No such object: <b>%h(zName)</b></p>
2139     @ </body>
2140     cgi_reply();
2141   }
2142 }
2143 
2144 /*
2145 ** COMMAND: cgi*
2146 **
2147 ** Usage: %fossil ?cgi? FILE
2148 **
2149 ** This command causes Fossil to generate reply to a CGI request.
2150 **
2151 ** The FILE argument is the name of a control file that provides Fossil
2152 ** with important information such as where to find its repository.  In
2153 ** a typical CGI deployment, FILE is the name of the CGI script and will
2154 ** typically look something like this:
2155 **
2156 **      #!/usr/bin/fossil
2157 **      repository: /home/somebody/project.db
2158 **
2159 ** The command name, "cgi", may be omitted if the GATEWAY_INTERFACE
2160 ** environment variable is set to "CGI", which should always be the
2161 ** case for CGI scripts run by a webserver.  Fossil ignores any lines
2162 ** that begin with "#".
2163 **
2164 ** The following control lines are recognized:
2165 **
2166 **    repository: PATH         Name of the Fossil repository
2167 **
2168 **    directory:  PATH         Name of a directory containing many Fossil
2169 **                             repositories whose names all end with ".fossil".
2170 **                             There should only be one of "repository:"
2171 **                             or "directory:"
2172 **
2173 **    notfound: URL            When in "directory:" mode, redirect to
2174 **                             URL if no suitable repository is found.
2175 **
2176 **    repolist                 When in "directory:" mode, display a page
2177 **                             showing a list of available repositories if
2178 **                             the URL is "/".
2179 **
2180 **    localauth                Grant administrator privileges to connections
2181 **                             from 127.0.0.1 or ::1.
2182 **
2183 **    skin: LABEL              Use the built-in skin called LABEL rather than
2184 **                             the default.  If there are no skins called LABEL
2185 **                             then this line is a no-op.
2186 **
2187 **    files: GLOBLIST          GLOBLIST is a comma-separated list of GLOB
2188 **                             patterns that specify files that can be
2189 **                             returned verbatim.  This feature allows Fossil
2190 **                             to act as a web server returning static
2191 **                             content.
2192 **
2193 **    setenv: NAME VALUE       Set environment variable NAME to VALUE.  Or
2194 **                             if VALUE is omitted, unset NAME.
2195 **
2196 **    HOME: PATH               Shorthand for "setenv: HOME PATH"
2197 **
2198 **    cgi-debug: FILE          Causing debugging information to be written
2199 **                             into FILE.
2200 **
2201 **    errorlog: FILE           Warnings, errors, and panics written to FILE.
2202 **
2203 **    timeout: SECONDS         Do not run for longer than SECONDS.  The default
2204 **                             timeout is FOSSIL_DEFAULT_TIMEOUT (600) seconds.
2205 **
2206 **    extroot: DIR             Directory that is the root of the sub-CGI tree
2207 **                             on the /ext page.
2208 **
2209 **    redirect: REPO URL       Extract the "name" query parameter and search
2210 **                             REPO for a check-in or ticket that matches the
2211 **                             value of "name", then redirect to URL.  There
2212 **                             can be multiple "redirect:" lines that are
2213 **                             processed in order.  If the REPO is "*", then
2214 **                             an unconditional redirect to URL is taken.
2215 **
2216 **    jsmode: VALUE            Specifies the delivery mode for JavaScript
2217 **                             files. See the help text for the --jsmode
2218 **                             flag of the http command.
2219 **
2220 **    mainmenu: FILE           Override the mainmenu config setting with the
2221 **                             contents of the given file.
2222 **
2223 ** Most CGI files contain only a "repository:" line.  It is uncommon to
2224 ** use any other option.
2225 **
2226 ** See also: [[http]], [[server]], [[winsrv]]
2227 */
cmd_cgi(void)2228 void cmd_cgi(void){
2229   const char *zFile;
2230   const char *zNotFound = 0;
2231   char **azRedirect = 0;             /* List of repositories to redirect to */
2232   int nRedirect = 0;                 /* Number of entries in azRedirect */
2233   Glob *pFileGlob = 0;               /* Pattern for files */
2234   int allowRepoList = 0;             /* Allow lists of repository files */
2235   Blob config, line, key, value, value2;
2236   /* Initialize the CGI environment. */
2237   g.httpOut = stdout;
2238   g.httpIn = stdin;
2239   fossil_binary_mode(g.httpOut);
2240   fossil_binary_mode(g.httpIn);
2241   g.cgiOutput = 1;
2242   fossil_set_timeout(FOSSIL_DEFAULT_TIMEOUT);
2243   /* Find the name of the CGI control file */
2244   if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){
2245     zFile = g.argv[2];
2246   }else if( g.argc>=2 ){
2247     zFile = g.argv[1];
2248   }else{
2249     cgi_panic("No CGI control file specified");
2250   }
2251   /* Read and parse the CGI control file. */
2252   blob_read_from_file(&config, zFile, ExtFILE);
2253   while( blob_line(&config, &line) ){
2254     if( !blob_token(&line, &key) ) continue;
2255     if( blob_buffer(&key)[0]=='#' ) continue;
2256     if( blob_eq(&key, "repository:") && blob_tail(&line, &value) ){
2257       /* repository: FILENAME
2258       **
2259       ** The name of the Fossil repository to be served via CGI.  Most
2260       ** fossil CGI scripts have a single non-comment line that contains
2261       ** this one entry.
2262       */
2263       blob_trim(&value);
2264       db_open_repository(blob_str(&value));
2265       blob_reset(&value);
2266       continue;
2267     }
2268     if( blob_eq(&key, "directory:") && blob_token(&line, &value) ){
2269       /* directory: DIRECTORY
2270       **
2271       ** If repository: is omitted, then terms of the PATH_INFO cgi parameter
2272       ** are appended to DIRECTORY looking for a repository (whose name ends
2273       ** in ".fossil") or a file in "files:".
2274       */
2275       db_close(1);
2276       g.zRepositoryName = mprintf("%s", blob_str(&value));
2277       blob_reset(&value);
2278       continue;
2279     }
2280     if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){
2281       /* notfound: URL
2282       **
2283       ** If using directory: and no suitable repository or file is found,
2284       ** then redirect to URL.
2285       */
2286       zNotFound = mprintf("%s", blob_str(&value));
2287       blob_reset(&value);
2288       continue;
2289     }
2290     if( blob_eq(&key, "localauth") ){
2291       /* localauth
2292       **
2293       ** Grant "administrator" privileges to users connecting with HTTP
2294       ** from IP address 127.0.0.1.  Do not bother checking credentials.
2295       */
2296       g.useLocalauth = 1;
2297       continue;
2298     }
2299     if( blob_eq(&key, "repolist") ){
2300       /* repolist
2301       **
2302       ** If using "directory:" and the URL is "/" then generate a page
2303       ** showing a list of available repositories.
2304       */
2305       allowRepoList = 1;
2306       continue;
2307     }
2308     if( blob_eq(&key, "redirect:") && blob_token(&line, &value)
2309             && blob_token(&line, &value2) ){
2310       /* See the header comment on the redirect_web_page() function
2311       ** above for details. */
2312       nRedirect++;
2313       azRedirect = fossil_realloc(azRedirect, 2*nRedirect*sizeof(char*));
2314       azRedirect[nRedirect*2-2] = mprintf("%s", blob_str(&value));
2315       azRedirect[nRedirect*2-1] = mprintf("%s", blob_str(&value2));
2316       blob_reset(&value);
2317       blob_reset(&value2);
2318       continue;
2319     }
2320     if( blob_eq(&key, "files:") && blob_token(&line, &value) ){
2321       /* files: GLOBLIST
2322       **
2323       ** GLOBLIST is a comma-separated list of filename globs.  For
2324       ** example:  *.html,*.css,*.js
2325       **
2326       ** If the repository: line is omitted and then PATH_INFO is searched
2327       ** for files that match any of these GLOBs and if any such file is
2328       ** found it is returned verbatim.  This feature allows "fossil server"
2329       ** to function as a primitive web-server delivering arbitrary content.
2330       */
2331       pFileGlob = glob_create(blob_str(&value));
2332       blob_reset(&value);
2333       continue;
2334     }
2335     if( blob_eq(&key, "setenv:") && blob_token(&line, &value) ){
2336       /* setenv: NAME VALUE
2337       ** setenv: NAME
2338       **
2339       ** Sets environment variable NAME to VALUE.  If VALUE is omitted, then
2340       ** the environment variable is unset.
2341       */
2342       blob_token(&line,&value2);
2343       fossil_setenv(blob_str(&value), blob_str(&value2));
2344       blob_reset(&value);
2345       blob_reset(&value2);
2346       continue;
2347     }
2348     if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
2349       /* errorlog: FILENAME
2350       **
2351       ** Causes messages from warnings, errors, and panics to be appended
2352       ** to FILENAME.
2353       */
2354       g.zErrlog = mprintf("%s", blob_str(&value));
2355       blob_reset(&value);
2356       continue;
2357     }
2358     if( blob_eq(&key, "extroot:") && blob_token(&line, &value) ){
2359       /* extroot: DIRECTORY
2360       **
2361       ** Enables the /ext webpage to use sub-cgi rooted at DIRECTORY
2362       */
2363       g.zExtRoot = mprintf("%s", blob_str(&value));
2364       blob_reset(&value);
2365       continue;
2366     }
2367     if( blob_eq(&key, "timeout:") && blob_token(&line, &value) ){
2368       /* timeout: SECONDS
2369       **
2370       ** Set an alarm() that kills the process after SECONDS.  The
2371       ** default value is FOSSIL_DEFAULT_TIMEOUT (600) seconds.
2372       */
2373       fossil_set_timeout(atoi(blob_str(&value)));
2374       continue;
2375     }
2376     if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){
2377       /* HOME: VALUE
2378       **
2379       ** Set CGI parameter "HOME" to VALUE.  This is legacy.  Use
2380       ** setenv: instead.
2381       */
2382       cgi_setenv("HOME", blob_str(&value));
2383       blob_reset(&value);
2384       continue;
2385     }
2386     if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){
2387       /* skin: LABEL
2388       **
2389       ** Use one of the built-in skins defined by LABEL.  LABEL is the
2390       ** name of the subdirectory under the skins/ directory that holds
2391       ** the elements of the built-in skin.  If LABEL does not match,
2392       ** this directive is a silent no-op.
2393       */
2394       fossil_free(skin_use_alternative(blob_str(&value), 1));
2395       blob_reset(&value);
2396       continue;
2397     }
2398     if( blob_eq(&key, "jsmode:") && blob_token(&line, &value) ){
2399       /* jsmode: MODE
2400       **
2401       ** Change how JavaScript resources are delivered with each HTML
2402       ** page.  MODE is "inline" to put all JS inline, or "separate" to
2403       ** cause each JS file to be requested using a separate HTTP request,
2404       ** or "bundled" to have all JS files to be fetched with a single
2405       ** auxiliary HTTP request. Noting, however, that "single" might
2406       ** actually mean more than one, depending on the script-timing
2407       ** requirements of any given page.
2408       */
2409       builtin_set_js_delivery_mode(blob_str(&value),0);
2410       blob_reset(&value);
2411       continue;
2412     }
2413     if( blob_eq(&key, "mainmenu:") && blob_token(&line, &value) ){
2414       /* mainmenu: FILENAME
2415       **
2416       ** Use the contents of FILENAME as the value of the site's
2417       ** "mainmenu" setting, overriding the contents (for this
2418       ** request) of the db-side setting or the hard-coded default.
2419       */
2420       g.zMainMenuFile = mprintf("%s", blob_str(&value));
2421       blob_reset(&value);
2422       continue;
2423     }
2424     if( blob_eq(&key, "cgi-debug:") && blob_token(&line, &value) ){
2425       /* cgi-debug: FILENAME
2426       **
2427       ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go
2428       ** into FILENAME.  Useful for debugging CGI configuration problems.
2429       */
2430       char *zNow = cgi_iso8601_datestamp();
2431       cgi_load_environment();
2432       g.fDebug = fossil_fopen(blob_str(&value), "ab");
2433       blob_reset(&value);
2434       cgi_debug("-------- BEGIN cgi at %s --------\n", zNow);
2435       fossil_free(zNow);
2436       cgi_print_all(1,2);
2437       continue;
2438     }
2439   }
2440   blob_reset(&config);
2441   if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
2442     cgi_panic("Unable to find or open the project repository");
2443   }
2444   cgi_init();
2445   if( nRedirect ){
2446     redirect_web_page(nRedirect, azRedirect);
2447   }else{
2448     process_one_web_page(zNotFound, pFileGlob, allowRepoList);
2449   }
2450 }
2451 
2452 /*
2453 ** If g.argv[arg] exists then it is either the name of a repository
2454 ** that will be used by a server, or else it is a directory that
2455 ** contains multiple repositories that can be served.  If g.argv[arg]
2456 ** is a directory, the repositories it contains must be named
2457 ** "*.fossil".  If g.argv[arg] does not exist, then we must be within
2458 ** an open check-out and the repository to serve is the repository of
2459 ** that check-out.
2460 **
2461 ** Open the repository to be served if it is known.  If g.argv[arg] is
2462 ** a directory full of repositories, then set g.zRepositoryName to
2463 ** the name of that directory and the specific repository will be
2464 ** opened later by process_one_web_page() based on the content of
2465 ** the PATH_INFO variable.
2466 **
2467 ** If the fCreate flag is set, then create the repository if it
2468 ** does not already exist. Always use "auto" hash-policy in this case.
2469 */
find_server_repository(int arg,int fCreate)2470 static void find_server_repository(int arg, int fCreate){
2471   if( g.argc<=arg ){
2472     db_must_be_within_tree();
2473   }else{
2474     const char *zRepo = g.argv[arg];
2475     int isDir = file_isdir(zRepo, ExtFILE);
2476     if( isDir==1 ){
2477       g.zRepositoryName = mprintf("%s", zRepo);
2478       file_simplify_name(g.zRepositoryName, -1, 0);
2479     }else{
2480       if( isDir==0 && fCreate ){
2481         const char *zPassword;
2482         db_create_repository(zRepo);
2483         db_open_repository(zRepo);
2484         db_begin_transaction();
2485         g.eHashPolicy = HPOLICY_SHA3;
2486         db_set_int("hash-policy", HPOLICY_SHA3, 0);
2487         db_initial_setup(0, "now", g.zLogin);
2488         db_end_transaction(0);
2489         fossil_print("project-id: %s\n", db_get("project-code", 0));
2490         fossil_print("server-id:  %s\n", db_get("server-code", 0));
2491         zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
2492         fossil_print("admin-user: %s (initial password is \"%s\")\n",
2493                      g.zLogin, zPassword);
2494         cache_initialize();
2495         g.zLogin = 0;
2496         g.userUid = 0;
2497       }else{
2498         db_open_repository(zRepo);
2499       }
2500     }
2501   }
2502 }
2503 
2504 #if defined(_WIN32) && USE_SEE
2505 /*
2506 ** This function attempts to parse a string value in the following
2507 ** format:
2508 **
2509 **     "%lu:%p:%u"
2510 **
2511 ** There are three parts, which must be delimited by colons.  The
2512 ** first part is an unsigned long integer in base-10 (decimal) format.
2513 ** The second part is a numerical representation of a native pointer,
2514 ** in the appropriate implementation defined format.  The third part
2515 ** is an unsigned integer in base-10 (decimal) format.
2516 **
2517 ** If the specified value cannot be parsed, for any reason, a fatal
2518 ** error will be raised and the process will be terminated.
2519 */
parse_pid_key_value(const char * zPidKey,DWORD * pProcessId,LPVOID * ppAddress,SIZE_T * pnSize)2520 void parse_pid_key_value(
2521   const char *zPidKey, /* The value to be parsed. */
2522   DWORD *pProcessId,   /* The extracted process identifier. */
2523   LPVOID *ppAddress,   /* The extracted pointer value. */
2524   SIZE_T *pnSize       /* The extracted size value. */
2525 ){
2526   unsigned int nSize = 0;
2527   if( sscanf(zPidKey, "%lu:%p:%u", pProcessId, ppAddress, &nSize)==3 ){
2528     *pnSize = (SIZE_T)nSize;
2529   }else{
2530     fossil_fatal("failed to parse pid key");
2531   }
2532 }
2533 #endif
2534 
2535 /*
2536 ** WEBPAGE: test-pid
2537 **
2538 ** Return the process identifier of the running Fossil server instance.
2539 **
2540 ** Query parameters:
2541 **
2542 **   usepidkey           When present and available, also return the
2543 **                       address and size, within this server process,
2544 **                       of the saved database encryption key.  This
2545 **                       is only supported when using SEE on Windows.
2546 */
test_pid_page(void)2547 void test_pid_page(void){
2548   login_check_credentials();
2549   if( !g.perm.Setup ){ login_needed(0); return; }
2550 #if defined(_WIN32) && USE_SEE
2551   if( P("usepidkey")!=0 ){
2552     if( g.zPidKey ){
2553       @ %s(g.zPidKey)
2554       return;
2555     }else{
2556       const char *zSavedKey = db_get_saved_encryption_key();
2557       size_t savedKeySize = db_get_saved_encryption_key_size();
2558       if( zSavedKey!=0 && savedKeySize>0 ){
2559         @ %lu(GetCurrentProcessId()):%p(zSavedKey):%u(savedKeySize)
2560         return;
2561       }
2562     }
2563   }
2564 #endif
2565   @ %d(GETPID())
2566 }
2567 
2568 /*
2569 ** COMMAND: http*
2570 **
2571 ** Usage: %fossil http ?REPOSITORY? ?OPTIONS?
2572 **
2573 ** Handle a single HTTP request appearing on stdin.  The resulting webpage
2574 ** is delivered on stdout.  This method is used to launch an HTTP request
2575 ** handler from inetd, for example.  The argument is the name of the
2576 ** repository.
2577 **
2578 ** If REPOSITORY is a directory that contains one or more repositories,
2579 ** either directly in REPOSITORY itself or in subdirectories, and
2580 ** with names of the form "*.fossil" then a prefix of the URL pathname
2581 ** selects from among the various repositories.  If the pathname does
2582 ** not select a valid repository and the --notfound option is available,
2583 ** then the server redirects (HTTP code 302) to the URL of --notfound.
2584 ** When REPOSITORY is a directory, the pathname must contain only
2585 ** alphanumerics, "_", "/", "-" and "." and no "-" may occur after a "/"
2586 ** and every "." must be surrounded on both sides by alphanumerics or else
2587 ** a 404 error is returned.  Static content files in the directory are
2588 ** returned if they match comma-separate GLOB pattern specified by --files
2589 ** and do not match "*.fossil*" and have a well-known suffix.
2590 **
2591 ** The --host option can be used to specify the hostname for the server.
2592 ** The --https option indicates that the request came from HTTPS rather
2593 ** than HTTP. If --nossl is given, then SSL connections will not be available,
2594 ** thus also no redirecting from http: to https: will take place.
2595 **
2596 ** If the --localauth option is given, then automatic login is performed
2597 ** for requests coming from localhost, if the "localauth" setting is not
2598 ** enabled.
2599 **
2600 ** Options:
2601 **   --baseurl URL    base URL (useful with reverse proxies)
2602 **   --ckout-alias N  Treat URIs of the form /doc/N/... as if they were
2603 **                       /doc/ckout/...
2604 **   --extroot DIR    document root for the /ext extension mechanism
2605 **   --files GLOB     comma-separate glob patterns for static file to serve
2606 **   --host NAME      specify hostname of the server
2607 **   --https          signal a request coming in via https
2608 **   --in FILE        Take input from FILE instead of standard input
2609 **   --ipaddr ADDR    Assume the request comes from the given IP address
2610 **   --jsmode MODE       Determine how JavaScript is delivered with pages.
2611 **                       Mode can be one of:
2612 **                          inline       All JavaScript is inserted inline at
2613 **                                       one or more points in the HTML file.
2614 **                          separate     Separate HTTP requests are made for
2615 **                                       each JavaScript file.
2616 **                          bundled      Groups JavaScript files into one or
2617 **                                       more bundled requests which
2618 **                                       concatenate scripts together.
2619 **                       Depending on the needs of any given page, inline
2620 **                       and bundled modes might result in a single
2621 **                       amalgamated script or several, but both approaches
2622 **                       result in fewer HTTP requests than the separate mode.
2623 **   --localauth      enable automatic login for local connections
2624 **   --nocompress     do not compress HTTP replies
2625 **   --nodelay        omit backoffice processing if it would delay process exit
2626 **   --nojail         drop root privilege but do not enter the chroot jail
2627 **   --nossl          signal that no SSL connections are available
2628 **   --notfound URL   use URL as "HTTP 404, object not found" page.
2629 **   --out FILE       write results to FILE instead of to standard output
2630 **   --repolist       If REPOSITORY is directory, URL "/" lists all repos
2631 **   --scgi           Interpret input as SCGI rather than HTTP
2632 **   --skin LABEL     Use override skin LABEL
2633 **   --th-trace       trace TH1 execution (for debugging purposes)
2634 **   --mainmenu FILE  Override the mainmenu config setting with the contents
2635 **                    of the given file.
2636 **   --usepidkey      Use saved encryption key from parent process.  This is
2637 **                    only necessary when using SEE on Windows.
2638 **
2639 ** See also: [[cgi]], [[server]], [[winsrv]]
2640 */
cmd_http(void)2641 void cmd_http(void){
2642   const char *zIpAddr = 0;
2643   const char *zNotFound;
2644   const char *zHost;
2645   const char *zAltBase;
2646   const char *zFileGlob;
2647   const char *zInFile;
2648   const char *zOutFile;
2649   int useSCGI;
2650   int noJail;
2651   int allowRepoList;
2652 
2653   Th_InitTraceLog();
2654   builtin_set_js_delivery_mode(find_option("jsmode",0,1),0);
2655 
2656   /* The winhttp module passes the --files option as --files-urlenc with
2657   ** the argument being URL encoded, to avoid wildcard expansion in the
2658   ** shell.  This option is for internal use and is undocumented.
2659   */
2660   zFileGlob = find_option("files-urlenc",0,1);
2661   if( zFileGlob ){
2662     char *z = mprintf("%s", zFileGlob);
2663     dehttpize(z);
2664     zFileGlob = z;
2665   }else{
2666     zFileGlob = find_option("files",0,1);
2667   }
2668   skin_override();
2669   zNotFound = find_option("notfound", 0, 1);
2670   noJail = find_option("nojail",0,0)!=0;
2671   allowRepoList = find_option("repolist",0,0)!=0;
2672   g.useLocalauth = find_option("localauth", 0, 0)!=0;
2673   g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
2674   g.fNoHttpCompress = find_option("nocompress",0,0)!=0;
2675   g.zExtRoot = find_option("extroot",0,1);
2676   g.zCkoutAlias = find_option("ckout-alias",0,1);
2677   zInFile = find_option("in",0,1);
2678   if( zInFile ){
2679     backoffice_disable();
2680     g.httpIn = fossil_fopen(zInFile, "rb");
2681     if( g.httpIn==0 ) fossil_fatal("cannot open \"%s\" for reading", zInFile);
2682   }else{
2683     g.httpIn = stdin;
2684   }
2685   zOutFile = find_option("out",0,1);
2686   if( zOutFile ){
2687     g.httpOut = fossil_fopen(zOutFile, "wb");
2688     if( g.httpOut==0 ) fossil_fatal("cannot open \"%s\" for writing", zOutFile);
2689   }else{
2690     g.httpOut = stdout;
2691   }
2692   zIpAddr = find_option("ipaddr",0,1);
2693   useSCGI = find_option("scgi", 0, 0)!=0;
2694   zAltBase = find_option("baseurl", 0, 1);
2695   if( find_option("nodelay",0,0)!=0 ) backoffice_no_delay();
2696   if( zAltBase ) set_base_url(zAltBase);
2697   if( find_option("https",0,0)!=0 ){
2698     zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */
2699     cgi_replace_parameter("HTTPS","on");
2700   }
2701   zHost = find_option("host", 0, 1);
2702   if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost);
2703   g.zMainMenuFile = find_option("mainmenu",0,1);
2704   if( g.zMainMenuFile!=0 && file_size(g.zMainMenuFile,ExtFILE)<0 ){
2705     fossil_fatal("Cannot read --mainmenu file %s", g.zMainMenuFile);
2706   }
2707 
2708   /* We should be done with options.. */
2709   verify_all_options();
2710 
2711   if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
2712   g.cgiOutput = 1;
2713   g.fullHttpReply = 1;
2714   find_server_repository(2, 0);
2715   if( zIpAddr==0 ){
2716     zIpAddr = cgi_ssh_remote_addr(0);
2717     if( zIpAddr && zIpAddr[0] ){
2718       g.fSshClient |= CGI_SSH_CLIENT;
2719     }
2720   }
2721   g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
2722   if( useSCGI ){
2723     cgi_handle_scgi_request();
2724   }else if( g.fSshClient & CGI_SSH_CLIENT ){
2725     ssh_request_loop(zIpAddr, glob_create(zFileGlob));
2726   }else{
2727     cgi_handle_http_request(zIpAddr);
2728   }
2729   process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
2730 }
2731 
2732 /*
2733 ** Process all requests in a single SSH connection if possible.
2734 */
ssh_request_loop(const char * zIpAddr,Glob * FileGlob)2735 void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){
2736   blob_zero(&g.cgiIn);
2737   do{
2738     cgi_handle_ssh_http_request(zIpAddr);
2739     process_one_web_page(0, FileGlob, 0);
2740     blob_reset(&g.cgiIn);
2741   } while ( g.fSshClient & CGI_SSH_FOSSIL ||
2742           g.fSshClient & CGI_SSH_COMPAT );
2743 }
2744 
2745 /*
2746 ** COMMAND: test-http
2747 **
2748 ** Works like the [[http]] command but gives setup permission to all users,
2749 ** or whatever permission is described by "--usercap CAP".
2750 **
2751 ** This command can used for interactive debugging of web pages.  For
2752 ** example, one can put a simple HTTP request in a file like this:
2753 **
2754 **     echo 'GET /timeline' >request.txt
2755 **
2756 ** Then run (in a debugger) a command like this:
2757 **
2758 **     fossil test-http --debug <request.txt
2759 **
2760 ** This command is also used internally by the "ssh" sync protocol.  Some
2761 ** special processing to support sync happens when this command is run
2762 ** and the SSH_CONNECTION environment variable is set.  Use the --test
2763 ** option on interactive sessions to avoid that special processing when
2764 ** using this command interactively over SSH.  A better solution would be
2765 ** to use a different command for "ssh" sync, but we cannot do that without
2766 ** breaking legacy.
2767 **
2768 ** Options:
2769 **   --test              Do not do special "sync" processing when operating
2770 **                       over an SSH link.
2771 **   --th-trace          Trace TH1 execution (for debugging purposes)
2772 **   --usercap   CAP     User capability string (Default: "sxy")
2773 **
2774 */
cmd_test_http(void)2775 void cmd_test_http(void){
2776   const char *zIpAddr;    /* IP address of remote client */
2777   const char *zUserCap;
2778   int bTest = 0;
2779 
2780   Th_InitTraceLog();
2781   zUserCap = find_option("usercap",0,1);
2782   if( zUserCap==0 ){
2783     g.useLocalauth = 1;
2784     zUserCap = "sxy";
2785   }
2786   bTest = find_option("test",0,0)!=0;
2787   login_set_capabilities(zUserCap, 0);
2788   g.httpIn = stdin;
2789   g.httpOut = stdout;
2790   fossil_binary_mode(g.httpOut);
2791   fossil_binary_mode(g.httpIn);
2792   g.zExtRoot = find_option("extroot",0,1);
2793   find_server_repository(2, 0);
2794   g.cgiOutput = 1;
2795   g.fNoHttpCompress = 1;
2796   g.fullHttpReply = 1;
2797   g.sslNotAvailable = 1;  /* Avoid attempts to redirect */
2798   zIpAddr = bTest ? 0 : cgi_ssh_remote_addr(0);
2799   if( zIpAddr && zIpAddr[0] ){
2800     g.fSshClient |= CGI_SSH_CLIENT;
2801     ssh_request_loop(zIpAddr, 0);
2802   }else{
2803     cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
2804     cgi_handle_http_request(0);
2805     process_one_web_page(0, 0, 1);
2806   }
2807 }
2808 
2809 /*
2810 ** Respond to a SIGALRM by writing a message to the error log (if there
2811 ** is one) and exiting.
2812 */
2813 #ifndef _WIN32
2814 static int nAlarmSeconds = 0;
sigalrm_handler(int x)2815 static void sigalrm_handler(int x){
2816   sqlite3_uint64 tmUser = 0, tmKernel = 0;
2817   fossil_cpu_times(&tmUser, &tmKernel);
2818   fossil_panic("Timeout after %d seconds during %s"
2819                " - user %,llu µs, sys %,llu µs",
2820                nAlarmSeconds, g.zPhase, tmUser, tmKernel);
2821 }
2822 #endif
2823 
2824 /*
2825 ** Arrange to timeout using SIGALRM after N seconds.  Or if N==0, cancel
2826 ** any pending timeout.
2827 **
2828 ** Bugs:
2829 ** (1) This only works on unix systems.
2830 ** (2) Any call to sleep() or sqlite3_sleep() will cancel the alarm.
2831 */
fossil_set_timeout(int N)2832 void fossil_set_timeout(int N){
2833 #ifndef _WIN32
2834   signal(SIGALRM, sigalrm_handler);
2835   alarm(N);
2836   nAlarmSeconds = N;
2837 #endif
2838 }
2839 
2840 /*
2841 ** COMMAND: server*
2842 ** COMMAND: ui
2843 **
2844 ** Usage: %fossil server ?OPTIONS? ?REPOSITORY?
2845 **    or: %fossil ui ?OPTIONS? ?REPOSITORY?
2846 **
2847 ** Open a socket and begin listening and responding to HTTP requests on
2848 ** TCP port 8080, or on any other TCP port defined by the -P or
2849 ** --port option.  The optional REPOSITORY argument is the name of the
2850 ** Fossil repository to be served.  The REPOSITORY argument may be omitted
2851 ** if the working directory is within an open checkout, in which case the
2852 ** repository associated with that checkout is used.
2853 **
2854 ** The "ui" command automatically starts a web browser after initializing
2855 ** the web server.  The "ui" command also binds to 127.0.0.1 and so will
2856 ** only process HTTP traffic from the local machine.
2857 **
2858 ** If REPOSITORY is a directory name which is the root of a
2859 ** checkout, then use the repository associated with that checkout.
2860 ** This only works for the "fossil ui" command, not the "fossil server"
2861 ** command.
2862 **
2863 ** If REPOSITORY begins with a "HOST:" or "USER@HOST:" prefix, then
2864 ** the command is run on the remote host specified and the results are
2865 ** tunneled back to the local machine via SSH.  This feature only works for
2866 ** the "fossil ui" command, not the "fossil server" command.
2867 **
2868 ** REPOSITORY may also be a directory (aka folder) that contains one or
2869 ** more repositories with names ending in ".fossil".  In this case, a
2870 ** prefix of the URL pathname is used to search the directory for an
2871 ** appropriate repository.  To thwart mischief, the pathname in the URL must
2872 ** contain only alphanumerics, "_", "/", "-", and ".", and no "-" may
2873 ** occur after "/", and every "." must be surrounded on both sides by
2874 ** alphanumerics.  Any pathname that does not satisfy these constraints
2875 ** results in a 404 error.  Files in REPOSITORY that match the comma-separated
2876 ** list of glob patterns given by --files and that have known suffixes
2877 ** such as ".txt" or ".html" or ".jpeg" and do not match the pattern
2878 ** "*.fossil*" will be served as static content.  With the "ui" command,
2879 ** the REPOSITORY can only be a directory if the --notfound option is
2880 ** also present.
2881 **
2882 ** For the special case REPOSITORY name of "/", the global configuration
2883 ** database is consulted for a list of all known repositories.  The --repolist
2884 ** option is implied by this special case.  The "fossil ui /" command is
2885 ** equivalent to "fossil all ui".  To see all repositories owned by "user"
2886 ** on machine "remote" via ssh, run "fossil ui user@remote:/".
2887 **
2888 ** By default, the "ui" command provides full administrative access without
2889 ** having to log in.  This can be disabled by turning off the "localauth"
2890 ** setting.  Automatic login for the "server" command is available if the
2891 ** --localauth option is present and the "localauth" setting is off and the
2892 ** connection is from localhost.  The "ui" command also enables --repolist
2893 ** by default.
2894 **
2895 ** Options:
2896 **   --baseurl URL       Use URL as the base (useful for reverse proxies)
2897 **   --ckout-alias NAME  Treat URIs of the form /doc/NAME/... as if they were
2898 **                       /doc/ckout/...
2899 **   --create            Create a new REPOSITORY if it does not already exist
2900 **   --extroot DIR       Document root for the /ext extension mechanism
2901 **   --files GLOBLIST    Comma-separated list of glob patterns for static files
2902 **   --fossilcmd PATH    Full pathname of the "fossil" executable on the remote
2903 **                       system when REPOSITORY is remote.  Default: "fossil"
2904 **   --localauth         enable automatic login for requests from localhost
2905 **   --localhost         listen on 127.0.0.1 only (always true for "ui")
2906 **   --https             Indicates that the input is coming through a reverse
2907 **                       proxy that has already translated HTTPS into HTTP.
2908 **   --jsmode MODE       Determine how JavaScript is delivered with pages.
2909 **                       Mode can be one of:
2910 **                          inline       All JavaScript is inserted inline at
2911 **                                       the end of the HTML file.
2912 **                          separate     Separate HTTP requests are made for
2913 **                                       each JavaScript file.
2914 **                          bundled      One single separate HTTP fetches all
2915 **                                       JavaScript concatenated together.
2916 **                       Depending on the needs of any given page, inline
2917 **                       and bundled modes might result in a single
2918 **                       amalgamated script or several, but both approaches
2919 **                       result in fewer HTTP requests than the separate mode.
2920 **   --mainmenu FILE     Override the mainmenu config setting with the contents
2921 **                       of the given file.
2922 **   --max-latency N     Do not let any single HTTP request run for more than N
2923 **                       seconds (only works on unix)
2924 **   --nobrowser         Do not automatically launch a web-browser for the
2925 **                       "fossil ui" command.
2926 **   --nocompress        Do not compress HTTP replies
2927 **   --nojail            Drop root privileges but do not enter the chroot jail
2928 **   --nossl             signal that no SSL connections are available (Always
2929 **                       set by default for the "ui" command)
2930 **   --notfound URL      Redirect
2931 **   --page PAGE         Start "ui" on PAGE.  ex: --page "timeline?y=ci"
2932 **   -P|--port TCPPORT   listen to request on port TCPPORT
2933 **   --repolist          If REPOSITORY is dir, URL "/" lists repos.
2934 **   --scgi              Accept SCGI rather than HTTP
2935 **   --skin LABEL        Use override skin LABEL
2936 **   --th-trace          trace TH1 execution (for debugging purposes)
2937 **   --usepidkey         Use saved encryption key from parent process.  This is
2938 **                       only necessary when using SEE on Windows.
2939 **
2940 ** See also: [[cgi]], [[http]], [[winsrv]]
2941 */
cmd_webserver(void)2942 void cmd_webserver(void){
2943   int iPort, mxPort;        /* Range of TCP ports allowed */
2944   const char *zPort;        /* Value of the --port option */
2945   const char *zBrowser;     /* Name of web browser program */
2946   char *zBrowserCmd = 0;    /* Command to launch the web browser */
2947   int isUiCmd;              /* True if command is "ui", not "server' */
2948   const char *zNotFound;    /* The --notfound option or NULL */
2949   int flags = 0;            /* Server flags */
2950 #if !defined(_WIN32)
2951   int noJail;               /* Do not enter the chroot jail */
2952   const char *zTimeout = 0; /* Max runtime of any single HTTP request */
2953 #endif
2954   int allowRepoList;         /* List repositories on URL "/" */
2955   const char *zAltBase;      /* Argument to the --baseurl option */
2956   const char *zFileGlob;     /* Static content must match this */
2957   char *zIpAddr = 0;         /* Bind to this IP address */
2958   int fCreate = 0;           /* The --create flag */
2959   int fNoBrowser = 0;        /* Do not auto-launch web-browser */
2960   const char *zInitPage = 0; /* Start on this page.  --page option */
2961   int findServerArg = 2;     /* argv index for find_server_repository() */
2962   char *zRemote = 0;         /* Remote host on which to run "fossil ui" */
2963   const char *zJsMode;       /* The --jsmode parameter */
2964   const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
2965 
2966 
2967 #if defined(_WIN32)
2968   const char *zStopperFile;    /* Name of file used to terminate server */
2969   zStopperFile = find_option("stopper", 0, 1);
2970 #endif
2971 
2972   if( g.zErrlog==0 ){
2973     g.zErrlog = "-";
2974   }
2975   g.zExtRoot = find_option("extroot",0,1);
2976   zJsMode = find_option("jsmode",0,1);
2977   builtin_set_js_delivery_mode(zJsMode,0);
2978   zFileGlob = find_option("files-urlenc",0,1);
2979   if( zFileGlob ){
2980     char *z = mprintf("%s", zFileGlob);
2981     dehttpize(z);
2982     zFileGlob = z;
2983   }else{
2984     zFileGlob = find_option("files",0,1);
2985   }
2986   skin_override();
2987 #if !defined(_WIN32)
2988   noJail = find_option("nojail",0,0)!=0;
2989   zTimeout = find_option("max-latency",0,1);
2990 #endif
2991   g.useLocalauth = find_option("localauth", 0, 0)!=0;
2992   Th_InitTraceLog();
2993   zPort = find_option("port", "P", 1);
2994   isUiCmd = g.argv[1][0]=='u';
2995   if( isUiCmd ){
2996     zInitPage = find_option("page", 0, 1);
2997     if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
2998     zFossilCmd = find_option("fossilcmd", 0, 1);
2999   }
3000   zNotFound = find_option("notfound", 0, 1);
3001   allowRepoList = find_option("repolist",0,0)!=0;
3002   if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1;
3003   zAltBase = find_option("baseurl", 0, 1);
3004   fCreate = find_option("create",0,0)!=0;
3005   if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI;
3006   if( zAltBase ){
3007     set_base_url(zAltBase);
3008   }
3009   g.sslNotAvailable = find_option("nossl", 0, 0)!=0 || isUiCmd;
3010   fNoBrowser = find_option("nobrowser", 0, 0)!=0;
3011   if( find_option("https",0,0)!=0 ){
3012     cgi_replace_parameter("HTTPS","on");
3013   }
3014   if( find_option("localhost", 0, 0)!=0 ){
3015     flags |= HTTP_SERVER_LOCALHOST;
3016   }
3017   g.zCkoutAlias = find_option("ckout-alias",0,1);
3018   g.zMainMenuFile = find_option("mainmenu",0,1);
3019   if( g.zMainMenuFile!=0 && file_size(g.zMainMenuFile,ExtFILE)<0 ){
3020     fossil_fatal("Cannot read --mainmenu file %s", g.zMainMenuFile);
3021   }
3022   /* We should be done with options.. */
3023   verify_all_options();
3024 
3025   if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
3026   if( isUiCmd && 3==g.argc && file_isdir(g.argv[2], ExtFILE)>0 ){
3027     /* If REPOSITORY arg is the root of a checkout,
3028     ** chdir to that checkout so that the current version
3029     ** gets highlighted in the timeline by default. */
3030     const char * zDir = g.argv[2];
3031     if(dir_has_ckout_db(zDir)){
3032       if(0!=file_chdir(zDir, 0)){
3033         fossil_fatal("Cannot chdir to %s", zDir);
3034       }
3035       findServerArg = 99;
3036       fCreate = 0;
3037       g.argv[2] = 0;
3038       --g.argc;
3039     }
3040   }
3041   if( isUiCmd && 3==g.argc
3042    && (zRemote = (char*)file_skip_userhost(g.argv[2]))!=0
3043   ){
3044     /* The REPOSITORY argument has a USER@HOST: or HOST: prefix */
3045     const char *zRepoTail = file_skip_userhost(g.argv[2]);
3046     unsigned x;
3047     int n;
3048     sqlite3_randomness(2,&x);
3049     zPort = mprintf("%d", 8100+(x%32000));
3050     n = (int)(zRepoTail - g.argv[2]) - 1;
3051     zRemote = mprintf("%.*s", n, g.argv[2]);
3052     g.argv[2] = (char*)zRepoTail;
3053   }
3054   if( isUiCmd ){
3055     flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST;
3056     g.useLocalauth = 1;
3057     allowRepoList = 1;
3058   }
3059   if( !zRemote ){
3060     find_server_repository(findServerArg, fCreate);
3061   }
3062   if( zInitPage==0 ){
3063     if( isUiCmd && g.localOpen ){
3064       zInitPage = "timeline?c=current";
3065     }else{
3066       zInitPage = "";
3067     }
3068   }
3069   if( zPort ){
3070     if( strchr(zPort,':') ){
3071       int i;
3072       for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){}
3073       if( i>0 ){
3074         if( zPort[0]=='[' && zPort[i-1]==']' ){
3075           zIpAddr = mprintf("%.*s", i-2, zPort+1);
3076         }else{
3077           zIpAddr = mprintf("%.*s", i, zPort);
3078         }
3079         zPort += i+1;
3080       }
3081     }
3082     iPort = mxPort = atoi(zPort);
3083   }else{
3084     iPort = db_get_int("http-port", 8080);
3085     mxPort = iPort+100;
3086   }
3087   if( isUiCmd && !fNoBrowser ){
3088     char *zBrowserArg;
3089     if( zRemote ) db_open_config(0,0);
3090     zBrowser = fossil_web_browser();
3091     if( zIpAddr==0 ){
3092       zBrowserArg = mprintf("http://localhost:%%d/%s", zInitPage);
3093     }else if( strchr(zIpAddr,':') ){
3094       zBrowserArg = mprintf("http://[%s]:%%d/%s", zIpAddr, zInitPage);
3095     }else{
3096       zBrowserArg = mprintf("http://%s:%%d/%s", zIpAddr, zInitPage);
3097     }
3098     zBrowserCmd = mprintf("%s %!$ &", zBrowser, zBrowserArg);
3099     fossil_free(zBrowserArg);
3100   }
3101   if( zRemote ){
3102     /* If a USER@HOST:REPO argument is supplied, then use SSH to run
3103     ** "fossil ui --nobrowser" on the remote system and to set up a
3104     ** tunnel from the local machine to the remote. */
3105     FILE *sshIn;
3106     Blob ssh;
3107     char zLine[1000];
3108     blob_init(&ssh, 0, 0);
3109     transport_ssh_command(&ssh);
3110     db_close_config();
3111     if( zFossilCmd==0 ) zFossilCmd = "fossil";
3112     blob_appendf(&ssh,
3113        " -t -L 127.0.0.1:%d:127.0.0.1:%d %!$"
3114        " %$ ui --nobrowser --localauth --port %d",
3115        iPort, iPort, zRemote, zFossilCmd, iPort);
3116     if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
3117     if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
3118     if( g.zCkoutAlias ) blob_appendf(&ssh, " --ckout-alias %!$",g.zCkoutAlias);
3119     if( g.zExtRoot ) blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
3120     if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
3121     if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
3122     if( fCreate ) blob_appendf(&ssh, " --create");
3123     blob_appendf(&ssh, " %$", g.argv[2]);
3124     fossil_print("%s\n", blob_str(&ssh));
3125     sshIn = popen(blob_str(&ssh), "r");
3126     if( sshIn==0 ){
3127       fossil_fatal("unable to %s", blob_str(&ssh));
3128     }
3129     while( fgets(zLine, sizeof(zLine), sshIn) ){
3130       fputs(zLine, stdout);
3131       fflush(stdout);
3132       if( zBrowserCmd && sqlite3_strglob("*Listening for HTTP*",zLine)==0 ){
3133         char *zCmd = mprintf(zBrowserCmd/*works-like:"%d"*/,iPort);
3134         fossil_system(zCmd);
3135         fossil_free(zCmd);
3136         fossil_free(zBrowserCmd);
3137         zBrowserCmd = 0;
3138       }
3139     }
3140     pclose(sshIn);
3141     fossil_free(zBrowserCmd);
3142     return;
3143   }
3144   if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
3145   if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
3146   db_close(1);
3147 
3148   /* Start up an HTTP server
3149   */
3150 #if !defined(_WIN32)
3151   /* Unix implementation */
3152   if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, flags) ){
3153     fossil_fatal("unable to listen on TCP socket %d", iPort);
3154   }
3155   /* For the parent process, the cgi_http_server() command above never
3156   ** returns (except in the case of an error).  Instead, for each incoming
3157   ** client connection, a child process is created, file descriptors 0
3158   ** and 1 are bound to that connection, and the child returns.
3159   **
3160   ** So, when control reaches this point, we are running as a
3161   ** child process, the HTTP or SCGI request is pending on file
3162   ** descriptor 0 and the reply should be written to file descriptor 1.
3163   */
3164   if( zTimeout ){
3165     fossil_set_timeout(atoi(zTimeout));
3166   }else{
3167     fossil_set_timeout(FOSSIL_DEFAULT_TIMEOUT);
3168   }
3169   g.httpIn = stdin;
3170   g.httpOut = stdout;
3171   signal(SIGSEGV, sigsegv_handler);
3172   signal(SIGPIPE, sigpipe_handler);
3173   if( g.fAnyTrace ){
3174     fprintf(stderr, "/***** Subprocess %d *****/\n", getpid());
3175   }
3176   g.cgiOutput = 1;
3177   find_server_repository(2, 0);
3178   if( fossil_strcmp(g.zRepositoryName,"/")==0 ){
3179     allowRepoList = 1;
3180   }else{
3181     g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
3182   }
3183   if( flags & HTTP_SERVER_SCGI ){
3184     cgi_handle_scgi_request();
3185   }else{
3186     cgi_handle_http_request(0);
3187   }
3188   process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
3189   if( g.fAnyTrace ){
3190     fprintf(stderr, "/***** Webpage finished in subprocess %d *****/\n",
3191             getpid());
3192   }
3193 #else
3194   /* Win32 implementation */
3195   if( allowRepoList ){
3196     flags |= HTTP_SERVER_REPOLIST;
3197   }
3198   if( win32_http_service(iPort, zAltBase, zNotFound, zFileGlob, flags) ){
3199     win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile,
3200                       zAltBase, zNotFound, zFileGlob, zIpAddr, flags);
3201   }
3202 #endif
3203 }
3204 
3205 /*
3206 ** COMMAND: test-echo
3207 **
3208 ** Usage:  %fossil test-echo [--hex] ARGS...
3209 **
3210 ** Echo all command-line arguments (enclosed in [...]) to the screen so that
3211 ** wildcard expansion behavior of the host shell can be investigated.
3212 **
3213 ** With the --hex option, show the output as hexadecimal.  This can be used
3214 ** to verify the fossil_path_to_utf8() routine on Windows and Mac.
3215 */
test_echo_cmd(void)3216 void test_echo_cmd(void){
3217   int i, j;
3218   if( find_option("hex",0,0)==0 ){
3219     fossil_print("g.nameOfExe = [%s]\n", g.nameOfExe);
3220     for(i=0; i<g.argc; i++){
3221       fossil_print("argv[%d] = [%s]\n", i, g.argv[i]);
3222     }
3223   }else{
3224     unsigned char *z, c;
3225     for(i=0; i<g.argc; i++){
3226       fossil_print("argv[%d] = [", i);
3227       z = (unsigned char*)g.argv[i];
3228       for(j=0; (c = z[j])!=0; j++){
3229         fossil_print("%02x", c);
3230       }
3231       fossil_print("]\n");
3232     }
3233   }
3234 }
3235 
3236 /*
3237 ** WEBPAGE: test-warning
3238 **
3239 ** Test error and warning log operation.  This webpage is accessible to
3240 ** the administrator only.
3241 **
3242 **     case=1           Issue a fossil_warning() while generating the page.
3243 **     case=2           Extra db_begin_transaction()
3244 **     case=3           Extra db_end_transaction()
3245 **     case=4           Error during SQL processing
3246 **     case=5           Call the segfault handler
3247 **     case=6           Call webpage_assert()
3248 **     case=7           Call webpage_error()
3249 */
test_warning_page(void)3250 void test_warning_page(void){
3251   int iCase = atoi(PD("case","0"));
3252   int i;
3253   login_check_credentials();
3254   if( !g.perm.Admin ){
3255     login_needed(0);
3256     return;
3257   }
3258   style_set_current_feature("test");
3259   style_header("Warning Test Page");
3260   style_submenu_element("Error Log","%R/errorlog");
3261   if( iCase<1 || iCase>4 ){
3262     @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
3263     @ by clicking on one of the following cases:
3264   }else{
3265     @ <p>This is the test page for case=%d(iCase).  All possible cases:
3266   }
3267   for(i=1; i<=8; i++){
3268     @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
3269   }
3270   @ </p>
3271   @ <p><ol>
3272   @ <li value='1'> Call fossil_warning()
3273   if( iCase==1 ){
3274     fossil_warning("Test warning message from /test-warning");
3275   }
3276   @ <li value='2'> Call db_begin_transaction()
3277   if( iCase==2 ){
3278     db_begin_transaction();
3279   }
3280   @ <li value='3'> Call db_end_transaction()
3281   if( iCase==3 ){
3282     db_end_transaction(0);
3283   }
3284   @ <li value='4'> warning during SQL
3285   if( iCase==4 ){
3286     Stmt q;
3287     db_prepare(&q, "SELECT uuid FROM blob LIMIT 5");
3288     db_step(&q);
3289     sqlite3_log(SQLITE_ERROR, "Test warning message during SQL");
3290     db_finalize(&q);
3291   }
3292   @ <li value='5'> simulate segfault handling
3293   if( iCase==5 ){
3294     sigsegv_handler(0);
3295   }
3296   @ <li value='6'> call webpage_assert(0)
3297   if( iCase==6 ){
3298     webpage_assert( 5==7 );
3299   }
3300   @ <li value='7'> call webpage_error()"
3301   if( iCase==7 ){
3302     cgi_reset_content();
3303     webpage_error("Case 7 from /test-warning");
3304   }
3305   @ <li value='8'> simulated timeout"
3306   if( iCase==8 ){
3307     fossil_set_timeout(1);
3308     cgi_reset_content();
3309     sqlite3_sleep(1100);
3310   }
3311   @ </ol>
3312   @ <p>End of test</p>
3313   style_finish_page();
3314 }
3315