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

atcodes.cpp 52.4 KB
Newer Older
1 2 3 4 5 6
/* Synchronet "@code" functions */

/****************************************************************************
 * @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
 *																			*
 * 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"
#include "cmdshell.h"
24 25
#include "utf8.h"
#include "unicode.h"
26
#include "cp437defs.h"
27
#include "ver.h"
28

29 30 31 32 33 34 35
#if defined(_WINSOCKAPI_)
	extern WSADATA WSAData;
	#define SOCKLIB_DESC WSAData.szDescription
#else
	#define	SOCKLIB_DESC NULL
#endif

36 37 38 39 40
static char* separate_thousands(const char* src, char *dest, size_t maxlen, char sep)
{
	if(strlen(src) * 1.3 > maxlen)
		return (char*)src;
	const char* tail = src;
41
	while(*tail && IS_DIGIT(*tail))
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
		tail++;
	if(tail == src)
		return (char*)src;
	size_t digits = tail - src;
	char* d = dest;
	for(size_t i = 0; i < digits; d++, i++) {
		*d = src[i];
		if(i && i + 3 < digits && (digits - (i + 1)) % 3 == 0)
			*(++d) = sep;
	}
	*d = 0;
	strcpy(d, tail);
	return dest;
}

57 58 59
/****************************************************************************/
/* Returns 0 if invalid @ code. Returns length of @ code if valid.          */
/****************************************************************************/
60
int sbbs_t::show_atcode(const char *instr, JSObject* obj)
61
{
62
	char	str[128],str2[128],*tp,*sp,*p;
63
    int     len;
64
	int		disp_len;
65 66 67 68 69 70
	enum {
		none,
		left,
		right,
		center
	} align = none;
71
	bool	zero_padded=false;
rswindell's avatar
rswindell committed
72
	bool	truncated = true;
rswindell's avatar
rswindell committed
73
	bool	doubled = false;
74
	bool	thousep = false;	// thousands-separated
75
	bool	uppercase = false;
76
	bool	width_specified = false;
77
	long	pmode = 0;
78
	const char *cp;
79

80 81
	if(*instr != '@')
		return 0;
82
	SAFECOPY(str,instr);
83 84 85
	tp=strchr(str+1,'@');
	if(!tp)                 /* no terminating @ */
		return(0);
86
	sp=strchr(str+1,' ');
87 88 89 90 91 92
	if(sp && sp<tp)         /* space before terminating @ */
		return(0);
	len=(tp-str)+1;
	(*tp)=0;
	sp=(str+1);

93
	if(*sp == '~' && *(sp + 1)) {	// Mouse hot-spot (hungry)
rswindell's avatar
rswindell committed
94 95 96 97 98 99 100 101 102
		sp++;
		tp = strchr(sp + 1, '~');
		if(tp == NULL)
			tp = sp;
		else {
			*tp = 0;
			tp++;
		}
		c_unescape_str(tp);
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
		add_hotspot(tp, /* hungry: */true, column, column + strlen(sp) - 1, row);
		bputs(sp);
		return len;
	}

	if(*sp == '`' && *(sp + 1)) {	// Mouse hot-spot (strict)
		sp++;
		tp = strchr(sp + 1, '`');
		if(tp == NULL)
			tp = sp;
		else {
			*tp = 0;
			tp++;
		}
		c_unescape_str(tp);
		add_hotspot(tp, /* hungry: */false, column, column + strlen(sp) - 1, row);
rswindell's avatar
rswindell committed
119 120 121 122
		bputs(sp);
		return len;
	}

123
	disp_len=len;
124 125 126
	if((p = strchr(sp, '|')) != NULL) {
		if(strchr(p, 'T') != NULL)
			thousep = true;
127 128
		if(strchr(p, 'U') != NULL)
			uppercase = true;
129
		if(strchr(p, 'L') != NULL)
130
			align = left;
131
		else if(strchr(p, 'R') != NULL)
132
			align = right;
133
		else if(strchr(p, 'C') != NULL)
134
			align = center;
135 136 137 138 139 140 141 142
		else if(strchr(p, 'W') != NULL)
			doubled = true;
		else if(strchr(p, 'Z') != NULL)
			zero_padded = true;
		else if(strchr(p, '>') != NULL)
			truncated = false;
	}
	else if(strchr(sp, ':') != NULL)
143 144
		p = NULL;
	else if((p=strstr(sp,"-L"))!=NULL)
145
		align = left;
146
	else if((p=strstr(sp,"-R"))!=NULL)
147
		align = right;
148
	else if((p=strstr(sp,"-C"))!=NULL)
149
		align = center;
rswindell's avatar
rswindell committed
150
	else if((p=strstr(sp,"-W"))!=NULL)	/* wide */
rswindell's avatar
rswindell committed
151
		doubled=true;
152 153
	else if((p=strstr(sp,"-Z"))!=NULL)
		zero_padded=true;
154 155
	else if((p=strstr(sp,"-T"))!=NULL)
		thousep=true;
156 157
	else if((p=strstr(sp,"-U"))!=NULL)
		uppercase=true;
rswindell's avatar
rswindell committed
158
	else if((p=strstr(sp,"->"))!=NULL)	/* wrap */
rswindell's avatar
rswindell committed
159
		truncated = false;
160
	if(p!=NULL) {
161
		char* lp = p;
162
		lp++;	// skip the '|' or '-'
163
		while(*lp == '>'|| IS_ALPHA(*lp))
164 165 166
			lp++;
		if(*lp)
			width_specified = true;
167
		while(*lp && !IS_DIGIT(*lp))
168
			lp++;
169
		if(*lp && IS_DIGIT(*lp)) {
rswindell's avatar
rswindell committed
170
			disp_len=atoi(lp);
171 172
			width_specified = true;
		}
173
		*p=0;
174
	}
175

176
	cp = atcode(sp, str2, sizeof(str2), &pmode, align == center, obj);
177
	if(cp==NULL)
178 179
		return(0);

180 181 182 183
	char separated[128];
	if(thousep)
		cp = separate_thousands(cp, separated, sizeof(separated), ',');

184
	char upper[128];
185 186 187 188 189 190
	if(uppercase) {
		SAFECOPY(upper, cp);
		strupr(upper);
		cp = upper;
	}

191
	if(p==NULL || truncated == false || (width_specified == false && align == none))
rswindell's avatar
rswindell committed
192 193
		disp_len = strlen(cp);

194 195 196
	if(uppercase && align == none)
		align = left;

197
	if(truncated && strchr(cp, '\n') == NULL) {
rswindell's avatar
rswindell committed
198 199 200 201 202 203 204
		if(column + disp_len > cols - 1) {
			if(column >= cols - 1)
				disp_len = 0;
			else
				disp_len = (cols - 1) - column;
		}
	}
205 206 207 208 209 210
	if(pmode & P_UTF8) {
		if(term_supports(UTF8))
			disp_len += strlen(cp) - utf8_str_total_width(cp);
		else
			disp_len += strlen(cp) - utf8_str_count_width(cp, /* min: */1, /* max: */2);
	}
211
	if(align == left)
212
		bprintf(pmode, "%-*.*s",disp_len,disp_len,cp);
213
	else if(align == right)
214
		bprintf(pmode, "%*.*s",disp_len,disp_len,cp);
215
	else if(align == center) {
rswindell's avatar
rswindell committed
216
		int vlen = strlen(cp);
217 218
		if(vlen < disp_len) {
			int left = (disp_len - vlen) / 2;
219
			bprintf(pmode, "%*s%-*s", left, "", disp_len - left, cp);
220
		} else
221
			bprintf(pmode, "%.*s", disp_len, cp);
rswindell's avatar
rswindell committed
222 223
	} else if(doubled) {
		wide(cp);
224
	} else if(zero_padded) {
225 226
		int vlen = strlen(cp);
		if(vlen < disp_len)
227
			bprintf(pmode, "%-.*s%s", (int)(disp_len - strlen(cp)), "0000000000", cp);
228
		else
229
			bprintf(pmode, "%.*s", disp_len, cp);
230
	} else
231
		bprintf(pmode, "%.*s", disp_len, cp);
232 233 234 235

	return(len);
}

rswindell's avatar
rswindell committed
236 237 238 239 240 241 242 243 244
static const char* getpath(scfg_t* cfg, const char* path)
{
	for(int i = 0; i < cfg->total_dirs; i++) {
		if(stricmp(cfg->dir[i]->code, path) == 0)
			return cfg->dir[i]->path;
	}
	return path;
}

245
const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, long* pmode, bool centered, JSObject* obj)
246
{
247
	char*	tp = NULL;
248
	uint	i;
249 250
	uint	ugrp;
	uint	usub;
251 252 253 254 255 256 257
	long	l;
    stats_t stats;
    node_t  node;
	struct	tm tm;

	str[0]=0;

258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
	if(strcmp(sp, "SHOW") == 0) {
		console &= ~CON_ECHO_OFF;
		return nulstr;
	}
	if(strncmp(sp, "SHOW:", 5) == 0) {
		uchar* ar = arstr(NULL, sp + 5, &cfg, NULL);
		if(ar != NULL) {
			if(!chk_ar(ar, &useron, &client))
				console |= CON_ECHO_OFF;
			else
				console &= ~CON_ECHO_OFF;
			free(ar);
		}
		return nulstr;
	}

rswindell's avatar
rswindell committed
274 275 276
	if(strcmp(sp, "HOT") == 0) { // Auto-mouse hot-spot attribute
		hot_attr = curatr;
		return nulstr;
rswindell's avatar
rswindell committed
277
	}
278
	if(strncmp(sp, "HOT:", 4) == 0) {	// Auto-mouse hot-spot attribute
rswindell's avatar
rswindell committed
279
		sp += 4;
280 281 282 283 284 285 286 287 288
		if(stricmp(sp, "hungry") == 0) {
			hungry_hotspots = true;
			hot_attr = curatr;
		}
		else if(stricmp(sp, "strict") == 0) {
			hungry_hotspots = false;
			hot_attr = curatr;
		}
		else if(stricmp(sp, "off") == 0)
rswindell's avatar
rswindell committed
289 290 291
			hot_attr = 0;
		else
			hot_attr = attrstr(sp);
292 293
		return nulstr;
	}
294 295 296 297
	if(strcmp(sp, "CLEAR_HOT") == 0) {
		clear_hotspots();
		return nulstr;
	}
298

299 300
	if(strncmp(sp, "U+", 2) == 0) {	// UNICODE
		enum unicode_codepoint codepoint = (enum unicode_codepoint)strtoul(sp + 2, &tp, 16);
rswindell's avatar
rswindell committed
301
		if(tp == NULL || *tp == 0)
302
			outchar(codepoint, unicode_to_cp437(codepoint));
rswindell's avatar
rswindell committed
303 304
		else if(*tp == ':')
			outchar(codepoint, tp + 1);
305 306
		else {
			char fallback = (char)strtoul(tp + 1, NULL, 16);
rswindell's avatar
rswindell committed
307
			if(*tp == ',')
308 309 310 311 312 313 314 315 316 317 318 319
				outchar(codepoint, fallback);
			else if(*tp == '!') {
				char ch = unicode_to_cp437(codepoint);
				if(ch != 0)
					fallback = ch;
				outchar(codepoint, fallback);
			}
			else return NULL; // Invalid @-code
		}
		return nulstr;
	}

320 321 322 323 324
	if(strcmp(sp, "CHECKMARK") == 0) {
		outchar(UNICODE_CHECK_MARK, CP437_CHECK_MARK);
		return nulstr;
	}

rswindell's avatar
rswindell committed
325 326 327 328
	if(strcmp(sp, "ELLIPSIS") == 0) {
		outchar(UNICODE_HORIZONTAL_ELLIPSIS, "...");
		return nulstr;
	}
329
	if(strcmp(sp, "COPY") == 0) {
rswindell's avatar
rswindell committed
330 331 332 333 334 335 336
		outchar(UNICODE_COPYRIGHT_SIGN, "(C)");
		return nulstr;
	}
	if(strcmp(sp, "SOUNDCOPY") == 0) {
		outchar(UNICODE_SOUND_RECORDING_COPYRIGHT, "(P)");
		return nulstr;
	}
rswindell's avatar
rswindell committed
337 338 339 340
	if(strcmp(sp, "REGISTERED") == 0) {
		outchar(UNICODE_REGISTERED_SIGN, "(R)");
		return nulstr;
	}
rswindell's avatar
rswindell committed
341 342 343 344 345
	if(strcmp(sp, "TRADEMARK") == 0) {
		outchar(UNICODE_TRADE_MARK_SIGN, "(TM)");
		return nulstr;
	}
	if(strcmp(sp, "DEGREE_C") == 0) {
rswindell's avatar
rswindell committed
346
		outchar(UNICODE_DEGREE_CELSIUS, "\xF8""C");
rswindell's avatar
rswindell committed
347 348 349
		return nulstr;
	}
	if(strcmp(sp, "DEGREE_F") == 0) {
rswindell's avatar
rswindell committed
350
		outchar(UNICODE_DEGREE_FAHRENHEIT, "\xF8""F");
rswindell's avatar
rswindell committed
351 352 353
		return nulstr;
	}

rswindell's avatar
rswindell committed
354 355 356 357 358
	if(strncmp(sp, "WIDE:", 5) == 0) {
		wide(sp + 5);
		return(nulstr);
	}

359
	if(!strcmp(sp,"VER"))
360
		return(VERSION);
361

362
	if(!strcmp(sp,"REV")) {
363
		safe_snprintf(str,maxlen,"%c",REVISION);
364 365
		return(str);
	}
366

367
	if(!strcmp(sp,"FULL_VER")) {
368
		safe_snprintf(str,maxlen,"%s%c%s",VERSION,REVISION,beta_version);
369
		truncsp(str);
370
#if defined(_DEBUG)
371
		strcat(str," Debug");
372
#endif
373
		return(str);
374 375
	}

376
	if(!strcmp(sp,"VER_NOTICE"))
377
		return(VERSION_NOTICE);
378

379 380
	if(!strcmp(sp,"OS_VER"))
		return(os_version(str));
381 382

#ifdef JAVASCRIPT
383 384
	if(!strcmp(sp,"JS_VER"))
		return((char *)JS_GetImplementationVersion());
385 386
#endif

387 388
	if(!strcmp(sp,"PLATFORM"))
		return(PLATFORM_DESC);
389

390 391
	if(!strcmp(sp,"COPYRIGHT"))
		return(COPYRIGHT_NOTICE);
392

393
	if(!strcmp(sp,"COMPILER")) {
394 395 396
		char compiler[32];
		DESCRIBE_COMPILER(compiler);
		strncpy(str, compiler, maxlen);
397
		return(str);
398 399
	}

400 401 402 403 404 405
	if(strcmp(sp, "GIT_HASH") == 0)
		return git_hash;

	if(strcmp(sp, "GIT_BRANCH") == 0)
		return git_branch;

406
	if(!strcmp(sp,"UPTIME")) {
407
		extern volatile time_t uptime;
408 409 410 411
		time_t up=0;
		now = time(NULL);
		if (uptime != 0 && now >= uptime)
			up = now-uptime;
412 413
		char   days[64]="";
		if((up/(24*60*60))>=2) {
414
	        sprintf(days,"%lu days ",(ulong)(up/(24L*60L*60L)));
415 416
			up%=(24*60*60);
		}
417
		safe_snprintf(str,maxlen,"%s%lu:%02lu"
418
	        ,days
419 420
			,(ulong)(up/(60L*60L))
			,(ulong)((up/60L)%60L)
421
			);
422
		return(str);
423 424
	}

425
	if(!strcmp(sp,"SERVED")) {
426
		extern volatile ulong served;
427
		safe_snprintf(str,maxlen,"%lu",served);
428 429 430
		return(str);
	}

431
	if(!strcmp(sp,"SOCKET_LIB"))
432
		return(socklib_version(str,SOCKLIB_DESC));
433

434
	if(!strcmp(sp,"MSG_LIB")) {
435
		safe_snprintf(str,maxlen,"SMBLIB %s",smb_lib_ver());
436 437
		return(str);
	}
438

439 440
	if(!strcmp(sp,"BBS") || !strcmp(sp,"BOARDNAME"))
		return(cfg.sys_name);
441

442
	if(!strcmp(sp,"BAUD") || !strcmp(sp,"BPS")) {
443
		safe_snprintf(str,maxlen,"%lu",cur_output_rate ? cur_output_rate : cur_rate);
444 445
		return(str);
	}
446

rswindell's avatar
rswindell committed
447 448 449 450 451 452 453 454
	if(!strcmp(sp,"COLS")) {
		safe_snprintf(str,maxlen,"%lu",cols);
		return(str);
	}
	if(!strcmp(sp,"ROWS")) {
		safe_snprintf(str,maxlen,"%lu",rows);
		return(str);
	}
455 456 457 458 459
	if(strcmp(sp,"TERM") == 0)
		return term_type();

	if(strcmp(sp,"CHARSET") == 0)
		return term_charset();
rswindell's avatar
rswindell committed
460

461 462
	if(!strcmp(sp,"CONN"))
		return(connection);
463

464 465
	if(!strcmp(sp,"SYSOP"))
		return(cfg.sys_op);
466

467 468 469
	if(strcmp(sp, "SYSAVAIL") == 0)
		return text[sysop_available(&cfg) ? LiSysopAvailable : LiSysopNotAvailable];

470 471 472
	if(strcmp(sp, "SYSAVAILYN") == 0)
		return text[sysop_available(&cfg) ? Yes : No];

473 474
	if(!strcmp(sp,"LOCATION"))
		return(cfg.sys_location);
475

476
	if(strcmp(sp,"NODE") == 0 || strcmp(sp,"NN") == 0) {
477
		safe_snprintf(str,maxlen,"%u",cfg.node_num);
478 479
		return(str);
	}
480
	if(strcmp(sp, "TNODES") == 0 || strcmp(sp, "TNODE") == 0 || strcmp(sp, "TN") == 0) {
481
		safe_snprintf(str,maxlen,"%u",cfg.sys_nodes);
482 483
		return(str);
	}
484 485 486 487 488 489 490 491
	if(strcmp(sp, "ANODES") == 0 || strcmp(sp, "ANODE") == 0 || strcmp(sp, "AN") == 0) {
		safe_snprintf(str, maxlen, "%u", count_nodes(/* self: */true));
		return str;
	}
	if(strcmp(sp, "ONODES") == 0 || strcmp(sp, "ONODE") == 0 || strcmp(sp, "ON") == 0) {
		safe_snprintf(str, maxlen, "%u", count_nodes(/* self: */false));
		return str;
	}
492

rswindell's avatar
rswindell committed
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
	if(strcmp(sp, "PWDAYS") == 0) {
		if(cfg.sys_pwdays) {
			safe_snprintf(str, maxlen, "%u", cfg.sys_pwdays);
			return str;
		}
		return text[Unlimited];
	}

	if(strcmp(sp, "AUTODEL") == 0) {
		if(cfg.sys_autodel) {
			safe_snprintf(str, maxlen, "%u", cfg.sys_autodel);
			return str;
		}
		return text[Unlimited];
	}

509 510 511 512 513 514
	if(strcmp(sp, "PAGER") == 0)
		return (thisnode.misc&NODE_POFF) ? text[Off] : text[On];

	if(strcmp(sp, "ALERTS") == 0)
		return (thisnode.misc&NODE_AOFF) ? text[Off] : text[On];

515 516 517
	if(strcmp(sp, "SPLITP") == 0)
		return (useron.chat&CHAT_SPLITP) ? text[On] : text[Off];

518 519
	if(!strcmp(sp,"INETADDR"))
		return(cfg.sys_inetaddr);
520

521
	if(!strcmp(sp,"HOSTNAME"))
522
		return server_host_name();
523

524
	if(!strcmp(sp,"FIDOADDR")) {
525
		if(cfg.total_faddrs)
526
			return(smb_faddrtoa(&cfg.faddr[0],str));
527
		return(nulstr);
528 529
	}

530
	if(!strcmp(sp,"EMAILADDR"))
531
		return(usermailaddr(&cfg, str
532
			,cfg.inetmail_misc&NMAIL_ALIAS ? useron.alias : useron.name));
533

534 535 536 537 538 539 540 541 542
	if(strcmp(sp, "NETMAIL") == 0)
		return useron.netmail;

	if(strcmp(sp, "FWD") == 0)
		return (useron.misc & NETMAIL) ? text[On] : text[Off];

	if(strcmp(sp, "TMP") == 0)
		return useron.tmpext;

543 544
	if(!strcmp(sp,"QWKID"))
		return(cfg.sys_id);
545

546
	if(!strcmp(sp,"TIME") || !strcmp(sp,"SYSTIME")) {
547
		now=time(NULL);
548
		memset(&tm,0,sizeof(tm));
549
		localtime_r(&now,&tm);
550
		if(cfg.sys_misc&SM_MILITARY)
551 552
			safe_snprintf(str,maxlen,"%02d:%02d:%02d"
		        	,tm.tm_hour,tm.tm_min,tm.tm_sec);
553
		else
554
			safe_snprintf(str,maxlen,"%02d:%02d %s"
555 556 557
				,tm.tm_hour==0 ? 12
				: tm.tm_hour>12 ? tm.tm_hour-12
				: tm.tm_hour, tm.tm_min, tm.tm_hour>11 ? "pm":"am");
558
		return(str);
559 560
	}

rswindell's avatar
rswindell committed
561 562 563
	if(!strcmp(sp,"TIMEZONE"))
		return(smb_zonestr(sys_timezone(&cfg),str));

564
	if(!strcmp(sp,"DATE") || !strcmp(sp,"SYSDATE")) {
565
		return(unixtodstr(&cfg,time32(NULL),str));
566
	}
567

568 569 570 571 572 573 574 575 576 577
	if(strncmp(sp, "DATE:", 5) == 0 || strncmp(sp, "TIME:", 5) == 0) {
		sp += 5;
		c_unescape_str(sp);
		now = time(NULL);
		memset(&tm, 0, sizeof(tm));
		localtime_r(&now, &tm);
		strftime(str, maxlen, sp, &tm);
		return str;
	}

rswindell's avatar
rswindell committed
578 579 580
	if(!strcmp(sp,"DATETIME"))
		return(timestr(time(NULL)));

rswindell's avatar
rswindell committed
581 582 583 584 585
	if(!strcmp(sp,"DATETIMEZONE")) {
		char zone[32];
		safe_snprintf(str, maxlen, "%s %s", timestr(time(NULL)), smb_zonestr(sys_timezone(&cfg),zone));
		return str;
	}
586 587 588 589
	
	if(strcmp(sp, "DATEFMT") == 0) {
		return cfg.sys_misc&SM_EURODATE ? "DD/MM/YY" : "MM/DD/YY";
	}
rswindell's avatar
rswindell committed
590

591
	if(strcmp(sp, "BDATEFMT") == 0 || strcmp(sp, "BIRTHFMT") == 0) {
592 593 594
		return birthdate_format(&cfg);
	}

595
	if(!strcmp(sp,"TMSG")) {
596 597
		l=0;
		for(i=0;i<cfg.total_subs;i++)
598
			l+=getposts(&cfg,i); 		/* l=total posts */
599
		safe_snprintf(str,maxlen,"%lu",l);
600 601
		return(str);
	}
602

603
	if(!strcmp(sp,"TUSER")) {
604
		safe_snprintf(str,maxlen,"%u",total_users(&cfg));
605 606
		return(str);
	}
607

608
	if(!strcmp(sp,"TFILE")) {
609 610
		l=0;
		for(i=0;i<cfg.total_dirs;i++)
611
			l+=getfiles(&cfg,i);
612
		safe_snprintf(str,maxlen,"%lu",l);
613 614
		return(str);
	}
615

616
	if(strncmp(sp, "FILES:", 6) == 0) {	// Number of files in specified directory
rswindell's avatar
rswindell committed
617
		const char* path = getpath(&cfg, sp + 6);
618
		safe_snprintf(str, maxlen, "%lu", getfilecount(path));
rswindell's avatar
rswindell committed
619
		return str;
620 621 622 623 624 625 626
	}

	if(strcmp(sp, "FILES") == 0) {	// Number of files in current directory
		safe_snprintf(str, maxlen, "%lu", (ulong)getfiles(&cfg, usrdir[curlib][curdir[curlib]]));
		return str;
	}

rswindell's avatar
rswindell committed
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686
	if(strncmp(sp, "FILESIZE:", 9) == 0) {
		const char* path = getpath(&cfg, sp + 9);
		byte_estimate_to_str(getfilesizetotal(path), str, maxlen, /* unit: */1, /* precision: */1);
		return str;
	}

	if(strcmp(sp, "FILESIZE") == 0) {
		byte_estimate_to_str(getfilesizetotal(cfg.dir[usrdir[curlib][curdir[curlib]]]->path)
			,str, maxlen, /* unit: */1, /* precision: */1);
		return str;
	}

	if(strncmp(sp, "FILEBYTES:", 10) == 0) {	// Number of bytes in current file directory
		const char* path = getpath(&cfg, sp + 10);
		safe_snprintf(str, maxlen, "%" PRIu64, getfilesizetotal(path));
		return str;
	}

	if(strcmp(sp, "FILEBYTES") == 0) {	// Number of bytes in current file directory
		safe_snprintf(str, maxlen, "%" PRIu64
			,getfilesizetotal(cfg.dir[usrdir[curlib][curdir[curlib]]]->path));
		return str;
	}

	if(strncmp(sp, "FILEKB:", 7) == 0) {	// Number of kibibytes in current file directory
		const char* path = getpath(&cfg, sp + 7);
		safe_snprintf(str, maxlen, "%1.1f", getfilesizetotal(path) / 1024.0);
		return str;
	}

	if(strcmp(sp, "FILEKB") == 0) {	// Number of kibibytes in current file directory
		safe_snprintf(str, maxlen, "%1.1f"
			,getfilesizetotal(cfg.dir[usrdir[curlib][curdir[curlib]]]->path) / 1024.0);
		return str;
	}

	if(strncmp(sp, "FILEMB:", 7) == 0) {	// Number of mebibytes in current file directory
		const char* path = getpath(&cfg, sp + 7);
		safe_snprintf(str, maxlen, "%1.1f", getfilesizetotal(path) / (1024.0 * 1024.0));
		return str;
	}

	if(strcmp(sp, "FILEMB") == 0) {	// Number of mebibytes in current file directory
		safe_snprintf(str, maxlen, "%1.1f"
			,getfilesizetotal(cfg.dir[usrdir[curlib][curdir[curlib]]]->path) / (1024.0 * 1024.0));
		return str;
	}

	if(strncmp(sp, "FILEGB:", 7) == 0) {	// Number of gibibytes in current file directory
		const char* path = getpath(&cfg, sp + 7);
		safe_snprintf(str, maxlen, "%1.1f", getfilesizetotal(path) / (1024.0 * 1024.0 * 1024.0));
		return str;
	}

	if(strcmp(sp, "FILEGB") == 0) {	// Number of gibibytes in current file directory
		safe_snprintf(str, maxlen, "%1.1f"
			,getfilesizetotal(cfg.dir[usrdir[curlib][curdir[curlib]]]->path) / (1024.0 * 1024.0 * 1024.0));
		return str;
	}

687
	if(!strcmp(sp,"TCALLS") || !strcmp(sp,"NUMCALLS")) {
688
		getstats(&cfg,0,&stats);
689
		safe_snprintf(str,maxlen,"%lu", (ulong)stats.logons);
690 691
		return(str);
	}
692

693
	if(!strcmp(sp,"PREVON") || !strcmp(sp,"LASTCALLERNODE")
694
		|| !strcmp(sp,"LASTCALLERSYSTEM"))
695
		return(lastuseron);
696

rswindell's avatar
rswindell committed
697
	if(!strcmp(sp,"CLS") || !strcmp(sp,"CLEAR")) {
698
		CLS;
699 700
		return(nulstr);
	}
701

702 703 704 705 706
	if(strcmp(sp, "GETKEY") == 0) {
		getkey();
		return(nulstr);
	}

Rob Swindell's avatar
Rob Swindell committed
707 708 709 710 711 712 713
	if(strcmp(sp, "CONTINUE") == 0) {
		char ch = getkey(K_UPPER);
		if(ch == text[YNQP][1] || ch == text[YNQP][2])
			sys_status|=SS_ABORT;
		return(nulstr);
	}

714
	if(strncmp(sp, "WAIT:", 5) == 0) {
Rob Swindell's avatar
Rob Swindell committed
715
		inkey(K_NONE, atoi(sp + 5) * 100);
716 717 718
		return(nulstr);
	}

719
	if(!strcmp(sp,"PAUSE") || !strcmp(sp,"MORE")) {
720
		pause();
721 722
		return(nulstr);
	}
723

724
	if(!strcmp(sp,"RESETPAUSE")) {
725
		lncntr=0;
726 727
		return(nulstr);
	}
728

729
	if(!strcmp(sp,"NOPAUSE") || !strcmp(sp,"POFF")) {
730
		sys_status^=SS_PAUSEOFF;
731 732
		return(nulstr);
	}
733

734
	if(!strcmp(sp,"PON") || !strcmp(sp,"AUTOMORE")) {
735
		sys_status^=SS_PAUSEON;
736 737
		return(nulstr);
	}
738

rswindell's avatar
rswindell committed
739 740
	if(strncmp(sp, "FILL:", 5) == 0) {
		sp += 5;
741 742
		long margin = centered ? column : 1;
		if(margin < 1) margin = 1;
743
		c_unescape_str(sp);
744
		while(*sp && online && column < cols - margin)
rswindell's avatar
rswindell committed
745 746 747 748
			bputs(sp, P_TRUNCATE);
		return nulstr;
	}

749 750 751 752 753
	if(strncmp(sp, "POS:", 4) == 0) {	// PCBoard	(nn is 1 based)
		i = atoi(sp + 4);
		if(i >= 1)	// Convert to 0-based
			i--;
		for(l = i - column; l > 0; l--)
754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
			outchar(' ');
		return nulstr;
	}

	if(strncmp(sp, "DELAY:", 6) == 0) {	// PCBoard
		mswait(atoi(sp + 6) * 100);
		return nulstr;
	}

	if(strcmp(sp, "YESCHAR") == 0) {	// PCBoard
		safe_snprintf(str, maxlen, "%c", text[YNQP][0]);
		return str;
	}

	if(strcmp(sp, "NOCHAR") == 0) {		// PCBoard
		safe_snprintf(str, maxlen, "%c", text[YNQP][1]);
		return str;
	}

	if(strcmp(sp, "QUITCHAR") == 0) {
		safe_snprintf(str, maxlen, "%c", text[YNQP][2]);
		return str;
	}

778 779 780 781 782
	if(strncmp(sp, "BPS:", 4) == 0) {
		set_output_rate((enum output_rate)atoi(sp + 4));
		return nulstr;
	}

783 784 785 786 787 788 789
	if(strncmp(sp, "TEXT:", 5) == 0) {
		i = atoi(sp + 5);
		if(i >= 1 && i <= TOTAL_TEXT)
			return text[i - 1];
		return nulstr;
	}

790 791 792 793
	/* NOSTOP */

	/* STOP */

794 795
	if(!strcmp(sp,"BELL") || !strcmp(sp,"BEEP"))
		return("\a");
796

797 798 799
	if(!strcmp(sp,"EVENT")) {
		if(event_time==0)
			return("<none>");
800
		return(timestr(event_time));
801
	}
802 803 804

	/* LASTCALL */

805
	if(!strncmp(sp,"NODE",4)) {
806 807 808
		i=atoi(sp+4);
		if(i && i<=cfg.sys_nodes) {
			getnodedat(i,&node,0);
809 810
			printnodedat(i,&node);
		}
811 812
		return(nulstr);
	}
813

814
	if(!strcmp(sp,"WHO")) {
815
		whos_online(true);
816 817
		return(nulstr);
	}
818 819 820

	/* User Codes */

821 822
	if(!strcmp(sp,"USER") || !strcmp(sp,"ALIAS") || !strcmp(sp,"NAME"))
		return(useron.alias);
823

824
	if(!strcmp(sp,"FIRST")) {
825
		safe_snprintf(str,maxlen,"%s",useron.alias);
826
		tp=strchr(str,' ');
827
		if(tp) *tp=0;
828
		return(str);
829
	}
830

831
	if(!strcmp(sp,"USERNUM")) {
832
		safe_snprintf(str,maxlen,"%u",useron.number);
833 834
		return(str);
	}
835

836
	if(!strcmp(sp,"PHONE") || !strcmp(sp,"HOMEPHONE")
837
		|| !strcmp(sp,"DATAPHONE") || !strcmp(sp,"DATA"))
838
		return(useron.phone);
839

840 841
	if(!strcmp(sp,"ADDR1"))
		return(useron.address);
842

843 844
	if(!strcmp(sp,"FROM"))
		return(useron.location);
845

846
	if(!strcmp(sp,"CITY")) {
847
		safe_snprintf(str,maxlen,"%s",useron.location);
848
		char* p=strchr(str,',');
849 850
		if(p) {
			*p=0;
851 852
			return(str);
		}
853 854
		return(nulstr);
	}
855

856 857
	if(!strcmp(sp,"STATE")) {
		char* p=strchr(useron.location,',');
858 859
		if(p) {
			p++;
860
			if(*p==' ')
861
				p++;
862 863
			return(p);
		}
864 865
		return(nulstr);
	}
866

867 868
	if(!strcmp(sp,"CPU"))
		return(useron.comp);