1 /*:ts=8*/
2 /*****************************************************************************
3  * FIDOGATE --- Gateway UNIX Mail/News <-> FIDO NetMail/EchoMail
4  *
5  * $Id: ffxqt.c,v 4.27 2004/08/22 20:19:11 n0ll Exp $
6  *
7  * Process incoming ffx control and data files
8  *
9  * With full supporting cast of busy files and locking. ;-)
10  *
11  *****************************************************************************
12  * Copyright (C) 1990-2004
13  *  _____ _____
14  * |     |___  |   Martin Junius             <mj.at.n0ll.dot.net>
15  * | | | |   | |   Radiumstr. 18
16  * |_|_|_|@home|   D-51069 Koeln, Germany
17  *
18  * This file is part of FIDOGATE.
19  *
20  * FIDOGATE is free software; you can redistribute it and/or modify it
21  * under the terms of the GNU General Public License as published by the
22  * Free Software Foundation; either version 2, or (at your option) any
23  * later version.
24  *
25  * FIDOGATE is distributed in the hope that it will be useful, but
26  * WITHOUT ANY WARRANTY; without even the implied warranty of
27  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
28  * General Public License for more details.
29  *
30  * You should have received a copy of the GNU General Public License
31  * along with FIDOGATE; see the file COPYING.  If not, write to the Free
32  * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
33  *****************************************************************************/
34 
35 #include "fidogate.h"
36 #include "getopt.h"
37 
38 #include <sys/wait.h>
39 
40 
41 
42 #define PROGRAM		"ffxqt"
43 #define VERSION		"$Revision: 4.27 $"
44 #define CONFIG		DEFAULT_CONFIG_FFX
45 
46 
47 #define MAXFFXCMD	16
48 
49 
50 
51 /*
52  * Info from ffx control file
53  */
54 typedef struct st_ffx
55 {
56     char *job;			/* Job name */
57     char *name;			/* .ffx file name */
58     Node from, to;		/* FTN addresses */
59     char *fqdn;			/* Sender FQDN */
60     char *passwd;		/* Password */
61     char *cmd;			/* Command with args */
62     char *in;			/* stdin file */
63     char *decompr;		/* Decompressor */
64     char *file;
65     int  status;		/* Status: TRUE=read # EOF */
66 } FFX;
67 
68 
69 
70 /*
71  * List of command from config.ffx
72  */
73 typedef struct st_ffxcmd
74 {
75     char type;			/* 'C'=command, 'U'=uncompress */
76     char *name;			/* Command name */
77     char *cmd;			/* Command to execute */
78 } FFXCmd;
79 
80 
81 
82 /*
83  * Prototypes
84  */
85 void	parse_ffxcmd		(void);
86 char   *find_ffxcmd		(int, char *);
87 int	do_ffx			(int);
88 void	remove_bad		(char *);
89 FFX    *parse_ffx		(char *);
90 int	exec_ffx		(FFX *);
91 int	run_ffx_cmd		(char *, char *, char *, char *, char *);
92 
93 void	short_usage		(void);
94 void	usage			(void);
95 
96 
97 
98 /*
99  * Command line options
100  */
101 static int g_flag = 0;				/* Processing grade */
102 
103 
104 /*
105  * Commands
106  */
107 static FFXCmd l_cmd[MAXFFXCMD];
108 static int n_cmd = 0;
109 
110 
111 
112 /*
113  * Parse FFXCommand / FFXUncompress config.ffx parameters
114  */
parse_ffxcmd()115 void parse_ffxcmd()
116 {
117     char *p, *name, *cmd;
118 
119     /* Commands */
120     for(p = cf_get_string("FFXCommand", TRUE);
121 	p && *p;
122 	p = cf_get_string("FFXCommand", FALSE) )
123     {
124 	if(n_cmd >= MAXFFXCMD)
125 	    continue;
126 	name = xstrtok(p   , "\n\t ");
127 	cmd  = xstrtok(NULL, "\n");
128 	if(!name || !cmd)
129 	    continue;
130 	while(isspace(*cmd))
131 	    cmd++;
132 
133 	debug(8, "config: FFXCommand %s %s", name, cmd);
134 
135 	BUF_EXPAND(buffer, cmd);
136 	l_cmd[n_cmd].type = 'C';
137 	l_cmd[n_cmd].name = name;
138 	l_cmd[n_cmd].cmd  = strsave(buffer);
139 	n_cmd++;
140     }
141 }
142 
143 
144 
145 /*
146  * Find FFXCommand / FFXUncompress
147  */
find_ffxcmd(int type,char * name)148 char *find_ffxcmd(int type, char *name)
149 {
150     int i;
151 
152     for(i=0; i<n_cmd; i++)
153 	if(type==l_cmd[i].type && strieq(name, l_cmd[i].name))
154 	    return l_cmd[i].cmd;
155 
156     return NULL;
157 }
158 
159 
160 
161 /*
162  * Processs *.ffx control files in Binkley inbound directory
163  */
do_ffx(int t_flag)164 int do_ffx(int t_flag)
165 {
166     FFX *ffx;
167     char *name;
168     Passwd *pwd;
169     char *passwd;
170     char buf[MAXPATH];
171     char pattern[16];
172 
173     BUF_COPY(pattern, "f???????.ffx");
174     if(g_flag)
175 	pattern[1] = g_flag;
176 
177     BUF_EXPAND(buffer, cf_p_pinbound());
178     if( chdir(buffer) == -1 )
179     {
180 	logit("$ERROR: can't chdir %s", buffer);
181 	return ERROR;
182     }
183 
184     dir_sortmode(DIR_SORTMTIME);
185     if(dir_open(".", pattern, TRUE) == ERROR)
186     {
187 	logit("$ERROR: can't open directory .");
188 	return ERROR;
189     }
190 
191     for(name=dir_get(TRUE); name; name=dir_get(FALSE))
192     {
193 	debug(1, "ffxqt: control file %s", name);
194 
195 	ffx = parse_ffx(name);
196 	if(!ffx)
197 	    /* No error, this might be a file still being written to */
198 	    continue;
199 
200 	if(!ffx->status)		/* BSY test, if not EOF */
201 	    if(bink_bsy_test(&ffx->from))	/* Skip if busy */
202 	    {
203 		debug(3, "ffxqt: %s busy, skipping", znfp1(&ffx->from));
204 		continue;
205 	    }
206 
207 	/*
208 	 * Get password for from node
209 	 */
210 	if( (pwd = passwd_lookup("ffx", &ffx->from)) )
211 	    passwd = pwd->passwd;
212 	else
213 	    passwd = NULL;
214 	if(passwd)
215 	    debug(3, "ffxqt: password %s", passwd);
216 
217 	/*
218 	 * Require password unless -t option is given
219 	 */
220 	if(!t_flag && !passwd)
221 	{
222 	    logit("ERROR: %s: no password for %s in PASSWD",
223 		name, znfp1(&ffx->from)  );
224 	    goto rename_to_bad;
225 	}
226 
227 	/*
228 	 * Check password
229 	 */
230 	if(passwd)
231 	{
232 	    if(ffx->passwd)
233 	    {
234 		if(stricmp(passwd, ffx->passwd))
235 		{
236 		    logit("ERROR: %s: wrong password from %s: ours=%s his=%s",
237 			name, znfp1(&ffx->from), passwd,
238 			ffx->passwd                              );
239 		    goto rename_to_bad;
240 		}
241 	    }
242 	    else
243 	    {
244 		logit("ERROR: %s: no password from %s: ours=%s", name,
245 		    znfp1(&ffx->from), passwd );
246 		goto rename_to_bad;
247 	    }
248 	}
249 
250 	logit("job %s: from %s data %s (%ldb) / %s",
251 	    ffx->job, znfp1(&ffx->from), ffx->file, check_size(ffx->file),
252 	    ffx->cmd);
253 
254 	if(exec_ffx(ffx) == ERROR)
255 	{
256 	    logit("%s: command failed", name);
257 	rename_to_bad:
258 	    /*
259 	     * Error: rename .ffx -> .bad
260 	     */
261 	    str_change_ext(buf, sizeof(buf), name, "bad");
262 	    rename(name, buf);
263 	    logit("%s: renamed to %s", name, buf);
264 	}
265 
266 	tmps_freeall();
267     }
268 
269     dir_close();
270 
271     return OK;
272 }
273 
274 
275 
276 /*
277  * Remove "bad" character from string
278  */
remove_bad(char * s)279 void remove_bad(char *s)
280 {
281     char *p = s;
282 
283     while(*p)
284 	if(*p>=' ' && *p < 127)
285 	    switch(*p)
286 	    {
287 	    case '$':
288 	    case '&':
289 	    case '(':
290 	    case ')':
291 	    case ';':
292 	    case '<':
293 	    case '>':
294 	    case '^':
295 	    case '`':
296 	    case '|':
297 		p++;		/* skip */
298 		break;
299 	    default:
300 		*s++ = *p++;
301 		break;
302 	    }
303 	else
304 	    p++;
305 
306     *s = 0;
307 }
308 
309 
310 
311 /*
312  * Parse control file and read into memory
313  */
314 #define SEP " \t\r\n"
315 
parse_ffx(char * name)316 FFX *parse_ffx(char *name)
317 {
318     FILE *fp;
319     static FFX ffx;
320     char *buf, *p;
321 
322     /**FIXME: this isn't really clean**/
323     xfree(ffx.job);	ffx.job     = NULL;
324     xfree(ffx.name);	ffx.name    = NULL;
325     xfree(ffx.fqdn);	ffx.fqdn    = NULL;
326     xfree(ffx.passwd);	ffx.passwd  = NULL;
327     xfree(ffx.cmd);	ffx.cmd     = NULL;
328     xfree(ffx.in);	ffx.in      = NULL;
329     xfree(ffx.decompr);	ffx.decompr = NULL;
330     xfree(ffx.file);	ffx.file    = NULL;
331     ffx.from.zone = ffx.from.net = ffx.from.node = ffx.from.point = 0;
332     ffx.to  .zone = ffx.to  .net = ffx.to  .node = ffx.to  .point = 0;
333     ffx.status = FALSE;
334     ffx.name = strsave(name);
335 
336     fp = fopen(ffx.name, R_MODE);
337     if(!fp)
338     {
339 	logit("$ERROR: can't open %s", ffx.name);
340 	return NULL;
341     }
342 
343     while((buf = fgets(buffer, BUFFERSIZE, fp)))
344     {
345 	strip_crlf(buf);
346 
347 	switch(*buf)
348 	{
349 	case '#':
350 	    /* Comment */
351 	    if(!strncmp(buf, "# EOF", 5))
352 		ffx.status = TRUE;
353 	    continue;
354 
355 	case 'J':			/* Job name */
356 	    ffx.job = strsave(buf + 2);
357 	    break;
358 
359 	case 'U':
360 	    /* User name */
361 	    p = strtok(buf+2, SEP);
362 	    /* From node */
363 	    p = strtok(NULL , SEP);
364 	    if(p)
365 		asc_to_node(p, &ffx.from, FALSE);
366 	    /* To node */
367 	    p = strtok(NULL , SEP);
368 	    if(p)
369 		asc_to_node(p, &ffx.to, FALSE);
370 	    /* FQDN */
371 	    p = strtok(NULL , SEP);
372 	    if(p)
373 		ffx.fqdn = strsave(p);
374 	    break;
375 
376 	case 'I':
377 	    /* data file */
378 	    p = strtok(buf+2, SEP);
379 	    if(p)
380 	    {
381 		ffx.in = strsave(p);
382 		remove_bad(ffx.in);
383 	    }
384 	    /* decompressor */
385 	    p = strtok(NULL , SEP);
386 	    if(p)
387 	    {
388 		ffx.decompr = strsave(p);
389 		remove_bad(ffx.decompr);
390 	    }
391 	    break;
392 
393 	case 'F':
394 	    ffx.file = strsave(buf + 2);
395 	    remove_bad(ffx.file);
396 	    break;
397 
398 	case 'C':
399 	    /* command */
400 	    ffx.cmd = strsave(buf + 2);
401 	    remove_bad(ffx.cmd);
402 	    break;
403 
404 	case 'P':
405 	    ffx.passwd = strsave(buf + 2);
406 	    break;
407 
408 	}
409     }
410 
411     fclose(fp);
412 
413     if(!ffx.cmd)
414 	return NULL;
415 
416     debug(3, "ffx: user=%s fqdn=%s", ffx.name, ffx.fqdn ? ffx.fqdn : "-none-");
417     debug(3, "     %s -> %s", znfp1(&ffx.from), znfp2(&ffx.to));
418     debug(3, "     J %s", ffx.job ? ffx.job : "");
419     debug(3, "     I %s %s",
420 	  ffx.in      ? ffx.in      : "",
421 	  ffx.decompr ? ffx.decompr : "" );
422     debug(3, "     F %s", ffx.file);
423     debug(3, "     C %s", ffx.cmd);
424     debug(3, "     P %s", ffx.passwd ? ffx.passwd : "");
425 
426     return &ffx;
427 }
428 
429 
430 
431 /*
432  * Execute command in ffx
433  */
exec_ffx(FFX * ffx)434 int exec_ffx(FFX *ffx)
435 {
436     int ret;
437     char *name, *args=NULL, *cmd_c=NULL;
438 
439     /* Extract command name and args */
440     name = strtok(ffx->cmd, "\n\t ");
441     args = strtok(NULL,     "\n"   );
442     if(!name)
443 	return ERROR;
444     if(!args)
445 	args = "";
446     while(isspace(*args))
447 	args++;
448 
449     /* Find command and uncompressor */
450     cmd_c = find_ffxcmd('C', name);
451     if(!cmd_c)
452     {
453 	logit("ERROR: no FFXCommand found for \"%s\"", name);
454 	return ERROR;
455     }
456     if(ffx->decompr)
457     {
458 	logit("ERROR: uncompressing no longer supported in this version");
459 	return ERROR;
460     }
461 
462     /* Execute */
463     debug(2, "Command: %s", cmd_c);
464     ret = run_ffx_cmd(cmd_c, name, ffx->fqdn, args, ffx->in);
465     debug(2, "Exit code=%d", ret);
466 
467     if(ret == 0)
468     {
469 	unlink(ffx->name);
470 	if(ffx->file)
471 	    unlink(ffx->file);
472 	return OK;
473     }
474 
475     return ERROR;
476 }
477 
478 
479 
480 /*
481  * Run ffx cmd using fork() and exec()
482  */
483 #define MAXARGS		256
484 
run_ffx_cmd(char * cmd,char * cmd_name,char * cmd_fqdn,char * cmd_args,char * data)485 int run_ffx_cmd(char *cmd,
486 		char *cmd_name, char *cmd_fqdn, char *cmd_args, char *data)
487 {
488     char *args[MAXARGS];
489     int n=0;
490     char *p;
491     pid_t pid;
492     int status;
493 
494     /* compile args[] vector */
495     args[n++] = cmd_name;
496     if(cmd_fqdn && streq(cmd_name, "rmail"))
497     {
498 	args[n++] = "-f";
499 	args[n++] = cmd_fqdn;
500     }
501 
502     for(p=strtok(cmd_args, SEP); p; p=strtok(NULL, SEP))
503     {
504 	if(n >= MAXARGS-1)
505 	{
506 	    logit("ERROR: too many args in run_ffx_cmd()");
507 	    return ERROR;
508 	}
509 	args[n++] = p;
510     }
511 
512     args[n] = NULL;
513 
514 #if 0
515     {
516 	int i;
517 	debug(7, "cmd=%s", cmd);
518 	for(i=0; i<=n; i++)
519 	    debug(7, "args[%d] = %s", i, args[i]);
520 	debug(7, "data=%s", data);
521     }
522 #endif
523 
524     /* fork() and exec() */
525     pid = fork();
526     if(pid == ERROR)
527     {
528 	logit("$ERROR: fork failed");
529 	return ERROR;
530     }
531 
532     if(pid)
533     {
534 	/* parent */
535 	if(waitpid(pid, &status, 0) == ERROR)
536 	{
537 	    logit("$ERROR: waitpid failed");
538 	    return ERROR;
539 	}
540 	if(WIFEXITED(status))
541 	    return WEXITSTATUS(status);
542 	if(WIFSIGNALED(status))
543 	    logit("ERROR: child %s caught signal %d", cmd, WTERMSIG(status));
544 	else
545 	    logit("ERROR: child %s, exit status=%04x", cmd, status);
546 	return ERROR;
547     }
548     else
549     {
550 	/* child */
551 	if(! freopen(data, R_MODE, stdin))
552 	{
553 	    logit("ERROR: can't freopen stdin to %s", data);
554 	    exit(1);
555 	}
556 	if( execv(cmd, args) == ERROR )
557 	    logit("ERROR: execv %s failed", cmd);
558 	exit(1);
559     }
560 
561     /**NOT REACHED**/
562     return ERROR;
563 }
564 
565 
566 
567 /*
568  * Usage messages
569  */
short_usage(void)570 void short_usage(void)
571 {
572     fprintf(stderr, "usage: %s [-options]\n", PROGRAM);
573     fprintf(stderr, "       %s --help  for more information\n", PROGRAM);
574     exit(EX_USAGE);
575 }
576 
577 
usage(void)578 void usage(void)
579 {
580     fprintf(stderr, "FIDOGATE %s  %s %s\n\n",
581 	    version_global(), PROGRAM, version_local(VERSION) );
582 
583     fprintf(stderr, "usage:   %s [-options]\n\n", PROGRAM);
584     fprintf(stderr, "\
585 options:  -g --grade G                 processing grade\n\
586           -I --inbound dir             set inbound dir (default: PINBOUND)\n\
587           -t --insecure                process ffx files without password\n\
588 \n\
589           -v --verbose                 more verbose\n\
590 	  -h --help                    this help\n\
591           -c --config name             read config file (\"\" = none)\n\
592 	  -a --addr Z:N/F.P            set FTN address\n\
593 	  -u --uplink-addr Z:N/F.P     set FTN uplink address\n");
594 
595     exit(0);
596 }
597 
598 
599 
600 /***** main() ****************************************************************/
601 
main(int argc,char ** argv)602 int main(int argc, char **argv)
603 {
604     int c;
605     char *I_flag=NULL;
606     int   t_flag=FALSE;
607     char *c_flag=NULL;
608     char *a_flag=NULL, *u_flag=NULL;
609 
610     int option_index;
611     static struct option long_options[] =
612     {
613 	{ "grade",        1, 0, 'g'},	/* grade */
614 	{ "insecure",     0, 0, 't'},	/* Insecure */
615 	{ "inbound",      1, 0, 'I'},	/* Set Binkley inbound */
616 
617 	{ "verbose",      0, 0, 'v'},	/* More verbose */
618 	{ "help",         0, 0, 'h'},	/* Help */
619 	{ "config",       1, 0, 'c'},	/* Config file */
620 	{ "addr",         1, 0, 'a'},	/* Set FIDO address */
621 	{ "uplink-addr",  1, 0, 'u'},	/* Set FIDO uplink address */
622 	{ 0,              0, 0, 0  }
623     };
624 
625     log_program(PROGRAM);
626 
627     /* Init configuration */
628     cf_initialize();
629 
630 
631     while ((c = getopt_long(argc, argv, "g:tI:vhc:a:u:",
632 			    long_options, &option_index     )) != EOF)
633 	switch (c) {
634 	/***** ffxqt options *****/
635 	case 'g':
636 	    g_flag = *optarg;
637 	    break;
638 	case 't':
639 	    t_flag = TRUE;
640 	    break;
641 	case 'I':
642 	    I_flag = optarg;
643 	    break;
644 
645 	/***** Common options *****/
646 	case 'v':
647 	    verbose++;
648 	    break;
649 	case 'h':
650 	    usage();
651 	    break;
652 	case 'c':
653 	    c_flag = optarg;
654 	    break;
655 	case 'a':
656 	    a_flag = optarg;
657 	    break;
658 	case 'u':
659 	    u_flag = optarg;
660 	    break;
661 	default:
662 	    short_usage();
663 	    break;
664 	}
665 
666     /* Read config file */
667     cf_read_config_file(c_flag ? c_flag : CONFIG);
668 
669     /* Process config options */
670     if(a_flag)
671 	cf_set_addr(a_flag);
672     if(u_flag)
673 	cf_set_uplink(u_flag);
674 
675     cf_debug();
676 
677     /*
678      * Process additional config statements
679      */
680     parse_ffxcmd();
681 
682     if(I_flag)
683 	cf_s_pinbound(I_flag);
684 
685     passwd_init();
686 
687 
688     if(lock_program(PROGRAM, NOWAIT) == ERROR)
689 	exit(EXIT_BUSY);
690 
691     do_ffx(t_flag);
692 
693     unlock_program(PROGRAM);
694 
695 
696     exit(0);
697 }
698