sbbsecho.c 177 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;

rswindell's avatar
rswindell committed
109
int mv(const char *insrc, const char *indest, bool copy);
110
time32_t fmsgtime(const char *str);
rswindell's avatar
rswindell committed
111
112
void export_echomail(const char *sub_code, const nodecfg_t*, bool rescan);

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

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

164
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
193
194
195
196
197
198
199
/* 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);
}

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

236
237
int echostat_compare(const void* c1, const void* c2)
{
238
239
	echostat_t* stat1 = (echostat_t*)c1;
	echostat_t* stat2 = (echostat_t*)c2;
240
241
242
243

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

244
245
246
247
248
249
250
251
252
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];
	echostat_msg_t msg = {0};

	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);
257
258
	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);
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
	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;
	echostat_msg_t msg = {0};

	SAFECOPY(msg.to		, fmsghdr.to);
	SAFECOPY(msg.from	, fmsghdr.from);
	SAFECOPY(msg.subj	, fmsghdr.subj);
277
	msg.msg_time		= fmsgtime(fmsghdr.time);
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
	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);
	}
301
302
	if((p = parse_control_line(fmsgbuf, "TZUTC:")) != NULL
		|| (p = parse_control_line(fmsgbuf, "TZUTCINFO:")) != NULL) {
303
304
305
		SAFECOPY(msg.msg_tz, p);
		free(p);
	}
306
307
308
309
310
311
	if(fmsgbuf != NULL)
		msg.length = strlen(fmsgbuf);

	return msg;
}

312
echostat_msg_t smsg_to_echostat_msg(smbmsg_t smsg, size_t msglen, fidoaddr_t addr)
313
314
315
316
317
318
319
{
	char* p;
	echostat_msg_t emsg = {0};

	SAFECOPY(emsg.to	, smsg.to);
	SAFECOPY(emsg.from	, smsg.from);
	SAFECOPY(emsg.subj	, smsg.subj);
320
	emsg.msg_time		= smsg.hdr.when_written.time;
321
	emsg.localtime		= time(NULL);
322
	SAFECOPY(emsg.msg_tz, smb_zonestr(smsg.hdr.when_written.zone, NULL));
323
324
325
326
327
328
329
330
331
332
	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);
333
334
	else
		SAFECOPY(emsg.tid, sbbsecho_pid());
335
	emsg.length = msglen;
336
	emsg.pkt_orig = addr;
337
338
339
340
341
342
343
344
345
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]);
		for(int type = 0; type < ECHOSTAT_MSG_TYPES; type++) {
			char prefix[32];
			sprintf(prefix, "First%s", echostat_msg_type[type])
				,stat->first[type]	= parse_echostat_msg(ini, stat->tag, prefix);
			sprintf(prefix, "Last%s", echostat_msg_type[type])
				,stat->last[type]	= parse_echostat_msg(ini, stat->tag, prefix);
			sprintf(prefix, "Total%s", echostat_msg_type[type])
				,stat->total[type] = iniGetLongInt(ini, stat->tag, prefix, 0);
		}
384
		stat->known = iniGetBool(ini, stat->tag, "Known", false);
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
	}
	iniFreeStringList(echoes);
	iniFreeStringList(ini);

	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)
402
		sprintf(tstr, "0x%lx", (ulong)t);
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
	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)
{
	echostat_msg_t zero = {0};
	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);
422
423
	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);
424
	fprintf(fp, "%s.localtime = %s\n"						, prefix, iniTimeStr(msg.localtime));
425
426
	if(msg.origaddr.zone)
		fprintf(fp, "%s.origaddr = %s\n"					, prefix, faddrtoa(&msg.origaddr));
427
428
429
430
431
432
433
	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;

434
435
	qsort(echostat, echo_count, sizeof(echostat_t), echostat_compare);

436
437
438
439
440
441
	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);
442
		fprintf(fp,	"Known = %s\n"					, stat->known ? "true" : "false");
443
444
445
446
		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);
447
448
			if(stat->total[type] != 0)
				fprintf(fp,	"Total%s = %lu\n"					, echostat_msg_type[type], stat->total[type]);
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
		}
		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
472
473
474
475
/**********************/
/* Log print function */
/**********************/
int lprintf(int level, char *fmt, ...)
rswindell's avatar
rswindell committed
476
477
{
	va_list argptr;
rswindell's avatar
rswindell committed
478
	char sbuf[1024];
rswindell's avatar
rswindell committed
479
480
	int chcount;

rswindell's avatar
rswindell committed
481
482
	va_start(argptr,fmt);
	chcount=vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
483
	sbuf[sizeof(sbuf)-1]=0;
rswindell's avatar
rswindell committed
484
	va_end(argptr);
485
486
	truncsp(sbuf);
	printf("%s\n",sbuf);
487

rswindell's avatar
rswindell committed
488
489
490
491
	if(fidologfile!=NULL && level<=cfg.log_level) {
	    time_t now = time(NULL);
		struct tm *tm;
		struct tm tmbuf = {0};
492
		char timestamp[128];
rswindell's avatar
rswindell committed
493
494
495
		strip_ctrl(sbuf, sbuf);
		if((tm = localtime(&now)) == NULL)
			tm = &tmbuf;
496
497
498
499
500
		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
501
502
		fflush(fidologfile);
	}
rswindell's avatar
rswindell committed
503
504
505
	return(chcount);
}

rswindell's avatar
rswindell committed
506
bool delfile(const char *filename, int line)
507
{
rswindell's avatar
rswindell committed
508
	lprintf(LOG_DEBUG, "Deleting %s (from line %u)", filename, line);
509
	if(remove(filename) != 0) {
510
511
		lprintf(LOG_ERR, "ERROR %u (%s) line %u removing file %s"
			,errno, strerror(errno), line, filename);
rswindell's avatar
rswindell committed
512
		return false;
513
	}
rswindell's avatar
rswindell committed
514
	return true;
515
516
}

517
518
519
/*****************************************************************************/
/* Returns command line generated from instr with %c replacments             */
/*****************************************************************************/
rswindell's avatar
rswindell committed
520
char *mycmdstr(scfg_t* cfg, const char *instr, const char *fpath, const char *fspec)
521
{
522
    static char cmd[MAX_PATH+1];
523
524
525
    char str[256],str2[128];
    int i,j,len;

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

629
	return(cmd);
630
631
632
}

/****************************************************************************/
rswindell's avatar
rswindell committed
633
/* Runs an external program directly using system()							*/
634
635
636
/****************************************************************************/
int execute(char *cmdline)
{
rswindell's avatar
rswindell committed
637
638
639
640
641
642
	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));
643

rswindell's avatar
rswindell committed
644
	return retval;
645
}
646

647
648
649
/******************************************************************************
 Returns the system address with the same zone as the address passed
******************************************************************************/
rswindell's avatar
rswindell committed
650
fidoaddr_t getsysfaddr(short zone)
651
652
{
	int i;
653

654
655
656
	for(i=0;i<scfg.total_faddrs;i++)
		if(scfg.faddr[i].zone==zone)
			return(scfg.faddr[i]);
657
	return(sys_faddr);
658
}
659

rswindell's avatar
rswindell committed
660
int get_outbound(fidoaddr_t dest, char* outbound, size_t maxlen, bool fileboxes)
661
{
rswindell's avatar
rswindell committed
662
663
	nodecfg_t*	nodecfg;

664
	strncpy(outbound,zone_root_outbound(dest.zone),maxlen);
rswindell's avatar
rswindell committed
665
666
667
668
669
670
	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/" */
671
672
673
			char* p = lastchar(outbound);
			if(IS_PATH_DELIM(*p))
				*p = 0;
rswindell's avatar
rswindell committed
674
675
676
677
678
679
			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);
		}
680
681
	}
	backslash(outbound);
682
683
684
	if(isdir(outbound))
		return 0;
	lprintf(LOG_DEBUG, "Creating outbound directory for %s: %s", smb_faddrtoa(&dest, NULL), outbound);
685
686
687
	return mkpath(outbound);
}

rswindell's avatar
rswindell committed
688
689
690
691
692
693
694
695
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)
696
{
rswindell's avatar
rswindell committed
697
	const char* outbound;
698
699
	char fname[MAX_PATH+1];

rswindell's avatar
rswindell committed
700
701
	if(!cfg.flo_mailer)
		return true;
702

rswindell's avatar
rswindell committed
703
	outbound = get_current_outbound(dest, /* fileboxes: */false);
704
705
706
707
708
709

	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
710
711
712
713
714
	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;
715
		lprintf(LOG_NOTICE, "Node (%s) externally locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
716
717
718
719
720
721
722
		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);
723
724
725
	}
	strListPush(&locked_bso_nodes, fname);
	lprintf(LOG_DEBUG, "Node (%s) successfully locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
	return true;
}

const char* bso_flo_filename(fidoaddr_t dest)
{
	nodecfg_t* nodecfg;
	char ch='f';
	const char* outbound;
	static char filename[MAX_PATH+1];

	if((nodecfg=findnodecfg(&cfg, dest, /* exact: */false)) != NULL) {
		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;
}

bool bso_file_attached_to_flo(const char* attachment, fidoaddr_t dest, bool bundle)
{
	char searchstr[MAX_PATH+1];
	SAFEPRINTF2(searchstr,"%c%s",(bundle && cfg.trunc_bundles) ? '#':'^', attachment);
	return findstr(searchstr, bso_flo_filename(dest));
760
761
}

762
763
764
765
766
/******************************************************************************
 This function creates or appends on existing Binkley compatible .?LO file
 attach file.
 Returns 0 on success.
******************************************************************************/
rswindell's avatar
rswindell committed
767
int write_flofile(const char *infile, fidoaddr_t dest, bool bundle, bool use_outbox)
768
{
rswindell's avatar
rswindell committed
769
770
	const char* flo_filename;
	char attachment[MAX_PATH+1];
771
	char searchstr[MAX_PATH+1];
rswindell's avatar
rswindell committed
772
773
	FILE *fp;
	nodecfg_t* nodecfg;
774

rswindell's avatar
rswindell committed
775
776
777
778
	if(use_outbox && (nodecfg=findnodecfg(&cfg, dest, /* exact: */false)) != NULL) {
		if(nodecfg->outbox[0])
			return 0;
	}
779
780
781
782

	if(!bso_lock_node(dest))
		return 1;

rswindell's avatar
rswindell committed
783
784
785
786
787
	flo_filename = bso_flo_filename(dest);
	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;
788
	}
rswindell's avatar
rswindell committed
789
790
791
792
793
794
795
796
797
798
799
800
	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;
801
802
}

803
/* Writes text buffer to file, expanding sole LFs to CRLFs */
rswindell's avatar
rswindell committed
804
size_t fwrite_crlf(const char* buf, size_t len, FILE* fp)
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
{
	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
826
bool fidoctrl_line_exists(const smbmsg_t* msg, const char* prefix)
827
{
828
	if(msg==NULL || prefix==NULL)
rswindell's avatar
rswindell committed
829
		return false;
830
831
832
	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
833
			return true;
834
	}
rswindell's avatar
rswindell committed
835
	return false;
836
837
}

rswindell's avatar
rswindell committed
838
fidoaddr_t fmsghdr_srcaddr(const fmsghdr_t* hdr)
839
840
841
842
843
844
845
846
847
848
849
{
	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
850
const char* fmsghdr_srcaddr_str(const fmsghdr_t* hdr)
851
852
853
854
855
856
857
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_srcaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

rswindell's avatar
rswindell committed
858
fidoaddr_t fmsghdr_destaddr(const fmsghdr_t* hdr)
859
860
861
862
863
864
865
866
867
868
869
{
	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
870
const char* fmsghdr_destaddr_str(const fmsghdr_t* hdr)
871
872
873
874
875
876
877
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_destaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
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
894
	if(origaddr.zone == 0 || faddr_contains_wildcard(&origaddr))
895
896
897
898
899
900
901
902
		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
903
904
905
906
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
907
	enum pkt_type type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
908
909
910
911
912
913
914
915
916
917
918
919
920
921

	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
922
923
924
925
	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
926
		dest.point = hdr->type2plus.destpoint;
rswindell's avatar
rswindell committed
927
928
929
930
931
932
		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
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
		}
	} 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
952
	enum pkt_type pkt_type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
	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)
982
		strncpy((char*)hdr->type2.password, nodecfg->pktpwd, sizeof(hdr->type2.password));
rswindell's avatar
rswindell committed
983

rswindell's avatar
rswindell committed
984
	if(pkt_type == PKT_TYPE_2)
rswindell's avatar
rswindell committed
985
986
987
988
		return true;

	if(pkt_type == PKT_TYPE_2_2) {
		hdr->type2_2.subversion = 2;	/* 2.2 */
989
990
		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
991
992
993
		return true;
	}
	
rswindell's avatar
rswindell committed
994
995
	/* 2e and 2+ */
	if(pkt_type != PKT_TYPE_2_EXT && pkt_type != PKT_TYPE_2_PLUS) {
rswindell's avatar
rswindell committed
996
997
998
999
		lprintf(LOG_ERR, "UNSUPPORTED PACKET TYPE: %u", pkt_type);
		return false;
	}

rswindell's avatar
rswindell committed
1000
1001
1002
1003
	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
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
	}
	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;
}

1017
1018
1019
1020
1021
/******************************************************************************
 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
1022
1023
int create_netmail(const char *to, const smbmsg_t* msg, const char *subject, const char *body, fidoaddr_t dest
					,bool file_attached)
1024
{
1025
	FILE *fp;
1026
	char tmp[256];
1027
	char fname[MAX_PATH+1];
1028
	char* from=NULL;
1029
1030
	uint i;
	static uint startmsg;
rswindell's avatar
rswindell committed
1031
	fidoaddr_t	faddr;
1032
	fmsghdr_t hdr;
1033
	time_t t;
1034
	struct tm *tm;
1035
	when_t when_written;
rswindell's avatar
rswindell committed
1036
1037
	nodecfg_t* nodecfg;
	bool	direct=false;
1038

1039
	if(msg==NULL) {
rswindell's avatar
rswindell committed
1040
		when_written.time = time32(NULL);
1041
1042
1043
1044
1045
		when_written.zone = sys_timezone(&scfg);
	} else {
		from = msg->from;
		when_written = msg->hdr.when_written;
	}
1046
1047
1048
1049
	if(from==NULL)
		from="SBBSecho";
	if(to==NULL)
		to="Sysop";
1050
	if(!startmsg) startmsg=1;
rswindell's avatar
rswindell committed
1051
1052
1053
	if((nodecfg=findnodecfg(&cfg, dest, 0)) != NULL) {
		if(nodecfg->status == MAIL_STATUS_NORMAL && !nodecfg->direct)
			nodecfg=findnodecfg(&cfg, dest, /* skip exact match: */2);
1054
	}
1055

1056
	MKDIR(scfg.netmail_dir);
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
	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;
1067
	if((fp=fnopen(NULL,fname,O_RDWR|O_CREAT))==NULL) {
1068
1069
1070
		lprintf(LOG_ERR,"ERROR %u (%s) line %d opening %s",errno,strerror(errno),__LINE__,fname);
		return(-1); 
	}
1071

1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
	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
1087
1088
1089
1090
	if(nodecfg != NULL) {
		switch(nodecfg->status) {
			case MAIL_STATUS_HOLD:	hdr.attr|=FIDO_HOLD;	break;
			case MAIL_STATUS_CRASH:	hdr.attr|=FIDO_CRASH;	break;
1091
			case MAIL_STATUS_NORMAL:						break;
rswindell's avatar
rswindell committed
1092
1093
1094
		}
		direct = nodecfg->direct;
	}
1095

1096
	t = when_written.time;
rswindell's avatar
rswindell committed
1097
	tm = localtime(&t);
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
	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);
	fprintf(fp,"\1INTL %hu:%hu/%hu %hu:%hu/%hu\r"
		,hdr.destzone,hdr.destnet,hdr.destnode
		,hdr.origzone,hdr.orignet,hdr.orignode);

1111
1112
1113
1114
1115
1116
1117
1118
1119
	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);
1120
	}
1121
1122
	/* Add FSC-53 FLAGS kludge */
	fprintf(fp,"\1FLAGS");
rswindell's avatar
rswindell committed
1123
	if(direct)
1124
1125
		fprintf(fp," DIR");
	if(file_attached) {
rswindell's avatar
rswindell committed
1126
		if(cfg.trunc_bundles)
1127
			fprintf(fp," TFS");
1128
		else
1129
1130
1131
			fprintf(fp," KFS");
	}
	fprintf(fp,"\r");
1132

1133
1134
1135
1136
	if(hdr.destpoint)
		fprintf(fp,"\1TOPT %hu\r",hdr.destpoint);
	if(hdr.origpoint)
		fprintf(fp,"\1FMPT %hu\r",hdr.origpoint);
1137
1138
1139
1140
1141
1142
1143
	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
1144
	if(!file_attached || (!direct && file_attached))
1145
1146
1147
		fwrite_crlf(body,strlen(body)+1,fp);	/* Write additional NULL */
	else
		fwrite("\0",1,1,fp);               /* Write NULL */
rswindell's avatar
rswindell committed
1148
1149
1150
	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);
1151
	return fclose(fp);
1152
1153
1154
1155
1156
1157
}

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

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

1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
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
1215
1216
/* Returns true if area is linked with specified node address */
bool area_is_linked(unsigned area_num, const fidoaddr_t* addr)
1217
{
1218
	unsigned i;
rswindell's avatar
rswindell committed
1219
1220
1221
1222
	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;
1223
1224
}

1225
1226
/* Returns area index */
uint find_area(const char* echotag)
rswindell's avatar
rswindell committed
1227
{
1228
1229
1230
	unsigned u;

	for(u=0; u < cfg.areas; u++)
1231
		if(cfg.area[u].tag != NULL && stricmp(cfg.area[u].tag, echotag) == 0)
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
			break;

	return u;
}

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

1242
1243
/* Returns subnum (INVALID_SUB if pass-through) or SUB_NOT_FOUND */
#define SUB_NOT_FOUND ((uint)-2)
1244
1245
1246
1247
1248
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))
1249
		return cfg.area[area].sub;
rswindell's avatar
rswindell committed
1250

1251
	return SUB_NOT_FOUND;
rswindell's avatar
rswindell committed
1252
1253
}

1254
1255
1256
1257
1258
/******************************************************************************
 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
1259
void gen_notify_list(void)
1260
1261
1262
{
	FILE *	tmpf;
	char	str[256];
deuce's avatar
deuce committed
1263
	uint	i,k;
1264

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

rswindell's avatar
rswindell committed
1267
		if(!cfg.nodecfg[k].send_notify)
1268
1269
1270
			continue;

		if((tmpf=tmpfile())==NULL) {
1271
			lprintf(LOG_ERR,"ERROR line %d couldn't open tmpfile",__LINE__);
1272
1273
			return; 
		}
1274
1275
1276
1277

		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
1278
		fprintf(tmpf,"Packet Type       %s\r\n", pktTypeStringList[cfg.nodecfg[k].pkt_type]);
1279
		fprintf(tmpf,"Archive Type      %s\r\n"
rswindell's avatar
rswindell committed
1280
1281
1282
			,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");
1283
		fprintf(tmpf,"Passive (paused)  %s\r\n", cfg.nodecfg[k].passive ? "Yes":"No");
1284
1285
1286
1287
1288
		fprintf(tmpf,"Remote AreaMgr    %s\r\n\r\n"
			,cfg.nodecfg[k].password[0] ? "Yes" : "No");

		fprintf(tmpf,"Connected Areas\r\n---------------\r\n");
		for(i=0;i<cfg.areas;i++) {
1289
			sprintf(str,"%s\r\n",cfg.area[i].tag);
1290
1291
			if(str[0]=='*')
				continue;
rswindell's avatar
rswindell committed
1292
			if(area_is_linked(i,&cfg.nodecfg[k].addr))
1293
1294
				fprintf(tmpf,"%s",str); 
		}
1295
1296

		if(ftell(tmpf))
rswindell's avatar
rswindell committed
1297
			file_to_netmail(tmpf,"SBBSecho Notify List",cfg.nodecfg[k].addr, /* To: */NULL);
1298
1299
		fclose(tmpf); 
	}
1300
}
1301

1302
1303
1304
1305
/******************************************************************************
 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).
******************************************************************************/
1306
1307
1308
1309
1310
enum arealist_type {
	 AREALIST_ALL			// %LIST
	,AREALIST_CONNECTED		// %QUERY
	,AREALIST_UNLINKED		// %UNLINKED
};
rswindell's avatar
rswindell committed
1311
void netmail_arealist(enum arealist_type type, fidoaddr_t addr, const char* to)
1312
{