diff --git a/src/sbbs3/atcodes.cpp b/src/sbbs3/atcodes.cpp
index bb63c77587ab57d7ae74e0caefb798a154ab36ac..5ba3bcc0cd2e37a7f93ddbca076a10b1a1688511 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 bb5f3ee5457061aa9adfaa233f8d87fdd25c3a12..a6e95edff385ff7c22af011f28399a52ca94c869 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 fb7b3f3e3cdf7b0ba320ea2a03e6d81a361ed6b9..888448de6b356773c6fccb86744c07af3deff6cd 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 5bfa29fc3725aaee12b7a17a5d97e02897312688..07bce84f3e39a177a9584b76d4716c6ed3f3274a 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 5539d9b72821ff109108a602ea0a150625e01228..3c460cc6b8e3fe49515ae162ec5786dc5ed7c4d3 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 56f6487132b35180e90cbc3bc618bf639ed85103..561f7e42392175edb5fb7857da05242c1e9628b5 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 0912b2a6dab6dde7ec6e42282d090d4049d3880b..c1c28a40a379f9287dc9b79a2140883ec18b5122 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 327b6d736404d3cea1c3f9dc197fb9ec28c474fb..20fd0ee4229244058388bdd2ba6ca86c47078175 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 20942268a5435e9b8d1200d7af1b63cf00b02de4..1f9f2242fc144719f5951be1a36fc33409153e15 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);