sbbsecho.c 186 KB
Newer Older
1
/* Synchronet FidoNet EchoMail Scanning/Tossing and NetMail Tossing Utility */
2

3
/* $Id$ */
4
// vi: tabstop=4
5
6
7
8
9

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
10
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 *																			*
 * 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										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/
36

37
/* Portions written by Allen Christiansen 1994-1996 						*/
38
39
40
41
42
43
44
45
46

#include <time.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
47
#include <sys/stat.h>
48
49
50
#if defined(__unix__)
	#include <signal.h>
#endif
51

rswindell's avatar
rswindell committed
52
#include "conwrap.h"		/* getch() */
53
54
55
56
57
58
#include "sbbs.h"			/* load_cfg() */
#include "sbbsdefs.h"
#include "smblib.h"
#include "scfglib.h"
#include "lzh.h"
#include "sbbsecho.h"
deuce's avatar
deuce committed
59
#include "genwrap.h"		/* PLATFORM_DESC */
rswindell's avatar
rswindell committed
60
61
62
#include "xpendian.h"

#define MAX_OPEN_SMBS	10
63
64

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

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

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

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

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

107
108
str_list_t	locked_bso_nodes;

109
int lprintf(int level, char *fmt, ...);
rswindell's avatar
rswindell committed
110
int mv(const char *insrc, const char *indest, bool copy);
111
time32_t fmsgtime(const char *str);
rswindell's avatar
rswindell committed
112
void export_echomail(const char *sub_code, const nodecfg_t*, bool rescan);
rswindell's avatar
rswindell committed
113
const char* area_desc(const char* areatag);
rswindell's avatar
rswindell committed
114

115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/* FTN-compliant "Program Identifier"/PID (also used as a "Tosser Identifier"/TID) */
const char* sbbsecho_pid(void)
{
	static char str[256];
	
	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];
	
	SAFEPRINTF2(str, "%u %s", getpid(), sbbsecho_pid());

	return str;
}

rswindell's avatar
rswindell committed
136
137
138
139
140
141
142
143
144
145
const char* tear_line(void)
{
	static char str[256];

	sprintf(str,"--- SBBSecho %u.%02u-%s\r"
		,SBBSECHO_VERSION_MAJOR,SBBSECHO_VERSION_MINOR,PLATFORM_DESC);

	return str;
}

146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
const char default_domain[] = "fidonet";

const char* zone_domain(uint16_t zone)
{
	struct zone_mapping *i;

	if (!cfg.use_ftn_domains)
		return default_domain;

	for (i=cfg.zone_map; i; i=i->next)
		if (i->zone == zone)
			return i->domain;

	return default_domain;
}

const char* zone_root_outbound(uint16_t zone)
{
	struct zone_mapping *i;

	if (!cfg.use_ftn_domains)
		return cfg.outbound;

	for (i=cfg.zone_map; i; i=i->next)
		if (i->zone == zone)
			return i->root;

	return cfg.outbound;
}

176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/* 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;
	sprintf(str, "\1%s", kludge);
	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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/* 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);
}
220
221
222
223
224
225
226
227

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

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

272
273
int echostat_compare(const void* c1, const void* c2)
{
274
275
	echostat_t* stat1 = (echostat_t*)c1;
	echostat_t* stat2 = (echostat_t*)c2;
276
277
278
279

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

280
281
282
283
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
284
	echostat_msg_t msg = {{0}};
285
286
287
288
289
290
291
292

	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);
293
294
	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);
295
296
297
298
299
300
301
302
303
304
305
306
307
	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;
}

echostat_msg_t fidomsg_to_echostat_msg(fmsghdr_t fmsghdr, fidoaddr_t* pkt_orig, const char* fmsgbuf)
{
	char* p;
rswindell's avatar
rswindell committed
308
	echostat_msg_t msg = {{0}};
309
310
311
312

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

	return msg;
}

348
echostat_msg_t smsg_to_echostat_msg(smbmsg_t smsg, size_t msglen, fidoaddr_t addr)
349
350
{
	char* p;
rswindell's avatar
rswindell committed
351
	echostat_msg_t emsg = {{0}};
352
353
354
355

	SAFECOPY(emsg.to	, smsg.to);
	SAFECOPY(emsg.from	, smsg.from);
	SAFECOPY(emsg.subj	, smsg.subj);
356
	emsg.msg_time		= smsg.hdr.when_written.time;
357
	emsg.localtime		= time(NULL);
358
	SAFECOPY(emsg.msg_tz, smb_zonestr(smsg.hdr.when_written.zone, NULL));
359
360
361
362
363
364
365
366
367
368
	if(smsg.from_net.type == NET_FIDO && smsg.from_net.addr != NULL)
		emsg.origaddr	= atofaddr(smsg.from_net.addr);
	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);
369
370
	else
		SAFECOPY(emsg.tid, sbbsecho_pid());
371
	emsg.length = msglen;
372
	emsg.pkt_orig = addr;
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410

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

429
	lprintf(LOG_DEBUG, "Read %u echo statistics from %s", echo_count, fname);
430
431
432
433
434
435
436
437
438
439
440
441
442
	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)
443
		sprintf(tstr, "0x%lx", (ulong)t);
444
445
446
447
448
449
	else
		sprintf(tstr, "%.3s %.2s %.4s %.8s", p+4, p+8, p+20, p+11);
	
	return tstr;
}

rswindell's avatar
rswindell committed
450
void fwrite_echostat_msg(FILE* fp, echostat_msg_t msg, const char* prefix)
451
{
rswindell's avatar
rswindell committed
452
	echostat_msg_t zero = {{0}};
453
454
455
456
457
458
459
460
461
462
	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);
	fprintf(fp, "%s.length = %lu\n"							, prefix, msg.length);
463
464
	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);
465
	fprintf(fp, "%s.localtime = %s\n"						, prefix, iniTimeStr(msg.localtime));
466
467
	if(msg.origaddr.zone)
		fprintf(fp, "%s.origaddr = %s\n"					, prefix, faddrtoa(&msg.origaddr));
468
469
470
	fprintf(fp, "%s.pkt_orig = %s\n"						, prefix, faddrtoa(&msg.pkt_orig));
}

rswindell's avatar
rswindell committed
471
472
473
474
475
476
477
478
479
480
481
482
483
void fwrite_echostat(FILE* fp, echostat_t* stat)
{
	fprintf(fp, "[%s]\n"						, stat->tag);
	fprintf(fp,	"Known = %s\n"					, stat->known ? "true" : "false");
	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]);
	}
}

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

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

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

	for(unsigned i = 0; i < echo_count; i++) {
rswindell's avatar
rswindell committed
494
		fwrite_echostat(fp, &echostat[i]);
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
		fprintf(fp, "\n");
	}
	fclose(fp);
	return true;
}

echostat_t* get_echostat(const char* tag)
{
	for(unsigned int i = 0; i < echostat_count; i++) {
		if(stricmp(echostat[i].tag, tag) == 0)
			return &echostat[i];
	}
	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
563
564
/*****************************************************************************/
/* Returns command line generated from instr with %c replacments             */
/*****************************************************************************/
rswindell's avatar
rswindell committed
565
char *mycmdstr(scfg_t* cfg, const char *instr, const char *fpath, const char *fspec)
566
{
567
    static char cmd[MAX_PATH+1];
568
569
570
    char str[256],str2[128];
    int i,j,len;

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

674
	return(cmd);
675
676
677
}

/****************************************************************************/
rswindell's avatar
rswindell committed
678
/* Runs an external program directly using system()							*/
679
680
681
/****************************************************************************/
int execute(char *cmdline)
{
rswindell's avatar
rswindell committed
682
683
684
685
686
687
	int retval;
	
	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));
688

rswindell's avatar
rswindell committed
689
	return retval;
690
}
691

692
693
694
/******************************************************************************
 Returns the system address with the same zone as the address passed
******************************************************************************/
rswindell's avatar
rswindell committed
695
fidoaddr_t getsysfaddr(short zone)
696
697
{
	int i;
698

699
700
701
	for(i=0;i<scfg.total_faddrs;i++)
		if(scfg.faddr[i].zone==zone)
			return(scfg.faddr[i]);
702
	return(sys_faddr);
703
}
704

rswindell's avatar
rswindell committed
705
int get_outbound(fidoaddr_t dest, char* outbound, size_t maxlen, bool fileboxes)
706
{
rswindell's avatar
rswindell committed
707
708
	nodecfg_t*	nodecfg;

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

rswindell's avatar
rswindell committed
733
734
735
736
737
738
739
740
const char* get_current_outbound(fidoaddr_t dest, bool fileboxes)
{
	static char outbound[MAX_PATH+1];
	get_outbound(dest, outbound, sizeof(outbound)-1, fileboxes);
	return outbound;
}

bool bso_lock_node(fidoaddr_t dest)
741
{
rswindell's avatar
rswindell committed
742
	const char* outbound;
743
744
	char fname[MAX_PATH+1];

rswindell's avatar
rswindell committed
745
746
	if(!cfg.flo_mailer)
		return true;
747

rswindell's avatar
rswindell committed
748
	outbound = get_current_outbound(dest, /* fileboxes: */false);
749
750
751
752
753
754

	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
755
756
757
758
759
	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;
760
		lprintf(LOG_NOTICE, "Node (%s) externally locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
761
762
763
764
765
766
767
		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);
768
769
770
	}
	strListPush(&locked_bso_nodes, fname);
	lprintf(LOG_DEBUG, "Node (%s) successfully locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
771
772
773
	return true;
}

774
const char* bso_flo_filename(fidoaddr_t dest, uint16_t attr)
rswindell's avatar
rswindell committed
775
776
777
778
779
780
{
	nodecfg_t* nodecfg;
	char ch='f';
	const char* outbound;
	static char filename[MAX_PATH+1];

781
782
783
784
785
	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
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
		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);

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

804
805
806
807
808
/******************************************************************************
 This function creates or appends on existing Binkley compatible .?LO file
 attach file.
 Returns 0 on success.
******************************************************************************/
809
int write_flofile(const char *infile, fidoaddr_t dest, bool bundle, bool use_outbox, uint16_t attr)
810
{
rswindell's avatar
rswindell committed
811
812
	const char* flo_filename;
	char attachment[MAX_PATH+1];
813
	char searchstr[MAX_PATH+1];
rswindell's avatar
rswindell committed
814
815
	FILE *fp;
	nodecfg_t* nodecfg;
816

rswindell's avatar
rswindell committed
817
818
819
820
	if(use_outbox && (nodecfg=findnodecfg(&cfg, dest, /* exact: */false)) != NULL) {
		if(nodecfg->outbox[0])
			return 0;
	}
821
822
823
824

	if(!bso_lock_node(dest))
		return 1;

825
	flo_filename = bso_flo_filename(dest, attr);
rswindell's avatar
rswindell committed
826
827
828
829
	SAFECOPY(attachment, infile);
	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;
830
	}
rswindell's avatar
rswindell committed
831
832
833
834
835
836
837
838
839
840
841
842
	SAFEPRINTF2(searchstr,"%c%s",bundle && (cfg.trunc_bundles) ? '#':'^', attachment);
	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);
		return -1; 
	}
	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;
843
844
}

845
/* Writes text buffer to file, expanding sole LFs to CRLFs */
rswindell's avatar
rswindell committed
846
size_t fwrite_crlf(const char* buf, size_t len, FILE* fp)
847
848
849
850
851
852
853
854
{
	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++;
		if(ch=='\n' && last_ch!='\r') {
rswindell's avatar
rswindell committed
855
856
			if(fputc('\r', fp) == EOF)
				break;
857
858
859
			wr++;
		}
		if(fputc(ch,fp)==EOF)
rswindell's avatar
rswindell committed
860
			break;
861
862
863
864
865
866
867
		wr++;
		last_ch=ch;
	}

	return(wr);
}

rswindell's avatar
rswindell committed
868
bool fidoctrl_line_exists(const smbmsg_t* msg, const char* prefix)
869
{
870
	if(msg==NULL || prefix==NULL)
rswindell's avatar
rswindell committed
871
		return false;
872
873
874
	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
875
			return true;
876
	}
rswindell's avatar
rswindell committed
877
	return false;
878
879
}

rswindell's avatar
rswindell committed
880
fidoaddr_t fmsghdr_srcaddr(const fmsghdr_t* hdr)
881
882
883
884
885
886
887
888
889
890
891
{
	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
892
const char* fmsghdr_srcaddr_str(const fmsghdr_t* hdr)
893
894
895
896
897
898
899
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_srcaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

rswindell's avatar
rswindell committed
900
fidoaddr_t fmsghdr_destaddr(const fmsghdr_t* hdr)
901
902
903
904
905
906
907
908
909
910
911
{
	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
912
const char* fmsghdr_destaddr_str(const fmsghdr_t* hdr)
913
914
915
916
917
918
919
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_destaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
bool parse_origin(const char* fmsgbuf, fmsghdr_t* hdr)
{
	char* p;
	fidoaddr_t origaddr;
	
	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
936
	if(origaddr.zone == 0 || faddr_contains_wildcard(&origaddr))
937
938
939
940
941
942
943
944
		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
945
946
947
948
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
949
	enum pkt_type type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
950
951
952
953
954
955
956
957
958
959
960
961
962
963

	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
964
965
966
967
	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
968
		dest.point = hdr->type2plus.destpoint;
rswindell's avatar
rswindell committed
969
970
971
972
973
974
		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
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
		}
	} 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;
		dest.point = hdr->type2_2.destpoint; 
	}

	if(pkt_type != NULL)
		*pkt_type = type;
	if(dest_addr != NULL)
		*dest_addr = dest;
	if(orig_addr != NULL)
		*orig_addr = orig;
	
	return true;
}

bool new_pkthdr(fpkthdr_t* hdr, fidoaddr_t orig, fidoaddr_t dest, const nodecfg_t* nodecfg)
{
rswindell's avatar
rswindell committed
994
	enum pkt_type pkt_type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
	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;
	
	hdr->type2.pkttype	= 2;
	hdr->type2.prodcode	= SBBSECHO_PRODUCT_CODE&0xff;
	hdr->type2.sernum	= SBBSECHO_VERSION_MAJOR;

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

rswindell's avatar
rswindell committed
1026
	if(pkt_type == PKT_TYPE_2)
rswindell's avatar
rswindell committed
1027
1028
1029
1030
		return true;

	if(pkt_type == PKT_TYPE_2_2) {
		hdr->type2_2.subversion = 2;	/* 2.2 */
1031
1032
		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
1033
1034
1035
		return true;
	}
	
rswindell's avatar
rswindell committed
1036
1037
	/* 2e and 2+ */
	if(pkt_type != PKT_TYPE_2_EXT && pkt_type != PKT_TYPE_2_PLUS) {
rswindell's avatar
rswindell committed
1038
1039
1040
1041
		lprintf(LOG_ERR, "UNSUPPORTED PACKET TYPE: %u", pkt_type);
		return false;
	}

rswindell's avatar
rswindell committed
1042
1043
1044
1045
	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
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
	}
	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;
}

1059
/******************************************************************************
rswindell's avatar
rswindell committed
1060
 This function will create a netmail message (FTS-1 "stored message" format).
1061
1062
1063
 If file is non-zero, will set file attachment bit (for bundles).
 Returns 0 on success.
******************************************************************************/
rswindell's avatar
rswindell committed
1064
1065
int create_netmail(const char *to, const smbmsg_t* msg, const char *subject, const char *body, fidoaddr_t dest
					,bool file_attached)
1066
{
1067
	FILE *fp;
1068
	char tmp[256];
1069
	char fname[MAX_PATH+1];
1070
	char* from=NULL;
1071
1072
	uint i;
	static uint startmsg;
rswindell's avatar
rswindell committed
1073
	fidoaddr_t	faddr;
1074
	fmsghdr_t hdr;
1075
	time_t t;
1076
	struct tm *tm;
1077
	when_t when_written;
rswindell's avatar
rswindell committed
1078
1079
	nodecfg_t* nodecfg;
	bool	direct=false;
1080

1081
	if(msg==NULL) {
rswindell's avatar
rswindell committed
1082
		when_written.time = time32(NULL);
1083
1084
1085
1086
1087
		when_written.zone = sys_timezone(&scfg);
	} else {
		from = msg->from;
		when_written = msg->hdr.when_written;
	}
rswindell's avatar
rswindell committed
1088
	if(from==NULL || *from==0) {
1089
		from="SBBSecho";
rswindell's avatar
rswindell committed
1090
1091
1092
1093
1094
1095
1096
		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)
1097
		to="Sysop";
1098
	if(!startmsg) startmsg=1;
rswindell's avatar
rswindell committed
1099
1100
1101
	if((nodecfg=findnodecfg(&cfg, dest, 0)) != NULL) {
		if(nodecfg->status == MAIL_STATUS_NORMAL && !nodecfg->direct)
			nodecfg=findnodecfg(&cfg, dest, /* skip exact match: */2);
1102
	}
1103

1104
	MKDIR(scfg.netmail_dir);
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
	for(i=startmsg;i;i++) {
		sprintf(fname,"%s%u.msg",scfg.netmail_dir,i);
		if(!fexistcase(fname))
			break; 
	}
	if(!i) {
		lprintf(LOG_WARNING,"Directory full: %s",scfg.netmail_dir);
		return(-1); 
	}
	startmsg=i+1;
1115
	if((fp=fnopen(NULL,fname,O_RDWR|O_CREAT))==NULL) {
1116
1117
1118
		lprintf(LOG_ERR,"ERROR %u (%s) line %d opening %s",errno,strerror(errno),__LINE__,fname);
		return(-1); 
	}
1119

1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
	faddr=getsysfaddr(dest.zone);
	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);
	if(file_attached)
		hdr.attr|=FIDO_FILE;

rswindell's avatar
rswindell committed
1135
1136
1137
1138
	if(nodecfg != NULL) {
		switch(nodecfg->status) {
			case MAIL_STATUS_HOLD:	hdr.attr|=FIDO_HOLD;	break;
			case MAIL_STATUS_CRASH:	hdr.attr|=FIDO_CRASH;	break;
1139
			case MAIL_STATUS_NORMAL:						break;
rswindell's avatar
rswindell committed
1140
1141
1142
		}
		direct = nodecfg->direct;
	}
1143

1144
	t = when_written.time;
rswindell's avatar
rswindell committed
1145
	tm = localtime(&t);
1146
1147
1148
1149
1150
1151
1152
1153
1154
	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);

	fwrite(&hdr,sizeof(fmsghdr_t),1,fp);
1155
	fwrite_intl_control_line(fp, &hdr);
1156

1157
1158
1159
1160
1161
1162
1163
1164
1165
	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);
1166
	}
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
	if(!cfg.flo_mailer) {
		/* Add FSC-53 FLAGS kludge */
		fprintf(fp,"\1FLAGS");
		if(direct)
			fprintf(fp," DIR");
		if(file_attached) {
			if(cfg.trunc_bundles)
				fprintf(fp," TFS");
			else
				fprintf(fp," KFS");
		}
		fprintf(fp,"\r");
1179
	}
1180

1181
1182
1183
1184
	if(hdr.destpoint)
		fprintf(fp,"\1TOPT %hu\r",hdr.destpoint);
	if(hdr.origpoint)
		fprintf(fp,"\1FMPT %hu\r",hdr.origpoint);
1185
1186
1187
1188
1189
1190
	fprintf(fp,"\1PID: %s\r", (msg==NULL || msg->ftn_pid==NULL) ? sbbsecho_pid() : msg->ftn_pid);
	if(msg != NULL) {
		/* 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]);
1191
1192
1193
1194
1195
1196
		/* comment headers are part of text */
		for(i=0; i<msg->total_hfields; i++)
			if(msg->hfield[i].type == SMB_COMMENT)
				fprintf(fp, "%s\r", (char*)msg->hfield_dat[i]);
		if(subject != msg->subj)
			fprintf(fp, "Subject: %s\r\r", msg->subj);
1197
	}
rswindell's avatar
rswindell committed
1198
	/* Write the body text */
1199
1200
	if(body != NULL) {
		int bodylen = strlen(body);
rswindell's avatar
rswindell committed
1201
		fwrite_crlf(body, bodylen, fp);
1202
1203
1204
1205
1206
		/* Write the tear line */
		if(bodylen > 0 && body[bodylen-1] != '\r' && body[bodylen-1] != '\n')
			fputc('\r', fp);
		fprintf(fp, "%s", tear_line());
	}
rswindell's avatar
rswindell committed
1207
	fputc(FIDO_STORED_MSG_TERMINATOR, fp);
rswindell's avatar
rswindell committed
1208
1209
1210
	lprintf(LOG_INFO, "Created NetMail (%s)%s from %s (%s) to %s (%s), attr: %04hX, subject: %s"
		,getfname(fname), file_attached ? " with attachment" : ""
		,from, smb_faddrtoa(&faddr, tmp), to, smb_faddrtoa(&dest, NULL), hdr.attr, subject);
1211
	return fclose(fp);
1212
1213
1214
}

/******************************************************************************
1215
1216
 This function takes the contents of 'infile' and puts it into netmail
 message(s) bound for addr.
1217
******************************************************************************/
1218
int file_to_netmail(FILE* infile, const char* title, fidoaddr_t dest, const char* to)
1219
1220
1221
{
	char *buf,*p;
	long l,m,len;
1222
	int netmails_created = 0;
1223

1224
	fseek(infile, 0, SEEK_END);
1225
1226
1227
1228
	l=len=ftell(infile);
	if(len>8192L)
		len=8192L;
	rewind(infile);
deuce's avatar
deuce committed
1229
	if((buf=(char *)malloc(len+1))==NULL) {
1230
		lprintf(LOG_ERR,"ERROR line %d allocating %lu for file to netmail buf",__LINE__,len);
1231
		return 0; 
1232
	}
1233
1234
1235
	while((m=fread(buf,1,(len>8064L) ? 8064L:len,infile))>0) {
		buf[m]=0;
		if(l>8064L && (p=strrchr(buf,'\n'))!=NULL) {
1236
			p++;
1237
1238
			if(*p) {
				*p=0;
1239
				p++;
1240
1241
1242
				fseek(infile,-1L,SEEK_CUR);
				while(*p) { 			/* Seek back to end of last line */
					p++;
1243
1244
1245
1246
					fseek(infile,-1L,SEEK_CUR); 
				} 
			} 
		}
1247
1248
		if(ftell(infile)<l)
			strcat(buf,"\r\nContinued in next message...\r\n");
1249
1250
		if(create_netmail(to, /* msg: */NULL, title, buf, dest, /* attachment: */false) == 0)
			netmails_created++;
1251
	}
deuce's avatar
deuce committed
1252
	free(buf);
1253
	return netmails_created;
1254
}
1255

1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
bool new_area(const char* tag, uint subnum, fidoaddr_t* link)
{
	area_t* area_list;
	if((area_list = realloc(cfg.area, sizeof(area_t) * (cfg.areas+1))) == NULL)
		return false;
	cfg.area = area_list;
	memset(&cfg.area[cfg.areas], 0 ,sizeof(area_t));
	cfg.area[cfg.areas].sub = subnum;
	if(tag != NULL) {
		if((cfg.area[cfg.areas].tag = strdup(tag)) == NULL)
			return false;
	}
	if(link != NULL) {
		if((cfg.area[cfg.areas].link = malloc(sizeof(fidoaddr_t))) == NULL)
			return false;
		cfg.area[cfg.areas].link[0] = *link;
		cfg.area[cfg.areas].links++;
	}
	cfg.areas++;

	return true;
}

rswindell's avatar
rswindell committed
1279
1280
/* Returns true if area is linked with specified node address */
bool area_is_linked(unsigned area_num, const fidoaddr_t* addr)
1281
{
1282
	unsigned i;
rswindell's avatar
rswindell committed
1283
1284
1285
1286
	for(i=0;i<cfg.area[area_num].links;i++)
		if(!memcmp(addr,&cfg.area[area_num].link[i],sizeof(fidoaddr_t)))
			return true;
	return false;
1287
1288
}

1289
1290
/* Returns area index */
uint find_area(const char* echotag)
rswindell's avatar
rswindell committed
1291
{
1292
1293
1294
	unsigned u;

	for(u=0; u < cfg.areas; u++)
1295
		if(cfg.area[u].tag != NULL && stricmp(cfg.area[u].tag, echotag) == 0)
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
			break;

	return u;
}

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

1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
uint find_sysfaddr(faddr_t addr)
{
	unsigned u;

	for(u=0; u < scfg.total_faddrs; u++)
		if(memcmp(&scfg.faddr[u], &addr, sizeof(addr)) == 0)
			break;

	return u;
}

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

1322
1323
/* Returns subnum (INVALID_SUB if pass-through) or SUB_NOT_FOUND */
#define SUB_NOT_FOUND ((uint)-2)
1324
1325
1326
1327
1328
uint find_linked_area(const char* echotag, fidoaddr_t addr)
{
	unsigned area;

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

1331
	return SUB_NOT_FOUND;
rswindell's avatar
rswindell committed
1332
1333
}

1334
1335
1336
1337
1338
/******************************************************************************
 This function sends a notify list to applicable nodes, this list includes the
 settings configured for the node, as well as a list of areas the node is
 connected to.
******************************************************************************/
rswindell's avatar
rswindell committed
1339
void gen_notify_list(nodecfg_t* nodecfg)
1340
1341
1342
{
	FILE *	tmpf;
	char	str[256];
deuce's avatar
deuce committed
1343
	uint	i,k;
1344

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

rswindell's avatar
rswindell committed
1347
1348
1349
		if(nodecfg != NULL && &cfg.nodecfg[k] != nodecfg)
			continue;

rswindell's avatar
rswindell committed
1350
		if(!cfg.nodecfg[k].send_notify)
1351
1352
1353
			continue;

		if((tmpf=tmpfile())==NULL) {
1354
			lprintf(LOG_ERR,"ERROR line %d couldn't open tmpfile",__LINE__);
1355
1356
			return; 
		}
1357
1358
1359
1360

		fprintf(tmpf,"Following are the options set for your system and a list "
			"of areas\r\nyou are connected to.  Please make sure everything "
			"is correct.\r\n\r\n");
rswindell's avatar
rswindell committed
1361
		fprintf(tmpf,"Name              %s\r\n", cfg.nodecfg[k].name);
rswindell's avatar
rswindell committed
1362
		fprintf(tmpf,"Packet Type       %s\r\n", pktTypeStringList[cfg.nodecfg[k].pkt_type]);
1363
		fprintf(tmpf,"Archive Type      %s\r\n"
rswindell's avatar
rswindell committed
1364
1365
1366
			,cfg.nodecfg[k].archive == SBBSECHO_ARCHIVE_NONE ? "None" : cfg.nodecfg[k].archive->name);
		fprintf(tmpf,"Mail Status       %s\r\n", mailStatusStringList[cfg.nodecfg[k].status]);
		fprintf(tmpf,"Direct            %s\r\n", cfg.nodecfg[k].direct ? "Yes":"No");
1367
		fprintf(tmpf,"Passive (paused)  %s\r\n", cfg.nodecfg[k].passive ? "Yes":"No");
rswindell's avatar
rswindell committed
1368
		fprintf(tmpf,"Notifications     %s\r\n", cfg.nodecfg[k].send_notify ? "Yes":"No");
1369
		fprintf(tmpf,"Remote AreaMgr    %s\r\n\r\n"
rswindell's avatar
rswindell committed
1370
			,cfg.nodecfg[k].areafix ? "Yes" : "No");
1371
1372
1373

		fprintf(tmpf,"Connected Areas\r\n---------------\r\n");
		for(i=0;i<cfg.areas;i++) {
1374
			sprintf(str,"%s\r\n",cfg.area[i].tag);
1375
1376
			if(str[0]=='*')
				continue;
rswindell's avatar
rswindell committed
1377
			if(area_is_linked(i,&cfg.nodecfg[k].addr))
1378
1379
				fprintf(tmpf,"%s",str); 
		}
1380
1381

		if(ftell(tmpf))
rswindell's avatar
rswindell committed
1382
			file_to_netmail(tmpf,"SBBSecho Notify List",cfg.nodecfg[k].addr, /* To: */cfg.nodecfg[k].name);
1383
1384
		fclose(tmpf); 
	}
1385
}
1386

1387
1388
1389
1390
/******************************************************************************
 This function creates a netmail to addr showing a list of available areas (0),
 a list of connected areas (1), or a list of removed areas (2).
******************************************************************************/
1391
1392
1393
1394
1395
enum arealist_type {
	 AREALIST_ALL			// %LIST
	,AREALIST_CONNECTED		// %QUERY
	,AREALIST_UNLINKED		// %UNLINKED
};
rswindell's avatar
rswindell committed
1396
void netmail_arealist(enum arealist_type type, fidoaddr_t addr, const char* to)
1397
{
1398
	char str[256],title[128],match,*p,*tp;
1399
	unsigned k,x;
1400
	unsigned u;
1401
	str_list_t	area_list;
1402

1403
	if(type == AREALIST_ALL)
1404
		strcpy(title,"List of Available Areas");
1405
	else if(type == AREALIST_CONNECTED)
1406
1407
1408
1409
		strcpy(title,"List of Connected Areas");
	else
		strcpy(title,"List of Unlinked Areas");

1410
1411
	if((area_list=strListInit()) == NULL) {
		lprintf(LOG_ERR,"ERROR line %d couldn't allocate string list",__LINE__);
1412
1413
		return; 
	}
1414

1415
	/* Include relevant areas from the area file (e.g. areas.bbs): */
1416
	for(u=0;u<cfg.areas;u++) {
rswindell's avatar
rswindell committed
1417
1418
		if(u == cfg.badecho)
			continue;
1419
		if((type == AREALIST_CONNECTED || cfg.add_from_echolists_only) && !area_is_linked(u,&addr))
1420
			continue;
1421
		if(type == AREALIST_UNLINKED && area_is_linked(u,&addr))
1422
			continue;
1423
		strListPush(&area_list, cfg.area[u].tag); 
1424
	} 
1425

1426
	if(type != AREALIST_CONNECTED) {
rswindell's avatar
rswindell committed
1427
1428
		nodecfg_t* nodecfg=findnodecfg(&cfg, addr,0);
		if(nodecfg != NULL) {
1429
			for(u=0;u<cfg.listcfgs;u++) {
1430
				match=0;
1431
				for(k=0; cfg.listcfg[u].keys[k]; k++) {
1432
					if(match) break;
rswindell's avatar
rswindell committed
1433
					for(x=0; nodecfg->keys[x]; x++) {
1434
						if(!stricmp(cfg.listcfg[u].keys[k]
rswindell's avatar
rswindell committed
1435
							,nodecfg->keys[x])) {
1436
							FILE* fp;
1437
							if((fp=fopen(cfg.listcfg[u].listpath,"r"))==NULL) {
1438
								lprintf(LOG_ERR,"ERROR %u (%s) line %d opening %s"
1439
									,errno,strerror(errno),__LINE__,cfg.listcfg[u].listpath);
1440
								match=1;
1441
1442
								break; 
							}
1443
							while(!feof(fp)) {
1444
								memset(str,0,sizeof(str));
1445
								if(!fgets(str,sizeof(str),fp))
1446
									break;
1447
								truncsp(str);
1448
								p=str;
1449
								SKIP_WHITESPACE(p);
1450
								if(*p==0 || *p==';')     /* Ignore Blank and Comment Lines */
1451
									continue;
1452
1453
1454
								tp=p;
								FIND_WHITESPACE(tp);
								*tp=0;
1455
								if(find_linked_area(p, addr) == SUB_NOT_FOUND) {
rswindell's avatar