Fossil SCM

Merged in trunk.

stephan 2020-05-04 07:54 unaddremove-command merge
Commit 628c39fb602423f5d96224e2e49736f3889748aa55e70321eb5bad57604fa8b0
+150 -60
--- src/alerts.c
+++ src/alerts.c
@@ -1208,10 +1208,19 @@
12081208
int i, j, n;
12091209
char c;
12101210
12111211
*peErr = 0;
12121212
*pzErr = 0;
1213
+
1214
+ /* Verify the captcha first */
1215
+ if( needCaptcha ){
1216
+ if( !captcha_is_correct(1) ){
1217
+ *peErr = 2;
1218
+ *pzErr = mprintf("incorrect security code");
1219
+ return 0;
1220
+ }
1221
+ }
12131222
12141223
/* Check the validity of the email address.
12151224
**
12161225
** (1) Exactly one '@' character.
12171226
** (2) No other characters besides [a-zA-Z0-9._+-]
@@ -1219,11 +1228,15 @@
12191228
** The local part is currently more restrictive than RFC 5322 allows:
12201229
** https://stackoverflow.com/a/2049510/142454 We will expand this as
12211230
** necessary.
12221231
*/
12231232
zEAddr = P("e");
1224
- if( zEAddr==0 ) return 0;
1233
+ if( zEAddr==0 ){
1234
+ *peErr = 1;
1235
+ *pzErr = mprintf("required");
1236
+ return 0;
1237
+ }
12251238
for(i=j=n=0; (c = zEAddr[i])!=0; i++){
12261239
if( c=='@' ){
12271240
n = i;
12281241
j++;
12291242
continue;
@@ -1249,14 +1262,13 @@
12491262
*peErr = 1;
12501263
*pzErr = mprintf("email domain too short");
12511264
return 0;
12521265
}
12531266
1254
- /* Verify the captcha */
1255
- if( needCaptcha && !captcha_is_correct(1) ){
1256
- *peErr = 2;
1257
- *pzErr = mprintf("incorrect security code");
1267
+ if( authorized_subscription_email(zEAddr)==0 ){
1268
+ *peErr = 1;
1269
+ *pzErr = mprintf("not an authorized email address");
12581270
return 0;
12591271
}
12601272
12611273
/* Check to make sure the email address is available for reuse */
12621274
if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q", zEAddr) ){
@@ -1347,10 +1359,14 @@
13471359
/* Everybody else jumps to the page to administer their own
13481360
** account only. */
13491361
cgi_redirectf("%R/alerts");
13501362
return;
13511363
}
1364
+ }
1365
+ if( !g.perm.Admin && !db_get_boolean("anon-subscribe",1) ){
1366
+ register_page();
1367
+ return;
13521368
}
13531369
alert_submenu_common();
13541370
needCaptcha = !login_is_individual();
13551371
if( P("submit")
13561372
&& cgi_csrf_safe(1)
@@ -1368,10 +1384,11 @@
13681384
if( PB("sa") ) ssub[nsub++] = 'a';
13691385
if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c';
13701386
if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f';
13711387
if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't';
13721388
if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w';
1389
+ if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x';
13731390
ssub[nsub] = 0;
13741391
db_multi_exec(
13751392
"INSERT INTO subscriber(semail,suname,"
13761393
" sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
13771394
"VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)",
@@ -1388,11 +1405,15 @@
13881405
id);
13891406
if( !needCaptcha ){
13901407
/* The new subscription has been added on behalf of a logged-in user.
13911408
** No verification is required. Jump immediately to /alerts page.
13921409
*/
1393
- cgi_redirectf("%R/alerts/%s", zCode);
1410
+ if( g.perm.Admin ){
1411
+ cgi_redirectf("%R/alerts/%.32s", zCode);
1412
+ }else{
1413
+ cgi_redirectf("%R/alerts");
1414
+ }
13941415
return;
13951416
}else{
13961417
/* We need to send a verification email */
13971418
Blob hdr, body;
13981419
AlertSender *pSender = alert_sender_new(0,0);
@@ -1410,11 +1431,11 @@
14101431
@ <blockquote><pre>
14111432
@ %h(pSender->zErr)
14121433
@ </pre></blockquote>
14131434
}else{
14141435
@ <p>An email has been sent to "%h(zEAddr)". That email contains a
1415
- @ hyperlink that you must click on in order to activate your
1436
+ @ hyperlink that you must click to activate your
14161437
@ subscription.</p>
14171438
}
14181439
alert_sender_free(pSender);
14191440
style_footer();
14201441
}
@@ -1442,16 +1463,22 @@
14421463
if( eErr==1 ){
14431464
@ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
14441465
}
14451466
@ </tr>
14461467
if( needCaptcha ){
1447
- uSeed = captcha_seed();
1468
+ const char *zInit = "";
1469
+ if( P("captchaseed")!=0 && eErr!=2 ){
1470
+ uSeed = strtoul(P("captchaseed"),0,10);
1471
+ zInit = P("captcha");
1472
+ }else{
1473
+ uSeed = captcha_seed();
1474
+ }
14481475
zDecoded = captcha_decode(uSeed);
14491476
zCaptcha = captcha_render(zDecoded);
14501477
@ <tr>
14511478
@ <td class="form_label">Security Code:</td>
1452
- @ <td><input type="text" name="captcha" value="" size="30">
1479
+ @ <td><input type="text" name="captcha" value="%h(zInit)" size="30">
14531480
captcha_speakit_button(uSeed, "Speak the code");
14541481
@ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
14551482
@ </tr>
14561483
if( eErr==2 ){
14571484
@ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
@@ -1478,10 +1505,12 @@
14781505
@ Check-ins</label><br>
14791506
}
14801507
if( g.perm.RdForum ){
14811508
@ <label><input type="checkbox" name="sf" %s(PCK("sf"))> \
14821509
@ Forum Posts</label><br>
1510
+ @ <label><input type="checkbox" name="sx" %s(PCK("sx"))> \
1511
+ @ Forum Edits</label><br>
14831512
}
14841513
if( g.perm.RdTkt ){
14851514
@ <label><input type="checkbox" name="st" %s(PCK("st"))> \
14861515
@ Ticket changes</label><br>
14871516
}
@@ -1531,21 +1560,20 @@
15311560
/*
15321561
** Either shutdown or completely delete a subscription entry given
15331562
** by the hex value zName. Then paint a webpage that explains that
15341563
** the entry has been removed.
15351564
*/
1536
-static void alert_unsubscribe(const char *zName){
1565
+static void alert_unsubscribe(int sid){
15371566
char *zEmail;
15381567
zEmail = db_text(0, "SELECT semail FROM subscriber"
1539
- " WHERE subscriberCode=hextoblob(%Q)", zName);
1568
+ " WHERE subscriberId=%d", sid);
15401569
if( zEmail==0 ){
15411570
style_header("Unsubscribe Fail");
15421571
@ <p>Unable to locate a subscriber with the requested key</p>
15431572
}else{
15441573
db_multi_exec(
1545
- "DELETE FROM subscriber WHERE subscriberCode=hextoblob(%Q)",
1546
- zName
1574
+ "DELETE FROM subscriber WHERE subscriberId=%d", sid
15471575
);
15481576
style_header("Unsubscribed");
15491577
@ <p>The "%h(zEmail)" email address has been delisted.
15501578
@ All traces of that email address have been removed</p>
15511579
}
@@ -1556,48 +1584,83 @@
15561584
/*
15571585
** WEBPAGE: alerts
15581586
**
15591587
** Edit email alert and notification settings.
15601588
**
1561
-** The subscriber is identified in either of two ways:
1589
+** The subscriber is identified in several ways:
1590
+**
1591
+** (1) The name= query parameter contains the complete subscriberCode.
1592
+** This only happens when the user receives a verification
1593
+** email and clicks on the link in the email. When a
1594
+** compilete subscriberCode is seen on the name= query parameter,
1595
+** that constitutes verification of the email address.
15621596
**
1563
-** (1) The name= query parameter contains the subscriberCode.
1597
+** (2) The sid= query parameter contains an integer subscriberId.
1598
+** This only works for the administrator. It allows the
1599
+** administrator to edit any subscription.
15641600
**
1565
-** (2) The user is logged into an account other than "nobody" or
1601
+** (3) The user is logged into an account other than "nobody" or
15661602
** "anonymous". In that case the notification settings
15671603
** associated with that account can be edited without needing
15681604
** to know the subscriber code.
1605
+**
1606
+** (4) The name= query parameter contains a 32-digit prefix of
1607
+** subscriber code. (Subscriber codes are normally 64 hex digits
1608
+** in length.) This uniquely identifies the subscriber without
1609
+** revealing the complete subscriber code, and hence without
1610
+** verifying the email address.
15691611
*/
15701612
void alert_page(void){
1571
- const char *zName = P("name");
1572
- Stmt q;
1573
- int sa, sc, sf, st, sw, sx;
1574
- int sdigest = 0, sdonotcall = 0, sverified = 0;
1575
- int isLogin; /* Logged in as an individual */
1576
- const char *ssub = 0;
1577
- const char *semail = 0;
1578
- const char *smip;
1579
- const char *suname = 0;
1580
- const char *mtime;
1581
- const char *sctime;
1582
- int eErr = 0;
1583
- char *zErr = 0;
1584
-
1585
- if( alert_webpages_disabled() ) return;
1613
+ const char *zName = 0; /* Value of the name= query parameter */
1614
+ Stmt q; /* For querying the database */
1615
+ int sa, sc, sf, st, sw, sx; /* Types of notifications requested */
1616
+ int sdigest = 0, sdonotcall = 0, sverified = 0; /* Other fields */
1617
+ int isLogin; /* True if logged in as an individual */
1618
+ const char *ssub = 0; /* Subscription flags */
1619
+ const char *semail = 0; /* Email address */
1620
+ const char *smip; /* */
1621
+ const char *suname = 0; /* Corresponding user.login value */
1622
+ const char *mtime; /* */
1623
+ const char *sctime; /* Time subscription created */
1624
+ int eErr = 0; /* Type of error */
1625
+ char *zErr = 0; /* Error message text */
1626
+ int sid = 0; /* Subscriber ID */
1627
+ int nName; /* Length of zName in bytes */
1628
+ char *zHalfCode; /* prefix of subscriberCode */
1629
+
1630
+ db_begin_transaction();
1631
+ if( alert_webpages_disabled() ){
1632
+ db_commit_transaction();
1633
+ return;
1634
+ }
15861635
login_check_credentials();
15871636
if( !g.perm.EmailAlert ){
1637
+ db_commit_transaction();
15881638
login_needed(g.anon.EmailAlert);
1589
- return;
1639
+ /*NOTREACHED*/
15901640
}
15911641
isLogin = login_is_individual();
1592
- if( zName==0 && isLogin ){
1593
- zName = db_text(0, "SELECT hex(subscriberCode) FROM subscriber"
1594
- " WHERE suname=%Q", g.zLogin);
1642
+ zName = P("name");
1643
+ nName = zName ? (int)strlen(zName) : 0;
1644
+ if( g.perm.Admin && P("sid")!=0 ){
1645
+ sid = atoi(P("sid"));
1646
+ }
1647
+ if( sid==0 && nName>=32 ){
1648
+ sid = db_int(0,
1649
+ "SELECT CASE WHEN hex(subscriberCode) LIKE (%Q||'%%')"
1650
+ " THEN subscriberId ELSE 0 END"
1651
+ " FROM subscriber WHERE subscriberCode>=hextoblob(%Q)"
1652
+ " LIMIT 1", zName, zName);
1653
+ }
1654
+ if( sid==0 && isLogin ){
1655
+ sid = db_int(0, "SELECT subscriberId FROM subscriber"
1656
+ " WHERE suname=%Q", g.zLogin);
15951657
}
1596
- if( zName==0 || !validate16(zName, -1) ){
1658
+ if( sid==0 ){
1659
+ db_commit_transaction();
15971660
cgi_redirect("subscribe");
1598
- return;
1661
+ /*NOTREACHED*/
15991662
}
16001663
alert_submenu_common();
16011664
if( P("submit")!=0 && cgi_csrf_safe(1) ){
16021665
char newSsub[10];
16031666
int nsub = 0;
@@ -1641,11 +1704,11 @@
16411704
if( semail==0 || email_address_is_valid(semail,0)==0 ){
16421705
eErr = 8;
16431706
}
16441707
blob_append_sql(&update, ", semail=%Q", semail);
16451708
}
1646
- blob_append_sql(&update," WHERE subscriberCode=hextoblob(%Q)", zName);
1709
+ blob_append_sql(&update," WHERE subscriberId=%d", sid);
16471710
if( eErr==0 ){
16481711
db_exec_sql(blob_str(&update));
16491712
ssub = 0;
16501713
}
16511714
blob_reset(&update);
@@ -1654,12 +1717,13 @@
16541717
if( !PB("dodelete") ){
16551718
eErr = 9;
16561719
zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to"
16571720
" unsubscribe");
16581721
}else{
1659
- alert_unsubscribe(zName);
1660
- return;
1722
+ alert_unsubscribe(sid);
1723
+ db_commit_transaction();
1724
+ return;
16611725
}
16621726
}
16631727
style_header("Update Subscription");
16641728
db_prepare(&q,
16651729
"SELECT"
@@ -1669,16 +1733,18 @@
16691733
" sdigest," /* 3 */
16701734
" ssub," /* 4 */
16711735
" smip," /* 5 */
16721736
" suname," /* 6 */
16731737
" datetime(mtime,'unixepoch')," /* 7 */
1674
- " datetime(sctime,'unixepoch')" /* 8 */
1675
- " FROM subscriber WHERE subscriberCode=hextoblob(%Q)", zName);
1738
+ " datetime(sctime,'unixepoch')," /* 8 */
1739
+ " hex(subscriberCode)" /* 9 */
1740
+ " FROM subscriber WHERE subscriberId=%d", sid);
16761741
if( db_step(&q)!=SQLITE_ROW ){
16771742
db_finalize(&q);
1743
+ db_commit_transaction();
16781744
cgi_redirect("subscribe");
1679
- return;
1745
+ /*NOTREACHED*/
16801746
}
16811747
if( ssub==0 ){
16821748
semail = db_column_text(&q, 0);
16831749
sdonotcall = db_column_int(&q, 2);
16841750
sdigest = db_column_int(&q, 3);
@@ -1696,23 +1762,45 @@
16961762
sx = strchr(ssub,'x')!=0;
16971763
smip = db_column_text(&q, 5);
16981764
mtime = db_column_text(&q, 7);
16991765
sctime = db_column_text(&q, 8);
17001766
if( !g.perm.Admin && !sverified ){
1701
- db_multi_exec(
1702
- "UPDATE subscriber SET sverified=1 WHERE subscriberCode=hextoblob(%Q)",
1703
- zName);
1704
- @ <h1>Your email alert subscription has been verified!</h1>
1705
- @ <p>Use the form below to update your subscription information.</p>
1706
- @ <p>Hint: Bookmark this page so that you can more easily update
1707
- @ your subscription information in the future</p>
1767
+ if( nName==64 ){
1768
+ db_multi_exec(
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
+ @ your subscription information in the future</p>
1788
+ }else{
1789
+ @ <h2>Your email address is unverified</h2>
1790
+ @ <p>You should have received an email message containing a link
1791
+ @ that you must visit to verify your account. No email notifications
1792
+ @ will be sent until your email address has been verified.</p>
1793
+ }
17081794
}else{
17091795
@ <p>Make changes to the email subscription shown below and
17101796
@ press "Submit".</p>
17111797
}
17121798
form_begin(0, "%R/alerts");
1713
- @ <input type="hidden" name="name" value="%h(zName)">
1799
+ zHalfCode = db_text("x","SELECT hex(substr(subscriberCode,1,16))"
1800
+ " FROM subscriber WHERE subscriberId=%d", sid);
1801
+ @ <input type="hidden" name="name" value="%h(zHalfCode)">
17141802
@ <table class="subscribe">
17151803
@ <tr>
17161804
@ <td class="form_label">Email&nbsp;Address:</td>
17171805
if( isLogin ){
17181806
@ <td><input type="text" name="semail" value="%h(semail)" size="30">\
@@ -1739,10 +1827,13 @@
17391827
@ </tr>
17401828
@ <tr>
17411829
@ <td class='form_label'>IP Address:</td>
17421830
@ <td>%h(smip)</td>
17431831
@ </tr>
1832
+ @ <tr>
1833
+ @ <td class='form_label'>Subscriber&nbsp;Code:</td>
1834
+ @ <td>%h(db_column_text(&q,9))</td>
17441835
@ <tr>
17451836
@ <td class="form_label">User:</td>
17461837
@ <td><input type="text" name="suname" value="%h(suname?suname:"")" \
17471838
@ size="30">\
17481839
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", suname);
@@ -1780,14 +1871,10 @@
17801871
@ <td><select size="1" name="sdigest">
17811872
@ <option value="0" %s(sdigest?"":"selected")>Individual Emails</option>
17821873
@ <option value="1" %s(sdigest?"selected":"")>Daily Digest</option>
17831874
@ </select></td>
17841875
@ </tr>
1785
-#if 0
1786
- @ <label><input type="checkbox" name="sdigest" %s(sdigest?"checked":"")>\
1787
- @ Daily digest only</label><br>
1788
-#endif
17891876
if( g.perm.Admin ){
17901877
@ <tr>
17911878
@ <td class="form_label">Admin Options:</td><td>
17921879
@ <label><input type="checkbox" name="sdonotcall" \
17931880
@ %s(sdonotcall?"checked":"")> Do not disturb</label><br>
@@ -1811,10 +1898,12 @@
18111898
@ </table>
18121899
@ </form>
18131900
fossil_free(zErr);
18141901
db_finalize(&q);
18151902
style_footer();
1903
+ db_commit_transaction();
1904
+ return;
18161905
}
18171906
18181907
/* This is the message that gets sent to describe how to change
18191908
** or modify a subscription
18201909
*/
@@ -1852,18 +1941,19 @@
18521941
char *zCaptcha = 0;
18531942
int dx;
18541943
int bSubmit;
18551944
const char *zEAddr;
18561945
char *zCode = 0;
1946
+ int sid = 0;
18571947
18581948
/* If a valid subscriber code is supplied, then unsubscribe immediately.
18591949
*/
18601950
if( zName
1861
- && db_exists("SELECT 1 FROM subscriber WHERE subscriberCode=hextoblob(%Q)",
1862
- zName)
1951
+ && (sid = db_int(0, "SELECT subscriberId FROM subscriber"
1952
+ " WHERE subscriberCode=hextoblob(%Q)", zName))!=0
18631953
){
1864
- alert_unsubscribe(zName);
1954
+ alert_unsubscribe(sid);
18651955
return;
18661956
}
18671957
18681958
/* Logged in users are redirected to the /alerts page */
18691959
login_check_credentials();
@@ -2021,11 +2111,11 @@
20212111
if( nDel>0 ){
20222112
@ <p>*** %d(nDel) pending subscriptions deleted ***</p>
20232113
}
20242114
blob_init(&sql, 0, 0);
20252115
blob_append_sql(&sql,
2026
- "SELECT hex(subscriberCode)," /* 0 */
2116
+ "SELECT subscriberId," /* 0 */
20272117
" semail," /* 1 */
20282118
" ssub," /* 2 */
20292119
" suname," /* 3 */
20302120
" sverified," /* 4 */
20312121
" sdigest," /* 5 */
@@ -2058,11 +2148,11 @@
20582148
sqlite3_int64 iMtime = db_column_int64(&q, 6);
20592149
double rAge = (iNow - iMtime)/86400.0;
20602150
int uid = db_column_int(&q, 8);
20612151
const char *zUname = db_column_text(&q, 3);
20622152
@ <tr>
2063
- @ <td><a href='%R/alerts/%s(db_column_text(&q,0))'>\
2153
+ @ <td><a href='%R/alerts?sid=%d(db_column_int(&q,0))'>\
20642154
@ %h(db_column_text(&q,1))</a></td>
20652155
@ <td>%h(db_column_text(&q,2))</td>
20662156
@ <td>%s(db_column_int(&q,5)?"digest":"")</td>
20672157
if( uid ){
20682158
@ <td><a href='%R/setup_uedit?id=%d(uid)'>%h(zUname)</a>
20692159
--- src/alerts.c
+++ src/alerts.c
@@ -1208,10 +1208,19 @@
1208 int i, j, n;
1209 char c;
1210
1211 *peErr = 0;
1212 *pzErr = 0;
 
 
 
 
 
 
 
 
 
1213
1214 /* Check the validity of the email address.
1215 **
1216 ** (1) Exactly one '@' character.
1217 ** (2) No other characters besides [a-zA-Z0-9._+-]
@@ -1219,11 +1228,15 @@
1219 ** The local part is currently more restrictive than RFC 5322 allows:
1220 ** https://stackoverflow.com/a/2049510/142454 We will expand this as
1221 ** necessary.
1222 */
1223 zEAddr = P("e");
1224 if( zEAddr==0 ) return 0;
 
 
 
 
1225 for(i=j=n=0; (c = zEAddr[i])!=0; i++){
1226 if( c=='@' ){
1227 n = i;
1228 j++;
1229 continue;
@@ -1249,14 +1262,13 @@
1249 *peErr = 1;
1250 *pzErr = mprintf("email domain too short");
1251 return 0;
1252 }
1253
1254 /* Verify the captcha */
1255 if( needCaptcha && !captcha_is_correct(1) ){
1256 *peErr = 2;
1257 *pzErr = mprintf("incorrect security code");
1258 return 0;
1259 }
1260
1261 /* Check to make sure the email address is available for reuse */
1262 if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q", zEAddr) ){
@@ -1347,10 +1359,14 @@
1347 /* Everybody else jumps to the page to administer their own
1348 ** account only. */
1349 cgi_redirectf("%R/alerts");
1350 return;
1351 }
 
 
 
 
1352 }
1353 alert_submenu_common();
1354 needCaptcha = !login_is_individual();
1355 if( P("submit")
1356 && cgi_csrf_safe(1)
@@ -1368,10 +1384,11 @@
1368 if( PB("sa") ) ssub[nsub++] = 'a';
1369 if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c';
1370 if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f';
1371 if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't';
1372 if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w';
 
1373 ssub[nsub] = 0;
1374 db_multi_exec(
1375 "INSERT INTO subscriber(semail,suname,"
1376 " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
1377 "VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)",
@@ -1388,11 +1405,15 @@
1388 id);
1389 if( !needCaptcha ){
1390 /* The new subscription has been added on behalf of a logged-in user.
1391 ** No verification is required. Jump immediately to /alerts page.
1392 */
1393 cgi_redirectf("%R/alerts/%s", zCode);
 
 
 
 
1394 return;
1395 }else{
1396 /* We need to send a verification email */
1397 Blob hdr, body;
1398 AlertSender *pSender = alert_sender_new(0,0);
@@ -1410,11 +1431,11 @@
1410 @ <blockquote><pre>
1411 @ %h(pSender->zErr)
1412 @ </pre></blockquote>
1413 }else{
1414 @ <p>An email has been sent to "%h(zEAddr)". That email contains a
1415 @ hyperlink that you must click on in order to activate your
1416 @ subscription.</p>
1417 }
1418 alert_sender_free(pSender);
1419 style_footer();
1420 }
@@ -1442,16 +1463,22 @@
1442 if( eErr==1 ){
1443 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
1444 }
1445 @ </tr>
1446 if( needCaptcha ){
1447 uSeed = captcha_seed();
 
 
 
 
 
 
1448 zDecoded = captcha_decode(uSeed);
1449 zCaptcha = captcha_render(zDecoded);
1450 @ <tr>
1451 @ <td class="form_label">Security Code:</td>
1452 @ <td><input type="text" name="captcha" value="" size="30">
1453 captcha_speakit_button(uSeed, "Speak the code");
1454 @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
1455 @ </tr>
1456 if( eErr==2 ){
1457 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
@@ -1478,10 +1505,12 @@
1478 @ Check-ins</label><br>
1479 }
1480 if( g.perm.RdForum ){
1481 @ <label><input type="checkbox" name="sf" %s(PCK("sf"))> \
1482 @ Forum Posts</label><br>
 
 
1483 }
1484 if( g.perm.RdTkt ){
1485 @ <label><input type="checkbox" name="st" %s(PCK("st"))> \
1486 @ Ticket changes</label><br>
1487 }
@@ -1531,21 +1560,20 @@
1531 /*
1532 ** Either shutdown or completely delete a subscription entry given
1533 ** by the hex value zName. Then paint a webpage that explains that
1534 ** the entry has been removed.
1535 */
1536 static void alert_unsubscribe(const char *zName){
1537 char *zEmail;
1538 zEmail = db_text(0, "SELECT semail FROM subscriber"
1539 " WHERE subscriberCode=hextoblob(%Q)", zName);
1540 if( zEmail==0 ){
1541 style_header("Unsubscribe Fail");
1542 @ <p>Unable to locate a subscriber with the requested key</p>
1543 }else{
1544 db_multi_exec(
1545 "DELETE FROM subscriber WHERE subscriberCode=hextoblob(%Q)",
1546 zName
1547 );
1548 style_header("Unsubscribed");
1549 @ <p>The "%h(zEmail)" email address has been delisted.
1550 @ All traces of that email address have been removed</p>
1551 }
@@ -1556,48 +1584,83 @@
1556 /*
1557 ** WEBPAGE: alerts
1558 **
1559 ** Edit email alert and notification settings.
1560 **
1561 ** The subscriber is identified in either of two ways:
 
 
 
 
 
 
1562 **
1563 ** (1) The name= query parameter contains the subscriberCode.
 
 
1564 **
1565 ** (2) The user is logged into an account other than "nobody" or
1566 ** "anonymous". In that case the notification settings
1567 ** associated with that account can be edited without needing
1568 ** to know the subscriber code.
 
 
 
 
 
 
1569 */
1570 void alert_page(void){
1571 const char *zName = P("name");
1572 Stmt q;
1573 int sa, sc, sf, st, sw, sx;
1574 int sdigest = 0, sdonotcall = 0, sverified = 0;
1575 int isLogin; /* Logged in as an individual */
1576 const char *ssub = 0;
1577 const char *semail = 0;
1578 const char *smip;
1579 const char *suname = 0;
1580 const char *mtime;
1581 const char *sctime;
1582 int eErr = 0;
1583 char *zErr = 0;
1584
1585 if( alert_webpages_disabled() ) return;
 
 
 
 
 
 
 
1586 login_check_credentials();
1587 if( !g.perm.EmailAlert ){
 
1588 login_needed(g.anon.EmailAlert);
1589 return;
1590 }
1591 isLogin = login_is_individual();
1592 if( zName==0 && isLogin ){
1593 zName = db_text(0, "SELECT hex(subscriberCode) FROM subscriber"
1594 " WHERE suname=%Q", g.zLogin);
 
 
 
 
 
 
 
 
 
 
 
 
1595 }
1596 if( zName==0 || !validate16(zName, -1) ){
 
1597 cgi_redirect("subscribe");
1598 return;
1599 }
1600 alert_submenu_common();
1601 if( P("submit")!=0 && cgi_csrf_safe(1) ){
1602 char newSsub[10];
1603 int nsub = 0;
@@ -1641,11 +1704,11 @@
1641 if( semail==0 || email_address_is_valid(semail,0)==0 ){
1642 eErr = 8;
1643 }
1644 blob_append_sql(&update, ", semail=%Q", semail);
1645 }
1646 blob_append_sql(&update," WHERE subscriberCode=hextoblob(%Q)", zName);
1647 if( eErr==0 ){
1648 db_exec_sql(blob_str(&update));
1649 ssub = 0;
1650 }
1651 blob_reset(&update);
@@ -1654,12 +1717,13 @@
1654 if( !PB("dodelete") ){
1655 eErr = 9;
1656 zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to"
1657 " unsubscribe");
1658 }else{
1659 alert_unsubscribe(zName);
1660 return;
 
1661 }
1662 }
1663 style_header("Update Subscription");
1664 db_prepare(&q,
1665 "SELECT"
@@ -1669,16 +1733,18 @@
1669 " sdigest," /* 3 */
1670 " ssub," /* 4 */
1671 " smip," /* 5 */
1672 " suname," /* 6 */
1673 " datetime(mtime,'unixepoch')," /* 7 */
1674 " datetime(sctime,'unixepoch')" /* 8 */
1675 " FROM subscriber WHERE subscriberCode=hextoblob(%Q)", zName);
 
1676 if( db_step(&q)!=SQLITE_ROW ){
1677 db_finalize(&q);
 
1678 cgi_redirect("subscribe");
1679 return;
1680 }
1681 if( ssub==0 ){
1682 semail = db_column_text(&q, 0);
1683 sdonotcall = db_column_int(&q, 2);
1684 sdigest = db_column_int(&q, 3);
@@ -1696,23 +1762,45 @@
1696 sx = strchr(ssub,'x')!=0;
1697 smip = db_column_text(&q, 5);
1698 mtime = db_column_text(&q, 7);
1699 sctime = db_column_text(&q, 8);
1700 if( !g.perm.Admin && !sverified ){
1701 db_multi_exec(
1702 "UPDATE subscriber SET sverified=1 WHERE subscriberCode=hextoblob(%Q)",
1703 zName);
1704 @ <h1>Your email alert subscription has been verified!</h1>
1705 @ <p>Use the form below to update your subscription information.</p>
1706 @ <p>Hint: Bookmark this page so that you can more easily update
1707 @ your subscription information in the future</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1708 }else{
1709 @ <p>Make changes to the email subscription shown below and
1710 @ press "Submit".</p>
1711 }
1712 form_begin(0, "%R/alerts");
1713 @ <input type="hidden" name="name" value="%h(zName)">
 
 
1714 @ <table class="subscribe">
1715 @ <tr>
1716 @ <td class="form_label">Email&nbsp;Address:</td>
1717 if( isLogin ){
1718 @ <td><input type="text" name="semail" value="%h(semail)" size="30">\
@@ -1739,10 +1827,13 @@
1739 @ </tr>
1740 @ <tr>
1741 @ <td class='form_label'>IP Address:</td>
1742 @ <td>%h(smip)</td>
1743 @ </tr>
 
 
 
1744 @ <tr>
1745 @ <td class="form_label">User:</td>
1746 @ <td><input type="text" name="suname" value="%h(suname?suname:"")" \
1747 @ size="30">\
1748 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", suname);
@@ -1780,14 +1871,10 @@
1780 @ <td><select size="1" name="sdigest">
1781 @ <option value="0" %s(sdigest?"":"selected")>Individual Emails</option>
1782 @ <option value="1" %s(sdigest?"selected":"")>Daily Digest</option>
1783 @ </select></td>
1784 @ </tr>
1785 #if 0
1786 @ <label><input type="checkbox" name="sdigest" %s(sdigest?"checked":"")>\
1787 @ Daily digest only</label><br>
1788 #endif
1789 if( g.perm.Admin ){
1790 @ <tr>
1791 @ <td class="form_label">Admin Options:</td><td>
1792 @ <label><input type="checkbox" name="sdonotcall" \
1793 @ %s(sdonotcall?"checked":"")> Do not disturb</label><br>
@@ -1811,10 +1898,12 @@
1811 @ </table>
1812 @ </form>
1813 fossil_free(zErr);
1814 db_finalize(&q);
1815 style_footer();
 
 
1816 }
1817
1818 /* This is the message that gets sent to describe how to change
1819 ** or modify a subscription
1820 */
@@ -1852,18 +1941,19 @@
1852 char *zCaptcha = 0;
1853 int dx;
1854 int bSubmit;
1855 const char *zEAddr;
1856 char *zCode = 0;
 
1857
1858 /* If a valid subscriber code is supplied, then unsubscribe immediately.
1859 */
1860 if( zName
1861 && db_exists("SELECT 1 FROM subscriber WHERE subscriberCode=hextoblob(%Q)",
1862 zName)
1863 ){
1864 alert_unsubscribe(zName);
1865 return;
1866 }
1867
1868 /* Logged in users are redirected to the /alerts page */
1869 login_check_credentials();
@@ -2021,11 +2111,11 @@
2021 if( nDel>0 ){
2022 @ <p>*** %d(nDel) pending subscriptions deleted ***</p>
2023 }
2024 blob_init(&sql, 0, 0);
2025 blob_append_sql(&sql,
2026 "SELECT hex(subscriberCode)," /* 0 */
2027 " semail," /* 1 */
2028 " ssub," /* 2 */
2029 " suname," /* 3 */
2030 " sverified," /* 4 */
2031 " sdigest," /* 5 */
@@ -2058,11 +2148,11 @@
2058 sqlite3_int64 iMtime = db_column_int64(&q, 6);
2059 double rAge = (iNow - iMtime)/86400.0;
2060 int uid = db_column_int(&q, 8);
2061 const char *zUname = db_column_text(&q, 3);
2062 @ <tr>
2063 @ <td><a href='%R/alerts/%s(db_column_text(&q,0))'>\
2064 @ %h(db_column_text(&q,1))</a></td>
2065 @ <td>%h(db_column_text(&q,2))</td>
2066 @ <td>%s(db_column_int(&q,5)?"digest":"")</td>
2067 if( uid ){
2068 @ <td><a href='%R/setup_uedit?id=%d(uid)'>%h(zUname)</a>
2069
--- src/alerts.c
+++ src/alerts.c
@@ -1208,10 +1208,19 @@
1208 int i, j, n;
1209 char c;
1210
1211 *peErr = 0;
1212 *pzErr = 0;
1213
1214 /* Verify the captcha first */
1215 if( needCaptcha ){
1216 if( !captcha_is_correct(1) ){
1217 *peErr = 2;
1218 *pzErr = mprintf("incorrect security code");
1219 return 0;
1220 }
1221 }
1222
1223 /* Check the validity of the email address.
1224 **
1225 ** (1) Exactly one '@' character.
1226 ** (2) No other characters besides [a-zA-Z0-9._+-]
@@ -1219,11 +1228,15 @@
1228 ** The local part is currently more restrictive than RFC 5322 allows:
1229 ** https://stackoverflow.com/a/2049510/142454 We will expand this as
1230 ** necessary.
1231 */
1232 zEAddr = P("e");
1233 if( zEAddr==0 ){
1234 *peErr = 1;
1235 *pzErr = mprintf("required");
1236 return 0;
1237 }
1238 for(i=j=n=0; (c = zEAddr[i])!=0; i++){
1239 if( c=='@' ){
1240 n = i;
1241 j++;
1242 continue;
@@ -1249,14 +1262,13 @@
1262 *peErr = 1;
1263 *pzErr = mprintf("email domain too short");
1264 return 0;
1265 }
1266
1267 if( authorized_subscription_email(zEAddr)==0 ){
1268 *peErr = 1;
1269 *pzErr = mprintf("not an authorized email address");
 
1270 return 0;
1271 }
1272
1273 /* Check to make sure the email address is available for reuse */
1274 if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q", zEAddr) ){
@@ -1347,10 +1359,14 @@
1359 /* Everybody else jumps to the page to administer their own
1360 ** account only. */
1361 cgi_redirectf("%R/alerts");
1362 return;
1363 }
1364 }
1365 if( !g.perm.Admin && !db_get_boolean("anon-subscribe",1) ){
1366 register_page();
1367 return;
1368 }
1369 alert_submenu_common();
1370 needCaptcha = !login_is_individual();
1371 if( P("submit")
1372 && cgi_csrf_safe(1)
@@ -1368,10 +1384,11 @@
1384 if( PB("sa") ) ssub[nsub++] = 'a';
1385 if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c';
1386 if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f';
1387 if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't';
1388 if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w';
1389 if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x';
1390 ssub[nsub] = 0;
1391 db_multi_exec(
1392 "INSERT INTO subscriber(semail,suname,"
1393 " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
1394 "VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)",
@@ -1388,11 +1405,15 @@
1405 id);
1406 if( !needCaptcha ){
1407 /* The new subscription has been added on behalf of a logged-in user.
1408 ** No verification is required. Jump immediately to /alerts page.
1409 */
1410 if( g.perm.Admin ){
1411 cgi_redirectf("%R/alerts/%.32s", zCode);
1412 }else{
1413 cgi_redirectf("%R/alerts");
1414 }
1415 return;
1416 }else{
1417 /* We need to send a verification email */
1418 Blob hdr, body;
1419 AlertSender *pSender = alert_sender_new(0,0);
@@ -1410,11 +1431,11 @@
1431 @ <blockquote><pre>
1432 @ %h(pSender->zErr)
1433 @ </pre></blockquote>
1434 }else{
1435 @ <p>An email has been sent to "%h(zEAddr)". That email contains a
1436 @ hyperlink that you must click to activate your
1437 @ subscription.</p>
1438 }
1439 alert_sender_free(pSender);
1440 style_footer();
1441 }
@@ -1442,16 +1463,22 @@
1463 if( eErr==1 ){
1464 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
1465 }
1466 @ </tr>
1467 if( needCaptcha ){
1468 const char *zInit = "";
1469 if( P("captchaseed")!=0 && eErr!=2 ){
1470 uSeed = strtoul(P("captchaseed"),0,10);
1471 zInit = P("captcha");
1472 }else{
1473 uSeed = captcha_seed();
1474 }
1475 zDecoded = captcha_decode(uSeed);
1476 zCaptcha = captcha_render(zDecoded);
1477 @ <tr>
1478 @ <td class="form_label">Security Code:</td>
1479 @ <td><input type="text" name="captcha" value="%h(zInit)" size="30">
1480 captcha_speakit_button(uSeed, "Speak the code");
1481 @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td>
1482 @ </tr>
1483 if( eErr==2 ){
1484 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
@@ -1478,10 +1505,12 @@
1505 @ Check-ins</label><br>
1506 }
1507 if( g.perm.RdForum ){
1508 @ <label><input type="checkbox" name="sf" %s(PCK("sf"))> \
1509 @ Forum Posts</label><br>
1510 @ <label><input type="checkbox" name="sx" %s(PCK("sx"))> \
1511 @ Forum Edits</label><br>
1512 }
1513 if( g.perm.RdTkt ){
1514 @ <label><input type="checkbox" name="st" %s(PCK("st"))> \
1515 @ Ticket changes</label><br>
1516 }
@@ -1531,21 +1560,20 @@
1560 /*
1561 ** Either shutdown or completely delete a subscription entry given
1562 ** by the hex value zName. Then paint a webpage that explains that
1563 ** the entry has been removed.
1564 */
1565 static void alert_unsubscribe(int sid){
1566 char *zEmail;
1567 zEmail = db_text(0, "SELECT semail FROM subscriber"
1568 " WHERE subscriberId=%d", sid);
1569 if( zEmail==0 ){
1570 style_header("Unsubscribe Fail");
1571 @ <p>Unable to locate a subscriber with the requested key</p>
1572 }else{
1573 db_multi_exec(
1574 "DELETE FROM subscriber WHERE subscriberId=%d", sid
 
1575 );
1576 style_header("Unsubscribed");
1577 @ <p>The "%h(zEmail)" email address has been delisted.
1578 @ All traces of that email address have been removed</p>
1579 }
@@ -1556,48 +1584,83 @@
1584 /*
1585 ** WEBPAGE: alerts
1586 **
1587 ** Edit email alert and notification settings.
1588 **
1589 ** The subscriber is identified in several ways:
1590 **
1591 ** (1) The name= query parameter contains the complete subscriberCode.
1592 ** This only happens when the user receives a verification
1593 ** email and clicks on the link in the email. When a
1594 ** compilete subscriberCode is seen on the name= query parameter,
1595 ** that constitutes verification of the email address.
1596 **
1597 ** (2) The sid= query parameter contains an integer subscriberId.
1598 ** This only works for the administrator. It allows the
1599 ** administrator to edit any subscription.
1600 **
1601 ** (3) The user is logged into an account other than "nobody" or
1602 ** "anonymous". In that case the notification settings
1603 ** associated with that account can be edited without needing
1604 ** to know the subscriber code.
1605 **
1606 ** (4) The name= query parameter contains a 32-digit prefix of
1607 ** subscriber code. (Subscriber codes are normally 64 hex digits
1608 ** in length.) This uniquely identifies the subscriber without
1609 ** revealing the complete subscriber code, and hence without
1610 ** verifying the email address.
1611 */
1612 void alert_page(void){
1613 const char *zName = 0; /* Value of the name= query parameter */
1614 Stmt q; /* For querying the database */
1615 int sa, sc, sf, st, sw, sx; /* Types of notifications requested */
1616 int sdigest = 0, sdonotcall = 0, sverified = 0; /* Other fields */
1617 int isLogin; /* True if logged in as an individual */
1618 const char *ssub = 0; /* Subscription flags */
1619 const char *semail = 0; /* Email address */
1620 const char *smip; /* */
1621 const char *suname = 0; /* Corresponding user.login value */
1622 const char *mtime; /* */
1623 const char *sctime; /* Time subscription created */
1624 int eErr = 0; /* Type of error */
1625 char *zErr = 0; /* Error message text */
1626 int sid = 0; /* Subscriber ID */
1627 int nName; /* Length of zName in bytes */
1628 char *zHalfCode; /* prefix of subscriberCode */
1629
1630 db_begin_transaction();
1631 if( alert_webpages_disabled() ){
1632 db_commit_transaction();
1633 return;
1634 }
1635 login_check_credentials();
1636 if( !g.perm.EmailAlert ){
1637 db_commit_transaction();
1638 login_needed(g.anon.EmailAlert);
1639 /*NOTREACHED*/
1640 }
1641 isLogin = login_is_individual();
1642 zName = P("name");
1643 nName = zName ? (int)strlen(zName) : 0;
1644 if( g.perm.Admin && P("sid")!=0 ){
1645 sid = atoi(P("sid"));
1646 }
1647 if( sid==0 && nName>=32 ){
1648 sid = db_int(0,
1649 "SELECT CASE WHEN hex(subscriberCode) LIKE (%Q||'%%')"
1650 " THEN subscriberId ELSE 0 END"
1651 " FROM subscriber WHERE subscriberCode>=hextoblob(%Q)"
1652 " LIMIT 1", zName, zName);
1653 }
1654 if( sid==0 && isLogin ){
1655 sid = db_int(0, "SELECT subscriberId FROM subscriber"
1656 " WHERE suname=%Q", g.zLogin);
1657 }
1658 if( sid==0 ){
1659 db_commit_transaction();
1660 cgi_redirect("subscribe");
1661 /*NOTREACHED*/
1662 }
1663 alert_submenu_common();
1664 if( P("submit")!=0 && cgi_csrf_safe(1) ){
1665 char newSsub[10];
1666 int nsub = 0;
@@ -1641,11 +1704,11 @@
1704 if( semail==0 || email_address_is_valid(semail,0)==0 ){
1705 eErr = 8;
1706 }
1707 blob_append_sql(&update, ", semail=%Q", semail);
1708 }
1709 blob_append_sql(&update," WHERE subscriberId=%d", sid);
1710 if( eErr==0 ){
1711 db_exec_sql(blob_str(&update));
1712 ssub = 0;
1713 }
1714 blob_reset(&update);
@@ -1654,12 +1717,13 @@
1717 if( !PB("dodelete") ){
1718 eErr = 9;
1719 zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to"
1720 " unsubscribe");
1721 }else{
1722 alert_unsubscribe(sid);
1723 db_commit_transaction();
1724 return;
1725 }
1726 }
1727 style_header("Update Subscription");
1728 db_prepare(&q,
1729 "SELECT"
@@ -1669,16 +1733,18 @@
1733 " sdigest," /* 3 */
1734 " ssub," /* 4 */
1735 " smip," /* 5 */
1736 " suname," /* 6 */
1737 " datetime(mtime,'unixepoch')," /* 7 */
1738 " datetime(sctime,'unixepoch')," /* 8 */
1739 " hex(subscriberCode)" /* 9 */
1740 " FROM subscriber WHERE subscriberId=%d", sid);
1741 if( db_step(&q)!=SQLITE_ROW ){
1742 db_finalize(&q);
1743 db_commit_transaction();
1744 cgi_redirect("subscribe");
1745 /*NOTREACHED*/
1746 }
1747 if( ssub==0 ){
1748 semail = db_column_text(&q, 0);
1749 sdonotcall = db_column_int(&q, 2);
1750 sdigest = db_column_int(&q, 3);
@@ -1696,23 +1762,45 @@
1762 sx = strchr(ssub,'x')!=0;
1763 smip = db_column_text(&q, 5);
1764 mtime = db_column_text(&q, 7);
1765 sctime = db_column_text(&q, 8);
1766 if( !g.perm.Admin && !sverified ){
1767 if( nName==64 ){
1768 db_multi_exec(
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 @ your subscription information in the future</p>
1788 }else{
1789 @ <h2>Your email address is unverified</h2>
1790 @ <p>You should have received an email message containing a link
1791 @ that you must visit to verify your account. No email notifications
1792 @ will be sent until your email address has been verified.</p>
1793 }
1794 }else{
1795 @ <p>Make changes to the email subscription shown below and
1796 @ press "Submit".</p>
1797 }
1798 form_begin(0, "%R/alerts");
1799 zHalfCode = db_text("x","SELECT hex(substr(subscriberCode,1,16))"
1800 " FROM subscriber WHERE subscriberId=%d", sid);
1801 @ <input type="hidden" name="name" value="%h(zHalfCode)">
1802 @ <table class="subscribe">
1803 @ <tr>
1804 @ <td class="form_label">Email&nbsp;Address:</td>
1805 if( isLogin ){
1806 @ <td><input type="text" name="semail" value="%h(semail)" size="30">\
@@ -1739,10 +1827,13 @@
1827 @ </tr>
1828 @ <tr>
1829 @ <td class='form_label'>IP Address:</td>
1830 @ <td>%h(smip)</td>
1831 @ </tr>
1832 @ <tr>
1833 @ <td class='form_label'>Subscriber&nbsp;Code:</td>
1834 @ <td>%h(db_column_text(&q,9))</td>
1835 @ <tr>
1836 @ <td class="form_label">User:</td>
1837 @ <td><input type="text" name="suname" value="%h(suname?suname:"")" \
1838 @ size="30">\
1839 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", suname);
@@ -1780,14 +1871,10 @@
1871 @ <td><select size="1" name="sdigest">
1872 @ <option value="0" %s(sdigest?"":"selected")>Individual Emails</option>
1873 @ <option value="1" %s(sdigest?"selected":"")>Daily Digest</option>
1874 @ </select></td>
1875 @ </tr>
 
 
 
 
1876 if( g.perm.Admin ){
1877 @ <tr>
1878 @ <td class="form_label">Admin Options:</td><td>
1879 @ <label><input type="checkbox" name="sdonotcall" \
1880 @ %s(sdonotcall?"checked":"")> Do not disturb</label><br>
@@ -1811,10 +1898,12 @@
1898 @ </table>
1899 @ </form>
1900 fossil_free(zErr);
1901 db_finalize(&q);
1902 style_footer();
1903 db_commit_transaction();
1904 return;
1905 }
1906
1907 /* This is the message that gets sent to describe how to change
1908 ** or modify a subscription
1909 */
@@ -1852,18 +1941,19 @@
1941 char *zCaptcha = 0;
1942 int dx;
1943 int bSubmit;
1944 const char *zEAddr;
1945 char *zCode = 0;
1946 int sid = 0;
1947
1948 /* If a valid subscriber code is supplied, then unsubscribe immediately.
1949 */
1950 if( zName
1951 && (sid = db_int(0, "SELECT subscriberId FROM subscriber"
1952 " WHERE subscriberCode=hextoblob(%Q)", zName))!=0
1953 ){
1954 alert_unsubscribe(sid);
1955 return;
1956 }
1957
1958 /* Logged in users are redirected to the /alerts page */
1959 login_check_credentials();
@@ -2021,11 +2111,11 @@
2111 if( nDel>0 ){
2112 @ <p>*** %d(nDel) pending subscriptions deleted ***</p>
2113 }
2114 blob_init(&sql, 0, 0);
2115 blob_append_sql(&sql,
2116 "SELECT subscriberId," /* 0 */
2117 " semail," /* 1 */
2118 " ssub," /* 2 */
2119 " suname," /* 3 */
2120 " sverified," /* 4 */
2121 " sdigest," /* 5 */
@@ -2058,11 +2148,11 @@
2148 sqlite3_int64 iMtime = db_column_int64(&q, 6);
2149 double rAge = (iNow - iMtime)/86400.0;
2150 int uid = db_column_int(&q, 8);
2151 const char *zUname = db_column_text(&q, 3);
2152 @ <tr>
2153 @ <td><a href='%R/alerts?sid=%d(db_column_int(&q,0))'>\
2154 @ %h(db_column_text(&q,1))</a></td>
2155 @ <td>%h(db_column_text(&q,2))</td>
2156 @ <td>%s(db_column_int(&q,5)?"digest":"")</td>
2157 if( uid ){
2158 @ <td><a href='%R/setup_uedit?id=%d(uid)'>%h(zUname)</a>
2159
--- src/capabilities.c
+++ src/capabilities.c
@@ -366,11 +366,11 @@
366366
CapabilityString *pCap;
367367
char *zSelfCap;
368368
char *zPubPages = db_get("public-pages",0);
369369
int hasPubPages = zPubPages && zPubPages[0];
370370
371
- pCap = capability_add(0, db_get("default-perms",0));
371
+ pCap = capability_add(0, db_get("default-perms","u"));
372372
capability_expand(pCap);
373373
zSelfCap = capability_string(pCap);
374374
capability_free(pCap);
375375
376376
db_prepare(&q,
377377
--- src/capabilities.c
+++ src/capabilities.c
@@ -366,11 +366,11 @@
366 CapabilityString *pCap;
367 char *zSelfCap;
368 char *zPubPages = db_get("public-pages",0);
369 int hasPubPages = zPubPages && zPubPages[0];
370
371 pCap = capability_add(0, db_get("default-perms",0));
372 capability_expand(pCap);
373 zSelfCap = capability_string(pCap);
374 capability_free(pCap);
375
376 db_prepare(&q,
377
--- src/capabilities.c
+++ src/capabilities.c
@@ -366,11 +366,11 @@
366 CapabilityString *pCap;
367 char *zSelfCap;
368 char *zPubPages = db_get("public-pages",0);
369 int hasPubPages = zPubPages && zPubPages[0];
370
371 pCap = capability_add(0, db_get("default-perms","u"));
372 capability_expand(pCap);
373 zSelfCap = capability_string(pCap);
374 capability_free(pCap);
375
376 db_prepare(&q,
377
+14 -3
--- src/checkin.c
+++ src/checkin.c
@@ -2090,12 +2090,17 @@
20902090
useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0;
20912091
noSign = find_option("nosign",0,0)!=0;
20922092
privateFlag = find_option("private",0,0)!=0;
20932093
forceDelta = find_option("delta",0,0)!=0;
20942094
forceBaseline = find_option("baseline",0,0)!=0;
2095
- if( forceDelta && forceBaseline ){
2096
- fossil_fatal("cannot use --delta and --baseline together");
2095
+ if( forceDelta ){
2096
+ if( forceBaseline ){
2097
+ fossil_fatal("cannot use --delta and --baseline together");
2098
+ }
2099
+ if( db_get_boolean("forbid-delta-manifests",0) ){
2100
+ fossil_fatal("delta manifests are prohibited in this repository");
2101
+ }
20972102
}
20982103
dryRunFlag = find_option("dry-run","n",0)!=0;
20992104
if( !dryRunFlag ){
21002105
dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
21012106
}
@@ -2174,11 +2179,14 @@
21742179
** manifest) can continue to use this repository, do not create a new
21752180
** delta-manifest unless this repository already contains one or more
21762181
** delta-manifests, or unless the delta-manifest is explicitly requested
21772182
** by the --delta option.
21782183
*/
2179
- if( !forceDelta && !db_get_boolean("seen-delta-manifest",0) ){
2184
+ if( !forceDelta
2185
+ && !db_get_boolean("seen-delta-manifest",0)
2186
+ && !db_get_boolean("forbid-delta-manifests",0)
2187
+ ){
21802188
forceBaseline = 1;
21812189
}
21822190
21832191
/*
21842192
** Autosync if autosync is enabled and this is not a private check-in.
@@ -2648,10 +2656,11 @@
26482656
/* Commit */
26492657
db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'");
26502658
db_multi_exec("PRAGMA repository.application_id=252006673;");
26512659
db_multi_exec("PRAGMA localdb.application_id=252006674;");
26522660
if( dryRunFlag ){
2661
+ leaf_ambiguity_warning(nvid,nvid);
26532662
db_end_transaction(1);
26542663
exit(1);
26552664
}
26562665
db_end_transaction(0);
26572666
@@ -2670,7 +2679,9 @@
26702679
int nTries = db_get_int("autosync-tries",1);
26712680
autosync_loop(syncFlags, nTries, 0);
26722681
}
26732682
if( count_nonbranch_children(vid)>1 ){
26742683
fossil_print("**** warning: a fork has occurred *****\n");
2684
+ }else{
2685
+ leaf_ambiguity_warning(nvid,nvid);
26752686
}
26762687
}
26772688
--- src/checkin.c
+++ src/checkin.c
@@ -2090,12 +2090,17 @@
2090 useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0;
2091 noSign = find_option("nosign",0,0)!=0;
2092 privateFlag = find_option("private",0,0)!=0;
2093 forceDelta = find_option("delta",0,0)!=0;
2094 forceBaseline = find_option("baseline",0,0)!=0;
2095 if( forceDelta && forceBaseline ){
2096 fossil_fatal("cannot use --delta and --baseline together");
 
 
 
 
 
2097 }
2098 dryRunFlag = find_option("dry-run","n",0)!=0;
2099 if( !dryRunFlag ){
2100 dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
2101 }
@@ -2174,11 +2179,14 @@
2174 ** manifest) can continue to use this repository, do not create a new
2175 ** delta-manifest unless this repository already contains one or more
2176 ** delta-manifests, or unless the delta-manifest is explicitly requested
2177 ** by the --delta option.
2178 */
2179 if( !forceDelta && !db_get_boolean("seen-delta-manifest",0) ){
 
 
 
2180 forceBaseline = 1;
2181 }
2182
2183 /*
2184 ** Autosync if autosync is enabled and this is not a private check-in.
@@ -2648,10 +2656,11 @@
2648 /* Commit */
2649 db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'");
2650 db_multi_exec("PRAGMA repository.application_id=252006673;");
2651 db_multi_exec("PRAGMA localdb.application_id=252006674;");
2652 if( dryRunFlag ){
 
2653 db_end_transaction(1);
2654 exit(1);
2655 }
2656 db_end_transaction(0);
2657
@@ -2670,7 +2679,9 @@
2670 int nTries = db_get_int("autosync-tries",1);
2671 autosync_loop(syncFlags, nTries, 0);
2672 }
2673 if( count_nonbranch_children(vid)>1 ){
2674 fossil_print("**** warning: a fork has occurred *****\n");
 
 
2675 }
2676 }
2677
--- src/checkin.c
+++ src/checkin.c
@@ -2090,12 +2090,17 @@
2090 useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0;
2091 noSign = find_option("nosign",0,0)!=0;
2092 privateFlag = find_option("private",0,0)!=0;
2093 forceDelta = find_option("delta",0,0)!=0;
2094 forceBaseline = find_option("baseline",0,0)!=0;
2095 if( forceDelta ){
2096 if( forceBaseline ){
2097 fossil_fatal("cannot use --delta and --baseline together");
2098 }
2099 if( db_get_boolean("forbid-delta-manifests",0) ){
2100 fossil_fatal("delta manifests are prohibited in this repository");
2101 }
2102 }
2103 dryRunFlag = find_option("dry-run","n",0)!=0;
2104 if( !dryRunFlag ){
2105 dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
2106 }
@@ -2174,11 +2179,14 @@
2179 ** manifest) can continue to use this repository, do not create a new
2180 ** delta-manifest unless this repository already contains one or more
2181 ** delta-manifests, or unless the delta-manifest is explicitly requested
2182 ** by the --delta option.
2183 */
2184 if( !forceDelta
2185 && !db_get_boolean("seen-delta-manifest",0)
2186 && !db_get_boolean("forbid-delta-manifests",0)
2187 ){
2188 forceBaseline = 1;
2189 }
2190
2191 /*
2192 ** Autosync if autosync is enabled and this is not a private check-in.
@@ -2648,10 +2656,11 @@
2656 /* Commit */
2657 db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'");
2658 db_multi_exec("PRAGMA repository.application_id=252006673;");
2659 db_multi_exec("PRAGMA localdb.application_id=252006674;");
2660 if( dryRunFlag ){
2661 leaf_ambiguity_warning(nvid,nvid);
2662 db_end_transaction(1);
2663 exit(1);
2664 }
2665 db_end_transaction(0);
2666
@@ -2670,7 +2679,9 @@
2679 int nTries = db_get_int("autosync-tries",1);
2680 autosync_loop(syncFlags, nTries, 0);
2681 }
2682 if( count_nonbranch_children(vid)>1 ){
2683 fossil_print("**** warning: a fork has occurred *****\n");
2684 }else{
2685 leaf_ambiguity_warning(nvid,nvid);
2686 }
2687 }
2688
--- src/configure.c
+++ src/configure.c
@@ -147,10 +147,11 @@
147147
{ "parent-project-code", CONFIGSET_PROJ },
148148
{ "parent-project-name", CONFIGSET_PROJ },
149149
{ "hash-policy", CONFIGSET_PROJ },
150150
{ "comment-format", CONFIGSET_PROJ },
151151
{ "mimetypes", CONFIGSET_PROJ },
152
+ { "forbid-delta-manifests", CONFIGSET_PROJ },
152153
153154
#ifdef FOSSIL_ENABLE_LEGACY_MV_RM
154155
{ "mv-rm-files", CONFIGSET_PROJ },
155156
#endif
156157
157158
--- src/configure.c
+++ src/configure.c
@@ -147,10 +147,11 @@
147 { "parent-project-code", CONFIGSET_PROJ },
148 { "parent-project-name", CONFIGSET_PROJ },
149 { "hash-policy", CONFIGSET_PROJ },
150 { "comment-format", CONFIGSET_PROJ },
151 { "mimetypes", CONFIGSET_PROJ },
 
152
153 #ifdef FOSSIL_ENABLE_LEGACY_MV_RM
154 { "mv-rm-files", CONFIGSET_PROJ },
155 #endif
156
157
--- src/configure.c
+++ src/configure.c
@@ -147,10 +147,11 @@
147 { "parent-project-code", CONFIGSET_PROJ },
148 { "parent-project-name", CONFIGSET_PROJ },
149 { "hash-policy", CONFIGSET_PROJ },
150 { "comment-format", CONFIGSET_PROJ },
151 { "mimetypes", CONFIGSET_PROJ },
152 { "forbid-delta-manifests", CONFIGSET_PROJ },
153
154 #ifdef FOSSIL_ENABLE_LEGACY_MV_RM
155 { "mv-rm-files", CONFIGSET_PROJ },
156 #endif
157
158
+4
--- src/db.c
+++ src/db.c
@@ -3556,10 +3556,14 @@
35563556
/*
35573557
** SETTING: pgp-command width=40
35583558
** Command used to clear-sign manifests at check-in.
35593559
** Default value is "gpg --clearsign -o"
35603560
*/
3561
+/*
3562
+** SETTING: forbid-delta-manifests boolean default=off
3563
+** If enabled, new delta manifests are prohibited.
3564
+*/
35613565
/*
35623566
** SETTING: proxy width=32 default=off
35633567
** URL of the HTTP proxy. If undefined or "off" then
35643568
** the "http_proxy" environment variable is consulted.
35653569
** If the http_proxy environment variable is undefined
35663570
--- src/db.c
+++ src/db.c
@@ -3556,10 +3556,14 @@
3556 /*
3557 ** SETTING: pgp-command width=40
3558 ** Command used to clear-sign manifests at check-in.
3559 ** Default value is "gpg --clearsign -o"
3560 */
 
 
 
 
3561 /*
3562 ** SETTING: proxy width=32 default=off
3563 ** URL of the HTTP proxy. If undefined or "off" then
3564 ** the "http_proxy" environment variable is consulted.
3565 ** If the http_proxy environment variable is undefined
3566
--- src/db.c
+++ src/db.c
@@ -3556,10 +3556,14 @@
3556 /*
3557 ** SETTING: pgp-command width=40
3558 ** Command used to clear-sign manifests at check-in.
3559 ** Default value is "gpg --clearsign -o"
3560 */
3561 /*
3562 ** SETTING: forbid-delta-manifests boolean default=off
3563 ** If enabled, new delta manifests are prohibited.
3564 */
3565 /*
3566 ** SETTING: proxy width=32 default=off
3567 ** URL of the HTTP proxy. If undefined or "off" then
3568 ** the "http_proxy" environment variable is consulted.
3569 ** If the http_proxy environment variable is undefined
3570
--- src/default_css.txt
+++ src/default_css.txt
@@ -841,5 +841,24 @@
841841
}
842842
.accordion_panel {
843843
overflow: hidden;
844844
transition: max-height 0.25s ease-out;
845845
}
846
+#setup_skinedit_css_defaults {
847
+ max-width: 98%;
848
+ font-family: monospace;
849
+// These are for the UL-based implementation:
850
+ column-width: auto;
851
+ column-count: 2;
852
+ padding-top: 1em;
853
+}
854
+// These are for the alternate table-based skinedit CSS list:
855
+// #setup_skinedit_css_defaults > tbody > tr > td {
856
+// font-family: monospace;
857
+// white-space: pre-wrap;
858
+// border: 1px solid black;
859
+// vertical-align: top;
860
+// }
861
+// #setup_skinedit_css_defaults > tbody > tr > td:nth-of-type(2) > div {
862
+// max-width: 30em;
863
+// overflow: auto;
864
+// }
846865
--- src/default_css.txt
+++ src/default_css.txt
@@ -841,5 +841,24 @@
841 }
842 .accordion_panel {
843 overflow: hidden;
844 transition: max-height 0.25s ease-out;
845 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
846
--- src/default_css.txt
+++ src/default_css.txt
@@ -841,5 +841,24 @@
841 }
842 .accordion_panel {
843 overflow: hidden;
844 transition: max-height 0.25s ease-out;
845 }
846 #setup_skinedit_css_defaults {
847 max-width: 98%;
848 font-family: monospace;
849 // These are for the UL-based implementation:
850 column-width: auto;
851 column-count: 2;
852 padding-top: 1em;
853 }
854 // These are for the alternate table-based skinedit CSS list:
855 // #setup_skinedit_css_defaults > tbody > tr > td {
856 // font-family: monospace;
857 // white-space: pre-wrap;
858 // border: 1px solid black;
859 // vertical-align: top;
860 // }
861 // #setup_skinedit_css_defaults > tbody > tr > td:nth-of-type(2) > div {
862 // max-width: 30em;
863 // overflow: auto;
864 // }
865
+1 -1
--- src/diff.c
+++ src/diff.c
@@ -2,11 +2,11 @@
22
** Copyright (c) 2007 D. Richard Hipp
33
**
44
** This program is free software; you can redistribute it and/or
55
** modify it under the terms of the Simplified BSD License (also
66
** known as the "2-Clause License" or "FreeBSD License".)
7
-
7
+**
88
** This program is distributed in the hope that it will be useful,
99
** but without any warranty; without even the implied warranty of
1010
** merchantability or fitness for a particular purpose.
1111
**
1212
** Author contact information:
1313
--- src/diff.c
+++ src/diff.c
@@ -2,11 +2,11 @@
2 ** Copyright (c) 2007 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
--- src/diff.c
+++ src/diff.c
@@ -2,11 +2,11 @@
2 ** Copyright (c) 2007 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
+1 -1
--- src/file.c
+++ src/file.c
@@ -294,11 +294,11 @@
294294
**
295295
** On windows, this routine returns only PERM_REG.
296296
*/
297297
int file_perm(const char *zFilename, int eFType){
298298
#if !defined(_WIN32)
299
- if( !getStat(zFilename, RepoFILE) ){
299
+ if( !getStat(zFilename, eFType) ){
300300
if( S_ISREG(fx.fileStat.st_mode) && ((S_IXUSR)&fx.fileStat.st_mode)!=0 )
301301
return PERM_EXE;
302302
else if( db_allow_symlinks() && S_ISLNK(fx.fileStat.st_mode) )
303303
return PERM_LNK;
304304
}
305305
--- src/file.c
+++ src/file.c
@@ -294,11 +294,11 @@
294 **
295 ** On windows, this routine returns only PERM_REG.
296 */
297 int file_perm(const char *zFilename, int eFType){
298 #if !defined(_WIN32)
299 if( !getStat(zFilename, RepoFILE) ){
300 if( S_ISREG(fx.fileStat.st_mode) && ((S_IXUSR)&fx.fileStat.st_mode)!=0 )
301 return PERM_EXE;
302 else if( db_allow_symlinks() && S_ISLNK(fx.fileStat.st_mode) )
303 return PERM_LNK;
304 }
305
--- src/file.c
+++ src/file.c
@@ -294,11 +294,11 @@
294 **
295 ** On windows, this routine returns only PERM_REG.
296 */
297 int file_perm(const char *zFilename, int eFType){
298 #if !defined(_WIN32)
299 if( !getStat(zFilename, eFType) ){
300 if( S_ISREG(fx.fileStat.st_mode) && ((S_IXUSR)&fx.fileStat.st_mode)!=0 )
301 return PERM_EXE;
302 else if( db_allow_symlinks() && S_ISLNK(fx.fileStat.st_mode) )
303 return PERM_LNK;
304 }
305
+5 -1
--- src/fshell.c
+++ src/fshell.c
@@ -60,11 +60,15 @@
6060
pid_t childPid;
6161
char *zLine = 0;
6262
char *zPrompt = 0;
6363
fDebug = find_option("debug", 0, 0)!=0;
6464
db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
65
- zPrompt = mprintf("fossil (%z)> ", db_get("project-name","no repo"));
65
+ if(g.zRepositoryName!=0){
66
+ zPrompt = mprintf("fossil (%z)> ", db_get("project-name","unnamed"));
67
+ }else{
68
+ zPrompt = mprintf("fossil (no repo)> ");
69
+ }
6670
db_close(0);
6771
sqlite3_shutdown();
6872
linenoiseSetMultiLine(1);
6973
while( (free(zLine), zLine = linenoise(zPrompt)) ){
7074
/* Remember shell history within the current session */
7175
--- src/fshell.c
+++ src/fshell.c
@@ -60,11 +60,15 @@
60 pid_t childPid;
61 char *zLine = 0;
62 char *zPrompt = 0;
63 fDebug = find_option("debug", 0, 0)!=0;
64 db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
65 zPrompt = mprintf("fossil (%z)> ", db_get("project-name","no repo"));
 
 
 
 
66 db_close(0);
67 sqlite3_shutdown();
68 linenoiseSetMultiLine(1);
69 while( (free(zLine), zLine = linenoise(zPrompt)) ){
70 /* Remember shell history within the current session */
71
--- src/fshell.c
+++ src/fshell.c
@@ -60,11 +60,15 @@
60 pid_t childPid;
61 char *zLine = 0;
62 char *zPrompt = 0;
63 fDebug = find_option("debug", 0, 0)!=0;
64 db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0);
65 if(g.zRepositoryName!=0){
66 zPrompt = mprintf("fossil (%z)> ", db_get("project-name","unnamed"));
67 }else{
68 zPrompt = mprintf("fossil (no repo)> ");
69 }
70 db_close(0);
71 sqlite3_shutdown();
72 linenoiseSetMultiLine(1);
73 while( (free(zLine), zLine = linenoise(zPrompt)) ){
74 /* Remember shell history within the current session */
75
+3 -1
--- src/graph.c
+++ src/graph.c
@@ -534,17 +534,19 @@
534534
pParent->idxTop = pRow->idxTop;
535535
}
536536
}
537537
538538
if( tmFlags & TIMELINE_FILLGAPS ){
539
- /* If a node has no pChild and there is a node higher up in the graph
539
+ /* If a node has no pChild in the graph
540
+ ** and there is a node higher up in the graph
540541
** that is in the same branch and has no in-graph parent, then
541542
** make the lower node a step-child of the upper node. This will
542543
** be represented on the graph by a thick dotted line without an arrowhead.
543544
*/
544545
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
545546
if( pRow->pChild ) continue;
547
+ if( pRow->isLeaf ) continue;
546548
for(pLoop=pRow->pPrev; pLoop; pLoop=pLoop->pPrev){
547549
if( pLoop->nParent>0
548550
&& pLoop->zBranch==pRow->zBranch
549551
&& hashFind(p,pLoop->aParent[0])==0
550552
){
551553
--- src/graph.c
+++ src/graph.c
@@ -534,17 +534,19 @@
534 pParent->idxTop = pRow->idxTop;
535 }
536 }
537
538 if( tmFlags & TIMELINE_FILLGAPS ){
539 /* If a node has no pChild and there is a node higher up in the graph
 
540 ** that is in the same branch and has no in-graph parent, then
541 ** make the lower node a step-child of the upper node. This will
542 ** be represented on the graph by a thick dotted line without an arrowhead.
543 */
544 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
545 if( pRow->pChild ) continue;
 
546 for(pLoop=pRow->pPrev; pLoop; pLoop=pLoop->pPrev){
547 if( pLoop->nParent>0
548 && pLoop->zBranch==pRow->zBranch
549 && hashFind(p,pLoop->aParent[0])==0
550 ){
551
--- src/graph.c
+++ src/graph.c
@@ -534,17 +534,19 @@
534 pParent->idxTop = pRow->idxTop;
535 }
536 }
537
538 if( tmFlags & TIMELINE_FILLGAPS ){
539 /* If a node has no pChild in the graph
540 ** and there is a node higher up in the graph
541 ** that is in the same branch and has no in-graph parent, then
542 ** make the lower node a step-child of the upper node. This will
543 ** be represented on the graph by a thick dotted line without an arrowhead.
544 */
545 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
546 if( pRow->pChild ) continue;
547 if( pRow->isLeaf ) continue;
548 for(pLoop=pRow->pPrev; pLoop; pLoop=pLoop->pPrev){
549 if( pLoop->nParent>0
550 && pLoop->zBranch==pRow->zBranch
551 && hashFind(p,pLoop->aParent[0])==0
552 ){
553
+228 -121
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -30,10 +30,11 @@
3030
#ifdef FOSSIL_ENABLE_SSL
3131
3232
#include <openssl/bio.h>
3333
#include <openssl/ssl.h>
3434
#include <openssl/err.h>
35
+#include <openssl/x509.h>
3536
3637
#include "http_ssl.h"
3738
#include <assert.h>
3839
#include <sys/types.h>
3940
@@ -45,11 +46,15 @@
4546
static int sslIsInit = 0; /* True after global initialization */
4647
static BIO *iBio = 0; /* OpenSSL I/O abstraction */
4748
static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */
4849
static SSL_CTX *sslCtx; /* SSL context */
4950
static SSL *ssl;
50
-
51
+static struct { /* Accept this SSL cert for this session only */
52
+ char *zHost; /* Subject or host name */
53
+ char *zHash; /* SHA2-256 hash of the cert */
54
+} sException;
55
+static int sslNoCertVerify = 0; /* Do not verify SSL certs */
5156
5257
/*
5358
** Clear the SSL error message
5459
*/
5560
static void ssl_clear_errmsg(void){
@@ -184,11 +189,12 @@
184189
Blob snd, reply;
185190
int done=0,end=0;
186191
blob_zero(&snd);
187192
blob_appendf(&snd, "CONNECT %s:%d HTTP/1.1\r\n", pUrlData->hostname,
188193
pUrlData->proxyOrigPort);
189
- blob_appendf(&snd, "Host: %s:%d\r\n", pUrlData->hostname, pUrlData->proxyOrigPort);
194
+ blob_appendf(&snd, "Host: %s:%d\r\n",
195
+ pUrlData->hostname, pUrlData->proxyOrigPort);
190196
if( pUrlData->proxyAuth ){
191197
blob_appendf(&snd, "Proxy-Authorization: %s\r\n", pUrlData->proxyAuth);
192198
}
193199
blob_append(&snd, "Proxy-Connection: keep-alive\r\n", -1);
194200
blob_appendf(&snd, "User-Agent: %s\r\n", get_user_agent());
@@ -222,46 +228,45 @@
222228
}while(!done);
223229
sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc);
224230
blob_reset(&reply);
225231
return rc;
226232
}
233
+
234
+/*
235
+** Invoke this routine to disable SSL cert verification. After
236
+** this call is made, any SSL cert that the server provides will
237
+** be accepted. Communication will still be encrypted, but the
238
+** client has no way of knowing whether it is talking to the
239
+** real server or a man-in-the-middle imposter.
240
+*/
241
+void ssl_disable_cert_verification(void){
242
+ sslNoCertVerify = 1;
243
+}
227244
228245
/*
229246
** Open an SSL connection. The identify of the server is determined
230247
** as follows:
231248
**
232
-** g.url.name Name of the server. Ex: www.fossil-scm.org
249
+** pUrlData->name Name of the server. Ex: www.fossil-scm.org
250
+** g.url.name Name of the proxy server, if proxying.
233251
** pUrlData->port TCP/IP port to use. Ex: 80
234252
**
235253
** Return the number of errors.
236254
*/
237255
int ssl_open(UrlData *pUrlData){
238256
X509 *cert;
239
- int hasSavedCertificate = 0;
240
- int trusted = 0;
241
- unsigned long e;
242257
243258
ssl_global_init();
244
-
245
- /* Get certificate for current server from global config and
246
- * (if we have it in config) add it to certificate store.
247
- */
248
- cert = ssl_get_certificate(pUrlData, &trusted);
249
- if ( cert!=NULL ){
250
- X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert);
251
- X509_free(cert);
252
- hasSavedCertificate = 1;
253
- }
254
-
255259
if( pUrlData->useProxy ){
256260
int rc;
257261
char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port);
258262
BIO *sBio = BIO_new_connect(connStr);
259263
free(connStr);
260264
if( BIO_do_connect(sBio)<=0 ){
261265
ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)",
262
- pUrlData->name, pUrlData->port, ERR_reason_error_string(ERR_get_error()));
266
+ pUrlData->name, pUrlData->port,
267
+ ERR_reason_error_string(ERR_get_error()));
263268
ssl_close();
264269
return 1;
265270
}
266271
rc = establish_proxy_tunnel(pUrlData, sBio);
267272
if( rc<200||rc>299 ){
@@ -282,11 +287,13 @@
282287
return 1;
283288
}
284289
BIO_get_ssl(iBio, &ssl);
285290
286291
#if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT)
287
- if( !SSL_set_tlsext_host_name(ssl, (pUrlData->useProxy?pUrlData->hostname:pUrlData->name)) ){
292
+ if( !SSL_set_tlsext_host_name(ssl,
293
+ (pUrlData->useProxy?pUrlData->hostname:pUrlData->name))
294
+ ){
288295
fossil_warning("WARNING: failed to set server name indication (SNI), "
289296
"continuing without it.\n");
290297
}
291298
#endif
292299
@@ -296,11 +303,12 @@
296303
char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port);
297304
BIO_set_conn_hostname(iBio, connStr);
298305
free(connStr);
299306
if( BIO_do_connect(iBio)<=0 ){
300307
ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)",
301
- pUrlData->name, pUrlData->port, ERR_reason_error_string(ERR_get_error()));
308
+ pUrlData->name, pUrlData->port,
309
+ ERR_reason_error_string(ERR_get_error()));
302310
ssl_close();
303311
return 1;
304312
}
305313
}
306314
@@ -319,81 +327,78 @@
319327
ssl_set_errmsg("No SSL certificate was presented by the peer");
320328
ssl_close();
321329
return 1;
322330
}
323331
324
- if( trusted<=0 && (e = SSL_get_verify_result(ssl)) != X509_V_OK ){
332
+ if( !sslNoCertVerify && SSL_get_verify_result(ssl)!=X509_V_OK ){
325333
char *desc, *prompt;
326
- const char *warning = "";
327334
Blob ans;
328335
char cReply;
329336
BIO *mem;
330337
unsigned char md[32];
331
- unsigned int mdLength = 31;
332
-
333
- mem = BIO_new(BIO_s_mem());
334
- X509_NAME_print_ex(mem, X509_get_subject_name(cert), 2, XN_FLAG_MULTILINE);
335
- BIO_puts(mem, "\n\nIssued By:\n\n");
336
- X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 2, XN_FLAG_MULTILINE);
337
- BIO_puts(mem, "\n\nSHA1 Fingerprint:\n\n ");
338
- if(X509_digest(cert, EVP_sha1(), md, &mdLength)){
339
- int j;
340
- for( j = 0; j < mdLength; ++j ) {
341
- BIO_printf(mem, " %02x", md[j]);
342
- }
343
- }
344
- BIO_write(mem, "", 1); /* nul-terminate mem buffer */
345
- BIO_get_mem_data(mem, &desc);
346
-
347
- if( hasSavedCertificate ){
348
- warning = "WARNING: Certificate doesn't match the "
349
- "saved certificate for this host!";
350
- }
351
- prompt = mprintf("\nSSL verification failed: %s\n"
352
- "Certificate received: \n\n%s\n\n%s\n"
353
- "Either:\n"
354
- " * verify the certificate is correct using the "
355
- "SHA1 fingerprint above\n"
356
- " * use the global ssl-ca-location setting to specify your CA root\n"
357
- " certificates list\n\n"
358
- "If you are not expecting this message, answer no and "
359
- "contact your server\nadministrator.\n\n"
360
- "Accept certificate for host %s (a=always/y/N)? ",
361
- X509_verify_cert_error_string(e), desc, warning,
362
- pUrlData->useProxy?pUrlData->hostname:pUrlData->name);
363
- BIO_free(mem);
364
-
365
- prompt_user(prompt, &ans);
366
- free(prompt);
367
- cReply = blob_str(&ans)[0];
368
- blob_reset(&ans);
369
- if( cReply!='y' && cReply!='Y' && cReply!='a' && cReply!='A') {
370
- X509_free(cert);
371
- ssl_set_errmsg("SSL certificate declined");
372
- ssl_close();
373
- return 1;
374
- }
375
- if( cReply=='a' || cReply=='A') {
376
- if ( trusted==0 ){
377
- prompt_user("\nSave this certificate as fully trusted (a=always/N)? ",
378
- &ans);
379
- cReply = blob_str(&ans)[0];
380
- trusted = ( cReply=='a' || cReply=='A' );
381
- blob_reset(&ans);
382
- }
383
- ssl_save_certificate(pUrlData, cert, trusted);
338
+ char zHash[32*2+1];
339
+ unsigned int mdLength = (int)sizeof(md);
340
+
341
+ memset(md, 0, sizeof(md));
342
+ zHash[0] = 0;
343
+ if( X509_digest(cert, EVP_sha256(), md, &mdLength) ){
344
+ int j;
345
+ for(j=0; j<mdLength && j*2+1<sizeof(zHash); ++j){
346
+ zHash[j*2] = "0123456789abcdef"[md[j]>>4];
347
+ zHash[j*2+1] = "0123456789abcdef"[md[j]&0xf];
348
+ }
349
+ zHash[j*2] = 0;
350
+ }
351
+
352
+ if( ssl_certificate_exception_exists(pUrlData, zHash) ){
353
+ /* Ignore the failure because an exception exists */
354
+ ssl_one_time_exception(pUrlData, zHash);
355
+ }else{
356
+ /* Tell the user about the failure and ask what to do */
357
+ mem = BIO_new(BIO_s_mem());
358
+ BIO_puts(mem, " subject: ");
359
+ X509_NAME_print_ex(mem, X509_get_subject_name(cert), 0, XN_FLAG_ONELINE);
360
+ BIO_puts(mem, "\n issuer: ");
361
+ X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE);
362
+ BIO_printf(mem, "\n sha256: %s", zHash);
363
+ BIO_get_mem_data(mem, &desc);
364
+
365
+ prompt = mprintf("Unable to verify SSL cert from %s\n%s\n"
366
+ "accept this cert and continue (y/N)? ",
367
+ pUrlData->name, desc);
368
+ BIO_free(mem);
369
+
370
+ prompt_user(prompt, &ans);
371
+ free(prompt);
372
+ cReply = blob_str(&ans)[0];
373
+ blob_reset(&ans);
374
+ if( cReply!='y' && cReply!='Y' ){
375
+ X509_free(cert);
376
+ ssl_set_errmsg("SSL cert declined");
377
+ ssl_close();
378
+ return 1;
379
+ }
380
+ ssl_one_time_exception(pUrlData, zHash);
381
+ prompt_user("remember this exception (y/N)? ", &ans);
382
+ cReply = blob_str(&ans)[0];
383
+ if( cReply=='y' || cReply=='Y') {
384
+ ssl_remember_certificate_exception(pUrlData, zHash);
385
+ }
386
+ blob_reset(&ans);
384387
}
385388
}
386389
387390
/* Set the Global.zIpAddr variable to the server we are talking to.
388391
** This is used to populate the ipaddr column of the rcvfrom table,
389392
** if any files are received from the server.
390393
*/
391394
{
392
- /* As soon as libressl implements BIO_ADDR_hostname_string/BIO_get_conn_address.
393
- * check here for the correct LIBRESSL_VERSION_NUMBER too. For now: disable */
394
- #if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000L \
395
+ /* As soon as libressl implements
396
+ ** BIO_ADDR_hostname_string/BIO_get_conn_address.
397
+ ** check here for the correct LIBRESSL_VERSION_NUMBER too. For now: disable
398
+ */
399
+#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000L \
395400
&& !defined(LIBRESSL_VERSION_NUMBER)
396401
char *ip = BIO_ADDR_hostname_string(BIO_get_conn_address(iBio),1);
397402
g.zIpAddr = mprintf("%s", ip);
398403
OPENSSL_free(ip);
399404
#else
@@ -407,58 +412,53 @@
407412
X509_free(cert);
408413
return 0;
409414
}
410415
411416
/*
412
-** Save certificate to global config.
417
+** Remember that the cert with the given hash is a acceptable for
418
+** use with pUrlData->name.
419
+*/
420
+LOCAL void ssl_remember_certificate_exception(
421
+ UrlData *pUrlData,
422
+ const char *zHash
423
+){
424
+ char *zName = mprintf("cert:%s", pUrlData->name);
425
+ db_set(zName, zHash, 1);
426
+ fossil_free(zName);
427
+}
428
+
429
+/*
430
+** Return true if the there exists a certificate exception for
431
+** pUrlData->name that matches the hash.
413432
*/
414
-void ssl_save_certificate(UrlData *pUrlData, X509 *cert, int trusted){
415
- BIO *mem;
416
- char *zCert, *zHost;
417
-
418
- mem = BIO_new(BIO_s_mem());
419
- PEM_write_bio_X509(mem, cert);
420
- BIO_write(mem, "", 1); /* nul-terminate mem buffer */
421
- BIO_get_mem_data(mem, &zCert);
422
- zHost = mprintf("cert:%s", pUrlData->useProxy?pUrlData->hostname:pUrlData->name);
423
- db_set(zHost, zCert, 1);
424
- free(zHost);
425
- zHost = mprintf("trusted:%s", pUrlData->useProxy?pUrlData->hostname:pUrlData->name);
426
- db_set_int(zHost, trusted, 1);
427
- free(zHost);
428
- BIO_free(mem);
433
+LOCAL int ssl_certificate_exception_exists(
434
+ UrlData *pUrlData,
435
+ const char *zHash
436
+){
437
+ char *zName, *zValue;
438
+ if( fossil_strcmp(sException.zHost,pUrlData->name)==0
439
+ && fossil_strcmp(sException.zHash,zHash)==0
440
+ ){
441
+ return 1;
442
+ }
443
+ zName = mprintf("cert:%s", pUrlData->name);
444
+ zValue = db_get(zName,0);
445
+ fossil_free(zName);
446
+ return zValue!=0 && strcmp(zHash,zValue)==0;
429447
}
430448
431449
/*
432
-** Get certificate for pUrlData->urlName from global config.
433
-** Return NULL if no certificate found.
450
+** Remember zHash as an acceptable certificate for this session only.
434451
*/
435
-X509 *ssl_get_certificate(UrlData *pUrlData, int *pTrusted){
436
- char *zHost, *zCert;
437
- BIO *mem;
438
- X509 *cert;
439
-
440
- zHost = mprintf("cert:%s",
441
- pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
442
- zCert = db_get(zHost, NULL);
443
- free(zHost);
444
- if ( zCert==NULL )
445
- return NULL;
446
-
447
- if ( pTrusted!=0 ){
448
- zHost = mprintf("trusted:%s",
449
- pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
450
- *pTrusted = db_get_int(zHost, 0);
451
- free(zHost);
452
- }
453
-
454
- mem = BIO_new(BIO_s_mem());
455
- BIO_puts(mem, zCert);
456
- cert = PEM_read_bio_X509(mem, NULL, 0, NULL);
457
- free(zCert);
458
- BIO_free(mem);
459
- return cert;
452
+LOCAL void ssl_one_time_exception(
453
+ UrlData *pUrlData,
454
+ const char *zHash
455
+){
456
+ fossil_free(sException.zHost);
457
+ sException.zHost = fossil_strdup(pUrlData->name);
458
+ fossil_free(sException.zHash);
459
+ sException.zHash = fossil_strdup(zHash);
460460
}
461461
462462
/*
463463
** Send content out over the SSL connection.
464464
*/
@@ -498,5 +498,112 @@
498498
}
499499
return total;
500500
}
501501
502502
#endif /* FOSSIL_ENABLE_SSL */
503
+
504
+/*
505
+** COMMAND: tls-config*
506
+**
507
+** Usage: %fossil tls-config [SUBCOMMAND] [OPTIONS...] [ARGS...]
508
+**
509
+** This command is used to view or modify the TLS (Transport Layer
510
+** Security) configuration for Fossil. TLS (formerly SSL) is the
511
+** encryption technology used for secure HTTPS transport.
512
+**
513
+** Sub-commands:
514
+**
515
+** show Show the TLS configuration
516
+**
517
+** remove-exception DOMAIN... Remove TLS cert exceptions
518
+** for the domains listed. Or if
519
+** the --all option is specified,
520
+** remove all TLS cert exceptions.
521
+*/
522
+void test_tlsconfig_info(void){
523
+ const char *zCmd;
524
+ size_t nCmd;
525
+ int nHit = 0;
526
+#if !defined(FOSSIL_ENABLE_SSL)
527
+ fossil_print("TLS disabled in this build\n");
528
+#else
529
+ db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
530
+ db_open_config(1,0);
531
+ zCmd = g.argc>=3 ? g.argv[2] : "show";
532
+ nCmd = strlen(zCmd);
533
+ if( strncmp("show",zCmd,nCmd)==0 ){
534
+ const char *zName, *zValue;
535
+ size_t nName;
536
+ Stmt q;
537
+ fossil_print("OpenSSL-version: %s\n", SSLeay_version(SSLEAY_VERSION));
538
+ fossil_print("OpenSSL-cert-file: %s\n", X509_get_default_cert_file());
539
+ fossil_print("OpenSSL-cert-dir: %s\n", X509_get_default_cert_dir());
540
+ zName = X509_get_default_cert_file_env();
541
+ zValue = fossil_getenv(zName);
542
+ if( zValue==0 ) zValue = "";
543
+ nName = strlen(zName);
544
+ fossil_print("%s:%.*s%s\n", zName, 19-nName, "", zValue);
545
+ zName = X509_get_default_cert_dir_env();
546
+ zValue = fossil_getenv(zName);
547
+ if( zValue==0 ) zValue = "";
548
+ nName = strlen(zName);
549
+ fossil_print("%s:%.*s%s\n", zName, 19-nName, "", zValue);
550
+ nHit++;
551
+ fossil_print("ssl-ca-location: %s\n", db_get("ssl-ca-location",""));
552
+ fossil_print("ssl-identity: %s\n", db_get("ssl-identity",""));
553
+ db_prepare(&q,
554
+ "SELECT name FROM global_config"
555
+ " WHERE name GLOB 'cert:*'"
556
+ "UNION ALL "
557
+ "SELECT name FROM config"
558
+ " WHERE name GLOB 'cert:*'"
559
+ " ORDER BY name"
560
+ );
561
+ while( db_step(&q)==SQLITE_ROW ){
562
+ fossil_print("exception: %s\n", db_column_text(&q,0)+5);
563
+ }
564
+ db_finalize(&q);
565
+ }else
566
+ if( strncmp("remove-exception",zCmd,nCmd)==0 ){
567
+ int i;
568
+ Blob sql;
569
+ char *zSep = "(";
570
+ db_begin_transaction();
571
+ blob_init(&sql, 0, 0);
572
+ if( g.argc==4 && find_option("all",0,0)!=0 ){
573
+ blob_append_sql(&sql,
574
+ "DELETE FROM global_config WHERE name GLOB 'cert:*';\n"
575
+ "DELETE FROM global_config WHERE name GLOB 'trusted:*';\n"
576
+ "DELETE FROM config WHERE name GLOB 'cert:*';\n"
577
+ "DELETE FROM config WHERE name GLOB 'trusted:*';\n"
578
+ );
579
+ }else{
580
+ if( g.argc<4 ){
581
+ usage("remove-exception DOMAIN-NAME ...");
582
+ }
583
+ blob_append_sql(&sql,"DELETE FROM global_config WHERE name IN ");
584
+ for(i=3; i<g.argc; i++){
585
+ blob_append_sql(&sql,"%s'cert:%q','trust:%q'",
586
+ zSep/*safe-for-%s*/, g.argv[i], g.argv[i]);
587
+ zSep = ",";
588
+ }
589
+ blob_append_sql(&sql,");\n");
590
+ zSep = "(";
591
+ blob_append_sql(&sql,"DELETE FROM config WHERE name IN ");
592
+ for(i=3; i<g.argc; i++){
593
+ blob_append_sql(&sql,"%s'cert:%q','trusted:%q'",
594
+ zSep/*safe-for-%s*/, g.argv[i], g.argv[i]);
595
+ zSep = ",";
596
+ }
597
+ blob_append_sql(&sql,");");
598
+ }
599
+ db_exec_sql(blob_str(&sql));
600
+ db_commit_transaction();
601
+ blob_reset(&sql);
602
+ }else
603
+ /*default*/{
604
+ fossil_fatal("unknown sub-command \"%s\".\nshould be one of:"
605
+ " remove-exception show",
606
+ zCmd);
607
+ }
608
+#endif
609
+}
503610
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -30,10 +30,11 @@
30 #ifdef FOSSIL_ENABLE_SSL
31
32 #include <openssl/bio.h>
33 #include <openssl/ssl.h>
34 #include <openssl/err.h>
 
35
36 #include "http_ssl.h"
37 #include <assert.h>
38 #include <sys/types.h>
39
@@ -45,11 +46,15 @@
45 static int sslIsInit = 0; /* True after global initialization */
46 static BIO *iBio = 0; /* OpenSSL I/O abstraction */
47 static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */
48 static SSL_CTX *sslCtx; /* SSL context */
49 static SSL *ssl;
50
 
 
 
 
51
52 /*
53 ** Clear the SSL error message
54 */
55 static void ssl_clear_errmsg(void){
@@ -184,11 +189,12 @@
184 Blob snd, reply;
185 int done=0,end=0;
186 blob_zero(&snd);
187 blob_appendf(&snd, "CONNECT %s:%d HTTP/1.1\r\n", pUrlData->hostname,
188 pUrlData->proxyOrigPort);
189 blob_appendf(&snd, "Host: %s:%d\r\n", pUrlData->hostname, pUrlData->proxyOrigPort);
 
190 if( pUrlData->proxyAuth ){
191 blob_appendf(&snd, "Proxy-Authorization: %s\r\n", pUrlData->proxyAuth);
192 }
193 blob_append(&snd, "Proxy-Connection: keep-alive\r\n", -1);
194 blob_appendf(&snd, "User-Agent: %s\r\n", get_user_agent());
@@ -222,46 +228,45 @@
222 }while(!done);
223 sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc);
224 blob_reset(&reply);
225 return rc;
226 }
 
 
 
 
 
 
 
 
 
 
 
227
228 /*
229 ** Open an SSL connection. The identify of the server is determined
230 ** as follows:
231 **
232 ** g.url.name Name of the server. Ex: www.fossil-scm.org
 
233 ** pUrlData->port TCP/IP port to use. Ex: 80
234 **
235 ** Return the number of errors.
236 */
237 int ssl_open(UrlData *pUrlData){
238 X509 *cert;
239 int hasSavedCertificate = 0;
240 int trusted = 0;
241 unsigned long e;
242
243 ssl_global_init();
244
245 /* Get certificate for current server from global config and
246 * (if we have it in config) add it to certificate store.
247 */
248 cert = ssl_get_certificate(pUrlData, &trusted);
249 if ( cert!=NULL ){
250 X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert);
251 X509_free(cert);
252 hasSavedCertificate = 1;
253 }
254
255 if( pUrlData->useProxy ){
256 int rc;
257 char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port);
258 BIO *sBio = BIO_new_connect(connStr);
259 free(connStr);
260 if( BIO_do_connect(sBio)<=0 ){
261 ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)",
262 pUrlData->name, pUrlData->port, ERR_reason_error_string(ERR_get_error()));
 
263 ssl_close();
264 return 1;
265 }
266 rc = establish_proxy_tunnel(pUrlData, sBio);
267 if( rc<200||rc>299 ){
@@ -282,11 +287,13 @@
282 return 1;
283 }
284 BIO_get_ssl(iBio, &ssl);
285
286 #if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT)
287 if( !SSL_set_tlsext_host_name(ssl, (pUrlData->useProxy?pUrlData->hostname:pUrlData->name)) ){
 
 
288 fossil_warning("WARNING: failed to set server name indication (SNI), "
289 "continuing without it.\n");
290 }
291 #endif
292
@@ -296,11 +303,12 @@
296 char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port);
297 BIO_set_conn_hostname(iBio, connStr);
298 free(connStr);
299 if( BIO_do_connect(iBio)<=0 ){
300 ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)",
301 pUrlData->name, pUrlData->port, ERR_reason_error_string(ERR_get_error()));
 
302 ssl_close();
303 return 1;
304 }
305 }
306
@@ -319,81 +327,78 @@
319 ssl_set_errmsg("No SSL certificate was presented by the peer");
320 ssl_close();
321 return 1;
322 }
323
324 if( trusted<=0 && (e = SSL_get_verify_result(ssl)) != X509_V_OK ){
325 char *desc, *prompt;
326 const char *warning = "";
327 Blob ans;
328 char cReply;
329 BIO *mem;
330 unsigned char md[32];
331 unsigned int mdLength = 31;
332
333 mem = BIO_new(BIO_s_mem());
334 X509_NAME_print_ex(mem, X509_get_subject_name(cert), 2, XN_FLAG_MULTILINE);
335 BIO_puts(mem, "\n\nIssued By:\n\n");
336 X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 2, XN_FLAG_MULTILINE);
337 BIO_puts(mem, "\n\nSHA1 Fingerprint:\n\n ");
338 if(X509_digest(cert, EVP_sha1(), md, &mdLength)){
339 int j;
340 for( j = 0; j < mdLength; ++j ) {
341 BIO_printf(mem, " %02x", md[j]);
342 }
343 }
344 BIO_write(mem, "", 1); /* nul-terminate mem buffer */
345 BIO_get_mem_data(mem, &desc);
346
347 if( hasSavedCertificate ){
348 warning = "WARNING: Certificate doesn't match the "
349 "saved certificate for this host!";
350 }
351 prompt = mprintf("\nSSL verification failed: %s\n"
352 "Certificate received: \n\n%s\n\n%s\n"
353 "Either:\n"
354 " * verify the certificate is correct using the "
355 "SHA1 fingerprint above\n"
356 " * use the global ssl-ca-location setting to specify your CA root\n"
357 " certificates list\n\n"
358 "If you are not expecting this message, answer no and "
359 "contact your server\nadministrator.\n\n"
360 "Accept certificate for host %s (a=always/y/N)? ",
361 X509_verify_cert_error_string(e), desc, warning,
362 pUrlData->useProxy?pUrlData->hostname:pUrlData->name);
363 BIO_free(mem);
364
365 prompt_user(prompt, &ans);
366 free(prompt);
367 cReply = blob_str(&ans)[0];
368 blob_reset(&ans);
369 if( cReply!='y' && cReply!='Y' && cReply!='a' && cReply!='A') {
370 X509_free(cert);
371 ssl_set_errmsg("SSL certificate declined");
372 ssl_close();
373 return 1;
374 }
375 if( cReply=='a' || cReply=='A') {
376 if ( trusted==0 ){
377 prompt_user("\nSave this certificate as fully trusted (a=always/N)? ",
378 &ans);
379 cReply = blob_str(&ans)[0];
380 trusted = ( cReply=='a' || cReply=='A' );
381 blob_reset(&ans);
382 }
383 ssl_save_certificate(pUrlData, cert, trusted);
384 }
385 }
386
387 /* Set the Global.zIpAddr variable to the server we are talking to.
388 ** This is used to populate the ipaddr column of the rcvfrom table,
389 ** if any files are received from the server.
390 */
391 {
392 /* As soon as libressl implements BIO_ADDR_hostname_string/BIO_get_conn_address.
393 * check here for the correct LIBRESSL_VERSION_NUMBER too. For now: disable */
394 #if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000L \
 
 
395 && !defined(LIBRESSL_VERSION_NUMBER)
396 char *ip = BIO_ADDR_hostname_string(BIO_get_conn_address(iBio),1);
397 g.zIpAddr = mprintf("%s", ip);
398 OPENSSL_free(ip);
399 #else
@@ -407,58 +412,53 @@
407 X509_free(cert);
408 return 0;
409 }
410
411 /*
412 ** Save certificate to global config.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413 */
414 void ssl_save_certificate(UrlData *pUrlData, X509 *cert, int trusted){
415 BIO *mem;
416 char *zCert, *zHost;
417
418 mem = BIO_new(BIO_s_mem());
419 PEM_write_bio_X509(mem, cert);
420 BIO_write(mem, "", 1); /* nul-terminate mem buffer */
421 BIO_get_mem_data(mem, &zCert);
422 zHost = mprintf("cert:%s", pUrlData->useProxy?pUrlData->hostname:pUrlData->name);
423 db_set(zHost, zCert, 1);
424 free(zHost);
425 zHost = mprintf("trusted:%s", pUrlData->useProxy?pUrlData->hostname:pUrlData->name);
426 db_set_int(zHost, trusted, 1);
427 free(zHost);
428 BIO_free(mem);
429 }
430
431 /*
432 ** Get certificate for pUrlData->urlName from global config.
433 ** Return NULL if no certificate found.
434 */
435 X509 *ssl_get_certificate(UrlData *pUrlData, int *pTrusted){
436 char *zHost, *zCert;
437 BIO *mem;
438 X509 *cert;
439
440 zHost = mprintf("cert:%s",
441 pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
442 zCert = db_get(zHost, NULL);
443 free(zHost);
444 if ( zCert==NULL )
445 return NULL;
446
447 if ( pTrusted!=0 ){
448 zHost = mprintf("trusted:%s",
449 pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
450 *pTrusted = db_get_int(zHost, 0);
451 free(zHost);
452 }
453
454 mem = BIO_new(BIO_s_mem());
455 BIO_puts(mem, zCert);
456 cert = PEM_read_bio_X509(mem, NULL, 0, NULL);
457 free(zCert);
458 BIO_free(mem);
459 return cert;
460 }
461
462 /*
463 ** Send content out over the SSL connection.
464 */
@@ -498,5 +498,112 @@
498 }
499 return total;
500 }
501
502 #endif /* FOSSIL_ENABLE_SSL */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -30,10 +30,11 @@
30 #ifdef FOSSIL_ENABLE_SSL
31
32 #include <openssl/bio.h>
33 #include <openssl/ssl.h>
34 #include <openssl/err.h>
35 #include <openssl/x509.h>
36
37 #include "http_ssl.h"
38 #include <assert.h>
39 #include <sys/types.h>
40
@@ -45,11 +46,15 @@
46 static int sslIsInit = 0; /* True after global initialization */
47 static BIO *iBio = 0; /* OpenSSL I/O abstraction */
48 static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */
49 static SSL_CTX *sslCtx; /* SSL context */
50 static SSL *ssl;
51 static struct { /* Accept this SSL cert for this session only */
52 char *zHost; /* Subject or host name */
53 char *zHash; /* SHA2-256 hash of the cert */
54 } sException;
55 static int sslNoCertVerify = 0; /* Do not verify SSL certs */
56
57 /*
58 ** Clear the SSL error message
59 */
60 static void ssl_clear_errmsg(void){
@@ -184,11 +189,12 @@
189 Blob snd, reply;
190 int done=0,end=0;
191 blob_zero(&snd);
192 blob_appendf(&snd, "CONNECT %s:%d HTTP/1.1\r\n", pUrlData->hostname,
193 pUrlData->proxyOrigPort);
194 blob_appendf(&snd, "Host: %s:%d\r\n",
195 pUrlData->hostname, pUrlData->proxyOrigPort);
196 if( pUrlData->proxyAuth ){
197 blob_appendf(&snd, "Proxy-Authorization: %s\r\n", pUrlData->proxyAuth);
198 }
199 blob_append(&snd, "Proxy-Connection: keep-alive\r\n", -1);
200 blob_appendf(&snd, "User-Agent: %s\r\n", get_user_agent());
@@ -222,46 +228,45 @@
228 }while(!done);
229 sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc);
230 blob_reset(&reply);
231 return rc;
232 }
233
234 /*
235 ** Invoke this routine to disable SSL cert verification. After
236 ** this call is made, any SSL cert that the server provides will
237 ** be accepted. Communication will still be encrypted, but the
238 ** client has no way of knowing whether it is talking to the
239 ** real server or a man-in-the-middle imposter.
240 */
241 void ssl_disable_cert_verification(void){
242 sslNoCertVerify = 1;
243 }
244
245 /*
246 ** Open an SSL connection. The identify of the server is determined
247 ** as follows:
248 **
249 ** pUrlData->name Name of the server. Ex: www.fossil-scm.org
250 ** g.url.name Name of the proxy server, if proxying.
251 ** pUrlData->port TCP/IP port to use. Ex: 80
252 **
253 ** Return the number of errors.
254 */
255 int ssl_open(UrlData *pUrlData){
256 X509 *cert;
 
 
 
257
258 ssl_global_init();
 
 
 
 
 
 
 
 
 
 
 
259 if( pUrlData->useProxy ){
260 int rc;
261 char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port);
262 BIO *sBio = BIO_new_connect(connStr);
263 free(connStr);
264 if( BIO_do_connect(sBio)<=0 ){
265 ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)",
266 pUrlData->name, pUrlData->port,
267 ERR_reason_error_string(ERR_get_error()));
268 ssl_close();
269 return 1;
270 }
271 rc = establish_proxy_tunnel(pUrlData, sBio);
272 if( rc<200||rc>299 ){
@@ -282,11 +287,13 @@
287 return 1;
288 }
289 BIO_get_ssl(iBio, &ssl);
290
291 #if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT)
292 if( !SSL_set_tlsext_host_name(ssl,
293 (pUrlData->useProxy?pUrlData->hostname:pUrlData->name))
294 ){
295 fossil_warning("WARNING: failed to set server name indication (SNI), "
296 "continuing without it.\n");
297 }
298 #endif
299
@@ -296,11 +303,12 @@
303 char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port);
304 BIO_set_conn_hostname(iBio, connStr);
305 free(connStr);
306 if( BIO_do_connect(iBio)<=0 ){
307 ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)",
308 pUrlData->name, pUrlData->port,
309 ERR_reason_error_string(ERR_get_error()));
310 ssl_close();
311 return 1;
312 }
313 }
314
@@ -319,81 +327,78 @@
327 ssl_set_errmsg("No SSL certificate was presented by the peer");
328 ssl_close();
329 return 1;
330 }
331
332 if( !sslNoCertVerify && SSL_get_verify_result(ssl)!=X509_V_OK ){
333 char *desc, *prompt;
 
334 Blob ans;
335 char cReply;
336 BIO *mem;
337 unsigned char md[32];
338 char zHash[32*2+1];
339 unsigned int mdLength = (int)sizeof(md);
340
341 memset(md, 0, sizeof(md));
342 zHash[0] = 0;
343 if( X509_digest(cert, EVP_sha256(), md, &mdLength) ){
344 int j;
345 for(j=0; j<mdLength && j*2+1<sizeof(zHash); ++j){
346 zHash[j*2] = "0123456789abcdef"[md[j]>>4];
347 zHash[j*2+1] = "0123456789abcdef"[md[j]&0xf];
348 }
349 zHash[j*2] = 0;
350 }
351
352 if( ssl_certificate_exception_exists(pUrlData, zHash) ){
353 /* Ignore the failure because an exception exists */
354 ssl_one_time_exception(pUrlData, zHash);
355 }else{
356 /* Tell the user about the failure and ask what to do */
357 mem = BIO_new(BIO_s_mem());
358 BIO_puts(mem, " subject: ");
359 X509_NAME_print_ex(mem, X509_get_subject_name(cert), 0, XN_FLAG_ONELINE);
360 BIO_puts(mem, "\n issuer: ");
361 X509_NAME_print_ex(mem, X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE);
362 BIO_printf(mem, "\n sha256: %s", zHash);
363 BIO_get_mem_data(mem, &desc);
364
365 prompt = mprintf("Unable to verify SSL cert from %s\n%s\n"
366 "accept this cert and continue (y/N)? ",
367 pUrlData->name, desc);
368 BIO_free(mem);
369
370 prompt_user(prompt, &ans);
371 free(prompt);
372 cReply = blob_str(&ans)[0];
373 blob_reset(&ans);
374 if( cReply!='y' && cReply!='Y' ){
375 X509_free(cert);
376 ssl_set_errmsg("SSL cert declined");
377 ssl_close();
378 return 1;
379 }
380 ssl_one_time_exception(pUrlData, zHash);
381 prompt_user("remember this exception (y/N)? ", &ans);
382 cReply = blob_str(&ans)[0];
383 if( cReply=='y' || cReply=='Y') {
384 ssl_remember_certificate_exception(pUrlData, zHash);
385 }
386 blob_reset(&ans);
 
 
 
 
387 }
388 }
389
390 /* Set the Global.zIpAddr variable to the server we are talking to.
391 ** This is used to populate the ipaddr column of the rcvfrom table,
392 ** if any files are received from the server.
393 */
394 {
395 /* As soon as libressl implements
396 ** BIO_ADDR_hostname_string/BIO_get_conn_address.
397 ** check here for the correct LIBRESSL_VERSION_NUMBER too. For now: disable
398 */
399 #if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000L \
400 && !defined(LIBRESSL_VERSION_NUMBER)
401 char *ip = BIO_ADDR_hostname_string(BIO_get_conn_address(iBio),1);
402 g.zIpAddr = mprintf("%s", ip);
403 OPENSSL_free(ip);
404 #else
@@ -407,58 +412,53 @@
412 X509_free(cert);
413 return 0;
414 }
415
416 /*
417 ** Remember that the cert with the given hash is a acceptable for
418 ** use with pUrlData->name.
419 */
420 LOCAL void ssl_remember_certificate_exception(
421 UrlData *pUrlData,
422 const char *zHash
423 ){
424 char *zName = mprintf("cert:%s", pUrlData->name);
425 db_set(zName, zHash, 1);
426 fossil_free(zName);
427 }
428
429 /*
430 ** Return true if the there exists a certificate exception for
431 ** pUrlData->name that matches the hash.
432 */
433 LOCAL int ssl_certificate_exception_exists(
434 UrlData *pUrlData,
435 const char *zHash
436 ){
437 char *zName, *zValue;
438 if( fossil_strcmp(sException.zHost,pUrlData->name)==0
439 && fossil_strcmp(sException.zHash,zHash)==0
440 ){
441 return 1;
442 }
443 zName = mprintf("cert:%s", pUrlData->name);
444 zValue = db_get(zName,0);
445 fossil_free(zName);
446 return zValue!=0 && strcmp(zHash,zValue)==0;
 
447 }
448
449 /*
450 ** Remember zHash as an acceptable certificate for this session only.
 
451 */
452 LOCAL void ssl_one_time_exception(
453 UrlData *pUrlData,
454 const char *zHash
455 ){
456 fossil_free(sException.zHost);
457 sException.zHost = fossil_strdup(pUrlData->name);
458 fossil_free(sException.zHash);
459 sException.zHash = fossil_strdup(zHash);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460 }
461
462 /*
463 ** Send content out over the SSL connection.
464 */
@@ -498,5 +498,112 @@
498 }
499 return total;
500 }
501
502 #endif /* FOSSIL_ENABLE_SSL */
503
504 /*
505 ** COMMAND: tls-config*
506 **
507 ** Usage: %fossil tls-config [SUBCOMMAND] [OPTIONS...] [ARGS...]
508 **
509 ** This command is used to view or modify the TLS (Transport Layer
510 ** Security) configuration for Fossil. TLS (formerly SSL) is the
511 ** encryption technology used for secure HTTPS transport.
512 **
513 ** Sub-commands:
514 **
515 ** show Show the TLS configuration
516 **
517 ** remove-exception DOMAIN... Remove TLS cert exceptions
518 ** for the domains listed. Or if
519 ** the --all option is specified,
520 ** remove all TLS cert exceptions.
521 */
522 void test_tlsconfig_info(void){
523 const char *zCmd;
524 size_t nCmd;
525 int nHit = 0;
526 #if !defined(FOSSIL_ENABLE_SSL)
527 fossil_print("TLS disabled in this build\n");
528 #else
529 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
530 db_open_config(1,0);
531 zCmd = g.argc>=3 ? g.argv[2] : "show";
532 nCmd = strlen(zCmd);
533 if( strncmp("show",zCmd,nCmd)==0 ){
534 const char *zName, *zValue;
535 size_t nName;
536 Stmt q;
537 fossil_print("OpenSSL-version: %s\n", SSLeay_version(SSLEAY_VERSION));
538 fossil_print("OpenSSL-cert-file: %s\n", X509_get_default_cert_file());
539 fossil_print("OpenSSL-cert-dir: %s\n", X509_get_default_cert_dir());
540 zName = X509_get_default_cert_file_env();
541 zValue = fossil_getenv(zName);
542 if( zValue==0 ) zValue = "";
543 nName = strlen(zName);
544 fossil_print("%s:%.*s%s\n", zName, 19-nName, "", zValue);
545 zName = X509_get_default_cert_dir_env();
546 zValue = fossil_getenv(zName);
547 if( zValue==0 ) zValue = "";
548 nName = strlen(zName);
549 fossil_print("%s:%.*s%s\n", zName, 19-nName, "", zValue);
550 nHit++;
551 fossil_print("ssl-ca-location: %s\n", db_get("ssl-ca-location",""));
552 fossil_print("ssl-identity: %s\n", db_get("ssl-identity",""));
553 db_prepare(&q,
554 "SELECT name FROM global_config"
555 " WHERE name GLOB 'cert:*'"
556 "UNION ALL "
557 "SELECT name FROM config"
558 " WHERE name GLOB 'cert:*'"
559 " ORDER BY name"
560 );
561 while( db_step(&q)==SQLITE_ROW ){
562 fossil_print("exception: %s\n", db_column_text(&q,0)+5);
563 }
564 db_finalize(&q);
565 }else
566 if( strncmp("remove-exception",zCmd,nCmd)==0 ){
567 int i;
568 Blob sql;
569 char *zSep = "(";
570 db_begin_transaction();
571 blob_init(&sql, 0, 0);
572 if( g.argc==4 && find_option("all",0,0)!=0 ){
573 blob_append_sql(&sql,
574 "DELETE FROM global_config WHERE name GLOB 'cert:*';\n"
575 "DELETE FROM global_config WHERE name GLOB 'trusted:*';\n"
576 "DELETE FROM config WHERE name GLOB 'cert:*';\n"
577 "DELETE FROM config WHERE name GLOB 'trusted:*';\n"
578 );
579 }else{
580 if( g.argc<4 ){
581 usage("remove-exception DOMAIN-NAME ...");
582 }
583 blob_append_sql(&sql,"DELETE FROM global_config WHERE name IN ");
584 for(i=3; i<g.argc; i++){
585 blob_append_sql(&sql,"%s'cert:%q','trust:%q'",
586 zSep/*safe-for-%s*/, g.argv[i], g.argv[i]);
587 zSep = ",";
588 }
589 blob_append_sql(&sql,");\n");
590 zSep = "(";
591 blob_append_sql(&sql,"DELETE FROM config WHERE name IN ");
592 for(i=3; i<g.argc; i++){
593 blob_append_sql(&sql,"%s'cert:%q','trusted:%q'",
594 zSep/*safe-for-%s*/, g.argv[i], g.argv[i]);
595 zSep = ",";
596 }
597 blob_append_sql(&sql,");");
598 }
599 db_exec_sql(blob_str(&sql));
600 db_commit_transaction();
601 blob_reset(&sql);
602 }else
603 /*default*/{
604 fossil_fatal("unknown sub-command \"%s\".\nshould be one of:"
605 " remove-exception show",
606 zCmd);
607 }
608 #endif
609 }
610
+62 -14
--- src/login.c
+++ src/login.c
@@ -481,11 +481,11 @@
481481
int login_self_register_available(const char *zNeeded){
482482
CapabilityString *pCap;
483483
int rc;
484484
if( !db_get_boolean("self-register",0) ) return 0;
485485
if( zNeeded==0 ) return 1;
486
- pCap = capability_add(0, db_get("default-perms", 0));
486
+ pCap = capability_add(0, db_get("default-perms", "u"));
487487
capability_expand(pCap);
488488
rc = capability_has_any(pCap, zNeeded);
489489
capability_free(pCap);
490490
return rc;
491491
}
@@ -1128,11 +1128,11 @@
11281128
if( zPublicPages!=0 ){
11291129
Glob *pGlob = glob_create(zPublicPages);
11301130
const char *zUri = PD("REQUEST_URI","");
11311131
zUri += (int)strlen(g.zTop);
11321132
if( glob_match(pGlob, zUri) ){
1133
- login_set_capabilities(db_get("default-perms", 0), 0);
1133
+ login_set_capabilities(db_get("default-perms", "u"), 0);
11341134
}
11351135
glob_free(pGlob);
11361136
}
11371137
}
11381138
@@ -1459,10 +1459,38 @@
14591459
"SELECT 1 FROM event WHERE user=%Q OR euser=%Q",
14601460
zUserID, zUserID, zUserID
14611461
);
14621462
return rc;
14631463
}
1464
+
1465
+/*
1466
+** Check an email address and confirm that it is valid for self-registration.
1467
+** The email address is known already to be well-formed. Return true
1468
+** if the email address is on the allowed list.
1469
+**
1470
+** The default behavior is that any valid email address is accepted.
1471
+** But if the "auth-sub-email" setting exists and is not empty, then
1472
+** it is a comma-separated list of GLOB patterns for email addresses
1473
+** that are authorized to self-register.
1474
+*/
1475
+int authorized_subscription_email(const char *zEAddr){
1476
+ char *zGlob = db_get("auth-sub-email",0);
1477
+ Glob *pGlob;
1478
+ char *zAddr;
1479
+ int rc;
1480
+
1481
+ if( zGlob==0 || zGlob[0]==0 ) return 1;
1482
+ zGlob = fossil_strtolwr(fossil_strdup(zGlob));
1483
+ pGlob = glob_create(zGlob);
1484
+ fossil_free(zGlob);
1485
+
1486
+ zAddr = fossil_strtolwr(fossil_strdup(zEAddr));
1487
+ rc = glob_match(pGlob, zAddr);
1488
+ fossil_free(zAddr);
1489
+ glob_free(pGlob);
1490
+ return rc!=0;
1491
+}
14641492
14651493
/*
14661494
** WEBPAGE: register
14671495
**
14681496
** Page to allow users to self-register. The "self-register" setting
@@ -1471,13 +1499,14 @@
14711499
void register_page(void){
14721500
const char *zUserID, *zPasswd, *zConfirm, *zEAddr;
14731501
const char *zDName;
14741502
unsigned int uSeed;
14751503
const char *zDecoded;
1476
- char *zCaptcha;
14771504
int iErrLine = -1;
14781505
const char *zErr = 0;
1506
+ int captchaIsCorrect = 0; /* True on a correct captcha */
1507
+ char *zCaptcha = ""; /* Value of the captcha text */
14791508
char *zPerms; /* Permissions for the default user */
14801509
int canDoAlerts = 0; /* True if receiving email alerts is possible */
14811510
int doAlerts = 0; /* True if subscription is wanted too */
14821511
if( !db_get_boolean("self-register", 0) ){
14831512
style_header("Registration not possible");
@@ -1484,11 +1513,11 @@
14841513
@ <p>This project does not allow user self-registration. Please contact the
14851514
@ project administrator to obtain an account.</p>
14861515
style_footer();
14871516
return;
14881517
}
1489
- zPerms = db_get("default-perms", 0);
1518
+ zPerms = db_get("default-perms", "u");
14901519
14911520
/* Prompt the user for email alerts if this repository is configured for
14921521
** email alerts and if the default permissions include "7" */
14931522
canDoAlerts = alert_tables_exist() && db_int(0,
14941523
"SELECT fullcap(%Q) GLOB '*7*'", zPerms
@@ -1503,11 +1532,11 @@
15031532
15041533
/* Verify user imputs */
15051534
if( P("new")==0 || !cgi_csrf_safe(1) ){
15061535
/* This is not a valid form submission. Fall through into
15071536
** the form display */
1508
- }else if( !captcha_is_correct(1) ){
1537
+ }else if( (captchaIsCorrect = captcha_is_correct(1))==0 ){
15091538
iErrLine = 6;
15101539
zErr = "Incorrect CAPTCHA";
15111540
}else if( strlen(zUserID)<6 ){
15121541
iErrLine = 1;
15131542
zErr = "User ID too short. Must be at least 6 characters.";
@@ -1521,10 +1550,13 @@
15211550
iErrLine = 3;
15221551
zErr = "Required";
15231552
}else if( email_address_is_valid(zEAddr,0)==0 ){
15241553
iErrLine = 3;
15251554
zErr = "Not a valid email address";
1555
+ }else if( authorized_subscription_email(zEAddr)==0 ){
1556
+ iErrLine = 3;
1557
+ zErr = "Not an authorized email address";
15261558
}else if( strlen(zPasswd)<6 ){
15271559
iErrLine = 4;
15281560
zErr = "Password must be at least 6 characters long";
15291561
}else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){
15301562
iErrLine = 5;
@@ -1548,16 +1580,24 @@
15481580
/* If all of the tests above have passed, that means that the submitted
15491581
** form contains valid data and we can proceed to create the new login */
15501582
Blob sql;
15511583
int uid;
15521584
char *zPass = sha1_shared_secret(zPasswd, zUserID, 0);
1585
+ const char *zStartPerms = zPerms;
1586
+ if( db_get_boolean("selfreg-verify",0) ){
1587
+ /* If email verification is required for self-registration, initalize
1588
+ ** the new user capabilities to just "7" (Sign up for email). The
1589
+ ** full "default-perms" permissions will be added when they click
1590
+ ** the verification link on the email they are sent. */
1591
+ zStartPerms = "7";
1592
+ }
15531593
blob_init(&sql, 0, 0);
15541594
blob_append_sql(&sql,
15551595
"INSERT INTO user(login,pw,cap,info,mtime)\n"
15561596
"VALUES(%Q,%Q,%Q,"
15571597
"'%q <%q>\nself-register from ip %q on '||datetime('now'),now())",
1558
- zUserID, zPass, zPerms, zDName, zEAddr, g.zIpAddr);
1598
+ zUserID, zPass, zStartPerms, zDName, zEAddr, g.zIpAddr);
15591599
fossil_free(zPass);
15601600
db_multi_exec("%s", blob_sql_text(&sql));
15611601
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID);
15621602
login_set_user_cookie(zUserID, uid, NULL);
15631603
if( doAlerts ){
@@ -1567,16 +1607,20 @@
15671607
sqlite3_int64 id; /* New subscriber Id */
15681608
const char *zCode; /* New subscriber code (in hex) */
15691609
const char *zGoto = P("g");
15701610
int nsub = 0;
15711611
char ssub[20];
1612
+ CapabilityString *pCap;
1613
+ pCap = capability_add(0, zPerms);
1614
+ capability_expand(pCap);
15721615
ssub[nsub++] = 'a';
1573
- if( g.perm.Read ) ssub[nsub++] = 'c';
1574
- if( g.perm.RdForum ) ssub[nsub++] = 'f';
1575
- if( g.perm.RdTkt ) ssub[nsub++] = 't';
1576
- if( g.perm.RdWiki ) ssub[nsub++] = 'w';
1616
+ if( capability_has_any(pCap,"o") ) ssub[nsub++] = 'c';
1617
+ if( capability_has_any(pCap,"2") ) ssub[nsub++] = 'f';
1618
+ if( capability_has_any(pCap,"r") ) ssub[nsub++] = 't';
1619
+ if( capability_has_any(pCap,"j") ) ssub[nsub++] = 'w';
15771620
ssub[nsub] = 0;
1621
+ capability_free(pCap);
15781622
/* Also add the user to the subscriber table. */
15791623
db_multi_exec(
15801624
"INSERT INTO subscriber(semail,suname,"
15811625
" sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
15821626
" VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)"
@@ -1616,12 +1660,11 @@
16161660
@ <blockquote><pre>
16171661
@ %h(pSender->zErr)
16181662
@ </pre></blockquote>
16191663
}else{
16201664
@ <p>An email has been sent to "%h(zEAddr)". That email contains a
1621
- @ hyperlink that you must click on in order to activate your
1622
- @ subscription.</p>
1665
+ @ hyperlink that you must click to activate your account.</p>
16231666
}
16241667
alert_sender_free(pSender);
16251668
if( zGoto ){
16261669
@ <p><a href='%h(zGoto)'>Continue</a>
16271670
}
@@ -1630,11 +1673,15 @@
16301673
}
16311674
redirect_to_g();
16321675
}
16331676
16341677
/* Prepare the captcha. */
1635
- uSeed = captcha_seed();
1678
+ if( captchaIsCorrect ){
1679
+ uSeed = strtoul(P("captchaseed"),0,10);
1680
+ }else{
1681
+ uSeed = captcha_seed();
1682
+ }
16361683
zDecoded = captcha_decode(uSeed);
16371684
zCaptcha = captcha_render(zDecoded);
16381685
16391686
style_header("Register");
16401687
/* Print out the registration form. */
@@ -1689,11 +1736,12 @@
16891736
if( iErrLine==5 ){
16901737
@ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
16911738
}
16921739
@ <tr>
16931740
@ <td class="form_label" align="right">Captcha:</td>
1694
- @ <td><input type="text" name="captcha" value="" size="30">
1741
+ @ <td><input type="text" name="captcha" size="30"\
1742
+ @ value="%h(captchaIsCorrect?zDecoded:"")" size="30">
16951743
captcha_speakit_button(uSeed, "Speak the captcha text");
16961744
@ </td>
16971745
@ </tr>
16981746
if( iErrLine==6 ){
16991747
@ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
17001748
--- src/login.c
+++ src/login.c
@@ -481,11 +481,11 @@
481 int login_self_register_available(const char *zNeeded){
482 CapabilityString *pCap;
483 int rc;
484 if( !db_get_boolean("self-register",0) ) return 0;
485 if( zNeeded==0 ) return 1;
486 pCap = capability_add(0, db_get("default-perms", 0));
487 capability_expand(pCap);
488 rc = capability_has_any(pCap, zNeeded);
489 capability_free(pCap);
490 return rc;
491 }
@@ -1128,11 +1128,11 @@
1128 if( zPublicPages!=0 ){
1129 Glob *pGlob = glob_create(zPublicPages);
1130 const char *zUri = PD("REQUEST_URI","");
1131 zUri += (int)strlen(g.zTop);
1132 if( glob_match(pGlob, zUri) ){
1133 login_set_capabilities(db_get("default-perms", 0), 0);
1134 }
1135 glob_free(pGlob);
1136 }
1137 }
1138
@@ -1459,10 +1459,38 @@
1459 "SELECT 1 FROM event WHERE user=%Q OR euser=%Q",
1460 zUserID, zUserID, zUserID
1461 );
1462 return rc;
1463 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1464
1465 /*
1466 ** WEBPAGE: register
1467 **
1468 ** Page to allow users to self-register. The "self-register" setting
@@ -1471,13 +1499,14 @@
1471 void register_page(void){
1472 const char *zUserID, *zPasswd, *zConfirm, *zEAddr;
1473 const char *zDName;
1474 unsigned int uSeed;
1475 const char *zDecoded;
1476 char *zCaptcha;
1477 int iErrLine = -1;
1478 const char *zErr = 0;
 
 
1479 char *zPerms; /* Permissions for the default user */
1480 int canDoAlerts = 0; /* True if receiving email alerts is possible */
1481 int doAlerts = 0; /* True if subscription is wanted too */
1482 if( !db_get_boolean("self-register", 0) ){
1483 style_header("Registration not possible");
@@ -1484,11 +1513,11 @@
1484 @ <p>This project does not allow user self-registration. Please contact the
1485 @ project administrator to obtain an account.</p>
1486 style_footer();
1487 return;
1488 }
1489 zPerms = db_get("default-perms", 0);
1490
1491 /* Prompt the user for email alerts if this repository is configured for
1492 ** email alerts and if the default permissions include "7" */
1493 canDoAlerts = alert_tables_exist() && db_int(0,
1494 "SELECT fullcap(%Q) GLOB '*7*'", zPerms
@@ -1503,11 +1532,11 @@
1503
1504 /* Verify user imputs */
1505 if( P("new")==0 || !cgi_csrf_safe(1) ){
1506 /* This is not a valid form submission. Fall through into
1507 ** the form display */
1508 }else if( !captcha_is_correct(1) ){
1509 iErrLine = 6;
1510 zErr = "Incorrect CAPTCHA";
1511 }else if( strlen(zUserID)<6 ){
1512 iErrLine = 1;
1513 zErr = "User ID too short. Must be at least 6 characters.";
@@ -1521,10 +1550,13 @@
1521 iErrLine = 3;
1522 zErr = "Required";
1523 }else if( email_address_is_valid(zEAddr,0)==0 ){
1524 iErrLine = 3;
1525 zErr = "Not a valid email address";
 
 
 
1526 }else if( strlen(zPasswd)<6 ){
1527 iErrLine = 4;
1528 zErr = "Password must be at least 6 characters long";
1529 }else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){
1530 iErrLine = 5;
@@ -1548,16 +1580,24 @@
1548 /* If all of the tests above have passed, that means that the submitted
1549 ** form contains valid data and we can proceed to create the new login */
1550 Blob sql;
1551 int uid;
1552 char *zPass = sha1_shared_secret(zPasswd, zUserID, 0);
 
 
 
 
 
 
 
 
1553 blob_init(&sql, 0, 0);
1554 blob_append_sql(&sql,
1555 "INSERT INTO user(login,pw,cap,info,mtime)\n"
1556 "VALUES(%Q,%Q,%Q,"
1557 "'%q <%q>\nself-register from ip %q on '||datetime('now'),now())",
1558 zUserID, zPass, zPerms, zDName, zEAddr, g.zIpAddr);
1559 fossil_free(zPass);
1560 db_multi_exec("%s", blob_sql_text(&sql));
1561 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID);
1562 login_set_user_cookie(zUserID, uid, NULL);
1563 if( doAlerts ){
@@ -1567,16 +1607,20 @@
1567 sqlite3_int64 id; /* New subscriber Id */
1568 const char *zCode; /* New subscriber code (in hex) */
1569 const char *zGoto = P("g");
1570 int nsub = 0;
1571 char ssub[20];
 
 
 
1572 ssub[nsub++] = 'a';
1573 if( g.perm.Read ) ssub[nsub++] = 'c';
1574 if( g.perm.RdForum ) ssub[nsub++] = 'f';
1575 if( g.perm.RdTkt ) ssub[nsub++] = 't';
1576 if( g.perm.RdWiki ) ssub[nsub++] = 'w';
1577 ssub[nsub] = 0;
 
1578 /* Also add the user to the subscriber table. */
1579 db_multi_exec(
1580 "INSERT INTO subscriber(semail,suname,"
1581 " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
1582 " VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)"
@@ -1616,12 +1660,11 @@
1616 @ <blockquote><pre>
1617 @ %h(pSender->zErr)
1618 @ </pre></blockquote>
1619 }else{
1620 @ <p>An email has been sent to "%h(zEAddr)". That email contains a
1621 @ hyperlink that you must click on in order to activate your
1622 @ subscription.</p>
1623 }
1624 alert_sender_free(pSender);
1625 if( zGoto ){
1626 @ <p><a href='%h(zGoto)'>Continue</a>
1627 }
@@ -1630,11 +1673,15 @@
1630 }
1631 redirect_to_g();
1632 }
1633
1634 /* Prepare the captcha. */
1635 uSeed = captcha_seed();
 
 
 
 
1636 zDecoded = captcha_decode(uSeed);
1637 zCaptcha = captcha_render(zDecoded);
1638
1639 style_header("Register");
1640 /* Print out the registration form. */
@@ -1689,11 +1736,12 @@
1689 if( iErrLine==5 ){
1690 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
1691 }
1692 @ <tr>
1693 @ <td class="form_label" align="right">Captcha:</td>
1694 @ <td><input type="text" name="captcha" value="" size="30">
 
1695 captcha_speakit_button(uSeed, "Speak the captcha text");
1696 @ </td>
1697 @ </tr>
1698 if( iErrLine==6 ){
1699 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
1700
--- src/login.c
+++ src/login.c
@@ -481,11 +481,11 @@
481 int login_self_register_available(const char *zNeeded){
482 CapabilityString *pCap;
483 int rc;
484 if( !db_get_boolean("self-register",0) ) return 0;
485 if( zNeeded==0 ) return 1;
486 pCap = capability_add(0, db_get("default-perms", "u"));
487 capability_expand(pCap);
488 rc = capability_has_any(pCap, zNeeded);
489 capability_free(pCap);
490 return rc;
491 }
@@ -1128,11 +1128,11 @@
1128 if( zPublicPages!=0 ){
1129 Glob *pGlob = glob_create(zPublicPages);
1130 const char *zUri = PD("REQUEST_URI","");
1131 zUri += (int)strlen(g.zTop);
1132 if( glob_match(pGlob, zUri) ){
1133 login_set_capabilities(db_get("default-perms", "u"), 0);
1134 }
1135 glob_free(pGlob);
1136 }
1137 }
1138
@@ -1459,10 +1459,38 @@
1459 "SELECT 1 FROM event WHERE user=%Q OR euser=%Q",
1460 zUserID, zUserID, zUserID
1461 );
1462 return rc;
1463 }
1464
1465 /*
1466 ** Check an email address and confirm that it is valid for self-registration.
1467 ** The email address is known already to be well-formed. Return true
1468 ** if the email address is on the allowed list.
1469 **
1470 ** The default behavior is that any valid email address is accepted.
1471 ** But if the "auth-sub-email" setting exists and is not empty, then
1472 ** it is a comma-separated list of GLOB patterns for email addresses
1473 ** that are authorized to self-register.
1474 */
1475 int authorized_subscription_email(const char *zEAddr){
1476 char *zGlob = db_get("auth-sub-email",0);
1477 Glob *pGlob;
1478 char *zAddr;
1479 int rc;
1480
1481 if( zGlob==0 || zGlob[0]==0 ) return 1;
1482 zGlob = fossil_strtolwr(fossil_strdup(zGlob));
1483 pGlob = glob_create(zGlob);
1484 fossil_free(zGlob);
1485
1486 zAddr = fossil_strtolwr(fossil_strdup(zEAddr));
1487 rc = glob_match(pGlob, zAddr);
1488 fossil_free(zAddr);
1489 glob_free(pGlob);
1490 return rc!=0;
1491 }
1492
1493 /*
1494 ** WEBPAGE: register
1495 **
1496 ** Page to allow users to self-register. The "self-register" setting
@@ -1471,13 +1499,14 @@
1499 void register_page(void){
1500 const char *zUserID, *zPasswd, *zConfirm, *zEAddr;
1501 const char *zDName;
1502 unsigned int uSeed;
1503 const char *zDecoded;
 
1504 int iErrLine = -1;
1505 const char *zErr = 0;
1506 int captchaIsCorrect = 0; /* True on a correct captcha */
1507 char *zCaptcha = ""; /* Value of the captcha text */
1508 char *zPerms; /* Permissions for the default user */
1509 int canDoAlerts = 0; /* True if receiving email alerts is possible */
1510 int doAlerts = 0; /* True if subscription is wanted too */
1511 if( !db_get_boolean("self-register", 0) ){
1512 style_header("Registration not possible");
@@ -1484,11 +1513,11 @@
1513 @ <p>This project does not allow user self-registration. Please contact the
1514 @ project administrator to obtain an account.</p>
1515 style_footer();
1516 return;
1517 }
1518 zPerms = db_get("default-perms", "u");
1519
1520 /* Prompt the user for email alerts if this repository is configured for
1521 ** email alerts and if the default permissions include "7" */
1522 canDoAlerts = alert_tables_exist() && db_int(0,
1523 "SELECT fullcap(%Q) GLOB '*7*'", zPerms
@@ -1503,11 +1532,11 @@
1532
1533 /* Verify user imputs */
1534 if( P("new")==0 || !cgi_csrf_safe(1) ){
1535 /* This is not a valid form submission. Fall through into
1536 ** the form display */
1537 }else if( (captchaIsCorrect = captcha_is_correct(1))==0 ){
1538 iErrLine = 6;
1539 zErr = "Incorrect CAPTCHA";
1540 }else if( strlen(zUserID)<6 ){
1541 iErrLine = 1;
1542 zErr = "User ID too short. Must be at least 6 characters.";
@@ -1521,10 +1550,13 @@
1550 iErrLine = 3;
1551 zErr = "Required";
1552 }else if( email_address_is_valid(zEAddr,0)==0 ){
1553 iErrLine = 3;
1554 zErr = "Not a valid email address";
1555 }else if( authorized_subscription_email(zEAddr)==0 ){
1556 iErrLine = 3;
1557 zErr = "Not an authorized email address";
1558 }else if( strlen(zPasswd)<6 ){
1559 iErrLine = 4;
1560 zErr = "Password must be at least 6 characters long";
1561 }else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){
1562 iErrLine = 5;
@@ -1548,16 +1580,24 @@
1580 /* If all of the tests above have passed, that means that the submitted
1581 ** form contains valid data and we can proceed to create the new login */
1582 Blob sql;
1583 int uid;
1584 char *zPass = sha1_shared_secret(zPasswd, zUserID, 0);
1585 const char *zStartPerms = zPerms;
1586 if( db_get_boolean("selfreg-verify",0) ){
1587 /* If email verification is required for self-registration, initalize
1588 ** the new user capabilities to just "7" (Sign up for email). The
1589 ** full "default-perms" permissions will be added when they click
1590 ** the verification link on the email they are sent. */
1591 zStartPerms = "7";
1592 }
1593 blob_init(&sql, 0, 0);
1594 blob_append_sql(&sql,
1595 "INSERT INTO user(login,pw,cap,info,mtime)\n"
1596 "VALUES(%Q,%Q,%Q,"
1597 "'%q <%q>\nself-register from ip %q on '||datetime('now'),now())",
1598 zUserID, zPass, zStartPerms, zDName, zEAddr, g.zIpAddr);
1599 fossil_free(zPass);
1600 db_multi_exec("%s", blob_sql_text(&sql));
1601 uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID);
1602 login_set_user_cookie(zUserID, uid, NULL);
1603 if( doAlerts ){
@@ -1567,16 +1607,20 @@
1607 sqlite3_int64 id; /* New subscriber Id */
1608 const char *zCode; /* New subscriber code (in hex) */
1609 const char *zGoto = P("g");
1610 int nsub = 0;
1611 char ssub[20];
1612 CapabilityString *pCap;
1613 pCap = capability_add(0, zPerms);
1614 capability_expand(pCap);
1615 ssub[nsub++] = 'a';
1616 if( capability_has_any(pCap,"o") ) ssub[nsub++] = 'c';
1617 if( capability_has_any(pCap,"2") ) ssub[nsub++] = 'f';
1618 if( capability_has_any(pCap,"r") ) ssub[nsub++] = 't';
1619 if( capability_has_any(pCap,"j") ) ssub[nsub++] = 'w';
1620 ssub[nsub] = 0;
1621 capability_free(pCap);
1622 /* Also add the user to the subscriber table. */
1623 db_multi_exec(
1624 "INSERT INTO subscriber(semail,suname,"
1625 " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)"
1626 " VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)"
@@ -1616,12 +1660,11 @@
1660 @ <blockquote><pre>
1661 @ %h(pSender->zErr)
1662 @ </pre></blockquote>
1663 }else{
1664 @ <p>An email has been sent to "%h(zEAddr)". That email contains a
1665 @ hyperlink that you must click to activate your account.</p>
 
1666 }
1667 alert_sender_free(pSender);
1668 if( zGoto ){
1669 @ <p><a href='%h(zGoto)'>Continue</a>
1670 }
@@ -1630,11 +1673,15 @@
1673 }
1674 redirect_to_g();
1675 }
1676
1677 /* Prepare the captcha. */
1678 if( captchaIsCorrect ){
1679 uSeed = strtoul(P("captchaseed"),0,10);
1680 }else{
1681 uSeed = captcha_seed();
1682 }
1683 zDecoded = captcha_decode(uSeed);
1684 zCaptcha = captcha_render(zDecoded);
1685
1686 style_header("Register");
1687 /* Print out the registration form. */
@@ -1689,11 +1736,12 @@
1736 if( iErrLine==5 ){
1737 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
1738 }
1739 @ <tr>
1740 @ <td class="form_label" align="right">Captcha:</td>
1741 @ <td><input type="text" name="captcha" size="30"\
1742 @ value="%h(captchaIsCorrect?zDecoded:"")" size="30">
1743 captcha_speakit_button(uSeed, "Speak the captcha text");
1744 @ </td>
1745 @ </tr>
1746 if( iErrLine==6 ){
1747 @ <tr><td><td><span class='loginError'>&uarr; %h(zErr)</span></td></tr>
1748
+8 -2
--- src/main.c
+++ src/main.c
@@ -2523,18 +2523,24 @@
25232523
**
25242524
** Works like the http command but gives setup permission to all users.
25252525
**
25262526
** Options:
25272527
** --th-trace trace TH1 execution (for debugging purposes)
2528
+** --usercap CAP user capability string. (Default: "sx")
25282529
**
25292530
*/
25302531
void cmd_test_http(void){
25312532
const char *zIpAddr; /* IP address of remote client */
2533
+ const char *zUserCap;
25322534
25332535
Th_InitTraceLog();
2534
- login_set_capabilities("sx", 0);
2535
- g.useLocalauth = 1;
2536
+ zUserCap = find_option("usercap",0,1);
2537
+ if( zUserCap==0 ){
2538
+ g.useLocalauth = 1;
2539
+ zUserCap = "sx";
2540
+ }
2541
+ login_set_capabilities(zUserCap, 0);
25362542
g.httpIn = stdin;
25372543
g.httpOut = stdout;
25382544
fossil_binary_mode(g.httpOut);
25392545
fossil_binary_mode(g.httpIn);
25402546
g.zExtRoot = find_option("extroot",0,1);
25412547
--- src/main.c
+++ src/main.c
@@ -2523,18 +2523,24 @@
2523 **
2524 ** Works like the http command but gives setup permission to all users.
2525 **
2526 ** Options:
2527 ** --th-trace trace TH1 execution (for debugging purposes)
 
2528 **
2529 */
2530 void cmd_test_http(void){
2531 const char *zIpAddr; /* IP address of remote client */
 
2532
2533 Th_InitTraceLog();
2534 login_set_capabilities("sx", 0);
2535 g.useLocalauth = 1;
 
 
 
 
2536 g.httpIn = stdin;
2537 g.httpOut = stdout;
2538 fossil_binary_mode(g.httpOut);
2539 fossil_binary_mode(g.httpIn);
2540 g.zExtRoot = find_option("extroot",0,1);
2541
--- src/main.c
+++ src/main.c
@@ -2523,18 +2523,24 @@
2523 **
2524 ** Works like the http command but gives setup permission to all users.
2525 **
2526 ** Options:
2527 ** --th-trace trace TH1 execution (for debugging purposes)
2528 ** --usercap CAP user capability string. (Default: "sx")
2529 **
2530 */
2531 void cmd_test_http(void){
2532 const char *zIpAddr; /* IP address of remote client */
2533 const char *zUserCap;
2534
2535 Th_InitTraceLog();
2536 zUserCap = find_option("usercap",0,1);
2537 if( zUserCap==0 ){
2538 g.useLocalauth = 1;
2539 zUserCap = "sx";
2540 }
2541 login_set_capabilities(zUserCap, 0);
2542 g.httpIn = stdin;
2543 g.httpOut = stdout;
2544 fossil_binary_mode(g.httpOut);
2545 fossil_binary_mode(g.httpIn);
2546 g.zExtRoot = find_option("extroot",0,1);
2547
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -714,11 +714,11 @@
714714
#### The directories where the OpenSSL include and library files are located.
715715
# The recommended usage here is to use the Sysinternals junction tool
716716
# to create a hard link between an "openssl-1.x" sub-directory of the
717717
# Fossil source code directory and the target OpenSSL source directory.
718718
#
719
-OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1f
719
+OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1g
720720
OPENSSLINCDIR = $(OPENSSLDIR)/include
721721
OPENSSLLIBDIR = $(OPENSSLDIR)
722722
723723
#### Either the directory where the Tcl library is installed or the Tcl
724724
# source code directory resides (depending on the value of the macro
@@ -1571,11 +1571,11 @@
15711571
!ifndef USE_SEE
15721572
USE_SEE = 0
15731573
!endif
15741574
15751575
!if $(FOSSIL_ENABLE_SSL)!=0
1576
-SSLDIR = $(B)\compat\openssl-1.1.1f
1576
+SSLDIR = $(B)\compat\openssl-1.1.1g
15771577
SSLINCDIR = $(SSLDIR)\include
15781578
!if $(FOSSIL_DYNAMIC_BUILD)!=0
15791579
SSLLIBDIR = $(SSLDIR)
15801580
!else
15811581
SSLLIBDIR = $(SSLDIR)
15821582
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -714,11 +714,11 @@
714 #### The directories where the OpenSSL include and library files are located.
715 # The recommended usage here is to use the Sysinternals junction tool
716 # to create a hard link between an "openssl-1.x" sub-directory of the
717 # Fossil source code directory and the target OpenSSL source directory.
718 #
719 OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1f
720 OPENSSLINCDIR = $(OPENSSLDIR)/include
721 OPENSSLLIBDIR = $(OPENSSLDIR)
722
723 #### Either the directory where the Tcl library is installed or the Tcl
724 # source code directory resides (depending on the value of the macro
@@ -1571,11 +1571,11 @@
1571 !ifndef USE_SEE
1572 USE_SEE = 0
1573 !endif
1574
1575 !if $(FOSSIL_ENABLE_SSL)!=0
1576 SSLDIR = $(B)\compat\openssl-1.1.1f
1577 SSLINCDIR = $(SSLDIR)\include
1578 !if $(FOSSIL_DYNAMIC_BUILD)!=0
1579 SSLLIBDIR = $(SSLDIR)
1580 !else
1581 SSLLIBDIR = $(SSLDIR)
1582
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -714,11 +714,11 @@
714 #### The directories where the OpenSSL include and library files are located.
715 # The recommended usage here is to use the Sysinternals junction tool
716 # to create a hard link between an "openssl-1.x" sub-directory of the
717 # Fossil source code directory and the target OpenSSL source directory.
718 #
719 OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1g
720 OPENSSLINCDIR = $(OPENSSLDIR)/include
721 OPENSSLLIBDIR = $(OPENSSLDIR)
722
723 #### Either the directory where the Tcl library is installed or the Tcl
724 # source code directory resides (depending on the value of the macro
@@ -1571,11 +1571,11 @@
1571 !ifndef USE_SEE
1572 USE_SEE = 0
1573 !endif
1574
1575 !if $(FOSSIL_ENABLE_SSL)!=0
1576 SSLDIR = $(B)\compat\openssl-1.1.1g
1577 SSLINCDIR = $(SSLDIR)\include
1578 !if $(FOSSIL_DYNAMIC_BUILD)!=0
1579 SSLLIBDIR = $(SSLDIR)
1580 !else
1581 SSLLIBDIR = $(SSLDIR)
1582
--- src/security_audit.c
+++ src/security_audit.c
@@ -122,11 +122,11 @@
122122
zAnonCap = db_text("", "SELECT fullcap(NULL)");
123123
zDevCap = db_text("", "SELECT fullcap('v')");
124124
zReadCap = db_text("", "SELECT fullcap('u')");
125125
zPubPages = db_get("public-pages",0);
126126
hasSelfReg = db_get_boolean("self-register",0);
127
- pCap = capability_add(0, db_get("default-perms",0));
127
+ pCap = capability_add(0, db_get("default-perms","u"));
128128
capability_expand(pCap);
129129
zSelfCap = capability_string(pCap);
130130
capability_free(pCap);
131131
if( hasAnyCap(zAnonCap,"as") ){
132132
@ <li><p>This repository is <big><b>Wildly INSECURE</b></big> because
133133
--- src/security_audit.c
+++ src/security_audit.c
@@ -122,11 +122,11 @@
122 zAnonCap = db_text("", "SELECT fullcap(NULL)");
123 zDevCap = db_text("", "SELECT fullcap('v')");
124 zReadCap = db_text("", "SELECT fullcap('u')");
125 zPubPages = db_get("public-pages",0);
126 hasSelfReg = db_get_boolean("self-register",0);
127 pCap = capability_add(0, db_get("default-perms",0));
128 capability_expand(pCap);
129 zSelfCap = capability_string(pCap);
130 capability_free(pCap);
131 if( hasAnyCap(zAnonCap,"as") ){
132 @ <li><p>This repository is <big><b>Wildly INSECURE</b></big> because
133
--- src/security_audit.c
+++ src/security_audit.c
@@ -122,11 +122,11 @@
122 zAnonCap = db_text("", "SELECT fullcap(NULL)");
123 zDevCap = db_text("", "SELECT fullcap('v')");
124 zReadCap = db_text("", "SELECT fullcap('u')");
125 zPubPages = db_get("public-pages",0);
126 hasSelfReg = db_get_boolean("self-register",0);
127 pCap = capability_add(0, db_get("default-perms","u"));
128 capability_expand(pCap);
129 zSelfCap = capability_string(pCap);
130 capability_free(pCap);
131 if( hasAnyCap(zAnonCap,"as") ){
132 @ <li><p>This repository is <big><b>Wildly INSECURE</b></big> because
133
+34 -7
--- src/setup.c
+++ src/setup.c
@@ -500,17 +500,44 @@
500500
@ (Property: "public-pages")
501501
@ </p>
502502
503503
@ <hr />
504504
onoff_attribute("Allow users to register themselves",
505
- "self-register", "selfregister", 0, 0);
506
- @ <p>Allow users to register themselves through the HTTP UI.
507
- @ The registration form always requires filling in a CAPTCHA
508
- @ (<em>auto-captcha</em> setting is ignored). Still, bear in mind that anyone
509
- @ can register under any user name. This option is useful for public projects
510
- @ where you do not want everyone in any ticket discussion to be named
511
- @ "Anonymous". (Property: "self-register")</p>
505
+ "self-register", "selfreg", 0, 0);
506
+ @ <p>Allow users to register themselves on the /register webpage.
507
+ @ A self-registration creates a new entry in the USER table and
508
+ @ perhaps also in the SUBSCRIBER table if email notification is
509
+ @ enabled.
510
+ @ (Property: "self-register")</p>
511
+
512
+ @ <hr />
513
+ onoff_attribute("Email verification required for self-registration",
514
+ "selfreg-verify", "sfverify", 0, 0);
515
+ @ <p>If enabled, self-registration creates a new entry in the USER table
516
+ @ with only capabilities "7". The default user capabilities are not
517
+ @ added until the email address associated with the self-registration
518
+ @ has been verified. This setting only makes sense if
519
+ @ email notifications are enabled.
520
+ @ (Property: "selfreg-verify")</p>
521
+
522
+ @ <hr />
523
+ onoff_attribute("Allow anonymous subscriptions",
524
+ "anon-subscribe", "anonsub", 1, 0);
525
+ @ <p>If disabled, email notification subscriptions are only allowed
526
+ @ for users with a login. If Nobody or Anonymous visit the /subscribe
527
+ @ page, they are redirected to /register or /login.
528
+ @ (Property: "anon-subscribe")</p>
529
+
530
+ @ <hr />
531
+ entry_attribute("Authorized subscription email addresses", 35,
532
+ "auth-sub-email", "asemail", "", 0);
533
+ @ <p>This is a comma-separated list of GLOB patterns that specify
534
+ @ email addresses that are authorized to subscriptions. If blank
535
+ @ (the usual case), then any email address can be used to self-register.
536
+ @ This setting is used to limit subscriptions to members of a particular
537
+ @ organization or group based on their email address.
538
+ @ (Property: "auth-sub-email")</p>
512539
513540
@ <hr />
514541
entry_attribute("Default privileges", 10, "default-perms",
515542
"defaultperms", "u", 0);
516543
@ <p>Permissions given to users that... <ul><li>register themselves using
517544
--- src/setup.c
+++ src/setup.c
@@ -500,17 +500,44 @@
500 @ (Property: "public-pages")
501 @ </p>
502
503 @ <hr />
504 onoff_attribute("Allow users to register themselves",
505 "self-register", "selfregister", 0, 0);
506 @ <p>Allow users to register themselves through the HTTP UI.
507 @ The registration form always requires filling in a CAPTCHA
508 @ (<em>auto-captcha</em> setting is ignored). Still, bear in mind that anyone
509 @ can register under any user name. This option is useful for public projects
510 @ where you do not want everyone in any ticket discussion to be named
511 @ "Anonymous". (Property: "self-register")</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
513 @ <hr />
514 entry_attribute("Default privileges", 10, "default-perms",
515 "defaultperms", "u", 0);
516 @ <p>Permissions given to users that... <ul><li>register themselves using
517
--- src/setup.c
+++ src/setup.c
@@ -500,17 +500,44 @@
500 @ (Property: "public-pages")
501 @ </p>
502
503 @ <hr />
504 onoff_attribute("Allow users to register themselves",
505 "self-register", "selfreg", 0, 0);
506 @ <p>Allow users to register themselves on the /register webpage.
507 @ A self-registration creates a new entry in the USER table and
508 @ perhaps also in the SUBSCRIBER table if email notification is
509 @ enabled.
510 @ (Property: "self-register")</p>
511
512 @ <hr />
513 onoff_attribute("Email verification required for self-registration",
514 "selfreg-verify", "sfverify", 0, 0);
515 @ <p>If enabled, self-registration creates a new entry in the USER table
516 @ with only capabilities "7". The default user capabilities are not
517 @ added until the email address associated with the self-registration
518 @ has been verified. This setting only makes sense if
519 @ email notifications are enabled.
520 @ (Property: "selfreg-verify")</p>
521
522 @ <hr />
523 onoff_attribute("Allow anonymous subscriptions",
524 "anon-subscribe", "anonsub", 1, 0);
525 @ <p>If disabled, email notification subscriptions are only allowed
526 @ for users with a login. If Nobody or Anonymous visit the /subscribe
527 @ page, they are redirected to /register or /login.
528 @ (Property: "anon-subscribe")</p>
529
530 @ <hr />
531 entry_attribute("Authorized subscription email addresses", 35,
532 "auth-sub-email", "asemail", "", 0);
533 @ <p>This is a comma-separated list of GLOB patterns that specify
534 @ email addresses that are authorized to subscriptions. If blank
535 @ (the usual case), then any email address can be used to self-register.
536 @ This setting is used to limit subscriptions to members of a particular
537 @ organization or group based on their email address.
538 @ (Property: "auth-sub-email")</p>
539
540 @ <hr />
541 entry_attribute("Default privileges", 10, "default-perms",
542 "defaultperms", "u", 0);
543 @ <p>Permissions given to users that... <ul><li>register themselves using
544
+5 -6
--- src/setupuser.c
+++ src/setupuser.c
@@ -549,18 +549,17 @@
549549
if( login_is_special(zLogin) ){
550550
@ <td><b>%h(zLogin)</b></td>
551551
}else{
552552
@ <td><input type="text" name="login" value="%h(zLogin)" />\
553553
if( alert_tables_exist() ){
554
- char *zSCode; /* Subscriber Code */
555
- zSCode = db_text(0, "SELECT hex(subscriberCode) FROM subscriber"
556
- " WHERE suname=%Q", zLogin);
557
- if( zSCode && zSCode[0] ){
558
- @ &nbsp;&nbsp;<a href="%R/alerts/%s(zSCode)">\
554
+ int sid;
555
+ sid = db_int(0, "SELECT subscriberId FROM subscriber"
556
+ " WHERE suname=%Q", zLogin);
557
+ if( sid>0 ){
558
+ @ &nbsp;&nbsp;<a href="%R/alerts?sid=%d(sid)">\
559559
@ (subscription info for %h(zLogin))</a>\
560560
}
561
- fossil_free(zSCode);
562561
}
563562
@ </td></tr>
564563
@ <tr>
565564
@ <td class="usetupEditLabel">Contact&nbsp;Info:</td>
566565
@ <td><textarea name="info" cols="40" rows="2">%h(zInfo)</textarea></td>
567566
--- src/setupuser.c
+++ src/setupuser.c
@@ -549,18 +549,17 @@
549 if( login_is_special(zLogin) ){
550 @ <td><b>%h(zLogin)</b></td>
551 }else{
552 @ <td><input type="text" name="login" value="%h(zLogin)" />\
553 if( alert_tables_exist() ){
554 char *zSCode; /* Subscriber Code */
555 zSCode = db_text(0, "SELECT hex(subscriberCode) FROM subscriber"
556 " WHERE suname=%Q", zLogin);
557 if( zSCode && zSCode[0] ){
558 @ &nbsp;&nbsp;<a href="%R/alerts/%s(zSCode)">\
559 @ (subscription info for %h(zLogin))</a>\
560 }
561 fossil_free(zSCode);
562 }
563 @ </td></tr>
564 @ <tr>
565 @ <td class="usetupEditLabel">Contact&nbsp;Info:</td>
566 @ <td><textarea name="info" cols="40" rows="2">%h(zInfo)</textarea></td>
567
--- src/setupuser.c
+++ src/setupuser.c
@@ -549,18 +549,17 @@
549 if( login_is_special(zLogin) ){
550 @ <td><b>%h(zLogin)</b></td>
551 }else{
552 @ <td><input type="text" name="login" value="%h(zLogin)" />\
553 if( alert_tables_exist() ){
554 int sid;
555 sid = db_int(0, "SELECT subscriberId FROM subscriber"
556 " WHERE suname=%Q", zLogin);
557 if( sid>0 ){
558 @ &nbsp;&nbsp;<a href="%R/alerts?sid=%d(sid)">\
559 @ (subscription info for %h(zLogin))</a>\
560 }
 
561 }
562 @ </td></tr>
563 @ <tr>
564 @ <td class="usetupEditLabel">Contact&nbsp;Info:</td>
565 @ <td><textarea name="info" cols="40" rows="2">%h(zInfo)</textarea></td>
566
+55
--- src/skins.c
+++ src/skins.c
@@ -688,10 +688,62 @@
688688
}
689689
}
690690
return zResult;
691691
}
692692
693
+extern const struct strctCssDefaults {
694
+/* From the generated default_css.h, which we cannot #include here
695
+** without causing an ODR violation.
696
+*/
697
+ const char *elementClass; /* Name of element needed */
698
+ const char *value; /* CSS text */
699
+} cssDefaultList[];
700
+
701
+/*
702
+** Emits the list of built-in default CSS selectors. Intended
703
+** for use only from the /setup_skinedit page.
704
+*/
705
+static void skin_emit_css_defaults(){
706
+ struct strctCssDefaults const * pCss;
707
+ fossil_print("<h1>CSS Defaults</h1>");
708
+ fossil_print("If a skin defines any of the following CSS selectors, "
709
+ "that definition replaces the default, as opposed to "
710
+ "cascading from it. ");
711
+ fossil_print("See <a href=\"https://fossil-scm.org/fossil/"
712
+ "doc/trunk/www/css-tricks.md\">this "
713
+ "document</a> for more details.");
714
+ /* To discuss: do we want to list only the default selectors or
715
+ ** also their default values? The latter increases the size of the
716
+ ** page considerably, but is arguably more useful. We could, of
717
+ ** course, offer a URL param to toggle the view, but that currently
718
+ ** seems like overkill.
719
+ **
720
+ ** Be sure to adjust the default_css.txt #setup_skinedit_css entry
721
+ ** for whichever impl ends up being selected.
722
+ */
723
+#if 1
724
+ /* List impl which elides style values */
725
+ fossil_print("<div class=\"columns\" "
726
+ "id=\"setup_skinedit_css_defaults\"><ul>");
727
+ for(pCss = &cssDefaultList[0]; pCss->value!=0; ++pCss){
728
+ fossil_print("<li>%s</li>", pCss->elementClass);
729
+ }
730
+ fossil_print("</ul>");
731
+#else
732
+ /* Table impl which also includes style values. */
733
+ fossil_print("<table id=\"setup_skinedit_css_defaults\"><tbody>");
734
+ for(pCss = &cssDefaultList[0]; pCss->value!=0; ++pCss){
735
+ fossil_print("<tr><td>%s</td>", pCss->elementClass);
736
+ /* A TD element apparently cannot be told to scroll its contents,
737
+ ** so we require a DIV inside the value TD to scroll the long
738
+ ** url(data:...) entries. */
739
+ fossil_print("<td><div>%s</div></td>", pCss->value);
740
+ fossil_print("</td></tr>");
741
+ }
742
+ fossil_print("</tbody></table>");
743
+#endif
744
+}
693745
694746
/*
695747
** WEBPAGE: setup_skinedit
696748
**
697749
** Edit aspects of a skin determined by the w= query parameter.
@@ -814,10 +866,13 @@
814866
blob_reset(&from);
815867
blob_reset(&to);
816868
blob_reset(&out);
817869
}
818870
@ </div></form>
871
+ if(ii==0/*CSS*/){
872
+ skin_emit_css_defaults();
873
+ }
819874
style_footer();
820875
db_end_transaction(0);
821876
}
822877
823878
/*
824879
--- src/skins.c
+++ src/skins.c
@@ -688,10 +688,62 @@
688 }
689 }
690 return zResult;
691 }
692
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
693
694 /*
695 ** WEBPAGE: setup_skinedit
696 **
697 ** Edit aspects of a skin determined by the w= query parameter.
@@ -814,10 +866,13 @@
814 blob_reset(&from);
815 blob_reset(&to);
816 blob_reset(&out);
817 }
818 @ </div></form>
 
 
 
819 style_footer();
820 db_end_transaction(0);
821 }
822
823 /*
824
--- src/skins.c
+++ src/skins.c
@@ -688,10 +688,62 @@
688 }
689 }
690 return zResult;
691 }
692
693 extern const struct strctCssDefaults {
694 /* From the generated default_css.h, which we cannot #include here
695 ** without causing an ODR violation.
696 */
697 const char *elementClass; /* Name of element needed */
698 const char *value; /* CSS text */
699 } cssDefaultList[];
700
701 /*
702 ** Emits the list of built-in default CSS selectors. Intended
703 ** for use only from the /setup_skinedit page.
704 */
705 static void skin_emit_css_defaults(){
706 struct strctCssDefaults const * pCss;
707 fossil_print("<h1>CSS Defaults</h1>");
708 fossil_print("If a skin defines any of the following CSS selectors, "
709 "that definition replaces the default, as opposed to "
710 "cascading from it. ");
711 fossil_print("See <a href=\"https://fossil-scm.org/fossil/"
712 "doc/trunk/www/css-tricks.md\">this "
713 "document</a> for more details.");
714 /* To discuss: do we want to list only the default selectors or
715 ** also their default values? The latter increases the size of the
716 ** page considerably, but is arguably more useful. We could, of
717 ** course, offer a URL param to toggle the view, but that currently
718 ** seems like overkill.
719 **
720 ** Be sure to adjust the default_css.txt #setup_skinedit_css entry
721 ** for whichever impl ends up being selected.
722 */
723 #if 1
724 /* List impl which elides style values */
725 fossil_print("<div class=\"columns\" "
726 "id=\"setup_skinedit_css_defaults\"><ul>");
727 for(pCss = &cssDefaultList[0]; pCss->value!=0; ++pCss){
728 fossil_print("<li>%s</li>", pCss->elementClass);
729 }
730 fossil_print("</ul>");
731 #else
732 /* Table impl which also includes style values. */
733 fossil_print("<table id=\"setup_skinedit_css_defaults\"><tbody>");
734 for(pCss = &cssDefaultList[0]; pCss->value!=0; ++pCss){
735 fossil_print("<tr><td>%s</td>", pCss->elementClass);
736 /* A TD element apparently cannot be told to scroll its contents,
737 ** so we require a DIV inside the value TD to scroll the long
738 ** url(data:...) entries. */
739 fossil_print("<td><div>%s</div></td>", pCss->value);
740 fossil_print("</td></tr>");
741 }
742 fossil_print("</tbody></table>");
743 #endif
744 }
745
746 /*
747 ** WEBPAGE: setup_skinedit
748 **
749 ** Edit aspects of a skin determined by the w= query parameter.
@@ -814,10 +866,13 @@
866 blob_reset(&from);
867 blob_reset(&to);
868 blob_reset(&out);
869 }
870 @ </div></form>
871 if(ii==0/*CSS*/){
872 skin_emit_css_defaults();
873 }
874 style_footer();
875 db_end_transaction(0);
876 }
877
878 /*
879
+1 -1
--- src/tar.c
+++ src/tar.c
@@ -443,11 +443,11 @@
443443
tar_begin(-1);
444444
for(i=3; i<g.argc; i++){
445445
Blob file;
446446
blob_zero(&file);
447447
blob_read_from_file(&file, g.argv[i], eFType);
448
- tar_add_file(g.argv[i], &file, file_perm(0,0), file_mtime(0,0));
448
+ tar_add_file(g.argv[i], &file, file_perm(0,eFType), file_mtime(0,eFType));
449449
blob_reset(&file);
450450
}
451451
tar_finish(&zip);
452452
blob_write_to_file(&zip, g.argv[2]);
453453
}
454454
--- src/tar.c
+++ src/tar.c
@@ -443,11 +443,11 @@
443 tar_begin(-1);
444 for(i=3; i<g.argc; i++){
445 Blob file;
446 blob_zero(&file);
447 blob_read_from_file(&file, g.argv[i], eFType);
448 tar_add_file(g.argv[i], &file, file_perm(0,0), file_mtime(0,0));
449 blob_reset(&file);
450 }
451 tar_finish(&zip);
452 blob_write_to_file(&zip, g.argv[2]);
453 }
454
--- src/tar.c
+++ src/tar.c
@@ -443,11 +443,11 @@
443 tar_begin(-1);
444 for(i=3; i<g.argc; i++){
445 Blob file;
446 blob_zero(&file);
447 blob_read_from_file(&file, g.argv[i], eFType);
448 tar_add_file(g.argv[i], &file, file_perm(0,eFType), file_mtime(0,eFType));
449 blob_reset(&file);
450 }
451 tar_finish(&zip);
452 blob_write_to_file(&zip, g.argv[2]);
453 }
454
+15 -1
--- src/timeline.c
+++ src/timeline.c
@@ -1877,11 +1877,25 @@
18771877
" HAVING count(*)>1;\n"
18781878
"INSERT OR IGNORE INTO rnfork(rid)"
18791879
" SELECT cid FROM plink\n"
18801880
" WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
18811881
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
1882
- " AND pid IN rnfork;",
1882
+ " GROUP BY cid"
1883
+ " HAVING count(*)>1;\n",
1884
+ TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
1885
+ );
1886
+ db_multi_exec(
1887
+ "INSERT OR IGNORE INTO rnfork(rid)\n"
1888
+ " SELECT cid FROM plink\n"
1889
+ " WHERE pid IN rnfork"
1890
+ " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
1891
+ " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
1892
+ " UNION "
1893
+ " SELECT pid FROM plink\n"
1894
+ " WHERE cid IN rnfork"
1895
+ " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
1896
+ " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n",
18831897
TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
18841898
);
18851899
tmFlags |= TIMELINE_UNHIDE;
18861900
zType = "ci";
18871901
disableY = 1;
18881902
--- src/timeline.c
+++ src/timeline.c
@@ -1877,11 +1877,25 @@
1877 " HAVING count(*)>1;\n"
1878 "INSERT OR IGNORE INTO rnfork(rid)"
1879 " SELECT cid FROM plink\n"
1880 " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
1881 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
1882 " AND pid IN rnfork;",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1883 TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
1884 );
1885 tmFlags |= TIMELINE_UNHIDE;
1886 zType = "ci";
1887 disableY = 1;
1888
--- src/timeline.c
+++ src/timeline.c
@@ -1877,11 +1877,25 @@
1877 " HAVING count(*)>1;\n"
1878 "INSERT OR IGNORE INTO rnfork(rid)"
1879 " SELECT cid FROM plink\n"
1880 " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
1881 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
1882 " GROUP BY cid"
1883 " HAVING count(*)>1;\n",
1884 TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
1885 );
1886 db_multi_exec(
1887 "INSERT OR IGNORE INTO rnfork(rid)\n"
1888 " SELECT cid FROM plink\n"
1889 " WHERE pid IN rnfork"
1890 " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
1891 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
1892 " UNION "
1893 " SELECT pid FROM plink\n"
1894 " WHERE cid IN rnfork"
1895 " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
1896 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n",
1897 TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
1898 );
1899 tmFlags |= TIMELINE_UNHIDE;
1900 zType = "ci";
1901 disableY = 1;
1902
+49 -10
--- src/unversioned.c
+++ src/unversioned.c
@@ -237,11 +237,14 @@
237237
** edit FILE Bring up FILE in a text editor for modification.
238238
**
239239
** export FILE OUTPUT Write the content of FILE into OUTPUT on disk
240240
**
241241
** list | ls Show all unversioned files held in the local
242
-** repository.
242
+** repository. Options:
243
+**
244
+** --glob PATTERN Show only files that match
245
+** --like PATTERN Show only files that match
243246
**
244247
** revert ?URL? Restore the state of all unversioned files in the
245248
** local repository to match the remote repository
246249
** URL.
247250
**
@@ -250,11 +253,14 @@
250253
** -n|--dryrun Show what would have happened
251254
**
252255
** remove|rm|delete FILE ...
253256
** Remove unversioned files from the local repository.
254257
** Changes are not pushed to other repositories until
255
-** the next sync.
258
+** the next sync. Options:
259
+**
260
+** --glob PATTERN Remove files that match
261
+** --like PATTERN Remove files that match
256262
**
257263
** sync ?URL? Synchronize the state of all unversioned files with
258264
** the remote repository URL. The most recent version
259265
** of each file is propagated to all repositories and
260266
** all prior versions are permanently forgotten.
@@ -339,11 +345,13 @@
339345
340346
verify_all_options();
341347
if( g.argc!=4) usage("edit UVFILE");
342348
zUVFile = g.argv[3];
343349
zEditor = fossil_text_editor();
344
- if( zEditor==0 ) fossil_fatal("no text editor - set the VISUAL env variable");
350
+ if( zEditor==0 ){
351
+ fossil_fatal("no text editor - set the VISUAL env variable");
352
+ }
345353
zTFile = fossil_temp_filename();
346354
if( zTFile==0 ) fossil_fatal("cannot find a temporary filename");
347355
db_begin_transaction();
348356
content_rcvid_init("#!fossil unversioned edit");
349357
if( unversioned_content(zUVFile, &content) ){
@@ -386,26 +394,40 @@
386394
fossil_print("%s\n", unversioned_content_hash(debugFlag));
387395
}else if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
388396
Stmt q;
389397
int allFlag = find_option("all","a",0)!=0;
390398
int longFlag = find_option("l",0,0)!=0 || (nCmd>1 && zCmd[1]=='i');
399
+ char *zPattern = sqlite3_mprintf("true");
400
+ const char *zGlob;
401
+ zGlob = find_option("glob",0,1);
402
+ if( zGlob ){
403
+ sqlite3_free(zPattern);
404
+ zPattern = sqlite3_mprintf("(name GLOB %Q)", zGlob);
405
+ }
406
+ zGlob = find_option("like",0,1);
407
+ if( zGlob ){
408
+ sqlite3_free(zPattern);
409
+ zPattern = sqlite3_mprintf("(name LIKE %Q)", zGlob);
410
+ }
391411
verify_all_options();
392412
if( !longFlag ){
393413
if( allFlag ){
394
- db_prepare(&q, "SELECT name FROM unversioned ORDER BY name");
414
+ db_prepare(&q, "SELECT name FROM unversioned WHERE %s ORDER BY name",
415
+ zPattern/*safe-for-%s*/);
395416
}else{
396
- db_prepare(&q, "SELECT name FROM unversioned WHERE hash IS NOT NULL"
397
- " ORDER BY name");
417
+ db_prepare(&q, "SELECT name FROM unversioned"
418
+ " WHERE %s AND hash IS NOT NULL"
419
+ " ORDER BY name", zPattern/*safe-for-%s*/);
398420
}
399421
while( db_step(&q)==SQLITE_ROW ){
400422
fossil_print("%s\n", db_column_text(&q,0));
401423
}
402424
}else{
403425
db_prepare(&q,
404426
"SELECT hash, datetime(mtime,'unixepoch'), sz, length(content), name"
405
- " FROM unversioned"
406
- " ORDER BY name;"
427
+ " FROM unversioned WHERE %s"
428
+ " ORDER BY name;", zPattern/*safe-for-%s*/
407429
);
408430
while( db_step(&q)==SQLITE_ROW ){
409431
const char *zHash = db_column_text(&q, 0);
410432
const char *zNoContent = "";
411433
if( zHash==0 ){
@@ -423,20 +445,37 @@
423445
zNoContent
424446
);
425447
}
426448
}
427449
db_finalize(&q);
450
+ sqlite3_free(zPattern);
428451
}else if( memcmp(zCmd, "revert", nCmd)==0 ){
429
- unsigned syncFlags = unversioned_sync_flags(SYNC_UNVERSIONED|SYNC_UV_REVERT);
452
+ unsigned syncFlags =
453
+ unversioned_sync_flags(SYNC_UNVERSIONED|SYNC_UV_REVERT);
430454
g.argv[1] = "sync";
431455
g.argv[2] = "--uv-noop";
432456
sync_unversioned(syncFlags);
433457
}else if( memcmp(zCmd, "remove", nCmd)==0 || memcmp(zCmd, "rm", nCmd)==0
434458
|| memcmp(zCmd, "delete", nCmd)==0 ){
435459
int i;
436
- verify_all_options();
460
+ const char *zGlob;
437461
db_begin_transaction();
462
+ while( (zGlob = find_option("glob",0,1))!=0 ){
463
+ db_multi_exec(
464
+ "UPDATE unversioned"
465
+ " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name GLOB %Q",
466
+ mtime, zGlob
467
+ );
468
+ }
469
+ while( (zGlob = find_option("like",0,1))!=0 ){
470
+ db_multi_exec(
471
+ "UPDATE unversioned"
472
+ " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name LIKE %Q",
473
+ mtime, zGlob
474
+ );
475
+ }
476
+ verify_all_options();
438477
for(i=3; i<g.argc; i++){
439478
db_multi_exec(
440479
"UPDATE unversioned"
441480
" SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name=%Q",
442481
mtime, g.argv[i]
443482
--- src/unversioned.c
+++ src/unversioned.c
@@ -237,11 +237,14 @@
237 ** edit FILE Bring up FILE in a text editor for modification.
238 **
239 ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk
240 **
241 ** list | ls Show all unversioned files held in the local
242 ** repository.
 
 
 
243 **
244 ** revert ?URL? Restore the state of all unversioned files in the
245 ** local repository to match the remote repository
246 ** URL.
247 **
@@ -250,11 +253,14 @@
250 ** -n|--dryrun Show what would have happened
251 **
252 ** remove|rm|delete FILE ...
253 ** Remove unversioned files from the local repository.
254 ** Changes are not pushed to other repositories until
255 ** the next sync.
 
 
 
256 **
257 ** sync ?URL? Synchronize the state of all unversioned files with
258 ** the remote repository URL. The most recent version
259 ** of each file is propagated to all repositories and
260 ** all prior versions are permanently forgotten.
@@ -339,11 +345,13 @@
339
340 verify_all_options();
341 if( g.argc!=4) usage("edit UVFILE");
342 zUVFile = g.argv[3];
343 zEditor = fossil_text_editor();
344 if( zEditor==0 ) fossil_fatal("no text editor - set the VISUAL env variable");
 
 
345 zTFile = fossil_temp_filename();
346 if( zTFile==0 ) fossil_fatal("cannot find a temporary filename");
347 db_begin_transaction();
348 content_rcvid_init("#!fossil unversioned edit");
349 if( unversioned_content(zUVFile, &content) ){
@@ -386,26 +394,40 @@
386 fossil_print("%s\n", unversioned_content_hash(debugFlag));
387 }else if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
388 Stmt q;
389 int allFlag = find_option("all","a",0)!=0;
390 int longFlag = find_option("l",0,0)!=0 || (nCmd>1 && zCmd[1]=='i');
 
 
 
 
 
 
 
 
 
 
 
 
391 verify_all_options();
392 if( !longFlag ){
393 if( allFlag ){
394 db_prepare(&q, "SELECT name FROM unversioned ORDER BY name");
 
395 }else{
396 db_prepare(&q, "SELECT name FROM unversioned WHERE hash IS NOT NULL"
397 " ORDER BY name");
 
398 }
399 while( db_step(&q)==SQLITE_ROW ){
400 fossil_print("%s\n", db_column_text(&q,0));
401 }
402 }else{
403 db_prepare(&q,
404 "SELECT hash, datetime(mtime,'unixepoch'), sz, length(content), name"
405 " FROM unversioned"
406 " ORDER BY name;"
407 );
408 while( db_step(&q)==SQLITE_ROW ){
409 const char *zHash = db_column_text(&q, 0);
410 const char *zNoContent = "";
411 if( zHash==0 ){
@@ -423,20 +445,37 @@
423 zNoContent
424 );
425 }
426 }
427 db_finalize(&q);
 
428 }else if( memcmp(zCmd, "revert", nCmd)==0 ){
429 unsigned syncFlags = unversioned_sync_flags(SYNC_UNVERSIONED|SYNC_UV_REVERT);
 
430 g.argv[1] = "sync";
431 g.argv[2] = "--uv-noop";
432 sync_unversioned(syncFlags);
433 }else if( memcmp(zCmd, "remove", nCmd)==0 || memcmp(zCmd, "rm", nCmd)==0
434 || memcmp(zCmd, "delete", nCmd)==0 ){
435 int i;
436 verify_all_options();
437 db_begin_transaction();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438 for(i=3; i<g.argc; i++){
439 db_multi_exec(
440 "UPDATE unversioned"
441 " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name=%Q",
442 mtime, g.argv[i]
443
--- src/unversioned.c
+++ src/unversioned.c
@@ -237,11 +237,14 @@
237 ** edit FILE Bring up FILE in a text editor for modification.
238 **
239 ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk
240 **
241 ** list | ls Show all unversioned files held in the local
242 ** repository. Options:
243 **
244 ** --glob PATTERN Show only files that match
245 ** --like PATTERN Show only files that match
246 **
247 ** revert ?URL? Restore the state of all unversioned files in the
248 ** local repository to match the remote repository
249 ** URL.
250 **
@@ -250,11 +253,14 @@
253 ** -n|--dryrun Show what would have happened
254 **
255 ** remove|rm|delete FILE ...
256 ** Remove unversioned files from the local repository.
257 ** Changes are not pushed to other repositories until
258 ** the next sync. Options:
259 **
260 ** --glob PATTERN Remove files that match
261 ** --like PATTERN Remove files that match
262 **
263 ** sync ?URL? Synchronize the state of all unversioned files with
264 ** the remote repository URL. The most recent version
265 ** of each file is propagated to all repositories and
266 ** all prior versions are permanently forgotten.
@@ -339,11 +345,13 @@
345
346 verify_all_options();
347 if( g.argc!=4) usage("edit UVFILE");
348 zUVFile = g.argv[3];
349 zEditor = fossil_text_editor();
350 if( zEditor==0 ){
351 fossil_fatal("no text editor - set the VISUAL env variable");
352 }
353 zTFile = fossil_temp_filename();
354 if( zTFile==0 ) fossil_fatal("cannot find a temporary filename");
355 db_begin_transaction();
356 content_rcvid_init("#!fossil unversioned edit");
357 if( unversioned_content(zUVFile, &content) ){
@@ -386,26 +394,40 @@
394 fossil_print("%s\n", unversioned_content_hash(debugFlag));
395 }else if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){
396 Stmt q;
397 int allFlag = find_option("all","a",0)!=0;
398 int longFlag = find_option("l",0,0)!=0 || (nCmd>1 && zCmd[1]=='i');
399 char *zPattern = sqlite3_mprintf("true");
400 const char *zGlob;
401 zGlob = find_option("glob",0,1);
402 if( zGlob ){
403 sqlite3_free(zPattern);
404 zPattern = sqlite3_mprintf("(name GLOB %Q)", zGlob);
405 }
406 zGlob = find_option("like",0,1);
407 if( zGlob ){
408 sqlite3_free(zPattern);
409 zPattern = sqlite3_mprintf("(name LIKE %Q)", zGlob);
410 }
411 verify_all_options();
412 if( !longFlag ){
413 if( allFlag ){
414 db_prepare(&q, "SELECT name FROM unversioned WHERE %s ORDER BY name",
415 zPattern/*safe-for-%s*/);
416 }else{
417 db_prepare(&q, "SELECT name FROM unversioned"
418 " WHERE %s AND hash IS NOT NULL"
419 " ORDER BY name", zPattern/*safe-for-%s*/);
420 }
421 while( db_step(&q)==SQLITE_ROW ){
422 fossil_print("%s\n", db_column_text(&q,0));
423 }
424 }else{
425 db_prepare(&q,
426 "SELECT hash, datetime(mtime,'unixepoch'), sz, length(content), name"
427 " FROM unversioned WHERE %s"
428 " ORDER BY name;", zPattern/*safe-for-%s*/
429 );
430 while( db_step(&q)==SQLITE_ROW ){
431 const char *zHash = db_column_text(&q, 0);
432 const char *zNoContent = "";
433 if( zHash==0 ){
@@ -423,20 +445,37 @@
445 zNoContent
446 );
447 }
448 }
449 db_finalize(&q);
450 sqlite3_free(zPattern);
451 }else if( memcmp(zCmd, "revert", nCmd)==0 ){
452 unsigned syncFlags =
453 unversioned_sync_flags(SYNC_UNVERSIONED|SYNC_UV_REVERT);
454 g.argv[1] = "sync";
455 g.argv[2] = "--uv-noop";
456 sync_unversioned(syncFlags);
457 }else if( memcmp(zCmd, "remove", nCmd)==0 || memcmp(zCmd, "rm", nCmd)==0
458 || memcmp(zCmd, "delete", nCmd)==0 ){
459 int i;
460 const char *zGlob;
461 db_begin_transaction();
462 while( (zGlob = find_option("glob",0,1))!=0 ){
463 db_multi_exec(
464 "UPDATE unversioned"
465 " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name GLOB %Q",
466 mtime, zGlob
467 );
468 }
469 while( (zGlob = find_option("like",0,1))!=0 ){
470 db_multi_exec(
471 "UPDATE unversioned"
472 " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name LIKE %Q",
473 mtime, zGlob
474 );
475 }
476 verify_all_options();
477 for(i=3; i<g.argc; i++){
478 db_multi_exec(
479 "UPDATE unversioned"
480 " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name=%Q",
481 mtime, g.argv[i]
482
+18 -14
--- src/url.c
+++ src/url.c
@@ -66,20 +66,10 @@
6666
int proxyOrigPort; /* Tunneled port number for https through proxy */
6767
};
6868
#endif /* INTERFACE */
6969
7070
71
-/*
72
-** Convert a string to lower-case.
73
-*/
74
-static void url_tolower(char *z){
75
- while( *z ){
76
- *z = fossil_tolower(*z);
77
- z++;
78
- }
79
-}
80
-
8171
/*
8272
** Parse the given URL. Populate members of the provided UrlData structure
8373
** as follows:
8474
**
8575
** isFile True if FILE:
@@ -177,11 +167,11 @@
177167
pUrlData->name++;
178168
pUrlData->name[n-2] = 0;
179169
}
180170
zLogin = mprintf("");
181171
}
182
- url_tolower(pUrlData->name);
172
+ fossil_strtolwr(pUrlData->name);
183173
if( c==':' ){
184174
pUrlData->port = 0;
185175
i++;
186176
while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){
187177
pUrlData->port = pUrlData->port*10 + c - '0';
@@ -370,18 +360,32 @@
370360
/*
371361
** Extract any proxy options from the command-line.
372362
**
373363
** --proxy URL|off
374364
**
375
-** This also happens to be a convenient function to use to look for
376
-** the --nosync option that will temporarily disable the "autosync"
377
-** feature.
365
+** The original purpose of this routine is the above. But this
366
+** also happens to be a convenient place to look for other
367
+** network-related options:
368
+**
369
+** --nosync Temporarily disable "autosync"
370
+**
371
+** --ipv4 Disallow IPv6. Use only IPv4.
372
+**
373
+** --accept-any-cert Disable server SSL cert validation. Accept
374
+** any SSL cert that the server provides.
375
+** WARNING: this option opens you up to
376
+** forged-DNS and man-in-the-middle attacks!
378377
*/
379378
void url_proxy_options(void){
380379
zProxyOpt = find_option("proxy", 0, 1);
381380
if( find_option("nosync",0,0) ) g.fNoSync = 1;
382381
if( find_option("ipv4",0,0) ) g.fIPv4 = 1;
382
+#ifdef FOSSIL_ENABLE_SSL
383
+ if( find_option("accept-any-cert",0,0) ){
384
+ ssl_disable_cert_verification();
385
+ }
386
+#endif /* FOSSIL_ENABLE_SSL */
383387
}
384388
385389
/*
386390
** If the "proxy" setting is defined, then change the URL settings
387391
** (initialized by a prior call to url_parse()) so that the HTTP
388392
--- src/url.c
+++ src/url.c
@@ -66,20 +66,10 @@
66 int proxyOrigPort; /* Tunneled port number for https through proxy */
67 };
68 #endif /* INTERFACE */
69
70
71 /*
72 ** Convert a string to lower-case.
73 */
74 static void url_tolower(char *z){
75 while( *z ){
76 *z = fossil_tolower(*z);
77 z++;
78 }
79 }
80
81 /*
82 ** Parse the given URL. Populate members of the provided UrlData structure
83 ** as follows:
84 **
85 ** isFile True if FILE:
@@ -177,11 +167,11 @@
177 pUrlData->name++;
178 pUrlData->name[n-2] = 0;
179 }
180 zLogin = mprintf("");
181 }
182 url_tolower(pUrlData->name);
183 if( c==':' ){
184 pUrlData->port = 0;
185 i++;
186 while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){
187 pUrlData->port = pUrlData->port*10 + c - '0';
@@ -370,18 +360,32 @@
370 /*
371 ** Extract any proxy options from the command-line.
372 **
373 ** --proxy URL|off
374 **
375 ** This also happens to be a convenient function to use to look for
376 ** the --nosync option that will temporarily disable the "autosync"
377 ** feature.
 
 
 
 
 
 
 
 
 
378 */
379 void url_proxy_options(void){
380 zProxyOpt = find_option("proxy", 0, 1);
381 if( find_option("nosync",0,0) ) g.fNoSync = 1;
382 if( find_option("ipv4",0,0) ) g.fIPv4 = 1;
 
 
 
 
 
383 }
384
385 /*
386 ** If the "proxy" setting is defined, then change the URL settings
387 ** (initialized by a prior call to url_parse()) so that the HTTP
388
--- src/url.c
+++ src/url.c
@@ -66,20 +66,10 @@
66 int proxyOrigPort; /* Tunneled port number for https through proxy */
67 };
68 #endif /* INTERFACE */
69
70
 
 
 
 
 
 
 
 
 
 
71 /*
72 ** Parse the given URL. Populate members of the provided UrlData structure
73 ** as follows:
74 **
75 ** isFile True if FILE:
@@ -177,11 +167,11 @@
167 pUrlData->name++;
168 pUrlData->name[n-2] = 0;
169 }
170 zLogin = mprintf("");
171 }
172 fossil_strtolwr(pUrlData->name);
173 if( c==':' ){
174 pUrlData->port = 0;
175 i++;
176 while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){
177 pUrlData->port = pUrlData->port*10 + c - '0';
@@ -370,18 +360,32 @@
360 /*
361 ** Extract any proxy options from the command-line.
362 **
363 ** --proxy URL|off
364 **
365 ** The original purpose of this routine is the above. But this
366 ** also happens to be a convenient place to look for other
367 ** network-related options:
368 **
369 ** --nosync Temporarily disable "autosync"
370 **
371 ** --ipv4 Disallow IPv6. Use only IPv4.
372 **
373 ** --accept-any-cert Disable server SSL cert validation. Accept
374 ** any SSL cert that the server provides.
375 ** WARNING: this option opens you up to
376 ** forged-DNS and man-in-the-middle attacks!
377 */
378 void url_proxy_options(void){
379 zProxyOpt = find_option("proxy", 0, 1);
380 if( find_option("nosync",0,0) ) g.fNoSync = 1;
381 if( find_option("ipv4",0,0) ) g.fIPv4 = 1;
382 #ifdef FOSSIL_ENABLE_SSL
383 if( find_option("accept-any-cert",0,0) ){
384 ssl_disable_cert_verification();
385 }
386 #endif /* FOSSIL_ENABLE_SSL */
387 }
388
389 /*
390 ** If the "proxy" setting is defined, then change the URL settings
391 ** (initialized by a prior call to url_parse()) so that the HTTP
392
+15
--- src/util.c
+++ src/util.c
@@ -140,10 +140,25 @@
140140
}
141141
#else
142142
fossil_free(p);
143143
#endif
144144
}
145
+
146
+/*
147
+** Translate every upper-case character in the input string into
148
+** its equivalent lower-case.
149
+*/
150
+char *fossil_strtolwr(char *zIn){
151
+ char *zStart = zIn;
152
+ if( zIn ){
153
+ while( *zIn ){
154
+ *zIn = fossil_tolower(*zIn);
155
+ zIn++;
156
+ }
157
+ }
158
+ return zStart;
159
+}
145160
146161
/*
147162
** This function implements a cross-platform "system()" interface.
148163
*/
149164
int fossil_system(const char *zOrigCmd){
150165
--- src/util.c
+++ src/util.c
@@ -140,10 +140,25 @@
140 }
141 #else
142 fossil_free(p);
143 #endif
144 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
146 /*
147 ** This function implements a cross-platform "system()" interface.
148 */
149 int fossil_system(const char *zOrigCmd){
150
--- src/util.c
+++ src/util.c
@@ -140,10 +140,25 @@
140 }
141 #else
142 fossil_free(p);
143 #endif
144 }
145
146 /*
147 ** Translate every upper-case character in the input string into
148 ** its equivalent lower-case.
149 */
150 char *fossil_strtolwr(char *zIn){
151 char *zStart = zIn;
152 if( zIn ){
153 while( *zIn ){
154 *zIn = fossil_tolower(*zIn);
155 zIn++;
156 }
157 }
158 return zStart;
159 }
160
161 /*
162 ** This function implements a cross-platform "system()" interface.
163 */
164 int fossil_system(const char *zOrigCmd){
165
+1 -1
--- src/zip.c
+++ src/zip.c
@@ -592,11 +592,11 @@
592592
}
593593
zip_open();
594594
for(i=3; i<g.argc; i++){
595595
blob_zero(&file);
596596
blob_read_from_file(&file, g.argv[i], eFType);
597
- zip_add_file(&sArchive, g.argv[i], &file, file_perm(0,0));
597
+ zip_add_file(&sArchive, g.argv[i], &file, file_perm(0,eFType));
598598
blob_reset(&file);
599599
}
600600
zip_close(&sArchive);
601601
blob_write_to_file(&zip, g.argv[2]);
602602
}
603603
--- src/zip.c
+++ src/zip.c
@@ -592,11 +592,11 @@
592 }
593 zip_open();
594 for(i=3; i<g.argc; i++){
595 blob_zero(&file);
596 blob_read_from_file(&file, g.argv[i], eFType);
597 zip_add_file(&sArchive, g.argv[i], &file, file_perm(0,0));
598 blob_reset(&file);
599 }
600 zip_close(&sArchive);
601 blob_write_to_file(&zip, g.argv[2]);
602 }
603
--- src/zip.c
+++ src/zip.c
@@ -592,11 +592,11 @@
592 }
593 zip_open();
594 for(i=3; i<g.argc; i++){
595 blob_zero(&file);
596 blob_read_from_file(&file, g.argv[i], eFType);
597 zip_add_file(&sArchive, g.argv[i], &file, file_perm(0,eFType));
598 blob_reset(&file);
599 }
600 zip_close(&sArchive);
601 blob_write_to_file(&zip, g.argv[2]);
602 }
603
+1 -1
--- test/tester.tcl
+++ test/tester.tcl
@@ -391,11 +391,11 @@
391391
if {[info exists ::env(FOSSIL_TEST_DANGEROUS_IGNORE_OPEN_CHECKOUT)] && \
392392
$::env(FOSSIL_TEST_DANGEROUS_IGNORE_OPEN_CHECKOUT) eq "YES_DO_IT"} {
393393
return
394394
}
395395
catch {exec $::fossilexe info} res
396
- if {![regexp {use --repository} $res]} {
396
+ if {[regexp {local-root:} $res]} {
397397
set projectName <unknown>
398398
set localRoot <unknown>
399399
regexp -line -- {^project-name: (.*)$} $res dummy projectName
400400
set projectName [string trim $projectName]
401401
regexp -line -- {^local-root: (.*)$} $res dummy localRoot
402402
403403
ADDED tools/fossil-diff-log
--- test/tester.tcl
+++ test/tester.tcl
@@ -391,11 +391,11 @@
391 if {[info exists ::env(FOSSIL_TEST_DANGEROUS_IGNORE_OPEN_CHECKOUT)] && \
392 $::env(FOSSIL_TEST_DANGEROUS_IGNORE_OPEN_CHECKOUT) eq "YES_DO_IT"} {
393 return
394 }
395 catch {exec $::fossilexe info} res
396 if {![regexp {use --repository} $res]} {
397 set projectName <unknown>
398 set localRoot <unknown>
399 regexp -line -- {^project-name: (.*)$} $res dummy projectName
400 set projectName [string trim $projectName]
401 regexp -line -- {^local-root: (.*)$} $res dummy localRoot
402
403 DDED tools/fossil-diff-log
--- test/tester.tcl
+++ test/tester.tcl
@@ -391,11 +391,11 @@
391 if {[info exists ::env(FOSSIL_TEST_DANGEROUS_IGNORE_OPEN_CHECKOUT)] && \
392 $::env(FOSSIL_TEST_DANGEROUS_IGNORE_OPEN_CHECKOUT) eq "YES_DO_IT"} {
393 return
394 }
395 catch {exec $::fossilexe info} res
396 if {[regexp {local-root:} $res]} {
397 set projectName <unknown>
398 set localRoot <unknown>
399 regexp -line -- {^project-name: (.*)$} $res dummy projectName
400 set projectName [string trim $projectName]
401 regexp -line -- {^local-root: (.*)$} $res dummy localRoot
402
403 DDED tools/fossil-diff-log

No diff available

No diff available

No diff available

--- a/tools/fossil-diff-log
+++ b/tools/fossil-diff-log
@@ -0,0 +1,72 @@
1
+#!/usr/bin/env perl
2
+# Fossil emulation of the "git log --patch / -p" feature: emit a stream
3
+# of diffs from one version to the next for each file named on the
4
+# command line.
5
+#
6
+# LIMITATIONS: It does not assume "all files" if you give no args, and
7
+# it cannot take a directory to mean "all files under this parent".
8
+#
9
+# PREREQUISITES: This script needs several CPAN modules to run properly.
10
+# There are multiple methods to install them:
11
+#
12
+# sudo dnf install perl-File-Which perl-IO-Interactive
13
+# sudo apt install libfile-which-perl libio-interactive-perl
14
+# sudo cpanm File::Which IO::Interactive
15
+# ...etc...
16
+
17
+use strict;
18
+use warnings;
19
+
20
+use Carp;
21
+use File::Which;
22
+use IO::Interactive qw(is_interactive);
23
+
24
+die "usage: $0 <files...>\n\n" unless @ARGV;
25
+
26
+my $out;
27
+if (is_interactive()) {
28
+ my $pager = $ENV{PAGER} || which('less') || which('more');
29
+ open $out, '|-', $pager or croak "Cannot pipe to $pager: $!";
30
+}
31
+else {
32
+ $out = *STDOUT;
33
+}
34
+
35
+open my $bcmd, '-|', 'fossil branch current'
36
+ or die "Cannot get branch: $!\n";
37
+my $cbranch = <$bcmd>;
38
+chomp $cbranch;
39
+close $bcmd;
40
+
41
+for my $file (@ARGV) {
42
+ my $lastckid;
43
+ open my $finfo, '-|', "fossil finfo --brief --limit 0 '$file'"
44
+ or die "Failed to get file info: $!\n";
45
+ my @filines = <$finfo>;
46
+ close $finfo;
47
+
48
+ for my $line (@filines) {
49
+ my ($currckid, $date, $user, $branch, @cwords) = split ' ', $line;
50
+ next unless $branch eq $cbranch;
51
+ if (defined $lastckid and defined $branch) {
52
+ my $comment = join ' ', @cwords;
53
+ open my $diff, '-|', 'fossil', 'diff', $file,
54
+ '--from', $currckid,
55
+ '--to', $lastckid,
56
+ or die "Failed to diff $currckid -> $lastckid: $!\n";
57
+ my @dl = <$diff>;
58
+ close $diff;
59
+ my $patch = join '', @dl;
60
+
61
+ print $out <<"OUT"
62
+Checkin ID $currckid to $branch by $user on $date
63
+Comment: $comment
64
+
65
+$patch
66
+
67
+OUT
68
+ }
69
+
70
+ $lastckid = $currckid;
71
+ }
72
+}
--- a/tools/fossil-diff-log
+++ b/tools/fossil-diff-log
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/tools/fossil-diff-log
+++ b/tools/fossil-diff-log
@@ -0,0 +1,72 @@
1 #!/usr/bin/env perl
2 # Fossil emulation of the "git log --patch / -p" feature: emit a stream
3 # of diffs from one version to the next for each file named on the
4 # command line.
5 #
6 # LIMITATIONS: It does not assume "all files" if you give no args, and
7 # it cannot take a directory to mean "all files under this parent".
8 #
9 # PREREQUISITES: This script needs several CPAN modules to run properly.
10 # There are multiple methods to install them:
11 #
12 # sudo dnf install perl-File-Which perl-IO-Interactive
13 # sudo apt install libfile-which-perl libio-interactive-perl
14 # sudo cpanm File::Which IO::Interactive
15 # ...etc...
16
17 use strict;
18 use warnings;
19
20 use Carp;
21 use File::Which;
22 use IO::Interactive qw(is_interactive);
23
24 die "usage: $0 <files...>\n\n" unless @ARGV;
25
26 my $out;
27 if (is_interactive()) {
28 my $pager = $ENV{PAGER} || which('less') || which('more');
29 open $out, '|-', $pager or croak "Cannot pipe to $pager: $!";
30 }
31 else {
32 $out = *STDOUT;
33 }
34
35 open my $bcmd, '-|', 'fossil branch current'
36 or die "Cannot get branch: $!\n";
37 my $cbranch = <$bcmd>;
38 chomp $cbranch;
39 close $bcmd;
40
41 for my $file (@ARGV) {
42 my $lastckid;
43 open my $finfo, '-|', "fossil finfo --brief --limit 0 '$file'"
44 or die "Failed to get file info: $!\n";
45 my @filines = <$finfo>;
46 close $finfo;
47
48 for my $line (@filines) {
49 my ($currckid, $date, $user, $branch, @cwords) = split ' ', $line;
50 next unless $branch eq $cbranch;
51 if (defined $lastckid and defined $branch) {
52 my $comment = join ' ', @cwords;
53 open my $diff, '-|', 'fossil', 'diff', $file,
54 '--from', $currckid,
55 '--to', $lastckid,
56 or die "Failed to diff $currckid -> $lastckid: $!\n";
57 my @dl = <$diff>;
58 close $diff;
59 my $patch = join '', @dl;
60
61 print $out <<"OUT"
62 Checkin ID $currckid to $branch by $user on $date
63 Comment: $comment
64
65 $patch
66
67 OUT
68 }
69
70 $lastckid = $currckid;
71 }
72 }

No diff available

No diff available

--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -174,11 +174,11 @@
174174
#### The directories where the OpenSSL include and library files are located.
175175
# The recommended usage here is to use the Sysinternals junction tool
176176
# to create a hard link between an "openssl-1.x" sub-directory of the
177177
# Fossil source code directory and the target OpenSSL source directory.
178178
#
179
-OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1f
179
+OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1g
180180
OPENSSLINCDIR = $(OPENSSLDIR)/include
181181
OPENSSLLIBDIR = $(OPENSSLDIR)
182182
183183
#### Either the directory where the Tcl library is installed or the Tcl
184184
# source code directory resides (depending on the value of the macro
185185
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -174,11 +174,11 @@
174 #### The directories where the OpenSSL include and library files are located.
175 # The recommended usage here is to use the Sysinternals junction tool
176 # to create a hard link between an "openssl-1.x" sub-directory of the
177 # Fossil source code directory and the target OpenSSL source directory.
178 #
179 OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1f
180 OPENSSLINCDIR = $(OPENSSLDIR)/include
181 OPENSSLLIBDIR = $(OPENSSLDIR)
182
183 #### Either the directory where the Tcl library is installed or the Tcl
184 # source code directory resides (depending on the value of the macro
185
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -174,11 +174,11 @@
174 #### The directories where the OpenSSL include and library files are located.
175 # The recommended usage here is to use the Sysinternals junction tool
176 # to create a hard link between an "openssl-1.x" sub-directory of the
177 # Fossil source code directory and the target OpenSSL source directory.
178 #
179 OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1g
180 OPENSSLINCDIR = $(OPENSSLDIR)/include
181 OPENSSLLIBDIR = $(OPENSSLDIR)
182
183 #### Either the directory where the Tcl library is installed or the Tcl
184 # source code directory resides (depending on the value of the macro
185
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -174,11 +174,11 @@
174174
#### The directories where the OpenSSL include and library files are located.
175175
# The recommended usage here is to use the Sysinternals junction tool
176176
# to create a hard link between an "openssl-1.x" sub-directory of the
177177
# Fossil source code directory and the target OpenSSL source directory.
178178
#
179
-OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1f
179
+OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1g
180180
OPENSSLINCDIR = $(OPENSSLDIR)/include
181181
OPENSSLLIBDIR = $(OPENSSLDIR)
182182
183183
#### Either the directory where the Tcl library is installed or the Tcl
184184
# source code directory resides (depending on the value of the macro
@@ -440,10 +440,11 @@
440440
SRC = \
441441
$(SRCDIR)/add.c \
442442
$(SRCDIR)/alerts.c \
443443
$(SRCDIR)/allrepo.c \
444444
$(SRCDIR)/attach.c \
445
+ $(SRCDIR)/backlink.c \
445446
$(SRCDIR)/backoffice.c \
446447
$(SRCDIR)/bag.c \
447448
$(SRCDIR)/bisect.c \
448449
$(SRCDIR)/blob.c \
449450
$(SRCDIR)/branch.c \
@@ -633,10 +634,11 @@
633634
$(SRCDIR)/../skins/rounded1/header.txt \
634635
$(SRCDIR)/../skins/xekri/css.txt \
635636
$(SRCDIR)/../skins/xekri/details.txt \
636637
$(SRCDIR)/../skins/xekri/footer.txt \
637638
$(SRCDIR)/../skins/xekri/header.txt \
639
+ $(SRCDIR)/accordion.js \
638640
$(SRCDIR)/ci_edit.js \
639641
$(SRCDIR)/copybtn.js \
640642
$(SRCDIR)/diff.tcl \
641643
$(SRCDIR)/forum.js \
642644
$(SRCDIR)/graph.js \
@@ -671,10 +673,11 @@
671673
TRANS_SRC = \
672674
$(OBJDIR)/add_.c \
673675
$(OBJDIR)/alerts_.c \
674676
$(OBJDIR)/allrepo_.c \
675677
$(OBJDIR)/attach_.c \
678
+ $(OBJDIR)/backlink_.c \
676679
$(OBJDIR)/backoffice_.c \
677680
$(OBJDIR)/bag_.c \
678681
$(OBJDIR)/bisect_.c \
679682
$(OBJDIR)/blob_.c \
680683
$(OBJDIR)/branch_.c \
@@ -812,10 +815,11 @@
812815
OBJ = \
813816
$(OBJDIR)/add.o \
814817
$(OBJDIR)/alerts.o \
815818
$(OBJDIR)/allrepo.o \
816819
$(OBJDIR)/attach.o \
820
+ $(OBJDIR)/backlink.o \
817821
$(OBJDIR)/backoffice.o \
818822
$(OBJDIR)/bag.o \
819823
$(OBJDIR)/bisect.o \
820824
$(OBJDIR)/blob.o \
821825
$(OBJDIR)/branch.o \
@@ -1173,10 +1177,11 @@
11731177
$(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/default_css.h $(MAKEHEADERS) $(OBJDIR)/VERSION.h
11741178
$(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h \
11751179
$(OBJDIR)/alerts_.c:$(OBJDIR)/alerts.h \
11761180
$(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \
11771181
$(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \
1182
+ $(OBJDIR)/backlink_.c:$(OBJDIR)/backlink.h \
11781183
$(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \
11791184
$(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \
11801185
$(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \
11811186
$(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \
11821187
$(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \
@@ -1348,10 +1353,18 @@
13481353
13491354
$(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h
13501355
$(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c
13511356
13521357
$(OBJDIR)/attach.h: $(OBJDIR)/headers
1358
+
1359
+$(OBJDIR)/backlink_.c: $(SRCDIR)/backlink.c $(TRANSLATE)
1360
+ $(TRANSLATE) $(SRCDIR)/backlink.c >$@
1361
+
1362
+$(OBJDIR)/backlink.o: $(OBJDIR)/backlink_.c $(OBJDIR)/backlink.h $(SRCDIR)/config.h
1363
+ $(XTCC) -o $(OBJDIR)/backlink.o -c $(OBJDIR)/backlink_.c
1364
+
1365
+$(OBJDIR)/backlink.h: $(OBJDIR)/headers
13531366
13541367
$(OBJDIR)/backoffice_.c: $(SRCDIR)/backoffice.c $(TRANSLATE)
13551368
$(TRANSLATE) $(SRCDIR)/backoffice.c >$@
13561369
13571370
$(OBJDIR)/backoffice.o: $(OBJDIR)/backoffice_.c $(OBJDIR)/backoffice.h $(SRCDIR)/config.h
13581371
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -174,11 +174,11 @@
174 #### The directories where the OpenSSL include and library files are located.
175 # The recommended usage here is to use the Sysinternals junction tool
176 # to create a hard link between an "openssl-1.x" sub-directory of the
177 # Fossil source code directory and the target OpenSSL source directory.
178 #
179 OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1f
180 OPENSSLINCDIR = $(OPENSSLDIR)/include
181 OPENSSLLIBDIR = $(OPENSSLDIR)
182
183 #### Either the directory where the Tcl library is installed or the Tcl
184 # source code directory resides (depending on the value of the macro
@@ -440,10 +440,11 @@
440 SRC = \
441 $(SRCDIR)/add.c \
442 $(SRCDIR)/alerts.c \
443 $(SRCDIR)/allrepo.c \
444 $(SRCDIR)/attach.c \
 
445 $(SRCDIR)/backoffice.c \
446 $(SRCDIR)/bag.c \
447 $(SRCDIR)/bisect.c \
448 $(SRCDIR)/blob.c \
449 $(SRCDIR)/branch.c \
@@ -633,10 +634,11 @@
633 $(SRCDIR)/../skins/rounded1/header.txt \
634 $(SRCDIR)/../skins/xekri/css.txt \
635 $(SRCDIR)/../skins/xekri/details.txt \
636 $(SRCDIR)/../skins/xekri/footer.txt \
637 $(SRCDIR)/../skins/xekri/header.txt \
 
638 $(SRCDIR)/ci_edit.js \
639 $(SRCDIR)/copybtn.js \
640 $(SRCDIR)/diff.tcl \
641 $(SRCDIR)/forum.js \
642 $(SRCDIR)/graph.js \
@@ -671,10 +673,11 @@
671 TRANS_SRC = \
672 $(OBJDIR)/add_.c \
673 $(OBJDIR)/alerts_.c \
674 $(OBJDIR)/allrepo_.c \
675 $(OBJDIR)/attach_.c \
 
676 $(OBJDIR)/backoffice_.c \
677 $(OBJDIR)/bag_.c \
678 $(OBJDIR)/bisect_.c \
679 $(OBJDIR)/blob_.c \
680 $(OBJDIR)/branch_.c \
@@ -812,10 +815,11 @@
812 OBJ = \
813 $(OBJDIR)/add.o \
814 $(OBJDIR)/alerts.o \
815 $(OBJDIR)/allrepo.o \
816 $(OBJDIR)/attach.o \
 
817 $(OBJDIR)/backoffice.o \
818 $(OBJDIR)/bag.o \
819 $(OBJDIR)/bisect.o \
820 $(OBJDIR)/blob.o \
821 $(OBJDIR)/branch.o \
@@ -1173,10 +1177,11 @@
1173 $(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/default_css.h $(MAKEHEADERS) $(OBJDIR)/VERSION.h
1174 $(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h \
1175 $(OBJDIR)/alerts_.c:$(OBJDIR)/alerts.h \
1176 $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \
1177 $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \
 
1178 $(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \
1179 $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \
1180 $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \
1181 $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \
1182 $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \
@@ -1348,10 +1353,18 @@
1348
1349 $(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h
1350 $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c
1351
1352 $(OBJDIR)/attach.h: $(OBJDIR)/headers
 
 
 
 
 
 
 
 
1353
1354 $(OBJDIR)/backoffice_.c: $(SRCDIR)/backoffice.c $(TRANSLATE)
1355 $(TRANSLATE) $(SRCDIR)/backoffice.c >$@
1356
1357 $(OBJDIR)/backoffice.o: $(OBJDIR)/backoffice_.c $(OBJDIR)/backoffice.h $(SRCDIR)/config.h
1358
--- win/Makefile.mingw.mistachkin
+++ win/Makefile.mingw.mistachkin
@@ -174,11 +174,11 @@
174 #### The directories where the OpenSSL include and library files are located.
175 # The recommended usage here is to use the Sysinternals junction tool
176 # to create a hard link between an "openssl-1.x" sub-directory of the
177 # Fossil source code directory and the target OpenSSL source directory.
178 #
179 OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1g
180 OPENSSLINCDIR = $(OPENSSLDIR)/include
181 OPENSSLLIBDIR = $(OPENSSLDIR)
182
183 #### Either the directory where the Tcl library is installed or the Tcl
184 # source code directory resides (depending on the value of the macro
@@ -440,10 +440,11 @@
440 SRC = \
441 $(SRCDIR)/add.c \
442 $(SRCDIR)/alerts.c \
443 $(SRCDIR)/allrepo.c \
444 $(SRCDIR)/attach.c \
445 $(SRCDIR)/backlink.c \
446 $(SRCDIR)/backoffice.c \
447 $(SRCDIR)/bag.c \
448 $(SRCDIR)/bisect.c \
449 $(SRCDIR)/blob.c \
450 $(SRCDIR)/branch.c \
@@ -633,10 +634,11 @@
634 $(SRCDIR)/../skins/rounded1/header.txt \
635 $(SRCDIR)/../skins/xekri/css.txt \
636 $(SRCDIR)/../skins/xekri/details.txt \
637 $(SRCDIR)/../skins/xekri/footer.txt \
638 $(SRCDIR)/../skins/xekri/header.txt \
639 $(SRCDIR)/accordion.js \
640 $(SRCDIR)/ci_edit.js \
641 $(SRCDIR)/copybtn.js \
642 $(SRCDIR)/diff.tcl \
643 $(SRCDIR)/forum.js \
644 $(SRCDIR)/graph.js \
@@ -671,10 +673,11 @@
673 TRANS_SRC = \
674 $(OBJDIR)/add_.c \
675 $(OBJDIR)/alerts_.c \
676 $(OBJDIR)/allrepo_.c \
677 $(OBJDIR)/attach_.c \
678 $(OBJDIR)/backlink_.c \
679 $(OBJDIR)/backoffice_.c \
680 $(OBJDIR)/bag_.c \
681 $(OBJDIR)/bisect_.c \
682 $(OBJDIR)/blob_.c \
683 $(OBJDIR)/branch_.c \
@@ -812,10 +815,11 @@
815 OBJ = \
816 $(OBJDIR)/add.o \
817 $(OBJDIR)/alerts.o \
818 $(OBJDIR)/allrepo.o \
819 $(OBJDIR)/attach.o \
820 $(OBJDIR)/backlink.o \
821 $(OBJDIR)/backoffice.o \
822 $(OBJDIR)/bag.o \
823 $(OBJDIR)/bisect.o \
824 $(OBJDIR)/blob.o \
825 $(OBJDIR)/branch.o \
@@ -1173,10 +1177,11 @@
1177 $(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/default_css.h $(MAKEHEADERS) $(OBJDIR)/VERSION.h
1178 $(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h \
1179 $(OBJDIR)/alerts_.c:$(OBJDIR)/alerts.h \
1180 $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \
1181 $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \
1182 $(OBJDIR)/backlink_.c:$(OBJDIR)/backlink.h \
1183 $(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \
1184 $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \
1185 $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \
1186 $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \
1187 $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \
@@ -1348,10 +1353,18 @@
1353
1354 $(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h
1355 $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c
1356
1357 $(OBJDIR)/attach.h: $(OBJDIR)/headers
1358
1359 $(OBJDIR)/backlink_.c: $(SRCDIR)/backlink.c $(TRANSLATE)
1360 $(TRANSLATE) $(SRCDIR)/backlink.c >$@
1361
1362 $(OBJDIR)/backlink.o: $(OBJDIR)/backlink_.c $(OBJDIR)/backlink.h $(SRCDIR)/config.h
1363 $(XTCC) -o $(OBJDIR)/backlink.o -c $(OBJDIR)/backlink_.c
1364
1365 $(OBJDIR)/backlink.h: $(OBJDIR)/headers
1366
1367 $(OBJDIR)/backoffice_.c: $(SRCDIR)/backoffice.c $(TRANSLATE)
1368 $(TRANSLATE) $(SRCDIR)/backoffice.c >$@
1369
1370 $(OBJDIR)/backoffice.o: $(OBJDIR)/backoffice_.c $(OBJDIR)/backoffice.h $(SRCDIR)/config.h
1371
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -98,11 +98,11 @@
9898
!ifndef USE_SEE
9999
USE_SEE = 0
100100
!endif
101101
102102
!if $(FOSSIL_ENABLE_SSL)!=0
103
-SSLDIR = $(B)\compat\openssl-1.1.1f
103
+SSLDIR = $(B)\compat\openssl-1.1.1g
104104
SSLINCDIR = $(SSLDIR)\include
105105
!if $(FOSSIL_DYNAMIC_BUILD)!=0
106106
SSLLIBDIR = $(SSLDIR)
107107
!else
108108
SSLLIBDIR = $(SSLDIR)
109109
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -98,11 +98,11 @@
98 !ifndef USE_SEE
99 USE_SEE = 0
100 !endif
101
102 !if $(FOSSIL_ENABLE_SSL)!=0
103 SSLDIR = $(B)\compat\openssl-1.1.1f
104 SSLINCDIR = $(SSLDIR)\include
105 !if $(FOSSIL_DYNAMIC_BUILD)!=0
106 SSLLIBDIR = $(SSLDIR)
107 !else
108 SSLLIBDIR = $(SSLDIR)
109
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -98,11 +98,11 @@
98 !ifndef USE_SEE
99 USE_SEE = 0
100 !endif
101
102 !if $(FOSSIL_ENABLE_SSL)!=0
103 SSLDIR = $(B)\compat\openssl-1.1.1g
104 SSLINCDIR = $(SSLDIR)\include
105 !if $(FOSSIL_DYNAMIC_BUILD)!=0
106 SSLLIBDIR = $(SSLDIR)
107 !else
108 SSLLIBDIR = $(SSLDIR)
109
+1 -1
--- www/build.wiki
+++ www/build.wiki
@@ -161,11 +161,11 @@
161161
the optional <a href="https://www.openssl.org/">OpenSSL</a> support,
162162
first <a href="https://www.openssl.org/source/">download the official
163163
source code for OpenSSL</a> and extract it to an appropriately named
164164
"<b>openssl-X.Y.ZA</b>" subdirectory within the local
165165
[/tree?ci=trunk&name=compat | compat] directory (e.g.
166
-"<b>compat/openssl-1.1.1f</b>"), then make sure that some recent
166
+"<b>compat/openssl-1.1.1g</b>"), then make sure that some recent
167167
<a href="http://www.perl.org/">Perl</a> binaries are installed locally,
168168
and finally run one of the following commands:
169169
<blockquote><pre>
170170
nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin
171171
</pre></blockquote>
172172
--- www/build.wiki
+++ www/build.wiki
@@ -161,11 +161,11 @@
161 the optional <a href="https://www.openssl.org/">OpenSSL</a> support,
162 first <a href="https://www.openssl.org/source/">download the official
163 source code for OpenSSL</a> and extract it to an appropriately named
164 "<b>openssl-X.Y.ZA</b>" subdirectory within the local
165 [/tree?ci=trunk&name=compat | compat] directory (e.g.
166 "<b>compat/openssl-1.1.1f</b>"), then make sure that some recent
167 <a href="http://www.perl.org/">Perl</a> binaries are installed locally,
168 and finally run one of the following commands:
169 <blockquote><pre>
170 nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin
171 </pre></blockquote>
172
--- www/build.wiki
+++ www/build.wiki
@@ -161,11 +161,11 @@
161 the optional <a href="https://www.openssl.org/">OpenSSL</a> support,
162 first <a href="https://www.openssl.org/source/">download the official
163 source code for OpenSSL</a> and extract it to an appropriately named
164 "<b>openssl-X.Y.ZA</b>" subdirectory within the local
165 [/tree?ci=trunk&name=compat | compat] directory (e.g.
166 "<b>compat/openssl-1.1.1g</b>"), then make sure that some recent
167 <a href="http://www.perl.org/">Perl</a> binaries are installed locally,
168 and finally run one of the following commands:
169 <blockquote><pre>
170 nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin
171 </pre></blockquote>
172
--- www/changes.wiki
+++ www/changes.wiki
@@ -31,11 +31,11 @@
3131
text of forum posts to be displayed.
3232
* Rework the "[/help?cmd=grep|fossil grep]" command to be more useful.
3333
* Expose the [/help?cmd=redirect-to-https|redirect-to-https]
3434
setting to the [/help?cmd=settings|settings] command.
3535
* Improve support for CGI on IIS web servers.
36
- * The [/help?cmd=/ext|/ext page] can now render index files,
36
+ * The [./serverext.wiki|/ext page] can now render index files,
3737
in the same way as the embedded docs.
3838
* Most commands now support the Unix-conventional "<tt>--</tt>"
3939
flag to treat all following arguments as filenames
4040
instead of flags.
4141
* Added the [/help?cmd=mimetypes|mimetypes config setting]
@@ -45,11 +45,11 @@
4545
* In [./embeddeddoc.wiki|embedded documentation], hyperlink URLs
4646
of the form "/doc/$CURRENT/..." the "$CURRENT" text is translated
4747
into the check-in hash for the document currently being viewed.
4848
* Added the [/help?cmd=/phantoms|/phantoms] webpage that shows all
4949
phantom artifacts.
50
- * Enhancements to phantom processing to try to reduce constant
50
+ * Enhancements to phantom processing to try to reduce
5151
bandwidth-using chatter about phantoms on the sync protocol.
5252
* Security: Fossil now assumes that the schema of every
5353
database it opens has been tampered with by an adversary and takes
5454
extra precautions to ensure that such tampering is harmless.
5555
* Security: Fossil now puts the Content-Security-Policy in the
@@ -65,13 +65,18 @@
6565
is used.
6666
* The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible
6767
to all users if the new "artifact_stats_enable" setting is turned
6868
on. There is a new checkbox under the /Admin/Access menu to turn
6969
that capability on and off.
70
+ * Add the [/help?cmd=tls-config|fossil tls-config] command for viewing
71
+ the TLS configuration and the list of SSL Cert exceptions.
7072
* Captchas all include a button to read the captcha using an audio
7173
file, so that they can be completed by the visually impaired.
7274
* Stop using the IP address as part of the login cookie.
75
+ * Bug fix: fix the SSL cert validation logic so that if an exception
76
+ is allowed for particular site, the exception expires as soon as the
77
+ cert changes values.
7378
* Bug fix: the FTS search into for forum posts is now kept up-to-date
7479
correctly.
7580
* Bug fix: the "fossil git export" command is now working on Windows
7681
* Bug fix: display Technote items on the timeline correctly
7782
* Bug fix: fix the capability summary matrix of the Security Audit
7883
--- www/changes.wiki
+++ www/changes.wiki
@@ -31,11 +31,11 @@
31 text of forum posts to be displayed.
32 * Rework the "[/help?cmd=grep|fossil grep]" command to be more useful.
33 * Expose the [/help?cmd=redirect-to-https|redirect-to-https]
34 setting to the [/help?cmd=settings|settings] command.
35 * Improve support for CGI on IIS web servers.
36 * The [/help?cmd=/ext|/ext page] can now render index files,
37 in the same way as the embedded docs.
38 * Most commands now support the Unix-conventional "<tt>--</tt>"
39 flag to treat all following arguments as filenames
40 instead of flags.
41 * Added the [/help?cmd=mimetypes|mimetypes config setting]
@@ -45,11 +45,11 @@
45 * In [./embeddeddoc.wiki|embedded documentation], hyperlink URLs
46 of the form "/doc/$CURRENT/..." the "$CURRENT" text is translated
47 into the check-in hash for the document currently being viewed.
48 * Added the [/help?cmd=/phantoms|/phantoms] webpage that shows all
49 phantom artifacts.
50 * Enhancements to phantom processing to try to reduce constant
51 bandwidth-using chatter about phantoms on the sync protocol.
52 * Security: Fossil now assumes that the schema of every
53 database it opens has been tampered with by an adversary and takes
54 extra precautions to ensure that such tampering is harmless.
55 * Security: Fossil now puts the Content-Security-Policy in the
@@ -65,13 +65,18 @@
65 is used.
66 * The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible
67 to all users if the new "artifact_stats_enable" setting is turned
68 on. There is a new checkbox under the /Admin/Access menu to turn
69 that capability on and off.
 
 
70 * Captchas all include a button to read the captcha using an audio
71 file, so that they can be completed by the visually impaired.
72 * Stop using the IP address as part of the login cookie.
 
 
 
73 * Bug fix: the FTS search into for forum posts is now kept up-to-date
74 correctly.
75 * Bug fix: the "fossil git export" command is now working on Windows
76 * Bug fix: display Technote items on the timeline correctly
77 * Bug fix: fix the capability summary matrix of the Security Audit
78
--- www/changes.wiki
+++ www/changes.wiki
@@ -31,11 +31,11 @@
31 text of forum posts to be displayed.
32 * Rework the "[/help?cmd=grep|fossil grep]" command to be more useful.
33 * Expose the [/help?cmd=redirect-to-https|redirect-to-https]
34 setting to the [/help?cmd=settings|settings] command.
35 * Improve support for CGI on IIS web servers.
36 * The [./serverext.wiki|/ext page] can now render index files,
37 in the same way as the embedded docs.
38 * Most commands now support the Unix-conventional "<tt>--</tt>"
39 flag to treat all following arguments as filenames
40 instead of flags.
41 * Added the [/help?cmd=mimetypes|mimetypes config setting]
@@ -45,11 +45,11 @@
45 * In [./embeddeddoc.wiki|embedded documentation], hyperlink URLs
46 of the form "/doc/$CURRENT/..." the "$CURRENT" text is translated
47 into the check-in hash for the document currently being viewed.
48 * Added the [/help?cmd=/phantoms|/phantoms] webpage that shows all
49 phantom artifacts.
50 * Enhancements to phantom processing to try to reduce
51 bandwidth-using chatter about phantoms on the sync protocol.
52 * Security: Fossil now assumes that the schema of every
53 database it opens has been tampered with by an adversary and takes
54 extra precautions to ensure that such tampering is harmless.
55 * Security: Fossil now puts the Content-Security-Policy in the
@@ -65,13 +65,18 @@
65 is used.
66 * The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible
67 to all users if the new "artifact_stats_enable" setting is turned
68 on. There is a new checkbox under the /Admin/Access menu to turn
69 that capability on and off.
70 * Add the [/help?cmd=tls-config|fossil tls-config] command for viewing
71 the TLS configuration and the list of SSL Cert exceptions.
72 * Captchas all include a button to read the captcha using an audio
73 file, so that they can be completed by the visually impaired.
74 * Stop using the IP address as part of the login cookie.
75 * Bug fix: fix the SSL cert validation logic so that if an exception
76 is allowed for particular site, the exception expires as soon as the
77 cert changes values.
78 * Bug fix: the FTS search into for forum posts is now kept up-to-date
79 correctly.
80 * Bug fix: the "fossil git export" command is now working on Windows
81 * Bug fix: display Technote items on the timeline correctly
82 * Bug fix: fix the capability summary matrix of the Security Audit
83
--- www/fossil-v-git.wiki
+++ www/fossil-v-git.wiki
@@ -114,14 +114,14 @@
114114
the design. One way to describe Fossil is that it is
115115
"[https://github.com/ | GitHub]-in-a-box."
116116
117117
Fossil can do operations over all local repo clones and check-out
118118
directories with a single command. For example, Fossil lets you say
119
-<tt>fossil all pull</tt> on a laptop prior to taking it off the network
119
+<tt>fossil all sync</tt> on a laptop prior to taking it off the network
120120
hosting those repos. You can sync up to all of the private repos on your
121121
company network plus those public Internet-hosted repos you use. Whether
122
-going out for a working lunch or on a transoceanic an airplane trip, one
122
+going out for a working lunch or on a transoceanic airplane trip, one
123123
command gets you in sync. This works with several other Fossil
124124
sub-commands, such as <tt>fossil all changes</tt> to get a list of files
125125
that you forgot to commit prior to the end of your working day, across
126126
all repos.
127127
@@ -258,11 +258,11 @@
258258
are looking at some historical check-in then you cannot ask "What came
259259
next?" or "What are the children of this check-in?"
260260
261261
Fossil, on the other hand, parses essential information about check-ins
262262
(parents, children, committers, comments, files changed, etc.) into a
263
-relational database that can be easily queried using concise SQL
263
+relational database that can easily be queried using concise SQL
264264
statements to find both ancestors and descendants of a check-in. This is
265265
the hybrid data model mentioned above: Fossil manages your check-in and
266266
other data in a NoSQL block chain structured data store, but that's backed
267267
by a set of relational lookup tables for quick indexing into that
268268
artifact store. (See "[./theory1.wiki|Thoughts On The Design Of The
@@ -759,13 +759,13 @@
759759
760760
Fossil cannot sensibly work that way because of its default-enabled
761761
autosync feature. Instead of jumping straight to the commit step, Fossil
762762
applies the proposed merge to the local working directory only,
763763
requiring a separate check-in step before the change is committed to the
764
-repository blockchain. This gives you a chance to test the change,
765
-whether manually, or by running your software's automatic tests, or
766
-both.
764
+repository blockchain. This gives you a chance to test the change first,
765
+either manually or by running your software's automatic tests. (Ideally,
766
+both!)
767767
768768
Another difference is that because Fossil requires an explicit commit
769769
for a merge, it makes you give an explicit commit <i>message</i> for
770770
each merge, whereas Git writes that commit message itself by default
771771
unless you give the optional <tt>--edit</tt> flag to override it.
@@ -805,11 +805,11 @@
805805
brief online help for <tt>[/help?cmd=merge | fossil merge]</tt> is
806806
currently 41 lines long, to which you want to add the 600 lines of
807807
[./branching.wiki | the branching document]. The equivalent
808808
documentation in Git is the aggregation of the man pages for the above
809809
three commands, which is over 1000 lines, much of it mutually redundant.
810
-(e.g. the <tt>--edit</tt> and <tt>--no-commit</tt> options get
810
+(e.g. Git's <tt>--edit</tt> and <tt>--no-commit</tt> options get
811811
described three times, each time differently.) Fossil's
812812
documentation is not only more concise, it gives a nice split of brief
813813
online help and full online documentation.
814814
815815
@@ -855,11 +855,11 @@
855855
weakening the case for continuing to use SHA-1.
856856
857857
The practical impact of attacks like SHAttered and SHAmbles on the
858858
Git and Fossil blockchains isn't clear, but you want to have your repositories
859859
moved over to a stronger hash algorithm before someone figures out how
860
-to make use of the weaknesses in the old one. Fossil had this covered
860
+to make use of the weaknesses in the old one. Fossil has had this covered
861861
for years now, so that the solution is now almost universally deployed.
862862
863863
<hr/>
864864
865865
<h3>Asides and Digressions</h3>
866866
--- www/fossil-v-git.wiki
+++ www/fossil-v-git.wiki
@@ -114,14 +114,14 @@
114 the design. One way to describe Fossil is that it is
115 "[https://github.com/ | GitHub]-in-a-box."
116
117 Fossil can do operations over all local repo clones and check-out
118 directories with a single command. For example, Fossil lets you say
119 <tt>fossil all pull</tt> on a laptop prior to taking it off the network
120 hosting those repos. You can sync up to all of the private repos on your
121 company network plus those public Internet-hosted repos you use. Whether
122 going out for a working lunch or on a transoceanic an airplane trip, one
123 command gets you in sync. This works with several other Fossil
124 sub-commands, such as <tt>fossil all changes</tt> to get a list of files
125 that you forgot to commit prior to the end of your working day, across
126 all repos.
127
@@ -258,11 +258,11 @@
258 are looking at some historical check-in then you cannot ask "What came
259 next?" or "What are the children of this check-in?"
260
261 Fossil, on the other hand, parses essential information about check-ins
262 (parents, children, committers, comments, files changed, etc.) into a
263 relational database that can be easily queried using concise SQL
264 statements to find both ancestors and descendants of a check-in. This is
265 the hybrid data model mentioned above: Fossil manages your check-in and
266 other data in a NoSQL block chain structured data store, but that's backed
267 by a set of relational lookup tables for quick indexing into that
268 artifact store. (See "[./theory1.wiki|Thoughts On The Design Of The
@@ -759,13 +759,13 @@
759
760 Fossil cannot sensibly work that way because of its default-enabled
761 autosync feature. Instead of jumping straight to the commit step, Fossil
762 applies the proposed merge to the local working directory only,
763 requiring a separate check-in step before the change is committed to the
764 repository blockchain. This gives you a chance to test the change,
765 whether manually, or by running your software's automatic tests, or
766 both.
767
768 Another difference is that because Fossil requires an explicit commit
769 for a merge, it makes you give an explicit commit <i>message</i> for
770 each merge, whereas Git writes that commit message itself by default
771 unless you give the optional <tt>--edit</tt> flag to override it.
@@ -805,11 +805,11 @@
805 brief online help for <tt>[/help?cmd=merge | fossil merge]</tt> is
806 currently 41 lines long, to which you want to add the 600 lines of
807 [./branching.wiki | the branching document]. The equivalent
808 documentation in Git is the aggregation of the man pages for the above
809 three commands, which is over 1000 lines, much of it mutually redundant.
810 (e.g. the <tt>--edit</tt> and <tt>--no-commit</tt> options get
811 described three times, each time differently.) Fossil's
812 documentation is not only more concise, it gives a nice split of brief
813 online help and full online documentation.
814
815
@@ -855,11 +855,11 @@
855 weakening the case for continuing to use SHA-1.
856
857 The practical impact of attacks like SHAttered and SHAmbles on the
858 Git and Fossil blockchains isn't clear, but you want to have your repositories
859 moved over to a stronger hash algorithm before someone figures out how
860 to make use of the weaknesses in the old one. Fossil had this covered
861 for years now, so that the solution is now almost universally deployed.
862
863 <hr/>
864
865 <h3>Asides and Digressions</h3>
866
--- www/fossil-v-git.wiki
+++ www/fossil-v-git.wiki
@@ -114,14 +114,14 @@
114 the design. One way to describe Fossil is that it is
115 "[https://github.com/ | GitHub]-in-a-box."
116
117 Fossil can do operations over all local repo clones and check-out
118 directories with a single command. For example, Fossil lets you say
119 <tt>fossil all sync</tt> on a laptop prior to taking it off the network
120 hosting those repos. You can sync up to all of the private repos on your
121 company network plus those public Internet-hosted repos you use. Whether
122 going out for a working lunch or on a transoceanic airplane trip, one
123 command gets you in sync. This works with several other Fossil
124 sub-commands, such as <tt>fossil all changes</tt> to get a list of files
125 that you forgot to commit prior to the end of your working day, across
126 all repos.
127
@@ -258,11 +258,11 @@
258 are looking at some historical check-in then you cannot ask "What came
259 next?" or "What are the children of this check-in?"
260
261 Fossil, on the other hand, parses essential information about check-ins
262 (parents, children, committers, comments, files changed, etc.) into a
263 relational database that can easily be queried using concise SQL
264 statements to find both ancestors and descendants of a check-in. This is
265 the hybrid data model mentioned above: Fossil manages your check-in and
266 other data in a NoSQL block chain structured data store, but that's backed
267 by a set of relational lookup tables for quick indexing into that
268 artifact store. (See "[./theory1.wiki|Thoughts On The Design Of The
@@ -759,13 +759,13 @@
759
760 Fossil cannot sensibly work that way because of its default-enabled
761 autosync feature. Instead of jumping straight to the commit step, Fossil
762 applies the proposed merge to the local working directory only,
763 requiring a separate check-in step before the change is committed to the
764 repository blockchain. This gives you a chance to test the change first,
765 either manually or by running your software's automatic tests. (Ideally,
766 both!)
767
768 Another difference is that because Fossil requires an explicit commit
769 for a merge, it makes you give an explicit commit <i>message</i> for
770 each merge, whereas Git writes that commit message itself by default
771 unless you give the optional <tt>--edit</tt> flag to override it.
@@ -805,11 +805,11 @@
805 brief online help for <tt>[/help?cmd=merge | fossil merge]</tt> is
806 currently 41 lines long, to which you want to add the 600 lines of
807 [./branching.wiki | the branching document]. The equivalent
808 documentation in Git is the aggregation of the man pages for the above
809 three commands, which is over 1000 lines, much of it mutually redundant.
810 (e.g. Git's <tt>--edit</tt> and <tt>--no-commit</tt> options get
811 described three times, each time differently.) Fossil's
812 documentation is not only more concise, it gives a nice split of brief
813 online help and full online documentation.
814
815
@@ -855,11 +855,11 @@
855 weakening the case for continuing to use SHA-1.
856
857 The practical impact of attacks like SHAttered and SHAmbles on the
858 Git and Fossil blockchains isn't clear, but you want to have your repositories
859 moved over to a stronger hash algorithm before someone figures out how
860 to make use of the weaknesses in the old one. Fossil has had this covered
861 for years now, so that the solution is now almost universally deployed.
862
863 <hr/>
864
865 <h3>Asides and Digressions</h3>
866
--- www/mirrorlimitations.md
+++ www/mirrorlimitations.md
@@ -9,52 +9,74 @@
99
Fossil is not included in an export to Git.
1010
1111
## (1) Wiki, Tickets, Technotes, Forum
1212
1313
Git only supports version control. The additional features of Fossil such
14
-as Wiki, Tickets, Technotes, and the Forum are not supported in Git and
14
+as Wiki, Tickets, Technotes, and the Forum are not supported in Git,
1515
so those features are not included in an export.
16
+
17
+Third-party Git based tooling may add some of these features (e.g.
18
+GitHub, GitLab) but because their data are not stored in the Git
19
+blockchain, there is no single destination for Fossil to convert its
20
+equivalent data *to*. For instance, Fossil tickets do not become GitHub
21
+issues, because that is a proprietary feature of GitHub separate from
22
+Git proper, stored outside the blockchain on the GitHub servers.
23
+
24
+You can also see the problem in its inverse case: you do not get a copy
25
+of your GitHub issues when cloning the Git repository. You *do* get the
26
+Fossil tickets, wiki, forum posts, etc. when cloning a remote Fossil
27
+repo.
1628
1729
## (2) Cherrypick Merges
1830
19
-The Git client supports cherrypick merges but does not remember them.
20
-In other words, Git does not record a history of cherrypick merges
21
-in its blockchain.
22
-
23
-Fossil tracks cherrypick merges in its blockchain and display cherrypicks
24
-(as dashed lines) in its timeline ([example](/timeline?c=0a9f12ce6655b7a5)).
25
-But history information of cherrypicks cannot be exported to Git because
26
-there is no way to represent it in the Git.
31
+The Git client supports cherrypick merges but does not record the
32
+cherrypick parent(s).
33
+
34
+Fossil tracks cherrypick merges in its blockchain and displays
35
+cherrypicks in its timeline. (As an example, the dashed lines
36
+[here](/timeline?c=0a9f12ce6655b7a5) are cherrypicks.) Because Git does
37
+not have a way to represent this same information in its blockchain, the
38
+history of Fossil cherrypicks cannot be exported to Git, only their
39
+direct effects on the managed file data.
2740
2841
## (3) Named Branches
2942
3043
Git has only limited support for named branches. Git identifies the head
3144
check-in of each branch. Depending on the check-in graph topology, this
3245
is sufficient to infer the branch for many historical check-ins as well.
3346
However, complex histories with lots of cross-merging
3447
can lead to ambiguities. Fossil keeps
35
-track of historical branch names unambiguously.
36
-But the extra details about branch names that Fossil keeps
48
+track of historical branch names unambiguously,
49
+but the extra details about branch names that Fossil keeps
3750
at hand cannot be exported to Git.
3851
3952
## (4) Non-unique Tags
4053
41
-Git requires tags to be unique. Each tag must refer to exactly one
54
+Git requires tags to be unique: each tag must refer to exactly one
4255
check-in. Fossil does not have this restriction, and so it is common
4356
in Fossil to tag multiple check-ins with the same name. For example,
44
-it is common in Fossil to tag every release check-in with the "release"
45
-tag, so that all historical releases can be found all at once.
46
-([example](/timeline?t=release))
57
+it is common in Fossil to tag each check-in creating a release both
58
+with a unique version tag *and* a common tag like "release"
59
+so that all historical releases can be found at once.
60
+([Example](/timeline?t=release).)
4761
4862
Git does not allow this. The "release" tag must refer to just one
4963
check-in. The work-around is that the non-unique tag in the Git export is
5064
made to refer to only the most recent check-in with that tag.
5165
66
+This is why the ["release" tag view][ghrtv] in the GitHub mirror of this
67
+repository shows only the latest release version; contrast the prior
68
+example. Both URLs are asking the repository the same question, but
69
+because of Git's relatively impoverished data model, it cannot give the
70
+same answer that Fossil does.
71
+
72
+[ghrtv]: https://github.com/drhsqlite/fossil-mirror/tree/release
73
+
5274
## (5) Amendments To Check-ins
5375
5476
Check-ins are immutable in both Fossil and Git.
55
-However, Fossil has a mechanism by which tags can be added
77
+However, Fossil has a mechanism by which tags can be added to
5678
its blockchain to provide after-the-fact corrections to prior check-ins.
5779
5880
For example, tags can be added to check-ins that correct typos in the
5981
check-in comment. The original check-in is immutable and so the
6082
original comment is preserved in addition to the correction. But
6183
--- www/mirrorlimitations.md
+++ www/mirrorlimitations.md
@@ -9,52 +9,74 @@
9 Fossil is not included in an export to Git.
10
11 ## (1) Wiki, Tickets, Technotes, Forum
12
13 Git only supports version control. The additional features of Fossil such
14 as Wiki, Tickets, Technotes, and the Forum are not supported in Git and
15 so those features are not included in an export.
 
 
 
 
 
 
 
 
 
 
 
 
16
17 ## (2) Cherrypick Merges
18
19 The Git client supports cherrypick merges but does not remember them.
20 In other words, Git does not record a history of cherrypick merges
21 in its blockchain.
22
23 Fossil tracks cherrypick merges in its blockchain and display cherrypicks
24 (as dashed lines) in its timeline ([example](/timeline?c=0a9f12ce6655b7a5)).
25 But history information of cherrypicks cannot be exported to Git because
26 there is no way to represent it in the Git.
 
27
28 ## (3) Named Branches
29
30 Git has only limited support for named branches. Git identifies the head
31 check-in of each branch. Depending on the check-in graph topology, this
32 is sufficient to infer the branch for many historical check-ins as well.
33 However, complex histories with lots of cross-merging
34 can lead to ambiguities. Fossil keeps
35 track of historical branch names unambiguously.
36 But the extra details about branch names that Fossil keeps
37 at hand cannot be exported to Git.
38
39 ## (4) Non-unique Tags
40
41 Git requires tags to be unique. Each tag must refer to exactly one
42 check-in. Fossil does not have this restriction, and so it is common
43 in Fossil to tag multiple check-ins with the same name. For example,
44 it is common in Fossil to tag every release check-in with the "release"
45 tag, so that all historical releases can be found all at once.
46 ([example](/timeline?t=release))
 
47
48 Git does not allow this. The "release" tag must refer to just one
49 check-in. The work-around is that the non-unique tag in the Git export is
50 made to refer to only the most recent check-in with that tag.
51
 
 
 
 
 
 
 
 
52 ## (5) Amendments To Check-ins
53
54 Check-ins are immutable in both Fossil and Git.
55 However, Fossil has a mechanism by which tags can be added
56 its blockchain to provide after-the-fact corrections to prior check-ins.
57
58 For example, tags can be added to check-ins that correct typos in the
59 check-in comment. The original check-in is immutable and so the
60 original comment is preserved in addition to the correction. But
61
--- www/mirrorlimitations.md
+++ www/mirrorlimitations.md
@@ -9,52 +9,74 @@
9 Fossil is not included in an export to Git.
10
11 ## (1) Wiki, Tickets, Technotes, Forum
12
13 Git only supports version control. The additional features of Fossil such
14 as Wiki, Tickets, Technotes, and the Forum are not supported in Git,
15 so those features are not included in an export.
16
17 Third-party Git based tooling may add some of these features (e.g.
18 GitHub, GitLab) but because their data are not stored in the Git
19 blockchain, there is no single destination for Fossil to convert its
20 equivalent data *to*. For instance, Fossil tickets do not become GitHub
21 issues, because that is a proprietary feature of GitHub separate from
22 Git proper, stored outside the blockchain on the GitHub servers.
23
24 You can also see the problem in its inverse case: you do not get a copy
25 of your GitHub issues when cloning the Git repository. You *do* get the
26 Fossil tickets, wiki, forum posts, etc. when cloning a remote Fossil
27 repo.
28
29 ## (2) Cherrypick Merges
30
31 The Git client supports cherrypick merges but does not record the
32 cherrypick parent(s).
33
34 Fossil tracks cherrypick merges in its blockchain and displays
35 cherrypicks in its timeline. (As an example, the dashed lines
36 [here](/timeline?c=0a9f12ce6655b7a5) are cherrypicks.) Because Git does
37 not have a way to represent this same information in its blockchain, the
38 history of Fossil cherrypicks cannot be exported to Git, only their
39 direct effects on the managed file data.
40
41 ## (3) Named Branches
42
43 Git has only limited support for named branches. Git identifies the head
44 check-in of each branch. Depending on the check-in graph topology, this
45 is sufficient to infer the branch for many historical check-ins as well.
46 However, complex histories with lots of cross-merging
47 can lead to ambiguities. Fossil keeps
48 track of historical branch names unambiguously,
49 but the extra details about branch names that Fossil keeps
50 at hand cannot be exported to Git.
51
52 ## (4) Non-unique Tags
53
54 Git requires tags to be unique: each tag must refer to exactly one
55 check-in. Fossil does not have this restriction, and so it is common
56 in Fossil to tag multiple check-ins with the same name. For example,
57 it is common in Fossil to tag each check-in creating a release both
58 with a unique version tag *and* a common tag like "release"
59 so that all historical releases can be found at once.
60 ([Example](/timeline?t=release).)
61
62 Git does not allow this. The "release" tag must refer to just one
63 check-in. The work-around is that the non-unique tag in the Git export is
64 made to refer to only the most recent check-in with that tag.
65
66 This is why the ["release" tag view][ghrtv] in the GitHub mirror of this
67 repository shows only the latest release version; contrast the prior
68 example. Both URLs are asking the repository the same question, but
69 because of Git's relatively impoverished data model, it cannot give the
70 same answer that Fossil does.
71
72 [ghrtv]: https://github.com/drhsqlite/fossil-mirror/tree/release
73
74 ## (5) Amendments To Check-ins
75
76 Check-ins are immutable in both Fossil and Git.
77 However, Fossil has a mechanism by which tags can be added to
78 its blockchain to provide after-the-fact corrections to prior check-ins.
79
80 For example, tags can be added to check-ins that correct typos in the
81 check-in comment. The original check-in is immutable and so the
82 original comment is preserved in addition to the correction. But
83
--- www/rebaseharm.md
+++ www/rebaseharm.md
@@ -317,14 +317,14 @@
317317
that blames the merged check-in as the source of the problem they’re
318318
chasing down; they then have to manually work out which of the 10 steps
319319
the original developer took to create it to find the source of the
320320
actual problem.
321321
322
-Fossil pushes all 11 check-ins to the parent repository by default, so
323
-that someone doing that bisect sees the complete check-in history, so
324
-the bisect will point them at the single original check-in that caused
325
-the problem.
322
+An equivalent push in Fossil will send all 11 check-ins to the parent
323
+repository so that a later investigator doing the same sort of bisect
324
+sees the complete check-in history. That bisect will point the
325
+investigator at the single original check-in that caused the problem.
326326
327327
### <a name="comments"></a>7.3 Multiple check-ins require multiple check-in comments
328328
329329
The more comments you have from a given developer on a given body of
330330
code, the more concise documentation you have of that developer's
331331
--- www/rebaseharm.md
+++ www/rebaseharm.md
@@ -317,14 +317,14 @@
317 that blames the merged check-in as the source of the problem they’re
318 chasing down; they then have to manually work out which of the 10 steps
319 the original developer took to create it to find the source of the
320 actual problem.
321
322 Fossil pushes all 11 check-ins to the parent repository by default, so
323 that someone doing that bisect sees the complete check-in history, so
324 the bisect will point them at the single original check-in that caused
325 the problem.
326
327 ### <a name="comments"></a>7.3 Multiple check-ins require multiple check-in comments
328
329 The more comments you have from a given developer on a given body of
330 code, the more concise documentation you have of that developer's
331
--- www/rebaseharm.md
+++ www/rebaseharm.md
@@ -317,14 +317,14 @@
317 that blames the merged check-in as the source of the problem they’re
318 chasing down; they then have to manually work out which of the 10 steps
319 the original developer took to create it to find the source of the
320 actual problem.
321
322 An equivalent push in Fossil will send all 11 check-ins to the parent
323 repository so that a later investigator doing the same sort of bisect
324 sees the complete check-in history. That bisect will point the
325 investigator at the single original check-in that caused the problem.
326
327 ### <a name="comments"></a>7.3 Multiple check-ins require multiple check-in comments
328
329 The more comments you have from a given developer on a given body of
330 code, the more concise documentation you have of that developer's
331
+26 -13
--- www/ssl.wiki
+++ www/ssl.wiki
@@ -104,25 +104,36 @@
104104
105105
106106
<h3 id="certs">Certificates</h3>
107107
108108
To verify the identify of a server, TLS uses
109
-[https://en.wikipedia.org/wiki/X.509#Certificates|X.509 certificates].
109
+[https://en.wikipedia.org/wiki/X.509#Certificates|X.509 certificates], a
110
+scheme that depends on a trust hierarchy of so-called
111
+[https://en.wikipedia.org/wiki/Certificate_authority | Certificate
112
+Authorities]. The tree of trust relationships ultimately ends in the
113
+CA roots, which are considered the ultimate arbiters of who to trust in
114
+this scheme.
115
+
116
+The question then is, what CA roots does Fossil trust?
110117
111
-If you are using a self-signed certificate, you'll be asked if you want
118
+If you are using a self-signed certificate, Fossil will initially not
119
+know that it can trust your certificate, so you'll be asked if you want
112120
to accept the certificate the first time you communicate with the
113121
server. Verify the certificate fingerprint is correct, then answer
114
-"always" to remember your decision.
122
+"always" if you want Fossil to remember your decision.
115123
116124
If you are cloning from or syncing to Fossil servers that use a
117
-certificate signed by a
118
-[https://en.wikipedia.org/wiki/Certificate_authority|certificate
119
-authority] (CA), Fossil needs to know which CAs you trust to sign those
120
-certificates. Fossil relies on the OpenSSL library to have some way to
121
-check a trusted list of CA signing keys.
125
+certificate signed by a well-known CA or one of its delegates, Fossil
126
+still has to know which CA roots to trust. When this fails, you get a
127
+big long error message that starts with this text:
128
+
129
+<pre>
130
+ SSL verification failed: unable to get local issuer certificate
131
+</pre>
122132
123
-There are two common ways this fails:
133
+Fossil relies on the OpenSSL library to have some way to check a trusted
134
+list of CA signing keys. There are two common ways this fails:
124135
125136
# <p>The OpenSSL library Fossil is linked to doesn't have a CA
126137
signing key set at all, so that it initially trusts no certificates
127138
at all.</p>
128139
# <p>The OpenSSL library does have a CA cert set, but your Fossil server's
@@ -137,11 +148,13 @@
137148
fossil set --global ssl-ca-location /path/to/local-ca.pem
138149
</pre>
139150
140151
The use of <tt>--global</tt> with this option is common, since you may
141152
have multiple reposotories served under certificates signed by that same
142
-CA.
153
+CA. However, if you have a mix of publicly-signed and locally-signed
154
+certificates, you might want to drop the <tt>--global</tt> flag and set
155
+this option on a per-repository basis instead.
143156
144157
A common way to run into the broader first problem is that you're on
145158
FreeBSD, which does not install a CA certificate set by default, even as
146159
a dependency of the OpenSSL library. If you're using a certificate
147160
signed by one of the major public CAs, you can solve this by installing
@@ -155,15 +168,15 @@
155168
certificate set, but it's not in a format that OpenSSL understands how
156169
to use. Rather than try to find a way to convert the data format, you
157170
may find it acceptable to use the same Mozilla NSS cert set. I do not
158171
know of a way to easily get this from Mozilla themselves, but I did find
159172
a [https://curl.haxx.se/docs/caextract.html|third party source] for the
160
-<tt>cacert.pem</tt> file. Install it somewhere on your system, then
161
-point Fossil at it like so:
173
+<tt>cacert.pem</tt> file. I suggest placing the file into your Windows
174
+user home directory so that you can then point Fossil at it like so:
162175
163176
<pre>
164
- fossil set --global ssl-ca-location /path/to/cacert.pem
177
+ fossil set --global ssl-ca-location %userprofile%\cacert.pem
165178
</pre>
166179
167180
This can also happen if you've linked Fossil to a version of OpenSSL
168181
[#openssl-src|built from source]. That same <tt>cacert.pem</tt> fix can
169182
work in that case, too.
170183
--- www/ssl.wiki
+++ www/ssl.wiki
@@ -104,25 +104,36 @@
104
105
106 <h3 id="certs">Certificates</h3>
107
108 To verify the identify of a server, TLS uses
109 [https://en.wikipedia.org/wiki/X.509#Certificates|X.509 certificates].
 
 
 
 
 
 
 
110
111 If you are using a self-signed certificate, you'll be asked if you want
 
112 to accept the certificate the first time you communicate with the
113 server. Verify the certificate fingerprint is correct, then answer
114 "always" to remember your decision.
115
116 If you are cloning from or syncing to Fossil servers that use a
117 certificate signed by a
118 [https://en.wikipedia.org/wiki/Certificate_authority|certificate
119 authority] (CA), Fossil needs to know which CAs you trust to sign those
120 certificates. Fossil relies on the OpenSSL library to have some way to
121 check a trusted list of CA signing keys.
 
 
122
123 There are two common ways this fails:
 
124
125 # <p>The OpenSSL library Fossil is linked to doesn't have a CA
126 signing key set at all, so that it initially trusts no certificates
127 at all.</p>
128 # <p>The OpenSSL library does have a CA cert set, but your Fossil server's
@@ -137,11 +148,13 @@
137 fossil set --global ssl-ca-location /path/to/local-ca.pem
138 </pre>
139
140 The use of <tt>--global</tt> with this option is common, since you may
141 have multiple reposotories served under certificates signed by that same
142 CA.
 
 
143
144 A common way to run into the broader first problem is that you're on
145 FreeBSD, which does not install a CA certificate set by default, even as
146 a dependency of the OpenSSL library. If you're using a certificate
147 signed by one of the major public CAs, you can solve this by installing
@@ -155,15 +168,15 @@
155 certificate set, but it's not in a format that OpenSSL understands how
156 to use. Rather than try to find a way to convert the data format, you
157 may find it acceptable to use the same Mozilla NSS cert set. I do not
158 know of a way to easily get this from Mozilla themselves, but I did find
159 a [https://curl.haxx.se/docs/caextract.html|third party source] for the
160 <tt>cacert.pem</tt> file. Install it somewhere on your system, then
161 point Fossil at it like so:
162
163 <pre>
164 fossil set --global ssl-ca-location /path/to/cacert.pem
165 </pre>
166
167 This can also happen if you've linked Fossil to a version of OpenSSL
168 [#openssl-src|built from source]. That same <tt>cacert.pem</tt> fix can
169 work in that case, too.
170
--- www/ssl.wiki
+++ www/ssl.wiki
@@ -104,25 +104,36 @@
104
105
106 <h3 id="certs">Certificates</h3>
107
108 To verify the identify of a server, TLS uses
109 [https://en.wikipedia.org/wiki/X.509#Certificates|X.509 certificates], a
110 scheme that depends on a trust hierarchy of so-called
111 [https://en.wikipedia.org/wiki/Certificate_authority | Certificate
112 Authorities]. The tree of trust relationships ultimately ends in the
113 CA roots, which are considered the ultimate arbiters of who to trust in
114 this scheme.
115
116 The question then is, what CA roots does Fossil trust?
117
118 If you are using a self-signed certificate, Fossil will initially not
119 know that it can trust your certificate, so you'll be asked if you want
120 to accept the certificate the first time you communicate with the
121 server. Verify the certificate fingerprint is correct, then answer
122 "always" if you want Fossil to remember your decision.
123
124 If you are cloning from or syncing to Fossil servers that use a
125 certificate signed by a well-known CA or one of its delegates, Fossil
126 still has to know which CA roots to trust. When this fails, you get a
127 big long error message that starts with this text:
128
129 <pre>
130 SSL verification failed: unable to get local issuer certificate
131 </pre>
132
133 Fossil relies on the OpenSSL library to have some way to check a trusted
134 list of CA signing keys. There are two common ways this fails:
135
136 # <p>The OpenSSL library Fossil is linked to doesn't have a CA
137 signing key set at all, so that it initially trusts no certificates
138 at all.</p>
139 # <p>The OpenSSL library does have a CA cert set, but your Fossil server's
@@ -137,11 +148,13 @@
148 fossil set --global ssl-ca-location /path/to/local-ca.pem
149 </pre>
150
151 The use of <tt>--global</tt> with this option is common, since you may
152 have multiple reposotories served under certificates signed by that same
153 CA. However, if you have a mix of publicly-signed and locally-signed
154 certificates, you might want to drop the <tt>--global</tt> flag and set
155 this option on a per-repository basis instead.
156
157 A common way to run into the broader first problem is that you're on
158 FreeBSD, which does not install a CA certificate set by default, even as
159 a dependency of the OpenSSL library. If you're using a certificate
160 signed by one of the major public CAs, you can solve this by installing
@@ -155,15 +168,15 @@
168 certificate set, but it's not in a format that OpenSSL understands how
169 to use. Rather than try to find a way to convert the data format, you
170 may find it acceptable to use the same Mozilla NSS cert set. I do not
171 know of a way to easily get this from Mozilla themselves, but I did find
172 a [https://curl.haxx.se/docs/caextract.html|third party source] for the
173 <tt>cacert.pem</tt> file. I suggest placing the file into your Windows
174 user home directory so that you can then point Fossil at it like so:
175
176 <pre>
177 fossil set --global ssl-ca-location %userprofile%\cacert.pem
178 </pre>
179
180 This can also happen if you've linked Fossil to a version of OpenSSL
181 [#openssl-src|built from source]. That same <tt>cacert.pem</tt> fix can
182 work in that case, too.
183

Keyboard Shortcuts

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