scfgxtrn.c 81.8 KB
Newer Older
1 2 3 4
/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
5
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
6 7 8 9 10 11 12 13 14 15 16 17 18
 *																			*
 * 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.	*
 ****************************************************************************/
rswindell's avatar
rswindell committed
19 20

#include "scfg.h"
21
#include "ciolib.h"	// CIO_KEY_*
rswindell's avatar
rswindell committed
22

23
char *daystr(char days);
24 25
static void hotkey_cfg(void);

26
static char* use_shell_opt = "Use Shell or New Context";
27 28 29
static char* use_shell_help =
	"`Use System Shell or New JavaScript Context to Execute:`\n"
	"\n"
30
	"If this command line requires the system command shell to execute\n"
31 32 33
	"(e.g. uses pipes/redirection or invokes a Unix shell script or\n"
	"DOS/Windows batch/command file), then set this option to ~Yes~.\n"
	"\n"
34
	"If this command line is invoking a Synchronet JavaScript module\n"
35 36 37 38 39
	"(e.g. it begins with a '`?`' character), then setting this option to ~Yes~\n"
	"will enable the creation and initialization of a new JavaScript run-time\n"
	"context for it to execute within, for every invocation."
	;
static char* use_shell_prompt = "Use System Shell or New JavaScript Context to Execute";
40 41 42 43 44 45
static char* native_help =
	"`Native Executable/Script:`\n"
	"\n"
	"If this program is `16-bit MS-DOS` executable, set this option to `No`,\n"
	"otherwise (it is a native program or script) set this option to `Yes`.\n"
	;
46

47 48 49 50 51 52 53 54 55 56
#define CUT_XTRNSEC_NUM	USHRT_MAX

static bool new_timed_event(unsigned new_event_num)
{
	event_t* new_event;
	if ((new_event = (event_t *)malloc(sizeof(*new_event))) == NULL) {
		errormsg(WHERE, ERR_ALLOC, "timed event", sizeof(*new_event));
		return false;
	}
	memset(new_event, 0, sizeof(*new_event));
57
	new_event->node = NODE_ANY;
58
	new_event->days = (uchar)0xff;
59
	new_event->errlevel = LOG_ERR;
60 61 62

	event_t** new_event_list = realloc(cfg.event, sizeof(event_t *)*(cfg.total_events + 1));
	if (new_event_list == NULL) {
63
		free(new_event);
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
		errormsg(WHERE, ERR_ALLOC, "timed event list", cfg.total_events + 1);
		return false;
	}
	cfg.event = new_event_list;

	for (unsigned u = cfg.total_events; u > new_event_num; u--)
		cfg.event[u] = cfg.event[u - 1];

	cfg.event[new_event_num] = new_event;
	cfg.total_events++;
	return true;
}

static bool new_external_program_section(unsigned new_section_num)
{
	xtrnsec_t* new_xtrnsec = malloc(sizeof(*new_xtrnsec));
	if (new_xtrnsec == NULL) {
		errormsg(WHERE, ERR_ALLOC, "external program section", sizeof(*new_xtrnsec));
		return false;
	}
	memset(new_xtrnsec, 0, sizeof(*new_xtrnsec));

	xtrnsec_t** new_xtrnsec_list = realloc(cfg.xtrnsec, sizeof(xtrnsec_t *)*(cfg.total_xtrnsecs + 1));
	if (new_xtrnsec_list == NULL) {
88
		free(new_xtrnsec);
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
		errormsg(WHERE, ERR_ALLOC, "external program section list", cfg.total_xtrnsecs + 1);
		return false;
	}
	cfg.xtrnsec = new_xtrnsec_list;
	for (unsigned u = cfg.total_xtrnsecs; u > new_section_num; u--)
		cfg.xtrnsec[u] = cfg.xtrnsec[u - 1];
	for (unsigned j = 0; j < cfg.total_xtrns; j++) {
		if (cfg.xtrn[j]->sec >= new_section_num && cfg.xtrn[j]->sec != CUT_XTRNSEC_NUM)
			cfg.xtrn[j]->sec++;
	}

	cfg.xtrnsec[new_section_num] = new_xtrnsec;
	cfg.total_xtrnsecs++;
	return true;
}

static bool new_external_program(unsigned new_xtrn_num, unsigned section)
{
	xtrn_t* new_xtrn = malloc(sizeof(*new_xtrn));
	if (new_xtrn == NULL) {
		errormsg(WHERE, ERR_ALLOC, "external program", sizeof(*new_xtrn));
		return false;
	}
	memset(new_xtrn, 0, sizeof(*new_xtrn));
	new_xtrn->sec = section;
114
	new_xtrn->misc = MULTIUSER;
115 116 117

	xtrn_t ** new_xtrn_list = realloc(cfg.xtrn, sizeof(xtrn_t *)*(cfg.total_xtrns + 1));
	if (new_xtrn_list == NULL) {
118
		free(new_xtrn);
119 120 121 122 123 124 125 126 127 128 129 130
		errormsg(WHERE, ERR_ALLOC, "external program list", cfg.total_xtrns + 1);
		return false;
	}
	cfg.xtrn = new_xtrn_list;
	for (unsigned n = cfg.total_xtrns; n > new_xtrn_num; n--)
		cfg.xtrn[n] = cfg.xtrn[n - 1];

	cfg.xtrn[new_xtrn_num] = new_xtrn;
	cfg.total_xtrns++;
	return true;
}

131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
static int next_program(const int num)
{
	for(int i = num + 1; i < cfg.total_xtrns; i++) {
		if(cfg.xtrn[i]->sec == cfg.xtrn[num]->sec)
			return i;
	}
	return num;
}

static int prev_program(const int num)
{
	for(int i = num - 1; i >= 0; i--) {
		if(cfg.xtrn[i]->sec == cfg.xtrn[num]->sec)
			return i;
	}
	return num;
}

149 150 151 152 153 154 155 156
static bool new_external_editor(unsigned new_xedit_num)
{
	xedit_t* new_xedit = malloc(sizeof(*new_xedit));
	if (new_xedit == NULL) {
		errormsg(WHERE, ERR_ALLOC, "external editor", sizeof(*new_xedit));
		return false;
	}
	memset(new_xedit, 0, sizeof(*new_xedit));
157
	new_xedit->misc |= QUOTEWRAP;
158 159 160

	xedit_t** new_xedit_list = realloc(cfg.xedit, sizeof(xedit_t *)*(cfg.total_xedits + 1));
	if (new_xedit_list == NULL) {
161
		free(new_xedit);
162 163 164 165 166 167 168 169 170 171 172 173
		errormsg(WHERE, ERR_ALLOC, "external editor list", cfg.total_xedits + 1);
		return false;
	}
	cfg.xedit = new_xedit_list;
	for (unsigned u = cfg.total_xedits; u > new_xedit_num; u--)
		cfg.xedit[u] = cfg.xedit[u - 1];

	cfg.xedit[new_xedit_num] = new_xedit;
	cfg.total_xedits++;
	return true;
}

174 175 176 177 178 179 180 181 182 183 184 185 186 187
static char* monthstr(uint16_t months)
{
	int		i;
	static	char str[256];

	if(months==0)
		return("Any");

	str[0]=0;
	for(i=0;i<12;i++) {
		if((months&(1<<i))==0)
			continue;
		if(str[0])
			strcat(str," ");
188
		SAFECAT(str,mon[i]);
189 190 191 192 193
	}
	
	return(str);
}

194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
static char* mdaystr(long mdays)
{
	int		i;
	char	tmp[16];
	static	char str[256];

	if(mdays==0 || mdays==1)
		return("Any");

	str[0]=0;
	for(i=1;i<32;i++) {
		if((mdays&(1<<i))==0)
			continue;
		if(str[0])
			strcat(str," ");
		sprintf(tmp,"%u",i);
		strcat(str,tmp);
	}
	
	return(str);
}

216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
static char* dropfile(int type, ulong misc)
{
	static char str[128];
	char fname[64]="";

	switch(type) {
		case XTRN_SBBS:
			strcpy(fname,"XTRN.DAT");
			break;
		case XTRN_WWIV:
			strcpy(fname,"CHAIN.TXT");
			break;
		case XTRN_GAP:
			strcpy(fname,"DOOR.SYS");
			break;
		case XTRN_RBBS:
			strcpy(fname,"DORINFO#.DEF");
			break;
		case XTRN_RBBS1:
			strcpy(fname,"DORINFO1.DEF");
            break;
		case XTRN_WILDCAT:
			strcpy(fname,"CALLINFO.BBS");
			break;
		case XTRN_PCBOARD:
			strcpy(fname,"PCBOARD.SYS");
			break;
		case XTRN_SPITFIRE:
			strcpy(fname,"SFDOORS.DAT");
			break;
		case XTRN_UTI:
			strcpy(fname,"UTIDOOR.TXT");
			break;
		case XTRN_SR:
			strcpy(fname,"DOORFILE.SR");
			break;
		case XTRN_TRIBBS:
			strcpy(fname,"TRIBBS.SYS");
			break;
        case XTRN_DOOR32:
            strcpy(fname,"DOOR32.SYS");
            break;
	}

	if(misc&XTRN_LWRCASE)
		strlwr(fname);

	switch(type) {
		case XTRN_SBBS:
			sprintf(str,"%-15s %s","Synchronet",fname);
			break;
		case XTRN_WWIV:
			sprintf(str,"%-15s %s","WWIV",fname);
			break;
		case XTRN_GAP:
			sprintf(str,"%-15s %s","GAP",fname);
			break;
		case XTRN_RBBS:
			sprintf(str,"%-15s %s","RBBS/QuickBBS",fname);
			break;
		case XTRN_RBBS1:
			sprintf(str,"%-15s %s","RBBS/QuickBBS",fname);
            break;
		case XTRN_WILDCAT:
			sprintf(str,"%-15s %s","Wildcat",fname);
			break;
		case XTRN_PCBOARD:
			sprintf(str,"%-15s %s","PCBoard",fname);
			break;
		case XTRN_SPITFIRE:
			sprintf(str,"%-15s %s","SpitFire",fname);
			break;
		case XTRN_UTI:
			sprintf(str,"%-15s %s","MegaMail",fname);
			break;
		case XTRN_SR:
			sprintf(str,"%-15s %s","Solar Realms",fname);
			break;
		case XTRN_TRIBBS:
			sprintf(str,"%-15s %s","TriBBS",fname);
			break;
        case XTRN_DOOR32:
            sprintf(str,"%-15s %s","Mystic",fname);
            break;
		default:
			strcpy(str,"None");
			break; 
	}
	return(str);
}

rswindell's avatar
rswindell committed
307 308 309 310 311
void xprogs_cfg()
{
	static int xprogs_dflt;
	int 	i;

312 313 314 315 316 317 318 319 320 321 322 323
	while(1) {
		i=0;
		strcpy(opt[i++],"Fixed Events");
		strcpy(opt[i++],"Timed Events");
		strcpy(opt[i++],"Native Program List");
		strcpy(opt[i++],"External Editors");
		strcpy(opt[i++],"Global Hot Key Events");
		strcpy(opt[i++],"Online Programs (Doors)");
		opt[i][0]=0;
		uifc.helpbuf=
			"`Online External Programs:`\n"
			"\n"
324 325
			"From this menu, you can configure external events, external message\n"
			"editors, or online external programs (e.g. `door games`).\n"
326 327 328 329 330 331 332 333 334
		;
		switch(uifc.list(WIN_ORG|WIN_CHE|WIN_ACT,0,0,0,&xprogs_dflt,0
			,"External Programs",opt)) {
			case -1:
				i=save_changes(WIN_MID);
				if(i==-1)
					break;
				if(!i) {
					cfg.new_install=new_install;
rswindell's avatar
rswindell committed
335 336
					save_xtrn_cfg(&cfg,backup_level);
					save_main_cfg(&cfg,backup_level);
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
					refresh_cfg(&cfg);
				}
				return;
			case 0:
				fevents_cfg();
				break;
			case 1:
				tevents_cfg();
				break;
			case 2:
				natvpgm_cfg();
				break;
			case 3:
				xedit_cfg();
				break;
			case 4:
				hotkey_cfg();
				break;
			case 5:
				xtrnsec_cfg();
				break; 
358 359
		}
	}
rswindell's avatar
rswindell committed
360 361 362 363 364 365 366
}

void fevents_cfg()
{
	static int event_dflt;
	int i;

367 368
	while(1) {
		i=0;
369 370 371
		sprintf(opt[i++],"%-32.32s%s","Logon Event",cfg.sys_logon);
		sprintf(opt[i++],"%-32.32s%s","Logout Event",cfg.sys_logout);
		sprintf(opt[i++],"%-32.32s%s","Daily Event",cfg.sys_daily);
372
		opt[i][0]=0;
373
		uifc.helpbuf=
374
			"`External Events:`\n"
375
			"\n"
376 377
			"From this menu, you can configure the logon and logout events, and the\n"
			"system daily event.\n"
378
		;
379 380
		switch(uifc.list(WIN_ACT|WIN_SAV|WIN_CHE|WIN_BOT|WIN_RHT,0,0,60,&event_dflt,0
			,"Fixed Events",opt)) {
rswindell's avatar
rswindell committed
381
			case -1:
382
				return;
rswindell's avatar
rswindell committed
383
			case 0:
384
				uifc.helpbuf=
385
					"`Logon Event:`\n"
386
					"\n"
387 388 389 390 391 392 393
					"This is the command line for a program that will execute during the\n"
					"logon sequence of every user. The program cannot have user interaction.\n"
					"The program will be executed after the LOGON message is displayed and\n"
					"before the logon user list is displayed. If you wish to place a program\n"
					"in the logon sequence of users that includes interaction or requires\n"
					"account information, you probably want to use an online external\n"
					"program configured to run as a logon event.\n"
394
					SCFG_CMDLINE_PREFIX_HELP
395
					SCFG_CMDLINE_SPEC_HELP
396
				;
397 398
				uifc.input(WIN_MID|WIN_SAV,0,0,"Logon Event"
					,cfg.sys_logon,sizeof(cfg.sys_logon)-1,K_EDIT);
399
				break;
400
			case 1:
401
				uifc.helpbuf=
402
					"`Logout Event:`\n"
403
					"\n"
404 405 406 407 408 409
					"This is the command line for a program that will execute during the\n"
					"logout sequence of every user. This program cannot have user\n"
					"interaction because it is executed after carrier is dropped. If you\n"
					"wish to have a program execute before carrier is dropped, you probably\n"
					"want to use an `Online External Program` configured to run as a logoff\n"
					"event.\n"
410
					SCFG_CMDLINE_PREFIX_HELP
411
					SCFG_CMDLINE_SPEC_HELP
412
				;
413 414
				uifc.input(WIN_MID|WIN_SAV,0,0,"Logout Event"
					,cfg.sys_logout,sizeof(cfg.sys_logout)-1,K_EDIT);
rswindell's avatar
rswindell committed
415
				break;
416
			case 2:
417
				uifc.helpbuf=
418
					"`Daily Event:`\n"
419
					"\n"
420 421
					"This is the command line for a program that will run after the first\n"
					"user that logs on after midnight, logs off (regardless of what node).\n"
422
					SCFG_CMDLINE_PREFIX_HELP
423
					SCFG_CMDLINE_SPEC_HELP
424
				;
425 426
				uifc.input(WIN_MID|WIN_SAV,0,0,"Daily Event"
					,cfg.sys_daily,sizeof(cfg.sys_daily)-1,K_EDIT);
427

428
				break; 
429 430
		} 
	}
rswindell's avatar
rswindell committed
431 432
}

433
void tevents_cfg()
rswindell's avatar
rswindell committed
434
{
435 436
	static int dflt,dfltopt,bar;
	char str[81],done=0,*p;
rswindell's avatar
rswindell committed
437
	int j,k;
438 439
	uint i;
	static event_t savevent;
440

441 442
	while(1) {
		for(i=0;i<cfg.total_events && i<MAX_OPTS;i++)
443 444 445
			sprintf(opt[i],"%-8.8s      %s"
				,cfg.event[i]->code
				,(cfg.event[i]->misc&EVENT_DISABLED) ? "<DISABLED>" : cfg.event[i]->cmd);
446 447 448
		opt[i][0]=0;
		j=WIN_SAV|WIN_ACT|WIN_CHE|WIN_RHT;
		if(cfg.total_events)
449
			j|=WIN_DEL|WIN_COPY|WIN_CUT;
450 451 452 453
		if(cfg.total_events<MAX_OPTS)
			j|=WIN_INS|WIN_INSACT|WIN_XTR;
		if(savevent.code[0])
			j|=WIN_PASTE | WIN_PASTEXTR;
454
		uifc.helpbuf=
455
			"`Timed Events:`\n"
456
			"\n"
457 458 459
			"This is a list of the configured timed external events.\n"
			"\n"
			"To add an event hit ~ INS ~.\n"
460
			"\n"
461 462 463
			"To delete an event, select it and hit ~ DEL ~.\n"
			"\n"
			"To configure an event, select it and hit ~ ENTER ~.\n"
464
		;
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
		i=uifc.list(j,0,0,45,&dflt,&bar,"Timed Events",opt);
		if((signed)i==-1)
			return;
		int msk = i & MSK_ON;
		i &= MSK_OFF;
		if(msk == MSK_INS) {
			uifc.helpbuf=
				"`Timed Event Internal Code:`\n"
				"\n"
				"This is the internal code for the timed event.\n"
			;
			if(uifc.input(WIN_MID|WIN_SAV,0,0,"Event Internal Code",str,LEN_CODE
				,K_UPPER)<1)
				continue;
			if (!new_timed_event(i))
				continue;
			SAFECOPY(cfg.event[i]->code,str);
			uifc.changes=1;
			continue; 
484
		}
485 486 487 488 489 490 491 492 493
		if(msk == MSK_DEL || msk == MSK_CUT) {
			if(msk == MSK_CUT)
				savevent = *cfg.event[i];
			free(cfg.event[i]);
			cfg.total_events--;
			for(j=i;j<cfg.total_events;j++)
				cfg.event[j]=cfg.event[j+1];
			uifc.changes=1;
			continue; 
494
		}
495 496
		if(msk == MSK_COPY) {
			savevent=*cfg.event[i];
497 498
			continue; 
		}
499 500 501
		if(msk == MSK_PASTE) {
			if (!new_timed_event(i))
				continue;
502 503 504
			*cfg.event[i]=savevent;
			uifc.changes=1;
			continue; 
505
		}
506 507 508 509 510 511
		if (msk != 0)
			continue;
		done=0;
		while(!done) {
			k=0;
			sprintf(opt[k++],"%-32.32s%s","Internal Code",cfg.event[i]->code);
512 513
			sprintf(opt[k++],"%-32.32s%s","Start-up Directory",cfg.event[i]->dir);
			sprintf(opt[k++],"%-32.32s%s","Command Line",cfg.event[i]->cmd);
514 515
			sprintf(opt[k++],"%-32.32s%s","Enabled"
				,cfg.event[i]->misc&EVENT_DISABLED ? "No":"Yes");
516 517 518 519 520
			if(cfg.event[i]->node == NODE_ANY)
				SAFECOPY(str, "Any");
			else
				SAFEPRINTF(str, "%u", cfg.event[i]->node);
			sprintf(opt[k++],"%-32.32s%s","Execution Node", str);
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537
			sprintf(opt[k++],"%-32.32s%s","Execution Months"
				,monthstr(cfg.event[i]->months));
			sprintf(opt[k++],"%-32.32s%s","Execution Days of Month"
				,mdaystr(cfg.event[i]->mdays));
			sprintf(opt[k++],"%-32.32s%s","Execution Days of Week",daystr(cfg.event[i]->days));
			if(cfg.event[i]->freq) {
				sprintf(str,"%u times a day",1440/cfg.event[i]->freq);
				sprintf(opt[k++],"%-32.32s%s","Execution Frequency",str);
			} else {
				sprintf(str,"%2.2u:%2.2u"
					,cfg.event[i]->time/60,cfg.event[i]->time%60);
				sprintf(opt[k++],"%-32.32s%s","Execution Time",str);
			}
			sprintf(opt[k++],"%-32.32s%s","Requires Exclusive Execution"
				,cfg.event[i]->misc&EVENT_EXCL ? "Yes":"No");
			sprintf(opt[k++],"%-32.32s%s","Force Users Off-line For Event"
				,cfg.event[i]->misc&EVENT_FORCE ? "Yes":"No");
538
			sprintf(opt[k++],"%-32.32s%s","Native Executable/Script"
539
				,cfg.event[i]->misc&EX_NATIVE ? "Yes" : "No");
540
			sprintf(opt[k++],"%-32.32s%s",use_shell_opt
541 542 543 544 545
				,cfg.event[i]->misc&XTRN_SH ? "Yes" : "No");
			sprintf(opt[k++],"%-32.32s%s","Background Execution"
				,cfg.event[i]->misc&EX_BG ? "Yes" : "No");
			sprintf(opt[k++],"%-32.32s%s","Always Run After Init/Re-init"
				,cfg.event[i]->misc&EVENT_INIT ? "Yes":"No");
546 547
			if(!(cfg.event[i]->misc&EX_BG))
				sprintf(opt[k++],"%-32.32s%s","Error Log Level", iniLogLevelStringList()[cfg.event[i]->errlevel]);
548 549 550 551 552 553 554 555
			opt[k][0]=0;
			uifc.helpbuf=
				"`Timed Event:`\n"
				"\n"
				"This is the configuration menu for a timed event. An event is an\n"
				"external program that performs some type of automated function on the\n"
				"system. Use this menu to configure how and when this event will be\n"
				"executed.\n"
556 557
				"\n"
				"The left and right arrow keys may be used to cycle through events.\n"
558 559
			;
			sprintf(str,"%s Timed Event",cfg.event[i]->code);
560 561 562 563 564 565
			uifc_winmode_t wmode = WIN_SAV|WIN_ACT|WIN_L2R|WIN_BOT|WIN_EXTKEYS;
			if(i > 0)
				wmode |= WIN_LEFTKEY;
			if(i + 1 < cfg.total_events)
				wmode |= WIN_RIGHTKEY;
			switch(uifc.list(wmode,0,0,70,&dfltopt,0
566 567 568 569
				,str,opt)) {
				case -1:
					done=1;
					break;
570 571 572 573 574 575 576 577
				case -CIO_KEY_LEFT-2:
					if(i > 0)
						i--;
					break;
				case -CIO_KEY_RIGHT-2:
					if(i + 1 < cfg.total_events)
						i++;
					break;
578
				case 0:
579
					SAFECOPY(str,cfg.event[i]->code);
580 581 582 583
					uifc.helpbuf=
						"`Timed Event Internal Code:`\n"
						"\n"
						"Every timed event must have its own unique internal code for Synchronet\n"
584
						"to reference it by. It is helpful if this code is an abbreviation of the\n"
585
						"command line or program name.\n"
586 587 588 589
					;
					uifc.input(WIN_MID|WIN_SAV,0,17,"Internal Code (unique)"
						,str,LEN_CODE,K_EDIT|K_UPPER);
					if(code_ok(str))
590
						SAFECOPY(cfg.event[i]->code,str);
591 592
					else {
						uifc.helpbuf=invalid_code;
593
						uifc.msg(strInvalidCode);
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
						uifc.helpbuf=0; 
					}
					break;
				case 1:
					uifc.helpbuf=
						"`Timed Event Start-up Directory:`\n"
						"\n"
						"This is the DOS drive/directory where the event program is located.\n"
						"If a path is specified here, it will be made the current directory\n"
						"before the event's command line is executed. This eliminates the need\n"
						"for batch files that just change the current drive and directory before\n"
						"executing the event.\n"
					;
					uifc.input(WIN_MID|WIN_SAV,0,10,"Directory"
						,cfg.event[i]->dir,sizeof(cfg.event[i]->dir)-1,K_EDIT);
					break;
				case 2:
					uifc.helpbuf=
						"`Timed Event Command Line:`\n"
						"\n"
						"This is the command line to execute upon this timed event.\n"
615
						SCFG_CMDLINE_PREFIX_HELP
616
						SCFG_CMDLINE_SPEC_HELP
617 618 619 620 621 622 623 624
					;
					uifc.input(WIN_MID|WIN_SAV,0,10,"Command"
						,cfg.event[i]->cmd,sizeof(cfg.event[i]->cmd)-1,K_EDIT);
					break;

				case 3:
					k=(cfg.event[i]->misc&EVENT_DISABLED) ? 1:0;
					uifc.helpbuf=
625
						"`Timed Event Enabled:`\n"
626 627 628 629
						"\n"
						"If you want disable this event from executing, set this option to ~No~.\n"
					;
					k=uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
630
						,"Event Enabled",uifcYesNoOpts);
631 632 633 634 635 636 637 638 639
					if((k==0 && cfg.event[i]->misc&EVENT_DISABLED) 
						|| (k==1 && !(cfg.event[i]->misc&EVENT_DISABLED))) {
						cfg.event[i]->misc^=EVENT_DISABLED;
						uifc.changes=1; 
					}
					break;

				case 4:
					uifc.helpbuf=
640
						"`Timed Event Execution Node:`\n"
641
						"\n"
642 643
						"The Execution Node Number specifies the instance of Synchronet that\n"
						"will run this timed event (or `Any`).\n"
644
					;
645 646 647 648
					if(cfg.event[i]->node == NODE_ANY)
						SAFECOPY(str, "Any");
					else
						SAFEPRINTF(str, "%u", cfg.event[i]->node);
649
					if(uifc.input(WIN_MID|WIN_SAV,0,0,"Execution Node Number (or Any)"
650
						,str,3,K_EDIT) > 0) {
651
						if(IS_DIGIT(*str))
652 653 654 655
							cfg.event[i]->node=atoi(str);
						else
							cfg.event[i]->node = NODE_ANY;
					}
656 657 658 659 660
					break;
				case 5:
					uifc.helpbuf=
						"`Months to Execute Event:`\n"
						"\n"
661 662
						"Specifies the months (`Jan`-`Dec`, separated by spaces) on which to\n"
						"execute this event, or `Any` to execute event in any/all months.\n"
663 664 665 666 667 668
					;
					SAFECOPY(str,monthstr(cfg.event[i]->months));
					uifc.input(WIN_MID|WIN_SAV,0,0,"Months to Execute Event (or Any)"
						,str,50,K_EDIT);
					cfg.event[i]->months=0;
					for(p=str;*p;p++) {
669 670 671
						int num = atoi(p);
						if(num > 0) {
							cfg.event[i]->months|=(1<<(num-1));
672
							while(*p && IS_DIGIT(*p))
673 674 675 676 677 678 679 680 681
								p++;
						} else {
							for(j=0;j<12;j++)
								if(strnicmp(mon[j],p,3)==0) {
									cfg.event[i]->months|=(1<<j);
									p+=2;
									break;
								}
							p++;
682
						}
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700
					}
					break;
				case 6:
					uifc.helpbuf=
						"`Days of Month to Execute Event:`\n"
						"\n"
						"Specifies the days of the month (`1-31`, separated by spaces) on which \n"
						"to execute this event, or `Any` to execute event on any and all days of\n"
						"the month.\n"
					;
					SAFECOPY(str,mdaystr(cfg.event[i]->mdays));
					uifc.input(WIN_MID|WIN_SAV,0,0,"Days of Month to Execute Event (or Any)"
						,str,16,K_EDIT);
					cfg.event[i]->mdays=0;
					for(p=str;*p;p++) {
						if(!isdigit(*p))
							continue;
						cfg.event[i]->mdays|=(1<<atoi(p));
701
						while(*p && IS_DIGIT(*p))
702 703 704 705 706 707 708 709 710 711 712 713
							p++;
					}
					break;
				case 7:
					j=0;
					while(1) {
						for(k=0;k<7;k++)
							sprintf(opt[k],"%-30s %3s"
								,wday[k],(cfg.event[i]->days&(1<<k)) ? "Yes":"No");
						strcpy(opt[k++],"All");
						strcpy(opt[k++],"None");
						opt[k][0]=0;
714
						uifc.helpbuf=
715
							"`Days of Week to Execute Event:`\n"
716
							"\n"
717
							"These are the days of the week that this event will be executed.\n"
718
						;
719 720 721 722 723
						k=uifc.list(WIN_MID|WIN_SAV|WIN_ACT,0,0,0,&j,0
							,"Days of Week to Execute Event",opt);
						if(k==-1)
							break;
						if(k==7)
724
							cfg.event[i]->days=0x7f;
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
						else if(k==8)
							cfg.event[i]->days=0;
						else
							cfg.event[i]->days^=(1<<k);
						uifc.changes=1; 
					}
					break;
				case 8:
					if(cfg.event[i]->freq==0)
						k=0;
					else
						k=1;
					uifc.helpbuf=
						"`Execute Event at a Specific Time:`\n"
						"\n"
						"If you want the system execute this event at a specific time, set\n"
						"this option to `Yes`. If you want the system to execute this event more\n"
						"than once a day at predetermined intervals, set this option to `No`.\n"
					;
					k=uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
						,"Execute Event at a Specific Time",uifcYesNoOpts);
					if(k==0) {
						sprintf(str,"%2.2u:%2.2u",cfg.event[i]->time/60
							,cfg.event[i]->time%60);
						uifc.helpbuf=
							"`Time to Execute Event:`\n"
							"\n"
							"This is the time (in 24 hour HH:MM format) to execute the event.\n"
						;
						if(uifc.input(WIN_MID|WIN_SAV,0,0
							,"Time to Execute Event (HH:MM)"
							,str,5,K_UPPER|K_EDIT)>0) {
							cfg.event[i]->freq=0;
							cfg.event[i]->time=atoi(str)*60;
							if((p=strchr(str,':'))!=NULL)
								cfg.event[i]->time+=atoi(p+1); 
						} 
					}
					else if(k==1) {
						sprintf(str,"%u"
							,cfg.event[i]->freq && cfg.event[i]->freq<=1440
								? 1440/cfg.event[i]->freq : 0);
						uifc.helpbuf=
							"`Number of Executions Per Day:`\n"
							"\n"
							"This is the maximum number of times the system will execute this event\n"
							"per day.\n"
						;
						if(uifc.input(WIN_MID|WIN_SAV,0,0
							,"Number of Executions Per Day"
							,str,4,K_NUMBER|K_EDIT)>0) {
							cfg.event[i]->time=0;
							k=atoi(str);
							if(k && k<=1440)
								cfg.event[i]->freq=1440/k;
							else
								cfg.event[i]->freq=0;
							}
783
						}
rswindell's avatar
rswindell committed
784
					break;
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
				case 9:
					k=(cfg.event[i]->misc&EVENT_EXCL) ? 0:1;
					uifc.helpbuf=
						"`Exclusive Event Execution:`\n"
						"\n"
						"If this event must be run exclusively (all nodes inactive), set this\n"
						"option to `Yes`.\n"
					;
					k=uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0,"Exclusive Execution"
						,uifcYesNoOpts);
					if(!k && !(cfg.event[i]->misc&EVENT_EXCL)) {
						cfg.event[i]->misc|=EVENT_EXCL;
						uifc.changes=1; 
					}
					else if(k==1 && cfg.event[i]->misc&EVENT_EXCL) {
						cfg.event[i]->misc&=~EVENT_EXCL;
						uifc.changes=1; 
802
					}
rswindell's avatar
rswindell committed
803
					break;
804 805 806 807 808 809 810 811 812 813 814 815 816
				case 10:
					k=(cfg.event[i]->misc&EVENT_FORCE) ? 0:1;
					uifc.helpbuf=
						"`Force Users Off-line for Event:`\n"
						"\n"
						"If you want to have your users' on-line time reduced so the event can\n"
						"execute precisely on time, set this option to `Yes`.\n"
					;
					k=uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
						,"Force Users Off-line for Event",uifcYesNoOpts);
					if(!k && !(cfg.event[i]->misc&EVENT_FORCE)) {
						cfg.event[i]->misc|=EVENT_FORCE;
						uifc.changes=1; 
817
					}
818 819 820
					else if(k==1 && (cfg.event[i]->misc&EVENT_FORCE)) {
						cfg.event[i]->misc&=~EVENT_FORCE;
						uifc.changes=1; 
821
					}
822 823 824 825
					break;

				case 11:
					k=(cfg.event[i]->misc&EX_NATIVE) ? 0:1;
826
					uifc.helpbuf=native_help;
827
					k=uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
828
						,"Native Executable/Script",uifcYesNoOpts);
829 830 831 832 833 834 835 836 837 838 839 840
					if(!k && !(cfg.event[i]->misc&EX_NATIVE)) {
						cfg.event[i]->misc|=EX_NATIVE;
						uifc.changes=TRUE;
					}
					else if(k==1 && (cfg.event[i]->misc&EX_NATIVE)) {
						cfg.event[i]->misc&=~EX_NATIVE;
						uifc.changes=TRUE;
					}
					break;

				case 12:
					k=(cfg.event[i]->misc&XTRN_SH) ? 0:1;
841
					uifc.helpbuf = use_shell_help;
842
					k=uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
843
						,use_shell_prompt, uifcYesNoOpts);
844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876
					if(!k && !(cfg.event[i]->misc&XTRN_SH)) {
						cfg.event[i]->misc|=XTRN_SH;
						uifc.changes=TRUE;
					}
					else if(k==1 && (cfg.event[i]->misc&XTRN_SH)) {
						cfg.event[i]->misc&=~XTRN_SH;
						uifc.changes=TRUE;
					}
					break;

				case 13:
					k=(cfg.event[i]->misc&EX_BG) ? 0:1;
					uifc.helpbuf=
						"`Execute Event in Background (Asynchronously):`\n"
						"\n"
						"If you would like this event to run simultaneously with other events,\n"
						"set this option to `Yes`. Exclusive events will not run in the background.\n"
					;
					k=uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
						,"Background (Asynchronous) Execution",uifcYesNoOpts);
					if(!k && !(cfg.event[i]->misc&EX_BG)) {
						cfg.event[i]->misc|=EX_BG;
						uifc.changes=TRUE;
					}
					else if(k==1 && (cfg.event[i]->misc&EX_BG)) {
						cfg.event[i]->misc&=~EX_BG;
						uifc.changes=TRUE;
					}
					break;

				case 14:
					k=(cfg.event[i]->misc&EVENT_INIT) ? 0:1;
					uifc.helpbuf=
877
						"`Always Run After (re-)Initialization:`\n"
878 879 880 881 882
						"\n"
						"If you want this event to always run after the BBS is initialized or\n"
						"re-initialized, set this option to ~Yes~.\n"
					;
					k=uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
883
						,"Always Run After (re-)Initialization",uifcYesNoOpts);
884 885 886 887 888 889 890 891 892
					if(!k && !(cfg.event[i]->misc&EVENT_INIT)) {
						cfg.event[i]->misc|=EVENT_INIT;
						uifc.changes=1; 
					}
					else if(k==1 && (cfg.event[i]->misc&EVENT_INIT)) {
						cfg.event[i]->misc&=~EVENT_INIT;
						uifc.changes=1; 
					}
					break;
893 894 895 896 897 898 899 900 901 902 903 904 905 906 907

				case 15:
					uifc.helpbuf=
						"`Error Log Level:`\n"
						"\n"
						"Specify the log level used when reporting an error executing this event.\n"
					;
					k = cfg.event[i]->errlevel;
					k=uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
						,"Error Log Level",iniLogLevelStringList());
					if(k > 0 && cfg.event[i]->errlevel != k) {
						cfg.event[i]->errlevel = k;
						uifc.changes = true;
					}
					break;
908 909 910 911 912
			} 
		} 
	}
}

913 914 915 916 917
const char* io_method(uint32_t mode)
{
	static char str[128];

	sprintf(str,"%s%s%s"
918 919 920
		,mode & XTRN_UART ? "UART" : (mode & XTRN_FOSSIL) ? "FOSSIL"
				: (mode & XTRN_STDIO ? "Standard"
					: mode & XTRN_CONIO ? "Console": (mode & XTRN_NATIVE ? "Socket" : "FOSSIL or UART"))
921 922 923 924 925 926 927 928 929
		,(mode & (XTRN_STDIO|WWIVCOLOR)) == (XTRN_STDIO|WWIVCOLOR) ? ", WWIV Color" : ""
		,(mode & (XTRN_STDIO|XTRN_NOECHO)) == (XTRN_STDIO|XTRN_NOECHO) ? ", No Echo" : "");
	return str;
}

void choose_io_method(uint32_t* misc)
{
	int k;

930
	switch((*misc) & (XTRN_STDIO|XTRN_UART|XTRN_FOSSIL)) {
931 932 933 934 935 936
		case XTRN_STDIO:
			k=0;
			break;
		case XTRN_UART:
			k=2;
			break;
937 938 939
		case XTRN_FOSSIL:
			k=3;
			break;
940 941 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 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992
		default:
			k=1;
			break;
	}
	strcpy(opt[0], "Standard");
	if((*misc) & XTRN_NATIVE) {
		uifc.helpbuf=
			"`I/O Method:`\n"
			"\n"
			"Select the type of input and output to/from this program that you would\n"
			"like to have intercepted and directed to the remote user's terminal.\n"
			"\n"
			"`Standard`\n"
			"   So-called 'Standard I/O' of console mode (typically UNIX) programs.\n"
			"\n"
			"`Socket`\n"
			"   Stream (TCP) socket interface to a native (e.g. Win32 or *nix)\n"
			"   program. The socket descriptor/handle (number) is passed to the\n"
			"   program either via drop file (e.g. DOOR32.SYS) or command-line\n"
			"   option.\n"
			"\n"
			"~ Note ~\n"
			"   This setting is not applied when invoking Baja or JavaScript modules.\n"
		;
		strcpy(opt[1], "Socket");
		opt[2][0] = '\0';
	} else {
		uifc.helpbuf=
			"`I/O Method:`\n"
			"\n"
			"Select the type of input and output to/from this program that you would\n"
			"like to have intercepted and directed to the remote user's terminal.\n"
			"\n"
			"`Standard`\n"
			"   So-called 'Standard I/O' of console mode (typically UNIX) programs.\n"
			"   Int29h is intercepted for output and int16h for keyboard input.\n"
			"   Will not intercept direct screen writes or PC-BIOS int10h calls.\n"
			"\n"
			"`FOSSIL`\n"
			"   Int14h (PC-BIOS) serial communication interface to 16-bit\n"
			"   MS-DOS programs. Most traditional BBS door games will support FOSSIL.\n"
			"   The port number (contained in the DX register of int14h calls) is\n"
			"   ignored by the Synchronet FOSSIL driver.\n"
			"\n"
			"`UART`\n"
			"   Communication port I/O of 16-bit MS-DOS programs via emulation of an\n"
			"   NS8250 Universal Asynchronous Receiver/Transmitter (UART), by\n"
			"   default as an IBM-PC `COM1` port (I/O port 3F8h, IRQ 4).\n"
			"\n"
			"~ Note ~\n"
			"   This setting is not applied when invoking Baja or JavaScript modules.\n"
		;
		strcpy(opt[1], "FOSSIL or UART");
993 994 995
		strcpy(opt[2], "UART Only");
		strcpy(opt[3], "FOSSIL Only");
		opt[4][0] = '\0';
996 997 998 999
	}
	switch(uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0,"I/O Method"
		,opt)) {
		case 0: /* Standard I/O */
1000
			if(((*misc) & (XTRN_STDIO|XTRN_UART|XTRN_FOSSIL)) != XTRN_STDIO) {
1001
				(*misc) |=XTRN_STDIO;
1002
				(*misc) &=~XTRN_UART|XTRN_FOSSIL;
1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
				uifc.changes = TRUE;
			}
			k=((*misc) & WWIVCOLOR) ? 0:1;
			uifc.helpbuf=
				"`Program Uses WWIV Color Codes:`\n"
				"\n"
				"If this program was written for use exclusively under ~WWIV~ BBS\n"
				"software, set this option to ~Yes~.\n"
			;
			k=uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
				,"Program Uses WWIV Color Codes"
				,uifcYesNoOpts);
			if(!k && !((*misc) & WWIVCOLOR)) {
				(*misc) |= WWIVCOLOR;
				uifc.changes=TRUE; 
			}
			else if(k==1 && ((*misc)&WWIVCOLOR)) {
				(*misc) &= ~WWIVCOLOR;
				uifc.changes=TRUE; 
			}
			k=((*misc) & XTRN_NOECHO) ? 1:0;
			uifc.helpbuf=
				"`Echo Input:`\n"
				"\n"
				"If you want the BBS to copy (\"echo\") all keyboard input to the screen\n"
				"output, set this option to ~Yes~ (for native Win32 programs only).\n"
			;
			k=uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
				,"Echo Keyboard Input"
				,uifcYesNoOpts);
			if(!k && ((*misc) & XTRN_NOECHO)) {
				(*misc) &=~XTRN_NOECHO;
				uifc.changes=TRUE; 
			} else if(k==1 && !((*misc) & XTRN_NOECHO)) {
				(*misc) |= XTRN_NOECHO;
				uifc.changes=TRUE; 
			}
			break;
1041 1042 1043
		case 1:	/* FOSSIL or UART or Socket */
			if(((*misc) & (XTRN_STDIO|XTRN_UART|XTRN_FOSSIL)) != 0) {
				(*misc) &= ~(XTRN_UART|XTRN_FOSSIL|XTRN_STDIO|WWIVCOLOR|XTRN_NOECHO);
1044 1045 1046 1047
				uifc.changes=TRUE; 
			}
			break;
		case 2: /* UART */
1048
			if(((*misc) & (XTRN_STDIO|XTRN_UART|XTRN_FOSSIL)) != XTRN_UART) {
1049
				(*misc) |= XTRN_UART;
1050 1051 1052 1053 1054 1055 1056 1057
				(*misc) &= ~(XTRN_FOSSIL|XTRN_STDIO|WWIVCOLOR|XTRN_NOECHO);
				uifc.changes=TRUE; 
			}
			break;
		case 3: /* FOSSIL */
			if(((*misc) & (XTRN_STDIO|XTRN_UART|XTRN_FOSSIL)) != XTRN_FOSSIL) {
				(*misc) |= XTRN_FOSSIL;
				(*misc) &= ~(XTRN_UART|XTRN_STDIO|WWIVCOLOR|XTRN_NOECHO);
1058 1059 1060 1061 1062
				uifc.changes=TRUE; 
			}
			break;
	}
}
1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081

void xtrn_cfg(uint section)
{
	static int ext_dflt,ext_bar,sub_bar,opt_dflt,time_dflt;
	char str[128],code[128],done=0;
	int j,k;
	uint i,xtrnnum[MAX_OPTS+1];
	static xtrn_t savxtrn;

	while(1) {
		for(i=0,j=0;i<cfg.total_xtrns && j<MAX_OPTS;i++)
			if(cfg.xtrn[i]->sec==section) {
				sprintf(opt[j],"%-25s",cfg.xtrn[i]->name);
				xtrnnum[j++]=i; 
			}
		xtrnnum[j]=cfg.total_xtrns;
		opt[j][0]=0;
		i=WIN_ACT|WIN_CHE|WIN_SAV|WIN_RHT;
		if(j)
1082
			i|=WIN_DEL | WIN_COPY | WIN_CUT;
1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098
		if(cfg.total_xtrns<MAX_OPTS)
			i|=WIN_INS|WIN_INSACT|WIN_XTR;
		if(savxtrn.name[0])
			i|=WIN_PASTE | WIN_PASTEXTR;
		uifc.helpbuf=
			"`Online External Programs:`\n"
			"\n"
			"This is a list of the configured online external programs (doors).\n"
			"\n"
			"To add a program, select the desired location with the arrow keys and\n"
			"hit ~ INS ~.\n"
			"\n"
			"To delete a program, select it with the arrow keys and hit ~ DEL ~.\n"
			"\n"
			"To configure a program, select it with the arrow keys and hit ~ ENTER ~.\n"
		;
1099
		sprintf(str,"%s Programs",cfg.xtrnsec[section]->name);
1100 1101 1102 1103 1104 1105 1106
		i=uifc.list(i,0,0,45,&ext_dflt,&ext_bar,str,opt);
		if((signed)i==-1)
			return;
		int msk = i & MSK_ON;
		i &= MSK_OFF;
		if(msk == MSK_INS) {
			uifc.helpbuf=
1107
				"`Program Name:`\n"
1108 1109 1110
				"\n"
				"This is the name or description of the online program (door).\n"
			;
1111
			if(uifc.input(WIN_MID|WIN_SAV,0,0,"Program Name",str,40
1112 1113 1114 1115 1116
				,0)<1)
				continue;
			SAFECOPY(code,str);
			prep_code(code,/* prefix: */NULL);
			uifc.helpbuf=
1117
				"`Program Internal Code:`\n"
1118 1119
				"\n"
				"Every online program must have its own unique code for Synchronet to\n"
1120
				"refer to it internally. This code is usually an abbreviation of the\n"
1121 1122 1123 1124 1125 1126 1127
				"online program name.\n"
			;
			if(uifc.input(WIN_MID|WIN_SAV,0,0,"Internal Code"
				,code,LEN_CODE,K_EDIT|K_UPPER)<1)
				continue;
			if(!code_ok(code)) {
				uifc.helpbuf=invalid_code;
1128
				uifc.msg(strInvalidCode);
1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152
				uifc.helpbuf=0;
				continue; 
			}
			if (!new_external_program(xtrnnum[i], section))
				continue;
			SAFECOPY(cfg.xtrn[xtrnnum[i]]->name,str);
			SAFECOPY(cfg.xtrn[xtrnnum[i]]->code,code);
			uifc.changes=TRUE;
			continue; 
		}
		if(msk == MSK_DEL || msk == MSK_CUT) {
			if(msk == MSK_CUT)
				savxtrn = *cfg.xtrn[xtrnnum[i]];
			free(cfg.xtrn[xtrnnum[i]]);
			cfg.total_xtrns--;
			for(j=xtrnnum[i];j<cfg.total_xtrns;j++)
				cfg.xtrn[j]=cfg.xtrn[j+1];
			uifc.changes=TRUE;
			continue; 
		}
		if(msk == MSK_COPY) {
			savxtrn=*cfg.xtrn[xtrnnum[i]];
			continue; 
		}
1153 1154 1155
		if(msk == MSK_PASTE) {
			if (!new_external_program(xtrnnum[i], section))
				continue;
1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168
			*cfg.xtrn[xtrnnum[i]]=savxtrn;
			cfg.xtrn[xtrnnum[i]]->sec=section;
			uifc.changes=TRUE;
			continue; 
		}
		if (msk != 0)
			continue;
		done=0;
		i=xtrnnum[i];
		while(!done) {
			k=0;
			sprintf(opt[k++],"%-27.27s%s","Name",cfg.xtrn[i]->name);
			sprintf(opt[k++],"%-27.27s%s","Internal Code",cfg.xtrn[i]->code);
1169 1170 1171
			sprintf(opt[k++],"%-27.27s%s","Start-up Directory",cfg.xtrn[i]->path);
			sprintf(opt[k++],"%-27.27s%s","Command Line",cfg.xtrn[i]->cmd);
			sprintf(opt[k++],"%-27.27s%s","Clean-up Command Line",cfg.xtrn[i]->clean);
1172 1173 1174 1175 1176
			if(cfg.xtrn[i]->cost)
				sprintf(str,"%"PRIu32" credits",cfg.xtrn[i]->cost);
			else
				strcpy(str,"None");
			sprintf(opt[k++],"%-27.27s%s","Execution Cost",str);
1177 1178
			sprintf(opt[k++],"%-27.27s%s","Access Requirements",cfg.xtrn[i]->arstr);
			sprintf(opt[k++],"%-27.27s%s","Execution Requirements"
1179 1180 1181
				,cfg.xtrn[i]->run_arstr);
			sprintf(opt[k++],"%-27.27s%s","Multiple Concurrent Users"
				,cfg.xtrn[i]->misc&MULTIUSER ? "Yes" : "No");
1182
			sprintf(opt[k++],"%-27.27s%s","I/O Method", io_method(cfg.xtrn[i]->misc));
1183
			sprintf(opt[k++],"%-27.27s%s","Native Executable/Script"
1184
				,cfg.xtrn[i]->misc&XTRN_NATIVE ? "Yes" : "No");
1185
			sprintf(opt[k++],"%-27.27s%s",use_shell_opt
1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222
				,cfg.xtrn[i]->misc&XTRN_SH ? "Yes" : "No");
			sprintf(opt[k++],"%-27.27s%s","Modify User Data"
				,cfg.xtrn[i]->misc&MODUSERDAT ? "Yes" : "No");
			switch(cfg.xtrn[i]->event) {
				case EVENT_LOGON:
					strcpy(str,"Logon");
					break;
				case EVENT_LOGOFF:
					strcpy(str,"Logoff");
					break;
				case EVENT_NEWUSER:
					strcpy(str,"New User");
					break;
				case EVENT_BIRTHDAY:
					strcpy(str,"Birthday");
					break;
				case EVENT_POST:
					strcpy(str,"Message Posted");
					break;
				case EVENT_UPLOAD:
					strcpy(str,"File Uploaded");
					break;
				case EVENT_DOWNLOAD:
					strcpy(str,"File Downloaded");
					break;
				case EVENT_LOCAL_CHAT:
					strcpy(str,"Local/Sysop Chat");
					break;
				default:
					strcpy(str,"No");
					break; 
			}
			if((cfg.xtrn[i]->misc&EVENTONLY) && cfg.xtrn[i]->