getnode.cpp 18.6 KB
Newer Older
1 2 3 4
/* getnode.cpp */

/* Synchronet node information retrieval functions */

5
/* $Id: getnode.cpp,v 1.56 2020/08/01 22:04:03 rswindell Exp $ */
6 7 8 9 10

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
11
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
 *																			*
 * 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										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
#include "cmdshell.h"

/****************************************************************************/
/* Reads the data for node number 'number' into the structure 'node'        */
/* from NODE.DAB															*/
/* if lockit is non-zero, locks this node's record. putnodedat() unlocks it */
/****************************************************************************/
46
int sbbs_t::getnodedat(uint number, node_t *node, bool lockit)
47
{
48
	char	str[MAX_PATH+1];
49 50
	int		rd=sizeof(node_t);
	int		count;
51

52
	if(node==NULL || number<1)
53
		return(-1);
54 55

	if(number>cfg.sys_nodes) {
56
		errormsg(WHERE,ERR_CHK,"node number",number);
57
		return(-1); 
58
	}
59

60 61
	if(node!=&thisnode)
		memset(node,0,sizeof(node_t));
rswindell's avatar
rswindell committed
62
	SAFEPRINTF(str,"%snode.dab",cfg.ctrl_dir);
63
	pthread_mutex_lock(&nodefile_mutex);
64 65 66
	if(nodefile==-1) {
		if((nodefile=nopen(str,O_RDWR|O_DENYNONE))==-1) {
			errormsg(WHERE,ERR_OPEN,str,O_RDWR|O_DENYNONE);
67
			pthread_mutex_unlock(&nodefile_mutex);
68
			return(errno); 
69 70
		}
	}
71 72
	else
		utime(str,NULL);		/* NFS fix... utime() forces a cache refresh */
73

74
	number--;	/* make zero based */
75 76
	for(count=0;count<LOOP_NODEDAB;count++) {
		if(count)
77
			mswait(100);
78
		if(lockit && lock(nodefile,(long)number*sizeof(node_t),sizeof(node_t))!=0) {
79
			unlock(nodefile,(long)number*sizeof(node_t),sizeof(node_t));
80
			continue;
81
		}
82
		lseek(nodefile,(long)number*sizeof(node_t),SEEK_SET);
83
		rd=read(nodefile,node,sizeof(node_t));
84
		if(rd!=sizeof(node_t))
85 86
			unlock(nodefile,(long)number*sizeof(node_t),sizeof(node_t));
		if(rd==sizeof(node_t))
87
			break;
88
	}
89
	if(!lockit && cfg.node_misc&NM_CLOSENODEDAB) {
90 91 92 93
		close(nodefile);
		nodefile=-1;
	}

94 95
	if(count==LOOP_NODEDAB) {
		errormsg(WHERE,rd==sizeof(node_t) ? ERR_LOCK : ERR_READ,"node.dab",number+1);
96 97
		if(nodefile!=-1)
			close(nodefile);
98
		nodefile=-1;
99
		pthread_mutex_unlock(&nodefile_mutex);
100 101
		return(-2);
	}
102
	pthread_mutex_unlock(&nodefile_mutex);
103
	if(count>(LOOP_NODEDAB/2)) {
rswindell's avatar
rswindell committed
104
		SAFEPRINTF2(str,"NODE.DAB (node %d) COLLISION - Count: %d"
105
			,number+1, count);
106
		logline(LOG_WARNING,"!!",str); 
107
	}
108 109

	return(0);
110 111
}

112 113 114 115 116 117 118 119 120 121 122 123
static int getpagingnode(scfg_t* cfg)
{
	for(int i = 1; i <= cfg->sys_nodes; i++) {
		node_t node;
		if(i == cfg->node_num)
			continue;
		if(getnodedat(cfg, i, &node, FALSE, NULL) == 0 && node.action == NODE_PAGE && node.aux == cfg->node_num)
			return i;
	}
	return 0;
}

124 125 126 127 128 129
/****************************************************************************/
/* Synchronizes all the nodes knowledge of the other nodes' actions, mode,  */
/* status and other flags.                                                  */
/* Assumes that getnodedat(node_num,&thisnode,0) was called right before it */
/* is called.  																*/
/****************************************************************************/
130
void sbbs_t::nodesync(bool clearline)
131 132
{
	char	str[256],today[32];
rswindell's avatar
rswindell committed
133
	int		atr=curatr;
134

rswindell's avatar
rswindell committed
135
	if(nodesync_inside || !online) 
136
		return;
137 138 139
	nodesync_inside=1;

	if(thisnode.action!=action) {
140 141 142 143
		if(getnodedat(cfg.node_num,&thisnode,true)==0) {
			thisnode.action=action;
			putnodedat(cfg.node_num,&thisnode); 
		}
144
	}
145 146 147 148

	criterrs=thisnode.errors;

	if(sys_status&SS_USERON) {
149 150

		if(thisnode.status==NODE_WFC) {
151
			lprintf(LOG_ERR, "Node %d NODE STATUS FIXUP", cfg.node_num);
152 153
			if(getnodedat(cfg.node_num,&thisnode,true)==0) {
				thisnode.status=NODE_INUSE;
154
				thisnode.useron=useron.number;
155 156
				putnodedat(cfg.node_num,&thisnode); 
			}
157 158
		}

159 160
		if(!(sys_status&SS_NEWDAY)) {
			now=time(NULL);
161 162
			unixtodstr(&cfg,(time32_t)logontime,str);
			unixtodstr(&cfg,(time32_t)now,today);
163 164
			if(strcmp(str,today)) { /* New day, clear "today" user vars */
				sys_status|=SS_NEWDAY;	// So we don't keep doing this over&over
165
				resetdailyuserdat(&cfg, &useron,/* write: */true);
166 167
			} 
		}
168 169
		if(thisnode.misc&NODE_UDAT && !(useron.rest&FLAG('G'))) {   /* not guest */
			getuserdat(&cfg, &useron);
170 171 172 173
			if(getnodedat(cfg.node_num,&thisnode,true)==0) {
				thisnode.misc&=~NODE_UDAT;
				putnodedat(cfg.node_num,&thisnode); 
			}
174
		}
175
		if(!(sys_status&SS_MOFF)) {
176
			if(thisnode.misc&NODE_MSGW)
177
				getsmsg(useron.number, clearline); 	/* getsmsg clears MSGW flag */
178
			if(thisnode.misc&NODE_NMSG)
179
				getnmsg(clearline);					/* getnmsg clears NMSG flag */
180
		}
181
	}
182 183 184 185 186 187

	if(cfg.sync_mod[0])
		exec_bin(cfg.sync_mod,&main_csi);

	if(thisnode.misc&NODE_INTR) {
		bputs(text[NodeLocked]);
188
		logline(LOG_NOTICE,nulstr,"Interrupted");
189 190
		hangup();
		nodesync_inside=0;
191 192
		return; 
	}
193 194 195 196 197 198

	if(thisnode.misc&NODE_LCHAT) { // pulled into local chat with sysop
		SAVELINE;
		privchat(true);
		RESTORELINE;
	}
199 200 201 202 203 204 205 206 207 208 209 210 211

	if(thisnode.misc&NODE_FCHAT) { // forced into private chat
		int n = getpagingnode(&cfg);
		if(n) {
			SAVELINE;
			privchat(true, n);
			RESTORELINE;
		}
		if(getnodedat(cfg.node_num, &thisnode, true)==0) {
			thisnode.misc &= ~NODE_FCHAT;
			putnodedat(cfg.node_num, &thisnode); 
		}
	}
212 213 214 215
		
	if(sys_status&SS_USERON && memcmp(&nodesync_user,&useron,sizeof(user_t))) {
		getusrdirs();
		getusrsubs();
216 217
		memcpy(&nodesync_user,&useron,sizeof(nodesync_user)); 
	}
218 219 220 221

	if(sys_status&SS_USERON && online && (timeleft/60)<(5-timeleft_warn)
		&& !SYSOP) {
		timeleft_warn=5-(timeleft/60);
222 223
		if(!(sys_status&SS_MOFF)) {
			attr(LIGHTGRAY);
224 225 226
			bprintf(text[OnlyXminutesLeft]
				,((ushort)timeleft/60)+1,(timeleft/60) ? "s" : nulstr); 
		}
227
	}
228 229 230 231 232 233 234 235

	attr(atr);	/* replace original attributes */
	nodesync_inside=0;
}

/****************************************************************************/
/* Prints short messages waiting for this node, if any...                   */
/****************************************************************************/
236
int sbbs_t::getnmsg(bool clearline)
237
{
deuce's avatar
deuce committed
238
	char	str[MAX_PATH+1], *buf;
239 240
	int		file;
	long	length;
241

242 243 244 245
	if(getnodedat(cfg.node_num,&thisnode,true)==0) {
		thisnode.misc&=~NODE_NMSG;          /* clear the NMSG flag */
		putnodedat(cfg.node_num,&thisnode);
	}
246

rswindell's avatar
rswindell committed
247
	SAFEPRINTF2(str,"%smsgs/n%3.3u.msg",cfg.data_dir,cfg.node_num);
248
	if(flength(str)<1L)
249
		return(0);
250 251 252 253
	if((file=nopen(str,O_RDWR))==-1) {
		/**
			errormsg(WHERE,ERR_OPEN,str,O_RDWR);
		**/
254
		return(errno); 
255
	}
256
	length=(long)filelength(file);
257 258
	if(!length) {
		close(file);
259 260
		return(0); 
	}
deuce's avatar
deuce committed
261
	if((buf=(char *)malloc(length+1))==NULL) {
262 263
		close(file);
		errormsg(WHERE,ERR_ALLOC,str,length+1);
264 265
		return(-1); 
	}
266 267
	if(lread(file,buf,length)!=length) {
		close(file);
deuce's avatar
deuce committed
268
		free(buf);
269
		errormsg(WHERE,ERR_READ,str,length);
270 271
		return(errno); 
	}
272 273 274 275
	chsize(file,0L);
	close(file);
	buf[length]=0;

276 277
	if(clearline)
		this->clearline();
278
	else if(column)
279
		CRLF; 
280
	putmsg(buf,P_NOATCODES);
deuce's avatar
deuce committed
281
	free(buf);
282 283

	return(0);
284 285 286 287 288
}

/****************************************************************************/
/* 'ext' must be at least 128 bytes!                                        */
/****************************************************************************/
289
int sbbs_t::getnodeext(uint number, char *ext)
290
{
291
    char	str[MAX_PATH+1];
292
    int		rd,count;
293 294 295

	if(!number || number>cfg.sys_nodes) {
		errormsg(WHERE,ERR_CHK,"node number",number);
296
		return(-1); 
297
	}
298

rswindell's avatar
rswindell committed
299
	SAFEPRINTF(str,"%snode.exb",cfg.ctrl_dir);
300 301 302
	if((node_ext=nopen(str,O_RDONLY|O_DENYNONE))==-1) {
		memset(ext,0,128);
		errormsg(WHERE,ERR_OPEN,str,O_RDONLY|O_DENYNONE);
303
		return(errno); 
304 305
	}

306
	number--;   /* make zero based */
307 308
	for(count=0;count<LOOP_NODEDAB;count++) {
		if(count)
309
			mswait(100);
310
		if(lock(node_ext,(long)number*128L,128)!=0) 
311
			continue; 
312
		lseek(node_ext,(long)number*128L,SEEK_SET);
313 314 315
		rd=read(node_ext,ext,128);
		unlock(node_ext,(long)number*128L,128);
		if(rd==128)
316
			break;
317
	}
318 319 320
	close(node_ext);
	node_ext=-1;

321 322 323 324 325 326

	if(count==LOOP_NODEDAB) {
		errormsg(WHERE,ERR_READ,"node.exb",number+1);
		return(-2);
	}
	if(count>(LOOP_NODEDAB/2)) {
rswindell's avatar
rswindell committed
327
		SAFEPRINTF2(str,"NODE.EXB (node %d) COLLISION - Count: %d"
328
			,number+1, count);
329 330
		logline("!!",str); 
	}
331 332

	return(0);
333 334 335 336 337
}


/****************************************************************************/
/* Prints short messages waiting for 'usernumber', if any...                */
338
/* then truncates the file.                                                 */
339
/****************************************************************************/
340
int sbbs_t::getsmsg(int usernumber, bool clearline)
341
{
deuce's avatar
deuce committed
342
	char	str[MAX_PATH+1], *buf;
343 344
    int		file;
    long	length;
345 346 347 348
	node_t	node;
	int		i;

	for(i=1;i<=cfg.sys_nodes;i++) {	/* clear msg waiting flag */
349 350
		if(getnodedat(i,&node,false) != 0 || node.useron != usernumber)
			continue;
351 352 353 354 355 356 357 358
		if(getnodedat(i,&node,true)==0) {
			if(node.useron==usernumber
					&& (node.status==NODE_INUSE || node.status==NODE_QUIET)
					&& node.misc&NODE_MSGW)
				node.misc&=~NODE_MSGW;
			putnodedat(i,&node); 
		} 
	}
359

rswindell's avatar
rswindell committed
360
	SAFEPRINTF2(str,"%smsgs/%4.4u.msg",cfg.data_dir,usernumber);
361
	if(flength(str)<1L)
362
		return(0);
363 364
	if((file=nopen(str,O_RDWR))==-1) {
		errormsg(WHERE,ERR_OPEN,str,O_RDWR);
365 366
		return(errno); 
	}
367
	length=(long)filelength(file);
deuce's avatar
deuce committed
368
	if((buf=(char *)malloc(length+1))==NULL) {
369 370
		close(file);
		errormsg(WHERE,ERR_ALLOC,str,length+1);
371 372
		return(-1); 
	}
373 374
	if(lread(file,buf,length)!=length) {
		close(file);
deuce's avatar
deuce committed
375
		free(buf);
376
		errormsg(WHERE,ERR_READ,str,length);
377 378
		return(errno); 
	}
379 380 381 382
	chsize(file,0L);
	close(file);
	buf[length]=0;
	getnodedat(cfg.node_num,&thisnode,0);
383 384 385
	if(clearline)
		this->clearline();
	else
386
		if(column)
387
			CRLF;
388
	strip_invalid_attr(buf);
389
	putmsg(buf,P_NOATCODES);
deuce's avatar
deuce committed
390
	free(buf);
391 392

	return(0);
393 394 395 396 397 398 399 400 401
}

/****************************************************************************/
/* This function lists users that are online.                               */
/* If listself is true, it will list the current node.                      */
/* Returns number of active nodes (not including current node).             */
/****************************************************************************/
int sbbs_t::whos_online(bool listself)
{
402 403
    int		i,j;
    node_t	node;
404

405 406 407 408
	if(cfg.whosonline_mod[0] != '\0') {
		return exec_bin(cfg.whosonline_mod, &main_csi);
	}

409 410 411
	CRLF;
	bputs(text[NodeLstHdr]);
	for(j=0,i=1;i<=cfg.sys_nodes && i<=cfg.sys_lastnode;i++) {
412
		getnodedat(i,&node,false);
413 414 415
		if(i==cfg.node_num) {
			if(listself)
				printnodedat(i,&node);
416 417
			continue; 
		}
418 419 420 421
		if(node.status==NODE_INUSE || (SYSOP && node.status==NODE_QUIET)) {
			printnodedat(i,&node);
			if(!lastnodemsg)
				lastnodemsg=i;
422 423 424
			j++; 
		} 
	}
425 426 427 428 429
	if(!j)
		bputs(text[NoOtherActiveNodes]);
	return(j);
}

430 431 432 433
void sbbs_t::nodelist(void)
{
	node_t	node;

434 435 436 437 438
	if(cfg.nodelist_mod[0] != '\0') {
		exec_bin(cfg.nodelist_mod, &main_csi);
		return;
	}

439 440 441
	CRLF;
	bputs(text[NodeLstHdr]);
	for(int i=1;i<=cfg.sys_nodes && i<=cfg.sys_lastnode;i++) {
442
		getnodedat(i,&node,false);
443 444 445 446
		printnodedat(i,&node); 
	}
}

447
static char* node_connection_desc(sbbs_t* sbbs, ushort conn, char* str)
448 449 450
{
	switch(conn) {
		case NODE_CONNECTION_LOCAL:
rswindell's avatar
rswindell committed
451
			return (char*)" Locally";	/* obsolete */
452
		case NODE_CONNECTION_TELNET:
453
			return sbbs->text[NodeConnectionTelnet];
454
		case NODE_CONNECTION_RLOGIN:
455
			return sbbs->text[NodeConnectionRLogin];
456
		case NODE_CONNECTION_SSH:
457
			return sbbs->text[NodeConnectionSSH];
458 459
		case NODE_CONNECTION_RAW:
			return sbbs->text[NodeConnectionRaw];
460
		default:
461
			sprintf(str,sbbs->text[NodeConnectionModem],conn);
462 463 464 465 466 467
			break;
	}

	return str;
}

468 469 470 471 472
/****************************************************************************/
/* Displays the information for node number 'number' contained in 'node'    */
/****************************************************************************/
void sbbs_t::printnodedat(uint number, node_t* node)
{
473 474 475
    uint	i;
    char	hour,mer[3];
	char 	tmp[512];
476 477 478 479 480 481

	attr(cfg.color[clr_nodenum]);
	bprintf("%3d  ",number);
	attr(cfg.color[clr_nodestatus]);
	switch(node->status) {
		case NODE_WFC:
482
			bputs(text[NodeStatusWaitingForCall]);
483 484
			break;
		case NODE_OFFLINE:
485
			bputs(text[NodeStatusOffline]);
486 487
			break;
		case NODE_NETTING:
488
			bputs("Networking");	/* obsolete */
489 490
			break;
		case NODE_LOGON:
491 492
			bputs(text[NodeStatusLogon]);
			bputs(node_connection_desc(this, node->connection, tmp));
493
			break;
494 495 496 497
		case NODE_LOGOUT:
			bprintf(text[NodeStatusLogout]
				,(node->misc&NODE_ANON) && !SYSOP ? text[UNKNOWN_USER] : username(&cfg,node->useron,tmp));
			break;
498
		case NODE_EVENT_WAITING:
499
			bputs(text[NodeStatusEventWaiting]);
500 501
			break;
		case NODE_EVENT_LIMBO:
502
			bprintf(text[NodeStatusEventLimbo],node->aux);
503 504
			break;
		case NODE_EVENT_RUNNING:
505
			bputs(text[NodeStatusEventRunning]);
506 507
			break;
		case NODE_NEWUSER:
508 509
			bputs(text[NodeStatusNewUser]);
			bputs(node_connection_desc(this, node->connection, tmp));
510 511 512
			break;
		case NODE_QUIET:
			if(!SYSOP) {
513
				bputs(text[NodeStatusWaitingForCall]);
514 515
				break; 
			}
516 517 518 519
		case NODE_INUSE:
			if(node->misc&NODE_EXT) {
				getnodeext(number,tmp);
				bputs(tmp);
520 521
				break; 
			}
522 523
			attr(cfg.color[clr_nodeuser]);
			if(node->misc&NODE_ANON && !SYSOP)
524
				bputs(text[UNKNOWN_USER]);
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
			else
				bputs(username(&cfg,node->useron,tmp));
			attr(cfg.color[clr_nodestatus]);
			bputs(" ");
			switch(node->action) {
				case NODE_MAIN:
					bputs("at main menu");
					break;
				case NODE_RMSG:
					bputs("reading messages");
					break;
				case NODE_RMAL:
					bputs("reading mail");
					break;
				case NODE_RSML:
					bputs("reading sent mail");
					break;
				case NODE_RTXT:
					bputs("reading text files");
					break;
				case NODE_PMSG:
					bputs("posting message");
					break;
				case NODE_SMAL:
					bputs("sending mail");
					break;
				case NODE_AMSG:
					bputs("posting auto-message");
					break;
				case NODE_XTRN:
					if(node->aux<1 || node->aux>cfg.total_xtrns)
						bputs("at external program menu");
					else {
						bputs("running ");
						i=node->aux-1;
rswindell's avatar
rswindell committed
560
						if(SYSOP || chk_ar(cfg.xtrn[i]->ar,&useron,&client))
561 562
							bputs(cfg.xtrn[node->aux-1]->name);
						else
563 564
							bputs("external program"); 
					}
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598
					break;
				case NODE_DFLT:
					bputs("changing defaults");
					break;
				case NODE_XFER:
					bputs("at transfer menu");
					break;
				case NODE_RFSD:
					bprintf("retrieving from device #%d",node->aux);
					break;
				case NODE_DLNG:
					bprintf("downloading");
					break;
				case NODE_ULNG:
					bputs("uploading");
					break;
				case NODE_BXFR:
					bputs("transferring bidirectional");
					break;
				case NODE_LFIL:
					bputs("listing files");
					break;
				case NODE_LOGN:
					bputs("logging on");
					break;
				case NODE_LCHT:
					bprintf("in local chat with %s",cfg.sys_op);
					break;
				case NODE_MCHT:
					if(node->aux) {
						bprintf("in multinode chat channel %d",node->aux&0xff);
						if(node->aux&0x1f00) { /* password */
							outchar('*');
							if(SYSOP)
599 600 601
								bprintf(" %s",unpackchatpass(tmp,node)); 
						} 
					}
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
					else
						bputs("in multinode global chat channel");
					break;
				case NODE_PAGE:
					bprintf("paging node %u for private chat",node->aux);
					break;
				case NODE_PCHT:
					if(node->aux)
						bprintf("in private chat with node %u",node->aux);
					else
						bprintf("in local chat with %s",cfg.sys_op);
					break;
				case NODE_GCHT:
					i=node->aux;
					if(i>=cfg.total_gurus)
						i=0;
					bprintf("chatting with %s",cfg.guru[i]->name);
					break;
				case NODE_CHAT:
					bputs("in chat section");
					break;
				case NODE_TQWK:
					bputs("transferring QWK packet");
					break;
				case NODE_SYSP:
					bputs("performing sysop activities");
					break;
				default:
630
					bputs(ultoa(node->action,tmp,10));
631
					break;  }
632
			bputs(node_connection_desc(this, node->connection, tmp));
633 634 635
			if(node->action==NODE_DLNG) {
				if(cfg.sys_misc&SM_MILITARY) {
					hour=node->aux/60;
636 637
					mer[0]=0; 
				}
638 639 640 641 642
				else if((node->aux/60)>=12) {
					if(node->aux/60==12)
						hour=12;
					else
						hour=(node->aux/60)-12;
643 644
					strcpy(mer,"pm"); 
				}
645 646 647 648
				else {
					if((node->aux/60)==0)    /* 12 midnite */
						hour=12;
					else hour=node->aux/60;
649 650
					strcpy(mer,"am"); 
				}
651
				bprintf(" ETA %02d:%02d %s"
652 653 654 655
					,hour,node->aux%60,mer); 
			}
			break; 
	}
656 657 658 659 660 661 662 663 664 665 666 667 668
	i=NODE_LOCK;
	if(node->status==NODE_INUSE || SYSOP)
		i|=NODE_POFF|NODE_AOFF|NODE_MSGW|NODE_NMSG;
	if(node->misc&i) {
		bputs(" (");
		if(node->misc&(i&NODE_AOFF))
			outchar('A');
		if(node->misc&NODE_LOCK)
			outchar('L');
		if(node->misc&(i&(NODE_MSGW|NODE_NMSG)))
			outchar('M');
		if(node->misc&(i&NODE_POFF))
			outchar('P');
669 670
		outchar(')'); 
	}
671
	if(SYSOP && ((node->misc
672
		&(NODE_ANON|NODE_UDAT|NODE_INTR|NODE_RRUN|NODE_EVENT|NODE_DOWN|NODE_LCHAT|NODE_FCHAT))
673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690
		|| node->status==NODE_QUIET)) {
		bputs(" [");
		if(node->misc&NODE_ANON)
			outchar('A');
		if(node->misc&NODE_INTR)
			outchar('I');
		if(node->misc&NODE_RRUN)
			outchar('R');
		if(node->misc&NODE_UDAT)
			outchar('U');
		if(node->status==NODE_QUIET)
			outchar('Q');
		if(node->misc&NODE_EVENT)
			outchar('E');
		if(node->misc&NODE_DOWN)
			outchar('D');
		if(node->misc&NODE_LCHAT)
			outchar('C');
691 692
		if(node->misc&NODE_FCHAT)
			outchar('F');
693 694
		outchar(']'); 
	}
695 696
	if(node->errors && SYSOP) {
		attr(cfg.color[clr_err]);
697 698
		bprintf(" %d error%c",node->errors, node->errors>1 ? 's' : '\0' ); 
	}
699 700 701
	attr(LIGHTGRAY);
	CRLF;
}
702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718

uint sbbs_t::count_nodes(bool self)
{
	uint count = 0;

	for(int i=1; i<=cfg.sys_nodes && i<=cfg.sys_lastnode; i++) {
	    node_t	node;
		if(getnodedat(i, &node, false) != 0)
			continue;
		if(!self && i==cfg.node_num)
			continue;
		if(node.status != NODE_INUSE)
			continue;
		count++;
	}
	return count;
}