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