sbbsecho.c 195 KB
Newer Older
1
/* Synchronet FidoNet EchoMail Scanning/Tossing and NetMail Tossing Utility */
2

3 4 5 6
/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
7
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
8 9 10 11 12 13 14 15 16 17 18 19 20
 *																			*
 * 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.	*
 ****************************************************************************/
21

22
/* Portions written by Allen Christiansen 1994-1996 						*/
23 24 25 26 27 28 29 30 31

#include <time.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
32
#include <sys/stat.h>
33 34 35
#if defined(__unix__)
	#include <signal.h>
#endif
36

rswindell's avatar
rswindell committed
37
#include "conwrap.h"		/* getch() */
38
#include "load_cfg.h"		/* load_cfg() */
39 40 41
#include "smblib.h"
#include "scfglib.h"
#include "sbbsecho.h"
deuce's avatar
deuce committed
42
#include "genwrap.h"		/* PLATFORM_DESC */
rswindell's avatar
rswindell committed
43
#include "xpendian.h"
44
#include "utf8.h"
45 46 47 48 49 50 51 52
#include "date_str.h"
#include "link_list.h"
#include "str_list.h"
#include "str_util.h"
#include "datewrap.h"
#include "nopen.h"
#include "crc32.h"
#include "userdat.h"
53
#include "filedat.h"
54 55 56
#include "msg_id.h"
#include "scfgsave.h"
#include "getmail.h"
57 58
#include "git_branch.h"
#include "git_hash.h"
rswindell's avatar
rswindell committed
59 60

#define MAX_OPEN_SMBS	10
61 62

smb_t *smb,*email;
rswindell's avatar
rswindell committed
63 64
bool opt_import_packets		= true;
bool opt_import_netmail		= true;
rswindell's avatar
rswindell committed
65
bool opt_delete_netmail		= true;		/* delete after importing (no effect on exported netmail) */
rswindell's avatar
rswindell committed
66 67 68 69 70 71 72 73 74
bool opt_import_echomail	= true;
bool opt_export_echomail	= true;
bool opt_export_netmail		= true;
bool opt_pack_netmail		= true;
bool opt_gen_notify_list	= false;
bool opt_export_ftn_echomail= false;	/* Export msgs previously imported from FidoNet */
bool opt_update_msgptrs		= false;
bool opt_ignore_msgptrs		= false;
bool opt_leave_msgptrs		= false;
75
bool opt_dump_area_file		= false;
76
bool opt_retoss_badmail		= false;	/* Re-toss from the badecho/unknown msg sub */
rswindell's avatar
rswindell committed
77

rswindell's avatar
rswindell committed
78 79 80 81 82 83
/* statistics */
ulong netmail=0;	/* imported */
ulong echomail=0;	/* imported */
ulong exported_netmail=0;
ulong exported_echomail=0;
ulong packed_netmail=0;
84 85 86 87
ulong packets_sent=0;
ulong packets_imported=0;
ulong bundles_sent=0;
ulong bundles_unpacked=0;
rswindell's avatar
rswindell committed
88

rswindell's avatar
rswindell committed
89
int cur_smb=0;
90
FILE *fidologfile=NULL;
91
str_list_t twit_list;
rswindell's avatar
rswindell committed
92
str_list_t bad_areas;
93

rswindell's avatar
rswindell committed
94 95 96 97
fidoaddr_t		sys_faddr = {1,1,1,0};		/* Default system address: 1:1/1.0 */
sbbsecho_cfg_t	cfg;
scfg_t			scfg;
char			compiler[32];
98

rswindell's avatar
rswindell committed
99 100 101 102
bool pause_on_exit=false;
bool pause_on_abend=false;
bool mtxfile_locked=false;
bool terminated=false;
103

104 105
str_list_t	locked_bso_nodes;

106 107 108 109 110
int lprintf(int level, char *fmt, ...)
#if defined(__GNUC__)   // Catch printf-format errors
    __attribute__ ((format (printf, 2, 3)));
#endif
;
rswindell's avatar
rswindell committed
111
int mv(const char *insrc, const char *indest, bool copy);
112
time32_t fmsgtime(const char *str);
rswindell's avatar
rswindell committed
113
void export_echomail(const char *sub_code, const nodecfg_t*, bool rescan);
rswindell's avatar
rswindell committed
114
const char* area_desc(const char* areatag);
rswindell's avatar
rswindell committed
115

116 117 118 119
/* FTN-compliant "Program Identifier"/PID (also used as a "Tosser Identifier"/TID) */
const char* sbbsecho_pid(void)
{
	static char str[256];
rswindell's avatar
rswindell committed
120

121
	sprintf(str, "SBBSecho %u.%02u-%s %s/%s %s %s"
122
		,SBBSECHO_VERSION_MAJOR,SBBSECHO_VERSION_MINOR,PLATFORM_DESC,GIT_BRANCH,GIT_HASH,__DATE__,compiler);
123 124 125 126 127 128 129 130

	return str;
}

/* for *.bsy file contents: */
const char* program_id(void)
{
	static char str[256];
rswindell's avatar
rswindell committed
131

132 133 134 135 136
	SAFEPRINTF2(str, "%u %s", getpid(), sbbsecho_pid());

	return str;
}

137
const char* tear_line(char ch)
rswindell's avatar
rswindell committed
138 139 140
{
	static char str[256];

141
	sprintf(str,"%c%c%c SBBSecho %u.%02u-%s\r", ch, ch, ch
rswindell's avatar
rswindell committed
142 143 144 145 146
		,SBBSECHO_VERSION_MAJOR,SBBSECHO_VERSION_MINOR,PLATFORM_DESC);

	return str;
}

147 148 149 150
const char default_domain[] = "fidonet";

const char* zone_domain(uint16_t zone)
{
151 152 153 154
	for(unsigned i = 0; i < cfg.domain_count; i++)
		for(unsigned j = 0; j < cfg.domain_list[i].zone_count; j++)
			if(cfg.domain_list[i].zone_list[j] == zone)
				return cfg.domain_list[i].name;
155 156 157 158 159 160

	return default_domain;
}

const char* zone_root_outbound(uint16_t zone)
{
161 162 163 164
	for(unsigned i = 0; i < cfg.domain_count; i++)
		for(unsigned j = 0; j < cfg.domain_list[i].zone_count; j++)
			if(cfg.domain_list[i].zone_list[j] == zone)
				return cfg.domain_list[i].root;
165 166 167 168

	return cfg.outbound;
}

169 170 171 172 173 174 175 176
/* Allocates the buffer and returns the data (or NULL) */
char* parse_control_line(const char* fmsgbuf, const char* kludge)
{
	char*	p;
	char	str[128];

	if(fmsgbuf == NULL)
		return NULL;
177
	SAFEPRINTF(str, "\1%s", kludge);
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
	p = strstr(fmsgbuf, str);
	if(p == NULL)
		return NULL;
	if(p != fmsgbuf && *(p-1) != '\r' && *(p-1) != '\n')	/* Kludge must start new-line */
		return NULL;
	SAFECOPY(str, p);

	/* Terminate at the CR */
	p = strchr(str, '\r');
	if(p == NULL)
		return NULL;
	*p = 0;

	/* Only return the data portion */
	p = str + strlen(kludge) + 1;
	SKIP_WHITESPACE(p);
	return strdup(p);
}

rswindell's avatar
rswindell committed
197 198 199 200 201 202
/* Write an FTS-4009 compliant "Via" control line to the (netmail) file */
int fwrite_via_control_line(FILE* fp, fidoaddr_t* addr)
{
	time_t t = time(NULL);
	struct tm* tm = gmtime(&t);
	return fprintf(fp,"\1Via %s @%04u%02u%02u.%02u%02u%02u.UTC "
203
		"SBBSecho %u.%02u-%s %s/%s\r"
rswindell's avatar
rswindell committed
204 205 206 207 208 209 210
		,smb_faddrtoa(addr, NULL)
		,tm->tm_year+1900
		,tm->tm_mon+1
		,tm->tm_mday
		,tm->tm_hour
		,tm->tm_min
		,tm->tm_sec
211
		,SBBSECHO_VERSION_MAJOR,SBBSECHO_VERSION_MINOR,PLATFORM_DESC,GIT_BRANCH,GIT_HASH);
rswindell's avatar
rswindell committed
212
}
213 214 215 216 217 218 219 220

int fwrite_intl_control_line(FILE* fp, fmsghdr_t* hdr)
{
	return fprintf(fp,"\1INTL %hu:%hu/%hu %hu:%hu/%hu\r"
		,hdr->destzone,hdr->destnet,hdr->destnode
		,hdr->origzone,hdr->orignet,hdr->orignode);
}

221 222 223 224 225 226 227 228
typedef struct echostat_msg {
	char msg_id[128];
	char reply_id[128];
	char pid[128];
	char tid[128];
	char to[FIDO_NAME_LEN];
	char from[FIDO_NAME_LEN];
	char subj[FIDO_SUBJ_LEN];
229 230
	char msg_tz[128];
	time_t msg_time;
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
	time_t localtime;
	size_t length;
	fidoaddr_t origaddr;
	fidoaddr_t pkt_orig;
} echostat_msg_t;

enum echostat_msg_type {
	ECHOSTAT_MSG_RECEIVED,
	ECHOSTAT_MSG_IMPORTED,
	ECHOSTAT_MSG_EXPORTED,
	ECHOSTAT_MSG_DUPLICATE,
	ECHOSTAT_MSG_CIRCULAR,
	ECHOSTAT_MSG_TYPES
};

const char* echostat_msg_type[ECHOSTAT_MSG_TYPES] = {
	"Received",
	"Imported",
	"Exported",
	"Duplicate",
	"Circular",
};

typedef struct echostat {
	char tag[FIDO_AREATAG_LEN + 1];
256
	bool known;	// listed in Area File
257 258 259 260 261 262 263 264
	echostat_msg_t last[ECHOSTAT_MSG_TYPES];
	echostat_msg_t first[ECHOSTAT_MSG_TYPES];
	ulong total[ECHOSTAT_MSG_TYPES];
} echostat_t;

echostat_t* echostat;
size_t echostat_count;

265 266
int echostat_compare(const void* c1, const void* c2)
{
267 268
	echostat_t* stat1 = (echostat_t*)c1;
	echostat_t* stat2 = (echostat_t*)c2;
269 270 271 272

	return stricmp(stat1->tag, stat2->tag);
}

273 274 275 276
echostat_msg_t parse_echostat_msg(str_list_t ini, const char* section, const char* prefix)
{
	char str[128];
	char key[128];
rswindell's avatar
rswindell committed
277
	echostat_msg_t msg = {{0}};
278 279 280 281 282 283 284 285

	sprintf(key, "%s.to", prefix),			iniGetString(ini, section, key, NULL, msg.to);
	sprintf(key, "%s.from", prefix),		iniGetString(ini, section, key, NULL, msg.from);
	sprintf(key, "%s.subj", prefix),		iniGetString(ini, section, key, NULL, msg.subj);
	sprintf(key, "%s.msg_id", prefix),		iniGetString(ini, section, key, NULL, msg.msg_id);
	sprintf(key, "%s.reply_id", prefix),	iniGetString(ini, section, key, NULL, msg.reply_id);
	sprintf(key, "%s.pid", prefix),			iniGetString(ini, section, key, NULL, msg.pid);
	sprintf(key, "%s.tid", prefix),			iniGetString(ini, section, key, NULL, msg.tid);
286 287
	sprintf(key, "%s.msg_tz", prefix),		iniGetString(ini, section, key, NULL, msg.msg_tz);
	sprintf(key, "%s.msg_time", prefix),	msg.msg_time = iniGetDateTime(ini, section, key, 0);
288 289 290 291 292 293 294 295 296 297
	sprintf(key, "%s.localtime", prefix),	msg.localtime = iniGetDateTime(ini, section, key, 0);
	sprintf(key, "%s.length", prefix),		msg.length = (size_t)iniGetBytes(ini, section, key, 1, 0);
	sprintf(key, "%s.origaddr", prefix),	iniGetString(ini, section, key, NULL, str);
	if(str[0])	 msg.origaddr = atofaddr(str);
	sprintf(key, "%s.pkt_orig", prefix),	iniGetString(ini, section, key, NULL, str);
	if(str[0])	 msg.pkt_orig = atofaddr(str);

	return msg;
}

298
echostat_msg_t fidomsg_to_echostat_msg(fmsghdr_t* hdr, fidoaddr_t* pkt_orig, const char* fmsgbuf)
299 300
{
	char* p;
rswindell's avatar
rswindell committed
301
	echostat_msg_t msg = {{0}};
302

303 304 305 306
	SAFECOPY(msg.to		, hdr->to);
	SAFECOPY(msg.from	, hdr->from);
	SAFECOPY(msg.subj	, hdr->subj);
	msg.msg_time		= fmsgtime(hdr->time);
307
	msg.localtime		= time(NULL);
308 309 310 311
	msg.origaddr.zone	= hdr->origzone;
	msg.origaddr.net	= hdr->orignet;
	msg.origaddr.node	= hdr->orignode;
	msg.origaddr.point	= hdr->origpoint;
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
	if(pkt_orig != NULL)
		msg.pkt_orig	= *pkt_orig;
	if((p = parse_control_line(fmsgbuf, "MSGID:")) != NULL) {
		SAFECOPY(msg.msg_id, p);
		free(p);
	}
	if((p = parse_control_line(fmsgbuf, "REPLY:")) != NULL) {
		SAFECOPY(msg.reply_id, p);
		free(p);
	}
	if((p = parse_control_line(fmsgbuf, "PID:")) != NULL) {
		SAFECOPY(msg.pid, p);
		free(p);
	}
	if((p = parse_control_line(fmsgbuf, "TID:")) != NULL) {
		SAFECOPY(msg.tid, p);
		free(p);
	}
330 331
	if((p = parse_control_line(fmsgbuf, "TZUTC:")) != NULL
		|| (p = parse_control_line(fmsgbuf, "TZUTCINFO:")) != NULL) {
332 333 334
		SAFECOPY(msg.msg_tz, p);
		free(p);
	}
335 336 337 338 339 340
	if(fmsgbuf != NULL)
		msg.length = strlen(fmsgbuf);

	return msg;
}

341
echostat_msg_t smsg_to_echostat_msg(const smbmsg_t* smsg, size_t msglen, fidoaddr_t addr)
342 343
{
	char* p;
rswindell's avatar
rswindell committed
344
	echostat_msg_t emsg = {{0}};
345

346 347 348 349
	SAFECOPY(emsg.to	, smsg->to);
	SAFECOPY(emsg.from	, smsg->from);
	SAFECOPY(emsg.subj	, smsg->subj);
	emsg.msg_time		= smsg->hdr.when_written.time;
350
	emsg.localtime		= time(NULL);
351 352 353 354
	SAFECOPY(emsg.msg_tz, smb_zonestr(smsg->hdr.when_written.zone, NULL));
	if(smsg->from_net.type == NET_FIDO && smsg->from_net.addr != NULL)
		emsg.origaddr	= *(fidoaddr_t*)smsg->from_net.addr;
	if((p = smsg->ftn_msgid) != NULL)
355
		SAFECOPY(emsg.msg_id, p);
356
	if((p = smsg->ftn_reply) != NULL)
357
		SAFECOPY(emsg.reply_id, p);
358
	if((p = smsg->ftn_pid) != NULL)
359
		SAFECOPY(emsg.pid, p);
360
	if((p = smsg->ftn_tid) != NULL)
361
		SAFECOPY(emsg.tid, p);
362 363
	else
		SAFECOPY(emsg.tid, sbbsecho_pid());
364
	emsg.length = msglen;
365
	emsg.pkt_orig = addr;
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403

	return emsg;
}

void new_echostat_msg(echostat_t* stat, enum echostat_msg_type type, echostat_msg_t msg)
{
	stat->last[type] = msg;
	if(stat->first[type].localtime == 0)
		stat->first[type] = msg;
	stat->total[type]++;
}

size_t read_echostats(const char* fname, echostat_t **echostat)
{
	str_list_t ini;
	FILE* fp = iniOpenFile(fname, FALSE);
	if(fp == NULL)
		return 0;

	ini = iniReadFile(fp);
	iniCloseFile(fp);

	str_list_t echoes = iniGetSectionList(ini, NULL);
	if(echoes == NULL) {
		iniFreeStringList(ini);
		return 0;
	}

	size_t echo_count = strListCount(echoes);
	*echostat = malloc(sizeof(echostat_t) * echo_count);
	if(*echostat == NULL) {
		iniFreeStringList(echoes);
		iniFreeStringList(ini);
		return 0;
	}
	for(unsigned i = 0; i < echo_count; i++) {
		echostat_t* stat = &(*echostat)[i];
		SAFECOPY(stat->tag, echoes[i]);
404 405 406
		str_list_t keys = iniGetSection(ini, stat->tag);
		if(keys == NULL)
			continue;
407 408 409
		for(int type = 0; type < ECHOSTAT_MSG_TYPES; type++) {
			char prefix[32];
			sprintf(prefix, "First%s", echostat_msg_type[type])
410
				,stat->first[type]	= parse_echostat_msg(keys, NULL, prefix);
411
			sprintf(prefix, "Last%s", echostat_msg_type[type])
412
				,stat->last[type]	= parse_echostat_msg(keys, NULL, prefix);
413
			sprintf(prefix, "Total%s", echostat_msg_type[type])
414
				,stat->total[type] = iniGetLongInt(keys, NULL, prefix, 0);
415
		}
416 417
		stat->known = iniGetBool(keys, NULL, "Known", false);
		iniFreeStringList(keys);
418 419 420 421
	}
	iniFreeStringList(echoes);
	iniFreeStringList(ini);

422
	lprintf(LOG_DEBUG, "Read %lu echo statistics from %s", (ulong)echo_count, fname);
423 424 425 426 427 428 429 430 431 432 433 434 435
	return echo_count;
}

/* Mimic iniSetDateTime() */
const char* iniTimeStr(time_t t)
{
	static char	tstr[32];
	char	tmp[32];
	char*	p;

	if(t == 0)
		return "Never";
	if((p = ctime_r(&t, tmp)) == NULL)
436
		sprintf(tstr, "0x%lx", (ulong)t);
437 438
	else
		sprintf(tstr, "%.3s %.2s %.4s %.8s", p+4, p+8, p+20, p+11);
rswindell's avatar
rswindell committed
439

440 441 442
	return tstr;
}

443
void fwrite_echostat_msg(FILE* fp, const echostat_msg_t* msg, const char* prefix)
444
{
rswindell's avatar
rswindell committed
445
	echostat_msg_t zero = {{0}};
446
	if(memcmp(msg, &zero, sizeof(*msg)) == 0)
447
		return;
448 449 450 451 452 453 454 455 456 457 458 459 460 461
	if(msg->to[0])			fprintf(fp, "%s.to = %s\n"		, prefix, msg->to);
	if(msg->from[0])		fprintf(fp, "%s.from = %s\n"	, prefix, msg->from);
	if(msg->subj[0])		fprintf(fp, "%s.subj = %s\n"	, prefix, msg->subj);
	if(msg->msg_id[0])		fprintf(fp, "%s.msg_id = %s\n"	, prefix, msg->msg_id);
	if(msg->reply_id[0])	fprintf(fp, "%s.reply_id = %s\n", prefix, msg->reply_id);
	if(msg->pid[0])			fprintf(fp, "%s.pid = %s\n"		, prefix, msg->pid);
	if(msg->tid[0])			fprintf(fp, "%s.tid = %s\n"		, prefix, msg->tid);
	fprintf(fp, "%s.length = %lu\n"							, prefix, (ulong)msg->length);
	fprintf(fp, "%s.msg_time = %s\n"						, prefix, iniTimeStr(msg->msg_time));
	if(msg->msg_tz[0])		fprintf(fp, "%s.msg_tz = %s\n"	, prefix, msg->msg_tz);
	fprintf(fp, "%s.localtime = %s\n"						, prefix, iniTimeStr(msg->localtime));
	if(msg->origaddr.zone)
		fprintf(fp, "%s.origaddr = %s\n"					, prefix, faddrtoa(&msg->origaddr));
	fprintf(fp, "%s.pkt_orig = %s\n"						, prefix, faddrtoa(&msg->pkt_orig));
462 463
}

rswindell's avatar
rswindell committed
464 465
void fwrite_echostat(FILE* fp, echostat_t* stat)
{
466
	const char* desc = area_desc(stat->tag);
rswindell's avatar
rswindell committed
467 468
	fprintf(fp, "[%s]\n"						, stat->tag);
	fprintf(fp,	"Known = %s\n"					, stat->known ? "true" : "false");
469 470
	if(desc != NULL)
		fprintf(fp, "Title = %s\n"				, desc);
rswindell's avatar
rswindell committed
471 472
	for(int type = 0; type < ECHOSTAT_MSG_TYPES; type++) {
		char prefix[32];
473 474
		sprintf(prefix, "First%s", echostat_msg_type[type])	, fwrite_echostat_msg(fp, &stat->first[type], prefix);
		sprintf(prefix, "Last%s", echostat_msg_type[type])	, fwrite_echostat_msg(fp, &stat->last[type], prefix);
rswindell's avatar
rswindell committed
475 476 477 478 479
		if(stat->total[type] != 0)
			fprintf(fp,	"Total%s = %lu\n"					, echostat_msg_type[type], stat->total[type]);
	}
}

480 481 482 483
bool write_echostats(const char* fname, echostat_t* echostat, size_t echo_count)
{
	FILE* fp;

484 485
	qsort(echostat, echo_count, sizeof(echostat_t), echostat_compare);

486 487 488 489
	if((fp=fopen(fname, "w"))==NULL)
		return false;

	for(unsigned i = 0; i < echo_count; i++) {
rswindell's avatar
rswindell committed
490
		fwrite_echostat(fp, &echostat[i]);
491 492 493 494 495 496
		fprintf(fp, "\n");
	}
	fclose(fp);
	return true;
}

497
echostat_t* get_echostat(const char* tag, bool create)
498 499 500 501 502
{
	for(unsigned int i = 0; i < echostat_count; i++) {
		if(stricmp(echostat[i].tag, tag) == 0)
			return &echostat[i];
	}
503 504
	if(!create)
		return NULL;
505 506 507 508 509 510 511 512 513 514
	echostat_t* new_echostat = realloc(echostat, sizeof(echostat_t) * (echostat_count + 1));
	if(new_echostat == NULL)
		return NULL;
	echostat = new_echostat;
	memset(&echostat[echostat_count], 0, sizeof(echostat_t));
	SAFECOPY(echostat[echostat_count].tag, tag);

	return &echostat[echostat_count++];
}

rswindell's avatar
rswindell committed
515 516 517 518
/**********************/
/* Log print function */
/**********************/
int lprintf(int level, char *fmt, ...)
rswindell's avatar
rswindell committed
519 520
{
	va_list argptr;
rswindell's avatar
rswindell committed
521
	char sbuf[1024];
rswindell's avatar
rswindell committed
522 523
	int chcount;

rswindell's avatar
rswindell committed
524 525
	va_start(argptr,fmt);
	chcount=vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
526
	sbuf[sizeof(sbuf)-1]=0;
rswindell's avatar
rswindell committed
527
	va_end(argptr);
528 529
	truncsp(sbuf);
	printf("%s\n",sbuf);
530

rswindell's avatar
rswindell committed
531 532 533 534
	if(fidologfile!=NULL && level<=cfg.log_level) {
	    time_t now = time(NULL);
		struct tm *tm;
		struct tm tmbuf = {0};
535
		char timestamp[128];
rswindell's avatar
rswindell committed
536 537 538
		strip_ctrl(sbuf, sbuf);
		if((tm = localtime(&now)) == NULL)
			tm = &tmbuf;
539 540 541 542 543
		if(strftime(timestamp, sizeof(timestamp), cfg.logtime, tm) <= 0)
			snprintf(timestamp, sizeof(timestamp)-1, "%u-%02u-%02u %02u:%02u:%02u"
				,1900+tm->tm_year, tm->tm_mon+1, tm->tm_mday
				,tm->tm_hour, tm->tm_min, tm->tm_sec);
		fprintf(fidologfile, "%s %s\n", timestamp, sbuf);
rswindell's avatar
rswindell committed
544 545
		fflush(fidologfile);
	}
rswindell's avatar
rswindell committed
546 547 548
	return(chcount);
}

rswindell's avatar
rswindell committed
549
bool delfile(const char *filename, int line)
550
{
rswindell's avatar
rswindell committed
551
	lprintf(LOG_DEBUG, "Deleting %s (from line %u)", filename, line);
552
	if(remove(filename) != 0) {
553 554
		lprintf(LOG_ERR, "ERROR %u (%s) line %u removing file %s"
			,errno, strerror(errno), line, filename);
rswindell's avatar
rswindell committed
555
		return false;
556
	}
rswindell's avatar
rswindell committed
557
	return true;
558 559
}

560
/****************************************************************************/
rswindell's avatar
rswindell committed
561
/* Runs an external program directly using system()							*/
562 563 564
/****************************************************************************/
int execute(char *cmdline)
{
rswindell's avatar
rswindell committed
565
	int retval;
rswindell's avatar
rswindell committed
566

rswindell's avatar
rswindell committed
567 568 569 570
	lprintf(LOG_DEBUG, "Executing: %s", cmdline);
	if((retval = system(cmdline)) != 0)
		lprintf(LOG_ERR,"ERROR executing '%s' system returned: %d, errno: %d (%s)"
			,cmdline, retval, errno, strerror(errno));
571

rswindell's avatar
rswindell committed
572
	return retval;
573
}
574

575
/******************************************************************************
rswindell's avatar
rswindell committed
576
 Returns the local system address that best correlates with the passed address
577
******************************************************************************/
rswindell's avatar
rswindell committed
578
fidoaddr_t getsysfaddr(fidoaddr_t addr)
579
{
rswindell's avatar
rswindell committed
580
	nodecfg_t*	nodecfg;
581

rswindell's avatar
rswindell committed
582 583 584 585 586 587 588
	nodecfg = findnodecfg(&cfg, addr, /* exact */FALSE);
	if(nodecfg != NULL && nodecfg->local_addr.zone)
		return nodecfg->local_addr;

	int i;
	for(i=0; i<scfg.total_faddrs && addr.net != 0; i++)
		if(scfg.faddr[i].zone == addr.zone && scfg.faddr[i].net == addr.net)
rswindell's avatar
rswindell committed
589
			return scfg.faddr[i];
rswindell's avatar
rswindell committed
590 591
	for(i=0; i<scfg.total_faddrs; i++)
		if(scfg.faddr[i].zone == addr.zone)
rswindell's avatar
rswindell committed
592 593
			return scfg.faddr[i];
	return sys_faddr;
594
}
595

rswindell's avatar
rswindell committed
596
int get_outbound(fidoaddr_t dest, char* outbound, size_t maxlen, bool fileboxes)
597
{
rswindell's avatar
rswindell committed
598 599
	nodecfg_t*	nodecfg;

600
	strncpy(outbound,zone_root_outbound(dest.zone),maxlen);
rswindell's avatar
rswindell committed
601 602 603 604 605 606
	if(fileboxes &&
		(nodecfg = findnodecfg(&cfg, dest, /* exact */true)) != NULL
		&& nodecfg->outbox[0])
		strncpy(outbound, nodecfg->outbox, maxlen);
	else if(cfg.flo_mailer) {
		if(dest.zone != sys_faddr.zone)	{ /* Inter-zone outbound is "OUTBOUND.ZZZ/" */
607 608 609
			char* p = lastchar(outbound);
			if(IS_PATH_DELIM(*p))
				*p = 0;
rswindell's avatar
rswindell committed
610 611 612 613 614 615
			safe_snprintf(outbound+strlen(outbound), maxlen,".%03x", dest.zone);
		}
		if(dest.point != 0) {			/* Point destination is "OUTBOUND[.ZZZ]/NNNNnnnn.pnt/" */
			backslash(outbound);
			safe_snprintf(outbound+strlen(outbound), maxlen, "%04x%04x.pnt", dest.net, dest.node);
		}
616 617
	}
	backslash(outbound);
618 619 620
	if(isdir(outbound))
		return 0;
	lprintf(LOG_DEBUG, "Creating outbound directory for %s: %s", smb_faddrtoa(&dest, NULL), outbound);
621 622 623
	return mkpath(outbound);
}

rswindell's avatar
rswindell committed
624 625 626
const char* get_current_outbound(fidoaddr_t dest, bool fileboxes)
{
	static char outbound[MAX_PATH+1];
627 628 629 630
	if(get_outbound(dest, outbound, sizeof(outbound)-1, fileboxes) != 0) {
		lprintf(LOG_ERR, "Error creating outbound directory");
		return NULL;
	}
rswindell's avatar
rswindell committed
631 632 633 634
	return outbound;
}

bool bso_lock_node(fidoaddr_t dest)
635
{
rswindell's avatar
rswindell committed
636
	const char* outbound;
637 638
	char fname[MAX_PATH+1];

rswindell's avatar
rswindell committed
639 640
	if(!cfg.flo_mailer)
		return true;
641

rswindell's avatar
rswindell committed
642
	outbound = get_current_outbound(dest, /* fileboxes: */false);
643 644
	if(outbound == NULL)
		return false;
645 646 647 648 649 650

	if(dest.point)
		SAFEPRINTF2(fname,"%s%08x.bsy",outbound,dest.point);
	else
		SAFEPRINTF3(fname,"%s%04x%04x.bsy",outbound,dest.net,dest.node);

rswindell's avatar
rswindell committed
651 652 653 654 655
	if(strListFind(locked_bso_nodes, fname, /* case_sensitive: */true) >= 0)
		return true;
	for(unsigned attempt=0;;) {
		if(fmutex(fname, program_id(), cfg.bsy_timeout))
			break;
656
		lprintf(LOG_NOTICE, "Node (%s) externally locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
657 658 659 660 661 662 663
		if(++attempt >= cfg.bso_lock_attempts) {
			lprintf(LOG_WARNING, "Giving up after %u attempts to lock node %s", attempt, smb_faddrtoa(&dest, NULL));
			return false;
		}
		if(terminated)
			return false;
		SLEEP(cfg.bso_lock_delay*1000);
664
		(void)mkpath(outbound); // Just in case something (e.g. Argus) deleted the outbound directory
665 666 667
	}
	strListPush(&locked_bso_nodes, fname);
	lprintf(LOG_DEBUG, "Node (%s) successfully locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
668 669 670
	return true;
}

671
const char* bso_flo_filename(fidoaddr_t dest, uint16_t attr)
rswindell's avatar
rswindell committed
672 673 674 675 676 677
{
	nodecfg_t* nodecfg;
	char ch='f';
	const char* outbound;
	static char filename[MAX_PATH+1];

678 679 680 681 682
	if(attr&FIDO_CRASH)
		ch='c';
	else if(attr&FIDO_HOLD)
		ch='h';
	else if((nodecfg=findnodecfg(&cfg, dest, /* exact: */false)) != NULL) {
rswindell's avatar
rswindell committed
683 684 685 686 687 688 689 690 691 692
		switch(nodecfg->status) {
			case MAIL_STATUS_CRASH:	ch='c';	break;
			case MAIL_STATUS_HOLD:	ch='h';	break;
			default:
				if(nodecfg->direct)
					ch='d';
				break;
		}
	}
	outbound = get_current_outbound(dest, /* fileboxes: */false);
693 694
	if(outbound == NULL)
		return NULL;
rswindell's avatar
rswindell committed
695 696 697 698 699 700 701 702

	if(dest.point)
		SAFEPRINTF3(filename,"%s%08x.%clo",outbound,dest.point,ch);
	else
		SAFEPRINTF4(filename,"%s%04x%04x.%clo",outbound,dest.net,dest.node,ch);
	return filename;
}

703 704 705 706 707
/******************************************************************************
 This function creates or appends on existing Binkley compatible .?LO file
 attach file.
 Returns 0 on success.
******************************************************************************/
708 709
int write_flofile(const char *infile, fidoaddr_t dest, bool bundle, bool use_outbox
									, bool del_file, uint16_t attr)
710
{
rswindell's avatar
rswindell committed
711 712
	const char* flo_filename;
	char attachment[MAX_PATH+1];
713
	char searchstr[MAX_PATH+1];
714
	char* p;
rswindell's avatar
rswindell committed
715 716
	FILE *fp;
	nodecfg_t* nodecfg;
717

rswindell's avatar
rswindell committed
718 719 720 721
	if(use_outbox && (nodecfg=findnodecfg(&cfg, dest, /* exact: */false)) != NULL) {
		if(nodecfg->outbox[0])
			return 0;
	}
722 723 724 725

	if(!bso_lock_node(dest))
		return 1;

726
	flo_filename = bso_flo_filename(dest, attr);
727 728 729
	if(flo_filename == NULL)
		return -2;

730 731 732
	if(*infile == '^')  /* work-around for BRE/FE inter-BBS attachment bug */
		infile++;

733
#ifdef __unix__
734
	if(IS_ALPHA(infile[0]) && infile[1] == ':')	// Ignore "C:" prefix
735 736
		infile += 2;
#endif
rswindell's avatar
rswindell committed
737
	SAFECOPY(attachment, infile);
738
	REPLACE_CHARS(attachment, '\\', '/', p);
rswindell's avatar
rswindell committed
739 740 741
	if(!fexistcase(attachment))	{ /* just in-case it's the wrong case for a Unix file system */
		lprintf(LOG_ERR, "ERROR line %u, attachment file not found: %s", __LINE__, attachment);
		return -1;
742
	}
rswindell's avatar
rswindell committed
743 744 745 746
	char* prefix = "";
	if(bundle) {
		prefix = (cfg.trunc_bundles) ? "#" : "^";
	} else {
747
		if(del_file)
rswindell's avatar
rswindell committed
748 749 750
			prefix = "^";
	}
	SAFEPRINTF2(searchstr, "%s%s", prefix, attachment);
rswindell's avatar
rswindell committed
751 752 753 754
	if(findstr(searchstr,flo_filename))	/* file already in FLO file */
		return 0;
	if((fp=fopen(flo_filename,"a"))==NULL) {
		lprintf(LOG_ERR,"ERROR %u (%s) opening %s",errno, strerror(errno), flo_filename);
rswindell's avatar
rswindell committed
755
		return -1;
rswindell's avatar
rswindell committed
756 757 758 759 760 761
	}
	fprintf(fp,"%s\n",searchstr);
	fclose(fp);
	lprintf(LOG_INFO, "File (%s, %1.1fKB) for %s added to BSO/FLO file: %s"
		,attachment, flength(attachment)/1024.0, smb_faddrtoa(&dest,NULL), flo_filename);
	return 0;
762 763
}

764
/* Writes text buffer to file, expanding sole LFs to CRLFs or stripping LFs */
rswindell's avatar
rswindell committed
765
size_t fwrite_crlf(const char* buf, size_t len, FILE* fp)
766 767 768 769 770 771 772
{
	char	ch,last_ch=0;
	size_t	i;
	size_t	wr=0;	/* total chars written (may be > len) */

	for(i=0;i<len;i++) {
		ch=*buf++;
773 774 775 776 777 778 779 780
		if(ch=='\n') {
			if(last_ch!='\r') {
				if(fputc('\r', fp) == EOF)
					break;
				wr++;
			}
			if(cfg.strip_lf)
				continue;
781 782
		}
		if(fputc(ch,fp)==EOF)
rswindell's avatar
rswindell committed
783
			break;
784 785 786 787 788 789 790
		wr++;
		last_ch=ch;
	}

	return(wr);
}

rswindell's avatar
rswindell committed
791
bool fidoctrl_line_exists(const smbmsg_t* msg, const char* prefix)
792
{
793
	if(msg==NULL || prefix==NULL)
rswindell's avatar
rswindell committed
794
		return false;
795 796 797
	for(int i=0; i<msg->total_hfields; i++) {
		if(msg->hfield[i].type == FIDOCTRL
			&& strncmp((char*)msg->hfield_dat[i], prefix, strlen(prefix)) == 0)
rswindell's avatar
rswindell committed
798
			return true;
799
	}
rswindell's avatar
rswindell committed
800
	return false;
801 802
}

rswindell's avatar
rswindell committed
803
fidoaddr_t fmsghdr_srcaddr(const fmsghdr_t* hdr)
804 805 806 807 808 809 810 811 812 813 814
{
	fidoaddr_t addr;

	addr.zone	= hdr->origzone;
	addr.net	= hdr->orignet;
	addr.node	= hdr->orignode;
	addr.point	= hdr->origpoint;

	return addr;
}

rswindell's avatar
rswindell committed
815
const char* fmsghdr_srcaddr_str(const fmsghdr_t* hdr)
816 817 818 819 820 821 822
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_srcaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

rswindell's avatar
rswindell committed
823
fidoaddr_t fmsghdr_destaddr(const fmsghdr_t* hdr)
824 825 826 827 828 829 830 831 832 833 834
{
	fidoaddr_t addr;

	addr.zone	= hdr->destzone;
	addr.net	= hdr->destnet;
	addr.node	= hdr->destnode;
	addr.point	= hdr->destpoint;

	return addr;
}

rswindell's avatar
rswindell committed
835
const char* fmsghdr_destaddr_str(const fmsghdr_t* hdr)
836 837 838 839 840 841 842
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_destaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

843 844 845 846
bool parse_origin(const char* fmsgbuf, fmsghdr_t* hdr)
{
	char* p;
	fidoaddr_t origaddr;
rswindell's avatar
rswindell committed
847

848 849 850 851 852 853 854 855 856 857 858
	if((p = strstr(fmsgbuf, FIDO_ORIGIN_PREFIX_FORM_1)) == NULL)
		p = strstr(fmsgbuf, FIDO_ORIGIN_PREFIX_FORM_2);
	if(p == NULL)
		return false;

	p += FIDO_ORIGIN_PREFIX_LEN;
	p = strrchr(p, '(');
	if(p == NULL)
		return false;
	p++;
	origaddr = atofaddr(p);
rswindell's avatar
rswindell committed
859
	if(origaddr.zone == 0 || faddr_contains_wildcard(&origaddr))
860 861 862 863 864 865 866 867
		return false;
	hdr->origzone	= origaddr.zone;
	hdr->orignet	= origaddr.net;
	hdr->orignode	= origaddr.node;
	hdr->origpoint	= origaddr.point;
	return true;
}

rswindell's avatar
rswindell committed
868 869 870 871
bool parse_pkthdr(const fpkthdr_t* hdr, fidoaddr_t* orig_addr, fidoaddr_t* dest_addr, enum pkt_type* pkt_type)
{
	fidoaddr_t	orig;
	fidoaddr_t	dest;
rswindell's avatar
rswindell committed
872
	enum pkt_type type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
873 874 875 876 877 878 879 880 881 882 883 884 885 886

	if(hdr->type2.pkttype != 2)
		return false;

	orig.zone	= hdr->type2.origzone;
	orig.net	= hdr->type2.orignet;
	orig.node	= hdr->type2.orignode;
	orig.point	= 0;

	dest.zone	= hdr->type2.destzone;
	dest.net	= hdr->type2.destnet;
	dest.node	= hdr->type2.destnode;
	dest.point	= 0;				/* No point info in the 2.0 hdr! */

rswindell's avatar
rswindell committed
887 888 889 890
	if(hdr->type2plus.cword == BYTE_SWAP_16(hdr->type2plus.cwcopy)  /* 2e Packet Header (FSC-39) */
		&& (hdr->type2plus.cword&1)) {	/* Some call this a Type-2+ packet, but it's not (yet) FSC-48 conforming */
		type = PKT_TYPE_2_EXT;
		orig.point = hdr->type2plus.origpoint;
rswindell's avatar
rswindell committed
891
		dest.point = hdr->type2plus.destpoint;
rswindell's avatar
rswindell committed
892 893 894 895 896 897
		if(orig.zone == 0) orig.zone = hdr->type2plus.origzone;
		if(dest.zone == 0) dest.zone = hdr->type2plus.destzone;
		if(hdr->type2plus.auxnet != 0) {	/* strictly speaking, auxnet may be 0 and a valid 2+ packet */
			type = PKT_TYPE_2_PLUS;
			if(orig.point != 0 && orig.net == 0xffff) 	/* see FSC-0048 for details */
				orig.net = hdr->type2plus.auxnet;
rswindell's avatar
rswindell committed
898 899 900 901
		}
	} else if(hdr->type2_2.subversion==2) {					/* Type 2.2 Packet Header (FSC-45) */
		type = PKT_TYPE_2_2;
		orig.point = hdr->type2_2.origpoint;
rswindell's avatar
rswindell committed
902
		dest.point = hdr->type2_2.destpoint;
rswindell's avatar
rswindell committed
903 904 905 906 907 908 909 910
	}

	if(pkt_type != NULL)
		*pkt_type = type;
	if(dest_addr != NULL)
		*dest_addr = dest;
	if(orig_addr != NULL)
		*orig_addr = orig;
rswindell's avatar
rswindell committed
911

rswindell's avatar
rswindell committed
912 913 914 915 916
	return true;
}

bool new_pkthdr(fpkthdr_t* hdr, fidoaddr_t orig, fidoaddr_t dest, const nodecfg_t* nodecfg)
{
rswindell's avatar
rswindell committed
917
	enum pkt_type pkt_type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940
	struct tm* tm;
	time_t now = time(NULL);

	if(nodecfg != NULL)
		pkt_type = nodecfg->pkt_type;

	memset(hdr, 0, sizeof(fpkthdr_t));

	if((tm = localtime(&now)) != NULL) {
		hdr->type2.year	= tm->tm_year+1900;
		hdr->type2.month= tm->tm_mon;
		hdr->type2.day	= tm->tm_mday;
		hdr->type2.hour	= tm->tm_hour;
		hdr->type2.min	= tm->tm_min;
		hdr->type2.sec	= tm->tm_sec;
	}

	hdr->type2.origzone = orig.zone;
	hdr->type2.orignet	= orig.net;
	hdr->type2.orignode	= orig.node;
	hdr->type2.destzone	= dest.zone;
	hdr->type2.destnet	= dest.net;
	hdr->type2.destnode	= dest.node;
rswindell's avatar
rswindell committed
941

rswindell's avatar
rswindell committed
942 943 944 945 946
	hdr->type2.pkttype	= 2;
	hdr->type2.prodcode	= SBBSECHO_PRODUCT_CODE&0xff;
	hdr->type2.sernum	= SBBSECHO_VERSION_MAJOR;

	if(nodecfg != NULL && nodecfg->pktpwd[0] != 0)
947
		strncpy((char*)hdr->type2.password, nodecfg->pktpwd, sizeof(hdr->type2.password));
rswindell's avatar
rswindell committed
948

rswindell's avatar
rswindell committed
949
	if(pkt_type == PKT_TYPE_2)
rswindell's avatar
rswindell committed
950 951 952 953
		return true;

	if(pkt_type == PKT_TYPE_2_2) {
		hdr->type2_2.subversion = 2;	/* 2.2 */
954 955
		strncpy((char*)hdr->type2_2.origdomn,zone_domain(orig.zone),sizeof(hdr->type2_2.origdomn));
		strncpy((char*)hdr->type2_2.destdomn,zone_domain(dest.zone),sizeof(hdr->type2_2.destdomn));
rswindell's avatar
rswindell committed
956 957
		return true;
	}
rswindell's avatar
rswindell committed
958

rswindell's avatar
rswindell committed
959 960
	/* 2e and 2+ */
	if(pkt_type != PKT_TYPE_2_EXT && pkt_type != PKT_TYPE_2_PLUS) {
rswindell's avatar
rswindell committed
961 962 963 964
		lprintf(LOG_ERR, "UNSUPPORTED PACKET TYPE: %u", pkt_type);
		return false;
	}

rswindell's avatar
rswindell committed
965 966 967 968
	if(pkt_type == PKT_TYPE_2_PLUS) {
		if(orig.point != 0)
			hdr->type2plus.orignet	= 0xffff;
		hdr->type2plus.auxnet	= orig.net; /* Squish always copies the orignet here */
rswindell's avatar
rswindell committed
969 970 971 972 973 974 975 976 977 978 979 980 981
	}
	hdr->type2plus.cword		= 0x0001;
	hdr->type2plus.cwcopy		= 0x0100;
	hdr->type2plus.prodcodeHi	= SBBSECHO_PRODUCT_CODE>>8;
	hdr->type2plus.prodrevMinor	= SBBSECHO_VERSION_MINOR;
	hdr->type2plus.origzone		= orig.zone;
	hdr->type2plus.destzone		= dest.zone;
	hdr->type2plus.origpoint	= orig.point;
	hdr->type2plus.destpoint	= dest.point;

	return true;
}

982
/******************************************************************************
rswindell's avatar
rswindell committed
983
 This function will create a netmail message (FTS-1 "stored message" format).
984 985 986
 If file is non-zero, will set file attachment bit (for bundles).
 Returns 0 on success.
******************************************************************************/
987 988
int create_netmail(const char *to, const smbmsg_t* msg, const char *subject, const char *body, fidoaddr_t dest
	, fidoaddr_t* src)
989
{
990
	FILE *fp;
991
	char tmp[256];
992
	char fname[MAX_PATH+1];
993
	char* from=NULL;
994 995
	uint i;
	static uint startmsg;
rswindell's avatar
rswindell committed
996
	fidoaddr_t	faddr;
997
	fmsghdr_t hdr;
998
	time_t t;
999
	struct tm *tm;
1000
	when_t when_written;
rswindell's avatar
rswindell committed
1001 1002
	nodecfg_t* nodecfg;
	bool	direct=false;
1003

1004
	if(msg==NULL) {
rswindell's avatar
rswindell committed
1005
		when_written.time = time32(NULL);
1006 1007 1008 1009 1010
		when_written.zone = sys_timezone(&scfg);
	} else {
		from = msg->from;
		when_written = msg->hdr.when_written;
	}