Fossil SCM
Merged in trunk.
Commit
628c39fb602423f5d96224e2e49736f3889748aa55e70321eb5bad57604fa8b0
Parent
a7a75e7d412130d…
40 files changed
+150
-60
+1
-1
+14
-3
+1
+4
+19
+1
-1
+1
-1
+5
-1
+3
-1
+228
-121
+62
-14
+8
-2
+2
-2
+1
-1
+34
-7
+5
-6
+55
+1
-1
+15
-1
+49
-10
+18
-14
+15
+1
-1
+1
-1
+72
+1
-1
+14
-1
+1
-1
+1
-1
+7
-2
+8
-8
+38
-16
+4
-4
+26
-13
~
src/alerts.c
~
src/capabilities.c
~
src/checkin.c
~
src/configure.c
~
src/db.c
~
src/default_css.txt
~
src/diff.c
~
src/file.c
~
src/fshell.c
~
src/graph.c
~
src/http_ssl.c
~
src/login.c
~
src/main.c
~
src/makemake.tcl
~
src/security_audit.c
~
src/setup.c
~
src/setupuser.c
~
src/skins.c
~
src/tar.c
~
src/timeline.c
~
src/unversioned.c
~
src/url.c
~
src/util.c
~
src/zip.c
~
test/tester.tcl
~
tools/email-monitor.tcl
~
tools/encode_math.sh
~
tools/fossil-autocomplete.bash
~
tools/fossil-diff-log
~
tools/fossil-stress.tcl
~
tools/fossil_chat.tcl
~
win/Makefile.mingw
~
win/Makefile.mingw.mistachkin
~
win/Makefile.msc
~
www/build.wiki
~
www/changes.wiki
~
www/fossil-v-git.wiki
~
www/mirrorlimitations.md
~
www/rebaseharm.md
~
www/ssl.wiki
+150
-60
| --- src/alerts.c | ||
| +++ src/alerts.c | ||
| @@ -1208,10 +1208,19 @@ | ||
| 1208 | 1208 | int i, j, n; |
| 1209 | 1209 | char c; |
| 1210 | 1210 | |
| 1211 | 1211 | *peErr = 0; |
| 1212 | 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 | + } | |
| 1213 | 1222 | |
| 1214 | 1223 | /* Check the validity of the email address. |
| 1215 | 1224 | ** |
| 1216 | 1225 | ** (1) Exactly one '@' character. |
| 1217 | 1226 | ** (2) No other characters besides [a-zA-Z0-9._+-] |
| @@ -1219,11 +1228,15 @@ | ||
| 1219 | 1228 | ** The local part is currently more restrictive than RFC 5322 allows: |
| 1220 | 1229 | ** https://stackoverflow.com/a/2049510/142454 We will expand this as |
| 1221 | 1230 | ** necessary. |
| 1222 | 1231 | */ |
| 1223 | 1232 | zEAddr = P("e"); |
| 1224 | - if( zEAddr==0 ) return 0; | |
| 1233 | + if( zEAddr==0 ){ | |
| 1234 | + *peErr = 1; | |
| 1235 | + *pzErr = mprintf("required"); | |
| 1236 | + return 0; | |
| 1237 | + } | |
| 1225 | 1238 | for(i=j=n=0; (c = zEAddr[i])!=0; i++){ |
| 1226 | 1239 | if( c=='@' ){ |
| 1227 | 1240 | n = i; |
| 1228 | 1241 | j++; |
| 1229 | 1242 | continue; |
| @@ -1249,14 +1262,13 @@ | ||
| 1249 | 1262 | *peErr = 1; |
| 1250 | 1263 | *pzErr = mprintf("email domain too short"); |
| 1251 | 1264 | return 0; |
| 1252 | 1265 | } |
| 1253 | 1266 | |
| 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"); | |
| 1258 | 1270 | return 0; |
| 1259 | 1271 | } |
| 1260 | 1272 | |
| 1261 | 1273 | /* Check to make sure the email address is available for reuse */ |
| 1262 | 1274 | if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q", zEAddr) ){ |
| @@ -1347,10 +1359,14 @@ | ||
| 1347 | 1359 | /* Everybody else jumps to the page to administer their own |
| 1348 | 1360 | ** account only. */ |
| 1349 | 1361 | cgi_redirectf("%R/alerts"); |
| 1350 | 1362 | return; |
| 1351 | 1363 | } |
| 1364 | + } | |
| 1365 | + if( !g.perm.Admin && !db_get_boolean("anon-subscribe",1) ){ | |
| 1366 | + register_page(); | |
| 1367 | + return; | |
| 1352 | 1368 | } |
| 1353 | 1369 | alert_submenu_common(); |
| 1354 | 1370 | needCaptcha = !login_is_individual(); |
| 1355 | 1371 | if( P("submit") |
| 1356 | 1372 | && cgi_csrf_safe(1) |
| @@ -1368,10 +1384,11 @@ | ||
| 1368 | 1384 | if( PB("sa") ) ssub[nsub++] = 'a'; |
| 1369 | 1385 | if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c'; |
| 1370 | 1386 | if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f'; |
| 1371 | 1387 | if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't'; |
| 1372 | 1388 | if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w'; |
| 1389 | + if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x'; | |
| 1373 | 1390 | ssub[nsub] = 0; |
| 1374 | 1391 | db_multi_exec( |
| 1375 | 1392 | "INSERT INTO subscriber(semail,suname," |
| 1376 | 1393 | " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)" |
| 1377 | 1394 | "VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)", |
| @@ -1388,11 +1405,15 @@ | ||
| 1388 | 1405 | id); |
| 1389 | 1406 | if( !needCaptcha ){ |
| 1390 | 1407 | /* The new subscription has been added on behalf of a logged-in user. |
| 1391 | 1408 | ** No verification is required. Jump immediately to /alerts page. |
| 1392 | 1409 | */ |
| 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 | + } | |
| 1394 | 1415 | return; |
| 1395 | 1416 | }else{ |
| 1396 | 1417 | /* We need to send a verification email */ |
| 1397 | 1418 | Blob hdr, body; |
| 1398 | 1419 | AlertSender *pSender = alert_sender_new(0,0); |
| @@ -1410,11 +1431,11 @@ | ||
| 1410 | 1431 | @ <blockquote><pre> |
| 1411 | 1432 | @ %h(pSender->zErr) |
| 1412 | 1433 | @ </pre></blockquote> |
| 1413 | 1434 | }else{ |
| 1414 | 1435 | @ <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 | |
| 1416 | 1437 | @ subscription.</p> |
| 1417 | 1438 | } |
| 1418 | 1439 | alert_sender_free(pSender); |
| 1419 | 1440 | style_footer(); |
| 1420 | 1441 | } |
| @@ -1442,16 +1463,22 @@ | ||
| 1442 | 1463 | if( eErr==1 ){ |
| 1443 | 1464 | @ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
| 1444 | 1465 | } |
| 1445 | 1466 | @ </tr> |
| 1446 | 1467 | 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 | + } | |
| 1448 | 1475 | zDecoded = captcha_decode(uSeed); |
| 1449 | 1476 | zCaptcha = captcha_render(zDecoded); |
| 1450 | 1477 | @ <tr> |
| 1451 | 1478 | @ <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"> | |
| 1453 | 1480 | captcha_speakit_button(uSeed, "Speak the code"); |
| 1454 | 1481 | @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td> |
| 1455 | 1482 | @ </tr> |
| 1456 | 1483 | if( eErr==2 ){ |
| 1457 | 1484 | @ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
| @@ -1478,10 +1505,12 @@ | ||
| 1478 | 1505 | @ Check-ins</label><br> |
| 1479 | 1506 | } |
| 1480 | 1507 | if( g.perm.RdForum ){ |
| 1481 | 1508 | @ <label><input type="checkbox" name="sf" %s(PCK("sf"))> \ |
| 1482 | 1509 | @ Forum Posts</label><br> |
| 1510 | + @ <label><input type="checkbox" name="sx" %s(PCK("sx"))> \ | |
| 1511 | + @ Forum Edits</label><br> | |
| 1483 | 1512 | } |
| 1484 | 1513 | if( g.perm.RdTkt ){ |
| 1485 | 1514 | @ <label><input type="checkbox" name="st" %s(PCK("st"))> \ |
| 1486 | 1515 | @ Ticket changes</label><br> |
| 1487 | 1516 | } |
| @@ -1531,21 +1560,20 @@ | ||
| 1531 | 1560 | /* |
| 1532 | 1561 | ** Either shutdown or completely delete a subscription entry given |
| 1533 | 1562 | ** by the hex value zName. Then paint a webpage that explains that |
| 1534 | 1563 | ** the entry has been removed. |
| 1535 | 1564 | */ |
| 1536 | -static void alert_unsubscribe(const char *zName){ | |
| 1565 | +static void alert_unsubscribe(int sid){ | |
| 1537 | 1566 | char *zEmail; |
| 1538 | 1567 | zEmail = db_text(0, "SELECT semail FROM subscriber" |
| 1539 | - " WHERE subscriberCode=hextoblob(%Q)", zName); | |
| 1568 | + " WHERE subscriberId=%d", sid); | |
| 1540 | 1569 | if( zEmail==0 ){ |
| 1541 | 1570 | style_header("Unsubscribe Fail"); |
| 1542 | 1571 | @ <p>Unable to locate a subscriber with the requested key</p> |
| 1543 | 1572 | }else{ |
| 1544 | 1573 | db_multi_exec( |
| 1545 | - "DELETE FROM subscriber WHERE subscriberCode=hextoblob(%Q)", | |
| 1546 | - zName | |
| 1574 | + "DELETE FROM subscriber WHERE subscriberId=%d", sid | |
| 1547 | 1575 | ); |
| 1548 | 1576 | style_header("Unsubscribed"); |
| 1549 | 1577 | @ <p>The "%h(zEmail)" email address has been delisted. |
| 1550 | 1578 | @ All traces of that email address have been removed</p> |
| 1551 | 1579 | } |
| @@ -1556,48 +1584,83 @@ | ||
| 1556 | 1584 | /* |
| 1557 | 1585 | ** WEBPAGE: alerts |
| 1558 | 1586 | ** |
| 1559 | 1587 | ** Edit email alert and notification settings. |
| 1560 | 1588 | ** |
| 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. | |
| 1562 | 1596 | ** |
| 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. | |
| 1564 | 1600 | ** |
| 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 | |
| 1566 | 1602 | ** "anonymous". In that case the notification settings |
| 1567 | 1603 | ** associated with that account can be edited without needing |
| 1568 | 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. | |
| 1569 | 1611 | */ |
| 1570 | 1612 | 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 | + } | |
| 1586 | 1635 | login_check_credentials(); |
| 1587 | 1636 | if( !g.perm.EmailAlert ){ |
| 1637 | + db_commit_transaction(); | |
| 1588 | 1638 | login_needed(g.anon.EmailAlert); |
| 1589 | - return; | |
| 1639 | + /*NOTREACHED*/ | |
| 1590 | 1640 | } |
| 1591 | 1641 | 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); | |
| 1595 | 1657 | } |
| 1596 | - if( zName==0 || !validate16(zName, -1) ){ | |
| 1658 | + if( sid==0 ){ | |
| 1659 | + db_commit_transaction(); | |
| 1597 | 1660 | cgi_redirect("subscribe"); |
| 1598 | - return; | |
| 1661 | + /*NOTREACHED*/ | |
| 1599 | 1662 | } |
| 1600 | 1663 | alert_submenu_common(); |
| 1601 | 1664 | if( P("submit")!=0 && cgi_csrf_safe(1) ){ |
| 1602 | 1665 | char newSsub[10]; |
| 1603 | 1666 | int nsub = 0; |
| @@ -1641,11 +1704,11 @@ | ||
| 1641 | 1704 | if( semail==0 || email_address_is_valid(semail,0)==0 ){ |
| 1642 | 1705 | eErr = 8; |
| 1643 | 1706 | } |
| 1644 | 1707 | blob_append_sql(&update, ", semail=%Q", semail); |
| 1645 | 1708 | } |
| 1646 | - blob_append_sql(&update," WHERE subscriberCode=hextoblob(%Q)", zName); | |
| 1709 | + blob_append_sql(&update," WHERE subscriberId=%d", sid); | |
| 1647 | 1710 | if( eErr==0 ){ |
| 1648 | 1711 | db_exec_sql(blob_str(&update)); |
| 1649 | 1712 | ssub = 0; |
| 1650 | 1713 | } |
| 1651 | 1714 | blob_reset(&update); |
| @@ -1654,12 +1717,13 @@ | ||
| 1654 | 1717 | if( !PB("dodelete") ){ |
| 1655 | 1718 | eErr = 9; |
| 1656 | 1719 | zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to" |
| 1657 | 1720 | " unsubscribe"); |
| 1658 | 1721 | }else{ |
| 1659 | - alert_unsubscribe(zName); | |
| 1660 | - return; | |
| 1722 | + alert_unsubscribe(sid); | |
| 1723 | + db_commit_transaction(); | |
| 1724 | + return; | |
| 1661 | 1725 | } |
| 1662 | 1726 | } |
| 1663 | 1727 | style_header("Update Subscription"); |
| 1664 | 1728 | db_prepare(&q, |
| 1665 | 1729 | "SELECT" |
| @@ -1669,16 +1733,18 @@ | ||
| 1669 | 1733 | " sdigest," /* 3 */ |
| 1670 | 1734 | " ssub," /* 4 */ |
| 1671 | 1735 | " smip," /* 5 */ |
| 1672 | 1736 | " suname," /* 6 */ |
| 1673 | 1737 | " 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); | |
| 1676 | 1741 | if( db_step(&q)!=SQLITE_ROW ){ |
| 1677 | 1742 | db_finalize(&q); |
| 1743 | + db_commit_transaction(); | |
| 1678 | 1744 | cgi_redirect("subscribe"); |
| 1679 | - return; | |
| 1745 | + /*NOTREACHED*/ | |
| 1680 | 1746 | } |
| 1681 | 1747 | if( ssub==0 ){ |
| 1682 | 1748 | semail = db_column_text(&q, 0); |
| 1683 | 1749 | sdonotcall = db_column_int(&q, 2); |
| 1684 | 1750 | sdigest = db_column_int(&q, 3); |
| @@ -1696,23 +1762,45 @@ | ||
| 1696 | 1762 | sx = strchr(ssub,'x')!=0; |
| 1697 | 1763 | smip = db_column_text(&q, 5); |
| 1698 | 1764 | mtime = db_column_text(&q, 7); |
| 1699 | 1765 | sctime = db_column_text(&q, 8); |
| 1700 | 1766 | 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 | + } | |
| 1708 | 1794 | }else{ |
| 1709 | 1795 | @ <p>Make changes to the email subscription shown below and |
| 1710 | 1796 | @ press "Submit".</p> |
| 1711 | 1797 | } |
| 1712 | 1798 | 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)"> | |
| 1714 | 1802 | @ <table class="subscribe"> |
| 1715 | 1803 | @ <tr> |
| 1716 | 1804 | @ <td class="form_label">Email Address:</td> |
| 1717 | 1805 | if( isLogin ){ |
| 1718 | 1806 | @ <td><input type="text" name="semail" value="%h(semail)" size="30">\ |
| @@ -1739,10 +1827,13 @@ | ||
| 1739 | 1827 | @ </tr> |
| 1740 | 1828 | @ <tr> |
| 1741 | 1829 | @ <td class='form_label'>IP Address:</td> |
| 1742 | 1830 | @ <td>%h(smip)</td> |
| 1743 | 1831 | @ </tr> |
| 1832 | + @ <tr> | |
| 1833 | + @ <td class='form_label'>Subscriber Code:</td> | |
| 1834 | + @ <td>%h(db_column_text(&q,9))</td> | |
| 1744 | 1835 | @ <tr> |
| 1745 | 1836 | @ <td class="form_label">User:</td> |
| 1746 | 1837 | @ <td><input type="text" name="suname" value="%h(suname?suname:"")" \ |
| 1747 | 1838 | @ size="30">\ |
| 1748 | 1839 | uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", suname); |
| @@ -1780,14 +1871,10 @@ | ||
| 1780 | 1871 | @ <td><select size="1" name="sdigest"> |
| 1781 | 1872 | @ <option value="0" %s(sdigest?"":"selected")>Individual Emails</option> |
| 1782 | 1873 | @ <option value="1" %s(sdigest?"selected":"")>Daily Digest</option> |
| 1783 | 1874 | @ </select></td> |
| 1784 | 1875 | @ </tr> |
| 1785 | -#if 0 | |
| 1786 | - @ <label><input type="checkbox" name="sdigest" %s(sdigest?"checked":"")>\ | |
| 1787 | - @ Daily digest only</label><br> | |
| 1788 | -#endif | |
| 1789 | 1876 | if( g.perm.Admin ){ |
| 1790 | 1877 | @ <tr> |
| 1791 | 1878 | @ <td class="form_label">Admin Options:</td><td> |
| 1792 | 1879 | @ <label><input type="checkbox" name="sdonotcall" \ |
| 1793 | 1880 | @ %s(sdonotcall?"checked":"")> Do not disturb</label><br> |
| @@ -1811,10 +1898,12 @@ | ||
| 1811 | 1898 | @ </table> |
| 1812 | 1899 | @ </form> |
| 1813 | 1900 | fossil_free(zErr); |
| 1814 | 1901 | db_finalize(&q); |
| 1815 | 1902 | style_footer(); |
| 1903 | + db_commit_transaction(); | |
| 1904 | + return; | |
| 1816 | 1905 | } |
| 1817 | 1906 | |
| 1818 | 1907 | /* This is the message that gets sent to describe how to change |
| 1819 | 1908 | ** or modify a subscription |
| 1820 | 1909 | */ |
| @@ -1852,18 +1941,19 @@ | ||
| 1852 | 1941 | char *zCaptcha = 0; |
| 1853 | 1942 | int dx; |
| 1854 | 1943 | int bSubmit; |
| 1855 | 1944 | const char *zEAddr; |
| 1856 | 1945 | char *zCode = 0; |
| 1946 | + int sid = 0; | |
| 1857 | 1947 | |
| 1858 | 1948 | /* If a valid subscriber code is supplied, then unsubscribe immediately. |
| 1859 | 1949 | */ |
| 1860 | 1950 | 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 | |
| 1863 | 1953 | ){ |
| 1864 | - alert_unsubscribe(zName); | |
| 1954 | + alert_unsubscribe(sid); | |
| 1865 | 1955 | return; |
| 1866 | 1956 | } |
| 1867 | 1957 | |
| 1868 | 1958 | /* Logged in users are redirected to the /alerts page */ |
| 1869 | 1959 | login_check_credentials(); |
| @@ -2021,11 +2111,11 @@ | ||
| 2021 | 2111 | if( nDel>0 ){ |
| 2022 | 2112 | @ <p>*** %d(nDel) pending subscriptions deleted ***</p> |
| 2023 | 2113 | } |
| 2024 | 2114 | blob_init(&sql, 0, 0); |
| 2025 | 2115 | blob_append_sql(&sql, |
| 2026 | - "SELECT hex(subscriberCode)," /* 0 */ | |
| 2116 | + "SELECT subscriberId," /* 0 */ | |
| 2027 | 2117 | " semail," /* 1 */ |
| 2028 | 2118 | " ssub," /* 2 */ |
| 2029 | 2119 | " suname," /* 3 */ |
| 2030 | 2120 | " sverified," /* 4 */ |
| 2031 | 2121 | " sdigest," /* 5 */ |
| @@ -2058,11 +2148,11 @@ | ||
| 2058 | 2148 | sqlite3_int64 iMtime = db_column_int64(&q, 6); |
| 2059 | 2149 | double rAge = (iNow - iMtime)/86400.0; |
| 2060 | 2150 | int uid = db_column_int(&q, 8); |
| 2061 | 2151 | const char *zUname = db_column_text(&q, 3); |
| 2062 | 2152 | @ <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))'>\ | |
| 2064 | 2154 | @ %h(db_column_text(&q,1))</a></td> |
| 2065 | 2155 | @ <td>%h(db_column_text(&q,2))</td> |
| 2066 | 2156 | @ <td>%s(db_column_int(&q,5)?"digest":"")</td> |
| 2067 | 2157 | if( uid ){ |
| 2068 | 2158 | @ <td><a href='%R/setup_uedit?id=%d(uid)'>%h(zUname)</a> |
| 2069 | 2159 |
| --- 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'>↑ %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'>↑ %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 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'>↑ %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'>↑ %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 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 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 |
+1
-1
| --- src/capabilities.c | ||
| +++ src/capabilities.c | ||
| @@ -366,11 +366,11 @@ | ||
| 366 | 366 | CapabilityString *pCap; |
| 367 | 367 | char *zSelfCap; |
| 368 | 368 | char *zPubPages = db_get("public-pages",0); |
| 369 | 369 | int hasPubPages = zPubPages && zPubPages[0]; |
| 370 | 370 | |
| 371 | - pCap = capability_add(0, db_get("default-perms",0)); | |
| 371 | + pCap = capability_add(0, db_get("default-perms","u")); | |
| 372 | 372 | capability_expand(pCap); |
| 373 | 373 | zSelfCap = capability_string(pCap); |
| 374 | 374 | capability_free(pCap); |
| 375 | 375 | |
| 376 | 376 | db_prepare(&q, |
| 377 | 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",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 @@ | ||
| 2090 | 2090 | useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0; |
| 2091 | 2091 | noSign = find_option("nosign",0,0)!=0; |
| 2092 | 2092 | privateFlag = find_option("private",0,0)!=0; |
| 2093 | 2093 | forceDelta = find_option("delta",0,0)!=0; |
| 2094 | 2094 | 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 | + } | |
| 2097 | 2102 | } |
| 2098 | 2103 | dryRunFlag = find_option("dry-run","n",0)!=0; |
| 2099 | 2104 | if( !dryRunFlag ){ |
| 2100 | 2105 | dryRunFlag = find_option("test",0,0)!=0; /* deprecated */ |
| 2101 | 2106 | } |
| @@ -2174,11 +2179,14 @@ | ||
| 2174 | 2179 | ** manifest) can continue to use this repository, do not create a new |
| 2175 | 2180 | ** delta-manifest unless this repository already contains one or more |
| 2176 | 2181 | ** delta-manifests, or unless the delta-manifest is explicitly requested |
| 2177 | 2182 | ** by the --delta option. |
| 2178 | 2183 | */ |
| 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 | + ){ | |
| 2180 | 2188 | forceBaseline = 1; |
| 2181 | 2189 | } |
| 2182 | 2190 | |
| 2183 | 2191 | /* |
| 2184 | 2192 | ** Autosync if autosync is enabled and this is not a private check-in. |
| @@ -2648,10 +2656,11 @@ | ||
| 2648 | 2656 | /* Commit */ |
| 2649 | 2657 | db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'"); |
| 2650 | 2658 | db_multi_exec("PRAGMA repository.application_id=252006673;"); |
| 2651 | 2659 | db_multi_exec("PRAGMA localdb.application_id=252006674;"); |
| 2652 | 2660 | if( dryRunFlag ){ |
| 2661 | + leaf_ambiguity_warning(nvid,nvid); | |
| 2653 | 2662 | db_end_transaction(1); |
| 2654 | 2663 | exit(1); |
| 2655 | 2664 | } |
| 2656 | 2665 | db_end_transaction(0); |
| 2657 | 2666 | |
| @@ -2670,7 +2679,9 @@ | ||
| 2670 | 2679 | int nTries = db_get_int("autosync-tries",1); |
| 2671 | 2680 | autosync_loop(syncFlags, nTries, 0); |
| 2672 | 2681 | } |
| 2673 | 2682 | if( count_nonbranch_children(vid)>1 ){ |
| 2674 | 2683 | fossil_print("**** warning: a fork has occurred *****\n"); |
| 2684 | + }else{ | |
| 2685 | + leaf_ambiguity_warning(nvid,nvid); | |
| 2675 | 2686 | } |
| 2676 | 2687 | } |
| 2677 | 2688 |
| --- 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 |
+1
| --- src/configure.c | ||
| +++ src/configure.c | ||
| @@ -147,10 +147,11 @@ | ||
| 147 | 147 | { "parent-project-code", CONFIGSET_PROJ }, |
| 148 | 148 | { "parent-project-name", CONFIGSET_PROJ }, |
| 149 | 149 | { "hash-policy", CONFIGSET_PROJ }, |
| 150 | 150 | { "comment-format", CONFIGSET_PROJ }, |
| 151 | 151 | { "mimetypes", CONFIGSET_PROJ }, |
| 152 | + { "forbid-delta-manifests", CONFIGSET_PROJ }, | |
| 152 | 153 | |
| 153 | 154 | #ifdef FOSSIL_ENABLE_LEGACY_MV_RM |
| 154 | 155 | { "mv-rm-files", CONFIGSET_PROJ }, |
| 155 | 156 | #endif |
| 156 | 157 | |
| 157 | 158 |
| --- 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 |
M
src/db.c
+4
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -3556,10 +3556,14 @@ | ||
| 3556 | 3556 | /* |
| 3557 | 3557 | ** SETTING: pgp-command width=40 |
| 3558 | 3558 | ** Command used to clear-sign manifests at check-in. |
| 3559 | 3559 | ** Default value is "gpg --clearsign -o" |
| 3560 | 3560 | */ |
| 3561 | +/* | |
| 3562 | +** SETTING: forbid-delta-manifests boolean default=off | |
| 3563 | +** If enabled, new delta manifests are prohibited. | |
| 3564 | +*/ | |
| 3561 | 3565 | /* |
| 3562 | 3566 | ** SETTING: proxy width=32 default=off |
| 3563 | 3567 | ** URL of the HTTP proxy. If undefined or "off" then |
| 3564 | 3568 | ** the "http_proxy" environment variable is consulted. |
| 3565 | 3569 | ** If the http_proxy environment variable is undefined |
| 3566 | 3570 |
| --- 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 |
+19
| --- src/default_css.txt | ||
| +++ src/default_css.txt | ||
| @@ -841,5 +841,24 @@ | ||
| 841 | 841 | } |
| 842 | 842 | .accordion_panel { |
| 843 | 843 | overflow: hidden; |
| 844 | 844 | transition: max-height 0.25s ease-out; |
| 845 | 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 | +// } | |
| 846 | 865 |
| --- 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 @@ | ||
| 2 | 2 | ** Copyright (c) 2007 D. Richard Hipp |
| 3 | 3 | ** |
| 4 | 4 | ** This program is free software; you can redistribute it and/or |
| 5 | 5 | ** modify it under the terms of the Simplified BSD License (also |
| 6 | 6 | ** known as the "2-Clause License" or "FreeBSD License".) |
| 7 | - | |
| 7 | +** | |
| 8 | 8 | ** This program is distributed in the hope that it will be useful, |
| 9 | 9 | ** but without any warranty; without even the implied warranty of |
| 10 | 10 | ** merchantability or fitness for a particular purpose. |
| 11 | 11 | ** |
| 12 | 12 | ** Author contact information: |
| 13 | 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 |
| --- 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 @@ | ||
| 294 | 294 | ** |
| 295 | 295 | ** On windows, this routine returns only PERM_REG. |
| 296 | 296 | */ |
| 297 | 297 | int file_perm(const char *zFilename, int eFType){ |
| 298 | 298 | #if !defined(_WIN32) |
| 299 | - if( !getStat(zFilename, RepoFILE) ){ | |
| 299 | + if( !getStat(zFilename, eFType) ){ | |
| 300 | 300 | if( S_ISREG(fx.fileStat.st_mode) && ((S_IXUSR)&fx.fileStat.st_mode)!=0 ) |
| 301 | 301 | return PERM_EXE; |
| 302 | 302 | else if( db_allow_symlinks() && S_ISLNK(fx.fileStat.st_mode) ) |
| 303 | 303 | return PERM_LNK; |
| 304 | 304 | } |
| 305 | 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, 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 @@ | ||
| 60 | 60 | pid_t childPid; |
| 61 | 61 | char *zLine = 0; |
| 62 | 62 | char *zPrompt = 0; |
| 63 | 63 | fDebug = find_option("debug", 0, 0)!=0; |
| 64 | 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")); | |
| 65 | + if(g.zRepositoryName!=0){ | |
| 66 | + zPrompt = mprintf("fossil (%z)> ", db_get("project-name","unnamed")); | |
| 67 | + }else{ | |
| 68 | + zPrompt = mprintf("fossil (no repo)> "); | |
| 69 | + } | |
| 66 | 70 | db_close(0); |
| 67 | 71 | sqlite3_shutdown(); |
| 68 | 72 | linenoiseSetMultiLine(1); |
| 69 | 73 | while( (free(zLine), zLine = linenoise(zPrompt)) ){ |
| 70 | 74 | /* Remember shell history within the current session */ |
| 71 | 75 |
| --- 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 @@ | ||
| 534 | 534 | pParent->idxTop = pRow->idxTop; |
| 535 | 535 | } |
| 536 | 536 | } |
| 537 | 537 | |
| 538 | 538 | 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 | |
| 540 | 541 | ** that is in the same branch and has no in-graph parent, then |
| 541 | 542 | ** make the lower node a step-child of the upper node. This will |
| 542 | 543 | ** be represented on the graph by a thick dotted line without an arrowhead. |
| 543 | 544 | */ |
| 544 | 545 | for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ |
| 545 | 546 | if( pRow->pChild ) continue; |
| 547 | + if( pRow->isLeaf ) continue; | |
| 546 | 548 | for(pLoop=pRow->pPrev; pLoop; pLoop=pLoop->pPrev){ |
| 547 | 549 | if( pLoop->nParent>0 |
| 548 | 550 | && pLoop->zBranch==pRow->zBranch |
| 549 | 551 | && hashFind(p,pLoop->aParent[0])==0 |
| 550 | 552 | ){ |
| 551 | 553 |
| --- 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 @@ | ||
| 30 | 30 | #ifdef FOSSIL_ENABLE_SSL |
| 31 | 31 | |
| 32 | 32 | #include <openssl/bio.h> |
| 33 | 33 | #include <openssl/ssl.h> |
| 34 | 34 | #include <openssl/err.h> |
| 35 | +#include <openssl/x509.h> | |
| 35 | 36 | |
| 36 | 37 | #include "http_ssl.h" |
| 37 | 38 | #include <assert.h> |
| 38 | 39 | #include <sys/types.h> |
| 39 | 40 | |
| @@ -45,11 +46,15 @@ | ||
| 45 | 46 | static int sslIsInit = 0; /* True after global initialization */ |
| 46 | 47 | static BIO *iBio = 0; /* OpenSSL I/O abstraction */ |
| 47 | 48 | static char *sslErrMsg = 0; /* Text of most recent OpenSSL error */ |
| 48 | 49 | static SSL_CTX *sslCtx; /* SSL context */ |
| 49 | 50 | 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 */ | |
| 51 | 56 | |
| 52 | 57 | /* |
| 53 | 58 | ** Clear the SSL error message |
| 54 | 59 | */ |
| 55 | 60 | static void ssl_clear_errmsg(void){ |
| @@ -184,11 +189,12 @@ | ||
| 184 | 189 | Blob snd, reply; |
| 185 | 190 | int done=0,end=0; |
| 186 | 191 | blob_zero(&snd); |
| 187 | 192 | blob_appendf(&snd, "CONNECT %s:%d HTTP/1.1\r\n", pUrlData->hostname, |
| 188 | 193 | 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); | |
| 190 | 196 | if( pUrlData->proxyAuth ){ |
| 191 | 197 | blob_appendf(&snd, "Proxy-Authorization: %s\r\n", pUrlData->proxyAuth); |
| 192 | 198 | } |
| 193 | 199 | blob_append(&snd, "Proxy-Connection: keep-alive\r\n", -1); |
| 194 | 200 | blob_appendf(&snd, "User-Agent: %s\r\n", get_user_agent()); |
| @@ -222,46 +228,45 @@ | ||
| 222 | 228 | }while(!done); |
| 223 | 229 | sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc); |
| 224 | 230 | blob_reset(&reply); |
| 225 | 231 | return rc; |
| 226 | 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 | +} | |
| 227 | 244 | |
| 228 | 245 | /* |
| 229 | 246 | ** Open an SSL connection. The identify of the server is determined |
| 230 | 247 | ** as follows: |
| 231 | 248 | ** |
| 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. | |
| 233 | 251 | ** pUrlData->port TCP/IP port to use. Ex: 80 |
| 234 | 252 | ** |
| 235 | 253 | ** Return the number of errors. |
| 236 | 254 | */ |
| 237 | 255 | int ssl_open(UrlData *pUrlData){ |
| 238 | 256 | X509 *cert; |
| 239 | - int hasSavedCertificate = 0; | |
| 240 | - int trusted = 0; | |
| 241 | - unsigned long e; | |
| 242 | 257 | |
| 243 | 258 | 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 | 259 | if( pUrlData->useProxy ){ |
| 256 | 260 | int rc; |
| 257 | 261 | char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port); |
| 258 | 262 | BIO *sBio = BIO_new_connect(connStr); |
| 259 | 263 | free(connStr); |
| 260 | 264 | if( BIO_do_connect(sBio)<=0 ){ |
| 261 | 265 | 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())); | |
| 263 | 268 | ssl_close(); |
| 264 | 269 | return 1; |
| 265 | 270 | } |
| 266 | 271 | rc = establish_proxy_tunnel(pUrlData, sBio); |
| 267 | 272 | if( rc<200||rc>299 ){ |
| @@ -282,11 +287,13 @@ | ||
| 282 | 287 | return 1; |
| 283 | 288 | } |
| 284 | 289 | BIO_get_ssl(iBio, &ssl); |
| 285 | 290 | |
| 286 | 291 | #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 | + ){ | |
| 288 | 295 | fossil_warning("WARNING: failed to set server name indication (SNI), " |
| 289 | 296 | "continuing without it.\n"); |
| 290 | 297 | } |
| 291 | 298 | #endif |
| 292 | 299 | |
| @@ -296,11 +303,12 @@ | ||
| 296 | 303 | char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port); |
| 297 | 304 | BIO_set_conn_hostname(iBio, connStr); |
| 298 | 305 | free(connStr); |
| 299 | 306 | if( BIO_do_connect(iBio)<=0 ){ |
| 300 | 307 | 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())); | |
| 302 | 310 | ssl_close(); |
| 303 | 311 | return 1; |
| 304 | 312 | } |
| 305 | 313 | } |
| 306 | 314 | |
| @@ -319,81 +327,78 @@ | ||
| 319 | 327 | ssl_set_errmsg("No SSL certificate was presented by the peer"); |
| 320 | 328 | ssl_close(); |
| 321 | 329 | return 1; |
| 322 | 330 | } |
| 323 | 331 | |
| 324 | - if( trusted<=0 && (e = SSL_get_verify_result(ssl)) != X509_V_OK ){ | |
| 332 | + if( !sslNoCertVerify && SSL_get_verify_result(ssl)!=X509_V_OK ){ | |
| 325 | 333 | char *desc, *prompt; |
| 326 | - const char *warning = ""; | |
| 327 | 334 | Blob ans; |
| 328 | 335 | char cReply; |
| 329 | 336 | BIO *mem; |
| 330 | 337 | 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); | |
| 384 | 387 | } |
| 385 | 388 | } |
| 386 | 389 | |
| 387 | 390 | /* Set the Global.zIpAddr variable to the server we are talking to. |
| 388 | 391 | ** This is used to populate the ipaddr column of the rcvfrom table, |
| 389 | 392 | ** if any files are received from the server. |
| 390 | 393 | */ |
| 391 | 394 | { |
| 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 \ | |
| 395 | 400 | && !defined(LIBRESSL_VERSION_NUMBER) |
| 396 | 401 | char *ip = BIO_ADDR_hostname_string(BIO_get_conn_address(iBio),1); |
| 397 | 402 | g.zIpAddr = mprintf("%s", ip); |
| 398 | 403 | OPENSSL_free(ip); |
| 399 | 404 | #else |
| @@ -407,58 +412,53 @@ | ||
| 407 | 412 | X509_free(cert); |
| 408 | 413 | return 0; |
| 409 | 414 | } |
| 410 | 415 | |
| 411 | 416 | /* |
| 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. | |
| 413 | 432 | */ |
| 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; | |
| 429 | 447 | } |
| 430 | 448 | |
| 431 | 449 | /* |
| 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. | |
| 434 | 451 | */ |
| 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); | |
| 460 | 460 | } |
| 461 | 461 | |
| 462 | 462 | /* |
| 463 | 463 | ** Send content out over the SSL connection. |
| 464 | 464 | */ |
| @@ -498,5 +498,112 @@ | ||
| 498 | 498 | } |
| 499 | 499 | return total; |
| 500 | 500 | } |
| 501 | 501 | |
| 502 | 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 | +} | |
| 503 | 610 |
| --- 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 @@ | ||
| 481 | 481 | int login_self_register_available(const char *zNeeded){ |
| 482 | 482 | CapabilityString *pCap; |
| 483 | 483 | int rc; |
| 484 | 484 | if( !db_get_boolean("self-register",0) ) return 0; |
| 485 | 485 | 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")); | |
| 487 | 487 | capability_expand(pCap); |
| 488 | 488 | rc = capability_has_any(pCap, zNeeded); |
| 489 | 489 | capability_free(pCap); |
| 490 | 490 | return rc; |
| 491 | 491 | } |
| @@ -1128,11 +1128,11 @@ | ||
| 1128 | 1128 | if( zPublicPages!=0 ){ |
| 1129 | 1129 | Glob *pGlob = glob_create(zPublicPages); |
| 1130 | 1130 | const char *zUri = PD("REQUEST_URI",""); |
| 1131 | 1131 | zUri += (int)strlen(g.zTop); |
| 1132 | 1132 | 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); | |
| 1134 | 1134 | } |
| 1135 | 1135 | glob_free(pGlob); |
| 1136 | 1136 | } |
| 1137 | 1137 | } |
| 1138 | 1138 | |
| @@ -1459,10 +1459,38 @@ | ||
| 1459 | 1459 | "SELECT 1 FROM event WHERE user=%Q OR euser=%Q", |
| 1460 | 1460 | zUserID, zUserID, zUserID |
| 1461 | 1461 | ); |
| 1462 | 1462 | return rc; |
| 1463 | 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 | +} | |
| 1464 | 1492 | |
| 1465 | 1493 | /* |
| 1466 | 1494 | ** WEBPAGE: register |
| 1467 | 1495 | ** |
| 1468 | 1496 | ** Page to allow users to self-register. The "self-register" setting |
| @@ -1471,13 +1499,14 @@ | ||
| 1471 | 1499 | void register_page(void){ |
| 1472 | 1500 | const char *zUserID, *zPasswd, *zConfirm, *zEAddr; |
| 1473 | 1501 | const char *zDName; |
| 1474 | 1502 | unsigned int uSeed; |
| 1475 | 1503 | const char *zDecoded; |
| 1476 | - char *zCaptcha; | |
| 1477 | 1504 | int iErrLine = -1; |
| 1478 | 1505 | const char *zErr = 0; |
| 1506 | + int captchaIsCorrect = 0; /* True on a correct captcha */ | |
| 1507 | + char *zCaptcha = ""; /* Value of the captcha text */ | |
| 1479 | 1508 | char *zPerms; /* Permissions for the default user */ |
| 1480 | 1509 | int canDoAlerts = 0; /* True if receiving email alerts is possible */ |
| 1481 | 1510 | int doAlerts = 0; /* True if subscription is wanted too */ |
| 1482 | 1511 | if( !db_get_boolean("self-register", 0) ){ |
| 1483 | 1512 | style_header("Registration not possible"); |
| @@ -1484,11 +1513,11 @@ | ||
| 1484 | 1513 | @ <p>This project does not allow user self-registration. Please contact the |
| 1485 | 1514 | @ project administrator to obtain an account.</p> |
| 1486 | 1515 | style_footer(); |
| 1487 | 1516 | return; |
| 1488 | 1517 | } |
| 1489 | - zPerms = db_get("default-perms", 0); | |
| 1518 | + zPerms = db_get("default-perms", "u"); | |
| 1490 | 1519 | |
| 1491 | 1520 | /* Prompt the user for email alerts if this repository is configured for |
| 1492 | 1521 | ** email alerts and if the default permissions include "7" */ |
| 1493 | 1522 | canDoAlerts = alert_tables_exist() && db_int(0, |
| 1494 | 1523 | "SELECT fullcap(%Q) GLOB '*7*'", zPerms |
| @@ -1503,11 +1532,11 @@ | ||
| 1503 | 1532 | |
| 1504 | 1533 | /* Verify user imputs */ |
| 1505 | 1534 | if( P("new")==0 || !cgi_csrf_safe(1) ){ |
| 1506 | 1535 | /* This is not a valid form submission. Fall through into |
| 1507 | 1536 | ** the form display */ |
| 1508 | - }else if( !captcha_is_correct(1) ){ | |
| 1537 | + }else if( (captchaIsCorrect = captcha_is_correct(1))==0 ){ | |
| 1509 | 1538 | iErrLine = 6; |
| 1510 | 1539 | zErr = "Incorrect CAPTCHA"; |
| 1511 | 1540 | }else if( strlen(zUserID)<6 ){ |
| 1512 | 1541 | iErrLine = 1; |
| 1513 | 1542 | zErr = "User ID too short. Must be at least 6 characters."; |
| @@ -1521,10 +1550,13 @@ | ||
| 1521 | 1550 | iErrLine = 3; |
| 1522 | 1551 | zErr = "Required"; |
| 1523 | 1552 | }else if( email_address_is_valid(zEAddr,0)==0 ){ |
| 1524 | 1553 | iErrLine = 3; |
| 1525 | 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"; | |
| 1526 | 1558 | }else if( strlen(zPasswd)<6 ){ |
| 1527 | 1559 | iErrLine = 4; |
| 1528 | 1560 | zErr = "Password must be at least 6 characters long"; |
| 1529 | 1561 | }else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){ |
| 1530 | 1562 | iErrLine = 5; |
| @@ -1548,16 +1580,24 @@ | ||
| 1548 | 1580 | /* If all of the tests above have passed, that means that the submitted |
| 1549 | 1581 | ** form contains valid data and we can proceed to create the new login */ |
| 1550 | 1582 | Blob sql; |
| 1551 | 1583 | int uid; |
| 1552 | 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 | + } | |
| 1553 | 1593 | blob_init(&sql, 0, 0); |
| 1554 | 1594 | blob_append_sql(&sql, |
| 1555 | 1595 | "INSERT INTO user(login,pw,cap,info,mtime)\n" |
| 1556 | 1596 | "VALUES(%Q,%Q,%Q," |
| 1557 | 1597 | "'%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); | |
| 1559 | 1599 | fossil_free(zPass); |
| 1560 | 1600 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 1561 | 1601 | uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID); |
| 1562 | 1602 | login_set_user_cookie(zUserID, uid, NULL); |
| 1563 | 1603 | if( doAlerts ){ |
| @@ -1567,16 +1607,20 @@ | ||
| 1567 | 1607 | sqlite3_int64 id; /* New subscriber Id */ |
| 1568 | 1608 | const char *zCode; /* New subscriber code (in hex) */ |
| 1569 | 1609 | const char *zGoto = P("g"); |
| 1570 | 1610 | int nsub = 0; |
| 1571 | 1611 | char ssub[20]; |
| 1612 | + CapabilityString *pCap; | |
| 1613 | + pCap = capability_add(0, zPerms); | |
| 1614 | + capability_expand(pCap); | |
| 1572 | 1615 | 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'; | |
| 1577 | 1620 | ssub[nsub] = 0; |
| 1621 | + capability_free(pCap); | |
| 1578 | 1622 | /* Also add the user to the subscriber table. */ |
| 1579 | 1623 | db_multi_exec( |
| 1580 | 1624 | "INSERT INTO subscriber(semail,suname," |
| 1581 | 1625 | " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip)" |
| 1582 | 1626 | " VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q)" |
| @@ -1616,12 +1660,11 @@ | ||
| 1616 | 1660 | @ <blockquote><pre> |
| 1617 | 1661 | @ %h(pSender->zErr) |
| 1618 | 1662 | @ </pre></blockquote> |
| 1619 | 1663 | }else{ |
| 1620 | 1664 | @ <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> | |
| 1623 | 1666 | } |
| 1624 | 1667 | alert_sender_free(pSender); |
| 1625 | 1668 | if( zGoto ){ |
| 1626 | 1669 | @ <p><a href='%h(zGoto)'>Continue</a> |
| 1627 | 1670 | } |
| @@ -1630,11 +1673,15 @@ | ||
| 1630 | 1673 | } |
| 1631 | 1674 | redirect_to_g(); |
| 1632 | 1675 | } |
| 1633 | 1676 | |
| 1634 | 1677 | /* Prepare the captcha. */ |
| 1635 | - uSeed = captcha_seed(); | |
| 1678 | + if( captchaIsCorrect ){ | |
| 1679 | + uSeed = strtoul(P("captchaseed"),0,10); | |
| 1680 | + }else{ | |
| 1681 | + uSeed = captcha_seed(); | |
| 1682 | + } | |
| 1636 | 1683 | zDecoded = captcha_decode(uSeed); |
| 1637 | 1684 | zCaptcha = captcha_render(zDecoded); |
| 1638 | 1685 | |
| 1639 | 1686 | style_header("Register"); |
| 1640 | 1687 | /* Print out the registration form. */ |
| @@ -1689,11 +1736,12 @@ | ||
| 1689 | 1736 | if( iErrLine==5 ){ |
| 1690 | 1737 | @ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
| 1691 | 1738 | } |
| 1692 | 1739 | @ <tr> |
| 1693 | 1740 | @ <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"> | |
| 1695 | 1743 | captcha_speakit_button(uSeed, "Speak the captcha text"); |
| 1696 | 1744 | @ </td> |
| 1697 | 1745 | @ </tr> |
| 1698 | 1746 | if( iErrLine==6 ){ |
| 1699 | 1747 | @ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> |
| 1700 | 1748 |
| --- 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'>↑ %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'>↑ %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'>↑ %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'>↑ %h(zErr)</span></td></tr> |
| 1748 |
+8
-2
| --- src/main.c | ||
| +++ src/main.c | ||
| @@ -2523,18 +2523,24 @@ | ||
| 2523 | 2523 | ** |
| 2524 | 2524 | ** Works like the http command but gives setup permission to all users. |
| 2525 | 2525 | ** |
| 2526 | 2526 | ** Options: |
| 2527 | 2527 | ** --th-trace trace TH1 execution (for debugging purposes) |
| 2528 | +** --usercap CAP user capability string. (Default: "sx") | |
| 2528 | 2529 | ** |
| 2529 | 2530 | */ |
| 2530 | 2531 | void cmd_test_http(void){ |
| 2531 | 2532 | const char *zIpAddr; /* IP address of remote client */ |
| 2533 | + const char *zUserCap; | |
| 2532 | 2534 | |
| 2533 | 2535 | 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); | |
| 2536 | 2542 | g.httpIn = stdin; |
| 2537 | 2543 | g.httpOut = stdout; |
| 2538 | 2544 | fossil_binary_mode(g.httpOut); |
| 2539 | 2545 | fossil_binary_mode(g.httpIn); |
| 2540 | 2546 | g.zExtRoot = find_option("extroot",0,1); |
| 2541 | 2547 |
| --- 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 |
+2
-2
| --- src/makemake.tcl | ||
| +++ src/makemake.tcl | ||
| @@ -714,11 +714,11 @@ | ||
| 714 | 714 | #### The directories where the OpenSSL include and library files are located. |
| 715 | 715 | # The recommended usage here is to use the Sysinternals junction tool |
| 716 | 716 | # to create a hard link between an "openssl-1.x" sub-directory of the |
| 717 | 717 | # Fossil source code directory and the target OpenSSL source directory. |
| 718 | 718 | # |
| 719 | -OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1f | |
| 719 | +OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1g | |
| 720 | 720 | OPENSSLINCDIR = $(OPENSSLDIR)/include |
| 721 | 721 | OPENSSLLIBDIR = $(OPENSSLDIR) |
| 722 | 722 | |
| 723 | 723 | #### Either the directory where the Tcl library is installed or the Tcl |
| 724 | 724 | # source code directory resides (depending on the value of the macro |
| @@ -1571,11 +1571,11 @@ | ||
| 1571 | 1571 | !ifndef USE_SEE |
| 1572 | 1572 | USE_SEE = 0 |
| 1573 | 1573 | !endif |
| 1574 | 1574 | |
| 1575 | 1575 | !if $(FOSSIL_ENABLE_SSL)!=0 |
| 1576 | -SSLDIR = $(B)\compat\openssl-1.1.1f | |
| 1576 | +SSLDIR = $(B)\compat\openssl-1.1.1g | |
| 1577 | 1577 | SSLINCDIR = $(SSLDIR)\include |
| 1578 | 1578 | !if $(FOSSIL_DYNAMIC_BUILD)!=0 |
| 1579 | 1579 | SSLLIBDIR = $(SSLDIR) |
| 1580 | 1580 | !else |
| 1581 | 1581 | SSLLIBDIR = $(SSLDIR) |
| 1582 | 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.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 |
+1
-1
| --- src/security_audit.c | ||
| +++ src/security_audit.c | ||
| @@ -122,11 +122,11 @@ | ||
| 122 | 122 | zAnonCap = db_text("", "SELECT fullcap(NULL)"); |
| 123 | 123 | zDevCap = db_text("", "SELECT fullcap('v')"); |
| 124 | 124 | zReadCap = db_text("", "SELECT fullcap('u')"); |
| 125 | 125 | zPubPages = db_get("public-pages",0); |
| 126 | 126 | 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")); | |
| 128 | 128 | capability_expand(pCap); |
| 129 | 129 | zSelfCap = capability_string(pCap); |
| 130 | 130 | capability_free(pCap); |
| 131 | 131 | if( hasAnyCap(zAnonCap,"as") ){ |
| 132 | 132 | @ <li><p>This repository is <big><b>Wildly INSECURE</b></big> because |
| 133 | 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",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 @@ | ||
| 500 | 500 | @ (Property: "public-pages") |
| 501 | 501 | @ </p> |
| 502 | 502 | |
| 503 | 503 | @ <hr /> |
| 504 | 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> | |
| 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> | |
| 512 | 539 | |
| 513 | 540 | @ <hr /> |
| 514 | 541 | entry_attribute("Default privileges", 10, "default-perms", |
| 515 | 542 | "defaultperms", "u", 0); |
| 516 | 543 | @ <p>Permissions given to users that... <ul><li>register themselves using |
| 517 | 544 |
| --- 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 @@ | ||
| 549 | 549 | if( login_is_special(zLogin) ){ |
| 550 | 550 | @ <td><b>%h(zLogin)</b></td> |
| 551 | 551 | }else{ |
| 552 | 552 | @ <td><input type="text" name="login" value="%h(zLogin)" />\ |
| 553 | 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 | - @ <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 | + @ <a href="%R/alerts?sid=%d(sid)">\ | |
| 559 | 559 | @ (subscription info for %h(zLogin))</a>\ |
| 560 | 560 | } |
| 561 | - fossil_free(zSCode); | |
| 562 | 561 | } |
| 563 | 562 | @ </td></tr> |
| 564 | 563 | @ <tr> |
| 565 | 564 | @ <td class="usetupEditLabel">Contact Info:</td> |
| 566 | 565 | @ <td><textarea name="info" cols="40" rows="2">%h(zInfo)</textarea></td> |
| 567 | 566 |
| --- 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 | @ <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 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 | @ <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 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 @@ | ||
| 688 | 688 | } |
| 689 | 689 | } |
| 690 | 690 | return zResult; |
| 691 | 691 | } |
| 692 | 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 | +} | |
| 693 | 745 | |
| 694 | 746 | /* |
| 695 | 747 | ** WEBPAGE: setup_skinedit |
| 696 | 748 | ** |
| 697 | 749 | ** Edit aspects of a skin determined by the w= query parameter. |
| @@ -814,10 +866,13 @@ | ||
| 814 | 866 | blob_reset(&from); |
| 815 | 867 | blob_reset(&to); |
| 816 | 868 | blob_reset(&out); |
| 817 | 869 | } |
| 818 | 870 | @ </div></form> |
| 871 | + if(ii==0/*CSS*/){ | |
| 872 | + skin_emit_css_defaults(); | |
| 873 | + } | |
| 819 | 874 | style_footer(); |
| 820 | 875 | db_end_transaction(0); |
| 821 | 876 | } |
| 822 | 877 | |
| 823 | 878 | /* |
| 824 | 879 |
| --- 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 @@ | ||
| 443 | 443 | tar_begin(-1); |
| 444 | 444 | for(i=3; i<g.argc; i++){ |
| 445 | 445 | Blob file; |
| 446 | 446 | blob_zero(&file); |
| 447 | 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)); | |
| 448 | + tar_add_file(g.argv[i], &file, file_perm(0,eFType), file_mtime(0,eFType)); | |
| 449 | 449 | blob_reset(&file); |
| 450 | 450 | } |
| 451 | 451 | tar_finish(&zip); |
| 452 | 452 | blob_write_to_file(&zip, g.argv[2]); |
| 453 | 453 | } |
| 454 | 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,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 @@ | ||
| 1877 | 1877 | " HAVING count(*)>1;\n" |
| 1878 | 1878 | "INSERT OR IGNORE INTO rnfork(rid)" |
| 1879 | 1879 | " SELECT cid FROM plink\n" |
| 1880 | 1880 | " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==" |
| 1881 | 1881 | " (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", | |
| 1883 | 1897 | TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH |
| 1884 | 1898 | ); |
| 1885 | 1899 | tmFlags |= TIMELINE_UNHIDE; |
| 1886 | 1900 | zType = "ci"; |
| 1887 | 1901 | disableY = 1; |
| 1888 | 1902 |
| --- 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 @@ | ||
| 237 | 237 | ** edit FILE Bring up FILE in a text editor for modification. |
| 238 | 238 | ** |
| 239 | 239 | ** export FILE OUTPUT Write the content of FILE into OUTPUT on disk |
| 240 | 240 | ** |
| 241 | 241 | ** 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 | |
| 243 | 246 | ** |
| 244 | 247 | ** revert ?URL? Restore the state of all unversioned files in the |
| 245 | 248 | ** local repository to match the remote repository |
| 246 | 249 | ** URL. |
| 247 | 250 | ** |
| @@ -250,11 +253,14 @@ | ||
| 250 | 253 | ** -n|--dryrun Show what would have happened |
| 251 | 254 | ** |
| 252 | 255 | ** remove|rm|delete FILE ... |
| 253 | 256 | ** Remove unversioned files from the local repository. |
| 254 | 257 | ** 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 | |
| 256 | 262 | ** |
| 257 | 263 | ** sync ?URL? Synchronize the state of all unversioned files with |
| 258 | 264 | ** the remote repository URL. The most recent version |
| 259 | 265 | ** of each file is propagated to all repositories and |
| 260 | 266 | ** all prior versions are permanently forgotten. |
| @@ -339,11 +345,13 @@ | ||
| 339 | 345 | |
| 340 | 346 | verify_all_options(); |
| 341 | 347 | if( g.argc!=4) usage("edit UVFILE"); |
| 342 | 348 | zUVFile = g.argv[3]; |
| 343 | 349 | 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 | + } | |
| 345 | 353 | zTFile = fossil_temp_filename(); |
| 346 | 354 | if( zTFile==0 ) fossil_fatal("cannot find a temporary filename"); |
| 347 | 355 | db_begin_transaction(); |
| 348 | 356 | content_rcvid_init("#!fossil unversioned edit"); |
| 349 | 357 | if( unversioned_content(zUVFile, &content) ){ |
| @@ -386,26 +394,40 @@ | ||
| 386 | 394 | fossil_print("%s\n", unversioned_content_hash(debugFlag)); |
| 387 | 395 | }else if( memcmp(zCmd, "list", nCmd)==0 || memcmp(zCmd, "ls", nCmd)==0 ){ |
| 388 | 396 | Stmt q; |
| 389 | 397 | int allFlag = find_option("all","a",0)!=0; |
| 390 | 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 | + } | |
| 391 | 411 | verify_all_options(); |
| 392 | 412 | if( !longFlag ){ |
| 393 | 413 | 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*/); | |
| 395 | 416 | }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*/); | |
| 398 | 420 | } |
| 399 | 421 | while( db_step(&q)==SQLITE_ROW ){ |
| 400 | 422 | fossil_print("%s\n", db_column_text(&q,0)); |
| 401 | 423 | } |
| 402 | 424 | }else{ |
| 403 | 425 | db_prepare(&q, |
| 404 | 426 | "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*/ | |
| 407 | 429 | ); |
| 408 | 430 | while( db_step(&q)==SQLITE_ROW ){ |
| 409 | 431 | const char *zHash = db_column_text(&q, 0); |
| 410 | 432 | const char *zNoContent = ""; |
| 411 | 433 | if( zHash==0 ){ |
| @@ -423,20 +445,37 @@ | ||
| 423 | 445 | zNoContent |
| 424 | 446 | ); |
| 425 | 447 | } |
| 426 | 448 | } |
| 427 | 449 | db_finalize(&q); |
| 450 | + sqlite3_free(zPattern); | |
| 428 | 451 | }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); | |
| 430 | 454 | g.argv[1] = "sync"; |
| 431 | 455 | g.argv[2] = "--uv-noop"; |
| 432 | 456 | sync_unversioned(syncFlags); |
| 433 | 457 | }else if( memcmp(zCmd, "remove", nCmd)==0 || memcmp(zCmd, "rm", nCmd)==0 |
| 434 | 458 | || memcmp(zCmd, "delete", nCmd)==0 ){ |
| 435 | 459 | int i; |
| 436 | - verify_all_options(); | |
| 460 | + const char *zGlob; | |
| 437 | 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(); | |
| 438 | 477 | for(i=3; i<g.argc; i++){ |
| 439 | 478 | db_multi_exec( |
| 440 | 479 | "UPDATE unversioned" |
| 441 | 480 | " SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name=%Q", |
| 442 | 481 | mtime, g.argv[i] |
| 443 | 482 |
| --- 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 @@ | ||
| 66 | 66 | int proxyOrigPort; /* Tunneled port number for https through proxy */ |
| 67 | 67 | }; |
| 68 | 68 | #endif /* INTERFACE */ |
| 69 | 69 | |
| 70 | 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 | 71 | /* |
| 82 | 72 | ** Parse the given URL. Populate members of the provided UrlData structure |
| 83 | 73 | ** as follows: |
| 84 | 74 | ** |
| 85 | 75 | ** isFile True if FILE: |
| @@ -177,11 +167,11 @@ | ||
| 177 | 167 | pUrlData->name++; |
| 178 | 168 | pUrlData->name[n-2] = 0; |
| 179 | 169 | } |
| 180 | 170 | zLogin = mprintf(""); |
| 181 | 171 | } |
| 182 | - url_tolower(pUrlData->name); | |
| 172 | + fossil_strtolwr(pUrlData->name); | |
| 183 | 173 | if( c==':' ){ |
| 184 | 174 | pUrlData->port = 0; |
| 185 | 175 | i++; |
| 186 | 176 | while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){ |
| 187 | 177 | pUrlData->port = pUrlData->port*10 + c - '0'; |
| @@ -370,18 +360,32 @@ | ||
| 370 | 360 | /* |
| 371 | 361 | ** Extract any proxy options from the command-line. |
| 372 | 362 | ** |
| 373 | 363 | ** --proxy URL|off |
| 374 | 364 | ** |
| 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! | |
| 378 | 377 | */ |
| 379 | 378 | void url_proxy_options(void){ |
| 380 | 379 | zProxyOpt = find_option("proxy", 0, 1); |
| 381 | 380 | if( find_option("nosync",0,0) ) g.fNoSync = 1; |
| 382 | 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 */ | |
| 383 | 387 | } |
| 384 | 388 | |
| 385 | 389 | /* |
| 386 | 390 | ** If the "proxy" setting is defined, then change the URL settings |
| 387 | 391 | ** (initialized by a prior call to url_parse()) so that the HTTP |
| 388 | 392 |
| --- 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 @@ | ||
| 140 | 140 | } |
| 141 | 141 | #else |
| 142 | 142 | fossil_free(p); |
| 143 | 143 | #endif |
| 144 | 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 | +} | |
| 145 | 160 | |
| 146 | 161 | /* |
| 147 | 162 | ** This function implements a cross-platform "system()" interface. |
| 148 | 163 | */ |
| 149 | 164 | int fossil_system(const char *zOrigCmd){ |
| 150 | 165 |
| --- 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 @@ | ||
| 592 | 592 | } |
| 593 | 593 | zip_open(); |
| 594 | 594 | for(i=3; i<g.argc; i++){ |
| 595 | 595 | blob_zero(&file); |
| 596 | 596 | 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)); | |
| 598 | 598 | blob_reset(&file); |
| 599 | 599 | } |
| 600 | 600 | zip_close(&sArchive); |
| 601 | 601 | blob_write_to_file(&zip, g.argv[2]); |
| 602 | 602 | } |
| 603 | 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,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 @@ | ||
| 391 | 391 | if {[info exists ::env(FOSSIL_TEST_DANGEROUS_IGNORE_OPEN_CHECKOUT)] && \ |
| 392 | 392 | $::env(FOSSIL_TEST_DANGEROUS_IGNORE_OPEN_CHECKOUT) eq "YES_DO_IT"} { |
| 393 | 393 | return |
| 394 | 394 | } |
| 395 | 395 | catch {exec $::fossilexe info} res |
| 396 | - if {![regexp {use --repository} $res]} { | |
| 396 | + if {[regexp {local-root:} $res]} { | |
| 397 | 397 | set projectName <unknown> |
| 398 | 398 | set localRoot <unknown> |
| 399 | 399 | regexp -line -- {^project-name: (.*)$} $res dummy projectName |
| 400 | 400 | set projectName [string trim $projectName] |
| 401 | 401 | regexp -line -- {^local-root: (.*)$} $res dummy localRoot |
| 402 | 402 | |
| 403 | 403 | 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
+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 | +} |
| --- 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
+1
-1
| --- win/Makefile.mingw | ||
| +++ win/Makefile.mingw | ||
| @@ -174,11 +174,11 @@ | ||
| 174 | 174 | #### The directories where the OpenSSL include and library files are located. |
| 175 | 175 | # The recommended usage here is to use the Sysinternals junction tool |
| 176 | 176 | # to create a hard link between an "openssl-1.x" sub-directory of the |
| 177 | 177 | # Fossil source code directory and the target OpenSSL source directory. |
| 178 | 178 | # |
| 179 | -OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1f | |
| 179 | +OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1g | |
| 180 | 180 | OPENSSLINCDIR = $(OPENSSLDIR)/include |
| 181 | 181 | OPENSSLLIBDIR = $(OPENSSLDIR) |
| 182 | 182 | |
| 183 | 183 | #### Either the directory where the Tcl library is installed or the Tcl |
| 184 | 184 | # source code directory resides (depending on the value of the macro |
| 185 | 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.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 |
+14
-1
| --- win/Makefile.mingw.mistachkin | ||
| +++ win/Makefile.mingw.mistachkin | ||
| @@ -174,11 +174,11 @@ | ||
| 174 | 174 | #### The directories where the OpenSSL include and library files are located. |
| 175 | 175 | # The recommended usage here is to use the Sysinternals junction tool |
| 176 | 176 | # to create a hard link between an "openssl-1.x" sub-directory of the |
| 177 | 177 | # Fossil source code directory and the target OpenSSL source directory. |
| 178 | 178 | # |
| 179 | -OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1f | |
| 179 | +OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.1.1g | |
| 180 | 180 | OPENSSLINCDIR = $(OPENSSLDIR)/include |
| 181 | 181 | OPENSSLLIBDIR = $(OPENSSLDIR) |
| 182 | 182 | |
| 183 | 183 | #### Either the directory where the Tcl library is installed or the Tcl |
| 184 | 184 | # source code directory resides (depending on the value of the macro |
| @@ -440,10 +440,11 @@ | ||
| 440 | 440 | SRC = \ |
| 441 | 441 | $(SRCDIR)/add.c \ |
| 442 | 442 | $(SRCDIR)/alerts.c \ |
| 443 | 443 | $(SRCDIR)/allrepo.c \ |
| 444 | 444 | $(SRCDIR)/attach.c \ |
| 445 | + $(SRCDIR)/backlink.c \ | |
| 445 | 446 | $(SRCDIR)/backoffice.c \ |
| 446 | 447 | $(SRCDIR)/bag.c \ |
| 447 | 448 | $(SRCDIR)/bisect.c \ |
| 448 | 449 | $(SRCDIR)/blob.c \ |
| 449 | 450 | $(SRCDIR)/branch.c \ |
| @@ -633,10 +634,11 @@ | ||
| 633 | 634 | $(SRCDIR)/../skins/rounded1/header.txt \ |
| 634 | 635 | $(SRCDIR)/../skins/xekri/css.txt \ |
| 635 | 636 | $(SRCDIR)/../skins/xekri/details.txt \ |
| 636 | 637 | $(SRCDIR)/../skins/xekri/footer.txt \ |
| 637 | 638 | $(SRCDIR)/../skins/xekri/header.txt \ |
| 639 | + $(SRCDIR)/accordion.js \ | |
| 638 | 640 | $(SRCDIR)/ci_edit.js \ |
| 639 | 641 | $(SRCDIR)/copybtn.js \ |
| 640 | 642 | $(SRCDIR)/diff.tcl \ |
| 641 | 643 | $(SRCDIR)/forum.js \ |
| 642 | 644 | $(SRCDIR)/graph.js \ |
| @@ -671,10 +673,11 @@ | ||
| 671 | 673 | TRANS_SRC = \ |
| 672 | 674 | $(OBJDIR)/add_.c \ |
| 673 | 675 | $(OBJDIR)/alerts_.c \ |
| 674 | 676 | $(OBJDIR)/allrepo_.c \ |
| 675 | 677 | $(OBJDIR)/attach_.c \ |
| 678 | + $(OBJDIR)/backlink_.c \ | |
| 676 | 679 | $(OBJDIR)/backoffice_.c \ |
| 677 | 680 | $(OBJDIR)/bag_.c \ |
| 678 | 681 | $(OBJDIR)/bisect_.c \ |
| 679 | 682 | $(OBJDIR)/blob_.c \ |
| 680 | 683 | $(OBJDIR)/branch_.c \ |
| @@ -812,10 +815,11 @@ | ||
| 812 | 815 | OBJ = \ |
| 813 | 816 | $(OBJDIR)/add.o \ |
| 814 | 817 | $(OBJDIR)/alerts.o \ |
| 815 | 818 | $(OBJDIR)/allrepo.o \ |
| 816 | 819 | $(OBJDIR)/attach.o \ |
| 820 | + $(OBJDIR)/backlink.o \ | |
| 817 | 821 | $(OBJDIR)/backoffice.o \ |
| 818 | 822 | $(OBJDIR)/bag.o \ |
| 819 | 823 | $(OBJDIR)/bisect.o \ |
| 820 | 824 | $(OBJDIR)/blob.o \ |
| 821 | 825 | $(OBJDIR)/branch.o \ |
| @@ -1173,10 +1177,11 @@ | ||
| 1173 | 1177 | $(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/default_css.h $(MAKEHEADERS) $(OBJDIR)/VERSION.h |
| 1174 | 1178 | $(MAKEHEADERS) $(OBJDIR)/add_.c:$(OBJDIR)/add.h \ |
| 1175 | 1179 | $(OBJDIR)/alerts_.c:$(OBJDIR)/alerts.h \ |
| 1176 | 1180 | $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \ |
| 1177 | 1181 | $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \ |
| 1182 | + $(OBJDIR)/backlink_.c:$(OBJDIR)/backlink.h \ | |
| 1178 | 1183 | $(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \ |
| 1179 | 1184 | $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \ |
| 1180 | 1185 | $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \ |
| 1181 | 1186 | $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \ |
| 1182 | 1187 | $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \ |
| @@ -1348,10 +1353,18 @@ | ||
| 1348 | 1353 | |
| 1349 | 1354 | $(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h |
| 1350 | 1355 | $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c |
| 1351 | 1356 | |
| 1352 | 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 | |
| 1353 | 1366 | |
| 1354 | 1367 | $(OBJDIR)/backoffice_.c: $(SRCDIR)/backoffice.c $(TRANSLATE) |
| 1355 | 1368 | $(TRANSLATE) $(SRCDIR)/backoffice.c >$@ |
| 1356 | 1369 | |
| 1357 | 1370 | $(OBJDIR)/backoffice.o: $(OBJDIR)/backoffice_.c $(OBJDIR)/backoffice.h $(SRCDIR)/config.h |
| 1358 | 1371 |
| --- 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 |
+1
-1
| --- win/Makefile.msc | ||
| +++ win/Makefile.msc | ||
| @@ -98,11 +98,11 @@ | ||
| 98 | 98 | !ifndef USE_SEE |
| 99 | 99 | USE_SEE = 0 |
| 100 | 100 | !endif |
| 101 | 101 | |
| 102 | 102 | !if $(FOSSIL_ENABLE_SSL)!=0 |
| 103 | -SSLDIR = $(B)\compat\openssl-1.1.1f | |
| 103 | +SSLDIR = $(B)\compat\openssl-1.1.1g | |
| 104 | 104 | SSLINCDIR = $(SSLDIR)\include |
| 105 | 105 | !if $(FOSSIL_DYNAMIC_BUILD)!=0 |
| 106 | 106 | SSLLIBDIR = $(SSLDIR) |
| 107 | 107 | !else |
| 108 | 108 | SSLLIBDIR = $(SSLDIR) |
| 109 | 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.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 @@ | ||
| 161 | 161 | the optional <a href="https://www.openssl.org/">OpenSSL</a> support, |
| 162 | 162 | first <a href="https://www.openssl.org/source/">download the official |
| 163 | 163 | source code for OpenSSL</a> and extract it to an appropriately named |
| 164 | 164 | "<b>openssl-X.Y.ZA</b>" subdirectory within the local |
| 165 | 165 | [/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 | |
| 167 | 167 | <a href="http://www.perl.org/">Perl</a> binaries are installed locally, |
| 168 | 168 | and finally run one of the following commands: |
| 169 | 169 | <blockquote><pre> |
| 170 | 170 | nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 FOSSIL_BUILD_SSL=1 PERLDIR=C:\full\path\to\Perl\bin |
| 171 | 171 | </pre></blockquote> |
| 172 | 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.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 |
+7
-2
| --- www/changes.wiki | ||
| +++ www/changes.wiki | ||
| @@ -31,11 +31,11 @@ | ||
| 31 | 31 | text of forum posts to be displayed. |
| 32 | 32 | * Rework the "[/help?cmd=grep|fossil grep]" command to be more useful. |
| 33 | 33 | * Expose the [/help?cmd=redirect-to-https|redirect-to-https] |
| 34 | 34 | setting to the [/help?cmd=settings|settings] command. |
| 35 | 35 | * 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, | |
| 37 | 37 | in the same way as the embedded docs. |
| 38 | 38 | * Most commands now support the Unix-conventional "<tt>--</tt>" |
| 39 | 39 | flag to treat all following arguments as filenames |
| 40 | 40 | instead of flags. |
| 41 | 41 | * Added the [/help?cmd=mimetypes|mimetypes config setting] |
| @@ -45,11 +45,11 @@ | ||
| 45 | 45 | * In [./embeddeddoc.wiki|embedded documentation], hyperlink URLs |
| 46 | 46 | of the form "/doc/$CURRENT/..." the "$CURRENT" text is translated |
| 47 | 47 | into the check-in hash for the document currently being viewed. |
| 48 | 48 | * Added the [/help?cmd=/phantoms|/phantoms] webpage that shows all |
| 49 | 49 | phantom artifacts. |
| 50 | - * Enhancements to phantom processing to try to reduce constant | |
| 50 | + * Enhancements to phantom processing to try to reduce | |
| 51 | 51 | bandwidth-using chatter about phantoms on the sync protocol. |
| 52 | 52 | * Security: Fossil now assumes that the schema of every |
| 53 | 53 | database it opens has been tampered with by an adversary and takes |
| 54 | 54 | extra precautions to ensure that such tampering is harmless. |
| 55 | 55 | * Security: Fossil now puts the Content-Security-Policy in the |
| @@ -65,13 +65,18 @@ | ||
| 65 | 65 | is used. |
| 66 | 66 | * The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible |
| 67 | 67 | to all users if the new "artifact_stats_enable" setting is turned |
| 68 | 68 | on. There is a new checkbox under the /Admin/Access menu to turn |
| 69 | 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. | |
| 70 | 72 | * Captchas all include a button to read the captcha using an audio |
| 71 | 73 | file, so that they can be completed by the visually impaired. |
| 72 | 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. | |
| 73 | 78 | * Bug fix: the FTS search into for forum posts is now kept up-to-date |
| 74 | 79 | correctly. |
| 75 | 80 | * Bug fix: the "fossil git export" command is now working on Windows |
| 76 | 81 | * Bug fix: display Technote items on the timeline correctly |
| 77 | 82 | * Bug fix: fix the capability summary matrix of the Security Audit |
| 78 | 83 |
| --- 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 |
+8
-8
| --- www/fossil-v-git.wiki | ||
| +++ www/fossil-v-git.wiki | ||
| @@ -114,14 +114,14 @@ | ||
| 114 | 114 | the design. One way to describe Fossil is that it is |
| 115 | 115 | "[https://github.com/ | GitHub]-in-a-box." |
| 116 | 116 | |
| 117 | 117 | Fossil can do operations over all local repo clones and check-out |
| 118 | 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 | |
| 119 | +<tt>fossil all sync</tt> on a laptop prior to taking it off the network | |
| 120 | 120 | hosting those repos. You can sync up to all of the private repos on your |
| 121 | 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 | |
| 122 | +going out for a working lunch or on a transoceanic airplane trip, one | |
| 123 | 123 | command gets you in sync. This works with several other Fossil |
| 124 | 124 | sub-commands, such as <tt>fossil all changes</tt> to get a list of files |
| 125 | 125 | that you forgot to commit prior to the end of your working day, across |
| 126 | 126 | all repos. |
| 127 | 127 | |
| @@ -258,11 +258,11 @@ | ||
| 258 | 258 | are looking at some historical check-in then you cannot ask "What came |
| 259 | 259 | next?" or "What are the children of this check-in?" |
| 260 | 260 | |
| 261 | 261 | Fossil, on the other hand, parses essential information about check-ins |
| 262 | 262 | (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 | |
| 264 | 264 | statements to find both ancestors and descendants of a check-in. This is |
| 265 | 265 | the hybrid data model mentioned above: Fossil manages your check-in and |
| 266 | 266 | other data in a NoSQL block chain structured data store, but that's backed |
| 267 | 267 | by a set of relational lookup tables for quick indexing into that |
| 268 | 268 | artifact store. (See "[./theory1.wiki|Thoughts On The Design Of The |
| @@ -759,13 +759,13 @@ | ||
| 759 | 759 | |
| 760 | 760 | Fossil cannot sensibly work that way because of its default-enabled |
| 761 | 761 | autosync feature. Instead of jumping straight to the commit step, Fossil |
| 762 | 762 | applies the proposed merge to the local working directory only, |
| 763 | 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. | |
| 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 | 767 | |
| 768 | 768 | Another difference is that because Fossil requires an explicit commit |
| 769 | 769 | for a merge, it makes you give an explicit commit <i>message</i> for |
| 770 | 770 | each merge, whereas Git writes that commit message itself by default |
| 771 | 771 | unless you give the optional <tt>--edit</tt> flag to override it. |
| @@ -805,11 +805,11 @@ | ||
| 805 | 805 | brief online help for <tt>[/help?cmd=merge | fossil merge]</tt> is |
| 806 | 806 | currently 41 lines long, to which you want to add the 600 lines of |
| 807 | 807 | [./branching.wiki | the branching document]. The equivalent |
| 808 | 808 | documentation in Git is the aggregation of the man pages for the above |
| 809 | 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 | |
| 810 | +(e.g. Git's <tt>--edit</tt> and <tt>--no-commit</tt> options get | |
| 811 | 811 | described three times, each time differently.) Fossil's |
| 812 | 812 | documentation is not only more concise, it gives a nice split of brief |
| 813 | 813 | online help and full online documentation. |
| 814 | 814 | |
| 815 | 815 | |
| @@ -855,11 +855,11 @@ | ||
| 855 | 855 | weakening the case for continuing to use SHA-1. |
| 856 | 856 | |
| 857 | 857 | The practical impact of attacks like SHAttered and SHAmbles on the |
| 858 | 858 | Git and Fossil blockchains isn't clear, but you want to have your repositories |
| 859 | 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 | |
| 860 | +to make use of the weaknesses in the old one. Fossil has had this covered | |
| 861 | 861 | for years now, so that the solution is now almost universally deployed. |
| 862 | 862 | |
| 863 | 863 | <hr/> |
| 864 | 864 | |
| 865 | 865 | <h3>Asides and Digressions</h3> |
| 866 | 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 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 |
+38
-16
| --- www/mirrorlimitations.md | ||
| +++ www/mirrorlimitations.md | ||
| @@ -9,52 +9,74 @@ | ||
| 9 | 9 | Fossil is not included in an export to Git. |
| 10 | 10 | |
| 11 | 11 | ## (1) Wiki, Tickets, Technotes, Forum |
| 12 | 12 | |
| 13 | 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 | |
| 14 | +as Wiki, Tickets, Technotes, and the Forum are not supported in Git, | |
| 15 | 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. | |
| 16 | 28 | |
| 17 | 29 | ## (2) Cherrypick Merges |
| 18 | 30 | |
| 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. | |
| 27 | 40 | |
| 28 | 41 | ## (3) Named Branches |
| 29 | 42 | |
| 30 | 43 | Git has only limited support for named branches. Git identifies the head |
| 31 | 44 | check-in of each branch. Depending on the check-in graph topology, this |
| 32 | 45 | is sufficient to infer the branch for many historical check-ins as well. |
| 33 | 46 | However, complex histories with lots of cross-merging |
| 34 | 47 | 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 | |
| 37 | 50 | at hand cannot be exported to Git. |
| 38 | 51 | |
| 39 | 52 | ## (4) Non-unique Tags |
| 40 | 53 | |
| 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 | |
| 42 | 55 | check-in. Fossil does not have this restriction, and so it is common |
| 43 | 56 | 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).) | |
| 47 | 61 | |
| 48 | 62 | Git does not allow this. The "release" tag must refer to just one |
| 49 | 63 | check-in. The work-around is that the non-unique tag in the Git export is |
| 50 | 64 | made to refer to only the most recent check-in with that tag. |
| 51 | 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 | + | |
| 52 | 74 | ## (5) Amendments To Check-ins |
| 53 | 75 | |
| 54 | 76 | 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 | |
| 56 | 78 | its blockchain to provide after-the-fact corrections to prior check-ins. |
| 57 | 79 | |
| 58 | 80 | For example, tags can be added to check-ins that correct typos in the |
| 59 | 81 | check-in comment. The original check-in is immutable and so the |
| 60 | 82 | original comment is preserved in addition to the correction. But |
| 61 | 83 |
| --- 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 |
+4
-4
| --- www/rebaseharm.md | ||
| +++ www/rebaseharm.md | ||
| @@ -317,14 +317,14 @@ | ||
| 317 | 317 | that blames the merged check-in as the source of the problem they’re |
| 318 | 318 | chasing down; they then have to manually work out which of the 10 steps |
| 319 | 319 | the original developer took to create it to find the source of the |
| 320 | 320 | actual problem. |
| 321 | 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. | |
| 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 | 326 | |
| 327 | 327 | ### <a name="comments"></a>7.3 Multiple check-ins require multiple check-in comments |
| 328 | 328 | |
| 329 | 329 | The more comments you have from a given developer on a given body of |
| 330 | 330 | code, the more concise documentation you have of that developer's |
| 331 | 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 | 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 @@ | ||
| 104 | 104 | |
| 105 | 105 | |
| 106 | 106 | <h3 id="certs">Certificates</h3> |
| 107 | 107 | |
| 108 | 108 | 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? | |
| 110 | 117 | |
| 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 | |
| 112 | 120 | to accept the certificate the first time you communicate with the |
| 113 | 121 | 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. | |
| 115 | 123 | |
| 116 | 124 | 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> | |
| 122 | 132 | |
| 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: | |
| 124 | 135 | |
| 125 | 136 | # <p>The OpenSSL library Fossil is linked to doesn't have a CA |
| 126 | 137 | signing key set at all, so that it initially trusts no certificates |
| 127 | 138 | at all.</p> |
| 128 | 139 | # <p>The OpenSSL library does have a CA cert set, but your Fossil server's |
| @@ -137,11 +148,13 @@ | ||
| 137 | 148 | fossil set --global ssl-ca-location /path/to/local-ca.pem |
| 138 | 149 | </pre> |
| 139 | 150 | |
| 140 | 151 | The use of <tt>--global</tt> with this option is common, since you may |
| 141 | 152 | 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. | |
| 143 | 156 | |
| 144 | 157 | A common way to run into the broader first problem is that you're on |
| 145 | 158 | FreeBSD, which does not install a CA certificate set by default, even as |
| 146 | 159 | a dependency of the OpenSSL library. If you're using a certificate |
| 147 | 160 | signed by one of the major public CAs, you can solve this by installing |
| @@ -155,15 +168,15 @@ | ||
| 155 | 168 | certificate set, but it's not in a format that OpenSSL understands how |
| 156 | 169 | to use. Rather than try to find a way to convert the data format, you |
| 157 | 170 | may find it acceptable to use the same Mozilla NSS cert set. I do not |
| 158 | 171 | know of a way to easily get this from Mozilla themselves, but I did find |
| 159 | 172 | 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: | |
| 162 | 175 | |
| 163 | 176 | <pre> |
| 164 | - fossil set --global ssl-ca-location /path/to/cacert.pem | |
| 177 | + fossil set --global ssl-ca-location %userprofile%\cacert.pem | |
| 165 | 178 | </pre> |
| 166 | 179 | |
| 167 | 180 | This can also happen if you've linked Fossil to a version of OpenSSL |
| 168 | 181 | [#openssl-src|built from source]. That same <tt>cacert.pem</tt> fix can |
| 169 | 182 | work in that case, too. |
| 170 | 183 |
| --- 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 |