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
#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 twit_list;
rswindell's avatar
rswindell committed
93
str_list_t bad_areas;
94

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

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

106
107
str_list_t	locked_bso_nodes;

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

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

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

	return str;
}

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

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

	return str;
}

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

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

	return str;
}

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

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

	return default_domain;
}

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

	return cfg.outbound;
}

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

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

223
224
225
226
227
228
229
230
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];
231
232
	char msg_tz[128];
	time_t msg_time;
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
	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];
258
	bool known;	// listed in Area File
259
260
261
262
263
264
265
266
	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;

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

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

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

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

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

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

	return msg;
}

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

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

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

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

442
443
444
	return tstr;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	if(!bso_lock_node(dest))
		return 1;

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

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

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

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

	return(wr);
}

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

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

	return smb_faddrtoa(&addr, buf);
}

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

	return smb_faddrtoa(&addr, buf);
}

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

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

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

	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
913

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

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

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

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

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

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

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

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

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

1006
	if(msg==NULL) {
rswindell's avatar
rswindell committed
1007
		when_written.time = time32(NULL);
1008
1009
1010
1011
1012
		when_written.zone = sys_timezone(&scfg);
	} else {
		from = msg->from;
		when_written = msg->hdr.when_written;
	}
rswindell's avatar
rswindell committed
1013
	if(from==NULL || *from==0) {
1014
		from="SBBSecho";
rswindell's avatar
rswindell committed
1015
1016
1017
1018
1019
1020
1021
		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)
1022
		to="Sysop";
1023
	if(!startmsg) startmsg=1;
rswindell's avatar
rswindell committed
1024
1025
1026
	if((nodecfg=findnodecfg(&cfg, dest, 0)) != NULL) {
		if(nodecfg->status == MAIL_STATUS_NORMAL && !nodecfg->direct)
			nodecfg=findnodecfg(&cfg, dest, /* skip exact match: */2);
1027
	}
1028

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

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

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

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

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

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

1136
1137
1138
1139
	if(hdr.destpoint)
		fprintf(fp,"\1TOPT %hu\r",hdr.destpoint);
	if(hdr.origpoint)
		fprintf(fp,"\1FMPT %hu\r",hdr.origpoint);
1140
1141
	fprintf(fp,"\1PID: %s\r", (msg==NULL ||