From 68a90f319663097514a5af3806b756e667fe80c1 Mon Sep 17 00:00:00 2001
From: rswindell <>
Date: Tue, 8 Nov 2016 20:17:12 +0000
Subject: [PATCH] Inspired by the U.S. presdential election: The beginnings of
 an SMB-based voting system - very experimental: The concept is that a "vote"
 message can be used to reply to: 1. A normal message, as either an upvote or
 a downvote, ala social media 2. A poll, polls can either allow a single
 choice answer or multiple answers Vote messages won't be visible as normal
 messages (e.g. when reading messages online) and SMB processing software
 (e.g. SBBSecho) should ignore these messages because they have no body text.
 Polls are going to need more work, but the idea is to have the poll question
 as a single (newly defined) hfield and the possible answers as dfields.

---
 src/sbbs3/atcodes.cpp  | 14 ++++++++--
 src/sbbs3/chksmb.c     |  9 ++++---
 src/sbbs3/fixsmb.c     |  4 +--
 src/sbbs3/readmsgs.cpp | 50 ++++++++++++++++++++++++++++++++----
 src/sbbs3/sbbsdefs.h   |  4 +--
 src/smblib/smbadd.c    | 58 ++++++++++++++++++++++++++++++++++++++++--
 src/smblib/smbdefs.h   | 32 +++++++++++++++++------
 src/smblib/smblib.c    | 47 ++++++++++++++++++++++++++++++++--
 src/smblib/smblib.h    |  4 +--
 9 files changed, 192 insertions(+), 30 deletions(-)

diff --git a/src/sbbs3/atcodes.cpp b/src/sbbs3/atcodes.cpp
index bb63c77587..5ba3bcc0cd 100644
--- a/src/sbbs3/atcodes.cpp
+++ b/src/sbbs3/atcodes.cpp
@@ -1,5 +1,3 @@
-/* atcodes.cpp */
-
 /* Synchronet "@code" functions */
 
 /* $Id$ */
@@ -1053,6 +1051,18 @@ const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen)
 		safe_snprintf(str,maxlen,"%lu",current_msg->hdr.number);
 		return(str);
 	}
+	if(!strcmp(sp,"MSG_SCORE") && current_msg!=NULL) {
+		safe_snprintf(str, maxlen, "%ld", current_msg->upvotes - current_msg->downvotes);
+		return(str);
+	}
+	if(!strcmp(sp,"MSG_UPVOTES") && current_msg!=NULL) {
+		safe_snprintf(str, maxlen, "%lu", current_msg->upvotes);
+		return(str);
+	}
+	if(!strcmp(sp,"MSG_DOWNVOTES") && current_msg!=NULL) {
+		safe_snprintf(str, maxlen, "%lu", current_msg->downvotes);
+		return(str);
+	}
 
 	if(!strcmp(sp,"SMB_AREA")) {
 		if(smb.subnum!=INVALID_SUB && smb.subnum<cfg.total_subs)
diff --git a/src/sbbs3/chksmb.c b/src/sbbs3/chksmb.c
index bb5f3ee545..a6e95edff3 100644
--- a/src/sbbs3/chksmb.c
+++ b/src/sbbs3/chksmb.c
@@ -1,5 +1,3 @@
-/* chksmb.c */
-
 /* Synchronet message base (SMB) validity checker */
 
 /* $Id$ */
@@ -8,7 +6,7 @@
  * @format.tab-size 4		(Plain Text/Source Code File Header)			*
  * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
  *																			*
- * Copyright 2012 Rob Swindell - http://www.synchro.net/copyright.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				*
@@ -432,7 +430,8 @@ int main(int argc, char **argv)
 							"index import date/time\n");
 					timeerr++; 
 				}
-				if(msg.idx.subj!=smb_subject_crc(msg.subj)) {
+				if(msg.hdr.type != SMB_MSG_TYPE_VOTE
+					&& msg.idx.subj!=smb_subject_crc(msg.subj)) {
 					fprintf(stderr,"%sSubject CRC mismatch\n",beep);
 					msgerr=TRUE;
 					if(extinfo)
@@ -453,6 +452,7 @@ int main(int argc, char **argv)
 					fromcrc++; 
 				}
 				if(!(smb.status.attr&SMB_EMAIL) 
+					&& msg.hdr.type != SMB_MSG_TYPE_VOTE
 					&& msg.idx.from!=smb_name_crc(msg.from)) {
 					fprintf(stderr,"%sFrom CRC mismatch\n",beep);
 					msgerr=TRUE;
@@ -474,6 +474,7 @@ int main(int argc, char **argv)
 					tocrc++; 
 				}
 				if(!(smb.status.attr&SMB_EMAIL) 
+					&& msg.hdr.type != SMB_MSG_TYPE_VOTE
 					&& msg.to_ext==NULL && msg.idx.to!=smb_name_crc(msg.to)) {
 					fprintf(stderr,"%sTo CRC mismatch\n",beep);
 					msgerr=TRUE;
diff --git a/src/sbbs3/fixsmb.c b/src/sbbs3/fixsmb.c
index fb7b3f3e3c..888448de6b 100644
--- a/src/sbbs3/fixsmb.c
+++ b/src/sbbs3/fixsmb.c
@@ -1,5 +1,3 @@
-/* fixsmb.c */
-
 /* Synchronet message base (SMB) index re-generator */
 
 /* $Id$ */
@@ -8,7 +6,7 @@
  * @format.tab-size 4		(Plain Text/Source Code File Header)			*
  * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
  *																			*
- * Copyright 2015 Rob Swindell - http://www.synchro.net/copyright.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				*
diff --git a/src/sbbs3/readmsgs.cpp b/src/sbbs3/readmsgs.cpp
index 5bfa29fc37..07bce84f3e 100644
--- a/src/sbbs3/readmsgs.cpp
+++ b/src/sbbs3/readmsgs.cpp
@@ -229,15 +229,12 @@ post_t * sbbs_t::loadposts(uint32_t *posts, uint subnum, ulong ptr, long mode, u
 	rewind(smb.sid_fp);
 
 	alloc_len=sizeof(post_t)*total;
-	#ifdef __OS2__
-		while(alloc_len%4096)
-			alloc_len++;
-	#endif
 	if((post=(post_t *)malloc(alloc_len))==NULL) {	/* alloc for max */
 		smb_unlocksmbhdr(&smb);
 		errormsg(WHERE,ERR_ALLOC,smb.file,sizeof(post_t *)*cfg.sub[subnum]->maxmsgs);
 		return(NULL); 
 	}
+	memset(post, 0, alloc_len);
 
 	if(unvalidated_num)
 		*unvalidated_num=ULONG_MAX;
@@ -268,6 +265,20 @@ post_t * sbbs_t::loadposts(uint32_t *posts, uint subnum, ulong ptr, long mode, u
 				break;
 		}
 
+		if(idx.attr&(MSG_UPVOTE|MSG_DOWNVOTE)) {
+			ulong u;
+			for(u = 0; u < l; u++)
+				if(post[u].idx.number == idx.msgnum)
+					break;
+			if(u < l) {
+				if(idx.attr&MSG_UPVOTE)
+					post[u].upvotes++;
+				else
+					post[u].downvotes++;
+			}
+			continue;
+		}
+
 		if(idx.attr&MSG_PRIVATE && !(mode&LP_PRIVATE)
 			&& !sub_op(subnum) && !(useron.rest&FLAG('Q'))) {
 			if(idx.to!=namecrc && idx.from!=namecrc
@@ -643,6 +654,8 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 			if(!reads && mode)
 				CRLF;
 
+			msg.upvotes = post[smb.curmsg].upvotes;
+			msg.downvotes = post[smb.curmsg].downvotes;
 			show_msg(&msg
 				,msg.from_ext && !strcmp(msg.from_ext,"1") && !msg.from_net.type
 					? 0:P_NOATCODES);
@@ -737,7 +750,7 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 			bprintf(text[UnvalidatedWarning],unvalidated+1);
 		bprintf(text[ReadingSub],ugrp,cfg.grp[cfg.sub[subnum]->grp]->sname
 			,usub,cfg.sub[subnum]->sname,smb.curmsg+1,smb.msgs);
-		sprintf(str,"ABCDEFILMNPQRTUY?<>[]{}-+()");
+		sprintf(str,"ABCDEFILMNPQRTUVY?<>[]{}-+()");
 		if(sub_op(subnum))
 			strcat(str,"O");
 		do_find=true;
@@ -999,6 +1012,33 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 				if(!showposts_toyou(subnum, post,0,smb.msgs, SCAN_UNREAD))
 					bputs(text[NoMessagesFound]);
 				break;
+			case 'V':	/* Vote in reply to message */
+			{
+				smbmsg_t vote;
+
+				if(smb_voted_already(&smb, msg.hdr.number
+					,cfg.sub[subnum]->misc&SUB_NAME ? useron.name : useron.alias, NET_NONE, NULL)) {
+					bputs(text[No]);
+					break;
+				}
+				ZERO_VAR(vote);
+				vote.hdr.attr = MSG_UPVOTE;
+				vote.hdr.thread_back = msg.hdr.number;
+				vote.hdr.when_written.time = msg.hdr.when_imported.time = time32(NULL);
+				vote.hdr.when_written.zone = msg.hdr.when_imported.zone = sys_timezone(&cfg);
+
+				smb_hfield_str(&vote, SENDER, cfg.sub[subnum]->misc&SUB_NAME ? useron.name : useron.alias);
+
+				sprintf(str, "%u", useron.number);
+				smb_hfield_str(&vote, SENDEREXT, str);
+
+				/* Security logging */
+				msg_client_hfields(&vote, &client);
+				smb_hfield_str(&vote, SENDERSERVER, startup->host_name);
+
+				smb_addvote(&smb, &vote, smb_storage_mode(&cfg, &smb));
+				break;
+			}
 			case '-':
 				if(smb.curmsg>0) smb.curmsg--;
 				do_find=false;
diff --git a/src/sbbs3/sbbsdefs.h b/src/sbbs3/sbbsdefs.h
index 5539d9b728..3c460cc6b8 100644
--- a/src/sbbs3/sbbsdefs.h
+++ b/src/sbbs3/sbbsdefs.h
@@ -1,5 +1,3 @@
-/* sbbsdefs.h */
-
 /* Synchronet constants, macros, and structure definitions */
 
 /* $Id$ */
@@ -997,6 +995,8 @@ typedef struct {						/* File (transfers) Data */
 typedef struct {
 	idxrec_t	idx;					/* defined in smbdefs.h */
 	uint32_t	num;					/* 1-based offset */
+	uint32_t	upvotes;
+	uint32_t	downvotes;
 } post_t;
 typedef idxrec_t mail_t;				/* defined in smbdefs.h */
 typedef fidoaddr_t faddr_t;				/* defined in smbdefs.h */
diff --git a/src/smblib/smbadd.c b/src/smblib/smbadd.c
index 56f6487132..561f7e4239 100644
--- a/src/smblib/smbadd.c
+++ b/src/smblib/smbadd.c
@@ -1,5 +1,3 @@
-/* smbadd.c */
-
 /* Synchronet message base (SMB) high-level "add message" function */
 
 /* $Id$ */
@@ -319,3 +317,59 @@ int SMBCALL smb_addmsg(smb_t* smb, smbmsg_t* msg, int storage, long dupechk_hash
 
 	return(retval);
 }
+
+int SMBCALL smb_addvote(smb_t* smb, smbmsg_t* msg, int storage)
+{
+	int			retval;
+	smbmsg_t	remsg;
+
+	if(!SMB_IS_OPEN(smb)) {
+		safe_snprintf(smb->last_error, sizeof(smb->last_error), "msgbase not open");
+		return SMB_ERR_NOT_OPEN;
+	}
+
+	if(filelength(fileno(smb->shd_fp)) < 1) {	 /* Create it if it doesn't exist */
+		/* smb->status.max_crcs, max_msgs, max_age, and attr should be pre-initialized */
+		if((retval=smb_create(smb))!=SMB_SUCCESS) 
+			return retval;
+	}
+
+	if(!smb->locked && smb_locksmbhdr(smb) != SMB_SUCCESS)
+		return SMB_ERR_LOCK;
+
+	msg->hdr.total_dfields = 0;
+
+	if((retval=smb_getstatus(smb)) != SMB_SUCCESS) {
+		smb_unlocksmbhdr(smb);
+		return retval;
+	}
+
+	msg->hdr.type = SMB_MSG_TYPE_VOTE;
+	msg->hdr.number = smb->status.last_msg+1;
+
+	if(msg->hdr.when_imported.time == 0) {
+		msg->hdr.when_imported.time = (uint32_t)time(NULL);
+		msg->hdr.when_imported.zone = 0;	/* how do we detect system TZ? */
+	}
+	if(msg->hdr.when_written.time == 0)	/* Uninitialized */
+		msg->hdr.when_written = msg->hdr.when_imported;
+
+	/* Look-up thread_back if RFC822 Reply-ID was specified */
+	if(msg->hdr.thread_back == 0 && msg->reply_id != NULL) {
+		if(smb_getmsgidx_by_msgid(smb, &remsg, msg->reply_id) == SMB_SUCCESS)
+			msg->hdr.thread_back = remsg.idx.number;	/* needed for threading backward */
+	}
+
+	/* Look-up thread_back if FTN REPLY was specified */
+	if(msg->hdr.thread_back == 0 && msg->ftn_reply != NULL) {
+		if(smb_getmsgidx_by_ftnid(smb, &remsg, msg->ftn_reply) == SMB_SUCCESS)
+			msg->hdr.thread_back = remsg.idx.number;	/* needed for threading backward */
+	}
+
+	retval = smb_addmsghdr(smb, msg, storage); /* calls smb_unlocksmbhdr() */
+
+	if(smb->locked)
+		smb_unlocksmbhdr(smb);
+
+	return retval;
+}
diff --git a/src/smblib/smbdefs.h b/src/smblib/smbdefs.h
index 0912b2a6da..c1c28a40a3 100644
--- a/src/smblib/smbdefs.h
+++ b/src/smblib/smbdefs.h
@@ -1,5 +1,3 @@
-/* smbdefs.h */
-
 /* Synchronet message base constant and structure definitions */
 
 /* $Id$ */
@@ -338,6 +336,8 @@
 #define MSG_VALIDATED		(1<<8)
 #define MSG_REPLIED			(1<<9)		/* User replied to this message */
 #define MSG_NOREPLY			(1<<10)		/* No replies (or bounces) should be sent to the sender */
+#define MSG_UPVOTE			(1<<11)		/* This message is an upvote */
+#define MSG_DOWNVOTE		(1<<12)		/* This message is a downvote */
 
 										/* Auxillary header attributes */
 #define MSG_FILEREQUEST 	(1<<0)		/* File request */
@@ -438,9 +438,17 @@ typedef struct _PACK {		/* Time with time-zone */
 
 typedef struct _PACK {		/* Index record */
 
-	uint16_t	to; 			/* 16-bit CRC of recipient name (lower case) */
-	uint16_t	from;			/* 16-bit CRC of sender name (lower case) */
-	uint16_t	subj;			/* 16-bit CRC of subject (lower case, w/o RE:) */
+	union {
+		struct {
+			uint16_t	to; 		/* 16-bit CRC of recipient name (lower case) or user # */
+			uint16_t	from;		/* 16-bit CRC of sender name (lower case) or user # */
+			uint16_t	subj;		/* 16-bit CRC of subject (lower case, w/o RE:) */
+		};
+		struct {
+			uint16_t	vote;		/* vote value */
+			uint32_t	msgnum;		/* number of message this vote is in response to */
+		};
+	};
 	uint16_t	attr;			/* attributes (read, permanent, etc.) */
 	uint32_t	offset; 		/* offset into header file */
 	uint32_t	number; 		/* number of message (1 based) */
@@ -515,14 +523,20 @@ typedef struct _PACK {		/* Message base status header */
 
 } smbstatus_t;
 
+enum smb_msg_type {
+     SMB_MSG_TYPE_NORMAL		/* Classic message (for reading) */
+	,SMB_MSG_TYPE_POLL			/* A poll question  */
+	,SMB_MSG_TYPE_VOTE			/* Voter response to poll or normal message */
+};
+
 typedef struct _PACK {		/* Message header */
 
 	/* 00 */ uchar		id[LEN_HEADER_ID];	/* SHD<^Z> */
-    /* 04 */ uint16_t	type;				/* Message type (normally 0) */
+    /* 04 */ uint16_t	type;				/* Message type (enum smb_msg_type) */
     /* 06 */ uint16_t	version;			/* Version of type (initially 100h for 1.00) */
     /* 08 */ uint16_t	length;				/* Total length of fixed record + all fields */
 	/* 0a */ uint16_t	attr;				/* Attributes (bit field) (duped in SID) */
-	/* 0c */ uint32_t	auxattr;			/* Auxillary attributes (bit field) */
+	/* 0c */ uint32_t	auxattr;			/* Auxiliary attributes (bit field) */
     /* 10 */ uint32_t	netattr;			/* Network attributes */
 	/* 14 */ when_t		when_written;		/* Date/time/zone message was written */
 	/* 1a */ when_t		when_imported;		/* Date/time/zone message was imported */
@@ -531,7 +545,7 @@ typedef struct _PACK {		/* Message header */
     /* 28 */ uint32_t	thread_next;		/* Next message in thread */
     /* 2c */ uint32_t	thread_first;		/* First reply to this message */
 	/* 30 */ uint16_t	delivery_attempts;	/* Delivery attempt counter */
-	/* 32 */ uchar		reserved[2];		/* Reserved for future use */
+	/* 32 */ int16_t	vote;				/* Vote value (response to poll) */
 	/* 34 */ uint32_t	thread_id;			/* Number of original message in thread (or 0 if unknown) */
 	/* 38 */ uint32_t	times_downloaded;	/* Total number of times downloaded (moved Mar-6-2012) */
 	/* 3c */ uint32_t	last_downloaded;	/* Date/time of last download (moved Mar-6-2012) */
@@ -624,6 +638,8 @@ typedef struct {				/* Message */
 	uint32_t	priority;		/* Message priority (0 is lowest) */
 	uint32_t	cost;			/* Cost to download/read */
 	uint32_t	flags;			/* Various smblib run-time flags (see MSG_FLAG_*) */
+	uint32_t	upvotes;		/* Vote tally for this message */
+	uint32_t	downvotes;		/* Vote tally for this message */
 
 } smbmsg_t;
 
diff --git a/src/smblib/smblib.c b/src/smblib/smblib.c
index 327b6d7364..20fd0ee422 100644
--- a/src/smblib/smblib.c
+++ b/src/smblib/smblib.c
@@ -1,5 +1,3 @@
-/* smblib.c */
-
 /* Synchronet message base (SMB) library routines */
 
 /* $Id$ */
@@ -1636,6 +1634,9 @@ int SMBCALL smb_init_idx(smb_t* smb, smbmsg_t* msg)
 			msg->idx.from=atoi(msg->from_ext);
 		else
 			msg->idx.from=0; 
+	} else if(msg->hdr.type == SMB_MSG_TYPE_VOTE) {
+		msg->idx.vote = msg->hdr.vote;
+		msg->idx.msgnum = msg->hdr.thread_back;
 	} else {
 		msg->idx.to=smb_name_crc(msg->to);
 		msg->idx.from=smb_name_crc(msg->from);
@@ -1649,6 +1650,48 @@ int SMBCALL smb_init_idx(smb_t* smb, smbmsg_t* msg)
 	return(SMB_SUCCESS);
 }
 
+BOOL SMBCALL smb_voted_already(smb_t* smb, uint32_t msgnum, const char* name, enum smb_net_type net_type, void* net_addr)
+{
+	BOOL result = FALSE;
+	smbmsg_t msg;
+
+	if(smb->sid_fp==NULL) {
+		safe_snprintf(smb->last_error, sizeof(smb->last_error), "index not open");
+		return SMB_ERR_NOT_OPEN;
+	}
+	clearerr(smb->sid_fp);
+	if(fseek(smb->sid_fp,0,SEEK_SET)) {
+		safe_snprintf(smb->last_error, sizeof(smb->last_error)
+			,"%d '%s' seeking to beginning of index file"
+			,get_errno(), STRERROR(get_errno()));
+		return SMB_ERR_SEEK;
+	}
+	while(!result && smb_fread(smb, &msg.idx, sizeof(msg.idx), smb->sid_fp) == sizeof(msg.idx)) {
+		if(!(msg.idx.attr&(MSG_UPVOTE|MSG_DOWNVOTE)))
+			continue;
+		if(msg.idx.msgnum != msgnum)
+			continue;
+		if(smb_getmsghdr(smb, &msg) != SMB_SUCCESS)
+			continue;
+		if(stricmp(msg.from, name) == 0) {
+			if(msg.from_net.type == net_type)
+				switch(net_type) {
+				case NET_NONE:
+					result = TRUE;
+					break;
+				case NET_FIDO:
+					result = memcmp(msg.from_net.addr, net_addr, sizeof(fidoaddr_t)) == 0;
+					break;
+				default:
+					result = stricmp(msg.from_net.addr, net_addr) == 0;
+					break;
+			}
+		}
+		smb_freemsgmem(&msg);
+	}
+	return result;
+}
+
 /****************************************************************************/
 /* Writes index information for 'msg'                                       */
 /* msg->idx 																*/
diff --git a/src/smblib/smblib.h b/src/smblib/smblib.h
index 20942268a5..1f9f2242fc 100644
--- a/src/smblib/smblib.h
+++ b/src/smblib/smblib.h
@@ -1,5 +1,3 @@
-/* smblib.h */
-
 /* Synchronet message base (SMB) library function prototypes */
 
 /* $Id$ */
@@ -156,10 +154,12 @@ SMBEXPORT int		SMBCALL smb_updatethread(smb_t* smb, smbmsg_t* remsg, ulong newms
 SMBEXPORT int		SMBCALL smb_updatemsg(smb_t* smb, smbmsg_t* msg);
 SMBEXPORT BOOL		SMBCALL smb_valid_hdr_offset(smb_t* smb, ulong offset);
 SMBEXPORT int		SMBCALL smb_init_idx(smb_t* smb, smbmsg_t* msg);
+SMBEXPORT BOOL		SMBCALL	smb_voted_already(smb_t*, uint32_t msgnum, const char* name, enum smb_net_type, void* net_addr);
 
 /* smbadd.c */
 SMBEXPORT int		SMBCALL smb_addmsg(smb_t* smb, smbmsg_t* msg, int storage, long dupechk_hashes
 						,uint16_t xlat, const uchar* body, const uchar* tail);
+SMBEXPORT int		SMBCALL smb_addvote(smb_t* smb, smbmsg_t* msg, int storage);
 
 /* smballoc.c */
 SMBEXPORT long		SMBCALL smb_allochdr(smb_t* smb, ulong length);
-- 
GitLab