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;
rswindell's avatar
rswindell committed
304
	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
341
342
343
	if(fmsgbuf != NULL)
		msg.length = strlen(fmsgbuf);

	return msg;
}

344
echostat_msg_t smsg_to_echostat_msg(const smbmsg_t* smsg, size_t msglen, fidoaddr_t addr)
345
346
{
	char* p;
rswindell's avatar
rswindell committed
347
	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
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
404
405
406

	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]);
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
999
	fidoaddr_t	faddr;
1000
	fmsghdr_t hdr;
1001
	time_t t;
1002
	struct tm *tm;
1003
	when_t when_written;
rswindell's avatar
rswindell committed
1004
1005
	nodecfg_t* nodecfg;
	bool	direct=false;
1006

1007
	if(msg==NULL) {
rswindell's avatar
rswindell committed
1008
		when_written.time = time32(NULL);
1009
1010
1011
1012
1013
		when_written.zone = sys_timezone(&scfg);
	} else {
		from = msg->from;
		when_written = msg->hdr.when_written;
	}
rswindell's avatar
rswindell committed
1014
	if(from==NULL || *from==0) {
1015
		from="SBBSecho";
rswindell's avatar
rswindell committed
1016
1017
1018
1019
1020
1021
1022
		if(to != NULL && stricmp(to, "SBBSecho") == 0) {
			lprintf(LOG_NOTICE, "Refusing to create netmail-loop msg from %s to %s", from, to);
			return -42;	// Refuse to create mail-loop between 2 SBBSecho-bots
		}
	}

	if(to==NULL || *to==0)
1023
		to="Sysop";
1024
	if(!startmsg) startmsg=1;
rswindell's avatar
rswindell committed
1025
1026
1027
	if((nodecfg=findnodecfg(&cfg, dest, 0)) != NULL) {
		if(nodecfg->status == MAIL_STATUS_NORMAL && !nodecfg->direct)
			nodecfg=findnodecfg(&cfg, dest, /* skip exact match: */2);
1028
	}
1029

rswindell's avatar
rswindell committed
1030
	if(!isdir(scfg.netmail_dir) && mkpath(scfg.netmail_dir) != 0) {
1031
1032
1033
		lprintf(LOG_ERR, "Error %u (%s) line %d creating directory: %s", errno, strerror(errno), __LINE__, scfg.netmail_dir);
		return -2;
	}
1034
1035
1036
	for(i=startmsg;i;i++) {
		sprintf(fname,"%s%u.msg",scfg.netmail_dir,i);
		if(!fexistcase(fname))
rswindell's avatar
rswindell committed
1037
			break;
1038
1039
1040
	}
	if(!i) {
		lprintf(LOG_WARNING,"Directory full: %s",scfg.netmail_dir);
rswindell's avatar
rswindell committed
1041
		return(-1);
1042
1043
	}
	startmsg=i+1;
1044
	if((fp=fnopen(NULL,fname,O_RDWR|O_CREAT))==NULL) {
1045
		lprintf(LOG_ERR,"ERROR %u (%s) line %d opening %s",errno,strerror(errno),__LINE__,fname);
rswindell's avatar
rswindell committed
1046
		return(-1);
1047
	}
1048

1049
1050
1051
	if(src != NULL)
		faddr = *src;
	else if(msg != NULL && msg->from_net.type == NET_FIDO && msg->from_net.addr != NULL)
1052
1053
		faddr = *(fidoaddr_t*)msg->from_net.addr;
	else
rswindell's avatar
rswindell committed
1054
		faddr = getsysfaddr(dest);
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
	memset(&hdr,0,sizeof(fmsghdr_t));
	hdr.origzone=faddr.zone;
	hdr.orignet=faddr.net;
	hdr.orignode=faddr.node;
	hdr.origpoint=faddr.point;
	hdr.destzone=dest.zone;
	hdr.destnet=dest.net;
	hdr.destnode=dest.node;
	hdr.destpoint=dest.point;

	hdr.attr=(FIDO_PRIVATE|FIDO_KILLSENT|FIDO_LOCAL);
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
	if(msg != NULL) {
		if(msg->hdr.netattr&MSG_CRASH)
			hdr.attr|=FIDO_CRASH;
		if(msg->hdr.auxattr&MSG_FILEATTACH)
			hdr.attr|=FIDO_FILE;
		if(msg->hdr.auxattr&MSG_FILEREQUEST)
			hdr.attr|=FIDO_FREQ;
		if(msg->hdr.auxattr&MSG_RECEIPTREQ)
			hdr.attr|=FIDO_RRREQ;
	}
1076

rswindell's avatar
rswindell committed
1077
1078
1079
1080
	if(nodecfg != NULL) {
		switch(nodecfg->status) {
			case MAIL_STATUS_HOLD:	hdr.attr|=FIDO_HOLD;	break;
			case MAIL_STATUS_CRASH:	hdr.attr|=FIDO_CRASH;	break;
1081
			case MAIL_STATUS_NORMAL:						break;
rswindell's avatar
rswindell committed
1082
1083
1084
		}
		direct = nodecfg->direct;
	}
1085

1086
	t = when_written.time;
rswindell's avatar
rswindell committed
1087
	tm = localtime(&t);
1088
1089
1090
1091
1092
1093
1094
1095
	sprintf(hdr.time,"%02u %3.3s %02u  %02u:%02u:%02u"
		,tm->tm_mday,mon[tm->tm_mon],TM_YEAR(tm->tm_year)
		,tm->tm_hour,tm->tm_min,tm->tm_sec);

	SAFECOPY(hdr.to,to);
	SAFECOPY(hdr.from,from);
	SAFECOPY(hdr.subj,subject);

1096
	(void)fwrite(&hdr,sizeof(fmsghdr_t),1,fp);
1097
	fwrite_intl_control_line(fp, &hdr);
1098

1099
1100
1101
1102
1103
1104
1105
1106
1107
	if(!fidoctrl_line_exists(msg, "TZUTC:")) {
		/* TZUTC (FSP-1001) */
		int tzone=smb_tzutc(when_written.zone);
		char* minus="";
		if(tzone<0) {
			minus="-";
			tzone=-tzone;
		}
		fprintf(fp,"\1TZUTC: %s%02d%02u\r", minus, tzone/60, tzone%60);
1108
	}
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
	if(msg == NULL) {
		if(!cfg.flo_mailer) {
			/* Add FSC-53 FLAGS kludge */
			fprintf(fp,"\1FLAGS");
			if(direct)
				fprintf(fp," DIR");
			if(hdr.attr&FIDO_FILE) {
				if(cfg.trunc_bundles)
					fprintf(fp," TFS");
				else
					fprintf(fp," KFS");
			}
			fprintf(fp,"\r");
1122
		}
Rob Swindell's avatar
Rob Swindell committed
1123
		fprintf(fp, "\1MSGID: %s %08lx\r", smb_faddrtoa(&faddr, NULL), (ulong)time32(NULL));
1124
	} else {
1125
1126
1127
1128
		if(msg->ftn_msgid != NULL)
			fprintf(fp, "\1MSGID: %.256s\r", msg->ftn_msgid);
		if(msg->ftn_reply != NULL)
			fprintf(fp, "\1REPLY: %.256s\r", msg->ftn_reply);
1129
1130
		if(msg->ftn_bbsid != NULL)
			fprintf(fp, "\1BBSID: %.256s\r", msg->ftn_bbsid);
1131
1132
1133
1134
		if(msg->ftn_flags != NULL)
			fprintf(fp, "\1FLAGS %s\r", msg->ftn_flags);
		else if(msg->hdr.auxattr&MSG_KILLFILE)
			fprintf(fp, "\1FLAGS %s\r", "KFS");
1135
	}
1136

1137
1138
1139
1140
	if(hdr.destpoint)
		fprintf(fp,"\1TOPT %hu\r",hdr.destpoint);
	if(hdr.origpoint)
		fprintf(fp,"\1FMPT %hu\r",hdr.origpoint);
1141
1142
	fprintf(fp,"\1PID: %s\r", (msg==NULL || msg->ftn_pid==NULL) ? sbbsecho_pid() : msg->ftn_pid);
	if(msg != NULL) {
1143
1144
		if(msg->columns)
			fprintf(fp,"\1COLS: %u\r", (unsigned int)msg->columns);
1145
1146
1147
1148
		/* Unknown kludge lines are added here */
		for(int i=0; i<msg->total_hfields; i++)
			if(msg->hfield[i].type == FIDOCTRL)
				fprintf(fp,"\1%.512s\r",(char*)msg->hfield_dat[i]);
1149
1150
		const char* charset = msg->ftn_charset;
		if(charset == NULL) {
1151
			if(smb_msg_is_utf8(msg) || (msg->hdr.auxattr & MSG_HFIELDS_UTF8))
1152
1153
1154
1155
1156
1157
1158
1159
1160
				charset = FIDO_CHARSET_UTF8;
			else if(str_is_ascii(body))
				charset = FIDO_CHARSET_ASCII;
			else
				charset = FIDO_CHARSET_CP437;
		}
		fprintf(fp, "\1CHRS: %s\r", charset);
		if(msg->editor != NULL)
			fprintf(fp, "\1NOTE: %s\r", msg->editor);
1161
1162
		if(subject != msg->subj)
			fprintf(fp, "Subject: %s\r\r", msg->subj);
1163
	}
rswindell's avatar
rswindell committed
1164
	/* Write the body text */
1165
1166
	if(body != NULL) {
		int bodylen = strlen(body);
rswindell's avatar
rswindell committed
1167
		fwrite_crlf(body, bodylen, fp);
1168
1169
1170
		/* Write the tear line */
		if(bodylen > 0 && body[bodylen-1] != '\r' && body[bodylen-1] != '\n')
			fputc('\r', fp);
1171
		fprintf(fp, "%s", tear_line(strstr(body, "\n--- ") == NULL ? '-' : ' '));
1172
	}
rswindell's avatar
rswindell committed
1173
	fputc(FIDO_STORED_MSG_TERMINATOR, fp);
rswindell's avatar
rswindell committed
1174
	lprintf(LOG_INFO, "Created NetMail (%s)%s from %s (%s) to %s (%s), attr: %04hX, subject: %s"
1175
		,getfname(fname), (hdr.attr&FIDO_FILE) ? " with attachment" : ""
rswindell's avatar
rswindell committed
1176
		,from, smb_faddrtoa(&faddr, tmp), to, smb_faddrtoa(&dest, NULL), hdr.attr, subject);
1177
	return fclose(fp);
1178
1179
1180
}

/******************************************************************************
1181
1182
 This function takes the contents of 'infile' and puts it into netmail
 message(s) bound for addr.
1183
******************************************************************************/
1184
int file_to_netmail(FILE* infile, const char* title, fidoaddr_t dest, const char* to)
1185
1186
1187
{
	char *buf,*p;
	long l,m,len;
1188
	int netmails_created = 0;
1189

1190
1191
1192
	if(fseek(infile, 0, SEEK_END) != 0)
		return 0;

1193
1194
1195
	l=len=ftell(infile);
	if(len>8192L)
		len=8192L;
1196
1197
	else if(len < 1)
		return 0;
1198
	rewind(infile);
deuce's avatar
deuce committed
1199
	if((buf=(char *)malloc(len+1))==NULL) {
1200
		lprintf(LOG_ERR,"ERROR line %d allocating %lu for file to netmail buf",__LINE__,len);
rswindell's avatar
rswindell committed
1201
		return 0;
1202
	}
1203
1204
1205
	while((m=fread(buf,1,(len>8064L) ? 8064L:len,infile))>0) {
		buf[m]=0;
		if(l>8064L && (p=strrchr(buf,'\n'))!=NULL) {
1206
			p++;
1207
1208
			if(*p) {
				*p=0;
1209
				p++;
1210
				(void)fseek(infile,-1L,SEEK_CUR);
1211
1212
				while(*p) { 			/* Seek back to end of last line */
					p++;
rswindell's avatar
rswindell committed
1213
1214
1215
					(void)fseek(infile,-1L,SEEK_CUR);
				}
			}
1216
		}
1217
1218
		if(ftell(infile)<l)
			strcat(buf,"\r\nContinued in next message...\r\n");
1219
		if(create_netmail(to, /* msg: */NULL, title, buf, dest, /* src: */NULL) == 0)
1220
			netmails_created++;
1221
	}
deuce's avatar
deuce committed
1222
	free(buf);
1223
	return netmails_created;
1224
}
1225

1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
bool new_area(const char* tag, uint subnum, fidoaddr_t* link)
{
	area_t* area_list;
	if((area_list = realloc(cfg.area, sizeof(area_t) * (cfg.areas+1))) == NULL)
		return false;
	cfg.area = area_list;
	memset(&cfg.area[cfg.areas], 0 ,sizeof(area_t));
	cfg.area[cfg.areas].sub = subnum;
	if(tag != NULL) {
		if((cfg.area[cfg.areas].tag = strdup(tag)) == NULL)
			return false;
	}
	if(link != NULL) {
		if((cfg.area[cfg.areas].link = malloc(sizeof(fidoaddr_t))) == NULL)
			return false;
		cfg.area[cfg.areas].link[0] = *link;
		cfg.area[cfg.areas].links++;
	}
	cfg.areas++;

	return true;
}

rswindell's avatar
rswindell committed
1249
1250
/* Returns true if area is linked with specified node address */
bool area_is_linked(unsigned area_num, const fidoaddr_t* addr)
1251
{
1252
	unsigned i;
rswindell's avatar
rswindell committed
1253
1254
1255
1256
	for(i=0;i<cfg.area[area_num].links;i++)
		if(!memcmp(addr,&cfg.area[area_num].link[i],sizeof(fidoaddr_t)))
			return true;
	return false;
1257
1258
}

1259
1260
/* Returns area index */
uint find_area(const char* echotag)
rswindell's avatar
rswindell committed
1261
{
1262
1263
1264
	unsigned u;

	for(u=0; u < cfg.areas; u++)
1265
		if(cfg.area[u].tag != NULL && stricmp(cfg.area[u].tag, echotag) == 0)
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
			break;

	return u;
}

bool area_is_valid(uint areanum)
{
	return areanum < cfg.areas;
}

1276
1277
// if full is true, compare full address, otherwise, exclude point in comparison
uint find_sysfaddr(faddr_t addr, bool full)
1278
1279
1280
{
	unsigned u;

1281
1282
	for(u=0; u < scfg.total_faddrs; u++) {
		if(full && memcmp(&scfg.faddr[u], &addr, sizeof(addr)) == 0)
1283
			break;
1284
1285
1286
1287
1288
1289
		if(!full
			&& scfg.faddr[u].zone == addr.zone
			&& scfg.faddr[u].net == addr.net
			&& scfg.faddr[u].node == addr.node)
			break;
	}
1290
1291
1292
1293
1294
1295
1296
1297
1298

	return u;
}

bool sysfaddr_is_valid(uint faddr_num)
{
	return faddr_num < scfg.total_faddrs;
}

1299
1300
/* Returns subnum (INVALID_SUB if pass-through) or SUB_NOT_FOUND */
#define SUB_NOT_FOUND ((uint)-2)
1301
1302
1303
1304
1305
uint find_linked_area(const char* echotag, fidoaddr_t addr)
{
	unsigned area;

	if(area_is_valid(area = find_area(echotag)) && area_is_linked(area, &addr))
1306
		return cfg.area[area].sub;
rswindell's avatar
rswindell committed
1307

1308
	return SUB_NOT_FOUND;
rswindell's avatar
rswindell committed
1309
1310
}

1311
1312
1313
1314
1315
/******************************************************************************
 This function sends a notify list to applicable nodes, this list includes the
 settings configured for the node, as well as a list of areas the node is
 connected to.
******************************************************************************/
rswindell's avatar
rswindell committed
1316
void gen_notify_list(nodecfg_t* nodecfg)
1317
1318
1319
{
	FILE *	tmpf;
	char	str[256];
deuce's avatar
deuce committed
1320
	uint	i,k;
1321

rswindell's avatar
rswindell committed
1322
	for(k=0;k<cfg.nodecfgs && !terminated;k++) {
1323

rswindell's avatar
rswindell committed
1324
1325
1326
		if(nodecfg != NULL && &cfg.nodecfg[k] != nodecfg)
			continue;