From e1df41ea97f6182ce306c193fe05609b2633a7e1 Mon Sep 17 00:00:00 2001
From: "Rob Swindell (on Debian Linux)" <rob@synchro.net>
Date: Sun, 3 Nov 2024 13:52:40 -0800
Subject: [PATCH] New utility: 'trashman' to manage/maintain text/*.can
 (filter) files

Though we've had the auto-filtering feature for about a year now, with
expiration dates supported/added to every trash record, nothing was
removing the expired trash/filter items. Until now.

Check the size of your text/*.can files: if they're really big, this is
the solution.

Not yet building for Windows

Sysops will want to run this periodically (monthly?), e.g.
	trashman /sbbs/text/*.can
---
 src/sbbs3/GNUmakefile |   5 ++
 src/sbbs3/objects.mk  |   5 +-
 src/sbbs3/scfglib.h   |   3 --
 src/sbbs3/scfglib1.c  |  14 ------
 src/sbbs3/targets.mk  |   8 +++-
 src/sbbs3/trash.c     |  55 +++++++++++++++-------
 src/sbbs3/trash.h     |   3 ++
 src/sbbs3/trashman.c  | 107 ++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 164 insertions(+), 36 deletions(-)
 create mode 100644 src/sbbs3/trashman.c

diff --git a/src/sbbs3/GNUmakefile b/src/sbbs3/GNUmakefile
index 1eeba71b34..6a69f59b5a 100644
--- a/src/sbbs3/GNUmakefile
+++ b/src/sbbs3/GNUmakefile
@@ -322,6 +322,11 @@ $(FMSGDUMP): $(FMSGDUMP_OBJS) | $(EXEODIR) $(OBJODIR)
 	@echo Linking $@
 	$(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(FMSGDUMP_OBJS)
 
+# TRASHMAN
+$(TRASHMAN): $(TRASHMAN_OBJS) | $(EXEODIR) $(OBJODIR)
+	@echo Linking $@
+	$(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $^ -lm
+
 $(UPGRADE_TO_V319): $(UPGRADE_TO_V319_OBJS) $(OBJODIR) | $(EXEODIR)
 	@echo Linking $@
 	$(QUIET)$(CC) $(CONSOLE_LDFLAGS) -o $@ $(UPGRADE_TO_V319_OBJS) $(SMBLIB_LIBS) $(XPDEV_LIBS) $(ENCODE_LIBS) $(HASH_LIBS) $(FILE_LIBS)
diff --git a/src/sbbs3/objects.mk b/src/sbbs3/objects.mk
index ff20d2664e..4261783d08 100644
--- a/src/sbbs3/objects.mk
+++ b/src/sbbs3/objects.mk
@@ -296,4 +296,7 @@ TEXTGEN_OBJS = 		$(OBJODIR)/textgen$(OFILE) \
 			$(OBJODIR)/getctrl$(OFILE) \
 			$(OBJODIR)/str_util$(OFILE)
 
-
+TRASHMAN_OBJS = 	$(OBJODIR)/trashman$(OFILE) \
+			$(OBJODIR)/trash$(OFILE) \
+ 		  	$(OBJODIR)/nopen$(OFILE) \
+			$(OBJODIR)/findstr$(OFILE)
diff --git a/src/sbbs3/scfglib.h b/src/sbbs3/scfglib.h
index 85f07c30be..51f02132f8 100644
--- a/src/sbbs3/scfglib.h
+++ b/src/sbbs3/scfglib.h
@@ -78,9 +78,6 @@ DLLEXPORT bool	is_valid_grpnum(scfg_t*, int);
 DLLEXPORT bool	is_valid_xtrnnum(scfg_t*, int);
 DLLEXPORT bool	is_valid_xtrnsec(scfg_t*, int);
 
-DLLEXPORT char *	trashcan_fname(scfg_t* cfg, const char *name, char* fname, size_t);
-DLLEXPORT char *	twitlist_fname(scfg_t* cfg, char* fname, size_t);
-
 DLLEXPORT char *	sub_newsgroup_name(scfg_t*, sub_t*, char*, size_t);
 DLLEXPORT char *	sub_area_tag(scfg_t*, sub_t*, char*, size_t);
 DLLEXPORT char *	dir_area_tag(scfg_t*, dir_t*, char*, size_t);
diff --git a/src/sbbs3/scfglib1.c b/src/sbbs3/scfglib1.c
index 56ca69705c..3ba078f6f3 100644
--- a/src/sbbs3/scfglib1.c
+++ b/src/sbbs3/scfglib1.c
@@ -931,20 +931,6 @@ faddr_t* nearest_sysfaddr(scfg_t* cfg, faddr_t* addr)
 	return &cfg->faddr[i];
 }
 
-/****************************************************************************/
-char* trashcan_fname(scfg_t* cfg, const char* name, char* fname, size_t maxlen)
-{
-	safe_snprintf(fname,maxlen,"%s%s.can",cfg->text_dir,name);
-	return fname;
-}
-
-/****************************************************************************/
-char* twitlist_fname(scfg_t* cfg, char* fname, size_t maxlen)
-{
-	safe_snprintf(fname, maxlen, "%stwitlist.cfg", cfg->ctrl_dir);
-	return fname;
-}
-
 char* sub_newsgroup_name(scfg_t* cfg, sub_t* sub, char* str, size_t size)
 {
 	memset(str, 0, size);
diff --git a/src/sbbs3/targets.mk b/src/sbbs3/targets.mk
index f23a519c82..117d615475 100644
--- a/src/sbbs3/targets.mk
+++ b/src/sbbs3/targets.mk
@@ -34,6 +34,7 @@ DUPEFIND	= $(EXEODIR)/dupefind$(EXEFILE)
 READSAUCE	= $(EXEODIR)/readsauce$(EXEFILE)
 PKTDUMP		= $(EXEODIR)/pktdump$(EXEFILE)
 FMSGDUMP	= $(EXEODIR)/fmsgdump$(EXEFILE)
+TRASHMAN	= $(EXEODIR)/trashman$(EXEFILE)
 UPGRADE_TO_V319 = $(EXEODIR)/upgrade_to_v319$(EXEFILE)
 UPGRADE_TO_V320 = $(EXEODIR)/upgrade_to_v320$(EXEFILE)
 
@@ -45,7 +46,9 @@ UTILS		= $(FIXSMB) $(CHKSMB) \
 			  $(QWKNODES) $(SLOG) \
 			  $(DELFILES) $(DUPEFIND) \
 			  $(SEXYZ) $(READSAUCE) \
-			  $(PKTDUMP) $(FMSGDUMP) $(UPGRADE_TO_V319) \
+			  $(PKTDUMP) $(FMSGDUMP) \
+			  $(TRASHMAN) \
+			  $(UPGRADE_TO_V319) \
 			  $(UPGRADE_TO_V320)
 
 GIT_INFO	= git_hash.h git_branch.h
@@ -74,7 +77,7 @@ standalone-utils: $(FIXSMB) $(CHKSMB) \
 			  $(QWKNODES) \
 			  $(DELFILES) $(DUPEFIND) \
 			  $(SEXYZ) $(READSAUCE) \
-			  $(PKTDUMP) $(FMSGDUMP)
+			  $(PKTDUMP) $(FMSGDUMP) $(TRASHMAN)
 
 .PHONY: libdeps
 libdeps: $(JS_DEPS) gitinfo smblib xpdev-mt $(MTOBJODIR) $(LIBODIR)
@@ -200,6 +203,7 @@ $(SLOG): $(XPDEV_LIB)
 $(DELFILES): $(XPDEV_LIB) $(SMBLIB)
 $(DUPEFIND): $(XPDEV_LIB) $(SMBLIB)
 $(READSAUCE): $(XPDEV_LIB)
+$(TRASHMAN): $(XPDEV_LIB)
 $(UPGRADE_TO_V319): $(XPDEV_LIB) $(SMBLIB)
 $(UPGRADE_TO_V320): $(XPDEV_LIB)
 
diff --git a/src/sbbs3/trash.c b/src/sbbs3/trash.c
index 90207e0998..56f8182280 100644
--- a/src/sbbs3/trash.c
+++ b/src/sbbs3/trash.c
@@ -27,6 +27,20 @@
 #include "findstr.h"
 #include "nopen.h"
 
+/****************************************************************************/
+char* trashcan_fname(scfg_t* cfg, const char* name, char* fname, size_t maxlen)
+{
+	safe_snprintf(fname,maxlen,"%s%s.can",cfg->text_dir,name);
+	return fname;
+}
+
+/****************************************************************************/
+char* twitlist_fname(scfg_t* cfg, char* fname, size_t maxlen)
+{
+	safe_snprintf(fname, maxlen, "%stwitlist.cfg", cfg->ctrl_dir);
+	return fname;
+}
+
 /****************************************************************************/
 /* Searches the file <name>.can in the TEXT directory for matches			*/
 /* Returns true if found in list, false if not.								*/
@@ -38,23 +52,32 @@ bool trashcan(scfg_t* cfg, const char* insearchof, const char* name)
 }
 
 /****************************************************************************/
-static void parse_trash_details(char* p, struct trash* trash)
+bool trash_parse_details(const char* p, struct trash* trash, char* item, size_t size)
 {
 	memset(trash, 0, sizeof(*trash));
 
-	str_list_t list = strListSplit(NULL, p, "\t");
+	str_list_t list = strListSplitCopy(NULL, p, "\t");
 	if(list == NULL)
-		return;
-
-	trash->added = iniGetDateTime(list, ROOT_SECTION, "t", 0);
-	trash->expires = iniGetDateTime(list, ROOT_SECTION, "e", 0);
-	if((p = iniGetValue(list, ROOT_SECTION, "p", NULL, NULL)) != NULL)
-		SAFECOPY(trash->prot, p);
-	if((p = iniGetValue(list, ROOT_SECTION, "u", NULL, NULL)) != NULL)
-		SAFECOPY(trash->user, p);
-	if((p = iniGetValue(list, ROOT_SECTION, "r", NULL, NULL)) != NULL)
-		SAFECOPY(trash->reason, p);
+		return false;
+
+	if(item != NULL && size > 0) {
+		if(list[0] == NULL)
+			*item = '\0';
+		else
+			strlcpy(item, list[0], size);
+	}
+	if(strListFastDelete(list, /* index: */0, /* count: */1)) {
+		trash->added = iniGetDateTime(list, ROOT_SECTION, "t", 0);
+		trash->expires = iniGetDateTime(list, ROOT_SECTION, "e", 0);
+		if((p = iniGetValue(list, ROOT_SECTION, "p", NULL, NULL)) != NULL)
+			SAFECOPY(trash->prot, p);
+		if((p = iniGetValue(list, ROOT_SECTION, "u", NULL, NULL)) != NULL)
+			SAFECOPY(trash->user, p);
+		if((p = iniGetValue(list, ROOT_SECTION, "r", NULL, NULL)) != NULL)
+			SAFECOPY(trash->reason, p);
+	}
 	strListFree(&list);
+	return true;
 }
 
 /****************************************************************************/
@@ -87,8 +110,8 @@ bool trashcan2(scfg_t* cfg, const char* str1, const char* str2, const char* name
 	if(!find2strs(str1, str2, trashcan_fname(cfg,name,fname,sizeof(fname)), details))
 		return false;
 	if(trash != NULL) {
-		parse_trash_details(details, trash);
-		if(trash->expires && trash->expires <= time(NULL))
+		if(trash_parse_details(details, trash, NULL, 0)
+			&& trash->expires && trash->expires <= time(NULL))
 			return false;
 	}
 	return true;
@@ -102,8 +125,8 @@ bool trash_in_list(const char* str1, const char* str2, str_list_t list, struct t
 	if(!find2strs_in_list(str1, str2, list, details))
 		return false;
 	if(trash != NULL) {
-		parse_trash_details(details, trash);
-		if(trash->expires && trash->expires <= time(NULL))
+		if(trash_parse_details(details, trash, NULL, 0)
+			&& trash->expires && trash->expires <= time(NULL))
 			return false;
 	}
 	return true;
diff --git a/src/sbbs3/trash.h b/src/sbbs3/trash.h
index f37630e85a..a94f638300 100644
--- a/src/sbbs3/trash.h
+++ b/src/sbbs3/trash.h
@@ -41,9 +41,12 @@ struct trash {
 extern "C" {
 #endif
 
+DLLEXPORT char*		trashcan_fname(scfg_t* cfg, const char *name, char* fname, size_t);
+DLLEXPORT char*		twitlist_fname(scfg_t* cfg, char* fname, size_t);
 DLLEXPORT bool		trashcan(scfg_t* cfg, const char *insearch, const char *name);
 DLLEXPORT bool		trashcan2(scfg_t* cfg, const char* str1, const char* str2, const char *name, struct trash*);
 DLLEXPORT bool		trash_in_list(const char* str1, const char* str2, str_list_t list, struct trash*);
+DLLEXPORT bool		trash_parse_details(const char* p, struct trash* trash, char* item, size_t);
 DLLEXPORT char *	trash_details(const struct trash*, char* str, size_t);
 DLLEXPORT str_list_t trashcan_list(scfg_t* cfg, const char* name);
 DLLEXPORT bool		is_host_exempt(scfg_t*, const char* ip_addr, const char* host_name);
diff --git a/src/sbbs3/trashman.c b/src/sbbs3/trashman.c
new file mode 100644
index 0000000000..e2b7b5e781
--- /dev/null
+++ b/src/sbbs3/trashman.c
@@ -0,0 +1,107 @@
+/* Synchronet client/content-filtering (trashcan/twit) maintenance */
+
+/****************************************************************************
+ * @format.tab-size 4		(Plain Text/Source Code File Header)			*
+ * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
+ *																			*
+ * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
+ *																			*
+ * This program is free software; you can redistribute it and/or			*
+ * modify it under the terms of the GNU General Public License				*
+ * as published by the Free Software Foundation; either version 2			*
+ * of the License, or (at your option) any later version.					*
+ * See the GNU General Public License for more details: gpl.txt or			*
+ * http://www.fsf.org/copyleft/gpl.html										*
+ *																			*
+ * For Synchronet coding style and modification guidelines, see				*
+ * http://www.synchro.net/source.html										*
+ *																			*
+ * Note: If this box doesn't appear square, then you need to fix your tabs.	*
+ ****************************************************************************/
+
+#include "trash.h"
+//#include "datewrap.h"
+//#include "xpdatetime.h"
+//#include "ini_file.h"
+//#include "scfglib.h"
+#include "findstr.h"
+#include "nopen.h"
+
+int verbosity = 0;
+
+int maint(const char* fname)
+{
+	int removed = 0;
+	str_list_t list = findstr_list(fname);
+	if(list == NULL) {
+		perror(fname);
+		return -1;
+	}
+	FILE* fp = fnopen(NULL, fname, O_WRONLY|O_TRUNC);
+	if(fp == NULL) {
+		perror(fname);
+		return -2;
+	}
+	time_t now = time(NULL);
+	for(int i = 0; list[i] != NULL; ++i) {
+		struct trash trash;
+		char item[256];
+		if(!trash_parse_details(list[i], &trash, item, sizeof item)) {
+			fputs(list[i], fp);
+			continue;
+		}
+		if(verbosity > 1) {
+			char details[256];
+			printf("%s %s\n", item, trash_details(&trash, details, sizeof details));
+		}
+		if(trash.expires && trash.expires < now) {
+			if(verbosity > 0)
+				printf("%s expired %s", item, ctime(&trash.expires));
+			++removed;
+			continue;
+		}
+		fputs(list[i], fp);
+	}
+	fclose(fp);
+	strListFree(&list);
+	return removed;
+}
+
+int usage(const char* prog)
+{
+	printf("usage: %s [-v][...] /path/to/file1.can [/path/to/file2.can][...]\n"
+		,getfname(prog));
+	return EXIT_SUCCESS;
+}
+
+int main(int argc, const char** argv)
+{
+	printf("\nSynchronet trash/filter file manager v1.0\n");
+
+	if(argc < 2)
+		return usage(argv[0]);
+	for(int i = 1; i < argc; ++i) {
+		const char* arg = argv[i];
+		if(*arg != '-')
+			continue;
+		switch(arg[1]) {
+			case 'v':
+				++verbosity;
+				break;
+			default:
+				return usage(argv[0]);
+		}
+	}
+	int total = 0;
+	for(int i = 1; i < argc; ++i) {
+		const char* arg = argv[i];
+		if(*arg == '-')
+			continue;
+		int  removed = maint(arg);
+		if(removed < 0)
+			return EXIT_FAILURE;
+		total += removed;
+	}
+	printf("%d total items removed\n", total);
+	return EXIT_SUCCESS;
+}
-- 
GitLab