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

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/* FTN-compliant "Program Identifier"/PID (also used as a "Tosser Identifier"/TID) */
const char* sbbsecho_pid(void)
{
	static char str[256];
	
	sprintf(str, "SBBSecho %u.%02u-%s r%s %s %s"
		,SBBSECHO_VERSION_MAJOR,SBBSECHO_VERSION_MINOR,PLATFORM_DESC,revision,__DATE__,compiler);

	return str;
}

/* for *.bsy file contents: */
const char* program_id(void)
{
	static char str[256];
	
	SAFEPRINTF2(str, "%u %s", getpid(), sbbsecho_pid());

	return str;
}

rswindell's avatar
rswindell committed
140
141
142
143
144
145
146
147
148
149
const char* tear_line(void)
{
	static char str[256];

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

	return str;
}

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

const char* zone_domain(uint16_t zone)
{
154
155
156
157
	for(unsigned i = 0; i < cfg.domain_count; i++)
		for(unsigned j = 0; j < cfg.domain_list[i].zone_count; j++)
			if(cfg.domain_list[i].zone_list[j] == zone)
				return cfg.domain_list[i].name;
158
159
160
161
162
163

	return default_domain;
}

const char* zone_root_outbound(uint16_t zone)
{
164
165
166
167
	for(unsigned i = 0; i < cfg.domain_count; i++)
		for(unsigned j = 0; j < cfg.domain_list[i].zone_count; j++)
			if(cfg.domain_list[i].zone_list[j] == zone)
				return cfg.domain_list[i].root;
168
169
170
171

	return cfg.outbound;
}

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

rswindell's avatar
rswindell committed
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/* Write an FTS-4009 compliant "Via" control line to the (netmail) file */
int fwrite_via_control_line(FILE* fp, fidoaddr_t* addr)
{
	time_t t = time(NULL);
	struct tm* tm = gmtime(&t);
	return fprintf(fp,"\1Via %s @%04u%02u%02u.%02u%02u%02u.UTC "
		"SBBSecho %u.%02u-%s r%s\r"
		,smb_faddrtoa(addr, NULL)
		,tm->tm_year+1900
		,tm->tm_mon+1
		,tm->tm_mday
		,tm->tm_hour
		,tm->tm_min
		,tm->tm_sec
		,SBBSECHO_VERSION_MAJOR,SBBSECHO_VERSION_MINOR,PLATFORM_DESC,revision);
}
216
217
218
219
220
221
222
223

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

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

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

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

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

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

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

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

	return msg;
}

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

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

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

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

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

rswindell's avatar
rswindell committed
467
468
469
470
471
472
473
474
475
476
477
478
479
void fwrite_echostat(FILE* fp, echostat_t* stat)
{
	fprintf(fp, "[%s]\n"						, stat->tag);
	fprintf(fp,	"Known = %s\n"					, stat->known ? "true" : "false");
	for(int type = 0; type < ECHOSTAT_MSG_TYPES; type++) {
		char prefix[32];
		sprintf(prefix, "First%s", echostat_msg_type[type])	, fwrite_echostat_msg(fp, stat->first[type], prefix);
		sprintf(prefix, "Last%s", echostat_msg_type[type])	, fwrite_echostat_msg(fp, stat->last[type], prefix);
		if(stat->total[type] != 0)
			fprintf(fp,	"Total%s = %lu\n"					, echostat_msg_type[type], stat->total[type]);
	}
}

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

484
485
	qsort(echostat, echo_count, sizeof(echostat_t), echostat_compare);

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

	for(unsigned i = 0; i < echo_count; i++) {
rswindell's avatar
rswindell committed
490
		fwrite_echostat(fp, &echostat[i]);
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
		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
513
514
515
516
/**********************/
/* Log print function */
/**********************/
int lprintf(int level, char *fmt, ...)
rswindell's avatar
rswindell committed
517
518
{
	va_list argptr;
rswindell's avatar
rswindell committed
519
	char sbuf[1024];
rswindell's avatar
rswindell committed
520
521
	int chcount;

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

rswindell's avatar
rswindell committed
529
530
531
532
	if(fidologfile!=NULL && level<=cfg.log_level) {
	    time_t now = time(NULL);
		struct tm *tm;
		struct tm tmbuf = {0};
533
		char timestamp[128];
rswindell's avatar
rswindell committed
534
535
536
		strip_ctrl(sbuf, sbuf);
		if((tm = localtime(&now)) == NULL)
			tm = &tmbuf;
537
538
539
540
541
		if(strftime(timestamp, sizeof(timestamp), cfg.logtime, tm) <= 0)
			snprintf(timestamp, sizeof(timestamp)-1, "%u-%02u-%02u %02u:%02u:%02u"
				,1900+tm->tm_year, tm->tm_mon+1, tm->tm_mday
				,tm->tm_hour, tm->tm_min, tm->tm_sec);
		fprintf(fidologfile, "%s %s\n", timestamp, sbuf);
rswindell's avatar
rswindell committed
542
543
		fflush(fidologfile);
	}
rswindell's avatar
rswindell committed
544
545
546
	return(chcount);
}

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

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

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

670
	return(cmd);
671
672
673
}

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

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

688
689
690
/******************************************************************************
 Returns the system address with the same zone as the address passed
******************************************************************************/
rswindell's avatar
rswindell committed
691
fidoaddr_t getsysfaddr(short zone)
692
693
{
	int i;
694

695
696
697
	for(i=0;i<scfg.total_faddrs;i++)
		if(scfg.faddr[i].zone==zone)
			return(scfg.faddr[i]);
698
	return(sys_faddr);
699
}
700

rswindell's avatar
rswindell committed
701
int get_outbound(fidoaddr_t dest, char* outbound, size_t maxlen, bool fileboxes)
702
{
rswindell's avatar
rswindell committed
703
704
	nodecfg_t*	nodecfg;

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

rswindell's avatar
rswindell committed
729
730
731
const char* get_current_outbound(fidoaddr_t dest, bool fileboxes)
{
	static char outbound[MAX_PATH+1];
732
733
734
735
	if(get_outbound(dest, outbound, sizeof(outbound)-1, fileboxes) != 0) {
		lprintf(LOG_ERR, "Error creating outbound directory");
		return NULL;
	}
rswindell's avatar
rswindell committed
736
737
738
739
	return outbound;
}

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

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

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

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

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

782
783
784
785
786
	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
787
788
789
790
791
792
793
794
795
796
		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);
797
798
	if(outbound == NULL)
		return NULL;
rswindell's avatar
rswindell committed
799
800
801
802
803
804
805
806

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

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

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

	if(!bso_lock_node(dest))
		return 1;

829
	flo_filename = bso_flo_filename(dest, attr);
830
831
832
	if(flo_filename == NULL)
		return -2;

833
834
835
836
#ifdef __unix__
	if(isalpha(infile[0]) && infile[1] == ':')	// Ignore "C:" prefix
		infile += 2;
#endif
rswindell's avatar
rswindell committed
837
	SAFECOPY(attachment, infile);
838
	REPLACE_CHARS(attachment, '\\', '/', p);
rswindell's avatar
rswindell committed
839
840
841
	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;
842
	}
rswindell's avatar
rswindell committed
843
844
845
846
847
848
849
850
851
852
853
854
	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;
855
856
}

857
/* Writes text buffer to file, expanding sole LFs to CRLFs */
rswindell's avatar
rswindell committed
858
size_t fwrite_crlf(const char* buf, size_t len, FILE* fp)
859
860
861
862
863
864
865
866
{
	char	ch,last_ch=0;
	size_t	i;
	size_t	wr=0;	/* total chars written (may be > len) */

	for(i=0;i<len;i++) {
		ch=*buf++;
		if(ch=='\n' && last_ch!='\r') {
rswindell's avatar
rswindell committed
867
868
			if(fputc('\r', fp) == EOF)
				break;
869
870
871
			wr++;
		}
		if(fputc(ch,fp)==EOF)
rswindell's avatar
rswindell committed
872
			break;
873
874
875
876
877
878
879
		wr++;
		last_ch=ch;
	}

	return(wr);
}

rswindell's avatar
rswindell committed
880
bool fidoctrl_line_exists(const smbmsg_t* msg, const char* prefix)
881
{
882
	if(msg==NULL || prefix==NULL)
rswindell's avatar
rswindell committed
883
		return false;
884
885
886
	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
887
			return true;
888
	}
rswindell's avatar
rswindell committed
889
	return false;
890
891
}

rswindell's avatar
rswindell committed
892
fidoaddr_t fmsghdr_srcaddr(const fmsghdr_t* hdr)
893
894
895
896
897
898
899
900
901
902
903
{
	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
904
const char* fmsghdr_srcaddr_str(const fmsghdr_t* hdr)
905
906
907
908
909
910
911
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_srcaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

rswindell's avatar
rswindell committed
912
fidoaddr_t fmsghdr_destaddr(const fmsghdr_t* hdr)
913
914
915
916
917
918
919
920
921
922
923
{
	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
924
const char* fmsghdr_destaddr_str(const fmsghdr_t* hdr)
925
926
927
928
929
930
931
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_destaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
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
948
	if(origaddr.zone == 0 || faddr_contains_wildcard(&origaddr))
949
950
951
952
953
954
955
956
		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
957
958
959
960
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
961
	enum pkt_type type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
962
963
964
965
966
967
968
969
970
971
972
973
974
975

	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
976
977
978
979
	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
980
		dest.point = hdr->type2plus.destpoint;
rswindell's avatar
rswindell committed
981
982
983
984
985
986
		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
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
		}
	} 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
1006
	enum pkt_type pkt_type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
	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)
1036
		strncpy((char*)hdr->type2.password, nodecfg->pktpwd, sizeof(hdr->type2.password));
rswindell's avatar
rswindell committed
1037

rswindell's avatar
rswindell committed
1038
	if(pkt_type == PKT_TYPE_2)
rswindell's avatar
rswindell committed
1039
1040
1041
1042
		return true;

	if(pkt_type == PKT_TYPE_2_2) {
		hdr->type2_2.subversion = 2;	/* 2.2 */
1043
1044
		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
1045
1046
1047
		return true;
	}
	
rswindell's avatar
rswindell committed
1048
1049
	/* 2e and 2+ */
	if(pkt_type != PKT_TYPE_2_EXT && pkt_type != PKT_TYPE_2_PLUS) {
rswindell's avatar
rswindell committed
1050
1051
1052
1053
		lprintf(LOG_ERR, "UNSUPPORTED PACKET TYPE: %u", pkt_type);
		return false;
	}

rswindell's avatar
rswindell committed
1054
1055
1056
1057
	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
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
	}
	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;
}

1071
/******************************************************************************
rswindell's avatar
rswindell committed
1072
 This function will create a netmail message (FTS-1 "stored message" format).
1073
1074
1075
 If file is non-zero, will set file attachment bit (for bundles).
 Returns 0 on success.
******************************************************************************/
1076
int create_netmail(const char *to, const smbmsg_t* msg, const char *subject, const char *body, fidoaddr_t dest)					
1077
{
1078
	FILE *fp;
1079
	char tmp[256];
1080
	char fname[MAX_PATH+1];
1081
	char* from=NULL;
1082
1083
	uint i;
	static uint startmsg;
rswindell's avatar
rswindell committed
1084
	fidoaddr_t	faddr;
1085
	fmsghdr_t hdr;
1086
	time_t t;
1087
	struct tm *tm;
1088
	when_t when_written;
rswindell's avatar
rswindell committed
1089
1090
	nodecfg_t* nodecfg;
	bool	direct=false;
1091

1092
	if(msg==NULL) {
rswindell's avatar
rswindell committed
1093
		when_written.time = time32(NULL);
1094
1095
1096
1097
1098
		when_written.zone = sys_timezone(&scfg);
	} else {
		from = msg->from;
		when_written = msg->hdr.when_written;
	}
rswindell's avatar
rswindell committed
1099
	if(from==NULL || *from==0) {
1100
		from="SBBSecho";
rswindell's avatar
rswindell committed
1101
1102
1103
1104
1105
1106
1107
		if(to != NULL && stricmp(to, "SBBSecho") == 0) {
			lprintf(LOG_NOTICE, "Refusing to create netmail-loop msg from %s to %s", from, to);
			return -42;	// Refuse to create mail-loop between 2 SBBSecho-bots
		}
	}

	if(to==NULL || *to==0)
1108
		to="Sysop";
1109
	if(!startmsg) startmsg=1;
rswindell's avatar
rswindell committed
1110
1111
1112
	if((nodecfg=findnodecfg(&cfg, dest, 0)) != NULL) {
		if(nodecfg->status == MAIL_STATUS_NORMAL && !nodecfg->direct)
			nodecfg=findnodecfg(&cfg, dest, /* skip exact match: */2);
1113
	}
1114

1115
1116
1117
1118
	if(!isdir(scfg.netmail_dir) && MKDIR(scfg.netmail_dir) != 0) {
		lprintf(LOG_ERR, "Error %u (%s) line %d creating directory: %s", errno, strerror(errno), __LINE__, scfg.netmail_dir);
		return -2;
	}
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
	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;
1129
	if((fp=fnopen(NULL,fname,O_RDWR|O_CREAT))==NULL) {
1130
1131
1132
		lprintf(LOG_ERR,"ERROR %u (%s) line %d opening %s",errno,strerror(errno),__LINE__,fname);
		return(-1); 
	}
1133

1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
	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);
1146
	if(msg != NULL && (msg->hdr.auxattr&MSG_FILEATTACH))
1147
1148
		hdr.attr|=FIDO_FILE;

rswindell's avatar
rswindell committed
1149
1150
1151
1152
	if(nodecfg != NULL) {
		switch(nodecfg->status) {
			case MAIL_STATUS_HOLD:	hdr.attr|=FIDO_HOLD;	break;
			case MAIL_STATUS_CRASH:	hdr.attr|=FIDO_CRASH;	break;
1153
			case MAIL_STATUS_NORMAL:						break;
rswindell's avatar
rswindell committed
1154
1155
1156
		}
		direct = nodecfg->direct;
	}
1157

1158
	t = when_written.time;
rswindell's avatar
rswindell committed
1159
	tm = localtime(&t);
1160
1161
1162
1163
1164
1165
1166
1167
	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);

1168
	(void)fwrite(&hdr,sizeof(fmsghdr_t),1,fp);
1169
	fwrite_intl_control_line(fp, &hdr);
1170

1171
1172
1173
1174
1175
1176
1177
1178
1179
	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);
1180
	}
1181
1182
1183
1184
1185
	if(!cfg.flo_mailer) {
		/* Add FSC-53 FLAGS kludge */
		fprintf(fp,"\1FLAGS");
		if(direct)
			fprintf(fp," DIR");
1186
		if(hdr.attr&FIDO_FILE) {
1187
1188
1189
1190
1191
1192
			if(cfg.trunc_bundles)
				fprintf(fp," TFS");
			else
				fprintf(fp," KFS");
		}
		fprintf(fp,"\r");
1193
	}
1194

1195
1196
1197
1198
	if(hdr.destpoint)
		fprintf(fp,"\1TOPT %hu\r",hdr.destpoint);
	if(hdr.origpoint)
		fprintf(fp,"\1FMPT %hu\r",hdr.origpoint);
1199
1200
1201
1202
1203
1204
	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]);
1205
1206
1207
1208
1209
1210
		/* comment headers are part of text */
		for(i=0; i<msg->total_hfields; i++)
			if(msg->hfield[i].type == SMB_COMMENT)
				fprintf(fp, "%s\r", (char*)msg->hfield_dat[i]);
		if(subject != msg->subj)
			fprintf(fp, "Subject: %s\r\r", msg->subj);
1211
	}
rswindell's avatar
rswindell committed
1212
	/* Write the body text */
1213
1214
	if(body != NULL) {
		int bodylen = strlen(body);
rswindell's avatar
rswindell committed
1215
		fwrite_crlf(body, bodylen, fp);
1216
1217
1218
1219
1220
		/* Write the tear line */
		if(bodylen > 0 && body[bodylen-1] != '\r' && body[bodylen-1] != '\n')
			fputc('\r', fp);
		fprintf(fp, "%s", tear_line());
	}
rswindell's avatar
rswindell committed
1221
	fputc(FIDO_STORED_MSG_TERMINATOR, fp);
rswindell's avatar
rswindell committed
1222
	lprintf(LOG_INFO, "Created NetMail (%s)%s from %s (%s) to %s (%s), attr: %04hX, subject: %s"
1223
		,getfname(fname), (hdr.attr&FIDO_FILE) ? " with attachment" : ""
rswindell's avatar
rswindell committed
1224
		,from, smb_faddrtoa(&faddr, tmp), to, smb_faddrtoa(&dest, NULL), hdr.attr, subject);
1225
	return fclose(fp);
1226
1227
1228
}

/******************************************************************************
1229
1230
 This function takes the contents of 'infile' and puts it into netmail
 message(s) bound for addr.
1231
******************************************************************************/
1232
int file_to_netmail(FILE* infile, const char* title, fidoaddr_t dest, const char* to)
1233
1234
1235
{
	char *buf,*p;
	long l,m,len;
1236
	int netmails_created = 0;
1237

1238
1239
1240
	if(fseek(infile, 0, SEEK_END) != 0)
		return 0;

1241
1242
1243
1244
	l=len=ftell(infile);
	if(len>8192L)
		len=8192L;
	rewind(infile);
deuce's avatar
deuce committed
1245
	if((buf=(char *)malloc(len+1))==NULL) {
1246
		lprintf(LOG_ERR,"ERROR line %d allocating %lu for file to netmail buf",__LINE__,len);
1247
		return 0; 
1248
	}
1249
1250
1251
	while((m=fread(buf,1,(len>8064L) ? 8064L:len,infile))>0) {
		buf[m]=0;
		if(l>8064L && (p=strrchr(buf,'\n'))!=NULL) {
1252
			p++;
1253
1254
			if(*p) {
				*p=0;
1255
				p++;
1256
				(void)fseek(infile,-1L,SEEK_CUR);
1257
1258
				while(*p) { 			/* Seek back to end of last line */
					p++;
1259
					(void)fseek(infile,-1L,SEEK_CUR); 
1260
1261
1262
				} 
			} 
		}
1263
1264
		if(ftell(infile)<l)
			strcat(buf,"\r\nContinued in next message...\r\n");
1265
		if(create_netmail(to, /* msg: */NULL, title, buf, dest) == 0)
1266
			netmails_created++;
1267
	}
deuce's avatar
deuce committed
1268
	free(buf);
1269
	return netmails_created;
1270
}
1271

1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
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
1295
1296
/* Returns true if area is linked with specified node address */
bool area_is_linked(unsigned area_num, const fidoaddr_t* addr)
1297
{
1298
	unsigned i;
rswindell's avatar
rswindell committed
1299
1300
1301
1302
	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;
1303
1304
}

1305
1306
/* Returns area index */
uint find_area(const char* echotag)
rswindell's avatar
rswindell committed
1307
{
1308
1309
1310
	unsigned u;

	for(u=0; u < cfg.areas; u++)
1311
		if(cfg.area[u].tag != NULL && stricmp(cfg.area[u].tag, echotag) == 0)
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
			break;

	return u;
}

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

1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
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;
}

1338
1339
/* Returns subnum (INVALID_SUB if pass-through) or SUB_NOT_FOUND */
#define SUB_NOT_FOUND ((uint)-2)
1340
1341
1342
1343
1344
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))
1345
		return cfg.area[area].sub;
rswindell's avatar
rswindell committed
1346

1347
	return SUB_NOT_FOUND;
rswindell's avatar
rswindell committed
1348
1349
}

1350
1351
1352
1353
1354
/******************************************************************************
 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
1355
void gen_notify_list(nodecfg_t* nodecfg)
1356
1357
1358
{
	FILE *	tmpf;
	char	str[256];
deuce's avatar
deuce committed
1359
	uint	i,k;
1360

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

rswindell's avatar
rswindell committed
1363
1364
1365
		if(nodecfg != NULL && &cfg.nodecfg[k] != nodecfg)
			continue;

rswindell's avatar
rswindell committed
1366
		if(!cfg.nodecfg[k].send_notify)
1367
1368
1369
			continue;

		if((tmpf=tmpfile())==NULL) {
1370
			lprintf(LOG_ERR,"ERROR line %d couldn't open tmpfile",__LINE__);
1371
1372
			return; 
		}
1373
1374
1375
1376

		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
1377
		fprintf(tmpf,"Name              %s\r\n", cfg.nodecfg[k].name);
rswindell's avatar
rswindell committed
1378
		fprintf(tmpf,"Packet Type       %s\r\n", pktTypeStringList[cfg.nodecfg[k].pkt_type]);
1379
		fprintf(tmpf,"Archive Type      %s\r\n"
rswindell's avatar
rswindell committed
1380
1381
1382
			,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");
1383
		fprintf(tmpf,"Passive (paused)  %s\r\n", cfg.nodecfg[k].passive ? "Yes":"No");
rswindell's avatar
rswindell committed
1384
		fprintf(tmpf,"Notifications     %s\r\n", cfg.nodecfg[k].send_notify ? "Yes":"No");
1385
		fprintf(tmpf,"Remote AreaMgr    %s\r\n\r\n"
rswindell's avatar
rswindell committed
1386
			,cfg.nodecfg[k].areafix ? "Yes" : "No");