sbbsecho.c 190 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;
93
str_list_t 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
/* FTN-compliant "Program Identifier"/PID (also used as a "Tosser Identifier"/TID) */
const char* sbbsecho_pid(void)
{
	static char str[256];
rswindell's avatar
rswindell committed
123

124
125
126
127
128
129
130
131
132
133
	sprintf(str, "SBBSecho %u.%02u-%s r%s %s %s"
		,SBBSECHO_VERSION_MAJOR,SBBSECHO_VERSION_MINOR,PLATFORM_DESC,revision,__DATE__,compiler);

	return str;
}

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

135
136
137
138
139
	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
/* 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;
180
	SAFEPRINTF(str, "\1%s", kludge);
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
	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
	if(smsg.from_net.type == NET_FIDO && smsg.from_net.addr != NULL)
356
		emsg.origaddr	= *(fidoaddr_t*)smsg.from_net.addr;
357
358
359
360
361
362
363
364
	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", (ulong)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
	else
		sprintf(tstr, "%.3s %.2s %.4s %.8s", p+4, p+8, p+20, p+11);
rswindell's avatar
rswindell committed
442

443
444
445
	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
	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);
458
	fprintf(fp, "%s.length = %lu\n"							, prefix, (ulong)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
		fprintf(fp, "\n");
	}
	fclose(fp);
	return true;
}

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

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

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

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

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

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

672
	return(cmd);
673
674
675
}

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

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

rswindell's avatar
rswindell committed
687
	return retval;
688
}
689

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

rswindell's avatar
rswindell committed
697
698
699
700
701
702
703
	nodecfg = findnodecfg(&cfg, addr, /* exact */FALSE);
	if(nodecfg != NULL && nodecfg->local_addr.zone)
		return nodecfg->local_addr;

	int i;
	for(i=0; i<scfg.total_faddrs && addr.net != 0; i++)
		if(scfg.faddr[i].zone == addr.zone && scfg.faddr[i].net == addr.net)
rswindell's avatar
rswindell committed
704
			return scfg.faddr[i];
rswindell's avatar
rswindell committed
705
706
	for(i=0; i<scfg.total_faddrs; i++)
		if(scfg.faddr[i].zone == addr.zone)
rswindell's avatar
rswindell committed
707
708
			return scfg.faddr[i];
	return sys_faddr;
709
}
710

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

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

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

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

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

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

	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
766
767
768
769
770
	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;
771
		lprintf(LOG_NOTICE, "Node (%s) externally locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
772
773
774
775
776
777
778
		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);
779
780
781
	}
	strListPush(&locked_bso_nodes, fname);
	lprintf(LOG_DEBUG, "Node (%s) successfully locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
782
783
784
	return true;
}

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

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

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

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

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

	if(!bso_lock_node(dest))
		return 1;

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

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

846
847
848
849
#ifdef __unix__
	if(isalpha(infile[0]) && infile[1] == ':')	// Ignore "C:" prefix
		infile += 2;
#endif
rswindell's avatar
rswindell committed
850
	SAFECOPY(attachment, infile);
851
	REPLACE_CHARS(attachment, '\\', '/', p);
rswindell's avatar
rswindell committed
852
853
854
	if(!fexistcase(attachment))	{ /* just in-case it's the wrong case for a Unix file system */
		lprintf(LOG_ERR, "ERROR line %u, attachment file not found: %s", __LINE__, attachment);
		return -1;
855
	}
rswindell's avatar
rswindell committed
856
857
858
859
860
	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);
rswindell's avatar
rswindell committed
861
		return -1;
rswindell's avatar
rswindell committed
862
863
864
865
866
867
	}
	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;
868
869
}

870
/* Writes text buffer to file, expanding sole LFs to CRLFs */
rswindell's avatar
rswindell committed
871
size_t fwrite_crlf(const char* buf, size_t len, FILE* fp)
872
873
874
875
876
877
878
879
{
	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
880
881
			if(fputc('\r', fp) == EOF)
				break;
882
883
884
			wr++;
		}
		if(fputc(ch,fp)==EOF)
rswindell's avatar
rswindell committed
885
			break;
886
887
888
889
890
891
892
		wr++;
		last_ch=ch;
	}

	return(wr);
}

rswindell's avatar
rswindell committed
893
bool fidoctrl_line_exists(const smbmsg_t* msg, const char* prefix)
894
{
895
	if(msg==NULL || prefix==NULL)
rswindell's avatar
rswindell committed
896
		return false;
897
898
899
	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
900
			return true;
901
	}
rswindell's avatar
rswindell committed
902
	return false;
903
904
}

rswindell's avatar
rswindell committed
905
fidoaddr_t fmsghdr_srcaddr(const fmsghdr_t* hdr)
906
907
908
909
910
911
912
913
914
915
916
{
	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
917
const char* fmsghdr_srcaddr_str(const fmsghdr_t* hdr)
918
919
920
921
922
923
924
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_srcaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

rswindell's avatar
rswindell committed
925
fidoaddr_t fmsghdr_destaddr(const fmsghdr_t* hdr)
926
927
928
929
930
931
932
933
934
935
936
{
	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
937
const char* fmsghdr_destaddr_str(const fmsghdr_t* hdr)
938
939
940
941
942
943
944
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_destaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

945
946
947
948
bool parse_origin(const char* fmsgbuf, fmsghdr_t* hdr)
{
	char* p;
	fidoaddr_t origaddr;
rswindell's avatar
rswindell committed
949

950
951
952
953
954
955
956
957
958
959
960
	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
961
	if(origaddr.zone == 0 || faddr_contains_wildcard(&origaddr))
962
963
964
965
966
967
968
969
		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
970
971
972
973
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
974
	enum pkt_type type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
975
976
977
978
979
980
981
982
983
984
985
986
987
988

	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
989
990
991
992
	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
993
		dest.point = hdr->type2plus.destpoint;
rswindell's avatar
rswindell committed
994
995
996
997
998
999
		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
1000
1001
1002
1003
		}
	} else if(hdr->type2_2.subversion==2) {					/* Type 2.2 Packet Header (FSC-45) */
		type = PKT_TYPE_2_2;
		orig.point = hdr->type2_2.origpoint;
rswindell's avatar
rswindell committed
1004
		dest.point = hdr->type2_2.destpoint;
rswindell's avatar
rswindell committed
1005
1006
1007
1008
1009
1010
1011
1012
	}

	if(pkt_type != NULL)
		*pkt_type = type;
	if(dest_addr != NULL)
		*dest_addr = dest;
	if(orig_addr != NULL)
		*orig_addr = orig;
rswindell's avatar
rswindell committed
1013

rswindell's avatar
rswindell committed
1014
1015
1016
1017
1018
	return true;
}

bool new_pkthdr(fpkthdr_t* hdr, fidoaddr_t orig, fidoaddr_t dest, const nodecfg_t* nodecfg)
{
rswindell's avatar
rswindell committed
1019
	enum pkt_type pkt_type = PKT_TYPE_2;
rswindell's avatar
rswindell committed
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
	struct tm* tm;
	time_t now = time(NULL);

	if(nodecfg != NULL)
		pkt_type = nodecfg->pkt_type;

	memset(hdr, 0, sizeof(fpkthdr_t));

	if((tm = localtime(&now)) != NULL) {
		hdr->type2.year	= tm->tm_year+1900;
		hdr->type2.month= tm->tm_mon;
		hdr->type2.day	= tm->tm_mday;
		hdr->type2.hour	= tm->tm_hour;
		hdr->type2.min	= tm->tm_min;
		hdr->type2.sec	= tm->tm_sec;
	}

	hdr->type2.origzone = orig.zone;
	hdr->type2.orignet	= orig.net;
	hdr->type2.orignode	= orig.node;
	hdr->type2.destzone	= dest.zone;
	hdr->type2.destnet	= dest.net;
	hdr->type2.destnode	= dest.node;
rswindell's avatar
rswindell committed
1043

rswindell's avatar
rswindell committed
1044
1045
1046
1047
1048
	hdr->type2.pkttype	= 2;
	hdr->type2.prodcode	= SBBSECHO_PRODUCT_CODE&0xff;
	hdr->type2.sernum	= SBBSECHO_VERSION_MAJOR;

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

rswindell's avatar
rswindell committed
1051
	if(pkt_type == PKT_TYPE_2)
rswindell's avatar
rswindell committed
1052
1053
1054
1055
		return true;

	if(pkt_type == PKT_TYPE_2_2) {
		hdr->type2_2.subversion = 2;	/* 2.2 */
1056
1057
		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
1058
1059
		return true;
	}
rswindell's avatar
rswindell committed
1060

rswindell's avatar
rswindell committed
1061
1062
	/* 2e and 2+ */
	if(pkt_type != PKT_TYPE_2_EXT && pkt_type != PKT_TYPE_2_PLUS) {
rswindell's avatar
rswindell committed
1063
1064
1065
1066
		lprintf(LOG_ERR, "UNSUPPORTED PACKET TYPE: %u", pkt_type);
		return false;
	}

rswindell's avatar
rswindell committed
1067
1068
1069
1070
	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
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
	}
	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;
}

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

1105
	if(msg==NULL) {
rswindell's avatar
rswindell committed
1106
		when_written.time = time32(NULL);
1107
1108
1109
1110
1111
		when_written.zone = sys_timezone(&scfg);
	} else {
		from = msg->from;
		when_written = msg->hdr.when_written;
	}
rswindell's avatar
rswindell committed
1112
	if(from==NULL || *from==0) {
1113
		from="SBBSecho";
rswindell's avatar
rswindell committed
1114
1115
1116
1117
1118
1119
1120
		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)
1121
		to="Sysop";
1122
	if(!startmsg) startmsg=1;
rswindell's avatar
rswindell committed
1123
1124
1125
	if((nodecfg=findnodecfg(&cfg, dest, 0)) != NULL) {
		if(nodecfg->status == MAIL_STATUS_NORMAL && !nodecfg->direct)
			nodecfg=findnodecfg(&cfg, dest, /* skip exact match: */2);
1126
	}
1127

1128
1129
1130
1131
	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;
	}
1132
1133
1134
	for(i=startmsg;i;i++) {
		sprintf(fname,"%s%u.msg",scfg.netmail_dir,i);
		if(!fexistcase(fname))
rswindell's avatar
rswindell committed
1135
			break;
1136
1137
1138
	}
	if(!i) {
		lprintf(LOG_WARNING,"Directory full: %s",scfg.netmail_dir);
rswindell's avatar
rswindell committed
1139
		return(-1);
1140
1141
	}
	startmsg=i+1;
1142
	if((fp=fnopen(NULL,fname,O_RDWR|O_CREAT))==NULL) {
1143
		lprintf(LOG_ERR,"ERROR %u (%s) line %d opening %s",errno,strerror(errno),__LINE__,fname);
rswindell's avatar
rswindell committed
1144
		return(-1);
1145
	}
1146

1147
1148
1149
	if(msg != NULL && msg->from_net.type == NET_FIDO && msg->from_net.addr != NULL)
		faddr = *(fidoaddr_t*)msg->from_net.addr;
	else
rswindell's avatar
rswindell committed
1150
		faddr = getsysfaddr(dest);
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
	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);
1162
	if(msg != NULL && (msg->hdr.auxattr&MSG_FILEATTACH))
1163
1164
		hdr.attr|=FIDO_FILE;

rswindell's avatar
rswindell committed
1165
1166
1167
1168
	if(nodecfg != NULL) {
		switch(nodecfg->status) {
			case MAIL_STATUS_HOLD:	hdr.attr|=FIDO_HOLD;	break;
			case MAIL_STATUS_CRASH:	hdr.attr|=FIDO_CRASH;	break;
1169
			case MAIL_STATUS_NORMAL:						break;
rswindell's avatar
rswindell committed
1170
1171
1172
		}
		direct = nodecfg->direct;
	}
1173

1174
	t = when_written.time;
rswindell's avatar
rswindell committed
1175
	tm = localtime(&t);
1176
1177
1178
1179
1180
1181
1182
1183
	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);

1184
	(void)fwrite(&hdr,sizeof(fmsghdr_t),1,fp);
1185
	fwrite_intl_control_line(fp, &hdr);
1186

1187
1188
1189
1190
1191
1192
1193
1194
1195
	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);
1196
	}
1197
1198
1199
1200
1201
	if(!cfg.flo_mailer) {
		/* Add FSC-53 FLAGS kludge */
		fprintf(fp,"\1FLAGS");
		if(direct)
			fprintf(fp," DIR");
1202
		if(hdr.attr&FIDO_FILE) {
1203
1204
1205
1206
1207
1208
			if(cfg.trunc_bundles)
				fprintf(fp," TFS");
			else
				fprintf(fp," KFS");
		}
		fprintf(fp,"\r");
1209
	}
1210

1211
1212
1213
1214
	if(hdr.destpoint)
		fprintf(fp,"\1TOPT %hu\r",hdr.destpoint);
	if(hdr.origpoint)
		fprintf(fp,"\1FMPT %hu\r",hdr.origpoint);
1215
1216
	fprintf(fp,"\1PID: %s\r", (msg==NULL || msg->ftn_pid==NULL) ? sbbsecho_pid() : msg->ftn_pid);
	if(msg != NULL) {
1217
1218
		if(msg->columns)
			fprintf(fp,"\1COLS: %u\r", (unsigned int)msg->columns);
1219
1220
1221
1222
		/* 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]);
1223
1224
1225
1226
1227
1228
		/* 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);
1229
	}
rswindell's avatar
rswindell committed
1230
	/* Write the body text */
1231
1232
	if(body != NULL) {
		int bodylen = strlen(body);
rswindell's avatar
rswindell committed
1233
		fwrite_crlf(body, bodylen, fp);
1234
1235
1236
1237
1238
		/* 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
1239
	fputc(FIDO_STORED_MSG_TERMINATOR, fp);
rswindell's avatar
rswindell committed
1240
	lprintf(LOG_INFO, "Created NetMail (%s)%s from %s (%s) to %s (%s), attr: %04hX, subject: %s"
1241
		,getfname(fname), (hdr.attr&FIDO_FILE) ? " with attachment" : ""
rswindell's avatar
rswindell committed
1242
		,from, smb_faddrtoa(&faddr, tmp), to, smb_faddrtoa(&dest, NULL), hdr.attr, subject);
1243
	return fclose(fp);
1244
1245
1246
}

/******************************************************************************
1247
1248
 This function takes the contents of 'infile' and puts it into netmail
 message(s) bound for addr.
1249
******************************************************************************/
1250
int file_to_netmail(FILE* infile, const char* title, fidoaddr_t dest, const char* to)
1251
1252
1253
{
	char *buf,*p;
	long l,m,len;
1254
	int netmails_created = 0;
1255

1256
1257
1258
	if(fseek(infile, 0, SEEK_END) != 0)
		return 0;

1259
1260
1261
1262
	l=len=ftell(infile);
	if(len>8192L)
		len=8192L;
	rewind(infile);
deuce's avatar
deuce committed
1263
	if((buf=(char *)malloc(len+1))==NULL) {
1264
		lprintf(LOG_ERR,"ERROR line %d allocating %lu for file to netmail buf",__LINE__,len);
rswindell's avatar
rswindell committed
1265
		return 0;
1266
	}
1267
1268
1269
	while((m=fread(buf,1,(len>8064L) ? 8064L:len,infile))>0) {
		buf[m]=0;
		if(l>8064L && (p=strrchr(buf,'\n'))!=NULL) {
1270
			p++;
1271
1272
			if(*p) {
				*p=0;
1273
				p++;
1274
				(void)fseek(infile,-1L,SEEK_CUR);
1275
1276
				while(*p) { 			/* Seek back to end of last line */
					p++;
rswindell's avatar
rswindell committed
1277
1278
1279
					(void)fseek(infile,-1L,SEEK_CUR);
				}
			}
1280
		}
1281
1282
		if(ftell(infile)<l)
			strcat(buf,"\r\nContinued in next message...\r\n");
1283
		if(create_netmail(to, /* msg: */NULL, title, buf, dest) == 0)
1284
			netmails_created++;
1285
	}
deuce's avatar
deuce committed
1286
	free(buf);
1287
	return netmails_created;
1288
}
1289

1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301