atcodes.cpp 52.7 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
124
125
126
127
128
129
	// @!x@ for Ctrl-A x equivalent(s) */
	if(*sp == '!') {
		for(p = sp + 1; *p != '\0' && *p != '@'; p++)
			ctrl_a(*p);
		return len;
	}

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

183
	cp = atcode(sp, str2, sizeof(str2), &pmode, align == center, obj);
184
	if(cp==NULL)
185
186
		return(0);

187
188
189
190
	char separated[128];
	if(thousep)
		cp = separate_thousands(cp, separated, sizeof(separated), ',');

191
	char upper[128];
192
193
194
195
196
197
	if(uppercase) {
		SAFECOPY(upper, cp);
		strupr(upper);
		cp = upper;
	}

198
	if(p==NULL || truncated == false || (width_specified == false && align == none))
rswindell's avatar
rswindell committed
199
200
		disp_len = strlen(cp);

201
202
203
	if(uppercase && align == none)
		align = left;

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

	return(len);
}

rswindell's avatar
rswindell committed
243
244
245
246
247
248
249
250
251
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;
}

252
const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, long* pmode, bool centered, JSObject* obj)
253
{
254
	char*	tp = NULL;
255
	uint	i;
256
257
	uint	ugrp;
	uint	usub;
258
259
260
261
262
263
264
	long	l;
    stats_t stats;
    node_t  node;
	struct	tm tm;

	str[0]=0;

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

306
307
	if(strncmp(sp, "U+", 2) == 0) {	// UNICODE
		enum unicode_codepoint codepoint = (enum unicode_codepoint)strtoul(sp + 2, &tp, 16);
rswindell's avatar
rswindell committed
308
		if(tp == NULL || *tp == 0)
309
			outchar(codepoint, unicode_to_cp437(codepoint));
rswindell's avatar
rswindell committed
310
311
		else if(*tp == ':')
			outchar(codepoint, tp + 1);
312
313
		else {
			char fallback = (char)strtoul(tp + 1, NULL, 16);
rswindell's avatar
rswindell committed
314
			if(*tp == ',')
315
316
317
318
319
320
321
322
323
324
325
326
				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;
	}

327
328
329
330
331
	if(strcmp(sp, "CHECKMARK") == 0) {
		outchar(UNICODE_CHECK_MARK, CP437_CHECK_MARK);
		return nulstr;
	}

rswindell's avatar
rswindell committed
332
333
334
335
	if(strcmp(sp, "ELLIPSIS") == 0) {
		outchar(UNICODE_HORIZONTAL_ELLIPSIS, "...");
		return nulstr;
	}
336
	if(strcmp(sp, "COPY") == 0) {
rswindell's avatar
rswindell committed
337
338
339
340
341
342
343
		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
344
345
346
347
	if(strcmp(sp, "REGISTERED") == 0) {
		outchar(UNICODE_REGISTERED_SIGN, "(R)");
		return nulstr;
	}
rswindell's avatar
rswindell committed
348
349
350
351
352
	if(strcmp(sp, "TRADEMARK") == 0) {
		outchar(UNICODE_TRADE_MARK_SIGN, "(TM)");
		return nulstr;
	}
	if(strcmp(sp, "DEGREE_C") == 0) {
rswindell's avatar
rswindell committed
353
		outchar(UNICODE_DEGREE_CELSIUS, "\xF8""C");
rswindell's avatar
rswindell committed
354
355
356
		return nulstr;
	}
	if(strcmp(sp, "DEGREE_F") == 0) {
rswindell's avatar
rswindell committed
357
		outchar(UNICODE_DEGREE_FAHRENHEIT, "\xF8""F");
rswindell's avatar
rswindell committed
358
359
360
		return nulstr;
	}

rswindell's avatar
rswindell committed
361
362
363
364
365
	if(strncmp(sp, "WIDE:", 5) == 0) {
		wide(sp + 5);
		return(nulstr);
	}

366
	if(!strcmp(sp,"VER"))
367
		return(VERSION);
368

369
	if(!strcmp(sp,"REV")) {
370
		safe_snprintf(str,maxlen,"%c",REVISION);
371
372
		return(str);
	}
373

374
	if(!strcmp(sp,"FULL_VER")) {
375
		safe_snprintf(str,maxlen,"%s%c%s",VERSION,REVISION,beta_version);
376
		truncsp(str);
377
#if defined(_DEBUG)
378
		strcat(str," Debug");
379
#endif
380
		return(str);
381
382
	}

383
	if(!strcmp(sp,"VER_NOTICE"))
384
		return(VERSION_NOTICE);
385

386
387
	if(!strcmp(sp,"OS_VER"))
		return(os_version(str));
388
389

#ifdef JAVASCRIPT
390
391
	if(!strcmp(sp,"JS_VER"))
		return((char *)JS_GetImplementationVersion());
392
393
#endif

394
395
	if(!strcmp(sp,"PLATFORM"))
		return(PLATFORM_DESC);
396

397
398
	if(!strcmp(sp,"COPYRIGHT"))
		return(COPYRIGHT_NOTICE);
399

400
	if(!strcmp(sp,"COMPILER")) {
401
402
403
		char compiler[32];
		DESCRIBE_COMPILER(compiler);
		strncpy(str, compiler, maxlen);
404
		return(str);
405
406
	}

407
408
409
410
411
412
	if(strcmp(sp, "GIT_HASH") == 0)
		return git_hash;

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

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

432
	if(!strcmp(sp,"SERVED")) {
433
		extern volatile ulong served;
434
		safe_snprintf(str,maxlen,"%lu",served);
435
436
437
		return(str);
	}

438
	if(!strcmp(sp,"SOCKET_LIB"))
439
		return(socklib_version(str,SOCKLIB_DESC));
440

441
	if(!strcmp(sp,"MSG_LIB")) {
442
		safe_snprintf(str,maxlen,"SMBLIB %s",smb_lib_ver());
443
444
		return(str);
	}
445

446
447
	if(!strcmp(sp,"BBS") || !strcmp(sp,"BOARDNAME"))
		return(cfg.sys_name);
448

449
	if(!strcmp(sp,"BAUD") || !strcmp(sp,"BPS")) {
450
		safe_snprintf(str,maxlen,"%lu",cur_output_rate ? cur_output_rate : cur_rate);
451
452
		return(str);
	}
453

rswindell's avatar
rswindell committed
454
455
456
457
458
459
460
461
	if(!strcmp(sp,"COLS")) {
		safe_snprintf(str,maxlen,"%lu",cols);
		return(str);
	}
	if(!strcmp(sp,"ROWS")) {
		safe_snprintf(str,maxlen,"%lu",rows);
		return(str);
	}
462
463
464
465
466
	if(strcmp(sp,"TERM") == 0)
		return term_type();

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

468
469
	if(!strcmp(sp,"CONN"))
		return(connection);
470

471
472
	if(!strcmp(sp,"SYSOP"))
		return(cfg.sys_op);
473

474
475
476
	if(strcmp(sp, "SYSAVAIL") == 0)
		return text[sysop_available(&cfg) ? LiSysopAvailable : LiSysopNotAvailable];

477
478
479
	if(strcmp(sp, "SYSAVAILYN") == 0)
		return text[sysop_available(&cfg) ? Yes : No];

480
481
	if(!strcmp(sp,"LOCATION"))
		return(cfg.sys_location);
482

483
	if(strcmp(sp,"NODE") == 0 || strcmp(sp,"NN") == 0) {
484
		safe_snprintf(str,maxlen,"%u",cfg.node_num);
485
486
		return(str);
	}
487
	if(strcmp(sp, "TNODES") == 0 || strcmp(sp, "TNODE") == 0 || strcmp(sp, "TN") == 0) {
488
		safe_snprintf(str,maxlen,"%u",cfg.sys_nodes);
489
490
		return(str);
	}
491
492
493
494
495
496
497
498
	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;
	}
499

rswindell's avatar
rswindell committed
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
	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];
	}

516
517
518
519
520
521
	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];

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

525
526
	if(!strcmp(sp,"INETADDR"))
		return(cfg.sys_inetaddr);
527

528
	if(!strcmp(sp,"HOSTNAME"))
529
		return server_host_name();
530

531
	if(!strcmp(sp,"FIDOADDR")) {
532
		if(cfg.total_faddrs)
533
			return(smb_faddrtoa(&cfg.faddr[0],str));
534
		return(nulstr);
535
536
	}

537
	if(!strcmp(sp,"EMAILADDR"))
538
		return(usermailaddr(&cfg, str
539
			,(cfg.inetmail_misc&NMAIL_ALIAS) || (useron.rest&FLAG('O')) ? useron.alias : useron.name));
540

541
542
543
544
545
546
547
548
549
	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;

Rob Swindell's avatar
Rob Swindell committed
550
551
552
553
554
555
556
557
	if(strcmp(sp, "SEX") == 0) {
		safe_snprintf(str, maxlen, "%c", useron.sex);
		return str;
	}
	if(strcmp(sp, "GENDERS") == 0) {
		return cfg.new_genders;
	}

558
559
	if(!strcmp(sp,"QWKID"))
		return(cfg.sys_id);
560

561
	if(!strcmp(sp,"TIME") || !strcmp(sp,"SYSTIME")) {
562
		now=time(NULL);
563
		memset(&tm,0,sizeof(tm));
564
		localtime_r(&now,&tm);
565
		if(cfg.sys_misc&SM_MILITARY)
566
567
			safe_snprintf(str,maxlen,"%02d:%02d:%02d"
		        	,tm.tm_hour,tm.tm_min,tm.tm_sec);
568
		else
569
			safe_snprintf(str,maxlen,"%02d:%02d %s"
570
571
572
				,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");
573
		return(str);
574
575
	}

rswindell's avatar
rswindell committed
576
577
578
	if(!strcmp(sp,"TIMEZONE"))
		return(smb_zonestr(sys_timezone(&cfg),str));

579
	if(!strcmp(sp,"DATE") || !strcmp(sp,"SYSDATE")) {
580
		return(unixtodstr(&cfg,time32(NULL),str));
581
	}
582

583
584
585
586
587
588
589
590
591
592
	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
593
594
595
	if(!strcmp(sp,"DATETIME"))
		return(timestr(time(NULL)));

rswindell's avatar
rswindell committed
596
597
598
599
600
	if(!strcmp(sp,"DATETIMEZONE")) {
		char zone[32];
		safe_snprintf(str, maxlen, "%s %s", timestr(time(NULL)), smb_zonestr(sys_timezone(&cfg),zone));
		return str;
	}
601
602
603
604
	
	if(strcmp(sp, "DATEFMT") == 0) {
		return cfg.sys_misc&SM_EURODATE ? "DD/MM/YY" : "MM/DD/YY";
	}
rswindell's avatar
rswindell committed
605

606
	if(strcmp(sp, "BDATEFMT") == 0 || strcmp(sp, "BIRTHFMT") == 0) {
607
608
609
		return birthdate_format(&cfg);
	}

610
611
612
	if(strcmp(sp, "GENDERS") == 0)
		return cfg.new_genders;

613
	if(!strcmp(sp,"TMSG")) {
614
615
		l=0;
		for(i=0;i<cfg.total_subs;i++)
616
			l+=getposts(&cfg,i); 		/* l=total posts */
617
		safe_snprintf(str,maxlen,"%lu",l);
618
619
		return(str);
	}
620

621
	if(!strcmp(sp,"TUSER")) {
622
		safe_snprintf(str,maxlen,"%u",total_users(&cfg));
623
624
		return(str);
	}
625

626
	if(!strcmp(sp,"TFILE")) {
627
628
		l=0;
		for(i=0;i<cfg.total_dirs;i++)
629
			l+=getfiles(&cfg,i);
630
		safe_snprintf(str,maxlen,"%lu",l);
631
632
		return(str);
	}
633

634
	if(strncmp(sp, "FILES:", 6) == 0) {	// Number of files in specified directory
rswindell's avatar
rswindell committed
635
		const char* path = getpath(&cfg, sp + 6);
636
		safe_snprintf(str, maxlen, "%lu", getfilecount(path));
rswindell's avatar
rswindell committed
637
		return str;
638
639
640
641
642
643
644
	}

	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
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
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
	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;
	}

705
	if(!strcmp(sp,"TCALLS") || !strcmp(sp,"NUMCALLS")) {
706
		getstats(&cfg,0,&stats);
707
		safe_snprintf(str,maxlen,"%lu", (ulong)stats.logons);
708
709
		return(str);
	}
710

711
	if(!strcmp(sp,"PREVON") || !strcmp(sp,"LASTCALLERNODE")
712
		|| !strcmp(sp,"LASTCALLERSYSTEM"))
713
		return(lastuseron);
714

rswindell's avatar
rswindell committed
715
	if(!strcmp(sp,"CLS") || !strcmp(sp,"CLEAR")) {
716
		CLS;
717
718
		return(nulstr);
	}
719

720
721
722
723
724
	if(strcmp(sp, "GETKEY") == 0) {
		getkey();
		return(nulstr);
	}

Rob Swindell's avatar
Rob Swindell committed
725
726
	if(strcmp(sp, "CONTINUE") == 0) {
		char ch = getkey(K_UPPER);
727
		if(ch == no_key() || ch == quit_key())
Rob Swindell's avatar
Rob Swindell committed
728
729
730
731
			sys_status|=SS_ABORT;
		return(nulstr);
	}

732
	if(strncmp(sp, "WAIT:", 5) == 0) {
Rob Swindell's avatar
Rob Swindell committed
733
		inkey(K_NONE, atoi(sp + 5) * 100);
734
735
736
		return(nulstr);
	}

737
	if(!strcmp(sp,"PAUSE") || !strcmp(sp,"MORE")) {
738
		pause();
739
740
		return(nulstr);
	}
741

742
	if(!strcmp(sp,"RESETPAUSE")) {
743
		lncntr=0;
744
745
		return(nulstr);
	}
746

747
	if(!strcmp(sp,"NOPAUSE") || !strcmp(sp,"POFF")) {
748
		sys_status^=SS_PAUSEOFF;
749
750
		return(nulstr);
	}
751

752
	if(!strcmp(sp,"PON") || !strcmp(sp,"AUTOMORE")) {
753
		sys_status^=SS_PAUSEON;
754
755
		return(nulstr);
	}
756

rswindell's avatar
rswindell committed
757
758
	if(strncmp(sp, "FILL:", 5) == 0) {
		sp += 5;
759
760
		long margin = centered ? column : 1;
		if(margin < 1) margin = 1;
761
		c_unescape_str(sp);
762
		while(*sp && online && column < cols - margin)
rswindell's avatar
rswindell committed
763
764
765
766
			bputs(sp, P_TRUNCATE);
		return nulstr;
	}

767
768
769
770
771
	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--)
772
773
774
775
776
777
778
779
780
781
			outchar(' ');
		return nulstr;
	}

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

	if(strcmp(sp, "YESCHAR") == 0) {	// PCBoard
782
		safe_snprintf(str, maxlen, "%c", yes_key());
783
784
785
786
		return str;
	}

	if(strcmp(sp, "NOCHAR") == 0) {		// PCBoard
787
		safe_snprintf(str, maxlen, "%c", no_key());
788
789
790
791
		return str;
	}

	if(strcmp(sp, "QUITCHAR") == 0) {
792
		safe_snprintf(str, maxlen, "%c", quit_key());
793
794
795
		return str;
	}

796
797
798
799
800
	if(strncmp(sp, "BPS:", 4) == 0) {
		set_output_rate((enum output_rate)atoi(sp + 4));
		return nulstr;
	}

801
802
803
804
805
806
807
	if(strncmp(sp, "TEXT:", 5) == 0) {
		i = atoi(sp + 5);
		if(i >= 1 && i <= TOTAL_TEXT)
			return text[i - 1];
		return nulstr;
	}

808
809
810
811
	/* NOSTOP */

	/* STOP */

812
813
	if(!strcmp(sp,"BELL") || !strcmp(sp,"BEEP"))
		return("\a");
814

815
816
817
	if(!strcmp(sp,"EVENT")) {
		if(event_time==0)
			return("<none>");
818
		return(timestr(event_time));
819
	}
820
821
822

	/* LASTCALL */

823
	if(!strncmp(sp,"NODE",4)) {
824
825
826
		i=atoi(sp+4);
		if(i && i<=cfg.sys_nodes) {
			getnodedat(i,&node,0);
827
828
			printnodedat(i,&node);
		}
829
830
		return(nulstr);
	}
831

832
	if(!strcmp(sp,"WHO")) {
833
		whos_online(true);
834
835
		return(nulstr);
	}
836
837
838

	/* User Codes */

839
840
	if(!strcmp(sp,"USER") || !strcmp(sp,"ALIAS") || !strcmp(sp,"NAME"))
		return(useron.alias);
841

842
	if(!strcmp(sp,"FIRST")) {
843
		safe_snprintf(str,maxlen,"%s",useron.alias);
844
		tp=strchr(str,' ');
845
		if(tp) *tp=0;
846
		return(str);
847
	}
848

849
	if(!strcmp(sp,"USERNUM")) {
850
		safe_snprintf(str,maxlen,"%u",useron.number);
851
852
		return(str);
	}
853

854
	if(!strcmp(sp,"PHONE") || !strcmp(sp,"HOMEPHONE")
855
		|| !strcmp(sp,"DATAPHONE") || !strcmp(sp,"DATA"))
856
		return(useron.phone);
857

858
859
	if(!strcmp(sp,"ADDR1"))
		return(useron.address);
860

861
862
	if(!strcmp(sp,"FROM"))
		return(useron.location);
863

864
	if(!strcmp(sp,"CITY")) {
865
		safe_snprintf(str,maxlen,"%s",useron.location);
866
		char* p=strchr(str,',');
867
868
		if(p) {
			*p=0;
869
870
			return(str);
		}
871
872
		return(nulstr);
	}
873

874
875
	if(!strcmp(sp,"STATE")) {
		char* p=strchr(useron.location,',');
876
877
		if(p) {
			p++;
878
			if(*p==' ')
879
				p++;
880
881
			return(p);
		}
882
883
		return(nulstr);
	}
884

885
886
	if(!strcmp(sp,"CPU"))
		return(useron.comp);
887

888
889
	if(!strcmp(sp,"HOST"))
		return(client_name);
890

891
	if(!strcmp(sp,"BDATE"))
892
		return getbirthdstr(&cfg, useron.birth, str, maxlen);
893

894
895
896
897
	if(strcmp(sp, "BIRTH") == 0)
		return format_birthdate(&cfg, useron.birth, str, maxlen);

	if(strncmp(sp, "BDATE:", 6) == 0 || strncmp(sp, "BIRTH:", 6) == 0) {
898
899
900
901
902
903
904
905
906
907
908
		sp += 6;
		c_unescape_str(sp);
		memset(&tm,0,sizeof(tm));
		tm.tm_year = getbirthyear(useron.birth) - 1900;
		tm.tm_mon = getbirthmonth(&cfg, useron.birth) - 1;
		tm.tm_mday = getbirthday(&cfg, useron.birth);
		mktime(&tm);
		strftime(str, maxlen, sp, &tm);
		return str;
	}

909
	if(!strcmp(sp,"AGE")) {
910
		safe_snprintf(str,maxlen,"%u",getage(&cfg,useron.birth));
911
912
		return(str);
	}
rswindell's avatar
rswindell committed
913

914
	if(!strcmp(sp,"CALLS") || !strcmp(sp,"NUMTIMESON")) {
915
		safe_snprintf(str,maxlen,"%u",useron.logons);
916
917
		return(str);
	}
918

rswindell's avatar
rswindell committed
919
920
921
922
923
924
925
	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)
926
		return(unixtodstr(&cfg,useron.pwmod,str));
927

928
929
930
931
932
933
934
935
936
937
	if(strncmp(sp, "PWDATE:", 7) == 0) {
		sp += 7;
		c_unescape_str(sp);
		memset(&tm, 0, sizeof(tm));
		time_t date = useron.pwmod;
		localtime_r(&date, &tm);
		strftime(str, maxlen, sp, &tm);
		return str;
	}

938
	if(!strcmp(sp,"SEC") || !strcmp(sp,"SECURITY")) {
939
		safe_snprintf(str,maxlen,"%u",useron.level);
940
941
		return(str);
	}
942

943
944
	if(!strcmp(sp,"SINCE"))
		return(unixtodstr(&cfg,useron.firston,str));
945

946
947
948
949
950
951
952
953
954
955
	if(strncmp(sp, "SINCE:", 6) == 0) {
		sp += 6;
		c_unescape_str(sp);
		memset(&tm, 0, sizeof(tm));
		time_t date = useron.firston;
		localtime_r(&date, &tm);
		strftime(str, maxlen, sp, &tm);
		return str;
	}

956
	if(!strcmp(sp,"TIMEON") || !strcmp(sp,"TIMEUSED")) {
957
		now=time(NULL);
958
		safe_snprintf(str,maxlen,"%lu",(ulong)(now-logontime)/60L);
959
960
		return(str);
	}
961

962
	if(!strcmp(sp,"TUSED")) {              /* Synchronet only */
963
		now=time(NULL);
964
		return(sectostr((uint)(now-logontime),str)+1);
965
	}
966

967
	if(!strcmp(sp,"TLEFT")) {              /* Synchronet only */
968