atcodes.cpp 47.7 KB
Newer Older
1
/* Synchronet "@code" functions */
2
// vi: tabstop=4
3

4
/* $Id: atcodes.cpp,v 1.142 2020/05/10 20:12:35 rswindell Exp $ */
5
6
7
8
9

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
10
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
11
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
 *																			*
 * 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"
39
40
#include "utf8.h"
#include "unicode.h"
41
#include "cp437defs.h"
42

43
44
45
46
47
48
49
#if defined(_WINSOCKAPI_)
	extern WSADATA WSAData;
	#define SOCKLIB_DESC WSAData.szDescription
#else
	#define	SOCKLIB_DESC NULL
#endif

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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;
	while(*tail && isdigit(*tail))
		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;
}

71
72
73
/****************************************************************************/
/* Returns 0 if invalid @ code. Returns length of @ code if valid.          */
/****************************************************************************/
74
int sbbs_t::show_atcode(const char *instr, JSObject* obj)
75
{
76
	char	str[128],str2[128],*tp,*sp,*p;
77
    int     len;
78
	int		disp_len;
79
80
81
82
83
84
	enum {
		none,
		left,
		right,
		center
	} align = none;
85
	bool	zero_padded=false;
rswindell's avatar
rswindell committed
86
	bool	truncated = true;
rswindell's avatar
rswindell committed
87
	bool	doubled = false;
88
	bool	thousep = false;	// thousands-separated
89
	bool	uppercase = false;
90
	bool	width_specified = false;
91
	long	pmode = 0;
92
	const char *cp;
93

94
95
	if(*instr != '@')
		return 0;
96
	SAFECOPY(str,instr);
97
98
99
	tp=strchr(str+1,'@');
	if(!tp)                 /* no terminating @ */
		return(0);
100
	sp=strchr(str+1,' ');
101
102
103
104
105
106
	if(sp && sp<tp)         /* space before terminating @ */
		return(0);
	len=(tp-str)+1;
	(*tp)=0;
	sp=(str+1);

107
	if(*sp == '~' && *(sp + 1)) {	// Mouse hot-spot (hungry)
rswindell's avatar
rswindell committed
108
109
110
111
112
113
114
115
116
		sp++;
		tp = strchr(sp + 1, '~');
		if(tp == NULL)
			tp = sp;
		else {
			*tp = 0;
			tp++;
		}
		c_unescape_str(tp);
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
		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
133
134
135
136
		bputs(sp);
		return len;
	}

137
	disp_len=len;
138
139
140
	if((p = strchr(sp, '|')) != NULL) {
		if(strchr(p, 'T') != NULL)
			thousep = true;
141
142
		if(strchr(p, 'U') != NULL)
			uppercase = true;
143
		if(strchr(p, 'L') != NULL)
144
			align = left;
145
		else if(strchr(p, 'R') != NULL)
146
			align = right;
147
		else if(strchr(p, 'C') != NULL)
148
			align = center;
149
150
151
152
153
154
155
156
		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)
157
158
		p = NULL;
	else if((p=strstr(sp,"-L"))!=NULL)
159
		align = left;
160
	else if((p=strstr(sp,"-R"))!=NULL)
161
		align = right;
162
	else if((p=strstr(sp,"-C"))!=NULL)
163
		align = center;
rswindell's avatar
rswindell committed
164
	else if((p=strstr(sp,"-W"))!=NULL)	/* wide */
rswindell's avatar
rswindell committed
165
		doubled=true;
166
167
	else if((p=strstr(sp,"-Z"))!=NULL)
		zero_padded=true;
168
169
	else if((p=strstr(sp,"-T"))!=NULL)
		thousep=true;
170
171
	else if((p=strstr(sp,"-U"))!=NULL)
		uppercase=true;
rswindell's avatar
rswindell committed
172
	else if((p=strstr(sp,"->"))!=NULL)	/* wrap */
rswindell's avatar
rswindell committed
173
		truncated = false;
174
	if(p!=NULL) {
175
		char* lp = p;
176
177
178
179
180
		lp++;	// skip the '|' or '-'
		while(*lp == '>'|| isalpha((uchar)*lp))
			lp++;
		if(*lp)
			width_specified = true;
181
		while(*lp && !isdigit((uchar)*lp))
182
			lp++;
183
		if(*lp && isdigit((uchar)*lp)) {
rswindell's avatar
rswindell committed
184
			disp_len=atoi(lp);
185
186
			width_specified = true;
		}
187
		*p=0;
188
	}
189

190
	cp = atcode(sp, str2, sizeof(str2), &pmode, align == center, obj);
191
	if(cp==NULL)
192
193
		return(0);

194
195
196
197
	char separated[128];
	if(thousep)
		cp = separate_thousands(cp, separated, sizeof(separated), ',');

198
199
200
201
202
203
204
	if(uppercase) {
		char upper[128];
		SAFECOPY(upper, cp);
		strupr(upper);
		cp = upper;
	}

205
	if(p==NULL || truncated == false || (width_specified == false && align == none))
rswindell's avatar
rswindell committed
206
207
		disp_len = strlen(cp);

208
209
210
	if(uppercase && align == none)
		align = left;

rswindell's avatar
rswindell committed
211
212
213
214
215
216
217
218
	if(truncated) {
		if(column + disp_len > cols - 1) {
			if(column >= cols - 1)
				disp_len = 0;
			else
				disp_len = (cols - 1) - column;
		}
	}
219
220
221
222
223
224
	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);
	}
225
	if(align == left)
226
		bprintf(pmode, "%-*.*s",disp_len,disp_len,cp);
227
	else if(align == right)
228
		bprintf(pmode, "%*.*s",disp_len,disp_len,cp);
229
	else if(align == center) {
rswindell's avatar
rswindell committed
230
		int vlen = strlen(cp);
231
232
		if(vlen < disp_len) {
			int left = (disp_len - vlen) / 2;
233
			bprintf(pmode, "%*s%-*s", left, "", disp_len - left, cp);
234
		} else
235
			bprintf(pmode, "%.*s", disp_len, cp);
rswindell's avatar
rswindell committed
236
237
	} else if(doubled) {
		wide(cp);
238
	} else if(zero_padded) {
239
240
		int vlen = strlen(cp);
		if(vlen < disp_len)
241
			bprintf(pmode, "%-.*s%s", (int)(disp_len - strlen(cp)), "0000000000", cp);
242
		else
243
			bprintf(pmode, "%.*s", disp_len, cp);
244
	} else
245
		bprintf(pmode, "%.*s", disp_len, cp);
246
247
248
249

	return(len);
}

rswindell's avatar
rswindell committed
250
251
252
253
254
255
256
257
258
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;
}

259
const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, long* pmode, bool centered, JSObject* obj)
260
{
261
	char*	tp = NULL;
262
	uint	i;
263
264
	uint	ugrp;
	uint	usub;
265
266
267
268
269
270
271
	long	l;
    stats_t stats;
    node_t  node;
	struct	tm tm;

	str[0]=0;

rswindell's avatar
rswindell committed
272
273
274
	if(strcmp(sp, "HOT") == 0) { // Auto-mouse hot-spot attribute
		hot_attr = curatr;
		return nulstr;
rswindell's avatar
rswindell committed
275
	}
276
	if(strncmp(sp, "HOT:", 4) == 0) {	// Auto-mouse hot-spot attribute
rswindell's avatar
rswindell committed
277
		sp += 4;
278
279
280
281
282
283
284
285
286
		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
287
288
289
			hot_attr = 0;
		else
			hot_attr = attrstr(sp);
290
291
		return nulstr;
	}
292
293
294
295
	if(strcmp(sp, "CLEAR_HOT") == 0) {
		clear_hotspots();
		return nulstr;
	}
296

297
298
	if(strncmp(sp, "U+", 2) == 0) {	// UNICODE
		enum unicode_codepoint codepoint = (enum unicode_codepoint)strtoul(sp + 2, &tp, 16);
rswindell's avatar
rswindell committed
299
		if(tp == NULL || *tp == 0)
300
			outchar(codepoint, unicode_to_cp437(codepoint));
rswindell's avatar
rswindell committed
301
302
		else if(*tp == ':')
			outchar(codepoint, tp + 1);
303
304
		else {
			char fallback = (char)strtoul(tp + 1, NULL, 16);
rswindell's avatar
rswindell committed
305
			if(*tp == ',')
306
307
308
309
310
311
312
313
314
315
316
317
				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;
	}

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

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

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

357
	if(!strcmp(sp,"VER"))
358
		return(VERSION);
359

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

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

374
	if(!strcmp(sp,"VER_NOTICE"))
375
		return(VERSION_NOTICE);
376

377
378
	if(!strcmp(sp,"OS_VER"))
		return(os_version(str));
379
380

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

385
386
	if(!strcmp(sp,"PLATFORM"))
		return(PLATFORM_DESC);
387

388
389
	if(!strcmp(sp,"COPYRIGHT"))
		return(COPYRIGHT_NOTICE);
390

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

398
	if(!strcmp(sp,"UPTIME")) {
399
		extern volatile time_t uptime;
400
401
402
403
		time_t up=0;
		now = time(NULL);
		if (uptime != 0 && now >= uptime)
			up = now-uptime;
404
405
		char   days[64]="";
		if((up/(24*60*60))>=2) {
406
	        sprintf(days,"%lu days ",(ulong)(up/(24L*60L*60L)));
407
408
			up%=(24*60*60);
		}
409
		safe_snprintf(str,maxlen,"%s%lu:%02lu"
410
	        ,days
411
412
			,(ulong)(up/(60L*60L))
			,(ulong)((up/60L)%60L)
413
			);
414
		return(str);
415
416
	}

417
	if(!strcmp(sp,"SERVED")) {
418
		extern volatile ulong served;
419
		safe_snprintf(str,maxlen,"%lu",served);
420
421
422
		return(str);
	}

423
	if(!strcmp(sp,"SOCKET_LIB"))
424
		return(socklib_version(str,SOCKLIB_DESC));
425

426
	if(!strcmp(sp,"MSG_LIB")) {
427
		safe_snprintf(str,maxlen,"SMBLIB %s",smb_lib_ver());
428
429
		return(str);
	}
430

431
432
	if(!strcmp(sp,"BBS") || !strcmp(sp,"BOARDNAME"))
		return(cfg.sys_name);
433

434
	if(!strcmp(sp,"BAUD") || !strcmp(sp,"BPS")) {
435
		safe_snprintf(str,maxlen,"%lu",cur_output_rate ? cur_output_rate : cur_rate);
436
437
		return(str);
	}
438

rswindell's avatar
rswindell committed
439
440
441
442
443
444
445
446
	if(!strcmp(sp,"COLS")) {
		safe_snprintf(str,maxlen,"%lu",cols);
		return(str);
	}
	if(!strcmp(sp,"ROWS")) {
		safe_snprintf(str,maxlen,"%lu",rows);
		return(str);
	}
447
448
449
450
451
	if(strcmp(sp,"TERM") == 0)
		return term_type();

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

453
454
	if(!strcmp(sp,"CONN"))
		return(connection);
455

456
457
	if(!strcmp(sp,"SYSOP"))
		return(cfg.sys_op);
458

459
460
461
	if(strcmp(sp, "SYSAVAIL") == 0)
		return text[sysop_available(&cfg) ? LiSysopAvailable : LiSysopNotAvailable];

462
463
	if(!strcmp(sp,"LOCATION"))
		return(cfg.sys_location);
464

465
	if(strcmp(sp,"NODE") == 0 || strcmp(sp,"NN") == 0) {
466
		safe_snprintf(str,maxlen,"%u",cfg.node_num);
467
468
		return(str);
	}
469
	if(strcmp(sp, "TNODES") == 0 || strcmp(sp, "TNODE") == 0 || strcmp(sp, "TN") == 0) {
470
		safe_snprintf(str,maxlen,"%u",cfg.sys_nodes);
471
472
		return(str);
	}
473
474
475
476
477
478
479
480
	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;
	}
481

rswindell's avatar
rswindell committed
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
	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];
	}

498
499
500
501
502
503
	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];

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

507
508
	if(!strcmp(sp,"INETADDR"))
		return(cfg.sys_inetaddr);
509

510
	if(!strcmp(sp,"HOSTNAME"))
511
		return server_host_name();
512

513
	if(!strcmp(sp,"FIDOADDR")) {
514
		if(cfg.total_faddrs)
515
			return(smb_faddrtoa(&cfg.faddr[0],str));
516
		return(nulstr);
517
518
	}

519
	if(!strcmp(sp,"EMAILADDR"))
520
		return(usermailaddr(&cfg, str
521
			,cfg.inetmail_misc&NMAIL_ALIAS ? useron.alias : useron.name));
522

523
524
	if(!strcmp(sp,"QWKID"))
		return(cfg.sys_id);
525

526
	if(!strcmp(sp,"TIME") || !strcmp(sp,"SYSTIME")) {
527
		now=time(NULL);
528
		memset(&tm,0,sizeof(tm));
529
		localtime_r(&now,&tm);
530
		if(cfg.sys_misc&SM_MILITARY)
531
532
			safe_snprintf(str,maxlen,"%02d:%02d:%02d"
		        	,tm.tm_hour,tm.tm_min,tm.tm_sec);
533
		else
534
			safe_snprintf(str,maxlen,"%02d:%02d %s"
535
536
537
				,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");
538
		return(str);
539
540
	}

rswindell's avatar
rswindell committed
541
542
543
	if(!strcmp(sp,"TIMEZONE"))
		return(smb_zonestr(sys_timezone(&cfg),str));

544
	if(!strcmp(sp,"DATE") || !strcmp(sp,"SYSDATE")) {
545
		return(unixtodstr(&cfg,time32(NULL),str));
546
	}
547

rswindell's avatar
rswindell committed
548
549
550
	if(!strcmp(sp,"DATETIME"))
		return(timestr(time(NULL)));

rswindell's avatar
rswindell committed
551
552
553
554
555
	if(!strcmp(sp,"DATETIMEZONE")) {
		char zone[32];
		safe_snprintf(str, maxlen, "%s %s", timestr(time(NULL)), smb_zonestr(sys_timezone(&cfg),zone));
		return str;
	}
556
557
558
559
	
	if(strcmp(sp, "DATEFMT") == 0) {
		return cfg.sys_misc&SM_EURODATE ? "DD/MM/YY" : "MM/DD/YY";
	}
rswindell's avatar
rswindell committed
560

561
	if(!strcmp(sp,"TMSG")) {
562
563
		l=0;
		for(i=0;i<cfg.total_subs;i++)
564
			l+=getposts(&cfg,i); 		/* l=total posts */
565
		safe_snprintf(str,maxlen,"%lu",l);
566
567
		return(str);
	}
568

569
	if(!strcmp(sp,"TUSER")) {
570
		safe_snprintf(str,maxlen,"%u",total_users(&cfg));
571
572
		return(str);
	}
573

574
	if(!strcmp(sp,"TFILE")) {
575
576
		l=0;
		for(i=0;i<cfg.total_dirs;i++)
577
			l+=getfiles(&cfg,i);
578
		safe_snprintf(str,maxlen,"%lu",l);
579
580
		return(str);
	}
581

582
	if(strncmp(sp, "FILES:", 6) == 0) {	// Number of files in specified directory
rswindell's avatar
rswindell committed
583
		const char* path = getpath(&cfg, sp + 6);
584
		safe_snprintf(str, maxlen, "%lu", getfilecount(path));
rswindell's avatar
rswindell committed
585
		return str;
586
587
588
589
590
591
592
	}

	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
593
594
595
596
597
598
599
600
601
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
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
	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;
	}

653
	if(!strcmp(sp,"TCALLS") || !strcmp(sp,"NUMCALLS")) {
654
		getstats(&cfg,0,&stats);
655
		safe_snprintf(str,maxlen,"%lu", (ulong)stats.logons);
656
657
		return(str);
	}
658

659
	if(!strcmp(sp,"PREVON") || !strcmp(sp,"LASTCALLERNODE")
660
		|| !strcmp(sp,"LASTCALLERSYSTEM"))
661
		return(lastuseron);
662

rswindell's avatar
rswindell committed
663
	if(!strcmp(sp,"CLS") || !strcmp(sp,"CLEAR")) {
664
		CLS;
665
666
		return(nulstr);
	}
667

668
	if(!strcmp(sp,"PAUSE") || !strcmp(sp,"MORE")) {
669
		pause();
670
671
		return(nulstr);
	}
672

673
	if(!strcmp(sp,"RESETPAUSE")) {
674
		lncntr=0;
675
676
		return(nulstr);
	}
677

678
	if(!strcmp(sp,"NOPAUSE") || !strcmp(sp,"POFF")) {
679
		sys_status^=SS_PAUSEOFF;
680
681
		return(nulstr);
	}
682

683
	if(!strcmp(sp,"PON") || !strcmp(sp,"AUTOMORE")) {
684
		sys_status^=SS_PAUSEON;
685
686
		return(nulstr);
	}
687

rswindell's avatar
rswindell committed
688
689
	if(strncmp(sp, "FILL:", 5) == 0) {
		sp += 5;
690
691
692
		long margin = centered ? column : 1;
		if(margin < 1) margin = 1;
		while(*sp && online && column < cols - margin)
rswindell's avatar
rswindell committed
693
694
695
696
			bputs(sp, P_TRUNCATE);
		return nulstr;
	}

697
698
699
700
701
	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--)
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
			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;
	}

726
727
728
729
730
	if(strncmp(sp, "BPS:", 4) == 0) {
		set_output_rate((enum output_rate)atoi(sp + 4));
		return nulstr;
	}

731
732
733
734
	/* NOSTOP */

	/* STOP */

735
736
	if(!strcmp(sp,"BELL") || !strcmp(sp,"BEEP"))
		return("\a");
737

738
739
740
	if(!strcmp(sp,"EVENT")) {
		if(event_time==0)
			return("<none>");
741
		return(timestr(event_time));
742
	}
743
744
745

	/* LASTCALL */

746
	if(!strncmp(sp,"NODE",4)) {
747
748
749
		i=atoi(sp+4);
		if(i && i<=cfg.sys_nodes) {
			getnodedat(i,&node,0);
750
751
			printnodedat(i,&node);
		}
752
753
		return(nulstr);
	}
754

755
	if(!strcmp(sp,"WHO")) {
756
		whos_online(true);
757
758
		return(nulstr);
	}
759
760
761

	/* User Codes */

762
763
	if(!strcmp(sp,"USER") || !strcmp(sp,"ALIAS") || !strcmp(sp,"NAME"))
		return(useron.alias);
764

765
	if(!strcmp(sp,"FIRST")) {
766
		safe_snprintf(str,maxlen,"%s",useron.alias);
767
		tp=strchr(str,' ');
768
		if(tp) *tp=0;
769
		return(str);
770
	}
771

772
	if(!strcmp(sp,"USERNUM")) {
773
		safe_snprintf(str,maxlen,"%u",useron.number);
774
775
		return(str);
	}
776

777
	if(!strcmp(sp,"PHONE") || !strcmp(sp,"HOMEPHONE")
778
		|| !strcmp(sp,"DATAPHONE") || !strcmp(sp,"DATA"))
779
		return(useron.phone);
780

781
782
	if(!strcmp(sp,"ADDR1"))
		return(useron.address);
783

784
785
	if(!strcmp(sp,"FROM"))
		return(useron.location);
786

787
	if(!strcmp(sp,"CITY")) {
788
		safe_snprintf(str,maxlen,"%s",useron.location);
789
		char* p=strchr(str,',');
790
791
		if(p) {
			*p=0;
792
793
			return(str);
		}
794
795
		return(nulstr);
	}
796

797
798
	if(!strcmp(sp,"STATE")) {
		char* p=strchr(useron.location,',');
799
800
		if(p) {
			p++;
801
			if(*p==' ')
802
				p++;
803
804
			return(p);
		}
805
806
		return(nulstr);
	}
807

808
809
	if(!strcmp(sp,"CPU"))
		return(useron.comp);
810

811
812
	if(!strcmp(sp,"HOST"))
		return(client_name);
813

814
815
	if(!strcmp(sp,"BDATE"))
		return(useron.birth);
816

817
	if(!strcmp(sp,"AGE")) {
818
		safe_snprintf(str,maxlen,"%u",getage(&cfg,useron.birth));
819
820
		return(str);
	}
rswindell's avatar
rswindell committed
821

822
	if(!strcmp(sp,"CALLS") || !strcmp(sp,"NUMTIMESON")) {
823
		safe_snprintf(str,maxlen,"%u",useron.logons);
824
825
		return(str);
	}
826

rswindell's avatar
rswindell committed
827
828
829
830
831
832
833
	if(strcmp(sp, "PWAGE") == 0) {
		time_t age = time(NULL) - useron.pwmod;
		safe_snprintf(str, maxlen, "%ld", (long)(age/(24*60*60)));
		return str;
	}

	if(strcmp(sp, "PWDATE") == 0 || strcmp(sp, "MEMO") == 0)
834
		return(unixtodstr(&cfg,useron.pwmod,str));
835

836
	if(!strcmp(sp,"SEC") || !strcmp(sp,"SECURITY")) {
837
		safe_snprintf(str,maxlen,"%u",useron.level);
838
839
		return(str);
	}
840

841
842
	if(!strcmp(sp,"SINCE"))
		return(unixtodstr(&cfg,useron.firston,str));
843

844
	if(!strcmp(sp,"TIMEON") || !strcmp(sp,"TIMEUSED")) {
845
		now=time(NULL);
846
		safe_snprintf(str,maxlen,"%lu",(ulong)(now-logontime)/60L);
847
848
		return(str);
	}
849

850
	if(!strcmp(sp,"TUSED")) {              /* Synchronet only */
851
		now=time(NULL);
852
		return(sectostr((uint)(now-logontime),str)+1);
853
	}
854

855
	if(!strcmp(sp,"TLEFT")) {              /* Synchronet only */
856
		gettimeleft();
857
		return(sectostr(timeleft,str)+1);
858
	}
859

860
	if(!strcmp(sp,"TPERD"))                /* Synchronet only */
rswindell's avatar
Add:    
rswindell committed
861
		return(sectostr(cfg.level_timeperday[useron.level],str)+4);
862

863
	if(!strcmp(sp,"TPERC"))                /* Synchronet only */
rswindell's avatar
Add:    
rswindell committed
864
		return(sectostr(cfg.level_timepercall[useron.level],str)+4);
865

rswindell's avatar
Add:    
rswindell committed
866
	if(strcmp(sp, "MPERC") == 0 || strcmp(sp, "TIMELIMIT") == 0) {
867
		safe_snprintf(str,maxlen,"%u",cfg.level_timepercall[useron.level]);
868
869
		return(str);
	}
870

rswindell's avatar
Add:    
rswindell committed
871
872
873
874
875
	if(strcmp(sp, "MPERD") == 0) {
		safe_snprintf(str,maxlen,"%u",cfg.level_timeperday[useron.level]);
		return str;
	}

rswindell's avatar
Add:    
rswindell committed
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
	if(strcmp(sp, "MAXCALLS") == 0) {
		safe_snprintf(str,maxlen,"%u",cfg.level_callsperday[useron.level]);
		return str;
	}

	if(strcmp(sp, "MAXPOSTS") == 0) {
		safe_snprintf(str,maxlen,"%u",cfg.level_postsperday[useron.level]);
		return str;
	}

	if(strcmp(sp, "MAXMAILS") == 0) {
		safe_snprintf(str,maxlen,"%u",cfg.level_emailperday[useron.level]);
		return str;
	}

	if(strcmp(sp, "MAXLINES") == 0) {
		safe_snprintf(str,maxlen,"%u",cfg.level_linespermsg[useron.level]);
		return str;
	}

896
	if(!strcmp(sp,"MINLEFT") || !strcmp(sp,"LEFT") || !strcmp(sp,"TIMELEFT")) {
897
		gettimeleft();
898
		safe_snprintf(str,maxlen,"%lu",timeleft/60);
899
900
		return(str);
	}
901

902
	if(!strcmp(sp,"LASTON"))
903
		return(timestr(useron.laston));
904

905
906
	if(!strcmp(sp,"LASTDATEON"))
		return(unixtodstr(&cfg,useron.laston,str));
907

908
	if(!strcmp(sp,"LASTTIMEON")) {
909
		memset(&tm,0,sizeof(tm));
910
		localtime32(&useron.laston,&tm);
911
912
913
914
915
916
917
918
		if(cfg.sys_misc&SM_MILITARY)
			safe_snprintf(str,maxlen,"%02d:%02d:%02d"
				,tm.tm_hour, tm.tm_min, tm.tm_sec);
		else
			safe_snprintf(str,maxlen,"%02d:%02d %s"
				,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");
919
		return(str);
920
921
	}

rswindell's avatar
rswindell committed
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
	if(!strcmp(sp,"FIRSTON"))
		return(timestr(useron.firston));

	if(!strcmp(sp,"FIRSTDATEON"))
		return(unixtodstr(&cfg,useron.firston,str));

	if(!strcmp(sp,"FIRSTTIMEON")) {
		memset(&tm,0,sizeof(tm));
		localtime32(&useron.firston,&tm);
		if(cfg.sys_misc&SM_MILITARY)
			safe_snprintf(str,maxlen,"%02d:%02d:%02d"
				,tm.tm_hour, tm.tm_min, tm.tm_sec);
		else
			safe_snprintf(str,maxlen,"%02d:%02d %s"
				,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");
		return(str);
	}

942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
	if(strcmp(sp, "EMAILS") == 0) {
		safe_snprintf(str, maxlen, "%u", useron.emails);
		return str;
	}

	if(strcmp(sp, "FBACKS") == 0) {
		safe_snprintf(str, maxlen, "%u", useron.fbacks);
		return str;
	}

	if(strcmp(sp, "ETODAY") == 0) {
		safe_snprintf(str, maxlen, "%u", useron.etoday);
		return str;
	}

	if(strcmp(sp, "PTODAY") == 0) {
		safe_snprintf(str, maxlen, "%u", useron.ptoday);
		return str;
	}

	if(strcmp(sp, "LTODAY") == 0) {
		safe_snprintf(str, maxlen, "%u", useron.ltoday);
		return str;
	}

rswindell's avatar
Add:    
rswindell committed
967
	if(strcmp(sp, "MTODAY") == 0) {
968