sbbsecho.c 194 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
53
54
55
#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"
#include "msg_id.h"
#include "scfgsave.h"
#include "getmail.h"
rswindell's avatar
rswindell committed
56
57

#define MAX_OPEN_SMBS	10
58
59

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

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

rswindell's avatar
rswindell committed
86
int cur_smb=0;
87
FILE *fidologfile=NULL;
88
str_list_t twit_list;
rswindell's avatar
rswindell committed
89
str_list_t bad_areas;
90

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

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

102
103
str_list_t	locked_bso_nodes;

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

114
115
116
117
/* 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
118

119
120
121
122
123
124
125
126
127
128
	sprintf(str, "SBBSecho %u.%02u-%s r%s %s %s"
		,SBBSECHO_VERSION_MAJOR,SBBSECHO_VERSION_MINOR,PLATFORM_DESC,revision,__DATE__,compiler);

	return str;
}

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

130
131
132
133
134
	SAFEPRINTF2(str, "%u %s", getpid(), sbbsecho_pid());

	return str;
}

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

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

	return str;
}

145
146
147
148
const char default_domain[] = "fidonet";

const char* zone_domain(uint16_t zone)
{
149
150
151
152
	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;
153
154
155
156
157
158

	return default_domain;
}

const char* zone_root_outbound(uint16_t zone)
{
159
160
161
162
	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;
163
164
165
166

	return cfg.outbound;
}

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

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

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

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

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

271
272
273
274
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
275
	echostat_msg_t msg = {{0}};
276
277
278
279
280
281
282
283

	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);
284
285
	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);
286
287
288
289
290
291
292
293
294
295
	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;
}

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

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

	return msg;
}

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

	SAFECOPY(emsg.to	, smsg.to);
	SAFECOPY(emsg.from	, smsg.from);
	SAFECOPY(emsg.subj	, smsg.subj);
347
	emsg.msg_time		= smsg.hdr.when_written.time;
348
	emsg.localtime		= time(NULL);
349
	SAFECOPY(emsg.msg_tz, smb_zonestr(smsg.hdr.when_written.zone, NULL));
350
	if(smsg.from_net.type == NET_FIDO && smsg.from_net.addr != NULL)
351
		emsg.origaddr	= *(fidoaddr_t*)smsg.from_net.addr;
352
353
354
355
356
357
358
359
	if((p = smsg.ftn_msgid) != NULL)
		SAFECOPY(emsg.msg_id, p);
	if((p = smsg.ftn_reply) != NULL)
		SAFECOPY(emsg.reply_id, p);
	if((p = smsg.ftn_pid) != NULL)
		SAFECOPY(emsg.pid, p);
	if((p = smsg.ftn_tid) != NULL)
		SAFECOPY(emsg.tid, p);
360
361
	else
		SAFECOPY(emsg.tid, sbbsecho_pid());
362
	emsg.length = msglen;
363
	emsg.pkt_orig = addr;
364
365
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

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

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

438
439
440
	return tstr;
}

rswindell's avatar
rswindell committed
441
void fwrite_echostat_msg(FILE* fp, echostat_msg_t msg, const char* prefix)
442
{
rswindell's avatar
rswindell committed
443
	echostat_msg_t zero = {{0}};
444
445
446
447
448
449
450
451
452
	if(memcmp(&msg, &zero, sizeof(msg)) == 0)
		return;
	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);
453
	fprintf(fp, "%s.length = %lu\n"							, prefix, (ulong)msg.length);
454
455
	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);
456
	fprintf(fp, "%s.localtime = %s\n"						, prefix, iniTimeStr(msg.localtime));
457
458
	if(msg.origaddr.zone)
		fprintf(fp, "%s.origaddr = %s\n"					, prefix, faddrtoa(&msg.origaddr));
459
460
461
	fprintf(fp, "%s.pkt_orig = %s\n"						, prefix, faddrtoa(&msg.pkt_orig));
}

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

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

482
483
	qsort(echostat, echo_count, sizeof(echostat_t), echostat_compare);

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

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

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

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

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

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

558
559
560
/*****************************************************************************/
/* Returns command line generated from instr with %c replacments             */
/*****************************************************************************/
rswindell's avatar
rswindell committed
561
char *mycmdstr(scfg_t* cfg, const char *instr, const char *fpath, const char *fspec)
562
{
563
    static char cmd[MAX_PATH+1];
564
565
566
    char str[256],str2[128];
    int i,j,len;

567
568
569
570
571
572
573
	len=strlen(instr);
	for(i=j=0;i<len && j<128;i++) {
		if(instr[i]=='%') {
			i++;
			cmd[j]=0;
			switch(toupper(instr[i])) {
				case 'F':   /* File path */
574
					SAFECAT(cmd,fpath);
575
576
					break;
				case 'G':   /* Temp directory */
rswindell's avatar
rswindell committed
577
578
					if(cfg->temp_dir[0]!='\\'
						&& cfg->temp_dir[0]!='/'
579
						&& cfg->temp_dir[1]!=':') {
580
581
						SAFECOPY(str,cfg->node_dir);
						SAFECAT(str,cfg->temp_dir);
582
583
584
						if(FULLPATH(str2,str,40))
							strcpy(str,str2);
						backslash(str);
585
						SAFECAT(cmd,str);}
586
					else
587
						SAFECAT(cmd,cfg->temp_dir);
588
589
					break;
				case 'J':
rswindell's avatar
rswindell committed
590
591
					if(cfg->data_dir[0]!='\\'
						&& cfg->data_dir[0]!='/'
592
						&& cfg->data_dir[1]!=':') {
593
594
						SAFECOPY(str,cfg->node_dir);
						SAFECAT(str,cfg->data_dir);
595
						if(FULLPATH(str2,str,40))
596
							SAFECOPY(str,str2);
597
						backslash(str);
rswindell's avatar
rswindell committed
598
						SAFECAT(cmd,str);
599
					}
600
					else
601
						SAFECAT(cmd,cfg->data_dir);
602
603
					break;
				case 'K':
rswindell's avatar
rswindell committed
604
605
					if(cfg->ctrl_dir[0]!='\\'
						&& cfg->ctrl_dir[0]!='/'
606
						&& cfg->ctrl_dir[1]!=':') {
607
608
						SAFECOPY(str,cfg->node_dir);
						SAFECAT(str,cfg->ctrl_dir);
609
						if(FULLPATH(str2,str,40))
610
							SAFECOPY(str,str2);
611
						backslash(str);
rswindell's avatar
rswindell committed
612
						SAFECAT(cmd,str);
613
					}
614
					else
615
						SAFECAT(cmd,cfg->ctrl_dir);
616
617
					break;
				case 'N':   /* Node Directory (same as SBBSNODE environment var) */
618
					SAFECAT(cmd,cfg->node_dir);
619
620
					break;
				case 'O':   /* SysOp */
621
					SAFECAT(cmd,cfg->sys_op);
622
623
					break;
				case 'Q':   /* QWK ID */
624
					SAFECAT(cmd,cfg->sys_id);
625
626
					break;
				case 'S':   /* File Spec */
627
					SAFECAT(cmd,fspec);
628
629
					break;
				case '!':   /* EXEC Directory */
630
					SAFECAT(cmd,cfg->exec_dir);
631
					break;
632
633
                case '@':   /* EXEC Directory for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
634
                    SAFECAT(cmd,cfg->exec_dir);
635
636
#endif
                    break;
637
638
				case '#':   /* Node number (same as SBBSNNUM environment var) */
					sprintf(str,"%d",cfg->node_num);
639
					SAFECAT(cmd,str);
640
641
642
					break;
				case '*':
					sprintf(str,"%03d",cfg->node_num);
643
					SAFECAT(cmd,str);
644
645
					break;
				case '%':   /* %% for percent sign */
646
					SAFECAT(cmd,"%");
647
					break;
648
649
				case '.':	/* .exe for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
650
					SAFECAT(cmd,".exe");
651
652
653
#endif
					break;
				case '?':	/* Platform */
654
					SAFECOPY(str,PLATFORM_DESC);
655
					strlwr(str);
656
					SAFECAT(cmd,str);
657
					break;
658
				default:    /* unknown specification */
659
					lprintf(LOG_ERR,"ERROR Checking Command Line '%s'",instr);
660
					bail(1);
rswindell's avatar
rswindell committed
661
					break;
662
			}
rswindell's avatar
rswindell committed
663
			j=strlen(cmd);
664
		}
665
		else
rswindell's avatar
rswindell committed
666
			cmd[j++]=instr[i];
667
}
668
	cmd[j]=0;
669

670
	return(cmd);
671
672
673
}

/****************************************************************************/
rswindell's avatar
rswindell committed
674
/* Runs an external program directly using system()							*/
675
676
677
/****************************************************************************/
int execute(char *cmdline)
{
rswindell's avatar
rswindell committed
678
	int retval;
rswindell's avatar
rswindell committed
679

rswindell's avatar
rswindell committed
680
681
682
683
	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));
684

rswindell's avatar
rswindell committed
685
	return retval;
686
}
687

688
/******************************************************************************
rswindell's avatar
rswindell committed
689
 Returns the local system address that best correlates with the passed address
690
******************************************************************************/
rswindell's avatar
rswindell committed
691
fidoaddr_t getsysfaddr(fidoaddr_t addr)
692
{
rswindell's avatar
rswindell committed
693
	nodecfg_t*	nodecfg;
694

rswindell's avatar
rswindell committed
695
696
697
698
699
700
701
	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
702
			return scfg.faddr[i];
rswindell's avatar
rswindell committed
703
704
	for(i=0; i<scfg.total_faddrs; i++)
		if(scfg.faddr[i].zone == addr.zone)
rswindell's avatar
rswindell committed
705
706
			return scfg.faddr[i];
	return sys_faddr;
707
}
708

rswindell's avatar
rswindell committed
709
int get_outbound(fidoaddr_t dest, char* outbound, size_t maxlen, bool fileboxes)
710
{
rswindell's avatar
rswindell committed
711
712
	nodecfg_t*	nodecfg;

713
	strncpy(outbound,zone_root_outbound(dest.zone),maxlen);
rswindell's avatar
rswindell committed
714
715
716
717
718
719
	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/" */
720
721
722
			char* p = lastchar(outbound);
			if(IS_PATH_DELIM(*p))
				*p = 0;
rswindell's avatar
rswindell committed
723
724
725
726
727
728
			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);
		}
729
730
	}
	backslash(outbound);
731
732
733
	if(isdir(outbound))
		return 0;
	lprintf(LOG_DEBUG, "Creating outbound directory for %s: %s", smb_faddrtoa(&dest, NULL), outbound);
734
735
736
	return mkpath(outbound);
}

rswindell's avatar
rswindell committed
737
738
739
const char* get_current_outbound(fidoaddr_t dest, bool fileboxes)
{
	static char outbound[MAX_PATH+1];
740
741
742
743
	if(get_outbound(dest, outbound, sizeof(outbound)-1, fileboxes) != 0) {
		lprintf(LOG_ERR, "Error creating outbound directory");
		return NULL;
	}
rswindell's avatar
rswindell committed
744
745
746
747
	return outbound;
}

bool bso_lock_node(fidoaddr_t dest)
748
{
rswindell's avatar
rswindell committed
749
	const char* outbound;
750
751
	char fname[MAX_PATH+1];

rswindell's avatar
rswindell committed
752
753
	if(!cfg.flo_mailer)
		return true;
754

rswindell's avatar
rswindell committed
755
	outbound = get_current_outbound(dest, /* fileboxes: */false);
756
757
	if(outbound == NULL)
		return false;
758
759
760
761
762
763

	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
764
765
766
767
768
	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;
769
		lprintf(LOG_NOTICE, "Node (%s) externally locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
770
771
772
773
774
775
776
		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);
777
		mkpath(outbound); // Just in case something (e.g. Argus) deleted the outbound directory
778
779
780
	}
	strListPush(&locked_bso_nodes, fname);
	lprintf(LOG_DEBUG, "Node (%s) successfully locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
781
782
783
	return true;
}

784
const char* bso_flo_filename(fidoaddr_t dest, uint16_t attr)
rswindell's avatar
rswindell committed
785
786
787
788
789
790
{
	nodecfg_t* nodecfg;
	char ch='f';
	const char* outbound;
	static char filename[MAX_PATH+1];

791
792
793
794
795
	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
796
797
798
799
800
801
802
803
804
805
		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);
806
807
	if(outbound == NULL)
		return NULL;
rswindell's avatar
rswindell committed
808
809
810
811
812
813
814
815

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

816
817
818
819
820
/******************************************************************************
 This function creates or appends on existing Binkley compatible .?LO file
 attach file.
 Returns 0 on success.
******************************************************************************/
821
822
int write_flofile(const char *infile, fidoaddr_t dest, bool bundle, bool use_outbox
									, bool del_file, uint16_t attr)
823
{
rswindell's avatar
rswindell committed
824
825
	const char* flo_filename;
	char attachment[MAX_PATH+1];
826
	char searchstr[MAX_PATH+1];
827
	char* p;
rswindell's avatar
rswindell committed
828
829
	FILE *fp;
	nodecfg_t* nodecfg;
830

rswindell's avatar
rswindell committed
831
832
833
834
	if(use_outbox && (nodecfg=findnodecfg(&cfg, dest, /* exact: */false)) != NULL) {
		if(nodecfg->outbox[0])
			return 0;
	}
835
836
837
838

	if(!bso_lock_node(dest))
		return 1;

839
	flo_filename = bso_flo_filename(dest, attr);
840
841
842
	if(flo_filename == NULL)
		return -2;

843
844
845
	if(*infile == '^')  /* work-around for BRE/FE inter-BBS attachment bug */
		infile++;

846
#ifdef __unix__
847
	if(IS_ALPHA(infile[0]) && infile[1] == ':')	// Ignore "C:" prefix
848
849
		infile += 2;
#endif
rswindell's avatar
rswindell committed
850
	SAFECOPY(attachment, infile);
851
	REPLACE_CHARS(attachment, '\\', '/', p);
rswindell's avatar
rswindell committed
852
853
854
	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;
855
	}
rswindell's avatar
rswindell committed
856
857
858
859
	char* prefix = "";
	if(bundle) {
		prefix = (cfg.trunc_bundles) ? "#" : "^";
	} else {
860
		if(del_file)
rswindell's avatar
rswindell committed
861
862
863
			prefix = "^";
	}
	SAFEPRINTF2(searchstr, "%s%s", prefix, attachment);
rswindell's avatar
rswindell committed
864
865
866
867
	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
868
		return -1;
rswindell's avatar
rswindell committed
869
870
871
872
873
874
	}
	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;
875
876
}

877
/* Writes text buffer to file, expanding sole LFs to CRLFs or stripping LFs */
rswindell's avatar
rswindell committed
878
size_t fwrite_crlf(const char* buf, size_t len, FILE* fp)
879
880
881
882
883
884
885
{
	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++;
886
887
888
889
890
891
892
893
		if(ch=='\n') {
			if(last_ch!='\r') {
				if(fputc('\r', fp) == EOF)
					break;
				wr++;
			}
			if(cfg.strip_lf)
				continue;
894
895
		}
		if(fputc(ch,fp)==EOF)
rswindell's avatar
rswindell committed
896
			break;
897
898
899
900
901
902
903
		wr++;
		last_ch=ch;
	}

	return(wr);
}

rswindell's avatar
rswindell committed
904
bool fidoctrl_line_exists(const smbmsg_t* msg, const char* prefix)
905
{
906
	if(msg==NULL || prefix==NULL)
rswindell's avatar
rswindell committed
907
		return false;
908
909
910
	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
911
			return true;
912
	}
rswindell's avatar
rswindell committed
913
	return false;
914
915
}

rswindell's avatar
rswindell committed
916
fidoaddr_t fmsghdr_srcaddr(const fmsghdr_t* hdr)
917
918
919
920
921
922
923
924
925
926
927
{
	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
928
const char* fmsghdr_srcaddr_str(const fmsghdr_t* hdr)
929
930
931
932
933
934
935
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_srcaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

rswindell's avatar
rswindell committed
936
fidoaddr_t fmsghdr_destaddr(const fmsghdr_t* hdr)
937
938
939
940
941
942
943
944
945
946
947
{
	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
948
const char* fmsghdr_destaddr_str(const fmsghdr_t* hdr)
949
950
951
952
953
954
955
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_destaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

956
957
958
959
bool parse_origin(const char* fmsgbuf, fmsghdr_t* hdr)
{
	char* p;
	fidoaddr_t origaddr;
rswindell's avatar
rswindell committed
960

961
962
963
964
965
966
967
968
969
970
971
	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
972
	if(origaddr.zone == 0 || faddr_contains_wildcard(&origaddr))
973
974
975
976
977
978
979
980
		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
981
982
983
984
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
985
	enum pkt_type type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
986
987
988
989
990
991
992
993
994
995
996
997
998
999

	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
1000
1001
1002
1003
	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
1004
		dest.point = hdr->type2plus.destpoint;
rswindell's avatar
rswindell committed
1005
1006
1007
1008
1009
1010
		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
1011
1012
1013
1014
		}
	} 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
1015
		dest.point = hdr->type2_2.destpoint;
rswindell's avatar
rswindell committed
1016
1017
1018
1019
1020
1021
1022
1023
	}

	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
1024

rswindell's avatar
rswindell committed
1025
1026
1027
1028
1029
	return true;
}

bool new_pkthdr(fpkthdr_t* hdr, fidoaddr_t orig, fidoaddr_t dest, const nodecfg_t* nodecfg)
{
rswindell's avatar
rswindell committed
1030
	enum pkt_type pkt_type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
	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
1054

rswindell's avatar
rswindell committed
1055
1056
1057
1058
1059
	hdr->type2.pkttype	= 2;
	hdr->type2.prodcode	= SBBSECHO_PRODUCT_CODE&0xff;
	hdr->type2.sernum	= SBBSECHO_VERSION_MAJOR;

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

rswindell's avatar
rswindell committed
1062
	if(pkt_type == PKT_TYPE_2)
rswindell's avatar
rswindell committed
1063
1064
1065
1066
		return true;

	if(pkt_type == PKT_TYPE_2_2) {
		hdr->type2_2.subversion = 2;	/* 2.2 */
1067
1068
		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
1069
1070
		return true;
	}
rswindell's avatar
rswindell committed
1071

rswindell's avatar
rswindell committed
1072
1073
	/* 2e and 2+ */
	if(pkt_type != PKT_TYPE_2_EXT && pkt_type != PKT_TYPE_2_PLUS) {
rswindell's avatar
rswindell committed
1074
1075
1076
1077
		lprintf(LOG_ERR, "UNSUPPORTED PACKET TYPE: %u", pkt_type);
		return false;
	}

rswindell's avatar
rswindell committed
1078
1079
1080
1081
	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
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
	}
	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;
}

1095
/******************************************************************************
rswindell's avatar
rswindell committed
1096
 This function will create a netmail message (FTS-1 "stored message" format).
1097
1098
1099
 If file is non-zero, will set file attachment bit (for bundles).
 Returns 0 on success.
******************************************************************************/
rswindell's avatar
rswindell committed
1100
int create_netmail(const char *to, const smbmsg_t* msg, const char *subject, const char *body, fidoaddr_t dest)
1101
{
1102
	FILE *fp;
1103
	char tmp[256];
1104
	char fname[MAX_PATH+1];
1105
	char* from=NULL;
1106
1107
	uint i;
	static uint startmsg;
rswindell's avatar
rswindell committed
1108
	fidoaddr_t	faddr;
1109
	fmsghdr_t hdr;
1110
	time_t t;
1111
	struct tm *tm;
1112
	when_t when_written;
rswindell's avatar
rswindell committed
1113
1114
	nodecfg_t* nodecfg;
	bool	direct=false;
1115

1116
	if(msg==NULL) {
rswindell's avatar
rswindell committed
1117
		when_written.time = time32(NULL);
1118
1119
1120
1121
1122
		when_written.zone = sys_timezone(&scfg);
	} else {
		from = msg->from;
		when_written = msg->hdr.when_written;
	}
rswindell's avatar
rswindell committed
1123
	if(from==NULL || *from==0) {
1124
		from="SBBSecho";
rswindell's avatar
rswindell committed
1125
1126
1127
1128
1129
1130
1131
		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)
1132
		to="Sysop";
1133
	if(!startmsg) startmsg=1;
rswindell's avatar
rswindell committed
1134
1135
1136
	if((nodecfg=findnodecfg(&cfg, dest, 0)) != NULL) {
		if(nodecfg->status == MAIL_STATUS_NORMAL && !nodecfg->direct)
			nodecfg=findnodecfg(&cfg, dest, /* skip exact match: */2);
1137
	}
1138

rswindell's avatar
rswindell committed
1139
	if(!isdir(scfg.netmail_dir) && mkpath(scfg.netmail_dir) != 0) {
1140
1141
1142
		lprintf(LOG_ERR, "Error %u (%s) line %d creating directory: %s", errno, strerror(errno), __LINE__, scfg.netmail_dir);
		return -2;
	}
1143
1144
1145
	for(i=startmsg;i;i++) {
		sprintf(fname,"%s%u.msg",scfg.netmail_dir,i);
		if(!fexistcase(fname))
rswindell's avatar
rswindell committed
1146
			break;
1147
1148
1149
	}
	if(!i) {
		lprintf(LOG_WARNING,"Directory full: %s",scfg.netmail_dir);
rswindell's avatar
rswindell committed
1150
		return(-1);
1151
1152
	}
	startmsg=i+1;
1153
	if((fp=fnopen(NULL,fname,O_RDWR|O_CREAT))==NULL) {
1154
		lprintf(LOG_ERR,"ERROR %u (%s) line %d opening %s",errno,strerror(errno),__LINE__,fname);
rswindell's avatar
rswindell committed
1155
		return(-1);
1156
	}
1157

1158
1159
1160
	if(msg != NULL && msg->from_net.type == NET_FIDO && msg->from_net.addr != NULL)
		faddr = *(fidoaddr_t*)msg->from_net.addr;
	else
rswindell's avatar
rswindell committed
1161
		faddr = getsysfaddr(dest);
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
	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);
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
	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;
	}
1183

rswindell's avatar
rswindell committed
1184
1185
1186
1187
	if(nodecfg != NULL) {
		switch(nodecfg->status) {
			case MAIL_STATUS_HOLD:	hdr.attr|=FIDO_HOLD;	break;
			case MAIL_STATUS_CRASH:	hdr.attr|=FIDO_CRASH;	break;
1188
			case MAIL_STATUS_NORMAL:						break;
rswindell's avatar
rswindell committed
1189
1190
1191
		}
		direct = nodecfg->direct;
	}
1192

1193
	t = when_written.time;
rswindell's avatar
rswindell committed
1194
	tm = localtime(&t);
1195
1196
1197
1198
1199
1200
1201
1202
	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);

1203
	(void)fwrite(&hdr,sizeof(fmsghdr_t),1,fp);
1204
	fwrite_intl_control_line(fp, &hdr);
1205

1206
1207
1208
1209
1210
1211
1212
1213
1214
	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);
1215
	}
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
	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");
1229
		}
1230
	} else {
1231
1232
1233
1234
		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);
1235
1236
1237
1238
		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");
1239
	}
1240

1241
1242
1243
1244
	if(hdr.destpoint)
		fprintf(fp,"\1TOPT %hu\r",hdr.destpoint);
	if(hdr.origpoint)
		fprintf(fp,"\1FMPT %hu\r",hdr.origpoint);
1245
1246
	fprintf(fp,"\1PID: %s\r", (msg==NULL || msg->ftn_pid==NULL) ? sbbsecho_pid() : msg->ftn_pid);
	if(msg != NULL) {
1247
1248
		if(msg->columns)
			fprintf(fp,"\1COLS: %u\r", (unsigned int)msg->columns);
1249
1250
1251
1252
		/* 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]);
1253
1254
		const char* charset = msg->ftn_charset;
		if(charset == NULL) {
1255
			if(smb_msg_is_utf8(msg) || (msg->hdr.auxattr & MSG_HFIELDS_UTF8))
1256
1257
1258
1259
1260
1261
1262
1263
1264
				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);
1265
1266
		if(subject != msg->subj)
			fprintf(fp, "Subject: %s\r\r", msg->subj);
1267
	}
rswindell's avatar
rswindell committed
1268
	/* Write the body text */
1269
1270
	if(body != NULL) {
		int bodylen = strlen(body);
rswindell's avatar
rswindell committed
1271
		fwrite_crlf(body, bodylen, fp);
1272
1273
1274
		/* Write the tear line */
		if(bodylen > 0 && body[bodylen-1] != '\r' && body[bodylen-1] != '\n')
			fputc('\r', fp);
1275
		fprintf(fp, "%s", tear_line(strstr(body, "\n--- ") == NULL ? '-' : ' '));
1276
	}
rswindell's avatar
rswindell committed
1277
	fputc(FIDO_STORED_MSG_TERMINATOR, fp);
rswindell's avatar
rswindell committed
1278
	lprintf(LOG_INFO, "Created NetMail (%s)%s from %s (%s) to %s (%s), attr: %04hX, subject: %s"
1279
		,getfname(fname), (hdr.attr&FIDO_FILE) ? " with attachment" : ""
rswindell's avatar
rswindell committed
1280
		,from, smb_faddrtoa(&faddr, tmp), to, smb_faddrtoa(&dest, NULL), hdr.attr, subject);
1281
	return fclose(fp);
1282
1283
1284
}

/******************************************************************************
1285
1286
 This function takes the contents of 'infile' and puts it into netmail
 message(s) bound for addr.
1287
******************************************************************************/
1288
int file_to_netmail(FILE* infile, const char* title, fidoaddr_t dest, const char* to)
1289
1290
1291
{
	char *buf,*p;
	long l,m,len;
1292
	int netmails_created = 0;
1293