Fossil SCM

Merge miscellanous auxiliary defenses and security enhancements. This check-in is not needed to fix any problems that are not already fixed in version 3.12.1. It merely provides additional defense in depth.

drh 2020-08-24 23:24 trunk merge
Commit f741baa6be99434854b3ca8f20e95230a0e964cf03b2cb7765e685cf9e85b07d
+30 -10
--- src/add.c
+++ src/add.c
@@ -156,10 +156,11 @@
156156
*/
157157
static int add_one_file(
158158
const char *zPath, /* Tree-name of file to add. */
159159
int vid /* Add to this VFILE */
160160
){
161
+ int doSkip = 0;
161162
if( !file_is_simple_pathname(zPath, 1) ){
162163
fossil_warning("filename contains illegal characters: %s", zPath);
163164
return 0;
164165
}
165166
if( db_exists("SELECT 1 FROM vfile"
@@ -168,17 +169,22 @@
168169
" WHERE pathname=%Q %s AND deleted",
169170
zPath, filename_collation());
170171
}else{
171172
char *zFullname = mprintf("%s%s", g.zLocalRoot, zPath);
172173
int isExe = file_isexe(zFullname, RepoFILE);
173
- db_multi_exec(
174
- "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink,mhash)"
175
- "VALUES(%d,0,0,0,%Q,%d,%d,NULL)",
176
- vid, zPath, isExe, file_islink(0));
174
+ if( file_nondir_objects_on_path(g.zLocalRoot, zFullname) ){
175
+ /* Do not add unsafe files to the vfile */
176
+ doSkip = 1;
177
+ }else{
178
+ db_multi_exec(
179
+ "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink,mhash)"
180
+ "VALUES(%d,0,0,0,%Q,%d,%d,NULL)",
181
+ vid, zPath, isExe, file_islink(0));
182
+ }
177183
fossil_free(zFullname);
178184
}
179
- if( db_changes() ){
185
+ if( db_changes() && !doSkip ){
180186
fossil_print("ADDED %s\n", zPath);
181187
return 1;
182188
}else{
183189
fossil_print("SKIP %s\n", zPath);
184190
return 0;
@@ -186,11 +192,13 @@
186192
}
187193
188194
/*
189195
** Add all files in the sfile temp table.
190196
**
191
-** Automatically exclude the repository file.
197
+** Automatically exclude the repository file and any other files
198
+** with reserved names. Also exclude files that are beneath an
199
+** existing symlink.
192200
*/
193201
static int add_files_in_sfile(int vid){
194202
const char *zRepo; /* Name of the repository database file */
195203
int nAdd = 0; /* Number of files added */
196204
int i; /* Loop counter */
@@ -208,18 +216,30 @@
208216
if( filenames_are_case_sensitive() ){
209217
xCmp = fossil_strcmp;
210218
}else{
211219
xCmp = fossil_stricmp;
212220
}
213
- db_prepare(&loop, "SELECT pathname FROM sfile ORDER BY pathname");
221
+ db_prepare(&loop,
222
+ "SELECT pathname FROM sfile"
223
+ " WHERE pathname NOT IN ("
224
+ "SELECT sfile.pathname FROM vfile, sfile"
225
+ " WHERE vfile.islink"
226
+ " AND NOT vfile.deleted"
227
+ " AND sfile.pathname>(vfile.pathname||'/')"
228
+ " AND sfile.pathname<(vfile.pathname||'0'))"
229
+ " ORDER BY pathname");
214230
while( db_step(&loop)==SQLITE_ROW ){
215231
const char *zToAdd = db_column_text(&loop, 0);
216232
if( fossil_strcmp(zToAdd, zRepo)==0 ) continue;
217
- for(i=0; (zReserved = fossil_reserved_name(i, 0))!=0; i++){
218
- if( xCmp(zToAdd, zReserved)==0 ) break;
233
+ if( strchr(zToAdd,'/') ){
234
+ if( file_is_reserved_name(zToAdd, -1) ) continue;
235
+ }else{
236
+ for(i=0; (zReserved = fossil_reserved_name(i, 0))!=0; i++){
237
+ if( xCmp(zToAdd, zReserved)==0 ) break;
238
+ }
239
+ if( zReserved ) continue;
219240
}
220
- if( zReserved ) continue;
221241
nAdd += add_one_file(zToAdd, vid);
222242
}
223243
db_finalize(&loop);
224244
blob_reset(&repoName);
225245
return nAdd;
226246
--- src/add.c
+++ src/add.c
@@ -156,10 +156,11 @@
156 */
157 static int add_one_file(
158 const char *zPath, /* Tree-name of file to add. */
159 int vid /* Add to this VFILE */
160 ){
 
161 if( !file_is_simple_pathname(zPath, 1) ){
162 fossil_warning("filename contains illegal characters: %s", zPath);
163 return 0;
164 }
165 if( db_exists("SELECT 1 FROM vfile"
@@ -168,17 +169,22 @@
168 " WHERE pathname=%Q %s AND deleted",
169 zPath, filename_collation());
170 }else{
171 char *zFullname = mprintf("%s%s", g.zLocalRoot, zPath);
172 int isExe = file_isexe(zFullname, RepoFILE);
173 db_multi_exec(
174 "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink,mhash)"
175 "VALUES(%d,0,0,0,%Q,%d,%d,NULL)",
176 vid, zPath, isExe, file_islink(0));
 
 
 
 
 
177 fossil_free(zFullname);
178 }
179 if( db_changes() ){
180 fossil_print("ADDED %s\n", zPath);
181 return 1;
182 }else{
183 fossil_print("SKIP %s\n", zPath);
184 return 0;
@@ -186,11 +192,13 @@
186 }
187
188 /*
189 ** Add all files in the sfile temp table.
190 **
191 ** Automatically exclude the repository file.
 
 
192 */
193 static int add_files_in_sfile(int vid){
194 const char *zRepo; /* Name of the repository database file */
195 int nAdd = 0; /* Number of files added */
196 int i; /* Loop counter */
@@ -208,18 +216,30 @@
208 if( filenames_are_case_sensitive() ){
209 xCmp = fossil_strcmp;
210 }else{
211 xCmp = fossil_stricmp;
212 }
213 db_prepare(&loop, "SELECT pathname FROM sfile ORDER BY pathname");
 
 
 
 
 
 
 
 
214 while( db_step(&loop)==SQLITE_ROW ){
215 const char *zToAdd = db_column_text(&loop, 0);
216 if( fossil_strcmp(zToAdd, zRepo)==0 ) continue;
217 for(i=0; (zReserved = fossil_reserved_name(i, 0))!=0; i++){
218 if( xCmp(zToAdd, zReserved)==0 ) break;
 
 
 
 
 
219 }
220 if( zReserved ) continue;
221 nAdd += add_one_file(zToAdd, vid);
222 }
223 db_finalize(&loop);
224 blob_reset(&repoName);
225 return nAdd;
226
--- src/add.c
+++ src/add.c
@@ -156,10 +156,11 @@
156 */
157 static int add_one_file(
158 const char *zPath, /* Tree-name of file to add. */
159 int vid /* Add to this VFILE */
160 ){
161 int doSkip = 0;
162 if( !file_is_simple_pathname(zPath, 1) ){
163 fossil_warning("filename contains illegal characters: %s", zPath);
164 return 0;
165 }
166 if( db_exists("SELECT 1 FROM vfile"
@@ -168,17 +169,22 @@
169 " WHERE pathname=%Q %s AND deleted",
170 zPath, filename_collation());
171 }else{
172 char *zFullname = mprintf("%s%s", g.zLocalRoot, zPath);
173 int isExe = file_isexe(zFullname, RepoFILE);
174 if( file_nondir_objects_on_path(g.zLocalRoot, zFullname) ){
175 /* Do not add unsafe files to the vfile */
176 doSkip = 1;
177 }else{
178 db_multi_exec(
179 "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink,mhash)"
180 "VALUES(%d,0,0,0,%Q,%d,%d,NULL)",
181 vid, zPath, isExe, file_islink(0));
182 }
183 fossil_free(zFullname);
184 }
185 if( db_changes() && !doSkip ){
186 fossil_print("ADDED %s\n", zPath);
187 return 1;
188 }else{
189 fossil_print("SKIP %s\n", zPath);
190 return 0;
@@ -186,11 +192,13 @@
192 }
193
194 /*
195 ** Add all files in the sfile temp table.
196 **
197 ** Automatically exclude the repository file and any other files
198 ** with reserved names. Also exclude files that are beneath an
199 ** existing symlink.
200 */
201 static int add_files_in_sfile(int vid){
202 const char *zRepo; /* Name of the repository database file */
203 int nAdd = 0; /* Number of files added */
204 int i; /* Loop counter */
@@ -208,18 +216,30 @@
216 if( filenames_are_case_sensitive() ){
217 xCmp = fossil_strcmp;
218 }else{
219 xCmp = fossil_stricmp;
220 }
221 db_prepare(&loop,
222 "SELECT pathname FROM sfile"
223 " WHERE pathname NOT IN ("
224 "SELECT sfile.pathname FROM vfile, sfile"
225 " WHERE vfile.islink"
226 " AND NOT vfile.deleted"
227 " AND sfile.pathname>(vfile.pathname||'/')"
228 " AND sfile.pathname<(vfile.pathname||'0'))"
229 " ORDER BY pathname");
230 while( db_step(&loop)==SQLITE_ROW ){
231 const char *zToAdd = db_column_text(&loop, 0);
232 if( fossil_strcmp(zToAdd, zRepo)==0 ) continue;
233 if( strchr(zToAdd,'/') ){
234 if( file_is_reserved_name(zToAdd, -1) ) continue;
235 }else{
236 for(i=0; (zReserved = fossil_reserved_name(i, 0))!=0; i++){
237 if( xCmp(zToAdd, zReserved)==0 ) break;
238 }
239 if( zReserved ) continue;
240 }
 
241 nAdd += add_one_file(zToAdd, vid);
242 }
243 db_finalize(&loop);
244 blob_reset(&repoName);
245 return nAdd;
246
+7 -5
--- src/alerts.c
+++ src/alerts.c
@@ -936,11 +936,11 @@
936936
** This is a short name used to identifies the repository in the Subject:
937937
** line of email alerts. Traditionally this name is included in square
938938
** brackets. Examples: "[fossil-src]", "[sqlite-src]".
939939
*/
940940
/*
941
-** SETTING: email-send-method width=5 default=off
941
+** SETTING: email-send-method width=5 default=off sensitive
942942
** Determine the method used to send email. Allowed values are
943943
** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value
944944
** means no email is ever sent. The "relay" value means emails are sent
945945
** to an Mail Sending Agent using SMTP located at email-send-relayhost.
946946
** The "pipe" value means email messages are piped into a command
@@ -949,33 +949,33 @@
949949
** by the email-send-dir setting. The "db" value means that emails
950950
** are added to an SQLite database named by the* email-send-db setting.
951951
** The "stdout" value writes email text to standard output, for debugging.
952952
*/
953953
/*
954
-** SETTING: email-send-command width=40
954
+** SETTING: email-send-command width=40 sensitive
955955
** This is a command to which outbound email content is piped when the
956956
** email-send-method is set to "pipe". The command must extract
957957
** recipient, sender, subject, and all other relevant information
958958
** from the email header.
959959
*/
960960
/*
961
-** SETTING: email-send-dir width=40
961
+** SETTING: email-send-dir width=40 sensitive
962962
** This is a directory into which outbound emails are written as individual
963963
** files if the email-send-method is set to "dir".
964964
*/
965965
/*
966
-** SETTING: email-send-db width=40
966
+** SETTING: email-send-db width=40 sensitive
967967
** This is an SQLite database file into which outbound emails are written
968968
** if the email-send-method is set to "db".
969969
*/
970970
/*
971971
** SETTING: email-self width=40
972972
** This is the email address for the repository. Outbound emails add
973973
** this email address as the "From:" field.
974974
*/
975975
/*
976
-** SETTING: email-send-relayhost width=40
976
+** SETTING: email-send-relayhost width=40 sensitive
977977
** This is the hostname and TCP port to which output email messages
978978
** are sent when email-send-method is "relay". There should be an
979979
** SMTP server configured as a Mail Submission Agent listening on the
980980
** designated host and port and all times.
981981
*/
@@ -1769,18 +1769,20 @@
17691769
"UPDATE subscriber SET sverified=1"
17701770
" WHERE subscriberCode=hextoblob(%Q)",
17711771
zName);
17721772
if( db_get_boolean("selfreg-verify",0) ){
17731773
char *zNewCap = db_get("default-perms","u");
1774
+ db_unprotect(PROTECT_USER);
17741775
db_multi_exec(
17751776
"UPDATE user"
17761777
" SET cap=%Q"
17771778
" WHERE cap='7' AND login=("
17781779
" SELECT suname FROM subscriber"
17791780
" WHERE subscriberCode=hextoblob(%Q))",
17801781
zNewCap, zName
17811782
);
1783
+ db_protect_pop();
17821784
login_set_capabilities(zNewCap, 0);
17831785
}
17841786
@ <h1>Your email alert subscription has been verified!</h1>
17851787
@ <p>Use the form below to update your subscription information.</p>
17861788
@ <p>Hint: Bookmark this page so that you can more easily update
17871789
--- src/alerts.c
+++ src/alerts.c
@@ -936,11 +936,11 @@
936 ** This is a short name used to identifies the repository in the Subject:
937 ** line of email alerts. Traditionally this name is included in square
938 ** brackets. Examples: "[fossil-src]", "[sqlite-src]".
939 */
940 /*
941 ** SETTING: email-send-method width=5 default=off
942 ** Determine the method used to send email. Allowed values are
943 ** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value
944 ** means no email is ever sent. The "relay" value means emails are sent
945 ** to an Mail Sending Agent using SMTP located at email-send-relayhost.
946 ** The "pipe" value means email messages are piped into a command
@@ -949,33 +949,33 @@
949 ** by the email-send-dir setting. The "db" value means that emails
950 ** are added to an SQLite database named by the* email-send-db setting.
951 ** The "stdout" value writes email text to standard output, for debugging.
952 */
953 /*
954 ** SETTING: email-send-command width=40
955 ** This is a command to which outbound email content is piped when the
956 ** email-send-method is set to "pipe". The command must extract
957 ** recipient, sender, subject, and all other relevant information
958 ** from the email header.
959 */
960 /*
961 ** SETTING: email-send-dir width=40
962 ** This is a directory into which outbound emails are written as individual
963 ** files if the email-send-method is set to "dir".
964 */
965 /*
966 ** SETTING: email-send-db width=40
967 ** This is an SQLite database file into which outbound emails are written
968 ** if the email-send-method is set to "db".
969 */
970 /*
971 ** SETTING: email-self width=40
972 ** This is the email address for the repository. Outbound emails add
973 ** this email address as the "From:" field.
974 */
975 /*
976 ** SETTING: email-send-relayhost width=40
977 ** This is the hostname and TCP port to which output email messages
978 ** are sent when email-send-method is "relay". There should be an
979 ** SMTP server configured as a Mail Submission Agent listening on the
980 ** designated host and port and all times.
981 */
@@ -1769,18 +1769,20 @@
1769 "UPDATE subscriber SET sverified=1"
1770 " WHERE subscriberCode=hextoblob(%Q)",
1771 zName);
1772 if( db_get_boolean("selfreg-verify",0) ){
1773 char *zNewCap = db_get("default-perms","u");
 
1774 db_multi_exec(
1775 "UPDATE user"
1776 " SET cap=%Q"
1777 " WHERE cap='7' AND login=("
1778 " SELECT suname FROM subscriber"
1779 " WHERE subscriberCode=hextoblob(%Q))",
1780 zNewCap, zName
1781 );
 
1782 login_set_capabilities(zNewCap, 0);
1783 }
1784 @ <h1>Your email alert subscription has been verified!</h1>
1785 @ <p>Use the form below to update your subscription information.</p>
1786 @ <p>Hint: Bookmark this page so that you can more easily update
1787
--- src/alerts.c
+++ src/alerts.c
@@ -936,11 +936,11 @@
936 ** This is a short name used to identifies the repository in the Subject:
937 ** line of email alerts. Traditionally this name is included in square
938 ** brackets. Examples: "[fossil-src]", "[sqlite-src]".
939 */
940 /*
941 ** SETTING: email-send-method width=5 default=off sensitive
942 ** Determine the method used to send email. Allowed values are
943 ** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value
944 ** means no email is ever sent. The "relay" value means emails are sent
945 ** to an Mail Sending Agent using SMTP located at email-send-relayhost.
946 ** The "pipe" value means email messages are piped into a command
@@ -949,33 +949,33 @@
949 ** by the email-send-dir setting. The "db" value means that emails
950 ** are added to an SQLite database named by the* email-send-db setting.
951 ** The "stdout" value writes email text to standard output, for debugging.
952 */
953 /*
954 ** SETTING: email-send-command width=40 sensitive
955 ** This is a command to which outbound email content is piped when the
956 ** email-send-method is set to "pipe". The command must extract
957 ** recipient, sender, subject, and all other relevant information
958 ** from the email header.
959 */
960 /*
961 ** SETTING: email-send-dir width=40 sensitive
962 ** This is a directory into which outbound emails are written as individual
963 ** files if the email-send-method is set to "dir".
964 */
965 /*
966 ** SETTING: email-send-db width=40 sensitive
967 ** This is an SQLite database file into which outbound emails are written
968 ** if the email-send-method is set to "db".
969 */
970 /*
971 ** SETTING: email-self width=40
972 ** This is the email address for the repository. Outbound emails add
973 ** this email address as the "From:" field.
974 */
975 /*
976 ** SETTING: email-send-relayhost width=40 sensitive
977 ** This is the hostname and TCP port to which output email messages
978 ** are sent when email-send-method is "relay". There should be an
979 ** SMTP server configured as a Mail Submission Agent listening on the
980 ** designated host and port and all times.
981 */
@@ -1769,18 +1769,20 @@
1769 "UPDATE subscriber SET sverified=1"
1770 " WHERE subscriberCode=hextoblob(%Q)",
1771 zName);
1772 if( db_get_boolean("selfreg-verify",0) ){
1773 char *zNewCap = db_get("default-perms","u");
1774 db_unprotect(PROTECT_USER);
1775 db_multi_exec(
1776 "UPDATE user"
1777 " SET cap=%Q"
1778 " WHERE cap='7' AND login=("
1779 " SELECT suname FROM subscriber"
1780 " WHERE subscriberCode=hextoblob(%Q))",
1781 zNewCap, zName
1782 );
1783 db_protect_pop();
1784 login_set_capabilities(zNewCap, 0);
1785 }
1786 @ <h1>Your email alert subscription has been verified!</h1>
1787 @ <p>Use the form below to update your subscription information.</p>
1788 @ <p>Hint: Bookmark this page so that you can more easily update
1789
--- src/allrepo.c
+++ src/allrepo.c
@@ -299,11 +299,13 @@
299299
useCheckouts?"ckout":"repo", blob_str(&fn)
300300
);
301301
if( dryRunFlag ){
302302
fossil_print("%s\n", blob_sql_text(&sql));
303303
}else{
304
+ db_unprotect(PROTECT_CONFIG);
304305
db_multi_exec("%s", blob_sql_text(&sql));
306
+ db_protect_pop();
305307
}
306308
}
307309
db_end_transaction(0);
308310
blob_reset(&sql);
309311
blob_reset(&fn);
@@ -334,11 +336,13 @@
334336
"VALUES('repo:%q',1)", z
335337
);
336338
if( dryRunFlag ){
337339
fossil_print("%s\n", blob_sql_text(&sql));
338340
}else{
341
+ db_unprotect(PROTECT_CONFIG);
339342
db_multi_exec("%s", blob_sql_text(&sql));
343
+ db_protect_pop();
340344
}
341345
}
342346
db_end_transaction(0);
343347
blob_reset(&sql);
344348
blob_reset(&fn);
@@ -428,9 +432,11 @@
428432
if( nToDel>0 ){
429433
const char *zSql = "DELETE FROM global_config WHERE name IN toDel";
430434
if( dryRunFlag ){
431435
fossil_print("%s\n", zSql);
432436
}else{
437
+ db_unprotect(PROTECT_CONFIG);
433438
db_multi_exec("%s", zSql /*safe-for-%s*/ );
439
+ db_protect_pop();
434440
}
435441
}
436442
}
437443
--- src/allrepo.c
+++ src/allrepo.c
@@ -299,11 +299,13 @@
299 useCheckouts?"ckout":"repo", blob_str(&fn)
300 );
301 if( dryRunFlag ){
302 fossil_print("%s\n", blob_sql_text(&sql));
303 }else{
 
304 db_multi_exec("%s", blob_sql_text(&sql));
 
305 }
306 }
307 db_end_transaction(0);
308 blob_reset(&sql);
309 blob_reset(&fn);
@@ -334,11 +336,13 @@
334 "VALUES('repo:%q',1)", z
335 );
336 if( dryRunFlag ){
337 fossil_print("%s\n", blob_sql_text(&sql));
338 }else{
 
339 db_multi_exec("%s", blob_sql_text(&sql));
 
340 }
341 }
342 db_end_transaction(0);
343 blob_reset(&sql);
344 blob_reset(&fn);
@@ -428,9 +432,11 @@
428 if( nToDel>0 ){
429 const char *zSql = "DELETE FROM global_config WHERE name IN toDel";
430 if( dryRunFlag ){
431 fossil_print("%s\n", zSql);
432 }else{
 
433 db_multi_exec("%s", zSql /*safe-for-%s*/ );
 
434 }
435 }
436 }
437
--- src/allrepo.c
+++ src/allrepo.c
@@ -299,11 +299,13 @@
299 useCheckouts?"ckout":"repo", blob_str(&fn)
300 );
301 if( dryRunFlag ){
302 fossil_print("%s\n", blob_sql_text(&sql));
303 }else{
304 db_unprotect(PROTECT_CONFIG);
305 db_multi_exec("%s", blob_sql_text(&sql));
306 db_protect_pop();
307 }
308 }
309 db_end_transaction(0);
310 blob_reset(&sql);
311 blob_reset(&fn);
@@ -334,11 +336,13 @@
336 "VALUES('repo:%q',1)", z
337 );
338 if( dryRunFlag ){
339 fossil_print("%s\n", blob_sql_text(&sql));
340 }else{
341 db_unprotect(PROTECT_CONFIG);
342 db_multi_exec("%s", blob_sql_text(&sql));
343 db_protect_pop();
344 }
345 }
346 db_end_transaction(0);
347 blob_reset(&sql);
348 blob_reset(&fn);
@@ -428,9 +432,11 @@
432 if( nToDel>0 ){
433 const char *zSql = "DELETE FROM global_config WHERE name IN toDel";
434 if( dryRunFlag ){
435 fossil_print("%s\n", zSql);
436 }else{
437 db_unprotect(PROTECT_CONFIG);
438 db_multi_exec("%s", zSql /*safe-for-%s*/ );
439 db_protect_pop();
440 }
441 }
442 }
443
--- src/backoffice.c
+++ src/backoffice.c
@@ -241,10 +241,11 @@
241241
** process (1) no longer exists and the current time exceeds (2).
242242
*/
243243
static void backofficeReadLease(Lease *pLease){
244244
Stmt q;
245245
memset(pLease, 0, sizeof(*pLease));
246
+ db_unprotect(PROTECT_CONFIG);
246247
db_prepare(&q, "SELECT value FROM repository.config"
247248
" WHERE name='backoffice'");
248249
if( db_step(&q)==SQLITE_ROW ){
249250
const char *z = db_column_text(&q,0);
250251
z = backofficeParseInt(z, &pLease->idCurrent);
@@ -251,10 +252,11 @@
251252
z = backofficeParseInt(z, &pLease->tmCurrent);
252253
z = backofficeParseInt(z, &pLease->idNext);
253254
backofficeParseInt(z, &pLease->tmNext);
254255
}
255256
db_finalize(&q);
257
+ db_protect_pop();
256258
}
257259
258260
/*
259261
** Return a string that describes how long it has been since the
260262
** last backoffice run. The string is obtained from fossil_malloc().
@@ -277,15 +279,17 @@
277279
278280
/*
279281
** Write a lease to the backoffice property
280282
*/
281283
static void backofficeWriteLease(Lease *pLease){
284
+ db_unprotect(PROTECT_CONFIG);
282285
db_multi_exec(
283286
"REPLACE INTO repository.config(name,value,mtime)"
284287
" VALUES('backoffice','%lld %lld %lld %lld',now())",
285288
pLease->idCurrent, pLease->tmCurrent,
286289
pLease->idNext, pLease->tmNext);
290
+ db_protect_pop();
287291
}
288292
289293
/*
290294
** Check to see if the specified Win32 process is still alive. It
291295
** should be noted that even if this function returns non-zero, the
292296
--- src/backoffice.c
+++ src/backoffice.c
@@ -241,10 +241,11 @@
241 ** process (1) no longer exists and the current time exceeds (2).
242 */
243 static void backofficeReadLease(Lease *pLease){
244 Stmt q;
245 memset(pLease, 0, sizeof(*pLease));
 
246 db_prepare(&q, "SELECT value FROM repository.config"
247 " WHERE name='backoffice'");
248 if( db_step(&q)==SQLITE_ROW ){
249 const char *z = db_column_text(&q,0);
250 z = backofficeParseInt(z, &pLease->idCurrent);
@@ -251,10 +252,11 @@
251 z = backofficeParseInt(z, &pLease->tmCurrent);
252 z = backofficeParseInt(z, &pLease->idNext);
253 backofficeParseInt(z, &pLease->tmNext);
254 }
255 db_finalize(&q);
 
256 }
257
258 /*
259 ** Return a string that describes how long it has been since the
260 ** last backoffice run. The string is obtained from fossil_malloc().
@@ -277,15 +279,17 @@
277
278 /*
279 ** Write a lease to the backoffice property
280 */
281 static void backofficeWriteLease(Lease *pLease){
 
282 db_multi_exec(
283 "REPLACE INTO repository.config(name,value,mtime)"
284 " VALUES('backoffice','%lld %lld %lld %lld',now())",
285 pLease->idCurrent, pLease->tmCurrent,
286 pLease->idNext, pLease->tmNext);
 
287 }
288
289 /*
290 ** Check to see if the specified Win32 process is still alive. It
291 ** should be noted that even if this function returns non-zero, the
292
--- src/backoffice.c
+++ src/backoffice.c
@@ -241,10 +241,11 @@
241 ** process (1) no longer exists and the current time exceeds (2).
242 */
243 static void backofficeReadLease(Lease *pLease){
244 Stmt q;
245 memset(pLease, 0, sizeof(*pLease));
246 db_unprotect(PROTECT_CONFIG);
247 db_prepare(&q, "SELECT value FROM repository.config"
248 " WHERE name='backoffice'");
249 if( db_step(&q)==SQLITE_ROW ){
250 const char *z = db_column_text(&q,0);
251 z = backofficeParseInt(z, &pLease->idCurrent);
@@ -251,10 +252,11 @@
252 z = backofficeParseInt(z, &pLease->tmCurrent);
253 z = backofficeParseInt(z, &pLease->idNext);
254 backofficeParseInt(z, &pLease->tmNext);
255 }
256 db_finalize(&q);
257 db_protect_pop();
258 }
259
260 /*
261 ** Return a string that describes how long it has been since the
262 ** last backoffice run. The string is obtained from fossil_malloc().
@@ -277,15 +279,17 @@
279
280 /*
281 ** Write a lease to the backoffice property
282 */
283 static void backofficeWriteLease(Lease *pLease){
284 db_unprotect(PROTECT_CONFIG);
285 db_multi_exec(
286 "REPLACE INTO repository.config(name,value,mtime)"
287 " VALUES('backoffice','%lld %lld %lld %lld',now())",
288 pLease->idCurrent, pLease->tmCurrent,
289 pLease->idNext, pLease->tmNext);
290 db_protect_pop();
291 }
292
293 /*
294 ** Check to see if the specified Win32 process is still alive. It
295 ** should be noted that even if this function returns non-zero, the
296
--- src/captcha.c
+++ src/captcha.c
@@ -458,14 +458,16 @@
458458
Blob b;
459459
static char zRes[20];
460460
461461
zSecret = db_get("captcha-secret", 0);
462462
if( zSecret==0 ){
463
+ db_unprotect(PROTECT_CONFIG);
463464
db_multi_exec(
464465
"REPLACE INTO config(name,value)"
465466
" VALUES('captcha-secret', lower(hex(randomblob(20))));"
466467
);
468
+ db_protect_pop();
467469
zSecret = db_get("captcha-secret", 0);
468470
assert( zSecret!=0 );
469471
}
470472
blob_init(&b, 0, 0);
471473
blob_appendf(&b, "%s-%x", zSecret, seed);
472474
--- src/captcha.c
+++ src/captcha.c
@@ -458,14 +458,16 @@
458 Blob b;
459 static char zRes[20];
460
461 zSecret = db_get("captcha-secret", 0);
462 if( zSecret==0 ){
 
463 db_multi_exec(
464 "REPLACE INTO config(name,value)"
465 " VALUES('captcha-secret', lower(hex(randomblob(20))));"
466 );
 
467 zSecret = db_get("captcha-secret", 0);
468 assert( zSecret!=0 );
469 }
470 blob_init(&b, 0, 0);
471 blob_appendf(&b, "%s-%x", zSecret, seed);
472
--- src/captcha.c
+++ src/captcha.c
@@ -458,14 +458,16 @@
458 Blob b;
459 static char zRes[20];
460
461 zSecret = db_get("captcha-secret", 0);
462 if( zSecret==0 ){
463 db_unprotect(PROTECT_CONFIG);
464 db_multi_exec(
465 "REPLACE INTO config(name,value)"
466 " VALUES('captcha-secret', lower(hex(randomblob(20))));"
467 );
468 db_protect_pop();
469 zSecret = db_get("captcha-secret", 0);
470 assert( zSecret!=0 );
471 }
472 blob_init(&b, 0, 0);
473 blob_appendf(&b, "%s-%x", zSecret, seed);
474
+6 -8
--- src/checkin.c
+++ src/checkin.c
@@ -62,10 +62,13 @@
6262
** Create a TEMP table named SFILE and add all unmanaged files named on
6363
** the command-line to that table. If directories are named, then add
6464
** all unmanaged files contained underneath those directories. If there
6565
** are no files or directories named on the command-line, then add all
6666
** unmanaged files anywhere in the checkout.
67
+**
68
+** This routine never follows symlinks. It always treats symlinks as
69
+** object unto themselves.
6770
*/
6871
static void locate_unmanaged_files(
6972
int argc, /* Number of command-line arguments to examine */
7073
char **argv, /* values of command-line arguments */
7174
unsigned scanFlags, /* Zero or more SCAN_xxx flags */
@@ -80,19 +83,19 @@
8083
db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s,"
8184
" mtime INTEGER, size INTEGER)", filename_collation());
8285
nRoot = (int)strlen(g.zLocalRoot);
8386
if( argc==0 ){
8487
blob_init(&name, g.zLocalRoot, nRoot - 1);
85
- vfile_scan(&name, blob_size(&name), scanFlags, pIgnore, 0, RepoFILE);
88
+ vfile_scan(&name, blob_size(&name), scanFlags, pIgnore, 0, SymFILE);
8689
blob_reset(&name);
8790
}else{
8891
for(i=0; i<argc; i++){
8992
file_canonical_name(argv[i], &name, 0);
9093
zName = blob_str(&name);
91
- isDir = file_isdir(zName, RepoFILE);
94
+ isDir = file_isdir(zName, SymFILE);
9295
if( isDir==1 ){
93
- vfile_scan(&name, nRoot-1, scanFlags, pIgnore, 0, RepoFILE);
96
+ vfile_scan(&name, nRoot-1, scanFlags, pIgnore, 0, SymFILE);
9497
}else if( isDir==0 ){
9598
fossil_warning("not found: %s", &zName[nRoot]);
9699
}else if( file_access(zName, R_OK) ){
97100
fossil_fatal("cannot open %s", &zName[nRoot]);
98101
}else{
@@ -856,12 +859,10 @@
856859
857860
if( zIgnoreFlag==0 ){
858861
zIgnoreFlag = db_get("ignore-glob", 0);
859862
}
860863
pIgnore = glob_create(zIgnoreFlag);
861
- /* Always consider symlinks. */
862
- g.allowSymlinks = db_allow_symlinks_by_default();
863864
locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore);
864865
glob_free(pIgnore);
865866
866867
blob_zero(&report);
867868
status_report(&report, flags);
@@ -1015,12 +1016,10 @@
10151016
verify_all_options();
10161017
pIgnore = glob_create(zIgnoreFlag);
10171018
pKeep = glob_create(zKeepFlag);
10181019
pClean = glob_create(zCleanFlag);
10191020
nRoot = (int)strlen(g.zLocalRoot);
1020
- /* Always consider symlinks. */
1021
- g.allowSymlinks = db_allow_symlinks_by_default();
10221021
if( !dirsOnlyFlag ){
10231022
Stmt q;
10241023
Blob repo;
10251024
if( !dryRunFlag && !disableUndo ) undo_begin();
10261025
locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore);
@@ -2734,11 +2733,10 @@
27342733
/* Commit */
27352734
db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'");
27362735
db_multi_exec("PRAGMA repository.application_id=252006673;");
27372736
db_multi_exec("PRAGMA localdb.application_id=252006674;");
27382737
if( dryRunFlag ){
2739
- leaf_ambiguity_warning(nvid,nvid);
27402738
db_end_transaction(1);
27412739
exit(1);
27422740
}
27432741
db_end_transaction(0);
27442742
27452743
--- src/checkin.c
+++ src/checkin.c
@@ -62,10 +62,13 @@
62 ** Create a TEMP table named SFILE and add all unmanaged files named on
63 ** the command-line to that table. If directories are named, then add
64 ** all unmanaged files contained underneath those directories. If there
65 ** are no files or directories named on the command-line, then add all
66 ** unmanaged files anywhere in the checkout.
 
 
 
67 */
68 static void locate_unmanaged_files(
69 int argc, /* Number of command-line arguments to examine */
70 char **argv, /* values of command-line arguments */
71 unsigned scanFlags, /* Zero or more SCAN_xxx flags */
@@ -80,19 +83,19 @@
80 db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s,"
81 " mtime INTEGER, size INTEGER)", filename_collation());
82 nRoot = (int)strlen(g.zLocalRoot);
83 if( argc==0 ){
84 blob_init(&name, g.zLocalRoot, nRoot - 1);
85 vfile_scan(&name, blob_size(&name), scanFlags, pIgnore, 0, RepoFILE);
86 blob_reset(&name);
87 }else{
88 for(i=0; i<argc; i++){
89 file_canonical_name(argv[i], &name, 0);
90 zName = blob_str(&name);
91 isDir = file_isdir(zName, RepoFILE);
92 if( isDir==1 ){
93 vfile_scan(&name, nRoot-1, scanFlags, pIgnore, 0, RepoFILE);
94 }else if( isDir==0 ){
95 fossil_warning("not found: %s", &zName[nRoot]);
96 }else if( file_access(zName, R_OK) ){
97 fossil_fatal("cannot open %s", &zName[nRoot]);
98 }else{
@@ -856,12 +859,10 @@
856
857 if( zIgnoreFlag==0 ){
858 zIgnoreFlag = db_get("ignore-glob", 0);
859 }
860 pIgnore = glob_create(zIgnoreFlag);
861 /* Always consider symlinks. */
862 g.allowSymlinks = db_allow_symlinks_by_default();
863 locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore);
864 glob_free(pIgnore);
865
866 blob_zero(&report);
867 status_report(&report, flags);
@@ -1015,12 +1016,10 @@
1015 verify_all_options();
1016 pIgnore = glob_create(zIgnoreFlag);
1017 pKeep = glob_create(zKeepFlag);
1018 pClean = glob_create(zCleanFlag);
1019 nRoot = (int)strlen(g.zLocalRoot);
1020 /* Always consider symlinks. */
1021 g.allowSymlinks = db_allow_symlinks_by_default();
1022 if( !dirsOnlyFlag ){
1023 Stmt q;
1024 Blob repo;
1025 if( !dryRunFlag && !disableUndo ) undo_begin();
1026 locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore);
@@ -2734,11 +2733,10 @@
2734 /* Commit */
2735 db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'");
2736 db_multi_exec("PRAGMA repository.application_id=252006673;");
2737 db_multi_exec("PRAGMA localdb.application_id=252006674;");
2738 if( dryRunFlag ){
2739 leaf_ambiguity_warning(nvid,nvid);
2740 db_end_transaction(1);
2741 exit(1);
2742 }
2743 db_end_transaction(0);
2744
2745
--- src/checkin.c
+++ src/checkin.c
@@ -62,10 +62,13 @@
62 ** Create a TEMP table named SFILE and add all unmanaged files named on
63 ** the command-line to that table. If directories are named, then add
64 ** all unmanaged files contained underneath those directories. If there
65 ** are no files or directories named on the command-line, then add all
66 ** unmanaged files anywhere in the checkout.
67 **
68 ** This routine never follows symlinks. It always treats symlinks as
69 ** object unto themselves.
70 */
71 static void locate_unmanaged_files(
72 int argc, /* Number of command-line arguments to examine */
73 char **argv, /* values of command-line arguments */
74 unsigned scanFlags, /* Zero or more SCAN_xxx flags */
@@ -80,19 +83,19 @@
83 db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s,"
84 " mtime INTEGER, size INTEGER)", filename_collation());
85 nRoot = (int)strlen(g.zLocalRoot);
86 if( argc==0 ){
87 blob_init(&name, g.zLocalRoot, nRoot - 1);
88 vfile_scan(&name, blob_size(&name), scanFlags, pIgnore, 0, SymFILE);
89 blob_reset(&name);
90 }else{
91 for(i=0; i<argc; i++){
92 file_canonical_name(argv[i], &name, 0);
93 zName = blob_str(&name);
94 isDir = file_isdir(zName, SymFILE);
95 if( isDir==1 ){
96 vfile_scan(&name, nRoot-1, scanFlags, pIgnore, 0, SymFILE);
97 }else if( isDir==0 ){
98 fossil_warning("not found: %s", &zName[nRoot]);
99 }else if( file_access(zName, R_OK) ){
100 fossil_fatal("cannot open %s", &zName[nRoot]);
101 }else{
@@ -856,12 +859,10 @@
859
860 if( zIgnoreFlag==0 ){
861 zIgnoreFlag = db_get("ignore-glob", 0);
862 }
863 pIgnore = glob_create(zIgnoreFlag);
 
 
864 locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore);
865 glob_free(pIgnore);
866
867 blob_zero(&report);
868 status_report(&report, flags);
@@ -1015,12 +1016,10 @@
1016 verify_all_options();
1017 pIgnore = glob_create(zIgnoreFlag);
1018 pKeep = glob_create(zKeepFlag);
1019 pClean = glob_create(zCleanFlag);
1020 nRoot = (int)strlen(g.zLocalRoot);
 
 
1021 if( !dirsOnlyFlag ){
1022 Stmt q;
1023 Blob repo;
1024 if( !dryRunFlag && !disableUndo ) undo_begin();
1025 locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore);
@@ -2734,11 +2733,10 @@
2733 /* Commit */
2734 db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'");
2735 db_multi_exec("PRAGMA repository.application_id=252006673;");
2736 db_multi_exec("PRAGMA localdb.application_id=252006674;");
2737 if( dryRunFlag ){
 
2738 db_end_transaction(1);
2739 exit(1);
2740 }
2741 db_end_transaction(0);
2742
2743
--- src/clone.c
+++ src/clone.c
@@ -202,15 +202,17 @@
202202
blob_zero(&fn);
203203
file_canonical_name(g.zSSLIdentity, &fn, 0);
204204
db_set("ssl-identity", blob_str(&fn), 0);
205205
blob_reset(&fn);
206206
}
207
+ db_unprotect(PROTECT_CONFIG);
207208
db_multi_exec(
208209
"REPLACE INTO config(name,value,mtime)"
209210
" VALUES('server-code', lower(hex(randomblob(20))), now());"
210211
"DELETE FROM config WHERE name='project-code';"
211212
);
213
+ db_protect_pop();
212214
url_enable_proxy(0);
213215
clone_ssh_db_set_options();
214216
url_get_password_if_needed();
215217
g.xlinkClusterOnly = 1;
216218
nErr = client_sync(syncFlags,CONFIGSET_ALL,0,0);
@@ -236,11 +238,13 @@
236238
fossil_print("Vacuuming the database... "); fflush(stdout);
237239
if( db_int(0, "PRAGMA page_count")>1000
238240
&& db_int(0, "PRAGMA page_size")<8192 ){
239241
db_multi_exec("PRAGMA page_size=8192;");
240242
}
243
+ db_unprotect(PROTECT_ALL);
241244
db_multi_exec("VACUUM");
245
+ db_protect_pop();
242246
fossil_print("\nproject-id: %s\n", db_get("project-code", 0));
243247
fossil_print("server-id: %s\n", db_get("server-code", 0));
244248
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
245249
fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
246250
}
247251
--- src/clone.c
+++ src/clone.c
@@ -202,15 +202,17 @@
202 blob_zero(&fn);
203 file_canonical_name(g.zSSLIdentity, &fn, 0);
204 db_set("ssl-identity", blob_str(&fn), 0);
205 blob_reset(&fn);
206 }
 
207 db_multi_exec(
208 "REPLACE INTO config(name,value,mtime)"
209 " VALUES('server-code', lower(hex(randomblob(20))), now());"
210 "DELETE FROM config WHERE name='project-code';"
211 );
 
212 url_enable_proxy(0);
213 clone_ssh_db_set_options();
214 url_get_password_if_needed();
215 g.xlinkClusterOnly = 1;
216 nErr = client_sync(syncFlags,CONFIGSET_ALL,0,0);
@@ -236,11 +238,13 @@
236 fossil_print("Vacuuming the database... "); fflush(stdout);
237 if( db_int(0, "PRAGMA page_count")>1000
238 && db_int(0, "PRAGMA page_size")<8192 ){
239 db_multi_exec("PRAGMA page_size=8192;");
240 }
 
241 db_multi_exec("VACUUM");
 
242 fossil_print("\nproject-id: %s\n", db_get("project-code", 0));
243 fossil_print("server-id: %s\n", db_get("server-code", 0));
244 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
245 fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
246 }
247
--- src/clone.c
+++ src/clone.c
@@ -202,15 +202,17 @@
202 blob_zero(&fn);
203 file_canonical_name(g.zSSLIdentity, &fn, 0);
204 db_set("ssl-identity", blob_str(&fn), 0);
205 blob_reset(&fn);
206 }
207 db_unprotect(PROTECT_CONFIG);
208 db_multi_exec(
209 "REPLACE INTO config(name,value,mtime)"
210 " VALUES('server-code', lower(hex(randomblob(20))), now());"
211 "DELETE FROM config WHERE name='project-code';"
212 );
213 db_protect_pop();
214 url_enable_proxy(0);
215 clone_ssh_db_set_options();
216 url_get_password_if_needed();
217 g.xlinkClusterOnly = 1;
218 nErr = client_sync(syncFlags,CONFIGSET_ALL,0,0);
@@ -236,11 +238,13 @@
238 fossil_print("Vacuuming the database... "); fflush(stdout);
239 if( db_int(0, "PRAGMA page_count")>1000
240 && db_int(0, "PRAGMA page_size")<8192 ){
241 db_multi_exec("PRAGMA page_size=8192;");
242 }
243 db_unprotect(PROTECT_ALL);
244 db_multi_exec("VACUUM");
245 db_protect_pop();
246 fossil_print("\nproject-id: %s\n", db_get("project-code", 0));
247 fossil_print("server-id: %s\n", db_get("server-code", 0));
248 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
249 fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
250 }
251
+8 -1
--- src/configure.c
+++ src/configure.c
@@ -145,11 +145,10 @@
145145
{ "keep-glob", CONFIGSET_PROJ },
146146
{ "crlf-glob", CONFIGSET_PROJ },
147147
{ "crnl-glob", CONFIGSET_PROJ },
148148
{ "encoding-glob", CONFIGSET_PROJ },
149149
{ "empty-dirs", CONFIGSET_PROJ },
150
- { "allow-symlinks", CONFIGSET_PROJ },
151150
{ "dotfiles", CONFIGSET_PROJ },
152151
{ "parent-project-code", CONFIGSET_PROJ },
153152
{ "parent-project-name", CONFIGSET_PROJ },
154153
{ "hash-policy", CONFIGSET_PROJ },
155154
{ "comment-format", CONFIGSET_PROJ },
@@ -446,10 +445,11 @@
446445
blob_append_sql(&sql,") VALUES(%s,%s",
447446
azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/);
448447
for(jj=2; jj<nToken; jj+=2){
449448
blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/);
450449
}
450
+ db_protect_only(PROTECT_SENSITIVE);
451451
db_multi_exec("%s)", blob_sql_text(&sql));
452452
if( db_changes()==0 ){
453453
blob_reset(&sql);
454454
blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s",
455455
&zName[1], azToken[0]/*safe-for-%s*/);
@@ -460,10 +460,11 @@
460460
blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s",
461461
aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/,
462462
azToken[0]/*safe-for-%s*/);
463463
db_multi_exec("%s", blob_sql_text(&sql));
464464
}
465
+ db_protect_pop();
465466
blob_reset(&sql);
466467
rebuildMask |= thisMask;
467468
}
468469
}
469470
@@ -861,13 +862,17 @@
861862
export_config(mask, g.argv[3], 0, zBackup);
862863
for(i=0; i<count(aConfig); i++){
863864
const char *zName = aConfig[i].zName;
864865
if( (aConfig[i].groupMask & mask)==0 ) continue;
865866
if( zName[0]!='@' ){
867
+ db_unprotect(PROTECT_CONFIG);
866868
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
869
+ db_protect_pop();
867870
}else if( fossil_strcmp(zName,"@user")==0 ){
871
+ db_unprotect(PROTECT_USER);
868872
db_multi_exec("DELETE FROM user");
873
+ db_protect_pop();
869874
db_create_default_users(0, 0);
870875
}else if( fossil_strcmp(zName,"@concealed")==0 ){
871876
db_multi_exec("DELETE FROM concealed");
872877
}else if( fossil_strcmp(zName,"@shun")==0 ){
873878
db_multi_exec("DELETE FROM shun");
@@ -1075,10 +1080,11 @@
10751080
}else if( zBlob ){
10761081
blob_read_from_file(&x, zBlob, ExtFILE);
10771082
}else{
10781083
blob_init(&x,g.argv[3],-1);
10791084
}
1085
+ db_unprotect(PROTECT_CONFIG);
10801086
db_prepare(&ins,
10811087
"REPLACE INTO config(name,value,mtime)"
10821088
"VALUES(%Q,:val,now())", zVar);
10831089
if( zBlob ){
10841090
db_bind_blob(&ins, ":val", &x);
@@ -1085,7 +1091,8 @@
10851091
}else{
10861092
db_bind_text(&ins, ":val", blob_str(&x));
10871093
}
10881094
db_step(&ins);
10891095
db_finalize(&ins);
1096
+ db_protect_pop();
10901097
blob_reset(&x);
10911098
}
10921099
--- src/configure.c
+++ src/configure.c
@@ -145,11 +145,10 @@
145 { "keep-glob", CONFIGSET_PROJ },
146 { "crlf-glob", CONFIGSET_PROJ },
147 { "crnl-glob", CONFIGSET_PROJ },
148 { "encoding-glob", CONFIGSET_PROJ },
149 { "empty-dirs", CONFIGSET_PROJ },
150 { "allow-symlinks", CONFIGSET_PROJ },
151 { "dotfiles", CONFIGSET_PROJ },
152 { "parent-project-code", CONFIGSET_PROJ },
153 { "parent-project-name", CONFIGSET_PROJ },
154 { "hash-policy", CONFIGSET_PROJ },
155 { "comment-format", CONFIGSET_PROJ },
@@ -446,10 +445,11 @@
446 blob_append_sql(&sql,") VALUES(%s,%s",
447 azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/);
448 for(jj=2; jj<nToken; jj+=2){
449 blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/);
450 }
 
451 db_multi_exec("%s)", blob_sql_text(&sql));
452 if( db_changes()==0 ){
453 blob_reset(&sql);
454 blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s",
455 &zName[1], azToken[0]/*safe-for-%s*/);
@@ -460,10 +460,11 @@
460 blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s",
461 aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/,
462 azToken[0]/*safe-for-%s*/);
463 db_multi_exec("%s", blob_sql_text(&sql));
464 }
 
465 blob_reset(&sql);
466 rebuildMask |= thisMask;
467 }
468 }
469
@@ -861,13 +862,17 @@
861 export_config(mask, g.argv[3], 0, zBackup);
862 for(i=0; i<count(aConfig); i++){
863 const char *zName = aConfig[i].zName;
864 if( (aConfig[i].groupMask & mask)==0 ) continue;
865 if( zName[0]!='@' ){
 
866 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
 
867 }else if( fossil_strcmp(zName,"@user")==0 ){
 
868 db_multi_exec("DELETE FROM user");
 
869 db_create_default_users(0, 0);
870 }else if( fossil_strcmp(zName,"@concealed")==0 ){
871 db_multi_exec("DELETE FROM concealed");
872 }else if( fossil_strcmp(zName,"@shun")==0 ){
873 db_multi_exec("DELETE FROM shun");
@@ -1075,10 +1080,11 @@
1075 }else if( zBlob ){
1076 blob_read_from_file(&x, zBlob, ExtFILE);
1077 }else{
1078 blob_init(&x,g.argv[3],-1);
1079 }
 
1080 db_prepare(&ins,
1081 "REPLACE INTO config(name,value,mtime)"
1082 "VALUES(%Q,:val,now())", zVar);
1083 if( zBlob ){
1084 db_bind_blob(&ins, ":val", &x);
@@ -1085,7 +1091,8 @@
1085 }else{
1086 db_bind_text(&ins, ":val", blob_str(&x));
1087 }
1088 db_step(&ins);
1089 db_finalize(&ins);
 
1090 blob_reset(&x);
1091 }
1092
--- src/configure.c
+++ src/configure.c
@@ -145,11 +145,10 @@
145 { "keep-glob", CONFIGSET_PROJ },
146 { "crlf-glob", CONFIGSET_PROJ },
147 { "crnl-glob", CONFIGSET_PROJ },
148 { "encoding-glob", CONFIGSET_PROJ },
149 { "empty-dirs", CONFIGSET_PROJ },
 
150 { "dotfiles", CONFIGSET_PROJ },
151 { "parent-project-code", CONFIGSET_PROJ },
152 { "parent-project-name", CONFIGSET_PROJ },
153 { "hash-policy", CONFIGSET_PROJ },
154 { "comment-format", CONFIGSET_PROJ },
@@ -446,10 +445,11 @@
445 blob_append_sql(&sql,") VALUES(%s,%s",
446 azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/);
447 for(jj=2; jj<nToken; jj+=2){
448 blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/);
449 }
450 db_protect_only(PROTECT_SENSITIVE);
451 db_multi_exec("%s)", blob_sql_text(&sql));
452 if( db_changes()==0 ){
453 blob_reset(&sql);
454 blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s",
455 &zName[1], azToken[0]/*safe-for-%s*/);
@@ -460,10 +460,11 @@
460 blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s",
461 aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/,
462 azToken[0]/*safe-for-%s*/);
463 db_multi_exec("%s", blob_sql_text(&sql));
464 }
465 db_protect_pop();
466 blob_reset(&sql);
467 rebuildMask |= thisMask;
468 }
469 }
470
@@ -861,13 +862,17 @@
862 export_config(mask, g.argv[3], 0, zBackup);
863 for(i=0; i<count(aConfig); i++){
864 const char *zName = aConfig[i].zName;
865 if( (aConfig[i].groupMask & mask)==0 ) continue;
866 if( zName[0]!='@' ){
867 db_unprotect(PROTECT_CONFIG);
868 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
869 db_protect_pop();
870 }else if( fossil_strcmp(zName,"@user")==0 ){
871 db_unprotect(PROTECT_USER);
872 db_multi_exec("DELETE FROM user");
873 db_protect_pop();
874 db_create_default_users(0, 0);
875 }else if( fossil_strcmp(zName,"@concealed")==0 ){
876 db_multi_exec("DELETE FROM concealed");
877 }else if( fossil_strcmp(zName,"@shun")==0 ){
878 db_multi_exec("DELETE FROM shun");
@@ -1075,10 +1080,11 @@
1080 }else if( zBlob ){
1081 blob_read_from_file(&x, zBlob, ExtFILE);
1082 }else{
1083 blob_init(&x,g.argv[3],-1);
1084 }
1085 db_unprotect(PROTECT_CONFIG);
1086 db_prepare(&ins,
1087 "REPLACE INTO config(name,value,mtime)"
1088 "VALUES(%Q,:val,now())", zVar);
1089 if( zBlob ){
1090 db_bind_blob(&ins, ":val", &x);
@@ -1085,7 +1091,8 @@
1091 }else{
1092 db_bind_text(&ins, ":val", blob_str(&x));
1093 }
1094 db_step(&ins);
1095 db_finalize(&ins);
1096 db_protect_pop();
1097 blob_reset(&x);
1098 }
1099
+367 -100
--- src/db.c
+++ src/db.c
@@ -69,10 +69,11 @@
6969
#endif /* INTERFACE */
7070
const struct Stmt empty_Stmt = empty_Stmt_m;
7171
7272
/*
7373
** Call this routine when a database error occurs.
74
+** This routine throws a fatal error. It does not return.
7475
*/
7576
static void db_err(const char *zFormat, ...){
7677
va_list ap;
7778
char *z;
7879
va_start(ap, zFormat);
@@ -113,10 +114,11 @@
113114
/*
114115
** All static variable that a used by only this file are gathered into
115116
** the following structure.
116117
*/
117118
static struct DbLocalData {
119
+ unsigned protectMask; /* Prevent changes to database */
118120
int nBegin; /* Nesting depth of BEGIN */
119121
int doRollback; /* True to force a rollback */
120122
int nCommitHook; /* Number of commit hooks */
121123
int wrTxn; /* Outer-most TNX is a write */
122124
Stmt *pAllStmt; /* List of all unfinalized statements */
@@ -130,11 +132,19 @@
130132
char *azBeforeCommit[5]; /* Commands to run prior to COMMIT */
131133
int nBeforeCommit; /* Number of entries in azBeforeCommit */
132134
int nPriorChanges; /* sqlite3_total_changes() at transaction start */
133135
const char *zStartFile; /* File in which transaction was started */
134136
int iStartLine; /* Line of zStartFile where transaction started */
135
-} db = {0, 0, 0, 0, 0, 0, };
137
+ int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
138
+ void *pAuthArg; /* Argument to the authorizer */
139
+ const char *zAuthName; /* Name of the authorizer */
140
+ int bProtectTriggers; /* True if protection triggers already exist */
141
+ int nProtect; /* Slots of aProtect used */
142
+ unsigned aProtect[10]; /* Saved values of protectMask */
143
+} db = {
144
+ PROTECT_USER|PROTECT_CONFIG|PROTECT_BASELINE, /* protectMask */
145
+ 0, 0, 0, 0, 0, 0, };
136146
137147
/*
138148
** Arrange for the given file to be deleted on a failure.
139149
*/
140150
void db_delete_on_failure(const char *zFilename){
@@ -238,17 +248,19 @@
238248
db.nBegin--;
239249
if( db.nBegin==0 ){
240250
int i;
241251
if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){
242252
i = 0;
253
+ db_protect_only(PROTECT_SENSITIVE);
243254
while( db.nBeforeCommit ){
244255
db.nBeforeCommit--;
245256
sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0);
246257
sqlite3_free(db.azBeforeCommit[i]);
247258
i++;
248259
}
249260
leaf_do_pending_checks();
261
+ db_protect_pop();
250262
}
251263
for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){
252264
int rc = db.aHook[i].xHook();
253265
if( rc ){
254266
db.doRollback = 1;
@@ -316,10 +328,230 @@
316328
}
317329
db.aHook[db.nCommitHook].sequence = sequence;
318330
db.aHook[db.nCommitHook].xHook = x;
319331
db.nCommitHook++;
320332
}
333
+
334
+#if INTERFACE
335
+/*
336
+** Flag bits for db_protect() and db_unprotect() indicating which parts
337
+** of the databases should be write protected or write enabled, respectively.
338
+*/
339
+#define PROTECT_USER 0x01 /* USER table */
340
+#define PROTECT_CONFIG 0x02 /* CONFIG and GLOBAL_CONFIG tables */
341
+#define PROTECT_SENSITIVE 0x04 /* Sensitive and/or global settings */
342
+#define PROTECT_READONLY 0x08 /* everything except TEMP tables */
343
+#define PROTECT_BASELINE 0x10 /* protection system is working */
344
+#define PROTECT_ALL 0x1f /* All of the above */
345
+#define PROTECT_NONE 0x00 /* Nothing. Everything is open */
346
+#endif /* INTERFACE */
347
+
348
+/*
349
+** Enable or disable database write protections.
350
+**
351
+** db_protext(X) Add protects on X
352
+** db_unprotect(X) Remove protections on X
353
+** db_protect_only(X) Remove all prior protections then set
354
+** protections to only X.
355
+**
356
+** Each of these routines pushes the previous protection mask onto
357
+** a finite-size stack. Each should be followed by a call to
358
+** db_protect_pop() to pop the stack and restore the protections that
359
+** existed prior to the call. The protection mask stack has a limited
360
+** depth, so take care not to next calls too deeply.
361
+**
362
+** About Database Write Protection
363
+** -------------------------------
364
+**
365
+** This is *not* a primary means of defending the application from
366
+** attack. Fossil should be secure even if this mechanism is disabled.
367
+** The purpose of database write protection is to provide an additional
368
+** layer of defense in case SQL injection bugs somehow slip into other
369
+** parts of the system. In other words, database write protection is
370
+** not primary defense but rather defense in depth.
371
+**
372
+** This mechanism mostly focuses on the USER table, to prevent an
373
+** attacker from giving themselves Admin privilegs, and on the
374
+** CONFIG table and specially "sensitive" settings such as
375
+** "diff-command" or "editor" that if compromised by an attacker
376
+** could lead to an RCE.
377
+**
378
+** By default, the USER and CONFIG tables are read-only. Various
379
+** subsystems that legitimately need to change those tables can
380
+** temporarily do so using:
381
+**
382
+** db_unprotect(PROTECT_xxx);
383
+** // make the legitmate changes here
384
+** db_protect_pop();
385
+**
386
+** Code that runs inside of reduced protections should be carefully
387
+** reviewed to ensure that it is harmless and not subject to SQL
388
+** injection.
389
+**
390
+** Read-only operations (such as many web pages like /timeline)
391
+** can invoke db_protect(PROTECT_ALL) to effectively make the database
392
+** read-only. TEMP tables (which are often used for these kinds of
393
+** pages) are still writable, however.
394
+**
395
+** The PROTECT_SENSITIVE protection is a subset of PROTECT_CONFIG
396
+** that blocks changes to all of the global_config table, but only
397
+** "sensitive" settings in the config table. PROTECT_SENSITIVE
398
+** relies on triggers and the protected_setting() SQL function to
399
+** prevent changes to sensitive settings.
400
+**
401
+** Additional Notes
402
+** ----------------
403
+**
404
+** Calls to routines like db_set() and db_unset() temporarily disable
405
+** the PROTECT_CONFIG protection. The assumption is that these calls
406
+** cannot be invoked by an SQL injection and are thus safe. Make sure
407
+** this is the case by always using a string literal as the name argument
408
+** to db_set() and db_unset() and friend, not a variable that might
409
+** be compromised by an attack.
410
+*/
411
+void db_protect_only(unsigned flags){
412
+ if( db.nProtect>=count(db.aProtect)-2 ){
413
+ fossil_panic("too many db_protect() calls");
414
+ }
415
+ db.aProtect[db.nProtect++] = db.protectMask;
416
+ if( (flags & PROTECT_SENSITIVE)!=0
417
+ && db.bProtectTriggers==0
418
+ && g.repositoryOpen
419
+ ){
420
+ /* Create the triggers needed to protect sensitive settings from
421
+ ** being created or modified the first time that PROTECT_SENSITIVE
422
+ ** is enabled. Deleting a sensitive setting is harmless, so there
423
+ ** is not trigger to block deletes. After being created once, the
424
+ ** triggers persist for the life of the database connection. */
425
+ db_multi_exec(
426
+ "CREATE TEMP TRIGGER protect_1 BEFORE INSERT ON config"
427
+ " WHEN protected_setting(new.name) BEGIN"
428
+ " SELECT raise(abort,'not authorized');"
429
+ "END;\n"
430
+ "CREATE TEMP TRIGGER protect_2 BEFORE UPDATE ON config"
431
+ " WHEN protected_setting(new.name) BEGIN"
432
+ " SELECT raise(abort,'not authorized');"
433
+ "END;\n"
434
+ );
435
+ db.bProtectTriggers = 1;
436
+ }
437
+ db.protectMask = flags;
438
+}
439
+void db_protect(unsigned flags){
440
+ db_protect_only(db.protectMask | flags);
441
+}
442
+void db_unprotect(unsigned flags){
443
+ if( db.nProtect>=count(db.aProtect)-2 ){
444
+ fossil_panic("too many db_unprotect() calls");
445
+ }
446
+ db.aProtect[db.nProtect++] = db.protectMask;
447
+ db.protectMask &= ~flags;
448
+}
449
+void db_protect_pop(void){
450
+ if( db.nProtect<1 ){
451
+ fossil_panic("too many db_protect_pop() calls");
452
+ }
453
+ db.protectMask = db.aProtect[--db.nProtect];
454
+}
455
+
456
+/*
457
+** Verify that the desired database write pertections are in place.
458
+** Throw a fatal error if not.
459
+*/
460
+void db_assert_protected(unsigned flags){
461
+ if( (flags & db.protectMask)!=flags ){
462
+ fossil_panic("missing database write protection bits: %02x",
463
+ flags & ~db.protectMask);
464
+ }
465
+}
466
+
467
+/*
468
+** Assert that either all protections are off (including PROTECT_BASELINE
469
+** which is usually always enabled), or the setting named in the argument
470
+** is no a sensitive setting.
471
+**
472
+** This assert() is used to verify that the db_set() and db_set_int()
473
+** interfaces do not modify a sensitive setting.
474
+*/
475
+void db_assert_protection_off_or_not_sensitive(const char *zName){
476
+ if( db.protectMask!=0 && db_setting_is_protected(zName) ){
477
+ fossil_panic("unauthorized change to protected setting \"%s\"", zName);
478
+ }
479
+}
480
+
481
+/*
482
+** Every Fossil database connection automatically registers the following
483
+** overarching authenticator callback, and leaves it registered for the
484
+** duration of the connection. This authenticator will call any
485
+** sub-authenticators that are registered using db_set_authorizer().
486
+*/
487
+int db_top_authorizer(
488
+ void *pNotUsed,
489
+ int eCode,
490
+ const char *z0,
491
+ const char *z1,
492
+ const char *z2,
493
+ const char *z3
494
+){
495
+ int rc = SQLITE_OK;
496
+ switch( eCode ){
497
+ case SQLITE_INSERT:
498
+ case SQLITE_UPDATE:
499
+ case SQLITE_DELETE: {
500
+ if( (db.protectMask & PROTECT_USER)!=0
501
+ && sqlite3_stricmp(z0,"user")==0 ){
502
+ rc = SQLITE_DENY;
503
+ }else if( (db.protectMask & PROTECT_CONFIG)!=0 &&
504
+ (sqlite3_stricmp(z0,"config")==0 ||
505
+ sqlite3_stricmp(z0,"global_config")==0) ){
506
+ rc = SQLITE_DENY;
507
+ }else if( (db.protectMask & PROTECT_SENSITIVE)!=0 &&
508
+ sqlite3_stricmp(z0,"global_config")==0 ){
509
+ rc = SQLITE_DENY;
510
+ }else if( (db.protectMask & PROTECT_READONLY)!=0
511
+ && sqlite3_stricmp(z2,"temp")!=0 ){
512
+ rc = SQLITE_DENY;
513
+ }
514
+ break;
515
+ }
516
+ case SQLITE_DROP_TEMP_TRIGGER: {
517
+ /* Do not allow the triggers that enforce PROTECT_SENSITIVE
518
+ ** to be dropped */
519
+ rc = SQLITE_DENY;
520
+ break;
521
+ }
522
+ }
523
+ if( db.xAuth && rc==SQLITE_OK ){
524
+ rc = db.xAuth(db.pAuthArg, eCode, z0, z1, z2, z3);
525
+ }
526
+ return rc;
527
+}
528
+
529
+/*
530
+** Set or unset the query authorizer callback function
531
+*/
532
+void db_set_authorizer(
533
+ int(*xAuth)(void*,int,const char*,const char*,const char*,const char*),
534
+ void *pArg,
535
+ const char *zName /* for tracing */
536
+){
537
+ if( db.xAuth ){
538
+ fossil_panic("multiple active db_set_authorizer() calls");
539
+ }
540
+ db.xAuth = xAuth;
541
+ db.pAuthArg = pArg;
542
+ db.zAuthName = zName;
543
+ if( g.fSqlTrace ) fossil_trace("-- set authorizer %s\n", zName);
544
+}
545
+void db_clear_authorizer(void){
546
+ if( db.zAuthName && g.fSqlTrace ){
547
+ fossil_trace("-- discontinue authorizer %s\n", db.zAuthName);
548
+ }
549
+ db.xAuth = 0;
550
+ db.pAuthArg = 0;
551
+ db.zAuthName = 0;
552
+}
321553
322554
#if INTERFACE
323555
/*
324556
** Possible flags to db_vprepare
325557
*/
@@ -334,21 +566,24 @@
334566
*/
335567
int db_vprepare(Stmt *pStmt, int flags, const char *zFormat, va_list ap){
336568
int rc;
337569
int prepFlags = 0;
338570
char *zSql;
571
+ const char *zExtra = 0;
339572
blob_zero(&pStmt->sql);
340573
blob_vappendf(&pStmt->sql, zFormat, ap);
341574
va_end(ap);
342575
zSql = blob_str(&pStmt->sql);
343576
db.nPrepare++;
344577
if( flags & DB_PREPARE_PERSISTENT ){
345578
prepFlags = SQLITE_PREPARE_PERSISTENT;
346579
}
347
- rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, 0);
580
+ rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);
348581
if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
349582
db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
583
+ }else if( zExtra && !fossil_all_whitespace(zExtra) ){
584
+ db_err("surplus text follows SQL: \"%s\"", zExtra);
350585
}
351586
pStmt->pNext = db.pAllStmt;
352587
pStmt->pPrev = 0;
353588
if( db.pAllStmt ) db.pAllStmt->pPrev = pStmt;
354589
db.pAllStmt = pStmt;
@@ -611,10 +846,11 @@
611846
return rc;
612847
}
613848
614849
/*
615850
** COMMAND: test-db-exec-error
851
+** Usage: %fossil test-db-exec-error
616852
**
617853
** Invoke the db_exec() interface with an erroneous SQL statement
618854
** in order to verify the error handling logic.
619855
*/
620856
void db_test_db_exec_cmd(void){
@@ -621,10 +857,27 @@
621857
Stmt err;
622858
db_find_and_open_repository(0,0);
623859
db_prepare(&err, "INSERT INTO repository.config(name) VALUES(NULL);");
624860
db_exec(&err);
625861
}
862
+
863
+/*
864
+** COMMAND: test-db-prepare
865
+** Usage: %fossil test-db-prepare ?OPTIONS? SQL
866
+**
867
+** Invoke db_prepare() on the SQL input. Report any errors encountered.
868
+** This command is used to verify error detection logic in the db_prepare()
869
+** utility routine.
870
+*/
871
+void db_test_db_prepare(void){
872
+ Stmt err;
873
+ db_find_and_open_repository(0,0);
874
+ verify_all_options();
875
+ if( g.argc!=3 ) usage("?OPTIONS? SQL");
876
+ db_prepare(&err, "%s", g.argv[2]/*safe-for-%s*/);
877
+ db_finalize(&err);
878
+}
626879
627880
/*
628881
** Print the output of one or more SQL queries on standard output.
629882
** This routine is used for debugging purposes only.
630883
*/
@@ -844,34 +1097,34 @@
8441097
void db_init_database(
8451098
const char *zFileName, /* Name of database file to create */
8461099
const char *zSchema, /* First part of schema */
8471100
... /* Additional SQL to run. Terminate with NULL. */
8481101
){
849
- sqlite3 *db;
1102
+ sqlite3 *xdb;
8501103
int rc;
8511104
const char *zSql;
8521105
va_list ap;
8531106
854
- db = db_open(zFileName ? zFileName : ":memory:");
855
- sqlite3_exec(db, "BEGIN EXCLUSIVE", 0, 0, 0);
856
- rc = sqlite3_exec(db, zSchema, 0, 0, 0);
1107
+ xdb = db_open(zFileName ? zFileName : ":memory:");
1108
+ sqlite3_exec(xdb, "BEGIN EXCLUSIVE", 0, 0, 0);
1109
+ rc = sqlite3_exec(xdb, zSchema, 0, 0, 0);
8571110
if( rc!=SQLITE_OK ){
858
- db_err("%s", sqlite3_errmsg(db));
1111
+ db_err("%s", sqlite3_errmsg(xdb));
8591112
}
8601113
va_start(ap, zSchema);
8611114
while( (zSql = va_arg(ap, const char*))!=0 ){
862
- rc = sqlite3_exec(db, zSql, 0, 0, 0);
1115
+ rc = sqlite3_exec(xdb, zSql, 0, 0, 0);
8631116
if( rc!=SQLITE_OK ){
864
- db_err("%s", sqlite3_errmsg(db));
1117
+ db_err("%s", sqlite3_errmsg(xdb));
8651118
}
8661119
}
8671120
va_end(ap);
868
- sqlite3_exec(db, "COMMIT", 0, 0, 0);
1121
+ sqlite3_exec(xdb, "COMMIT", 0, 0, 0);
8691122
if( zFileName || g.db!=0 ){
870
- sqlite3_close(db);
1123
+ sqlite3_close(xdb);
8711124
}else{
872
- g.db = db;
1125
+ g.db = xdb;
8731126
}
8741127
}
8751128
8761129
/*
8771130
** Function to return the number of seconds since 1970. This is
@@ -1060,10 +1313,37 @@
10601313
}
10611314
strcpy(zOut, zTemp = obscure((char*)zIn));
10621315
fossil_free(zTemp);
10631316
sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free);
10641317
}
1318
+
1319
+/*
1320
+** Return True if zName is a protected (a.k.a. "sensitive") setting.
1321
+*/
1322
+int db_setting_is_protected(const char *zName){
1323
+ const Setting *pSetting = zName ? db_find_setting(zName,0) : 0;
1324
+ return pSetting!=0 && pSetting->sensitive!=0;
1325
+}
1326
+
1327
+/*
1328
+** Implement the protected_setting(X) SQL function. This function returns
1329
+** true if X is the name of a protected (security-sensitive) setting and
1330
+** the db.protectSensitive flag is enabled. It returns false otherwise.
1331
+*/
1332
+LOCAL void db_protected_setting_func(
1333
+ sqlite3_context *context,
1334
+ int argc,
1335
+ sqlite3_value **argv
1336
+){
1337
+ const char *zSetting;
1338
+ if( (db.protectMask & PROTECT_SENSITIVE)==0 ){
1339
+ sqlite3_result_int(context, 0);
1340
+ return;
1341
+ }
1342
+ zSetting = (const char*)sqlite3_value_text(argv[0]);
1343
+ sqlite3_result_int(context, db_setting_is_protected(zSetting));
1344
+}
10651345
10661346
/*
10671347
** Register the SQL functions that are useful both to the internal
10681348
** representation and to the "fossil sql" command.
10691349
*/
@@ -1090,10 +1370,12 @@
10901370
alert_find_emailaddr_func, 0, 0);
10911371
sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0,
10921372
alert_display_name_func, 0, 0);
10931373
sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
10941374
db_obscure, 0, 0);
1375
+ sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
1376
+ db_protected_setting_func, 0, 0);
10951377
}
10961378
10971379
#if USE_SEE
10981380
/*
10991381
** This is a pointer to the saved database encryption key string.
@@ -1348,10 +1630,11 @@
13481630
if( g.fSqlTrace ) sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, db_sql_trace, 0);
13491631
db_add_aux_functions(db);
13501632
re_add_sql_func(db); /* The REGEXP operator */
13511633
foci_register(db); /* The "files_of_checkin" virtual table */
13521634
sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, 0, &rc);
1635
+ sqlite3_set_authorizer(db, db_top_authorizer, db);
13531636
return db;
13541637
}
13551638
13561639
13571640
/*
@@ -1791,22 +2074,10 @@
17912074
}
17922075
}
17932076
return zRepo;
17942077
}
17952078
1796
-/*
1797
-** Returns non-zero if the default value for the "allow-symlinks" setting
1798
-** is "on". When on Windows, this always returns false.
1799
-*/
1800
-int db_allow_symlinks_by_default(void){
1801
-#if defined(_WIN32)
1802
- return 0;
1803
-#else
1804
- return 1;
1805
-#endif
1806
-}
1807
-
18082079
/*
18092080
** Returns non-zero if support for symlinks is currently enabled.
18102081
*/
18112082
int db_allow_symlinks(void){
18122083
return g.allowSymlinks;
@@ -1848,13 +2119,14 @@
18482119
g.zRepositoryName = mprintf("%s", zDbName);
18492120
db_open_or_attach(g.zRepositoryName, "repository");
18502121
g.repositoryOpen = 1;
18512122
sqlite3_file_control(g.db, "repository", SQLITE_FCNTL_DATA_VERSION,
18522123
&g.iRepoDataVers);
2124
+
18532125
/* Cache "allow-symlinks" option, because we'll need it on every stat call */
1854
- g.allowSymlinks = db_get_boolean("allow-symlinks",
1855
- db_allow_symlinks_by_default());
2126
+ g.allowSymlinks = db_get_boolean("allow-symlinks",0);
2127
+
18562128
g.zAuxSchema = db_get("aux-schema","");
18572129
g.eHashPolicy = db_get_int("hash-policy",-1);
18582130
if( g.eHashPolicy<0 ){
18592131
g.eHashPolicy = hname_default_policy();
18602132
db_set_int("hash-policy", g.eHashPolicy, 0);
@@ -2089,10 +2361,11 @@
20892361
** argument is true. Ignore unfinalized statements when false.
20902362
*/
20912363
void db_close(int reportErrors){
20922364
sqlite3_stmt *pStmt;
20932365
if( g.db==0 ) return;
2366
+ sqlite3_set_authorizer(g.db, 0, 0);
20942367
if( g.fSqlStats ){
20952368
int cur, hiwtr;
20962369
sqlite3_db_status(g.db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur, &hiwtr, 0);
20972370
fprintf(stderr, "-- LOOKASIDE_USED %10d %10d\n", cur, hiwtr);
20982371
sqlite3_db_status(g.db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &cur, &hiwtr, 0);
@@ -2118,17 +2391,20 @@
21182391
fprintf(stderr, "-- prepared statements %10d\n", db.nPrepare);
21192392
}
21202393
while( db.pAllStmt ){
21212394
db_finalize(db.pAllStmt);
21222395
}
2123
- if( db.nBegin && reportErrors ){
2124
- fossil_warning("Transaction started at %s:%d never commits",
2125
- db.zStartFile, db.iStartLine);
2396
+ if( db.nBegin ){
2397
+ if( reportErrors ){
2398
+ fossil_warning("Transaction started at %s:%d never commits",
2399
+ db.zStartFile, db.iStartLine);
2400
+ }
21262401
db_end_transaction(1);
21272402
}
21282403
pStmt = 0;
2129
- g.dbIgnoreErrors++; /* Stop "database locked" warnings from PRAGMA optimize */
2404
+ sqlite3_busy_timeout(g.db, 0);
2405
+ g.dbIgnoreErrors++; /* Stop "database locked" warnings */
21302406
sqlite3_exec(g.db, "PRAGMA optimize", 0, 0, 0);
21312407
g.dbIgnoreErrors--;
21322408
db_close_config();
21332409
21342410
/* If the localdb has a lot of unused free space,
@@ -2136,11 +2412,13 @@
21362412
*/
21372413
if( db_database_slot("localdb")>=0 ){
21382414
int nFree = db_int(0, "PRAGMA localdb.freelist_count");
21392415
int nTotal = db_int(0, "PRAGMA localdb.page_count");
21402416
if( nFree>nTotal/4 ){
2417
+ db_unprotect(PROTECT_ALL);
21412418
db_multi_exec("VACUUM localdb;");
2419
+ db_protect_pop();
21422420
}
21432421
}
21442422
21452423
if( g.db ){
21462424
int rc;
@@ -2154,10 +2432,11 @@
21542432
}
21552433
g.db = 0;
21562434
}
21572435
g.repositoryOpen = 0;
21582436
g.localOpen = 0;
2437
+ db.bProtectTriggers = 0;
21592438
assert( g.dbConfig==0 );
21602439
assert( g.zConfigDbName==0 );
21612440
backoffice_run_if_needed();
21622441
}
21632442
@@ -2168,10 +2447,11 @@
21682447
if( g.db ){
21692448
int rc;
21702449
sqlite3_wal_checkpoint(g.db, 0);
21712450
rc = sqlite3_close(g.db);
21722451
if( g.fSqlTrace ) fossil_trace("-- sqlite3_close(%d)\n", rc);
2452
+ db_clear_authorizer();
21732453
}
21742454
g.db = 0;
21752455
g.repositoryOpen = 0;
21762456
g.localOpen = 0;
21772457
}
@@ -2215,10 +2495,11 @@
22152495
zUser = fossil_getenv("USERNAME");
22162496
}
22172497
if( zUser==0 ){
22182498
zUser = "root";
22192499
}
2500
+ db_unprotect(PROTECT_USER);
22202501
db_multi_exec(
22212502
"INSERT OR IGNORE INTO user(login, info) VALUES(%Q,'')", zUser
22222503
);
22232504
db_multi_exec(
22242505
"UPDATE user SET cap='s', pw=%Q"
@@ -2234,10 +2515,11 @@
22342515
" VALUES('developer','','ei','Dev');"
22352516
"INSERT OR IGNORE INTO user(login,pw,cap,info)"
22362517
" VALUES('reader','','kptw','Reader');"
22372518
);
22382519
}
2520
+ db_protect_pop();
22392521
}
22402522
22412523
/*
22422524
** Return a pointer to a string that contains the RHS of an IN operator
22432525
** that will select CONFIG table names that are in the list of control
@@ -2285,10 +2567,11 @@
22852567
){
22862568
char *zDate;
22872569
Blob hash;
22882570
Blob manifest;
22892571
2572
+ db_unprotect(PROTECT_ALL);
22902573
db_set("content-schema", CONTENT_SCHEMA, 0);
22912574
db_set("aux-schema", AUX_SCHEMA_MAX, 0);
22922575
db_set("rebuilt", get_version(), 0);
22932576
db_set("admin-log", "1", 0);
22942577
db_set("access-log", "1", 0);
@@ -2343,10 +2626,11 @@
23432626
" photo = (SELECT u2.photo FROM settingSrc.user u2"
23442627
" WHERE u2.login = user.login)"
23452628
" WHERE user.login IN ('anonymous','nobody','developer','reader');"
23462629
);
23472630
}
2631
+ db_protect_pop();
23482632
23492633
if( zInitialDate ){
23502634
int rid;
23512635
blob_zero(&manifest);
23522636
blob_appendf(&manifest, "C initial\\sempty\\scheck-in\n");
@@ -2839,10 +3123,12 @@
28393123
z = db_text(0, "SELECT strftime(%Q,%Q,'unixepoch');", zFormat, z);
28403124
}
28413125
return z;
28423126
}
28433127
void db_set(const char *zName, const char *zValue, int globalFlag){
3128
+ db_assert_protection_off_or_not_sensitive(zName);
3129
+ db_unprotect(PROTECT_CONFIG);
28443130
db_begin_transaction();
28453131
if( globalFlag ){
28463132
db_swap_connections();
28473133
db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)",
28483134
zName, zValue);
@@ -2853,13 +3139,15 @@
28533139
}
28543140
if( globalFlag && g.repositoryOpen ){
28553141
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
28563142
}
28573143
db_end_transaction(0);
3144
+ db_protect_pop();
28583145
}
28593146
void db_unset(const char *zName, int globalFlag){
28603147
db_begin_transaction();
3148
+ db_unprotect(PROTECT_CONFIG);
28613149
if( globalFlag ){
28623150
db_swap_connections();
28633151
db_multi_exec("DELETE FROM global_config WHERE name=%Q", zName);
28643152
db_swap_connections();
28653153
}else{
@@ -2866,10 +3154,11 @@
28663154
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
28673155
}
28683156
if( globalFlag && g.repositoryOpen ){
28693157
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
28703158
}
3159
+ db_protect_pop();
28713160
db_end_transaction(0);
28723161
}
28733162
int db_is_global(const char *zName){
28743163
int rc = 0;
28753164
if( g.zConfigDbName ){
@@ -2899,10 +3188,12 @@
28993188
db_swap_connections();
29003189
}
29013190
return v;
29023191
}
29033192
void db_set_int(const char *zName, int value, int globalFlag){
3193
+ db_assert_protection_off_or_not_sensitive(zName);
3194
+ db_unprotect(PROTECT_CONFIG);
29043195
if( globalFlag ){
29053196
db_swap_connections();
29063197
db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)",
29073198
zName, value);
29083199
db_swap_connections();
@@ -2911,10 +3202,11 @@
29113202
zName, value);
29123203
}
29133204
if( globalFlag && g.repositoryOpen ){
29143205
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
29153206
}
3207
+ db_protect_pop();
29163208
}
29173209
int db_get_boolean(const char *zName, int dflt){
29183210
char *zVal = db_get(zName, dflt ? "on" : "off");
29193211
if( is_truth(zVal) ){
29203212
dflt = 1;
@@ -3040,24 +3332,28 @@
30403332
}
30413333
file_canonical_name(zName, &full, 0);
30423334
(void)filename_collation(); /* Initialize before connection swap */
30433335
db_swap_connections();
30443336
zRepoSetting = mprintf("repo:%q", blob_str(&full));
3337
+
3338
+ db_unprotect(PROTECT_CONFIG);
30453339
db_multi_exec(
30463340
"DELETE FROM global_config WHERE name %s = %Q;",
30473341
filename_collation(), zRepoSetting
30483342
);
30493343
db_multi_exec(
30503344
"INSERT OR IGNORE INTO global_config(name,value)"
30513345
"VALUES(%Q,1);",
30523346
zRepoSetting
30533347
);
3348
+ db_protect_pop();
30543349
fossil_free(zRepoSetting);
30553350
if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){
30563351
Blob localRoot;
30573352
file_canonical_name(g.zLocalRoot, &localRoot, 1);
30583353
zCkoutSetting = mprintf("ckout:%q", blob_str(&localRoot));
3354
+ db_unprotect(PROTECT_CONFIG);
30593355
db_multi_exec(
30603356
"DELETE FROM global_config WHERE name %s = %Q;",
30613357
filename_collation(), zCkoutSetting
30623358
);
30633359
db_multi_exec(
@@ -3073,10 +3369,11 @@
30733369
db_optional_sql("repository",
30743370
"REPLACE INTO config(name,value,mtime)"
30753371
"VALUES(%Q,1,now());",
30763372
zCkoutSetting
30773373
);
3374
+ db_protect_pop();
30783375
fossil_free(zCkoutSetting);
30793376
blob_reset(&localRoot);
30803377
}else{
30813378
db_swap_connections();
30823379
}
@@ -3131,11 +3428,10 @@
31313428
void cmd_open(void){
31323429
int emptyFlag;
31333430
int keepFlag;
31343431
int forceMissingFlag;
31353432
int allowNested;
3136
- int allowSymlinks;
31373433
int setmtimeFlag; /* --setmtime. Set mtimes on files */
31383434
int bForce = 0; /* --force. Open even if non-empty dir */
31393435
static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 };
31403436
const char *zWorkDir; /* --workdir value */
31413437
const char *zRepo = 0; /* Name of the repository file */
@@ -3242,23 +3538,10 @@
32423538
}else if( db_exists("SELECT 1 FROM event WHERE type='ci'") ){
32433539
g.zOpenRevision = db_get("main-branch", 0);
32443540
}
32453541
}
32463542
3247
- if( g.zOpenRevision ){
3248
- /* Since the repository is open and we know the revision now,
3249
- ** refresh the allow-symlinks flag. Since neither the local
3250
- ** checkout nor the configuration database are open at this
3251
- ** point, this should always return the versioned setting,
3252
- ** if any, or the default value, which is negative one. The
3253
- ** value negative one, in this context, means that the code
3254
- ** below should fallback to using the setting value from the
3255
- ** repository or global configuration databases only. */
3256
- allowSymlinks = db_get_versioned_boolean("allow-symlinks", -1);
3257
- }else{
3258
- allowSymlinks = -1; /* Use non-versioned settings only. */
3259
- }
32603543
32613544
#if defined(_WIN32) || defined(__CYGWIN__)
32623545
# define LOCALDB_NAME "./_FOSSIL_"
32633546
#else
32643547
# define LOCALDB_NAME "./.fslckout"
@@ -3268,26 +3551,10 @@
32683551
"COMMIT; PRAGMA journal_mode=WAL; BEGIN;",
32693552
#endif
32703553
(char*)0);
32713554
db_delete_on_failure(LOCALDB_NAME);
32723555
db_open_local(0);
3273
- if( allowSymlinks>=0 ){
3274
- /* Use the value from the versioned setting, which was read
3275
- ** prior to opening the local checkout (i.e. which is most
3276
- ** likely empty and does not actually contain any versioned
3277
- ** setting files yet). Normally, this value would be given
3278
- ** first priority within db_get_boolean(); however, this is
3279
- ** a special case because we know the on-disk files may not
3280
- ** exist yet. */
3281
- g.allowSymlinks = allowSymlinks;
3282
- }else{
3283
- /* Since the local checkout may not have any files at this
3284
- ** point, this will probably be the setting value from the
3285
- ** repository or global configuration databases. */
3286
- g.allowSymlinks = db_get_boolean("allow-symlinks",
3287
- db_allow_symlinks_by_default());
3288
- }
32893556
db_lset("repository", zRepo);
32903557
db_record_repository_filename(zRepo);
32913558
db_set_checkout(0);
32923559
azNewArgv[0] = g.argv[0];
32933560
g.argv = azNewArgv;
@@ -3376,12 +3643,13 @@
33763643
const char *name; /* Name of the setting */
33773644
const char *var; /* Internal variable name used by db_set() */
33783645
int width; /* Width of display. 0 for boolean values and
33793646
** negative for values which should not appear
33803647
** on the /setup_settings page. */
3381
- int versionable; /* Is this setting versionable? */
3382
- int forceTextArea; /* Force using a text area for display? */
3648
+ char versionable; /* Is this setting versionable? */
3649
+ char forceTextArea; /* Force using a text area for display? */
3650
+ char sensitive; /* True if this a security-sensitive setting */
33833651
const char *def; /* Default value */
33843652
};
33853653
#endif /* INTERFACE */
33863654
33873655
/*
@@ -3395,32 +3663,29 @@
33953663
** SETTING: admin-log boolean default=off
33963664
**
33973665
** When the admin-log setting is enabled, configuration changes are recorded
33983666
** in the "admin_log" table of the repository.
33993667
*/
3400
-#if defined(_WIN32)
3401
-/*
3402
-** SETTING: allow-symlinks boolean default=off versionable
3403
-**
3404
-** When allow-symlinks is OFF, symbolic links in the repository are followed
3405
-** and treated no differently from real files. When allow-symlinks is ON,
3406
-** the object to which the symbolic link points is ignored, and the content
3407
-** of the symbolic link that is stored in the repository is the name of the
3408
-** object to which the symbolic link points.
3409
-*/
3410
-#endif
3411
-#if !defined(_WIN32)
3412
-/*
3413
-** SETTING: allow-symlinks boolean default=on versionable
3414
-**
3415
-** When allow-symlinks is OFF, symbolic links in the repository are followed
3416
-** and treated no differently from real files. When allow-symlinks is ON,
3417
-** the object to which the symbolic link points is ignored, and the content
3418
-** of the symbolic link that is stored in the repository is the name of the
3419
-** object to which the symbolic link points.
3420
-*/
3421
-#endif
3668
+/*
3669
+** SETTING: allow-symlinks boolean default=off sensitive
3670
+**
3671
+** When allow-symlinks is OFF, Fossil does not see symbolic links
3672
+** (a.k.a "symlinks") on disk as a separate class of object. Instead Fossil
3673
+** sees the object that the symlink points to. Fossil will only manage files
3674
+** and directories, not symlinks. When a symlink is added to a repository,
3675
+** the object that the symlink points to is added, not the symlink itself.
3676
+**
3677
+** When allow-symlinks is ON, Fossil sees symlinks on disk as a separate
3678
+** object class that is distinct from files and directories. When a symlink
3679
+** is added to a repository, Fossil stores the target filename. In other
3680
+** words, Fossil stores the symlink itself, not the object that the symlink
3681
+** points to.
3682
+**
3683
+** Symlinks are not cross-platform. They are not available on all
3684
+** operating systems and file systems. Hence the allow-symlinks setting is
3685
+** OFF by default, for portability.
3686
+*/
34223687
/*
34233688
** SETTING: auto-captcha boolean default=on variable=autocaptcha
34243689
** If enabled, the /login page provides a button that will automatically
34253690
** fill in the captcha password. This makes things easier for human users,
34263691
** at the expense of also making logins easier for malicious robots.
@@ -3470,11 +3735,11 @@
34703735
** there is no cron job periodically running "fossil backoffice",
34713736
** email notifications and other work normally done by the
34723737
** backoffice will not occur.
34733738
*/
34743739
/*
3475
-** SETTING: backoffice-logfile width=40
3740
+** SETTING: backoffice-logfile width=40 sensitive
34763741
** If backoffice-logfile is not an empty string and is a valid
34773742
** filename, then a one-line message is appended to that file
34783743
** every time the backoffice runs. This can be used for debugging,
34793744
** to ensure that backoffice is running appropriately.
34803745
*/
@@ -3547,11 +3812,11 @@
35473812
/*
35483813
** SETTING: crnl-glob width=40 versionable block-text
35493814
** This is an alias for the crlf-glob setting.
35503815
*/
35513816
/*
3552
-** SETTING: default-perms width=16 default=u
3817
+** SETTING: default-perms width=16 default=u sensitive
35533818
** Permissions given automatically to new users. For more
35543819
** information on permissions see the Users page in Server
35553820
** Administration of the HTTP UI.
35563821
*/
35573822
/*
@@ -3559,11 +3824,11 @@
35593824
** If enabled, permit files that may be binary
35603825
** or that match the "binary-glob" setting to be used with
35613826
** external diff programs. If disabled, skip these files.
35623827
*/
35633828
/*
3564
-** SETTING: diff-command width=40
3829
+** SETTING: diff-command width=40 sensitive
35653830
** The value is an external command to run when performing a diff.
35663831
** If undefined, the internal text diff will be used.
35673832
*/
35683833
/*
35693834
** SETTING: dont-push boolean default=off
@@ -3574,11 +3839,11 @@
35743839
/*
35753840
** SETTING: dotfiles boolean versionable default=off
35763841
** If enabled, include --dotfiles option for all compatible commands.
35773842
*/
35783843
/*
3579
-** SETTING: editor width=32
3844
+** SETTING: editor width=32 sensitive
35803845
** The value is an external command that will launch the
35813846
** text editor command used for check-in comments.
35823847
*/
35833848
/*
35843849
** SETTING: empty-dirs width=40 versionable block-text
@@ -3617,16 +3882,16 @@
36173882
** An empty list prohibits editing via that page. Note that
36183883
** it cannot edit binary files, so the list should not
36193884
** contain any globs for, e.g., images or PDFs.
36203885
*/
36213886
/*
3622
-** SETTING: gdiff-command width=40 default=gdiff
3887
+** SETTING: gdiff-command width=40 default=gdiff sensitive
36233888
** The value is an external command to run when performing a graphical
36243889
** diff. If undefined, text diff will be used.
36253890
*/
36263891
/*
3627
-** SETTING: gmerge-command width=40
3892
+** SETTING: gmerge-command width=40 sensitive
36283893
** The value is a graphical merge conflict resolver command operating
36293894
** on four files. Examples:
36303895
**
36313896
** kdiff3 "%baseline" "%original" "%merge" -o "%output"
36323897
** xxdiff "%original" "%baseline" "%merge" -M "%output"
@@ -3757,11 +4022,11 @@
37574022
** the associated files within the checkout -AND- the "rm"
37584023
** and "delete" commands will also remove the associated
37594024
** files from within the checkout.
37604025
*/
37614026
/*
3762
-** SETTING: pgp-command width=40
4027
+** SETTING: pgp-command width=40 sensitive
37634028
** Command used to clear-sign manifests at check-in.
37644029
** Default value is "gpg --clearsign -o"
37654030
*/
37664031
/*
37674032
** SETTING: forbid-delta-manifests boolean default=off
@@ -3817,22 +4082,22 @@
38174082
**
38184083
** If repolist-skin has a value of 2, then the repository is omitted from
38194084
** the list in use cases 1 through 4, but not for 5 and 6.
38204085
*/
38214086
/*
3822
-** SETTING: self-register boolean default=off
4087
+** SETTING: self-register boolean default=off sensitive
38234088
** Allow users to register themselves through the HTTP UI.
38244089
** This is useful if you want to see other names than
38254090
** "Anonymous" in e.g. ticketing system. On the other hand
38264091
** users can not be deleted.
38274092
*/
38284093
/*
3829
-** SETTING: ssh-command width=40
4094
+** SETTING: ssh-command width=40 sensitive
38304095
** The command used to talk to a remote machine with the "ssh://" protocol.
38314096
*/
38324097
/*
3833
-** SETTING: ssl-ca-location width=40
4098
+** SETTING: ssl-ca-location width=40 sensitive
38344099
** The full pathname to a file containing PEM encoded
38354100
** CA root certificates, or a directory of certificates
38364101
** with filenames formed from the certificate hashes as
38374102
** required by OpenSSL.
38384103
**
@@ -3842,11 +4107,11 @@
38424107
** Checking your platform behaviour is required if the
38434108
** exact contents of the CA root is critical for your
38444109
** application.
38454110
*/
38464111
/*
3847
-** SETTING: ssl-identity width=40
4112
+** SETTING: ssl-identity width=40 sensitive
38484113
** The full pathname to a file containing a certificate
38494114
** and private key in PEM format. Create by concatenating
38504115
** the certificate and private key files.
38514116
**
38524117
** This identity will be presented to SSL servers to
@@ -3853,33 +4118,33 @@
38534118
** authenticate this client, in addition to the normal
38544119
** password authentication.
38554120
*/
38564121
#ifdef FOSSIL_ENABLE_TCL
38574122
/*
3858
-** SETTING: tcl boolean default=off
4123
+** SETTING: tcl boolean default=off sensitive
38594124
** If enabled Tcl integration commands will be added to the TH1
38604125
** interpreter, allowing arbitrary Tcl expressions and
38614126
** scripts to be evaluated from TH1. Additionally, the Tcl
38624127
** interpreter will be able to evaluate arbitrary TH1
38634128
** expressions and scripts.
38644129
*/
38654130
/*
3866
-** SETTING: tcl-setup width=40 block-text
4131
+** SETTING: tcl-setup width=40 block-text sensitive
38674132
** This is the setup script to be evaluated after creating
38684133
** and initializing the Tcl interpreter. By default, this
38694134
** is empty and no extra setup is performed.
38704135
*/
38714136
#endif /* FOSSIL_ENABLE_TCL */
38724137
/*
3873
-** SETTING: tclsh width=80 default=tclsh
4138
+** SETTING: tclsh width=80 default=tclsh sensitive
38744139
** Name of the external TCL interpreter used for such things
38754140
** as running the GUI diff viewer launched by the --tk option
38764141
** of the various "diff" commands.
38774142
*/
38784143
#ifdef FOSSIL_ENABLE_TH1_DOCS
38794144
/*
3880
-** SETTING: th1-docs boolean default=off
4145
+** SETTING: th1-docs boolean default=off sensitive
38814146
** If enabled, this allows embedded documentation files to contain
38824147
** arbitrary TH1 scripts that are evaluated on the server. If native
38834148
** Tcl integration is also enabled, this setting has the
38844149
** potential to allow anybody with check-in privileges to
38854150
** do almost anything that the associated operating system
@@ -3932,11 +4197,11 @@
39324197
** of a "fossil clone" or "fossil sync" command. The
39334198
** default is false, in which case the -u option is
39344199
** needed to clone or sync unversioned files.
39354200
*/
39364201
/*
3937
-** SETTING: web-browser width=30
4202
+** SETTING: web-browser width=30 sensitive
39384203
** A shell command used to launch your preferred
39394204
** web browser when given a URL as an argument.
39404205
** Defaults to "start" on windows, "open" on Mac,
39414206
** and "firefox" on Unix.
39424207
*/
@@ -4058,11 +4323,13 @@
40584323
fossil_fatal("cannot set 'manifest' globally");
40594324
}
40604325
if( unsetFlag ){
40614326
db_unset(pSetting->name, globalFlag);
40624327
}else{
4328
+ db_protect_only(PROTECT_NONE);
40634329
db_set(pSetting->name, g.argv[3], globalFlag);
4330
+ db_protect_pop();
40644331
}
40654332
if( isManifest && g.localOpen ){
40664333
manifest_to_disk(db_lget_int("checkout", 0));
40674334
}
40684335
}else{
40694336
--- src/db.c
+++ src/db.c
@@ -69,10 +69,11 @@
69 #endif /* INTERFACE */
70 const struct Stmt empty_Stmt = empty_Stmt_m;
71
72 /*
73 ** Call this routine when a database error occurs.
 
74 */
75 static void db_err(const char *zFormat, ...){
76 va_list ap;
77 char *z;
78 va_start(ap, zFormat);
@@ -113,10 +114,11 @@
113 /*
114 ** All static variable that a used by only this file are gathered into
115 ** the following structure.
116 */
117 static struct DbLocalData {
 
118 int nBegin; /* Nesting depth of BEGIN */
119 int doRollback; /* True to force a rollback */
120 int nCommitHook; /* Number of commit hooks */
121 int wrTxn; /* Outer-most TNX is a write */
122 Stmt *pAllStmt; /* List of all unfinalized statements */
@@ -130,11 +132,19 @@
130 char *azBeforeCommit[5]; /* Commands to run prior to COMMIT */
131 int nBeforeCommit; /* Number of entries in azBeforeCommit */
132 int nPriorChanges; /* sqlite3_total_changes() at transaction start */
133 const char *zStartFile; /* File in which transaction was started */
134 int iStartLine; /* Line of zStartFile where transaction started */
135 } db = {0, 0, 0, 0, 0, 0, };
 
 
 
 
 
 
 
 
136
137 /*
138 ** Arrange for the given file to be deleted on a failure.
139 */
140 void db_delete_on_failure(const char *zFilename){
@@ -238,17 +248,19 @@
238 db.nBegin--;
239 if( db.nBegin==0 ){
240 int i;
241 if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){
242 i = 0;
 
243 while( db.nBeforeCommit ){
244 db.nBeforeCommit--;
245 sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0);
246 sqlite3_free(db.azBeforeCommit[i]);
247 i++;
248 }
249 leaf_do_pending_checks();
 
250 }
251 for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){
252 int rc = db.aHook[i].xHook();
253 if( rc ){
254 db.doRollback = 1;
@@ -316,10 +328,230 @@
316 }
317 db.aHook[db.nCommitHook].sequence = sequence;
318 db.aHook[db.nCommitHook].xHook = x;
319 db.nCommitHook++;
320 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
322 #if INTERFACE
323 /*
324 ** Possible flags to db_vprepare
325 */
@@ -334,21 +566,24 @@
334 */
335 int db_vprepare(Stmt *pStmt, int flags, const char *zFormat, va_list ap){
336 int rc;
337 int prepFlags = 0;
338 char *zSql;
 
339 blob_zero(&pStmt->sql);
340 blob_vappendf(&pStmt->sql, zFormat, ap);
341 va_end(ap);
342 zSql = blob_str(&pStmt->sql);
343 db.nPrepare++;
344 if( flags & DB_PREPARE_PERSISTENT ){
345 prepFlags = SQLITE_PREPARE_PERSISTENT;
346 }
347 rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, 0);
348 if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
349 db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
 
 
350 }
351 pStmt->pNext = db.pAllStmt;
352 pStmt->pPrev = 0;
353 if( db.pAllStmt ) db.pAllStmt->pPrev = pStmt;
354 db.pAllStmt = pStmt;
@@ -611,10 +846,11 @@
611 return rc;
612 }
613
614 /*
615 ** COMMAND: test-db-exec-error
 
616 **
617 ** Invoke the db_exec() interface with an erroneous SQL statement
618 ** in order to verify the error handling logic.
619 */
620 void db_test_db_exec_cmd(void){
@@ -621,10 +857,27 @@
621 Stmt err;
622 db_find_and_open_repository(0,0);
623 db_prepare(&err, "INSERT INTO repository.config(name) VALUES(NULL);");
624 db_exec(&err);
625 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
626
627 /*
628 ** Print the output of one or more SQL queries on standard output.
629 ** This routine is used for debugging purposes only.
630 */
@@ -844,34 +1097,34 @@
844 void db_init_database(
845 const char *zFileName, /* Name of database file to create */
846 const char *zSchema, /* First part of schema */
847 ... /* Additional SQL to run. Terminate with NULL. */
848 ){
849 sqlite3 *db;
850 int rc;
851 const char *zSql;
852 va_list ap;
853
854 db = db_open(zFileName ? zFileName : ":memory:");
855 sqlite3_exec(db, "BEGIN EXCLUSIVE", 0, 0, 0);
856 rc = sqlite3_exec(db, zSchema, 0, 0, 0);
857 if( rc!=SQLITE_OK ){
858 db_err("%s", sqlite3_errmsg(db));
859 }
860 va_start(ap, zSchema);
861 while( (zSql = va_arg(ap, const char*))!=0 ){
862 rc = sqlite3_exec(db, zSql, 0, 0, 0);
863 if( rc!=SQLITE_OK ){
864 db_err("%s", sqlite3_errmsg(db));
865 }
866 }
867 va_end(ap);
868 sqlite3_exec(db, "COMMIT", 0, 0, 0);
869 if( zFileName || g.db!=0 ){
870 sqlite3_close(db);
871 }else{
872 g.db = db;
873 }
874 }
875
876 /*
877 ** Function to return the number of seconds since 1970. This is
@@ -1060,10 +1313,37 @@
1060 }
1061 strcpy(zOut, zTemp = obscure((char*)zIn));
1062 fossil_free(zTemp);
1063 sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free);
1064 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1065
1066 /*
1067 ** Register the SQL functions that are useful both to the internal
1068 ** representation and to the "fossil sql" command.
1069 */
@@ -1090,10 +1370,12 @@
1090 alert_find_emailaddr_func, 0, 0);
1091 sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0,
1092 alert_display_name_func, 0, 0);
1093 sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
1094 db_obscure, 0, 0);
 
 
1095 }
1096
1097 #if USE_SEE
1098 /*
1099 ** This is a pointer to the saved database encryption key string.
@@ -1348,10 +1630,11 @@
1348 if( g.fSqlTrace ) sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, db_sql_trace, 0);
1349 db_add_aux_functions(db);
1350 re_add_sql_func(db); /* The REGEXP operator */
1351 foci_register(db); /* The "files_of_checkin" virtual table */
1352 sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, 0, &rc);
 
1353 return db;
1354 }
1355
1356
1357 /*
@@ -1791,22 +2074,10 @@
1791 }
1792 }
1793 return zRepo;
1794 }
1795
1796 /*
1797 ** Returns non-zero if the default value for the "allow-symlinks" setting
1798 ** is "on". When on Windows, this always returns false.
1799 */
1800 int db_allow_symlinks_by_default(void){
1801 #if defined(_WIN32)
1802 return 0;
1803 #else
1804 return 1;
1805 #endif
1806 }
1807
1808 /*
1809 ** Returns non-zero if support for symlinks is currently enabled.
1810 */
1811 int db_allow_symlinks(void){
1812 return g.allowSymlinks;
@@ -1848,13 +2119,14 @@
1848 g.zRepositoryName = mprintf("%s", zDbName);
1849 db_open_or_attach(g.zRepositoryName, "repository");
1850 g.repositoryOpen = 1;
1851 sqlite3_file_control(g.db, "repository", SQLITE_FCNTL_DATA_VERSION,
1852 &g.iRepoDataVers);
 
1853 /* Cache "allow-symlinks" option, because we'll need it on every stat call */
1854 g.allowSymlinks = db_get_boolean("allow-symlinks",
1855 db_allow_symlinks_by_default());
1856 g.zAuxSchema = db_get("aux-schema","");
1857 g.eHashPolicy = db_get_int("hash-policy",-1);
1858 if( g.eHashPolicy<0 ){
1859 g.eHashPolicy = hname_default_policy();
1860 db_set_int("hash-policy", g.eHashPolicy, 0);
@@ -2089,10 +2361,11 @@
2089 ** argument is true. Ignore unfinalized statements when false.
2090 */
2091 void db_close(int reportErrors){
2092 sqlite3_stmt *pStmt;
2093 if( g.db==0 ) return;
 
2094 if( g.fSqlStats ){
2095 int cur, hiwtr;
2096 sqlite3_db_status(g.db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur, &hiwtr, 0);
2097 fprintf(stderr, "-- LOOKASIDE_USED %10d %10d\n", cur, hiwtr);
2098 sqlite3_db_status(g.db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &cur, &hiwtr, 0);
@@ -2118,17 +2391,20 @@
2118 fprintf(stderr, "-- prepared statements %10d\n", db.nPrepare);
2119 }
2120 while( db.pAllStmt ){
2121 db_finalize(db.pAllStmt);
2122 }
2123 if( db.nBegin && reportErrors ){
2124 fossil_warning("Transaction started at %s:%d never commits",
2125 db.zStartFile, db.iStartLine);
 
 
2126 db_end_transaction(1);
2127 }
2128 pStmt = 0;
2129 g.dbIgnoreErrors++; /* Stop "database locked" warnings from PRAGMA optimize */
 
2130 sqlite3_exec(g.db, "PRAGMA optimize", 0, 0, 0);
2131 g.dbIgnoreErrors--;
2132 db_close_config();
2133
2134 /* If the localdb has a lot of unused free space,
@@ -2136,11 +2412,13 @@
2136 */
2137 if( db_database_slot("localdb")>=0 ){
2138 int nFree = db_int(0, "PRAGMA localdb.freelist_count");
2139 int nTotal = db_int(0, "PRAGMA localdb.page_count");
2140 if( nFree>nTotal/4 ){
 
2141 db_multi_exec("VACUUM localdb;");
 
2142 }
2143 }
2144
2145 if( g.db ){
2146 int rc;
@@ -2154,10 +2432,11 @@
2154 }
2155 g.db = 0;
2156 }
2157 g.repositoryOpen = 0;
2158 g.localOpen = 0;
 
2159 assert( g.dbConfig==0 );
2160 assert( g.zConfigDbName==0 );
2161 backoffice_run_if_needed();
2162 }
2163
@@ -2168,10 +2447,11 @@
2168 if( g.db ){
2169 int rc;
2170 sqlite3_wal_checkpoint(g.db, 0);
2171 rc = sqlite3_close(g.db);
2172 if( g.fSqlTrace ) fossil_trace("-- sqlite3_close(%d)\n", rc);
 
2173 }
2174 g.db = 0;
2175 g.repositoryOpen = 0;
2176 g.localOpen = 0;
2177 }
@@ -2215,10 +2495,11 @@
2215 zUser = fossil_getenv("USERNAME");
2216 }
2217 if( zUser==0 ){
2218 zUser = "root";
2219 }
 
2220 db_multi_exec(
2221 "INSERT OR IGNORE INTO user(login, info) VALUES(%Q,'')", zUser
2222 );
2223 db_multi_exec(
2224 "UPDATE user SET cap='s', pw=%Q"
@@ -2234,10 +2515,11 @@
2234 " VALUES('developer','','ei','Dev');"
2235 "INSERT OR IGNORE INTO user(login,pw,cap,info)"
2236 " VALUES('reader','','kptw','Reader');"
2237 );
2238 }
 
2239 }
2240
2241 /*
2242 ** Return a pointer to a string that contains the RHS of an IN operator
2243 ** that will select CONFIG table names that are in the list of control
@@ -2285,10 +2567,11 @@
2285 ){
2286 char *zDate;
2287 Blob hash;
2288 Blob manifest;
2289
 
2290 db_set("content-schema", CONTENT_SCHEMA, 0);
2291 db_set("aux-schema", AUX_SCHEMA_MAX, 0);
2292 db_set("rebuilt", get_version(), 0);
2293 db_set("admin-log", "1", 0);
2294 db_set("access-log", "1", 0);
@@ -2343,10 +2626,11 @@
2343 " photo = (SELECT u2.photo FROM settingSrc.user u2"
2344 " WHERE u2.login = user.login)"
2345 " WHERE user.login IN ('anonymous','nobody','developer','reader');"
2346 );
2347 }
 
2348
2349 if( zInitialDate ){
2350 int rid;
2351 blob_zero(&manifest);
2352 blob_appendf(&manifest, "C initial\\sempty\\scheck-in\n");
@@ -2839,10 +3123,12 @@
2839 z = db_text(0, "SELECT strftime(%Q,%Q,'unixepoch');", zFormat, z);
2840 }
2841 return z;
2842 }
2843 void db_set(const char *zName, const char *zValue, int globalFlag){
 
 
2844 db_begin_transaction();
2845 if( globalFlag ){
2846 db_swap_connections();
2847 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)",
2848 zName, zValue);
@@ -2853,13 +3139,15 @@
2853 }
2854 if( globalFlag && g.repositoryOpen ){
2855 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
2856 }
2857 db_end_transaction(0);
 
2858 }
2859 void db_unset(const char *zName, int globalFlag){
2860 db_begin_transaction();
 
2861 if( globalFlag ){
2862 db_swap_connections();
2863 db_multi_exec("DELETE FROM global_config WHERE name=%Q", zName);
2864 db_swap_connections();
2865 }else{
@@ -2866,10 +3154,11 @@
2866 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
2867 }
2868 if( globalFlag && g.repositoryOpen ){
2869 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
2870 }
 
2871 db_end_transaction(0);
2872 }
2873 int db_is_global(const char *zName){
2874 int rc = 0;
2875 if( g.zConfigDbName ){
@@ -2899,10 +3188,12 @@
2899 db_swap_connections();
2900 }
2901 return v;
2902 }
2903 void db_set_int(const char *zName, int value, int globalFlag){
 
 
2904 if( globalFlag ){
2905 db_swap_connections();
2906 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)",
2907 zName, value);
2908 db_swap_connections();
@@ -2911,10 +3202,11 @@
2911 zName, value);
2912 }
2913 if( globalFlag && g.repositoryOpen ){
2914 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
2915 }
 
2916 }
2917 int db_get_boolean(const char *zName, int dflt){
2918 char *zVal = db_get(zName, dflt ? "on" : "off");
2919 if( is_truth(zVal) ){
2920 dflt = 1;
@@ -3040,24 +3332,28 @@
3040 }
3041 file_canonical_name(zName, &full, 0);
3042 (void)filename_collation(); /* Initialize before connection swap */
3043 db_swap_connections();
3044 zRepoSetting = mprintf("repo:%q", blob_str(&full));
 
 
3045 db_multi_exec(
3046 "DELETE FROM global_config WHERE name %s = %Q;",
3047 filename_collation(), zRepoSetting
3048 );
3049 db_multi_exec(
3050 "INSERT OR IGNORE INTO global_config(name,value)"
3051 "VALUES(%Q,1);",
3052 zRepoSetting
3053 );
 
3054 fossil_free(zRepoSetting);
3055 if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){
3056 Blob localRoot;
3057 file_canonical_name(g.zLocalRoot, &localRoot, 1);
3058 zCkoutSetting = mprintf("ckout:%q", blob_str(&localRoot));
 
3059 db_multi_exec(
3060 "DELETE FROM global_config WHERE name %s = %Q;",
3061 filename_collation(), zCkoutSetting
3062 );
3063 db_multi_exec(
@@ -3073,10 +3369,11 @@
3073 db_optional_sql("repository",
3074 "REPLACE INTO config(name,value,mtime)"
3075 "VALUES(%Q,1,now());",
3076 zCkoutSetting
3077 );
 
3078 fossil_free(zCkoutSetting);
3079 blob_reset(&localRoot);
3080 }else{
3081 db_swap_connections();
3082 }
@@ -3131,11 +3428,10 @@
3131 void cmd_open(void){
3132 int emptyFlag;
3133 int keepFlag;
3134 int forceMissingFlag;
3135 int allowNested;
3136 int allowSymlinks;
3137 int setmtimeFlag; /* --setmtime. Set mtimes on files */
3138 int bForce = 0; /* --force. Open even if non-empty dir */
3139 static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 };
3140 const char *zWorkDir; /* --workdir value */
3141 const char *zRepo = 0; /* Name of the repository file */
@@ -3242,23 +3538,10 @@
3242 }else if( db_exists("SELECT 1 FROM event WHERE type='ci'") ){
3243 g.zOpenRevision = db_get("main-branch", 0);
3244 }
3245 }
3246
3247 if( g.zOpenRevision ){
3248 /* Since the repository is open and we know the revision now,
3249 ** refresh the allow-symlinks flag. Since neither the local
3250 ** checkout nor the configuration database are open at this
3251 ** point, this should always return the versioned setting,
3252 ** if any, or the default value, which is negative one. The
3253 ** value negative one, in this context, means that the code
3254 ** below should fallback to using the setting value from the
3255 ** repository or global configuration databases only. */
3256 allowSymlinks = db_get_versioned_boolean("allow-symlinks", -1);
3257 }else{
3258 allowSymlinks = -1; /* Use non-versioned settings only. */
3259 }
3260
3261 #if defined(_WIN32) || defined(__CYGWIN__)
3262 # define LOCALDB_NAME "./_FOSSIL_"
3263 #else
3264 # define LOCALDB_NAME "./.fslckout"
@@ -3268,26 +3551,10 @@
3268 "COMMIT; PRAGMA journal_mode=WAL; BEGIN;",
3269 #endif
3270 (char*)0);
3271 db_delete_on_failure(LOCALDB_NAME);
3272 db_open_local(0);
3273 if( allowSymlinks>=0 ){
3274 /* Use the value from the versioned setting, which was read
3275 ** prior to opening the local checkout (i.e. which is most
3276 ** likely empty and does not actually contain any versioned
3277 ** setting files yet). Normally, this value would be given
3278 ** first priority within db_get_boolean(); however, this is
3279 ** a special case because we know the on-disk files may not
3280 ** exist yet. */
3281 g.allowSymlinks = allowSymlinks;
3282 }else{
3283 /* Since the local checkout may not have any files at this
3284 ** point, this will probably be the setting value from the
3285 ** repository or global configuration databases. */
3286 g.allowSymlinks = db_get_boolean("allow-symlinks",
3287 db_allow_symlinks_by_default());
3288 }
3289 db_lset("repository", zRepo);
3290 db_record_repository_filename(zRepo);
3291 db_set_checkout(0);
3292 azNewArgv[0] = g.argv[0];
3293 g.argv = azNewArgv;
@@ -3376,12 +3643,13 @@
3376 const char *name; /* Name of the setting */
3377 const char *var; /* Internal variable name used by db_set() */
3378 int width; /* Width of display. 0 for boolean values and
3379 ** negative for values which should not appear
3380 ** on the /setup_settings page. */
3381 int versionable; /* Is this setting versionable? */
3382 int forceTextArea; /* Force using a text area for display? */
 
3383 const char *def; /* Default value */
3384 };
3385 #endif /* INTERFACE */
3386
3387 /*
@@ -3395,32 +3663,29 @@
3395 ** SETTING: admin-log boolean default=off
3396 **
3397 ** When the admin-log setting is enabled, configuration changes are recorded
3398 ** in the "admin_log" table of the repository.
3399 */
3400 #if defined(_WIN32)
3401 /*
3402 ** SETTING: allow-symlinks boolean default=off versionable
3403 **
3404 ** When allow-symlinks is OFF, symbolic links in the repository are followed
3405 ** and treated no differently from real files. When allow-symlinks is ON,
3406 ** the object to which the symbolic link points is ignored, and the content
3407 ** of the symbolic link that is stored in the repository is the name of the
3408 ** object to which the symbolic link points.
3409 */
3410 #endif
3411 #if !defined(_WIN32)
3412 /*
3413 ** SETTING: allow-symlinks boolean default=on versionable
3414 **
3415 ** When allow-symlinks is OFF, symbolic links in the repository are followed
3416 ** and treated no differently from real files. When allow-symlinks is ON,
3417 ** the object to which the symbolic link points is ignored, and the content
3418 ** of the symbolic link that is stored in the repository is the name of the
3419 ** object to which the symbolic link points.
3420 */
3421 #endif
3422 /*
3423 ** SETTING: auto-captcha boolean default=on variable=autocaptcha
3424 ** If enabled, the /login page provides a button that will automatically
3425 ** fill in the captcha password. This makes things easier for human users,
3426 ** at the expense of also making logins easier for malicious robots.
@@ -3470,11 +3735,11 @@
3470 ** there is no cron job periodically running "fossil backoffice",
3471 ** email notifications and other work normally done by the
3472 ** backoffice will not occur.
3473 */
3474 /*
3475 ** SETTING: backoffice-logfile width=40
3476 ** If backoffice-logfile is not an empty string and is a valid
3477 ** filename, then a one-line message is appended to that file
3478 ** every time the backoffice runs. This can be used for debugging,
3479 ** to ensure that backoffice is running appropriately.
3480 */
@@ -3547,11 +3812,11 @@
3547 /*
3548 ** SETTING: crnl-glob width=40 versionable block-text
3549 ** This is an alias for the crlf-glob setting.
3550 */
3551 /*
3552 ** SETTING: default-perms width=16 default=u
3553 ** Permissions given automatically to new users. For more
3554 ** information on permissions see the Users page in Server
3555 ** Administration of the HTTP UI.
3556 */
3557 /*
@@ -3559,11 +3824,11 @@
3559 ** If enabled, permit files that may be binary
3560 ** or that match the "binary-glob" setting to be used with
3561 ** external diff programs. If disabled, skip these files.
3562 */
3563 /*
3564 ** SETTING: diff-command width=40
3565 ** The value is an external command to run when performing a diff.
3566 ** If undefined, the internal text diff will be used.
3567 */
3568 /*
3569 ** SETTING: dont-push boolean default=off
@@ -3574,11 +3839,11 @@
3574 /*
3575 ** SETTING: dotfiles boolean versionable default=off
3576 ** If enabled, include --dotfiles option for all compatible commands.
3577 */
3578 /*
3579 ** SETTING: editor width=32
3580 ** The value is an external command that will launch the
3581 ** text editor command used for check-in comments.
3582 */
3583 /*
3584 ** SETTING: empty-dirs width=40 versionable block-text
@@ -3617,16 +3882,16 @@
3617 ** An empty list prohibits editing via that page. Note that
3618 ** it cannot edit binary files, so the list should not
3619 ** contain any globs for, e.g., images or PDFs.
3620 */
3621 /*
3622 ** SETTING: gdiff-command width=40 default=gdiff
3623 ** The value is an external command to run when performing a graphical
3624 ** diff. If undefined, text diff will be used.
3625 */
3626 /*
3627 ** SETTING: gmerge-command width=40
3628 ** The value is a graphical merge conflict resolver command operating
3629 ** on four files. Examples:
3630 **
3631 ** kdiff3 "%baseline" "%original" "%merge" -o "%output"
3632 ** xxdiff "%original" "%baseline" "%merge" -M "%output"
@@ -3757,11 +4022,11 @@
3757 ** the associated files within the checkout -AND- the "rm"
3758 ** and "delete" commands will also remove the associated
3759 ** files from within the checkout.
3760 */
3761 /*
3762 ** SETTING: pgp-command width=40
3763 ** Command used to clear-sign manifests at check-in.
3764 ** Default value is "gpg --clearsign -o"
3765 */
3766 /*
3767 ** SETTING: forbid-delta-manifests boolean default=off
@@ -3817,22 +4082,22 @@
3817 **
3818 ** If repolist-skin has a value of 2, then the repository is omitted from
3819 ** the list in use cases 1 through 4, but not for 5 and 6.
3820 */
3821 /*
3822 ** SETTING: self-register boolean default=off
3823 ** Allow users to register themselves through the HTTP UI.
3824 ** This is useful if you want to see other names than
3825 ** "Anonymous" in e.g. ticketing system. On the other hand
3826 ** users can not be deleted.
3827 */
3828 /*
3829 ** SETTING: ssh-command width=40
3830 ** The command used to talk to a remote machine with the "ssh://" protocol.
3831 */
3832 /*
3833 ** SETTING: ssl-ca-location width=40
3834 ** The full pathname to a file containing PEM encoded
3835 ** CA root certificates, or a directory of certificates
3836 ** with filenames formed from the certificate hashes as
3837 ** required by OpenSSL.
3838 **
@@ -3842,11 +4107,11 @@
3842 ** Checking your platform behaviour is required if the
3843 ** exact contents of the CA root is critical for your
3844 ** application.
3845 */
3846 /*
3847 ** SETTING: ssl-identity width=40
3848 ** The full pathname to a file containing a certificate
3849 ** and private key in PEM format. Create by concatenating
3850 ** the certificate and private key files.
3851 **
3852 ** This identity will be presented to SSL servers to
@@ -3853,33 +4118,33 @@
3853 ** authenticate this client, in addition to the normal
3854 ** password authentication.
3855 */
3856 #ifdef FOSSIL_ENABLE_TCL
3857 /*
3858 ** SETTING: tcl boolean default=off
3859 ** If enabled Tcl integration commands will be added to the TH1
3860 ** interpreter, allowing arbitrary Tcl expressions and
3861 ** scripts to be evaluated from TH1. Additionally, the Tcl
3862 ** interpreter will be able to evaluate arbitrary TH1
3863 ** expressions and scripts.
3864 */
3865 /*
3866 ** SETTING: tcl-setup width=40 block-text
3867 ** This is the setup script to be evaluated after creating
3868 ** and initializing the Tcl interpreter. By default, this
3869 ** is empty and no extra setup is performed.
3870 */
3871 #endif /* FOSSIL_ENABLE_TCL */
3872 /*
3873 ** SETTING: tclsh width=80 default=tclsh
3874 ** Name of the external TCL interpreter used for such things
3875 ** as running the GUI diff viewer launched by the --tk option
3876 ** of the various "diff" commands.
3877 */
3878 #ifdef FOSSIL_ENABLE_TH1_DOCS
3879 /*
3880 ** SETTING: th1-docs boolean default=off
3881 ** If enabled, this allows embedded documentation files to contain
3882 ** arbitrary TH1 scripts that are evaluated on the server. If native
3883 ** Tcl integration is also enabled, this setting has the
3884 ** potential to allow anybody with check-in privileges to
3885 ** do almost anything that the associated operating system
@@ -3932,11 +4197,11 @@
3932 ** of a "fossil clone" or "fossil sync" command. The
3933 ** default is false, in which case the -u option is
3934 ** needed to clone or sync unversioned files.
3935 */
3936 /*
3937 ** SETTING: web-browser width=30
3938 ** A shell command used to launch your preferred
3939 ** web browser when given a URL as an argument.
3940 ** Defaults to "start" on windows, "open" on Mac,
3941 ** and "firefox" on Unix.
3942 */
@@ -4058,11 +4323,13 @@
4058 fossil_fatal("cannot set 'manifest' globally");
4059 }
4060 if( unsetFlag ){
4061 db_unset(pSetting->name, globalFlag);
4062 }else{
 
4063 db_set(pSetting->name, g.argv[3], globalFlag);
 
4064 }
4065 if( isManifest && g.localOpen ){
4066 manifest_to_disk(db_lget_int("checkout", 0));
4067 }
4068 }else{
4069
--- src/db.c
+++ src/db.c
@@ -69,10 +69,11 @@
69 #endif /* INTERFACE */
70 const struct Stmt empty_Stmt = empty_Stmt_m;
71
72 /*
73 ** Call this routine when a database error occurs.
74 ** This routine throws a fatal error. It does not return.
75 */
76 static void db_err(const char *zFormat, ...){
77 va_list ap;
78 char *z;
79 va_start(ap, zFormat);
@@ -113,10 +114,11 @@
114 /*
115 ** All static variable that a used by only this file are gathered into
116 ** the following structure.
117 */
118 static struct DbLocalData {
119 unsigned protectMask; /* Prevent changes to database */
120 int nBegin; /* Nesting depth of BEGIN */
121 int doRollback; /* True to force a rollback */
122 int nCommitHook; /* Number of commit hooks */
123 int wrTxn; /* Outer-most TNX is a write */
124 Stmt *pAllStmt; /* List of all unfinalized statements */
@@ -130,11 +132,19 @@
132 char *azBeforeCommit[5]; /* Commands to run prior to COMMIT */
133 int nBeforeCommit; /* Number of entries in azBeforeCommit */
134 int nPriorChanges; /* sqlite3_total_changes() at transaction start */
135 const char *zStartFile; /* File in which transaction was started */
136 int iStartLine; /* Line of zStartFile where transaction started */
137 int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
138 void *pAuthArg; /* Argument to the authorizer */
139 const char *zAuthName; /* Name of the authorizer */
140 int bProtectTriggers; /* True if protection triggers already exist */
141 int nProtect; /* Slots of aProtect used */
142 unsigned aProtect[10]; /* Saved values of protectMask */
143 } db = {
144 PROTECT_USER|PROTECT_CONFIG|PROTECT_BASELINE, /* protectMask */
145 0, 0, 0, 0, 0, 0, };
146
147 /*
148 ** Arrange for the given file to be deleted on a failure.
149 */
150 void db_delete_on_failure(const char *zFilename){
@@ -238,17 +248,19 @@
248 db.nBegin--;
249 if( db.nBegin==0 ){
250 int i;
251 if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){
252 i = 0;
253 db_protect_only(PROTECT_SENSITIVE);
254 while( db.nBeforeCommit ){
255 db.nBeforeCommit--;
256 sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0);
257 sqlite3_free(db.azBeforeCommit[i]);
258 i++;
259 }
260 leaf_do_pending_checks();
261 db_protect_pop();
262 }
263 for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){
264 int rc = db.aHook[i].xHook();
265 if( rc ){
266 db.doRollback = 1;
@@ -316,10 +328,230 @@
328 }
329 db.aHook[db.nCommitHook].sequence = sequence;
330 db.aHook[db.nCommitHook].xHook = x;
331 db.nCommitHook++;
332 }
333
334 #if INTERFACE
335 /*
336 ** Flag bits for db_protect() and db_unprotect() indicating which parts
337 ** of the databases should be write protected or write enabled, respectively.
338 */
339 #define PROTECT_USER 0x01 /* USER table */
340 #define PROTECT_CONFIG 0x02 /* CONFIG and GLOBAL_CONFIG tables */
341 #define PROTECT_SENSITIVE 0x04 /* Sensitive and/or global settings */
342 #define PROTECT_READONLY 0x08 /* everything except TEMP tables */
343 #define PROTECT_BASELINE 0x10 /* protection system is working */
344 #define PROTECT_ALL 0x1f /* All of the above */
345 #define PROTECT_NONE 0x00 /* Nothing. Everything is open */
346 #endif /* INTERFACE */
347
348 /*
349 ** Enable or disable database write protections.
350 **
351 ** db_protext(X) Add protects on X
352 ** db_unprotect(X) Remove protections on X
353 ** db_protect_only(X) Remove all prior protections then set
354 ** protections to only X.
355 **
356 ** Each of these routines pushes the previous protection mask onto
357 ** a finite-size stack. Each should be followed by a call to
358 ** db_protect_pop() to pop the stack and restore the protections that
359 ** existed prior to the call. The protection mask stack has a limited
360 ** depth, so take care not to next calls too deeply.
361 **
362 ** About Database Write Protection
363 ** -------------------------------
364 **
365 ** This is *not* a primary means of defending the application from
366 ** attack. Fossil should be secure even if this mechanism is disabled.
367 ** The purpose of database write protection is to provide an additional
368 ** layer of defense in case SQL injection bugs somehow slip into other
369 ** parts of the system. In other words, database write protection is
370 ** not primary defense but rather defense in depth.
371 **
372 ** This mechanism mostly focuses on the USER table, to prevent an
373 ** attacker from giving themselves Admin privilegs, and on the
374 ** CONFIG table and specially "sensitive" settings such as
375 ** "diff-command" or "editor" that if compromised by an attacker
376 ** could lead to an RCE.
377 **
378 ** By default, the USER and CONFIG tables are read-only. Various
379 ** subsystems that legitimately need to change those tables can
380 ** temporarily do so using:
381 **
382 ** db_unprotect(PROTECT_xxx);
383 ** // make the legitmate changes here
384 ** db_protect_pop();
385 **
386 ** Code that runs inside of reduced protections should be carefully
387 ** reviewed to ensure that it is harmless and not subject to SQL
388 ** injection.
389 **
390 ** Read-only operations (such as many web pages like /timeline)
391 ** can invoke db_protect(PROTECT_ALL) to effectively make the database
392 ** read-only. TEMP tables (which are often used for these kinds of
393 ** pages) are still writable, however.
394 **
395 ** The PROTECT_SENSITIVE protection is a subset of PROTECT_CONFIG
396 ** that blocks changes to all of the global_config table, but only
397 ** "sensitive" settings in the config table. PROTECT_SENSITIVE
398 ** relies on triggers and the protected_setting() SQL function to
399 ** prevent changes to sensitive settings.
400 **
401 ** Additional Notes
402 ** ----------------
403 **
404 ** Calls to routines like db_set() and db_unset() temporarily disable
405 ** the PROTECT_CONFIG protection. The assumption is that these calls
406 ** cannot be invoked by an SQL injection and are thus safe. Make sure
407 ** this is the case by always using a string literal as the name argument
408 ** to db_set() and db_unset() and friend, not a variable that might
409 ** be compromised by an attack.
410 */
411 void db_protect_only(unsigned flags){
412 if( db.nProtect>=count(db.aProtect)-2 ){
413 fossil_panic("too many db_protect() calls");
414 }
415 db.aProtect[db.nProtect++] = db.protectMask;
416 if( (flags & PROTECT_SENSITIVE)!=0
417 && db.bProtectTriggers==0
418 && g.repositoryOpen
419 ){
420 /* Create the triggers needed to protect sensitive settings from
421 ** being created or modified the first time that PROTECT_SENSITIVE
422 ** is enabled. Deleting a sensitive setting is harmless, so there
423 ** is not trigger to block deletes. After being created once, the
424 ** triggers persist for the life of the database connection. */
425 db_multi_exec(
426 "CREATE TEMP TRIGGER protect_1 BEFORE INSERT ON config"
427 " WHEN protected_setting(new.name) BEGIN"
428 " SELECT raise(abort,'not authorized');"
429 "END;\n"
430 "CREATE TEMP TRIGGER protect_2 BEFORE UPDATE ON config"
431 " WHEN protected_setting(new.name) BEGIN"
432 " SELECT raise(abort,'not authorized');"
433 "END;\n"
434 );
435 db.bProtectTriggers = 1;
436 }
437 db.protectMask = flags;
438 }
439 void db_protect(unsigned flags){
440 db_protect_only(db.protectMask | flags);
441 }
442 void db_unprotect(unsigned flags){
443 if( db.nProtect>=count(db.aProtect)-2 ){
444 fossil_panic("too many db_unprotect() calls");
445 }
446 db.aProtect[db.nProtect++] = db.protectMask;
447 db.protectMask &= ~flags;
448 }
449 void db_protect_pop(void){
450 if( db.nProtect<1 ){
451 fossil_panic("too many db_protect_pop() calls");
452 }
453 db.protectMask = db.aProtect[--db.nProtect];
454 }
455
456 /*
457 ** Verify that the desired database write pertections are in place.
458 ** Throw a fatal error if not.
459 */
460 void db_assert_protected(unsigned flags){
461 if( (flags & db.protectMask)!=flags ){
462 fossil_panic("missing database write protection bits: %02x",
463 flags & ~db.protectMask);
464 }
465 }
466
467 /*
468 ** Assert that either all protections are off (including PROTECT_BASELINE
469 ** which is usually always enabled), or the setting named in the argument
470 ** is no a sensitive setting.
471 **
472 ** This assert() is used to verify that the db_set() and db_set_int()
473 ** interfaces do not modify a sensitive setting.
474 */
475 void db_assert_protection_off_or_not_sensitive(const char *zName){
476 if( db.protectMask!=0 && db_setting_is_protected(zName) ){
477 fossil_panic("unauthorized change to protected setting \"%s\"", zName);
478 }
479 }
480
481 /*
482 ** Every Fossil database connection automatically registers the following
483 ** overarching authenticator callback, and leaves it registered for the
484 ** duration of the connection. This authenticator will call any
485 ** sub-authenticators that are registered using db_set_authorizer().
486 */
487 int db_top_authorizer(
488 void *pNotUsed,
489 int eCode,
490 const char *z0,
491 const char *z1,
492 const char *z2,
493 const char *z3
494 ){
495 int rc = SQLITE_OK;
496 switch( eCode ){
497 case SQLITE_INSERT:
498 case SQLITE_UPDATE:
499 case SQLITE_DELETE: {
500 if( (db.protectMask & PROTECT_USER)!=0
501 && sqlite3_stricmp(z0,"user")==0 ){
502 rc = SQLITE_DENY;
503 }else if( (db.protectMask & PROTECT_CONFIG)!=0 &&
504 (sqlite3_stricmp(z0,"config")==0 ||
505 sqlite3_stricmp(z0,"global_config")==0) ){
506 rc = SQLITE_DENY;
507 }else if( (db.protectMask & PROTECT_SENSITIVE)!=0 &&
508 sqlite3_stricmp(z0,"global_config")==0 ){
509 rc = SQLITE_DENY;
510 }else if( (db.protectMask & PROTECT_READONLY)!=0
511 && sqlite3_stricmp(z2,"temp")!=0 ){
512 rc = SQLITE_DENY;
513 }
514 break;
515 }
516 case SQLITE_DROP_TEMP_TRIGGER: {
517 /* Do not allow the triggers that enforce PROTECT_SENSITIVE
518 ** to be dropped */
519 rc = SQLITE_DENY;
520 break;
521 }
522 }
523 if( db.xAuth && rc==SQLITE_OK ){
524 rc = db.xAuth(db.pAuthArg, eCode, z0, z1, z2, z3);
525 }
526 return rc;
527 }
528
529 /*
530 ** Set or unset the query authorizer callback function
531 */
532 void db_set_authorizer(
533 int(*xAuth)(void*,int,const char*,const char*,const char*,const char*),
534 void *pArg,
535 const char *zName /* for tracing */
536 ){
537 if( db.xAuth ){
538 fossil_panic("multiple active db_set_authorizer() calls");
539 }
540 db.xAuth = xAuth;
541 db.pAuthArg = pArg;
542 db.zAuthName = zName;
543 if( g.fSqlTrace ) fossil_trace("-- set authorizer %s\n", zName);
544 }
545 void db_clear_authorizer(void){
546 if( db.zAuthName && g.fSqlTrace ){
547 fossil_trace("-- discontinue authorizer %s\n", db.zAuthName);
548 }
549 db.xAuth = 0;
550 db.pAuthArg = 0;
551 db.zAuthName = 0;
552 }
553
554 #if INTERFACE
555 /*
556 ** Possible flags to db_vprepare
557 */
@@ -334,21 +566,24 @@
566 */
567 int db_vprepare(Stmt *pStmt, int flags, const char *zFormat, va_list ap){
568 int rc;
569 int prepFlags = 0;
570 char *zSql;
571 const char *zExtra = 0;
572 blob_zero(&pStmt->sql);
573 blob_vappendf(&pStmt->sql, zFormat, ap);
574 va_end(ap);
575 zSql = blob_str(&pStmt->sql);
576 db.nPrepare++;
577 if( flags & DB_PREPARE_PERSISTENT ){
578 prepFlags = SQLITE_PREPARE_PERSISTENT;
579 }
580 rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);
581 if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
582 db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
583 }else if( zExtra && !fossil_all_whitespace(zExtra) ){
584 db_err("surplus text follows SQL: \"%s\"", zExtra);
585 }
586 pStmt->pNext = db.pAllStmt;
587 pStmt->pPrev = 0;
588 if( db.pAllStmt ) db.pAllStmt->pPrev = pStmt;
589 db.pAllStmt = pStmt;
@@ -611,10 +846,11 @@
846 return rc;
847 }
848
849 /*
850 ** COMMAND: test-db-exec-error
851 ** Usage: %fossil test-db-exec-error
852 **
853 ** Invoke the db_exec() interface with an erroneous SQL statement
854 ** in order to verify the error handling logic.
855 */
856 void db_test_db_exec_cmd(void){
@@ -621,10 +857,27 @@
857 Stmt err;
858 db_find_and_open_repository(0,0);
859 db_prepare(&err, "INSERT INTO repository.config(name) VALUES(NULL);");
860 db_exec(&err);
861 }
862
863 /*
864 ** COMMAND: test-db-prepare
865 ** Usage: %fossil test-db-prepare ?OPTIONS? SQL
866 **
867 ** Invoke db_prepare() on the SQL input. Report any errors encountered.
868 ** This command is used to verify error detection logic in the db_prepare()
869 ** utility routine.
870 */
871 void db_test_db_prepare(void){
872 Stmt err;
873 db_find_and_open_repository(0,0);
874 verify_all_options();
875 if( g.argc!=3 ) usage("?OPTIONS? SQL");
876 db_prepare(&err, "%s", g.argv[2]/*safe-for-%s*/);
877 db_finalize(&err);
878 }
879
880 /*
881 ** Print the output of one or more SQL queries on standard output.
882 ** This routine is used for debugging purposes only.
883 */
@@ -844,34 +1097,34 @@
1097 void db_init_database(
1098 const char *zFileName, /* Name of database file to create */
1099 const char *zSchema, /* First part of schema */
1100 ... /* Additional SQL to run. Terminate with NULL. */
1101 ){
1102 sqlite3 *xdb;
1103 int rc;
1104 const char *zSql;
1105 va_list ap;
1106
1107 xdb = db_open(zFileName ? zFileName : ":memory:");
1108 sqlite3_exec(xdb, "BEGIN EXCLUSIVE", 0, 0, 0);
1109 rc = sqlite3_exec(xdb, zSchema, 0, 0, 0);
1110 if( rc!=SQLITE_OK ){
1111 db_err("%s", sqlite3_errmsg(xdb));
1112 }
1113 va_start(ap, zSchema);
1114 while( (zSql = va_arg(ap, const char*))!=0 ){
1115 rc = sqlite3_exec(xdb, zSql, 0, 0, 0);
1116 if( rc!=SQLITE_OK ){
1117 db_err("%s", sqlite3_errmsg(xdb));
1118 }
1119 }
1120 va_end(ap);
1121 sqlite3_exec(xdb, "COMMIT", 0, 0, 0);
1122 if( zFileName || g.db!=0 ){
1123 sqlite3_close(xdb);
1124 }else{
1125 g.db = xdb;
1126 }
1127 }
1128
1129 /*
1130 ** Function to return the number of seconds since 1970. This is
@@ -1060,10 +1313,37 @@
1313 }
1314 strcpy(zOut, zTemp = obscure((char*)zIn));
1315 fossil_free(zTemp);
1316 sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free);
1317 }
1318
1319 /*
1320 ** Return True if zName is a protected (a.k.a. "sensitive") setting.
1321 */
1322 int db_setting_is_protected(const char *zName){
1323 const Setting *pSetting = zName ? db_find_setting(zName,0) : 0;
1324 return pSetting!=0 && pSetting->sensitive!=0;
1325 }
1326
1327 /*
1328 ** Implement the protected_setting(X) SQL function. This function returns
1329 ** true if X is the name of a protected (security-sensitive) setting and
1330 ** the db.protectSensitive flag is enabled. It returns false otherwise.
1331 */
1332 LOCAL void db_protected_setting_func(
1333 sqlite3_context *context,
1334 int argc,
1335 sqlite3_value **argv
1336 ){
1337 const char *zSetting;
1338 if( (db.protectMask & PROTECT_SENSITIVE)==0 ){
1339 sqlite3_result_int(context, 0);
1340 return;
1341 }
1342 zSetting = (const char*)sqlite3_value_text(argv[0]);
1343 sqlite3_result_int(context, db_setting_is_protected(zSetting));
1344 }
1345
1346 /*
1347 ** Register the SQL functions that are useful both to the internal
1348 ** representation and to the "fossil sql" command.
1349 */
@@ -1090,10 +1370,12 @@
1370 alert_find_emailaddr_func, 0, 0);
1371 sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0,
1372 alert_display_name_func, 0, 0);
1373 sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
1374 db_obscure, 0, 0);
1375 sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
1376 db_protected_setting_func, 0, 0);
1377 }
1378
1379 #if USE_SEE
1380 /*
1381 ** This is a pointer to the saved database encryption key string.
@@ -1348,10 +1630,11 @@
1630 if( g.fSqlTrace ) sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, db_sql_trace, 0);
1631 db_add_aux_functions(db);
1632 re_add_sql_func(db); /* The REGEXP operator */
1633 foci_register(db); /* The "files_of_checkin" virtual table */
1634 sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, 0, &rc);
1635 sqlite3_set_authorizer(db, db_top_authorizer, db);
1636 return db;
1637 }
1638
1639
1640 /*
@@ -1791,22 +2074,10 @@
2074 }
2075 }
2076 return zRepo;
2077 }
2078
 
 
 
 
 
 
 
 
 
 
 
 
2079 /*
2080 ** Returns non-zero if support for symlinks is currently enabled.
2081 */
2082 int db_allow_symlinks(void){
2083 return g.allowSymlinks;
@@ -1848,13 +2119,14 @@
2119 g.zRepositoryName = mprintf("%s", zDbName);
2120 db_open_or_attach(g.zRepositoryName, "repository");
2121 g.repositoryOpen = 1;
2122 sqlite3_file_control(g.db, "repository", SQLITE_FCNTL_DATA_VERSION,
2123 &g.iRepoDataVers);
2124
2125 /* Cache "allow-symlinks" option, because we'll need it on every stat call */
2126 g.allowSymlinks = db_get_boolean("allow-symlinks",0);
2127
2128 g.zAuxSchema = db_get("aux-schema","");
2129 g.eHashPolicy = db_get_int("hash-policy",-1);
2130 if( g.eHashPolicy<0 ){
2131 g.eHashPolicy = hname_default_policy();
2132 db_set_int("hash-policy", g.eHashPolicy, 0);
@@ -2089,10 +2361,11 @@
2361 ** argument is true. Ignore unfinalized statements when false.
2362 */
2363 void db_close(int reportErrors){
2364 sqlite3_stmt *pStmt;
2365 if( g.db==0 ) return;
2366 sqlite3_set_authorizer(g.db, 0, 0);
2367 if( g.fSqlStats ){
2368 int cur, hiwtr;
2369 sqlite3_db_status(g.db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur, &hiwtr, 0);
2370 fprintf(stderr, "-- LOOKASIDE_USED %10d %10d\n", cur, hiwtr);
2371 sqlite3_db_status(g.db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &cur, &hiwtr, 0);
@@ -2118,17 +2391,20 @@
2391 fprintf(stderr, "-- prepared statements %10d\n", db.nPrepare);
2392 }
2393 while( db.pAllStmt ){
2394 db_finalize(db.pAllStmt);
2395 }
2396 if( db.nBegin ){
2397 if( reportErrors ){
2398 fossil_warning("Transaction started at %s:%d never commits",
2399 db.zStartFile, db.iStartLine);
2400 }
2401 db_end_transaction(1);
2402 }
2403 pStmt = 0;
2404 sqlite3_busy_timeout(g.db, 0);
2405 g.dbIgnoreErrors++; /* Stop "database locked" warnings */
2406 sqlite3_exec(g.db, "PRAGMA optimize", 0, 0, 0);
2407 g.dbIgnoreErrors--;
2408 db_close_config();
2409
2410 /* If the localdb has a lot of unused free space,
@@ -2136,11 +2412,13 @@
2412 */
2413 if( db_database_slot("localdb")>=0 ){
2414 int nFree = db_int(0, "PRAGMA localdb.freelist_count");
2415 int nTotal = db_int(0, "PRAGMA localdb.page_count");
2416 if( nFree>nTotal/4 ){
2417 db_unprotect(PROTECT_ALL);
2418 db_multi_exec("VACUUM localdb;");
2419 db_protect_pop();
2420 }
2421 }
2422
2423 if( g.db ){
2424 int rc;
@@ -2154,10 +2432,11 @@
2432 }
2433 g.db = 0;
2434 }
2435 g.repositoryOpen = 0;
2436 g.localOpen = 0;
2437 db.bProtectTriggers = 0;
2438 assert( g.dbConfig==0 );
2439 assert( g.zConfigDbName==0 );
2440 backoffice_run_if_needed();
2441 }
2442
@@ -2168,10 +2447,11 @@
2447 if( g.db ){
2448 int rc;
2449 sqlite3_wal_checkpoint(g.db, 0);
2450 rc = sqlite3_close(g.db);
2451 if( g.fSqlTrace ) fossil_trace("-- sqlite3_close(%d)\n", rc);
2452 db_clear_authorizer();
2453 }
2454 g.db = 0;
2455 g.repositoryOpen = 0;
2456 g.localOpen = 0;
2457 }
@@ -2215,10 +2495,11 @@
2495 zUser = fossil_getenv("USERNAME");
2496 }
2497 if( zUser==0 ){
2498 zUser = "root";
2499 }
2500 db_unprotect(PROTECT_USER);
2501 db_multi_exec(
2502 "INSERT OR IGNORE INTO user(login, info) VALUES(%Q,'')", zUser
2503 );
2504 db_multi_exec(
2505 "UPDATE user SET cap='s', pw=%Q"
@@ -2234,10 +2515,11 @@
2515 " VALUES('developer','','ei','Dev');"
2516 "INSERT OR IGNORE INTO user(login,pw,cap,info)"
2517 " VALUES('reader','','kptw','Reader');"
2518 );
2519 }
2520 db_protect_pop();
2521 }
2522
2523 /*
2524 ** Return a pointer to a string that contains the RHS of an IN operator
2525 ** that will select CONFIG table names that are in the list of control
@@ -2285,10 +2567,11 @@
2567 ){
2568 char *zDate;
2569 Blob hash;
2570 Blob manifest;
2571
2572 db_unprotect(PROTECT_ALL);
2573 db_set("content-schema", CONTENT_SCHEMA, 0);
2574 db_set("aux-schema", AUX_SCHEMA_MAX, 0);
2575 db_set("rebuilt", get_version(), 0);
2576 db_set("admin-log", "1", 0);
2577 db_set("access-log", "1", 0);
@@ -2343,10 +2626,11 @@
2626 " photo = (SELECT u2.photo FROM settingSrc.user u2"
2627 " WHERE u2.login = user.login)"
2628 " WHERE user.login IN ('anonymous','nobody','developer','reader');"
2629 );
2630 }
2631 db_protect_pop();
2632
2633 if( zInitialDate ){
2634 int rid;
2635 blob_zero(&manifest);
2636 blob_appendf(&manifest, "C initial\\sempty\\scheck-in\n");
@@ -2839,10 +3123,12 @@
3123 z = db_text(0, "SELECT strftime(%Q,%Q,'unixepoch');", zFormat, z);
3124 }
3125 return z;
3126 }
3127 void db_set(const char *zName, const char *zValue, int globalFlag){
3128 db_assert_protection_off_or_not_sensitive(zName);
3129 db_unprotect(PROTECT_CONFIG);
3130 db_begin_transaction();
3131 if( globalFlag ){
3132 db_swap_connections();
3133 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)",
3134 zName, zValue);
@@ -2853,13 +3139,15 @@
3139 }
3140 if( globalFlag && g.repositoryOpen ){
3141 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
3142 }
3143 db_end_transaction(0);
3144 db_protect_pop();
3145 }
3146 void db_unset(const char *zName, int globalFlag){
3147 db_begin_transaction();
3148 db_unprotect(PROTECT_CONFIG);
3149 if( globalFlag ){
3150 db_swap_connections();
3151 db_multi_exec("DELETE FROM global_config WHERE name=%Q", zName);
3152 db_swap_connections();
3153 }else{
@@ -2866,10 +3154,11 @@
3154 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
3155 }
3156 if( globalFlag && g.repositoryOpen ){
3157 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
3158 }
3159 db_protect_pop();
3160 db_end_transaction(0);
3161 }
3162 int db_is_global(const char *zName){
3163 int rc = 0;
3164 if( g.zConfigDbName ){
@@ -2899,10 +3188,12 @@
3188 db_swap_connections();
3189 }
3190 return v;
3191 }
3192 void db_set_int(const char *zName, int value, int globalFlag){
3193 db_assert_protection_off_or_not_sensitive(zName);
3194 db_unprotect(PROTECT_CONFIG);
3195 if( globalFlag ){
3196 db_swap_connections();
3197 db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)",
3198 zName, value);
3199 db_swap_connections();
@@ -2911,10 +3202,11 @@
3202 zName, value);
3203 }
3204 if( globalFlag && g.repositoryOpen ){
3205 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
3206 }
3207 db_protect_pop();
3208 }
3209 int db_get_boolean(const char *zName, int dflt){
3210 char *zVal = db_get(zName, dflt ? "on" : "off");
3211 if( is_truth(zVal) ){
3212 dflt = 1;
@@ -3040,24 +3332,28 @@
3332 }
3333 file_canonical_name(zName, &full, 0);
3334 (void)filename_collation(); /* Initialize before connection swap */
3335 db_swap_connections();
3336 zRepoSetting = mprintf("repo:%q", blob_str(&full));
3337
3338 db_unprotect(PROTECT_CONFIG);
3339 db_multi_exec(
3340 "DELETE FROM global_config WHERE name %s = %Q;",
3341 filename_collation(), zRepoSetting
3342 );
3343 db_multi_exec(
3344 "INSERT OR IGNORE INTO global_config(name,value)"
3345 "VALUES(%Q,1);",
3346 zRepoSetting
3347 );
3348 db_protect_pop();
3349 fossil_free(zRepoSetting);
3350 if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){
3351 Blob localRoot;
3352 file_canonical_name(g.zLocalRoot, &localRoot, 1);
3353 zCkoutSetting = mprintf("ckout:%q", blob_str(&localRoot));
3354 db_unprotect(PROTECT_CONFIG);
3355 db_multi_exec(
3356 "DELETE FROM global_config WHERE name %s = %Q;",
3357 filename_collation(), zCkoutSetting
3358 );
3359 db_multi_exec(
@@ -3073,10 +3369,11 @@
3369 db_optional_sql("repository",
3370 "REPLACE INTO config(name,value,mtime)"
3371 "VALUES(%Q,1,now());",
3372 zCkoutSetting
3373 );
3374 db_protect_pop();
3375 fossil_free(zCkoutSetting);
3376 blob_reset(&localRoot);
3377 }else{
3378 db_swap_connections();
3379 }
@@ -3131,11 +3428,10 @@
3428 void cmd_open(void){
3429 int emptyFlag;
3430 int keepFlag;
3431 int forceMissingFlag;
3432 int allowNested;
 
3433 int setmtimeFlag; /* --setmtime. Set mtimes on files */
3434 int bForce = 0; /* --force. Open even if non-empty dir */
3435 static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 };
3436 const char *zWorkDir; /* --workdir value */
3437 const char *zRepo = 0; /* Name of the repository file */
@@ -3242,23 +3538,10 @@
3538 }else if( db_exists("SELECT 1 FROM event WHERE type='ci'") ){
3539 g.zOpenRevision = db_get("main-branch", 0);
3540 }
3541 }
3542
 
 
 
 
 
 
 
 
 
 
 
 
 
3543
3544 #if defined(_WIN32) || defined(__CYGWIN__)
3545 # define LOCALDB_NAME "./_FOSSIL_"
3546 #else
3547 # define LOCALDB_NAME "./.fslckout"
@@ -3268,26 +3551,10 @@
3551 "COMMIT; PRAGMA journal_mode=WAL; BEGIN;",
3552 #endif
3553 (char*)0);
3554 db_delete_on_failure(LOCALDB_NAME);
3555 db_open_local(0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3556 db_lset("repository", zRepo);
3557 db_record_repository_filename(zRepo);
3558 db_set_checkout(0);
3559 azNewArgv[0] = g.argv[0];
3560 g.argv = azNewArgv;
@@ -3376,12 +3643,13 @@
3643 const char *name; /* Name of the setting */
3644 const char *var; /* Internal variable name used by db_set() */
3645 int width; /* Width of display. 0 for boolean values and
3646 ** negative for values which should not appear
3647 ** on the /setup_settings page. */
3648 char versionable; /* Is this setting versionable? */
3649 char forceTextArea; /* Force using a text area for display? */
3650 char sensitive; /* True if this a security-sensitive setting */
3651 const char *def; /* Default value */
3652 };
3653 #endif /* INTERFACE */
3654
3655 /*
@@ -3395,32 +3663,29 @@
3663 ** SETTING: admin-log boolean default=off
3664 **
3665 ** When the admin-log setting is enabled, configuration changes are recorded
3666 ** in the "admin_log" table of the repository.
3667 */
3668 /*
3669 ** SETTING: allow-symlinks boolean default=off sensitive
3670 **
3671 ** When allow-symlinks is OFF, Fossil does not see symbolic links
3672 ** (a.k.a "symlinks") on disk as a separate class of object. Instead Fossil
3673 ** sees the object that the symlink points to. Fossil will only manage files
3674 ** and directories, not symlinks. When a symlink is added to a repository,
3675 ** the object that the symlink points to is added, not the symlink itself.
3676 **
3677 ** When allow-symlinks is ON, Fossil sees symlinks on disk as a separate
3678 ** object class that is distinct from files and directories. When a symlink
3679 ** is added to a repository, Fossil stores the target filename. In other
3680 ** words, Fossil stores the symlink itself, not the object that the symlink
3681 ** points to.
3682 **
3683 ** Symlinks are not cross-platform. They are not available on all
3684 ** operating systems and file systems. Hence the allow-symlinks setting is
3685 ** OFF by default, for portability.
3686 */
 
 
 
3687 /*
3688 ** SETTING: auto-captcha boolean default=on variable=autocaptcha
3689 ** If enabled, the /login page provides a button that will automatically
3690 ** fill in the captcha password. This makes things easier for human users,
3691 ** at the expense of also making logins easier for malicious robots.
@@ -3470,11 +3735,11 @@
3735 ** there is no cron job periodically running "fossil backoffice",
3736 ** email notifications and other work normally done by the
3737 ** backoffice will not occur.
3738 */
3739 /*
3740 ** SETTING: backoffice-logfile width=40 sensitive
3741 ** If backoffice-logfile is not an empty string and is a valid
3742 ** filename, then a one-line message is appended to that file
3743 ** every time the backoffice runs. This can be used for debugging,
3744 ** to ensure that backoffice is running appropriately.
3745 */
@@ -3547,11 +3812,11 @@
3812 /*
3813 ** SETTING: crnl-glob width=40 versionable block-text
3814 ** This is an alias for the crlf-glob setting.
3815 */
3816 /*
3817 ** SETTING: default-perms width=16 default=u sensitive
3818 ** Permissions given automatically to new users. For more
3819 ** information on permissions see the Users page in Server
3820 ** Administration of the HTTP UI.
3821 */
3822 /*
@@ -3559,11 +3824,11 @@
3824 ** If enabled, permit files that may be binary
3825 ** or that match the "binary-glob" setting to be used with
3826 ** external diff programs. If disabled, skip these files.
3827 */
3828 /*
3829 ** SETTING: diff-command width=40 sensitive
3830 ** The value is an external command to run when performing a diff.
3831 ** If undefined, the internal text diff will be used.
3832 */
3833 /*
3834 ** SETTING: dont-push boolean default=off
@@ -3574,11 +3839,11 @@
3839 /*
3840 ** SETTING: dotfiles boolean versionable default=off
3841 ** If enabled, include --dotfiles option for all compatible commands.
3842 */
3843 /*
3844 ** SETTING: editor width=32 sensitive
3845 ** The value is an external command that will launch the
3846 ** text editor command used for check-in comments.
3847 */
3848 /*
3849 ** SETTING: empty-dirs width=40 versionable block-text
@@ -3617,16 +3882,16 @@
3882 ** An empty list prohibits editing via that page. Note that
3883 ** it cannot edit binary files, so the list should not
3884 ** contain any globs for, e.g., images or PDFs.
3885 */
3886 /*
3887 ** SETTING: gdiff-command width=40 default=gdiff sensitive
3888 ** The value is an external command to run when performing a graphical
3889 ** diff. If undefined, text diff will be used.
3890 */
3891 /*
3892 ** SETTING: gmerge-command width=40 sensitive
3893 ** The value is a graphical merge conflict resolver command operating
3894 ** on four files. Examples:
3895 **
3896 ** kdiff3 "%baseline" "%original" "%merge" -o "%output"
3897 ** xxdiff "%original" "%baseline" "%merge" -M "%output"
@@ -3757,11 +4022,11 @@
4022 ** the associated files within the checkout -AND- the "rm"
4023 ** and "delete" commands will also remove the associated
4024 ** files from within the checkout.
4025 */
4026 /*
4027 ** SETTING: pgp-command width=40 sensitive
4028 ** Command used to clear-sign manifests at check-in.
4029 ** Default value is "gpg --clearsign -o"
4030 */
4031 /*
4032 ** SETTING: forbid-delta-manifests boolean default=off
@@ -3817,22 +4082,22 @@
4082 **
4083 ** If repolist-skin has a value of 2, then the repository is omitted from
4084 ** the list in use cases 1 through 4, but not for 5 and 6.
4085 */
4086 /*
4087 ** SETTING: self-register boolean default=off sensitive
4088 ** Allow users to register themselves through the HTTP UI.
4089 ** This is useful if you want to see other names than
4090 ** "Anonymous" in e.g. ticketing system. On the other hand
4091 ** users can not be deleted.
4092 */
4093 /*
4094 ** SETTING: ssh-command width=40 sensitive
4095 ** The command used to talk to a remote machine with the "ssh://" protocol.
4096 */
4097 /*
4098 ** SETTING: ssl-ca-location width=40 sensitive
4099 ** The full pathname to a file containing PEM encoded
4100 ** CA root certificates, or a directory of certificates
4101 ** with filenames formed from the certificate hashes as
4102 ** required by OpenSSL.
4103 **
@@ -3842,11 +4107,11 @@
4107 ** Checking your platform behaviour is required if the
4108 ** exact contents of the CA root is critical for your
4109 ** application.
4110 */
4111 /*
4112 ** SETTING: ssl-identity width=40 sensitive
4113 ** The full pathname to a file containing a certificate
4114 ** and private key in PEM format. Create by concatenating
4115 ** the certificate and private key files.
4116 **
4117 ** This identity will be presented to SSL servers to
@@ -3853,33 +4118,33 @@
4118 ** authenticate this client, in addition to the normal
4119 ** password authentication.
4120 */
4121 #ifdef FOSSIL_ENABLE_TCL
4122 /*
4123 ** SETTING: tcl boolean default=off sensitive
4124 ** If enabled Tcl integration commands will be added to the TH1
4125 ** interpreter, allowing arbitrary Tcl expressions and
4126 ** scripts to be evaluated from TH1. Additionally, the Tcl
4127 ** interpreter will be able to evaluate arbitrary TH1
4128 ** expressions and scripts.
4129 */
4130 /*
4131 ** SETTING: tcl-setup width=40 block-text sensitive
4132 ** This is the setup script to be evaluated after creating
4133 ** and initializing the Tcl interpreter. By default, this
4134 ** is empty and no extra setup is performed.
4135 */
4136 #endif /* FOSSIL_ENABLE_TCL */
4137 /*
4138 ** SETTING: tclsh width=80 default=tclsh sensitive
4139 ** Name of the external TCL interpreter used for such things
4140 ** as running the GUI diff viewer launched by the --tk option
4141 ** of the various "diff" commands.
4142 */
4143 #ifdef FOSSIL_ENABLE_TH1_DOCS
4144 /*
4145 ** SETTING: th1-docs boolean default=off sensitive
4146 ** If enabled, this allows embedded documentation files to contain
4147 ** arbitrary TH1 scripts that are evaluated on the server. If native
4148 ** Tcl integration is also enabled, this setting has the
4149 ** potential to allow anybody with check-in privileges to
4150 ** do almost anything that the associated operating system
@@ -3932,11 +4197,11 @@
4197 ** of a "fossil clone" or "fossil sync" command. The
4198 ** default is false, in which case the -u option is
4199 ** needed to clone or sync unversioned files.
4200 */
4201 /*
4202 ** SETTING: web-browser width=30 sensitive
4203 ** A shell command used to launch your preferred
4204 ** web browser when given a URL as an argument.
4205 ** Defaults to "start" on windows, "open" on Mac,
4206 ** and "firefox" on Unix.
4207 */
@@ -4058,11 +4323,13 @@
4323 fossil_fatal("cannot set 'manifest' globally");
4324 }
4325 if( unsetFlag ){
4326 db_unset(pSetting->name, globalFlag);
4327 }else{
4328 db_protect_only(PROTECT_NONE);
4329 db_set(pSetting->name, g.argv[3], globalFlag);
4330 db_protect_pop();
4331 }
4332 if( isManifest && g.localOpen ){
4333 manifest_to_disk(db_lget_int("checkout", 0));
4334 }
4335 }else{
4336
+176 -20
--- src/file.c
+++ src/file.c
@@ -47,22 +47,21 @@
4747
** used for files that are under management by a Fossil repository. ExtFILE
4848
** should be used for files that are not under management. SymFILE is for
4949
** a few special cases such as the "fossil test-tarball" command when we never
5050
** want to follow symlinks.
5151
**
52
-** If RepoFILE is used and if the allow-symlinks setting is true and if
53
-** the object is a symbolic link, then the object is treated like an ordinary
54
-** file whose content is name of the object to which the symbolic link
55
-** points.
56
-**
57
-** If ExtFILE is used or allow-symlinks is false, then operations on a
58
-** symbolic link are the same as operations on the object to which the
59
-** symbolic link points.
60
-**
61
-** SymFILE is like RepoFILE except that it always uses the target filename of
62
-** a symbolic link as the content, instead of the content of the object
63
-** that the symlink points to. SymFILE acts as if allow-symlinks is always ON.
52
+** ExtFILE Symbolic links always refer to the object to which the
53
+** link points. Symlinks are never recognized as symlinks but
54
+** instead always appear to the the target object.
55
+**
56
+** SymFILE Symbolic links always appear to be files whose name is
57
+** the target pathname of the symbolic link.
58
+**
59
+** RepoFILE Like symfile is allow-symlinks is true, or like
60
+** ExtFile if allow-symlinks is false. In other words,
61
+** symbolic links are only recognized as something different
62
+** from files or directories if allow-symlinks is true.
6463
*/
6564
#define ExtFILE 0 /* Always follow symlinks */
6665
#define RepoFILE 1 /* Follow symlinks if and only if allow-symlinks is OFF */
6766
#define SymFILE 2 /* Never follow symlinks */
6867
@@ -134,13 +133,16 @@
134133
int eFType /* Look at symlink itself if RepoFILE and enabled. */
135134
){
136135
int rc;
137136
void *zMbcs = fossil_utf8_to_path(zFilename, 0);
138137
#if !defined(_WIN32)
139
- if( eFType>=RepoFILE && (eFType==SymFILE || db_allow_symlinks()) ){
138
+ if( (eFType=RepoFILE && db_allow_symlinks())
139
+ || eFType==SymFILE ){
140
+ /* Symlinks look like files whose content is the name of the target */
140141
rc = lstat(zMbcs, buf);
141142
}else{
143
+ /* Symlinks look like the object to which they point */
142144
rc = stat(zMbcs, buf);
143145
}
144146
#else
145147
rc = win32_stat(zMbcs, buf, eFType);
146148
#endif
@@ -316,17 +318,90 @@
316318
317319
/*
318320
** Return TRUE if the named file is a symlink and symlinks are allowed.
319321
** Return false for all other cases.
320322
**
321
-** This routines RepoFILE - that zFilename is always a file under management.
323
+** This routines assumes RepoFILE - that zFilename is always a file
324
+** under management.
322325
**
323326
** On Windows, always return False.
324327
*/
325328
int file_islink(const char *zFilename){
326329
return file_perm(zFilename, RepoFILE)==PERM_LNK;
327330
}
331
+
332
+/*
333
+** Check every sub-directory of zRoot along the path to zFile.
334
+** If any sub-directory is really an ordinary file or a symbolic link,
335
+** return an integer which is the length of the prefix of zFile which
336
+** is the name of that object. Return 0 if all no non-directory
337
+** objects are found along the path.
338
+**
339
+** Example: Given inputs
340
+**
341
+** zRoot = /home/alice/project1
342
+** zFile = /home/alice/project1/main/src/js/fileA.js
343
+**
344
+** Look for objects in the following order:
345
+**
346
+** /home/alice/project/main
347
+** /home/alice/project/main/src
348
+** /home/alice/project/main/src/js
349
+**
350
+** If any of those objects exist and are something other than a directory
351
+** then return the length of the name of the first non-directory object
352
+** seen.
353
+*/
354
+int file_nondir_objects_on_path(const char *zRoot, const char *zFile){
355
+ int i = (int)strlen(zRoot);
356
+ char *z = fossil_strdup(zFile);
357
+ assert( fossil_strnicmp(zRoot, z, i)==0 );
358
+ if( i && zRoot[i-1]=='/' ) i--;
359
+ while( z[i]=='/' ){
360
+ int j, rc;
361
+ for(j=i+1; z[j] && z[j]!='/'; j++){}
362
+ if( z[j]!='/' ) break;
363
+ z[j] = 0;
364
+ rc = file_isdir(z, SymFILE);
365
+ if( rc!=1 ){
366
+ if( rc==2 ){
367
+ fossil_free(z);
368
+ return j;
369
+ }
370
+ break;
371
+ }
372
+ z[j] = '/';
373
+ i = j;
374
+ }
375
+ fossil_free(z);
376
+ return 0;
377
+}
378
+
379
+/*
380
+** The file named zFile is suppose to be an in-tree file. Check to
381
+** ensure that it will be safe to write to this file by verifying that
382
+** there are no symlinks or other non-directory objects in between the
383
+** root of the checkout and zFile.
384
+**
385
+** If a problem is found, print a warning message (using fossil_warning())
386
+** and return non-zero. If everything is ok, return zero.
387
+*/
388
+int file_unsafe_in_tree_path(const char *zFile){
389
+ int n;
390
+ if( !file_is_absolute_path(zFile) ){
391
+ fossil_panic("%s is not an absolute pathname",zFile);
392
+ }
393
+ if( fossil_strnicmp(g.zLocalRoot, zFile, (int)strlen(g.zLocalRoot)) ){
394
+ fossil_panic("%s is not a prefix of %s", g.zLocalRoot, zFile);
395
+ }
396
+ n = file_nondir_objects_on_path(g.zLocalRoot, zFile);
397
+ if( n ){
398
+ fossil_warning("cannot write to %s because non-directory object %.*s"
399
+ " is in the way", zFile, n, zFile);
400
+ }
401
+ return n;
402
+}
328403
329404
/*
330405
** Return 1 if zFilename is a directory. Return 0 if zFilename
331406
** does not exist. Return 2 if zFilename exists but is something
332407
** other than a directory.
@@ -570,11 +645,14 @@
570645
*/
571646
int file_setexe(const char *zFilename, int onoff){
572647
int rc = 0;
573648
#if !defined(_WIN32)
574649
struct stat buf;
575
- if( fossil_stat(zFilename, &buf, RepoFILE)!=0 || S_ISLNK(buf.st_mode) ){
650
+ if( fossil_stat(zFilename, &buf, RepoFILE)!=0
651
+ || S_ISLNK(buf.st_mode)
652
+ || S_ISDIR(buf.st_mode)
653
+ ){
576654
return 0;
577655
}
578656
if( onoff ){
579657
int targetMode = (buf.st_mode & 0444)>>2;
580658
if( (buf.st_mode & 0100)==0 ){
@@ -1236,12 +1314,12 @@
12361314
sqlite3_int64 iMtime;
12371315
struct fossilStat testFileStat;
12381316
memset(zBuf, 0, sizeof(zBuf));
12391317
blob_zero(&x);
12401318
file_canonical_name(zPath, &x, slash);
1241
- fossil_print("[%s] -> [%s]\n", zPath, blob_buffer(&x));
1242
- blob_reset(&x);
1319
+ char *zFull = blob_str(&x);
1320
+ fossil_print("[%s] -> [%s]\n", zPath, zFull);
12431321
memset(&testFileStat, 0, sizeof(struct fossilStat));
12441322
rc = fossil_stat(zPath, &testFileStat, 0);
12451323
fossil_print(" stat_rc = %d\n", rc);
12461324
sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size);
12471325
fossil_print(" stat_size = %s\n", zBuf);
@@ -1285,10 +1363,13 @@
12851363
fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zPath));
12861364
fossil_print(" file_islink = %d\n", file_islink(zPath));
12871365
fossil_print(" file_isexe(RepoFILE) = %d\n", file_isexe(zPath,RepoFILE));
12881366
fossil_print(" file_isdir(RepoFILE) = %d\n", file_isdir(zPath,RepoFILE));
12891367
fossil_print(" file_is_repository = %d\n", file_is_repository(zPath));
1368
+ fossil_print(" file_is_reserved_name = %d\n",
1369
+ file_is_reserved_name(zFull,-1));
1370
+ blob_reset(&x);
12901371
if( reset ) resetStat();
12911372
}
12921373
12931374
/*
12941375
** COMMAND: test-file-environment
@@ -1300,32 +1381,45 @@
13001381
**
13011382
** Options:
13021383
**
13031384
** --allow-symlinks BOOLEAN Temporarily turn allow-symlinks on/off
13041385
** --open-config Open the configuration database first.
1305
-** --slash Trailing slashes, if any, are retained.
13061386
** --reset Reset cached stat() info for each file.
1387
+** --root ROOT Use ROOT as the root of the checkout
1388
+** --slash Trailing slashes, if any, are retained.
13071389
*/
13081390
void cmd_test_file_environment(void){
13091391
int i;
13101392
int slashFlag = find_option("slash",0,0)!=0;
13111393
int resetFlag = find_option("reset",0,0)!=0;
1394
+ const char *zRoot = find_option("root",0,1);
13121395
const char *zAllow = find_option("allow-symlinks",0,1);
13131396
if( find_option("open-config", 0, 0)!=0 ){
13141397
Th_OpenConfig(1);
13151398
}
13161399
db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
13171400
fossil_print("filenames_are_case_sensitive() = %d\n",
13181401
filenames_are_case_sensitive());
1319
- fossil_print("db_allow_symlinks_by_default() = %d\n",
1320
- db_allow_symlinks_by_default());
13211402
if( zAllow ){
13221403
g.allowSymlinks = !is_false(zAllow);
13231404
}
1405
+ if( zRoot==0 ) zRoot = g.zLocalRoot;
13241406
fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
1407
+ fossil_print("local-root = [%s]\n", zRoot);
13251408
for(i=2; i<g.argc; i++){
1409
+ char *z;
13261410
emitFileStat(g.argv[i], slashFlag, resetFlag);
1411
+ z = file_canonical_name_dup(g.argv[i]);
1412
+ fossil_print(" file_canonical_name = %s\n", z);
1413
+ fossil_print(" file_nondir_path = ");
1414
+ if( fossil_strnicmp(zRoot,z,(int)strlen(zRoot))!=0 ){
1415
+ fossil_print("(--root is not a prefix of this file)\n");
1416
+ }else{
1417
+ int n = file_nondir_objects_on_path(zRoot, z);
1418
+ fossil_print("%.*s\n", n, z);
1419
+ }
1420
+ fossil_free(z);
13271421
}
13281422
}
13291423
13301424
/*
13311425
** COMMAND: test-canonical-name
@@ -2408,5 +2502,67 @@
24082502
*/
24092503
const char * file_extension(const char *zFileName){
24102504
const char * zExt = zFileName ? strrchr(zFileName, '.') : 0;
24112505
return zExt ? &zExt[1] : 0;
24122506
}
2507
+
2508
+/*
2509
+** Returns non-zero if the specified file name ends with any reserved name,
2510
+** e.g.: _FOSSIL_ or .fslckout. Specifically, it returns 1 for exact match
2511
+** or 2 for a tail match on a longer file name.
2512
+**
2513
+** For the sake of efficiency, zFilename must be a canonical name, e.g. an
2514
+** absolute path using only forward slash ('/') as a directory separator.
2515
+**
2516
+** nFilename must be the length of zFilename. When negative, strlen() will
2517
+** be used to calculate it.
2518
+*/
2519
+int file_is_reserved_name(const char *zFilename, int nFilename){
2520
+ const char *zEnd; /* one-after-the-end of zFilename */
2521
+ int gotSuffix = 0; /* length of suffix (-wal, -shm, -journal) */
2522
+
2523
+ assert( zFilename && "API misuse" );
2524
+ if( nFilename<0 ) nFilename = (int)strlen(zFilename);
2525
+ if( nFilename<8 ) return 0; /* strlen("_FOSSIL_") */
2526
+ zEnd = zFilename + nFilename;
2527
+ if( nFilename>=12 ){ /* strlen("_FOSSIL_-(shm|wal)") */
2528
+ /* Check for (-wal, -shm, -journal) suffixes, with an eye towards
2529
+ ** runtime speed. */
2530
+ if( zEnd[-4]=='-' ){
2531
+ if( fossil_strnicmp("wal", &zEnd[-3], 3)
2532
+ && fossil_strnicmp("shm", &zEnd[-3], 3) ){
2533
+ return 0;
2534
+ }
2535
+ gotSuffix = 4;
2536
+ }else if( nFilename>=16 && zEnd[-8]=='-' ){ /*strlen(_FOSSIL_-journal) */
2537
+ if( fossil_strnicmp("journal", &zEnd[-7], 7) ) return 0;
2538
+ gotSuffix = 8;
2539
+ }
2540
+ if( gotSuffix ){
2541
+ assert( 4==gotSuffix || 8==gotSuffix );
2542
+ zEnd -= gotSuffix;
2543
+ nFilename -= gotSuffix;
2544
+ gotSuffix = 1;
2545
+ }
2546
+ assert( nFilename>=8 && "strlen(_FOSSIL_)" );
2547
+ assert( gotSuffix==0 || gotSuffix==1 );
2548
+ }
2549
+ switch( zEnd[-1] ){
2550
+ case '_':{
2551
+ if( fossil_strnicmp("_FOSSIL_", &zEnd[-8], 8) ) return 0;
2552
+ if( 8==nFilename ) return 1;
2553
+ return zEnd[-9]=='/' ? 2 : gotSuffix;
2554
+ }
2555
+ case 'T':
2556
+ case 't':{
2557
+ if( nFilename<9 || zEnd[-9]!='.'
2558
+ || fossil_strnicmp(".fslckout", &zEnd[-9], 9) ){
2559
+ return 0;
2560
+ }
2561
+ if( 9==nFilename ) return 1;
2562
+ return zEnd[-10]=='/' ? 2 : gotSuffix;
2563
+ }
2564
+ default:{
2565
+ return 0;
2566
+ }
2567
+ }
2568
+}
24132569
--- src/file.c
+++ src/file.c
@@ -47,22 +47,21 @@
47 ** used for files that are under management by a Fossil repository. ExtFILE
48 ** should be used for files that are not under management. SymFILE is for
49 ** a few special cases such as the "fossil test-tarball" command when we never
50 ** want to follow symlinks.
51 **
52 ** If RepoFILE is used and if the allow-symlinks setting is true and if
53 ** the object is a symbolic link, then the object is treated like an ordinary
54 ** file whose content is name of the object to which the symbolic link
55 ** points.
56 **
57 ** If ExtFILE is used or allow-symlinks is false, then operations on a
58 ** symbolic link are the same as operations on the object to which the
59 ** symbolic link points.
60 **
61 ** SymFILE is like RepoFILE except that it always uses the target filename of
62 ** a symbolic link as the content, instead of the content of the object
63 ** that the symlink points to. SymFILE acts as if allow-symlinks is always ON.
64 */
65 #define ExtFILE 0 /* Always follow symlinks */
66 #define RepoFILE 1 /* Follow symlinks if and only if allow-symlinks is OFF */
67 #define SymFILE 2 /* Never follow symlinks */
68
@@ -134,13 +133,16 @@
134 int eFType /* Look at symlink itself if RepoFILE and enabled. */
135 ){
136 int rc;
137 void *zMbcs = fossil_utf8_to_path(zFilename, 0);
138 #if !defined(_WIN32)
139 if( eFType>=RepoFILE && (eFType==SymFILE || db_allow_symlinks()) ){
 
 
140 rc = lstat(zMbcs, buf);
141 }else{
 
142 rc = stat(zMbcs, buf);
143 }
144 #else
145 rc = win32_stat(zMbcs, buf, eFType);
146 #endif
@@ -316,17 +318,90 @@
316
317 /*
318 ** Return TRUE if the named file is a symlink and symlinks are allowed.
319 ** Return false for all other cases.
320 **
321 ** This routines RepoFILE - that zFilename is always a file under management.
 
322 **
323 ** On Windows, always return False.
324 */
325 int file_islink(const char *zFilename){
326 return file_perm(zFilename, RepoFILE)==PERM_LNK;
327 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
329 /*
330 ** Return 1 if zFilename is a directory. Return 0 if zFilename
331 ** does not exist. Return 2 if zFilename exists but is something
332 ** other than a directory.
@@ -570,11 +645,14 @@
570 */
571 int file_setexe(const char *zFilename, int onoff){
572 int rc = 0;
573 #if !defined(_WIN32)
574 struct stat buf;
575 if( fossil_stat(zFilename, &buf, RepoFILE)!=0 || S_ISLNK(buf.st_mode) ){
 
 
 
576 return 0;
577 }
578 if( onoff ){
579 int targetMode = (buf.st_mode & 0444)>>2;
580 if( (buf.st_mode & 0100)==0 ){
@@ -1236,12 +1314,12 @@
1236 sqlite3_int64 iMtime;
1237 struct fossilStat testFileStat;
1238 memset(zBuf, 0, sizeof(zBuf));
1239 blob_zero(&x);
1240 file_canonical_name(zPath, &x, slash);
1241 fossil_print("[%s] -> [%s]\n", zPath, blob_buffer(&x));
1242 blob_reset(&x);
1243 memset(&testFileStat, 0, sizeof(struct fossilStat));
1244 rc = fossil_stat(zPath, &testFileStat, 0);
1245 fossil_print(" stat_rc = %d\n", rc);
1246 sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size);
1247 fossil_print(" stat_size = %s\n", zBuf);
@@ -1285,10 +1363,13 @@
1285 fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zPath));
1286 fossil_print(" file_islink = %d\n", file_islink(zPath));
1287 fossil_print(" file_isexe(RepoFILE) = %d\n", file_isexe(zPath,RepoFILE));
1288 fossil_print(" file_isdir(RepoFILE) = %d\n", file_isdir(zPath,RepoFILE));
1289 fossil_print(" file_is_repository = %d\n", file_is_repository(zPath));
 
 
 
1290 if( reset ) resetStat();
1291 }
1292
1293 /*
1294 ** COMMAND: test-file-environment
@@ -1300,32 +1381,45 @@
1300 **
1301 ** Options:
1302 **
1303 ** --allow-symlinks BOOLEAN Temporarily turn allow-symlinks on/off
1304 ** --open-config Open the configuration database first.
1305 ** --slash Trailing slashes, if any, are retained.
1306 ** --reset Reset cached stat() info for each file.
 
 
1307 */
1308 void cmd_test_file_environment(void){
1309 int i;
1310 int slashFlag = find_option("slash",0,0)!=0;
1311 int resetFlag = find_option("reset",0,0)!=0;
 
1312 const char *zAllow = find_option("allow-symlinks",0,1);
1313 if( find_option("open-config", 0, 0)!=0 ){
1314 Th_OpenConfig(1);
1315 }
1316 db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
1317 fossil_print("filenames_are_case_sensitive() = %d\n",
1318 filenames_are_case_sensitive());
1319 fossil_print("db_allow_symlinks_by_default() = %d\n",
1320 db_allow_symlinks_by_default());
1321 if( zAllow ){
1322 g.allowSymlinks = !is_false(zAllow);
1323 }
 
1324 fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
 
1325 for(i=2; i<g.argc; i++){
 
1326 emitFileStat(g.argv[i], slashFlag, resetFlag);
 
 
 
 
 
 
 
 
 
 
1327 }
1328 }
1329
1330 /*
1331 ** COMMAND: test-canonical-name
@@ -2408,5 +2502,67 @@
2408 */
2409 const char * file_extension(const char *zFileName){
2410 const char * zExt = zFileName ? strrchr(zFileName, '.') : 0;
2411 return zExt ? &zExt[1] : 0;
2412 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2413
--- src/file.c
+++ src/file.c
@@ -47,22 +47,21 @@
47 ** used for files that are under management by a Fossil repository. ExtFILE
48 ** should be used for files that are not under management. SymFILE is for
49 ** a few special cases such as the "fossil test-tarball" command when we never
50 ** want to follow symlinks.
51 **
52 ** ExtFILE Symbolic links always refer to the object to which the
53 ** link points. Symlinks are never recognized as symlinks but
54 ** instead always appear to the the target object.
55 **
56 ** SymFILE Symbolic links always appear to be files whose name is
57 ** the target pathname of the symbolic link.
58 **
59 ** RepoFILE Like symfile is allow-symlinks is true, or like
60 ** ExtFile if allow-symlinks is false. In other words,
61 ** symbolic links are only recognized as something different
62 ** from files or directories if allow-symlinks is true.
 
63 */
64 #define ExtFILE 0 /* Always follow symlinks */
65 #define RepoFILE 1 /* Follow symlinks if and only if allow-symlinks is OFF */
66 #define SymFILE 2 /* Never follow symlinks */
67
@@ -134,13 +133,16 @@
133 int eFType /* Look at symlink itself if RepoFILE and enabled. */
134 ){
135 int rc;
136 void *zMbcs = fossil_utf8_to_path(zFilename, 0);
137 #if !defined(_WIN32)
138 if( (eFType=RepoFILE && db_allow_symlinks())
139 || eFType==SymFILE ){
140 /* Symlinks look like files whose content is the name of the target */
141 rc = lstat(zMbcs, buf);
142 }else{
143 /* Symlinks look like the object to which they point */
144 rc = stat(zMbcs, buf);
145 }
146 #else
147 rc = win32_stat(zMbcs, buf, eFType);
148 #endif
@@ -316,17 +318,90 @@
318
319 /*
320 ** Return TRUE if the named file is a symlink and symlinks are allowed.
321 ** Return false for all other cases.
322 **
323 ** This routines assumes RepoFILE - that zFilename is always a file
324 ** under management.
325 **
326 ** On Windows, always return False.
327 */
328 int file_islink(const char *zFilename){
329 return file_perm(zFilename, RepoFILE)==PERM_LNK;
330 }
331
332 /*
333 ** Check every sub-directory of zRoot along the path to zFile.
334 ** If any sub-directory is really an ordinary file or a symbolic link,
335 ** return an integer which is the length of the prefix of zFile which
336 ** is the name of that object. Return 0 if all no non-directory
337 ** objects are found along the path.
338 **
339 ** Example: Given inputs
340 **
341 ** zRoot = /home/alice/project1
342 ** zFile = /home/alice/project1/main/src/js/fileA.js
343 **
344 ** Look for objects in the following order:
345 **
346 ** /home/alice/project/main
347 ** /home/alice/project/main/src
348 ** /home/alice/project/main/src/js
349 **
350 ** If any of those objects exist and are something other than a directory
351 ** then return the length of the name of the first non-directory object
352 ** seen.
353 */
354 int file_nondir_objects_on_path(const char *zRoot, const char *zFile){
355 int i = (int)strlen(zRoot);
356 char *z = fossil_strdup(zFile);
357 assert( fossil_strnicmp(zRoot, z, i)==0 );
358 if( i && zRoot[i-1]=='/' ) i--;
359 while( z[i]=='/' ){
360 int j, rc;
361 for(j=i+1; z[j] && z[j]!='/'; j++){}
362 if( z[j]!='/' ) break;
363 z[j] = 0;
364 rc = file_isdir(z, SymFILE);
365 if( rc!=1 ){
366 if( rc==2 ){
367 fossil_free(z);
368 return j;
369 }
370 break;
371 }
372 z[j] = '/';
373 i = j;
374 }
375 fossil_free(z);
376 return 0;
377 }
378
379 /*
380 ** The file named zFile is suppose to be an in-tree file. Check to
381 ** ensure that it will be safe to write to this file by verifying that
382 ** there are no symlinks or other non-directory objects in between the
383 ** root of the checkout and zFile.
384 **
385 ** If a problem is found, print a warning message (using fossil_warning())
386 ** and return non-zero. If everything is ok, return zero.
387 */
388 int file_unsafe_in_tree_path(const char *zFile){
389 int n;
390 if( !file_is_absolute_path(zFile) ){
391 fossil_panic("%s is not an absolute pathname",zFile);
392 }
393 if( fossil_strnicmp(g.zLocalRoot, zFile, (int)strlen(g.zLocalRoot)) ){
394 fossil_panic("%s is not a prefix of %s", g.zLocalRoot, zFile);
395 }
396 n = file_nondir_objects_on_path(g.zLocalRoot, zFile);
397 if( n ){
398 fossil_warning("cannot write to %s because non-directory object %.*s"
399 " is in the way", zFile, n, zFile);
400 }
401 return n;
402 }
403
404 /*
405 ** Return 1 if zFilename is a directory. Return 0 if zFilename
406 ** does not exist. Return 2 if zFilename exists but is something
407 ** other than a directory.
@@ -570,11 +645,14 @@
645 */
646 int file_setexe(const char *zFilename, int onoff){
647 int rc = 0;
648 #if !defined(_WIN32)
649 struct stat buf;
650 if( fossil_stat(zFilename, &buf, RepoFILE)!=0
651 || S_ISLNK(buf.st_mode)
652 || S_ISDIR(buf.st_mode)
653 ){
654 return 0;
655 }
656 if( onoff ){
657 int targetMode = (buf.st_mode & 0444)>>2;
658 if( (buf.st_mode & 0100)==0 ){
@@ -1236,12 +1314,12 @@
1314 sqlite3_int64 iMtime;
1315 struct fossilStat testFileStat;
1316 memset(zBuf, 0, sizeof(zBuf));
1317 blob_zero(&x);
1318 file_canonical_name(zPath, &x, slash);
1319 char *zFull = blob_str(&x);
1320 fossil_print("[%s] -> [%s]\n", zPath, zFull);
1321 memset(&testFileStat, 0, sizeof(struct fossilStat));
1322 rc = fossil_stat(zPath, &testFileStat, 0);
1323 fossil_print(" stat_rc = %d\n", rc);
1324 sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size);
1325 fossil_print(" stat_size = %s\n", zBuf);
@@ -1285,10 +1363,13 @@
1363 fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zPath));
1364 fossil_print(" file_islink = %d\n", file_islink(zPath));
1365 fossil_print(" file_isexe(RepoFILE) = %d\n", file_isexe(zPath,RepoFILE));
1366 fossil_print(" file_isdir(RepoFILE) = %d\n", file_isdir(zPath,RepoFILE));
1367 fossil_print(" file_is_repository = %d\n", file_is_repository(zPath));
1368 fossil_print(" file_is_reserved_name = %d\n",
1369 file_is_reserved_name(zFull,-1));
1370 blob_reset(&x);
1371 if( reset ) resetStat();
1372 }
1373
1374 /*
1375 ** COMMAND: test-file-environment
@@ -1300,32 +1381,45 @@
1381 **
1382 ** Options:
1383 **
1384 ** --allow-symlinks BOOLEAN Temporarily turn allow-symlinks on/off
1385 ** --open-config Open the configuration database first.
 
1386 ** --reset Reset cached stat() info for each file.
1387 ** --root ROOT Use ROOT as the root of the checkout
1388 ** --slash Trailing slashes, if any, are retained.
1389 */
1390 void cmd_test_file_environment(void){
1391 int i;
1392 int slashFlag = find_option("slash",0,0)!=0;
1393 int resetFlag = find_option("reset",0,0)!=0;
1394 const char *zRoot = find_option("root",0,1);
1395 const char *zAllow = find_option("allow-symlinks",0,1);
1396 if( find_option("open-config", 0, 0)!=0 ){
1397 Th_OpenConfig(1);
1398 }
1399 db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
1400 fossil_print("filenames_are_case_sensitive() = %d\n",
1401 filenames_are_case_sensitive());
 
 
1402 if( zAllow ){
1403 g.allowSymlinks = !is_false(zAllow);
1404 }
1405 if( zRoot==0 ) zRoot = g.zLocalRoot;
1406 fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
1407 fossil_print("local-root = [%s]\n", zRoot);
1408 for(i=2; i<g.argc; i++){
1409 char *z;
1410 emitFileStat(g.argv[i], slashFlag, resetFlag);
1411 z = file_canonical_name_dup(g.argv[i]);
1412 fossil_print(" file_canonical_name = %s\n", z);
1413 fossil_print(" file_nondir_path = ");
1414 if( fossil_strnicmp(zRoot,z,(int)strlen(zRoot))!=0 ){
1415 fossil_print("(--root is not a prefix of this file)\n");
1416 }else{
1417 int n = file_nondir_objects_on_path(zRoot, z);
1418 fossil_print("%.*s\n", n, z);
1419 }
1420 fossil_free(z);
1421 }
1422 }
1423
1424 /*
1425 ** COMMAND: test-canonical-name
@@ -2408,5 +2502,67 @@
2502 */
2503 const char * file_extension(const char *zFileName){
2504 const char * zExt = zFileName ? strrchr(zFileName, '.') : 0;
2505 return zExt ? &zExt[1] : 0;
2506 }
2507
2508 /*
2509 ** Returns non-zero if the specified file name ends with any reserved name,
2510 ** e.g.: _FOSSIL_ or .fslckout. Specifically, it returns 1 for exact match
2511 ** or 2 for a tail match on a longer file name.
2512 **
2513 ** For the sake of efficiency, zFilename must be a canonical name, e.g. an
2514 ** absolute path using only forward slash ('/') as a directory separator.
2515 **
2516 ** nFilename must be the length of zFilename. When negative, strlen() will
2517 ** be used to calculate it.
2518 */
2519 int file_is_reserved_name(const char *zFilename, int nFilename){
2520 const char *zEnd; /* one-after-the-end of zFilename */
2521 int gotSuffix = 0; /* length of suffix (-wal, -shm, -journal) */
2522
2523 assert( zFilename && "API misuse" );
2524 if( nFilename<0 ) nFilename = (int)strlen(zFilename);
2525 if( nFilename<8 ) return 0; /* strlen("_FOSSIL_") */
2526 zEnd = zFilename + nFilename;
2527 if( nFilename>=12 ){ /* strlen("_FOSSIL_-(shm|wal)") */
2528 /* Check for (-wal, -shm, -journal) suffixes, with an eye towards
2529 ** runtime speed. */
2530 if( zEnd[-4]=='-' ){
2531 if( fossil_strnicmp("wal", &zEnd[-3], 3)
2532 && fossil_strnicmp("shm", &zEnd[-3], 3) ){
2533 return 0;
2534 }
2535 gotSuffix = 4;
2536 }else if( nFilename>=16 && zEnd[-8]=='-' ){ /*strlen(_FOSSIL_-journal) */
2537 if( fossil_strnicmp("journal", &zEnd[-7], 7) ) return 0;
2538 gotSuffix = 8;
2539 }
2540 if( gotSuffix ){
2541 assert( 4==gotSuffix || 8==gotSuffix );
2542 zEnd -= gotSuffix;
2543 nFilename -= gotSuffix;
2544 gotSuffix = 1;
2545 }
2546 assert( nFilename>=8 && "strlen(_FOSSIL_)" );
2547 assert( gotSuffix==0 || gotSuffix==1 );
2548 }
2549 switch( zEnd[-1] ){
2550 case '_':{
2551 if( fossil_strnicmp("_FOSSIL_", &zEnd[-8], 8) ) return 0;
2552 if( 8==nFilename ) return 1;
2553 return zEnd[-9]=='/' ? 2 : gotSuffix;
2554 }
2555 case 'T':
2556 case 't':{
2557 if( nFilename<9 || zEnd[-9]!='.'
2558 || fossil_strnicmp(".fslckout", &zEnd[-9], 9) ){
2559 return 0;
2560 }
2561 if( 9==nFilename ) return 1;
2562 return zEnd[-10]=='/' ? 2 : gotSuffix;
2563 }
2564 default:{
2565 return 0;
2566 }
2567 }
2568 }
2569
--- src/forum.c
+++ src/forum.c
@@ -1114,13 +1114,15 @@
11141114
moderation_approve('f', fpid);
11151115
if( g.perm.AdminForum
11161116
&& PB("trust")
11171117
&& (zUserToTrust = P("trustuser"))!=0
11181118
){
1119
+ db_unprotect(PROTECT_USER);
11191120
db_multi_exec("UPDATE user SET cap=cap||'4' "
11201121
"WHERE login=%Q AND cap NOT GLOB '*4*'",
11211122
zUserToTrust);
1123
+ db_protect_pop();
11221124
}
11231125
cgi_redirectf("%R/forumpost/%S",P("fpid"));
11241126
return;
11251127
}
11261128
if( P("reject") ){
11271129
--- src/forum.c
+++ src/forum.c
@@ -1114,13 +1114,15 @@
1114 moderation_approve('f', fpid);
1115 if( g.perm.AdminForum
1116 && PB("trust")
1117 && (zUserToTrust = P("trustuser"))!=0
1118 ){
 
1119 db_multi_exec("UPDATE user SET cap=cap||'4' "
1120 "WHERE login=%Q AND cap NOT GLOB '*4*'",
1121 zUserToTrust);
 
1122 }
1123 cgi_redirectf("%R/forumpost/%S",P("fpid"));
1124 return;
1125 }
1126 if( P("reject") ){
1127
--- src/forum.c
+++ src/forum.c
@@ -1114,13 +1114,15 @@
1114 moderation_approve('f', fpid);
1115 if( g.perm.AdminForum
1116 && PB("trust")
1117 && (zUserToTrust = P("trustuser"))!=0
1118 ){
1119 db_unprotect(PROTECT_USER);
1120 db_multi_exec("UPDATE user SET cap=cap||'4' "
1121 "WHERE login=%Q AND cap NOT GLOB '*4*'",
1122 zUserToTrust);
1123 db_protect_pop();
1124 }
1125 cgi_redirectf("%R/forumpost/%S",P("fpid"));
1126 return;
1127 }
1128 if( P("reject") ){
1129
+8
--- src/hook.c
+++ src/hook.c
@@ -123,15 +123,17 @@
123123
** If N==0, then there is no expectation of new artifacts arriving
124124
** soon and so post-receive hooks can be run without delay.
125125
*/
126126
void hook_expecting_more_artifacts(int N){
127127
if( N>0 ){
128
+ db_unprotect(PROTECT_CONFIG);
128129
db_multi_exec(
129130
"REPLACE INTO config(name,value,mtime)"
130131
"VALUES('hook-embargo',now()+%d,now())",
131132
N
132133
);
134
+ db_protect_pop();
133135
}else{
134136
db_unset("hook-embargo",0);
135137
}
136138
}
137139
@@ -243,10 +245,11 @@
243245
fossil_fatal("the --command and --type options are required");
244246
}
245247
validate_type(zType);
246248
nSeq = zSeq ? atoi(zSeq) : 10;
247249
db_begin_write();
250
+ db_unprotect(PROTECT_CONFIG);
248251
db_multi_exec(
249252
"INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
250253
"UPDATE config"
251254
" SET value=json_insert("
252255
" CASE WHEN json_valid(value) THEN value ELSE '[]' END,'$[#]',"
@@ -253,10 +256,11 @@
253256
" json_object('cmd',%Q,'type',%Q,'seq',%d)),"
254257
" mtime=now()"
255258
" WHERE name='hooks';",
256259
zCmd, zType, nSeq
257260
);
261
+ db_protect_pop();
258262
db_commit_transaction();
259263
}else
260264
if( strncmp(zCmd, "edit", nCmd)==0 ){
261265
const char *zCmd = find_option("command",0,1);
262266
const char *zType = find_option("type",0,1);
@@ -290,20 +294,23 @@
290294
}
291295
if( zSeq ){
292296
blob_append_sql(&sql, ",'$[%d].seq',%d", id, nSeq);
293297
}
294298
blob_append_sql(&sql,") WHERE name='hooks';");
299
+ db_unprotect(PROTECT_CONFIG);
295300
db_multi_exec("%s", blob_sql_text(&sql));
301
+ db_protect_pop();
296302
blob_reset(&sql);
297303
}
298304
db_commit_transaction();
299305
}else
300306
if( strncmp(zCmd, "delete", nCmd)==0 ){
301307
int i;
302308
verify_all_options();
303309
if( g.argc<4 ) usage("delete ID ...");
304310
db_begin_write();
311
+ db_unprotect(PROTECT_CONFIG);
305312
db_multi_exec(
306313
"INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
307314
);
308315
for(i=3; i<g.argc; i++){
309316
const char *zId = g.argv[i];
@@ -321,10 +328,11 @@
321328
" mtime=now()"
322329
" WHERE name='hooks';",
323330
atoi(zId)
324331
);
325332
}
333
+ db_protect_pop();
326334
db_commit_transaction();
327335
}else
328336
if( strncmp(zCmd, "list", nCmd)==0 ){
329337
Stmt q;
330338
int n = 0;
331339
--- src/hook.c
+++ src/hook.c
@@ -123,15 +123,17 @@
123 ** If N==0, then there is no expectation of new artifacts arriving
124 ** soon and so post-receive hooks can be run without delay.
125 */
126 void hook_expecting_more_artifacts(int N){
127 if( N>0 ){
 
128 db_multi_exec(
129 "REPLACE INTO config(name,value,mtime)"
130 "VALUES('hook-embargo',now()+%d,now())",
131 N
132 );
 
133 }else{
134 db_unset("hook-embargo",0);
135 }
136 }
137
@@ -243,10 +245,11 @@
243 fossil_fatal("the --command and --type options are required");
244 }
245 validate_type(zType);
246 nSeq = zSeq ? atoi(zSeq) : 10;
247 db_begin_write();
 
248 db_multi_exec(
249 "INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
250 "UPDATE config"
251 " SET value=json_insert("
252 " CASE WHEN json_valid(value) THEN value ELSE '[]' END,'$[#]',"
@@ -253,10 +256,11 @@
253 " json_object('cmd',%Q,'type',%Q,'seq',%d)),"
254 " mtime=now()"
255 " WHERE name='hooks';",
256 zCmd, zType, nSeq
257 );
 
258 db_commit_transaction();
259 }else
260 if( strncmp(zCmd, "edit", nCmd)==0 ){
261 const char *zCmd = find_option("command",0,1);
262 const char *zType = find_option("type",0,1);
@@ -290,20 +294,23 @@
290 }
291 if( zSeq ){
292 blob_append_sql(&sql, ",'$[%d].seq',%d", id, nSeq);
293 }
294 blob_append_sql(&sql,") WHERE name='hooks';");
 
295 db_multi_exec("%s", blob_sql_text(&sql));
 
296 blob_reset(&sql);
297 }
298 db_commit_transaction();
299 }else
300 if( strncmp(zCmd, "delete", nCmd)==0 ){
301 int i;
302 verify_all_options();
303 if( g.argc<4 ) usage("delete ID ...");
304 db_begin_write();
 
305 db_multi_exec(
306 "INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
307 );
308 for(i=3; i<g.argc; i++){
309 const char *zId = g.argv[i];
@@ -321,10 +328,11 @@
321 " mtime=now()"
322 " WHERE name='hooks';",
323 atoi(zId)
324 );
325 }
 
326 db_commit_transaction();
327 }else
328 if( strncmp(zCmd, "list", nCmd)==0 ){
329 Stmt q;
330 int n = 0;
331
--- src/hook.c
+++ src/hook.c
@@ -123,15 +123,17 @@
123 ** If N==0, then there is no expectation of new artifacts arriving
124 ** soon and so post-receive hooks can be run without delay.
125 */
126 void hook_expecting_more_artifacts(int N){
127 if( N>0 ){
128 db_unprotect(PROTECT_CONFIG);
129 db_multi_exec(
130 "REPLACE INTO config(name,value,mtime)"
131 "VALUES('hook-embargo',now()+%d,now())",
132 N
133 );
134 db_protect_pop();
135 }else{
136 db_unset("hook-embargo",0);
137 }
138 }
139
@@ -243,10 +245,11 @@
245 fossil_fatal("the --command and --type options are required");
246 }
247 validate_type(zType);
248 nSeq = zSeq ? atoi(zSeq) : 10;
249 db_begin_write();
250 db_unprotect(PROTECT_CONFIG);
251 db_multi_exec(
252 "INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
253 "UPDATE config"
254 " SET value=json_insert("
255 " CASE WHEN json_valid(value) THEN value ELSE '[]' END,'$[#]',"
@@ -253,10 +256,11 @@
256 " json_object('cmd',%Q,'type',%Q,'seq',%d)),"
257 " mtime=now()"
258 " WHERE name='hooks';",
259 zCmd, zType, nSeq
260 );
261 db_protect_pop();
262 db_commit_transaction();
263 }else
264 if( strncmp(zCmd, "edit", nCmd)==0 ){
265 const char *zCmd = find_option("command",0,1);
266 const char *zType = find_option("type",0,1);
@@ -290,20 +294,23 @@
294 }
295 if( zSeq ){
296 blob_append_sql(&sql, ",'$[%d].seq',%d", id, nSeq);
297 }
298 blob_append_sql(&sql,") WHERE name='hooks';");
299 db_unprotect(PROTECT_CONFIG);
300 db_multi_exec("%s", blob_sql_text(&sql));
301 db_protect_pop();
302 blob_reset(&sql);
303 }
304 db_commit_transaction();
305 }else
306 if( strncmp(zCmd, "delete", nCmd)==0 ){
307 int i;
308 verify_all_options();
309 if( g.argc<4 ) usage("delete ID ...");
310 db_begin_write();
311 db_unprotect(PROTECT_CONFIG);
312 db_multi_exec(
313 "INSERT OR IGNORE INTO config(name,value) VALUES('hooks','[]');\n"
314 );
315 for(i=3; i<g.argc; i++){
316 const char *zId = g.argv[i];
@@ -321,10 +328,11 @@
328 " mtime=now()"
329 " WHERE name='hooks';",
330 atoi(zId)
331 );
332 }
333 db_protect_pop();
334 db_commit_transaction();
335 }else
336 if( strncmp(zCmd, "list", nCmd)==0 ){
337 Stmt q;
338 int n = 0;
339
+10 -1
--- src/http.c
+++ src/http.c
@@ -375,17 +375,26 @@
375375
j -= 4;
376376
zLine[j] = 0;
377377
}
378378
if( (mHttpFlags & HTTP_QUIET)==0 ){
379379
fossil_print("redirect with status %d to %s\n", rc, &zLine[i]);
380
+ }
381
+ if( g.url.isFile || g.url.isSsh ){
382
+ fossil_warning("cannot redirect from %s to %s", g.url.canonical,
383
+ &zLine[i]);
384
+ goto write_err;
380385
}
381386
wasHttps = g.url.isHttps;
382387
url_parse(&zLine[i], 0);
383388
if( wasHttps && !g.url.isHttps ){
384389
fossil_warning("cannot redirect from HTTPS to HTTP");
385390
goto write_err;
386
- }
391
+ }
392
+ if( g.url.isSsh || g.url.isFile ){
393
+ fossil_warning("cannot redirect to %s", &zLine[i]);
394
+ goto write_err;
395
+ }
387396
transport_close(&g.url);
388397
transport_global_shutdown(&g.url);
389398
fSeenHttpAuth = 0;
390399
if( g.zHttpAuth ) free(g.zHttpAuth);
391400
g.zHttpAuth = get_httpauth();
392401
--- src/http.c
+++ src/http.c
@@ -375,17 +375,26 @@
375 j -= 4;
376 zLine[j] = 0;
377 }
378 if( (mHttpFlags & HTTP_QUIET)==0 ){
379 fossil_print("redirect with status %d to %s\n", rc, &zLine[i]);
 
 
 
 
 
380 }
381 wasHttps = g.url.isHttps;
382 url_parse(&zLine[i], 0);
383 if( wasHttps && !g.url.isHttps ){
384 fossil_warning("cannot redirect from HTTPS to HTTP");
385 goto write_err;
386 }
 
 
 
 
387 transport_close(&g.url);
388 transport_global_shutdown(&g.url);
389 fSeenHttpAuth = 0;
390 if( g.zHttpAuth ) free(g.zHttpAuth);
391 g.zHttpAuth = get_httpauth();
392
--- src/http.c
+++ src/http.c
@@ -375,17 +375,26 @@
375 j -= 4;
376 zLine[j] = 0;
377 }
378 if( (mHttpFlags & HTTP_QUIET)==0 ){
379 fossil_print("redirect with status %d to %s\n", rc, &zLine[i]);
380 }
381 if( g.url.isFile || g.url.isSsh ){
382 fossil_warning("cannot redirect from %s to %s", g.url.canonical,
383 &zLine[i]);
384 goto write_err;
385 }
386 wasHttps = g.url.isHttps;
387 url_parse(&zLine[i], 0);
388 if( wasHttps && !g.url.isHttps ){
389 fossil_warning("cannot redirect from HTTPS to HTTP");
390 goto write_err;
391 }
392 if( g.url.isSsh || g.url.isFile ){
393 fossil_warning("cannot redirect to %s", &zLine[i]);
394 goto write_err;
395 }
396 transport_close(&g.url);
397 transport_global_shutdown(&g.url);
398 fSeenHttpAuth = 0;
399 if( g.zHttpAuth ) free(g.zHttpAuth);
400 g.zHttpAuth = get_httpauth();
401
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -576,16 +576,18 @@
576576
Blob sql;
577577
char *zSep = "(";
578578
db_begin_transaction();
579579
blob_init(&sql, 0, 0);
580580
if( g.argc==4 && find_option("all",0,0)!=0 ){
581
+ db_unprotect(PROTECT_CONFIG);
581582
blob_append_sql(&sql,
582583
"DELETE FROM global_config WHERE name GLOB 'cert:*';\n"
583584
"DELETE FROM global_config WHERE name GLOB 'trusted:*';\n"
584585
"DELETE FROM config WHERE name GLOB 'cert:*';\n"
585586
"DELETE FROM config WHERE name GLOB 'trusted:*';\n"
586587
);
588
+ db_protect_pop();
587589
}else{
588590
if( g.argc<4 ){
589591
usage("remove-exception DOMAIN-NAME ...");
590592
}
591593
blob_append_sql(&sql,"DELETE FROM global_config WHERE name IN ");
592594
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -576,16 +576,18 @@
576 Blob sql;
577 char *zSep = "(";
578 db_begin_transaction();
579 blob_init(&sql, 0, 0);
580 if( g.argc==4 && find_option("all",0,0)!=0 ){
 
581 blob_append_sql(&sql,
582 "DELETE FROM global_config WHERE name GLOB 'cert:*';\n"
583 "DELETE FROM global_config WHERE name GLOB 'trusted:*';\n"
584 "DELETE FROM config WHERE name GLOB 'cert:*';\n"
585 "DELETE FROM config WHERE name GLOB 'trusted:*';\n"
586 );
 
587 }else{
588 if( g.argc<4 ){
589 usage("remove-exception DOMAIN-NAME ...");
590 }
591 blob_append_sql(&sql,"DELETE FROM global_config WHERE name IN ");
592
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -576,16 +576,18 @@
576 Blob sql;
577 char *zSep = "(";
578 db_begin_transaction();
579 blob_init(&sql, 0, 0);
580 if( g.argc==4 && find_option("all",0,0)!=0 ){
581 db_unprotect(PROTECT_CONFIG);
582 blob_append_sql(&sql,
583 "DELETE FROM global_config WHERE name GLOB 'cert:*';\n"
584 "DELETE FROM global_config WHERE name GLOB 'trusted:*';\n"
585 "DELETE FROM config WHERE name GLOB 'cert:*';\n"
586 "DELETE FROM config WHERE name GLOB 'trusted:*';\n"
587 );
588 db_protect_pop();
589 }else{
590 if( g.argc<4 ){
591 usage("remove-exception DOMAIN-NAME ...");
592 }
593 blob_append_sql(&sql,"DELETE FROM global_config WHERE name IN ");
594
--- src/import.c
+++ src/import.c
@@ -1759,10 +1759,11 @@
17591759
if( forceFlag ) file_delete(g.argv[2]);
17601760
db_create_repository(g.argv[2]);
17611761
}
17621762
db_open_repository(g.argv[2]);
17631763
db_open_config(0, 0);
1764
+ db_unprotect(PROTECT_ALL);
17641765
17651766
db_begin_transaction();
17661767
if( !incrFlag ){
17671768
db_initial_setup(0, 0, zDefaultUser);
17681769
db_set("main-branch", gimport.zTrunkName, 0);
17691770
--- src/import.c
+++ src/import.c
@@ -1759,10 +1759,11 @@
1759 if( forceFlag ) file_delete(g.argv[2]);
1760 db_create_repository(g.argv[2]);
1761 }
1762 db_open_repository(g.argv[2]);
1763 db_open_config(0, 0);
 
1764
1765 db_begin_transaction();
1766 if( !incrFlag ){
1767 db_initial_setup(0, 0, zDefaultUser);
1768 db_set("main-branch", gimport.zTrunkName, 0);
1769
--- src/import.c
+++ src/import.c
@@ -1759,10 +1759,11 @@
1759 if( forceFlag ) file_delete(g.argv[2]);
1760 db_create_repository(g.argv[2]);
1761 }
1762 db_open_repository(g.argv[2]);
1763 db_open_config(0, 0);
1764 db_unprotect(PROTECT_ALL);
1765
1766 db_begin_transaction();
1767 if( !incrFlag ){
1768 db_initial_setup(0, 0, zDefaultUser);
1769 db_set("main-branch", gimport.zTrunkName, 0);
1770
+10 -2
--- src/interwiki.c
+++ src/interwiki.c
@@ -192,40 +192,44 @@
192192
const char *zHash = find_option("hash",0,1);
193193
const char *zWiki = find_option("wiki",0,1);
194194
verify_all_options();
195195
if( g.argc!=4 ) usage("add TAG ?OPTIONS?");
196196
zName = g.argv[3];
197
- if( zBase){
197
+ if( zBase==0 ){
198198
fossil_fatal("the --base option is required");
199199
}
200200
if( !interwiki_valid_name(zName) ){
201201
fossil_fatal("not a valid interwiki tag: \"%s\"", zName);
202202
}
203203
db_begin_write();
204
+ db_unprotect(PROTECT_CONFIG);
204205
db_multi_exec(
205206
"REPLACE INTO config(name,value,mtime)"
206207
" VALUES('interwiki:'||lower(%Q),"
207208
" json_object('base',%Q,'hash',%Q,'wiki',%Q),"
208209
" now());",
209210
zName, zBase, zHash, zWiki
210211
);
211212
setup_incr_cfgcnt();
213
+ db_protect_pop();
212214
db_commit_transaction();
213215
}else
214216
if( strncmp(zCmd, "delete", nCmd)==0 ){
215217
int i;
216218
verify_all_options();
217219
if( g.argc<4 ) usage("delete ID ...");
218220
db_begin_write();
221
+ db_unprotect(PROTECT_CONFIG);
219222
for(i=3; i<g.argc; i++){
220223
const char *zName = g.argv[i];
221224
db_multi_exec(
222225
"DELETE FROM config WHERE name='interwiki:%q'",
223226
zName
224227
);
225228
}
226229
setup_incr_cfgcnt();
230
+ db_protect_pop();
227231
db_commit_transaction();
228232
}else
229233
if( strncmp(zCmd, "list", nCmd)==0 ){
230234
Stmt q;
231235
int n = 0;
@@ -316,22 +320,26 @@
316320
zTag = PT("tag");
317321
zBase = PT("base");
318322
zHash = PT("hash");
319323
zWiki = PT("wiki");
320324
if( zTag==0 || zTag[0]==0 || !interwiki_valid_name(zTag) ){
321
- zErr = mprintf("Not a valid interwiki tag name: \"%s\"", zTag ? zTag : "");
325
+ zErr = mprintf("Not a valid interwiki tag name: \"%s\"", zTag?zTag : "");
322326
}else if( zBase==0 || zBase[0]==0 ){
327
+ db_unprotect(PROTECT_CONFIG);
323328
db_multi_exec("DELETE FROM config WHERE name='interwiki:%q';", zTag);
329
+ db_protect_pop();
324330
}else{
325331
if( zHash && zHash[0]==0 ) zHash = 0;
326332
if( zWiki && zWiki[0]==0 ) zWiki = 0;
333
+ db_unprotect(PROTECT_CONFIG);
327334
db_multi_exec(
328335
"REPLACE INTO config(name,value,mtime)"
329336
"VALUES('interwiki:'||lower(%Q),"
330337
" json_object('base',%Q,'hash',%Q,'wiki',%Q),"
331338
" now());",
332339
zTag, zBase, zHash, zWiki);
340
+ db_protect_pop();
333341
}
334342
}
335343
336344
style_header("Interwiki Map Configuration");
337345
@ <p>Interwiki links are hyperlink targets of the form
338346
--- src/interwiki.c
+++ src/interwiki.c
@@ -192,40 +192,44 @@
192 const char *zHash = find_option("hash",0,1);
193 const char *zWiki = find_option("wiki",0,1);
194 verify_all_options();
195 if( g.argc!=4 ) usage("add TAG ?OPTIONS?");
196 zName = g.argv[3];
197 if( zBase){
198 fossil_fatal("the --base option is required");
199 }
200 if( !interwiki_valid_name(zName) ){
201 fossil_fatal("not a valid interwiki tag: \"%s\"", zName);
202 }
203 db_begin_write();
 
204 db_multi_exec(
205 "REPLACE INTO config(name,value,mtime)"
206 " VALUES('interwiki:'||lower(%Q),"
207 " json_object('base',%Q,'hash',%Q,'wiki',%Q),"
208 " now());",
209 zName, zBase, zHash, zWiki
210 );
211 setup_incr_cfgcnt();
 
212 db_commit_transaction();
213 }else
214 if( strncmp(zCmd, "delete", nCmd)==0 ){
215 int i;
216 verify_all_options();
217 if( g.argc<4 ) usage("delete ID ...");
218 db_begin_write();
 
219 for(i=3; i<g.argc; i++){
220 const char *zName = g.argv[i];
221 db_multi_exec(
222 "DELETE FROM config WHERE name='interwiki:%q'",
223 zName
224 );
225 }
226 setup_incr_cfgcnt();
 
227 db_commit_transaction();
228 }else
229 if( strncmp(zCmd, "list", nCmd)==0 ){
230 Stmt q;
231 int n = 0;
@@ -316,22 +320,26 @@
316 zTag = PT("tag");
317 zBase = PT("base");
318 zHash = PT("hash");
319 zWiki = PT("wiki");
320 if( zTag==0 || zTag[0]==0 || !interwiki_valid_name(zTag) ){
321 zErr = mprintf("Not a valid interwiki tag name: \"%s\"", zTag ? zTag : "");
322 }else if( zBase==0 || zBase[0]==0 ){
 
323 db_multi_exec("DELETE FROM config WHERE name='interwiki:%q';", zTag);
 
324 }else{
325 if( zHash && zHash[0]==0 ) zHash = 0;
326 if( zWiki && zWiki[0]==0 ) zWiki = 0;
 
327 db_multi_exec(
328 "REPLACE INTO config(name,value,mtime)"
329 "VALUES('interwiki:'||lower(%Q),"
330 " json_object('base',%Q,'hash',%Q,'wiki',%Q),"
331 " now());",
332 zTag, zBase, zHash, zWiki);
 
333 }
334 }
335
336 style_header("Interwiki Map Configuration");
337 @ <p>Interwiki links are hyperlink targets of the form
338
--- src/interwiki.c
+++ src/interwiki.c
@@ -192,40 +192,44 @@
192 const char *zHash = find_option("hash",0,1);
193 const char *zWiki = find_option("wiki",0,1);
194 verify_all_options();
195 if( g.argc!=4 ) usage("add TAG ?OPTIONS?");
196 zName = g.argv[3];
197 if( zBase==0 ){
198 fossil_fatal("the --base option is required");
199 }
200 if( !interwiki_valid_name(zName) ){
201 fossil_fatal("not a valid interwiki tag: \"%s\"", zName);
202 }
203 db_begin_write();
204 db_unprotect(PROTECT_CONFIG);
205 db_multi_exec(
206 "REPLACE INTO config(name,value,mtime)"
207 " VALUES('interwiki:'||lower(%Q),"
208 " json_object('base',%Q,'hash',%Q,'wiki',%Q),"
209 " now());",
210 zName, zBase, zHash, zWiki
211 );
212 setup_incr_cfgcnt();
213 db_protect_pop();
214 db_commit_transaction();
215 }else
216 if( strncmp(zCmd, "delete", nCmd)==0 ){
217 int i;
218 verify_all_options();
219 if( g.argc<4 ) usage("delete ID ...");
220 db_begin_write();
221 db_unprotect(PROTECT_CONFIG);
222 for(i=3; i<g.argc; i++){
223 const char *zName = g.argv[i];
224 db_multi_exec(
225 "DELETE FROM config WHERE name='interwiki:%q'",
226 zName
227 );
228 }
229 setup_incr_cfgcnt();
230 db_protect_pop();
231 db_commit_transaction();
232 }else
233 if( strncmp(zCmd, "list", nCmd)==0 ){
234 Stmt q;
235 int n = 0;
@@ -316,22 +320,26 @@
320 zTag = PT("tag");
321 zBase = PT("base");
322 zHash = PT("hash");
323 zWiki = PT("wiki");
324 if( zTag==0 || zTag[0]==0 || !interwiki_valid_name(zTag) ){
325 zErr = mprintf("Not a valid interwiki tag name: \"%s\"", zTag?zTag : "");
326 }else if( zBase==0 || zBase[0]==0 ){
327 db_unprotect(PROTECT_CONFIG);
328 db_multi_exec("DELETE FROM config WHERE name='interwiki:%q';", zTag);
329 db_protect_pop();
330 }else{
331 if( zHash && zHash[0]==0 ) zHash = 0;
332 if( zWiki && zWiki[0]==0 ) zWiki = 0;
333 db_unprotect(PROTECT_CONFIG);
334 db_multi_exec(
335 "REPLACE INTO config(name,value,mtime)"
336 "VALUES('interwiki:'||lower(%Q),"
337 " json_object('base',%Q,'hash',%Q,'wiki',%Q),"
338 " now());",
339 zTag, zBase, zHash, zWiki);
340 db_protect_pop();
341 }
342 }
343
344 style_header("Interwiki Map Configuration");
345 @ <p>Interwiki links are hyperlink targets of the form
346
+10 -2
--- src/interwiki.c
+++ src/interwiki.c
@@ -192,40 +192,44 @@
192192
const char *zHash = find_option("hash",0,1);
193193
const char *zWiki = find_option("wiki",0,1);
194194
verify_all_options();
195195
if( g.argc!=4 ) usage("add TAG ?OPTIONS?");
196196
zName = g.argv[3];
197
- if( zBase){
197
+ if( zBase==0 ){
198198
fossil_fatal("the --base option is required");
199199
}
200200
if( !interwiki_valid_name(zName) ){
201201
fossil_fatal("not a valid interwiki tag: \"%s\"", zName);
202202
}
203203
db_begin_write();
204
+ db_unprotect(PROTECT_CONFIG);
204205
db_multi_exec(
205206
"REPLACE INTO config(name,value,mtime)"
206207
" VALUES('interwiki:'||lower(%Q),"
207208
" json_object('base',%Q,'hash',%Q,'wiki',%Q),"
208209
" now());",
209210
zName, zBase, zHash, zWiki
210211
);
211212
setup_incr_cfgcnt();
213
+ db_protect_pop();
212214
db_commit_transaction();
213215
}else
214216
if( strncmp(zCmd, "delete", nCmd)==0 ){
215217
int i;
216218
verify_all_options();
217219
if( g.argc<4 ) usage("delete ID ...");
218220
db_begin_write();
221
+ db_unprotect(PROTECT_CONFIG);
219222
for(i=3; i<g.argc; i++){
220223
const char *zName = g.argv[i];
221224
db_multi_exec(
222225
"DELETE FROM config WHERE name='interwiki:%q'",
223226
zName
224227
);
225228
}
226229
setup_incr_cfgcnt();
230
+ db_protect_pop();
227231
db_commit_transaction();
228232
}else
229233
if( strncmp(zCmd, "list", nCmd)==0 ){
230234
Stmt q;
231235
int n = 0;
@@ -316,22 +320,26 @@
316320
zTag = PT("tag");
317321
zBase = PT("base");
318322
zHash = PT("hash");
319323
zWiki = PT("wiki");
320324
if( zTag==0 || zTag[0]==0 || !interwiki_valid_name(zTag) ){
321
- zErr = mprintf("Not a valid interwiki tag name: \"%s\"", zTag ? zTag : "");
325
+ zErr = mprintf("Not a valid interwiki tag name: \"%s\"", zTag?zTag : "");
322326
}else if( zBase==0 || zBase[0]==0 ){
327
+ db_unprotect(PROTECT_CONFIG);
323328
db_multi_exec("DELETE FROM config WHERE name='interwiki:%q';", zTag);
329
+ db_protect_pop();
324330
}else{
325331
if( zHash && zHash[0]==0 ) zHash = 0;
326332
if( zWiki && zWiki[0]==0 ) zWiki = 0;
333
+ db_unprotect(PROTECT_CONFIG);
327334
db_multi_exec(
328335
"REPLACE INTO config(name,value,mtime)"
329336
"VALUES('interwiki:'||lower(%Q),"
330337
" json_object('base',%Q,'hash',%Q,'wiki',%Q),"
331338
" now());",
332339
zTag, zBase, zHash, zWiki);
340
+ db_protect_pop();
333341
}
334342
}
335343
336344
style_header("Interwiki Map Configuration");
337345
@ <p>Interwiki links are hyperlink targets of the form
338346
--- src/interwiki.c
+++ src/interwiki.c
@@ -192,40 +192,44 @@
192 const char *zHash = find_option("hash",0,1);
193 const char *zWiki = find_option("wiki",0,1);
194 verify_all_options();
195 if( g.argc!=4 ) usage("add TAG ?OPTIONS?");
196 zName = g.argv[3];
197 if( zBase){
198 fossil_fatal("the --base option is required");
199 }
200 if( !interwiki_valid_name(zName) ){
201 fossil_fatal("not a valid interwiki tag: \"%s\"", zName);
202 }
203 db_begin_write();
 
204 db_multi_exec(
205 "REPLACE INTO config(name,value,mtime)"
206 " VALUES('interwiki:'||lower(%Q),"
207 " json_object('base',%Q,'hash',%Q,'wiki',%Q),"
208 " now());",
209 zName, zBase, zHash, zWiki
210 );
211 setup_incr_cfgcnt();
 
212 db_commit_transaction();
213 }else
214 if( strncmp(zCmd, "delete", nCmd)==0 ){
215 int i;
216 verify_all_options();
217 if( g.argc<4 ) usage("delete ID ...");
218 db_begin_write();
 
219 for(i=3; i<g.argc; i++){
220 const char *zName = g.argv[i];
221 db_multi_exec(
222 "DELETE FROM config WHERE name='interwiki:%q'",
223 zName
224 );
225 }
226 setup_incr_cfgcnt();
 
227 db_commit_transaction();
228 }else
229 if( strncmp(zCmd, "list", nCmd)==0 ){
230 Stmt q;
231 int n = 0;
@@ -316,22 +320,26 @@
316 zTag = PT("tag");
317 zBase = PT("base");
318 zHash = PT("hash");
319 zWiki = PT("wiki");
320 if( zTag==0 || zTag[0]==0 || !interwiki_valid_name(zTag) ){
321 zErr = mprintf("Not a valid interwiki tag name: \"%s\"", zTag ? zTag : "");
322 }else if( zBase==0 || zBase[0]==0 ){
 
323 db_multi_exec("DELETE FROM config WHERE name='interwiki:%q';", zTag);
 
324 }else{
325 if( zHash && zHash[0]==0 ) zHash = 0;
326 if( zWiki && zWiki[0]==0 ) zWiki = 0;
 
327 db_multi_exec(
328 "REPLACE INTO config(name,value,mtime)"
329 "VALUES('interwiki:'||lower(%Q),"
330 " json_object('base',%Q,'hash',%Q,'wiki',%Q),"
331 " now());",
332 zTag, zBase, zHash, zWiki);
 
333 }
334 }
335
336 style_header("Interwiki Map Configuration");
337 @ <p>Interwiki links are hyperlink targets of the form
338
--- src/interwiki.c
+++ src/interwiki.c
@@ -192,40 +192,44 @@
192 const char *zHash = find_option("hash",0,1);
193 const char *zWiki = find_option("wiki",0,1);
194 verify_all_options();
195 if( g.argc!=4 ) usage("add TAG ?OPTIONS?");
196 zName = g.argv[3];
197 if( zBase==0 ){
198 fossil_fatal("the --base option is required");
199 }
200 if( !interwiki_valid_name(zName) ){
201 fossil_fatal("not a valid interwiki tag: \"%s\"", zName);
202 }
203 db_begin_write();
204 db_unprotect(PROTECT_CONFIG);
205 db_multi_exec(
206 "REPLACE INTO config(name,value,mtime)"
207 " VALUES('interwiki:'||lower(%Q),"
208 " json_object('base',%Q,'hash',%Q,'wiki',%Q),"
209 " now());",
210 zName, zBase, zHash, zWiki
211 );
212 setup_incr_cfgcnt();
213 db_protect_pop();
214 db_commit_transaction();
215 }else
216 if( strncmp(zCmd, "delete", nCmd)==0 ){
217 int i;
218 verify_all_options();
219 if( g.argc<4 ) usage("delete ID ...");
220 db_begin_write();
221 db_unprotect(PROTECT_CONFIG);
222 for(i=3; i<g.argc; i++){
223 const char *zName = g.argv[i];
224 db_multi_exec(
225 "DELETE FROM config WHERE name='interwiki:%q'",
226 zName
227 );
228 }
229 setup_incr_cfgcnt();
230 db_protect_pop();
231 db_commit_transaction();
232 }else
233 if( strncmp(zCmd, "list", nCmd)==0 ){
234 Stmt q;
235 int n = 0;
@@ -316,22 +320,26 @@
320 zTag = PT("tag");
321 zBase = PT("base");
322 zHash = PT("hash");
323 zWiki = PT("wiki");
324 if( zTag==0 || zTag[0]==0 || !interwiki_valid_name(zTag) ){
325 zErr = mprintf("Not a valid interwiki tag name: \"%s\"", zTag?zTag : "");
326 }else if( zBase==0 || zBase[0]==0 ){
327 db_unprotect(PROTECT_CONFIG);
328 db_multi_exec("DELETE FROM config WHERE name='interwiki:%q';", zTag);
329 db_protect_pop();
330 }else{
331 if( zHash && zHash[0]==0 ) zHash = 0;
332 if( zWiki && zWiki[0]==0 ) zWiki = 0;
333 db_unprotect(PROTECT_CONFIG);
334 db_multi_exec(
335 "REPLACE INTO config(name,value,mtime)"
336 "VALUES('interwiki:'||lower(%Q),"
337 " json_object('base',%Q,'hash',%Q,'wiki',%Q),"
338 " now());",
339 zTag, zBase, zHash, zWiki);
340 db_protect_pop();
341 }
342 }
343
344 style_header("Interwiki Map Configuration");
345 @ <p>Interwiki links are hyperlink targets of the form
346
--- src/json_config.c
+++ src/json_config.c
@@ -83,11 +83,10 @@
8383
{ "keep-glob", CONFIGSET_PROJ },
8484
{ "crlf-glob", CONFIGSET_PROJ },
8585
{ "crnl-glob", CONFIGSET_PROJ },
8686
{ "encoding-glob", CONFIGSET_PROJ },
8787
{ "empty-dirs", CONFIGSET_PROJ },
88
-{ "allow-symlinks", CONFIGSET_PROJ },
8988
{ "dotfiles", CONFIGSET_PROJ },
9089
9190
{ "ticket-table", CONFIGSET_TKT },
9291
{ "ticket-common", CONFIGSET_TKT },
9392
{ "ticket-change", CONFIGSET_TKT },
9493
--- src/json_config.c
+++ src/json_config.c
@@ -83,11 +83,10 @@
83 { "keep-glob", CONFIGSET_PROJ },
84 { "crlf-glob", CONFIGSET_PROJ },
85 { "crnl-glob", CONFIGSET_PROJ },
86 { "encoding-glob", CONFIGSET_PROJ },
87 { "empty-dirs", CONFIGSET_PROJ },
88 { "allow-symlinks", CONFIGSET_PROJ },
89 { "dotfiles", CONFIGSET_PROJ },
90
91 { "ticket-table", CONFIGSET_TKT },
92 { "ticket-common", CONFIGSET_TKT },
93 { "ticket-change", CONFIGSET_TKT },
94
--- src/json_config.c
+++ src/json_config.c
@@ -83,11 +83,10 @@
83 { "keep-glob", CONFIGSET_PROJ },
84 { "crlf-glob", CONFIGSET_PROJ },
85 { "crnl-glob", CONFIGSET_PROJ },
86 { "encoding-glob", CONFIGSET_PROJ },
87 { "empty-dirs", CONFIGSET_PROJ },
 
88 { "dotfiles", CONFIGSET_PROJ },
89
90 { "ticket-table", CONFIGSET_TKT },
91 { "ticket-common", CONFIGSET_TKT },
92 { "ticket-change", CONFIGSET_TKT },
93
--- src/json_user.c
+++ src/json_user.c
@@ -212,13 +212,15 @@
212212
json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS,
213213
"User %s already exists.", zName);
214214
goto error;
215215
}else{
216216
Stmt ins = empty_Stmt;
217
+ db_unprotect(PROTECT_USER);
217218
db_prepare(&ins, "INSERT INTO user (login) VALUES(%Q)",zName);
218219
db_step( &ins );
219220
db_finalize(&ins);
221
+ db_protect_pop();
220222
uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName);
221223
assert(uid>0);
222224
zNameNew = zName;
223225
cson_object_set( pUser, "uid", cson_value_new_integer(uid) );
224226
}
@@ -345,13 +347,15 @@
345347
#endif
346348
#if 0
347349
puts(blob_str(&sql));
348350
cson_output_FILE( cson_object_value(pUser), stdout, NULL );
349351
#endif
352
+ db_unprotect(PROTECT_USER);
350353
db_prepare(&q, "%s", blob_sql_text(&sql));
351354
db_exec(&q);
352355
db_finalize(&q);
356
+ db_protect_pop();
353357
#if TRY_LOGIN_GROUP
354358
if( zPW || cson_value_get_bool(forceLogout) ){
355359
Blob groupSql = empty_blob;
356360
char * zErr = NULL;
357361
blob_append_sql(&groupSql,
@@ -358,11 +362,13 @@
358362
"INSERT INTO user(login)"
359363
" SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);",
360364
zName, zName
361365
);
362366
blob_append(&groupSql, blob_str(&sql), blob_size(&sql));
367
+ db_unprotect(PROTECT_USER);
363368
login_group_sql(blob_str(&groupSql), NULL, NULL, &zErr);
369
+ db_protect_pop();
364370
blob_reset(&groupSql);
365371
if( zErr ){
366372
json_set_err( FSL_JSON_E_UNKNOWN,
367373
"Repo-group update at least partially failed: %s",
368374
zErr);
369375
--- src/json_user.c
+++ src/json_user.c
@@ -212,13 +212,15 @@
212 json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS,
213 "User %s already exists.", zName);
214 goto error;
215 }else{
216 Stmt ins = empty_Stmt;
 
217 db_prepare(&ins, "INSERT INTO user (login) VALUES(%Q)",zName);
218 db_step( &ins );
219 db_finalize(&ins);
 
220 uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName);
221 assert(uid>0);
222 zNameNew = zName;
223 cson_object_set( pUser, "uid", cson_value_new_integer(uid) );
224 }
@@ -345,13 +347,15 @@
345 #endif
346 #if 0
347 puts(blob_str(&sql));
348 cson_output_FILE( cson_object_value(pUser), stdout, NULL );
349 #endif
 
350 db_prepare(&q, "%s", blob_sql_text(&sql));
351 db_exec(&q);
352 db_finalize(&q);
 
353 #if TRY_LOGIN_GROUP
354 if( zPW || cson_value_get_bool(forceLogout) ){
355 Blob groupSql = empty_blob;
356 char * zErr = NULL;
357 blob_append_sql(&groupSql,
@@ -358,11 +362,13 @@
358 "INSERT INTO user(login)"
359 " SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);",
360 zName, zName
361 );
362 blob_append(&groupSql, blob_str(&sql), blob_size(&sql));
 
363 login_group_sql(blob_str(&groupSql), NULL, NULL, &zErr);
 
364 blob_reset(&groupSql);
365 if( zErr ){
366 json_set_err( FSL_JSON_E_UNKNOWN,
367 "Repo-group update at least partially failed: %s",
368 zErr);
369
--- src/json_user.c
+++ src/json_user.c
@@ -212,13 +212,15 @@
212 json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS,
213 "User %s already exists.", zName);
214 goto error;
215 }else{
216 Stmt ins = empty_Stmt;
217 db_unprotect(PROTECT_USER);
218 db_prepare(&ins, "INSERT INTO user (login) VALUES(%Q)",zName);
219 db_step( &ins );
220 db_finalize(&ins);
221 db_protect_pop();
222 uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName);
223 assert(uid>0);
224 zNameNew = zName;
225 cson_object_set( pUser, "uid", cson_value_new_integer(uid) );
226 }
@@ -345,13 +347,15 @@
347 #endif
348 #if 0
349 puts(blob_str(&sql));
350 cson_output_FILE( cson_object_value(pUser), stdout, NULL );
351 #endif
352 db_unprotect(PROTECT_USER);
353 db_prepare(&q, "%s", blob_sql_text(&sql));
354 db_exec(&q);
355 db_finalize(&q);
356 db_protect_pop();
357 #if TRY_LOGIN_GROUP
358 if( zPW || cson_value_get_bool(forceLogout) ){
359 Blob groupSql = empty_blob;
360 char * zErr = NULL;
361 blob_append_sql(&groupSql,
@@ -358,11 +362,13 @@
362 "INSERT INTO user(login)"
363 " SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);",
364 zName, zName
365 );
366 blob_append(&groupSql, blob_str(&sql), blob_size(&sql));
367 db_unprotect(PROTECT_USER);
368 login_group_sql(blob_str(&groupSql), NULL, NULL, &zErr);
369 db_protect_pop();
370 blob_reset(&groupSql);
371 if( zErr ){
372 json_set_err( FSL_JSON_E_UNKNOWN,
373 "Repo-group update at least partially failed: %s",
374 zErr);
375
+21 -2
--- src/login.c
+++ src/login.c
@@ -293,13 +293,15 @@
293293
if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
294294
zCookie = login_gen_user_cookie_value(zUsername, zHash);
295295
cgi_set_cookie(zCookieName, zCookie, login_cookie_path(),
296296
bSessionCookie ? 0 : expires);
297297
record_login_attempt(zUsername, zIpAddr, 1);
298
+ db_unprotect(PROTECT_USER);
298299
db_multi_exec("UPDATE user SET cookie=%Q,"
299300
" cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
300301
zHash, expires, uid);
302
+ db_protect_pop();
301303
fossil_free(zHash);
302304
if( zDest ){
303305
*zDest = zCookie;
304306
}else{
305307
free(zCookie);
@@ -356,14 +358,16 @@
356358
}else{
357359
const char *cookie = login_cookie_name();
358360
/* To logout, change the cookie value to an empty string */
359361
cgi_set_cookie(cookie, "",
360362
login_cookie_path(), -86400);
363
+ db_unprotect(PROTECT_USER);
361364
db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
362365
" cexpire=0 WHERE uid=%d"
363366
" AND login NOT IN ('anonymous','nobody',"
364367
" 'developer','reader')", g.userUid);
368
+ db_protect_pop();
365369
cgi_replace_parameter(cookie, NULL);
366370
cgi_replace_parameter("anon", NULL);
367371
}
368372
}
369373
@@ -580,22 +584,27 @@
580584
;
581585
}else{
582586
char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0);
583587
char *zChngPw;
584588
char *zErr;
589
+ int rc;
590
+
591
+ db_unprotect(PROTECT_USER);
585592
db_multi_exec(
586593
"UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid
587594
);
588
- fossil_free(zNewPw);
589595
zChngPw = mprintf(
590596
"UPDATE user"
591597
" SET pw=shared_secret(%Q,%Q,"
592598
" (SELECT value FROM config WHERE name='project-code'))"
593599
" WHERE login=%Q",
594600
zNew1, g.zLogin, g.zLogin
595601
);
596
- if( login_group_sql(zChngPw, "<p>", "</p>\n", &zErr) ){
602
+ fossil_free(zNewPw);
603
+ rc = login_group_sql(zChngPw, "<p>", "</p>\n", &zErr);
604
+ db_protect_pop();
605
+ if( rc ){
597606
zErrMsg = mprintf("<span class=\"loginError\">%s</span>", zErr);
598607
fossil_free(zErr);
599608
}else{
600609
redirect_to_g();
601610
return;
@@ -835,16 +844,18 @@
835844
zLogin, zHash
836845
);
837846
pStmt = 0;
838847
rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
839848
if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
849
+ db_unprotect(PROTECT_USER);
840850
db_multi_exec(
841851
"UPDATE user SET cookie=%Q, cexpire=%.17g"
842852
" WHERE login=%Q",
843853
zHash,
844854
sqlite3_column_double(pStmt, 0), zLogin
845855
);
856
+ db_protect_pop();
846857
nXfer++;
847858
}
848859
sqlite3_finalize(pStmt);
849860
}
850861
sqlite3_close(pOther);
@@ -1619,11 +1630,13 @@
16191630
"INSERT INTO user(login,pw,cap,info,mtime)\n"
16201631
"VALUES(%Q,%Q,%Q,"
16211632
"'%q <%q>\nself-register from ip %q on '||datetime('now'),now())",
16221633
zUserID, zPass, zStartPerms, zDName, zEAddr, g.zIpAddr);
16231634
fossil_free(zPass);
1635
+ db_unprotect(PROTECT_USER);
16241636
db_multi_exec("%s", blob_sql_text(&sql));
1637
+ db_protect_pop();
16251638
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID);
16261639
login_set_user_cookie(zUserID, uid, NULL, 0);
16271640
if( doAlerts ){
16281641
/* Also make the new user a subscriber. */
16291642
Blob hdr, body;
@@ -1832,14 +1845,16 @@
18321845
while( db_step(&q)==SQLITE_ROW ){
18331846
const char *zRepoName = db_column_text(&q, 1);
18341847
if( file_size(zRepoName, ExtFILE)<0 ){
18351848
/* Silently remove non-existent repositories from the login group. */
18361849
const char *zLabel = db_column_text(&q, 0);
1850
+ db_unprotect(PROTECT_CONFIG);
18371851
db_multi_exec(
18381852
"DELETE FROM config WHERE name GLOB 'peer-*-%q'",
18391853
&zLabel[10]
18401854
);
1855
+ db_protect_pop();
18411856
continue;
18421857
}
18431858
rc = sqlite3_open_v2(
18441859
zRepoName, &pPeer,
18451860
SQLITE_OPEN_READWRITE,
@@ -2004,11 +2019,13 @@
20042019
"REPLACE INTO config(name,value,mtime) VALUES('peer-name-%q',%Q,now());"
20052020
"REPLACE INTO config(name,value,mtime) VALUES('peer-repo-%q',%Q,now());"
20062021
"COMMIT;",
20072022
zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo
20082023
);
2024
+ db_unprotect(PROTECT_CONFIG);
20092025
login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
2026
+ db_protect_pop();
20102027
fossil_free(zSql);
20112028
}
20122029
20132030
/*
20142031
** Leave the login group that we are currently part of.
@@ -2025,17 +2042,19 @@
20252042
" WHERE name='login-group-name'"
20262043
" AND (SELECT count(*) FROM config WHERE name GLOB 'peer-*')==0;",
20272044
zProjCode
20282045
);
20292046
fossil_free(zProjCode);
2047
+ db_unprotect(PROTECT_CONFIG);
20302048
login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
20312049
fossil_free(zSql);
20322050
db_multi_exec(
20332051
"DELETE FROM config "
20342052
" WHERE name GLOB 'peer-*'"
20352053
" OR name GLOB 'login-group-*';"
20362054
);
2055
+ db_protect_pop();
20372056
}
20382057
20392058
/*
20402059
** COMMAND: login-group*
20412060
**
20422061
--- src/login.c
+++ src/login.c
@@ -293,13 +293,15 @@
293 if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
294 zCookie = login_gen_user_cookie_value(zUsername, zHash);
295 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(),
296 bSessionCookie ? 0 : expires);
297 record_login_attempt(zUsername, zIpAddr, 1);
 
298 db_multi_exec("UPDATE user SET cookie=%Q,"
299 " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
300 zHash, expires, uid);
 
301 fossil_free(zHash);
302 if( zDest ){
303 *zDest = zCookie;
304 }else{
305 free(zCookie);
@@ -356,14 +358,16 @@
356 }else{
357 const char *cookie = login_cookie_name();
358 /* To logout, change the cookie value to an empty string */
359 cgi_set_cookie(cookie, "",
360 login_cookie_path(), -86400);
 
361 db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
362 " cexpire=0 WHERE uid=%d"
363 " AND login NOT IN ('anonymous','nobody',"
364 " 'developer','reader')", g.userUid);
 
365 cgi_replace_parameter(cookie, NULL);
366 cgi_replace_parameter("anon", NULL);
367 }
368 }
369
@@ -580,22 +584,27 @@
580 ;
581 }else{
582 char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0);
583 char *zChngPw;
584 char *zErr;
 
 
 
585 db_multi_exec(
586 "UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid
587 );
588 fossil_free(zNewPw);
589 zChngPw = mprintf(
590 "UPDATE user"
591 " SET pw=shared_secret(%Q,%Q,"
592 " (SELECT value FROM config WHERE name='project-code'))"
593 " WHERE login=%Q",
594 zNew1, g.zLogin, g.zLogin
595 );
596 if( login_group_sql(zChngPw, "<p>", "</p>\n", &zErr) ){
 
 
 
597 zErrMsg = mprintf("<span class=\"loginError\">%s</span>", zErr);
598 fossil_free(zErr);
599 }else{
600 redirect_to_g();
601 return;
@@ -835,16 +844,18 @@
835 zLogin, zHash
836 );
837 pStmt = 0;
838 rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
839 if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 
840 db_multi_exec(
841 "UPDATE user SET cookie=%Q, cexpire=%.17g"
842 " WHERE login=%Q",
843 zHash,
844 sqlite3_column_double(pStmt, 0), zLogin
845 );
 
846 nXfer++;
847 }
848 sqlite3_finalize(pStmt);
849 }
850 sqlite3_close(pOther);
@@ -1619,11 +1630,13 @@
1619 "INSERT INTO user(login,pw,cap,info,mtime)\n"
1620 "VALUES(%Q,%Q,%Q,"
1621 "'%q <%q>\nself-register from ip %q on '||datetime('now'),now())",
1622 zUserID, zPass, zStartPerms, zDName, zEAddr, g.zIpAddr);
1623 fossil_free(zPass);
 
1624 db_multi_exec("%s", blob_sql_text(&sql));
 
1625 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID);
1626 login_set_user_cookie(zUserID, uid, NULL, 0);
1627 if( doAlerts ){
1628 /* Also make the new user a subscriber. */
1629 Blob hdr, body;
@@ -1832,14 +1845,16 @@
1832 while( db_step(&q)==SQLITE_ROW ){
1833 const char *zRepoName = db_column_text(&q, 1);
1834 if( file_size(zRepoName, ExtFILE)<0 ){
1835 /* Silently remove non-existent repositories from the login group. */
1836 const char *zLabel = db_column_text(&q, 0);
 
1837 db_multi_exec(
1838 "DELETE FROM config WHERE name GLOB 'peer-*-%q'",
1839 &zLabel[10]
1840 );
 
1841 continue;
1842 }
1843 rc = sqlite3_open_v2(
1844 zRepoName, &pPeer,
1845 SQLITE_OPEN_READWRITE,
@@ -2004,11 +2019,13 @@
2004 "REPLACE INTO config(name,value,mtime) VALUES('peer-name-%q',%Q,now());"
2005 "REPLACE INTO config(name,value,mtime) VALUES('peer-repo-%q',%Q,now());"
2006 "COMMIT;",
2007 zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo
2008 );
 
2009 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
 
2010 fossil_free(zSql);
2011 }
2012
2013 /*
2014 ** Leave the login group that we are currently part of.
@@ -2025,17 +2042,19 @@
2025 " WHERE name='login-group-name'"
2026 " AND (SELECT count(*) FROM config WHERE name GLOB 'peer-*')==0;",
2027 zProjCode
2028 );
2029 fossil_free(zProjCode);
 
2030 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
2031 fossil_free(zSql);
2032 db_multi_exec(
2033 "DELETE FROM config "
2034 " WHERE name GLOB 'peer-*'"
2035 " OR name GLOB 'login-group-*';"
2036 );
 
2037 }
2038
2039 /*
2040 ** COMMAND: login-group*
2041 **
2042
--- src/login.c
+++ src/login.c
@@ -293,13 +293,15 @@
293 if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))");
294 zCookie = login_gen_user_cookie_value(zUsername, zHash);
295 cgi_set_cookie(zCookieName, zCookie, login_cookie_path(),
296 bSessionCookie ? 0 : expires);
297 record_login_attempt(zUsername, zIpAddr, 1);
298 db_unprotect(PROTECT_USER);
299 db_multi_exec("UPDATE user SET cookie=%Q,"
300 " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d",
301 zHash, expires, uid);
302 db_protect_pop();
303 fossil_free(zHash);
304 if( zDest ){
305 *zDest = zCookie;
306 }else{
307 free(zCookie);
@@ -356,14 +358,16 @@
358 }else{
359 const char *cookie = login_cookie_name();
360 /* To logout, change the cookie value to an empty string */
361 cgi_set_cookie(cookie, "",
362 login_cookie_path(), -86400);
363 db_unprotect(PROTECT_USER);
364 db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
365 " cexpire=0 WHERE uid=%d"
366 " AND login NOT IN ('anonymous','nobody',"
367 " 'developer','reader')", g.userUid);
368 db_protect_pop();
369 cgi_replace_parameter(cookie, NULL);
370 cgi_replace_parameter("anon", NULL);
371 }
372 }
373
@@ -580,22 +584,27 @@
584 ;
585 }else{
586 char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0);
587 char *zChngPw;
588 char *zErr;
589 int rc;
590
591 db_unprotect(PROTECT_USER);
592 db_multi_exec(
593 "UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid
594 );
 
595 zChngPw = mprintf(
596 "UPDATE user"
597 " SET pw=shared_secret(%Q,%Q,"
598 " (SELECT value FROM config WHERE name='project-code'))"
599 " WHERE login=%Q",
600 zNew1, g.zLogin, g.zLogin
601 );
602 fossil_free(zNewPw);
603 rc = login_group_sql(zChngPw, "<p>", "</p>\n", &zErr);
604 db_protect_pop();
605 if( rc ){
606 zErrMsg = mprintf("<span class=\"loginError\">%s</span>", zErr);
607 fossil_free(zErr);
608 }else{
609 redirect_to_g();
610 return;
@@ -835,16 +844,18 @@
844 zLogin, zHash
845 );
846 pStmt = 0;
847 rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0);
848 if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
849 db_unprotect(PROTECT_USER);
850 db_multi_exec(
851 "UPDATE user SET cookie=%Q, cexpire=%.17g"
852 " WHERE login=%Q",
853 zHash,
854 sqlite3_column_double(pStmt, 0), zLogin
855 );
856 db_protect_pop();
857 nXfer++;
858 }
859 sqlite3_finalize(pStmt);
860 }
861 sqlite3_close(pOther);
@@ -1619,11 +1630,13 @@
1630 "INSERT INTO user(login,pw,cap,info,mtime)\n"
1631 "VALUES(%Q,%Q,%Q,"
1632 "'%q <%q>\nself-register from ip %q on '||datetime('now'),now())",
1633 zUserID, zPass, zStartPerms, zDName, zEAddr, g.zIpAddr);
1634 fossil_free(zPass);
1635 db_unprotect(PROTECT_USER);
1636 db_multi_exec("%s", blob_sql_text(&sql));
1637 db_protect_pop();
1638 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID);
1639 login_set_user_cookie(zUserID, uid, NULL, 0);
1640 if( doAlerts ){
1641 /* Also make the new user a subscriber. */
1642 Blob hdr, body;
@@ -1832,14 +1845,16 @@
1845 while( db_step(&q)==SQLITE_ROW ){
1846 const char *zRepoName = db_column_text(&q, 1);
1847 if( file_size(zRepoName, ExtFILE)<0 ){
1848 /* Silently remove non-existent repositories from the login group. */
1849 const char *zLabel = db_column_text(&q, 0);
1850 db_unprotect(PROTECT_CONFIG);
1851 db_multi_exec(
1852 "DELETE FROM config WHERE name GLOB 'peer-*-%q'",
1853 &zLabel[10]
1854 );
1855 db_protect_pop();
1856 continue;
1857 }
1858 rc = sqlite3_open_v2(
1859 zRepoName, &pPeer,
1860 SQLITE_OPEN_READWRITE,
@@ -2004,11 +2019,13 @@
2019 "REPLACE INTO config(name,value,mtime) VALUES('peer-name-%q',%Q,now());"
2020 "REPLACE INTO config(name,value,mtime) VALUES('peer-repo-%q',%Q,now());"
2021 "COMMIT;",
2022 zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo
2023 );
2024 db_unprotect(PROTECT_CONFIG);
2025 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
2026 db_protect_pop();
2027 fossil_free(zSql);
2028 }
2029
2030 /*
2031 ** Leave the login group that we are currently part of.
@@ -2025,17 +2042,19 @@
2042 " WHERE name='login-group-name'"
2043 " AND (SELECT count(*) FROM config WHERE name GLOB 'peer-*')==0;",
2044 zProjCode
2045 );
2046 fossil_free(zProjCode);
2047 db_unprotect(PROTECT_CONFIG);
2048 login_group_sql(zSql, "<li> ", "</li>", pzErrMsg);
2049 fossil_free(zSql);
2050 db_multi_exec(
2051 "DELETE FROM config "
2052 " WHERE name GLOB 'peer-*'"
2053 " OR name GLOB 'login-group-*';"
2054 );
2055 db_protect_pop();
2056 }
2057
2058 /*
2059 ** COMMAND: login-group*
2060 **
2061
+2
--- src/main.c
+++ src/main.c
@@ -1372,19 +1372,21 @@
13721372
g.zTop = &g.zBaseURL[7+strlen(zHost)];
13731373
g.zHttpsURL = mprintf("https://%s%.*s", zHost, i, zCur);
13741374
}
13751375
}
13761376
if( db_is_writeable("repository") ){
1377
+ db_unprotect(PROTECT_CONFIG);
13771378
if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", g.zBaseURL)){
13781379
db_multi_exec("INSERT INTO config(name,value,mtime)"
13791380
"VALUES('baseurl:%q',1,now())", g.zBaseURL);
13801381
}else{
13811382
db_optional_sql("repository",
13821383
"REPLACE INTO config(name,value,mtime)"
13831384
"VALUES('baseurl:%q',1,now())", g.zBaseURL
13841385
);
13851386
}
1387
+ db_protect_pop();
13861388
}
13871389
}
13881390
13891391
/*
13901392
** Send an HTTP redirect back to the designated Index Page.
13911393
--- src/main.c
+++ src/main.c
@@ -1372,19 +1372,21 @@
1372 g.zTop = &g.zBaseURL[7+strlen(zHost)];
1373 g.zHttpsURL = mprintf("https://%s%.*s", zHost, i, zCur);
1374 }
1375 }
1376 if( db_is_writeable("repository") ){
 
1377 if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", g.zBaseURL)){
1378 db_multi_exec("INSERT INTO config(name,value,mtime)"
1379 "VALUES('baseurl:%q',1,now())", g.zBaseURL);
1380 }else{
1381 db_optional_sql("repository",
1382 "REPLACE INTO config(name,value,mtime)"
1383 "VALUES('baseurl:%q',1,now())", g.zBaseURL
1384 );
1385 }
 
1386 }
1387 }
1388
1389 /*
1390 ** Send an HTTP redirect back to the designated Index Page.
1391
--- src/main.c
+++ src/main.c
@@ -1372,19 +1372,21 @@
1372 g.zTop = &g.zBaseURL[7+strlen(zHost)];
1373 g.zHttpsURL = mprintf("https://%s%.*s", zHost, i, zCur);
1374 }
1375 }
1376 if( db_is_writeable("repository") ){
1377 db_unprotect(PROTECT_CONFIG);
1378 if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", g.zBaseURL)){
1379 db_multi_exec("INSERT INTO config(name,value,mtime)"
1380 "VALUES('baseurl:%q',1,now())", g.zBaseURL);
1381 }else{
1382 db_optional_sql("repository",
1383 "REPLACE INTO config(name,value,mtime)"
1384 "VALUES('baseurl:%q',1,now())", g.zBaseURL
1385 );
1386 }
1387 db_protect_pop();
1388 }
1389 }
1390
1391 /*
1392 ** Send an HTTP redirect back to the designated Index Page.
1393
+21 -3
--- src/manifest.c
+++ src/manifest.c
@@ -481,14 +481,23 @@
481481
blob_appendf(pErr, "line 1 not recognized");
482482
return 0;
483483
}
484484
/* Then verify the Z-card.
485485
*/
486
+#if 1
487
+ /* Disable this ***ONLY*** (ONLY!) when testing hand-written inputs
488
+ for card-related syntax errors. */
486489
if( verify_z_card(z, n, pErr)==2 ){
487490
blob_reset(pContent);
488491
return 0;
489492
}
493
+#else
494
+#warning ACHTUNG - z-card check is disabled for testing purposes.
495
+ if(0 && verify_z_card(NULL, 0, NULL)){
496
+ /*avoid unused static func error*/
497
+ }
498
+#endif
490499
491500
/* Allocate a Manifest object to hold the parsed control artifact.
492501
*/
493502
p = fossil_malloc( sizeof(*p) );
494503
memset(p, 0, sizeof(*p));
@@ -601,10 +610,11 @@
601610
case 'E': {
602611
if( p->rEventDate>0.0 ) SYNTAX("more than one E-card");
603612
p->rEventDate = db_double(0.0,"SELECT julianday(%Q)", next_token(&x,0));
604613
if( p->rEventDate<=0.0 ) SYNTAX("malformed date on E-card");
605614
p->zEventId = next_token(&x, &sz);
615
+ if( p->zEventId==0 ) SYNTAX("missing hash on E-card");
606616
if( !hname_validate(p->zEventId, sz) ){
607617
SYNTAX("malformed hash on E-card");
608618
}
609619
p->type = CFTYPE_EVENT;
610620
break;
@@ -625,10 +635,11 @@
625635
if( !file_is_simple_pathname_nonstrict(zName) ){
626636
SYNTAX("F-card filename is not a simple path");
627637
}
628638
zUuid = next_token(&x, &sz);
629639
if( p->zBaseline==0 || zUuid!=0 ){
640
+ if( zUuid==0 ) SYNTAX("missing hash on F-card");
630641
if( !hname_validate(zUuid,sz) ){
631642
SYNTAX("F-card hash invalid");
632643
}
633644
}
634645
zPerm = next_token(&x,0);
@@ -643,17 +654,24 @@
643654
p->nFileAlloc = p->nFileAlloc*2 + 10;
644655
p->aFile = fossil_realloc(p->aFile,
645656
p->nFileAlloc*sizeof(p->aFile[0]) );
646657
}
647658
i = p->nFile++;
659
+ if( i>0 && fossil_strcmp(p->aFile[i-1].zName, zName)>=0 ){
660
+ SYNTAX("incorrect F-card sort order");
661
+ }
662
+ if( file_is_reserved_name(zName,-1) ){
663
+ /* If reserved names leaked into historical manifests due to
664
+ ** slack oversight by older versions of Fossil, simply ignore
665
+ ** those files */
666
+ p->nFile--;
667
+ break;
668
+ }
648669
p->aFile[i].zName = zName;
649670
p->aFile[i].zUuid = zUuid;
650671
p->aFile[i].zPerm = zPerm;
651672
p->aFile[i].zPrior = zPriorName;
652
- if( i>0 && fossil_strcmp(p->aFile[i-1].zName, zName)>=0 ){
653
- SYNTAX("incorrect F-card sort order");
654
- }
655673
p->type = CFTYPE_MANIFEST;
656674
break;
657675
}
658676
659677
/*
660678
--- src/manifest.c
+++ src/manifest.c
@@ -481,14 +481,23 @@
481 blob_appendf(pErr, "line 1 not recognized");
482 return 0;
483 }
484 /* Then verify the Z-card.
485 */
 
 
 
486 if( verify_z_card(z, n, pErr)==2 ){
487 blob_reset(pContent);
488 return 0;
489 }
 
 
 
 
 
 
490
491 /* Allocate a Manifest object to hold the parsed control artifact.
492 */
493 p = fossil_malloc( sizeof(*p) );
494 memset(p, 0, sizeof(*p));
@@ -601,10 +610,11 @@
601 case 'E': {
602 if( p->rEventDate>0.0 ) SYNTAX("more than one E-card");
603 p->rEventDate = db_double(0.0,"SELECT julianday(%Q)", next_token(&x,0));
604 if( p->rEventDate<=0.0 ) SYNTAX("malformed date on E-card");
605 p->zEventId = next_token(&x, &sz);
 
606 if( !hname_validate(p->zEventId, sz) ){
607 SYNTAX("malformed hash on E-card");
608 }
609 p->type = CFTYPE_EVENT;
610 break;
@@ -625,10 +635,11 @@
625 if( !file_is_simple_pathname_nonstrict(zName) ){
626 SYNTAX("F-card filename is not a simple path");
627 }
628 zUuid = next_token(&x, &sz);
629 if( p->zBaseline==0 || zUuid!=0 ){
 
630 if( !hname_validate(zUuid,sz) ){
631 SYNTAX("F-card hash invalid");
632 }
633 }
634 zPerm = next_token(&x,0);
@@ -643,17 +654,24 @@
643 p->nFileAlloc = p->nFileAlloc*2 + 10;
644 p->aFile = fossil_realloc(p->aFile,
645 p->nFileAlloc*sizeof(p->aFile[0]) );
646 }
647 i = p->nFile++;
 
 
 
 
 
 
 
 
 
 
648 p->aFile[i].zName = zName;
649 p->aFile[i].zUuid = zUuid;
650 p->aFile[i].zPerm = zPerm;
651 p->aFile[i].zPrior = zPriorName;
652 if( i>0 && fossil_strcmp(p->aFile[i-1].zName, zName)>=0 ){
653 SYNTAX("incorrect F-card sort order");
654 }
655 p->type = CFTYPE_MANIFEST;
656 break;
657 }
658
659 /*
660
--- src/manifest.c
+++ src/manifest.c
@@ -481,14 +481,23 @@
481 blob_appendf(pErr, "line 1 not recognized");
482 return 0;
483 }
484 /* Then verify the Z-card.
485 */
486 #if 1
487 /* Disable this ***ONLY*** (ONLY!) when testing hand-written inputs
488 for card-related syntax errors. */
489 if( verify_z_card(z, n, pErr)==2 ){
490 blob_reset(pContent);
491 return 0;
492 }
493 #else
494 #warning ACHTUNG - z-card check is disabled for testing purposes.
495 if(0 && verify_z_card(NULL, 0, NULL)){
496 /*avoid unused static func error*/
497 }
498 #endif
499
500 /* Allocate a Manifest object to hold the parsed control artifact.
501 */
502 p = fossil_malloc( sizeof(*p) );
503 memset(p, 0, sizeof(*p));
@@ -601,10 +610,11 @@
610 case 'E': {
611 if( p->rEventDate>0.0 ) SYNTAX("more than one E-card");
612 p->rEventDate = db_double(0.0,"SELECT julianday(%Q)", next_token(&x,0));
613 if( p->rEventDate<=0.0 ) SYNTAX("malformed date on E-card");
614 p->zEventId = next_token(&x, &sz);
615 if( p->zEventId==0 ) SYNTAX("missing hash on E-card");
616 if( !hname_validate(p->zEventId, sz) ){
617 SYNTAX("malformed hash on E-card");
618 }
619 p->type = CFTYPE_EVENT;
620 break;
@@ -625,10 +635,11 @@
635 if( !file_is_simple_pathname_nonstrict(zName) ){
636 SYNTAX("F-card filename is not a simple path");
637 }
638 zUuid = next_token(&x, &sz);
639 if( p->zBaseline==0 || zUuid!=0 ){
640 if( zUuid==0 ) SYNTAX("missing hash on F-card");
641 if( !hname_validate(zUuid,sz) ){
642 SYNTAX("F-card hash invalid");
643 }
644 }
645 zPerm = next_token(&x,0);
@@ -643,17 +654,24 @@
654 p->nFileAlloc = p->nFileAlloc*2 + 10;
655 p->aFile = fossil_realloc(p->aFile,
656 p->nFileAlloc*sizeof(p->aFile[0]) );
657 }
658 i = p->nFile++;
659 if( i>0 && fossil_strcmp(p->aFile[i-1].zName, zName)>=0 ){
660 SYNTAX("incorrect F-card sort order");
661 }
662 if( file_is_reserved_name(zName,-1) ){
663 /* If reserved names leaked into historical manifests due to
664 ** slack oversight by older versions of Fossil, simply ignore
665 ** those files */
666 p->nFile--;
667 break;
668 }
669 p->aFile[i].zName = zName;
670 p->aFile[i].zUuid = zUuid;
671 p->aFile[i].zPerm = zPerm;
672 p->aFile[i].zPrior = zPriorName;
 
 
 
673 p->type = CFTYPE_MANIFEST;
674 break;
675 }
676
677 /*
678
+5 -1
--- src/mkindex.c
+++ src/mkindex.c
@@ -90,10 +90,11 @@
9090
#define CMDFLAG_SETTING 0x0020 /* A setting */
9191
#define CMDFLAG_VERSIONABLE 0x0040 /* A versionable setting */
9292
#define CMDFLAG_BLOCKTEXT 0x0080 /* Multi-line text setting */
9393
#define CMDFLAG_BOOLEAN 0x0100 /* A boolean setting */
9494
#define CMDFLAG_RAWCONTENT 0x0200 /* Do not interpret webpage content */
95
+#define CMDFLAG_SENSITIVE 0x0400 /* Security-sensitive setting */
9596
/**************************************************************************/
9697
9798
/*
9899
** Each entry looks like this:
99100
*/
@@ -248,10 +249,12 @@
248249
}else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){
249250
aEntry[nUsed].eType &= ~(CMDFLAG_BOOLEAN);
250251
aEntry[nUsed].eType |= CMDFLAG_BLOCKTEXT;
251252
}else if( j==11 && strncmp(&zLine[i], "versionable", j)==0 ){
252253
aEntry[nUsed].eType |= CMDFLAG_VERSIONABLE;
254
+ }else if( j==9 && strncmp(&zLine[i], "sensitive", j)==0 ){
255
+ aEntry[nUsed].eType |= CMDFLAG_SENSITIVE;
253256
}else if( j>6 && strncmp(&zLine[i], "width=", 6)==0 ){
254257
aEntry[nUsed].iWidth = atoi(&zLine[i+6]);
255258
}else if( j>8 && strncmp(&zLine[i], "default=", 8)==0 ){
256259
aEntry[nUsed].zDflt = string_dup(&zLine[i+8], j-8);
257260
}else if( j>9 && strncmp(&zLine[i], "variable=", 9)==0 ){
@@ -479,14 +482,15 @@
479482
if( zVar ){
480483
printf(" \"%s\",%*s", zVar, (int)(15-strlen(zVar)), "");
481484
}else{
482485
printf(" 0,%*s", 16, "");
483486
}
484
- printf(" %3d, %d, %d, \"%s\"%*s },\n",
487
+ printf(" %3d, %d, %d, %d, \"%s\"%*s },\n",
485488
aEntry[i].iWidth,
486489
(aEntry[i].eType & CMDFLAG_VERSIONABLE)!=0,
487490
(aEntry[i].eType & CMDFLAG_BLOCKTEXT)!=0,
491
+ (aEntry[i].eType & CMDFLAG_SENSITIVE)!=0,
488492
zDef, (int)(10-strlen(zDef)), ""
489493
);
490494
if( aEntry[i].zIf ){
491495
printf("#endif\n");
492496
}
493497
--- src/mkindex.c
+++ src/mkindex.c
@@ -90,10 +90,11 @@
90 #define CMDFLAG_SETTING 0x0020 /* A setting */
91 #define CMDFLAG_VERSIONABLE 0x0040 /* A versionable setting */
92 #define CMDFLAG_BLOCKTEXT 0x0080 /* Multi-line text setting */
93 #define CMDFLAG_BOOLEAN 0x0100 /* A boolean setting */
94 #define CMDFLAG_RAWCONTENT 0x0200 /* Do not interpret webpage content */
 
95 /**************************************************************************/
96
97 /*
98 ** Each entry looks like this:
99 */
@@ -248,10 +249,12 @@
248 }else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){
249 aEntry[nUsed].eType &= ~(CMDFLAG_BOOLEAN);
250 aEntry[nUsed].eType |= CMDFLAG_BLOCKTEXT;
251 }else if( j==11 && strncmp(&zLine[i], "versionable", j)==0 ){
252 aEntry[nUsed].eType |= CMDFLAG_VERSIONABLE;
 
 
253 }else if( j>6 && strncmp(&zLine[i], "width=", 6)==0 ){
254 aEntry[nUsed].iWidth = atoi(&zLine[i+6]);
255 }else if( j>8 && strncmp(&zLine[i], "default=", 8)==0 ){
256 aEntry[nUsed].zDflt = string_dup(&zLine[i+8], j-8);
257 }else if( j>9 && strncmp(&zLine[i], "variable=", 9)==0 ){
@@ -479,14 +482,15 @@
479 if( zVar ){
480 printf(" \"%s\",%*s", zVar, (int)(15-strlen(zVar)), "");
481 }else{
482 printf(" 0,%*s", 16, "");
483 }
484 printf(" %3d, %d, %d, \"%s\"%*s },\n",
485 aEntry[i].iWidth,
486 (aEntry[i].eType & CMDFLAG_VERSIONABLE)!=0,
487 (aEntry[i].eType & CMDFLAG_BLOCKTEXT)!=0,
 
488 zDef, (int)(10-strlen(zDef)), ""
489 );
490 if( aEntry[i].zIf ){
491 printf("#endif\n");
492 }
493
--- src/mkindex.c
+++ src/mkindex.c
@@ -90,10 +90,11 @@
90 #define CMDFLAG_SETTING 0x0020 /* A setting */
91 #define CMDFLAG_VERSIONABLE 0x0040 /* A versionable setting */
92 #define CMDFLAG_BLOCKTEXT 0x0080 /* Multi-line text setting */
93 #define CMDFLAG_BOOLEAN 0x0100 /* A boolean setting */
94 #define CMDFLAG_RAWCONTENT 0x0200 /* Do not interpret webpage content */
95 #define CMDFLAG_SENSITIVE 0x0400 /* Security-sensitive setting */
96 /**************************************************************************/
97
98 /*
99 ** Each entry looks like this:
100 */
@@ -248,10 +249,12 @@
249 }else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){
250 aEntry[nUsed].eType &= ~(CMDFLAG_BOOLEAN);
251 aEntry[nUsed].eType |= CMDFLAG_BLOCKTEXT;
252 }else if( j==11 && strncmp(&zLine[i], "versionable", j)==0 ){
253 aEntry[nUsed].eType |= CMDFLAG_VERSIONABLE;
254 }else if( j==9 && strncmp(&zLine[i], "sensitive", j)==0 ){
255 aEntry[nUsed].eType |= CMDFLAG_SENSITIVE;
256 }else if( j>6 && strncmp(&zLine[i], "width=", 6)==0 ){
257 aEntry[nUsed].iWidth = atoi(&zLine[i+6]);
258 }else if( j>8 && strncmp(&zLine[i], "default=", 8)==0 ){
259 aEntry[nUsed].zDflt = string_dup(&zLine[i+8], j-8);
260 }else if( j>9 && strncmp(&zLine[i], "variable=", 9)==0 ){
@@ -479,14 +482,15 @@
482 if( zVar ){
483 printf(" \"%s\",%*s", zVar, (int)(15-strlen(zVar)), "");
484 }else{
485 printf(" 0,%*s", 16, "");
486 }
487 printf(" %3d, %d, %d, %d, \"%s\"%*s },\n",
488 aEntry[i].iWidth,
489 (aEntry[i].eType & CMDFLAG_VERSIONABLE)!=0,
490 (aEntry[i].eType & CMDFLAG_BLOCKTEXT)!=0,
491 (aEntry[i].eType & CMDFLAG_SENSITIVE)!=0,
492 zDef, (int)(10-strlen(zDef)), ""
493 );
494 if( aEntry[i].zIf ){
495 printf("#endif\n");
496 }
497
--- src/printf.c
+++ src/printf.c
@@ -1148,12 +1148,15 @@
11481148
rc = fossil_print_error(rc, z);
11491149
abort();
11501150
exit(rc);
11511151
}
11521152
NORETURN void fossil_fatal(const char *zFormat, ...){
1153
+ static int once = 0;
11531154
char *z;
11541155
int rc = 1;
1156
+ if( once ) exit(1);
1157
+ once = 1;
11551158
va_list ap;
11561159
mainInFatalError = 1;
11571160
va_start(ap, zFormat);
11581161
z = vmprintf(zFormat, ap);
11591162
va_end(ap);
11601163
--- src/printf.c
+++ src/printf.c
@@ -1148,12 +1148,15 @@
1148 rc = fossil_print_error(rc, z);
1149 abort();
1150 exit(rc);
1151 }
1152 NORETURN void fossil_fatal(const char *zFormat, ...){
 
1153 char *z;
1154 int rc = 1;
 
 
1155 va_list ap;
1156 mainInFatalError = 1;
1157 va_start(ap, zFormat);
1158 z = vmprintf(zFormat, ap);
1159 va_end(ap);
1160
--- src/printf.c
+++ src/printf.c
@@ -1148,12 +1148,15 @@
1148 rc = fossil_print_error(rc, z);
1149 abort();
1150 exit(rc);
1151 }
1152 NORETURN void fossil_fatal(const char *zFormat, ...){
1153 static int once = 0;
1154 char *z;
1155 int rc = 1;
1156 if( once ) exit(1);
1157 once = 1;
1158 va_list ap;
1159 mainInFatalError = 1;
1160 va_start(ap, zFormat);
1161 z = vmprintf(zFormat, ap);
1162 va_end(ap);
1163
--- src/rebuild.c
+++ src/rebuild.c
@@ -52,10 +52,11 @@
5252
}
5353
5454
/* Add the user.mtime column if it is missing. (2011-04-27)
5555
*/
5656
if( !db_table_has_column("repository", "user", "mtime") ){
57
+ db_unprotect(PROTECT_ALL);
5758
db_multi_exec(
5859
"CREATE TEMP TABLE temp_user AS SELECT * FROM user;"
5960
"DROP TABLE user;"
6061
"CREATE TABLE user(\n"
6162
" uid INTEGER PRIMARY KEY,\n"
@@ -72,19 +73,22 @@
7273
"INSERT OR IGNORE INTO user"
7374
" SELECT uid, login, pw, cap, cookie,"
7475
" ipaddr, cexpire, info, now(), photo FROM temp_user;"
7576
"DROP TABLE temp_user;"
7677
);
78
+ db_protect_pop();
7779
}
7880
7981
/* Add the config.mtime column if it is missing. (2011-04-27)
8082
*/
8183
if( !db_table_has_column("repository", "config", "mtime") ){
84
+ db_unprotect(PROTECT_CONFIG);
8285
db_multi_exec(
8386
"ALTER TABLE config ADD COLUMN mtime INTEGER;"
8487
"UPDATE config SET mtime=now();"
8588
);
89
+ db_protect_pop();
8690
}
8791
8892
/* Add the shun.mtime and shun.scom columns if they are missing.
8993
** (2011-04-27)
9094
*/
@@ -382,10 +386,11 @@
382386
percent_complete(0);
383387
}
384388
alert_triggers_disable();
385389
rebuild_update_schema();
386390
blob_init(&sql, 0, 0);
391
+ db_unprotect(PROTECT_ALL);
387392
db_prepare(&q,
388393
"SELECT name FROM sqlite_schema /*scan*/"
389394
" WHERE type='table'"
390395
" AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
391396
"'config','shun','private','reportfmt',"
@@ -475,10 +480,11 @@
475480
alert_triggers_enable();
476481
if(!g.fQuiet && ttyOutput ){
477482
percent_complete(1000);
478483
fossil_print("\n");
479484
}
485
+ db_protect_pop();
480486
return errCnt;
481487
}
482488
483489
/*
484490
** Number of neighbors to search
@@ -667,10 +673,11 @@
667673
668674
/* We should be done with options.. */
669675
verify_all_options();
670676
671677
db_begin_transaction();
678
+ db_unprotect(PROTECT_ALL);
672679
if( !compressOnlyFlag ){
673680
search_drop_index();
674681
ttyOutput = 1;
675682
errCnt = rebuild_db(randomizeFlag, 1, doClustering);
676683
reconstruct_private_table();
@@ -720,10 +727,11 @@
720727
if( activateWal ){
721728
db_multi_exec("PRAGMA journal_mode=WAL;");
722729
}
723730
}
724731
if( runReindex ) search_rebuild_index();
732
+ db_protect_pop();
725733
if( showStats ){
726734
static const struct { int idx; const char *zLabel; } aStat[] = {
727735
{ CFTYPE_ANY, "Artifacts:" },
728736
{ CFTYPE_MANIFEST, "Manifests:" },
729737
{ CFTYPE_CLUSTER, "Clusters:" },
@@ -755,18 +763,20 @@
755763
** testing by cloning a working project repository.
756764
*/
757765
void test_detach_cmd(void){
758766
db_find_and_open_repository(0, 2);
759767
db_begin_transaction();
768
+ db_unprotect(PROTECT_CONFIG);
760769
db_multi_exec(
761770
"DELETE FROM config WHERE name GLOB 'last-sync-*';"
762771
"DELETE FROM config WHERE name GLOB 'sync-*:*';"
763772
"UPDATE config SET value=lower(hex(randomblob(20)))"
764773
" WHERE name='project-code';"
765774
"UPDATE config SET value='detached-' || value"
766775
" WHERE name='project-name' AND value NOT GLOB 'detached-*';"
767776
);
777
+ db_protect_pop();
768778
db_end_transaction(0);
769779
}
770780
771781
/*
772782
** COMMAND: test-create-clusters
@@ -910,10 +920,11 @@
910920
if( privateOnly || bVerily ){
911921
bNeedRebuild = db_exists("SELECT 1 FROM private");
912922
delete_private_content();
913923
}
914924
if( !privateOnly ){
925
+ db_unprotect(PROTECT_ALL);
915926
db_multi_exec(
916927
"UPDATE user SET pw='';"
917928
"DELETE FROM config WHERE name GLOB 'last-sync-*';"
918929
"DELETE FROM config WHERE name GLOB 'sync-*:*';"
919930
"DELETE FROM config WHERE name GLOB 'peer-*';"
@@ -933,14 +944,17 @@
933944
"DROP TABLE IF EXISTS purgeitem;\n"
934945
"DROP TABLE IF EXISTS admin_log;\n"
935946
"DROP TABLE IF EXISTS vcache;\n"
936947
);
937948
}
949
+ db_protect_pop();
938950
}
939951
if( !bNeedRebuild ){
940952
db_end_transaction(0);
953
+ db_unprotect(PROTECT_ALL);
941954
db_multi_exec("VACUUM;");
955
+ db_protect_pop();
942956
}else{
943957
rebuild_db(0, 1, 0);
944958
db_end_transaction(0);
945959
}
946960
}
947961
--- src/rebuild.c
+++ src/rebuild.c
@@ -52,10 +52,11 @@
52 }
53
54 /* Add the user.mtime column if it is missing. (2011-04-27)
55 */
56 if( !db_table_has_column("repository", "user", "mtime") ){
 
57 db_multi_exec(
58 "CREATE TEMP TABLE temp_user AS SELECT * FROM user;"
59 "DROP TABLE user;"
60 "CREATE TABLE user(\n"
61 " uid INTEGER PRIMARY KEY,\n"
@@ -72,19 +73,22 @@
72 "INSERT OR IGNORE INTO user"
73 " SELECT uid, login, pw, cap, cookie,"
74 " ipaddr, cexpire, info, now(), photo FROM temp_user;"
75 "DROP TABLE temp_user;"
76 );
 
77 }
78
79 /* Add the config.mtime column if it is missing. (2011-04-27)
80 */
81 if( !db_table_has_column("repository", "config", "mtime") ){
 
82 db_multi_exec(
83 "ALTER TABLE config ADD COLUMN mtime INTEGER;"
84 "UPDATE config SET mtime=now();"
85 );
 
86 }
87
88 /* Add the shun.mtime and shun.scom columns if they are missing.
89 ** (2011-04-27)
90 */
@@ -382,10 +386,11 @@
382 percent_complete(0);
383 }
384 alert_triggers_disable();
385 rebuild_update_schema();
386 blob_init(&sql, 0, 0);
 
387 db_prepare(&q,
388 "SELECT name FROM sqlite_schema /*scan*/"
389 " WHERE type='table'"
390 " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
391 "'config','shun','private','reportfmt',"
@@ -475,10 +480,11 @@
475 alert_triggers_enable();
476 if(!g.fQuiet && ttyOutput ){
477 percent_complete(1000);
478 fossil_print("\n");
479 }
 
480 return errCnt;
481 }
482
483 /*
484 ** Number of neighbors to search
@@ -667,10 +673,11 @@
667
668 /* We should be done with options.. */
669 verify_all_options();
670
671 db_begin_transaction();
 
672 if( !compressOnlyFlag ){
673 search_drop_index();
674 ttyOutput = 1;
675 errCnt = rebuild_db(randomizeFlag, 1, doClustering);
676 reconstruct_private_table();
@@ -720,10 +727,11 @@
720 if( activateWal ){
721 db_multi_exec("PRAGMA journal_mode=WAL;");
722 }
723 }
724 if( runReindex ) search_rebuild_index();
 
725 if( showStats ){
726 static const struct { int idx; const char *zLabel; } aStat[] = {
727 { CFTYPE_ANY, "Artifacts:" },
728 { CFTYPE_MANIFEST, "Manifests:" },
729 { CFTYPE_CLUSTER, "Clusters:" },
@@ -755,18 +763,20 @@
755 ** testing by cloning a working project repository.
756 */
757 void test_detach_cmd(void){
758 db_find_and_open_repository(0, 2);
759 db_begin_transaction();
 
760 db_multi_exec(
761 "DELETE FROM config WHERE name GLOB 'last-sync-*';"
762 "DELETE FROM config WHERE name GLOB 'sync-*:*';"
763 "UPDATE config SET value=lower(hex(randomblob(20)))"
764 " WHERE name='project-code';"
765 "UPDATE config SET value='detached-' || value"
766 " WHERE name='project-name' AND value NOT GLOB 'detached-*';"
767 );
 
768 db_end_transaction(0);
769 }
770
771 /*
772 ** COMMAND: test-create-clusters
@@ -910,10 +920,11 @@
910 if( privateOnly || bVerily ){
911 bNeedRebuild = db_exists("SELECT 1 FROM private");
912 delete_private_content();
913 }
914 if( !privateOnly ){
 
915 db_multi_exec(
916 "UPDATE user SET pw='';"
917 "DELETE FROM config WHERE name GLOB 'last-sync-*';"
918 "DELETE FROM config WHERE name GLOB 'sync-*:*';"
919 "DELETE FROM config WHERE name GLOB 'peer-*';"
@@ -933,14 +944,17 @@
933 "DROP TABLE IF EXISTS purgeitem;\n"
934 "DROP TABLE IF EXISTS admin_log;\n"
935 "DROP TABLE IF EXISTS vcache;\n"
936 );
937 }
 
938 }
939 if( !bNeedRebuild ){
940 db_end_transaction(0);
 
941 db_multi_exec("VACUUM;");
 
942 }else{
943 rebuild_db(0, 1, 0);
944 db_end_transaction(0);
945 }
946 }
947
--- src/rebuild.c
+++ src/rebuild.c
@@ -52,10 +52,11 @@
52 }
53
54 /* Add the user.mtime column if it is missing. (2011-04-27)
55 */
56 if( !db_table_has_column("repository", "user", "mtime") ){
57 db_unprotect(PROTECT_ALL);
58 db_multi_exec(
59 "CREATE TEMP TABLE temp_user AS SELECT * FROM user;"
60 "DROP TABLE user;"
61 "CREATE TABLE user(\n"
62 " uid INTEGER PRIMARY KEY,\n"
@@ -72,19 +73,22 @@
73 "INSERT OR IGNORE INTO user"
74 " SELECT uid, login, pw, cap, cookie,"
75 " ipaddr, cexpire, info, now(), photo FROM temp_user;"
76 "DROP TABLE temp_user;"
77 );
78 db_protect_pop();
79 }
80
81 /* Add the config.mtime column if it is missing. (2011-04-27)
82 */
83 if( !db_table_has_column("repository", "config", "mtime") ){
84 db_unprotect(PROTECT_CONFIG);
85 db_multi_exec(
86 "ALTER TABLE config ADD COLUMN mtime INTEGER;"
87 "UPDATE config SET mtime=now();"
88 );
89 db_protect_pop();
90 }
91
92 /* Add the shun.mtime and shun.scom columns if they are missing.
93 ** (2011-04-27)
94 */
@@ -382,10 +386,11 @@
386 percent_complete(0);
387 }
388 alert_triggers_disable();
389 rebuild_update_schema();
390 blob_init(&sql, 0, 0);
391 db_unprotect(PROTECT_ALL);
392 db_prepare(&q,
393 "SELECT name FROM sqlite_schema /*scan*/"
394 " WHERE type='table'"
395 " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
396 "'config','shun','private','reportfmt',"
@@ -475,10 +480,11 @@
480 alert_triggers_enable();
481 if(!g.fQuiet && ttyOutput ){
482 percent_complete(1000);
483 fossil_print("\n");
484 }
485 db_protect_pop();
486 return errCnt;
487 }
488
489 /*
490 ** Number of neighbors to search
@@ -667,10 +673,11 @@
673
674 /* We should be done with options.. */
675 verify_all_options();
676
677 db_begin_transaction();
678 db_unprotect(PROTECT_ALL);
679 if( !compressOnlyFlag ){
680 search_drop_index();
681 ttyOutput = 1;
682 errCnt = rebuild_db(randomizeFlag, 1, doClustering);
683 reconstruct_private_table();
@@ -720,10 +727,11 @@
727 if( activateWal ){
728 db_multi_exec("PRAGMA journal_mode=WAL;");
729 }
730 }
731 if( runReindex ) search_rebuild_index();
732 db_protect_pop();
733 if( showStats ){
734 static const struct { int idx; const char *zLabel; } aStat[] = {
735 { CFTYPE_ANY, "Artifacts:" },
736 { CFTYPE_MANIFEST, "Manifests:" },
737 { CFTYPE_CLUSTER, "Clusters:" },
@@ -755,18 +763,20 @@
763 ** testing by cloning a working project repository.
764 */
765 void test_detach_cmd(void){
766 db_find_and_open_repository(0, 2);
767 db_begin_transaction();
768 db_unprotect(PROTECT_CONFIG);
769 db_multi_exec(
770 "DELETE FROM config WHERE name GLOB 'last-sync-*';"
771 "DELETE FROM config WHERE name GLOB 'sync-*:*';"
772 "UPDATE config SET value=lower(hex(randomblob(20)))"
773 " WHERE name='project-code';"
774 "UPDATE config SET value='detached-' || value"
775 " WHERE name='project-name' AND value NOT GLOB 'detached-*';"
776 );
777 db_protect_pop();
778 db_end_transaction(0);
779 }
780
781 /*
782 ** COMMAND: test-create-clusters
@@ -910,10 +920,11 @@
920 if( privateOnly || bVerily ){
921 bNeedRebuild = db_exists("SELECT 1 FROM private");
922 delete_private_content();
923 }
924 if( !privateOnly ){
925 db_unprotect(PROTECT_ALL);
926 db_multi_exec(
927 "UPDATE user SET pw='';"
928 "DELETE FROM config WHERE name GLOB 'last-sync-*';"
929 "DELETE FROM config WHERE name GLOB 'sync-*:*';"
930 "DELETE FROM config WHERE name GLOB 'peer-*';"
@@ -933,14 +944,17 @@
944 "DROP TABLE IF EXISTS purgeitem;\n"
945 "DROP TABLE IF EXISTS admin_log;\n"
946 "DROP TABLE IF EXISTS vcache;\n"
947 );
948 }
949 db_protect_pop();
950 }
951 if( !bNeedRebuild ){
952 db_end_transaction(0);
953 db_unprotect(PROTECT_ALL);
954 db_multi_exec("VACUUM;");
955 db_protect_pop();
956 }else{
957 rebuild_db(0, 1, 0);
958 db_end_transaction(0);
959 }
960 }
961
+3 -3
--- src/report.c
+++ src/report.c
@@ -230,15 +230,15 @@
230230
231231
/*
232232
** Activate the query authorizer
233233
*/
234234
void report_restrict_sql(char **pzErr){
235
- sqlite3_set_authorizer(g.db, report_query_authorizer, (void*)pzErr);
235
+ db_set_authorizer(report_query_authorizer,(void*)pzErr,"Ticket-Report");
236236
sqlite3_limit(g.db, SQLITE_LIMIT_VDBE_OP, 10000);
237237
}
238238
void report_unrestrict_sql(void){
239
- sqlite3_set_authorizer(g.db, 0, 0);
239
+ db_clear_authorizer();
240240
}
241241
242242
243243
/*
244244
** Check the given SQL to see if is a valid query that does not
@@ -680,11 +680,11 @@
680680
*/
681681
if( pState->nCount==0 ){
682682
/* Turn off the authorizer. It is no longer doing anything since the
683683
** query has already been prepared.
684684
*/
685
- sqlite3_set_authorizer(g.db, 0, 0);
685
+ db_clear_authorizer();
686686
687687
/* Figure out the number of columns, the column that determines background
688688
** color, and whether or not this row of data is represented by multiple
689689
** rows in the table.
690690
*/
691691
--- src/report.c
+++ src/report.c
@@ -230,15 +230,15 @@
230
231 /*
232 ** Activate the query authorizer
233 */
234 void report_restrict_sql(char **pzErr){
235 sqlite3_set_authorizer(g.db, report_query_authorizer, (void*)pzErr);
236 sqlite3_limit(g.db, SQLITE_LIMIT_VDBE_OP, 10000);
237 }
238 void report_unrestrict_sql(void){
239 sqlite3_set_authorizer(g.db, 0, 0);
240 }
241
242
243 /*
244 ** Check the given SQL to see if is a valid query that does not
@@ -680,11 +680,11 @@
680 */
681 if( pState->nCount==0 ){
682 /* Turn off the authorizer. It is no longer doing anything since the
683 ** query has already been prepared.
684 */
685 sqlite3_set_authorizer(g.db, 0, 0);
686
687 /* Figure out the number of columns, the column that determines background
688 ** color, and whether or not this row of data is represented by multiple
689 ** rows in the table.
690 */
691
--- src/report.c
+++ src/report.c
@@ -230,15 +230,15 @@
230
231 /*
232 ** Activate the query authorizer
233 */
234 void report_restrict_sql(char **pzErr){
235 db_set_authorizer(report_query_authorizer,(void*)pzErr,"Ticket-Report");
236 sqlite3_limit(g.db, SQLITE_LIMIT_VDBE_OP, 10000);
237 }
238 void report_unrestrict_sql(void){
239 db_clear_authorizer();
240 }
241
242
243 /*
244 ** Check the given SQL to see if is a valid query that does not
@@ -680,11 +680,11 @@
680 */
681 if( pState->nCount==0 ){
682 /* Turn off the authorizer. It is no longer doing anything since the
683 ** query has already been prepared.
684 */
685 db_clear_authorizer();
686
687 /* Figure out the number of columns, the column that determines background
688 ** color, and whether or not this row of data is represented by multiple
689 ** rows in the table.
690 */
691
--- src/security_audit.c
+++ src/security_audit.c
@@ -281,10 +281,18 @@
281281
@ <p>Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum"
282282
@ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5")
283283
@ from users "anonymous" and "nobody"
284284
@ on the <a href="setup_ulist">User Configuration</a> page.
285285
}
286
+
287
+ /* The strict-manifest-syntax setting should be on. */
288
+ if( db_get_boolean("strict-manifest-syntax",1)==0 ){
289
+ @ <li><p><b>WARNING:</b>
290
+ @ The "strict-manifest-syntax" flag is off. This is a security
291
+ @ risk. Turn this setting on (its default) to protect the users
292
+ @ of this repository.
293
+ }
286294
287295
/* Obsolete: */
288296
if( hasAnyCap(zAnonCap, "d") ||
289297
hasAnyCap(zDevCap, "d") ||
290298
hasAnyCap(zReadCap, "d") ){
@@ -596,15 +604,17 @@
596604
if( P("cancel") ){
597605
/* User pressed the cancel button. Go back */
598606
cgi_redirect("secaudit0");
599607
}
600608
if( P("apply") ){
609
+ db_unprotect(PROTECT_USER);
601610
db_multi_exec(
602611
"UPDATE user SET cap=''"
603612
" WHERE login IN ('nobody','anonymous');"
604613
"DELETE FROM config WHERE name='public-pages';"
605614
);
615
+ db_protect_pop();
606616
db_set("self-register","0",0);
607617
cgi_redirect("secaudit0");
608618
}
609619
style_header("Make This Website Private");
610620
@ <p>Click the "Make It Private" button below to disable all
611621
--- src/security_audit.c
+++ src/security_audit.c
@@ -281,10 +281,18 @@
281 @ <p>Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum"
282 @ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5")
283 @ from users "anonymous" and "nobody"
284 @ on the <a href="setup_ulist">User Configuration</a> page.
285 }
 
 
 
 
 
 
 
 
286
287 /* Obsolete: */
288 if( hasAnyCap(zAnonCap, "d") ||
289 hasAnyCap(zDevCap, "d") ||
290 hasAnyCap(zReadCap, "d") ){
@@ -596,15 +604,17 @@
596 if( P("cancel") ){
597 /* User pressed the cancel button. Go back */
598 cgi_redirect("secaudit0");
599 }
600 if( P("apply") ){
 
601 db_multi_exec(
602 "UPDATE user SET cap=''"
603 " WHERE login IN ('nobody','anonymous');"
604 "DELETE FROM config WHERE name='public-pages';"
605 );
 
606 db_set("self-register","0",0);
607 cgi_redirect("secaudit0");
608 }
609 style_header("Make This Website Private");
610 @ <p>Click the "Make It Private" button below to disable all
611
--- src/security_audit.c
+++ src/security_audit.c
@@ -281,10 +281,18 @@
281 @ <p>Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum"
282 @ privileges (<a href="%R/setup_ucap_list">capabilities</a> "fq5")
283 @ from users "anonymous" and "nobody"
284 @ on the <a href="setup_ulist">User Configuration</a> page.
285 }
286
287 /* The strict-manifest-syntax setting should be on. */
288 if( db_get_boolean("strict-manifest-syntax",1)==0 ){
289 @ <li><p><b>WARNING:</b>
290 @ The "strict-manifest-syntax" flag is off. This is a security
291 @ risk. Turn this setting on (its default) to protect the users
292 @ of this repository.
293 }
294
295 /* Obsolete: */
296 if( hasAnyCap(zAnonCap, "d") ||
297 hasAnyCap(zDevCap, "d") ||
298 hasAnyCap(zReadCap, "d") ){
@@ -596,15 +604,17 @@
604 if( P("cancel") ){
605 /* User pressed the cancel button. Go back */
606 cgi_redirect("secaudit0");
607 }
608 if( P("apply") ){
609 db_unprotect(PROTECT_USER);
610 db_multi_exec(
611 "UPDATE user SET cap=''"
612 " WHERE login IN ('nobody','anonymous');"
613 "DELETE FROM config WHERE name='public-pages';"
614 );
615 db_protect_pop();
616 db_set("self-register","0",0);
617 cgi_redirect("secaudit0");
618 }
619 style_header("Make This Website Private");
620 @ <p>Click the "Make It Private" button below to disable all
621
+22
--- src/setup.c
+++ src/setup.c
@@ -27,14 +27,16 @@
2727
*/
2828
void setup_incr_cfgcnt(void){
2929
static int once = 1;
3030
if( once ){
3131
once = 0;
32
+ db_unprotect(PROTECT_CONFIG);
3233
db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'");
3334
if( db_changes()==0 ){
3435
db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)");
3536
}
37
+ db_protect_pop();
3638
}
3739
}
3840
3941
/*
4042
** Output a single entry for a menu generated using an HTML table.
@@ -195,11 +197,13 @@
195197
}
196198
if( zQ ){
197199
int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
198200
if( iQ!=iVal ){
199201
login_verify_csrf_secret();
202
+ db_protect_only(PROTECT_NONE);
200203
db_set(zVar, iQ ? "1" : "0", 0);
204
+ db_protect_pop();
201205
setup_incr_cfgcnt();
202206
admin_log("Set option [%q] to [%q].",
203207
zVar, iQ ? "on" : "off");
204208
iVal = iQ;
205209
}
@@ -230,11 +234,13 @@
230234
const char *zQ = P(zQParm);
231235
if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
232236
const int nZQ = (int)strlen(zQ);
233237
login_verify_csrf_secret();
234238
setup_incr_cfgcnt();
239
+ db_protect_only(PROTECT_NONE);
235240
db_set(zVar, zQ, 0);
241
+ db_protect_pop();
236242
admin_log("Set entry_attribute %Q to: %.*s%s",
237243
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
238244
zVal = zQ;
239245
}
240246
@ <input aria-label="%h(zLabel[0]?zLabel:zQParm)" type="text" \
@@ -260,11 +266,13 @@
260266
const char *z = db_get(zVar, zDflt);
261267
const char *zQ = P(zQP);
262268
if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
263269
const int nZQ = (int)strlen(zQ);
264270
login_verify_csrf_secret();
271
+ db_protect_only(PROTECT_NONE);
265272
db_set(zVar, zQ, 0);
273
+ db_protect_pop();
266274
setup_incr_cfgcnt();
267275
admin_log("Set textarea_attribute %Q to: %.*s%s",
268276
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
269277
z = zQ;
270278
}
@@ -1162,11 +1170,13 @@
11621170
login_needed(0);
11631171
return;
11641172
}
11651173
db_begin_transaction();
11661174
if( P("clear")!=0 && cgi_csrf_safe(1) ){
1175
+ db_unprotect(PROTECT_CONFIG);
11671176
db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
1177
+ db_protect_pop();
11681178
cgi_replace_parameter("adunit","");
11691179
cgi_replace_parameter("adright","");
11701180
setup_incr_cfgcnt();
11711181
}
11721182
@@ -1260,10 +1270,11 @@
12601270
if( !g.perm.Admin ){
12611271
login_needed(0);
12621272
return;
12631273
}
12641274
db_begin_transaction();
1275
+ db_unprotect(PROTECT_CONFIG);
12651276
if( !cgi_csrf_safe(1) ){
12661277
/* Allow no state changes if not safe from CSRF */
12671278
}else if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
12681279
Blob img;
12691280
Stmt ins;
@@ -1290,10 +1301,11 @@
12901301
cgi_redirect("setup_logo");
12911302
}else if( P("setbg")!=0 && zBgMime && zBgMime[0] && szBgImg>0 ){
12921303
Blob img;
12931304
Stmt ins;
12941305
blob_init(&img, aBgImg, szBgImg);
1306
+ db_unprotect(PROTECT_CONFIG);
12951307
db_prepare(&ins,
12961308
"REPLACE INTO config(name,value,mtime)"
12971309
" VALUES('background-image',:bytes,now())"
12981310
);
12991311
db_bind_blob(&ins, ":bytes", &img);
@@ -1302,13 +1314,15 @@
13021314
db_multi_exec(
13031315
"REPLACE INTO config(name,value,mtime)"
13041316
" VALUES('background-mimetype',%Q,now())",
13051317
zBgMime
13061318
);
1319
+ db_protect_pop();
13071320
db_end_transaction(0);
13081321
cgi_redirect("setup_logo");
13091322
}else if( P("clrbg")!=0 ){
1323
+ db_unprotect(PROTECT_CONFIG);
13101324
db_multi_exec(
13111325
"DELETE FROM config WHERE name IN "
13121326
"('background-image','background-mimetype')"
13131327
);
13141328
db_end_transaction(0);
@@ -1315,10 +1329,11 @@
13151329
cgi_redirect("setup_logo");
13161330
}else if( P("seticon")!=0 && zIconMime && zIconMime[0] && szIconImg>0 ){
13171331
Blob img;
13181332
Stmt ins;
13191333
blob_init(&img, aIconImg, szIconImg);
1334
+ db_unprotect(PROTECT_CONFIG);
13201335
db_prepare(&ins,
13211336
"REPLACE INTO config(name,value,mtime)"
13221337
" VALUES('icon-image',:bytes,now())"
13231338
);
13241339
db_bind_blob(&ins, ":bytes", &img);
@@ -1327,10 +1342,11 @@
13271342
db_multi_exec(
13281343
"REPLACE INTO config(name,value,mtime)"
13291344
" VALUES('icon-mimetype',%Q,now())",
13301345
zIconMime
13311346
);
1347
+ db_protect_pop();
13321348
db_end_transaction(0);
13331349
cgi_redirect("setup_logo");
13341350
}else if( P("clricon")!=0 ){
13351351
db_multi_exec(
13361352
"DELETE FROM config WHERE name IN "
@@ -1786,22 +1802,27 @@
17861802
const char *zValue
17871803
){
17881804
if( !cgi_csrf_safe(1) ) return;
17891805
if( zNewName[0]==0 || zValue[0]==0 ){
17901806
if( zOldName[0] ){
1807
+ db_unprotect(PROTECT_CONFIG);
17911808
blob_append_sql(pSql,
17921809
"DELETE FROM config WHERE name='walias:%q';\n",
17931810
zOldName);
1811
+ db_protect_pop();
17941812
}
17951813
return;
17961814
}
17971815
if( zOldName[0]==0 ){
1816
+ db_unprotect(PROTECT_CONFIG);
17981817
blob_append_sql(pSql,
17991818
"INSERT INTO config(name,value,mtime) VALUES('walias:%q',%Q,now());\n",
18001819
zNewName, zValue);
1820
+ db_protect_pop();
18011821
return;
18021822
}
1823
+ db_unprotect(PROTECT_CONFIG);
18031824
if( strcmp(zOldName, zNewName)!=0 ){
18041825
blob_append_sql(pSql,
18051826
"UPDATE config SET name='walias:%q', value=%Q, mtime=now()"
18061827
" WHERE name='walias:%q';\n",
18071828
zNewName, zValue, zOldName);
@@ -1809,10 +1830,11 @@
18091830
blob_append_sql(pSql,
18101831
"UPDATE config SET value=%Q, mtime=now()"
18111832
" WHERE name='walias:%q' AND value<>%Q;\n",
18121833
zValue, zOldName, zValue);
18131834
}
1835
+ db_protect_pop();
18141836
}
18151837
18161838
/*
18171839
** WEBPAGE: waliassetup
18181840
**
18191841
--- src/setup.c
+++ src/setup.c
@@ -27,14 +27,16 @@
27 */
28 void setup_incr_cfgcnt(void){
29 static int once = 1;
30 if( once ){
31 once = 0;
 
32 db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'");
33 if( db_changes()==0 ){
34 db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)");
35 }
 
36 }
37 }
38
39 /*
40 ** Output a single entry for a menu generated using an HTML table.
@@ -195,11 +197,13 @@
195 }
196 if( zQ ){
197 int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
198 if( iQ!=iVal ){
199 login_verify_csrf_secret();
 
200 db_set(zVar, iQ ? "1" : "0", 0);
 
201 setup_incr_cfgcnt();
202 admin_log("Set option [%q] to [%q].",
203 zVar, iQ ? "on" : "off");
204 iVal = iQ;
205 }
@@ -230,11 +234,13 @@
230 const char *zQ = P(zQParm);
231 if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
232 const int nZQ = (int)strlen(zQ);
233 login_verify_csrf_secret();
234 setup_incr_cfgcnt();
 
235 db_set(zVar, zQ, 0);
 
236 admin_log("Set entry_attribute %Q to: %.*s%s",
237 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
238 zVal = zQ;
239 }
240 @ <input aria-label="%h(zLabel[0]?zLabel:zQParm)" type="text" \
@@ -260,11 +266,13 @@
260 const char *z = db_get(zVar, zDflt);
261 const char *zQ = P(zQP);
262 if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
263 const int nZQ = (int)strlen(zQ);
264 login_verify_csrf_secret();
 
265 db_set(zVar, zQ, 0);
 
266 setup_incr_cfgcnt();
267 admin_log("Set textarea_attribute %Q to: %.*s%s",
268 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
269 z = zQ;
270 }
@@ -1162,11 +1170,13 @@
1162 login_needed(0);
1163 return;
1164 }
1165 db_begin_transaction();
1166 if( P("clear")!=0 && cgi_csrf_safe(1) ){
 
1167 db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
 
1168 cgi_replace_parameter("adunit","");
1169 cgi_replace_parameter("adright","");
1170 setup_incr_cfgcnt();
1171 }
1172
@@ -1260,10 +1270,11 @@
1260 if( !g.perm.Admin ){
1261 login_needed(0);
1262 return;
1263 }
1264 db_begin_transaction();
 
1265 if( !cgi_csrf_safe(1) ){
1266 /* Allow no state changes if not safe from CSRF */
1267 }else if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
1268 Blob img;
1269 Stmt ins;
@@ -1290,10 +1301,11 @@
1290 cgi_redirect("setup_logo");
1291 }else if( P("setbg")!=0 && zBgMime && zBgMime[0] && szBgImg>0 ){
1292 Blob img;
1293 Stmt ins;
1294 blob_init(&img, aBgImg, szBgImg);
 
1295 db_prepare(&ins,
1296 "REPLACE INTO config(name,value,mtime)"
1297 " VALUES('background-image',:bytes,now())"
1298 );
1299 db_bind_blob(&ins, ":bytes", &img);
@@ -1302,13 +1314,15 @@
1302 db_multi_exec(
1303 "REPLACE INTO config(name,value,mtime)"
1304 " VALUES('background-mimetype',%Q,now())",
1305 zBgMime
1306 );
 
1307 db_end_transaction(0);
1308 cgi_redirect("setup_logo");
1309 }else if( P("clrbg")!=0 ){
 
1310 db_multi_exec(
1311 "DELETE FROM config WHERE name IN "
1312 "('background-image','background-mimetype')"
1313 );
1314 db_end_transaction(0);
@@ -1315,10 +1329,11 @@
1315 cgi_redirect("setup_logo");
1316 }else if( P("seticon")!=0 && zIconMime && zIconMime[0] && szIconImg>0 ){
1317 Blob img;
1318 Stmt ins;
1319 blob_init(&img, aIconImg, szIconImg);
 
1320 db_prepare(&ins,
1321 "REPLACE INTO config(name,value,mtime)"
1322 " VALUES('icon-image',:bytes,now())"
1323 );
1324 db_bind_blob(&ins, ":bytes", &img);
@@ -1327,10 +1342,11 @@
1327 db_multi_exec(
1328 "REPLACE INTO config(name,value,mtime)"
1329 " VALUES('icon-mimetype',%Q,now())",
1330 zIconMime
1331 );
 
1332 db_end_transaction(0);
1333 cgi_redirect("setup_logo");
1334 }else if( P("clricon")!=0 ){
1335 db_multi_exec(
1336 "DELETE FROM config WHERE name IN "
@@ -1786,22 +1802,27 @@
1786 const char *zValue
1787 ){
1788 if( !cgi_csrf_safe(1) ) return;
1789 if( zNewName[0]==0 || zValue[0]==0 ){
1790 if( zOldName[0] ){
 
1791 blob_append_sql(pSql,
1792 "DELETE FROM config WHERE name='walias:%q';\n",
1793 zOldName);
 
1794 }
1795 return;
1796 }
1797 if( zOldName[0]==0 ){
 
1798 blob_append_sql(pSql,
1799 "INSERT INTO config(name,value,mtime) VALUES('walias:%q',%Q,now());\n",
1800 zNewName, zValue);
 
1801 return;
1802 }
 
1803 if( strcmp(zOldName, zNewName)!=0 ){
1804 blob_append_sql(pSql,
1805 "UPDATE config SET name='walias:%q', value=%Q, mtime=now()"
1806 " WHERE name='walias:%q';\n",
1807 zNewName, zValue, zOldName);
@@ -1809,10 +1830,11 @@
1809 blob_append_sql(pSql,
1810 "UPDATE config SET value=%Q, mtime=now()"
1811 " WHERE name='walias:%q' AND value<>%Q;\n",
1812 zValue, zOldName, zValue);
1813 }
 
1814 }
1815
1816 /*
1817 ** WEBPAGE: waliassetup
1818 **
1819
--- src/setup.c
+++ src/setup.c
@@ -27,14 +27,16 @@
27 */
28 void setup_incr_cfgcnt(void){
29 static int once = 1;
30 if( once ){
31 once = 0;
32 db_unprotect(PROTECT_CONFIG);
33 db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'");
34 if( db_changes()==0 ){
35 db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)");
36 }
37 db_protect_pop();
38 }
39 }
40
41 /*
42 ** Output a single entry for a menu generated using an HTML table.
@@ -195,11 +197,13 @@
197 }
198 if( zQ ){
199 int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
200 if( iQ!=iVal ){
201 login_verify_csrf_secret();
202 db_protect_only(PROTECT_NONE);
203 db_set(zVar, iQ ? "1" : "0", 0);
204 db_protect_pop();
205 setup_incr_cfgcnt();
206 admin_log("Set option [%q] to [%q].",
207 zVar, iQ ? "on" : "off");
208 iVal = iQ;
209 }
@@ -230,11 +234,13 @@
234 const char *zQ = P(zQParm);
235 if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
236 const int nZQ = (int)strlen(zQ);
237 login_verify_csrf_secret();
238 setup_incr_cfgcnt();
239 db_protect_only(PROTECT_NONE);
240 db_set(zVar, zQ, 0);
241 db_protect_pop();
242 admin_log("Set entry_attribute %Q to: %.*s%s",
243 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
244 zVal = zQ;
245 }
246 @ <input aria-label="%h(zLabel[0]?zLabel:zQParm)" type="text" \
@@ -260,11 +266,13 @@
266 const char *z = db_get(zVar, zDflt);
267 const char *zQ = P(zQP);
268 if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
269 const int nZQ = (int)strlen(zQ);
270 login_verify_csrf_secret();
271 db_protect_only(PROTECT_NONE);
272 db_set(zVar, zQ, 0);
273 db_protect_pop();
274 setup_incr_cfgcnt();
275 admin_log("Set textarea_attribute %Q to: %.*s%s",
276 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
277 z = zQ;
278 }
@@ -1162,11 +1170,13 @@
1170 login_needed(0);
1171 return;
1172 }
1173 db_begin_transaction();
1174 if( P("clear")!=0 && cgi_csrf_safe(1) ){
1175 db_unprotect(PROTECT_CONFIG);
1176 db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'");
1177 db_protect_pop();
1178 cgi_replace_parameter("adunit","");
1179 cgi_replace_parameter("adright","");
1180 setup_incr_cfgcnt();
1181 }
1182
@@ -1260,10 +1270,11 @@
1270 if( !g.perm.Admin ){
1271 login_needed(0);
1272 return;
1273 }
1274 db_begin_transaction();
1275 db_unprotect(PROTECT_CONFIG);
1276 if( !cgi_csrf_safe(1) ){
1277 /* Allow no state changes if not safe from CSRF */
1278 }else if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){
1279 Blob img;
1280 Stmt ins;
@@ -1290,10 +1301,11 @@
1301 cgi_redirect("setup_logo");
1302 }else if( P("setbg")!=0 && zBgMime && zBgMime[0] && szBgImg>0 ){
1303 Blob img;
1304 Stmt ins;
1305 blob_init(&img, aBgImg, szBgImg);
1306 db_unprotect(PROTECT_CONFIG);
1307 db_prepare(&ins,
1308 "REPLACE INTO config(name,value,mtime)"
1309 " VALUES('background-image',:bytes,now())"
1310 );
1311 db_bind_blob(&ins, ":bytes", &img);
@@ -1302,13 +1314,15 @@
1314 db_multi_exec(
1315 "REPLACE INTO config(name,value,mtime)"
1316 " VALUES('background-mimetype',%Q,now())",
1317 zBgMime
1318 );
1319 db_protect_pop();
1320 db_end_transaction(0);
1321 cgi_redirect("setup_logo");
1322 }else if( P("clrbg")!=0 ){
1323 db_unprotect(PROTECT_CONFIG);
1324 db_multi_exec(
1325 "DELETE FROM config WHERE name IN "
1326 "('background-image','background-mimetype')"
1327 );
1328 db_end_transaction(0);
@@ -1315,10 +1329,11 @@
1329 cgi_redirect("setup_logo");
1330 }else if( P("seticon")!=0 && zIconMime && zIconMime[0] && szIconImg>0 ){
1331 Blob img;
1332 Stmt ins;
1333 blob_init(&img, aIconImg, szIconImg);
1334 db_unprotect(PROTECT_CONFIG);
1335 db_prepare(&ins,
1336 "REPLACE INTO config(name,value,mtime)"
1337 " VALUES('icon-image',:bytes,now())"
1338 );
1339 db_bind_blob(&ins, ":bytes", &img);
@@ -1327,10 +1342,11 @@
1342 db_multi_exec(
1343 "REPLACE INTO config(name,value,mtime)"
1344 " VALUES('icon-mimetype',%Q,now())",
1345 zIconMime
1346 );
1347 db_protect_pop();
1348 db_end_transaction(0);
1349 cgi_redirect("setup_logo");
1350 }else if( P("clricon")!=0 ){
1351 db_multi_exec(
1352 "DELETE FROM config WHERE name IN "
@@ -1786,22 +1802,27 @@
1802 const char *zValue
1803 ){
1804 if( !cgi_csrf_safe(1) ) return;
1805 if( zNewName[0]==0 || zValue[0]==0 ){
1806 if( zOldName[0] ){
1807 db_unprotect(PROTECT_CONFIG);
1808 blob_append_sql(pSql,
1809 "DELETE FROM config WHERE name='walias:%q';\n",
1810 zOldName);
1811 db_protect_pop();
1812 }
1813 return;
1814 }
1815 if( zOldName[0]==0 ){
1816 db_unprotect(PROTECT_CONFIG);
1817 blob_append_sql(pSql,
1818 "INSERT INTO config(name,value,mtime) VALUES('walias:%q',%Q,now());\n",
1819 zNewName, zValue);
1820 db_protect_pop();
1821 return;
1822 }
1823 db_unprotect(PROTECT_CONFIG);
1824 if( strcmp(zOldName, zNewName)!=0 ){
1825 blob_append_sql(pSql,
1826 "UPDATE config SET name='walias:%q', value=%Q, mtime=now()"
1827 " WHERE name='walias:%q';\n",
1828 zNewName, zValue, zOldName);
@@ -1809,10 +1830,11 @@
1830 blob_append_sql(pSql,
1831 "UPDATE config SET value=%Q, mtime=now()"
1832 " WHERE name='walias:%q' AND value<>%Q;\n",
1833 zValue, zOldName, zValue);
1834 }
1835 db_protect_pop();
1836 }
1837
1838 /*
1839 ** WEBPAGE: waliassetup
1840 **
1841
--- src/setupuser.c
+++ src/setupuser.c
@@ -315,11 +315,13 @@
315315
/* Check for requests to delete the user */
316316
if( P("delete") && cgi_csrf_safe(1) ){
317317
int n;
318318
if( P("verifydelete") ){
319319
/* Verified delete user request */
320
+ db_unprotect(PROTECT_USER);
320321
db_multi_exec("DELETE FROM user WHERE uid=%d", uid);
322
+ db_protect_pop();
321323
moderation_disapprove_for_missing_users();
322324
admin_log("Deleted user [%s] (uid %d).",
323325
PD("login","???")/*safe-for-%s*/, uid);
324326
cgi_redirect(cgi_referer("setup_ulist"));
325327
return;
@@ -401,15 +403,17 @@
401403
@ [Bummer]</a></p>
402404
style_footer();
403405
return;
404406
}
405407
login_verify_csrf_secret();
408
+ db_unprotect(PROTECT_USER);
406409
db_multi_exec(
407410
"REPLACE INTO user(uid,login,info,pw,cap,mtime) "
408411
"VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
409412
uid, zLogin, P("info"), zPw, zCap
410413
);
414
+ db_protect_pop();
411415
setup_incr_cfgcnt();
412416
admin_log( "Updated user [%q] with capabilities [%q].",
413417
zLogin, zCap );
414418
if( atoi(PD("all","0"))>0 ){
415419
Blob sql;
@@ -432,11 +436,13 @@
432436
" mtime=now()"
433437
" WHERE login=%Q;",
434438
zLogin, P("pw"), zLogin, P("info"), zCap,
435439
zOldLogin
436440
);
441
+ db_unprotect(PROTECT_USER);
437442
login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
443
+ db_protect_pop();
438444
blob_reset(&sql);
439445
admin_log( "Updated user [%q] in all login groups "
440446
"with capabilities [%q].",
441447
zLogin, zCap );
442448
if( zErr ){
443449
--- src/setupuser.c
+++ src/setupuser.c
@@ -315,11 +315,13 @@
315 /* Check for requests to delete the user */
316 if( P("delete") && cgi_csrf_safe(1) ){
317 int n;
318 if( P("verifydelete") ){
319 /* Verified delete user request */
 
320 db_multi_exec("DELETE FROM user WHERE uid=%d", uid);
 
321 moderation_disapprove_for_missing_users();
322 admin_log("Deleted user [%s] (uid %d).",
323 PD("login","???")/*safe-for-%s*/, uid);
324 cgi_redirect(cgi_referer("setup_ulist"));
325 return;
@@ -401,15 +403,17 @@
401 @ [Bummer]</a></p>
402 style_footer();
403 return;
404 }
405 login_verify_csrf_secret();
 
406 db_multi_exec(
407 "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
408 "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
409 uid, zLogin, P("info"), zPw, zCap
410 );
 
411 setup_incr_cfgcnt();
412 admin_log( "Updated user [%q] with capabilities [%q].",
413 zLogin, zCap );
414 if( atoi(PD("all","0"))>0 ){
415 Blob sql;
@@ -432,11 +436,13 @@
432 " mtime=now()"
433 " WHERE login=%Q;",
434 zLogin, P("pw"), zLogin, P("info"), zCap,
435 zOldLogin
436 );
 
437 login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
 
438 blob_reset(&sql);
439 admin_log( "Updated user [%q] in all login groups "
440 "with capabilities [%q].",
441 zLogin, zCap );
442 if( zErr ){
443
--- src/setupuser.c
+++ src/setupuser.c
@@ -315,11 +315,13 @@
315 /* Check for requests to delete the user */
316 if( P("delete") && cgi_csrf_safe(1) ){
317 int n;
318 if( P("verifydelete") ){
319 /* Verified delete user request */
320 db_unprotect(PROTECT_USER);
321 db_multi_exec("DELETE FROM user WHERE uid=%d", uid);
322 db_protect_pop();
323 moderation_disapprove_for_missing_users();
324 admin_log("Deleted user [%s] (uid %d).",
325 PD("login","???")/*safe-for-%s*/, uid);
326 cgi_redirect(cgi_referer("setup_ulist"));
327 return;
@@ -401,15 +403,17 @@
403 @ [Bummer]</a></p>
404 style_footer();
405 return;
406 }
407 login_verify_csrf_secret();
408 db_unprotect(PROTECT_USER);
409 db_multi_exec(
410 "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
411 "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
412 uid, zLogin, P("info"), zPw, zCap
413 );
414 db_protect_pop();
415 setup_incr_cfgcnt();
416 admin_log( "Updated user [%q] with capabilities [%q].",
417 zLogin, zCap );
418 if( atoi(PD("all","0"))>0 ){
419 Blob sql;
@@ -432,11 +436,13 @@
436 " mtime=now()"
437 " WHERE login=%Q;",
438 zLogin, P("pw"), zLogin, P("info"), zCap,
439 zOldLogin
440 );
441 db_unprotect(PROTECT_USER);
442 login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
443 db_protect_pop();
444 blob_reset(&sql);
445 admin_log( "Updated user [%q] in all login groups "
446 "with capabilities [%q].",
447 zLogin, zCap );
448 if( zErr ){
449
+14
--- src/skins.c
+++ src/skins.c
@@ -360,14 +360,16 @@
360360
zLabel = mprintf("skins/default/%s.txt", azSkinFile[i]);
361361
z = builtin_text(zLabel);
362362
fossil_free(zLabel);
363363
}
364364
}
365
+ db_unprotect(PROTECT_CONFIG);
365366
blob_appendf(&val,
366367
"REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now());\n",
367368
azSkinFile[i], z
368369
);
370
+ db_protect_pop();
369371
}
370372
return blob_str(&val);
371373
}
372374
373375
/*
@@ -402,14 +404,16 @@
402404
login_insert_csrf_secret();
403405
@ </div></form>
404406
style_footer();
405407
return 1;
406408
}
409
+ db_unprotect(PROTECT_CONFIG);
407410
db_multi_exec(
408411
"UPDATE config SET name='skin:%q' WHERE name='skin:%q';",
409412
zNewName, zOldName
410413
);
414
+ db_protect_pop();
411415
return 0;
412416
}
413417
414418
/*
415419
** Respond to a Save button press. Return TRUE if a dialog was painted.
@@ -440,15 +444,17 @@
440444
login_insert_csrf_secret();
441445
@ </div></form>
442446
style_footer();
443447
return 1;
444448
}
449
+ db_unprotect(PROTECT_CONFIG);
445450
db_multi_exec(
446451
"INSERT OR IGNORE INTO config(name, value, mtime)"
447452
"VALUES('skin:%q',%Q,now())",
448453
zNewName, zCurrent
449454
);
455
+ db_protect_pop();
450456
return 0;
451457
}
452458
453459
/*
454460
** WEBPAGE: setup_skin_admin
@@ -491,16 +497,20 @@
491497
style_footer();
492498
db_end_transaction(1);
493499
return;
494500
}
495501
if( P("del2")!=0 && (zName = skinVarName(P("sn"), 1))!=0 ){
502
+ db_unprotect(PROTECT_CONFIG);
496503
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
504
+ db_protect_pop();
497505
}
498506
if( P("draftdel")!=0 ){
499507
const char *zDraft = P("name");
500508
if( sqlite3_strglob("draft[1-9]",zDraft)==0 ){
509
+ db_unprotect(PROTECT_CONFIG);
501510
db_multi_exec("DELETE FROM config WHERE name GLOB '%q-*'", zDraft);
511
+ db_protect_pop();
502512
}
503513
}
504514
if( skinRename() || skinSave(zCurrent) ){
505515
db_end_transaction(0);
506516
return;
@@ -521,15 +531,17 @@
521531
}
522532
if( !seen ){
523533
seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
524534
" AND value=%Q", zCurrent);
525535
if( !seen ){
536
+ db_unprotect(PROTECT_CONFIG);
526537
db_multi_exec(
527538
"INSERT INTO config(name,value,mtime) VALUES("
528539
" strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
529540
" %Q,now())", zCurrent
530541
);
542
+ db_protect_pop();
531543
}
532544
}
533545
seen = 0;
534546
for(i=0; i<count(aBuiltinSkin); i++){
535547
if( fossil_strcmp(aBuiltinSkin[i].zDesc, z)==0 ){
@@ -867,15 +879,17 @@
867879
if( !seen ){
868880
seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
869881
" AND value=%Q", zCurrent);
870882
}
871883
if( !seen ){
884
+ db_unprotect(PROTECT_CONFIG);
872885
db_multi_exec(
873886
"INSERT INTO config(name,value,mtime) VALUES("
874887
" strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
875888
" %Q,now())", zCurrent
876889
);
890
+ db_protect_pop();
877891
}
878892
879893
/* Publish draft iSkin */
880894
for(i=0; i<count(azSkinFile); i++){
881895
char *zNew = db_get_mprintf("", "draft%d-%s", iSkin, azSkinFile[i]);
882896
--- src/skins.c
+++ src/skins.c
@@ -360,14 +360,16 @@
360 zLabel = mprintf("skins/default/%s.txt", azSkinFile[i]);
361 z = builtin_text(zLabel);
362 fossil_free(zLabel);
363 }
364 }
 
365 blob_appendf(&val,
366 "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now());\n",
367 azSkinFile[i], z
368 );
 
369 }
370 return blob_str(&val);
371 }
372
373 /*
@@ -402,14 +404,16 @@
402 login_insert_csrf_secret();
403 @ </div></form>
404 style_footer();
405 return 1;
406 }
 
407 db_multi_exec(
408 "UPDATE config SET name='skin:%q' WHERE name='skin:%q';",
409 zNewName, zOldName
410 );
 
411 return 0;
412 }
413
414 /*
415 ** Respond to a Save button press. Return TRUE if a dialog was painted.
@@ -440,15 +444,17 @@
440 login_insert_csrf_secret();
441 @ </div></form>
442 style_footer();
443 return 1;
444 }
 
445 db_multi_exec(
446 "INSERT OR IGNORE INTO config(name, value, mtime)"
447 "VALUES('skin:%q',%Q,now())",
448 zNewName, zCurrent
449 );
 
450 return 0;
451 }
452
453 /*
454 ** WEBPAGE: setup_skin_admin
@@ -491,16 +497,20 @@
491 style_footer();
492 db_end_transaction(1);
493 return;
494 }
495 if( P("del2")!=0 && (zName = skinVarName(P("sn"), 1))!=0 ){
 
496 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
 
497 }
498 if( P("draftdel")!=0 ){
499 const char *zDraft = P("name");
500 if( sqlite3_strglob("draft[1-9]",zDraft)==0 ){
 
501 db_multi_exec("DELETE FROM config WHERE name GLOB '%q-*'", zDraft);
 
502 }
503 }
504 if( skinRename() || skinSave(zCurrent) ){
505 db_end_transaction(0);
506 return;
@@ -521,15 +531,17 @@
521 }
522 if( !seen ){
523 seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
524 " AND value=%Q", zCurrent);
525 if( !seen ){
 
526 db_multi_exec(
527 "INSERT INTO config(name,value,mtime) VALUES("
528 " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
529 " %Q,now())", zCurrent
530 );
 
531 }
532 }
533 seen = 0;
534 for(i=0; i<count(aBuiltinSkin); i++){
535 if( fossil_strcmp(aBuiltinSkin[i].zDesc, z)==0 ){
@@ -867,15 +879,17 @@
867 if( !seen ){
868 seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
869 " AND value=%Q", zCurrent);
870 }
871 if( !seen ){
 
872 db_multi_exec(
873 "INSERT INTO config(name,value,mtime) VALUES("
874 " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
875 " %Q,now())", zCurrent
876 );
 
877 }
878
879 /* Publish draft iSkin */
880 for(i=0; i<count(azSkinFile); i++){
881 char *zNew = db_get_mprintf("", "draft%d-%s", iSkin, azSkinFile[i]);
882
--- src/skins.c
+++ src/skins.c
@@ -360,14 +360,16 @@
360 zLabel = mprintf("skins/default/%s.txt", azSkinFile[i]);
361 z = builtin_text(zLabel);
362 fossil_free(zLabel);
363 }
364 }
365 db_unprotect(PROTECT_CONFIG);
366 blob_appendf(&val,
367 "REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now());\n",
368 azSkinFile[i], z
369 );
370 db_protect_pop();
371 }
372 return blob_str(&val);
373 }
374
375 /*
@@ -402,14 +404,16 @@
404 login_insert_csrf_secret();
405 @ </div></form>
406 style_footer();
407 return 1;
408 }
409 db_unprotect(PROTECT_CONFIG);
410 db_multi_exec(
411 "UPDATE config SET name='skin:%q' WHERE name='skin:%q';",
412 zNewName, zOldName
413 );
414 db_protect_pop();
415 return 0;
416 }
417
418 /*
419 ** Respond to a Save button press. Return TRUE if a dialog was painted.
@@ -440,15 +444,17 @@
444 login_insert_csrf_secret();
445 @ </div></form>
446 style_footer();
447 return 1;
448 }
449 db_unprotect(PROTECT_CONFIG);
450 db_multi_exec(
451 "INSERT OR IGNORE INTO config(name, value, mtime)"
452 "VALUES('skin:%q',%Q,now())",
453 zNewName, zCurrent
454 );
455 db_protect_pop();
456 return 0;
457 }
458
459 /*
460 ** WEBPAGE: setup_skin_admin
@@ -491,16 +497,20 @@
497 style_footer();
498 db_end_transaction(1);
499 return;
500 }
501 if( P("del2")!=0 && (zName = skinVarName(P("sn"), 1))!=0 ){
502 db_unprotect(PROTECT_CONFIG);
503 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
504 db_protect_pop();
505 }
506 if( P("draftdel")!=0 ){
507 const char *zDraft = P("name");
508 if( sqlite3_strglob("draft[1-9]",zDraft)==0 ){
509 db_unprotect(PROTECT_CONFIG);
510 db_multi_exec("DELETE FROM config WHERE name GLOB '%q-*'", zDraft);
511 db_protect_pop();
512 }
513 }
514 if( skinRename() || skinSave(zCurrent) ){
515 db_end_transaction(0);
516 return;
@@ -521,15 +531,17 @@
531 }
532 if( !seen ){
533 seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
534 " AND value=%Q", zCurrent);
535 if( !seen ){
536 db_unprotect(PROTECT_CONFIG);
537 db_multi_exec(
538 "INSERT INTO config(name,value,mtime) VALUES("
539 " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
540 " %Q,now())", zCurrent
541 );
542 db_protect_pop();
543 }
544 }
545 seen = 0;
546 for(i=0; i<count(aBuiltinSkin); i++){
547 if( fossil_strcmp(aBuiltinSkin[i].zDesc, z)==0 ){
@@ -867,15 +879,17 @@
879 if( !seen ){
880 seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
881 " AND value=%Q", zCurrent);
882 }
883 if( !seen ){
884 db_unprotect(PROTECT_CONFIG);
885 db_multi_exec(
886 "INSERT INTO config(name,value,mtime) VALUES("
887 " strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
888 " %Q,now())", zCurrent
889 );
890 db_protect_pop();
891 }
892
893 /* Publish draft iSkin */
894 for(i=0; i<count(azSkinFile); i++){
895 char *zNew = db_get_mprintf("", "draft%d-%s", iSkin, azSkinFile[i]);
896
+40
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -153,10 +153,44 @@
153153
sqlcmd_decompress, 0, 0);
154154
sqlite3_create_function(db, "gather_artifact_stats", 0, SQLITE_UTF8, 0,
155155
sqlcmd_gather_artifact_stats, 0, 0);
156156
return SQLITE_OK;
157157
}
158
+
159
+/*
160
+** Undocumented test SQL functions:
161
+**
162
+** db_protect(X)
163
+** db_protect_pop(X)
164
+**
165
+** These invoke the corresponding C routines. Misuse may result in
166
+** an assertion fault.
167
+*/
168
+static void sqlcmd_db_protect(
169
+ sqlite3_context *context,
170
+ int argc,
171
+ sqlite3_value **argv
172
+){
173
+ unsigned mask = 0;
174
+ const char *z = (const char*)sqlite3_value_text(argv[0]);
175
+ if( sqlite3_stricmp(z,"user")==0 ) mask |= PROTECT_USER;
176
+ if( sqlite3_stricmp(z,"config")==0 ) mask |= PROTECT_CONFIG;
177
+ if( sqlite3_stricmp(z,"sensitive")==0 ) mask |= PROTECT_SENSITIVE;
178
+ if( sqlite3_stricmp(z,"readonly")==0 ) mask |= PROTECT_READONLY;
179
+ if( sqlite3_stricmp(z,"all")==0 ) mask |= PROTECT_ALL;
180
+ db_protect(mask);
181
+}
182
+static void sqlcmd_db_protect_pop(
183
+ sqlite3_context *context,
184
+ int argc,
185
+ sqlite3_value **argv
186
+){
187
+ db_protect_pop();
188
+}
189
+
190
+
191
+
158192
159193
/*
160194
** This is the "automatic extension" initializer that runs right after
161195
** the connection to the repository database is opened. Set up the
162196
** database connection to be more useful to the human operator.
@@ -193,10 +227,16 @@
193227
}
194228
/* Arrange to trace close operations so that static prepared statements
195229
** will get cleaned up when the shell closes the database connection */
196230
if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE;
197231
sqlite3_trace_v2(db, mTrace, db_sql_trace, 0);
232
+ db_protect_only(PROTECT_NONE);
233
+ sqlite3_set_authorizer(db, db_top_authorizer, db);
234
+ sqlite3_create_function(db, "db_protect", 1, SQLITE_UTF8, 0,
235
+ sqlcmd_db_protect, 0, 0);
236
+ sqlite3_create_function(db, "db_protect_pop", 0, SQLITE_UTF8, 0,
237
+ sqlcmd_db_protect_pop, 0, 0);
198238
return SQLITE_OK;
199239
}
200240
201241
/*
202242
** atexit() handler that cleans up global state modified by this module.
203243
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -153,10 +153,44 @@
153 sqlcmd_decompress, 0, 0);
154 sqlite3_create_function(db, "gather_artifact_stats", 0, SQLITE_UTF8, 0,
155 sqlcmd_gather_artifact_stats, 0, 0);
156 return SQLITE_OK;
157 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
159 /*
160 ** This is the "automatic extension" initializer that runs right after
161 ** the connection to the repository database is opened. Set up the
162 ** database connection to be more useful to the human operator.
@@ -193,10 +227,16 @@
193 }
194 /* Arrange to trace close operations so that static prepared statements
195 ** will get cleaned up when the shell closes the database connection */
196 if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE;
197 sqlite3_trace_v2(db, mTrace, db_sql_trace, 0);
 
 
 
 
 
 
198 return SQLITE_OK;
199 }
200
201 /*
202 ** atexit() handler that cleans up global state modified by this module.
203
--- src/sqlcmd.c
+++ src/sqlcmd.c
@@ -153,10 +153,44 @@
153 sqlcmd_decompress, 0, 0);
154 sqlite3_create_function(db, "gather_artifact_stats", 0, SQLITE_UTF8, 0,
155 sqlcmd_gather_artifact_stats, 0, 0);
156 return SQLITE_OK;
157 }
158
159 /*
160 ** Undocumented test SQL functions:
161 **
162 ** db_protect(X)
163 ** db_protect_pop(X)
164 **
165 ** These invoke the corresponding C routines. Misuse may result in
166 ** an assertion fault.
167 */
168 static void sqlcmd_db_protect(
169 sqlite3_context *context,
170 int argc,
171 sqlite3_value **argv
172 ){
173 unsigned mask = 0;
174 const char *z = (const char*)sqlite3_value_text(argv[0]);
175 if( sqlite3_stricmp(z,"user")==0 ) mask |= PROTECT_USER;
176 if( sqlite3_stricmp(z,"config")==0 ) mask |= PROTECT_CONFIG;
177 if( sqlite3_stricmp(z,"sensitive")==0 ) mask |= PROTECT_SENSITIVE;
178 if( sqlite3_stricmp(z,"readonly")==0 ) mask |= PROTECT_READONLY;
179 if( sqlite3_stricmp(z,"all")==0 ) mask |= PROTECT_ALL;
180 db_protect(mask);
181 }
182 static void sqlcmd_db_protect_pop(
183 sqlite3_context *context,
184 int argc,
185 sqlite3_value **argv
186 ){
187 db_protect_pop();
188 }
189
190
191
192
193 /*
194 ** This is the "automatic extension" initializer that runs right after
195 ** the connection to the repository database is opened. Set up the
196 ** database connection to be more useful to the human operator.
@@ -193,10 +227,16 @@
227 }
228 /* Arrange to trace close operations so that static prepared statements
229 ** will get cleaned up when the shell closes the database connection */
230 if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE;
231 sqlite3_trace_v2(db, mTrace, db_sql_trace, 0);
232 db_protect_only(PROTECT_NONE);
233 sqlite3_set_authorizer(db, db_top_authorizer, db);
234 sqlite3_create_function(db, "db_protect", 1, SQLITE_UTF8, 0,
235 sqlcmd_db_protect, 0, 0);
236 sqlite3_create_function(db, "db_protect_pop", 0, SQLITE_UTF8, 0,
237 sqlcmd_db_protect_pop, 0, 0);
238 return SQLITE_OK;
239 }
240
241 /*
242 ** atexit() handler that cleans up global state modified by this module.
243
--- src/stash.c
+++ src/stash.c
@@ -334,10 +334,12 @@
334334
blob_write_to_file(&delta, zNPath);
335335
file_setexe(zNPath, isExec);
336336
}else if( isRemoved ){
337337
fossil_print("DELETE %s\n", zOrig);
338338
file_delete(zOPath);
339
+ }else if( file_unsafe_in_tree_path(zNPath) ){
340
+ /* Ignore the unsafe path */
339341
}else{
340342
Blob a, b, out, disk;
341343
int isNewLink = file_islink(zOPath);
342344
db_ephemeral_blob(&q, 6, &delta);
343345
blob_read_from_file(&disk, zOPath, RepoFILE);
344346
--- src/stash.c
+++ src/stash.c
@@ -334,10 +334,12 @@
334 blob_write_to_file(&delta, zNPath);
335 file_setexe(zNPath, isExec);
336 }else if( isRemoved ){
337 fossil_print("DELETE %s\n", zOrig);
338 file_delete(zOPath);
 
 
339 }else{
340 Blob a, b, out, disk;
341 int isNewLink = file_islink(zOPath);
342 db_ephemeral_blob(&q, 6, &delta);
343 blob_read_from_file(&disk, zOPath, RepoFILE);
344
--- src/stash.c
+++ src/stash.c
@@ -334,10 +334,12 @@
334 blob_write_to_file(&delta, zNPath);
335 file_setexe(zNPath, isExec);
336 }else if( isRemoved ){
337 fossil_print("DELETE %s\n", zOrig);
338 file_delete(zOPath);
339 }else if( file_unsafe_in_tree_path(zNPath) ){
340 /* Ignore the unsafe path */
341 }else{
342 Blob a, b, out, disk;
343 int isNewLink = file_islink(zOPath);
344 db_ephemeral_blob(&q, 6, &delta);
345 blob_read_from_file(&disk, zOPath, RepoFILE);
346
+7
--- src/sync.c
+++ src/sync.c
@@ -425,13 +425,15 @@
425425
/* fossil remote off
426426
** Forget the last-sync-URL and its password
427427
*/
428428
if( g.argc!=3 ) usage("off");
429429
remote_delete_default:
430
+ db_unprotect(PROTECT_CONFIG);
430431
db_multi_exec(
431432
"DELETE FROM config WHERE name GLOB 'last-sync-*';"
432433
);
434
+ db_protect_pop();
433435
return;
434436
}
435437
if( strncmp(zArg, "list", nArg)==0 || strcmp(zArg,"ls")==0 ){
436438
Stmt q;
437439
if( g.argc!=3 ) usage("list");
@@ -457,10 +459,11 @@
457459
zName = g.argv[3];
458460
zUrl = g.argv[4];
459461
if( strcmp(zName,"default")==0 ) goto remote_add_default;
460462
url_parse_local(zUrl, URL_PROMPT_PW, &x);
461463
db_begin_write();
464
+ db_unprotect(PROTECT_CONFIG);
462465
db_multi_exec(
463466
"REPLACE INTO config(name, value, mtime)"
464467
" VALUES('sync-url:%q',%Q,now())",
465468
zName, x.canonical
466469
);
@@ -467,21 +470,24 @@
467470
db_multi_exec(
468471
"REPLACE INTO config(name, value, mtime)"
469472
" VALUES('sync-pw:%q',obscure(%Q),now())",
470473
zName, x.passwd
471474
);
475
+ db_protect_pop();
472476
db_commit_transaction();
473477
return;
474478
}
475479
if( strncmp(zArg, "delete", nArg)==0 ){
476480
char *zName;
477481
if( g.argc!=4 ) usage("delete NAME");
478482
zName = g.argv[3];
479483
if( strcmp(zName,"default")==0 ) goto remote_delete_default;
480484
db_begin_write();
485
+ db_unprotect(PROTECT_CONFIG);
481486
db_multi_exec("DELETE FROM config WHERE name glob 'sync-url:%q'", zName);
482487
db_multi_exec("DELETE FROM config WHERE name glob 'sync-pw:%q'", zName);
488
+ db_protect_pop();
483489
db_commit_transaction();
484490
return;
485491
}
486492
if( sqlite3_strlike("http://%",zArg,0)==0
487493
|| sqlite3_strlike("https://%",zArg,0)==0
@@ -539,7 +545,8 @@
539545
}
540546
}else{
541547
fossil_fatal("backup \"%s\" already exists", zDest);
542548
}
543549
}
550
+ db_unprotect(PROTECT_ALL);
544551
db_multi_exec("VACUUM repository INTO %Q", zDest);
545552
}
546553
--- src/sync.c
+++ src/sync.c
@@ -425,13 +425,15 @@
425 /* fossil remote off
426 ** Forget the last-sync-URL and its password
427 */
428 if( g.argc!=3 ) usage("off");
429 remote_delete_default:
 
430 db_multi_exec(
431 "DELETE FROM config WHERE name GLOB 'last-sync-*';"
432 );
 
433 return;
434 }
435 if( strncmp(zArg, "list", nArg)==0 || strcmp(zArg,"ls")==0 ){
436 Stmt q;
437 if( g.argc!=3 ) usage("list");
@@ -457,10 +459,11 @@
457 zName = g.argv[3];
458 zUrl = g.argv[4];
459 if( strcmp(zName,"default")==0 ) goto remote_add_default;
460 url_parse_local(zUrl, URL_PROMPT_PW, &x);
461 db_begin_write();
 
462 db_multi_exec(
463 "REPLACE INTO config(name, value, mtime)"
464 " VALUES('sync-url:%q',%Q,now())",
465 zName, x.canonical
466 );
@@ -467,21 +470,24 @@
467 db_multi_exec(
468 "REPLACE INTO config(name, value, mtime)"
469 " VALUES('sync-pw:%q',obscure(%Q),now())",
470 zName, x.passwd
471 );
 
472 db_commit_transaction();
473 return;
474 }
475 if( strncmp(zArg, "delete", nArg)==0 ){
476 char *zName;
477 if( g.argc!=4 ) usage("delete NAME");
478 zName = g.argv[3];
479 if( strcmp(zName,"default")==0 ) goto remote_delete_default;
480 db_begin_write();
 
481 db_multi_exec("DELETE FROM config WHERE name glob 'sync-url:%q'", zName);
482 db_multi_exec("DELETE FROM config WHERE name glob 'sync-pw:%q'", zName);
 
483 db_commit_transaction();
484 return;
485 }
486 if( sqlite3_strlike("http://%",zArg,0)==0
487 || sqlite3_strlike("https://%",zArg,0)==0
@@ -539,7 +545,8 @@
539 }
540 }else{
541 fossil_fatal("backup \"%s\" already exists", zDest);
542 }
543 }
 
544 db_multi_exec("VACUUM repository INTO %Q", zDest);
545 }
546
--- src/sync.c
+++ src/sync.c
@@ -425,13 +425,15 @@
425 /* fossil remote off
426 ** Forget the last-sync-URL and its password
427 */
428 if( g.argc!=3 ) usage("off");
429 remote_delete_default:
430 db_unprotect(PROTECT_CONFIG);
431 db_multi_exec(
432 "DELETE FROM config WHERE name GLOB 'last-sync-*';"
433 );
434 db_protect_pop();
435 return;
436 }
437 if( strncmp(zArg, "list", nArg)==0 || strcmp(zArg,"ls")==0 ){
438 Stmt q;
439 if( g.argc!=3 ) usage("list");
@@ -457,10 +459,11 @@
459 zName = g.argv[3];
460 zUrl = g.argv[4];
461 if( strcmp(zName,"default")==0 ) goto remote_add_default;
462 url_parse_local(zUrl, URL_PROMPT_PW, &x);
463 db_begin_write();
464 db_unprotect(PROTECT_CONFIG);
465 db_multi_exec(
466 "REPLACE INTO config(name, value, mtime)"
467 " VALUES('sync-url:%q',%Q,now())",
468 zName, x.canonical
469 );
@@ -467,21 +470,24 @@
470 db_multi_exec(
471 "REPLACE INTO config(name, value, mtime)"
472 " VALUES('sync-pw:%q',obscure(%Q),now())",
473 zName, x.passwd
474 );
475 db_protect_pop();
476 db_commit_transaction();
477 return;
478 }
479 if( strncmp(zArg, "delete", nArg)==0 ){
480 char *zName;
481 if( g.argc!=4 ) usage("delete NAME");
482 zName = g.argv[3];
483 if( strcmp(zName,"default")==0 ) goto remote_delete_default;
484 db_begin_write();
485 db_unprotect(PROTECT_CONFIG);
486 db_multi_exec("DELETE FROM config WHERE name glob 'sync-url:%q'", zName);
487 db_multi_exec("DELETE FROM config WHERE name glob 'sync-pw:%q'", zName);
488 db_protect_pop();
489 db_commit_transaction();
490 return;
491 }
492 if( sqlite3_strlike("http://%",zArg,0)==0
493 || sqlite3_strlike("https://%",zArg,0)==0
@@ -539,7 +545,8 @@
545 }
546 }else{
547 fossil_fatal("backup \"%s\" already exists", zDest);
548 }
549 }
550 db_unprotect(PROTECT_ALL);
551 db_multi_exec("VACUUM repository INTO %Q", zDest);
552 }
553
+71
--- src/tkt.c
+++ src/tkt.c
@@ -370,10 +370,79 @@
370370
Th_FossilInit(TH_INIT_DEFAULT);
371371
Th_Store("uuid", zUuid);
372372
zConfig = ticket_change_code();
373373
return Th_Eval(g.interp, 0, zConfig, -1);
374374
}
375
+
376
+/*
377
+** An authorizer function for the SQL used to initialize the
378
+** schema for the ticketing system. Only allow CREATE TABLE and
379
+** CREATE INDEX for tables whose names begin with "ticket" and
380
+** changes to tables whose names begin with "ticket".
381
+*/
382
+static int ticket_schema_auth(
383
+ void *pNErr,
384
+ int eCode,
385
+ const char *z0,
386
+ const char *z1,
387
+ const char *z2,
388
+ const char *z3
389
+){
390
+ switch( eCode ){
391
+ case SQLITE_CREATE_TABLE: {
392
+ if( sqlite3_stricmp(z2,"main")!=0
393
+ && sqlite3_stricmp(z2,"repository")!=0
394
+ ){
395
+ goto ticket_schema_error;
396
+ }
397
+ if( sqlite3_strnicmp(z0,"ticket",6)!=0 ){
398
+ goto ticket_schema_error;
399
+ }
400
+ break;
401
+ }
402
+ case SQLITE_CREATE_INDEX: {
403
+ if( sqlite3_stricmp(z2,"main")!=0
404
+ && sqlite3_stricmp(z2,"repository")!=0
405
+ ){
406
+ goto ticket_schema_error;
407
+ }
408
+ if( sqlite3_strnicmp(z1,"ticket",6)!=0 ){
409
+ goto ticket_schema_error;
410
+ }
411
+ break;
412
+ }
413
+ case SQLITE_INSERT:
414
+ case SQLITE_UPDATE:
415
+ case SQLITE_DELETE: {
416
+ if( sqlite3_stricmp(z2,"main")!=0
417
+ && sqlite3_stricmp(z2,"repository")!=0
418
+ ){
419
+ goto ticket_schema_error;
420
+ }
421
+ if( sqlite3_strnicmp(z0,"ticket",6)!=0
422
+ && sqlite3_strnicmp(z0,"sqlite_",7)!=0
423
+ ){
424
+ goto ticket_schema_error;
425
+ }
426
+ break;
427
+ }
428
+ case SQLITE_REINDEX:
429
+ case SQLITE_TRANSACTION:
430
+ case SQLITE_READ: {
431
+ break;
432
+ }
433
+ default: {
434
+ goto ticket_schema_error;
435
+ }
436
+ }
437
+ return SQLITE_OK;
438
+
439
+ticket_schema_error:
440
+ if( pNErr ) *(int*)pNErr = 1;
441
+ return SQLITE_DENY;
442
+}
443
+
375444
376445
/*
377446
** Recreate the TICKET and TICKETCHNG tables.
378447
*/
379448
void ticket_create_table(int separateConnection){
@@ -382,16 +451,18 @@
382451
db_multi_exec(
383452
"DROP TABLE IF EXISTS ticket;"
384453
"DROP TABLE IF EXISTS ticketchng;"
385454
);
386455
zSql = ticket_table_schema();
456
+ db_set_authorizer(ticket_schema_auth,0,"Ticket-Schema");
387457
if( separateConnection ){
388458
if( db_transaction_nesting_depth() ) db_end_transaction(0);
389459
db_init_database(g.zRepositoryName, zSql, 0);
390460
}else{
391461
db_multi_exec("%s", zSql/*safe-for-%s*/);
392462
}
463
+ db_clear_authorizer();
393464
fossil_free(zSql);
394465
}
395466
396467
/*
397468
** Repopulate the TICKET and TICKETCHNG tables from scratch using all
398469
--- src/tkt.c
+++ src/tkt.c
@@ -370,10 +370,79 @@
370 Th_FossilInit(TH_INIT_DEFAULT);
371 Th_Store("uuid", zUuid);
372 zConfig = ticket_change_code();
373 return Th_Eval(g.interp, 0, zConfig, -1);
374 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
376 /*
377 ** Recreate the TICKET and TICKETCHNG tables.
378 */
379 void ticket_create_table(int separateConnection){
@@ -382,16 +451,18 @@
382 db_multi_exec(
383 "DROP TABLE IF EXISTS ticket;"
384 "DROP TABLE IF EXISTS ticketchng;"
385 );
386 zSql = ticket_table_schema();
 
387 if( separateConnection ){
388 if( db_transaction_nesting_depth() ) db_end_transaction(0);
389 db_init_database(g.zRepositoryName, zSql, 0);
390 }else{
391 db_multi_exec("%s", zSql/*safe-for-%s*/);
392 }
 
393 fossil_free(zSql);
394 }
395
396 /*
397 ** Repopulate the TICKET and TICKETCHNG tables from scratch using all
398
--- src/tkt.c
+++ src/tkt.c
@@ -370,10 +370,79 @@
370 Th_FossilInit(TH_INIT_DEFAULT);
371 Th_Store("uuid", zUuid);
372 zConfig = ticket_change_code();
373 return Th_Eval(g.interp, 0, zConfig, -1);
374 }
375
376 /*
377 ** An authorizer function for the SQL used to initialize the
378 ** schema for the ticketing system. Only allow CREATE TABLE and
379 ** CREATE INDEX for tables whose names begin with "ticket" and
380 ** changes to tables whose names begin with "ticket".
381 */
382 static int ticket_schema_auth(
383 void *pNErr,
384 int eCode,
385 const char *z0,
386 const char *z1,
387 const char *z2,
388 const char *z3
389 ){
390 switch( eCode ){
391 case SQLITE_CREATE_TABLE: {
392 if( sqlite3_stricmp(z2,"main")!=0
393 && sqlite3_stricmp(z2,"repository")!=0
394 ){
395 goto ticket_schema_error;
396 }
397 if( sqlite3_strnicmp(z0,"ticket",6)!=0 ){
398 goto ticket_schema_error;
399 }
400 break;
401 }
402 case SQLITE_CREATE_INDEX: {
403 if( sqlite3_stricmp(z2,"main")!=0
404 && sqlite3_stricmp(z2,"repository")!=0
405 ){
406 goto ticket_schema_error;
407 }
408 if( sqlite3_strnicmp(z1,"ticket",6)!=0 ){
409 goto ticket_schema_error;
410 }
411 break;
412 }
413 case SQLITE_INSERT:
414 case SQLITE_UPDATE:
415 case SQLITE_DELETE: {
416 if( sqlite3_stricmp(z2,"main")!=0
417 && sqlite3_stricmp(z2,"repository")!=0
418 ){
419 goto ticket_schema_error;
420 }
421 if( sqlite3_strnicmp(z0,"ticket",6)!=0
422 && sqlite3_strnicmp(z0,"sqlite_",7)!=0
423 ){
424 goto ticket_schema_error;
425 }
426 break;
427 }
428 case SQLITE_REINDEX:
429 case SQLITE_TRANSACTION:
430 case SQLITE_READ: {
431 break;
432 }
433 default: {
434 goto ticket_schema_error;
435 }
436 }
437 return SQLITE_OK;
438
439 ticket_schema_error:
440 if( pNErr ) *(int*)pNErr = 1;
441 return SQLITE_DENY;
442 }
443
444
445 /*
446 ** Recreate the TICKET and TICKETCHNG tables.
447 */
448 void ticket_create_table(int separateConnection){
@@ -382,16 +451,18 @@
451 db_multi_exec(
452 "DROP TABLE IF EXISTS ticket;"
453 "DROP TABLE IF EXISTS ticketchng;"
454 );
455 zSql = ticket_table_schema();
456 db_set_authorizer(ticket_schema_auth,0,"Ticket-Schema");
457 if( separateConnection ){
458 if( db_transaction_nesting_depth() ) db_end_transaction(0);
459 db_init_database(g.zRepositoryName, zSql, 0);
460 }else{
461 db_multi_exec("%s", zSql/*safe-for-%s*/);
462 }
463 db_clear_authorizer();
464 fossil_free(zSql);
465 }
466
467 /*
468 ** Repopulate the TICKET and TICKETCHNG tables from scratch using all
469
+4 -2
--- src/undo.c
+++ src/undo.c
@@ -52,11 +52,11 @@
5252
int new_exe;
5353
int new_link;
5454
int old_link;
5555
Blob current;
5656
Blob new;
57
- zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname);
57
+ zFullname = mprintf("%s%s", g.zLocalRoot, zPathname);
5858
old_link = db_column_int(&q, 3);
5959
new_exists = file_size(zFullname, RepoFILE)>=0;
6060
new_link = file_islink(0);
6161
if( new_exists ){
6262
blob_read_from_file(&current, zFullname, RepoFILE);
@@ -69,11 +69,13 @@
6969
old_exists = db_column_int(&q, 1);
7070
old_exe = db_column_int(&q, 2);
7171
if( old_exists ){
7272
db_ephemeral_blob(&q, 0, &new);
7373
}
74
- if( old_exists ){
74
+ if( file_unsafe_in_tree_path(zFullname) ){
75
+ /* do nothign with this unsafe file */
76
+ }else if( old_exists ){
7577
if( new_exists ){
7678
fossil_print("%s %s\n", redoFlag ? "REDO" : "UNDO", zPathname);
7779
}else{
7880
fossil_print("NEW %s\n", zPathname);
7981
}
8082
--- src/undo.c
+++ src/undo.c
@@ -52,11 +52,11 @@
52 int new_exe;
53 int new_link;
54 int old_link;
55 Blob current;
56 Blob new;
57 zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname);
58 old_link = db_column_int(&q, 3);
59 new_exists = file_size(zFullname, RepoFILE)>=0;
60 new_link = file_islink(0);
61 if( new_exists ){
62 blob_read_from_file(&current, zFullname, RepoFILE);
@@ -69,11 +69,13 @@
69 old_exists = db_column_int(&q, 1);
70 old_exe = db_column_int(&q, 2);
71 if( old_exists ){
72 db_ephemeral_blob(&q, 0, &new);
73 }
74 if( old_exists ){
 
 
75 if( new_exists ){
76 fossil_print("%s %s\n", redoFlag ? "REDO" : "UNDO", zPathname);
77 }else{
78 fossil_print("NEW %s\n", zPathname);
79 }
80
--- src/undo.c
+++ src/undo.c
@@ -52,11 +52,11 @@
52 int new_exe;
53 int new_link;
54 int old_link;
55 Blob current;
56 Blob new;
57 zFullname = mprintf("%s%s", g.zLocalRoot, zPathname);
58 old_link = db_column_int(&q, 3);
59 new_exists = file_size(zFullname, RepoFILE)>=0;
60 new_link = file_islink(0);
61 if( new_exists ){
62 blob_read_from_file(&current, zFullname, RepoFILE);
@@ -69,11 +69,13 @@
69 old_exists = db_column_int(&q, 1);
70 old_exe = db_column_int(&q, 2);
71 if( old_exists ){
72 db_ephemeral_blob(&q, 0, &new);
73 }
74 if( file_unsafe_in_tree_path(zFullname) ){
75 /* do nothign with this unsafe file */
76 }else if( old_exists ){
77 if( new_exists ){
78 fossil_print("%s %s\n", redoFlag ? "REDO" : "UNDO", zPathname);
79 }else{
80 fossil_print("NEW %s\n", zPathname);
81 }
82
--- src/update.c
+++ src/update.c
@@ -927,10 +927,12 @@
927927
" SET pathname=origname, origname=NULL"
928928
" WHERE pathname=%Q AND origname!=pathname;"
929929
"DELETE FROM vfile WHERE pathname=%Q",
930930
zFile, zFile
931931
);
932
+ }else if( file_unsafe_in_tree_path(zFull) ){
933
+ /* Ignore this file */
932934
}else{
933935
sqlite3_int64 mtime;
934936
int rvChnged = 0;
935937
int rvPerm = manifest_file_mperm(pRvFile);
936938
937939
--- src/update.c
+++ src/update.c
@@ -927,10 +927,12 @@
927 " SET pathname=origname, origname=NULL"
928 " WHERE pathname=%Q AND origname!=pathname;"
929 "DELETE FROM vfile WHERE pathname=%Q",
930 zFile, zFile
931 );
 
 
932 }else{
933 sqlite3_int64 mtime;
934 int rvChnged = 0;
935 int rvPerm = manifest_file_mperm(pRvFile);
936
937
--- src/update.c
+++ src/update.c
@@ -927,10 +927,12 @@
927 " SET pathname=origname, origname=NULL"
928 " WHERE pathname=%Q AND origname!=pathname;"
929 "DELETE FROM vfile WHERE pathname=%Q",
930 zFile, zFile
931 );
932 }else if( file_unsafe_in_tree_path(zFull) ){
933 /* Ignore this file */
934 }else{
935 sqlite3_int64 mtime;
936 int rvChnged = 0;
937 int rvPerm = manifest_file_mperm(pRvFile);
938
939
+3 -3
--- src/url.c
+++ src/url.c
@@ -50,11 +50,11 @@
5050
int isHttps; /* True if a "https:" url */
5151
int isSsh; /* True if an "ssh:" url */
5252
int isAlias; /* Input URL was an alias */
5353
char *name; /* Hostname for http: or filename for file: */
5454
char *hostname; /* The HOST: parameter on http headers */
55
- const char *protocol; /* "http" or "https" or "ssh" */
55
+ const char *protocol; /* "http" or "https" or "ssh" or "file" */
5656
int port; /* TCP port number for http: or https: */
5757
int dfltPort; /* The default port for the given protocol */
5858
char *path; /* Pathname for http: */
5959
char *user; /* User id for http: */
6060
char *passwd; /* Password for http: */
@@ -76,11 +76,11 @@
7676
** as follows:
7777
**
7878
** isFile True if FILE:
7979
** isHttps True if HTTPS:
8080
** isSsh True if SSH:
81
-** protocol "http" or "https" or "file"
81
+** protocol "http" or "https" or "file" or "ssh"
8282
** name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
8383
** port TCP port number for HTTP or HTTPS.
8484
** dfltPort Default TCP port number (80 or 443).
8585
** path Path name for HTTP or HTTPS.
8686
** user Userid.
@@ -305,11 +305,11 @@
305305
** form last-sync-pw.
306306
**
307307
** g.url.isFile True if FILE:
308308
** g.url.isHttps True if HTTPS:
309309
** g.url.isSsh True if SSH:
310
-** g.url.protocol "http" or "https" or "file"
310
+** g.url.protocol "http" or "https" or "file" or "ssh"
311311
** g.url.name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
312312
** g.url.port TCP port number for HTTP or HTTPS.
313313
** g.url.dfltPort Default TCP port number (80 or 443).
314314
** g.url.path Path name for HTTP or HTTPS.
315315
** g.url.user Userid.
316316
--- src/url.c
+++ src/url.c
@@ -50,11 +50,11 @@
50 int isHttps; /* True if a "https:" url */
51 int isSsh; /* True if an "ssh:" url */
52 int isAlias; /* Input URL was an alias */
53 char *name; /* Hostname for http: or filename for file: */
54 char *hostname; /* The HOST: parameter on http headers */
55 const char *protocol; /* "http" or "https" or "ssh" */
56 int port; /* TCP port number for http: or https: */
57 int dfltPort; /* The default port for the given protocol */
58 char *path; /* Pathname for http: */
59 char *user; /* User id for http: */
60 char *passwd; /* Password for http: */
@@ -76,11 +76,11 @@
76 ** as follows:
77 **
78 ** isFile True if FILE:
79 ** isHttps True if HTTPS:
80 ** isSsh True if SSH:
81 ** protocol "http" or "https" or "file"
82 ** name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
83 ** port TCP port number for HTTP or HTTPS.
84 ** dfltPort Default TCP port number (80 or 443).
85 ** path Path name for HTTP or HTTPS.
86 ** user Userid.
@@ -305,11 +305,11 @@
305 ** form last-sync-pw.
306 **
307 ** g.url.isFile True if FILE:
308 ** g.url.isHttps True if HTTPS:
309 ** g.url.isSsh True if SSH:
310 ** g.url.protocol "http" or "https" or "file"
311 ** g.url.name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
312 ** g.url.port TCP port number for HTTP or HTTPS.
313 ** g.url.dfltPort Default TCP port number (80 or 443).
314 ** g.url.path Path name for HTTP or HTTPS.
315 ** g.url.user Userid.
316
--- src/url.c
+++ src/url.c
@@ -50,11 +50,11 @@
50 int isHttps; /* True if a "https:" url */
51 int isSsh; /* True if an "ssh:" url */
52 int isAlias; /* Input URL was an alias */
53 char *name; /* Hostname for http: or filename for file: */
54 char *hostname; /* The HOST: parameter on http headers */
55 const char *protocol; /* "http" or "https" or "ssh" or "file" */
56 int port; /* TCP port number for http: or https: */
57 int dfltPort; /* The default port for the given protocol */
58 char *path; /* Pathname for http: */
59 char *user; /* User id for http: */
60 char *passwd; /* Password for http: */
@@ -76,11 +76,11 @@
76 ** as follows:
77 **
78 ** isFile True if FILE:
79 ** isHttps True if HTTPS:
80 ** isSsh True if SSH:
81 ** protocol "http" or "https" or "file" or "ssh"
82 ** name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
83 ** port TCP port number for HTTP or HTTPS.
84 ** dfltPort Default TCP port number (80 or 443).
85 ** path Path name for HTTP or HTTPS.
86 ** user Userid.
@@ -305,11 +305,11 @@
305 ** form last-sync-pw.
306 **
307 ** g.url.isFile True if FILE:
308 ** g.url.isHttps True if HTTPS:
309 ** g.url.isSsh True if SSH:
310 ** g.url.protocol "http" or "https" or "file" or "ssh"
311 ** g.url.name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
312 ** g.url.port TCP port number for HTTP or HTTPS.
313 ** g.url.dfltPort Default TCP port number (80 or 443).
314 ** g.url.path Path name for HTTP or HTTPS.
315 ** g.url.user Userid.
316
+5
--- src/user.c
+++ src/user.c
@@ -432,12 +432,14 @@
432432
}
433433
if( blob_size(&pw)==0 ){
434434
fossil_print("password unchanged\n");
435435
}else{
436436
char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3], 0);
437
+ db_unprotect(PROTECT_USER);
437438
db_multi_exec("UPDATE user SET pw=%Q, mtime=now() WHERE uid=%d",
438439
zSecret, uid);
440
+ db_protect_pop();
439441
free(zSecret);
440442
}
441443
}else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
442444
int uid;
443445
if( g.argc!=4 && g.argc!=5 ){
@@ -446,14 +448,16 @@
446448
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
447449
if( uid==0 ){
448450
fossil_fatal("no such user: %s", g.argv[3]);
449451
}
450452
if( g.argc==5 ){
453
+ db_unprotect(PROTECT_USER);
451454
db_multi_exec(
452455
"UPDATE user SET cap=%Q, mtime=now() WHERE uid=%d",
453456
g.argv[4], uid
454457
);
458
+ db_protect_pop();
455459
}
456460
fossil_print("%s\n", db_text(0, "SELECT cap FROM user WHERE uid=%d", uid));
457461
}else{
458462
fossil_fatal("user subcommand should be one of: "
459463
"capabilities default list new password");
@@ -573,10 +577,11 @@
573577
void user_hash_passwords_cmd(void){
574578
if( g.argc!=3 ) usage("REPOSITORY");
575579
db_open_repository(g.argv[2]);
576580
sqlite3_create_function(g.db, "shared_secret", 2, SQLITE_UTF8, 0,
577581
sha1_shared_secret_sql_function, 0, 0);
582
+ db_unprotect(PROTECT_ALL);
578583
db_multi_exec(
579584
"UPDATE user SET pw=shared_secret(pw,login), mtime=now()"
580585
" WHERE length(pw)>0 AND length(pw)!=40"
581586
);
582587
}
583588
--- src/user.c
+++ src/user.c
@@ -432,12 +432,14 @@
432 }
433 if( blob_size(&pw)==0 ){
434 fossil_print("password unchanged\n");
435 }else{
436 char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3], 0);
 
437 db_multi_exec("UPDATE user SET pw=%Q, mtime=now() WHERE uid=%d",
438 zSecret, uid);
 
439 free(zSecret);
440 }
441 }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
442 int uid;
443 if( g.argc!=4 && g.argc!=5 ){
@@ -446,14 +448,16 @@
446 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
447 if( uid==0 ){
448 fossil_fatal("no such user: %s", g.argv[3]);
449 }
450 if( g.argc==5 ){
 
451 db_multi_exec(
452 "UPDATE user SET cap=%Q, mtime=now() WHERE uid=%d",
453 g.argv[4], uid
454 );
 
455 }
456 fossil_print("%s\n", db_text(0, "SELECT cap FROM user WHERE uid=%d", uid));
457 }else{
458 fossil_fatal("user subcommand should be one of: "
459 "capabilities default list new password");
@@ -573,10 +577,11 @@
573 void user_hash_passwords_cmd(void){
574 if( g.argc!=3 ) usage("REPOSITORY");
575 db_open_repository(g.argv[2]);
576 sqlite3_create_function(g.db, "shared_secret", 2, SQLITE_UTF8, 0,
577 sha1_shared_secret_sql_function, 0, 0);
 
578 db_multi_exec(
579 "UPDATE user SET pw=shared_secret(pw,login), mtime=now()"
580 " WHERE length(pw)>0 AND length(pw)!=40"
581 );
582 }
583
--- src/user.c
+++ src/user.c
@@ -432,12 +432,14 @@
432 }
433 if( blob_size(&pw)==0 ){
434 fossil_print("password unchanged\n");
435 }else{
436 char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3], 0);
437 db_unprotect(PROTECT_USER);
438 db_multi_exec("UPDATE user SET pw=%Q, mtime=now() WHERE uid=%d",
439 zSecret, uid);
440 db_protect_pop();
441 free(zSecret);
442 }
443 }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
444 int uid;
445 if( g.argc!=4 && g.argc!=5 ){
@@ -446,14 +448,16 @@
448 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
449 if( uid==0 ){
450 fossil_fatal("no such user: %s", g.argv[3]);
451 }
452 if( g.argc==5 ){
453 db_unprotect(PROTECT_USER);
454 db_multi_exec(
455 "UPDATE user SET cap=%Q, mtime=now() WHERE uid=%d",
456 g.argv[4], uid
457 );
458 db_protect_pop();
459 }
460 fossil_print("%s\n", db_text(0, "SELECT cap FROM user WHERE uid=%d", uid));
461 }else{
462 fossil_fatal("user subcommand should be one of: "
463 "capabilities default list new password");
@@ -573,10 +577,11 @@
577 void user_hash_passwords_cmd(void){
578 if( g.argc!=3 ) usage("REPOSITORY");
579 db_open_repository(g.argv[2]);
580 sqlite3_create_function(g.db, "shared_secret", 2, SQLITE_UTF8, 0,
581 sha1_shared_secret_sql_function, 0, 0);
582 db_unprotect(PROTECT_ALL);
583 db_multi_exec(
584 "UPDATE user SET pw=shared_secret(pw,login), mtime=now()"
585 " WHERE length(pw)>0 AND length(pw)!=40"
586 );
587 }
588
--- src/vfile.c
+++ src/vfile.c
@@ -313,10 +313,13 @@
313313
id = db_column_int(&q, 0);
314314
zName = db_column_text(&q, 1);
315315
rid = db_column_int(&q, 2);
316316
isExe = db_column_int(&q, 3);
317317
isLink = db_column_int(&q, 4);
318
+ if( file_unsafe_in_tree_path(zName) ){
319
+ continue;
320
+ }
318321
content_get(rid, &content);
319322
if( file_is_the_same(&content, zName) ){
320323
blob_reset(&content);
321324
if( file_setexe(zName, isExe) ){
322325
db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d",
323326
--- src/vfile.c
+++ src/vfile.c
@@ -313,10 +313,13 @@
313 id = db_column_int(&q, 0);
314 zName = db_column_text(&q, 1);
315 rid = db_column_int(&q, 2);
316 isExe = db_column_int(&q, 3);
317 isLink = db_column_int(&q, 4);
 
 
 
318 content_get(rid, &content);
319 if( file_is_the_same(&content, zName) ){
320 blob_reset(&content);
321 if( file_setexe(zName, isExe) ){
322 db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d",
323
--- src/vfile.c
+++ src/vfile.c
@@ -313,10 +313,13 @@
313 id = db_column_int(&q, 0);
314 zName = db_column_text(&q, 1);
315 rid = db_column_int(&q, 2);
316 isExe = db_column_int(&q, 3);
317 isLink = db_column_int(&q, 4);
318 if( file_unsafe_in_tree_path(zName) ){
319 continue;
320 }
321 content_get(rid, &content);
322 if( file_is_the_same(&content, zName) ){
323 blob_reset(&content);
324 if( file_setexe(zName, isExe) ){
325 db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d",
326
+6
--- src/xfer.c
+++ src/xfer.c
@@ -1657,11 +1657,13 @@
16571657
int x = db_column_int(&q,3);
16581658
const char *zName = db_column_text(&q,4);
16591659
if( db_column_int64(&q,1)<=iNow-maxAge || !is_a_leaf(x) ){
16601660
/* check-in locks expire after maxAge seconds, or when the
16611661
** check-in is no longer a leaf */
1662
+ db_unprotect(PROTECT_CONFIG);
16621663
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
1664
+ db_protect_pop();
16631665
continue;
16641666
}
16651667
if( fossil_strcmp(zName+8, blob_str(&xfer.aToken[2]))==0 ){
16661668
const char *zClientId = db_column_text(&q, 2);
16671669
const char *zLogin = db_column_text(&q,0);
@@ -1672,16 +1674,18 @@
16721674
seenFault = 1;
16731675
}
16741676
}
16751677
db_finalize(&q);
16761678
if( !seenFault ){
1679
+ db_unprotect(PROTECT_CONFIG);
16771680
db_multi_exec(
16781681
"REPLACE INTO config(name,value,mtime)"
16791682
"VALUES('ci-lock-%q',json_object('login',%Q,'clientid',%Q),now())",
16801683
blob_str(&xfer.aToken[2]), g.zLogin,
16811684
blob_str(&xfer.aToken[3])
16821685
);
1686
+ db_protect_pop();
16831687
}
16841688
if( db_get_boolean("forbid-delta-manifests",0) ){
16851689
@ pragma avoid-delta-manifests
16861690
}
16871691
}
@@ -1694,16 +1698,18 @@
16941698
*/
16951699
if( blob_eq(&xfer.aToken[1], "ci-unlock")
16961700
&& xfer.nToken==3
16971701
&& blob_is_hname(&xfer.aToken[2])
16981702
){
1703
+ db_unprotect(PROTECT_CONFIG);
16991704
db_multi_exec(
17001705
"DELETE FROM config"
17011706
" WHERE name GLOB 'ci-lock-*'"
17021707
" AND json_extract(value,'$.clientid')=%Q",
17031708
blob_str(&xfer.aToken[2])
17041709
);
1710
+ db_protect_pop();
17051711
}
17061712
17071713
}else
17081714
17091715
/* Unknown message
17101716
17111717
ADDED test/reserved-names.test
--- src/xfer.c
+++ src/xfer.c
@@ -1657,11 +1657,13 @@
1657 int x = db_column_int(&q,3);
1658 const char *zName = db_column_text(&q,4);
1659 if( db_column_int64(&q,1)<=iNow-maxAge || !is_a_leaf(x) ){
1660 /* check-in locks expire after maxAge seconds, or when the
1661 ** check-in is no longer a leaf */
 
1662 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
 
1663 continue;
1664 }
1665 if( fossil_strcmp(zName+8, blob_str(&xfer.aToken[2]))==0 ){
1666 const char *zClientId = db_column_text(&q, 2);
1667 const char *zLogin = db_column_text(&q,0);
@@ -1672,16 +1674,18 @@
1672 seenFault = 1;
1673 }
1674 }
1675 db_finalize(&q);
1676 if( !seenFault ){
 
1677 db_multi_exec(
1678 "REPLACE INTO config(name,value,mtime)"
1679 "VALUES('ci-lock-%q',json_object('login',%Q,'clientid',%Q),now())",
1680 blob_str(&xfer.aToken[2]), g.zLogin,
1681 blob_str(&xfer.aToken[3])
1682 );
 
1683 }
1684 if( db_get_boolean("forbid-delta-manifests",0) ){
1685 @ pragma avoid-delta-manifests
1686 }
1687 }
@@ -1694,16 +1698,18 @@
1694 */
1695 if( blob_eq(&xfer.aToken[1], "ci-unlock")
1696 && xfer.nToken==3
1697 && blob_is_hname(&xfer.aToken[2])
1698 ){
 
1699 db_multi_exec(
1700 "DELETE FROM config"
1701 " WHERE name GLOB 'ci-lock-*'"
1702 " AND json_extract(value,'$.clientid')=%Q",
1703 blob_str(&xfer.aToken[2])
1704 );
 
1705 }
1706
1707 }else
1708
1709 /* Unknown message
1710
1711 DDED test/reserved-names.test
--- src/xfer.c
+++ src/xfer.c
@@ -1657,11 +1657,13 @@
1657 int x = db_column_int(&q,3);
1658 const char *zName = db_column_text(&q,4);
1659 if( db_column_int64(&q,1)<=iNow-maxAge || !is_a_leaf(x) ){
1660 /* check-in locks expire after maxAge seconds, or when the
1661 ** check-in is no longer a leaf */
1662 db_unprotect(PROTECT_CONFIG);
1663 db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
1664 db_protect_pop();
1665 continue;
1666 }
1667 if( fossil_strcmp(zName+8, blob_str(&xfer.aToken[2]))==0 ){
1668 const char *zClientId = db_column_text(&q, 2);
1669 const char *zLogin = db_column_text(&q,0);
@@ -1672,16 +1674,18 @@
1674 seenFault = 1;
1675 }
1676 }
1677 db_finalize(&q);
1678 if( !seenFault ){
1679 db_unprotect(PROTECT_CONFIG);
1680 db_multi_exec(
1681 "REPLACE INTO config(name,value,mtime)"
1682 "VALUES('ci-lock-%q',json_object('login',%Q,'clientid',%Q),now())",
1683 blob_str(&xfer.aToken[2]), g.zLogin,
1684 blob_str(&xfer.aToken[3])
1685 );
1686 db_protect_pop();
1687 }
1688 if( db_get_boolean("forbid-delta-manifests",0) ){
1689 @ pragma avoid-delta-manifests
1690 }
1691 }
@@ -1694,16 +1698,18 @@
1698 */
1699 if( blob_eq(&xfer.aToken[1], "ci-unlock")
1700 && xfer.nToken==3
1701 && blob_is_hname(&xfer.aToken[2])
1702 ){
1703 db_unprotect(PROTECT_CONFIG);
1704 db_multi_exec(
1705 "DELETE FROM config"
1706 " WHERE name GLOB 'ci-lock-*'"
1707 " AND json_extract(value,'$.clientid')=%Q",
1708 blob_str(&xfer.aToken[2])
1709 );
1710 db_protect_pop();
1711 }
1712
1713 }else
1714
1715 /* Unknown message
1716
1717 DDED test/reserved-names.test
--- a/test/reserved-names.test
+++ b/test/reserved-names.test
@@ -0,0 +1,121 @@
1
+#
2
+# Copyright (c) 2020 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
+# [email protected]
14
+# http://www.hwaci.com/drh/
15
+#
16
+############################################################################
17
+#
18
+# Tests for reserved names.
19
+#
20
+
21
+test_setup
22
+
23
+###############################################################################
24
+
25
+set reserved_names_tests [list \
26
+ {0 {}} \
27
+ {0 a.fslckout} \
28
+ {1 .fslckout} \
29
+ {1 .FSlckOUT} \
30
+ {2 a/.fslckout} \
31
+ {0 .fslckout/b} \
32
+ {0 fslckout} \
33
+ {0 .fslckoutx} \
34
+ {1 _FOSSIL_} \
35
+ {0 _FOSSIL} \
36
+ {0 FOSSIL_} \
37
+ {0 FOSSIL_} \
38
+ {0 a_FOSSIL_} \
39
+ {0 _FOSSIL__} \
40
+ {0 __FOSSIL__} \
41
+ {0 __FOssIL__} \
42
+ {0 _FOSSIL_/a} \
43
+ {2 a/_FOSSIL_} \
44
+ {2 _FOSSIL_/c/.fslckout} \
45
+ {2 _FOSSIL_/c/.fslckout/_FOSSIL_} \
46
+ {0 _FOSSIL_/c/.fslckout/._FOSSIL_t} \
47
+ {0 _FOSSIL_/c/.fslckout/t._FOSSIL_} \
48
+ {0 a} \
49
+ {0 a/b} \
50
+ {0 a/b/c} \
51
+ {0 a/b/c/} \
52
+ {0 a/_FOSSIL/} \
53
+ {0 a/fslckout/} \
54
+ {0 a/_fslckout/} \
55
+ {0 _FOSSIL-wal} \
56
+ {0 _FOSSIL-shm} \
57
+ {0 _FOSSIL-journal} \
58
+ {0 _FOSSIL_-wal/a} \
59
+ {0 _FOSSIL_-shm/a} \
60
+ {0 _FOSSIL_-journal/a} \
61
+ {1 _FOSSIL_-wal} \
62
+ {1 _FOSSIL_-shm} \
63
+ {1 _FOSSIL_-journal} \
64
+ {2 a/_FOSSIL_-wal} \
65
+ {2 a/_FOSSIL_-shm} \
66
+ {2 a/_FOSSIL_-journal} \
67
+ {0 .fslckout-wal/a} \
68
+ {0 .fslckout-shm/a} \
69
+ {0 .fslckout-journal/a} \
70
+ {1 .fslckout-wal} \
71
+ {1 .fslckout-shm} \
72
+ {1 .fslckout-journal} \
73
+ {2 a/.fslckout-wal} \
74
+ {2 a/.fslckout-shm} \
75
+ {2 a/.fslckout-journal} \
76
+]
77
+
78
+###############################################################################
79
+
80
+set testNo 0
81
+
82
+foreach reserved_names_test $reserved_names_tests {
83
+ incr testNo
84
+
85
+ set reserved_result [lindex $reserved_names_test 0]
86
+ set reserved_name [lindex $reserved_names_test 1]
87
+
88
+ fossil test-is-reserved-name $reserved_name
89
+
90
+ test reserved-result-$testNo {
91
+ [lindex [normalize_result] 0] eq $reserved_result
92
+ }
93
+
94
+ test reserved-name-$testNo {
95
+ [lindex [normalize_result] 1] eq $reserved_name
96
+ }
97
+
98
+ fossil test-is-reserved-name [string toupper $reserved_name]
99
+
100
+ test reserved-result-upper-$testNo {
101
+ [lindex [normalize_result] 0] eq $reserved_result
102
+ }
103
+
104
+ test reserved-name-upper-$testNo {
105
+ [lindex [normalize_result] 1] eq [string toupper $reserved_name]
106
+ }
107
+
108
+ fossil test-is-reserved-name [string tolower $reserved_name]
109
+
110
+ test reserved-result-lower-$testNo {
111
+ [lindex [normalize_result] 0] eq $reserved_result
112
+ }
113
+
114
+ test reserved-name-lower-$testNo {
115
+ [lindex [normalize_result] 1] eq [string tolower $reserved_name]
116
+ }
117
+}
118
+
119
+###############################################################################
120
+
121
+test_cleanup
--- a/test/reserved-names.test
+++ b/test/reserved-names.test
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/test/reserved-names.test
+++ b/test/reserved-names.test
@@ -0,0 +1,121 @@
1 #
2 # Copyright (c) 2020 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 # [email protected]
14 # http://www.hwaci.com/drh/
15 #
16 ############################################################################
17 #
18 # Tests for reserved names.
19 #
20
21 test_setup
22
23 ###############################################################################
24
25 set reserved_names_tests [list \
26 {0 {}} \
27 {0 a.fslckout} \
28 {1 .fslckout} \
29 {1 .FSlckOUT} \
30 {2 a/.fslckout} \
31 {0 .fslckout/b} \
32 {0 fslckout} \
33 {0 .fslckoutx} \
34 {1 _FOSSIL_} \
35 {0 _FOSSIL} \
36 {0 FOSSIL_} \
37 {0 FOSSIL_} \
38 {0 a_FOSSIL_} \
39 {0 _FOSSIL__} \
40 {0 __FOSSIL__} \
41 {0 __FOssIL__} \
42 {0 _FOSSIL_/a} \
43 {2 a/_FOSSIL_} \
44 {2 _FOSSIL_/c/.fslckout} \
45 {2 _FOSSIL_/c/.fslckout/_FOSSIL_} \
46 {0 _FOSSIL_/c/.fslckout/._FOSSIL_t} \
47 {0 _FOSSIL_/c/.fslckout/t._FOSSIL_} \
48 {0 a} \
49 {0 a/b} \
50 {0 a/b/c} \
51 {0 a/b/c/} \
52 {0 a/_FOSSIL/} \
53 {0 a/fslckout/} \
54 {0 a/_fslckout/} \
55 {0 _FOSSIL-wal} \
56 {0 _FOSSIL-shm} \
57 {0 _FOSSIL-journal} \
58 {0 _FOSSIL_-wal/a} \
59 {0 _FOSSIL_-shm/a} \
60 {0 _FOSSIL_-journal/a} \
61 {1 _FOSSIL_-wal} \
62 {1 _FOSSIL_-shm} \
63 {1 _FOSSIL_-journal} \
64 {2 a/_FOSSIL_-wal} \
65 {2 a/_FOSSIL_-shm} \
66 {2 a/_FOSSIL_-journal} \
67 {0 .fslckout-wal/a} \
68 {0 .fslckout-shm/a} \
69 {0 .fslckout-journal/a} \
70 {1 .fslckout-wal} \
71 {1 .fslckout-shm} \
72 {1 .fslckout-journal} \
73 {2 a/.fslckout-wal} \
74 {2 a/.fslckout-shm} \
75 {2 a/.fslckout-journal} \
76 ]
77
78 ###############################################################################
79
80 set testNo 0
81
82 foreach reserved_names_test $reserved_names_tests {
83 incr testNo
84
85 set reserved_result [lindex $reserved_names_test 0]
86 set reserved_name [lindex $reserved_names_test 1]
87
88 fossil test-is-reserved-name $reserved_name
89
90 test reserved-result-$testNo {
91 [lindex [normalize_result] 0] eq $reserved_result
92 }
93
94 test reserved-name-$testNo {
95 [lindex [normalize_result] 1] eq $reserved_name
96 }
97
98 fossil test-is-reserved-name [string toupper $reserved_name]
99
100 test reserved-result-upper-$testNo {
101 [lindex [normalize_result] 0] eq $reserved_result
102 }
103
104 test reserved-name-upper-$testNo {
105 [lindex [normalize_result] 1] eq [string toupper $reserved_name]
106 }
107
108 fossil test-is-reserved-name [string tolower $reserved_name]
109
110 test reserved-result-lower-$testNo {
111 [lindex [normalize_result] 0] eq $reserved_result
112 }
113
114 test reserved-name-lower-$testNo {
115 [lindex [normalize_result] 1] eq [string tolower $reserved_name]
116 }
117 }
118
119 ###############################################################################
120
121 test_cleanup

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button