atcodes.cpp 47.5 KB
Newer Older
1
/* Synchronet "@code" functions */
2
// vi: tabstop=4
3
4
5
6
7
8
9

/* $Id$ */

/****************************************************************************
 * @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
292
		return nulstr;
	}

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

314
315
316
317
318
	if(strcmp(sp, "CHECKMARK") == 0) {
		outchar(UNICODE_CHECK_MARK, CP437_CHECK_MARK);
		return nulstr;
	}

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

rswindell's avatar
rswindell committed
348
349
350
351
352
	if(strncmp(sp, "WIDE:", 5) == 0) {
		wide(sp + 5);
		return(nulstr);
	}

353
	if(!strcmp(sp,"VER"))
354
		return(VERSION);
355

356
	if(!strcmp(sp,"REV")) {
357
		safe_snprintf(str,maxlen,"%c",REVISION);
358
359
		return(str);
	}
360

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

370
	if(!strcmp(sp,"VER_NOTICE"))
371
		return(VERSION_NOTICE);
372

373
374
	if(!strcmp(sp,"OS_VER"))
		return(os_version(str));
375
376

#ifdef JAVASCRIPT
377
378
	if(!strcmp(sp,"JS_VER"))
		return((char *)JS_GetImplementationVersion());
379
380
#endif

381
382
	if(!strcmp(sp,"PLATFORM"))
		return(PLATFORM_DESC);
383

384
385
	if(!strcmp(sp,"COPYRIGHT"))
		return(COPYRIGHT_NOTICE);
386

387
	if(!strcmp(sp,"COMPILER")) {
388
389
390
		char compiler[32];
		DESCRIBE_COMPILER(compiler);
		strncpy(str, compiler, maxlen);
391
		return(str);
392
393
	}

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

413
	if(!strcmp(sp,"SERVED")) {
414
		extern volatile ulong served;
415
		safe_snprintf(str,maxlen,"%lu",served);
416
417
418
		return(str);
	}

419
	if(!strcmp(sp,"SOCKET_LIB"))
420
		return(socklib_version(str,SOCKLIB_DESC));
421

422
	if(!strcmp(sp,"MSG_LIB")) {
423
		safe_snprintf(str,maxlen,"SMBLIB %s",smb_lib_ver());
424
425
		return(str);
	}
426

427
428
	if(!strcmp(sp,"BBS") || !strcmp(sp,"BOARDNAME"))
		return(cfg.sys_name);
429

430
	if(!strcmp(sp,"BAUD") || !strcmp(sp,"BPS")) {
431
		safe_snprintf(str,maxlen,"%lu",cur_output_rate ? cur_output_rate : cur_rate);
432
433
		return(str);
	}
434

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

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

449
450
	if(!strcmp(sp,"CONN"))
		return(connection);
451

452
453
	if(!strcmp(sp,"SYSOP"))
		return(cfg.sys_op);
454

455
456
	if(!strcmp(sp,"LOCATION"))
		return(cfg.sys_location);
457

458
	if(strcmp(sp,"NODE") == 0 || strcmp(sp,"NN") == 0) {
459
		safe_snprintf(str,maxlen,"%u",cfg.node_num);
460
461
		return(str);
	}
462
	if(strcmp(sp, "TNODES") == 0 || strcmp(sp, "TNODE") == 0 || strcmp(sp, "TN") == 0) {
463
		safe_snprintf(str,maxlen,"%u",cfg.sys_nodes);
464
465
		return(str);
	}
466
467
468
469
470
471
472
473
	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;
	}
474

rswindell's avatar
rswindell committed
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
	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];
	}

491
492
493
494
495
496
	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];

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

500
501
	if(!strcmp(sp,"INETADDR"))
		return(cfg.sys_inetaddr);
502

503
504
505
	if(!strcmp(sp,"HOSTNAME"))
		return(startup->host_name);

506
	if(!strcmp(sp,"FIDOADDR")) {
507
		if(cfg.total_faddrs)
508
			return(smb_faddrtoa(&cfg.faddr[0],str));
509
		return(nulstr);
510
511
	}

512
	if(!strcmp(sp,"EMAILADDR"))
513
		return(usermailaddr(&cfg, str
514
			,cfg.inetmail_misc&NMAIL_ALIAS ? useron.alias : useron.name));
515

516
517
	if(!strcmp(sp,"QWKID"))
		return(cfg.sys_id);
518

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

rswindell's avatar
rswindell committed
534
535
536
	if(!strcmp(sp,"TIMEZONE"))
		return(smb_zonestr(sys_timezone(&cfg),str));

537
	if(!strcmp(sp,"DATE") || !strcmp(sp,"SYSDATE")) {
538
		return(unixtodstr(&cfg,time32(NULL),str));
539
	}
540

rswindell's avatar
rswindell committed
541
542
543
	if(!strcmp(sp,"DATETIME"))
		return(timestr(time(NULL)));

rswindell's avatar
rswindell committed
544
545
546
547
548
	if(!strcmp(sp,"DATETIMEZONE")) {
		char zone[32];
		safe_snprintf(str, maxlen, "%s %s", timestr(time(NULL)), smb_zonestr(sys_timezone(&cfg),zone));
		return str;
	}
549
550
551
552
	
	if(strcmp(sp, "DATEFMT") == 0) {
		return cfg.sys_misc&SM_EURODATE ? "DD/MM/YY" : "MM/DD/YY";
	}
rswindell's avatar
rswindell committed
553

554
	if(!strcmp(sp,"TMSG")) {
555
556
		l=0;
		for(i=0;i<cfg.total_subs;i++)
557
			l+=getposts(&cfg,i); 		/* l=total posts */
558
		safe_snprintf(str,maxlen,"%lu",l);
559
560
		return(str);
	}
561

562
	if(!strcmp(sp,"TUSER")) {
563
		safe_snprintf(str,maxlen,"%u",total_users(&cfg));
564
565
		return(str);
	}
566

567
	if(!strcmp(sp,"TFILE")) {
568
569
		l=0;
		for(i=0;i<cfg.total_dirs;i++)
570
			l+=getfiles(&cfg,i);
571
		safe_snprintf(str,maxlen,"%lu",l);
572
573
		return(str);
	}
574

575
	if(strncmp(sp, "FILES:", 6) == 0) {	// Number of files in specified directory
rswindell's avatar
rswindell committed
576
		const char* path = getpath(&cfg, sp + 6);
577
		safe_snprintf(str, maxlen, "%lu", getfilecount(path));
rswindell's avatar
rswindell committed
578
		return str;
579
580
581
582
583
584
585
	}

	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
586
587
588
589
590
591
592
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
	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;
	}

646
	if(!strcmp(sp,"TCALLS") || !strcmp(sp,"NUMCALLS")) {
647
		getstats(&cfg,0,&stats);
648
		safe_snprintf(str,maxlen,"%lu", (ulong)stats.logons);
649
650
		return(str);
	}
651

652
	if(!strcmp(sp,"PREVON") || !strcmp(sp,"LASTCALLERNODE")
653
		|| !strcmp(sp,"LASTCALLERSYSTEM"))
654
		return(lastuseron);
655

rswindell's avatar
rswindell committed
656
	if(!strcmp(sp,"CLS") || !strcmp(sp,"CLEAR")) {
657
		CLS;
658
659
		return(nulstr);
	}
660

661
	if(!strcmp(sp,"PAUSE") || !strcmp(sp,"MORE")) {
662
		pause();
663
664
		return(nulstr);
	}
665

666
	if(!strcmp(sp,"RESETPAUSE")) {
667
		lncntr=0;
668
669
		return(nulstr);
	}
670

671
	if(!strcmp(sp,"NOPAUSE") || !strcmp(sp,"POFF")) {
672
		sys_status^=SS_PAUSEOFF;
673
674
		return(nulstr);
	}
675

676
	if(!strcmp(sp,"PON") || !strcmp(sp,"AUTOMORE")) {
677
		sys_status^=SS_PAUSEON;
678
679
		return(nulstr);
	}
680

rswindell's avatar
rswindell committed
681
682
	if(strncmp(sp, "FILL:", 5) == 0) {
		sp += 5;
683
684
685
		long margin = centered ? column : 1;
		if(margin < 1) margin = 1;
		while(*sp && online && column < cols - margin)
rswindell's avatar
rswindell committed
686
687
688
689
			bputs(sp, P_TRUNCATE);
		return nulstr;
	}

690
691
692
693
694
	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--)
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
			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;
	}

719
720
721
722
723
	if(strncmp(sp, "BPS:", 4) == 0) {
		set_output_rate((enum output_rate)atoi(sp + 4));
		return nulstr;
	}

724
725
726
727
	/* NOSTOP */

	/* STOP */

728
729
	if(!strcmp(sp,"BELL") || !strcmp(sp,"BEEP"))
		return("\a");
730

731
732
733
	if(!strcmp(sp,"EVENT")) {
		if(event_time==0)
			return("<none>");
734
		return(timestr(event_time));
735
	}
736
737
738

	/* LASTCALL */

739
	if(!strncmp(sp,"NODE",4)) {
740
741
742
		i=atoi(sp+4);
		if(i && i<=cfg.sys_nodes) {
			getnodedat(i,&node,0);
743
744
			printnodedat(i,&node);
		}
745
746
		return(nulstr);
	}
747

748
	if(!strcmp(sp,"WHO")) {
749
		whos_online(true);
750
751
		return(nulstr);
	}
752
753
754

	/* User Codes */

755
756
	if(!strcmp(sp,"USER") || !strcmp(sp,"ALIAS") || !strcmp(sp,"NAME"))
		return(useron.alias);
757

758
	if(!strcmp(sp,"FIRST")) {
759
		safe_snprintf(str,maxlen,"%s",useron.alias);
760
		tp=strchr(str,' ');
761
		if(tp) *tp=0;
762
		return(str);
763
	}
764

765
	if(!strcmp(sp,"USERNUM")) {
766
		safe_snprintf(str,maxlen,"%u",useron.number);
767
768
		return(str);
	}
769

770
	if(!strcmp(sp,"PHONE") || !strcmp(sp,"HOMEPHONE")
771
		|| !strcmp(sp,"DATAPHONE") || !strcmp(sp,"DATA"))
772
		return(useron.phone);
773

774
775
	if(!strcmp(sp,"ADDR1"))
		return(useron.address);
776

777
778
	if(!strcmp(sp,"FROM"))
		return(useron.location);
779

780
	if(!strcmp(sp,"CITY")) {
781
		safe_snprintf(str,maxlen,"%s",useron.location);
782
		char* p=strchr(str,',');
783
784
		if(p) {
			*p=0;
785
786
			return(str);
		}
787
788
		return(nulstr);
	}
789

790
791
	if(!strcmp(sp,"STATE")) {
		char* p=strchr(useron.location,',');
792
793
		if(p) {
			p++;
794
			if(*p==' ')
795
				p++;
796
797
			return(p);
		}
798
799
		return(nulstr);
	}
800

801
802
	if(!strcmp(sp,"CPU"))
		return(useron.comp);
803

804
805
	if(!strcmp(sp,"HOST"))
		return(client_name);
806

807
808
	if(!strcmp(sp,"BDATE"))
		return(useron.birth);
809

810
	if(!strcmp(sp,"AGE")) {
811
		safe_snprintf(str,maxlen,"%u",getage(&cfg,useron.birth));
812
813
		return(str);
	}
rswindell's avatar
rswindell committed
814

815
	if(!strcmp(sp,"CALLS") || !strcmp(sp,"NUMTIMESON")) {
816
		safe_snprintf(str,maxlen,"%u",useron.logons);
817
818
		return(str);
	}
819

rswindell's avatar
rswindell committed
820
821
822
823
824
825
826
	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)
827
		return(unixtodstr(&cfg,useron.pwmod,str));
828

829
	if(!strcmp(sp,"SEC") || !strcmp(sp,"SECURITY")) {
830
		safe_snprintf(str,maxlen,"%u",useron.level);
831
832
		return(str);
	}
833

834
835
	if(!strcmp(sp,"SINCE"))
		return(unixtodstr(&cfg,useron.firston,str));
836

837
	if(!strcmp(sp,"TIMEON") || !strcmp(sp,"TIMEUSED")) {
838
		now=time(NULL);
839
		safe_snprintf(str,maxlen,"%lu",(ulong)(now-logontime)/60L);
840
841
		return(str);
	}
842

843
	if(!strcmp(sp,"TUSED")) {              /* Synchronet only */
844
		now=time(NULL);
845
		return(sectostr((uint)(now-logontime),str)+1);
846
	}
847

848
	if(!strcmp(sp,"TLEFT")) {              /* Synchronet only */
849
		gettimeleft();
850
		return(sectostr(timeleft,str)+1);
851
	}
852

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

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

rswindell's avatar
Add:    
rswindell committed
859
	if(strcmp(sp, "MPERC") == 0 || strcmp(sp, "TIMELIMIT") == 0) {
860
		safe_snprintf(str,maxlen,"%u",cfg.level_timepercall[useron.level]);
861
862
		return(str);
	}
863

rswindell's avatar
Add:    
rswindell committed
864
865
866
867
868
	if(strcmp(sp, "MPERD") == 0) {
		safe_snprintf(str,maxlen,"%u",cfg.level_timeperday[useron.level]);
		return str;
	}

rswindell's avatar
Add:    
rswindell committed
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
	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;
	}

889
	if(!strcmp(sp,"MINLEFT") || !strcmp(sp,"LEFT") || !strcmp(sp,"TIMELEFT")) {
890
		gettimeleft();
891
		safe_snprintf(str,maxlen,"%lu",timeleft/60);
892
893
		return(str);
	}
894

895
	if(!strcmp(sp,"LASTON"))
896
		return(timestr(useron.laston));
897

898
899
	if(!strcmp(sp,"LASTDATEON"))
		return(unixtodstr(&cfg,useron.laston,str));
900

901
	if(!strcmp(sp,"LASTTIMEON")) {
902
		memset(&tm,0,sizeof(tm));
903
		localtime32(&useron.laston,&tm);
904
905
906
907
908
909
910
911
		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");
912
		return(str);
913
914
	}

rswindell's avatar
rswindell committed
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
	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);
	}

935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
	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
960
	if(strcmp(sp, "MTODAY") == 0) {
961
962
963
964
		safe_snprintf(str, maxlen, "%u", useron.ttoday);
		return str;
	}

rswindell's avatar
Add:    
rswindell committed
965
966
967
968
969
970
	if(strcmp(sp, "MTOTAL") == 0) {
		safe_snprintf(str, maxlen, "%u", useron.timeon);
		return str;
	}

	if(strcmp(sp, "TTODAY") == 0)
rswindell's avatar
Add:    
rswindell committed
971
		return sectostr(useron.ttoday, str) + 3;
rswindell's avatar
Add:    
rswindell committed
972
973

	if(strcmp(sp, "TTOTAL") == 0)
rswindell's avatar
Add:    
rswindell committed
974
		return sectostr(useron.timeon, str) + 3;
rswindell's avatar
Add:    
rswindell committed
975

rswindell's avatar