Fossil SCM
Add the ability to transfer subscriber information using the "fossil config sync alert" command.
Commit
22c3354dcbb718927396250ca32e2579b96e9fa1de1b629fd94786d689aeb7ea
Parent
b7b877ef4928572…
1 file changed
+79
-36
+79
-36
| --- src/configure.c | ||
| +++ src/configure.c | ||
| @@ -36,12 +36,14 @@ | ||
| 36 | 36 | #define CONFIGSET_SHUN 0x000010 /* Shun settings */ |
| 37 | 37 | #define CONFIGSET_USER 0x000020 /* The USER table */ |
| 38 | 38 | #define CONFIGSET_ADDR 0x000040 /* The CONCEALED table */ |
| 39 | 39 | #define CONFIGSET_XFER 0x000080 /* Transfer configuration */ |
| 40 | 40 | #define CONFIGSET_ALIAS 0x000100 /* URL Aliases */ |
| 41 | +#define CONFIGSET_ALERT 0x000200 /* Email alerts */ | |
| 42 | +#define CONFIGSET_FORUM 0x000400 /* Forum posts */ | |
| 41 | 43 | |
| 42 | -#define CONFIGSET_ALL 0x0001ff /* Everything */ | |
| 44 | +#define CONFIGSET_ALL 0x0007ff /* Everything */ | |
| 43 | 45 | |
| 44 | 46 | #define CONFIGSET_OVERWRITE 0x100000 /* Causes overwrite instead of merge */ |
| 45 | 47 | |
| 46 | 48 | /* |
| 47 | 49 | ** This mask is used for the common TH1 configuration settings (i.e. those |
| @@ -68,10 +70,12 @@ | ||
| 68 | 70 | { "/shun", CONFIGSET_SHUN, "List of shunned artifacts" }, |
| 69 | 71 | { "/ticket", CONFIGSET_TKT, "Ticket setup", }, |
| 70 | 72 | { "/user", CONFIGSET_USER, "Users and privilege settings" }, |
| 71 | 73 | { "/xfer", CONFIGSET_XFER, "Transfer setup", }, |
| 72 | 74 | { "/alias", CONFIGSET_ALIAS, "URL Aliases", }, |
| 75 | + { "/alert", CONFIGSET_ALERT, "Notification sent by email", }, | |
| 76 | + { "/forum", CONFIGSET_FORUM, "Forum posts", }, | |
| 73 | 77 | { "/all", CONFIGSET_ALL, "All of the above" }, |
| 74 | 78 | }; |
| 75 | 79 | |
| 76 | 80 | |
| 77 | 81 | /* |
| @@ -157,10 +161,12 @@ | ||
| 157 | 161 | |
| 158 | 162 | { "@shun", CONFIGSET_SHUN }, |
| 159 | 163 | |
| 160 | 164 | { "@alias", CONFIGSET_ALIAS }, |
| 161 | 165 | |
| 166 | + { "@subscriber", CONFIGSET_ALERT }, | |
| 167 | + | |
| 162 | 168 | { "xfer-common-script", CONFIGSET_XFER }, |
| 163 | 169 | { "xfer-push-script", CONFIGSET_XFER }, |
| 164 | 170 | { "xfer-commit-script", CONFIGSET_XFER }, |
| 165 | 171 | { "xfer-ticket-script", CONFIGSET_XFER }, |
| 166 | 172 | |
| @@ -213,11 +219,11 @@ | ||
| 213 | 219 | |
| 214 | 220 | /* |
| 215 | 221 | ** Return the mask for the named configuration parameter if it can be |
| 216 | 222 | ** safely exported. Return 0 if the parameter is not safe to export. |
| 217 | 223 | ** |
| 218 | -** "Safe" in the previous paragraph means the permission is created to | |
| 224 | +** "Safe" in the previous paragraph means the permission is granted to | |
| 219 | 225 | ** export the property. In other words, the requesting side has presented |
| 220 | 226 | ** login credentials and has sufficient capabilities to access the requested |
| 221 | 227 | ** information. |
| 222 | 228 | */ |
| 223 | 229 | int configure_is_exportable(const char *zName){ |
| @@ -229,11 +235,14 @@ | ||
| 229 | 235 | } |
| 230 | 236 | for(i=0; i<count(aConfig); i++){ |
| 231 | 237 | if( strncmp(zName, aConfig[i].zName, n)==0 && aConfig[i].zName[n]==0 ){ |
| 232 | 238 | int m = aConfig[i].groupMask; |
| 233 | 239 | if( !g.perm.Admin ){ |
| 234 | - m &= ~CONFIGSET_USER; | |
| 240 | + m &= ~(CONFIGSET_USER|CONFIGSET_ALERT); | |
| 241 | + } | |
| 242 | + if( !g.perm.RdForum ){ | |
| 243 | + m &= ~(CONFIGSET_FORUM); | |
| 235 | 244 | } |
| 236 | 245 | if( !g.perm.RdAddr ){ |
| 237 | 246 | m &= ~CONFIGSET_ADDR; |
| 238 | 247 | } |
| 239 | 248 | return m; |
| @@ -312,11 +321,15 @@ | ||
| 312 | 321 | ** Mask consists of one or more CONFIGSET_* values ORed together, to |
| 313 | 322 | ** designate what types of configuration we are allowed to receive. |
| 314 | 323 | ** |
| 315 | 324 | ** NEW FORMAT: |
| 316 | 325 | ** |
| 317 | -** zName is one of "/config", "/user", "/shun", "/reportfmt", or "/concealed". | |
| 326 | +** zName is one of: | |
| 327 | +** | |
| 328 | +** "/config", "/user", "/shun", "/reportfmt", "/concealed", | |
| 329 | +** "/alert", "/forum" | |
| 330 | +** | |
| 318 | 331 | ** zName indicates the table that holds the configuration information being |
| 319 | 332 | ** transferred. pContent is a string that consist of alternating Fossil |
| 320 | 333 | ** and SQL tokens. The First token is a timestamp in seconds since 1970. |
| 321 | 334 | ** The second token is a primary key for the table identified by zName. If |
| 322 | 335 | ** The entry with the corresponding primary key exists and has a more recent |
| @@ -332,48 +345,37 @@ | ||
| 332 | 345 | ** /config $MTIME $NAME value $VALUE |
| 333 | 346 | ** /user $MTIME $LOGIN pw $VALUE cap $VALUE info $VALUE photo $VALUE |
| 334 | 347 | ** /shun $MTIME $UUID scom $VALUE |
| 335 | 348 | ** /reportfmt $MTIME $TITLE owner $VALUE cols $VALUE sqlcode $VALUE |
| 336 | 349 | ** /concealed $MTIME $HASH content $VALUE |
| 337 | -** | |
| 338 | -** OLD FORMAT: | |
| 339 | -** | |
| 340 | -** The old format is retained for backwards compatibility, but is deprecated. | |
| 341 | -** The cutover from old format to new was on 2011-04-25. After sufficient | |
| 342 | -** time has passed, support for the old format will be removed. | |
| 343 | -** Update: Support for the old format was removed on 2017-09-20. | |
| 344 | -** | |
| 345 | -** zName is either the NAME of an element of the CONFIG table, or else | |
| 346 | -** one of the special names "@shun", "@reportfmt", "@user", or "@concealed". | |
| 347 | -** If zName is a CONFIG table name, then CONTENT replaces (overwrites) the | |
| 348 | -** element in the CONFIG table. For one of the @-labels, CONTENT is raw | |
| 349 | -** SQL that is evaluated. Note that the raw SQL in CONTENT might not | |
| 350 | -** insert directly into the target table but might instead use a proxy | |
| 351 | -** table like _fer_reportfmt or _xfer_user. Such tables must be created | |
| 352 | -** ahead of time using configure_prepare_to_receive(). Then after multiple | |
| 353 | -** calls to this routine, configure_finalize_receive() to transfer the | |
| 354 | -** information received into the true target table. | |
| 350 | +** /subscriber $SMTIME $SEMAIL suname $V sdigest $V sdonotcall $V ssub $V | |
| 355 | 351 | */ |
| 356 | 352 | void configure_receive(const char *zName, Blob *pContent, int groupMask){ |
| 353 | + int checkMask; /* Masks for which we must first check existance of tables */ | |
| 354 | + | |
| 355 | + checkMask = CONFIGSET_ALERT; | |
| 357 | 356 | if( zName[0]=='/' ){ |
| 358 | 357 | /* The new format */ |
| 359 | 358 | char *azToken[12]; |
| 360 | 359 | int nToken = 0; |
| 361 | 360 | int ii, jj; |
| 362 | 361 | int thisMask; |
| 363 | 362 | Blob name, value, sql; |
| 364 | 363 | static const struct receiveType { |
| 365 | - const char *zName; | |
| 366 | - const char *zPrimKey; | |
| 367 | - int nField; | |
| 368 | - const char *azField[4]; | |
| 364 | + const char *zName; /* Configuration key for this table */ | |
| 365 | + const char *zPrimKey; /* Primary key column */ | |
| 366 | + const char *zMTime; /* Column holding the mtime */ | |
| 367 | + int nField; /* Number of data fields */ | |
| 368 | + const char *azField[4]; /* Names of the data fields */ | |
| 369 | 369 | } aType[] = { |
| 370 | - { "/config", "name", 1, { "value", 0, 0, 0 } }, | |
| 371 | - { "@user", "login", 4, { "pw", "cap", "info", "photo" } }, | |
| 372 | - { "@shun", "uuid", 1, { "scom", 0, 0, 0 } }, | |
| 373 | - { "@reportfmt", "title", 3, { "owner", "cols", "sqlcode", 0 } }, | |
| 374 | - { "@concealed", "hash", 1, { "content", 0, 0, 0 } }, | |
| 370 | + { "/config", "name", "mtime", 1, { "value", 0, 0, 0 } }, | |
| 371 | + { "@user", "login", "mtime", 4, { "pw", "cap", "info", "photo" } }, | |
| 372 | + { "@shun", "uuid", "mtime", 1, { "scom", 0, 0, 0 } }, | |
| 373 | + { "@reportfmt", "title", "mtime", 3, { "owner", "cols", "sqlcode", 0 } }, | |
| 374 | + { "@concealed", "hash", "mtime", 1, { "content", 0, 0, 0 } }, | |
| 375 | + { "@subscriber","semail","smtime",4, { "suname","sdigest", | |
| 376 | + "sdonotcall","ssub"} }, | |
| 375 | 377 | }; |
| 376 | 378 | for(ii=0; ii<count(aType); ii++){ |
| 377 | 379 | if( fossil_strcmp(&aType[ii].zName[1],&zName[1])==0 ) break; |
| 378 | 380 | } |
| 379 | 381 | if( ii>=count(aType) ) return; |
| @@ -398,10 +400,17 @@ | ||
| 398 | 400 | thisMask = configure_is_exportable(azToken[1]); |
| 399 | 401 | }else{ |
| 400 | 402 | thisMask = configure_is_exportable(aType[ii].zName); |
| 401 | 403 | } |
| 402 | 404 | if( (thisMask & groupMask)==0 ) return; |
| 405 | + if( (thisMask & checkMask)!=0 ){ | |
| 406 | + if( (thisMask & CONFIGSET_ALERT)!=0 ){ | |
| 407 | + email_schema(1); | |
| 408 | + } | |
| 409 | + checkMask &= ~thisMask; | |
| 410 | + } | |
| 411 | + | |
| 403 | 412 | |
| 404 | 413 | blob_zero(&sql); |
| 405 | 414 | if( groupMask & CONFIGSET_OVERWRITE ){ |
| 406 | 415 | if( (thisMask & configHasBeenReset)==0 && aType[ii].zName[0]!='/' ){ |
| 407 | 416 | db_multi_exec("DELETE FROM \"%w\"", &aType[ii].zName[1]); |
| @@ -409,11 +418,12 @@ | ||
| 409 | 418 | } |
| 410 | 419 | blob_append_sql(&sql, "REPLACE INTO "); |
| 411 | 420 | }else{ |
| 412 | 421 | blob_append_sql(&sql, "INSERT OR IGNORE INTO "); |
| 413 | 422 | } |
| 414 | - blob_append_sql(&sql, "\"%w\"(\"%w\", mtime", &zName[1], aType[ii].zPrimKey); | |
| 423 | + blob_append_sql(&sql, "\"%w\"(\"%w\", \"%w\"", | |
| 424 | + &zName[1], aType[ii].zPrimKey, aType[ii].zMTime); | |
| 415 | 425 | for(jj=2; jj<nToken; jj+=2){ |
| 416 | 426 | blob_append_sql(&sql, ",\"%w\"", azToken[jj]); |
| 417 | 427 | } |
| 418 | 428 | blob_append_sql(&sql,") VALUES(%s,%s", |
| 419 | 429 | azToken[1] /*safe-for-%s*/, azToken[0] /*safe-for-%s*/); |
| @@ -421,19 +431,19 @@ | ||
| 421 | 431 | blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/); |
| 422 | 432 | } |
| 423 | 433 | db_multi_exec("%s)", blob_sql_text(&sql)); |
| 424 | 434 | if( db_changes()==0 ){ |
| 425 | 435 | blob_reset(&sql); |
| 426 | - blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s", | |
| 427 | - &zName[1], azToken[0]/*safe-for-%s*/); | |
| 436 | + blob_append_sql(&sql, "UPDATE \"%w\" SET \"%w\"=%s", | |
| 437 | + &zName[1], aType[ii].zMTime, azToken[0]/*safe-for-%s*/); | |
| 428 | 438 | for(jj=2; jj<nToken; jj+=2){ |
| 429 | 439 | blob_append_sql(&sql, ", \"%w\"=%s", |
| 430 | 440 | azToken[jj], azToken[jj+1]/*safe-for-%s*/); |
| 431 | 441 | } |
| 432 | - blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s", | |
| 442 | + blob_append_sql(&sql, " WHERE \"%w\"=%s AND \"%w\"<%s", | |
| 433 | 443 | aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/, |
| 434 | - azToken[0]/*safe-for-%s*/); | |
| 444 | + aType[ii].zMTime, azToken[0]/*safe-for-%s*/); | |
| 435 | 445 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 436 | 446 | } |
| 437 | 447 | blob_reset(&sql); |
| 438 | 448 | rebuildMask |= thisMask; |
| 439 | 449 | } |
| @@ -573,10 +583,34 @@ | ||
| 573 | 583 | blob_size(&rec), blob_str(&rec)); |
| 574 | 584 | nCard++; |
| 575 | 585 | blob_reset(&rec); |
| 576 | 586 | } |
| 577 | 587 | db_finalize(&q); |
| 588 | + } | |
| 589 | + if( (groupMask & CONFIGSET_ALERT)!=0 | |
| 590 | + && db_table_exists("repository","subscriber") | |
| 591 | + ){ | |
| 592 | + db_prepare(&q, "SELECT (smtime-2440587.5)*86400," | |
| 593 | + " quote(semail), quote(suname), quote(sdigest)," | |
| 594 | + " quote(sdonotcall), quote(ssub)" | |
| 595 | + " FROM subscriber WHERE sverified" | |
| 596 | + " AND (smtime-2440587.5)*86400>=%lld", iStart); | |
| 597 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 598 | + blob_appendf(&rec,"%lld %s suname %s sdigest %s sdonotcall %s ssub %s", | |
| 599 | + db_column_int64(&q, 0), /* smtime */ | |
| 600 | + db_column_text(&q, 1), /* semail (PK) */ | |
| 601 | + db_column_text(&q, 2), /* suname */ | |
| 602 | + db_column_text(&q, 3), /* sdigest */ | |
| 603 | + db_column_text(&q, 4), /* sdonotcall */ | |
| 604 | + db_column_text(&q, 5) /* ssub */ | |
| 605 | + ); | |
| 606 | + blob_appendf(pOut, "config /subscriber %d\n%s\n", | |
| 607 | + blob_size(&rec), blob_str(&rec)); | |
| 608 | + nCard++; | |
| 609 | + blob_reset(&rec); | |
| 610 | + } | |
| 611 | + db_finalize(&q); | |
| 578 | 612 | } |
| 579 | 613 | db_prepare(&q, "SELECT mtime, quote(name), quote(value) FROM config" |
| 580 | 614 | " WHERE name=:name AND mtime>=%lld", iStart); |
| 581 | 615 | for(ii=0; ii<count(aConfig); ii++){ |
| 582 | 616 | if( (aConfig[ii].groupMask & groupMask)!=0 && aConfig[ii].zName[0]!='@' ){ |
| @@ -792,10 +826,19 @@ | ||
| 792 | 826 | db_create_default_users(0, 0); |
| 793 | 827 | }else if( fossil_strcmp(zName,"@concealed")==0 ){ |
| 794 | 828 | db_multi_exec("DELETE FROM concealed"); |
| 795 | 829 | }else if( fossil_strcmp(zName,"@shun")==0 ){ |
| 796 | 830 | db_multi_exec("DELETE FROM shun"); |
| 831 | + }else if( fossil_strcmp(zName,"@alert")==0 ){ | |
| 832 | + if( db_table_exists("repository","subscriber") ){ | |
| 833 | + db_multi_exec("DELETE FROM subscriber"); | |
| 834 | + } | |
| 835 | + }else if( fossil_strcmp(zName,"@forum")==0 ){ | |
| 836 | + if( db_table_exists("repository","forumpost") ){ | |
| 837 | + db_multi_exec("DELETE FROM forumpost"); | |
| 838 | + db_multi_exec("DELETE FROM forumthread"); | |
| 839 | + } | |
| 797 | 840 | }else if( fossil_strcmp(zName,"@reportfmt")==0 ){ |
| 798 | 841 | db_multi_exec("DELETE FROM reportfmt"); |
| 799 | 842 | assert( strchr(zRepositorySchemaDefaultReports,'%')==0 ); |
| 800 | 843 | db_multi_exec(zRepositorySchemaDefaultReports /*works-like:""*/); |
| 801 | 844 | } |
| 802 | 845 |
| --- src/configure.c | |
| +++ src/configure.c | |
| @@ -36,12 +36,14 @@ | |
| 36 | #define CONFIGSET_SHUN 0x000010 /* Shun settings */ |
| 37 | #define CONFIGSET_USER 0x000020 /* The USER table */ |
| 38 | #define CONFIGSET_ADDR 0x000040 /* The CONCEALED table */ |
| 39 | #define CONFIGSET_XFER 0x000080 /* Transfer configuration */ |
| 40 | #define CONFIGSET_ALIAS 0x000100 /* URL Aliases */ |
| 41 | |
| 42 | #define CONFIGSET_ALL 0x0001ff /* Everything */ |
| 43 | |
| 44 | #define CONFIGSET_OVERWRITE 0x100000 /* Causes overwrite instead of merge */ |
| 45 | |
| 46 | /* |
| 47 | ** This mask is used for the common TH1 configuration settings (i.e. those |
| @@ -68,10 +70,12 @@ | |
| 68 | { "/shun", CONFIGSET_SHUN, "List of shunned artifacts" }, |
| 69 | { "/ticket", CONFIGSET_TKT, "Ticket setup", }, |
| 70 | { "/user", CONFIGSET_USER, "Users and privilege settings" }, |
| 71 | { "/xfer", CONFIGSET_XFER, "Transfer setup", }, |
| 72 | { "/alias", CONFIGSET_ALIAS, "URL Aliases", }, |
| 73 | { "/all", CONFIGSET_ALL, "All of the above" }, |
| 74 | }; |
| 75 | |
| 76 | |
| 77 | /* |
| @@ -157,10 +161,12 @@ | |
| 157 | |
| 158 | { "@shun", CONFIGSET_SHUN }, |
| 159 | |
| 160 | { "@alias", CONFIGSET_ALIAS }, |
| 161 | |
| 162 | { "xfer-common-script", CONFIGSET_XFER }, |
| 163 | { "xfer-push-script", CONFIGSET_XFER }, |
| 164 | { "xfer-commit-script", CONFIGSET_XFER }, |
| 165 | { "xfer-ticket-script", CONFIGSET_XFER }, |
| 166 | |
| @@ -213,11 +219,11 @@ | |
| 213 | |
| 214 | /* |
| 215 | ** Return the mask for the named configuration parameter if it can be |
| 216 | ** safely exported. Return 0 if the parameter is not safe to export. |
| 217 | ** |
| 218 | ** "Safe" in the previous paragraph means the permission is created to |
| 219 | ** export the property. In other words, the requesting side has presented |
| 220 | ** login credentials and has sufficient capabilities to access the requested |
| 221 | ** information. |
| 222 | */ |
| 223 | int configure_is_exportable(const char *zName){ |
| @@ -229,11 +235,14 @@ | |
| 229 | } |
| 230 | for(i=0; i<count(aConfig); i++){ |
| 231 | if( strncmp(zName, aConfig[i].zName, n)==0 && aConfig[i].zName[n]==0 ){ |
| 232 | int m = aConfig[i].groupMask; |
| 233 | if( !g.perm.Admin ){ |
| 234 | m &= ~CONFIGSET_USER; |
| 235 | } |
| 236 | if( !g.perm.RdAddr ){ |
| 237 | m &= ~CONFIGSET_ADDR; |
| 238 | } |
| 239 | return m; |
| @@ -312,11 +321,15 @@ | |
| 312 | ** Mask consists of one or more CONFIGSET_* values ORed together, to |
| 313 | ** designate what types of configuration we are allowed to receive. |
| 314 | ** |
| 315 | ** NEW FORMAT: |
| 316 | ** |
| 317 | ** zName is one of "/config", "/user", "/shun", "/reportfmt", or "/concealed". |
| 318 | ** zName indicates the table that holds the configuration information being |
| 319 | ** transferred. pContent is a string that consist of alternating Fossil |
| 320 | ** and SQL tokens. The First token is a timestamp in seconds since 1970. |
| 321 | ** The second token is a primary key for the table identified by zName. If |
| 322 | ** The entry with the corresponding primary key exists and has a more recent |
| @@ -332,48 +345,37 @@ | |
| 332 | ** /config $MTIME $NAME value $VALUE |
| 333 | ** /user $MTIME $LOGIN pw $VALUE cap $VALUE info $VALUE photo $VALUE |
| 334 | ** /shun $MTIME $UUID scom $VALUE |
| 335 | ** /reportfmt $MTIME $TITLE owner $VALUE cols $VALUE sqlcode $VALUE |
| 336 | ** /concealed $MTIME $HASH content $VALUE |
| 337 | ** |
| 338 | ** OLD FORMAT: |
| 339 | ** |
| 340 | ** The old format is retained for backwards compatibility, but is deprecated. |
| 341 | ** The cutover from old format to new was on 2011-04-25. After sufficient |
| 342 | ** time has passed, support for the old format will be removed. |
| 343 | ** Update: Support for the old format was removed on 2017-09-20. |
| 344 | ** |
| 345 | ** zName is either the NAME of an element of the CONFIG table, or else |
| 346 | ** one of the special names "@shun", "@reportfmt", "@user", or "@concealed". |
| 347 | ** If zName is a CONFIG table name, then CONTENT replaces (overwrites) the |
| 348 | ** element in the CONFIG table. For one of the @-labels, CONTENT is raw |
| 349 | ** SQL that is evaluated. Note that the raw SQL in CONTENT might not |
| 350 | ** insert directly into the target table but might instead use a proxy |
| 351 | ** table like _fer_reportfmt or _xfer_user. Such tables must be created |
| 352 | ** ahead of time using configure_prepare_to_receive(). Then after multiple |
| 353 | ** calls to this routine, configure_finalize_receive() to transfer the |
| 354 | ** information received into the true target table. |
| 355 | */ |
| 356 | void configure_receive(const char *zName, Blob *pContent, int groupMask){ |
| 357 | if( zName[0]=='/' ){ |
| 358 | /* The new format */ |
| 359 | char *azToken[12]; |
| 360 | int nToken = 0; |
| 361 | int ii, jj; |
| 362 | int thisMask; |
| 363 | Blob name, value, sql; |
| 364 | static const struct receiveType { |
| 365 | const char *zName; |
| 366 | const char *zPrimKey; |
| 367 | int nField; |
| 368 | const char *azField[4]; |
| 369 | } aType[] = { |
| 370 | { "/config", "name", 1, { "value", 0, 0, 0 } }, |
| 371 | { "@user", "login", 4, { "pw", "cap", "info", "photo" } }, |
| 372 | { "@shun", "uuid", 1, { "scom", 0, 0, 0 } }, |
| 373 | { "@reportfmt", "title", 3, { "owner", "cols", "sqlcode", 0 } }, |
| 374 | { "@concealed", "hash", 1, { "content", 0, 0, 0 } }, |
| 375 | }; |
| 376 | for(ii=0; ii<count(aType); ii++){ |
| 377 | if( fossil_strcmp(&aType[ii].zName[1],&zName[1])==0 ) break; |
| 378 | } |
| 379 | if( ii>=count(aType) ) return; |
| @@ -398,10 +400,17 @@ | |
| 398 | thisMask = configure_is_exportable(azToken[1]); |
| 399 | }else{ |
| 400 | thisMask = configure_is_exportable(aType[ii].zName); |
| 401 | } |
| 402 | if( (thisMask & groupMask)==0 ) return; |
| 403 | |
| 404 | blob_zero(&sql); |
| 405 | if( groupMask & CONFIGSET_OVERWRITE ){ |
| 406 | if( (thisMask & configHasBeenReset)==0 && aType[ii].zName[0]!='/' ){ |
| 407 | db_multi_exec("DELETE FROM \"%w\"", &aType[ii].zName[1]); |
| @@ -409,11 +418,12 @@ | |
| 409 | } |
| 410 | blob_append_sql(&sql, "REPLACE INTO "); |
| 411 | }else{ |
| 412 | blob_append_sql(&sql, "INSERT OR IGNORE INTO "); |
| 413 | } |
| 414 | blob_append_sql(&sql, "\"%w\"(\"%w\", mtime", &zName[1], aType[ii].zPrimKey); |
| 415 | for(jj=2; jj<nToken; jj+=2){ |
| 416 | blob_append_sql(&sql, ",\"%w\"", azToken[jj]); |
| 417 | } |
| 418 | blob_append_sql(&sql,") VALUES(%s,%s", |
| 419 | azToken[1] /*safe-for-%s*/, azToken[0] /*safe-for-%s*/); |
| @@ -421,19 +431,19 @@ | |
| 421 | blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/); |
| 422 | } |
| 423 | db_multi_exec("%s)", blob_sql_text(&sql)); |
| 424 | if( db_changes()==0 ){ |
| 425 | blob_reset(&sql); |
| 426 | blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s", |
| 427 | &zName[1], azToken[0]/*safe-for-%s*/); |
| 428 | for(jj=2; jj<nToken; jj+=2){ |
| 429 | blob_append_sql(&sql, ", \"%w\"=%s", |
| 430 | azToken[jj], azToken[jj+1]/*safe-for-%s*/); |
| 431 | } |
| 432 | blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s", |
| 433 | aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/, |
| 434 | azToken[0]/*safe-for-%s*/); |
| 435 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 436 | } |
| 437 | blob_reset(&sql); |
| 438 | rebuildMask |= thisMask; |
| 439 | } |
| @@ -573,10 +583,34 @@ | |
| 573 | blob_size(&rec), blob_str(&rec)); |
| 574 | nCard++; |
| 575 | blob_reset(&rec); |
| 576 | } |
| 577 | db_finalize(&q); |
| 578 | } |
| 579 | db_prepare(&q, "SELECT mtime, quote(name), quote(value) FROM config" |
| 580 | " WHERE name=:name AND mtime>=%lld", iStart); |
| 581 | for(ii=0; ii<count(aConfig); ii++){ |
| 582 | if( (aConfig[ii].groupMask & groupMask)!=0 && aConfig[ii].zName[0]!='@' ){ |
| @@ -792,10 +826,19 @@ | |
| 792 | db_create_default_users(0, 0); |
| 793 | }else if( fossil_strcmp(zName,"@concealed")==0 ){ |
| 794 | db_multi_exec("DELETE FROM concealed"); |
| 795 | }else if( fossil_strcmp(zName,"@shun")==0 ){ |
| 796 | db_multi_exec("DELETE FROM shun"); |
| 797 | }else if( fossil_strcmp(zName,"@reportfmt")==0 ){ |
| 798 | db_multi_exec("DELETE FROM reportfmt"); |
| 799 | assert( strchr(zRepositorySchemaDefaultReports,'%')==0 ); |
| 800 | db_multi_exec(zRepositorySchemaDefaultReports /*works-like:""*/); |
| 801 | } |
| 802 |
| --- src/configure.c | |
| +++ src/configure.c | |
| @@ -36,12 +36,14 @@ | |
| 36 | #define CONFIGSET_SHUN 0x000010 /* Shun settings */ |
| 37 | #define CONFIGSET_USER 0x000020 /* The USER table */ |
| 38 | #define CONFIGSET_ADDR 0x000040 /* The CONCEALED table */ |
| 39 | #define CONFIGSET_XFER 0x000080 /* Transfer configuration */ |
| 40 | #define CONFIGSET_ALIAS 0x000100 /* URL Aliases */ |
| 41 | #define CONFIGSET_ALERT 0x000200 /* Email alerts */ |
| 42 | #define CONFIGSET_FORUM 0x000400 /* Forum posts */ |
| 43 | |
| 44 | #define CONFIGSET_ALL 0x0007ff /* Everything */ |
| 45 | |
| 46 | #define CONFIGSET_OVERWRITE 0x100000 /* Causes overwrite instead of merge */ |
| 47 | |
| 48 | /* |
| 49 | ** This mask is used for the common TH1 configuration settings (i.e. those |
| @@ -68,10 +70,12 @@ | |
| 70 | { "/shun", CONFIGSET_SHUN, "List of shunned artifacts" }, |
| 71 | { "/ticket", CONFIGSET_TKT, "Ticket setup", }, |
| 72 | { "/user", CONFIGSET_USER, "Users and privilege settings" }, |
| 73 | { "/xfer", CONFIGSET_XFER, "Transfer setup", }, |
| 74 | { "/alias", CONFIGSET_ALIAS, "URL Aliases", }, |
| 75 | { "/alert", CONFIGSET_ALERT, "Notification sent by email", }, |
| 76 | { "/forum", CONFIGSET_FORUM, "Forum posts", }, |
| 77 | { "/all", CONFIGSET_ALL, "All of the above" }, |
| 78 | }; |
| 79 | |
| 80 | |
| 81 | /* |
| @@ -157,10 +161,12 @@ | |
| 161 | |
| 162 | { "@shun", CONFIGSET_SHUN }, |
| 163 | |
| 164 | { "@alias", CONFIGSET_ALIAS }, |
| 165 | |
| 166 | { "@subscriber", CONFIGSET_ALERT }, |
| 167 | |
| 168 | { "xfer-common-script", CONFIGSET_XFER }, |
| 169 | { "xfer-push-script", CONFIGSET_XFER }, |
| 170 | { "xfer-commit-script", CONFIGSET_XFER }, |
| 171 | { "xfer-ticket-script", CONFIGSET_XFER }, |
| 172 | |
| @@ -213,11 +219,11 @@ | |
| 219 | |
| 220 | /* |
| 221 | ** Return the mask for the named configuration parameter if it can be |
| 222 | ** safely exported. Return 0 if the parameter is not safe to export. |
| 223 | ** |
| 224 | ** "Safe" in the previous paragraph means the permission is granted to |
| 225 | ** export the property. In other words, the requesting side has presented |
| 226 | ** login credentials and has sufficient capabilities to access the requested |
| 227 | ** information. |
| 228 | */ |
| 229 | int configure_is_exportable(const char *zName){ |
| @@ -229,11 +235,14 @@ | |
| 235 | } |
| 236 | for(i=0; i<count(aConfig); i++){ |
| 237 | if( strncmp(zName, aConfig[i].zName, n)==0 && aConfig[i].zName[n]==0 ){ |
| 238 | int m = aConfig[i].groupMask; |
| 239 | if( !g.perm.Admin ){ |
| 240 | m &= ~(CONFIGSET_USER|CONFIGSET_ALERT); |
| 241 | } |
| 242 | if( !g.perm.RdForum ){ |
| 243 | m &= ~(CONFIGSET_FORUM); |
| 244 | } |
| 245 | if( !g.perm.RdAddr ){ |
| 246 | m &= ~CONFIGSET_ADDR; |
| 247 | } |
| 248 | return m; |
| @@ -312,11 +321,15 @@ | |
| 321 | ** Mask consists of one or more CONFIGSET_* values ORed together, to |
| 322 | ** designate what types of configuration we are allowed to receive. |
| 323 | ** |
| 324 | ** NEW FORMAT: |
| 325 | ** |
| 326 | ** zName is one of: |
| 327 | ** |
| 328 | ** "/config", "/user", "/shun", "/reportfmt", "/concealed", |
| 329 | ** "/alert", "/forum" |
| 330 | ** |
| 331 | ** zName indicates the table that holds the configuration information being |
| 332 | ** transferred. pContent is a string that consist of alternating Fossil |
| 333 | ** and SQL tokens. The First token is a timestamp in seconds since 1970. |
| 334 | ** The second token is a primary key for the table identified by zName. If |
| 335 | ** The entry with the corresponding primary key exists and has a more recent |
| @@ -332,48 +345,37 @@ | |
| 345 | ** /config $MTIME $NAME value $VALUE |
| 346 | ** /user $MTIME $LOGIN pw $VALUE cap $VALUE info $VALUE photo $VALUE |
| 347 | ** /shun $MTIME $UUID scom $VALUE |
| 348 | ** /reportfmt $MTIME $TITLE owner $VALUE cols $VALUE sqlcode $VALUE |
| 349 | ** /concealed $MTIME $HASH content $VALUE |
| 350 | ** /subscriber $SMTIME $SEMAIL suname $V sdigest $V sdonotcall $V ssub $V |
| 351 | */ |
| 352 | void configure_receive(const char *zName, Blob *pContent, int groupMask){ |
| 353 | int checkMask; /* Masks for which we must first check existance of tables */ |
| 354 | |
| 355 | checkMask = CONFIGSET_ALERT; |
| 356 | if( zName[0]=='/' ){ |
| 357 | /* The new format */ |
| 358 | char *azToken[12]; |
| 359 | int nToken = 0; |
| 360 | int ii, jj; |
| 361 | int thisMask; |
| 362 | Blob name, value, sql; |
| 363 | static const struct receiveType { |
| 364 | const char *zName; /* Configuration key for this table */ |
| 365 | const char *zPrimKey; /* Primary key column */ |
| 366 | const char *zMTime; /* Column holding the mtime */ |
| 367 | int nField; /* Number of data fields */ |
| 368 | const char *azField[4]; /* Names of the data fields */ |
| 369 | } aType[] = { |
| 370 | { "/config", "name", "mtime", 1, { "value", 0, 0, 0 } }, |
| 371 | { "@user", "login", "mtime", 4, { "pw", "cap", "info", "photo" } }, |
| 372 | { "@shun", "uuid", "mtime", 1, { "scom", 0, 0, 0 } }, |
| 373 | { "@reportfmt", "title", "mtime", 3, { "owner", "cols", "sqlcode", 0 } }, |
| 374 | { "@concealed", "hash", "mtime", 1, { "content", 0, 0, 0 } }, |
| 375 | { "@subscriber","semail","smtime",4, { "suname","sdigest", |
| 376 | "sdonotcall","ssub"} }, |
| 377 | }; |
| 378 | for(ii=0; ii<count(aType); ii++){ |
| 379 | if( fossil_strcmp(&aType[ii].zName[1],&zName[1])==0 ) break; |
| 380 | } |
| 381 | if( ii>=count(aType) ) return; |
| @@ -398,10 +400,17 @@ | |
| 400 | thisMask = configure_is_exportable(azToken[1]); |
| 401 | }else{ |
| 402 | thisMask = configure_is_exportable(aType[ii].zName); |
| 403 | } |
| 404 | if( (thisMask & groupMask)==0 ) return; |
| 405 | if( (thisMask & checkMask)!=0 ){ |
| 406 | if( (thisMask & CONFIGSET_ALERT)!=0 ){ |
| 407 | email_schema(1); |
| 408 | } |
| 409 | checkMask &= ~thisMask; |
| 410 | } |
| 411 | |
| 412 | |
| 413 | blob_zero(&sql); |
| 414 | if( groupMask & CONFIGSET_OVERWRITE ){ |
| 415 | if( (thisMask & configHasBeenReset)==0 && aType[ii].zName[0]!='/' ){ |
| 416 | db_multi_exec("DELETE FROM \"%w\"", &aType[ii].zName[1]); |
| @@ -409,11 +418,12 @@ | |
| 418 | } |
| 419 | blob_append_sql(&sql, "REPLACE INTO "); |
| 420 | }else{ |
| 421 | blob_append_sql(&sql, "INSERT OR IGNORE INTO "); |
| 422 | } |
| 423 | blob_append_sql(&sql, "\"%w\"(\"%w\", \"%w\"", |
| 424 | &zName[1], aType[ii].zPrimKey, aType[ii].zMTime); |
| 425 | for(jj=2; jj<nToken; jj+=2){ |
| 426 | blob_append_sql(&sql, ",\"%w\"", azToken[jj]); |
| 427 | } |
| 428 | blob_append_sql(&sql,") VALUES(%s,%s", |
| 429 | azToken[1] /*safe-for-%s*/, azToken[0] /*safe-for-%s*/); |
| @@ -421,19 +431,19 @@ | |
| 431 | blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/); |
| 432 | } |
| 433 | db_multi_exec("%s)", blob_sql_text(&sql)); |
| 434 | if( db_changes()==0 ){ |
| 435 | blob_reset(&sql); |
| 436 | blob_append_sql(&sql, "UPDATE \"%w\" SET \"%w\"=%s", |
| 437 | &zName[1], aType[ii].zMTime, azToken[0]/*safe-for-%s*/); |
| 438 | for(jj=2; jj<nToken; jj+=2){ |
| 439 | blob_append_sql(&sql, ", \"%w\"=%s", |
| 440 | azToken[jj], azToken[jj+1]/*safe-for-%s*/); |
| 441 | } |
| 442 | blob_append_sql(&sql, " WHERE \"%w\"=%s AND \"%w\"<%s", |
| 443 | aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/, |
| 444 | aType[ii].zMTime, azToken[0]/*safe-for-%s*/); |
| 445 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 446 | } |
| 447 | blob_reset(&sql); |
| 448 | rebuildMask |= thisMask; |
| 449 | } |
| @@ -573,10 +583,34 @@ | |
| 583 | blob_size(&rec), blob_str(&rec)); |
| 584 | nCard++; |
| 585 | blob_reset(&rec); |
| 586 | } |
| 587 | db_finalize(&q); |
| 588 | } |
| 589 | if( (groupMask & CONFIGSET_ALERT)!=0 |
| 590 | && db_table_exists("repository","subscriber") |
| 591 | ){ |
| 592 | db_prepare(&q, "SELECT (smtime-2440587.5)*86400," |
| 593 | " quote(semail), quote(suname), quote(sdigest)," |
| 594 | " quote(sdonotcall), quote(ssub)" |
| 595 | " FROM subscriber WHERE sverified" |
| 596 | " AND (smtime-2440587.5)*86400>=%lld", iStart); |
| 597 | while( db_step(&q)==SQLITE_ROW ){ |
| 598 | blob_appendf(&rec,"%lld %s suname %s sdigest %s sdonotcall %s ssub %s", |
| 599 | db_column_int64(&q, 0), /* smtime */ |
| 600 | db_column_text(&q, 1), /* semail (PK) */ |
| 601 | db_column_text(&q, 2), /* suname */ |
| 602 | db_column_text(&q, 3), /* sdigest */ |
| 603 | db_column_text(&q, 4), /* sdonotcall */ |
| 604 | db_column_text(&q, 5) /* ssub */ |
| 605 | ); |
| 606 | blob_appendf(pOut, "config /subscriber %d\n%s\n", |
| 607 | blob_size(&rec), blob_str(&rec)); |
| 608 | nCard++; |
| 609 | blob_reset(&rec); |
| 610 | } |
| 611 | db_finalize(&q); |
| 612 | } |
| 613 | db_prepare(&q, "SELECT mtime, quote(name), quote(value) FROM config" |
| 614 | " WHERE name=:name AND mtime>=%lld", iStart); |
| 615 | for(ii=0; ii<count(aConfig); ii++){ |
| 616 | if( (aConfig[ii].groupMask & groupMask)!=0 && aConfig[ii].zName[0]!='@' ){ |
| @@ -792,10 +826,19 @@ | |
| 826 | db_create_default_users(0, 0); |
| 827 | }else if( fossil_strcmp(zName,"@concealed")==0 ){ |
| 828 | db_multi_exec("DELETE FROM concealed"); |
| 829 | }else if( fossil_strcmp(zName,"@shun")==0 ){ |
| 830 | db_multi_exec("DELETE FROM shun"); |
| 831 | }else if( fossil_strcmp(zName,"@alert")==0 ){ |
| 832 | if( db_table_exists("repository","subscriber") ){ |
| 833 | db_multi_exec("DELETE FROM subscriber"); |
| 834 | } |
| 835 | }else if( fossil_strcmp(zName,"@forum")==0 ){ |
| 836 | if( db_table_exists("repository","forumpost") ){ |
| 837 | db_multi_exec("DELETE FROM forumpost"); |
| 838 | db_multi_exec("DELETE FROM forumthread"); |
| 839 | } |
| 840 | }else if( fossil_strcmp(zName,"@reportfmt")==0 ){ |
| 841 | db_multi_exec("DELETE FROM reportfmt"); |
| 842 | assert( strchr(zRepositorySchemaDefaultReports,'%')==0 ); |
| 843 | db_multi_exec(zRepositorySchemaDefaultReports /*works-like:""*/); |
| 844 | } |
| 845 |