sbbsecho.c 196 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;
	}
rswindell's avatar
rswindell committed
1011
	if(from==NULL || *from==0) {
1012
		from="SBBSecho";
rswindell's avatar
rswindell committed
1013
1014
1015
1016
1017
1018
1019
		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)
1020
		to="Sysop";
1021
	if(!startmsg) startmsg=1;
rswindell's avatar
rswindell committed
1022
1023
1024
	if((nodecfg=findnodecfg(&cfg, dest, 0)) != NULL) {
		if(nodecfg->status == MAIL_STATUS_NORMAL && !nodecfg->direct)
			nodecfg=findnodecfg(&cfg, dest, /* skip exact match: */2);
1025
	}
1026

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

1046
1047
1048
	if(src != NULL)
		faddr = *src;
	else if(msg != NULL && msg->from_net.type == NET_FIDO && msg->from_net.addr != NULL)
1049
1050
		faddr = *(fidoaddr_t*)msg->from_net.addr;
	else
rswindell's avatar
rswindell committed
1051
		faddr = getsysfaddr(dest);
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
	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);
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
	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;
	}
1073

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

1083
	t = when_written.time;
rswindell's avatar
rswindell committed
1084
	tm = localtime(&t);
1085
1086
1087
1088
1089
1090
1091
1092
	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);

1093
	(void)fwrite(&hdr,sizeof(fmsghdr_t),1,fp);
1094
	fwrite_intl_control_line(fp, &hdr);
1095

1096
1097
1098
1099
1100
1101
1102
1103
1104
	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);
1105
	}
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
	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");
1119
		}
Rob Swindell's avatar
Rob Swindell committed
1120
		fprintf(fp, "\1MSGID: %s %08lx\r", smb_faddrtoa(&faddr, NULL), (ulong)time32(NULL));
1121
	} else {
1122
1123
1124
1125
		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);
1126
1127
		if(msg->ftn_bbsid != NULL)
			fprintf(fp, "\1BBSID: %.256s\r", msg->ftn_bbsid);
1128
1129
1130
1131
		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");
1132
	}
1133

1134
1135
1136
1137
	if(hdr.destpoint)
		fprintf(fp,"\1TOPT %hu\r",hdr.destpoint);
	if(hdr.origpoint)
		fprintf(fp,"\1FMPT %hu\r",hdr.origpoint);
1138
1139
	fprintf(fp,"\1PID: %s\r", (msg==NULL || msg->ftn_pid==NULL) ? sbbsecho_pid() : msg->ftn_pid);
	if(msg != NULL) {
1140
1141
		if(msg->columns)
			fprintf(fp,"\1COLS: %u\r", (unsigned int)msg->columns);