From 4f49b16079b27d7ce633619e4cddfdf2bffbc952 Mon Sep 17 00:00:00 2001 From: cashmere Date: Fri, 4 Sep 2026 09:05:24 +0200 Subject: initial commit --- src/shun.c | 615 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 615 insertions(+) create mode 100644 src/shun.c (limited to 'src/shun.c') diff --git a/src/shun.c b/src/shun.c new file mode 100644 index 0000000..f1fe14b --- /dev/null +++ b/src/shun.c @@ -0,0 +1,615 @@ +/* +** Copyright (c) 2008 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code used to manage SHUN table of the repository +*/ +#include "config.h" +#include "shun.h" +#include + +/* +** Return true if the given artifact ID should be shunned. +*/ +int uuid_is_shunned(const char *zUuid){ + static Stmt q; + int rc; + if( zUuid==0 || zUuid[0]==0 ) return 0; + if( g.eHashPolicy==HPOLICY_SHUN_SHA1 && zUuid[HNAME_LEN_SHA1]==0 ) return 1; + db_static_prepare(&q, "SELECT 1 FROM shun WHERE uuid=:uuid"); + db_bind_text(&q, ":uuid", zUuid); + rc = db_step(&q); + db_reset(&q); + return rc==SQLITE_ROW; +} + +/* +** WEBPAGE: shun +** +** View the hashes of all shunned artifacts. Add new hashes +** to the shun set. Requires Admin privilege. +*/ +void shun_page(void){ + Stmt q; + int cnt = 0; + const char *zUuid = P("uuid"); + const char *zShun = P("shun"); + const char *zAccept = P("accept"); + const char *zRcvid = P("rcvid"); + int reviewList = P("review")!=0; + int nRcvid = 0; + int numRows = 3; + char *zCanonical = 0; + + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + if( P("rebuild") ){ + db_close(1); + db_open_repository(g.zRepositoryName); + db_begin_transaction(); + rebuild_db(0, 0); + admin_log("Rebuilt database."); + db_end_transaction(0); + } + if( zUuid ){ + char *p; + int i = 0; + int j = 0; + zCanonical = fossil_malloc(strlen(zUuid)+2); + while( zUuid[i] ){ + if( fossil_isspace(zUuid[i]) ){ + if( j && zCanonical[j-1] ){ + zCanonical[j] = 0; + j++; + } + }else{ + zCanonical[j] = zUuid[i]; + j++; + } + i++; + } + zCanonical[j+1] = zCanonical[j] = 0; + p = zCanonical; + while( *p ){ + int nUuid = strlen(p); + if( !(reviewList || hname_validate(p, nUuid)) ){ + @

Error: Bad artifact IDs.

+ fossil_free(zCanonical); + zCanonical = 0; + break; + }else{ + canonical16(p, nUuid); + p += nUuid+1; + } + } + zUuid = zCanonical; + } + style_header("Shunned Artifacts"); + if( zUuid && P("sub") && cgi_csrf_safe(2) ){ + const char *p = zUuid; + int allExist = 1; + while( *p ){ + db_multi_exec("DELETE FROM shun WHERE uuid=%Q", p); + if( !db_exists("SELECT 1 FROM blob WHERE uuid=%Q", p) ){ + allExist = 0; + } + admin_log("Unshunned %Q", p); + p += strlen(p)+1; + } + if( allExist ){ + @

Artifact(s)
+ for( p = zUuid ; *p ; p += strlen(p)+1 ){ + @ %s(p)
+ } + @ are no longer being shunned.

+ }else{ + @

Artifact(s)
+ for( p = zUuid ; *p ; p += strlen(p)+1 ){ + @ %s(p)
+ } + @ will no longer be shunned but they may not exist in the repository. + @ It may be necessary to rebuild the repository + @ before the artifact content can be pulled in + @ from other repositories.

+ } + } + if( zUuid && P("add") && cgi_csrf_safe(2) ){ + const char *p = zUuid; + int rid, tagid; + while( *p ){ + db_multi_exec( + "INSERT OR IGNORE INTO shun(uuid,mtime)" + " VALUES(%Q, now())", p); + db_multi_exec("DELETE FROM attachment WHERE src=%Q", p); + rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", p); + if( rid ){ + db_multi_exec("DELETE FROM event WHERE objid=%d", rid); + } + tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='tkt-%q'", p); + if( tagid ){ + db_multi_exec("DELETE FROM ticket WHERE tkt_uuid=%Q", p); + db_multi_exec("DELETE FROM tag WHERE tagid=%d", tagid); + db_multi_exec("DELETE FROM tagxref WHERE tagid=%d", tagid); + } + admin_log("Shunned %Q", p); + p += strlen(p)+1; + } + @

Artifact(s)
+ for( p = zUuid ; *p ; p += strlen(p)+1 ){ + @ %s(p)
+ } + @ have been shunned. They will no longer be pushed. + @ They will be removed from the repository the next time the repository + @ is rebuilt using the fossil rebuild command-line

+ } + if( zUuid && reviewList ){ + const char *p; + int nTotal = 0; + int nOk = 0; + @
+ for( p = zUuid ; *p ; p += strlen(p)+1 ){ + int rid = symbolic_name_to_rid(p, 0); + nTotal++; + if( rid < 0 ){ + @ Ambiguous
+ }else if( rid == 0 ){ + if( !hname_validate(p, strlen(p)) ){ + @ Bad artifact
+ }else if(db_int(0, "SELECT 1 FROM shun WHERE uuid=%Q", p)){ + @ Already shunned
+ }else{ + @ Unknown
+ } + }else{ + char *zCmpUuid = db_text(0, + "SELECT uuid" + " FROM blob, rcvfrom" + " WHERE rid=%d" + " AND rcvfrom.rcvid=blob.rcvid", + rid); + if( fossil_strcmp(p, zCmpUuid)==0 ){ + nOk++; + @ OK
+ }else{ + @ Abbreviated
+ } + } + } + @
+ for( p = zUuid ; *p ; p += strlen(p)+1 ){ + int rid = symbolic_name_to_rid(p, 0); + if( rid > 0 ){ + @ %s(p)
+ }else{ + @ %s(p)
+ } + } + @
+ @

+ if( nOk < nTotal){ + @ Warning: Not all artifacts + }else if( nTotal==1 ){ + @ The artifact is present and + }else{ + @ All %i(nOk) artifacts are present and + } + @ can be shunned with its hash above.

+ } + if( zRcvid ){ + nRcvid = atoi(zRcvid); + numRows = db_int(0, "SELECT min(count(), 10) FROM blob WHERE rcvid=%d", + nRcvid); + } + @

A shunned artifact will not be pushed nor accepted in a pull and the + @ artifact content will be purged from the repository the next time the + @ repository is rebuilt. A list of shunned artifacts can be seen at the + @ bottom of this page.

+ @ + @ + @

To shun artifacts, enter their artifact hashes (the 40- or + @ 64-character lowercase hexadecimal hash of the artifact content) in the + @ following box and press the "Shun" button. This will cause the artifacts + @ to be removed from the repository and will prevent the artifacts from being + @ readded to the repository by subsequent sync operation.

+ @ + @

Note that you must enter full artifact hashes, not abbreviations + @ or symbolic tags.

+ @ + @

Warning: Shunning should only be used to remove inappropriate content + @ from the repository. Inappropriate content includes such things as + @ spam added to Wiki, files that violate copyright or patent agreements, + @ or artifacts that by design or accident interfere with the processing + @ of the repository. Do not shun artifacts merely to remove them from + @ sight - set the "hidden" tag on such artifacts instead.

+ @ + @
+ @
+ login_insert_csrf_secret(); + @ + @ + @ + @
+ @
+ @ + @ + @

Enter the UUIDs of previously shunned artifacts to cause them to be + @ accepted again in the repository. The artifacts content is not + @ restored because the content is unknown. The only change is that + @ the formerly shunned artifacts will be accepted on subsequent sync + @ operations.

+ @ + @
+ @
+ login_insert_csrf_secret(); + @ + @ + @
+ @
+ @ + @

Press the Rebuild button below to rebuild the repository. The + @ content of newly shunned artifacts is not purged until the repository + @ is rebuilt. On larger repositories, the rebuild may take minute or + @ two, so be patient after pressing the button.

+ @ + @
+ @
+ login_insert_csrf_secret(); + @ + @
+ @
+ @ + @

Shunned Artifacts:

+ @

+ db_prepare(&q, + "SELECT uuid, EXISTS(SELECT 1 FROM blob WHERE blob.uuid=shun.uuid)" + " FROM shun ORDER BY uuid"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zUuid = db_column_text(&q, 0); + int stillExists = db_column_int(&q, 1); + cnt++; + if( stillExists ){ + @ %s(zUuid)
+ }else{ + @ %s(zUuid)
+ } + } + if( cnt==0 ){ + @ no artifacts are shunned on this server + } + db_finalize(&q); + @

+ style_finish_page(); + fossil_free(zCanonical); +} + +/* +** Remove from the BLOB table all artifacts that are in the SHUN table. +*/ +void shun_artifacts(void){ + Stmt q; + db_multi_exec( + "CREATE TEMP TABLE toshun(rid INTEGER PRIMARY KEY);" + "INSERT INTO toshun SELECT rid FROM blob, shun WHERE blob.uuid=shun.uuid;" + ); + db_prepare(&q, + "SELECT rid FROM delta WHERE srcid IN toshun" + ); + while( db_step(&q)==SQLITE_ROW ){ + int srcid = db_column_int(&q, 0); + content_undelta(srcid); + } + db_finalize(&q); + db_multi_exec( + "DELETE FROM delta WHERE rid IN toshun;" + "DELETE FROM blob WHERE rid IN toshun;" + "DROP TABLE toshun;" + "DELETE FROM private " + " WHERE NOT EXISTS (SELECT 1 FROM blob WHERE rid=private.rid);" + ); +} + +/* +** WEBPAGE: rcvfromlist +** +** Show a listing of RCVFROM table entries. +** +** The RCVFROM table records where this repository received each +** artifact, including the time of receipt, user, and IP address. +** +** Access requires Admin privilege. +*/ +void rcvfromlist_page(void){ + int ofst = atoi(PD("ofst","0")); + int showAll = P("all")!=0; + int cnt; + Stmt q; + const int perScreen = 500; /* RCVIDs per page */ + + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + style_header("Xfer Log"); + style_submenu_element("Log-Menu", "setup-logmenu"); + if( showAll ){ + ofst = 0; + }else{ + style_submenu_element("All", "rcvfromlist?all=1"); + } + if( ofst>0 ){ + style_submenu_element("Newer", "rcvfromlist?ofst=%d", + ofst>perScreen ? ofst-perScreen : 0); + } + style_submenu_element("Artifacts", "bloblist"); + style_submenu_element("Top-250", "bigbloblist"); + db_multi_exec( + "CREATE TEMP TABLE rcvidUsed(x INTEGER PRIMARY KEY);" + "CREATE TEMP TABLE rcvidSha1(x INTEGER PRIMARY KEY);" + "CREATE TEMP TABLE rcvidSha3(x INTEGER PRIMARY KEY);" + "INSERT OR IGNORE INTO rcvidUsed(x) SELECT rcvid FROM blob;" + "INSERT OR IGNORE INTO rcvidSha1(x)" + " SELECT rcvid FROM blob WHERE length(uuid)==40;" + "INSERT OR IGNORE INTO rcvidSha3(x)" + " SELECT rcvid FROM blob WHERE length(uuid)==64;" + ); + if( db_table_exists("repository","unversioned") ){ + db_multi_exec( + "INSERT OR IGNORE INTO rcvidUsed(x) SELECT rcvid FROM unversioned;" + "INSERT OR IGNORE INTO rcvidSha1(x)" + " SELECT rcvid FROM unversioned WHERE length(hash)==40;" + "INSERT OR IGNORE INTO rcvidSha3(x)" + " SELECT rcvid FROM unversioned WHERE length(hash)==64;" + ); + } + db_prepare(&q, + "SELECT rcvid, login, datetime(rcvfrom.mtime), rcvfrom.ipaddr," + " EXISTS(SELECT 1 FROM rcvidUsed WHERE x=rcvfrom.rcvid)," + " EXISTS(SELECT 1 FROM rcvidSha1 WHERE x=rcvfrom.rcvid)," + " EXISTS(SELECT 1 FROM rcvidSha3 WHERE x=rcvfrom.rcvid)" + " FROM rcvfrom LEFT JOIN user USING(uid)" + " ORDER BY rcvid DESC LIMIT %d OFFSET %d", + showAll ? -1 : perScreen+1, ofst + ); + @

Whenever new artifacts are added to the repository, either by + @ push or using the web interface or by "fossil commit" or similar, + @ an entry is made in the RCVFROM table + @ to record the source of those artifacts. This log facilitates + @ finding and fixing attempts to inject illicit content into the + @ repository.

+ @ + @

Click on the "rcvid" to show a list of specific artifacts received + @ by a transaction. After identifying illicit artifacts, remove them + @ using the "Shun" button. If an "rcvid" is not hyperlinked, that means + @ all artifacts associated with that rcvid have already been shunned + @ or purged.

+ @ + @ + @ + @ + @ + @ + @ + cnt = 0; + while( db_step(&q)==SQLITE_ROW ){ + int rcvid = db_column_int(&q, 0); + const char *zUser = db_column_text(&q, 1); + const char *zDate = db_column_text(&q, 2); + const char *zIpAddr = db_column_text(&q, 3); + int usesSha1 = db_column_int(&q, 5)!=0; + int usesSha3 = db_column_int(&q, 6)!=0; + static const char *const zHashType[] = { "", "sha1", "sha3", "both" }; + const char *zHash = zHashType[usesSha1+usesSha3*2]; + if( cnt==perScreen && !showAll ){ + style_submenu_element("Older", "rcvfromlist?ofst=%d", ofst+perScreen); + }else{ + cnt++; + @ + if( db_column_int(&q,4) ){ + @ + }else{ + @ + } + @ + @ + @ + @ + @ + } + } + db_finalize(&q); + @
rcvidDateUserHashIP Address
+ @ %d(rcvid)%d(rcvid)%s(zDate)%h(zUser)%s(zHash)%s(zIpAddr)
+ style_finish_page(); +} + +/* +** WEBPAGE: rcvfrom +** +** Show a single RCVFROM table entry identified by the rcvid= query +** parameters. Requires Admin privilege. +*/ +void rcvfrom_page(void){ + int rcvid = atoi(PD("rcvid","0")); + Stmt q; + int cnt; + + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + style_header("Artifact Receipt %d", rcvid); + if( db_exists( + "SELECT 1 FROM blob WHERE rcvid=%d AND" + " NOT EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid) + ){ + style_submenu_element("Shun All", "shun?shun&rcvid=%d#addshun", rcvid); + } + if( db_exists( + "SELECT 1 FROM blob WHERE rcvid=%d AND" + " EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid) + ){ + style_submenu_element("Unshun All", "shun?accept&rcvid=%d#delshun", rcvid); + } + db_prepare(&q, + "SELECT login, datetime(rcvfrom.mtime), rcvfrom.ipaddr" + " FROM rcvfrom LEFT JOIN user USING(uid)" + " WHERE rcvid=%d", + rcvid + ); + @ + @ + @ + if( db_step(&q)==SQLITE_ROW ){ + const char *zUser = db_column_text(&q, 0); + const char *zDate = db_column_text(&q, 1); + const char *zIpAddr = db_column_text(&q, 2); + @ + @ + @ + @ + @ + @ + } + db_finalize(&q); + db_multi_exec( + "CREATE TEMP TABLE toshow(rid INTEGER PRIMARY KEY);" + "INSERT INTO toshow SELECT rid FROM blob WHERE rcvid=%d", rcvid + ); + describe_artifacts("IN toshow"); + db_prepare(&q, + "SELECT blob.rid, blob.uuid, blob.size, description.summary\n" + " FROM blob LEFT JOIN description ON (blob.rid=description.rid)" + " WHERE blob.rcvid=%d", rcvid + ); + cnt = 0; + while( db_step(&q)==SQLITE_ROW ){ + const char *zUuid = db_column_text(&q, 1); + int size = db_column_int(&q, 2); + const char *zDesc = db_column_text(&q, 3); + if( zDesc==0 ) zDesc = ""; + if( cnt==0 ){ + @ + @ + } + if( db_table_exists("repository","unversioned") ){ + cnt = 0; + if( PB("uvdelete") && PB("confirmdelete") ){ + db_multi_exec( + "DELETE FROM unversioned WHERE rcvid=%d", rcvid + ); + } + db_finalize(&q); + db_prepare(&q, + "SELECT name, hash, sz\n" + " FROM unversioned " + " WHERE rcvid=%d", rcvid + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q,0); + const char *zHash = db_column_text(&q,1); + int size = db_column_int(&q,2); + int isDeleted = zHash==0; + if( cnt==0 ){ + @ + @ + } + } + @
rcvid:%d(rcvid)
User:%s(zUser)
Date:%s(zDate)
IP Address:%s(zIpAddr)
Artifacts: + } + cnt++; + @ %s(zUuid) + @ %h(zDesc) (size: %d(size))
+ } + if( cnt>0 ){ + @

+ if( db_exists( + "SELECT 1 FROM blob WHERE rcvid=%d AND" + " NOT EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid) + ){ + @

+ @ + @ + @ + @
+ } + if( db_exists( + "SELECT 1 FROM blob WHERE rcvid=%d AND" + " EXISTS (SELECT 1 FROM shun WHERE shun.uuid=blob.uuid)", rcvid) + ){ + @
+ @ + @ + @ + @
+ } + @
Unversioned Files: + } + cnt++; + if( isDeleted ){ + @ %h(zName) (deleted)
+ }else{ + @ %h(zName) (size: %d(size))
+ } + } + if( cnt>0 ){ + @

+ @ + @ + if( PB("uvdelete") ){ + @ + @ + }else{ + @ + } + @
+ @
+ db_finalize(&q); + style_finish_page(); +} -- cgit v1.3.1