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 #endif
26 #include "main.h"
27 #include <string.h>
28 #include <time.h>
29 #include <fcntl.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <stdlib.h> /* atexit() */
33 #if !defined(_WIN32)
34 # include <errno.h> /* errno global */
35 #endif
36 #ifdef FOSSIL_ENABLE_SSL
37 # include "openssl/crypto.h"
38 #endif
39 #if defined(FOSSIL_ENABLE_MINIZ)
40 # define MINIZ_HEADER_FILE_ONLY
41 # include "miniz.c"
42 #else
43 # include <zlib.h>
44 #endif
45 #if INTERFACE
46 #ifdef FOSSIL_ENABLE_TCL
47 # include "tcl.h"
48 #endif
49 #ifdef FOSSIL_ENABLE_JSON
50 # include "cson_amalgamation.h" /* JSON API. */
51 # include "json_detail.h"
52 #endif
53
54 /*
55 ** Size of a UUID in characters. A UUID is a randomly generated
56 ** lower-case hexadecimal number used to identify tickets.
57 **
58 ** In Fossil 1.x, UUID also referred to a SHA1 artifact hash. But that
59 ** usage is now obsolete. The term UUID should now mean only a very large
60 ** random number used as a unique identifier for tickets or other objects.
61 */
62 #define UUID_SIZE 40
63
64 /*
65 ** Maximum number of auxiliary parameters on reports
66 */
67 #define MX_AUX 5
68
69 /*
70 ** Holds flags for fossil user permissions.
71 */
72 struct FossilUserPerms {
73 char Setup; /* s: use Setup screens on web interface */
74 char Admin; /* a: administrative permission */
75 char Delete; /* d: delete wiki or tickets */
76 char Password; /* p: change password */
77 char Query; /* q: create new reports */
78 char Write; /* i: xfer inbound. check-in */
79 char Read; /* o: xfer outbound. check-out */
80 char Hyperlink; /* h: enable the display of hyperlinks */
81 char Clone; /* g: clone */
82 char RdWiki; /* j: view wiki via web */
83 char NewWiki; /* f: create new wiki via web */
84 char ApndWiki; /* m: append to wiki via web */
85 char WrWiki; /* k: edit wiki via web */
86 char ModWiki; /* l: approve and publish wiki content (Moderator) */
87 char RdTkt; /* r: view tickets via web */
88 char NewTkt; /* n: create new tickets */
89 char ApndTkt; /* c: append to tickets via the web */
90 char WrTkt; /* w: make changes to tickets via web */
91 char ModTkt; /* q: approve and publish ticket changes (Moderator) */
92 char Attach; /* b: add attachments */
93 char TktFmt; /* t: create new ticket report formats */
94 char RdAddr; /* e: read email addresses or other private data */
95 char Zip; /* z: download zipped artifact via /zip URL */
96 char Private; /* x: can send and receive private content */
97 char WrUnver; /* y: can push unversioned content */
98 };
99
100 #ifdef FOSSIL_ENABLE_TCL
101 /*
102 ** All Tcl related context information is in this structure. This structure
103 ** definition has been copied from and should be kept in sync with the one in
104 ** "th_tcl.c".
105 */
106 struct TclContext {
107 int argc; /* Number of original (expanded) arguments. */
108 char **argv; /* Full copy of the original (expanded) arguments. */
109 void *hLibrary; /* The Tcl library module handle. */
110 void *xFindExecutable; /* See tcl_FindExecutableProc in th_tcl.c. */
111 void *xCreateInterp; /* See tcl_CreateInterpProc in th_tcl.c. */
112 void *xDeleteInterp; /* See tcl_DeleteInterpProc in th_tcl.c. */
113 void *xFinalize; /* See tcl_FinalizeProc in th_tcl.c. */
114 Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */
115 int useObjProc; /* Non-zero if an objProc can be called directly. */
116 int useTip285; /* Non-zero if TIP #285 is available. */
117 char *setup; /* The optional Tcl setup script. */
118 void *xPreEval; /* Optional, called before Tcl_Eval*(). */
119 void *pPreContext; /* Optional, provided to xPreEval(). */
120 void *xPostEval; /* Optional, called after Tcl_Eval*(). */
121 void *pPostContext; /* Optional, provided to xPostEval(). */
122 };
123 #endif
124
125 struct Global {
126 int argc; char **argv; /* Command-line arguments to the program */
127 char *nameOfExe; /* Full path of executable. */
128 const char *zErrlog; /* Log errors to this file, if not NULL */
129 int isConst; /* True if the output is unchanging & cacheable */
130 const char *zVfsName; /* The VFS to use for database connections */
131 sqlite3 *db; /* The connection to the databases */
132 sqlite3 *dbConfig; /* Separate connection for global_config table */
133 char *zAuxSchema; /* Main repository aux-schema */
134 int dbIgnoreErrors; /* Ignore database errors if true */
135 const char *zConfigDbName;/* Path of the config database. NULL if not open */
136 sqlite3_int64 now; /* Seconds since 1970 */
137 int repositoryOpen; /* True if the main repository database is open */
138 char *zRepositoryOption; /* Most recent cached repository option value */
139 char *zRepositoryName; /* Name of the repository database file */
140 char *zLocalDbName; /* Name of the local database file */
141 char *zOpenRevision; /* Check-in version to use during database open */
142 int localOpen; /* True if the local database is open */
143 char *zLocalRoot; /* The directory holding the local database */
144 int minPrefix; /* Number of digits needed for a distinct UUID */
145 int eHashPolicy; /* Current hash policy. One of HPOLICY_* */
146 int fNoDirSymlinks; /* True if --no-dir-symlinks flag is present */
147 int fSqlTrace; /* True if --sqltrace flag is present */
148 int fSqlStats; /* True if --sqltrace or --sqlstats are present */
149 int fSqlPrint; /* True if -sqlprint flag is present */
150 int fQuiet; /* True if -quiet flag is present */
151 int fJail; /* True if running with a chroot jail */
152 int fHttpTrace; /* Trace outbound HTTP requests */
153 int fAnyTrace; /* Any kind of tracing */
154 char *zHttpAuth; /* HTTP Authorization user:pass information */
155 int fSystemTrace; /* Trace calls to fossil_system(), --systemtrace */
156 int fSshTrace; /* Trace the SSH setup traffic */
157 int fSshClient; /* HTTP client flags for SSH client */
158 char *zSshCmd; /* SSH command string */
159 int fNoSync; /* Do not do an autosync ever. --nosync */
160 int fIPv4; /* Use only IPv4, not IPv6. --ipv4 */
161 char *zPath; /* Name of webpage being served */
162 char *zExtra; /* Extra path information past the webpage name */
163 char *zBaseURL; /* Full text of the URL being served */
164 char *zHttpsURL; /* zBaseURL translated to https: */
165 char *zTop; /* Parent directory of zPath */
166 const char *zContentType; /* The content type of the input HTTP request */
167 int iErrPriority; /* Priority of current error message */
168 char *zErrMsg; /* Text of an error message */
169 int sslNotAvailable; /* SSL is not available. Do not redirect to https: */
170 Blob cgiIn; /* Input to an xfer www method */
171 int cgiOutput; /* Write error and status messages to CGI */
172 int xferPanic; /* Write error messages in XFER protocol */
173 int fullHttpReply; /* True for full HTTP reply. False for CGI reply */
174 Th_Interp *interp; /* The TH1 interpreter */
175 char *th1Setup; /* The TH1 post-creation setup script, if any */
176 int th1Flags; /* The TH1 integration state flags */
177 FILE *httpIn; /* Accept HTTP input from here */
178 FILE *httpOut; /* Send HTTP output here */
179 int xlinkClusterOnly; /* Set when cloning. Only process clusters */
180 int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */
181 int *aCommitFile; /* Array of files to be committed */
182 int markPrivate; /* All new artifacts are private if true */
183 int clockSkewSeen; /* True if clocks on client and server out of sync */
184 int wikiFlags; /* Wiki conversion flags applied to %W */
185 char isHTTP; /* True if server/CGI modes, else assume CLI. */
186 char javascriptHyperlink; /* If true, set href= using script, not HTML */
187 Blob httpHeader; /* Complete text of the HTTP request header */
188 UrlData url; /* Information about current URL */
189 const char *zLogin; /* Login name. NULL or "" if not logged in. */
190 const char *zSSLIdentity; /* Value of --ssl-identity option, filename of
191 ** SSL client identity */
192 int useLocalauth; /* No login required if from 127.0.0.1 */
193 int noPswd; /* Logged in without password (on 127.0.0.1) */
194 int userUid; /* Integer user id */
195 int isHuman; /* True if access by a human, not a spider or bot */
196 int comFmtFlags; /* Zero or more "COMMENT_PRINT_*" bit flags */
197
198 /* Information used to populate the RCVFROM table */
199 int rcvid; /* The rcvid. 0 if not yet defined. */
200 char *zIpAddr; /* The remote IP address */
201 char *zNonce; /* The nonce used for login */
202
203 /* permissions available to current user */
204 struct FossilUserPerms perm;
205
206 /* permissions available to current user or to "anonymous".
207 ** This is the logical union of perm permissions above with
208 ** the value that perm would take if g.zLogin were "anonymous". */
209 struct FossilUserPerms anon;
210
211 #ifdef FOSSIL_ENABLE_TCL
212 /* all Tcl related context necessary for integration */
213 struct TclContext tcl;
214 #endif
215
216 /* For defense against Cross-site Request Forgery attacks */
217 char zCsrfToken[12]; /* Value of the anti-CSRF token */
218 int okCsrf; /* Anti-CSRF token is present and valid */
219
220 int parseCnt[10]; /* Counts of artifacts parsed */
221 FILE *fDebug; /* Write debug information here, if the file exists */
222 #ifdef FOSSIL_ENABLE_TH1_HOOKS
223 int fNoThHook; /* Disable all TH1 command/webpage hooks */
224 #endif
225 int thTrace; /* True to enable TH1 debugging output */
226 Blob thLog; /* Text of the TH1 debugging output */
227
228 int isHome; /* True if rendering the "home" page */
229
230 /* Storage for the aux() and/or option() SQL function arguments */
231 int nAux; /* Number of distinct aux() or option() values */
232 const char *azAuxName[MX_AUX]; /* Name of each aux() or option() value */
233 char *azAuxParam[MX_AUX]; /* Param of each aux() or option() value */
234 const char *azAuxVal[MX_AUX]; /* Value of each aux() or option() value */
235 const char **azAuxOpt[MX_AUX]; /* Options of each option() value */
236 int anAuxCols[MX_AUX]; /* Number of columns for option() values */
237
238 int allowSymlinks; /* Cached "allow-symlinks" option */
239
240 int mainTimerId; /* Set to fossil_timer_start() */
241 #ifdef FOSSIL_ENABLE_JSON
242 struct FossilJsonBits {
243 int isJsonMode; /* True if running in JSON mode, else
244 false. This changes how errors are
245 reported. In JSON mode we try to
246 always output JSON-form error
247 responses and always exit() with
248 code 0 to avoid an HTTP 500 error.
249 */
250 int resultCode; /* used for passing back specific codes
251 ** from /json callbacks. */
252 int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
253 cson_output_opt outOpt; /* formatting options for JSON mode. */
254 cson_value *authToken; /* authentication token */
255 const char *jsonp; /* Name of JSONP function wrapper. */
256 unsigned char dispatchDepth /* Tells JSON command dispatching
257 which argument we are currently
258 working on. For this purpose, arg#0
259 is the "json" path/CLI arg.
260 */;
261 struct { /* "garbage collector" */
262 cson_value *v;
263 cson_array *a;
264 } gc;
265 struct { /* JSON POST data. */
266 cson_value *v;
267 cson_array *a;
268 int offset; /* Tells us which PATH_INFO/CLI args
269 part holds the "json" command, so
270 that we can account for sub-repos
271 and path prefixes. This is handled
272 differently for CLI and CGI modes.
273 */
274 const char *commandStr /*"command" request param.*/;
275 } cmd;
276 struct { /* JSON POST data. */
277 cson_value *v;
278 cson_object *o;
279 } post;
280 struct { /* GET/COOKIE params in JSON mode. */
281 cson_value *v;
282 cson_object *o;
283 } param;
284 struct {
285 cson_value *v;
286 cson_object *o;
287 } reqPayload; /* request payload object (if any) */
288 cson_array *warnings; /* response warnings */
289 int timerId; /* fetched from fossil_timer_start() */
290 } json;
291 #endif /* FOSSIL_ENABLE_JSON */
292 };
293
294 /*
295 ** Macro for debugging:
296 */
297 #define CGIDEBUG(X) if( g.fDebug ) cgi_debug X
298
299 #endif
300
301 Global g;
302
303 /*
304 ** atexit() handler which frees up "some" of the resources
305 ** used by fossil.
306 */
307 static void fossil_atexit(void) {
308 #if USE_SEE
309 /*
310 ** Zero, unlock, and free the saved database encryption key now.
311 */
312 db_unsave_encryption_key();
313 #endif
314 #if defined(_WIN32) || defined(__BIONIC__)
315 /*
316 ** Free the secure getpass() buffer now.
317 */
318 freepass();
319 #endif
320 #if defined(_WIN32) && !defined(_WIN64) && defined(FOSSIL_ENABLE_TCL) && \
321 defined(USE_TCL_STUBS)
322 /*
323 ** If Tcl is compiled on Windows using the latest MinGW, Fossil can crash
324 ** when exiting while a stubs-enabled Tcl is still loaded. This is due to
325 ** a bug in MinGW, see:
326 **
327 ** http://comments.gmane.org/gmane.comp.gnu.mingw.user/41724
328 **
329 ** The workaround is to manually unload the loaded Tcl library prior to
330 ** exiting the process. This issue does not impact 64-bit Windows.
331 */
332 unloadTcl(g.interp, &g.tcl);
333 #endif
334 #ifdef FOSSIL_ENABLE_JSON
335 cson_value_free(g.json.gc.v);
336 memset(&g.json, 0, sizeof(g.json));
337 #endif
338 free(g.zErrMsg);
339 if(g.db){
340 db_close(0);
341 }
342 /*
343 ** FIXME: The next two lines cannot always be enabled; however, they
344 ** are very useful for tracking down TH1 memory leaks.
345 */
346 if( fossil_getenv("TH1_DELETE_INTERP")!=0 ){
347 if( g.interp ){
348 Th_DeleteInterp(g.interp); g.interp = 0;
349 }
350 assert( Th_GetOutstandingMalloc()==0 );
351 }
352 }
353
354 /*
355 ** Convert all arguments from mbcs (or unicode) to UTF-8. Then
356 ** search g.argv for arguments "--args FILENAME". If found, then
357 ** (1) remove the two arguments from g.argv
358 ** (2) Read the file FILENAME
359 ** (3) Use the contents of FILE to replace the two removed arguments:
360 ** (a) Ignore blank lines in the file
361 ** (b) Each non-empty line of the file is an argument, except
362 ** (c) If the line begins with "-" and contains a space, it is broken
363 ** into two arguments at the space.
364 */
365 static void expand_args_option(int argc, void *argv){
366 Blob file = empty_blob; /* Content of the file */
367 Blob line = empty_blob; /* One line of the file */
368 unsigned int nLine; /* Number of lines in the file*/
369 unsigned int i, j, k; /* Loop counters */
370 int n; /* Number of bytes in one line */
371 char *z; /* General use string pointer */
372 char **newArgv; /* New expanded g.argv under construction */
373 const char *zFileName; /* input file name */
374 FILE *inFile; /* input FILE */
375 #if defined(_WIN32)
376 wchar_t buf[MAX_PATH];
377 #endif
378
379 g.argc = argc;
380 g.argv = argv;
381 sqlite3_initialize();
382 #if defined(_WIN32) && defined(BROKEN_MINGW_CMDLINE)
383 for(i=0; i<g.argc; i++) g.argv[i] = fossil_mbcs_to_utf8(g.argv[i]);
384 #else
385 for(i=0; i<g.argc; i++) g.argv[i] = fossil_path_to_utf8(g.argv[i]);
386 #endif
387 #if defined(_WIN32)
388 GetModuleFileNameW(NULL, buf, MAX_PATH);
389 g.nameOfExe = fossil_path_to_utf8(buf);
390 #else
391 g.nameOfExe = g.argv[0];
392 #endif
393 for(i=1; i<g.argc-1; i++){
394 z = g.argv[i];
395 if( z[0]!='-' ) continue;
396 z++;
397 if( z[0]=='-' ) z++;
398 if( z[0]==0 ) return; /* Stop searching at "--" */
399 if( fossil_strcmp(z, "args")==0 ) break;
400 }
401 if( i>=g.argc-1 ) return;
402
403 zFileName = g.argv[i+1];
404 inFile = (0==strcmp("-",zFileName))
405 ? stdin
406 : fossil_fopen(zFileName,"rb");
407 if(!inFile){
408 fossil_fatal("Cannot open -args file [%s]", zFileName);
409 }else{
410 blob_read_from_channel(&file, inFile, -1);
411 if(stdin != inFile){
412 fclose(inFile);
413 }
414 inFile = NULL;
415 }
416 blob_to_utf8_no_bom(&file, 1);
417 z = blob_str(&file);
418 for(k=0, nLine=1; z[k]; k++) if( z[k]=='\n' ) nLine++;
419 newArgv = fossil_malloc( sizeof(char*)*(g.argc + nLine*2) );
420 for(j=0; j<i; j++) newArgv[j] = g.argv[j];
421
422 blob_rewind(&file);
423 while( (n = blob_line(&file, &line))>0 ){
424 if( n<1 ) continue
425 /**
426 ** Reminder: corner-case: a line with 1 byte and no newline.
427 */;
428 z = blob_buffer(&line);
429 if('\n'==z[n-1]){
430 z[n-1] = 0;
431 }
432
433 if((n>1) && ('\r'==z[n-2])){
434 if(n==2) continue /*empty line*/;
435 z[n-2] = 0;
436 }
437 if(!z[0]) continue;
438 newArgv[j++] = z;
439 if( z[0]=='-' ){
440 for(k=1; z[k] && !fossil_isspace(z[k]); k++){}
441 if( z[k] ){
442 z[k] = 0;
443 k++;
444 if( z[k] ) newArgv[j++] = &z[k];
445 }
446 }
447 }
448 i += 2;
449 while( i<g.argc ) newArgv[j++] = g.argv[i++];
450 newArgv[j] = 0;
451 g.argc = j;
452 g.argv = newArgv;
453 }
454
455 #ifdef FOSSIL_ENABLE_TCL
456 /*
457 ** Make a deep copy of the provided argument array and return it.
458 */
459 static char **copy_args(int argc, char **argv){
460 char **zNewArgv;
461 int i;
462 zNewArgv = fossil_malloc( sizeof(char*)*(argc+1) );
463 memset(zNewArgv, 0, sizeof(char*)*(argc+1));
464 for(i=0; i<argc; i++){
465 zNewArgv[i] = fossil_strdup(argv[i]);
466 }
467 return zNewArgv;
468 }
469 #endif
470
471 /*
472 ** Returns a name for a SQLite return code.
473 */
474 static const char *fossil_sqlite_return_code_name(int rc){
475 static char zCode[30];
476 switch( rc & 0xff ){
477 case SQLITE_OK: return "SQLITE_OK";
478 case SQLITE_ERROR: return "SQLITE_ERROR";
479 case SQLITE_INTERNAL: return "SQLITE_INTERNAL";
480 case SQLITE_PERM: return "SQLITE_PERM";
481 case SQLITE_ABORT: return "SQLITE_ABORT";
482 case SQLITE_BUSY: return "SQLITE_BUSY";
483 case SQLITE_LOCKED: return "SQLITE_LOCKED";
484 case SQLITE_NOMEM: return "SQLITE_NOMEM";
485 case SQLITE_READONLY: return "SQLITE_READONLY";
486 case SQLITE_INTERRUPT: return "SQLITE_INTERRUPT";
487 case SQLITE_IOERR: return "SQLITE_IOERR";
488 case SQLITE_CORRUPT: return "SQLITE_CORRUPT";
489 case SQLITE_NOTFOUND: return "SQLITE_NOTFOUND";
490 case SQLITE_FULL: return "SQLITE_FULL";
491 case SQLITE_CANTOPEN: return "SQLITE_CANTOPEN";
492 case SQLITE_PROTOCOL: return "SQLITE_PROTOCOL";
493 case SQLITE_EMPTY: return "SQLITE_EMPTY";
494 case SQLITE_SCHEMA: return "SQLITE_SCHEMA";
495 case SQLITE_TOOBIG: return "SQLITE_TOOBIG";
496 case SQLITE_CONSTRAINT: return "SQLITE_CONSTRAINT";
497 case SQLITE_MISMATCH: return "SQLITE_MISMATCH";
498 case SQLITE_MISUSE: return "SQLITE_MISUSE";
499 case SQLITE_NOLFS: return "SQLITE_NOLFS";
500 case SQLITE_AUTH: return "SQLITE_AUTH";
501 case SQLITE_FORMAT: return "SQLITE_FORMAT";
502 case SQLITE_RANGE: return "SQLITE_RANGE";
503 case SQLITE_NOTADB: return "SQLITE_NOTADB";
504 case SQLITE_NOTICE: return "SQLITE_NOTICE";
505 case SQLITE_WARNING: return "SQLITE_WARNING";
506 case SQLITE_ROW: return "SQLITE_ROW";
507 case SQLITE_DONE: return "SQLITE_DONE";
508 default: {
509 sqlite3_snprintf(sizeof(zCode), zCode, "SQLite return code %d", rc);
510 }
511 }
512 return zCode;
513 }
514
515 /* Error logs from SQLite */
516 static void fossil_sqlite_log(void *notUsed, int iCode, const char *zErrmsg){
517 #ifdef __APPLE__
518 /* Disable the file alias warning on apple products because Time Machine
519 ** creates lots of aliases and the warning alarms people. */
520 if( iCode==SQLITE_WARNING ) return;
521 #endif
522 if( iCode==SQLITE_SCHEMA ) return;
523 if( g.dbIgnoreErrors ) return;
524 fossil_warning("%s: %s", fossil_sqlite_return_code_name(iCode), zErrmsg);
525 }
526
527 /*
528 ** This function attempts to find command line options known to contain
529 ** bitwise flags and initializes the associated global variables. After
530 ** this function executes, all global variables (i.e. in the "g" struct)
531 ** containing option-settable bitwise flag fields must be initialized.
532 */
533 static void fossil_init_flags_from_options(void){
534 const char *zValue = find_option("comfmtflags", 0, 1);
535 if( zValue ){
536 g.comFmtFlags = atoi(zValue);
537 }else{
538 g.comFmtFlags = COMMENT_PRINT_DEFAULT;
539 }
540 }
541
542 /*
543 ** This procedure runs first.
544 */
545 #if defined(_WIN32) && !defined(BROKEN_MINGW_CMDLINE)
546 int _dowildcard = -1; /* This turns on command-line globbing in MinGW-w64 */
547 int wmain(int argc, wchar_t **argv)
548 #else
549 #if defined(_WIN32)
550 int _CRT_glob = 0x0001; /* See MinGW bug #2062 */
551 #endif
552 int main(int argc, char **argv)
553 #endif
554 {
555 const char *zCmdName = "unknown";
556 const CmdOrPage *pCmd = 0;
557 int rc;
558
559 fossil_limit_memory(1);
560 if( sqlite3_libversion_number()<3014000 ){
561 fossil_fatal("Unsuitable SQLite version %s, must be at least 3.14.0",
562 sqlite3_libversion());
563 }
564 sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
565 sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0);
566 memset(&g, 0, sizeof(g));
567 g.now = time(0);
568 g.httpHeader = empty_blob;
569 #ifdef FOSSIL_ENABLE_JSON
570 #if defined(NDEBUG)
571 g.json.errorDetailParanoia = 2 /* FIXME: make configurable
572 One problem we have here is that this
573 code is needed before the db is opened,
574 so we can't sql for it.*/;
575 #else
576 g.json.errorDetailParanoia = 0;
577 #endif
578 g.json.outOpt = cson_output_opt_empty;
579 g.json.outOpt.addNewline = 1;
580 g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */;
581 #endif /* FOSSIL_ENABLE_JSON */
582 expand_args_option(argc, argv);
583 #ifdef FOSSIL_ENABLE_TCL
584 memset(&g.tcl, 0, sizeof(TclContext));
585 g.tcl.argc = g.argc;
586 g.tcl.argv = copy_args(g.argc, g.argv); /* save full arguments */
587 #endif
588 g.mainTimerId = fossil_timer_start();
589 capture_case_sensitive_option();
590 g.zVfsName = find_option("vfs",0,1);
591 if( g.zVfsName==0 ){
592 g.zVfsName = fossil_getenv("FOSSIL_VFS");
593 }
594 if( g.zVfsName ){
595 sqlite3_vfs *pVfs = sqlite3_vfs_find(g.zVfsName);
596 if( pVfs ){
597 sqlite3_vfs_register(pVfs, 1);
598 }else{
599 fossil_fatal("no such VFS: \"%s\"", g.zVfsName);
600 }
601 }
602 if( fossil_getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){
603 zCmdName = "cgi";
604 g.isHTTP = 1;
605 }else if( g.argc<2 ){
606 fossil_print(
607 "Usage: %s COMMAND ...\n"
608 " or: %s help -- for a list of common commands\n"
609 " or: %s help COMMAND -- for help with the named command\n",
610 g.argv[0], g.argv[0], g.argv[0]);
611 fossil_print(
612 "\nCommands and filenames may be passed on to fossil from a file\n"
613 "by using:\n"
614 "\n %s --args FILENAME ...\n",
615 g.argv[0]
616 );
617 fossil_print(
618 "\nEach line of the file is assumed to be a filename unless it starts\n"
619 "with '-' and contains a space, in which case it is assumed to be\n"
620 "another flag and is treated as such. --args FILENAME may be used\n"
621 "in conjunction with any other flags.\n");
622 fossil_exit(1);
623 }else{
624 const char *zChdir = find_option("chdir",0,1);
625 g.isHTTP = 0;
626 g.rcvid = 0;
627 g.fNoDirSymlinks = find_option("no-dir-symlinks", 0, 0)!=0;
628 g.fQuiet = find_option("quiet", 0, 0)!=0;
629 g.fSqlTrace = find_option("sqltrace", 0, 0)!=0;
630 g.fSqlStats = find_option("sqlstats", 0, 0)!=0;
631 g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
632 g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
633 g.fSshClient = 0;
634 g.zSshCmd = 0;
635 if( g.fSqlTrace ) g.fSqlStats = 1;
636 g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
637 #ifdef FOSSIL_ENABLE_TH1_HOOKS
638 g.fNoThHook = find_option("no-th-hook", 0, 0)!=0;
639 #endif
640 g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|g.fHttpTrace;
641 g.zHttpAuth = 0;
642 g.zLogin = find_option("user", "U", 1);
643 g.zSSLIdentity = find_option("ssl-identity", 0, 1);
644 g.zErrlog = find_option("errorlog", 0, 1);
645 fossil_init_flags_from_options();
646 if( find_option("utc",0,0) ) g.fTimeFormat = 1;
647 if( find_option("localtime",0,0) ) g.fTimeFormat = 2;
648 if( zChdir && file_chdir(zChdir, 0) ){
649 fossil_fatal("unable to change directories to %s", zChdir);
650 }
651 if( find_option("help",0,0)!=0 ){
652 /* If --help is found anywhere on the command line, translate the command
653 * to "fossil help cmdname" where "cmdname" is the first argument that
654 * does not begin with a "-" character. If all arguments start with "-",
655 * translate to "fossil help argv[1] argv[2]...". */
656 int i, nNewArgc;
657 char **zNewArgv = fossil_malloc( sizeof(char*)*(g.argc+2) );
658 zNewArgv[0] = g.argv[0];
659 zNewArgv[1] = "help";
660 for(i=1; i<g.argc; i++){
661 if( g.argv[i][0]!='-' ){
662 nNewArgc = 3;
663 zNewArgv[2] = g.argv[i];
664 zNewArgv[3] = 0;
665 break;
666 }
667 }
668 if( i==g.argc ){
669 for(i=1; i<g.argc; i++) zNewArgv[i+1] = g.argv[i];
670 nNewArgc = g.argc+1;
671 zNewArgv[i+1] = 0;
672 }
673 g.argc = nNewArgc;
674 g.argv = zNewArgv;
675 }
676 zCmdName = g.argv[1];
677 }
678 #ifndef _WIN32
679 /* There is a bug in stunnel4 in which it sometimes starts up client
680 ** processes without first opening file descriptor 2 (standard error).
681 ** If this happens, and a subsequent open() of a database returns file
682 ** descriptor 2, and then an assert() fires and writes on fd 2, that
683 ** can corrupt the data file. To avoid this problem, make sure open()
684 ** will never return file descriptor 2 or less. */
685 if( !is_valid_fd(2) ){
686 int nTry = 0;
687 int fd = 0;
688 int x = 0;
689 do{
690 fd = open("/dev/null",O_WRONLY);
691 if( fd>=2 ) break;
692 if( fd<0 ) x = errno;
693 }while( nTry++ < 2 );
694 if( fd<2 ){
695 g.cgiOutput = 1;
696 g.httpOut = stdout;
697 g.fullHttpReply = !g.isHTTP;
698 fossil_fatal("file descriptor 2 is not open. (fd=%d, errno=%d)",
699 fd, x);
700 }
701 }
702 #endif
703 rc = dispatch_name_search(zCmdName, CMDFLAG_COMMAND|CMDFLAG_PREFIX, &pCmd);
704 if( rc==1 ){
705 #ifdef FOSSIL_ENABLE_TH1_HOOKS
706 if( !g.isHTTP && !g.fNoThHook ){
707 rc = Th_CommandHook(zCmdName, 0);
708 }else{
709 rc = TH_OK;
710 }
711 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
712 if( rc==TH_OK || rc==TH_RETURN ){
713 #endif
714 fossil_fatal("%s: unknown command: %s\n"
715 "%s: use \"help\" for more information",
716 g.argv[0], zCmdName, g.argv[0]);
717 #ifdef FOSSIL_ENABLE_TH1_HOOKS
718 }
719 if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
720 Th_CommandNotify(zCmdName, 0);
721 }
722 }
723 fossil_exit(0);
724 #endif
725 }else if( rc==2 ){
726 Blob couldbe;
727 blob_init(&couldbe,0,0);
728 dispatch_matching_names(zCmdName, &couldbe);
729 fossil_print("%s: ambiguous command prefix: %s\n"
730 "%s: could be any of:%s\n"
731 "%s: use \"help\" for more information\n",
732 g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]);
733 fossil_exit(1);
734 }
735 atexit( fossil_atexit );
736 #ifdef FOSSIL_ENABLE_TH1_HOOKS
737 /*
738 ** The TH1 return codes from the hook will be handled as follows:
739 **
740 ** TH_OK: The xFunc() and the TH1 notification will both be executed.
741 **
742 ** TH_ERROR: The xFunc() will be executed, the TH1 notification will be
743 ** skipped. If the xFunc() is being hooked, the error message
744 ** will be emitted.
745 **
746 ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped.
747 **
748 ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be
749 ** skipped.
750 **
751 ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be
752 ** executed.
753 */
754 if( !g.isHTTP && !g.fNoThHook ){
755 rc = Th_CommandHook(pCmd->zName, pCmd->eCmdFlags);
756 }else{
757 rc = TH_OK;
758 }
759 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
760 if( rc==TH_OK || rc==TH_RETURN ){
761 #endif
762 pCmd->xFunc();
763 #ifdef FOSSIL_ENABLE_TH1_HOOKS
764 }
765 if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
766 Th_CommandNotify(pCmd->zName, pCmd->eCmdFlags);
767 }
768 }
769 #endif
770 fossil_exit(0);
771 /*NOT_REACHED*/
772 return 0;
773 }
774
775 /*
776 ** Print a usage comment and quit
777 */
778 void usage(const char *zFormat){
779 fossil_fatal("Usage: %s %s %s", g.argv[0], g.argv[1], zFormat);
780 }
781
782 /*
783 ** Remove n elements from g.argv beginning with the i-th element.
784 */
785 static void remove_from_argv(int i, int n){
786 int j;
787 for(j=i+n; j<g.argc; i++, j++){
788 g.argv[i] = g.argv[j];
789 }
790 g.argc = i;
791 }
792
793
794 /*
795 ** Look for a command-line option. If present, return a pointer.
796 ** Return NULL if missing.
797 **
798 ** hasArg==0 means the option is a flag. It is either present or not.
799 ** hasArg==1 means the option has an argument. Return a pointer to the
800 ** argument.
801 */
802 const char *find_option(const char *zLong, const char *zShort, int hasArg){
803 int i;
804 int nLong;
805 const char *zReturn = 0;
806 assert( hasArg==0 || hasArg==1 );
807 nLong = strlen(zLong);
808 for(i=1; i<g.argc; i++){
809 char *z;
810 if( i+hasArg >= g.argc ) break;
811 z = g.argv[i];
812 if( z[0]!='-' ) continue;
813 z++;
814 if( z[0]=='-' ){
815 if( z[1]==0 ){
816 remove_from_argv(i, 1);
817 break;
818 }
819 z++;
820 }
821 if( strncmp(z,zLong,nLong)==0 ){
822 if( hasArg && z[nLong]=='=' ){
823 zReturn = &z[nLong+1];
824 remove_from_argv(i, 1);
825 break;
826 }else if( z[nLong]==0 ){
827 zReturn = g.argv[i+hasArg];
828 remove_from_argv(i, 1+hasArg);
829 break;
830 }
831 }else if( fossil_strcmp(z,zShort)==0 ){
832 zReturn = g.argv[i+hasArg];
833 remove_from_argv(i, 1+hasArg);
834 break;
835 }
836 }
837 return zReturn;
838 }
839
840 /*
841 ** Look for multiple occurrences of a command-line option with the
842 ** corresponding argument.
843 **
844 ** Return a malloc allocated array of pointers to the arguments.
845 **
846 ** pnUsedArgs is used to store the number of matched arguments.
847 **
848 ** Caller is responsible to free allocated memory.
849 */
850 const char **find_repeatable_option(
851 const char *zLong,
852 const char *zShort,
853 int *pnUsedArgs
854 ){
855 const char *zOption;
856 const char **pzArgs = 0;
857 int nAllocArgs = 0;
858 int nUsedArgs = 0;
859
860 while( (zOption = find_option(zLong, zShort, 1))!=0 ){
861 if( pzArgs==0 && nAllocArgs==0 ){
862 nAllocArgs = 1;
863 pzArgs = fossil_malloc( nAllocArgs*sizeof(pzArgs[0]) );
864 }else if( nAllocArgs<=nUsedArgs ){
865 nAllocArgs = nAllocArgs*2;
866 pzArgs = fossil_realloc( (void *)pzArgs, nAllocArgs*sizeof(pzArgs[0]) );
867 }
868 pzArgs[nUsedArgs++] = zOption;
869 }
870 *pnUsedArgs = nUsedArgs;
871 return pzArgs;
872 }
873
874 /*
875 ** Look for a repository command-line option. If present, [re-]cache it in
876 ** the global state and return the new pointer, freeing any previous value.
877 ** If absent and there is no cached value, return NULL.
878 */
879 const char *find_repository_option(){
880 const char *zRepository = find_option("repository", "R", 1);
881 if( zRepository ){
882 if( g.zRepositoryOption ) fossil_free(g.zRepositoryOption);
883 g.zRepositoryOption = mprintf("%s", zRepository);
884 }
885 return g.zRepositoryOption;
886 }
887
888 /*
889 ** Verify that there are no unprocessed command-line options. If
890 ** Any remaining command-line argument begins with "-" print
891 ** an error message and quit.
892 */
893 void verify_all_options(void){
894 int i;
895 for(i=1; i<g.argc; i++){
896 if( g.argv[i][0]=='-' && g.argv[i][1]!=0 ){
897 fossil_fatal(
898 "unrecognized command-line option, or missing argument: %s",
899 g.argv[i]);
900 }
901 }
902 }
903
904 /*
905 ** Print a list of words in multiple columns.
906 */
907
908
909 /*
910 ** This function returns a human readable version string.
911 */
912 const char *get_version(){
913 static const char version[] = RELEASE_VERSION " " MANIFEST_VERSION " "
914 MANIFEST_DATE " UTC";
915 return version;
916 }
917
918 /*
919 ** This function populates a blob with version information. It is used by
920 ** the "version" command and "test-version" web page. It assumes the blob
921 ** passed to it is uninitialized; otherwise, it will leak memory.
922 */
923 static void get_version_blob(
924 Blob *pOut, /* Write the manifest here */
925 int bVerbose /* Non-zero for full information. */
926 ){
927 #if defined(FOSSIL_ENABLE_TCL)
928 int rc;
929 const char *zRc;
930 #endif
931 Stmt q;
932 blob_zero(pOut);
933 blob_appendf(pOut, "This is fossil version %s\n", get_version());
934 if( !bVerbose ) return;
935 blob_appendf(pOut, "Compiled on %s %s using %s (%d-bit)\n",
936 __DATE__, __TIME__, COMPILER_NAME, sizeof(void*)*8);
937 blob_appendf(pOut, "Schema version %s\n", AUX_SCHEMA_MAX);
938 #if defined(FOSSIL_ENABLE_MINIZ)
939 blob_appendf(pOut, "miniz %s, loaded %s\n", MZ_VERSION, mz_version());
940 #else
941 blob_appendf(pOut, "zlib %s, loaded %s\n", ZLIB_VERSION, zlibVersion());
942 #endif
943 #if FOSSIL_HARDENED_SHA1
944 blob_appendf(pOut, "hardened-SHA1 by Marc Stevens and Dan Shumow\n");
945 #endif
946 #if defined(FOSSIL_ENABLE_SSL)
947 blob_appendf(pOut, "SSL (%s)\n", SSLeay_version(SSLEAY_VERSION));
948 #endif
949 #if defined(FOSSIL_HAVE_FUSEFS)
950 blob_appendf(pOut, "libfuse %s, loaded %s\n", fusefs_inc_version(),
951 fusefs_lib_version());
952 #endif
953 #if defined(FOSSIL_DEBUG)
954 blob_append(pOut, "FOSSIL_DEBUG\n", -1);
955 #endif
956 #if defined(FOSSIL_OMIT_DELTA_CKSUM_TEST)
957 blob_append(pOut, "FOSSIL_OMIT_DELTA_CKSUM_TEST\n", -1);
958 #endif
959 #if defined(FOSSIL_ENABLE_LEGACY_MV_RM)
960 blob_append(pOut, "FOSSIL_ENABLE_LEGACY_MV_RM\n", -1);
961 #endif
962 #if defined(FOSSIL_ENABLE_EXEC_REL_PATHS)
963 blob_append(pOut, "FOSSIL_ENABLE_EXEC_REL_PATHS\n", -1);
964 #endif
965 #if defined(FOSSIL_ENABLE_TH1_DOCS)
966 blob_append(pOut, "FOSSIL_ENABLE_TH1_DOCS\n", -1);
967 #endif
968 #if defined(FOSSIL_ENABLE_TH1_HOOKS)
969 blob_append(pOut, "FOSSIL_ENABLE_TH1_HOOKS\n", -1);
970 #endif
971 #if defined(FOSSIL_ENABLE_TCL)
972 Th_FossilInit(TH_INIT_DEFAULT | TH_INIT_FORCE_TCL);
973 rc = Th_Eval(g.interp, 0, "tclInvoke info patchlevel", -1);
974 zRc = Th_ReturnCodeName(rc, 0);
975 blob_appendf(pOut, "TCL (Tcl %s, loaded %s: %s)\n",
976 TCL_PATCH_LEVEL, zRc, Th_GetResult(g.interp, 0)
977 );
978 #endif
979 #if defined(USE_TCL_STUBS)
980 blob_append(pOut, "USE_TCL_STUBS\n", -1);
981 #endif
982 #if defined(FOSSIL_ENABLE_TCL_STUBS)
983 blob_append(pOut, "FOSSIL_TCL_STUBS\n", -1);
984 #endif
985 #if defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS)
986 blob_append(pOut, "FOSSIL_ENABLE_TCL_PRIVATE_STUBS\n", -1);
987 #endif
988 #if defined(FOSSIL_ENABLE_JSON)
989 blob_appendf(pOut, "JSON (API %s)\n", FOSSIL_JSON_API_VERSION);
990 #endif
991 #if defined(BROKEN_MINGW_CMDLINE)
992 blob_append(pOut, "MBCS_COMMAND_LINE\n", -1);
993 #else
994 blob_append(pOut, "UNICODE_COMMAND_LINE\n", -1);
995 #endif
996 #if defined(FOSSIL_DYNAMIC_BUILD)
997 blob_append(pOut, "FOSSIL_DYNAMIC_BUILD\n", -1);
998 #else
999 blob_append(pOut, "FOSSIL_STATIC_BUILD\n", -1);
1000 #endif
1001 #if defined(USE_SEE)
1002 blob_append(pOut, "USE_SEE\n", -1);
1003 #endif
1004 #if defined(FOSSIL_ALLOW_OUT_OF_ORDER_DATES)
1005 blob_append(pOut, "FOSSIL_ALLOW_OUT_OF_ORDER_DATES\n");
1006 #endif
1007 blob_appendf(pOut, "SQLite %s %.30s\n", sqlite3_libversion(),
1008 sqlite3_sourceid());
1009 if( g.db==0 ) sqlite3_open(":memory:", &g.db);
1010 db_prepare(&q,
1011 "pragma compile_options");
1012 while( db_step(&q)==SQLITE_ROW ){
1013 const char *text = db_column_text(&q, 0);
1014 if( strncmp(text, "COMPILER", 8) ){
1015 blob_appendf(pOut, "SQLITE_%s\n", text);
1016 }
1017 }
1018 db_finalize(&q);
1019 }
1020
1021 /*
1022 ** This function returns the user-agent string for Fossil, for
1023 ** use in HTTP(S) requests.
1024 */
1025 const char *get_user_agent(){
1026 static const char version[] = "Fossil/" RELEASE_VERSION " (" MANIFEST_DATE
1027 " " MANIFEST_VERSION ")";
1028 return version;
1029 }
1030
1031
1032 /*
1033 ** COMMAND: version
1034 **
1035 ** Usage: %fossil version ?-verbose|-v?
1036 **
1037 ** Print the source code version number for the fossil executable.
1038 ** If the verbose option is specified, additional details will
1039 ** be output about what optional features this binary was compiled
1040 ** with
1041 */
1042 void version_cmd(void){
1043 Blob versionInfo;
1044 int verboseFlag = find_option("verbose","v",0)!=0;
1045
1046 /* We should be done with options.. */
1047 verify_all_options();
1048 get_version_blob(&versionInfo, verboseFlag);
1049 fossil_print("%s", blob_str(&versionInfo));
1050 }
1051
1052
1053 /*
1054 ** WEBPAGE: version
1055 **
1056 ** Show the version information for Fossil.
1057 **
1058 ** Query parameters:
1059 **
1060 ** verbose Show details
1061 */
1062 void test_version_page(void){
1063 Blob versionInfo;
1064 int verboseFlag;
1065
1066 login_check_credentials();
1067 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1068 verboseFlag = PD("verbose", 0) != 0;
1069 style_header("Version Information");
1070 style_submenu_element("Stat", "stat");
1071 get_version_blob(&versionInfo, verboseFlag);
1072 @ <pre>
1073 @ %h(blob_str(&versionInfo))
1074 @ </pre>
1075 style_footer();
1076 }
1077
1078
1079 /*
1080 ** Set the g.zBaseURL value to the full URL for the toplevel of
1081 ** the fossil tree. Set g.zTop to g.zBaseURL without the
1082 ** leading "http://" and the host and port.
1083 **
1084 ** The g.zBaseURL is normally set based on HTTP_HOST and SCRIPT_NAME
1085 ** environment variables. However, if zAltBase is not NULL then it
1086 ** is the argument to the --baseurl option command-line option and
1087 ** g.zBaseURL and g.zTop is set from that instead.
1088 */
1089 static void set_base_url(const char *zAltBase){
1090 int i;
1091 const char *zHost;
1092 const char *zMode;
1093 const char *zCur;
1094
1095 if( g.zBaseURL!=0 ) return;
1096 if( zAltBase ){
1097 int i, n, c;
1098 g.zTop = g.zBaseURL = mprintf("%s", zAltBase);
1099 if( strncmp(g.zTop, "http://", 7)==0 ){
1100 /* it is HTTP, replace prefix with HTTPS. */
1101 g.zHttpsURL = mprintf("https://%s", &g.zTop[7]);
1102 }else if( strncmp(g.zTop, "https://", 8)==0 ){
1103 /* it is already HTTPS, use it. */
1104 g.zHttpsURL = mprintf("%s", g.zTop);
1105 }else{
1106 fossil_fatal("argument to --baseurl should be 'http://host/path'"
1107 " or 'https://host/path'");
1108 }
1109 for(i=n=0; (c = g.zTop[i])!=0; i++){
1110 if( c=='/' ){
1111 n++;
1112 if( n==3 ){
1113 g.zTop += i;
1114 break;
1115 }
1116 }
1117 }
1118 if( g.zTop==g.zBaseURL ){
1119 fossil_fatal("argument to --baseurl should be 'http://host/path'"
1120 " or 'https://host/path'");
1121 }
1122 if( g.zTop[1]==0 ) g.zTop++;
1123 }else{
1124 zHost = PD("HTTP_HOST","");
1125 zMode = PD("HTTPS","off");
1126 zCur = PD("SCRIPT_NAME","/");
1127 i = strlen(zCur);
1128 while( i>0 && zCur[i-1]=='/' ) i--;
1129 if( fossil_stricmp(zMode,"on")==0 ){
1130 g.zBaseURL = mprintf("https://%s%.*s", zHost, i, zCur);
1131 g.zTop = &g.zBaseURL[8+strlen(zHost)];
1132 g.zHttpsURL = g.zBaseURL;
1133 }else{
1134 g.zBaseURL = mprintf("http://%s%.*s", zHost, i, zCur);
1135 g.zTop = &g.zBaseURL[7+strlen(zHost)];
1136 g.zHttpsURL = mprintf("https://%s%.*s", zHost, i, zCur);
1137 }
1138 }
1139 if( db_is_writeable("repository") ){
1140 if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", g.zBaseURL)){
1141 db_multi_exec("INSERT INTO config(name,value,mtime)"
1142 "VALUES('baseurl:%q',1,now())", g.zBaseURL);
1143 }else{
1144 db_optional_sql("repository",
1145 "REPLACE INTO config(name,value,mtime)"
1146 "VALUES('baseurl:%q',1,now())", g.zBaseURL
1147 );
1148 }
1149 }
1150 }
1151
1152 /*
1153 ** Send an HTTP redirect back to the designated Index Page.
1154 */
1155 NORETURN void fossil_redirect_home(void){
1156 cgi_redirectf("%s%s", g.zTop, db_get("index-page", "/index"));
1157 }
1158
1159 /*
1160 ** If running as root, chroot to the directory containing the
1161 ** repository zRepo and then drop root privileges. Return the
1162 ** new repository name.
1163 **
1164 ** zRepo might be a directory itself. In that case chroot into
1165 ** the directory zRepo.
1166 **
1167 ** Assume the user-id and group-id of the repository, or if zRepo
1168 ** is a directory, of that directory.
1169 **
1170 ** The noJail flag means that the chroot jail is not entered. But
1171 ** privileges are still lowered to that of the user-id and group-id
1172 ** of the repository file.
1173 */
1174 static char *enter_chroot_jail(char *zRepo, int noJail){
1175 #if !defined(_WIN32)
1176 if( getuid()==0 ){
1177 int i;
1178 struct stat sStat;
1179 Blob dir;
1180 char *zDir;
1181 if( g.db!=0 ){
1182 db_close(1);
1183 }
1184
1185 file_canonical_name(zRepo, &dir, 0);
1186 zDir = blob_str(&dir);
1187 if( !noJail ){
1188 if( file_isdir(zDir)==1 ){
1189 if( file_chdir(zDir, 1) ){
1190 fossil_fatal("unable to chroot into %s", zDir);
1191 }
1192 g.fJail = 1;
1193 zRepo = "/";
1194 }else{
1195 for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
1196 if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
1197 if( i>0 ){
1198 zDir[i] = 0;
1199 if( file_chdir(zDir, 1) ){
1200 fossil_fatal("unable to chroot into %s", zDir);
1201 }
1202 zDir[i] = '/';
1203 }
1204 zRepo = &zDir[i];
1205 }
1206 }
1207 if( stat(zRepo, &sStat)!=0 ){
1208 fossil_fatal("cannot stat() repository: %s", zRepo);
1209 }
1210 i = setgid(sStat.st_gid);
1211 i = i || setuid(sStat.st_uid);
1212 if(i){
1213 fossil_fatal("setgid/uid() failed with errno %d", errno);
1214 }
1215 if( g.db==0 && file_isfile(zRepo) ){
1216 db_open_repository(zRepo);
1217 }
1218 }
1219 #endif
1220 return zRepo;
1221 }
1222
1223 /*
1224 ** Generate a web-page that lists all repositories located under the
1225 ** g.zRepositoryName directory and return non-zero.
1226 **
1227 ** For the special case when g.zRepositoryName a non-chroot-jail "/",
1228 ** compose the list using the "repo:" entries in the global_config
1229 ** table of the configuration database. These entries comprise all
1230 ** of the repositories known to the "all" command. The special case
1231 ** processing is disallowed for chroot jails because g.zRepositoryName
1232 ** is always "/" inside a chroot jail and so it cannot be used as a flag
1233 ** to signal the special processing in that case. The special case
1234 ** processing is intended for the "fossil all ui" command which never
1235 ** runs in a chroot jail anyhow.
1236 **
1237 ** Or, if no repositories can be located beneath g.zRepositoryName,
1238 ** return 0.
1239 */
1240 static int repo_list_page(void){
1241 Blob base;
1242 int n = 0;
1243 int allRepo;
1244
1245 assert( g.db==0 );
1246 if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
1247 /* For the special case of the "repository directory" being "/",
1248 ** show all of the repositories named in the ~/.fossil database.
1249 **
1250 ** On unix systems, then entries are of the form "repo:/home/..."
1251 ** and on Windows systems they are like on unix, starting with a "/"
1252 ** or they can begin with a drive letter: "repo:C:/Users/...". In either
1253 ** case, we want returned path to omit any initial "/".
1254 */
1255 db_open_config(1, 0);
1256 db_multi_exec(
1257 "CREATE TEMP VIEW sfile AS"
1258 " SELECT ltrim(substr(name,6),'/') AS 'pathname' FROM global_config"
1259 " WHERE name GLOB 'repo:*'"
1260 );
1261 allRepo = 1;
1262 }else{
1263 /* The default case: All repositories under the g.zRepositoryName
1264 ** directory.
1265 */
1266 blob_init(&base, g.zRepositoryName, -1);
1267 sqlite3_open(":memory:", &g.db);
1268 db_multi_exec("CREATE TABLE sfile(pathname TEXT);");
1269 db_multi_exec("CREATE TABLE vfile(pathname);");
1270 vfile_scan(&base, blob_size(&base), 0, 0, 0);
1271 db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'");
1272 allRepo = 0;
1273 }
1274 @ <html>
1275 @ <head>
1276 @ <base href="%s(g.zBaseURL)/" />
1277 @ <title>Repository List</title>
1278 @ </head>
1279 @ <body>
1280 n = db_int(0, "SELECT count(*) FROM sfile");
1281 if( n>0 ){
1282 Stmt q;
1283 @ <h1>Available Repositories:</h1>
1284 @ <ol>
1285 db_prepare(&q, "SELECT pathname"
1286 " FROM sfile ORDER BY pathname COLLATE nocase;");
1287 while( db_step(&q)==SQLITE_ROW ){
1288 const char *zName = db_column_text(&q, 0);
1289 int nName = (int)strlen(zName);
1290 char *zUrl;
1291 if( nName<7 ) continue;
1292 zUrl = sqlite3_mprintf("%.*s", nName-7, zName);
1293 if( sqlite3_strglob("*.fossil", zName)!=0 ){
1294 /* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands
1295 ** do not work for repositories whose names do not end in ".fossil".
1296 ** So do not hyperlink those cases. */
1297 @ <li>%h(zName)</li>
1298 } else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){
1299 @ <li><a href="%R/%T(zUrl)/home" target="_blank">/%h(zName)</a></li>
1300 }else{
1301 @ <li><a href="%R/%T(zUrl)/home" target="_blank">%h(zName)</a></li>
1302 }
1303 sqlite3_free(zUrl);
1304 }
1305 @ </ol>
1306 }else{
1307 @ <h1>No Repositories Found</h1>
1308 }
1309 @ </body>
1310 @ </html>
1311 cgi_reply();
1312 sqlite3_close(g.db);
1313 g.db = 0;
1314 return n;
1315 }
1316
1317 /*
1318 ** Preconditions:
1319 **
1320 ** * Environment variables are set up according to the CGI standard.
1321 **
1322 ** If the repository is known, it has already been opened. If unknown,
1323 ** then g.zRepositoryName holds the directory that contains the repository
1324 ** and the actual repository is taken from the first element of PATH_INFO.
1325 **
1326 ** Process the webpage specified by the PATH_INFO or REQUEST_URI
1327 ** environment variable.
1328 **
1329 ** If the repository is not known, then a search is done through the
1330 ** file hierarchy rooted at g.zRepositoryName for a suitable repository
1331 ** with a name of $prefix.fossil, where $prefix is any prefix of PATH_INFO.
1332 ** Or, if an ordinary file named $prefix is found, and $prefix matches
1333 ** pFileGlob and $prefix does not match "*.fossil*" and the mimetype of
1334 ** $prefix can be determined from its suffix, then the file $prefix is
1335 ** returned as static text.
1336 **
1337 ** If no suitable webpage is found, try to redirect to zNotFound.
1338 */
1339 static void process_one_web_page(
1340 const char *zNotFound, /* Redirect here on a 404 if not NULL */
1341 Glob *pFileGlob, /* Deliver static files matching */
1342 int allowRepoList /* Send repo list for "/" URL */
1343 ){
1344 const char *zPathInfo = PD("PATH_INFO", "");
1345 char *zPath = NULL;
1346 int i;
1347 const CmdOrPage *pCmd = 0;
1348 const char *zBase = g.zRepositoryName;
1349
1350 /* Handle universal query parameters */
1351 if( PB("utc") ){
1352 g.fTimeFormat = 1;
1353 }else if( PB("localtime") ){
1354 g.fTimeFormat = 2;
1355 }
1356
1357 /* If the repository has not been opened already, then find the
1358 ** repository based on the first element of PATH_INFO and open it.
1359 */
1360 if( !g.repositoryOpen ){
1361 char *zRepo; /* Candidate repository name */
1362 char *zToFree = 0; /* Malloced memory that needs to be freed */
1363 const char *zCleanRepo; /* zRepo with surplus leading "/" removed */
1364 const char *zOldScript = PD("SCRIPT_NAME", ""); /* Original SCRIPT_NAME */
1365 char *zNewScript; /* Revised SCRIPT_NAME after processing */
1366 int j, k; /* Loop variables */
1367 i64 szFile; /* File size of the candidate repository */
1368
1369 i = zPathInfo[0]!=0;
1370 if( fossil_strcmp(g.zRepositoryName, "/")==0 ){
1371 zBase++;
1372 #if defined(_WIN32) || defined(__CYGWIN__)
1373 if( sqlite3_strglob("/[a-zA-Z]:/*", zPathInfo)==0 ) i = 4;
1374 #endif
1375 }
1376 while( 1 ){
1377 while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; }
1378
1379 /* The candidate repository name is some prefix of the PATH_INFO
1380 ** with ".fossil" appended */
1381 zRepo = zToFree = mprintf("%s%.*s.fossil",zBase,i,zPathInfo);
1382 if( g.fHttpTrace ){
1383 @ <!-- Looking for repository named "%h(zRepo)" -->
1384 fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo);
1385 }
1386
1387
1388 /* For safety -- to prevent an attacker from accessing arbitrary disk
1389 ** files by sending a maliciously crafted request URI to a public
1390 ** server -- make sure the repository basename contains no
1391 ** characters other than alphanumerics, "/", "_", "-", and ".", and
1392 ** that "-" never occurs immediately after a "/" and that "." is always
1393 ** surrounded by two alphanumerics. Any character that does not
1394 ** satisfy these constraints is converted into "_".
1395 */
1396 szFile = 0;
1397 for(j=strlen(zBase)+1, k=0; zRepo[j] && k<i-1; j++, k++){
1398 char c = zRepo[j];
1399 if( fossil_isalnum(c) ) continue;
1400 #if defined(_WIN32) || defined(__CYGWIN__)
1401 /* Allow names to begin with "/X:/" on windows */
1402 if( c==':' && j==2 && sqlite3_strglob("/[a-zA-Z]:/*", zRepo)==0 ){
1403 continue;
1404 }
1405 #endif
1406 if( c=='/' ) continue;
1407 if( c=='_' ) continue;
1408 if( c=='-' && zRepo[j-1]!='/' ) continue;
1409 if( c=='.' && fossil_isalnum(zRepo[j-1]) && fossil_isalnum(zRepo[j+1])){
1410 continue;
1411 }
1412 /* If we reach this point, it means that the request URI contains
1413 ** an illegal character or character combination. Provoke a
1414 ** "Not Found" error. */
1415 szFile = 1;
1416 if( g.fHttpTrace ){
1417 @ <!-- Unsafe pathname rejected: "%h(zRepo)" -->
1418 fprintf(stderr, "# unsafe pathname rejected: %s\n", zRepo);
1419 }
1420 break;
1421 }
1422
1423 /* Check to see if a file name zRepo exists. If a file named zRepo
1424 ** does not exist, szFile will become -1. If the file does exist,
1425 ** then szFile will become zero (for an empty file) or positive.
1426 ** Special case: Assume any file with a basename of ".fossil" does
1427 ** not exist.
1428 */
1429 zCleanRepo = file_cleanup_fullpath(zRepo);
1430 if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
1431 szFile = file_size(zCleanRepo);
1432 if( g.fHttpTrace ){
1433 char zBuf[24];
1434 sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", szFile);
1435 @ <!-- file_size(%h(zCleanRepo)) is %s(zBuf) -->
1436 fprintf(stderr, "# file_size(%s) = %s\n", zCleanRepo, zBuf);
1437 }
1438 }
1439
1440 /* If no file named by zRepo exists, remove the added ".fossil" suffix
1441 ** and check to see if there is a file or directory with the same
1442 ** name as the raw PATH_INFO text.
1443 */
1444 if( szFile<0 && i>0 ){
1445 const char *zMimetype;
1446 assert( fossil_strcmp(&zRepo[j], ".fossil")==0 );
1447 zRepo[j] = 0; /* Remove the ".fossil" suffix */
1448
1449 /* The PATH_INFO prefix seen so far is a valid directory.
1450 ** Continue the loop with the next element of the PATH_INFO */
1451 if( zPathInfo[i]=='/' && file_isdir(zCleanRepo)==1 ){
1452 fossil_free(zToFree);
1453 i++;
1454 continue;
1455 }
1456
1457 /* If zRepo is the name of an ordinary file that matches the
1458 ** "--file GLOB" pattern, then the CGI reply is the text of
1459 ** of the file.
1460 **
1461 ** For safety, do not allow any file whose name contains ".fossil"
1462 ** to be returned this way, to prevent complete repositories from
1463 ** being delivered accidently. This is not intended to be a
1464 ** general-purpose web server. The "--file GLOB" mechanism is
1465 ** designed to allow the delivery of a few static images or HTML
1466 ** pages.
1467 */
1468 if( pFileGlob!=0
1469 && file_isfile(zCleanRepo)
1470 && glob_match(pFileGlob, file_cleanup_fullpath(zRepo))
1471 && sqlite3_strglob("*.fossil*",zRepo)!=0
1472 && (zMimetype = mimetype_from_name(zRepo))!=0
1473 && strcmp(zMimetype, "application/x-fossil-artifact")!=0
1474 ){
1475 Blob content;
1476 blob_read_from_file(&content, file_cleanup_fullpath(zRepo));
1477 cgi_set_content_type(zMimetype);
1478 cgi_set_content(&content);
1479 cgi_reply();
1480 return;
1481 }
1482 zRepo[j] = '.';
1483 }
1484
1485 /* If we reach this point, it means that the search of the PATH_INFO
1486 ** string is finished. Either zRepo contains the name of the
1487 ** repository to be used, or else no repository could be found an
1488 ** some kind of error response is required.
1489 */
1490 if( szFile<1024 ){
1491 set_base_url(0);
1492 if( strcmp(zPathInfo,"/")==0
1493 && allowRepoList
1494 && repo_list_page() ){
1495 /* Will return a list of repositories */
1496 }else if( zNotFound ){
1497 cgi_redirect(zNotFound);
1498 }else{
1499 #ifdef FOSSIL_ENABLE_JSON
1500 if(g.json.isJsonMode){
1501 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
1502 return;
1503 }
1504 #endif
1505 @ <h1>Not Found</h1>
1506 cgi_set_status(404, "not found");
1507 cgi_reply();
1508 }
1509 return;
1510 }
1511 break;
1512 }
1513
1514 /* Add the repository name (without the ".fossil" suffix) to the end
1515 ** of SCRIPT_NAME and g.zTop and g.zBaseURL and remove the repository
1516 ** name from the beginning of PATH_INFO.
1517 */
1518 zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo);
1519 if( g.zTop ) g.zTop = mprintf("%s%.*s", g.zTop, i, zPathInfo);
1520 if( g.zBaseURL ) g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo);
1521 cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]);
1522 zPathInfo += i;
1523 cgi_replace_parameter("SCRIPT_NAME", zNewScript);
1524 db_open_repository(file_cleanup_fullpath(zRepo));
1525 if( g.fHttpTrace ){
1526 @ <!-- repository: "%h(zRepo)" -->
1527 @ <!-- translated PATH_INFO: "%h(zPathInfo)" -->
1528 @ <!-- translated SCRIPT_NAME: "%h(zNewScript)" -->
1529 fprintf(stderr,
1530 "# repository: [%s]\n"
1531 "# translated PATH_INFO = [%s]\n"
1532 "# translated SCRIPT_NAME = [%s]\n",
1533 zRepo, zPathInfo, zNewScript);
1534 if( g.zTop ){
1535 @ <!-- translated g.zTop: "%h(g.zTop)" -->
1536 fprintf(stderr, "# translated g.zTop = [%s]\n", g.zTop);
1537 }
1538 if( g.zBaseURL ){
1539 @ <!-- translated g.zBaseURL: "%h(g.zBaseURL)" -->
1540 fprintf(stderr, "# translated g.zBaseURL = [%s]\n", g.zBaseURL);
1541 }
1542 }
1543 }
1544
1545 /* At this point, the appropriate repository database file will have
1546 ** been opened. Use the first element of PATH_INFO as the page name
1547 ** and deliver the appropriate page back to the user.
1548 */
1549 if( g.zContentType &&
1550 strncmp(g.zContentType, "application/x-fossil", 20)==0 ){
1551 /* Special case: If the content mimetype shows that it is "fossil sync"
1552 ** payload, then pretend that the PATH_INFO is /xfer so that we always
1553 ** invoke the sync page. */
1554 zPathInfo = "/xfer";
1555 }
1556 set_base_url(0);
1557 if( zPathInfo==0 || zPathInfo[0]==0
1558 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
1559 /* Second special case: If the PATH_INFO is blank, issue a redirect to
1560 ** the home page identified by the "index-page" setting in the repository
1561 ** CONFIG table, to "/index" if there no "index-page" setting. */
1562 #ifdef FOSSIL_ENABLE_JSON
1563 if(g.json.isJsonMode){
1564 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
1565 fossil_exit(0);
1566 }
1567 #endif
1568 fossil_redirect_home() /*does not return*/;
1569 }else{
1570 zPath = mprintf("%s", zPathInfo);
1571 }
1572
1573 /* Make g.zPath point to the first element of the path. Make
1574 ** g.zExtra point to everything past that point.
1575 */
1576 while(1){
1577 g.zPath = &zPath[1];
1578 for(i=1; zPath[i] && zPath[i]!='/'; i++){}
1579 if( zPath[i]=='/' ){
1580 zPath[i] = 0;
1581 g.zExtra = &zPath[i+1];
1582 }else{
1583 g.zExtra = 0;
1584 }
1585 break;
1586 }
1587 #ifdef FOSSIL_ENABLE_JSON
1588 /*
1589 ** Workaround to allow us to customize some following behaviour for
1590 ** JSON mode. The problem is, we don't always know if we're in JSON
1591 ** mode at this point (namely, for GET mode we don't know but POST
1592 ** we do), so we snoop g.zPath and cheat a bit.
1593 */
1594 if( !g.json.isJsonMode && g.zPath && (0==strncmp("json",g.zPath,4)) ){
1595 g.json.isJsonMode = 1;
1596 }
1597 #endif
1598 if( g.zExtra ){
1599 /* CGI parameters get this treatment elsewhere, but places like getfile
1600 ** will use g.zExtra directly.
1601 ** Reminder: the login mechanism uses 'name' differently, and may
1602 ** eventually have a problem/collision with this.
1603 **
1604 ** Disabled by stephan when running in JSON mode because this
1605 ** particular parameter name is very common and i have had no end
1606 ** of grief with this handling. The JSON API never relies on the
1607 ** handling below, and by disabling it in JSON mode I can remove
1608 ** lots of special-case handling in several JSON handlers.
1609 */
1610 #ifdef FOSSIL_ENABLE_JSON
1611 if(!g.json.isJsonMode){
1612 #endif
1613 dehttpize(g.zExtra);
1614 cgi_set_parameter_nocopy("name", g.zExtra, 1);
1615 #ifdef FOSSIL_ENABLE_JSON
1616 }
1617 #endif
1618 }
1619
1620 /* Locate the method specified by the path and execute the function
1621 ** that implements that method.
1622 */
1623 if( dispatch_name_search(g.zPath-1, CMDFLAG_WEBPAGE, &pCmd) ){
1624 #ifdef FOSSIL_ENABLE_JSON
1625 if(g.json.isJsonMode){
1626 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,0);
1627 }else
1628 #endif
1629 {
1630 #ifdef FOSSIL_ENABLE_TH1_HOOKS
1631 int rc;
1632 if( !g.fNoThHook ){
1633 rc = Th_WebpageHook(g.zPath, 0);
1634 }else{
1635 rc = TH_OK;
1636 }
1637 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
1638 if( rc==TH_OK || rc==TH_RETURN ){
1639 #endif
1640 cgi_set_status(404,"Not Found");
1641 @ <h1>Not Found</h1>
1642 @ <p>Page not found: %h(g.zPath)</p>
1643 #ifdef FOSSIL_ENABLE_TH1_HOOKS
1644 }
1645 if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
1646 Th_WebpageNotify(g.zPath, 0);
1647 }
1648 }
1649 #endif
1650 }
1651 }else if( pCmd->xFunc!=page_xfer && db_schema_is_outofdate() ){
1652 #ifdef FOSSIL_ENABLE_JSON
1653 if(g.json.isJsonMode){
1654 json_err(FSL_JSON_E_DB_NEEDS_REBUILD,NULL,0);
1655 }else
1656 #endif
1657 {
1658 @ <h1>Server Configuration Error</h1>
1659 @ <p>The database schema on the server is out-of-date. Please ask
1660 @ the administrator to run <b>fossil rebuild</b>.</p>
1661 }
1662 }else{
1663 #ifdef FOSSIL_ENABLE_TH1_HOOKS
1664 /*
1665 ** The TH1 return codes from the hook will be handled as follows:
1666 **
1667 ** TH_OK: The xFunc() and the TH1 notification will both be executed.
1668 **
1669 ** TH_ERROR: The xFunc() will be executed, the TH1 notification will be
1670 ** skipped. If the xFunc() is being hooked, the error message
1671 ** will be emitted.
1672 **
1673 ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped.
1674 **
1675 ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be
1676 ** skipped.
1677 **
1678 ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be
1679 ** executed.
1680 */
1681 int rc;
1682 if( !g.fNoThHook ){
1683 rc = Th_WebpageHook(pCmd->zName+1, pCmd->eCmdFlags);
1684 }else{
1685 rc = TH_OK;
1686 }
1687 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
1688 if( rc==TH_OK || rc==TH_RETURN ){
1689 #endif
1690 pCmd->xFunc();
1691 #ifdef FOSSIL_ENABLE_TH1_HOOKS
1692 }
1693 if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
1694 Th_WebpageNotify(pCmd->zName+1, pCmd->eCmdFlags);
1695 }
1696 }
1697 #endif
1698 }
1699
1700 /* Return the result.
1701 */
1702 cgi_reply();
1703 }
1704
1705 /* If the CGI program contains one or more lines of the form
1706 **
1707 ** redirect: repository-filename http://hostname/path/%s
1708 **
1709 ** then control jumps here. Search each repository for an artifact ID
1710 ** or ticket ID that matches the "name" CGI parameter and for the
1711 ** first match, redirect to the corresponding URL with the "name" CGI
1712 ** parameter inserted. Paint an error page if no match is found.
1713 **
1714 ** If there is a line of the form:
1715 **
1716 ** redirect: * URL
1717 **
1718 ** Then a redirect is made to URL if no match is found. Otherwise a
1719 ** very primitive error message is returned.
1720 */
1721 static void redirect_web_page(int nRedirect, char **azRedirect){
1722 int i; /* Loop counter */
1723 const char *zNotFound = 0; /* Not found URL */
1724 const char *zName = P("name");
1725 set_base_url(0);
1726 if( zName==0 ){
1727 zName = P("SCRIPT_NAME");
1728 if( zName && zName[0]=='/' ) zName++;
1729 }
1730 if( zName && validate16(zName, strlen(zName)) ){
1731 for(i=0; i<nRedirect; i++){
1732 if( fossil_strcmp(azRedirect[i*2],"*")==0 ){
1733 zNotFound = azRedirect[i*2+1];
1734 continue;
1735 }
1736 db_open_repository(azRedirect[i*2]);
1737 if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'", zName) ||
1738 db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){
1739 cgi_redirectf(azRedirect[i*2+1] /*works-like:"%s"*/, zName);
1740 return;
1741 }
1742 db_close(1);
1743 }
1744 }
1745 if( zNotFound ){
1746 cgi_redirectf(zNotFound /*works-like:"%s"*/, zName);
1747 }else{
1748 @ <html>
1749 @ <head><title>No Such Object</title></head>
1750 @ <body>
1751 @ <p>No such object: <b>%h(zName)</b></p>
1752 @ </body>
1753 cgi_reply();
1754 }
1755 }
1756
1757 /*
1758 ** COMMAND: cgi*
1759 **
1760 ** Usage: %fossil ?cgi? FILE
1761 **
1762 ** This command causes Fossil to generate reply to a CGI request.
1763 **
1764 ** The FILE argument is the name of a control file that provides Fossil
1765 ** with important information such as where to find its repository. In
1766 ** a typical CGI deployment, FILE is the name of the CGI script and will
1767 ** typically look something like this:
1768 **
1769 ** #!/usr/bin/fossil
1770 ** repository: /home/somebody/project.db
1771 **
1772 ** The command name, "cgi", may be omitted if the GATEWAY_INTERFACE
1773 ** environment variable is set to "CGI", which should always be the
1774 ** case for CGI scripts run by a webserver. Fossil ignores any lines
1775 ** that begin with "#".
1776 **
1777 ** The following control lines are recognized:
1778 **
1779 ** repository: PATH Name of the Fossil repository
1780 **
1781 ** directory: PATH Name of a directory containing many Fossil
1782 ** repositories whose names all end with ".fossil".
1783 ** There should only be one of "repository:"
1784 ** or "directory:"
1785 **
1786 ** notfound: URL When in "directory:" mode, redirect to
1787 ** URL if no suitable repository is found.
1788 **
1789 ** repolist When in "directory:" mode, display a page
1790 ** showing a list of available repositories if
1791 ** the URL is "/".
1792 **
1793 ** localauth Grant administrator privileges to connections
1794 ** from 127.0.0.1 or ::1.
1795 **
1796 ** skin: LABEL Use the built-in skin called LABEL rather than
1797 ** the default. If there are no skins called LABEL
1798 ** then this line is a no-op.
1799 **
1800 ** files: GLOBLIST GLOBLIST is a comma-separated list of GLOB
1801 ** patterns that specify files that can be
1802 ** returned verbatim. This feature allows Fossil
1803 ** to act as a web server returning static
1804 ** content.
1805 **
1806 ** setenv: NAME VALUE Set environment variable NAME to VALUE. Or
1807 ** if VALUE is omitted, unset NAME.
1808 **
1809 ** HOME: PATH Shorthand for "setenv: HOME PATH"
1810 **
1811 ** debug: FILE Causing debugging information to be written
1812 ** into FILE.
1813 **
1814 ** errorlog: FILE Warnings, errors, and panics written to FILE.
1815 **
1816 ** redirect: REPO URL Extract the "name" query parameter and search
1817 ** REPO for a check-in or ticket that matches the
1818 ** value of "name", then redirect to URL. There
1819 ** can be multiple "redirect:" lines that are
1820 ** processed in order. If the REPO is "*", then
1821 ** an unconditional redirect to URL is taken.
1822 **
1823 ** Most CGI files contain only a "repository:" line. It is uncommon to
1824 ** use any other option.
1825 **
1826 ** See also: http, server, winsrv
1827 */
1828 void cmd_cgi(void){
1829 const char *zFile;
1830 const char *zNotFound = 0;
1831 char **azRedirect = 0; /* List of repositories to redirect to */
1832 int nRedirect = 0; /* Number of entries in azRedirect */
1833 Glob *pFileGlob = 0; /* Pattern for files */
1834 int allowRepoList = 0; /* Allow lists of repository files */
1835 Blob config, line, key, value, value2;
1836 if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){
1837 zFile = g.argv[2];
1838 }else{
1839 zFile = g.argv[1];
1840 }
1841 g.httpOut = stdout;
1842 g.httpIn = stdin;
1843 fossil_binary_mode(g.httpOut);
1844 fossil_binary_mode(g.httpIn);
1845 g.cgiOutput = 1;
1846 blob_read_from_file(&config, zFile);
1847 while( blob_line(&config, &line) ){
1848 if( !blob_token(&line, &key) ) continue;
1849 if( blob_buffer(&key)[0]=='#' ) continue;
1850 if( blob_eq(&key, "repository:") && blob_tail(&line, &value) ){
1851 /* repository: FILENAME
1852 **
1853 ** The name of the Fossil repository to be served via CGI. Most
1854 ** fossil CGI scripts have a single non-comment line that contains
1855 ** this one entry.
1856 */
1857 blob_trim(&value);
1858 db_open_repository(blob_str(&value));
1859 blob_reset(&value);
1860 continue;
1861 }
1862 if( blob_eq(&key, "directory:") && blob_token(&line, &value) ){
1863 /* directory: DIRECTORY
1864 **
1865 ** If repository: is omitted, then terms of the PATH_INFO cgi parameter
1866 ** are appended to DIRECTORY looking for a repository (whose name ends
1867 ** in ".fossil") or a file in "files:".
1868 */
1869 db_close(1);
1870 g.zRepositoryName = mprintf("%s", blob_str(&value));
1871 blob_reset(&value);
1872 continue;
1873 }
1874 if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){
1875 /* notfound: URL
1876 **
1877 ** If using directory: and no suitable repository or file is found,
1878 ** then redirect to URL.
1879 */
1880 zNotFound = mprintf("%s", blob_str(&value));
1881 blob_reset(&value);
1882 continue;
1883 }
1884 if( blob_eq(&key, "localauth") ){
1885 /* localauth
1886 **
1887 ** Grant "administrator" privileges to users connecting with HTTP
1888 ** from IP address 127.0.0.1. Do not bother checking credentials.
1889 */
1890 g.useLocalauth = 1;
1891 continue;
1892 }
1893 if( blob_eq(&key, "repolist") ){
1894 /* repolist
1895 **
1896 ** If using "directory:" and the URL is "/" then generate a page
1897 ** showing a list of available repositories.
1898 */
1899 allowRepoList = 1;
1900 continue;
1901 }
1902 if( blob_eq(&key, "redirect:") && blob_token(&line, &value)
1903 && blob_token(&line, &value2) ){
1904 /* See the header comment on the redirect_web_page() function
1905 ** above for details. */
1906 nRedirect++;
1907 azRedirect = fossil_realloc(azRedirect, 2*nRedirect*sizeof(char*));
1908 azRedirect[nRedirect*2-2] = mprintf("%s", blob_str(&value));
1909 azRedirect[nRedirect*2-1] = mprintf("%s", blob_str(&value2));
1910 blob_reset(&value);
1911 blob_reset(&value2);
1912 continue;
1913 }
1914 if( blob_eq(&key, "files:") && blob_token(&line, &value) ){
1915 /* files: GLOBLIST
1916 **
1917 ** GLOBLIST is a comma-separated list of filename globs. For
1918 ** example: *.html,*.css,*.js
1919 **
1920 ** If the repository: line is omitted and then PATH_INFO is searched
1921 ** for files that match any of these GLOBs and if any such file is
1922 ** found it is returned verbatim. This feature allows "fossil server"
1923 ** to function as a primitive web-server delivering arbitrary content.
1924 */
1925 pFileGlob = glob_create(blob_str(&value));
1926 blob_reset(&value);
1927 continue;
1928 }
1929 if( blob_eq(&key, "setenv:") && blob_token(&line, &value) ){
1930 /* setenv: NAME VALUE
1931 ** setenv: NAME
1932 **
1933 ** Sets environment variable NAME to VALUE. If VALUE is omitted, then
1934 ** the environment variable is unset.
1935 */
1936 blob_token(&line,&value2);
1937 fossil_setenv(blob_str(&value), blob_str(&value2));
1938 blob_reset(&value);
1939 blob_reset(&value2);
1940 continue;
1941 }
1942 if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){
1943 /* debug: FILENAME
1944 **
1945 ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go
1946 ** into FILENAME.
1947 */
1948 g.fDebug = fossil_fopen(blob_str(&value), "ab");
1949 blob_reset(&value);
1950 continue;
1951 }
1952 if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
1953 /* errorlog: FILENAME
1954 **
1955 ** Causes messages from warnings, errors, and panics to be appended
1956 ** to FILENAME.
1957 */
1958 g.zErrlog = mprintf("%s", blob_str(&value));
1959 blob_reset(&value);
1960 continue;
1961 }
1962 if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){
1963 /* HOME: VALUE
1964 **
1965 ** Set CGI parameter "HOME" to VALUE. This is legacy. Use
1966 ** setenv: instead.
1967 */
1968 cgi_setenv("HOME", blob_str(&value));
1969 blob_reset(&value);
1970 continue;
1971 }
1972 if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){
1973 /* skin: LABEL
1974 **
1975 ** Use one of the built-in skins defined by LABEL. LABEL is the
1976 ** name of the subdirectory under the skins/ directory that holds
1977 ** the elements of the built-in skin. If LABEL does not match,
1978 ** this directive is a silent no-op.
1979 */
1980 skin_use_alternative(blob_str(&value));
1981 blob_reset(&value);
1982 continue;
1983 }
1984 }
1985 blob_reset(&config);
1986 if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
1987 cgi_panic("Unable to find or open the project repository");
1988 }
1989 cgi_init();
1990 if( nRedirect ){
1991 redirect_web_page(nRedirect, azRedirect);
1992 }else{
1993 process_one_web_page(zNotFound, pFileGlob, allowRepoList);
1994 }
1995 }
1996
1997 /*
1998 ** If g.argv[arg] exists then it is either the name of a repository
1999 ** that will be used by a server, or else it is a directory that
2000 ** contains multiple repositories that can be served. If g.argv[arg]
2001 ** is a directory, the repositories it contains must be named
2002 ** "*.fossil". If g.argv[arg] does not exist, then we must be within
2003 ** an open check-out and the repository serve is the repository of
2004 ** that check-out.
2005 **
2006 ** Open the repository to be served if it is known. If g.argv[arg] is
2007 ** a directory full of repositories, then set g.zRepositoryName to
2008 ** the name of that directory and the specific repository will be
2009 ** opened later by process_one_web_page() based on the content of
2010 ** the PATH_INFO variable.
2011 **
2012 ** If the fCreate flag is set, then create the repository if it
2013 ** does not already exist. Always use "auto" hash-policy in this case.
2014 */
2015 static void find_server_repository(int arg, int fCreate){
2016 if( g.argc<=arg ){
2017 db_must_be_within_tree();
2018 }else{
2019 const char *zRepo = g.argv[arg];
2020 int isDir = file_isdir(zRepo);
2021 if( isDir==1 ){
2022 g.zRepositoryName = mprintf("%s", zRepo);
2023 file_simplify_name(g.zRepositoryName, -1, 0);
2024 }else{
2025 if( isDir==0 && fCreate ){
2026 const char *zPassword;
2027 db_create_repository(zRepo);
2028 db_open_repository(zRepo);
2029 db_begin_transaction();
2030 g.eHashPolicy = HPOLICY_AUTO;
2031 db_set_int("hash-policy", HPOLICY_AUTO, 0);
2032 db_initial_setup(0, "now", g.zLogin);
2033 db_end_transaction(0);
2034 fossil_print("project-id: %s\n", db_get("project-code", 0));
2035 fossil_print("server-id: %s\n", db_get("server-code", 0));
2036 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
2037 fossil_print("admin-user: %s (initial password is \"%s\")\n",
2038 g.zLogin, zPassword);
2039 cache_initialize();
2040 g.zLogin = 0;
2041 g.userUid = 0;
2042 }else{
2043 db_open_repository(zRepo);
2044 }
2045 }
2046 }
2047 }
2048
2049 #if defined(_WIN32) && USE_SEE
2050 /*
2051 ** This function attempts to parse a string value in the following
2052 ** format:
2053 **
2054 ** "%lu:%p:%u"
2055 **
2056 ** There are three parts, which must be delimited by colons. The
2057 ** first part is an unsigned long integer in base-10 (decimal) format.
2058 ** The second part is a numerical representation of a native pointer,
2059 ** in the appropriate implementation defined format. The third part
2060 ** is an unsigned integer in base-10 (decimal) format.
2061 **
2062 ** If the specified value cannot be parsed, for any reason, a fatal
2063 ** error will be raised and the process will be terminated.
2064 */
2065 void parse_pid_key_value(
2066 const char *zPidKey, /* The value to be parsed. */
2067 DWORD *pProcessId, /* The extracted process identifier. */
2068 LPVOID *ppAddress, /* The extracted pointer value. */
2069 SIZE_T *pnSize /* The extracted size value. */
2070 ){
2071 unsigned int nSize = 0;
2072 if( sscanf(zPidKey, "%lu:%p:%u", pProcessId, ppAddress, &nSize)==3 ){
2073 *pnSize = (SIZE_T)nSize;
2074 }else{
2075 fossil_fatal("failed to parse pid key");
2076 }
2077 }
2078 #endif
2079
2080 /*
2081 ** undocumented format:
2082 **
2083 ** fossil http INFILE OUTFILE IPADDR ?REPOSITORY?
2084 **
2085 ** The argv==6 form (with no options) is used by the win32 server only.
2086 **
2087 ** COMMAND: http*
2088 **
2089 ** Usage: %fossil http ?REPOSITORY? ?OPTIONS?
2090 **
2091 ** Handle a single HTTP request appearing on stdin. The resulting webpage
2092 ** is delivered on stdout. This method is used to launch an HTTP request
2093 ** handler from inetd, for example. The argument is the name of the
2094 ** repository.
2095 **
2096 ** If REPOSITORY is a directory that contains one or more repositories,
2097 ** either directly in REPOSITORY itself or in subdirectories, and
2098 ** with names of the form "*.fossil" then a prefix of the URL pathname
2099 ** selects from among the various repositories. If the pathname does
2100 ** not select a valid repository and the --notfound option is available,
2101 ** then the server redirects (HTTP code 302) to the URL of --notfound.
2102 ** When REPOSITORY is a directory, the pathname must contain only
2103 ** alphanumerics, "_", "/", "-" and "." and no "-" may occur after a "/"
2104 ** and every "." must be surrounded on both sides by alphanumerics or else
2105 ** a 404 error is returned. Static content files in the directory are
2106 ** returned if they match comma-separate GLOB pattern specified by --files
2107 ** and do not match "*.fossil*" and have a well-known suffix.
2108 **
2109 ** The --host option can be used to specify the hostname for the server.
2110 ** The --https option indicates that the request came from HTTPS rather
2111 ** than HTTP. If --nossl is given, then SSL connections will not be available,
2112 ** thus also no redirecting from http: to https: will take place.
2113 **
2114 ** If the --localauth option is given, then automatic login is performed
2115 ** for requests coming from localhost, if the "localauth" setting is not
2116 ** enabled.
2117 **
2118 ** Options:
2119 ** --baseurl URL base URL (useful with reverse proxies)
2120 ** --files GLOB comma-separate glob patterns for static file to serve
2121 ** --localauth enable automatic login for local connections
2122 ** --host NAME specify hostname of the server
2123 ** --https signal a request coming in via https
2124 ** --nojail drop root privilege but do not enter the chroot jail
2125 ** --nossl signal that no SSL connections are available
2126 ** --notfound URL use URL as "HTTP 404, object not found" page.
2127 ** --repolist If REPOSITORY is directory, URL "/" lists all repos
2128 ** --scgi Interpret input as SCGI rather than HTTP
2129 ** --skin LABEL Use override skin LABEL
2130 ** --th-trace trace TH1 execution (for debugging purposes)
2131 ** --usepidkey Use saved encryption key from parent process. This is
2132 ** only necessary when using SEE on Windows.
2133 **
2134 ** See also: cgi, server, winsrv
2135 */
2136 void cmd_http(void){
2137 const char *zIpAddr = 0;
2138 const char *zNotFound;
2139 const char *zHost;
2140 const char *zAltBase;
2141 const char *zFileGlob;
2142 int useSCGI;
2143 int noJail;
2144 int allowRepoList;
2145 #if defined(_WIN32) && USE_SEE
2146 const char *zPidKey;
2147 #endif
2148
2149 Th_InitTraceLog();
2150
2151 /* The winhttp module passes the --files option as --files-urlenc with
2152 ** the argument being URL encoded, to avoid wildcard expansion in the
2153 ** shell. This option is for internal use and is undocumented.
2154 */
2155 zFileGlob = find_option("files-urlenc",0,1);
2156 if( zFileGlob ){
2157 char *z = mprintf("%s", zFileGlob);
2158 dehttpize(z);
2159 zFileGlob = z;
2160 }else{
2161 zFileGlob = find_option("files",0,1);
2162 }
2163 skin_override();
2164 zNotFound = find_option("notfound", 0, 1);
2165 noJail = find_option("nojail",0,0)!=0;
2166 allowRepoList = find_option("repolist",0,0)!=0;
2167 g.useLocalauth = find_option("localauth", 0, 0)!=0;
2168 g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
2169 useSCGI = find_option("scgi", 0, 0)!=0;
2170 zAltBase = find_option("baseurl", 0, 1);
2171 if( zAltBase ) set_base_url(zAltBase);
2172 if( find_option("https",0,0)!=0 ){
2173 zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */
2174 cgi_replace_parameter("HTTPS","on");
2175 }
2176 zHost = find_option("host", 0, 1);
2177 if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost);
2178
2179 #if defined(_WIN32) && USE_SEE
2180 zPidKey = find_option("usepidkey", 0, 1);
2181 if( zPidKey ){
2182 DWORD processId = 0;
2183 LPVOID pAddress = NULL;
2184 SIZE_T nSize = 0;
2185 parse_pid_key_value(zPidKey, &processId, &pAddress, &nSize);
2186 db_read_saved_encryption_key_from_process(processId, pAddress, nSize);
2187 }
2188 #endif
2189
2190 /* We should be done with options.. */
2191 verify_all_options();
2192
2193 if( g.argc!=2 && g.argc!=3 && g.argc!=5 && g.argc!=6 ){
2194 fossil_fatal("no repository specified");
2195 }
2196 g.cgiOutput = 1;
2197 g.fullHttpReply = 1;
2198 if( g.argc>=5 ){
2199 g.httpIn = fossil_fopen(g.argv[2], "rb");
2200 g.httpOut = fossil_fopen(g.argv[3], "wb");
2201 zIpAddr = g.argv[4];
2202 find_server_repository(5, 0);
2203 }else{
2204 g.httpIn = stdin;
2205 g.httpOut = stdout;
2206 find_server_repository(2, 0);
2207 }
2208 if( zIpAddr==0 ){
2209 zIpAddr = cgi_ssh_remote_addr(0);
2210 if( zIpAddr && zIpAddr[0] ){
2211 g.fSshClient |= CGI_SSH_CLIENT;
2212 }
2213 }
2214 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
2215 if( useSCGI ){
2216 cgi_handle_scgi_request();
2217 }else if( g.fSshClient & CGI_SSH_CLIENT ){
2218 ssh_request_loop(zIpAddr, glob_create(zFileGlob));
2219 }else{
2220 cgi_handle_http_request(zIpAddr);
2221 }
2222 process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
2223 }
2224
2225 /*
2226 ** Process all requests in a single SSH connection if possible.
2227 */
2228 void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){
2229 blob_zero(&g.cgiIn);
2230 do{
2231 cgi_handle_ssh_http_request(zIpAddr);
2232 process_one_web_page(0, FileGlob, 0);
2233 blob_reset(&g.cgiIn);
2234 } while ( g.fSshClient & CGI_SSH_FOSSIL ||
2235 g.fSshClient & CGI_SSH_COMPAT );
2236 }
2237
2238 /*
2239 ** Note that the following command is used by ssh:// processing.
2240 **
2241 ** COMMAND: test-http
2242 **
2243 ** Works like the http command but gives setup permission to all users.
2244 **
2245 ** Options:
2246 ** --th-trace trace TH1 execution (for debugging purposes)
2247 **
2248 */
2249 void cmd_test_http(void){
2250 const char *zIpAddr; /* IP address of remote client */
2251
2252 Th_InitTraceLog();
2253 login_set_capabilities("sx", 0);
2254 g.useLocalauth = 1;
2255 g.httpIn = stdin;
2256 g.httpOut = stdout;
2257 find_server_repository(2, 0);
2258 g.cgiOutput = 1;
2259 g.fullHttpReply = 1;
2260 zIpAddr = cgi_ssh_remote_addr(0);
2261 if( zIpAddr && zIpAddr[0] ){
2262 g.fSshClient |= CGI_SSH_CLIENT;
2263 ssh_request_loop(zIpAddr, 0);
2264 }else{
2265 cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
2266 cgi_handle_http_request(0);
2267 process_one_web_page(0, 0, 0);
2268 }
2269 }
2270
2271 #if !defined(_WIN32)
2272 #if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
2273 /*
2274 ** Search for an executable on the PATH environment variable.
2275 ** Return true (1) if found and false (0) if not found.
2276 */
2277 static int binaryOnPath(const char *zBinary){
2278 const char *zPath = fossil_getenv("PATH");
2279 char *zFull;
2280 int i;
2281 int bExists;
2282 while( zPath && zPath[0] ){
2283 while( zPath[0]==':' ) zPath++;
2284 for(i=0; zPath[i] && zPath[i]!=':'; i++){}
2285 zFull = mprintf("%.*s/%s", i, zPath, zBinary);
2286 bExists = file_access(zFull, X_OK);
2287 fossil_free(zFull);
2288 if( bExists==0 ) return 1;
2289 zPath += i;
2290 }
2291 return 0;
2292 }
2293 #endif
2294 #endif
2295
2296 /*
2297 ** COMMAND: server*
2298 ** COMMAND: ui
2299 **
2300 ** Usage: %fossil server ?OPTIONS? ?REPOSITORY?
2301 ** or: %fossil ui ?OPTIONS? ?REPOSITORY?
2302 **
2303 ** Open a socket and begin listening and responding to HTTP requests on
2304 ** TCP port 8080, or on any other TCP port defined by the -P or
2305 ** --port option. The optional argument is the name of the repository.
2306 ** The repository argument may be omitted if the working directory is
2307 ** within an open checkout.
2308 **
2309 ** The "ui" command automatically starts a web browser after initializing
2310 ** the web server. The "ui" command also binds to 127.0.0.1 and so will
2311 ** only process HTTP traffic from the local machine.
2312 **
2313 ** The REPOSITORY can be a directory (aka folder) that contains one or
2314 ** more repositories with names ending in ".fossil". In this case, a
2315 ** prefix of the URL pathname is used to search the directory for an
2316 ** appropriate repository. To thwart mischief, the pathname in the URL must
2317 ** contain only alphanumerics, "_", "/", "-", and ".", and no "-" may
2318 ** occur after "/", and every "." must be surrounded on both sides by
2319 ** alphanumerics. Any pathname that does not satisfy these constraints
2320 ** results in a 404 error. Files in REPOSITORY that match the comma-separated
2321 ** list of glob patterns given by --files and that have known suffixes
2322 ** such as ".txt" or ".html" or ".jpeg" and do not match the pattern
2323 ** "*.fossil*" will be served as static content. With the "ui" command,
2324 ** the REPOSITORY can only be a directory if the --notfound option is
2325 ** also present.
2326 **
2327 ** For the special case REPOSITORY name of "/", the list global configuration
2328 ** database is consulted for a list of all known repositories. The --repolist
2329 ** option is implied by this special case. See also the "fossil all ui"
2330 ** command.
2331 **
2332 ** By default, the "ui" command provides full administrative access without
2333 ** having to log in. This can be disabled by turning off the "localauth"
2334 ** setting. Automatic login for the "server" command is available if the
2335 ** --localauth option is present and the "localauth" setting is off and the
2336 ** connection is from localhost. The "ui" command also enables --repolist
2337 ** by default.
2338 **
2339 ** Options:
2340 ** --baseurl URL Use URL as the base (useful for reverse proxies)
2341 ** --create Create a new REPOSITORY if it does not already exist
2342 ** --page PAGE Start "ui" on PAGE. ex: --page "timeline?y=ci"
2343 ** --files GLOBLIST Comma-separated list of glob patterns for static files
2344 ** --localauth enable automatic login for requests from localhost
2345 ** --localhost listen on 127.0.0.1 only (always true for "ui")
2346 ** --https signal a request coming in via https
2347 ** --nojail Drop root privileges but do not enter the chroot jail
2348 ** --nossl signal that no SSL connections are available
2349 ** --notfound URL Redirect
2350 ** -P|--port TCPPORT listen to request on port TCPPORT
2351 ** --th-trace trace TH1 execution (for debugging purposes)
2352 ** --repolist If REPOSITORY is dir, URL "/" lists repos.
2353 ** --scgi Accept SCGI rather than HTTP
2354 ** --skin LABEL Use override skin LABEL
2355 ** --usepidkey Use saved encryption key from parent process. This is
2356 ** only necessary when using SEE on Windows.
2357 **
2358 ** See also: cgi, http, winsrv
2359 */
2360 void cmd_webserver(void){
2361 int iPort, mxPort; /* Range of TCP ports allowed */
2362 const char *zPort; /* Value of the --port option */
2363 const char *zBrowser; /* Name of web browser program */
2364 char *zBrowserCmd = 0; /* Command to launch the web browser */
2365 int isUiCmd; /* True if command is "ui", not "server' */
2366 const char *zNotFound; /* The --notfound option or NULL */
2367 int flags = 0; /* Server flags */
2368 #if !defined(_WIN32)
2369 int noJail; /* Do not enter the chroot jail */
2370 #endif
2371 int allowRepoList; /* List repositories on URL "/" */
2372 const char *zAltBase; /* Argument to the --baseurl option */
2373 const char *zFileGlob; /* Static content must match this */
2374 char *zIpAddr = 0; /* Bind to this IP address */
2375 int fCreate = 0; /* The --create flag */
2376 const char *zInitPage = 0; /* Start on this page. --page option */
2377 #if defined(_WIN32) && USE_SEE
2378 const char *zPidKey;
2379 #endif
2380
2381 #if defined(_WIN32)
2382 const char *zStopperFile; /* Name of file used to terminate server */
2383 zStopperFile = find_option("stopper", 0, 1);
2384 #endif
2385
2386 zFileGlob = find_option("files-urlenc",0,1);
2387 if( zFileGlob ){
2388 char *z = mprintf("%s", zFileGlob);
2389 dehttpize(z);
2390 zFileGlob = z;
2391 }else{
2392 zFileGlob = find_option("files",0,1);
2393 }
2394 skin_override();
2395 #if !defined(_WIN32)
2396 noJail = find_option("nojail",0,0)!=0;
2397 #endif
2398 g.useLocalauth = find_option("localauth", 0, 0)!=0;
2399 Th_InitTraceLog();
2400 zPort = find_option("port", "P", 1);
2401 isUiCmd = g.argv[1][0]=='u';
2402 if( isUiCmd ){
2403 zInitPage = find_option("page", 0, 1);
2404 }
2405 zNotFound = find_option("notfound", 0, 1);
2406 allowRepoList = find_option("repolist",0,0)!=0;
2407 zAltBase = find_option("baseurl", 0, 1);
2408 fCreate = find_option("create",0,0)!=0;
2409 if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI;
2410 if( zAltBase ){
2411 set_base_url(zAltBase);
2412 }
2413 g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
2414 if( find_option("https",0,0)!=0 ){
2415 cgi_replace_parameter("HTTPS","on");
2416 }else{
2417 /* without --https, defaults to not available. */
2418 g.sslNotAvailable = 1;
2419 }
2420 if( find_option("localhost", 0, 0)!=0 ){
2421 flags |= HTTP_SERVER_LOCALHOST;
2422 }
2423
2424 #if defined(_WIN32) && USE_SEE
2425 zPidKey = find_option("usepidkey", 0, 1);
2426 if( zPidKey ){
2427 DWORD processId = 0;
2428 LPVOID pAddress = NULL;
2429 SIZE_T nSize = 0;
2430 parse_pid_key_value(zPidKey, &processId, &pAddress, &nSize);
2431 db_read_saved_encryption_key_from_process(processId, pAddress, nSize);
2432 }
2433 #endif
2434
2435 /* We should be done with options.. */
2436 verify_all_options();
2437
2438 if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
2439 if( isUiCmd ){
2440 flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST;
2441 g.useLocalauth = 1;
2442 allowRepoList = 1;
2443 }
2444 find_server_repository(2, fCreate);
2445 if( zInitPage==0 ){
2446 if( isUiCmd && g.localOpen ){
2447 zInitPage = "timeline?c=current";
2448 }else{
2449 zInitPage = "";
2450 }
2451 }
2452 if( zPort ){
2453 int i;
2454 for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){}
2455 if( i>0 ){
2456 zIpAddr = mprintf("%.*s", i, zPort);
2457 zPort += i+1;
2458 }
2459 iPort = mxPort = atoi(zPort);
2460 }else{
2461 iPort = db_get_int("http-port", 8080);
2462 mxPort = iPort+100;
2463 }
2464 #if !defined(_WIN32)
2465 /* Unix implementation */
2466 if( isUiCmd ){
2467 #if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
2468 zBrowser = db_get("web-browser", 0);
2469 if( zBrowser==0 ){
2470 static const char *const azBrowserProg[] =
2471 { "xdg-open", "gnome-open", "firefox", "google-chrome" };
2472 int i;
2473 zBrowser = "echo";
2474 for(i=0; i<count(azBrowserProg); i++){
2475 if( binaryOnPath(azBrowserProg[i]) ){
2476 zBrowser = azBrowserProg[i];
2477 break;
2478 }
2479 }
2480 }
2481 #else
2482 zBrowser = db_get("web-browser", "open");
2483 #endif
2484 if( zIpAddr ){
2485 zBrowserCmd = mprintf("%s http://%s:%%d/%s &",
2486 zBrowser, zIpAddr, zInitPage);
2487 }else{
2488 zBrowserCmd = mprintf("%s http://localhost:%%d/%s &",
2489 zBrowser, zInitPage);
2490 }
2491 }
2492 if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
2493 if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
2494 db_close(1);
2495 if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, flags) ){
2496 fossil_fatal("unable to listen on TCP socket %d", iPort);
2497 }
2498 g.httpIn = stdin;
2499 g.httpOut = stdout;
2500 if( g.fHttpTrace || g.fSqlTrace ){
2501 fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
2502 }
2503 g.cgiOutput = 1;
2504 find_server_repository(2, 0);
2505 if( fossil_strcmp(g.zRepositoryName,"/")==0 ){
2506 allowRepoList = 1;
2507 }else{
2508 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
2509 }
2510 if( flags & HTTP_SERVER_SCGI ){
2511 cgi_handle_scgi_request();
2512 }else{
2513 cgi_handle_http_request(0);
2514 }
2515 process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
2516 #else
2517 /* Win32 implementation */
2518 if( isUiCmd ){
2519 zBrowser = db_get("web-browser", "start");
2520 if( zIpAddr ){
2521 zBrowserCmd = mprintf("%s http://%s:%%d/%s &",
2522 zBrowser, zIpAddr, zInitPage);
2523 }else{
2524 zBrowserCmd = mprintf("%s http://localhost:%%d/%s &",
2525 zBrowser, zInitPage);
2526 }
2527 }
2528 if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
2529 if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
2530 db_close(1);
2531 if( allowRepoList ){
2532 flags |= HTTP_SERVER_REPOLIST;
2533 }
2534 if( win32_http_service(iPort, zAltBase, zNotFound, zFileGlob, flags) ){
2535 win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile,
2536 zAltBase, zNotFound, zFileGlob, zIpAddr, flags);
2537 }
2538 #endif
2539 }
2540
2541 /*
2542 ** COMMAND: test-echo
2543 **
2544 ** Usage: %fossil test-echo [--hex] ARGS...
2545 **
2546 ** Echo all command-line arguments (enclosed in [...]) to the screen so that
2547 ** wildcard expansion behavior of the host shell can be investigated.
2548 **
2549 ** With the --hex option, show the output as hexadecimal. This can be used
2550 ** to verify the fossil_path_to_utf8() routine on Windows and Mac.
2551 */
2552 void test_echo_cmd(void){
2553 int i, j;
2554 if( find_option("hex",0,0)==0 ){
2555 fossil_print("g.nameOfExe = [%s]\n", g.nameOfExe);
2556 for(i=0; i<g.argc; i++){
2557 fossil_print("argv[%d] = [%s]\n", i, g.argv[i]);
2558 }
2559 }else{
2560 unsigned char *z, c;
2561 for(i=0; i<g.argc; i++){
2562 fossil_print("argv[%d] = [", i);
2563 z = (unsigned char*)g.argv[i];
2564 for(j=0; (c = z[j])!=0; j++){
2565 fossil_print("%02x", c);
2566 }
2567 fossil_print("]\n");
2568 }
2569 }
2570 }