sbbsecho.c 178 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
113
void export_echomail(const char *sub_code, const nodecfg_t*, bool rescan);

114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/* 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;
}

135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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;
}

165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/* 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);
}

193
194
195
196
197
198
199
200

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

201
202
203
204
205
206
207
208
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];
209
210
	char msg_tz[128];
	time_t msg_time;
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
	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];
236
	bool known;	// listed in Area File
237
238
239
240
241
242
243
244
	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;

245
246
int echostat_compare(const void* c1, const void* c2)
{
247
248
	echostat_t* stat1 = (echostat_t*)c1;
	echostat_t* stat2 = (echostat_t*)c2;
249
250
251
252

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

253
254
255
256
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
257
	echostat_msg_t msg = {{0}};
258
259
260
261
262
263
264
265

	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);
266
267
	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);
268
269
270
271
272
273
274
275
276
277
278
279
280
	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
281
	echostat_msg_t msg = {{0}};
282
283
284
285

	SAFECOPY(msg.to		, fmsghdr.to);
	SAFECOPY(msg.from	, fmsghdr.from);
	SAFECOPY(msg.subj	, fmsghdr.subj);
286
	msg.msg_time		= fmsgtime(fmsghdr.time);
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
	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);
	}
310
311
	if((p = parse_control_line(fmsgbuf, "TZUTC:")) != NULL
		|| (p = parse_control_line(fmsgbuf, "TZUTCINFO:")) != NULL) {
312
313
314
		SAFECOPY(msg.msg_tz, p);
		free(p);
	}
315
316
317
318
319
320
	if(fmsgbuf != NULL)
		msg.length = strlen(fmsgbuf);

	return msg;
}

321
echostat_msg_t smsg_to_echostat_msg(smbmsg_t smsg, size_t msglen, fidoaddr_t addr)
322
323
{
	char* p;
rswindell's avatar
rswindell committed
324
	echostat_msg_t emsg = {{0}};
325
326
327
328

	SAFECOPY(emsg.to	, smsg.to);
	SAFECOPY(emsg.from	, smsg.from);
	SAFECOPY(emsg.subj	, smsg.subj);
329
	emsg.msg_time		= smsg.hdr.when_written.time;
330
	emsg.localtime		= time(NULL);
331
	SAFECOPY(emsg.msg_tz, smb_zonestr(smsg.hdr.when_written.zone, NULL));
332
333
334
335
336
337
338
339
340
341
	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);
342
343
	else
		SAFECOPY(emsg.tid, sbbsecho_pid());
344
	emsg.length = msglen;
345
	emsg.pkt_orig = addr;
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383

	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]);
384
385
386
		str_list_t keys = iniGetSection(ini, stat->tag);
		if(keys == NULL)
			continue;
387
388
389
		for(int type = 0; type < ECHOSTAT_MSG_TYPES; type++) {
			char prefix[32];
			sprintf(prefix, "First%s", echostat_msg_type[type])
390
				,stat->first[type]	= parse_echostat_msg(keys, NULL, prefix);
391
			sprintf(prefix, "Last%s", echostat_msg_type[type])
392
				,stat->last[type]	= parse_echostat_msg(keys, NULL, prefix);
393
			sprintf(prefix, "Total%s", echostat_msg_type[type])
394
				,stat->total[type] = iniGetLongInt(keys, NULL, prefix, 0);
395
		}
396
397
		stat->known = iniGetBool(keys, NULL, "Known", false);
		iniFreeStringList(keys);
398
399
400
401
	}
	iniFreeStringList(echoes);
	iniFreeStringList(ini);

402
	lprintf(LOG_DEBUG, "Read %u echo statistics from %s", echo_count, fname);
403
404
405
406
407
408
409
410
411
412
413
414
415
	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)
416
		sprintf(tstr, "0x%lx", (ulong)t);
417
418
419
420
421
422
423
424
	else
		sprintf(tstr, "%.3s %.2s %.4s %.8s", p+4, p+8, p+20, p+11);
	
	return tstr;
}

void write_echostat_msg(FILE* fp, echostat_msg_t msg, const char* prefix)
{
rswindell's avatar
rswindell committed
425
	echostat_msg_t zero = {{0}};
426
427
428
429
430
431
432
433
434
435
	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);
436
437
	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);
438
	fprintf(fp, "%s.localtime = %s\n"						, prefix, iniTimeStr(msg.localtime));
439
440
	if(msg.origaddr.zone)
		fprintf(fp, "%s.origaddr = %s\n"					, prefix, faddrtoa(&msg.origaddr));
441
442
443
444
445
446
447
	fprintf(fp, "%s.pkt_orig = %s\n"						, prefix, faddrtoa(&msg.pkt_orig));
}

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

448
449
	qsort(echostat, echo_count, sizeof(echostat_t), echostat_compare);

450
451
452
453
454
455
	if((fp=fopen(fname, "w"))==NULL)
		return false;

	for(unsigned i = 0; i < echo_count; i++) {
		echostat_t* stat = &echostat[i];
		fprintf(fp, "[%s]\n"						, stat->tag);
456
		fprintf(fp,	"Known = %s\n"					, stat->known ? "true" : "false");
457
458
459
460
		for(int type = 0; type < ECHOSTAT_MSG_TYPES; type++) {
			char prefix[32];
			sprintf(prefix, "First%s", echostat_msg_type[type])	, write_echostat_msg(fp, stat->first[type], prefix);
			sprintf(prefix, "Last%s", echostat_msg_type[type])	, write_echostat_msg(fp, stat->last[type], prefix);
461
462
			if(stat->total[type] != 0)
				fprintf(fp,	"Total%s = %lu\n"					, echostat_msg_type[type], stat->total[type]);
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
		}
		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
486
487
488
489
/**********************/
/* Log print function */
/**********************/
int lprintf(int level, char *fmt, ...)
rswindell's avatar
rswindell committed
490
491
{
	va_list argptr;
rswindell's avatar
rswindell committed
492
	char sbuf[1024];
rswindell's avatar
rswindell committed
493
494
	int chcount;

rswindell's avatar
rswindell committed
495
496
	va_start(argptr,fmt);
	chcount=vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
497
	sbuf[sizeof(sbuf)-1]=0;
rswindell's avatar
rswindell committed
498
	va_end(argptr);
499
500
	truncsp(sbuf);
	printf("%s\n",sbuf);
501

rswindell's avatar
rswindell committed
502
503
504
505
	if(fidologfile!=NULL && level<=cfg.log_level) {
	    time_t now = time(NULL);
		struct tm *tm;
		struct tm tmbuf = {0};
506
		char timestamp[128];
rswindell's avatar
rswindell committed
507
508
509
		strip_ctrl(sbuf, sbuf);
		if((tm = localtime(&now)) == NULL)
			tm = &tmbuf;
510
511
512
513
514
		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
515
516
		fflush(fidologfile);
	}
rswindell's avatar
rswindell committed
517
518
519
	return(chcount);
}

rswindell's avatar
rswindell committed
520
bool delfile(const char *filename, int line)
521
{
rswindell's avatar
rswindell committed
522
	lprintf(LOG_DEBUG, "Deleting %s (from line %u)", filename, line);
523
	if(remove(filename) != 0) {
524
525
		lprintf(LOG_ERR, "ERROR %u (%s) line %u removing file %s"
			,errno, strerror(errno), line, filename);
rswindell's avatar
rswindell committed
526
		return false;
527
	}
rswindell's avatar
rswindell committed
528
	return true;
529
530
}

531
532
533
/*****************************************************************************/
/* Returns command line generated from instr with %c replacments             */
/*****************************************************************************/
rswindell's avatar
rswindell committed
534
char *mycmdstr(scfg_t* cfg, const char *instr, const char *fpath, const char *fspec)
535
{
536
    static char cmd[MAX_PATH+1];
537
538
539
    char str[256],str2[128];
    int i,j,len;

540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
	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);
571
572
						strcat(cmd,str); 
					}
573
574
575
576
577
578
579
580
581
582
583
584
					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);
585
586
						strcat(cmd,str); 
					}
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
					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 */
603
					strcat(cmd,cfg->exec_dir);
604
					break;
605
606
607
608
609
                case '@':   /* EXEC Directory for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
                    strcat(cmd,cfg->exec_dir);
#endif
                    break;
610
611
612
613
614
615
616
617
618
619
620
				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;
621
622
623
624
625
626
627
628
629
630
				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;
631
				default:    /* unknown specification */
632
					lprintf(LOG_ERR,"ERROR Checking Command Line '%s'",instr);
633
					bail(1);
634
635
636
637
					break; 
			}
			j=strlen(cmd); 
		}
638
		else
639
640
			cmd[j++]=instr[i]; 
}
641
	cmd[j]=0;
642

643
	return(cmd);
644
645
646
}

/****************************************************************************/
rswindell's avatar
rswindell committed
647
/* Runs an external program directly using system()							*/
648
649
650
/****************************************************************************/
int execute(char *cmdline)
{
rswindell's avatar
rswindell committed
651
652
653
654
655
656
	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));
657

rswindell's avatar
rswindell committed
658
	return retval;
659
}
660

661
662
663
/******************************************************************************
 Returns the system address with the same zone as the address passed
******************************************************************************/
rswindell's avatar
rswindell committed
664
fidoaddr_t getsysfaddr(short zone)
665
666
{
	int i;
667

668
669
670
	for(i=0;i<scfg.total_faddrs;i++)
		if(scfg.faddr[i].zone==zone)
			return(scfg.faddr[i]);
671
	return(sys_faddr);
672
}
673

rswindell's avatar
rswindell committed
674
int get_outbound(fidoaddr_t dest, char* outbound, size_t maxlen, bool fileboxes)
675
{
rswindell's avatar
rswindell committed
676
677
	nodecfg_t*	nodecfg;

678
	strncpy(outbound,zone_root_outbound(dest.zone),maxlen);
rswindell's avatar
rswindell committed
679
680
681
682
683
684
	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/" */
685
686
687
			char* p = lastchar(outbound);
			if(IS_PATH_DELIM(*p))
				*p = 0;
rswindell's avatar
rswindell committed
688
689
690
691
692
693
			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);
		}
694
695
	}
	backslash(outbound);
696
697
698
	if(isdir(outbound))
		return 0;
	lprintf(LOG_DEBUG, "Creating outbound directory for %s: %s", smb_faddrtoa(&dest, NULL), outbound);
699
700
701
	return mkpath(outbound);
}

rswindell's avatar
rswindell committed
702
703
704
705
706
707
708
709
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)
710
{
rswindell's avatar
rswindell committed
711
	const char* outbound;
712
713
	char fname[MAX_PATH+1];

rswindell's avatar
rswindell committed
714
715
	if(!cfg.flo_mailer)
		return true;
716

rswindell's avatar
rswindell committed
717
	outbound = get_current_outbound(dest, /* fileboxes: */false);
718
719
720
721
722
723

	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
724
725
726
727
728
	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;
729
		lprintf(LOG_NOTICE, "Node (%s) externally locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
730
731
732
733
734
735
736
		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);
737
738
739
	}
	strListPush(&locked_bso_nodes, fname);
	lprintf(LOG_DEBUG, "Node (%s) successfully locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
740
741
742
	return true;
}

743
const char* bso_flo_filename(fidoaddr_t dest, uint16_t attr)
rswindell's avatar
rswindell committed
744
745
746
747
748
749
{
	nodecfg_t* nodecfg;
	char ch='f';
	const char* outbound;
	static char filename[MAX_PATH+1];

750
751
752
753
754
	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
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
		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;
}

773
774
775
776
777
/******************************************************************************
 This function creates or appends on existing Binkley compatible .?LO file
 attach file.
 Returns 0 on success.
******************************************************************************/
778
int write_flofile(const char *infile, fidoaddr_t dest, bool bundle, bool use_outbox, uint16_t attr)
779
{
rswindell's avatar
rswindell committed
780
781
	const char* flo_filename;
	char attachment[MAX_PATH+1];
782
	char searchstr[MAX_PATH+1];
rswindell's avatar
rswindell committed
783
784
	FILE *fp;
	nodecfg_t* nodecfg;
785

rswindell's avatar
rswindell committed
786
787
788
789
	if(use_outbox && (nodecfg=findnodecfg(&cfg, dest, /* exact: */false)) != NULL) {
		if(nodecfg->outbox[0])
			return 0;
	}
790
791
792
793

	if(!bso_lock_node(dest))
		return 1;

794
	flo_filename = bso_flo_filename(dest, attr);
rswindell's avatar
rswindell committed
795
796
797
798
	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;
799
	}
rswindell's avatar
rswindell committed
800
801
802
803
804
805
806
807
808
809
810
811
	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;
812
813
}

814
/* Writes text buffer to file, expanding sole LFs to CRLFs */
rswindell's avatar
rswindell committed
815
size_t fwrite_crlf(const char* buf, size_t len, FILE* fp)
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
{
	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') {
			if(fputc('\r',fp)==EOF)
				return(wr);
			wr++;
		}
		if(fputc(ch,fp)==EOF)
			return(wr);
		wr++;
		last_ch=ch;
	}

	return(wr);
}

rswindell's avatar
rswindell committed
837
bool fidoctrl_line_exists(const smbmsg_t* msg, const char* prefix)
838
{
839
	if(msg==NULL || prefix==NULL)
rswindell's avatar
rswindell committed
840
		return false;
841
842
843
	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
844
			return true;
845
	}
rswindell's avatar
rswindell committed
846
	return false;
847
848
}

rswindell's avatar
rswindell committed
849
fidoaddr_t fmsghdr_srcaddr(const fmsghdr_t* hdr)
850
851
852
853
854
855
856
857
858
859
860
{
	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
861
const char* fmsghdr_srcaddr_str(const fmsghdr_t* hdr)
862
863
864
865
866
867
868
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_srcaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

rswindell's avatar
rswindell committed
869
fidoaddr_t fmsghdr_destaddr(const fmsghdr_t* hdr)
870
871
872
873
874
875
876
877
878
879
880
{
	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
881
const char* fmsghdr_destaddr_str(const fmsghdr_t* hdr)
882
883
884
885
886
887
888
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_destaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
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
905
	if(origaddr.zone == 0 || faddr_contains_wildcard(&origaddr))
906
907
908
909
910
911
912
913
		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
914
915
916
917
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
918
	enum pkt_type type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
919
920
921
922
923
924
925
926
927
928
929
930
931
932

	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
933
934
935
936
	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
937
		dest.point = hdr->type2plus.destpoint;
rswindell's avatar
rswindell committed
938
939
940
941
942
943
		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
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
		}
	} 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
963
	enum pkt_type pkt_type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
	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)
993
		strncpy((char*)hdr->type2.password, nodecfg->pktpwd, sizeof(hdr->type2.password));
rswindell's avatar
rswindell committed
994

rswindell's avatar
rswindell committed
995
	if(pkt_type == PKT_TYPE_2)
rswindell's avatar
rswindell committed
996
997
998
999
		return true;

	if(pkt_type == PKT_TYPE_2_2) {
		hdr->type2_2.subversion = 2;	/* 2.2 */
1000
1001
		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
1002
1003
1004
		return true;
	}
	
rswindell's avatar
rswindell committed
1005
1006
	/* 2e and 2+ */
	if(pkt_type != PKT_TYPE_2_EXT && pkt_type != PKT_TYPE_2_PLUS) {
rswindell's avatar
rswindell committed
1007
1008
1009
1010
		lprintf(LOG_ERR, "UNSUPPORTED PACKET TYPE: %u", pkt_type);
		return false;
	}

rswindell's avatar
rswindell committed
1011
1012
1013
1014
	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
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
	}
	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;
}

1028
1029
1030
1031
1032
/******************************************************************************
 This function will create a netmail message (.MSG format).
 If file is non-zero, will set file attachment bit (for bundles).
 Returns 0 on success.
******************************************************************************/
rswindell's avatar
rswindell committed
1033
1034
int create_netmail(const char *to, const smbmsg_t* msg, const char *subject, const char *body, fidoaddr_t dest
					,bool file_attached)
1035
{
1036
	FILE *fp;
1037
	char tmp[256];
1038
	char fname[MAX_PATH+1];
1039
	char* from=NULL;
1040
1041
	uint i;
	static uint startmsg;
rswindell's avatar
rswindell committed
1042
	fidoaddr_t	faddr;
1043
	fmsghdr_t hdr;
1044
	time_t t;
1045
	struct tm *tm;
1046
	when_t when_written;
rswindell's avatar
rswindell committed
1047
1048
	nodecfg_t* nodecfg;
	bool	direct=false;
1049

1050
	if(msg==NULL) {
rswindell's avatar
rswindell committed
1051
		when_written.time = time32(NULL);
1052
1053
1054
1055
1056
		when_written.zone = sys_timezone(&scfg);
	} else {
		from = msg->from;
		when_written = msg->hdr.when_written;
	}
1057
1058
1059
1060
	if(from==NULL)
		from="SBBSecho";
	if(to==NULL)
		to="Sysop";
1061
	if(!startmsg) startmsg=1;
rswindell's avatar
rswindell committed
1062
1063
1064
	if((nodecfg=findnodecfg(&cfg, dest, 0)) != NULL) {
		if(nodecfg->status == MAIL_STATUS_NORMAL && !nodecfg->direct)
			nodecfg=findnodecfg(&cfg, dest, /* skip exact match: */2);
1065
	}
1066

1067
	MKDIR(scfg.netmail_dir);
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
	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;
1078
	if((fp=fnopen(NULL,fname,O_RDWR|O_CREAT))==NULL) {
1079
1080
1081
		lprintf(LOG_ERR,"ERROR %u (%s) line %d opening %s",errno,strerror(errno),__LINE__,fname);
		return(-1); 
	}
1082

1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
	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
1098
1099
1100
1101
	if(nodecfg != NULL) {
		switch(nodecfg->status) {
			case MAIL_STATUS_HOLD:	hdr.attr|=FIDO_HOLD;	break;
			case MAIL_STATUS_CRASH:	hdr.attr|=FIDO_CRASH;	break;
1102
			case MAIL_STATUS_NORMAL:						break;
rswindell's avatar
rswindell committed
1103
1104
1105
		}
		direct = nodecfg->direct;
	}
1106

1107
	t = when_written.time;
rswindell's avatar
rswindell committed
1108
	tm = localtime(&t);
1109
1110
1111
1112
1113
1114
1115
1116
1117
	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);
1118
	fwrite_intl_control_line(fp, &hdr);
1119

1120
1121
1122
1123
1124
1125
1126
1127
1128
	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);
1129
	}
1130
1131
	/* Add FSC-53 FLAGS kludge */
	fprintf(fp,"\1FLAGS");
rswindell's avatar
rswindell committed
1132
	if(direct)
1133
1134
		fprintf(fp," DIR");
	if(file_attached) {
rswindell's avatar
rswindell committed
1135
		if(cfg.trunc_bundles)
1136
			fprintf(fp," TFS");
1137
		else
1138
1139
1140
			fprintf(fp," KFS");
	}
	fprintf(fp,"\r");
1141

1142
1143
1144
1145
	if(hdr.destpoint)
		fprintf(fp,"\1TOPT %hu\r",hdr.destpoint);
	if(hdr.origpoint)
		fprintf(fp,"\1FMPT %hu\r",hdr.origpoint);
1146
1147
1148
1149
1150
1151
1152
	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]);
	}
rswindell's avatar
rswindell committed
1153
	if(!file_attached || (!direct && file_attached))
1154
1155
1156
		fwrite_crlf(body,strlen(body)+1,fp);	/* Write additional NULL */
	else
		fwrite("\0",1,1,fp);               /* Write NULL */
rswindell's avatar
rswindell committed
1157
1158
1159
	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);
1160
	return fclose(fp);
1161
1162
1163
1164
1165
1166
}

/******************************************************************************
 This function takes the contents of 'infile' and puts it into a netmail
 message bound for addr.
******************************************************************************/
rswindell's avatar
rswindell committed
1167
void file_to_netmail(FILE* infile, const char* title, fidoaddr_t dest, const char* to)
1168
1169
1170
1171
{
	char *buf,*p;
	long l,m,len;

1172
1173
1174
1175
	l=len=ftell(infile);
	if(len>8192L)
		len=8192L;
	rewind(infile);
deuce's avatar
deuce committed
1176
	if((buf=(char *)malloc(len+1))==NULL) {
1177
		lprintf(LOG_ERR,"ERROR line %d allocating %lu for file to netmail buf",__LINE__,len);
1178
1179
		return; 
	}
1180
1181
1182
	while((m=fread(buf,1,(len>8064L) ? 8064L:len,infile))>0) {
		buf[m]=0;
		if(l>8064L && (p=strrchr(buf,'\n'))!=NULL) {
1183
			p++;
1184
1185
			if(*p) {
				*p=0;
1186
				p++;
1187
1188
1189
				fseek(infile,-1L,SEEK_CUR);
				while(*p) { 			/* Seek back to end of last line */
					p++;
1190
1191
1192
1193
					fseek(infile,-1L,SEEK_CUR); 
				} 
			} 
		}
1194
1195
		if(ftell(infile)<l)
			strcat(buf,"\r\nContinued in next message...\r\n");
rswindell's avatar
rswindell committed
1196
		create_netmail(to, /* msg: */NULL, title, buf, dest, /* attachment: */false); 
1197
	}
deuce's avatar
deuce committed
1198
	free(buf);
1199
}
1200

1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
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
1224
1225
/* Returns true if area is linked with specified node address */
bool area_is_linked(unsigned area_num, const fidoaddr_t* addr)
1226
{
1227
	unsigned i;
rswindell's avatar
rswindell committed
1228
1229
1230
1231
	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;
1232
1233
}

1234
1235
/* Returns area index */
uint find_area(const char* echotag)
rswindell's avatar
rswindell committed
1236
{
1237
1238
1239
	unsigned u;

	for(u=0; u < cfg.areas; u++)
1240
		if(cfg.area[u].tag != NULL && stricmp(cfg.area[u].tag, echotag) == 0)
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
			break;

	return u;
}

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

1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
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;
}

1267
1268
/* Returns subnum (INVALID_SUB if pass-through) or SUB_NOT_FOUND */
#define SUB_NOT_FOUND ((uint)-2)
1269
1270
1271
1272
1273
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))
1274
		return cfg.area[area].sub;
rswindell's avatar
rswindell committed
1275

1276
	return SUB_NOT_FOUND;
rswindell's avatar
rswindell committed
1277
1278
}

1279
1280
1281
1282
1283
/******************************************************************************
 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
1284
void gen_notify_list(void)
1285
1286
1287
{
	FILE *	tmpf;
	char	str[256];
deuce's avatar
deuce committed
1288
	uint	i,k;
1289

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

rswindell's avatar
rswindell committed
1292
		if(!cfg.nodecfg[k].send_notify)
1293
1294
1295
			continue;

		if((tmpf=tmpfile())==NULL) {