Index: src/add.c
==================================================================
--- src/add.c
+++ src/add.c
@@ -156,10 +156,11 @@
*/
static int add_one_file(
const char *zPath, /* Tree-name of file to add. */
int vid /* Add to this VFILE */
){
+ int doSkip = 0;
if( !file_is_simple_pathname(zPath, 1) ){
fossil_warning("filename contains illegal characters: %s", zPath);
return 0;
}
if( db_exists("SELECT 1 FROM vfile"
@@ -168,17 +169,22 @@
" WHERE pathname=%Q %s AND deleted",
zPath, filename_collation());
}else{
char *zFullname = mprintf("%s%s", g.zLocalRoot, zPath);
int isExe = file_isexe(zFullname, RepoFILE);
- db_multi_exec(
- "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink,mhash)"
- "VALUES(%d,0,0,0,%Q,%d,%d,NULL)",
- vid, zPath, isExe, file_islink(0));
+ if( file_nondir_objects_on_path(g.zLocalRoot, zFullname) ){
+ /* Do not add unsafe files to the vfile */
+ doSkip = 1;
+ }else{
+ db_multi_exec(
+ "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink,mhash)"
+ "VALUES(%d,0,0,0,%Q,%d,%d,NULL)",
+ vid, zPath, isExe, file_islink(0));
+ }
fossil_free(zFullname);
}
- if( db_changes() ){
+ if( db_changes() && !doSkip ){
fossil_print("ADDED %s\n", zPath);
return 1;
}else{
fossil_print("SKIP %s\n", zPath);
return 0;
@@ -186,11 +192,13 @@
}
/*
** Add all files in the sfile temp table.
**
-** Automatically exclude the repository file.
+** Automatically exclude the repository file and any other files
+** with reserved names. Also exclude files that are beneath an
+** existing symlink.
*/
static int add_files_in_sfile(int vid){
const char *zRepo; /* Name of the repository database file */
int nAdd = 0; /* Number of files added */
int i; /* Loop counter */
@@ -208,18 +216,30 @@
if( filenames_are_case_sensitive() ){
xCmp = fossil_strcmp;
}else{
xCmp = fossil_stricmp;
}
- db_prepare(&loop, "SELECT pathname FROM sfile ORDER BY pathname");
+ db_prepare(&loop,
+ "SELECT pathname FROM sfile"
+ " WHERE pathname NOT IN ("
+ "SELECT sfile.pathname FROM vfile, sfile"
+ " WHERE vfile.islink"
+ " AND NOT vfile.deleted"
+ " AND sfile.pathname>(vfile.pathname||'/')"
+ " AND sfile.pathname<(vfile.pathname||'0'))"
+ " ORDER BY pathname");
while( db_step(&loop)==SQLITE_ROW ){
const char *zToAdd = db_column_text(&loop, 0);
if( fossil_strcmp(zToAdd, zRepo)==0 ) continue;
- for(i=0; (zReserved = fossil_reserved_name(i, 0))!=0; i++){
- if( xCmp(zToAdd, zReserved)==0 ) break;
+ if( strchr(zToAdd,'/') ){
+ if( file_is_reserved_name(zToAdd, -1) ) continue;
+ }else{
+ for(i=0; (zReserved = fossil_reserved_name(i, 0))!=0; i++){
+ if( xCmp(zToAdd, zReserved)==0 ) break;
+ }
+ if( zReserved ) continue;
}
- if( zReserved ) continue;
nAdd += add_one_file(zToAdd, vid);
}
db_finalize(&loop);
blob_reset(&repoName);
return nAdd;
Index: src/alerts.c
==================================================================
--- src/alerts.c
+++ src/alerts.c
@@ -936,11 +936,11 @@
** This is a short name used to identifies the repository in the Subject:
** line of email alerts. Traditionally this name is included in square
** brackets. Examples: "[fossil-src]", "[sqlite-src]".
*/
/*
-** SETTING: email-send-method width=5 default=off
+** SETTING: email-send-method width=5 default=off sensitive
** Determine the method used to send email. Allowed values are
** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value
** means no email is ever sent. The "relay" value means emails are sent
** to an Mail Sending Agent using SMTP located at email-send-relayhost.
** The "pipe" value means email messages are piped into a command
@@ -949,33 +949,33 @@
** by the email-send-dir setting. The "db" value means that emails
** are added to an SQLite database named by the* email-send-db setting.
** The "stdout" value writes email text to standard output, for debugging.
*/
/*
-** SETTING: email-send-command width=40
+** SETTING: email-send-command width=40 sensitive
** This is a command to which outbound email content is piped when the
** email-send-method is set to "pipe". The command must extract
** recipient, sender, subject, and all other relevant information
** from the email header.
*/
/*
-** SETTING: email-send-dir width=40
+** SETTING: email-send-dir width=40 sensitive
** This is a directory into which outbound emails are written as individual
** files if the email-send-method is set to "dir".
*/
/*
-** SETTING: email-send-db width=40
+** SETTING: email-send-db width=40 sensitive
** This is an SQLite database file into which outbound emails are written
** if the email-send-method is set to "db".
*/
/*
** SETTING: email-self width=40
** This is the email address for the repository. Outbound emails add
** this email address as the "From:" field.
*/
/*
-** SETTING: email-send-relayhost width=40
+** SETTING: email-send-relayhost width=40 sensitive
** This is the hostname and TCP port to which output email messages
** are sent when email-send-method is "relay". There should be an
** SMTP server configured as a Mail Submission Agent listening on the
** designated host and port and all times.
*/
@@ -1769,18 +1769,20 @@
"UPDATE subscriber SET sverified=1"
" WHERE subscriberCode=hextoblob(%Q)",
zName);
if( db_get_boolean("selfreg-verify",0) ){
char *zNewCap = db_get("default-perms","u");
+ db_unprotect(PROTECT_USER);
db_multi_exec(
"UPDATE user"
" SET cap=%Q"
" WHERE cap='7' AND login=("
" SELECT suname FROM subscriber"
" WHERE subscriberCode=hextoblob(%Q))",
zNewCap, zName
);
+ db_protect_pop();
login_set_capabilities(zNewCap, 0);
}
@
Your email alert subscription has been verified!
@
Use the form below to update your subscription information.
@
Hint: Bookmark this page so that you can more easily update
Index: src/allrepo.c
==================================================================
--- src/allrepo.c
+++ src/allrepo.c
@@ -299,11 +299,13 @@
useCheckouts?"ckout":"repo", blob_str(&fn)
);
if( dryRunFlag ){
fossil_print("%s\n", blob_sql_text(&sql));
}else{
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec("%s", blob_sql_text(&sql));
+ db_protect_pop();
}
}
db_end_transaction(0);
blob_reset(&sql);
blob_reset(&fn);
@@ -334,11 +336,13 @@
"VALUES('repo:%q',1)", z
);
if( dryRunFlag ){
fossil_print("%s\n", blob_sql_text(&sql));
}else{
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec("%s", blob_sql_text(&sql));
+ db_protect_pop();
}
}
db_end_transaction(0);
blob_reset(&sql);
blob_reset(&fn);
@@ -428,9 +432,11 @@
if( nToDel>0 ){
const char *zSql = "DELETE FROM global_config WHERE name IN toDel";
if( dryRunFlag ){
fossil_print("%s\n", zSql);
}else{
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec("%s", zSql /*safe-for-%s*/ );
+ db_protect_pop();
}
}
}
Index: src/backoffice.c
==================================================================
--- src/backoffice.c
+++ src/backoffice.c
@@ -241,10 +241,11 @@
** process (1) no longer exists and the current time exceeds (2).
*/
static void backofficeReadLease(Lease *pLease){
Stmt q;
memset(pLease, 0, sizeof(*pLease));
+ db_unprotect(PROTECT_CONFIG);
db_prepare(&q, "SELECT value FROM repository.config"
" WHERE name='backoffice'");
if( db_step(&q)==SQLITE_ROW ){
const char *z = db_column_text(&q,0);
z = backofficeParseInt(z, &pLease->idCurrent);
@@ -251,10 +252,11 @@
z = backofficeParseInt(z, &pLease->tmCurrent);
z = backofficeParseInt(z, &pLease->idNext);
backofficeParseInt(z, &pLease->tmNext);
}
db_finalize(&q);
+ db_protect_pop();
}
/*
** Return a string that describes how long it has been since the
** last backoffice run. The string is obtained from fossil_malloc().
@@ -277,15 +279,17 @@
/*
** Write a lease to the backoffice property
*/
static void backofficeWriteLease(Lease *pLease){
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec(
"REPLACE INTO repository.config(name,value,mtime)"
" VALUES('backoffice','%lld %lld %lld %lld',now())",
pLease->idCurrent, pLease->tmCurrent,
pLease->idNext, pLease->tmNext);
+ db_protect_pop();
}
/*
** Check to see if the specified Win32 process is still alive. It
** should be noted that even if this function returns non-zero, the
Index: src/captcha.c
==================================================================
--- src/captcha.c
+++ src/captcha.c
@@ -458,14 +458,16 @@
Blob b;
static char zRes[20];
zSecret = db_get("captcha-secret", 0);
if( zSecret==0 ){
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec(
"REPLACE INTO config(name,value)"
" VALUES('captcha-secret', lower(hex(randomblob(20))));"
);
+ db_protect_pop();
zSecret = db_get("captcha-secret", 0);
assert( zSecret!=0 );
}
blob_init(&b, 0, 0);
blob_appendf(&b, "%s-%x", zSecret, seed);
Index: src/checkin.c
==================================================================
--- src/checkin.c
+++ src/checkin.c
@@ -62,10 +62,13 @@
** Create a TEMP table named SFILE and add all unmanaged files named on
** the command-line to that table. If directories are named, then add
** all unmanaged files contained underneath those directories. If there
** are no files or directories named on the command-line, then add all
** unmanaged files anywhere in the checkout.
+**
+** This routine never follows symlinks. It always treats symlinks as
+** object unto themselves.
*/
static void locate_unmanaged_files(
int argc, /* Number of command-line arguments to examine */
char **argv, /* values of command-line arguments */
unsigned scanFlags, /* Zero or more SCAN_xxx flags */
@@ -80,19 +83,19 @@
db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s,"
" mtime INTEGER, size INTEGER)", filename_collation());
nRoot = (int)strlen(g.zLocalRoot);
if( argc==0 ){
blob_init(&name, g.zLocalRoot, nRoot - 1);
- vfile_scan(&name, blob_size(&name), scanFlags, pIgnore, 0, RepoFILE);
+ vfile_scan(&name, blob_size(&name), scanFlags, pIgnore, 0, SymFILE);
blob_reset(&name);
}else{
for(i=0; i1000
&& db_int(0, "PRAGMA page_size")<8192 ){
db_multi_exec("PRAGMA page_size=8192;");
}
+ db_unprotect(PROTECT_ALL);
db_multi_exec("VACUUM");
+ db_protect_pop();
fossil_print("\nproject-id: %s\n", db_get("project-code", 0));
fossil_print("server-id: %s\n", db_get("server-code", 0));
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
}
Index: src/configure.c
==================================================================
--- src/configure.c
+++ src/configure.c
@@ -145,11 +145,10 @@
{ "keep-glob", CONFIGSET_PROJ },
{ "crlf-glob", CONFIGSET_PROJ },
{ "crnl-glob", CONFIGSET_PROJ },
{ "encoding-glob", CONFIGSET_PROJ },
{ "empty-dirs", CONFIGSET_PROJ },
- { "allow-symlinks", CONFIGSET_PROJ },
{ "dotfiles", CONFIGSET_PROJ },
{ "parent-project-code", CONFIGSET_PROJ },
{ "parent-project-name", CONFIGSET_PROJ },
{ "hash-policy", CONFIGSET_PROJ },
{ "comment-format", CONFIGSET_PROJ },
@@ -446,10 +445,11 @@
blob_append_sql(&sql,") VALUES(%s,%s",
azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/);
for(jj=2; jj=count(db.aProtect)-2 ){
+ fossil_panic("too many db_protect() calls");
+ }
+ db.aProtect[db.nProtect++] = db.protectMask;
+ if( (flags & PROTECT_SENSITIVE)!=0
+ && db.bProtectTriggers==0
+ && g.repositoryOpen
+ ){
+ /* Create the triggers needed to protect sensitive settings from
+ ** being created or modified the first time that PROTECT_SENSITIVE
+ ** is enabled. Deleting a sensitive setting is harmless, so there
+ ** is not trigger to block deletes. After being created once, the
+ ** triggers persist for the life of the database connection. */
+ db_multi_exec(
+ "CREATE TEMP TRIGGER protect_1 BEFORE INSERT ON config"
+ " WHEN protected_setting(new.name) BEGIN"
+ " SELECT raise(abort,'not authorized');"
+ "END;\n"
+ "CREATE TEMP TRIGGER protect_2 BEFORE UPDATE ON config"
+ " WHEN protected_setting(new.name) BEGIN"
+ " SELECT raise(abort,'not authorized');"
+ "END;\n"
+ );
+ db.bProtectTriggers = 1;
+ }
+ db.protectMask = flags;
+}
+void db_protect(unsigned flags){
+ db_protect_only(db.protectMask | flags);
+}
+void db_unprotect(unsigned flags){
+ if( db.nProtect>=count(db.aProtect)-2 ){
+ fossil_panic("too many db_unprotect() calls");
+ }
+ db.aProtect[db.nProtect++] = db.protectMask;
+ db.protectMask &= ~flags;
+}
+void db_protect_pop(void){
+ if( db.nProtect<1 ){
+ fossil_panic("too many db_protect_pop() calls");
+ }
+ db.protectMask = db.aProtect[--db.nProtect];
+}
+
+/*
+** Verify that the desired database write pertections are in place.
+** Throw a fatal error if not.
+*/
+void db_assert_protected(unsigned flags){
+ if( (flags & db.protectMask)!=flags ){
+ fossil_panic("missing database write protection bits: %02x",
+ flags & ~db.protectMask);
+ }
+}
+
+/*
+** Assert that either all protections are off (including PROTECT_BASELINE
+** which is usually always enabled), or the setting named in the argument
+** is no a sensitive setting.
+**
+** This assert() is used to verify that the db_set() and db_set_int()
+** interfaces do not modify a sensitive setting.
+*/
+void db_assert_protection_off_or_not_sensitive(const char *zName){
+ if( db.protectMask!=0 && db_setting_is_protected(zName) ){
+ fossil_panic("unauthorized change to protected setting \"%s\"", zName);
+ }
+}
+
+/*
+** Every Fossil database connection automatically registers the following
+** overarching authenticator callback, and leaves it registered for the
+** duration of the connection. This authenticator will call any
+** sub-authenticators that are registered using db_set_authorizer().
+*/
+int db_top_authorizer(
+ void *pNotUsed,
+ int eCode,
+ const char *z0,
+ const char *z1,
+ const char *z2,
+ const char *z3
+){
+ int rc = SQLITE_OK;
+ switch( eCode ){
+ case SQLITE_INSERT:
+ case SQLITE_UPDATE:
+ case SQLITE_DELETE: {
+ if( (db.protectMask & PROTECT_USER)!=0
+ && sqlite3_stricmp(z0,"user")==0 ){
+ rc = SQLITE_DENY;
+ }else if( (db.protectMask & PROTECT_CONFIG)!=0 &&
+ (sqlite3_stricmp(z0,"config")==0 ||
+ sqlite3_stricmp(z0,"global_config")==0) ){
+ rc = SQLITE_DENY;
+ }else if( (db.protectMask & PROTECT_SENSITIVE)!=0 &&
+ sqlite3_stricmp(z0,"global_config")==0 ){
+ rc = SQLITE_DENY;
+ }else if( (db.protectMask & PROTECT_READONLY)!=0
+ && sqlite3_stricmp(z2,"temp")!=0 ){
+ rc = SQLITE_DENY;
+ }
+ break;
+ }
+ case SQLITE_DROP_TEMP_TRIGGER: {
+ /* Do not allow the triggers that enforce PROTECT_SENSITIVE
+ ** to be dropped */
+ rc = SQLITE_DENY;
+ break;
+ }
+ }
+ if( db.xAuth && rc==SQLITE_OK ){
+ rc = db.xAuth(db.pAuthArg, eCode, z0, z1, z2, z3);
+ }
+ return rc;
+}
+
+/*
+** Set or unset the query authorizer callback function
+*/
+void db_set_authorizer(
+ int(*xAuth)(void*,int,const char*,const char*,const char*,const char*),
+ void *pArg,
+ const char *zName /* for tracing */
+){
+ if( db.xAuth ){
+ fossil_panic("multiple active db_set_authorizer() calls");
+ }
+ db.xAuth = xAuth;
+ db.pAuthArg = pArg;
+ db.zAuthName = zName;
+ if( g.fSqlTrace ) fossil_trace("-- set authorizer %s\n", zName);
+}
+void db_clear_authorizer(void){
+ if( db.zAuthName && g.fSqlTrace ){
+ fossil_trace("-- discontinue authorizer %s\n", db.zAuthName);
+ }
+ db.xAuth = 0;
+ db.pAuthArg = 0;
+ db.zAuthName = 0;
+}
#if INTERFACE
/*
** Possible flags to db_vprepare
*/
@@ -334,21 +566,24 @@
*/
int db_vprepare(Stmt *pStmt, int flags, const char *zFormat, va_list ap){
int rc;
int prepFlags = 0;
char *zSql;
+ const char *zExtra = 0;
blob_zero(&pStmt->sql);
blob_vappendf(&pStmt->sql, zFormat, ap);
va_end(ap);
zSql = blob_str(&pStmt->sql);
db.nPrepare++;
if( flags & DB_PREPARE_PERSISTENT ){
prepFlags = SQLITE_PREPARE_PERSISTENT;
}
- rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, 0);
+ rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);
if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
+ }else if( zExtra && !fossil_all_whitespace(zExtra) ){
+ db_err("surplus text follows SQL: \"%s\"", zExtra);
}
pStmt->pNext = db.pAllStmt;
pStmt->pPrev = 0;
if( db.pAllStmt ) db.pAllStmt->pPrev = pStmt;
db.pAllStmt = pStmt;
@@ -611,10 +846,11 @@
return rc;
}
/*
** COMMAND: test-db-exec-error
+** Usage: %fossil test-db-exec-error
**
** Invoke the db_exec() interface with an erroneous SQL statement
** in order to verify the error handling logic.
*/
void db_test_db_exec_cmd(void){
@@ -621,10 +857,27 @@
Stmt err;
db_find_and_open_repository(0,0);
db_prepare(&err, "INSERT INTO repository.config(name) VALUES(NULL);");
db_exec(&err);
}
+
+/*
+** COMMAND: test-db-prepare
+** Usage: %fossil test-db-prepare ?OPTIONS? SQL
+**
+** Invoke db_prepare() on the SQL input. Report any errors encountered.
+** This command is used to verify error detection logic in the db_prepare()
+** utility routine.
+*/
+void db_test_db_prepare(void){
+ Stmt err;
+ db_find_and_open_repository(0,0);
+ verify_all_options();
+ if( g.argc!=3 ) usage("?OPTIONS? SQL");
+ db_prepare(&err, "%s", g.argv[2]/*safe-for-%s*/);
+ db_finalize(&err);
+}
/*
** Print the output of one or more SQL queries on standard output.
** This routine is used for debugging purposes only.
*/
@@ -844,34 +1097,34 @@
void db_init_database(
const char *zFileName, /* Name of database file to create */
const char *zSchema, /* First part of schema */
... /* Additional SQL to run. Terminate with NULL. */
){
- sqlite3 *db;
+ sqlite3 *xdb;
int rc;
const char *zSql;
va_list ap;
- db = db_open(zFileName ? zFileName : ":memory:");
- sqlite3_exec(db, "BEGIN EXCLUSIVE", 0, 0, 0);
- rc = sqlite3_exec(db, zSchema, 0, 0, 0);
+ xdb = db_open(zFileName ? zFileName : ":memory:");
+ sqlite3_exec(xdb, "BEGIN EXCLUSIVE", 0, 0, 0);
+ rc = sqlite3_exec(xdb, zSchema, 0, 0, 0);
if( rc!=SQLITE_OK ){
- db_err("%s", sqlite3_errmsg(db));
+ db_err("%s", sqlite3_errmsg(xdb));
}
va_start(ap, zSchema);
while( (zSql = va_arg(ap, const char*))!=0 ){
- rc = sqlite3_exec(db, zSql, 0, 0, 0);
+ rc = sqlite3_exec(xdb, zSql, 0, 0, 0);
if( rc!=SQLITE_OK ){
- db_err("%s", sqlite3_errmsg(db));
+ db_err("%s", sqlite3_errmsg(xdb));
}
}
va_end(ap);
- sqlite3_exec(db, "COMMIT", 0, 0, 0);
+ sqlite3_exec(xdb, "COMMIT", 0, 0, 0);
if( zFileName || g.db!=0 ){
- sqlite3_close(db);
+ sqlite3_close(xdb);
}else{
- g.db = db;
+ g.db = xdb;
}
}
/*
** Function to return the number of seconds since 1970. This is
@@ -1060,10 +1313,37 @@
}
strcpy(zOut, zTemp = obscure((char*)zIn));
fossil_free(zTemp);
sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free);
}
+
+/*
+** Return True if zName is a protected (a.k.a. "sensitive") setting.
+*/
+int db_setting_is_protected(const char *zName){
+ const Setting *pSetting = zName ? db_find_setting(zName,0) : 0;
+ return pSetting!=0 && pSetting->sensitive!=0;
+}
+
+/*
+** Implement the protected_setting(X) SQL function. This function returns
+** true if X is the name of a protected (security-sensitive) setting and
+** the db.protectSensitive flag is enabled. It returns false otherwise.
+*/
+LOCAL void db_protected_setting_func(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const char *zSetting;
+ if( (db.protectMask & PROTECT_SENSITIVE)==0 ){
+ sqlite3_result_int(context, 0);
+ return;
+ }
+ zSetting = (const char*)sqlite3_value_text(argv[0]);
+ sqlite3_result_int(context, db_setting_is_protected(zSetting));
+}
/*
** Register the SQL functions that are useful both to the internal
** representation and to the "fossil sql" command.
*/
@@ -1090,10 +1370,12 @@
alert_find_emailaddr_func, 0, 0);
sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0,
alert_display_name_func, 0, 0);
sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
db_obscure, 0, 0);
+ sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
+ db_protected_setting_func, 0, 0);
}
#if USE_SEE
/*
** This is a pointer to the saved database encryption key string.
@@ -1348,10 +1630,11 @@
if( g.fSqlTrace ) sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, db_sql_trace, 0);
db_add_aux_functions(db);
re_add_sql_func(db); /* The REGEXP operator */
foci_register(db); /* The "files_of_checkin" virtual table */
sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, 0, &rc);
+ sqlite3_set_authorizer(db, db_top_authorizer, db);
return db;
}
/*
@@ -1791,22 +2074,10 @@
}
}
return zRepo;
}
-/*
-** Returns non-zero if the default value for the "allow-symlinks" setting
-** is "on". When on Windows, this always returns false.
-*/
-int db_allow_symlinks_by_default(void){
-#if defined(_WIN32)
- return 0;
-#else
- return 1;
-#endif
-}
-
/*
** Returns non-zero if support for symlinks is currently enabled.
*/
int db_allow_symlinks(void){
return g.allowSymlinks;
@@ -1848,13 +2119,14 @@
g.zRepositoryName = mprintf("%s", zDbName);
db_open_or_attach(g.zRepositoryName, "repository");
g.repositoryOpen = 1;
sqlite3_file_control(g.db, "repository", SQLITE_FCNTL_DATA_VERSION,
&g.iRepoDataVers);
+
/* Cache "allow-symlinks" option, because we'll need it on every stat call */
- g.allowSymlinks = db_get_boolean("allow-symlinks",
- db_allow_symlinks_by_default());
+ g.allowSymlinks = db_get_boolean("allow-symlinks",0);
+
g.zAuxSchema = db_get("aux-schema","");
g.eHashPolicy = db_get_int("hash-policy",-1);
if( g.eHashPolicy<0 ){
g.eHashPolicy = hname_default_policy();
db_set_int("hash-policy", g.eHashPolicy, 0);
@@ -2089,10 +2361,11 @@
** argument is true. Ignore unfinalized statements when false.
*/
void db_close(int reportErrors){
sqlite3_stmt *pStmt;
if( g.db==0 ) return;
+ sqlite3_set_authorizer(g.db, 0, 0);
if( g.fSqlStats ){
int cur, hiwtr;
sqlite3_db_status(g.db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur, &hiwtr, 0);
fprintf(stderr, "-- LOOKASIDE_USED %10d %10d\n", cur, hiwtr);
sqlite3_db_status(g.db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &cur, &hiwtr, 0);
@@ -2118,17 +2391,20 @@
fprintf(stderr, "-- prepared statements %10d\n", db.nPrepare);
}
while( db.pAllStmt ){
db_finalize(db.pAllStmt);
}
- if( db.nBegin && reportErrors ){
- fossil_warning("Transaction started at %s:%d never commits",
- db.zStartFile, db.iStartLine);
+ if( db.nBegin ){
+ if( reportErrors ){
+ fossil_warning("Transaction started at %s:%d never commits",
+ db.zStartFile, db.iStartLine);
+ }
db_end_transaction(1);
}
pStmt = 0;
- g.dbIgnoreErrors++; /* Stop "database locked" warnings from PRAGMA optimize */
+ sqlite3_busy_timeout(g.db, 0);
+ g.dbIgnoreErrors++; /* Stop "database locked" warnings */
sqlite3_exec(g.db, "PRAGMA optimize", 0, 0, 0);
g.dbIgnoreErrors--;
db_close_config();
/* If the localdb has a lot of unused free space,
@@ -2136,11 +2412,13 @@
*/
if( db_database_slot("localdb")>=0 ){
int nFree = db_int(0, "PRAGMA localdb.freelist_count");
int nTotal = db_int(0, "PRAGMA localdb.page_count");
if( nFree>nTotal/4 ){
+ db_unprotect(PROTECT_ALL);
db_multi_exec("VACUUM localdb;");
+ db_protect_pop();
}
}
if( g.db ){
int rc;
@@ -2154,10 +2432,11 @@
}
g.db = 0;
}
g.repositoryOpen = 0;
g.localOpen = 0;
+ db.bProtectTriggers = 0;
assert( g.dbConfig==0 );
assert( g.zConfigDbName==0 );
backoffice_run_if_needed();
}
@@ -2168,10 +2447,11 @@
if( g.db ){
int rc;
sqlite3_wal_checkpoint(g.db, 0);
rc = sqlite3_close(g.db);
if( g.fSqlTrace ) fossil_trace("-- sqlite3_close(%d)\n", rc);
+ db_clear_authorizer();
}
g.db = 0;
g.repositoryOpen = 0;
g.localOpen = 0;
}
@@ -2215,10 +2495,11 @@
zUser = fossil_getenv("USERNAME");
}
if( zUser==0 ){
zUser = "root";
}
+ db_unprotect(PROTECT_USER);
db_multi_exec(
"INSERT OR IGNORE INTO user(login, info) VALUES(%Q,'')", zUser
);
db_multi_exec(
"UPDATE user SET cap='s', pw=%Q"
@@ -2234,10 +2515,11 @@
" VALUES('developer','','ei','Dev');"
"INSERT OR IGNORE INTO user(login,pw,cap,info)"
" VALUES('reader','','kptw','Reader');"
);
}
+ db_protect_pop();
}
/*
** Return a pointer to a string that contains the RHS of an IN operator
** that will select CONFIG table names that are in the list of control
@@ -2285,10 +2567,11 @@
){
char *zDate;
Blob hash;
Blob manifest;
+ db_unprotect(PROTECT_ALL);
db_set("content-schema", CONTENT_SCHEMA, 0);
db_set("aux-schema", AUX_SCHEMA_MAX, 0);
db_set("rebuilt", get_version(), 0);
db_set("admin-log", "1", 0);
db_set("access-log", "1", 0);
@@ -2343,10 +2626,11 @@
" photo = (SELECT u2.photo FROM settingSrc.user u2"
" WHERE u2.login = user.login)"
" WHERE user.login IN ('anonymous','nobody','developer','reader');"
);
}
+ db_protect_pop();
if( zInitialDate ){
int rid;
blob_zero(&manifest);
blob_appendf(&manifest, "C initial\\sempty\\scheck-in\n");
@@ -2839,10 +3123,12 @@
z = db_text(0, "SELECT strftime(%Q,%Q,'unixepoch');", zFormat, z);
}
return z;
}
void db_set(const char *zName, const char *zValue, int globalFlag){
+ db_assert_protection_off_or_not_sensitive(zName);
+ db_unprotect(PROTECT_CONFIG);
db_begin_transaction();
if( globalFlag ){
db_swap_connections();
db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)",
zName, zValue);
@@ -2853,13 +3139,15 @@
}
if( globalFlag && g.repositoryOpen ){
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
}
db_end_transaction(0);
+ db_protect_pop();
}
void db_unset(const char *zName, int globalFlag){
db_begin_transaction();
+ db_unprotect(PROTECT_CONFIG);
if( globalFlag ){
db_swap_connections();
db_multi_exec("DELETE FROM global_config WHERE name=%Q", zName);
db_swap_connections();
}else{
@@ -2866,10 +3154,11 @@
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
}
if( globalFlag && g.repositoryOpen ){
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
}
+ db_protect_pop();
db_end_transaction(0);
}
int db_is_global(const char *zName){
int rc = 0;
if( g.zConfigDbName ){
@@ -2899,10 +3188,12 @@
db_swap_connections();
}
return v;
}
void db_set_int(const char *zName, int value, int globalFlag){
+ db_assert_protection_off_or_not_sensitive(zName);
+ db_unprotect(PROTECT_CONFIG);
if( globalFlag ){
db_swap_connections();
db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)",
zName, value);
db_swap_connections();
@@ -2911,10 +3202,11 @@
zName, value);
}
if( globalFlag && g.repositoryOpen ){
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
}
+ db_protect_pop();
}
int db_get_boolean(const char *zName, int dflt){
char *zVal = db_get(zName, dflt ? "on" : "off");
if( is_truth(zVal) ){
dflt = 1;
@@ -3040,24 +3332,28 @@
}
file_canonical_name(zName, &full, 0);
(void)filename_collation(); /* Initialize before connection swap */
db_swap_connections();
zRepoSetting = mprintf("repo:%q", blob_str(&full));
+
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec(
"DELETE FROM global_config WHERE name %s = %Q;",
filename_collation(), zRepoSetting
);
db_multi_exec(
"INSERT OR IGNORE INTO global_config(name,value)"
"VALUES(%Q,1);",
zRepoSetting
);
+ db_protect_pop();
fossil_free(zRepoSetting);
if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){
Blob localRoot;
file_canonical_name(g.zLocalRoot, &localRoot, 1);
zCkoutSetting = mprintf("ckout:%q", blob_str(&localRoot));
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec(
"DELETE FROM global_config WHERE name %s = %Q;",
filename_collation(), zCkoutSetting
);
db_multi_exec(
@@ -3073,10 +3369,11 @@
db_optional_sql("repository",
"REPLACE INTO config(name,value,mtime)"
"VALUES(%Q,1,now());",
zCkoutSetting
);
+ db_protect_pop();
fossil_free(zCkoutSetting);
blob_reset(&localRoot);
}else{
db_swap_connections();
}
@@ -3131,11 +3428,10 @@
void cmd_open(void){
int emptyFlag;
int keepFlag;
int forceMissingFlag;
int allowNested;
- int allowSymlinks;
int setmtimeFlag; /* --setmtime. Set mtimes on files */
int bForce = 0; /* --force. Open even if non-empty dir */
static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 };
const char *zWorkDir; /* --workdir value */
const char *zRepo = 0; /* Name of the repository file */
@@ -3242,23 +3538,10 @@
}else if( db_exists("SELECT 1 FROM event WHERE type='ci'") ){
g.zOpenRevision = db_get("main-branch", 0);
}
}
- if( g.zOpenRevision ){
- /* Since the repository is open and we know the revision now,
- ** refresh the allow-symlinks flag. Since neither the local
- ** checkout nor the configuration database are open at this
- ** point, this should always return the versioned setting,
- ** if any, or the default value, which is negative one. The
- ** value negative one, in this context, means that the code
- ** below should fallback to using the setting value from the
- ** repository or global configuration databases only. */
- allowSymlinks = db_get_versioned_boolean("allow-symlinks", -1);
- }else{
- allowSymlinks = -1; /* Use non-versioned settings only. */
- }
#if defined(_WIN32) || defined(__CYGWIN__)
# define LOCALDB_NAME "./_FOSSIL_"
#else
# define LOCALDB_NAME "./.fslckout"
@@ -3268,26 +3551,10 @@
"COMMIT; PRAGMA journal_mode=WAL; BEGIN;",
#endif
(char*)0);
db_delete_on_failure(LOCALDB_NAME);
db_open_local(0);
- if( allowSymlinks>=0 ){
- /* Use the value from the versioned setting, which was read
- ** prior to opening the local checkout (i.e. which is most
- ** likely empty and does not actually contain any versioned
- ** setting files yet). Normally, this value would be given
- ** first priority within db_get_boolean(); however, this is
- ** a special case because we know the on-disk files may not
- ** exist yet. */
- g.allowSymlinks = allowSymlinks;
- }else{
- /* Since the local checkout may not have any files at this
- ** point, this will probably be the setting value from the
- ** repository or global configuration databases. */
- g.allowSymlinks = db_get_boolean("allow-symlinks",
- db_allow_symlinks_by_default());
- }
db_lset("repository", zRepo);
db_record_repository_filename(zRepo);
db_set_checkout(0);
azNewArgv[0] = g.argv[0];
g.argv = azNewArgv;
@@ -3376,12 +3643,13 @@
const char *name; /* Name of the setting */
const char *var; /* Internal variable name used by db_set() */
int width; /* Width of display. 0 for boolean values and
** negative for values which should not appear
** on the /setup_settings page. */
- int versionable; /* Is this setting versionable? */
- int forceTextArea; /* Force using a text area for display? */
+ char versionable; /* Is this setting versionable? */
+ char forceTextArea; /* Force using a text area for display? */
+ char sensitive; /* True if this a security-sensitive setting */
const char *def; /* Default value */
};
#endif /* INTERFACE */
/*
@@ -3395,32 +3663,29 @@
** SETTING: admin-log boolean default=off
**
** When the admin-log setting is enabled, configuration changes are recorded
** in the "admin_log" table of the repository.
*/
-#if defined(_WIN32)
-/*
-** SETTING: allow-symlinks boolean default=off versionable
-**
-** When allow-symlinks is OFF, symbolic links in the repository are followed
-** and treated no differently from real files. When allow-symlinks is ON,
-** the object to which the symbolic link points is ignored, and the content
-** of the symbolic link that is stored in the repository is the name of the
-** object to which the symbolic link points.
-*/
-#endif
-#if !defined(_WIN32)
-/*
-** SETTING: allow-symlinks boolean default=on versionable
-**
-** When allow-symlinks is OFF, symbolic links in the repository are followed
-** and treated no differently from real files. When allow-symlinks is ON,
-** the object to which the symbolic link points is ignored, and the content
-** of the symbolic link that is stored in the repository is the name of the
-** object to which the symbolic link points.
-*/
-#endif
+/*
+** SETTING: allow-symlinks boolean default=off sensitive
+**
+** When allow-symlinks is OFF, Fossil does not see symbolic links
+** (a.k.a "symlinks") on disk as a separate class of object. Instead Fossil
+** sees the object that the symlink points to. Fossil will only manage files
+** and directories, not symlinks. When a symlink is added to a repository,
+** the object that the symlink points to is added, not the symlink itself.
+**
+** When allow-symlinks is ON, Fossil sees symlinks on disk as a separate
+** object class that is distinct from files and directories. When a symlink
+** is added to a repository, Fossil stores the target filename. In other
+** words, Fossil stores the symlink itself, not the object that the symlink
+** points to.
+**
+** Symlinks are not cross-platform. They are not available on all
+** operating systems and file systems. Hence the allow-symlinks setting is
+** OFF by default, for portability.
+*/
/*
** SETTING: auto-captcha boolean default=on variable=autocaptcha
** If enabled, the /login page provides a button that will automatically
** fill in the captcha password. This makes things easier for human users,
** at the expense of also making logins easier for malicious robots.
@@ -3470,11 +3735,11 @@
** there is no cron job periodically running "fossil backoffice",
** email notifications and other work normally done by the
** backoffice will not occur.
*/
/*
-** SETTING: backoffice-logfile width=40
+** SETTING: backoffice-logfile width=40 sensitive
** If backoffice-logfile is not an empty string and is a valid
** filename, then a one-line message is appended to that file
** every time the backoffice runs. This can be used for debugging,
** to ensure that backoffice is running appropriately.
*/
@@ -3547,11 +3812,11 @@
/*
** SETTING: crnl-glob width=40 versionable block-text
** This is an alias for the crlf-glob setting.
*/
/*
-** SETTING: default-perms width=16 default=u
+** SETTING: default-perms width=16 default=u sensitive
** Permissions given automatically to new users. For more
** information on permissions see the Users page in Server
** Administration of the HTTP UI.
*/
/*
@@ -3559,11 +3824,11 @@
** If enabled, permit files that may be binary
** or that match the "binary-glob" setting to be used with
** external diff programs. If disabled, skip these files.
*/
/*
-** SETTING: diff-command width=40
+** SETTING: diff-command width=40 sensitive
** The value is an external command to run when performing a diff.
** If undefined, the internal text diff will be used.
*/
/*
** SETTING: dont-push boolean default=off
@@ -3574,11 +3839,11 @@
/*
** SETTING: dotfiles boolean versionable default=off
** If enabled, include --dotfiles option for all compatible commands.
*/
/*
-** SETTING: editor width=32
+** SETTING: editor width=32 sensitive
** The value is an external command that will launch the
** text editor command used for check-in comments.
*/
/*
** SETTING: empty-dirs width=40 versionable block-text
@@ -3617,16 +3882,16 @@
** An empty list prohibits editing via that page. Note that
** it cannot edit binary files, so the list should not
** contain any globs for, e.g., images or PDFs.
*/
/*
-** SETTING: gdiff-command width=40 default=gdiff
+** SETTING: gdiff-command width=40 default=gdiff sensitive
** The value is an external command to run when performing a graphical
** diff. If undefined, text diff will be used.
*/
/*
-** SETTING: gmerge-command width=40
+** SETTING: gmerge-command width=40 sensitive
** The value is a graphical merge conflict resolver command operating
** on four files. Examples:
**
** kdiff3 "%baseline" "%original" "%merge" -o "%output"
** xxdiff "%original" "%baseline" "%merge" -M "%output"
@@ -3757,11 +4022,11 @@
** the associated files within the checkout -AND- the "rm"
** and "delete" commands will also remove the associated
** files from within the checkout.
*/
/*
-** SETTING: pgp-command width=40
+** SETTING: pgp-command width=40 sensitive
** Command used to clear-sign manifests at check-in.
** Default value is "gpg --clearsign -o"
*/
/*
** SETTING: forbid-delta-manifests boolean default=off
@@ -3817,22 +4082,22 @@
**
** If repolist-skin has a value of 2, then the repository is omitted from
** the list in use cases 1 through 4, but not for 5 and 6.
*/
/*
-** SETTING: self-register boolean default=off
+** SETTING: self-register boolean default=off sensitive
** Allow users to register themselves through the HTTP UI.
** This is useful if you want to see other names than
** "Anonymous" in e.g. ticketing system. On the other hand
** users can not be deleted.
*/
/*
-** SETTING: ssh-command width=40
+** SETTING: ssh-command width=40 sensitive
** The command used to talk to a remote machine with the "ssh://" protocol.
*/
/*
-** SETTING: ssl-ca-location width=40
+** SETTING: ssl-ca-location width=40 sensitive
** The full pathname to a file containing PEM encoded
** CA root certificates, or a directory of certificates
** with filenames formed from the certificate hashes as
** required by OpenSSL.
**
@@ -3842,11 +4107,11 @@
** Checking your platform behaviour is required if the
** exact contents of the CA root is critical for your
** application.
*/
/*
-** SETTING: ssl-identity width=40
+** SETTING: ssl-identity width=40 sensitive
** The full pathname to a file containing a certificate
** and private key in PEM format. Create by concatenating
** the certificate and private key files.
**
** This identity will be presented to SSL servers to
@@ -3853,33 +4118,33 @@
** authenticate this client, in addition to the normal
** password authentication.
*/
#ifdef FOSSIL_ENABLE_TCL
/*
-** SETTING: tcl boolean default=off
+** SETTING: tcl boolean default=off sensitive
** If enabled Tcl integration commands will be added to the TH1
** interpreter, allowing arbitrary Tcl expressions and
** scripts to be evaluated from TH1. Additionally, the Tcl
** interpreter will be able to evaluate arbitrary TH1
** expressions and scripts.
*/
/*
-** SETTING: tcl-setup width=40 block-text
+** SETTING: tcl-setup width=40 block-text sensitive
** This is the setup script to be evaluated after creating
** and initializing the Tcl interpreter. By default, this
** is empty and no extra setup is performed.
*/
#endif /* FOSSIL_ENABLE_TCL */
/*
-** SETTING: tclsh width=80 default=tclsh
+** SETTING: tclsh width=80 default=tclsh sensitive
** Name of the external TCL interpreter used for such things
** as running the GUI diff viewer launched by the --tk option
** of the various "diff" commands.
*/
#ifdef FOSSIL_ENABLE_TH1_DOCS
/*
-** SETTING: th1-docs boolean default=off
+** SETTING: th1-docs boolean default=off sensitive
** If enabled, this allows embedded documentation files to contain
** arbitrary TH1 scripts that are evaluated on the server. If native
** Tcl integration is also enabled, this setting has the
** potential to allow anybody with check-in privileges to
** do almost anything that the associated operating system
@@ -3932,11 +4197,11 @@
** of a "fossil clone" or "fossil sync" command. The
** default is false, in which case the -u option is
** needed to clone or sync unversioned files.
*/
/*
-** SETTING: web-browser width=30
+** SETTING: web-browser width=30 sensitive
** A shell command used to launch your preferred
** web browser when given a URL as an argument.
** Defaults to "start" on windows, "open" on Mac,
** and "firefox" on Unix.
*/
@@ -4058,11 +4323,13 @@
fossil_fatal("cannot set 'manifest' globally");
}
if( unsetFlag ){
db_unset(pSetting->name, globalFlag);
}else{
+ db_protect_only(PROTECT_NONE);
db_set(pSetting->name, g.argv[3], globalFlag);
+ db_protect_pop();
}
if( isManifest && g.localOpen ){
manifest_to_disk(db_lget_int("checkout", 0));
}
}else{
Index: src/file.c
==================================================================
--- src/file.c
+++ src/file.c
@@ -47,22 +47,21 @@
** used for files that are under management by a Fossil repository. ExtFILE
** should be used for files that are not under management. SymFILE is for
** a few special cases such as the "fossil test-tarball" command when we never
** want to follow symlinks.
**
-** If RepoFILE is used and if the allow-symlinks setting is true and if
-** the object is a symbolic link, then the object is treated like an ordinary
-** file whose content is name of the object to which the symbolic link
-** points.
-**
-** If ExtFILE is used or allow-symlinks is false, then operations on a
-** symbolic link are the same as operations on the object to which the
-** symbolic link points.
-**
-** SymFILE is like RepoFILE except that it always uses the target filename of
-** a symbolic link as the content, instead of the content of the object
-** that the symlink points to. SymFILE acts as if allow-symlinks is always ON.
+** ExtFILE Symbolic links always refer to the object to which the
+** link points. Symlinks are never recognized as symlinks but
+** instead always appear to the the target object.
+**
+** SymFILE Symbolic links always appear to be files whose name is
+** the target pathname of the symbolic link.
+**
+** RepoFILE Like symfile is allow-symlinks is true, or like
+** ExtFile if allow-symlinks is false. In other words,
+** symbolic links are only recognized as something different
+** from files or directories if allow-symlinks is true.
*/
#define ExtFILE 0 /* Always follow symlinks */
#define RepoFILE 1 /* Follow symlinks if and only if allow-symlinks is OFF */
#define SymFILE 2 /* Never follow symlinks */
@@ -134,13 +133,16 @@
int eFType /* Look at symlink itself if RepoFILE and enabled. */
){
int rc;
void *zMbcs = fossil_utf8_to_path(zFilename, 0);
#if !defined(_WIN32)
- if( eFType>=RepoFILE && (eFType==SymFILE || db_allow_symlinks()) ){
+ if( (eFType=RepoFILE && db_allow_symlinks())
+ || eFType==SymFILE ){
+ /* Symlinks look like files whose content is the name of the target */
rc = lstat(zMbcs, buf);
}else{
+ /* Symlinks look like the object to which they point */
rc = stat(zMbcs, buf);
}
#else
rc = win32_stat(zMbcs, buf, eFType);
#endif
@@ -316,17 +318,90 @@
/*
** Return TRUE if the named file is a symlink and symlinks are allowed.
** Return false for all other cases.
**
-** This routines RepoFILE - that zFilename is always a file under management.
+** This routines assumes RepoFILE - that zFilename is always a file
+** under management.
**
** On Windows, always return False.
*/
int file_islink(const char *zFilename){
return file_perm(zFilename, RepoFILE)==PERM_LNK;
}
+
+/*
+** Check every sub-directory of zRoot along the path to zFile.
+** If any sub-directory is really an ordinary file or a symbolic link,
+** return an integer which is the length of the prefix of zFile which
+** is the name of that object. Return 0 if all no non-directory
+** objects are found along the path.
+**
+** Example: Given inputs
+**
+** zRoot = /home/alice/project1
+** zFile = /home/alice/project1/main/src/js/fileA.js
+**
+** Look for objects in the following order:
+**
+** /home/alice/project/main
+** /home/alice/project/main/src
+** /home/alice/project/main/src/js
+**
+** If any of those objects exist and are something other than a directory
+** then return the length of the name of the first non-directory object
+** seen.
+*/
+int file_nondir_objects_on_path(const char *zRoot, const char *zFile){
+ int i = (int)strlen(zRoot);
+ char *z = fossil_strdup(zFile);
+ assert( fossil_strnicmp(zRoot, z, i)==0 );
+ if( i && zRoot[i-1]=='/' ) i--;
+ while( z[i]=='/' ){
+ int j, rc;
+ for(j=i+1; z[j] && z[j]!='/'; j++){}
+ if( z[j]!='/' ) break;
+ z[j] = 0;
+ rc = file_isdir(z, SymFILE);
+ if( rc!=1 ){
+ if( rc==2 ){
+ fossil_free(z);
+ return j;
+ }
+ break;
+ }
+ z[j] = '/';
+ i = j;
+ }
+ fossil_free(z);
+ return 0;
+}
+
+/*
+** The file named zFile is suppose to be an in-tree file. Check to
+** ensure that it will be safe to write to this file by verifying that
+** there are no symlinks or other non-directory objects in between the
+** root of the checkout and zFile.
+**
+** If a problem is found, print a warning message (using fossil_warning())
+** and return non-zero. If everything is ok, return zero.
+*/
+int file_unsafe_in_tree_path(const char *zFile){
+ int n;
+ if( !file_is_absolute_path(zFile) ){
+ fossil_panic("%s is not an absolute pathname",zFile);
+ }
+ if( fossil_strnicmp(g.zLocalRoot, zFile, (int)strlen(g.zLocalRoot)) ){
+ fossil_panic("%s is not a prefix of %s", g.zLocalRoot, zFile);
+ }
+ n = file_nondir_objects_on_path(g.zLocalRoot, zFile);
+ if( n ){
+ fossil_warning("cannot write to %s because non-directory object %.*s"
+ " is in the way", zFile, n, zFile);
+ }
+ return n;
+}
/*
** Return 1 if zFilename is a directory. Return 0 if zFilename
** does not exist. Return 2 if zFilename exists but is something
** other than a directory.
@@ -570,11 +645,14 @@
*/
int file_setexe(const char *zFilename, int onoff){
int rc = 0;
#if !defined(_WIN32)
struct stat buf;
- if( fossil_stat(zFilename, &buf, RepoFILE)!=0 || S_ISLNK(buf.st_mode) ){
+ if( fossil_stat(zFilename, &buf, RepoFILE)!=0
+ || S_ISLNK(buf.st_mode)
+ || S_ISDIR(buf.st_mode)
+ ){
return 0;
}
if( onoff ){
int targetMode = (buf.st_mode & 0444)>>2;
if( (buf.st_mode & 0100)==0 ){
@@ -1236,12 +1314,12 @@
sqlite3_int64 iMtime;
struct fossilStat testFileStat;
memset(zBuf, 0, sizeof(zBuf));
blob_zero(&x);
file_canonical_name(zPath, &x, slash);
- fossil_print("[%s] -> [%s]\n", zPath, blob_buffer(&x));
- blob_reset(&x);
+ char *zFull = blob_str(&x);
+ fossil_print("[%s] -> [%s]\n", zPath, zFull);
memset(&testFileStat, 0, sizeof(struct fossilStat));
rc = fossil_stat(zPath, &testFileStat, 0);
fossil_print(" stat_rc = %d\n", rc);
sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size);
fossil_print(" stat_size = %s\n", zBuf);
@@ -1285,10 +1363,13 @@
fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zPath));
fossil_print(" file_islink = %d\n", file_islink(zPath));
fossil_print(" file_isexe(RepoFILE) = %d\n", file_isexe(zPath,RepoFILE));
fossil_print(" file_isdir(RepoFILE) = %d\n", file_isdir(zPath,RepoFILE));
fossil_print(" file_is_repository = %d\n", file_is_repository(zPath));
+ fossil_print(" file_is_reserved_name = %d\n",
+ file_is_reserved_name(zFull,-1));
+ blob_reset(&x);
if( reset ) resetStat();
}
/*
** COMMAND: test-file-environment
@@ -1300,32 +1381,45 @@
**
** Options:
**
** --allow-symlinks BOOLEAN Temporarily turn allow-symlinks on/off
** --open-config Open the configuration database first.
-** --slash Trailing slashes, if any, are retained.
** --reset Reset cached stat() info for each file.
+** --root ROOT Use ROOT as the root of the checkout
+** --slash Trailing slashes, if any, are retained.
*/
void cmd_test_file_environment(void){
int i;
int slashFlag = find_option("slash",0,0)!=0;
int resetFlag = find_option("reset",0,0)!=0;
+ const char *zRoot = find_option("root",0,1);
const char *zAllow = find_option("allow-symlinks",0,1);
if( find_option("open-config", 0, 0)!=0 ){
Th_OpenConfig(1);
}
db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
fossil_print("filenames_are_case_sensitive() = %d\n",
filenames_are_case_sensitive());
- fossil_print("db_allow_symlinks_by_default() = %d\n",
- db_allow_symlinks_by_default());
if( zAllow ){
g.allowSymlinks = !is_false(zAllow);
}
+ if( zRoot==0 ) zRoot = g.zLocalRoot;
fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
+ fossil_print("local-root = [%s]\n", zRoot);
for(i=2; i=12 ){ /* strlen("_FOSSIL_-(shm|wal)") */
+ /* Check for (-wal, -shm, -journal) suffixes, with an eye towards
+ ** runtime speed. */
+ if( zEnd[-4]=='-' ){
+ if( fossil_strnicmp("wal", &zEnd[-3], 3)
+ && fossil_strnicmp("shm", &zEnd[-3], 3) ){
+ return 0;
+ }
+ gotSuffix = 4;
+ }else if( nFilename>=16 && zEnd[-8]=='-' ){ /*strlen(_FOSSIL_-journal) */
+ if( fossil_strnicmp("journal", &zEnd[-7], 7) ) return 0;
+ gotSuffix = 8;
+ }
+ if( gotSuffix ){
+ assert( 4==gotSuffix || 8==gotSuffix );
+ zEnd -= gotSuffix;
+ nFilename -= gotSuffix;
+ gotSuffix = 1;
+ }
+ assert( nFilename>=8 && "strlen(_FOSSIL_)" );
+ assert( gotSuffix==0 || gotSuffix==1 );
+ }
+ switch( zEnd[-1] ){
+ case '_':{
+ if( fossil_strnicmp("_FOSSIL_", &zEnd[-8], 8) ) return 0;
+ if( 8==nFilename ) return 1;
+ return zEnd[-9]=='/' ? 2 : gotSuffix;
+ }
+ case 'T':
+ case 't':{
+ if( nFilename<9 || zEnd[-9]!='.'
+ || fossil_strnicmp(".fslckout", &zEnd[-9], 9) ){
+ return 0;
+ }
+ if( 9==nFilename ) return 1;
+ return zEnd[-10]=='/' ? 2 : gotSuffix;
+ }
+ default:{
+ return 0;
+ }
+ }
+}
Index: src/forum.c
==================================================================
--- src/forum.c
+++ src/forum.c
@@ -1114,13 +1114,15 @@
moderation_approve('f', fpid);
if( g.perm.AdminForum
&& PB("trust")
&& (zUserToTrust = P("trustuser"))!=0
){
+ db_unprotect(PROTECT_USER);
db_multi_exec("UPDATE user SET cap=cap||'4' "
"WHERE login=%Q AND cap NOT GLOB '*4*'",
zUserToTrust);
+ db_protect_pop();
}
cgi_redirectf("%R/forumpost/%S",P("fpid"));
return;
}
if( P("reject") ){
Index: src/hook.c
==================================================================
--- src/hook.c
+++ src/hook.c
@@ -123,15 +123,17 @@
** If N==0, then there is no expectation of new artifacts arriving
** soon and so post-receive hooks can be run without delay.
*/
void hook_expecting_more_artifacts(int N){
if( N>0 ){
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec(
"REPLACE INTO config(name,value,mtime)"
"VALUES('hook-embargo',now()+%d,now())",
N
);
+ db_protect_pop();
}else{
db_unset("hook-embargo",0);
}
}
@@ -243,10 +245,11 @@
fossil_fatal("the --command and --type options are required");
}
validate_type(zType);
nSeq = zSeq ? atoi(zSeq) : 10;
db_begin_write();
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec(
"INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
"UPDATE config"
" SET value=json_insert("
" CASE WHEN json_valid(value) THEN value ELSE '[]' END,'$[#]',"
@@ -253,10 +256,11 @@
" json_object('cmd',%Q,'type',%Q,'seq',%d)),"
" mtime=now()"
" WHERE name='hooks';",
zCmd, zType, nSeq
);
+ db_protect_pop();
db_commit_transaction();
}else
if( strncmp(zCmd, "edit", nCmd)==0 ){
const char *zCmd = find_option("command",0,1);
const char *zType = find_option("type",0,1);
@@ -290,20 +294,23 @@
}
if( zSeq ){
blob_append_sql(&sql, ",'$[%d].seq',%d", id, nSeq);
}
blob_append_sql(&sql,") WHERE name='hooks';");
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec("%s", blob_sql_text(&sql));
+ db_protect_pop();
blob_reset(&sql);
}
db_commit_transaction();
}else
if( strncmp(zCmd, "delete", nCmd)==0 ){
int i;
verify_all_options();
if( g.argc<4 ) usage("delete ID ...");
db_begin_write();
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec(
"INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
);
for(i=3; iInterwiki links are hyperlink targets of the form
Index: src/json_config.c
==================================================================
--- src/json_config.c
+++ src/json_config.c
@@ -83,11 +83,10 @@
{ "keep-glob", CONFIGSET_PROJ },
{ "crlf-glob", CONFIGSET_PROJ },
{ "crnl-glob", CONFIGSET_PROJ },
{ "encoding-glob", CONFIGSET_PROJ },
{ "empty-dirs", CONFIGSET_PROJ },
-{ "allow-symlinks", CONFIGSET_PROJ },
{ "dotfiles", CONFIGSET_PROJ },
{ "ticket-table", CONFIGSET_TKT },
{ "ticket-common", CONFIGSET_TKT },
{ "ticket-change", CONFIGSET_TKT },
Index: src/json_user.c
==================================================================
--- src/json_user.c
+++ src/json_user.c
@@ -212,13 +212,15 @@
json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS,
"User %s already exists.", zName);
goto error;
}else{
Stmt ins = empty_Stmt;
+ db_unprotect(PROTECT_USER);
db_prepare(&ins, "INSERT INTO user (login) VALUES(%Q)",zName);
db_step( &ins );
db_finalize(&ins);
+ db_protect_pop();
uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName);
assert(uid>0);
zNameNew = zName;
cson_object_set( pUser, "uid", cson_value_new_integer(uid) );
}
@@ -345,13 +347,15 @@
#endif
#if 0
puts(blob_str(&sql));
cson_output_FILE( cson_object_value(pUser), stdout, NULL );
#endif
+ db_unprotect(PROTECT_USER);
db_prepare(&q, "%s", blob_sql_text(&sql));
db_exec(&q);
db_finalize(&q);
+ db_protect_pop();
#if TRY_LOGIN_GROUP
if( zPW || cson_value_get_bool(forceLogout) ){
Blob groupSql = empty_blob;
char * zErr = NULL;
blob_append_sql(&groupSql,
@@ -358,11 +362,13 @@
"INSERT INTO user(login)"
" SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);",
zName, zName
);
blob_append(&groupSql, blob_str(&sql), blob_size(&sql));
+ db_unprotect(PROTECT_USER);
login_group_sql(blob_str(&groupSql), NULL, NULL, &zErr);
+ db_protect_pop();
blob_reset(&groupSql);
if( zErr ){
json_set_err( FSL_JSON_E_UNKNOWN,
"Repo-group update at least partially failed: %s",
zErr);
Index: src/login.c
==================================================================
--- src/login.c
+++ src/login.c
@@ -293,13 +293,15 @@
if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
zCookie = login_gen_user_cookie_value(zUsername, zHash);
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(),
bSessionCookie ? 0 : expires);
record_login_attempt(zUsername, zIpAddr, 1);
+ db_unprotect(PROTECT_USER);
db_multi_exec("UPDATE user SET cookie=%Q,"
" cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
zHash, expires, uid);
+ db_protect_pop();
fossil_free(zHash);
if( zDest ){
*zDest = zCookie;
}else{
free(zCookie);
@@ -356,14 +358,16 @@
}else{
const char *cookie = login_cookie_name();
/* To logout, change the cookie value to an empty string */
cgi_set_cookie(cookie, "",
login_cookie_path(), -86400);
+ db_unprotect(PROTECT_USER);
db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
" cexpire=0 WHERE uid=%d"
" AND login NOT IN ('anonymous','nobody',"
" 'developer','reader')", g.userUid);
+ db_protect_pop();
cgi_replace_parameter(cookie, NULL);
cgi_replace_parameter("anon", NULL);
}
}
@@ -580,22 +584,27 @@
;
}else{
char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0);
char *zChngPw;
char *zErr;
+ int rc;
+
+ db_unprotect(PROTECT_USER);
db_multi_exec(
"UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid
);
- fossil_free(zNewPw);
zChngPw = mprintf(
"UPDATE user"
" SET pw=shared_secret(%Q,%Q,"
" (SELECT value FROM config WHERE name='project-code'))"
" WHERE login=%Q",
zNew1, g.zLogin, g.zLogin
);
- if( login_group_sql(zChngPw, "
\n", &zErr);
+ db_protect_pop();
+ if( rc ){
zErrMsg = mprintf("%s", zErr);
fossil_free(zErr);
}else{
redirect_to_g();
return;
@@ -835,16 +844,18 @@
zLogin, zHash
);
pStmt = 0;
rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
+ db_unprotect(PROTECT_USER);
db_multi_exec(
"UPDATE user SET cookie=%Q, cexpire=%.17g"
" WHERE login=%Q",
zHash,
sqlite3_column_double(pStmt, 0), zLogin
);
+ db_protect_pop();
nXfer++;
}
sqlite3_finalize(pStmt);
}
sqlite3_close(pOther);
@@ -1619,11 +1630,13 @@
"INSERT INTO user(login,pw,cap,info,mtime)\n"
"VALUES(%Q,%Q,%Q,"
"'%q <%q>\nself-register from ip %q on '||datetime('now'),now())",
zUserID, zPass, zStartPerms, zDName, zEAddr, g.zIpAddr);
fossil_free(zPass);
+ db_unprotect(PROTECT_USER);
db_multi_exec("%s", blob_sql_text(&sql));
+ db_protect_pop();
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID);
login_set_user_cookie(zUserID, uid, NULL, 0);
if( doAlerts ){
/* Also make the new user a subscriber. */
Blob hdr, body;
@@ -1832,14 +1845,16 @@
while( db_step(&q)==SQLITE_ROW ){
const char *zRepoName = db_column_text(&q, 1);
if( file_size(zRepoName, ExtFILE)<0 ){
/* Silently remove non-existent repositories from the login group. */
const char *zLabel = db_column_text(&q, 0);
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec(
"DELETE FROM config WHERE name GLOB 'peer-*-%q'",
&zLabel[10]
);
+ db_protect_pop();
continue;
}
rc = sqlite3_open_v2(
zRepoName, &pPeer,
SQLITE_OPEN_READWRITE,
@@ -2004,11 +2019,13 @@
"REPLACE INTO config(name,value,mtime) VALUES('peer-name-%q',%Q,now());"
"REPLACE INTO config(name,value,mtime) VALUES('peer-repo-%q',%Q,now());"
"COMMIT;",
zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo
);
+ db_unprotect(PROTECT_CONFIG);
login_group_sql(zSql, "
", "
", pzErrMsg);
+ db_protect_pop();
fossil_free(zSql);
}
/*
** Leave the login group that we are currently part of.
@@ -2025,17 +2042,19 @@
" WHERE name='login-group-name'"
" AND (SELECT count(*) FROM config WHERE name GLOB 'peer-*')==0;",
zProjCode
);
fossil_free(zProjCode);
+ db_unprotect(PROTECT_CONFIG);
login_group_sql(zSql, "
", "
", pzErrMsg);
fossil_free(zSql);
db_multi_exec(
"DELETE FROM config "
" WHERE name GLOB 'peer-*'"
" OR name GLOB 'login-group-*';"
);
+ db_protect_pop();
}
/*
** COMMAND: login-group*
**
Index: src/main.c
==================================================================
--- src/main.c
+++ src/main.c
@@ -1372,19 +1372,21 @@
g.zTop = &g.zBaseURL[7+strlen(zHost)];
g.zHttpsURL = mprintf("https://%s%.*s", zHost, i, zCur);
}
}
if( db_is_writeable("repository") ){
+ db_unprotect(PROTECT_CONFIG);
if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", g.zBaseURL)){
db_multi_exec("INSERT INTO config(name,value,mtime)"
"VALUES('baseurl:%q',1,now())", g.zBaseURL);
}else{
db_optional_sql("repository",
"REPLACE INTO config(name,value,mtime)"
"VALUES('baseurl:%q',1,now())", g.zBaseURL
);
}
+ db_protect_pop();
}
}
/*
** Send an HTTP redirect back to the designated Index Page.
Index: src/manifest.c
==================================================================
--- src/manifest.c
+++ src/manifest.c
@@ -481,14 +481,23 @@
blob_appendf(pErr, "line 1 not recognized");
return 0;
}
/* Then verify the Z-card.
*/
+#if 1
+ /* Disable this ***ONLY*** (ONLY!) when testing hand-written inputs
+ for card-related syntax errors. */
if( verify_z_card(z, n, pErr)==2 ){
blob_reset(pContent);
return 0;
}
+#else
+#warning ACHTUNG - z-card check is disabled for testing purposes.
+ if(0 && verify_z_card(NULL, 0, NULL)){
+ /*avoid unused static func error*/
+ }
+#endif
/* Allocate a Manifest object to hold the parsed control artifact.
*/
p = fossil_malloc( sizeof(*p) );
memset(p, 0, sizeof(*p));
@@ -601,10 +610,11 @@
case 'E': {
if( p->rEventDate>0.0 ) SYNTAX("more than one E-card");
p->rEventDate = db_double(0.0,"SELECT julianday(%Q)", next_token(&x,0));
if( p->rEventDate<=0.0 ) SYNTAX("malformed date on E-card");
p->zEventId = next_token(&x, &sz);
+ if( p->zEventId==0 ) SYNTAX("missing hash on E-card");
if( !hname_validate(p->zEventId, sz) ){
SYNTAX("malformed hash on E-card");
}
p->type = CFTYPE_EVENT;
break;
@@ -625,10 +635,11 @@
if( !file_is_simple_pathname_nonstrict(zName) ){
SYNTAX("F-card filename is not a simple path");
}
zUuid = next_token(&x, &sz);
if( p->zBaseline==0 || zUuid!=0 ){
+ if( zUuid==0 ) SYNTAX("missing hash on F-card");
if( !hname_validate(zUuid,sz) ){
SYNTAX("F-card hash invalid");
}
}
zPerm = next_token(&x,0);
@@ -643,17 +654,24 @@
p->nFileAlloc = p->nFileAlloc*2 + 10;
p->aFile = fossil_realloc(p->aFile,
p->nFileAlloc*sizeof(p->aFile[0]) );
}
i = p->nFile++;
+ if( i>0 && fossil_strcmp(p->aFile[i-1].zName, zName)>=0 ){
+ SYNTAX("incorrect F-card sort order");
+ }
+ if( file_is_reserved_name(zName,-1) ){
+ /* If reserved names leaked into historical manifests due to
+ ** slack oversight by older versions of Fossil, simply ignore
+ ** those files */
+ p->nFile--;
+ break;
+ }
p->aFile[i].zName = zName;
p->aFile[i].zUuid = zUuid;
p->aFile[i].zPerm = zPerm;
p->aFile[i].zPrior = zPriorName;
- if( i>0 && fossil_strcmp(p->aFile[i-1].zName, zName)>=0 ){
- SYNTAX("incorrect F-card sort order");
- }
p->type = CFTYPE_MANIFEST;
break;
}
/*
Index: src/mkindex.c
==================================================================
--- src/mkindex.c
+++ src/mkindex.c
@@ -90,10 +90,11 @@
#define CMDFLAG_SETTING 0x0020 /* A setting */
#define CMDFLAG_VERSIONABLE 0x0040 /* A versionable setting */
#define CMDFLAG_BLOCKTEXT 0x0080 /* Multi-line text setting */
#define CMDFLAG_BOOLEAN 0x0100 /* A boolean setting */
#define CMDFLAG_RAWCONTENT 0x0200 /* Do not interpret webpage content */
+#define CMDFLAG_SENSITIVE 0x0400 /* Security-sensitive setting */
/**************************************************************************/
/*
** Each entry looks like this:
*/
@@ -248,10 +249,12 @@
}else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){
aEntry[nUsed].eType &= ~(CMDFLAG_BOOLEAN);
aEntry[nUsed].eType |= CMDFLAG_BLOCKTEXT;
}else if( j==11 && strncmp(&zLine[i], "versionable", j)==0 ){
aEntry[nUsed].eType |= CMDFLAG_VERSIONABLE;
+ }else if( j==9 && strncmp(&zLine[i], "sensitive", j)==0 ){
+ aEntry[nUsed].eType |= CMDFLAG_SENSITIVE;
}else if( j>6 && strncmp(&zLine[i], "width=", 6)==0 ){
aEntry[nUsed].iWidth = atoi(&zLine[i+6]);
}else if( j>8 && strncmp(&zLine[i], "default=", 8)==0 ){
aEntry[nUsed].zDflt = string_dup(&zLine[i+8], j-8);
}else if( j>9 && strncmp(&zLine[i], "variable=", 9)==0 ){
@@ -479,14 +482,15 @@
if( zVar ){
printf(" \"%s\",%*s", zVar, (int)(15-strlen(zVar)), "");
}else{
printf(" 0,%*s", 16, "");
}
- printf(" %3d, %d, %d, \"%s\"%*s },\n",
+ printf(" %3d, %d, %d, %d, \"%s\"%*s },\n",
aEntry[i].iWidth,
(aEntry[i].eType & CMDFLAG_VERSIONABLE)!=0,
(aEntry[i].eType & CMDFLAG_BLOCKTEXT)!=0,
+ (aEntry[i].eType & CMDFLAG_SENSITIVE)!=0,
zDef, (int)(10-strlen(zDef)), ""
);
if( aEntry[i].zIf ){
printf("#endif\n");
}
Index: src/printf.c
==================================================================
--- src/printf.c
+++ src/printf.c
@@ -1148,12 +1148,15 @@
rc = fossil_print_error(rc, z);
abort();
exit(rc);
}
NORETURN void fossil_fatal(const char *zFormat, ...){
+ static int once = 0;
char *z;
int rc = 1;
+ if( once ) exit(1);
+ once = 1;
va_list ap;
mainInFatalError = 1;
va_start(ap, zFormat);
z = vmprintf(zFormat, ap);
va_end(ap);
Index: src/rebuild.c
==================================================================
--- src/rebuild.c
+++ src/rebuild.c
@@ -52,10 +52,11 @@
}
/* Add the user.mtime column if it is missing. (2011-04-27)
*/
if( !db_table_has_column("repository", "user", "mtime") ){
+ db_unprotect(PROTECT_ALL);
db_multi_exec(
"CREATE TEMP TABLE temp_user AS SELECT * FROM user;"
"DROP TABLE user;"
"CREATE TABLE user(\n"
" uid INTEGER PRIMARY KEY,\n"
@@ -72,19 +73,22 @@
"INSERT OR IGNORE INTO user"
" SELECT uid, login, pw, cap, cookie,"
" ipaddr, cexpire, info, now(), photo FROM temp_user;"
"DROP TABLE temp_user;"
);
+ db_protect_pop();
}
/* Add the config.mtime column if it is missing. (2011-04-27)
*/
if( !db_table_has_column("repository", "config", "mtime") ){
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec(
"ALTER TABLE config ADD COLUMN mtime INTEGER;"
"UPDATE config SET mtime=now();"
);
+ db_protect_pop();
}
/* Add the shun.mtime and shun.scom columns if they are missing.
** (2011-04-27)
*/
@@ -382,10 +386,11 @@
percent_complete(0);
}
alert_triggers_disable();
rebuild_update_schema();
blob_init(&sql, 0, 0);
+ db_unprotect(PROTECT_ALL);
db_prepare(&q,
"SELECT name FROM sqlite_schema /*scan*/"
" WHERE type='table'"
" AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
"'config','shun','private','reportfmt',"
@@ -475,10 +480,11 @@
alert_triggers_enable();
if(!g.fQuiet && ttyOutput ){
percent_complete(1000);
fossil_print("\n");
}
+ db_protect_pop();
return errCnt;
}
/*
** Number of neighbors to search
@@ -667,10 +673,11 @@
/* We should be done with options.. */
verify_all_options();
db_begin_transaction();
+ db_unprotect(PROTECT_ALL);
if( !compressOnlyFlag ){
search_drop_index();
ttyOutput = 1;
errCnt = rebuild_db(randomizeFlag, 1, doClustering);
reconstruct_private_table();
@@ -720,10 +727,11 @@
if( activateWal ){
db_multi_exec("PRAGMA journal_mode=WAL;");
}
}
if( runReindex ) search_rebuild_index();
+ db_protect_pop();
if( showStats ){
static const struct { int idx; const char *zLabel; } aStat[] = {
{ CFTYPE_ANY, "Artifacts:" },
{ CFTYPE_MANIFEST, "Manifests:" },
{ CFTYPE_CLUSTER, "Clusters:" },
@@ -755,18 +763,20 @@
** testing by cloning a working project repository.
*/
void test_detach_cmd(void){
db_find_and_open_repository(0, 2);
db_begin_transaction();
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec(
"DELETE FROM config WHERE name GLOB 'last-sync-*';"
"DELETE FROM config WHERE name GLOB 'sync-*:*';"
"UPDATE config SET value=lower(hex(randomblob(20)))"
" WHERE name='project-code';"
"UPDATE config SET value='detached-' || value"
" WHERE name='project-name' AND value NOT GLOB 'detached-*';"
);
+ db_protect_pop();
db_end_transaction(0);
}
/*
** COMMAND: test-create-clusters
@@ -910,10 +920,11 @@
if( privateOnly || bVerily ){
bNeedRebuild = db_exists("SELECT 1 FROM private");
delete_private_content();
}
if( !privateOnly ){
+ db_unprotect(PROTECT_ALL);
db_multi_exec(
"UPDATE user SET pw='';"
"DELETE FROM config WHERE name GLOB 'last-sync-*';"
"DELETE FROM config WHERE name GLOB 'sync-*:*';"
"DELETE FROM config WHERE name GLOB 'peer-*';"
@@ -933,14 +944,17 @@
"DROP TABLE IF EXISTS purgeitem;\n"
"DROP TABLE IF EXISTS admin_log;\n"
"DROP TABLE IF EXISTS vcache;\n"
);
}
+ db_protect_pop();
}
if( !bNeedRebuild ){
db_end_transaction(0);
+ db_unprotect(PROTECT_ALL);
db_multi_exec("VACUUM;");
+ db_protect_pop();
}else{
rebuild_db(0, 1, 0);
db_end_transaction(0);
}
}
Index: src/report.c
==================================================================
--- src/report.c
+++ src/report.c
@@ -230,15 +230,15 @@
/*
** Activate the query authorizer
*/
void report_restrict_sql(char **pzErr){
- sqlite3_set_authorizer(g.db, report_query_authorizer, (void*)pzErr);
+ db_set_authorizer(report_query_authorizer,(void*)pzErr,"Ticket-Report");
sqlite3_limit(g.db, SQLITE_LIMIT_VDBE_OP, 10000);
}
void report_unrestrict_sql(void){
- sqlite3_set_authorizer(g.db, 0, 0);
+ db_clear_authorizer();
}
/*
** Check the given SQL to see if is a valid query that does not
@@ -680,11 +680,11 @@
*/
if( pState->nCount==0 ){
/* Turn off the authorizer. It is no longer doing anything since the
** query has already been prepared.
*/
- sqlite3_set_authorizer(g.db, 0, 0);
+ db_clear_authorizer();
/* Figure out the number of columns, the column that determines background
** color, and whether or not this row of data is represented by multiple
** rows in the table.
*/
Index: src/security_audit.c
==================================================================
--- src/security_audit.c
+++ src/security_audit.c
@@ -281,10 +281,18 @@
@
Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum"
@ privileges (capabilities "fq5")
@ from users "anonymous" and "nobody"
@ on the User Configuration page.
}
+
+ /* The strict-manifest-syntax setting should be on. */
+ if( db_get_boolean("strict-manifest-syntax",1)==0 ){
+ @
WARNING:
+ @ The "strict-manifest-syntax" flag is off. This is a security
+ @ risk. Turn this setting on (its default) to protect the users
+ @ of this repository.
+ }
/* Obsolete: */
if( hasAnyCap(zAnonCap, "d") ||
hasAnyCap(zDevCap, "d") ||
hasAnyCap(zReadCap, "d") ){
@@ -596,15 +604,17 @@
if( P("cancel") ){
/* User pressed the cancel button. Go back */
cgi_redirect("secaudit0");
}
if( P("apply") ){
+ db_unprotect(PROTECT_USER);
db_multi_exec(
"UPDATE user SET cap=''"
" WHERE login IN ('nobody','anonymous');"
"DELETE FROM config WHERE name='public-pages';"
);
+ db_protect_pop();
db_set("self-register","0",0);
cgi_redirect("secaudit0");
}
style_header("Make This Website Private");
@
Click the "Make It Private" button below to disable all
Index: src/setup.c
==================================================================
--- src/setup.c
+++ src/setup.c
@@ -27,14 +27,16 @@
*/
void setup_incr_cfgcnt(void){
static int once = 1;
if( once ){
once = 0;
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'");
if( db_changes()==0 ){
db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)");
}
+ db_protect_pop();
}
}
/*
** Output a single entry for a menu generated using an HTML table.
@@ -195,11 +197,13 @@
}
if( zQ ){
int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
if( iQ!=iVal ){
login_verify_csrf_secret();
+ db_protect_only(PROTECT_NONE);
db_set(zVar, iQ ? "1" : "0", 0);
+ db_protect_pop();
setup_incr_cfgcnt();
admin_log("Set option [%q] to [%q].",
zVar, iQ ? "on" : "off");
iVal = iQ;
}
@@ -230,11 +234,13 @@
const char *zQ = P(zQParm);
if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
const int nZQ = (int)strlen(zQ);
login_verify_csrf_secret();
setup_incr_cfgcnt();
+ db_protect_only(PROTECT_NONE);
db_set(zVar, zQ, 0);
+ db_protect_pop();
admin_log("Set entry_attribute %Q to: %.*s%s",
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
zVal = zQ;
}
@ 20 ? "..." : ""));
z = zQ;
}
@@ -1162,11 +1170,13 @@
login_needed(0);
return;
}
db_begin_transaction();
if( P("clear")!=0 && cgi_csrf_safe(1) ){
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
+ db_protect_pop();
cgi_replace_parameter("adunit","");
cgi_replace_parameter("adright","");
setup_incr_cfgcnt();
}
@@ -1260,10 +1270,11 @@
if( !g.perm.Admin ){
login_needed(0);
return;
}
db_begin_transaction();
+ db_unprotect(PROTECT_CONFIG);
if( !cgi_csrf_safe(1) ){
/* Allow no state changes if not safe from CSRF */
}else if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
Blob img;
Stmt ins;
@@ -1290,10 +1301,11 @@
cgi_redirect("setup_logo");
}else if( P("setbg")!=0 && zBgMime && zBgMime[0] && szBgImg>0 ){
Blob img;
Stmt ins;
blob_init(&img, aBgImg, szBgImg);
+ db_unprotect(PROTECT_CONFIG);
db_prepare(&ins,
"REPLACE INTO config(name,value,mtime)"
" VALUES('background-image',:bytes,now())"
);
db_bind_blob(&ins, ":bytes", &img);
@@ -1302,13 +1314,15 @@
db_multi_exec(
"REPLACE INTO config(name,value,mtime)"
" VALUES('background-mimetype',%Q,now())",
zBgMime
);
+ db_protect_pop();
db_end_transaction(0);
cgi_redirect("setup_logo");
}else if( P("clrbg")!=0 ){
+ db_unprotect(PROTECT_CONFIG);
db_multi_exec(
"DELETE FROM config WHERE name IN "
"('background-image','background-mimetype')"
);
db_end_transaction(0);
@@ -1315,10 +1329,11 @@
cgi_redirect("setup_logo");
}else if( P("seticon")!=0 && zIconMime && zIconMime[0] && szIconImg>0 ){
Blob img;
Stmt ins;
blob_init(&img, aIconImg, szIconImg);
+ db_unprotect(PROTECT_CONFIG);
db_prepare(&ins,
"REPLACE INTO config(name,value,mtime)"
" VALUES('icon-image',:bytes,now())"
);
db_bind_blob(&ins, ":bytes", &img);
@@ -1327,10 +1342,11 @@
db_multi_exec(
"REPLACE INTO config(name,value,mtime)"
" VALUES('icon-mimetype',%Q,now())",
zIconMime
);
+ db_protect_pop();
db_end_transaction(0);
cgi_redirect("setup_logo");
}else if( P("clricon")!=0 ){
db_multi_exec(
"DELETE FROM config WHERE name IN "
@@ -1786,22 +1802,27 @@
const char *zValue
){
if( !cgi_csrf_safe(1) ) return;
if( zNewName[0]==0 || zValue[0]==0 ){
if( zOldName[0] ){
+ db_unprotect(PROTECT_CONFIG);
blob_append_sql(pSql,
"DELETE FROM config WHERE name='walias:%q';\n",
zOldName);
+ db_protect_pop();
}
return;
}
if( zOldName[0]==0 ){
+ db_unprotect(PROTECT_CONFIG);
blob_append_sql(pSql,
"INSERT INTO config(name,value,mtime) VALUES('walias:%q',%Q,now());\n",
zNewName, zValue);
+ db_protect_pop();
return;
}
+ db_unprotect(PROTECT_CONFIG);
if( strcmp(zOldName, zNewName)!=0 ){
blob_append_sql(pSql,
"UPDATE config SET name='walias:%q', value=%Q, mtime=now()"
" WHERE name='walias:%q';\n",
zNewName, zValue, zOldName);
@@ -1809,10 +1830,11 @@
blob_append_sql(pSql,
"UPDATE config SET value=%Q, mtime=now()"
" WHERE name='walias:%q' AND value<>%Q;\n",
zValue, zOldName, zValue);
}
+ db_protect_pop();
}
/*
** WEBPAGE: waliassetup
**
Index: src/setupuser.c
==================================================================
--- src/setupuser.c
+++ src/setupuser.c
@@ -315,11 +315,13 @@
/* Check for requests to delete the user */
if( P("delete") && cgi_csrf_safe(1) ){
int n;
if( P("verifydelete") ){
/* Verified delete user request */
+ db_unprotect(PROTECT_USER);
db_multi_exec("DELETE FROM user WHERE uid=%d", uid);
+ db_protect_pop();
moderation_disapprove_for_missing_users();
admin_log("Deleted user [%s] (uid %d).",
PD("login","???")/*safe-for-%s*/, uid);
cgi_redirect(cgi_referer("setup_ulist"));
return;
@@ -401,15 +403,17 @@
@ [Bummer]