readmsgs.cpp 48.6 KB
Newer Older
1 2 3 4 5 6
/* Synchronet public message reading function */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
7
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *																			*
 * 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 "sbbs.h"

int sbbs_t::sub_op(uint subnum)
{
26
	return(is_user_subop(&cfg, subnum, &useron, &client));
27 28
}

rswindell's avatar
rswindell committed
29
uchar sbbs_t::msg_listing_flag(uint subnum, smbmsg_t* msg, post_t* post)
30 31 32 33 34 35 36
{
	if(msg->hdr.attr&MSG_DELETE)						return '-';
	if((stricmp(msg->to,useron.alias)==0 || stricmp(msg->to,useron.name)==0)
		&& !(msg->hdr.attr&MSG_READ))					return '!';
	if(msg->hdr.attr&MSG_PERMANENT)						return 'p';
	if(msg->hdr.attr&MSG_LOCKED)						return 'L';
	if(msg->hdr.attr&MSG_KILLREAD)						return 'K';
37
	if(msg->hdr.attr&MSG_NOREPLY)						return '#';
38
	if(msg->hdr.number > subscan[subnum].ptr)			return '*';
rswindell's avatar
rswindell committed
39 40
	if(msg->hdr.attr&MSG_PRIVATE)						return 'P';
	if(msg->hdr.attr&MSG_POLL)							return '?'; 
rswindell's avatar
rswindell committed
41
	if(post->upvotes > post->downvotes)					return 251;
42 43 44
	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';
45 46
	return ' ';
}
47

48
long sbbs_t::listmsgs(uint subnum, long mode, post_t *post, long start, long posts, bool reading)
49 50
{
	smbmsg_t msg;
51
	long listed=0;
52

53
	for(long i = start; i<posts && !msgabort(); i++) {
54 55
		if(mode&SCAN_NEW && post[i].idx.number<=subscan[subnum].ptr)
			continue;
56
		msg.idx.offset=post[i].idx.offset;
57
		if(loadmsg(&msg,post[i].idx.number) < 0)
58 59
			break;
		smb_unlockmsghdr(&smb,&msg);
60 61
		if(listed==0)
			bputs(text[MailOnSystemLstHdr]);
rswindell's avatar
rswindell committed
62 63
		bprintf(P_TRUNCATE|(msg.hdr.auxattr&MSG_HFIELDS_UTF8)
			,msghdr_text(&msg, SubMsgLstFmt), reading ? (i+1) : post[i].num
64 65 66
			,msg.hdr.attr&MSG_ANONYMOUS && !sub_op(subnum)
			? text[Anonymous] : msg.from
			,msg.to
67
			,msg_listing_flag(subnum, &msg, &post[i])
68 69 70
			,msg.subj);
		smb_freemsgmem(&msg);
		msg.total_hfields=0;
71
		listed++;
72
	}
73 74

	return(listed);
75 76
}

77
void sbbs_t::dump_msghdr(smbmsg_t* msg)
78
{
79 80 81 82 83
	newline();
	str_list_t list = smb_msghdr_str_list(msg);
	if(list != NULL) {
		for(int i = 0; list[i] != NULL && !msgabort(); i++) {
			bprintf("%s\r\n", list[i]);
84
		}
85
		strListFree(&list);
86
	}
87 88 89
}

/****************************************************************************/
90 91 92
/* posts is the actual number of posts returned in the allocated array		*/
/* visible is the number of visible posts to the user (not all included in	*/
/* returned array)															*/
93
/****************************************************************************/
94
post_t * sbbs_t::loadposts(uint32_t *posts, uint subnum, ulong ptr, long mode, ulong *unvalidated_num, uint32_t* visible)
95 96 97 98
{
	ushort aliascrc,namecrc,sysop;
	int i,skip;
	ulong l=0,total,alloc_len;
99 100 101 102
	uint32_t	curmsg=0;
	smbmsg_t	msg;
	idxrec_t	idx;
	post_t *	post;
103 104 105 106 107 108 109

	if(posts==NULL)
		return(NULL);

	(*posts)=0;

	if((i=smb_locksmbhdr(&smb))!=0) {				/* Be sure noone deletes or */
110
		errormsg(WHERE,ERR_LOCK,smb.file,i,smb.last_error);		/* adds while we're reading */
111 112
		return(NULL); 
	}
113

114
	total=(long)filelength(fileno(smb.sid_fp))/sizeof(idxrec_t); /* total msgs in sub */
115 116 117

	if(!total) {			/* empty */
		smb_unlocksmbhdr(&smb);
118 119
		return(NULL); 
	}
120

121 122
	namecrc=smb_name_crc(useron.name);
	aliascrc=smb_name_crc(useron.alias);
123
	sysop=crc16("sysop",0);
124 125 126 127

	rewind(smb.sid_fp);

	alloc_len=sizeof(post_t)*total;
deuce's avatar
deuce committed
128
	if((post=(post_t *)malloc(alloc_len))==NULL) {	/* alloc for max */
129
		smb_unlocksmbhdr(&smb);
130
		errormsg(WHERE,ERR_ALLOC,smb.file,alloc_len);
131 132
		return(NULL); 
	}
133
	memset(post, 0, alloc_len);
134 135 136 137

	if(unvalidated_num)
		*unvalidated_num=ULONG_MAX;

138 139
	while(!feof(smb.sid_fp)) {
		skip=0;
140
		if(smb_fread(&smb, &idx,sizeof(idx),smb.sid_fp) != sizeof(idx))
141 142
			break;

143 144 145
		if(idx.number==0)	/* invalid message number, ignore */
			continue;

146 147 148
		if(mode&LP_NOMSGS && (idx.attr&MSG_POLL_VOTE_MASK) == 0)
			continue;

149 150 151 152 153 154 155 156 157 158
		if(idx.attr&MSG_DELETE) {		/* Pre-flagged */
			if(mode&LP_REP) 			/* Don't include deleted msgs in REP pkt */
				continue;
			if(!(cfg.sys_misc&SM_SYSVDELM)) /* Noone can view deleted msgs */
				continue;
			if(!(cfg.sys_misc&SM_USRVDELM)	/* Users can't view deleted msgs */
				&& !sub_op(subnum)) 	/* not sub-op */
				continue;
			if(!sub_op(subnum)			/* not sub-op */
				&& idx.from!=namecrc && idx.from!=aliascrc) /* not for you */
159 160
				continue; 
		}
161

162 163 164 165
		if(idx.attr&MSG_MODERATED && !(idx.attr&MSG_VALIDATED)) {
			if(mode&LP_REP || !sub_op(subnum))
				break;
		}
rswindell's avatar
rswindell committed
166 167 168 169 170 171
		
		switch(idx.attr&MSG_POLL_VOTE_MASK) {
		case MSG_VOTE:
		case MSG_UPVOTE:
		case MSG_DOWNVOTE:
		{
172 173
			ulong u;
			for(u = 0; u < l; u++)
174
				if(post[u].idx.number == idx.remsg)
175 176
					break;
			if(u < l) {
177
				post[u].total_votes++;
178 179
				switch(idx.attr&MSG_VOTE) {
				case MSG_UPVOTE:
180
					post[u].upvotes++;
181 182
					break;
				case MSG_DOWNVOTE:
183
					post[u].downvotes++;
184
					break;
rswindell's avatar
rswindell committed
185
				default:
186
					for(int b=0; b < MSG_POLL_MAX_ANSWERS; b++) {
rswindell's avatar
rswindell committed
187
						if(idx.votes&(1<<b))
rswindell's avatar
rswindell committed
188 189
							post[u].votes[b]++;
					}
190
				}
191
			}
192 193
			if(!(mode&LP_VOTES))
				continue;
rswindell's avatar
rswindell committed
194
			break;
195
		}
rswindell's avatar
rswindell committed
196
		case MSG_POLL:
197 198
			if(!(mode&LP_POLLS))
				continue;
rswindell's avatar
rswindell committed
199 200 201 202 203
			break;
		case MSG_POLL_CLOSURE:
			if(!(mode&LP_VOTES))
				continue;
			break;
204 205
		}

206 207 208 209 210 211
		if(idx.attr&MSG_PRIVATE && !(mode&LP_PRIVATE)
			&& !sub_op(subnum) && !(useron.rest&FLAG('Q'))) {
			if(idx.to!=namecrc && idx.from!=namecrc
				&& idx.to!=aliascrc && idx.from!=aliascrc
				&& (useron.number!=1 || idx.to!=sysop))
				continue;
212
			msg.idx=idx;
213 214 215 216 217 218 219 220 221
			if(!smb_lockmsghdr(&smb,&msg)) {
				if(!smb_getmsghdr(&smb,&msg)) {
					if(stricmp(msg.to,useron.alias)
						&& stricmp(msg.from,useron.alias)
						&& stricmp(msg.to,useron.name)
						&& stricmp(msg.from,useron.name)
						&& (useron.number!=1 || stricmp(msg.to,"sysop")
						|| msg.from_net.type))
						skip=1;
222 223 224 225
					smb_freemsgmem(&msg); 
				}
				smb_unlockmsghdr(&smb,&msg); 
			}
226
			if(skip)
227 228
				continue; 
		}
229

230 231 232 233 234 235 236
		curmsg++;

		if(idx.number<=ptr)
			continue;

		if(idx.attr&MSG_READ && mode&LP_UNREAD) /* Skip read messages */
			continue;
237 238 239 240 241 242 243 244

		if(!(mode&LP_BYSELF) && (idx.from==namecrc || idx.from==aliascrc)) {
			msg.idx=idx;
			if(!smb_lockmsghdr(&smb,&msg)) {
				if(!smb_getmsghdr(&smb,&msg)) {
					if(!stricmp(msg.from,useron.alias)
						|| !stricmp(msg.from,useron.name))
						skip=1;
245 246 247 248
					smb_freemsgmem(&msg); 
				}
				smb_unlockmsghdr(&smb,&msg); 
			}
249
			if(skip)
250 251
				continue; 
		}
252 253 254 255 256 257 258 259 260 261 262 263

		if(!(mode&LP_OTHERS)) {
			if(idx.to!=namecrc && idx.to!=aliascrc
				&& (useron.number!=1 || idx.to!=sysop))
				continue;
			msg.idx=idx;
			if(!smb_lockmsghdr(&smb,&msg)) {
				if(!smb_getmsghdr(&smb,&msg)) {
					if(stricmp(msg.to,useron.alias) && stricmp(msg.to,useron.name)
						&& (useron.number!=1 || stricmp(msg.to,"sysop")
						|| msg.from_net.type))
						skip=1;
264 265 266 267
					smb_freemsgmem(&msg); 
				}
				smb_unlockmsghdr(&smb,&msg); 
			}
268
			if(skip)
269 270
				continue; 
		}
271

272 273 274 275 276
		if(idx.attr&MSG_MODERATED && !(idx.attr&MSG_VALIDATED)) {
			if(unvalidated_num && *unvalidated_num > l)
				*unvalidated_num=l;
		}

277 278
		memcpy(&post[l].idx,&idx,sizeof(idx));
		post[l].num = curmsg;
279
		l++;
280
	}
281
	smb_unlocksmbhdr(&smb);
282 283 284
	if(!l)
		FREE_AND_NULL(post);

285 286
	if(visible!=NULL)	/* Total number of currently visible/readable messages to the user */
		*visible=curmsg;
287 288 289 290
	(*posts)=l;
	return(post);
}

291
int64_t sbbs_t::get_start_msgnum(smb_t* smb, int next)
292
{
293
	uint32_t	j=smb->curmsg + next;
294
	int64_t		i;
295 296 297 298 299

	if(j<smb->msgs)
		j++;
	else
		j=1;
300 301
	bprintf(text[StartWithN],j);
	if((i = getnum(smb->msgs)) < 0)
302 303 304 305 306 307
		return(i);
	if(i==0)
		return(j-1);
	return(i-1);
}

308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
static int score_post(post_t* post)
{
	if((post->idx.attr&MSG_POLL_VOTE_MASK) == MSG_POLL)
		return 0;
	return (post->upvotes*2) + ((post->idx.attr&MSG_REPLIED) ? 1:0) - (post->downvotes*2);
}

static int rank_post(const void* a1, const void* a2)
{
	post_t* p1 = (post_t*)a1;
	post_t* p2 = (post_t*)a2;
	int diff = score_post(p2) - score_post(p1);
	if(diff == 0)
		return p2->idx.time - p1->idx.time;
	return diff;
}

325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
static int find_post(smb_t* smb, uint32_t msgnum, post_t* post)
{
	uint32_t i;

	/* ToDo: optimize search */
	for(i=0; i<smb->msgs; i++)
		if(post[i].idx.number == msgnum)
			return i;
	return -1;
}

void sbbs_t::show_thread(uint32_t msgnum, post_t* post, unsigned curmsg, int thread_depth, uint64_t reply_mask)
{
	char date[32];
	smbmsg_t msg;

	int i = find_post(&smb, msgnum, post);
	if(i < 0)
		return;

	memset(&msg, 0, sizeof(msg));
	msg.idx = post[i].idx;
	if(smb_getmsghdr(&smb, &msg) != SMB_SUCCESS)
		return;
	attr(LIGHTGRAY);
	if(thread_depth) {
351 352 353
		for(int j=0; j < thread_depth; j++)
			bprintf("%*s%c", j > 10 ? 0 : j > 5 ? 1 : 2, ""
			,j+1 == thread_depth ? msg.hdr.thread_next ? 195 : 192 : (reply_mask&(1LL<<j)) ? 179 : ' ');
354
	}
rswindell's avatar
rswindell committed
355
	if((unsigned)i == curmsg)
356 357
		attr(HIGH);
	bprintf("\1c%u\1g%c "
358 359
		,post[i].num
//		,msg.hdr.number
360 361
		,(unsigned)i == curmsg ? '>' : ':');
	bprintf("\1w%-*.*s\1g%c\1g%c \1w%s\r\n"
362 363
		,(int)(cols-column-12)
		,(int)(cols-column-12)
364
		,msg.hdr.attr&MSG_ANONYMOUS && !sub_op(smb.subnum)
rswindell's avatar
rswindell committed
365
			? text[Anonymous] : msghdr_field(&msg, msg.from)
366
		,(unsigned)i == curmsg ? '<' : ' '
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
		,msg_listing_flag(smb.subnum, &msg, &post[i])
		,unixtodstr(&cfg, msg.hdr.when_written.time, date));

	if(thread_depth) {
		if(msg.hdr.thread_first)
			reply_mask |= (1LL<<(thread_depth-1));
		else
			reply_mask &= ~(1LL<<(thread_depth-1));
	}
	if(thread_depth && !msg.hdr.thread_next)
		reply_mask &= ~(1LL<<(thread_depth-1));
	if(msg.hdr.thread_first)
		show_thread(msg.hdr.thread_first, post, curmsg, thread_depth+1, reply_mask);
	if(msg.hdr.thread_next)
		show_thread(msg.hdr.thread_next, post, curmsg, thread_depth, reply_mask);
	smb_freemsgmem(&msg);
}

385 386 387 388 389 390
/****************************************************************************/
/* Reads posts on subboard sub. 'mode' determines new-posts only, browse,   */
/* or continuous read.                                                      */
/* Returns 0 if normal completion, 1 if aborted.                            */
/* Called from function main_sec                                            */
/****************************************************************************/
391
int sbbs_t::scanposts(uint subnum, long mode, const char *find)
392
{
393
	char	str[256],str2[256],do_find=true,mismatches=0
394
			,done=0,domsg=1,*buf;
395
	char	find_buf[128];
396
	char	tmp[128];
397
	int		i;
398
	int64_t	i64;
399
	int		quit=0;
400
	uint 	usub,ugrp,reads=0;
401
	uint	lp = LP_BYSELF;
402
	long	org_mode=mode;
403
	ulong	msgs,l,unvalidated;
deuce's avatar
deuce committed
404
	uint32_t last;
405
	uint32_t u;
deuce's avatar
deuce committed
406
	post_t	*post;
407
	smbmsg_t	msg;
408
	bool	thread_mode = false;
409

410
	action=NODE_RMSG;
411
	cursubnum=subnum;	/* for ARS */
412 413 414 415
	if(cfg.scanposts_mod[0] && !scanposts_inside) {
		char cmdline[256];

		scanposts_inside = true;
416
		safe_snprintf(cmdline, sizeof(cmdline), "%s %s %ld %s", cfg.scanposts_mod, cfg.sub[subnum]->code, mode, find);
417 418 419 420
		i=exec_bin(cmdline, &main_csi);
		scanposts_inside = false;
		return i;
	}
421
	find_buf[0]=0;
rswindell's avatar
rswindell committed
422
	if(!chk_ar(cfg.sub[subnum]->read_ar,&useron,&client)) {
423
		bprintf(text[CantReadSub]
424
				,cfg.grp[cfg.sub[subnum]->grp]->sname,cfg.sub[subnum]->sname);
425 426
		return(0); 
	}
427
	ZERO_VAR(msg);				/* init to NULL, specify not-allocated */
428 429 430 431 432 433
	if(!(mode&SCAN_CONST))
		lncntr=0;
	if((msgs=getlastmsg(subnum,&last,0))==0) {
		if(mode&(SCAN_NEW|SCAN_TOYOU))
			bprintf(text[NScanStatusFmt]
				,cfg.grp[cfg.sub[subnum]->grp]->sname,cfg.sub[subnum]->lname,0L,0L);
434
		else if(!(mode&SCAN_POLLS))
435 436
			bprintf(text[NoMsgsOnSub]
				,cfg.grp[cfg.sub[subnum]->grp]->sname,cfg.sub[subnum]->sname);
437 438
		return(0); 
	}
439 440
	if(mode&SCAN_NEW && subscan[subnum].ptr>=last && !(mode&SCAN_BACK)) {
		if(subscan[subnum].ptr>last)
441 442 443
			subscan[subnum].ptr=last;
		if(subscan[subnum].last>last)
			subscan[subnum].last=last;
444 445
		bprintf(text[NScanStatusFmt]
			,cfg.grp[cfg.sub[subnum]->grp]->sname,cfg.sub[subnum]->lname,0L,msgs);
446 447
		return(0); 
	}
448 449 450

	if((i=smb_stack(&smb,SMB_STACK_PUSH))!=0) {
		errormsg(WHERE,ERR_OPEN,cfg.sub[subnum]->code,i);
451 452
		return(0); 
	}
453
	SAFEPRINTF2(smb.file,"%s%s",cfg.sub[subnum]->data_dir,cfg.sub[subnum]->code);
454
	smb.retry_time=cfg.smb_retry_time;
455
	smb.subnum=subnum;
456 457
	if((i=smb_open(&smb))!=0) {
		errormsg(WHERE,ERR_OPEN,smb.file,i,smb.last_error);
458
		smb_stack(&smb,SMB_STACK_POP);
459 460
		return(0); 
	}
461 462
	usub = getusrsub(subnum);
	ugrp = getusrgrp(subnum);
463 464

	if(!(mode&SCAN_TOYOU)
465
		&& (!mode || mode&SCAN_FIND || !(subscan[subnum].cfg&SUB_CFG_YSCAN)))
466
		lp|=LP_OTHERS;
467
	if(mode&SCAN_TOYOU && mode&SCAN_UNREAD)
468
		lp|=LP_UNREAD;
rswindell's avatar
rswindell committed
469 470
	if(!(cfg.sub[subnum]->misc&SUB_NOVOTING))
		lp|=LP_POLLS;
471 472
	if(mode&SCAN_POLLS)
		lp|=LP_NOMSGS;
473
	post=loadposts(&smb.msgs,subnum,0,lp,&unvalidated);
474
	if(mode&SCAN_NEW) { 		  /* Scanning for new messages */
475
		for(smb.curmsg=0;smb.curmsg<smb.msgs;smb.curmsg++)
476
			if(subscan[subnum].ptr<post[smb.curmsg].idx.number)
477
				break;
478
		lncntr = 0;
479
		bprintf(text[NScanStatusFmt]
480 481
			,cfg.grp[cfg.sub[subnum]->grp]->sname,cfg.sub[subnum]->lname,smb.msgs-smb.curmsg,msgs);
		if(!smb.msgs) {		  /* no messages at all */
482 483
			smb_close(&smb);
			smb_stack(&smb,SMB_STACK_POP);
484 485
			return(0); 
		}
486
		if(smb.curmsg==smb.msgs) {  /* no new messages */
487 488
			if(!(mode&SCAN_BACK)) {
				if(post)
deuce's avatar
deuce committed
489
					free(post);
490 491
				smb_close(&smb);
				smb_stack(&smb,SMB_STACK_POP);
492 493 494 495 496
				return(0); 
			}
			smb.curmsg=smb.msgs-1; 
		} 
	}
497
	else {
498
		cleartoeol();
499
		lncntr = 0;
500 501
		if(mode&SCAN_TOYOU)
			bprintf(text[NScanStatusFmt]
502 503
			   ,cfg.grp[cfg.sub[subnum]->grp]->sname,cfg.sub[subnum]->lname,smb.msgs,msgs);
		if(!smb.msgs) {
rswindell's avatar
rswindell committed
504
			if(!(mode&(SCAN_TOYOU|SCAN_POLLS)))
505 506 507 508
				bprintf(text[NoMsgsOnSub]
					,cfg.grp[cfg.sub[subnum]->grp]->sname,cfg.sub[subnum]->sname);
			smb_close(&smb);
			smb_stack(&smb,SMB_STACK_POP);
509 510
			return(0); 
		}
511 512
		if(mode&SCAN_FIND) {
			bprintf(text[SearchSubFmt]
513
				,cfg.grp[cfg.sub[subnum]->grp]->sname,cfg.sub[subnum]->lname,smb.msgs);
514
			domsg=1;
515 516
			smb.curmsg=0; 
		}
517
		else if(mode&(SCAN_TOYOU|SCAN_POLLS))
518
			smb.curmsg=0;
519
		else {
520
			for(smb.curmsg=0;smb.curmsg<smb.msgs;smb.curmsg++)
521
				if(post[smb.curmsg].idx.number>=subscan[subnum].last)
522
					break;
523 524
			if(smb.curmsg==smb.msgs)
				smb.curmsg=smb.msgs-1;
525

526 527 528
			domsg=1; 
		} 
	}
529 530

	if(useron.misc&RIP)
531
		menu("msgscan");
532 533 534

	if((i=smb_locksmbhdr(&smb))!=0) {
		smb_close(&smb);
535
		errormsg(WHERE,ERR_LOCK,smb.file,i,smb.last_error);
536
		smb_stack(&smb,SMB_STACK_POP);
537 538
		return(0); 
	}
539 540
	if((i=smb_getstatus(&smb))!=0) {
		smb_close(&smb);
541
		errormsg(WHERE,ERR_READ,smb.file,i,smb.last_error);
542
		smb_stack(&smb,SMB_STACK_POP);
543 544
		return(0); 
	}
545 546 547 548 549 550
	smb_unlocksmbhdr(&smb);
	last=smb.status.last_msg;

	if(mode&SCAN_CONST) {   /* update action */
		getnodedat(cfg.node_num,&thisnode,1);
		thisnode.action=NODE_RMSG;
551 552
		putnodedat(cfg.node_num,&thisnode); 
	}
553
	current_msg=&msg;	/* For MSG_* @-codes and bbs.msg_* property values */
554 555 556 557 558 559 560 561
	while(online && !done) {

		action=NODE_RMSG;

		if(mode&(SCAN_CONST|SCAN_FIND) && sys_status&SS_ABORT)
			break;

		if(post==NULL)	/* Been unloaded */
562
			post=loadposts(&smb.msgs,subnum,0,lp,&unvalidated);   /* So re-load */
563

564
		if(!smb.msgs) {
565
			done=1;
566 567
			continue; 
		}
568

569
		while(smb.curmsg>=smb.msgs) smb.curmsg--;
570

571
		msg.idx=post[smb.curmsg].idx;
572 573

		if((i=smb_locksmbhdr(&smb))!=0) {
574
			errormsg(WHERE,ERR_LOCK,smb.file,i,smb.last_error);
575 576
			break; 
		}
577 578
		if((i=smb_getstatus(&smb))!=0) {
			smb_unlocksmbhdr(&smb);
579
			errormsg(WHERE,ERR_READ,smb.file,i,smb.last_error);
580 581
			break; 
		}
582 583 584 585 586
		smb_unlocksmbhdr(&smb);

		if(smb.status.last_msg!=last) { 	/* New messages */
			last=smb.status.last_msg;
			if(post) {
deuce's avatar
deuce committed
587
				free((void *)post); 
588
			}
589
			post=loadposts(&smb.msgs,subnum,0,lp,&unvalidated);   /* So re-load */
590
			if(!smb.msgs)
591
				break;
592
			for(smb.curmsg=0;smb.curmsg<smb.msgs;smb.curmsg++)
593
				if(post[smb.curmsg].idx.number==msg.idx.number)
594
					break;
595 596
			if(smb.curmsg>(smb.msgs-1))
				smb.curmsg=(smb.msgs-1);
597 598
			continue; 
		}
599 600 601 602 603

		if(msg.total_hfields)
			smb_freemsgmem(&msg);
		msg.total_hfields=0;

604
		if(loadmsg(&msg,post[smb.curmsg].idx.number) < 0) {
605
			if(mismatches>5) {	/* We can't do this too many times in a row */
606
				errormsg(WHERE,ERR_CHK,smb.file,post[smb.curmsg].idx.number);
607 608
				break; 
			}
609
			if(post)
deuce's avatar
deuce committed
610
				free(post);
611
			post=loadposts(&smb.msgs,subnum,0,lp,&unvalidated);
612
			if(!smb.msgs)
613
				break;
614 615
			if(smb.curmsg>(smb.msgs-1))
				smb.curmsg=(smb.msgs-1);
616
			mismatches++;
617 618
			continue; 
		}
619 620 621 622
		smb_unlockmsghdr(&smb,&msg);

		mismatches=0;

623
		if(thread_mode) {
624 625
			uint32_t first = smb_first_in_thread(&smb, &msg, NULL);
			if(first <= 0) {
626 627 628
				bputs(text[NoMessagesFound]);
				break;
			}
629
			bprintf("\1n\1l\1h\1bThread\1n\1b: \1h\1c");
rswindell's avatar
rswindell committed
630
			bprintf("%-.*s\r\n", (int)(cols-(column+1)), msghdr_field(&msg, msg.subj));
631 632 633 634
			show_thread(first, post, smb.curmsg);
			subscan[subnum].last = post[smb.curmsg].idx.number;
		}
		else if(domsg && !(sys_status&SS_ABORT)) {
635

636
			if(do_find && mode&SCAN_FIND) { 			/* Find text in messages */
637
				buf=smb_getmsgtxt(&smb,&msg,GETMSGTXT_ALL);
638
				if(!buf) {
639 640 641 642 643 644 645 646
					if(smb.curmsg<smb.msgs-1) 
						smb.curmsg++;
					else if(org_mode&SCAN_FIND)  
						done=1;
					else if(smb.curmsg>=smb.msgs-1)
							domsg=0;
					continue; 
				}
647 648
				if(strcasestr(buf,find) == NULL && strcasestr(msg.subj, find) == NULL
					&& (msg.tags == NULL || strcasestr(msg.tags, find) == NULL)) {
deuce's avatar
deuce committed
649
					free(buf);
650 651
					if(smb.curmsg<smb.msgs-1) 
						smb.curmsg++;
652
					else if(org_mode&SCAN_FIND) 
653
							done=1;
654 655
					else if(smb.curmsg>=smb.msgs-1)
							domsg=0;
656 657
					continue; 
				}
deuce's avatar
deuce committed
658
				free(buf); 
659
			}
660 661

			if(mode&SCAN_CONST)
662
				bprintf(text[ZScanPostHdr],ugrp,usub,smb.curmsg+1,smb.msgs);
663 664 665 666

			if(!reads && mode)
				CRLF;

667 668
			msg.upvotes = post[smb.curmsg].upvotes;
			msg.downvotes = post[smb.curmsg].downvotes;
669
			msg.total_votes = post[smb.curmsg].total_votes;
rswindell's avatar
rswindell committed
670
			show_msg(&smb, &msg
671
				,msg.from_ext && !strcmp(msg.from_ext,"1") && !msg.from_net.type
rswindell's avatar
rswindell committed
672 673
					? 0:P_NOATCODES
				,&post[smb.curmsg]);
674 675 676 677 678 679 680 681 682 683 684 685 686

			reads++;	/* number of messages actually read during this sub-scan */

			/* Message is to this user and hasn't been read, so flag as read */
			if((!stricmp(msg.to,useron.name) || !stricmp(msg.to,useron.alias)
				|| (useron.number==1 && !stricmp(msg.to,"sysop")
				&& !msg.from_net.type))
				&& !(msg.hdr.attr&MSG_READ)) {
				if(msg.total_hfields)
					smb_freemsgmem(&msg);
				msg.total_hfields=0;
				msg.idx.offset=0;
				if(!smb_locksmbhdr(&smb)) { 			  /* Lock the entire base */
687
					if(loadmsg(&msg,msg.idx.number) >= 0) {
688 689 690
						msg.hdr.attr|=MSG_READ;
						msg.idx.attr=msg.hdr.attr;
						if((i=smb_putmsg(&smb,&msg))!=0)
691
							errormsg(WHERE,ERR_WRITE,smb.file,i,smb.last_error);
692 693 694 695
						smb_unlockmsghdr(&smb,&msg); 
					}
					smb_unlocksmbhdr(&smb); 
				}
696 697
				if(!msg.total_hfields) {				/* unsuccessful reload */
					domsg=0;
698 699 700
					continue; 
				} 
			}
701

702
			subscan[subnum].last=post[smb.curmsg].idx.number;
703

704
			if(subscan[subnum].ptr<post[smb.curmsg].idx.number && !(mode&SCAN_TOYOU)) {
705
				posts_read++;
706
				subscan[subnum].ptr=post[smb.curmsg].idx.number; 
707
			} 
708

709 710
			if(sub_op(subnum) && (msg.hdr.attr&(MSG_MODERATED|MSG_VALIDATED)) == MSG_MODERATED) {
				uint16_t msg_attr = msg.hdr.attr;
rswindell's avatar
rswindell committed
711
				SAFEPRINTF2(str,text[ValidatePostQ],smb.curmsg+1,msghdr_field(&msg, msg.subj));
712
				if(!noyes(str))
deuce's avatar
deuce committed
713
					msg_attr|=MSG_VALIDATED;
714
				else {
rswindell's avatar
rswindell committed
715
					SAFEPRINTF2(str,text[DeletePostQ],smb.curmsg+1,msghdr_field(&msg, msg.subj));
716
					if(yesno(str))
deuce's avatar
deuce committed
717
						msg_attr|=MSG_DELETE;
718
				}
719
				if(msg_attr!=msg.hdr.attr) {
720 721 722 723 724
					if(msg.total_hfields)
						smb_freemsgmem(&msg);
					msg.total_hfields=0;
					msg.idx.offset=0;
					if(!smb_locksmbhdr(&smb)) { 			  /* Lock the entire base */
725
						if(loadmsg(&msg,msg.idx.number) >= 0) {
726
							msg.hdr.attr=msg.idx.attr=msg_attr;
727 728 729 730 731 732
							if((i=smb_putmsg(&smb,&msg))!=0)
								errormsg(WHERE,ERR_WRITE,smb.file,i,smb.last_error);
							smb_unlockmsghdr(&smb,&msg); 
						}
						smb_unlocksmbhdr(&smb); 
					}
deuce's avatar
deuce committed
733
					if(msg_attr & MSG_DELETE) {
deuce's avatar
deuce committed
734
						if(cfg.sys_misc&SM_SYSVDELM)
735 736 737 738 739
							domsg=0;	// If you can view deleted messages, don't redisplay.
					}
					else {
						domsg=0;		// If you just validated, don't redisplay.
					}
740 741 742 743 744 745 746 747 748
					if(post)
						free(post);
					post=loadposts(&smb.msgs,subnum,0,lp,&unvalidated);
					if(!smb.msgs)
						break;
					if(smb.curmsg>(smb.msgs-1))
						smb.curmsg=(smb.msgs-1);
					mismatches++;
					continue; 
749 750
				}
			}
751
		}
752 753
		else domsg=1;
		if(mode&SCAN_CONST) {
754
			if(smb.curmsg<smb.msgs-1) smb.curmsg++;
755
				else done=1;
756 757
			continue; 
		}
758
		if(useron.misc&WIP)
759
			menu("msgscan");
760
		ASYNC;
761 762
		if(unvalidated < smb.curmsg)
			bprintf(text[UnvalidatedWarning],unvalidated+1);
763 764
		if(lncntr >= rows-2)
			lncntr--;
765
		bprintf(text[ReadingSub],ugrp,cfg.grp[cfg.sub[subnum]->grp]->sname
766
			,usub,cfg.sub[subnum]->sname,smb.curmsg+1,smb.msgs);
767 768 769 770 771 772
		sprintf(str,"ABCDEFHILMNPQRTUVY?*<>[]{}-+()\b%c%c%c%c"
			,TERM_KEY_LEFT
			,TERM_KEY_RIGHT
			,TERM_KEY_HOME
			,TERM_KEY_END
			);
773
		if(thread_mode)
774
			sprintf(str+strlen(str),"%c%c"
775
				,TERM_KEY_UP
776 777
				,TERM_KEY_DOWN);

778 779
		if(sub_op(subnum))
			strcat(str,"O");
780
		do_find=true;
781
		l=getkeys(str,smb.msgs);
782 783
		if(l&0x80000000L) {
			if((long)l==-1) { /* ctrl-c */
784 785
				quit=1;
				break; 
786
			}
787
			smb.curmsg=(l&~0x80000000L)-1;
788 789 790
			do_find = false;
			thread_mode = false;
			domsg = true;
791 792
			continue; 
		}
793
		if(thread_mode && (IS_ALPHA(l) || l=='?')) {
794 795 796
			thread_mode = false;
			domsg = true;
		}
797
		switch(l) {
798 799 800 801 802
			case '*':
				thread_mode = !thread_mode;
				if(!thread_mode)
					domsg = true;
				continue;
803 804 805
			case 'A':   
			case 'R':   
				if((char)l==(cfg.sys_misc&SM_RA_EMU ? 'A' : 'R')) { 
806
					do_find=false;	/* re-read last message */
807 808 809 810
					break;
				}
				/* Reply to last message */
				domsg=0;
rswindell's avatar
rswindell committed
811
				if(!chk_ar(cfg.sub[subnum]->post_ar,&useron,&client)) {
812
					bputs(text[CantPostOnSub]);
813 814
					break; 
				}
815 816 817 818
				if(msg.hdr.attr&MSG_NOREPLY && !sub_op(subnum)) {
					bputs(text[CantReplyToMsg]);
					break; 
				}
819
				FREE_AND_NULL(post);