sbbsecho.c 148 KB
Newer Older
1
/* sbbsecho.c */
2

3
/* Synchronet FidoNet EchoMail Scanning/Tossing and NetMail Tossing Utility */
4

5
6
7
8
9
10
/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
11
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 *																			*
 * 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.	*
 ****************************************************************************/
37

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

#include <time.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
48
#include <sys/stat.h>
49

rswindell's avatar
rswindell committed
50
#include "conwrap.h"		/* getch() */
51
52
53
54
55
56
#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
57
#include "genwrap.h"		/* PLATFORM_DESC */
rswindell's avatar
rswindell committed
58
59
60
#include "xpendian.h"

#define MAX_OPEN_SMBS	10
61
62

smb_t *smb,*email;
rswindell's avatar
rswindell committed
63
64
65
66
67
68
69
70
71
72
73
74
bool opt_import_packets		= true;
bool opt_import_netmail		= true;
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;

rswindell's avatar
rswindell committed
75
76
77
78
79
80
81
/* statistics */
ulong netmail=0;	/* imported */
ulong echomail=0;	/* imported */
ulong exported_netmail=0;
ulong exported_echomail=0;
ulong packed_netmail=0;

rswindell's avatar
rswindell committed
82
83
char tmp[256];
int cur_smb=0;
84
FILE *fidologfile=NULL;
rswindell's avatar
rswindell committed
85
bool twit_list;
86

rswindell's avatar
rswindell committed
87
88
89
90
91
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];
92

rswindell's avatar
rswindell committed
93
94
95
96
bool pause_on_exit=false;
bool pause_on_abend=false;
bool mtxfile_locked=false;
bool terminated=false;
97

98
99
str_list_t	locked_bso_nodes;

rswindell's avatar
rswindell committed
100
101
102
int mv(const char *insrc, const char *indest, bool copy);
void export_echomail(const char *sub_code, const nodecfg_t*, bool rescan);

103
104
105
106
107
108
109
110
111
112
113
/* 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;
}

rswindell's avatar
rswindell committed
114
115
/* for *.bsy file contents: */
const char* program_id(void)
116
{
rswindell's avatar
rswindell committed
117
118
119
	static char str[256];
	
	SAFEPRINTF2(str, "%u %s", getpid(), sbbsecho_pid());
120

rswindell's avatar
rswindell committed
121
	return str;
122
123
}

rswindell's avatar
rswindell committed
124
125
126
127
/**********************/
/* Log print function */
/**********************/
int lprintf(int level, char *fmt, ...)
rswindell's avatar
rswindell committed
128
129
{
	va_list argptr;
rswindell's avatar
rswindell committed
130
	char sbuf[1024];
rswindell's avatar
rswindell committed
131
132
	int chcount;

rswindell's avatar
rswindell committed
133
134
	va_start(argptr,fmt);
	chcount=vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
135
	sbuf[sizeof(sbuf)-1]=0;
rswindell's avatar
rswindell committed
136
	va_end(argptr);
137
138
	truncsp(sbuf);
	printf("%s\n",sbuf);
139

rswindell's avatar
rswindell committed
140
141
142
143
144
145
146
147
148
149
150
151
152
	if(fidologfile!=NULL && level<=cfg.log_level) {
	    time_t now = time(NULL);
		struct tm *tm;
		struct tm tmbuf = {0};
		strip_ctrl(sbuf, sbuf);
		if((tm = localtime(&now)) == NULL)
			tm = &tmbuf;
		fprintf(fidologfile,"%u-%02u-%02u %02u:%02u:%02u %s\n"
			,1900+tm->tm_year, tm->tm_mon+1, tm->tm_mday
			,tm->tm_hour, tm->tm_min, tm->tm_sec
			,sbuf);
		fflush(fidologfile);
	}
rswindell's avatar
rswindell committed
153
154
155
	return(chcount);
}

rswindell's avatar
rswindell committed
156
bool delfile(const char *filename, int line)
157
{
rswindell's avatar
rswindell committed
158
	lprintf(LOG_DEBUG, "Deleting %s (from line %u)", filename, line);
159
	if(remove(filename) != 0) {
160
161
		lprintf(LOG_ERR, "ERROR %u (%s) line %u removing file %s"
			,errno, strerror(errno), line, filename);
rswindell's avatar
rswindell committed
162
		return false;
163
	}
rswindell's avatar
rswindell committed
164
	return true;
165
166
}

167
168
169
/*****************************************************************************/
/* Returns command line generated from instr with %c replacments             */
/*****************************************************************************/
rswindell's avatar
rswindell committed
170
char *mycmdstr(scfg_t* cfg, const char *instr, const char *fpath, const char *fspec)
171
{
172
    static char cmd[MAX_PATH+1];
173
174
175
    char str[256],str2[128];
    int i,j,len;

176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
	len=strlen(instr);
	for(i=j=0;i<len && j<128;i++) {
		if(instr[i]=='%') {
			i++;
			cmd[j]=0;
			switch(toupper(instr[i])) {
				case 'F':   /* File path */
					strcat(cmd,fpath);
					break;
				case 'G':   /* Temp directory */
					if(cfg->temp_dir[0]!='\\' 
						&& cfg->temp_dir[0]!='/' 
						&& cfg->temp_dir[1]!=':') {
						strcpy(str,cfg->node_dir);
						strcat(str,cfg->temp_dir);
						if(FULLPATH(str2,str,40))
							strcpy(str,str2);
						backslash(str);
						strcat(cmd,str);}
					else
						strcat(cmd,cfg->temp_dir);
					break;
				case 'J':
					if(cfg->data_dir[0]!='\\' 
						&& cfg->data_dir[0]!='/' 
						&& cfg->data_dir[1]!=':') {
						strcpy(str,cfg->node_dir);
						strcat(str,cfg->data_dir);
						if(FULLPATH(str2,str,40))
							strcpy(str,str2);
						backslash(str);
207
208
						strcat(cmd,str); 
					}
209
210
211
212
213
214
215
216
217
218
219
220
					else
						strcat(cmd,cfg->data_dir);
					break;
				case 'K':
					if(cfg->ctrl_dir[0]!='\\' 
						&& cfg->ctrl_dir[0]!='/' 
						&& cfg->ctrl_dir[1]!=':') {
						strcpy(str,cfg->node_dir);
						strcat(str,cfg->ctrl_dir);
						if(FULLPATH(str2,str,40))
							strcpy(str,str2);
						backslash(str);
221
222
						strcat(cmd,str); 
					}
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
					else
						strcat(cmd,cfg->ctrl_dir);
					break;
				case 'N':   /* Node Directory (same as SBBSNODE environment var) */
					strcat(cmd,cfg->node_dir);
					break;
				case 'O':   /* SysOp */
					strcat(cmd,cfg->sys_op);
					break;
				case 'Q':   /* QWK ID */
					strcat(cmd,cfg->sys_id);
					break;
				case 'S':   /* File Spec */
					strcat(cmd,fspec);
					break;
				case '!':   /* EXEC Directory */
239
					strcat(cmd,cfg->exec_dir);
240
					break;
241
242
243
244
245
                case '@':   /* EXEC Directory for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
                    strcat(cmd,cfg->exec_dir);
#endif
                    break;
246
247
248
249
250
251
252
253
254
255
256
				case '#':   /* Node number (same as SBBSNNUM environment var) */
					sprintf(str,"%d",cfg->node_num);
					strcat(cmd,str);
					break;
				case '*':
					sprintf(str,"%03d",cfg->node_num);
					strcat(cmd,str);
					break;
				case '%':   /* %% for percent sign */
					strcat(cmd,"%");
					break;
257
258
259
260
261
262
263
264
265
266
				case '.':	/* .exe for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
					strcat(cmd,".exe");
#endif
					break;
				case '?':	/* Platform */
					strcpy(str,PLATFORM_DESC);
					strlwr(str);
					strcat(cmd,str);
					break;
267
				default:    /* unknown specification */
268
					lprintf(LOG_ERR,"ERROR Checking Command Line '%s'",instr);
269
					bail(1);
270
271
272
273
					break; 
			}
			j=strlen(cmd); 
		}
274
		else
275
276
			cmd[j++]=instr[i]; 
}
277
	cmd[j]=0;
278

279
	return(cmd);
280
281
282
}

/****************************************************************************/
rswindell's avatar
rswindell committed
283
/* Runs an external program directly using system()							*/
284
285
286
/****************************************************************************/
int execute(char *cmdline)
{
rswindell's avatar
rswindell committed
287
288
289
290
291
292
	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));
293

rswindell's avatar
rswindell committed
294
	return retval;
295
}
296

297
298
299
/******************************************************************************
 Returns the system address with the same zone as the address passed
******************************************************************************/
rswindell's avatar
rswindell committed
300
fidoaddr_t getsysfaddr(short zone)
301
302
{
	int i;
303

304
305
306
	for(i=0;i<scfg.total_faddrs;i++)
		if(scfg.faddr[i].zone==zone)
			return(scfg.faddr[i]);
307
	return(sys_faddr);
308
}
309

rswindell's avatar
rswindell committed
310
int get_outbound(fidoaddr_t dest, char* outbound, size_t maxlen, bool fileboxes)
311
{
rswindell's avatar
rswindell committed
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
	nodecfg_t*	nodecfg;

	strncpy(outbound,cfg.outbound,maxlen);
	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/" */
			*lastchar(outbound) = 0;
			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);
		}
328
329
	}
	backslash(outbound);
rswindell's avatar
rswindell committed
330
331
	if(!isdir(outbound))
		lprintf(LOG_DEBUG, "Creating outbound directory for %s: %s", smb_faddrtoa(&dest, NULL), outbound);
332
333
334
	return mkpath(outbound);
}

rswindell's avatar
rswindell committed
335
336
337
338
339
340
341
342
const char* get_current_outbound(fidoaddr_t dest, bool fileboxes)
{
	static char outbound[MAX_PATH+1];
	get_outbound(dest, outbound, sizeof(outbound)-1, fileboxes);
	return outbound;
}

bool bso_lock_node(fidoaddr_t dest)
343
{
rswindell's avatar
rswindell committed
344
	const char* outbound;
345
346
	char fname[MAX_PATH+1];

rswindell's avatar
rswindell committed
347
348
	if(!cfg.flo_mailer)
		return true;
349

rswindell's avatar
rswindell committed
350
	outbound = get_current_outbound(dest, /* fileboxes: */false);
351
352
353
354
355
356

	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
357
358
359
360
361
	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;
362
		lprintf(LOG_NOTICE, "Node (%s) externally locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
363
364
365
366
367
368
369
		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);
370
371
372
	}
	strListPush(&locked_bso_nodes, fname);
	lprintf(LOG_DEBUG, "Node (%s) successfully locked via: %s", smb_faddrtoa(&dest, NULL), fname);
rswindell's avatar
rswindell committed
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 true;
}

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

	if((nodecfg=findnodecfg(&cfg, dest, /* exact: */false)) != NULL) {
		switch(nodecfg->status) {
			case MAIL_STATUS_CRASH:	ch='c';	break;
			case MAIL_STATUS_HOLD:	ch='h';	break;
			default:
				if(nodecfg->direct)
					ch='d';
				break;
		}
	}
	outbound = get_current_outbound(dest, /* fileboxes: */false);

	if(dest.point)
		SAFEPRINTF3(filename,"%s%08x.%clo",outbound,dest.point,ch);
	else
		SAFEPRINTF4(filename,"%s%04x%04x.%clo",outbound,dest.net,dest.node,ch);
	return filename;
}

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

409
410
411
412
413
/******************************************************************************
 This function creates or appends on existing Binkley compatible .?LO file
 attach file.
 Returns 0 on success.
******************************************************************************/
rswindell's avatar
rswindell committed
414
int write_flofile(const char *infile, fidoaddr_t dest, bool bundle, bool use_outbox)
415
{
rswindell's avatar
rswindell committed
416
417
	const char* flo_filename;
	char attachment[MAX_PATH+1];
418
	char searchstr[MAX_PATH+1];
rswindell's avatar
rswindell committed
419
420
	FILE *fp;
	nodecfg_t* nodecfg;
421

rswindell's avatar
rswindell committed
422
423
424
425
	if(use_outbox && (nodecfg=findnodecfg(&cfg, dest, /* exact: */false)) != NULL) {
		if(nodecfg->outbox[0])
			return 0;
	}
426
427
428
429

	if(!bso_lock_node(dest))
		return 1;

rswindell's avatar
rswindell committed
430
431
432
433
434
	flo_filename = bso_flo_filename(dest);
	SAFECOPY(attachment, infile);
	if(!fexistcase(attachment))	{ /* just in-case it's the wrong case for a Unix file system */
		lprintf(LOG_ERR, "ERROR line %u, attachment file not found: %s", __LINE__, attachment);
		return -1;
435
	}
rswindell's avatar
rswindell committed
436
437
438
439
440
441
442
443
444
445
446
447
	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;
448
449
}

450
/* Writes text buffer to file, expanding sole LFs to CRLFs */
rswindell's avatar
rswindell committed
451
size_t fwrite_crlf(const char* buf, size_t len, FILE* fp)
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
{
	char	ch,last_ch=0;
	size_t	i;
	size_t	wr=0;	/* total chars written (may be > len) */

	for(i=0;i<len;i++) {
		ch=*buf++;
		if(ch=='\n' && last_ch!='\r') {
			if(fputc('\r',fp)==EOF)
				return(wr);
			wr++;
		}
		if(fputc(ch,fp)==EOF)
			return(wr);
		wr++;
		last_ch=ch;
	}

	return(wr);
}

rswindell's avatar
rswindell committed
473
bool fidoctrl_line_exists(const smbmsg_t* msg, const char* prefix)
474
{
475
	if(msg==NULL || prefix==NULL)
rswindell's avatar
rswindell committed
476
		return false;
477
478
479
	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
480
			return true;
481
	}
rswindell's avatar
rswindell committed
482
	return false;
483
484
}

rswindell's avatar
rswindell committed
485
fidoaddr_t fmsghdr_srcaddr(const fmsghdr_t* hdr)
486
487
488
489
490
491
492
493
494
495
496
{
	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
497
const char* fmsghdr_srcaddr_str(const fmsghdr_t* hdr)
498
499
500
501
502
503
504
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_srcaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

rswindell's avatar
rswindell committed
505
fidoaddr_t fmsghdr_destaddr(const fmsghdr_t* hdr)
506
507
508
509
510
511
512
513
514
515
516
{
	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
517
const char* fmsghdr_destaddr_str(const fmsghdr_t* hdr)
518
519
520
521
522
523
524
{
	static char buf[64];
	fidoaddr_t addr = fmsghdr_destaddr(hdr);

	return smb_faddrtoa(&addr, buf);
}

rswindell's avatar
rswindell committed
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
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;
	enum pkt_type type = PKT_TYPE_2_0;

	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! */

	if(hdr->type2plus.cword == BYTE_SWAP_16(hdr->type2plus.cwcopy)  /* 2+ Packet Header (FSC-39) */
		&& (hdr->type2plus.cword&1)) {
		type = PKT_TYPE_2_PLUS;
		dest.point = hdr->type2plus.destpoint;
		if(hdr->type2plus.origpoint!=0 && orig.net == 0xffff) {	/* see FSC-0048 for details */
			orig.net = hdr->type2plus.auxnet;
			orig.point = hdr->type2plus.origpoint;
		}
	} 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)
{
	enum pkt_type pkt_type = PKT_TYPE_2_0;
	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)
600
		strncpy((char*)hdr->type2.password, nodecfg->pktpwd, sizeof(hdr->type2.password));
rswindell's avatar
rswindell committed
601
602
603
604
605
606

	if(pkt_type == PKT_TYPE_2_0)
		return true;

	if(pkt_type == PKT_TYPE_2_2) {
		hdr->type2_2.subversion = 2;	/* 2.2 */
607
608
		strncpy((char*)hdr->type2_2.origdomn,"fidonet",sizeof(hdr->type2_2.origdomn));
		strncpy((char*)hdr->type2_2.destdomn,"fidonet",sizeof(hdr->type2_2.destdomn));
rswindell's avatar
rswindell committed
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
		return true;
	}
	
	/* 2+ */
	if(pkt_type != PKT_TYPE_2_PLUS) {
		lprintf(LOG_ERR, "UNSUPPORTED PACKET TYPE: %u", pkt_type);
		return false;
	}

	if(orig.point != 0) {
		hdr->type2plus.orignet	= 0xffff;
		hdr->type2plus.auxnet	= orig.net; 
	}
	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;
}

634
635
636
637
638
/******************************************************************************
 This function will create a netmail message (.MSG format).
 If file is non-zero, will set file attachment bit (for bundles).
 Returns 0 on success.
******************************************************************************/
rswindell's avatar
rswindell committed
639
640
int create_netmail(const char *to, const smbmsg_t* msg, const char *subject, const char *body, fidoaddr_t dest
					,bool file_attached)
641
{
642
643
	FILE *fp;
	char fname[MAX_PATH+1];
644
	char* from=NULL;
645
646
	uint i;
	static uint startmsg;
rswindell's avatar
rswindell committed
647
	fidoaddr_t	faddr;
648
	fmsghdr_t hdr;
649
	time_t t;
650
	struct tm *tm;
651
	when_t when_written;
rswindell's avatar
rswindell committed
652
653
	nodecfg_t* nodecfg;
	bool	direct=false;
654

655
	if(msg==NULL) {
rswindell's avatar
rswindell committed
656
		when_written.time = time32(NULL);
657
658
659
660
661
		when_written.zone = sys_timezone(&scfg);
	} else {
		from = msg->from;
		when_written = msg->hdr.when_written;
	}
662
663
664
665
	if(from==NULL)
		from="SBBSecho";
	if(to==NULL)
		to="Sysop";
666
	if(!startmsg) startmsg=1;
rswindell's avatar
rswindell committed
667
668
669
	if((nodecfg=findnodecfg(&cfg, dest, 0)) != NULL) {
		if(nodecfg->status == MAIL_STATUS_NORMAL && !nodecfg->direct)
			nodecfg=findnodecfg(&cfg, dest, /* skip exact match: */2);
670
	}
671

672
	MKDIR(scfg.netmail_dir);
673
674
675
676
677
678
679
680
681
682
	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;
683
	if((fp=fnopen(NULL,fname,O_RDWR|O_CREAT))==NULL) {
684
685
686
		lprintf(LOG_ERR,"ERROR %u (%s) line %d opening %s",errno,strerror(errno),__LINE__,fname);
		return(-1); 
	}
687

688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
	faddr=getsysfaddr(dest.zone);
	memset(&hdr,0,sizeof(fmsghdr_t));
	hdr.origzone=faddr.zone;
	hdr.orignet=faddr.net;
	hdr.orignode=faddr.node;
	hdr.origpoint=faddr.point;
	hdr.destzone=dest.zone;
	hdr.destnet=dest.net;
	hdr.destnode=dest.node;
	hdr.destpoint=dest.point;

	hdr.attr=(FIDO_PRIVATE|FIDO_KILLSENT|FIDO_LOCAL);
	if(file_attached)
		hdr.attr|=FIDO_FILE;

rswindell's avatar
rswindell committed
703
704
705
706
	if(nodecfg != NULL) {
		switch(nodecfg->status) {
			case MAIL_STATUS_HOLD:	hdr.attr|=FIDO_HOLD;	break;
			case MAIL_STATUS_CRASH:	hdr.attr|=FIDO_CRASH;	break;
707
			case MAIL_STATUS_NORMAL:						break;
rswindell's avatar
rswindell committed
708
709
710
		}
		direct = nodecfg->direct;
	}
711

712
	t = when_written.time;
rswindell's avatar
rswindell committed
713
	tm = localtime(&t);
714
715
716
717
718
719
720
721
722
723
724
725
726
	sprintf(hdr.time,"%02u %3.3s %02u  %02u:%02u:%02u"
		,tm->tm_mday,mon[tm->tm_mon],TM_YEAR(tm->tm_year)
		,tm->tm_hour,tm->tm_min,tm->tm_sec);

	SAFECOPY(hdr.to,to);
	SAFECOPY(hdr.from,from);
	SAFECOPY(hdr.subj,subject);

	fwrite(&hdr,sizeof(fmsghdr_t),1,fp);
	fprintf(fp,"\1INTL %hu:%hu/%hu %hu:%hu/%hu\r"
		,hdr.destzone,hdr.destnet,hdr.destnode
		,hdr.origzone,hdr.orignet,hdr.orignode);

727
728
729
730
731
732
733
734
735
	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);
736
	}
737
738
	/* Add FSC-53 FLAGS kludge */
	fprintf(fp,"\1FLAGS");
rswindell's avatar
rswindell committed
739
	if(direct)
740
741
		fprintf(fp," DIR");
	if(file_attached) {
rswindell's avatar
rswindell committed
742
		if(cfg.trunc_bundles)
743
			fprintf(fp," TFS");
744
		else
745
746
747
			fprintf(fp," KFS");
	}
	fprintf(fp,"\r");
748

749
750
751
752
	if(hdr.destpoint)
		fprintf(fp,"\1TOPT %hu\r",hdr.destpoint);
	if(hdr.origpoint)
		fprintf(fp,"\1FMPT %hu\r",hdr.origpoint);
753
754
755
756
757
758
759
	fprintf(fp,"\1PID: %s\r", (msg==NULL || msg->ftn_pid==NULL) ? sbbsecho_pid() : msg->ftn_pid);
	if(msg != NULL) {
		/* Unknown kludge lines are added here */
		for(int i=0; i<msg->total_hfields; i++)
			if(msg->hfield[i].type == FIDOCTRL)
				fprintf(fp,"\1%.512s\r",(char*)msg->hfield_dat[i]);
	}
rswindell's avatar
rswindell committed
760
	if(!file_attached || (!direct && file_attached))
761
762
763
		fwrite_crlf(body,strlen(body)+1,fp);	/* Write additional NULL */
	else
		fwrite("\0",1,1,fp);               /* Write NULL */
rswindell's avatar
rswindell committed
764
765
766
	lprintf(LOG_INFO, "Created NetMail (%s)%s from %s (%s) to %s (%s), attr: %04hX, subject: %s"
		,getfname(fname), file_attached ? " with attachment" : ""
		,from, smb_faddrtoa(&faddr, tmp), to, smb_faddrtoa(&dest, NULL), hdr.attr, subject);
767
	return fclose(fp);
768
769
770
771
772
773
}

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

779
780
781
782
	l=len=ftell(infile);
	if(len>8192L)
		len=8192L;
	rewind(infile);
deuce's avatar
deuce committed
783
	if((buf=(char *)malloc(len+1))==NULL) {
784
		lprintf(LOG_ERR,"ERROR line %d allocating %lu for file to netmail buf",__LINE__,len);
785
786
		return; 
	}
787
788
789
	while((m=fread(buf,1,(len>8064L) ? 8064L:len,infile))>0) {
		buf[m]=0;
		if(l>8064L && (p=strrchr(buf,'\n'))!=NULL) {
790
			p++;
791
792
			if(*p) {
				*p=0;
793
				p++;
794
795
796
				fseek(infile,-1L,SEEK_CUR);
				while(*p) { 			/* Seek back to end of last line */
					p++;
797
798
799
800
					fseek(infile,-1L,SEEK_CUR); 
				} 
			} 
		}
801
802
		if(ftell(infile)<l)
			strcat(buf,"\r\nContinued in next message...\r\n");
rswindell's avatar
rswindell committed
803
		create_netmail(to, /* msg: */NULL, title, buf, dest, /* attachment: */false); 
804
	}
deuce's avatar
deuce committed
805
	free(buf);
806
}
807

rswindell's avatar
rswindell committed
808
809
/* Returns true if area is linked with specified node address */
bool area_is_linked(unsigned area_num, const fidoaddr_t* addr)
810
{
811
	unsigned i;
rswindell's avatar
rswindell committed
812
813
814
815
	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;
816
817
}

818
819
820
821
822
/******************************************************************************
 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
823
void gen_notify_list(void)
824
825
826
{
	FILE *	tmpf;
	char	str[256];
deuce's avatar
deuce committed
827
	uint	i,k;
828

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

rswindell's avatar
rswindell committed
831
		if(!cfg.nodecfg[k].send_notify)
832
833
834
			continue;

		if((tmpf=tmpfile())==NULL) {
835
			lprintf(LOG_ERR,"ERROR line %d couldn't open tmpfile",__LINE__);
836
837
			return; 
		}
838
839
840
841

		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
842
		fprintf(tmpf,"Packet Type       %s\r\n", pktTypeStringList[cfg.nodecfg[k].pkt_type]);
843
		fprintf(tmpf,"Archive Type      %s\r\n"
rswindell's avatar
rswindell committed
844
845
846
847
			,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");
		fprintf(tmpf,"Passive           %s\r\n", cfg.nodecfg[k].passive ? "Yes":"No");
848
849
850
851
852
853
854
855
		fprintf(tmpf,"Remote AreaMgr    %s\r\n\r\n"
			,cfg.nodecfg[k].password[0] ? "Yes" : "No");

		fprintf(tmpf,"Connected Areas\r\n---------------\r\n");
		for(i=0;i<cfg.areas;i++) {
			sprintf(str,"%s\r\n",cfg.area[i].name);
			if(str[0]=='*')
				continue;
rswindell's avatar
rswindell committed
856
			if(area_is_linked(i,&cfg.nodecfg[k].addr))
857
858
				fprintf(tmpf,"%s",str); 
		}
859
860

		if(ftell(tmpf))
rswindell's avatar
rswindell committed
861
			file_to_netmail(tmpf,"SBBSecho Notify List",cfg.nodecfg[k].addr, /* To: */NULL);
862
863
		fclose(tmpf); 
	}
864
}
865

866
867
868
869
/******************************************************************************
 This function creates a netmail to addr showing a list of available areas (0),
 a list of connected areas (1), or a list of removed areas (2).
******************************************************************************/
870
871
872
873
874
enum arealist_type {
	 AREALIST_ALL			// %LIST
	,AREALIST_CONNECTED		// %QUERY
	,AREALIST_UNLINKED		// %UNLINKED
};
rswindell's avatar
rswindell committed
875
void netmail_arealist(enum arealist_type type, fidoaddr_t addr, const char* to)
876
{
877
878
	char str[256],title[128],match,*p,*tp;
	int i,j,k,x,y;
879
	str_list_t	area_list;
880

881
	if(type == AREALIST_ALL)
882
		strcpy(title,"List of Available Areas");
883
	else if(type == AREALIST_CONNECTED)
884
885
886
887
		strcpy(title,"List of Connected Areas");
	else
		strcpy(title,"List of Unlinked Areas");

888
889
	if((area_list=strListInit()) == NULL) {
		lprintf(LOG_ERR,"ERROR line %d couldn't allocate string list",__LINE__);
890
891
		return; 
	}
892

893
894
	/* Include relevant areas from the area file (e.g. areas.bbs): */
	for(i=0;i<cfg.areas;i++) {
rswindell's avatar
rswindell committed
895
		if((type == AREALIST_CONNECTED || cfg.add_from_echolists_only) && !area_is_linked(i,&addr))
896
897
898
			continue;
		if(type == AREALIST_UNLINKED && area_is_linked(i,&addr))
			continue;
899
		strListPush(&area_list, cfg.area[i].name); 
900
	} 
901

902
	if(type != AREALIST_CONNECTED) {
rswindell's avatar
rswindell committed
903
904
		nodecfg_t* nodecfg=findnodecfg(&cfg, addr,0);
		if(nodecfg != NULL) {
905
906
			for(j=0;j<cfg.listcfgs;j++) {
				match=0;
rswindell's avatar
rswindell committed
907
				for(k=0; cfg.listcfg[j].keys[k]; k++) {
908
					if(match) break;
rswindell's avatar
rswindell committed
909
910
911
					for(x=0; nodecfg->keys[x]; x++) {
						if(!stricmp(cfg.listcfg[j].keys[k]
							,nodecfg->keys[x])) {
912
913
							FILE* fp;
							if((fp=fopen(cfg.listcfg[j].listpath,"r"))==NULL) {
914
915
								lprintf(LOG_ERR,"ERROR %u (%s) line %d opening %s"
									,errno,strerror(errno),__LINE__,cfg.listcfg[j].listpath);
916
								match=1;
917
918
								break; 
							}
919
							while(!feof(fp)) {
920
								memset(str,0,sizeof(str));
921
								if(!fgets(str,sizeof(str),fp))
922
									break;
923
								truncsp(str);
924
								p=str;
925
								SKIP_WHITESPACE(p);
926
								if(*p==0 || *p==';')     /* Ignore Blank and Comment Lines */
927
									continue;
928
929
930
								tp=p;
								FIND_WHITESPACE(tp);
								*tp=0;
931
932
933
								for(y=0;y<cfg.areas;y++)
									if(!stricmp(cfg.area[y].name,p))
										break;
934
935
								if(y>=cfg.areas || !area_is_linked(y,&addr))
									strListPush(&area_list, p); 
936
							}
937
							fclose(fp);
938
							match=1;
939
							break; 
940
941
						}
					}
942
943
944
945
				} 
			} 
		} 
	}
946
947
	strListSortAlpha(area_list);
	if(!strListCount(area_list))
rswindell's avatar
rswindell committed
948
		create_netmail(to,/* msg: */NULL,title,"None.",addr,/* attachment: */false);
949
950
951
952
953
954
955
956
957
958
959
960
	else {
		FILE* fp;
		if((fp=tmpfile())==NULL) {
			lprintf(LOG_ERR,"ERROR line %d couldn't open tmpfile",__LINE__);
		} else {
			strListWriteFile(fp, area_list, "\r\n");
			file_to_netmail(fp,title,addr,to);
			fclose(fp);
		}
	}
	lprintf(LOG_INFO,"Created AreaFix response netmail with %s (%u areas)", title, strListCount(area_list));
	strListFree(&area_list);
961
}
962

rswindell's avatar
rswindell committed
963
int check_elists(const char *areatag, fidoaddr_t addr)
964
965
{
	FILE *stream;
966
	char str[1025],quit=0,*p,*tp;
rswindell's avatar
rswindell committed
967
	int j,k,x,match=0;
968

rswindell's avatar
rswindell committed
969
970
	nodecfg_t* nodecfg=findnodecfg(&cfg, addr,0);
	if(nodecfg!=NULL) {
rswindell's avatar
rswindell committed
971
972
		for(j=0;j<cfg.listcfgs;j++) {
			quit=0;
rswindell's avatar
rswindell committed
973
			for(k=0; cfg.listcfg[j].keys[k]; k++) {
rswindell's avatar
rswindell committed
974
				if(quit) break;
rswindell's avatar
rswindell committed
975
976
977
				for(x=0; nodecfg->keys[x] ;x++)
					if(!stricmp(cfg.listcfg[j].keys[k]
						,nodecfg->keys[x])) {
978
						if((stream=fopen(cfg.listcfg[j].listpath,"r"))==NULL) {
979
980
							lprintf(LOG_ERR,"ERROR %u (%s) line %d opening %s"
								,errno,strerror(errno),__LINE__,cfg.listcfg[j].listpath);
rswindell's avatar
rswindell committed
981
							quit=1;
982
983
							break; 
						}
rswindell's avatar
rswindell committed
984
						while(!feof(stream)) {
985
							if(!fgets(str,sizeof(str),stream))
rswindell's avatar
rswindell committed
986
987
								break;
							p=str;
988
							SKIP_WHITESPACE(p);
rswindell's avatar
rswindell committed
989
990
							if(*p==';')     /* Ignore Comment Lines */
								continue;
991
992
993
994
							tp=p;
							FIND_WHITESPACE(tp);
							*tp=0;
							if(!stricmp(areatag,p)) {
rswindell's avatar
rswindell committed
995
								match=1;
996
997
998
								break; 
							} 
						}
rswindell's avatar
rswindell committed
999
						fclose(stream);
1000
						quit=1;
rswindell's avatar
rswindell committed
1001
1002
						if(match)
							return(match);
1003
1004
1005
1006
1007
						break; 
					} 
			} 
		} 
	}
rswindell's avatar
rswindell committed
1008
	return(match);
1009
1010
1011
1012
}
/******************************************************************************
 Used by AREAFIX to add/remove/change areas in the areas file
******************************************************************************/
rswindell's avatar
rswindell committed
1013
void alter_areas(str_list_t add_area, str_list_t del_area, fidoaddr_t addr, const char* to)
1014
1015
{
	FILE *nmfile,*afilein,*afileout,*fwdfile;
1016
1017
	char str[1024],fields[1024],field1[256],field2[256],field3[256]
		,outpath[MAX_PATH+1]
1018
		,*outname,*p,*tp,nomatch=0,match=0;
1019
	int i,j,k,x,y;
1020
1021
	ulong tagcrc;

1022
1023
	SAFECOPY(outpath,cfg.areafile);
	*getfname(outpath)=0;
1024
	if((outname=tempnam(outpath,"AREAS"))==NULL) {
1025
		lprintf(LOG_ERR,"ERROR tempnam(%s,AREAS)",outpath);
1026
1027
		return; 
	}
1028
	if((nmfile=tmpfile())==NULL) {
1029
		lprintf(LOG_ERR,"ERROR in tmpfile()");
1030
		free(outname);
1031
1032
		return; 
	}
1033
	if((afileout=fopen(outname,"w+"))==NULL) {
1034
		lprintf(LOG_ERR,"ERROR %u (%s) line %d opening %s",errno,strerror(errno),__LINE__,outname);
1035
1036
		fclose(nmfile);
		free(outname);
1037
1038
		return; 
	}
1039
	if((afilein=fopen(cfg.areafile,"r"))==NULL) {
1040
		lprintf(LOG_ERR,"ERROR %u (%s) line %d opening %s",errno,strerror(errno),__LINE__,cfg.areafile);
1041
1042
1043
		fclose(afileout);
		fclose(nmfile);
		free(outname);
1044
1045
		return; 
	}
1046
	while(!feof(afilein)) {
1047
		if(!fgets(fields,sizeof(fields),afilein))
1048
1049
1050
			break;
		truncsp(fields);
		p=fields;
1051
		SKIP_WHITESPACE(p);
1052
		if(*p==';') {    /* Skip Comment Lines */
1053
			fprintf(afileout,"%s\n",fields);
1054
1055
			continue; 
		}
1056
		SAFECOPY(field1,p);         /* Internal Code Field */
1057
		truncstr(field1," \t\r\n");
1058
1059
1060
		FIND_WHITESPACE(p);
		SKIP_WHITESPACE(p);
		SAFECOPY(field2,p);         /* Areatag Field */
1061
		truncstr(field2," \t\r\n");
1062
1063
		FIND_WHITESPACE(p);
		SKIP_WHITESPACE(p);
1064
		if((tp=strchr(p,';'))!=NULL) {
1065
1066
			SAFECOPY(field3,p);     /* Comment Field (if any) */
			FIND_WHITESPACE(tp);
1067
1068
			*tp=0; 
		}
1069
1070
		else
			field3[0]=0;
1071
		if(strListCount(del_area)) { 				/* Check for areas to remove */
1072
			lprintf(LOG_DEBUG,"Removing areas for %s from %s", smb_faddrtoa(&addr,NULL), cfg.areafile);
1073
1074
1075
1076
1077
1078
			for(i=0;del_area[i]!=NULL;i++) {
				if(!stricmp(del_area[i],field2) ||
					!stricmp(del_area[0],"-ALL"))     /* Match Found */
					break; 
			}
			if(del_area[i]!=NULL) {
1079
1080
				for(i=0;i<cfg.areas;i++) {
					if(!stricmp(field2,cfg.area[i].name)) {
1081
						lprintf(LOG_DEBUG,"Unlinking area (%s) for %s in %s", field2, smb_faddrtoa(&addr,NULL), cfg.areafile);
1082
						if(!area_is_linked(i,&addr)) {
1083
1084
							fprintf(afileout,"%s\n",fields);
							/* bugfix here Mar-25-2004 (wasn't breaking for "-ALL") */
1085
							if(stricmp(del_area[0],"-ALL"))
1086
1087
1088
								fprintf(nmfile,"%s not connected.\r\n",field2);
							break; 
						}
1089

rswindell's avatar
rswindell committed
1090
						/* Added 12/4/95 to remove link from connected link */
1091

rswindell's avatar
rswindell committed
1092
1093
1094
1095
1096
1097
						for(k=j;k<cfg.area[i].links-1;k++)
							memcpy(&cfg.area[i].link[k],&cfg.area[i].link[k+1]
								,sizeof(fidoaddr_t));
						--cfg.area[i].links;
						if(cfg.area[i].links==0) {
							FREE_AND_NULL(cfg.area[i].link);
1098
						} else {
rswindell's avatar
rswindell committed
1099
1100
1101
							if((cfg.area[i].link=(fidoaddr_t *)
								realloc(cfg.area[i].link,sizeof(fidoaddr_t)
								*(cfg.area[i].links)))==NULL) {
1102
								lprintf(LOG_ERR,"ERROR line %d allocating memory for area "
rswindell's avatar
rswindell committed
1103
									"#%u links.",__LINE__,i+1);
1104
								bail(1); 
1105
								return;
1106
							}
1107
						}
1108
1109

						fprintf(afileout,"%-16s%-23s ",field1,field2);
rswindell's avatar
rswindell committed
1110
1111
1112
						for(j=0;j<cfg.area[i].links;j++) {
							if(!memcmp(&cfg.area[i].link[j],&addr
								,sizeof(fidoaddr_t)))
1113
1114
								continue;
							fprintf(afileout,"%s "
rswindell's avatar
rswindell committed
1115
								,smb_faddrtoa(&cfg.area[i].link[j],NULL)); 
1116
						}
1117
1118
						if(