sbbsecho.c 197 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
#include "text.h"
58 59
#include "git_branch.h"
#include "git_hash.h"
rswindell's avatar
rswindell committed
60 61

#define MAX_OPEN_SMBS	10
62 63

smb_t *smb,*email;
rswindell's avatar
rswindell committed
64 65
bool opt_import_packets		= true;
bool opt_import_netmail		= true;
rswindell's avatar
rswindell committed
66
bool opt_delete_netmail		= true;		/* delete after importing (no effect on exported netmail) */
rswindell's avatar
rswindell committed
67 68 69 70 71 72 73 74 75
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;
76
bool opt_dump_area_file		= false;
77
bool opt_retoss_badmail		= false;	/* Re-toss from the badecho/unknown msg sub */
rswindell's avatar
rswindell committed
78

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

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

rswindell's avatar
rswindell committed
96 97 98 99
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];
100
char*			text[TOTAL_TEXT];
101

rswindell's avatar
rswindell committed
102 103 104 105
bool pause_on_exit=false;
bool pause_on_abend=false;
bool mtxfile_locked=false;
bool terminated=false;
106

107 108
str_list_t	locked_bso_nodes;

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

119 120 121 122
/* 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
123

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

	return str;
}

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

135 136 137 138 139
	SAFEPRINTF2(str, "%u %s", getpid(), sbbsecho_pid());

	return str;
}

140
const char* tear_line(char ch)
rswindell's avatar
rswindell committed
141 142 143
{
	static char str[256];

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

	return str;
}

150 151 152 153
const char default_domain[] = "fidonet";

const char* zone_domain(uint16_t zone)
{
154 155 156 157
	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;
158 159 160 161 162 163

	return default_domain;
}

const char* zone_root_outbound(uint16_t zone)
{
164 165 166 167
	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;
168 169 170 171

	return cfg.outbound;
}

172 173 174 175 176 177 178 179
/* 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;
180
	SAFEPRINTF(str, "\1%s", kludge);
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
	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
200 201 202 203 204 205
/* 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 "
206
		"SBBSecho %u.%02u-%s %s/%s\r"
rswindell's avatar
rswindell committed
207 208 209 210 211 212 213
		,smb_faddrtoa(addr, NULL)
		,tm->tm_year+1900
		,tm->tm_mon+1
		,tm->tm_mday
		,tm->tm_hour
		,tm->tm_min
		,tm->tm_sec
214
		,SBBSECHO_VERSION_MAJOR,SBBSECHO_VERSION_MINOR,PLATFORM_DESC,GIT_BRANCH,GIT_HASH);
rswindell's avatar
rswindell committed
215
}
216 217 218 219 220 221 222 223

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);
}

224 225 226 227 228 229 230 231
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];
232 233
	char msg_tz[128];
	time_t msg_time;
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
	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];
259
	bool known;	// listed in Area File
260 261 262 263 264 265 266 267
	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;

268 269
int echostat_compare(const void* c1, const void* c2)
{
270 271
	echostat_t* stat1 = (echostat_t*)c1;
	echostat_t* stat2 = (echostat_t*)c2;
272 273 274 275

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

276 277 278 279
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
280
	echostat_msg_t msg = {{0}};
281 282 283 284 285 286 287 288

	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);
289 290
	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);
291 292 293 294 295 296 297 298 299 300
	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;
}

301
echostat_msg_t* fidomsg_to_echostat_msg(fmsghdr_t* hdr, fidoaddr_t* pkt_orig, const char* fmsgbuf)
302 303
{
	char* p;
304
	static echostat_msg_t msg = {{0}};
305

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

341
	return &msg;
342 343
}

344
echostat_msg_t* smsg_to_echostat_msg(const smbmsg_t* smsg, size_t msglen, fidoaddr_t addr)
345 346
{
	char* p;
347
	static echostat_msg_t emsg = {{0}};
348

349 350 351 352
	SAFECOPY(emsg.to	, smsg->to);
	SAFECOPY(emsg.from	, smsg->from);
	SAFECOPY(emsg.subj	, smsg->subj);
	emsg.msg_time		= smsg->hdr.when_written.time;
353
	emsg.localtime		= time(NULL);
354 355 356 357
	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)
358
		SAFECOPY(emsg.msg_id, p);
359
	if((p = smsg->ftn_reply) != NULL)
360
		SAFECOPY(emsg.reply_id, p);
361
	if((p = smsg->ftn_pid) != NULL)
362
		SAFECOPY(emsg.pid, p);
363
	if((p = smsg->ftn_tid) != NULL)
364
		SAFECOPY(emsg.tid, p);
365 366
	else
		SAFECOPY(emsg.tid, sbbsecho_pid());
367
	emsg.length = msglen;
368
	emsg.pkt_orig = addr;
369

370
	return &emsg;
371 372
}

373
void new_echostat_msg(echostat_t* stat, enum echostat_msg_type type, echostat_msg_t* msg)
374
{
375
	stat->last[type] = *msg;
376
	if(stat->first[type].localtime == 0)
377
		stat->first[type] = *msg;
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 404 405 406
	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]);
407 408 409
		str_list_t keys = iniGetSection(ini, stat->tag);
		if(keys == NULL)
			continue;
410 411 412
		for(int type = 0; type < ECHOSTAT_MSG_TYPES; type++) {
			char prefix[32];
			sprintf(prefix, "First%s", echostat_msg_type[type])
413
				,stat->first[type]	= parse_echostat_msg(keys, NULL, prefix);
414
			sprintf(prefix, "Last%s", echostat_msg_type[type])
415
				,stat->last[type]	= parse_echostat_msg(keys, NULL, prefix);
416
			sprintf(prefix, "Total%s", echostat_msg_type[type])
417
				,stat->total[type] = iniGetLongInt(keys, NULL, prefix, 0);
418
		}
419 420
		stat->known = iniGetBool(keys, NULL, "Known", false);
		iniFreeStringList(keys);
421 422 423 424
	}
	iniFreeStringList(echoes);
	iniFreeStringList(ini);

425
	lprintf(LOG_DEBUG, "Read %lu echo statistics from %s", (ulong)echo_count, fname);
426 427 428 429 430 431 432 433 434 435 436 437 438
	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)
439
		sprintf(tstr, "0x%lx", (ulong)t);
440 441
	else
		sprintf(tstr, "%.3s %.2s %.4s %.8s", p+4, p+8, p+20, p+11);
rswindell's avatar
rswindell committed
442

443 444 445
	return tstr;
}

446
void fwrite_echostat_msg(FILE* fp, const echostat_msg_t* msg, const char* prefix)
447
{
rswindell's avatar
rswindell committed
448
	echostat_msg_t zero = {{0}};
449
	if(memcmp(msg, &zero, sizeof(*msg)) == 0)
450
		return;
451 452 453 454 455 456 457 458 459 460 461 462 463 464
	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));
465 466
}

rswindell's avatar
rswindell committed
467 468
void fwrite_echostat(FILE* fp, echostat_t* stat)
{
469
	const char* desc = area_desc(stat->tag);
rswindell's avatar
rswindell committed
470 471
	fprintf(fp, "[%s]\n"						, stat->tag);
	fprintf(fp,	"Known = %s\n"					, stat->known ? "true" : "false");
472 473
	if(desc != NULL)
		fprintf(fp, "Title = %s\n"				, desc);
rswindell's avatar
rswindell committed
474 475
	for(int type = 0; type < ECHOSTAT_MSG_TYPES; type++) {
		char prefix[32];
476 477
		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
478 479 480 481 482
		if(stat->total[type] != 0)
			fprintf(fp,	"Total%s = %lu\n"					, echostat_msg_type[type], stat->total[type]);
	}
}

483 484 485 486
bool write_echostats(const char* fname, echostat_t* echostat, size_t echo_count)
{
	FILE* fp;

487 488
	qsort(echostat, echo_count, sizeof(echostat_t), echostat_compare);

489 490 491 492
	if((fp=fopen(fname, "w"))==NULL)
		return false;

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

500
echostat_t* get_echostat(const char* tag, bool create)
501 502 503 504 505
{
	for(unsigned int i = 0; i < echostat_count; i++) {
		if(stricmp(echostat[i].tag, tag) == 0)
			return &echostat[i];
	}
506 507
	if(!create)
		return NULL;
508 509 510 511 512 513 514 515 516 517
	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
518 519 520 521
/**********************/
/* Log print function */
/**********************/
int lprintf(int level, char *fmt, ...)
rswindell's avatar
rswindell committed
522 523
{
	va_list argptr;
rswindell's avatar
rswindell committed
524
	char sbuf[1024];
rswindell's avatar
rswindell committed
525 526
	int chcount;

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

rswindell's avatar
rswindell committed
534 535 536 537
	if(fidologfile!=NULL && level<=cfg.log_level) {
	    time_t now = time(NULL);
		struct tm *tm;
		struct tm tmbuf = {0};
538
		char timestamp[128];
rswindell's avatar
rswindell committed
539 540 541
		strip_ctrl(sbuf, sbuf);
		if((tm = localtime(&now)) == NULL)
			tm = &tmbuf;
542 543 544 545 546
		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
547 548
		fflush(fidologfile);
	}
rswindell's avatar
rswindell committed
549 550 551
	return(chcount);
}

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

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

rswindell's avatar
rswindell committed
570 571 572 573
	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));
574

rswindell's avatar
rswindell committed
575
	return retval;
576
}
577

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

rswindell's avatar
rswindell committed
585 586 587 588 589 590 591
	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
592
			return scfg.faddr[i];
rswindell's avatar
rswindell committed
593 594
	for(i=0; i<scfg.total_faddrs; i++)
		if(scfg.faddr[i].zone == addr.zone)
rswindell's avatar
rswindell committed
595 596
			return scfg.faddr[i];
	return sys_faddr;
597
}
598

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

603
	strncpy(outbound,zone_root_outbound(dest.zone),maxlen);
rswindell's avatar
rswindell committed
604 605 606 607 608 609
	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/" */
610 611 612
			char* p = lastchar(outbound);
			if(IS_PATH_DELIM(*p))
				*p = 0;
rswindell's avatar
rswindell committed
613 614 615 616 617 618
			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);
		}
619 620
	}
	backslash(outbound);
621 622 623
	if(isdir(outbound))
		return 0;
	lprintf(LOG_DEBUG, "Creating outbound directory for %s: %s", smb_faddrtoa(&dest, NULL), outbound);
624 625 626
	return mkpath(outbound);
}

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

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

rswindell's avatar
rswindell committed
642 643
	if(!cfg.flo_mailer)
		return true;
644

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

	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
654 655 656 657 658
	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;
659
		lprintf(LOG_NOTICE, "Node (%s) externally locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
660 661 662 663 664 665 666
		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);
667
		(void)mkpath(outbound); // Just in case something (e.g. Argus) deleted the outbound directory
668 669 670
	}
	strListPush(&locked_bso_nodes, fname);
	lprintf(LOG_DEBUG, "Node (%s) successfully locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
671 672 673
	return true;
}

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

681 682 683 684 685
	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
686 687 688 689 690 691 692 693 694 695
		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);
696 697
	if(outbound == NULL)
		return NULL;
rswindell's avatar
rswindell committed
698 699 700 701 702 703 704 705

	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;
}

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

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

	if(!bso_lock_node(dest))
		return 1;

729
	flo_filename = bso_flo_filename(dest, attr);
730 731 732
	if(flo_filename == NULL)
		return -2;

733 734 735
	if(*infile == '^')  /* work-around for BRE/FE inter-BBS attachment bug */
		infile++;

736
#ifdef __unix__
737
	if(IS_ALPHA(infile[0]) && infile[1] == ':')	// Ignore "C:" prefix
738 739
		infile += 2;
#endif
rswindell's avatar
rswindell committed
740
	SAFECOPY(attachment, infile);
741
	REPLACE_CHARS(attachment, '\\', '/', p);
rswindell's avatar
rswindell committed
742 743 744
	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;
745
	}
rswindell's avatar
rswindell committed
746 747 748 749
	char* prefix = "";
	if(bundle) {
		prefix = (cfg.trunc_bundles) ? "#" : "^";
	} else {
750
		if(del_file)
rswindell's avatar
rswindell committed
751 752 753
			prefix = "^";
	}
	SAFEPRINTF2(searchstr, "%s%s", prefix, attachment);
rswindell's avatar
rswindell committed
754 755 756 757
	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
758
		return -1;
rswindell's avatar
rswindell committed
759 760 761 762 763 764
	}
	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;
765 766
}

767
/* Writes text buffer to file, expanding sole LFs to CRLFs or stripping LFs */
rswindell's avatar
rswindell committed
768
size_t fwrite_crlf(const char* buf, size_t len, FILE* fp)
769 770 771 772 773 774 775
{
	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++;
776 777 778 779 780 781 782 783
		if(ch=='\n') {
			if(last_ch!='\r') {
				if(fputc('\r', fp) == EOF)
					break;
				wr++;
			}
			if(cfg.strip_lf)
				continue;
784 785
		}
		if(fputc(ch,fp)==EOF)
rswindell's avatar
rswindell committed
786
			break;
787 788 789 790 791 792 793
		wr++;
		last_ch=ch;
	}

	return(wr);
}

rswindell's avatar
rswindell committed
794
bool fidoctrl_line_exists(const smbmsg_t* msg, const char* prefix)
795
{
796
	if(msg==NULL || prefix==NULL)
rswindell's avatar
rswindell committed
797
		return false;
798 799 800
	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
801
			return true;
802
	}
rswindell's avatar
rswindell committed
803
	return false;
804 805
}

rswindell's avatar
rswindell committed
806
fidoaddr_t fmsghdr_srcaddr(const fmsghdr_t* hdr)
807 808 809 810 811 812 813 814 815 816 817
{
	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
818
const char* fmsghdr_srcaddr_str(const fmsghdr_t* hdr)
819 820 821 822 823 824 825
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_srcaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

rswindell's avatar
rswindell committed
826
fidoaddr_t fmsghdr_destaddr(const fmsghdr_t* hdr)
827 828 829 830 831 832 833 834 835 836 837
{
	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
838
const char* fmsghdr_destaddr_str(const fmsghdr_t* hdr)
839 840 841 842 843 844 845
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_destaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

846 847 848 849
bool parse_origin(const char* fmsgbuf, fmsghdr_t* hdr)
{
	char* p;
	fidoaddr_t origaddr;
rswindell's avatar
rswindell committed
850

851 852 853 854 855 856 857 858 859 860 861
	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
862
	if(origaddr.zone == 0 || faddr_contains_wildcard(&origaddr))
863 864 865 866 867 868 869 870
		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
871 872 873 874
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
875
	enum pkt_type type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
876 877 878 879 880 881 882 883 884 885 886 887 888 889

	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
890 891 892 893
	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
894
		dest.point = hdr->type2plus.destpoint;
rswindell's avatar
rswindell committed
895 896 897 898 899 900
		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
901 902 903 904
		}
	} 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
905
		dest.point = hdr->type2_2.destpoint;
rswindell's avatar
rswindell committed
906 907 908 909 910 911 912 913
	}

	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
914

rswindell's avatar
rswindell committed
915 916 917 918 919
	return true;
}

bool new_pkthdr(fpkthdr_t* hdr, fidoaddr_t orig, fidoaddr_t dest, const nodecfg_t* nodecfg)
{
rswindell's avatar
rswindell committed
920
	enum pkt_type pkt_type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943
	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
944

rswindell's avatar
rswindell committed
945 946 947 948 949
	hdr->type2.pkttype	= 2;
	hdr->type2.prodcode	= SBBSECHO_PRODUCT_CODE&0xff;
	hdr->type2.sernum	= SBBSECHO_VERSION_MAJOR;

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

rswindell's avatar
rswindell committed
952
	if(pkt_type == PKT_TYPE_2)
rswindell's avatar
rswindell committed
953 954 955 956
		return true;

	if(pkt_type == PKT_TYPE_2_2) {
		hdr->type2_2.subversion = 2;	/* 2.2 */
957 958
		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
959 960
		return true;
	}
rswindell's avatar
rswindell committed
961

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

rswindell's avatar
rswindell committed
968 969 970 971
	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
972 973 974 975 976 977 978 979 980 981 982 983 984
	}
	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;
}

985
/******************************************************************************
rswindell's avatar
rswindell committed
986
 This function will create a netmail message (FTS-1 "stored message" format).
987 988 989
 If file is non-zero, will set file attachment bit (for bundles).
 Returns 0 on success.
******************************************************************************/
990 991
int create_netmail(const char *to, const smbmsg_t* msg, const char *subject, const char *body, fidoaddr_t dest
	, fidoaddr_t* src)
992
{
993
	FILE *fp;
994
	char tmp[256];
995
	char fname[MAX_PATH+1];
996
	char* from=NULL;
997 998
	uint i;
	static uint startmsg;
rswindell's avatar
rswindell committed