diff --git a/src/sbbs3/atcodes.cpp b/src/sbbs3/atcodes.cpp
index 06ff5b7939b201ae757a4aa2cf6fea6616e146f6..52aa402e7c3f8e9ddf1462d59144a724fb123586 100644
--- a/src/sbbs3/atcodes.cpp
+++ b/src/sbbs3/atcodes.cpp
@@ -996,18 +996,23 @@ const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen)
 	if(!strcmp(sp,"MSG_TIMEZONE") && current_msg!=NULL)
 		return(smb_zonestr(current_msg->hdr.when_written.zone,NULL));
 	if(!strcmp(sp,"MSG_ATTR") && current_msg!=NULL) {
-		safe_snprintf(str,maxlen,"%s%s%s%s%s%s%s%s%s%s%s"
-			,current_msg->hdr.attr&MSG_PRIVATE		? "Private  "   :nulstr
-			,current_msg->hdr.attr&MSG_READ			? "Read  "      :nulstr
-			,current_msg->hdr.attr&MSG_DELETE		? "Deleted  "   :nulstr
-			,current_msg->hdr.attr&MSG_KILLREAD		? "Kill  "      :nulstr
-			,current_msg->hdr.attr&MSG_ANONYMOUS	? "Anonymous  " :nulstr
-			,current_msg->hdr.attr&MSG_LOCKED		? "Locked  "    :nulstr
-			,current_msg->hdr.attr&MSG_PERMANENT	? "Permanent  " :nulstr
-			,current_msg->hdr.attr&MSG_MODERATED	? "Moderated  " :nulstr
-			,current_msg->hdr.attr&MSG_VALIDATED	? "Validated  " :nulstr
-			,current_msg->hdr.attr&MSG_REPLIED		? "Replied  "	:nulstr
-			,current_msg->hdr.attr&MSG_NOREPLY		? "NoReply  "	:nulstr
+		uint16_t attr = current_msg->hdr.attr;
+		uint16_t poll = attr&MSG_POLL_VOTE_MASK;
+		uint32_t auxattr = current_msg->hdr.auxattr;
+		safe_snprintf(str,maxlen,"%s%s%s%s%s%s%s%s%s%s%s%s%s"
+			,attr&MSG_PRIVATE						? "Private  "   :nulstr
+			,attr&MSG_READ							? "Read  "      :nulstr
+			,attr&MSG_DELETE						? "Deleted  "   :nulstr
+			,attr&MSG_KILLREAD						? "Kill  "      :nulstr
+			,attr&MSG_ANONYMOUS						? "Anonymous  " :nulstr
+			,attr&MSG_LOCKED						? "Locked  "    :nulstr
+			,attr&MSG_PERMANENT						? "Permanent  " :nulstr
+			,attr&MSG_MODERATED						? "Moderated  " :nulstr
+			,attr&MSG_VALIDATED						? "Validated  " :nulstr
+			,attr&MSG_REPLIED						? "Replied  "	:nulstr
+			,attr&MSG_NOREPLY						? "NoReply  "	:nulstr
+			,poll == MSG_POLL						? "Poll  "		:nulstr
+			,poll == MSG_POLL && auxattr&POLL_CLOSED	? "(Closed)  "	:nulstr
 			);
 		return(str);
 	}
diff --git a/src/sbbs3/getmsg.cpp b/src/sbbs3/getmsg.cpp
index d75049c496a5b3de49b9e14d9a927083022f6bc1..ee626cb2bcc06f0407f774c10ef630a7c1566c49 100644
--- a/src/sbbs3/getmsg.cpp
+++ b/src/sbbs3/getmsg.cpp
@@ -88,8 +88,11 @@ int sbbs_t::loadmsg(smbmsg_t *msg, ulong number)
 }
 
 
-void sbbs_t::show_msgattr(ushort attr)
+void sbbs_t::show_msgattr(smbmsg_t* msg)
 {
+	uint16_t attr = msg->hdr.attr;
+	uint16_t poll = attr&MSG_POLL_VOTE_MASK;
+	uint32_t auxattr = msg->hdr.auxattr;
 
 	bprintf(text[MsgAttr]
 		,attr&MSG_PRIVATE	? "Private  "   :nulstr
@@ -103,7 +106,8 @@ void sbbs_t::show_msgattr(ushort attr)
 		,attr&MSG_VALIDATED ? "Validated  " :nulstr
 		,attr&MSG_REPLIED	? "Replied  "	:nulstr
 		,attr&MSG_NOREPLY	? "NoReply  "	:nulstr
-		,attr&MSG_POLL	    ? "Poll  "		:nulstr
+		,poll == MSG_POLL	? "Poll  "		:nulstr
+		,poll == MSG_POLL && auxattr&POLL_CLOSED ? "(Closed)  "	:nulstr
 		,nulstr
 		,nulstr
 		,nulstr
@@ -135,7 +139,7 @@ void sbbs_t::show_msghdr(smbmsg_t* msg)
 
 	bprintf(text[MsgSubj],msg->subj);
 	if(msg->hdr.attr)
-		show_msgattr(msg->hdr.attr);
+		show_msgattr(msg);
 	if(msg->to && *msg->to) {
 		bprintf(text[MsgTo],msg->to);
 		if(msg->to_net.addr!=NULL)
@@ -186,7 +190,7 @@ void sbbs_t::show_msg(smbmsg_t* msg, long mode, post_t* post)
 
 	show_msghdr(msg);
 
-	if(msg->hdr.type == SMB_MSG_TYPE_POLL && post != NULL) {
+	if(msg->hdr.type == SMB_MSG_TYPE_POLL && post != NULL && smb.subnum < cfg.total_subs) {
 		char* answer;
 		int longest_answer = 0;
 		uint16_t votes = smb_voted_already(&smb, msg->hdr.number
@@ -222,7 +226,16 @@ void sbbs_t::show_msg(smbmsg_t* msg, long mode, post_t* post)
 			else if(width > cols-20)
 				width = cols-20;
 			bprintf(text[PollAnswerNumber], answers+1);
-			if(votes || sub_op(smb.subnum)) {
+			bool results_visible = false;
+			if((msg->hdr.auxattr&POLL_RESULTS_MASK) == POLL_RESULTS_OPEN)
+				results_visible = true;
+			else if(smb_msg_is_from(msg, cfg.sub[smb.subnum]->misc&SUB_NAME ? useron.name : useron.alias, NET_NONE, NULL))
+				results_visible = true;
+			else if((msg->hdr.auxattr&POLL_RESULTS_MASK) == POLL_RESULTS_CLOSED)
+				results_visible = (msg->hdr.auxattr&POLL_CLOSED) ? true : false;
+			else if((msg->hdr.auxattr&POLL_RESULTS_MASK) != POLL_RESULTS_SECRET)
+				results_visible = votes ? true : false;
+			if(results_visible) {
 				safe_snprintf(str, sizeof(str), text[PollAnswerFmt]
 					,width, width, answer, post->votes[answers], pct);
 				backfill(str, pct);
@@ -235,6 +248,8 @@ void sbbs_t::show_msg(smbmsg_t* msg, long mode, post_t* post)
 			CRLF;
 			answers++;
 		}
+		if(!votes && !(useron.misc&EXPERT) && !(msg->hdr.auxattr&POLL_CLOSED) && !(useron.rest&FLAG('V')))
+			mnemonics("\r\nTo vote in this poll, hit ~V now.\r\n");
 		return;
 	}
 	if((txt=smb_getmsgtxt(&smb,msg,(console&CON_RAW_IN) ? 0:GETMSGTXT_PLAIN)) != NULL) {
diff --git a/src/sbbs3/mailsrvr.c b/src/sbbs3/mailsrvr.c
index 3a6ca1f950d445a0664186f20189e13fd7794431..dbeeb5167882e5080e66f4a50287c17836f5515c 100644
--- a/src/sbbs3/mailsrvr.c
+++ b/src/sbbs3/mailsrvr.c
@@ -4425,7 +4425,7 @@ BOOL bounce(SOCKET sock, smb_t* smb, smbmsg_t* msg, char* err, BOOL immediate)
 	strcpy(str,"Reason:");
 	smb_hfield_str(&newmsg, SMB_COMMENT, str);
 	smb_hfield_str(&newmsg, SMB_COMMENT, err);
-	smb_hfield_str(&newmsg, SMB_COMMENT, "\r\nOriginal message text follows:\r\n");
+	smb_hfield_str(&newmsg, SMB_COMMENT, "\r\nOriginal message text follows:");
 
 	if((i=smb_addmsghdr(smb,&newmsg,SMB_SELFPACK))!=SMB_SUCCESS)
 		lprintf(LOG_ERR,"%04d !BOUNCE ERROR %d (%s) adding message header"
diff --git a/src/sbbs3/msgtoqwk.cpp b/src/sbbs3/msgtoqwk.cpp
index 4bb03e8ca3d417e6ddd14b698c9520419b500ee0..a9a336f645071e9222411316c51d38cc40474cff 100644
--- a/src/sbbs3/msgtoqwk.cpp
+++ b/src/sbbs3/msgtoqwk.cpp
@@ -58,32 +58,43 @@ ulong sbbs_t::msgtoqwk(smbmsg_t* msg, FILE *qwk_fp, long mode, uint subnum
 
 	get_msgid(&cfg, subnum, msg, msgid, sizeof(msgid));
 
-	if(msg->hdr.type == SMB_MSG_TYPE_POLL || msg->hdr.type == SMB_MSG_TYPE_BALLOT) {
+	if(msg->hdr.type != SMB_MSG_TYPE_NORMAL) {
 		if(voting == NULL)
 			return 0;
-		if(msg->hdr.type == SMB_MSG_TYPE_BALLOT) {
+		switch(msg->hdr.type) {
+		case SMB_MSG_TYPE_BALLOT:
 			fprintf(voting, "[vote:%s]\n", msgid);
-			if((p = msg->reply_id) != NULL)
-				fprintf(voting, "%s: %s\n", smb_hfieldtype(RFC822REPLYID), p);
 			if((msg->hdr.attr&MSG_VOTE) == MSG_VOTE)
 				fprintf(voting, "Votes = 0x%hx\n", msg->hdr.votes);
 			else
 				fprintf(voting, "%sVote = true\n", msg->hdr.attr&MSG_UPVOTE ? "Up" : "Down");
-		} else {
+			break;
+		case SMB_MSG_TYPE_POLL:
+		{
+			unsigned comments = 0;
+			unsigned answers = 0;
 			fprintf(voting, "[poll:%s]\n", msgid);
 			if(msg->hdr.votes)
 				fprintf(voting, "MaxVotes = %hd\n", msg->hdr.votes);
-			unsigned comments = 0;
-			unsigned answers = 0;
+			if(msg->hdr.auxattr&POLL_RESULTS_MASK)
+				fprintf(voting , "Results = %u\n", (msg->hdr.auxattr&POLL_RESULTS_MASK) >> POLL_RESULTS_SHIFT);
 			for(i=0; i < msg->total_hfields; i++) {
 				if(msg->hfield[i].type == SMB_COMMENT)
 					fprintf(voting, "%s%u = %s\n", smb_hfieldtype(msg->hfield[i].type), comments++, (char*)msg->hfield_dat[i]);
 				else if(msg->hfield[i].type == SMB_POLL_ANSWER)
 					fprintf(voting, "%s%u = %s\n", smb_hfieldtype(msg->hfield[i].type), answers++, (char*)msg->hfield_dat[i]);
 			}
+			break;
+		}
+		case SMB_MSG_TYPE_POLL_CLOSURE:
+			fprintf(voting, "[close:%s]\n", msgid);
+			break;
 		}
 		if(msg->subj && *msg->subj)
 			fprintf(voting, "%s: %s\n",smb_hfieldtype(SUBJECT), msg->subj);
+		if(msg->reply_id)
+			fprintf(voting, "%s: %s\n", smb_hfieldtype(RFC822REPLYID), msg->reply_id);
+
 		/* SENDER */
 		fprintf(voting, "%s: %s\n", smb_hfieldtype(SENDER), msg->from);
 		if(msg->from_net.type)
diff --git a/src/sbbs3/postmsg.cpp b/src/sbbs3/postmsg.cpp
index 23913932a7e655402fd977c97a2db647876f243f..37898aaebce9f6b0440d35725e986d4b878ad99c 100644
--- a/src/sbbs3/postmsg.cpp
+++ b/src/sbbs3/postmsg.cpp
@@ -565,40 +565,60 @@ extern "C" int DLLCALL votemsg(scfg_t* cfg, smb_t* smb, smbmsg_t* msg, const cha
 	/* 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 */
+			msg->hdr.thread_back = remsg.idx.number;	/* poll or message being voted on */
 	}
 	if(smb_voted_already(smb, msg->hdr.thread_back, msg->from, (enum smb_net_type)msg->from_net.type, msg->from_net.addr))
 		return SMB_DUPE_MSG;
-	result = smb_addvote(smb, msg, smb_storage_mode(cfg, smb));
-	if(result == SMB_SUCCESS && smsgfmt != NULL) {
-		remsg.hdr.number = msg->hdr.thread_back;
-		if(smb_getmsgidx(smb, &remsg) == SMB_SUCCESS
-			&& smb_getmsghdr(smb, &remsg) == SMB_SUCCESS) {
-			if(remsg.from_ext != NULL) {
-				user_t user;
-				ZERO_VAR(user);
-				user.number = atoi(remsg.from_ext);
-				if(getuserdat(cfg, &user) == 0 && 
-					(stricmp(remsg.from, user.alias) == 0 || stricmp(remsg.from, user.name) == 0)) {
-					char from[256];
-					char tstr[128];
-					char smsg[256];
-					if(msg->from_net.type)
-						safe_snprintf(from, sizeof(from), "%s (%s)", msg->from, smb_netaddr(&msg->from_net));
-					else
-						SAFECOPY(from, msg->from);
-					safe_snprintf(smsg, sizeof(smsg), smsgfmt
-						,timestr(cfg, msg->hdr.when_written.time, tstr)
-						,cfg->grp[cfg->sub[smb->subnum]->grp]->sname
-						,cfg->sub[smb->subnum]->sname
-						,from
-						,remsg.subj);
-					putsmsg(cfg, user.number, smsg);
-				}
-			}
-			smb_freemsgmem(&remsg);
+	remsg.hdr.number = msg->hdr.thread_back;
+	if((result = smb_getmsgidx(smb, &remsg)) != SMB_SUCCESS)
+		return result;
+	if((result = smb_getmsghdr(smb, &remsg)) != SMB_SUCCESS)
+		return result;
+	if(remsg.hdr.auxattr&POLL_CLOSED)
+		result = SMB_CLOSED;
+	else
+		result = smb_addvote(smb, msg, smb_storage_mode(cfg, smb));
+	if(result == SMB_SUCCESS && smsgfmt != NULL && remsg.from_ext != NULL) {
+		user_t user;
+		ZERO_VAR(user);
+		user.number = atoi(remsg.from_ext);
+		if(getuserdat(cfg, &user) == 0 && 
+			(stricmp(remsg.from, user.alias) == 0 || stricmp(remsg.from, user.name) == 0)) {
+			char from[256];
+			char tstr[128];
+			char smsg[256];
+			if(msg->from_net.type)
+				safe_snprintf(from, sizeof(from), "%s (%s)", msg->from, smb_netaddr(&msg->from_net));
+			else
+				SAFECOPY(from, msg->from);
+			safe_snprintf(smsg, sizeof(smsg), smsgfmt
+				,timestr(cfg, msg->hdr.when_written.time, tstr)
+				,cfg->grp[cfg->sub[smb->subnum]->grp]->sname
+				,cfg->sub[smb->subnum]->sname
+				,from
+				,remsg.subj);
+			putsmsg(cfg, user.number, smsg);
 		}
 	}
+	smb_freemsgmem(&remsg);
+	return result;
+}
+
+extern "C" int DLLCALL closepoll(scfg_t* cfg, smb_t* smb, uint32_t msgnum, const char* username)
+{
+	int result;
+	smbmsg_t msg;
+
+	ZERO_VAR(msg);
+
+	msg.hdr.when_imported.time = time32(NULL);
+	msg.hdr.when_imported.zone = sys_timezone(cfg);
+	msg.hdr.when_written = msg.hdr.when_imported;
+	msg.hdr.thread_back = msgnum;
+	smb_hfield_str(&msg, SENDER, username);
 
+	result = smb_addpollclosure(smb, &msg, smb_storage_mode(cfg, smb));
+
+	smb_freemsgmem(&msg);
 	return result;
-}
\ No newline at end of file
+}
diff --git a/src/sbbs3/qwk.cpp b/src/sbbs3/qwk.cpp
index 3589b83b3d9af996beab40350570296fda50202e..a9747cc0f635f1f9b3373f784cad6364761c785d 100644
--- a/src/sbbs3/qwk.cpp
+++ b/src/sbbs3/qwk.cpp
@@ -1002,12 +1002,57 @@ int sbbs_t::set_qwk_flag(ulong flag)
 	return putuserrec(&cfg,useron.number,U_QWK,8,ultoa(useron.qwk,str,16));
 }
 
+static void parse_common_header_fields(str_list_t ini, const char* section, smbmsg_t* msg, smb_net_type_t net_type, const char* qnet_id)
+{
+	char*	p;
+	char	zone[32];
+
+	if((p=iniGetString(ini, section, "WhenWritten", NULL, NULL)) != NULL) {
+		xpDateTime_t dt=isoDateTimeStr_parse(p);
+		msg->hdr.when_written.time=(uint32_t)xpDateTime_to_localtime(dt);
+		msg->hdr.when_written.zone=dt.zone;
+		sscanf(p,"%*s %s",zone);
+		if(zone[0])
+			msg->hdr.when_written.zone=(ushort)strtoul(zone,NULL,16);
+	}
+
+	if((p=iniGetString(ini, section, smb_hfieldtype(SENDER), NULL, NULL)) != NULL)
+		smb_hfield_str(msg, SENDER, p);
+
+	if(net_type == NET_QWK) {
+		char fulladdr[256];
+		const char* netaddr = iniGetString(ini, section, smb_hfieldtype(SENDERNETADDR), NULL, NULL);
+		if(netaddr == NULL)
+			netaddr = qnet_id;
+		else {
+			SAFEPRINTF2(fulladdr, "%s/%s", qnet_id, netaddr);
+			netaddr = fulladdr;
+		}
+		if(netaddr != NULL) {
+			smb_hfield_netaddr(msg, SENDERNETADDR, netaddr, &net_type);
+			smb_hfield(msg, SENDERNETTYPE, sizeof(net_type), &net_type);
+		}
+	}
+
+	if((p=iniGetString(ini, section, smb_hfieldtype(RFC822REPLYID), NULL, NULL)) != NULL)
+		smb_hfield_str(msg, RFC822REPLYID, p);
+
+	/* Trace header fields */
+	while((p=iniGetString(ini, section, smb_hfieldtype(SENDERIPADDR), NULL, NULL)) != NULL)
+		smb_hfield_str(msg, SENDERIPADDR, p);
+	while((p=iniGetString(ini, section, smb_hfieldtype(SENDERHOSTNAME), NULL, NULL)) != NULL)
+		smb_hfield_str(msg, SENDERHOSTNAME, p);
+	while((p=iniGetString(ini, section, smb_hfieldtype(SENDERPROTOCOL), NULL, NULL)) != NULL)
+		smb_hfield_str(msg, SENDERPROTOCOL, p);
+	while((p=iniGetString(ini,section, smb_hfieldtype(SENDERORG), NULL, NULL)) != NULL)
+		smb_hfield_str(msg, SENDERORG, p);
+}
+
 bool sbbs_t::qwk_voting(const char* fname, smb_net_type_t net_type, const char* qnet_id)
 {
 	FILE *fp;
 	str_list_t ini;
-	str_list_t poll_list;
-	str_list_t ballot_list;
+	str_list_t list;
 
 	if((fp=fopen(fname,"r")) == NULL) {
 		errormsg(WHERE, ERR_OPEN, fname, 0);
@@ -1016,31 +1061,32 @@ bool sbbs_t::qwk_voting(const char* fname, smb_net_type_t net_type, const char*
 	ini = iniReadFile(fp);
 	fclose(fp);
 
-	if((poll_list = iniGetSectionList(ini, "poll:")) != NULL) {
+	if((list = iniGetSectionList(ini, "poll:")) != NULL) {
 		smb_t smb;
 		unsigned u;
 
 		ZERO_VAR(smb);
 		smb.subnum = INVALID_SUB;
 
-		for(u = 0; poll_list[u] != NULL; u++) {
+		for(u = 0; list[u] != NULL; u++) {
+			uint subnum = resolve_qwkconf(iniGetInteger(ini, list[u], "Conference", 0));
+			if(subnum == INVALID_SUB)
+				continue;
+			if(cfg.sub[subnum]->misc&SUB_NOVOTING)
+				continue;
+
 			smbmsg_t msg;
 
 			ZERO_VAR(msg);
-			smb_hfield_str(&msg, RFC822MSGID, poll_list[u] + 5);
-			smb_hfield_str(&msg, SENDER, iniGetString(ini, poll_list[u], smb_hfieldtype(SENDER), NULL, NULL)); 
-			msg.hdr.votes = iniGetShortInt(ini, poll_list[u], "votes", 0);
-			if(net_type != NET_NONE) {
-				const char* netaddr = iniGetString(ini, poll_list[u], smb_hfieldtype(SENDERNETADDR), NULL, NULL);
-				if(netaddr == NULL)
-					netaddr = qnet_id;
-				smb_hfield_netaddr(&msg, SENDERNETADDR, netaddr, &net_type);
-				smb_hfield(&msg, SENDERNETTYPE, sizeof(net_type), &net_type);
-			}
+			smb_hfield_str(&msg, RFC822MSGID, list[u] + 5);
+			parse_common_header_fields(ini, list[u], &msg, net_type, qnet_id);
+			msg.hdr.votes = iniGetShortInt(ini, list[u], "votes", 0);
+			ulong results = iniGetLongInt(ini, list[u], "results", 0);
+			msg.hdr.auxattr = (results << POLL_RESULTS_SHIFT) & POLL_RESULTS_MASK;
 			for(int i=0;;i++) {
 				char str[128];
 				SAFEPRINTF2(str, "%s%u", smb_hfieldtype(SMB_COMMENT), i);
-				const char* comment = iniGetString(ini, poll_list[u], str, NULL, NULL);
+				const char* comment = iniGetString(ini, list[u], str, NULL, NULL);
 				if(comment == NULL)
 					break;
 				smb_hfield_str(&msg, SMB_COMMENT, comment);
@@ -1048,89 +1094,115 @@ bool sbbs_t::qwk_voting(const char* fname, smb_net_type_t net_type, const char*
 			for(int i=0;;i++) {
 				char str[128];
 				SAFEPRINTF2(str, "%s%u", smb_hfieldtype(SMB_POLL_ANSWER), i);
-				const char* answer = iniGetString(ini, poll_list[u], str, NULL, NULL);
+				const char* answer = iniGetString(ini, list[u], str, NULL, NULL);
 				if(answer == NULL)
 					break;
 				smb_hfield_str(&msg, SMB_POLL_ANSWER, answer);
 			}
-			uint subnum = resolve_qwkconf(iniGetInteger(ini, poll_list[u], "Conference", 0));
-			if(subnum == INVALID_SUB)
-				continue;
-			if(cfg.sub[subnum]->misc&SUB_NOVOTING)
-				continue;
 			if(subnum != smb.subnum) {
 				if(smb.subnum != INVALID_SUB) {
 					smb_close(&smb);
 					smb.subnum = INVALID_SUB;
 				}
-				if(smb_open_sub(&cfg, &smb, subnum) != SMB_SUCCESS)
-					continue;
+				smb_open_sub(&cfg, &smb, subnum);
 			}
 			int i;
 			if((i=smb_addpoll(&smb, &msg, smb_storage_mode(&cfg, &smb))) != SMB_SUCCESS)
 				errormsg(WHERE,ERR_WRITE,smb.file,i,smb.last_error);
+			smb_freemsgmem(&msg);
 		}
 		if(smb.subnum != INVALID_SUB)
 			smb_close(&smb);
-		iniFreeStringList(poll_list);
+		iniFreeStringList(list);
 	}
 
-	if((ballot_list = iniGetSectionList(ini, "vote:")) != NULL) {
+	if((list = iniGetSectionList(ini, "vote:")) != NULL) {
 		smb_t smb;
 		unsigned u;
 
 		ZERO_VAR(smb);
 		smb.subnum = INVALID_SUB;
 
-		for(u = 0; ballot_list[u] != NULL; u++) {
+		for(u = 0; list[u] != NULL; u++) {
+			uint subnum = resolve_qwkconf(iniGetInteger(ini, list[u], "Conference", 0));
+			if(subnum == INVALID_SUB)
+				continue;
+			if(cfg.sub[subnum]->misc&SUB_NOVOTING)
+				continue;
+
 			smbmsg_t msg;
 			const char* notice = NULL;
 
 			ZERO_VAR(msg);
-			smb_hfield_str(&msg, RFC822MSGID, ballot_list[u] + 5);
-			smb_hfield_str(&msg, RFC822REPLYID, iniGetString(ini, ballot_list[u], smb_hfieldtype(RFC822REPLYID), NULL, NULL));
-			smb_hfield_str(&msg, SENDER, iniGetString(ini, ballot_list[u], smb_hfieldtype(SENDER), NULL, NULL)); 
-			if(iniGetBool(ini, ballot_list[u], "upvote", FALSE)) {
+			smb_hfield_str(&msg, RFC822MSGID, list[u] + 5);
+			parse_common_header_fields(ini, list[u], &msg, net_type, qnet_id);
+			if(iniGetBool(ini, list[u], "upvote", FALSE)) {
 				msg.hdr.attr = MSG_UPVOTE;
 				notice = text[MsgUpVoteNotice];
 			}
-			else if(iniGetBool(ini, ballot_list[u], "downvote", FALSE)) {
+			else if(iniGetBool(ini, list[u], "downvote", FALSE)) {
 				msg.hdr.attr = MSG_DOWNVOTE;
 				notice = text[MsgDownVoteNotice];
 			}
 			else {
 				msg.hdr.attr = MSG_VOTE;
-				msg.hdr.votes = iniGetShortInt(ini, ballot_list[u], "votes", 0);
+				msg.hdr.votes = iniGetShortInt(ini, list[u], "votes", 0);
 				notice = text[PollVoteNotice];
 			}
-			if(net_type != NET_NONE) {
-				const char* netaddr = iniGetString(ini,ballot_list[u], smb_hfieldtype(SENDERNETADDR), NULL, NULL);
-				if(netaddr == NULL)
-					netaddr = qnet_id;
-				smb_hfield_netaddr(&msg, SENDERNETADDR, netaddr, &net_type);
-				smb_hfield(&msg, SENDERNETTYPE, sizeof(net_type), &net_type);
+			if(subnum != smb.subnum) {
+				if(smb.subnum != INVALID_SUB) {
+					smb_close(&smb);
+					smb.subnum = INVALID_SUB;
+				}
+				smb_open_sub(&cfg, &smb, subnum);
 			}
-			uint subnum = resolve_qwkconf(iniGetInteger(ini, ballot_list[u], "Conference", 0));
+			int i;
+			if((i=votemsg(&cfg, &smb, &msg, notice)) != SMB_SUCCESS)
+				errormsg(WHERE,ERR_WRITE,smb.file,i,smb.last_error);
+			smb_freemsgmem(&msg);
+		}
+		if(smb.subnum != INVALID_SUB)
+			smb_close(&smb);
+		iniFreeStringList(list);
+	}
+
+	if((list = iniGetSectionList(ini, "close:")) != NULL) {
+		smb_t smb;
+		unsigned u;
+
+		ZERO_VAR(smb);
+		smb.subnum = INVALID_SUB;
+
+		for(u = 0; list[u] != NULL; u++) {
+			uint subnum = resolve_qwkconf(iniGetInteger(ini, list[u], "Conference", 0));
 			if(subnum == INVALID_SUB)
 				continue;
 			if(cfg.sub[subnum]->misc&SUB_NOVOTING)
 				continue;
+
+			smbmsg_t msg;
+			const char* notice = NULL;
+
+			ZERO_VAR(msg);
+			smb_hfield_str(&msg, RFC822MSGID, list[u] + 6);
+			parse_common_header_fields(ini, list[u], &msg, net_type, qnet_id);
 			if(subnum != smb.subnum) {
 				if(smb.subnum != INVALID_SUB) {
 					smb_close(&smb);
 					smb.subnum = INVALID_SUB;
 				}
-				if(smb_open_sub(&cfg, &smb, subnum) != SMB_SUCCESS)
-					continue;
+				smb_open_sub(&cfg, &smb, subnum);
 			}
 			int i;
-			if((i=votemsg(&cfg, &smb, &msg, notice)) != SMB_SUCCESS)
+			if((i=smb_addpollclosure(&smb, &msg, smb_storage_mode(&cfg, &smb))) != SMB_SUCCESS)
 				errormsg(WHERE,ERR_WRITE,smb.file,i,smb.last_error);
+			smb_freemsgmem(&msg);
 		}
 		if(smb.subnum != INVALID_SUB)
 			smb_close(&smb);
-		iniFreeStringList(ballot_list);
+		iniFreeStringList(list);
 	}
+
 	iniFreeStringList(ini);
 	return true;
 }
diff --git a/src/sbbs3/qwktomsg.cpp b/src/sbbs3/qwktomsg.cpp
index f581b5a718cbf2ff168a6147b899342cf68ca48c..123820d272e8fe9ebed5ddbb1683cbd35fdc5e53 100644
--- a/src/sbbs3/qwktomsg.cpp
+++ b/src/sbbs3/qwktomsg.cpp
@@ -110,9 +110,9 @@ static void qwk_parse_header_list(ulong confnum, smbmsg_t* msg, str_list_t* head
 		if(parse_sender_hfields)
 			smb_hfield_str(msg,hfield_type,p);
 	}
-	while((p=iniPopKey(headers,ROOT_SECTION,"Organization",value))!=NULL) {
+	while((p=iniPopKey(headers,ROOT_SECTION,smb_hfieldtype(hfield_type=SENDERORG),value))!=NULL) {
 		if(parse_sender_hfields)
-			smb_hfield_str(msg,SENDERORG,p);
+			smb_hfield_str(msg,hfield_type,p);
 	}
 
 	/* FidoNet header fields */
diff --git a/src/sbbs3/readmail.cpp b/src/sbbs3/readmail.cpp
index 20f9fbe6ff948bb6dd7da25d73b6c478b66467cf..59496c8e18f6c9c584e69f1ccddd8bf6fa99856a 100644
--- a/src/sbbs3/readmail.cpp
+++ b/src/sbbs3/readmail.cpp
@@ -575,7 +575,7 @@ void sbbs_t::readmail(uint usernumber, int which)
 				done=1;
 				break;
 			case 'C':   /* Change attributes of last piece */
-				i=chmsgattr(msg.hdr.attr);
+				i=chmsgattr(msg);
 				if(msg.hdr.attr==i)
 					break;
 				if(msg.total_hfields)
diff --git a/src/sbbs3/readmsgs.cpp b/src/sbbs3/readmsgs.cpp
index 037d1e536bdacaa4f4159f5552a01bd05565b2de..458384d8ad1fabda252fb26c302b26aa80e6d72a 100644
--- a/src/sbbs3/readmsgs.cpp
+++ b/src/sbbs3/readmsgs.cpp
@@ -40,7 +40,7 @@ int sbbs_t::sub_op(uint subnum)
 	return(is_user_subop(&cfg, subnum, &useron, &client));
 }
 
-char sbbs_t::msg_listing_flag(uint subnum, smbmsg_t* msg, post_t* post)
+uchar sbbs_t::msg_listing_flag(uint subnum, smbmsg_t* msg, post_t* post)
 {
 	if(msg->hdr.attr&MSG_DELETE)						return '-';
 	if((stricmp(msg->to,useron.alias)==0 || stricmp(msg->to,useron.name)==0)
@@ -52,7 +52,7 @@ char sbbs_t::msg_listing_flag(uint subnum, smbmsg_t* msg, post_t* post)
 	if(msg->hdr.number > subscan[subnum].ptr)			return '*';
 	if(msg->hdr.attr&MSG_PRIVATE)						return 'P';
 	if(msg->hdr.attr&MSG_POLL)							return '?'; 
-	if(post->upvotes > post->downvotes)					return 'V';
+	if(post->upvotes > post->downvotes)					return 251;
 	if(post->upvotes || post->downvotes)				return 'v';
 	if(msg->hdr.attr&MSG_REPLIED)						return 'R';
 	if(sub_op(subnum) && msg->hdr.attr&MSG_ANONYMOUS)	return 'A';
@@ -266,8 +266,12 @@ post_t * sbbs_t::loadposts(uint32_t *posts, uint subnum, ulong ptr, long mode, u
 			if(mode&LP_REP || !sub_op(subnum))
 				break;
 		}
-
-		if(idx.attr&MSG_VOTE) {
+		
+		switch(idx.attr&MSG_POLL_VOTE_MASK) {
+		case MSG_VOTE:
+		case MSG_UPVOTE:
+		case MSG_DOWNVOTE:
+		{
 			ulong u;
 			for(u = 0; u < l; u++)
 				if(post[u].idx.number == idx.remsg)
@@ -289,10 +293,16 @@ post_t * sbbs_t::loadposts(uint32_t *posts, uint subnum, ulong ptr, long mode, u
 			}
 			if(!(mode&LP_VOTES))
 				continue;
+			break;
 		}
-		if(idx.attr&MSG_POLL) {
+		case MSG_POLL:
 			if(!(mode&LP_POLLS))
 				continue;
+			break;
+		case MSG_POLL_CLOSURE:
+			if(!(mode&LP_VOTES))
+				continue;
+			break;
 		}
 
 		if(idx.attr&MSG_PRIVATE && !(mode&LP_PRIVATE)
@@ -824,6 +834,18 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 				else done=1;
 				break;
 			case 'D':       /* Delete message on sub-board */
+				if(!(msg.hdr.attr&MSG_DELETE) && msg.hdr.type == SMB_MSG_TYPE_POLL 
+					&& smb_msg_is_from(&msg, cfg.sub[subnum]->misc&SUB_NAME ? useron.name : useron.alias, NET_NONE, NULL)
+					&& !(msg.hdr.auxattr&POLL_CLOSED)) {
+					if(noyes("Close Poll")) {
+						domsg=false;
+						break;
+					}
+					i=closepoll(&cfg, &smb, msg.hdr.number, cfg.sub[subnum]->misc&SUB_NAME ? useron.name : useron.alias);
+					if(i != SMB_SUCCESS)
+						errormsg(WHERE,ERR_WRITE,smb.file,i,smb.last_error);
+					break;
+				}
 				domsg=0;
 				if(!sub_op(subnum)) {
 					if(!(cfg.sub[subnum]->misc&SUB_DEL)) {
@@ -1055,8 +1077,15 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 					domsg = false;
 					break;
 				}
+
+				if(msg.hdr.auxattr&POLL_CLOSED) {
+					bputs(text[CantReplyToMsg]);
+					domsg = false;
+					break; 
+				}
+
 				ZERO_VAR(vote);
-				if(msg.hdr.attr&MSG_POLL) {
+				if(msg.hdr.type == SMB_MSG_TYPE_POLL) {
 					unsigned answers=0;
 					for(i=0; i<msg.total_hfields; i++) {
 						if(msg.hfield[i].type != SMB_POLL_ANSWER)
@@ -1131,7 +1160,7 @@ int sbbs_t::scanposts(uint subnum, long mode, const char *find)
 								: matchuser(&cfg,msg.from,FALSE));
 							break;
 						case 'C':   /* Change message attributes */
-							i=chmsgattr(msg.hdr.attr);
+							i=chmsgattr(msg);
 							if(msg.hdr.attr==i)
 								break;
 							if(msg.total_hfields)
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index 64abe465e6818ae645216d81aecefbefadd11359..73c34795197007710bd2fe7f3ee1b2b20e23c3f7 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -621,7 +621,7 @@ public:
 	void	removeline(char *str, char *str2, char num, char skip);
 	ulong	msgeditor(char *buf, const char *top, char *title);
 	bool	editfile(char *path, bool msg=false);
-	ushort	chmsgattr(ushort attr);
+	ushort	chmsgattr(smbmsg_t);
 	void	quotemsg(smbmsg_t* msg, int tails);
 	void	editmsg(smbmsg_t* msg, uint subnum);
 	void	editor_inf(int xeditnum, const char *to, const char* from, const char *subj, long mode
@@ -641,7 +641,7 @@ public:
 
 	/* getmsg.cpp */
 	int		loadmsg(smbmsg_t *msg, ulong number);
-	void	show_msgattr(ushort attr);
+	void	show_msgattr(smbmsg_t*);
 	void	show_msghdr(smbmsg_t* msg);
 	void	show_msg(smbmsg_t* msg, long mode, post_t* post = NULL);
 	void	msgtotxt(smbmsg_t* msg, char *str, bool header, ulong mode);
@@ -757,7 +757,7 @@ public:
 	long	searchposts(uint subnum, post_t* post, long start, long msgs, const char* find);
 	long	showposts_toyou(uint subnum, post_t* post, ulong start, long posts, long mode=0);
 	void	msghdr(smbmsg_t* msg);
-	char	msg_listing_flag(uint subnum, smbmsg_t*, post_t*);
+	uchar	msg_listing_flag(uint subnum, smbmsg_t*, post_t*);
 
 	/* chat.cpp */
 	void	chatsection(void);
@@ -1007,6 +1007,7 @@ extern "C" {
 	/* postmsg.cpp */
 	DLLEXPORT int		DLLCALL savemsg(scfg_t*, smb_t*, smbmsg_t*, client_t*, const char* server, char* msgbuf);
 	DLLEXPORT int		DLLCALL votemsg(scfg_t*, smb_t*, smbmsg_t*, const char* msgfmt);
+	DLLEXPORT int		DLLCALL closepoll(scfg_t*, smb_t*, uint32_t msgnum, const char* username);
 	DLLEXPORT void		DLLCALL signal_sub_sem(scfg_t*, uint subnum);
 	DLLEXPORT int		DLLCALL msg_client_hfields(smbmsg_t*, client_t*);
 	DLLEXPORT char*		DLLCALL msg_program_id(char* pid);
diff --git a/src/sbbs3/sbbsecho.c b/src/sbbs3/sbbsecho.c
index ad97995718086cf9ccd7d19f3dc9c9e4aa005e1f..37066cce8ed1f3525cd42725a9c7b29c06f08ad0 100644
--- a/src/sbbs3/sbbsecho.c
+++ b/src/sbbs3/sbbsecho.c
@@ -2228,7 +2228,7 @@ ulong loadmsgs(post_t** post, ulong ptr)
 		if(idx.number==0)	/* invalid message number, ignore */
 			continue;
 
-		if(idx.attr&(MSG_VOTE|MSG_POLL))
+		if(idx.attr&MSG_POLL_VOTE_MASK)
 			continue;
 
 		if(idx.number<=ptr || (idx.attr&MSG_DELETE))
diff --git a/src/sbbs3/writemsg.cpp b/src/sbbs3/writemsg.cpp
index 948284c7f222294a5513ad8dbebf85beed458f7a..b8b8d466dce644e435466e6ba3db80bd9ab5daef 100644
--- a/src/sbbs3/writemsg.cpp
+++ b/src/sbbs3/writemsg.cpp
@@ -1,5 +1,3 @@
-/* writemsg.cpp */
-
 /* Synchronet message creation routines */
 
 /* $Id$ */
@@ -1537,13 +1535,14 @@ bool sbbs_t::movemsg(smbmsg_t* msg, uint subnum)
 	return(true);
 }
 
-ushort sbbs_t::chmsgattr(ushort attr)
+ushort sbbs_t::chmsgattr(smbmsg_t msg)
 {
 	int ch;
+	uint16_t attr = msg.hdr.attr;
 
 	while(online && !(sys_status&SS_ABORT)) {
 		CRLF;
-		show_msgattr(attr);
+		show_msgattr(&msg);
 		menu("msgattr");
 		ch=getkey(K_UPPER);
 		if(ch)
@@ -1576,6 +1575,9 @@ ushort sbbs_t::chmsgattr(ushort attr)
 			case 'L':
 				attr^=MSG_LOCKED;
 				break;
+			case 'C':
+				attr^=MSG_NOREPLY;
+				break;
 			default:
 				return(attr); 
 		}