Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

getnode.cpp 18 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
}

/****************************************************************************/
/* 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.  																*/
/****************************************************************************/
118
void sbbs_t::nodesync(bool clearline)
119 120
{
	char	str[256],today[32];
rswindell's avatar
rswindell committed
121
	int		atr=curatr;
122

rswindell's avatar
rswindell committed
123
	if(nodesync_inside || !online) 
124
		return;
125 126 127
	nodesync_inside=1;

	if(thisnode.action!=action) {
128 129 130 131
		if(getnodedat(cfg.node_num,&thisnode,true)==0) {
			thisnode.action=action;
			putnodedat(cfg.node_num,&thisnode); 
		}
132
	}
133 134 135 136

	criterrs=thisnode.errors;

	if(sys_status&SS_USERON) {
137 138

		if(thisnode.status==NODE_WFC) {
139
			lprintf(LOG_ERR, "Node %d NODE STATUS FIXUP", cfg.node_num);
140 141
			if(getnodedat(cfg.node_num,&thisnode,true)==0) {
				thisnode.status=NODE_INUSE;
142
				thisnode.useron=useron.number;
143 144
				putnodedat(cfg.node_num,&thisnode); 
			}
145 146
		}

147 148
		if(!(sys_status&SS_NEWDAY)) {
			now=time(NULL);
149 150
			unixtodstr(&cfg,(time32_t)logontime,str);
			unixtodstr(&cfg,(time32_t)now,today);
151 152
			if(strcmp(str,today)) { /* New day, clear "today" user vars */
				sys_status|=SS_NEWDAY;	// So we don't keep doing this over&over
153
				resetdailyuserdat(&cfg, &useron,/* write: */true);
154 155
			} 
		}
156 157
		if(thisnode.misc&NODE_UDAT && !(useron.rest&FLAG('G'))) {   /* not guest */
			getuserdat(&cfg, &useron);
158 159 160 161
			if(getnodedat(cfg.node_num,&thisnode,true)==0) {
				thisnode.misc&=~NODE_UDAT;
				putnodedat(cfg.node_num,&thisnode); 
			}
162
		}
163
		if(!(sys_status&SS_MOFF)) {
164
			if(thisnode.misc&NODE_MSGW)
165
				getsmsg(useron.number, clearline); 	/* getsmsg clears MSGW flag */
166
			if(thisnode.misc&NODE_NMSG)
167
				getnmsg(clearline);					/* getnmsg clears NMSG flag */
168
		}
169
	}
170 171 172 173 174 175

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

	if(thisnode.misc&NODE_INTR) {
		bputs(text[NodeLocked]);
176
		logline(LOG_NOTICE,nulstr,"Interrupted");
177 178
		hangup();
		nodesync_inside=0;
179 180
		return; 
	}
181 182 183 184 185 186 187 188 189 190

	if(thisnode.misc&NODE_LCHAT) { // pulled into local chat with sysop
		SAVELINE;
		privchat(true);
		RESTORELINE;
	}
		
	if(sys_status&SS_USERON && memcmp(&nodesync_user,&useron,sizeof(user_t))) {
		getusrdirs();
		getusrsubs();
191 192
		memcpy(&nodesync_user,&useron,sizeof(nodesync_user)); 
	}
193 194 195 196

	if(sys_status&SS_USERON && online && (timeleft/60)<(5-timeleft_warn)
		&& !SYSOP) {
		timeleft_warn=5-(timeleft/60);
197 198
		if(!(sys_status&SS_MOFF)) {
			attr(LIGHTGRAY);
199 200 201
			bprintf(text[OnlyXminutesLeft]
				,((ushort)timeleft/60)+1,(timeleft/60) ? "s" : nulstr); 
		}
202
	}
203 204 205 206 207 208 209 210

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

/****************************************************************************/
/* Prints short messages waiting for this node, if any...                   */
/****************************************************************************/
211
int sbbs_t::getnmsg(bool clearline)
212
{
deuce's avatar
deuce committed
213
	char	str[MAX_PATH+1], *buf;
214 215
	int		file;
	long	length;
216

217 218 219 220
	if(getnodedat(cfg.node_num,&thisnode,true)==0) {
		thisnode.misc&=~NODE_NMSG;          /* clear the NMSG flag */
		putnodedat(cfg.node_num,&thisnode);
	}
221

rswindell's avatar
rswindell committed
222
	SAFEPRINTF2(str,"%smsgs/n%3.3u.msg",cfg.data_dir,cfg.node_num);
223
	if(flength(str)<1L)
224
		return(0);
225 226 227 228
	if((file=nopen(str,O_RDWR))==-1) {
		/**
			errormsg(WHERE,ERR_OPEN,str,O_RDWR);
		**/
229
		return(errno); 
230
	}
231
	length=(long)filelength(file);
232 233
	if(!length) {
		close(file);
234 235
		return(0); 
	}
deuce's avatar
deuce committed
236
	if((buf=(char *)malloc(length+1))==NULL) {
237 238
		close(file);
		errormsg(WHERE,ERR_ALLOC,str,length+1);
239 240
		return(-1); 
	}
241 242
	if(lread(file,buf,length)!=length) {
		close(file);
deuce's avatar
deuce committed
243
		free(buf);
244
		errormsg(WHERE,ERR_READ,str,length);
245 246
		return(errno); 
	}
247 248 249 250
	chsize(file,0L);
	close(file);
	buf[length]=0;

251 252
	if(clearline)
		this->clearline();
253
	else if(column)
254
		CRLF; 
255
	putmsg(buf,P_NOATCODES);
deuce's avatar
deuce committed
256
	free(buf);
257 258

	return(0);
259 260 261 262 263
}

/****************************************************************************/
/* 'ext' must be at least 128 bytes!                                        */
/****************************************************************************/
264
int sbbs_t::getnodeext(uint number, char *ext)
265
{
266
    char	str[MAX_PATH+1];
267
    int		rd,count;
268 269 270

	if(!number || number>cfg.sys_nodes) {
		errormsg(WHERE,ERR_CHK,"node number",number);
271
		return(-1); 
272
	}
273

rswindell's avatar
rswindell committed
274
	SAFEPRINTF(str,"%snode.exb",cfg.ctrl_dir);
275 276 277
	if((node_ext=nopen(str,O_RDONLY|O_DENYNONE))==-1) {
		memset(ext,0,128);
		errormsg(WHERE,ERR_OPEN,str,O_RDONLY|O_DENYNONE);
278
		return(errno); 
279 280
	}

281
	number--;   /* make zero based */
282 283
	for(count=0;count<LOOP_NODEDAB;count++) {
		if(count)
284
			mswait(100);
285
		if(lock(node_ext,(long)number*128L,128)!=0) 
286
			continue; 
287
		lseek(node_ext,(long)number*128L,SEEK_SET);
288 289 290
		rd=read(node_ext,ext,128);
		unlock(node_ext,(long)number*128L,128);
		if(rd==128)
291
			break;
292
	}
293 294 295
	close(node_ext);
	node_ext=-1;

296 297 298 299 300 301

	if(count==LOOP_NODEDAB) {
		errormsg(WHERE,ERR_READ,"node.exb",number+1);
		return(-2);
	}
	if(count>(LOOP_NODEDAB/2)) {
rswindell's avatar
rswindell committed
302
		SAFEPRINTF2(str,"NODE.EXB (node %d) COLLISION - Count: %d"
303
			,number+1, count);
304 305
		logline("!!",str); 
	}
306 307

	return(0);
308 309 310 311 312
}


/****************************************************************************/
/* Prints short messages waiting for 'usernumber', if any...                */
313
/* then truncates the file.                                                 */
314
/****************************************************************************/
315
int sbbs_t::getsmsg(int usernumber, bool clearline)
316
{
deuce's avatar
deuce committed
317
	char	str[MAX_PATH+1], *buf;
318 319
    int		file;
    long	length;
320 321 322 323
	node_t	node;
	int		i;

	for(i=1;i<=cfg.sys_nodes;i++) {	/* clear msg waiting flag */
324 325
		if(getnodedat(i,&node,false) != 0 || node.useron != usernumber)
			continue;
326 327 328 329 330 331 332 333
		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); 
		} 
	}
334

rswindell's avatar
rswindell committed
335
	SAFEPRINTF2(str,"%smsgs/%4.4u.msg",cfg.data_dir,usernumber);
336
	if(flength(str)<1L)
337
		return(0);
338 339
	if((file=nopen(str,O_RDWR))==-1) {
		errormsg(WHERE,ERR_OPEN,str,O_RDWR);
340 341
		return(errno); 
	}
342
	length=(long)filelength(file);
deuce's avatar
deuce committed
343
	if((buf=(char *)malloc(length+1))==NULL) {
344 345
		close(file);
		errormsg(WHERE,ERR_ALLOC,str,length+1);
346 347
		return(-1); 
	}
348 349
	if(lread(file,buf,length)!=length) {
		close(file);
deuce's avatar
deuce committed
350
		free(buf);
351
		errormsg(WHERE,ERR_READ,str,length);
352 353
		return(errno); 
	}
354 355 356 357
	chsize(file,0L);
	close(file);
	buf[length]=0;
	getnodedat(cfg.node_num,&thisnode,0);
358 359 360
	if(clearline)
		this->clearline();
	else
361
		if(column)
362
			CRLF;
363
	strip_invalid_attr(buf);
364
	putmsg(buf,P_NOATCODES);
deuce's avatar
deuce committed
365
	free(buf);
366 367

	return(0);
368 369 370 371 372 373 374 375 376
}

/****************************************************************************/
/* 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)
{
377 378
    int		i,j;
    node_t	node;
379

380 381 382 383
	if(cfg.whosonline_mod[0] != '\0') {
		return exec_bin(cfg.whosonline_mod, &main_csi);
	}

384 385 386
	CRLF;
	bputs(text[NodeLstHdr]);
	for(j=0,i=1;i<=cfg.sys_nodes && i<=cfg.sys_lastnode;i++) {
387
		getnodedat(i,&node,false);
388 389 390
		if(i==cfg.node_num) {
			if(listself)
				printnodedat(i,&node);
391 392
			continue; 
		}
393 394 395 396
		if(node.status==NODE_INUSE || (SYSOP && node.status==NODE_QUIET)) {
			printnodedat(i,&node);
			if(!lastnodemsg)
				lastnodemsg=i;
397 398 399
			j++; 
		} 
	}
400 401 402 403 404
	if(!j)
		bputs(text[NoOtherActiveNodes]);
	return(j);
}

405 406 407 408
void sbbs_t::nodelist(void)
{
	node_t	node;

409 410 411 412 413
	if(cfg.nodelist_mod[0] != '\0') {
		exec_bin(cfg.nodelist_mod, &main_csi);
		return;
	}

414 415 416
	CRLF;
	bputs(text[NodeLstHdr]);
	for(int i=1;i<=cfg.sys_nodes && i<=cfg.sys_lastnode;i++) {
417
		getnodedat(i,&node,false);
418 419 420 421
		printnodedat(i,&node); 
	}
}

422
static char* node_connection_desc(sbbs_t* sbbs, ushort conn, char* str)
423 424 425
{
	switch(conn) {
		case NODE_CONNECTION_LOCAL:
rswindell's avatar
rswindell committed
426
			return (char*)" Locally";	/* obsolete */
427
		case NODE_CONNECTION_TELNET:
428
			return sbbs->text[NodeConnectionTelnet];
429
		case NODE_CONNECTION_RLOGIN:
430
			return sbbs->text[NodeConnectionRLogin];
431
		case NODE_CONNECTION_SSH:
432
			return sbbs->text[NodeConnectionSSH];
433 434
		case NODE_CONNECTION_RAW:
			return sbbs->text[NodeConnectionRaw];
435
		default:
436
			sprintf(str,sbbs->text[NodeConnectionModem],conn);
437 438 439 440 441 442
			break;
	}

	return str;
}

443 444 445 446 447
/****************************************************************************/
/* Displays the information for node number 'number' contained in 'node'    */
/****************************************************************************/
void sbbs_t::printnodedat(uint number, node_t* node)
{
448 449 450
    uint	i;
    char	hour,mer[3];
	char 	tmp[512];
451 452 453 454 455 456

	attr(cfg.color[clr_nodenum]);
	bprintf("%3d  ",number);
	attr(cfg.color[clr_nodestatus]);
	switch(node->status) {
		case NODE_WFC:
457
			bputs(text[NodeStatusWaitingForCall]);
458 459
			break;
		case NODE_OFFLINE:
460
			bputs(text[NodeStatusOffline]);
461 462
			break;
		case NODE_NETTING:
463
			bputs("Networking");	/* obsolete */
464 465
			break;
		case NODE_LOGON:
466 467
			bputs(text[NodeStatusLogon]);
			bputs(node_connection_desc(this, node->connection, tmp));
468
			break;
469 470 471 472
		case NODE_LOGOUT:
			bprintf(text[NodeStatusLogout]
				,(node->misc&NODE_ANON) && !SYSOP ? text[UNKNOWN_USER] : username(&cfg,node->useron,tmp));
			break;
473
		case NODE_EVENT_WAITING:
474
			bputs(text[NodeStatusEventWaiting]);
475 476
			break;
		case NODE_EVENT_LIMBO:
477
			bprintf(text[NodeStatusEventLimbo],node->aux);
478 479
			break;
		case NODE_EVENT_RUNNING:
480
			bputs(text[NodeStatusEventRunning]);
481 482
			break;
		case NODE_NEWUSER:
483 484
			bputs(text[NodeStatusNewUser]);
			bputs(node_connection_desc(this, node->connection, tmp));
485 486 487
			break;
		case NODE_QUIET:
			if(!SYSOP) {
488
				bputs(text[NodeStatusWaitingForCall]);
489 490
				break; 
			}
491 492 493 494
		case NODE_INUSE:
			if(node->misc&NODE_EXT) {
				getnodeext(number,tmp);
				bputs(tmp);
495 496
				break; 
			}
497 498
			attr(cfg.color[clr_nodeuser]);
			if(node->misc&NODE_ANON && !SYSOP)
499
				bputs(text[UNKNOWN_USER]);
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
			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
535
						if(SYSOP || chk_ar(cfg.xtrn[i]->ar,&useron,&client))
536 537
							bputs(cfg.xtrn[node->aux-1]->name);
						else
538 539
							bputs("external program"); 
					}
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
					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)
574 575 576
								bprintf(" %s",unpackchatpass(tmp,node)); 
						} 
					}
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
					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:
605
					bputs(ultoa(node->action,tmp,10));
606
					break;  }
607
			bputs(node_connection_desc(this, node->connection, tmp));
608 609 610
			if(node->action==NODE_DLNG) {
				if(cfg.sys_misc&SM_MILITARY) {
					hour=node->aux/60;
611 612
					mer[0]=0; 
				}
613 614 615 616 617
				else if((node->aux/60)>=12) {
					if(node->aux/60==12)
						hour=12;
					else
						hour=(node->aux/60)-12;
618 619
					strcpy(mer,"pm"); 
				}
620 621 622 623
				else {
					if((node->aux/60)==0)    /* 12 midnite */
						hour=12;
					else hour=node->aux/60;
624 625
					strcpy(mer,"am"); 
				}
626
				bprintf(" ETA %02d:%02d %s"
627 628 629 630
					,hour,node->aux%60,mer); 
			}
			break; 
	}
631 632 633 634 635 636 637 638 639 640 641 642 643
	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');
644 645
		outchar(')'); 
	}
646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
	if(SYSOP && ((node->misc
		&(NODE_ANON|NODE_UDAT|NODE_INTR|NODE_RRUN|NODE_EVENT|NODE_DOWN|NODE_LCHAT))
		|| 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');
666 667
		outchar(']'); 
	}
668 669
	if(node->errors && SYSOP) {
		attr(cfg.color[clr_err]);
670 671
		bprintf(" %d error%c",node->errors, node->errors>1 ? 's' : '\0' ); 
	}
672 673 674
	attr(LIGHTGRAY);
	CRLF;
}
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691

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;
}