Skip to content
Snippets Groups Projects
echocfg.c 114 KiB
Newer Older

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * 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.	*
 ****************************************************************************/

/* Portions written by Allen Christiansen 1994-1996 						*/

#include <stdio.h>
#undef JAVASCRIPT
/* XPDEV Headers */
#include "gen_defs.h"

#define __COLORS
#include "ciolib.h"
#include "uifc.h"
#include "scfgdefs.h"
#include "sockwrap.h"
#include "str_util.h"
#include "git_branch.h"
#include "git_hash.h"
sbbsecho_cfg_t  cfg;
uifcapi_t       uifc;
int             ciolib_mode = CIOLIB_MODE_AUTO;
enum text_modes video_mode = LCD80X25;
	if (uifc.bail != NULL)
/* These correlate with the LOG_* definitions in syslog.h/gen_defs.h */
rswindell's avatar
rswindell committed
static char* logLevelStringList[]
    = {"Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Informational", "Debugging", NULL};
#define PACKET_TYPE_HELP_TEXT \
		"`Type-2  ` packets are defined in FTS-0001.16 (Stone Age)\n" \
		"`Type-2e ` packets are defined in FSC-0039.04 (Sometimes called 2+)\n" \
		"`Type-2+ ` packets are defined in FSC-0048.02 (4D address support)\n" \
		"`Type-2.2` packets are defined in FSC-0045.01 (5D address support)\n" \
rswindell's avatar
rswindell committed
void global_settings(void)
{
	static int global_opt;
Deucе's avatar
Deucе committed
	static int global_bar;
rswindell's avatar
rswindell committed

	while (1) {
		int  i = 0;
rswindell's avatar
rswindell committed
		char str[128];
rswindell's avatar
rswindell committed
		char duration[64];
		sprintf(opt[i++], "%-30s %s", "Mailer Type"
		        , cfg.flo_mailer ? "Binkley/FLO":"ArcMail/Attach");
		sprintf(opt[i++], "%-30s %s", "Log Level", logLevelStringList[cfg.log_level]);
		sprintf(opt[i++], "%-30s %s", "Log Timestamp Format", cfg.logtime);
		if (cfg.max_log_size) {
			SAFEPRINTF2(str, "%s bytes, keep %lu"
			            , byte_count_to_str(cfg.max_log_size, tmp, sizeof(tmp))
			            , cfg.max_logs_kept);
		} else {
			SAFECOPY(str, "Unlimited");
		}
		sprintf(opt[i++], "%-30s %s", "Maximum Log File Size", str);
		sprintf(opt[i++], "%-30s %s", "Strict Packet Passwords", cfg.strict_packet_passwords ? "Enabled" : "Disabled");
		sprintf(opt[i++], "%-30s %u", "Config File Backups", cfg.cfgfile_backups);
		sprintf(opt[i++], "%-30s %s bytes", "Minimum Free Disk Space"
		        , byte_count_to_str(cfg.min_free_diskspace, str, sizeof(str)));

		snprintf(opt[i++], MAX_OPLN - 1, "%-30s %-3.3s", "Strip Incoming Soft CRs "
		         , cfg.strip_soft_cr ? "Yes":"No");
		snprintf(opt[i++], MAX_OPLN - 1, "%-30s %-3.3s", "Strip Outgoing Line Feeds "
		         , cfg.strip_lf ? "Yes":"No");
		snprintf(opt[i++], MAX_OPLN - 1, "%-30s %-3.3s", "Auto-detect UTF-8 Messages "
		         , cfg.auto_utf8 ? "Yes":"No");
		snprintf(opt[i++], MAX_OPLN - 1, "%-30s %-3.3s", "Use Outboxes for Mail Files "
		         , cfg.use_outboxes ? "Yes":"No");
		snprintf(opt[i++], MAX_OPLN - 1, "%-30s %-3.3s", "Sort Linked Node List  "
		         , cfg.sort_nodelist ? "Yes":"No");
		snprintf(opt[i++], MAX_OPLN - 1, "%-30s %-3.3s", "Delete Processed Packets"
		         , cfg.delete_packets ? "Yes":"No");
		snprintf(opt[i++], MAX_OPLN - 1, "%-30s %s", "Incoming Bad Packets"
		         , cfg.delete_bad_packets ? "Deleted" : cfg.verbose_bad_packet_names ? "Renamed *.reason.bad" : "Renamed *.bad");
		snprintf(opt[i++], MAX_OPLN - 1, "%-30s %s", "Default Packet Type", pktTypeStringList[cfg.default_packet_type]);
		sprintf(opt[i++], "%-30s %s", "BSY Mutex File Timeout", duration_to_vstr(cfg.bsy_timeout, duration, sizeof(duration)));
		if (cfg.flo_mailer) {
			sprintf(opt[i++], "%-30s %s", "BSO Lock Attempt Delay", duration_to_vstr(cfg.bso_lock_delay, duration, sizeof(duration)));
			sprintf(opt[i++], "%-30s %lu", "BSO Lock Attempt Limit", cfg.bso_lock_attempts);
			sprintf(opt[i++], "%-30s %s", "BinkP Capabilities", cfg.binkp_caps);
			sprintf(opt[i++], "%-30s %s", "BinkP Sysop Name", cfg.binkp_sysop);
			sprintf(opt[i++], "%-30s %s", "BinkP Authentication", cfg.binkp_plainAuthOnly ? "Plain Only" : "Plain or CRAM-MD5");
			sprintf(opt[i++], "%-30s %s", "BinkP Encryption", !cfg.binkp_plainTextOnly && !cfg.binkp_plainAuthOnly ? "Supported" : "Unsupported");
rswindell's avatar
rswindell committed
		opt[i][0] = 0;
rswindell's avatar
rswindell committed
			"~ Global Settings ~\n"
			"\n"
			"`Mailer Type` should normally be set to `Binkley/FLO` to enable SBBSecho's\n"
			"    \"Binkley-Style Outbound\" operating mode (a.k.a. `BSO` or `FLO` mode).\n"
			"    If you are using an `Attach`, `ArcMail`, or `FrontDoor` style FidoNet\n"
			"    mailer, then set this setting to `ArcMail/Attach`, but know that most\n"
			"    modern FidoNet mailers are Binkley-Style and therefore that mode of\n"
			"    operation in SBBSecho is much more widely tested and supported.\n"
rswindell's avatar
rswindell committed
			"\n"
			"`Log Level` should normally be set to `Informational` but if you're\n"
			"    experiencing problems with SBBSecho and would like more verbose log\n"
			"    output, set this to `Debugging`. If you want less verbose logging,\n"
			"    set to higher-severity levels to reduce the number of log messages.\n"
			"\n"
			"`Log Timestamp Format` defines the format of the date/time-stamps added\n"
rswindell's avatar
rswindell committed
			"    along with each log message to the log file (e.g. sbbsecho.log).\n"
			"    The timestamp format is defined using standard C `strftime` notation.\n"
			"    The default format is: `" DEFAULT_LOG_TIME_FMT "`\n"
			"    For SBBSecho v2 timestamp format, use `%m/%d/%y %H:%M:%S`\n"
			"\n"
			"`Strict Packet Passwords`, when enabled, requires that Packet Passwords\n"
			"    must match the password for the linked node from which the packet\n"
			"    was received, even if that linked node has no password configured.\n"
rswindell's avatar
rswindell committed
			"    If you wish to revert to the SBBSecho v2 behavior with less strict\n"
			"    enforcement of matching packet passwords, disable this option.\n"
			"    Default: Enabled\n"
			"\n"
			"`Config File Backups` determines the number of automatic backups of your\n"
			"    SBBSecho configuration file (e.g. `sbbsecho.ini`) that will be\n"
			"    maintained by FidoNet Config (`echocfg`) and SBBSecho AreaManager.\n"
			"`Minimum Free Disk Space` determines the minimum amount of free disk\n"
			"    space for SBBSecho to run.  SBBSecho will just exit with an error\n"
			"    message (and an error level of 1) if the minimum amount of free\n"
			"    space is not found in directories into which SBBSecho may write.\n"
			"\n"
			"`Strip Incoming Soft CRs` instructs SBBSecho to remove any \"Soft\"\n"
			"    Carriage Return (ASCII 141) characters from the text of `imported`\n"
			"    EchoMail and NetMail messages.\n"
			"\n"
			"`Strip Outgoing Line Feeds` instructs SBBSecho to remove any Line Feed\n"
			"    (ASCII 10) characters from the body text of `exported` EchoMail and\n"
			"    NetMail messages.\n"
			"\n"
			"`Auto-detect UTF-8 Messages` instructs SBBSecho to treat incoming\n"
			"    messages which lack a CHRS/CHARSET control line and contain valid\n"
			"    UTF-8 character sequences in the message text, as UTF-8 encoded\n"
			"    messages.\n"
			"\n"
			"`Use Outboxes for Mail Files` instructs SBBSecho to place outbound\n"
			"    NetMail and EchoMail files into the configured `Outbox Directory`\n"
			"    of the relevant linked node. If the linked node has no configured\n"
			"    outbox, then outbound mail files for that node are placed in the\n"
			"    normal outbound directory hierarchy.  The BinkIT mailer will\n"
			"    send files from configured outboxes in addition to the normal\n"
			"    outbound directories, even when this option is set to `No`.\n"
			"\n"
			"`Sort Linked Node List` instructs SBBSecho to sort the list of linked\n"
			"    nodes (in sbbsecho.ini) both when reading and writing the file.\n"
			"\n"
			"`Delete Processed Packets` instructs SBBSecho to delete packet files\n"
			"    after they've been imported (as one would normally expect).\n"
			"\n"
			"`Incoming Bad Packets` can be `Deleted` or `Renamed` (*.bad) and optionally\n"
			"    include the `reason` in the renamed packet filename (the default).\n"
			"`Default Packet Type` is the type of packets that SBBSecho will generate\n"
			"    by default (i.e. for newly created and un-linked destination nodes).\n"
			"\n"
rswindell's avatar
rswindell committed
			"`BSY Mutex File Timeout` determines the maximum age of an existing\n"
			"    mutex file (`*.bsy`) before SBBSecho will act as though the mutex\n"
			"    file was not present.  This setting applies to the global\n"
			"    `sbbsecho.bsy` file as well as the BSO lock (`*.bsy`) files for\n"
			"    individual nodes.\n"
			"    Default: 12 hours\n"
			"\n"
			"`BSO Lock Attempt Delay` determines the amount of time between BSO\n"
			"    node lock attempts (via `*.bsy` files in the relevant outbound\n"
			"    directory).\n"
			"    Default: 10 seconds\n"
			"\n"
			"`BSO Lock Attempt Limit` determines the maximum number of BSO node lock\n"
			"    attempts before SBBSecho will give-up and move on to another node\n"
			"    to process mail.  This value multiplied by the `BSO Lock Attempt\n"
			"    Delay` should be much less than the `BSY Mutex File Timeout` value.\n"
			"    Default: 60 attempts\n"
			"\n"
			"`BinkP Capabilities` may be used to over-ride the default BinkP node\n"
			"    capabilities sent during a `BinkIT` mailer session (via the NDL\n"
			"    command). Default capabilities value is '115200,TCP,BINKP'\n"
			"\n"
			"`BinkP Sysop` may be used to over-ride the default BinkP sysop name\n"
			"    sent during a `BinkIT` mailer session (via the ZYZ command).\n"
			"    Default sysop name is that set in `SCFG->System->Operator`\n"
			"\n"
			"`BinkP Authentication` may be set to `Plain Only` if you wish to disable\n"
			"    CRAM-MD5 authentication for both inbound and outbound sessions.\n"
			"    Note: CRAM-MD5 authentication is required for encrypted sessions.\n"
			"\n"
			"`BinkP Encryption` may be set to `Supported` (the default) only when\n"
			"    BinkP Authentication is set to Plain or CRAM-MD5.\n"
			"    Default: Supported\n"
rswindell's avatar
rswindell committed

		int key = uifc.list(WIN_ACT | WIN_SAV, 0, 0, 0, &global_opt, &global_bar, "Global Settings", opt);
rswindell's avatar
rswindell committed

rswindell's avatar
rswindell committed

			case -1:
				return;

			case 0:
				cfg.flo_mailer = !cfg.flo_mailer;
				break;

			case 1:
				uifc.helpbuf =
					"~ Log Level ~\n"
					"\n"
					"Select the minimum severity of log entries to be logged to the log file.\n"
					"The default/normal setting is `Informational`.";
rswindell's avatar
rswindell committed
				int i = cfg.log_level;
				i = uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &i, 0, "Log Level", logLevelStringList);
				if (i >= 0 && i <= LOG_DEBUG)
					cfg.log_level = i;
rswindell's avatar
rswindell committed
				break;

			case 2:
				uifc.input(WIN_MID | WIN_SAV, 0, 0
				           , "Log Timestamp Format", cfg.logtime, sizeof(cfg.logtime) - 1, K_EDIT);
rswindell's avatar
rswindell committed
				break;

			case 3:
				byte_count_to_str(cfg.max_log_size, str, sizeof(str));
				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "Maximum Log File Size (in bytes, 0=unlimited)", str, 10, K_EDIT | K_UPPER) > 0) {
					cfg.max_log_size = parse_byte_count(str, 1);
					if (cfg.max_log_size) {
						SAFEPRINTF(str, "%lu", cfg.max_logs_kept);
						if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "Maximum Number of old Log Files to Keep", str, 5, K_EDIT | K_NUMBER) > 0)
							cfg.max_logs_kept = strtoul(str, NULL, 10);
					}
				}
rswindell's avatar
rswindell committed
				break;

			case 4:
				cfg.strict_packet_passwords = !cfg.strict_packet_passwords;
				break;

			case 5:
				sprintf(str, "%u", cfg.cfgfile_backups);
				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "Configuration File Backups", str, 5, K_EDIT | K_NUMBER) > 0)
				byte_count_to_str(cfg.min_free_diskspace, str, sizeof(str));
				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "Minimum Free Disk Space (in bytes)", str, 10, K_EDIT | K_UPPER) > 0)
					cfg.min_free_diskspace = parse_byte_count(str, 1);
				break;

				switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
				                  , "Strip Soft Carriage Returns from Incoming Messages", uifcYesNoOpts)) {
					case 0: cfg.strip_soft_cr = true;   break;
					case 1: cfg.strip_soft_cr = false;  break;
				switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
				                  , "Strip Line Feeds from Outgoing Messages", uifcYesNoOpts)) {
					case 0: cfg.strip_lf = true;    break;
					case 1: cfg.strip_lf = false;   break;
				switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
				                  , "Auto-detect incoming UTF-8 encoded messages", uifcYesNoOpts)) {
					case 0: cfg.auto_utf8 = true;   break;
					case 1: cfg.auto_utf8 = false;  break;
				switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
				                  , "Use Outboxes for Outbound NetMail and EchoMail", uifcYesNoOpts)) {
					case 0: cfg.use_outboxes = true;    break;
					case 1: cfg.use_outboxes = false;   break;
				switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
				                  , "Sort List of Linked Nodes", uifcYesNoOpts)) {
					case 0: cfg.sort_nodelist = true;   break;
					case 1: cfg.sort_nodelist = false;  break;
				switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
				                  , "Delete Incoming Processed Packets (.pkt files)", uifcYesNoOpts)) {
					case 0: cfg.delete_packets = true;  break;
					case 1: cfg.delete_packets = false; break;
				int k = !cfg.delete_bad_packets;
				switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
				                  , "Delete Bad Incoming Packets (.pkt files) When Detected", uifcYesNoOpts)) {
					case 0: cfg.delete_bad_packets = true;
					case 1: cfg.delete_bad_packets = false;
						int k = !cfg.verbose_bad_packet_names;
						switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
						                  , "Include Reason in Renamed Bad Packet Filenames", uifcYesNoOpts)) {
							case 0: cfg.verbose_bad_packet_names = true;    break;
							case 1: cfg.verbose_bad_packet_names = false;   break;
					"~ Default Packet Type ~\n\n"
					"This is the packet header type that will be used in mail packets\n"
					"by default (for newly created and un-linked nodes).\n"
					"\n"
					PACKET_TYPE_HELP_TEXT
					"\n"
					"The suggested default packet type is `Type-2+`.\n"
				;
				int k = cfg.default_packet_type;
				k = uifc.list(WIN_RHT | WIN_SAV, 0, 0, 0, &k, 0, "Packet Type", pktTypeStringList);
				if (k < 0)
				cfg.default_packet_type = k;
				uifc.changes = TRUE;
rswindell's avatar
rswindell committed
				duration_to_vstr(cfg.bsy_timeout, duration, sizeof(duration));
				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "BSY Mutex File Timeout", duration, 10, K_EDIT) > 0)
rswindell's avatar
rswindell committed
					cfg.bsy_timeout = (ulong)parse_duration(duration);
				break;

rswindell's avatar
rswindell committed
				duration_to_vstr(cfg.bso_lock_delay, duration, sizeof(duration));
				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "Delay Between BSO Lock Attempts", duration, 10, K_EDIT) > 0)
rswindell's avatar
rswindell committed
					cfg.bso_lock_delay = (ulong)parse_duration(duration);
				break;

				sprintf(str, "%lu", cfg.bso_lock_attempts);
				if (uifc.input(WIN_MID | WIN_SAV, 0, 0, "Maximum BSO Lock Attempts", str, 5, K_EDIT | K_NUMBER) > 0)
rswindell's avatar
rswindell committed
					cfg.bso_lock_attempts = atoi(str);
				break;

				uifc.input(WIN_MID | WIN_SAV, 0, 0
				           , "BinkP Capabilities (BinkIT)", cfg.binkp_caps, sizeof(cfg.binkp_caps) - 1, K_EDIT);
				uifc.input(WIN_MID | WIN_SAV, 0, 0
				           , "BinkP Sysop Name (BinkIT)", cfg.binkp_sysop, sizeof(cfg.binkp_sysop) - 1, K_EDIT);
			{
				int k = !cfg.binkp_plainAuthOnly;
				strcpy(opt[0], "Plain-Password Only");
				strcpy(opt[1], "Plain-Password or CRAM-MD5");
				opt[2][0] = 0;
				switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
				                  , "BinkP Authentication", opt)) {
					case 0:
						cfg.binkp_plainAuthOnly = true;
						break;
					case 1:
						cfg.binkp_plainAuthOnly = false;
						break;
				}
				break;
			}
				if (cfg.binkp_plainAuthOnly) {
					uifc.msg("CRAM-MD5 authentication/encryption has been disabled globally");
					break;
				}
				switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
				                  , "BinkP Encryption Supported", uifcYesNoOpts)) {
static bool new_node(unsigned new_nodenum)
{
	nodecfg_t* nodecfg = realloc(cfg.nodecfg, sizeof(nodecfg_t) * (cfg.nodecfgs + 1));
	if (nodecfg == NULL)
	for (unsigned int i = cfg.nodecfgs; i > new_nodenum; i--)
		memcpy(&cfg.nodecfg[i], &cfg.nodecfg[i - 1], sizeof(nodecfg_t));

	cfg.nodecfgs++;
	memset(&cfg.nodecfg[new_nodenum], 0, sizeof(nodecfg_t));
	cfg.nodecfg[new_nodenum].binkp_allowPlainText = true;
	cfg.nodecfg[new_nodenum].binkp_port = IPPORT_BINKP;
	cfg.nodecfg[new_nodenum].pkt_type = cfg.default_packet_type;
	return true;
}

static bool new_arcdef(unsigned new_arcnum)
{
	arcdef_t * arcdef = realloc(cfg.arcdef, sizeof(arcdef_t) * (cfg.arcdefs + 1));
	if (arcdef == NULL)
	for (unsigned j = cfg.arcdefs; j > new_arcnum; j--)
		memcpy(&cfg.arcdef[j], &cfg.arcdef[j - 1], sizeof(arcdef_t));
	cfg.arcdefs++;
	memset(&cfg.arcdef[new_arcnum], 0, sizeof(arcdef_t));
	return true;
}

static bool new_list(unsigned new_listnum)
{
	echolist_t *listcfg = realloc(cfg.listcfg, sizeof(echolist_t) * (cfg.listcfgs + 1));
	if (listcfg == NULL)
	for (unsigned j = cfg.listcfgs; j > new_listnum; j--)
		memcpy(&cfg.listcfg[j], &cfg.listcfg[j - 1], sizeof(echolist_t));
	memset(&cfg.listcfg[new_listnum], 0, sizeof(echolist_t));
static bool new_domain(unsigned new_domnum)
{
	struct fido_domain* new_list = realloc(cfg.domain_list, sizeof(struct fido_domain) * (cfg.domain_count + 1));
	if (new_list == NULL)
		return false;
	cfg.domain_list = new_list;
	for (unsigned i = cfg.domain_count; i > new_domnum; i--)
		memcpy(&cfg.domain_list[i], &cfg.domain_list[i - 1], sizeof(struct fido_domain));
	cfg.domain_count++;
	memset(&cfg.domain_list[new_domnum], 0, sizeof(struct fido_domain));
	return true;
}

static bool new_robot(unsigned new_botnum)
{
	struct robot* new_list = realloc(cfg.robot_list, sizeof(struct robot) * (cfg.robot_count + 1));
	if (new_list == NULL)
		return false;
	cfg.robot_list = new_list;
	for (unsigned i = cfg.robot_count; i > new_botnum; i--)
		memcpy(&cfg.robot_list[i], &cfg.robot_list[i - 1], sizeof(struct robot));
	cfg.robot_count++;
	memset(&cfg.robot_list[new_botnum], 0, sizeof(struct robot));
	return true;
}

static char* int_list(int* list, unsigned count)
{
	static char str[128];

	str[0] = 0;
	for (unsigned i = 0; i < count; i++) {
		if (i)
			sprintf(str + strlen(str), ",%d", *(list + i));
		else
			sprintf(str, "%d", *list);
	}

	return str;
}

void binkp_settings(nodecfg_t* node)
{
	while (1) {
		char  str[128];
		int   i = 0;
		sprintf(opt[i++], "%-20s %s", "Host", node->binkp_host);
		sprintf(opt[i++], "%-20s %u", "Port", node->binkp_port);
		sprintf(opt[i++], "%-20s %s", "Poll", node->binkp_poll ? "Yes" : "No");
		char* auth = "Plain Only";
		char* crypt = "Unsupported";
		if (!cfg.binkp_plainAuthOnly && !node->binkp_plainAuthOnly) {
			if (!cfg.binkp_plainTextOnly)
				crypt = node->binkp_allowPlainText ? "Supported" : "Required";
			if (node->binkp_allowPlainAuth)
				auth = "Plain or CRAM-MD5";
			else
				auth = "CRAM-MD5 Only";
		}
		sprintf(opt[i++], "%-20s %s", "Authentication", auth);
		sprintf(opt[i++], "%-20s %s", "Encryption", crypt);
		sprintf(opt[i++], "%-20s %s", "Implicit TLS", node->binkp_tls ? "Yes" : "No");
		sprintf(opt[i++], "%-20s %s", "Source Address", node->binkp_src);
		char title[128];
		SAFEPRINTF(title, "%s BinkP Settings", faddrtoa(&node->addr));
			"~ BinkP Settings ~\n"
			"\nThese settings are used by the Synchronet `BinkIT` BinkP/BSO mailer.\n"
			"\n"
			"`Host` defines the TCP/IP address or host name with which to connect for\n"
			"    sessions with this linked node.  If the host is not set, then a\n"
			"    DNS-based look-up will be attempted (e.g. the IP address for\n"
			"    `1:103/705` would be looked-up via host name `f705.n103.z1.binkp.net`).\n"
			"    Nodelist-based look-ups are also supported.\n"
			"\n"
			"`Port` defines the TCP port used by this linked node for BinkP sessions.\n"
			"    The default BinkP TCP port is `24554`.\n"
			"\n"
			"`Poll` defines whether or not to periodically poll this linked node.\n"
			"\n"
			"`Authentication` determines what types of authentication will be supported\n"
			"    during both inbound and outbound sessions with this linked node.\n"
			"    The supported BinkP-auth methods are `Plain-Password` and `CRAM-MD5`.\n"
			"    Note: For `incoming` connections, CRAM-MD5 will be supported unless\n"
			"    CRAM-MD5 authentication has been `globally` disabled.\n"
			"`Encryption` determines whether unencrypted data transfers will be\n"
			"    supported or required when communicating with this linked node.\n"
			"    With this setting set to `Required`, ~only~ BinkD-style-encrypted BinkP\n"
			"    sessions will be supported.\n"
			"    CRAM-MD5 authentication `must` be used when encrypting BinkP sessions.\n"
			"`Implicit TLS` defines whether or not to use `BINKPS` when connecting\n"
			"    (outbound) with this linked node.\n"
			"\n"
			"`Source Address` allows you to override the source FTN address used\n"
			"    with outgoing BinkP mailer sessions with this linked node.\n"
			"    Normally, this setting is left blank (not set).\n"
		;
		int k = uifc.list(WIN_RHT | WIN_BOT | WIN_SAV | WIN_ACT, 0, 0, 0, &cur, 0, title, opt);
		if (k < 0)
				uifc.input(WIN_MID | WIN_SAV, 0, 0
				           , "Host name or IP address", node->binkp_host, sizeof(node->binkp_host) - 1, K_EDIT);
				break;
			case 1:
				sprintf(str, "%u", node->binkp_port);
				if (uifc.input(WIN_MID | WIN_SAV, 0, 0
				               , "TCP Port Number (e.g. 24554)", str, 5, K_EDIT | K_NUMBER) > 0) {
					node->binkp_port = atoi(str);
					uifc.changes = TRUE;
				}
				break;
			case 2:
				k = !node->binkp_poll;
				switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
				                  , "Poll This Node Periodically for Inbound Files/Mail", uifcYesNoOpts)) {
					case 0: node->binkp_poll = true;    uifc.changes = TRUE; break;
					case 1: node->binkp_poll = false;   uifc.changes = TRUE; break;
				if (cfg.binkp_plainAuthOnly) {
					uifc.msg("CRAM-MD5 authentication/ has been disabled globally");
				k = node->binkp_plainAuthOnly ? 0 : (1 + !node->binkp_allowPlainAuth);
				strcpy(opt[0], "Plain-Password Only");
				strcpy(opt[1], "Plain-Password or CRAM-MD5");
				strcpy(opt[2], "CRAM-MD5 Only");
				opt[3][0] = 0;
				switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
				                  , "Authentication", opt)) {
						node->binkp_plainAuthOnly = true;
						node->binkp_allowPlainAuth = true;
						node->binkp_allowPlainText = true;
						uifc.changes = TRUE;
						break;
					case 1:
						node->binkp_allowPlainAuth = true;
						node->binkp_plainAuthOnly = false;
						node->binkp_allowPlainText = true;
						uifc.changes = TRUE;
						break;
					case 2:
						node->binkp_allowPlainAuth = false;
						node->binkp_plainAuthOnly = false;
						uifc.changes = TRUE;
				if (cfg.binkp_plainTextOnly) {
					uifc.msg("BinkP encryption has been disabled globally");
					break;
				}
				if (cfg.binkp_plainAuthOnly) {
					uifc.msg("CRAM-MD5 authentication/encryption has been disabled globally");
					break;
				}
				switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
				                  , "Allow Plain-Text (Unencrypted) Sessions", uifcYesNoOpts)) {
					case 0:
						node->binkp_allowPlainText = true;
						node->binkp_allowPlainAuth = true;
						uifc.changes = TRUE;
						break;
					case 1:
						node->binkp_allowPlainText = false;
						node->binkp_allowPlainAuth = false;
						node->binkp_plainAuthOnly = false;
						uifc.changes = TRUE;
				switch (uifc.list(WIN_MID | WIN_SAV, 0, 0, 0, &k, 0
				                  , "Use BINKPS (Implicit TLS) Connections with This Node", uifcYesNoOpts)) {
					case 0: node->binkp_tls = true;     uifc.changes = TRUE; break;
					case 1: node->binkp_tls = false;    uifc.changes = TRUE; break;
				uifc.helpbuf =
					"~ Source Address ~\n\n"
					"This is the FidoNet style address to use as the source address when\n"
					"conducting BinkP sessions with this linked node.";
				uifc.input(WIN_MID | WIN_SAV, 0, 0
				           , "Source Node Address (optional)"
				           , node->binkp_src
				           , sizeof(node->binkp_src) - 1, K_EDIT);
#ifdef _WIN32
	#define printf cprintf
#endif

void banner()
{

	DESCRIBE_COMPILER(compiler);

	printf("\nSynchronet FidoNet Configuration  Version %u.%02u  " COPYRIGHT_NOTICE
	       "\n\n", SBBSECHO_VERSION_MAJOR, SBBSECHO_VERSION_MINOR);
	printf("Compiled %s/%s %s with %s\n", GIT_BRANCH, GIT_HASH, GIT_DATE, compiler);
void read_echocfg_ini()
{
	char path[MAX_PATH + 1];

	snprintf(path, sizeof path, "%s/echocfg.ini", get_ctrl_dir(/* warn: */ false));
	if (!fexist(path))
		snprintf(path, sizeof path, "%s/uifc.ini", get_ctrl_dir(/* warn: */ false));
	read_uifc_ini(path, &uifc, &ciolib_mode, &video_mode);
}

int main(int argc, char **argv)
{
	char               str[256], *p;
	int                i, j, k, x, dflt, nodeop = 0, nodeopbar = 0, packop = 0, listop = 0;
	echolist_t         savlistcfg;
	nodecfg_t          savnodecfg;
	arcdef_t           savarcdef;
	struct robot       saverobot = {
Deucе's avatar
Deucе committed
		.name = ""
	};
	BOOL               door_mode = FALSE;
	unsigned int       u;
	char               sysop_aliases[256];
	sbbsecho_cfg_t     orig_cfg;
	ZERO_VAR(savlistcfg);
	ZERO_VAR(savnodecfg);
	ZERO_VAR(savarcdef);
#if defined(_WIN32)
	cio_api.options |= CONIO_OPT_DISABLE_CLOSE;
#else
	banner();
#endif
	memset(&cfg, 0, sizeof(cfg));
	str[0] = 0;
	for (i = 1; i < argc; i++) {
		if (strcmp(argv[i], "-insert") == 0) {
		if (argv[i][0] == '-')
			switch (toupper(argv[i][1])) {
				case 'D':
rswindell's avatar
rswindell committed
					printf("NOTICE: The -d option is deprecated, use -id instead\n");
					SLEEP(2000);
					door_mode = TRUE;
					break;
				case 'L':
					uifc.scrn_len = atoi(argv[i] + 2);
					break;
				case 'E':
					uifc.esc_delay = atoi(argv[i] + 2);
					break;
					switch (toupper(argv[i][2])) {
							ciolib_mode = CIOLIB_MODE_ANSI;
#if defined __unix__
							ciolib_mode = CIOLIB_MODE_CURSES;
							break;
						case 0:
rswindell's avatar
rswindell committed
							printf("NOTICE: The -i option is deprecated, use -if instead\n");
							SLEEP(2000);
							ciolib_mode = CIOLIB_MODE_CURSES_IBM;
							ciolib_mode = CIOLIB_MODE_CURSES_ASCII;
							ciolib_mode = CIOLIB_MODE_X;
#elif defined _WIN32
							ciolib_mode = CIOLIB_MODE_CONIO;
						case 'G':
							switch (toupper(argv[i][3])) {
								case 0:
								case 'W':
									ciolib_mode = CIOLIB_MODE_GDI;
									break;
								case 'F':
									ciolib_mode = CIOLIB_MODE_GDI_FULLSCREEN;
									break;
							}
							break;
#endif
							door_mode = TRUE;
							break;
						default:
							goto USAGE;
					}
				case 'K':   /* Keyboard-only mode (no mouse support) */
				case 'M':   /* Monochrome mode */
					uifc.mode |= UIFC_MONO;
					break;
				case 'C':
					uifc.mode |= UIFC_COLOR;
					break;
				case 'V':
					video_mode = atoi(argv[i] + 2);
					break;
					ciolib_initial_scaling = strtod(argv[i] + 2, NULL);
#ifdef _WIN32
					uifc.size = sizeof(uifc);
					uifc.mode |= UIFC_NOMOUSE;
					initciolib(CIOLIB_MODE_CONIO);
					uifcini32(&uifc);
					banner();
#endif
					printf("\nusage: echocfg [path/to/sbbsecho.ini] [options]"
					       "\n\noptions:\n\n"
					       "-k     keyboard mode only (no mouse support)\n"
					       "-c     force color mode\n"
					       "-m     force monochrome mode\n"
					       "-e#    set escape delay to #msec\n"
					       "-iX    set interface mode to X (default=auto) where X is one of:\n"
#ifdef __unix__
					       "        X  = X11 mode\n"
					       "        C  = Curses mode\n"
					       "        F  = Curses mode with forced IBM charset\n"
					       "        I  = Curses mode with forced ASCII charset\n"
					       "        W  = Win32 console mode\n"
#if defined(WITH_GDI)
					       "        G  = Win32 graphics mode\n"
					       "        GF = Win32 graphics mode, full screen\n"
#endif // WITH_GDI
					       "        A  = ANSI mode\n"
					       "        D  = standard input/output/door mode\n"
					       "-v#    set video mode to # (default=%u)\n"
					       "-l#    set window lines to # (default=auto-detect)\n"
					       "-s#    set window scaling factor to # (default=1.0)\n"
					       , video_mode
					       );
#ifdef _WIN32
					printf("\nHit a key to close...");
					getch();
#undef printf
#endif
			SAFECOPY(str, argv[i]);
	if (str[0] == 0) {
		SAFECOPY(cfg.cfgfile, get_ctrl_dir(/* warn: */ true));
		backslash(cfg.cfgfile);
		SAFECAT(cfg.cfgfile, "sbbsecho.ini");
	} else {
		SAFECOPY(cfg.cfgfile, str);
	if (!sbbsecho_read_ini(&cfg)) {
rswindell's avatar
rswindell committed
		fprintf(stderr, "ERROR %d (%s) reading %s\n", errno, strerror(errno), cfg.cfgfile);
		exit(1);
	}
	orig_cfg = cfg;
	if ((opt = (char **)malloc(sizeof(char *) * 1000)) == NULL) {
		puts("memory allocation error\n");
rswindell's avatar
rswindell committed
		exit(1);
	for (i = 0; i < 1000; i++)
		if ((opt[i] = (char *)malloc(MAX_OPLN + 1)) == NULL) {
			puts("memory allocation error\n");
rswindell's avatar
rswindell committed
			exit(1);
	uifc.size = sizeof(uifc);
	if (!door_mode) {
		ciolib_initial_mode = video_mode;
		i = initciolib(ciolib_mode);
		if (i != 0) {
			printf("ciolib library init returned error %d\n", i);
			exit(1);
		ciolib_settitle("Synchronet FidoNet Configuration");
		i = uifcini32(&uifc);  /* curses/conio/X/ANSI */
		i = uifcinix(&uifc); /* stdio */
	if (i != 0) {
		printf("uifc library init returned error %d\n", i);
rswindell's avatar
rswindell committed
	uifc.timedisplay = NULL;
	sprintf(str, "Synchronet FidoNet Config v%u.%02u", SBBSECHO_VERSION_MAJOR, SBBSECHO_VERSION_MINOR);
	uifc.scrn(str);
	p = cfg.cfgfile;
	if (strlen(p) + strlen(str) + 4 > uifc.scrn_width)
		p = getfname(cfg.cfgfile);
	uifc.printf(uifc.scrn_width - (strlen(p) + 1), 1, uifc.bclr | (uifc.cclr << 4), p);
	if (cfg.used_include && uifc.deny("%s uses !include, continue read only", getfname(p))) {
rswindell's avatar
rswindell committed
	/* Remember current menu item selections using these vars: */
	int netmail_opt = 0;
	int echomail_opt = 0;
	int path_opt = 0;
	int node_opt = 0;
rswindell's avatar
rswindell committed
	int archive_opt = 0;
	int archive_bar = 0;
rswindell's avatar
rswindell committed
	int echolist_opt = 0;
	int echolist_bar = 0;
	int domain_bar = 0;
	int robot_opt = 0;
	int robot_bar = 0;
	dflt = 0;
	while (1) {
		if (memcmp(&cfg, &orig_cfg, sizeof(cfg)) != 0)
rswindell's avatar
rswindell committed
			uifc.changes = TRUE;
		uifc.helpbuf =
			"~ FidoNet Configuration ~\n\n"
			"This program allows you to easily configure the Synchronet BBS\n"
			"FidoNet-style EchoMail program known as `SBBSecho` and the FidoNet/BinkP\n"
			"mailer known as `BinkIT`.  Alternatively, you may edit the configuration\n"
			"file (e.g. `ctrl/sbbsecho.ini`) using an ASCII/plain-text editor.\n"
			"\n"
			"For detailed documentation, see `http://wiki.synchro.net/util:sbbsecho`\n"
			"                            and `http://wiki.synchro.net/module:binkit`\n"
			"\n"
			"The `Global Settings` sub-menu is where FidoNet configuration settings\n"
			"are located which are neither NetMail nor EchoMail specific, but more\n"
			"general to the operation of the tosser (SBBSecho) and mailer (BinkIT).\n"
			"\n"
			"The `Linked Nodes` sub-menu is where you configure your FidoNet-style\n"
			"links: other FidoNet-style nodes/systems you regularly connect with\n"
			"to exchange mail/files.\n"
			"\n"
			"The `Archive Types` sub-menu is where you configure your archive\n"
			"programs (a.k.a. \"packers\") used for the packing and unpacking of\n"
			"EchoMail bundle files (usually in 'PKZIP' format).\n"
			"\n"
			"The `NetMail Settings` sub-menu is where you configure settings specific\n"
			"to NetMail (private one-on-one networked mail).\n"
			"\n"
			"The `EchoMail Settings` sub-menu is where you configure settings specific\n"
			"to EchoMail (public group discussions in topical message areas, echoes).\n"
			"\n"
			"The `Paths and Filenames` sub-menu is where you configure your system's\n"
			"directory and file paths used by SBBSecho.\n"
			"\n"
			"The `Robots` sub-menu is where NetMail Robots (e.g. TickFix) are\n"
			"configured.\n"
			"\n"
			"The `Domains` sub-menu is where FidoNet-style domains (the '@domain'\n"
			"of 5D FTN addresses) are mapped to zone numbers, DNS suffixes, NodeLists\n"
			"and BSO root directories for use by the BinkIT mailer.\n"
			"\n"
			"The `EchoLists` sub-menu is for configuring additional (optional)\n"
			"lists of FidoNet-style message areas (echoes) in `BACKBONE.NA` file\n"
			"format.  These lists, if configured, are used in addition to your main\n"
			"`Area File` (e.g. areas.bbs) for advanced AreaManager/AreaFix operations."
		;
		i = 0;
		sprintf(opt[i++], "Global Settings");
		sprintf(opt[i++], "Linked Nodes");